From 1cab22b1a8567fcc18e329b5876cb4d925734c29 Mon Sep 17 00:00:00 2001 From: marsalva Date: Fri, 26 Dec 2025 10:01:01 +0000 Subject: [PATCH] Actualizar robot.js --- robot.js | 103 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 14 deletions(-) diff --git a/robot.js b/robot.js index 2723e36..de44381 100644 --- a/robot.js +++ b/robot.js @@ -113,8 +113,20 @@ async function getAllPendingDocIds() { return snap.docs.map(d => d.id); } +async function archiveDoc(docRef, nowISO, reason, extra = {}) { + // Marca como archivado para que NO aparezca como "nuevo" (verde) + await docRef.set({ + status: "archived", + archivedReason: reason, + archivedAt: nowISO, + updatedAt: nowISO, + ...extra + }, { merge: true }); +} + async function runRobot() { - console.log('🤖 [V7.5] Robot HomeServe (no dupes + in_system + archiva solo completed)...'); + console.log('🤖 [V7.6] Robot HomeServe (no dupes + in_system + archiva completed + archiva missing/bloqueado)...'); + // 0) Credenciales let creds; try { @@ -124,6 +136,7 @@ async function runRobot() { console.error("❌ No se pudieron cargar credenciales:", e.message); process.exit(1); } + const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], @@ -131,12 +144,14 @@ async function runRobot() { const context = await browser.newContext(); const page = await context.newPage(); const nowISO = new Date().toISOString(); + try { // LOGIN console.log('🔐 Entrando al login...'); await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=PROF_PASS', { timeout: 60000 }); const selectorUsuario = 'input[name="CODIGO"]'; const selectorPass = 'input[type="password"]'; + if (await page.isVisible(selectorUsuario)) { await page.fill(selectorUsuario, ""); await page.fill(selectorPass, ""); @@ -148,9 +163,11 @@ async function runRobot() { } else { console.log("⚠️ No veo login (quizá ya logueado)."); } + // LISTA console.log('📂 Leyendo lista de servicios...'); await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=lista_servicios_total'); + const referenciasEnWeb = await page.evaluate(() => { const filas = Array.from(document.querySelectorAll('table tr')); const refs = []; @@ -164,20 +181,25 @@ async function runRobot() { }); return Array.from(new Set(refs)); }); + console.log(`🔎 Encontrados ${referenciasEnWeb.length} servicios válidos.`); const referenciasNormalizadas = referenciasEnWeb .map(normalizeServiceNumber) .filter(Boolean); + const webSet = new Set(referenciasNormalizadas); + // ✅ precargar info del sistema console.log("🧠 Precargando datos del sistema (appointments/services)..."); const apptInfoMap = await preloadAppointmentsInfo(referenciasNormalizadas); const servicesSet = await preloadServicesExistence(referenciasNormalizadas); console.log(`🧾 En appointments: ${apptInfoMap.size} | En services: ${servicesSet.size}`); + // ✅ ids que ya estaban en homeserve_pendientes console.log("📦 Cargando documentos actuales de homeserve_pendientes..."); const pendingDocIds = await getAllPendingDocIds(); const pendingSet = new Set(pendingDocIds); + // --- CONTADORES --- let actualizados = 0; let nuevos = 0; @@ -185,14 +207,18 @@ async function runRobot() { let saltadosSinDatos = 0; let marcadosInSystem = 0; let archivadosCompleted = 0; - let marcadosMissingNoArchive = 0; + let archivadosMissing = 0; + let archivadosBlocked = 0; + for (const ref of referenciasEnWeb) { const normalized = normalizeServiceNumber(ref); if (!normalized) continue; + const appt = apptInfoMap.get(normalized); const existsInAppointments = !!appt; const existsInServices = servicesSet.has(normalized); const existsInSystem = existsInAppointments || existsInServices; + // ✅ Si ya existe en sistema: // - SI completed => archived // - SI NO completed => in_system @@ -227,24 +253,36 @@ async function runRobot() { // No hace falta scrapear si ya existe el doc y ya está integrado continue; } + // Si existe en sistema pero NO existe en homeserve_pendientes (no lo tenías): - // lo tratamos como antes (scrape) y lo guardamos con status in_system (no archived). - // Si está completed, no creamos doc nuevo (no aporta mucho). + // si está completed, no creamos doc nuevo (no aporta mucho). if (existsInSystem && !pendingSet.has(normalized) && existsInAppointments && isCompletedStatus(appt.status)) { archivadosCompleted++; console.log(`⏭️ SALTADO (completed y sin doc pendiente): ${normalized}`); continue; } + // Navegar a la lista y click await page.goto('https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=lista_servicios_total'); try { await page.click(`text="${normalized}"`, { timeout: 5000 }); await page.waitForTimeout(1500); } catch (e) { + // 🔥 CAMBIO: si está bloqueado/no accesible, lo archivamos para que NO salga como "nuevo" saltadosBloqueo++; console.warn(`⛔ SALTADO (bloqueado/no accesible): ${normalized}`); + + const docRef = db.collection(COLLECTION_NAME).doc(normalized); + await archiveDoc(docRef, nowISO, "blocked_or_unreadable", { + blockedAt: nowISO, + missingFromHomeServe: false, + lastSeenAt: nowISO + }); + archivadosBlocked++; + continue; } + const detalles = await page.evaluate(() => { const d = {}; const filas = Array.from(document.querySelectorAll('tr')); @@ -268,19 +306,32 @@ async function runRobot() { }); return d; }); + if (!hasMinimumData(detalles)) { + // 🔥 CAMBIO: si no hay datos mínimos, también lo archivamos para que NO se quede "nuevo" saltadosSinDatos++; console.warn(`⛔ SALTADO (sin datos mínimos): ${normalized}`); + + const docRef = db.collection(COLLECTION_NAME).doc(normalized); + await archiveDoc(docRef, nowISO, "missing_minimum_data", { + missingFromHomeServe: false, + lastSeenAt: nowISO + }); + archivadosBlocked++; + continue; } + const fullAddress = `${detalles.addressPart || ""} ${detalles.cityPart || ""}`.trim(); let rawCompany = detalles.company || ""; if (rawCompany && !rawCompany.toUpperCase().includes("HOMESERVE")) { rawCompany = `HOMESERVE - ${rawCompany}`; } + const docRef = db.collection(COLLECTION_NAME).doc(normalized); const docSnapshot = await docRef.get(); const datosAntiguos = docSnapshot.exists ? docSnapshot.data() : null; + // Estado según sistema (si existe, es in_system; si no, pendiente_validacion) let status = "pendiente_validacion"; let integratedIn = ""; @@ -294,6 +345,7 @@ async function runRobot() { integratedIn = "services"; } } + const servicioFinal = { serviceNumber: normalized, clientName: detalles.clientName || "Desconocido", @@ -310,7 +362,9 @@ async function runRobot() { updatedAt: nowISO, missingFromHomeServe: false, }; + if (!datosAntiguos) servicioFinal.createdAt = nowISO; + if (!datosAntiguos) { await docRef.set(servicioFinal); console.log(`NUEVO: ${normalized} (status=${status})`); @@ -322,6 +376,7 @@ async function runRobot() { const cambioCliente = (datosAntiguos.clientName || "") !== (servicioFinal.clientName || ""); const cambioCompany = (datosAntiguos.company || "") !== (servicioFinal.company || ""); const cambioStatus = (datosAntiguos.status || "") !== (servicioFinal.status || ""); + if (cambioEstado || cambioTelefono || cambioAddress || cambioCliente || cambioCompany || cambioStatus) { console.log(`♻️ ACTUALIZADO: ${normalized}`); await docRef.set(servicioFinal, { merge: true }); @@ -329,6 +384,7 @@ async function runRobot() { } else { await docRef.set({ lastSeenAt: nowISO, updatedAt: nowISO, missingFromHomeServe: false }, { merge: true }); } + // Si estaba archivado pero reaparece y NO está completed => lo “desarchivamos” if ((datosAntiguos.status || "") === "archived") { const shouldStayArchived = existsInAppointments && isCompletedStatus(appt?.status); @@ -343,26 +399,30 @@ async function runRobot() { } } } + // ✅ Gestionar lo que ha desaparecido de HomeServe - // Regla NUEVA: NO archivar por desaparecer; SOLO archivar si completed. - console.log("🗄️ Revisando los que han desaparecido de HomeServe (sin archivar salvo completed)..."); + console.log("🗄️ Revisando los que han desaparecido de HomeServe..."); const missingIds = pendingDocIds .map(normalizeServiceNumber) .filter(Boolean) .filter(sn => !webSet.has(sn)); + // precargamos su estado en sistema para decidir const apptMissingMap = await preloadAppointmentsInfo(missingIds); const servicesMissingSet = await preloadServicesExistence(missingIds); + for (const sn of missingIds) { const ref = db.collection(COLLECTION_NAME).doc(sn); const snap = await ref.get(); const data = snap.exists ? (snap.data() || {}) : {}; + const appt = apptMissingMap.get(sn); const existsInAppointments = !!appt; const existsInServices = servicesMissingSet.has(sn); const existsInSystem = existsInAppointments || existsInServices; + if (existsInAppointments && isCompletedStatus(appt.status)) { - // ✅ SOLO AQUÍ archivamos + // ✅ SOLO AQUÍ archivamos por completed if ((data.status || "") !== "archived") { await ref.set({ status: "archived", @@ -375,9 +435,16 @@ async function runRobot() { missingAt: nowISO }, { merge: true }); archivadosCompleted++; + } else { + await ref.set({ + updatedAt: nowISO, + missingFromHomeServe: true, + missingAt: nowISO + }, { merge: true }); } continue; } + if (existsInSystem) { // Está en tu sistema pero no está completed => en sistema, NO archived await ref.set({ @@ -391,20 +458,27 @@ async function runRobot() { marcadosInSystem++; continue; } - // No está en sistema y ha desaparecido de HomeServe -> NO archivar, solo marcar missing + + // 🔥 CAMBIO: No está en sistema y ha desaparecido de HomeServe -> ARCHIVAR + // (para que NO aparezca como nuevo/verde y te quede rastro de que existió) await ref.set({ + status: "archived", + archivedReason: "missing_from_homeserve", + archivedAt: nowISO, missingFromHomeServe: true, missingAt: nowISO, updatedAt: nowISO }, { merge: true }); - marcadosMissingNoArchive++; + archivadosMissing++; } + console.log( - `🏁 FIN V7.5: ${nuevos} nuevos, ${actualizados} actualizados, ` + - `${saltadosBloqueo} saltados (bloqueados), ${saltadosSinDatos} saltados (sin datos), ` + + `🏁 FIN V7.6: ${nuevos} nuevos, ${actualizados} actualizados, ` + + `${saltadosBloqueo} bloqueados/no accesibles, ${saltadosSinDatos} sin datos mínimos, ` + `${marcadosInSystem} marcados in_system, ${archivadosCompleted} archivados (completed), ` + - `${marcadosMissingNoArchive} marcados missing (NO archived).` + `${archivadosMissing} archivados (missing), ${archivadosBlocked} archivados (blocked/sin_datos).` ); + } catch (error) { console.error('❌ ERROR:', error.message); process.exit(1); @@ -414,12 +488,13 @@ async function runRobot() { // --- PAUSA DE 15 MINUTOS ANTES DE REINICIAR --- const MINUTOS_ESPERA = 15; console.log(`😴 Pausando ejecución durante ${MINUTOS_ESPERA} minutos...`); - + // 15 minutos * 60 segundos * 1000 milisegundos await new Promise(resolve => setTimeout(resolve, MINUTOS_ESPERA * 60 * 1000)); - + console.log("👋 Tiempo cumplido. Reiniciando proceso..."); process.exit(0); } } + runRobot(); \ No newline at end of file