diff --git a/robot_cobros.js b/robot_cobros.js index e0a2ba9..f7e675e 100644 --- a/robot_cobros.js +++ b/robot_cobros.js @@ -3,7 +3,7 @@ const { chromium } = require('playwright'); const admin = require('firebase-admin'); const cors = require('cors'); -// --- CONFIGURACIÓN --- +// --- CONFIGURACIÓN FIREBASE --- try { if (process.env.FIREBASE_PRIVATE_KEY) { if (!admin.apps.length) { @@ -15,150 +15,199 @@ try { }), }); } + console.log("✅ Firebase inicializado."); } -} catch (e) { console.error("Firebase Error:", e.message); } +} catch (e) { console.error("❌ Error Firebase:", e.message); } const db = admin.apps.length ? admin.firestore() : null; +const APPOINTMENTS_COL = "appointments"; + const app = express(); app.use(cors({ origin: '*' })); app.use(express.json()); -// --- ENDPOINT --- +// --- ENDPOINT UNIFICADO --- app.post('/api/robot-cobros', async (req, res) => { - console.log("🔔 DIAGNÓSTICO: Iniciando..."); + const { action, urls } = req.body; // action: 'scan' o 'process' + + console.log(`🔔 Orden recibida: ${action.toUpperCase()}`); + try { - const reporte = await runDiagnostic(); - res.json({ success: true, message: "Diagnóstico finalizado", data: reporte }); + if (action === 'scan') { + 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 }); + } + else { + throw new Error("Acción no válida"); + } } catch (err) { - console.error("❌ Error Fatal:", err); + console.error("❌ Error:", err.message); res.status(500).json({ success: false, message: err.message }); } }); -app.get('/', (req, res) => res.send('🤖 Robot Sherlock ONLINE')); - -// --- LÓGICA DE DETECTIVE --- -async function runDiagnostic() { +// --- FUNCIÓN 1: ESCANEAR FECHAS DISPONIBLES --- +async function runScanner() { let browser = null; - const logs = []; // Aquí guardaremos las pistas - - function apuntar(msg) { - console.log(msg); - logs.push({ expediente: "INFO", direccion: msg, importeLimpio: 0 }); - } + try { + const { browser: b, page } = await loginAndGoToLiquidaciones(); + browser = b; + + console.log("🔍 Buscando lista de liquidaciones..."); + + // Extraemos todos los enlaces de fechas + const liquidaciones = await page.evaluate(() => { + const links = Array.from(document.querySelectorAll('a')); + const results = []; + + links.forEach(l => { + const txt = l.innerText.trim(); + // Filtramos por formato fecha (DD/MM/YYYY) o similar + if (/\d{2}\/\d{2}\/\d{4}/.test(txt)) { + // Guardamos la URL absoluta para navegar directo luego + 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 --- +async function runProcessor(urlsAProcesar) { + let browser = null; + let totalActualizados = 0; try { - // 1. CREDENCIALES - let user = ""; let pass = ""; - if (db) { - const doc = await db.collection("providerCredentials").doc("homeserve").get(); - if (doc.exists) { user = doc.data().user; pass = doc.data().pass; } - } - if (!user) { - apuntar("❌ ERROR: No tengo usuario/pass en Firebase."); - return logs; - } + const { browser: b, page } = await loginAndGoToLiquidaciones(); // Login inicial + browser = b; - apuntar("🚀 Lanzando navegador..."); - browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); - const page = await browser.newPage(); + for (const targetUrl of urlsAProcesar) { + console.log(`➡️ Procesando URL: ${targetUrl}`); + + // 1. Navegar a la fecha específica + await page.goto(targetUrl, { timeout: 60000 }); + await page.waitForTimeout(2000); - // 2. LOGIN - apuntar("🌍 Entrando al Login..."); - await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=PROF_PASS', { timeout: 60000 }); - - const enLogin = await page.isVisible('input[name="CODIGO"]'); - if (enLogin) { - apuntar("🔑 Formulario detectado. Escribiendo..."); - await page.fill('input[name="CODIGO"]', user); - await page.fill('input[type="password"]', pass); - await page.keyboard.press('Enter'); - await page.waitForTimeout(4000); - } else { - apuntar("⚠️ No vi el formulario de login. ¿Ya estaba dentro?"); - } - - // 3. NAVEGAR A LIQUIDACIONES - apuntar("📂 Yendo a Consultar Liquidaciones..."); - await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=CONSULTALIQ_WEB'); - await page.waitForTimeout(2000); - - apuntar(`📍 URL actual: ${page.url()}`); - - // 4. INTENTAR CLICKAR FECHA - // Buscamos enlaces que parezcan una fecha YYYYMMDD o similar - const clickado = await page.evaluate(() => { - const links = Array.from(document.querySelectorAll('a')); - // Busca el primer enlace que NO sea de menú y tenga numeros - const target = links.find(l => { - const txt = l.innerText.trim(); - // Heurística: Si tiene más de 4 números seguidos, probablemente es una fecha/expediente - return /\d{4}/.test(txt) && !txt.includes('FACTURACION'); + // 2. Buscar botón "Desglose" y pulsar si existe + const hayDesglose = await page.evaluate(() => { + const btn = Array.from(document.querySelectorAll('input[type="button"], button, a')) + .find(el => (el.value || el.innerText || "").toLowerCase().includes('desglose')); + if(btn) { btn.click(); return true; } + return false; }); - if (target) { - const texto = target.innerText; - target.click(); - return texto; + + if(hayDesglose) { + console.log(" 🔘 Entrando al Desglose..."); + await page.waitForTimeout(3000); } - return null; - }); - if (clickado) { - apuntar(`🖱️ Click realizado en enlace: "${clickado}"`); - await page.waitForTimeout(3000); - } else { - apuntar("⚠️ No encontré ningún enlace de fecha para hacer clic. ¿Está la lista vacía?"); - } - - // 5. BUSCAR DESGLOSE - const hayBotonDesglose = await page.isVisible('input[value*="Desglose" i]'); - if (hayBotonDesglose) { - apuntar("🔘 Botón 'Desglose' detectado. Pulsando..."); - await page.click('input[value*="Desglose" i]'); - await page.waitForTimeout(3000); - } - - // 6. ANÁLISIS VISUAL DE LA TABLA (EL MOMENTO DE LA VERDAD) - apuntar("👀 Analizando estructura de la página..."); - - const diagnosticoPagina = await page.evaluate(() => { - const resultado = []; - - // A) Buscar la tabla amarilla específica de saldo.html - const tablaAmarilla = document.querySelector('table[bgcolor="#F8E8A6"]'); - - if (tablaAmarilla) { - resultado.push({ expediente: "EXITO", direccion: "✅ TABLA AMARILLA ENCONTRADA", importeLimpio: 1 }); + // 3. LEER LA TABLA (EXTRAER SALDO) + const datosTabla = await page.evaluate(() => { + const filas = Array.from(document.querySelectorAll('tr')); + const datos = []; - // Leemos las 3 primeras filas para ver qué hay dentro - const filas = Array.from(tablaAmarilla.querySelectorAll('tr')); - resultado.push({ expediente: "INFO", direccion: `La tabla tiene ${filas.length} filas.`, importeLimpio: 0 }); - - filas.slice(0, 5).forEach((tr, i) => { - resultado.push({ - expediente: `FILA ${i}`, - direccion: tr.innerText.substring(0, 50) + "...", // Primeros 50 caracteres - importeLimpio: 0 - }); + filas.forEach(tr => { + const tds = tr.querySelectorAll('td'); + // Según tu saldo.html: + // Col 0: Servicio | Col 5 (Sexta columna): Saldo + if (tds.length >= 6) { + const servicio = tds[0].innerText.trim(); + const saldoRaw = tds[5].innerText.trim(); // <--- COLUMNA SALDO + + // Validar que sea un servicio numérico + if (/^\d{5,}$/.test(servicio)) { + datos.push({ servicio, saldoRaw }); + } + } }); - } else { - // B) Si no hay tabla amarilla, ¿qué hay? - resultado.push({ expediente: "ERROR", direccion: "❌ NO VEO LA TABLA AMARILLA", importeLimpio: 0 }); - const bodyText = document.body.innerText.substring(0, 100).replace(/\n/g, ' '); - resultado.push({ expediente: "DUMP", direccion: `Texto visible: ${bodyText}`, importeLimpio: 0 }); + return datos; + }); + + console.log(` 💰 Leídos ${datosTabla.length} servicios. Guardando en Firebase...`); + + // 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 (Positivo) + let cleanSaldo = item.saldoRaw.replace(/[^\d.-]/g, ''); // Deja solo numeros, punto y menos + let importe = parseFloat(cleanSaldo); + + if (isNaN(importe)) continue; + + // IMPORTANTE: Convertimos a positivo para paidAmount + // Si prefieres negativo, quita Math.abs() + importe = Math.abs(importe); + + // Buscar el documento en appointments + const q = await db.collection(APPOINTMENTS_COL).where("serviceNumber", "==", item.servicio).get(); + + q.forEach(doc => { + batch.update(doc.ref, { + paidAmount: importe, // <--- GUARDAMOS EL SALDO AQUÍ + status: 'completed', // Asumimos completado + paymentDate: nowISO, + homeservePaymentStatus: 'paid_saldo', + lastUpdatedByRobot: nowISO + }); + batchCount++; + }); + } + + if (batchCount > 0) { + await batch.commit(); + totalActualizados += batchCount; + } } - - return resultado; - }); + } + + return totalActualizados; - return [...logs, ...diagnosticoPagina]; + } catch (e) { throw e; } finally { if(browser) await browser.close(); } +} - } catch (e) { - apuntar("❌ CRASH: " + e.message); - return logs; - } finally { - if (browser) await browser.close(); +// --- AUXILIAR LOGIN --- +async function loginAndGoToLiquidaciones() { + let user = "SIN_USER", pass = "SIN_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 }); + + 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 }; } const PORT = process.env.PORT || 3000;