From 7e9e0186e2afad157352b69fdd75417975479d29 Mon Sep 17 00:00:00 2001 From: marsalva Date: Tue, 30 Dec 2025 09:09:31 +0000 Subject: [PATCH] Actualizar robot_cobros.js --- robot_cobros.js | 259 ++++++++++++++++++++---------------------------- 1 file changed, 106 insertions(+), 153 deletions(-) diff --git a/robot_cobros.js b/robot_cobros.js index 1690137..256d60b 100644 --- a/robot_cobros.js +++ b/robot_cobros.js @@ -3,8 +3,7 @@ const { chromium } = require('playwright'); const admin = require('firebase-admin'); const cors = require('cors'); -// --- CONFIGURACIÓN FIREBASE --- - +// --- 1. CONFIGURACIÓN FIREBASE --- try { if (process.env.FIREBASE_PRIVATE_KEY) { if (!admin.apps.length) { @@ -17,40 +16,44 @@ try { }); } console.log("✅ Firebase inicializado."); + } else { + console.error("❌ FALTA CONFIGURACIÓN DE FIREBASE (Variables de entorno)"); } } catch (e) { console.error("❌ Error Firebase:", e.message); } const db = admin.apps.length ? admin.firestore() : null; const APPOINTMENTS_COL = "appointments"; -// URL LOGIN MULTIASISTENCIA (Verifica que sea esta) -const MULTI_LOGIN_URL = "https://web.multiasistencia.com/w3multi/acceso.php"; +const URLS = { + MULTI_LOGIN: "https://web.multiasistencia.com/w3multi/acceso.php", + MULTI_LIST: "https://web.multiasistencia.com/w3multi/cerrados.php", + HS_LOGIN: "https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=PROF_PASS", + HS_LIQ: "https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=CONSULTALIQ_WEB" +}; const app = express(); app.use(cors({ origin: '*' })); -app.use(express.json({ limit: '10mb' })); +app.use(express.json({ limit: '50mb' })); -// --- ENDPOINT PRINCIPAL --- +// --- 2. ENDPOINT PRINCIPAL --- app.post('/api/robot-cobros', async (req, res) => { const { action, url, provider, dataToSave, month, year } = req.body; - console.log(`🔔 Orden: ${action.toUpperCase()} [${provider || 'HS'}]`); + console.log(`🔔 Petición: ${action} [${provider}]`); try { - // === ROBOT MULTIASISTENCIA === if (provider === 'MULTI') { if (action === 'scan') { - // Escanear lista y luego entrar en detalles const lista = await runMultiFullScan(month, year); res.json({ success: true, data: lista }); } else if (action === 'save_data') { - // Guardar const count = await runSaver(dataToSave, 'MULTI'); res.json({ success: true, count }); } + else { throw new Error("Acción MULTI inválida"); } } - // === ROBOT HOMESERVE === else { + // HOMESERVE if (action === 'scan') { const lista = await runHSScanner(); res.json({ success: true, data: lista }); @@ -64,183 +67,148 @@ app.post('/api/robot-cobros', async (req, res) => { const count = await runSaver(dataToSave, 'HS'); res.json({ success: true, count }); } + else { throw new Error("Acción HS inválida"); } } } catch (err) { - console.error("❌ Error:", err.message); + console.error("❌ CRASH:", err.message); res.status(500).json({ success: false, message: err.message }); } }); -// ========================================== -// 🤖 LÓGICA MULTIASISTENCIA -// ========================================== +// ================================================================== +// 🤖 3. LÓGICA MULTIASISTENCIA (LOGIN CORREGIDO) +// ================================================================== async function runMultiFullScan(mes, anio) { let browser = null; const resultados = []; const noEncontrados = []; - - // Formato fecha web: "1_2025", "12_2025" const valD1 = `${mes}_${anio}`; const valD2 = `${anio}`; try { - console.log(`🚀 Iniciando MULTI para ${valD1}...`); + console.log(`🚀 (MULTI) Iniciando: ${valD1}`); - // 1. Login + // 1. LOGIN (Usando tu lógica probada) const { browser: b, page } = await loginMulti(); browser = b; - // 2. Ir a Cerrados - console.log("📂 Yendo a Cerrados..."); - await page.goto('https://web.multiasistencia.com/w3multi/cerrados.php'); + // 2. IR A CERRADOS + console.log("📂 (MULTI) Yendo a Cerrados..."); + await page.goto(URLS.MULTI_LIST, { waitUntil: 'domcontentloaded' }); - // 3. Filtrar por Fecha - console.log(`📅 Filtrando fecha: ${valD1}`); - // Rellenar formulario de filtro si existe + // 3. FILTRAR + console.log(`📅 (MULTI) Filtrando...`); if (await page.isVisible('select[name="D1"]')) { await page.selectOption('select[name="D1"]', valD1); await page.selectOption('select[name="D2"]', valD2); - await page.click('input[name="continuar"]'); // Botón Continuar + await page.click('input[name="continuar"]'); await page.waitForTimeout(2000); } - // 4. Recorrer Páginas y Extraer IDs + // 4. RECORRER PÁGINAS let idsServicios = []; let tieneSiguiente = true; let pagActual = 1; - while (tieneSiguiente && pagActual <= 5) { // Límite seguridad 5 págs - console.log(`📄 Leyendo página ${pagActual}...`); - - // Extraer filas de la tabla (Clase 'tdet') + while (tieneSiguiente && pagActual <= 5) { + console.log(`📄 (MULTI) Página ${pagActual}...`); const nuevosIds = await page.evaluate(() => { const celdas = Array.from(document.querySelectorAll('td.tdet')); const lista = []; - // La estructura parece ser filas de celdas. Buscamos celdas que tengan 8 dígitos numéricos celdas.forEach(td => { const txt = td.innerText.trim(); - if (/^\d{8}$/.test(txt)) { - lista.push(txt); - } + if (/^\d{8}$/.test(txt)) lista.push(txt); }); return lista; }); + idsServicios.push(...nuevosIds); - // Eliminar duplicados en la misma página - const unicosPag = [...new Set(nuevosIds)]; - idsServicios = [...idsServicios, ...unicosPag]; - console.log(` -> Encontrados ${unicosPag.length} servicios.`); + // Botón Siguiente + const haySiguiente = await page.evaluate(() => { + const img = document.querySelector('img[src*="seguir.gif"]'); + if (img && img.parentElement.tagName === 'A') { + img.parentElement.click(); return true; + } + return false; + }); - // Buscar botón siguiente - // En el HTML: onclick="javascript:document.visor.paginasiguiente.value=1+1" - // Intentaremos detectar si hay imagen de "seguir.gif" o texto "Página siguiente" - const btnSiguiente = await page.$('img[src*="seguir.gif"]'); - if (btnSiguiente) { - const padreLink = await btnSiguiente.evaluateHandle(el => el.parentElement); // El o el padre - if (padreLink) { - await padreLink.click(); // Clicamos en la flecha/enlace - await page.waitForTimeout(2500); - pagActual++; - } else { tieneSiguiente = false; } - } else { - tieneSiguiente = false; - } + if (haySiguiente) { await page.waitForTimeout(3000); pagActual++; } + else { tieneSiguiente = false; } } - // Eliminar duplicados totales idsServicios = [...new Set(idsServicios)]; - console.log(`🔍 Total Servicios a revisar: ${idsServicios.length}`); + console.log(`🔍 (MULTI) Servicios a analizar: ${idsServicios.length}`); - // 5. Entrar en CADA servicio para ver el dinero (Presupuesto) + if (idsServicios.length === 0) return { encontrados: [], noEncontrados: [] }; + + // 5. DETALLES (PRESUPUESTO) for (const idServicio of idsServicios) { - console.log(`💰 Analizando servicio: ${idServicio}`); - - // URL Directa al presupuesto + // Ir directo al presupuesto const urlPresupuesto = `https://web.multiasistencia.com/w3multi/valprincipal.php?reparacion=${idServicio}&modo=0`; try { - await page.goto(urlPresupuesto, { timeout: 30000, waitUntil: 'domcontentloaded' }); - - // Esperar un poco a que carguen los componentes Angular/JS - await page.waitForTimeout(1000); + await page.goto(urlPresupuesto, { timeout: 45000, waitUntil: 'domcontentloaded' }); + await page.waitForTimeout(1500); - // Extraer Datos del Presupuesto const info = await page.evaluate(() => { - // Buscar "Nombre Cliente" y "Dirección" en los bloques de cabecera - const buscarTexto = (label) => { - // Buscamos en los divs de info - const divs = Array.from(document.querySelectorAll('.policy-info-block')); - for (let div of divs) { - if (div.innerText.includes(label)) { - const valDiv = div.querySelector('.policy-info-value'); - return valDiv ? valDiv.innerText.trim() : ''; - } - } - // Fallback a tablas antiguas + const clean = (t) => t ? t.trim() : "Desconocido"; + let cliente = "", direccion = "", totalStr = "0"; + + // Datos Cliente (Bloques modernos o Tablas antiguas) + 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; + }); + + if (!cliente) { const tds = Array.from(document.querySelectorAll('td')); - const targetTd = tds.find(td => td.innerText.includes(label)); - if(targetTd && targetTd.nextElementSibling) return targetTd.nextElementSibling.innerText.trim(); - return ''; - }; - - const cliente = buscarTexto('Nombre Cliente') || "Desconocido"; - const direccion = buscarTexto('Dirección') || "Desconocido"; - - // Buscar el TOTAL DINERO - // Estrategia 1: Buscar celda con atributo data-label="TOTAL REPARACION:" - let totalStr = ""; - const celdaTotal = document.querySelector('td[data-label*="TOTAL REPARACION"]'); - if (celdaTotal) { - totalStr = celdaTotal.innerText; - } else { - // Estrategia 2: Buscar texto "TOTAL REPARACION" y coger el siguiente - const allTd = Array.from(document.querySelectorAll('td, th')); - const header = allTd.find(el => el.innerText.includes("TOTAL REPARACION")); - if (header && header.nextElementSibling) totalStr = header.nextElementSibling.innerText; - else if (header) { - // A veces está en la misma celda o fila rara - totalStr = header.innerText.replace("TOTAL REPARACION", ""); + for(let i=0; i el.innerText && el.innerText.includes("TOTAL REPARACION")); + if (header) { + if(header.tagName==='TD' || header.tagName==='TH') { + if(header.nextElementSibling) totalStr = header.nextElementSibling.innerText; + } else { + totalStr = header.innerText.replace("TOTAL REPARACION", "").replace(":", ""); + } + } + } + return { cliente: clean(cliente), direccion: clean(direccion), totalStr }; }); - // Limpiar importe let importe = 0; if (info.totalStr) { - // "53,66 €" -> 53.66 - let clean = info.totalStr.replace(/[^\d.,]/g, '').replace(',', '.'); - importe = parseFloat(clean) || 0; + let cleanMoney = info.totalStr.replace(/[^\d.,-]/g, '').replace(',', '.'); + importe = Math.abs(parseFloat(cleanMoney) || 0); } - // Cruzar con Firebase if (db) { const q = await db.collection(APPOINTMENTS_COL).where("serviceNumber", "==", idServicio).get(); if (!q.empty) { q.forEach(doc => { - resultados.push({ - servicio: idServicio, - direccion: info.direccion, - cliente: info.cliente, - importe: importe, - docId: doc.id - }); + resultados.push({ servicio: idServicio, direccion: info.direccion, importe, docId: doc.id }); }); } else { - noEncontrados.push({ - servicio: idServicio, - direccion: info.direccion, - importe: importe - }); + noEncontrados.push({ servicio: idServicio, direccion: info.direccion, importe }); } } - } catch (errDetail) { - console.error(` ⚠️ Error leyendo ${idServicio}: ${errDetail.message}`); - } + } catch (errDetail) { console.error(` ⚠️ Error ${idServicio}: ${errDetail.message}`); } } return { encontrados: resultados, noEncontrados }; @@ -257,15 +225,20 @@ async function loginMulti() { if(!user) throw new Error("Faltan credenciales Multiasistencia en Firebase"); const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); - const page = await browser.newPage(); + const context = await browser.newContext({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' }); + const page = await context.newPage(); - console.log("🌍 Login Multi..."); - await page.goto(MULTI_LOGIN_URL, { timeout: 60000 }); + console.log("🌍 (MULTI) Entrando al login..."); + await page.goto(URLS.MULTI_LOGIN, { waitUntil: 'networkidle', timeout: 60000 }); - // Rellenar form (según tu código anterior) + // TU LÓGICA DE LOGIN QUE FUNCIONA const userFilled = await page.evaluate((u) => { const el = document.querySelector('input[name="usuario"]') || document.querySelector('input[type="text"]'); - if (el) { el.value = u; el.dispatchEvent(new Event('input', { bubbles: true })); return true; } + if (el) { + el.value = u; + el.dispatchEvent(new Event('input', { bubbles: true })); + return true; + } return false; }, user); @@ -273,20 +246,15 @@ async function loginMulti() { await page.fill('input[type="password"]', pass); await page.click('input[type="submit"]'); await page.waitForTimeout(4000); - + + console.log("✅ (MULTI) Login OK"); return { browser, page }; } - -// ========================================== -// 🤖 ZONA HOMESERVE (Mantenemos intacta) -// ========================================== +// ================================================================== +// 🤖 4. LÓGICA HOMESERVE (Mantenida igual) +// ================================================================== async function runHSScanner() { - // ... (El código de HomeServe que ya tenías, resumido aquí para ahorrar espacio) ... - // COPIA AQUÍ LA FUNCIÓN runScanner DE LA VERSIÓN ANTERIOR - // O DÍMELO SI QUIERES QUE TE PEGUE EL ARCHIVO ENTERO DE 400 LÍNEAS - // Para simplificar, asumo que mantienes las funciones HS que funcionaban. - // Voy a poner una versión mínima que llama a loginHS let browser = null; try { const { browser: b, page } = await loginHS(); @@ -305,14 +273,12 @@ async function runHSScanner() { } async function runHSAnalyzer(targetUrl) { - // ... (Tu función runAnalyzer de HomeServe v6/v7) ... let browser = null; try { const { browser: b, page } = await loginHS(); browser = b; await page.goto(targetUrl, { timeout: 60000 }); await page.waitForTimeout(1500); - // ... Logica buscar boton y leer tabla ... const botonPulsado = await page.evaluate(() => { const elementos = Array.from(document.querySelectorAll('input, button, a')); const target = elementos.find(el => { @@ -362,31 +328,26 @@ async function loginHS() { } const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); const page = await browser.newPage(); - await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=PROF_PASS', { timeout: 60000 }); + await page.goto(URLS.HS_LOGIN, { 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); } - await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=CONSULTALIQ_WEB'); + await page.goto(URLS.HS_LIQ); await page.waitForTimeout(2000); return { browser, page }; } -// ========================================== -// 💾 GUARDADOR GENÉRICO -// ========================================== +// 💾 GUARDADOR async function runSaver(items, providerType) { 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); - - // Datos comunes const updateData = { paidAmount: item.importe, paymentState: "Pagado", @@ -394,20 +355,12 @@ async function runSaver(items, providerType) { paymentDate: nowISO, lastUpdatedByRobot: nowISO }; - - // Campos específicos para saber de dónde vino el pago - if (providerType === 'MULTI') { - updateData.multiasistenciaPaymentStatus = 'paid_verified'; - } else { - updateData.homeservePaymentStatus = 'paid_saldo'; - } - + if (providerType === 'MULTI') updateData.multiasistenciaPaymentStatus = 'paid_verified'; + else updateData.homeservePaymentStatus = 'paid_saldo'; batch.update(ref, updateData); count++; }); - await batch.commit(); - console.log(`💾 Guardados ${count} documentos (${providerType}).`); return count; }