From 3cb5cdcaaef487fcd489c6a3c709729a62b0a1b8 Mon Sep 17 00:00:00 2001 From: marsalva Date: Sun, 28 Dec 2025 21:28:36 +0000 Subject: [PATCH] Actualizar robot_cobros.js --- robot_cobros.js | 230 ++++++++++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 123 deletions(-) diff --git a/robot_cobros.js b/robot_cobros.js index c652c81..27aa969 100644 --- a/robot_cobros.js +++ b/robot_cobros.js @@ -24,25 +24,33 @@ const APPOINTMENTS_COL = "appointments"; const app = express(); app.use(cors({ origin: '*' })); -app.use(express.json()); +app.use(express.json({ limit: '10mb' })); // Permitir payloads grandes para guardar -// --- ENDPOINT UNIFICADO --- +// --- ENDPOINT PRINCIPAL --- app.post('/api/robot-cobros', async (req, res) => { - const { action, urls } = req.body; - console.log(`🔔 Orden recibida: ${action ? action.toUpperCase() : 'DESCONOCIDA'}`); + const { action, url, provider, dataToSave } = req.body; + console.log(`🔔 Orden: ${action.toUpperCase()} (${provider || 'HS'})`); try { if (action === 'scan') { + // Paso 1: Buscar fechas const lista = await runScanner(); - res.json({ success: true, mode: 'scan', data: lista }); - } - else if (action === 'process') { - if (!urls || urls.length === 0) throw new Error("No has seleccionado ninguna fecha."); - const procesados = await runProcessor(urls); - res.json({ success: true, mode: 'process', count: procesados }); + res.json({ success: true, data: lista }); } + else if (action === 'analyze') { + // Paso 2: Leer fecha y cruzar con DB (Sin guardar) + if (!url) throw new Error("Falta URL"); + const analisis = await runAnalyzer(url); + res.json({ success: true, ...analisis }); + } + else if (action === 'save_data') { + // Paso 3: Guardar lo confirmado + if (!dataToSave || !Array.isArray(dataToSave)) throw new Error("No hay datos"); + const count = await runSaver(dataToSave); + res.json({ success: true, count }); + } else { - throw new Error("Acción no válida o faltan parámetros."); + throw new Error("Acción desconocida"); } } catch (err) { console.error("❌ Error:", err.message); @@ -50,175 +58,151 @@ app.post('/api/robot-cobros', async (req, res) => { } }); -// --- FUNCIÓN 1: ESCANEAR FECHAS (SCAN) --- +// --- 1. ESCÁNER DE FECHAS --- async function runScanner() { let browser = null; try { const { browser: b, page } = await loginAndGoToLiquidaciones(); browser = b; - console.log("🔍 Buscando lista de liquidaciones..."); - const liquidaciones = await page.evaluate(() => { const links = Array.from(document.querySelectorAll('a')); const results = []; links.forEach(l => { const txt = l.innerText.trim(); - // Detectar formato fecha (DD/MM/YYYY) if (/\d{2}\/\d{2}\/\d{4}/.test(txt)) { results.push({ fecha: txt, url: l.href }); } }); return results; }); - - console.log(`✅ Encontradas ${liquidaciones.length} fechas.`); return liquidaciones; - } catch (e) { throw e; } finally { if(browser) await browser.close(); } } -// --- FUNCIÓN 2: PROCESAR SELECCIONADAS (PROCESS) --- -async function runProcessor(urlsAProcesar) { +// --- 2. ANALIZADOR (PREVISUALIZACIÓN) --- +async function runAnalyzer(targetUrl) { let browser = null; - let totalActualizados = 0; - try { const { browser: b, page } = await loginAndGoToLiquidaciones(); browser = b; - for (const targetUrl of urlsAProcesar) { - console.log(`➡️ Procesando URL: ${targetUrl}`); - - // 1. Ir a la fecha - await page.goto(targetUrl, { timeout: 60000 }); - await page.waitForTimeout(1500); + console.log(`➡️ Analizando: ${targetUrl}`); + await page.goto(targetUrl, { timeout: 60000 }); + await page.waitForTimeout(1500); - // 2. BUSCAR BOTÓN ESPECÍFICO "DESGLOSE SERVICIOS" - console.log(" 🔘 Buscando botón 'Desglose Servicios'..."); - - const botonPulsado = await page.evaluate(() => { - // Buscamos inputs, botones y enlaces - const elementos = Array.from(document.querySelectorAll('input[type="button"], input[type="submit"], button, a')); - - // Opción A: Buscar texto exacto "DESGLOSE" y "SERVICIO" - const target = elementos.find(el => { - const texto = (el.value || el.innerText || "").toUpperCase(); - return texto.includes("SERVICIO") && texto.includes("DESGLOSE"); - }); - - // Opción B (Fallback): Buscar solo "SERVICIOS" si falla lo anterior - // CORRECCIÓN AQUÍ: antes ponía 'elements' y fallaba - const targetSecundario = elementos.find(el => (el.value || el.innerText || "").toUpperCase().includes("SERVICIOS")); - - const finalTarget = target || targetSecundario; - - if (finalTarget) { - finalTarget.click(); - return true; - } - return false; + // Buscar botón "Desglose Servicios" + const botonPulsado = await page.evaluate(() => { + const elementos = Array.from(document.querySelectorAll('input, button, a')); + const target = elementos.find(el => { + const txt = (el.value || el.innerText || "").toUpperCase(); + return txt.includes("SERVICIO") && txt.includes("DESGLOSE"); }); + if (target) { target.click(); return true; } + return false; + }); - if (botonPulsado) { - console.log(" ✅ Botón pulsado. Esperando tabla..."); - await page.waitForTimeout(3000); - } else { - console.warn(" ⚠️ No encontré el botón de Servicios. Intentando leer por si ya estamos dentro."); - } + if (botonPulsado) await page.waitForTimeout(3000); - // 3. LEER LA TABLA DE DATOS - const datosTabla = await page.evaluate(() => { - const filas = Array.from(document.querySelectorAll('tr')); - const datos = []; - - filas.forEach(tr => { - const tds = tr.querySelectorAll('td'); - // Estructura saldo.html: - // Col 0: Servicio | Col 5: Saldo - if (tds.length >= 6) { - const servicio = tds[0].innerText.trim(); - const saldoRaw = tds[5].innerText.trim(); - - // Validar que sea un número de servicio real (5 o más dígitos) - if (/^\d{5,}$/.test(servicio)) { - datos.push({ servicio, saldoRaw }); - } + // Extraer datos visuales + const datosRaw = await page.evaluate(() => { + const filas = Array.from(document.querySelectorAll('tr')); + const datos = []; + filas.forEach(tr => { + const tds = tr.querySelectorAll('td'); + // Col 0: Servicio, Col 1: Dirección, Col 5: Saldo + if (tds.length >= 6) { + const servicio = tds[0].innerText.trim(); + const direccion = tds[1].innerText.trim(); + const saldoRaw = tds[5].innerText.trim(); + if (/^\d{5,}$/.test(servicio)) { + datos.push({ servicio, direccion, saldoRaw }); } - }); - return datos; - }); - - console.log(` 💰 Leídos ${datosTabla.length} servicios en esta liquidación.`); - - // 4. GUARDAR EN FIREBASE - if (db && datosTabla.length > 0) { - const batch = db.batch(); - let batchCount = 0; - const nowISO = new Date().toISOString(); - - for (const item of datosTabla) { - // Limpieza "-33.48" -> 33.48 - let cleanSaldo = item.saldoRaw.replace(/[^\d.-]/g, ''); - let importe = parseFloat(cleanSaldo); - - if (isNaN(importe)) continue; - - // Convertimos a positivo para guardar en 'paidAmount' - importe = Math.abs(importe); - - const q = await db.collection(APPOINTMENTS_COL).where("serviceNumber", "==", item.servicio).get(); - - q.forEach(doc => { - batch.update(doc.ref, { - paidAmount: importe, - status: 'completed', - paymentDate: nowISO, - homeservePaymentStatus: 'paid_saldo', - lastUpdatedByRobot: nowISO - }); - batchCount++; - }); } + }); + return datos; + }); + + // Cruzar con Firebase (Lectura) + const encontrados = []; + const noEncontrados = []; + + if (db) { + for (const item of datosRaw) { + let cleanSaldo = item.saldoRaw.replace(/[^\d.-]/g, ''); + let importe = Math.abs(parseFloat(cleanSaldo) || 0); + + const q = await db.collection(APPOINTMENTS_COL).where("serviceNumber", "==", item.servicio).get(); - if (batchCount > 0) { - await batch.commit(); - totalActualizados += batchCount; - console.log(` 💾 Guardados ${batchCount} registros.`); + if (!q.empty) { + // Si hay duplicados, cogemos el primero o todos + q.forEach(doc => { + encontrados.push({ + servicio: item.servicio, + direccion: item.direccion, + importe: importe, + docId: doc.id + }); + }); + } else { + noEncontrados.push({ + servicio: item.servicio, + direccion: item.direccion, + importe: importe + }); } } } - - return totalActualizados; + + console.log(`✅ Análisis: ${encontrados.length} encontrados, ${noEncontrados.length} no encontrados.`); + return { encontrados, noEncontrados }; } catch (e) { throw e; } finally { if(browser) await browser.close(); } } +// --- 3. GUARDADOR (RÁPIDO) --- +async function runSaver(items) { + if (!db) return 0; + const batch = db.batch(); + const nowISO = new Date().toISOString(); + let count = 0; + + items.forEach(item => { + const ref = db.collection(APPOINTMENTS_COL).doc(item.docId); + batch.update(ref, { + paidAmount: item.importe, + status: 'completed', + paymentDate: nowISO, + homeservePaymentStatus: 'paid_saldo', + lastUpdatedByRobot: nowISO + }); + count++; + }); + + await batch.commit(); + console.log(`💾 Guardados ${count} documentos en DB.`); + return count; +} + // --- LOGIN --- async function loginAndGoToLiquidaciones() { - let user = "SIN_USER", pass = "SIN_PASS"; + let user = "", pass = ""; if (db) { const doc = await db.collection("providerCredentials").doc("homeserve").get(); if (doc.exists) { user = doc.data().user; pass = doc.data().pass; } } - const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); const page = await browser.newPage(); - - console.log("🌍 Login..."); - await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=PROF_PASS', { timeout: 60000 }); + await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=PROF_PASS', { timeout: 60000 }); if (await page.isVisible('input[name="CODIGO"]')) { await page.fill('input[name="CODIGO"]', user); await page.fill('input[type="password"]', pass); await page.keyboard.press('Enter'); await page.waitForTimeout(4000); } - - console.log("📂 Sección Liquidaciones..."); await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=CONSULTALIQ_WEB'); await page.waitForTimeout(2000); - return { browser, page }; }