From 9e957a84983ce465c1248393037898ff31d0e5f7 Mon Sep 17 00:00:00 2001 From: marsalva Date: Tue, 30 Dec 2025 09:49:12 +0000 Subject: [PATCH] Actualizar robot_cobros.js --- robot_cobros.js | 160 ++++++++++++++++++++++-------------------------- 1 file changed, 72 insertions(+), 88 deletions(-) diff --git a/robot_cobros.js b/robot_cobros.js index 43c84a9..03b2391 100644 --- a/robot_cobros.js +++ b/robot_cobros.js @@ -33,55 +33,35 @@ const app = express(); app.use(cors({ origin: '*' })); app.use(express.json({ limit: '50mb' })); -// --- ENDPOINT DE STREAMING --- app.post('/api/robot-cobros', async (req, res) => { - // Configurar cabeceras para STREAMING (Evita timeout 504) res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.setHeader('Transfer-Encoding', 'chunked'); - - // Función auxiliar para enviar mensajes al navegador - const send = (type, payload) => { - res.write(JSON.stringify({ type, payload }) + "\n"); - }; + const send = (type, payload) => res.write(JSON.stringify({ type, payload }) + "\n"); const { action, url, provider, dataToSave, month, year } = req.body; console.log(`🔔 Orden: ${action} [${provider}]`); try { if (provider === 'MULTI') { - if (action === 'scan') { - await runMultiStream(month, year, send); - } + if (action === 'scan') await runMultiStream(month, year, send); else if (action === 'save_data') { const count = await runSaver(dataToSave, 'MULTI'); send('DONE', { count }); } - } - else { - // HOMESERVE (Mantenemos modo normal por ahora, adaptado a respuesta simple) - if (action === 'scan') { - const lista = await runHSScanner(); - send('HS_DATES', lista); - } - else if (action === 'analyze') { - const analisis = await runHSAnalyzer(url); - send('HS_DATA', analisis); - } - else if (action === 'save_data') { - const count = await runSaver(dataToSave, 'HS'); - send('DONE', { count }); - } + } else { + // HOMESERVE (Simplificado) + if (action === 'scan') { const l = await runHSScanner(); send('HS_DATES', l); } + else if (action === 'analyze') { const a = await runHSAnalyzer(url); send('HS_DATA', a); } + else if (action === 'save_data') { const c = await runSaver(dataToSave, 'HS'); send('DONE', { count: c }); } } } catch (err) { console.error("❌ CRASH:", err.message); send('ERROR', err.message); - } finally { - res.end(); // Cerrar conexión al terminar - } + } finally { res.end(); } }); // ================================================================== -// 🤖 LÓGICA MULTIASISTENCIA (STREAMING) +// 🤖 LÓGICA MULTIASISTENCIA V13 (LECTURA MEJORADA) // ================================================================== async function runMultiStream(mes, anio, send) { @@ -90,23 +70,21 @@ async function runMultiStream(mes, anio, send) { const valD2 = `${anio}`; try { - send('LOG', `🚀 Iniciando robot para: ${valD1}`); + send('LOG', `🚀 Iniciando para: ${valD1}`); const { browser: b, page } = await loginMulti(); browser = b; - send('LOG', "📂 Accediendo a Servicios Cerrados..."); + send('LOG', "📂 Yendo a Servicios Cerrados..."); await page.goto(URLS.MULTI_LIST, { waitUntil: 'domcontentloaded' }); - // Filtro Fecha if (await page.isVisible('select[name="D1"]')) { - send('LOG', `📅 Aplicando filtro de fecha...`); await page.selectOption('select[name="D1"]', valD1); await page.selectOption('select[name="D2"]', valD2); await page.click('input[name="continuar"]'); await page.waitForTimeout(2000); } - // Recolectar IDs + // --- FASE 1: RECOLECTAR IDs --- let idsServicios = []; let tieneSiguiente = true; let pagActual = 1; @@ -116,9 +94,7 @@ async function runMultiStream(mes, anio, send) { const nuevosIds = await page.evaluate(() => { const celdas = Array.from(document.querySelectorAll('td.tdet')); const lista = []; - celdas.forEach(td => { - if (/^\d{8}$/.test(td.innerText.trim())) lista.push(td.innerText.trim()); - }); + celdas.forEach(td => { if (/^\d{8}$/.test(td.innerText.trim())) lista.push(td.innerText.trim()); }); return lista; }); @@ -127,17 +103,14 @@ async function runMultiStream(mes, anio, send) { send('LOG', ` -> Encontrados ${unicosPag.length} servicios.`); const haySiguiente = await page.$('a:has-text("Página siguiente")'); - if (haySiguiente) { - await haySiguiente.click(); - await page.waitForTimeout(3000); - pagActual++; - } else { tieneSiguiente = false; } + if (haySiguiente) { await haySiguiente.click(); await page.waitForTimeout(3000); pagActual++; } + else { tieneSiguiente = false; } } idsServicios = [...new Set(idsServicios)]; - send('LOG', `🔍 Total servicios a revisar: ${idsServicios.length}`); + send('LOG', `🔍 Total a revisar: ${idsServicios.length}`); - // PROCESAR UNO A UNO Y ENVIAR AL MOMENTO + // --- FASE 2: LEER DETALLES --- for (const [index, idServicio] of idsServicios.entries()) { send('PROGRESS', { current: index + 1, total: idsServicios.length }); @@ -145,72 +118,77 @@ async function runMultiStream(mes, anio, send) { try { await page.goto(urlPresupuesto, { timeout: 45000, waitUntil: 'domcontentloaded' }); - - // ESPERA EXPLÍCITA DEL PRECIO (Clave para que no salga 0€) - try { - // Esperamos hasta 5 segundos a que aparezca el elemento del precio - await page.waitForSelector('td[data-label*="TOTAL REPARACION"]', { timeout: 5000 }); - } catch(e) { /* Si falla, seguimos, quizás no hay precio */ } + // Espera crítica para que Angular pinte el precio + try { await page.waitForSelector('td[data-label*="TOTAL"]', { timeout: 4000 }); } catch(e){} const info = await page.evaluate(() => { - const clean = (t) => t ? t.trim() : "Desconocido"; - let cliente = "", direccion = "", totalStr = "0"; + const clean = (t) => t ? t.trim().replace(/\s+/g, ' ') : ""; + let cliente = "", direccion = "", totalStr = ""; - // Datos Cliente + // 1. BUSCAR DIRECCIÓN Y CLIENTE (Iterando los bloques azules) + // Buscamos contenedores que tengan título y valor const bloques = Array.from(document.querySelectorAll('.policy-info-block')); bloques.forEach(b => { - const t = b.querySelector('.policy-info-title')?.innerText.toUpperCase() || ""; - const v = b.querySelector('.policy-info-value')?.innerText || ""; - if (t.includes("CLIENTE")) cliente = v; - if (t.includes("DIRECCIÓN")) direccion = v; + const titulo = b.querySelector('.policy-info-title')?.innerText.toUpperCase() || ""; + const valor = b.querySelector('.policy-info-value')?.innerText || ""; + + if (titulo.includes("CLIENTE")) cliente = valor; + if (titulo.includes("DIRECCIÓN") || titulo.includes("DIRECCION")) direccion = valor; }); - // Fallback antiguo - if (!cliente) { - const tds = Array.from(document.querySelectorAll('td')); - for(let i=0; i el.innerText && el.innerText.includes("TOTAL REPARACION") && el.innerText.includes("€")); + if(celdaTexto) totalStr = celdaTexto.innerText.split("TOTAL")[1] || celdaTexto.innerText; } - // PRECIO (Selector exacto que me diste) - const celdaTotal = document.querySelector('td[data-label*="TOTAL REPARACION"]'); - if (celdaTotal) totalStr = celdaTotal.innerText; - return { cliente: clean(cliente), direccion: clean(direccion), totalStr }; }); + // LIMPIAR PRECIO let importe = 0; if (info.totalStr) { - let cleanMoney = info.totalStr.replace(/[^\d.,-]/g, '').replace(',', '.'); - importe = Math.abs(parseFloat(cleanMoney) || 0); + // Quitamos símbolos raros, espacios, letras... dejamos solo números y coma + let cleanMoney = info.totalStr.replace(/[^\d,]/g, '').replace(',', '.'); + importe = parseFloat(cleanMoney) || 0; } - // Cruzar con BD + // CONSULTAR BASE DE DATOS (Para saber el color) let docId = null; let enBD = false; + let direccionBD = ""; + if (db) { const q = await db.collection(APPOINTMENTS_COL).where("serviceNumber", "==", idServicio).get(); if (!q.empty) { - docId = q.docs[0].id; + const doc = q.docs[0]; + docId = doc.id; enBD = true; + direccionBD = doc.data().address || ""; } } - // ENVIAR DATO EN TIEMPO REAL AL NAVEGADOR + // SI LA WEB NO DA DIRECCIÓN, USAR LA DE LA BD (O viceversa) + const direccionFinal = info.direccion && info.direccion.length > 3 ? info.direccion : (direccionBD || "Desconocido"); + + // ENVIAR send('ITEM_FOUND', { servicio: idServicio, - direccion: info.direccion, + direccion: direccionFinal, + cliente: info.cliente, importe: importe, enBD: enBD, docId: docId }); } catch (errDetail) { - console.error(errDetail); - send('LOG', `⚠️ Error en ${idServicio}: ${errDetail.message}`); + send('LOG', `⚠️ Error ${idServicio}: ${errDetail.message}`); } } @@ -246,10 +224,7 @@ async function loginMulti() { return { browser, page }; } -// ... (Resto de funciones HS y Saver se mantienen igual que la V10) ... -// IMPORTANTE: Asegúrate de incluirlas aquí abajo (runHSScanner, runHSAnalyzer, runSaver, loginHS) -// Copialas del mensaje anterior si hace falta, no cambian. - +// ... (RESTO DE FUNCIONES HS Y SAVER IGUALES) ... async function runHSScanner() { let browser = null; try { @@ -328,14 +303,23 @@ async function runSaver(items, providerType) { const nowISO = new Date().toISOString(); let count = 0; items.forEach(item => { - const ref = db.collection(APPOINTMENTS_COL).doc(item.docId); - const updateData = { paidAmount: item.importe, paymentState: "Pagado", status: 'completed', paymentDate: nowISO, lastUpdatedByRobot: nowISO }; - if (providerType === 'MULTI') updateData.multiasistenciaPaymentStatus = 'paid_verified'; - else updateData.homeservePaymentStatus = 'paid_saldo'; - batch.update(ref, updateData); - count++; + const ref = db.collection(APPOINTMENTS_COL).doc(item.docId || item.servicio); // Fallback al servicio si no hay docId (caso nuevo) + + // Si el documento no existe, lo creamos con lo básico (opcional) + // Pero tu petición es guardar importes en servicios existentes. + // Si no existe, no hacemos update, o hacemos un set con merge. + // Asumo update si existe. + + if (item.enBD || item.docId) { + const updateData = { paidAmount: item.importe, paymentState: "Pagado", status: 'completed', paymentDate: nowISO, lastUpdatedByRobot: nowISO }; + if (providerType === 'MULTI') updateData.multiasistenciaPaymentStatus = 'paid_verified'; + else updateData.homeservePaymentStatus = 'paid_saldo'; + batch.update(ref, updateData); + count++; + } }); - await batch.commit(); + // Solo hacemos commit de lo que está en BD + if(count > 0) await batch.commit(); return count; }