diff --git a/robot.js b/robot.js index 38af478..695ebff 100644 --- a/robot.js +++ b/robot.js @@ -41,11 +41,13 @@ async function main() { } } +// ... (Mantén tus imports y la función main intactos) + // ========================================== -// 🏥 MULTIASISTENCIA V8 (LÓGICA BASADA EN FRAMES) +// 🏥 MULTIASISTENCIA V9 (LÓGICA DE FRAMES & SELECTORES) // ========================================== async function runMultiasistencia(ownerId, user, pass) { - const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); + const browser = await chromium.launch({ headless: HEADLESS, args: ['--no-sandbox'] }); const context = await browser.newContext(); const page = await context.newPage(); @@ -58,7 +60,7 @@ async function runMultiasistencia(ownerId, user, pass) { await page.click('input[type="submit"]'); await page.waitForTimeout(4000); - // Forzar recarga de listado + // Forzar recarga de listado (paso crítico del código de éxito) await page.goto('https://web.multiasistencia.com/w3multi/frepasos_new.php?refresh=1'); await page.waitForTimeout(2000); @@ -67,7 +69,7 @@ async function runMultiasistencia(ownerId, user, pass) { return Array.from(new Set(links.map(a => a.href.match(/reparacion=(\d+)/)?.[1]).filter(Boolean))); }); - console.log(`🔍 [Multi] ${expedientes.length} expedientes encontrados.`); + console.log(`🔍 [Multi] Procesando ${expedientes.length} expedientes.`); for (const ref of expedientes) { await page.goto(`https://web.multiasistencia.com/w3multi/repasos1.php?reparacion=${ref}`, { waitUntil: 'domcontentloaded' }); @@ -75,58 +77,72 @@ async function runMultiasistencia(ownerId, user, pass) { let scrapData = null; - // RECORREMOS LOS FRAMES (La clave del éxito) + // NAVEGACIÓN POR FRAMES (Soluciona el problema de campos vacíos) for (const frame of page.frames()) { try { scrapData = await frame.evaluate(() => { const clean = (t) => t ? t.replace(/\s+/g, ' ').trim() : ""; const body = document.body?.innerText || ""; - // Si este frame no tiene datos de cliente, lo saltamos + // Si el frame no contiene datos clave, pasamos al siguiente if (!body.includes("Nombre Cliente") && !body.includes("Asegurado")) return null; - const allCells = Array.from(document.querySelectorAll('td, th, b')); + const allCells = Array.from(document.querySelectorAll('td, th, b, div')); - // Lógica de búsqueda horizontal (Etiqueta -> Valor al lado) + // Lógica Horizontal: Etiqueta -> Valor a la derecha const getH = (keys) => { const header = allCells.find(el => keys.some(k => el.innerText.toUpperCase().includes(k.toUpperCase()))); return header && header.nextElementSibling ? clean(header.nextElementSibling.innerText) : null; }; - // Lógica de búsqueda vertical (Etiqueta -> Valor abajo) + // Lógica Vertical: Etiqueta -> Valor en la fila de abajo (usada para Dirección/Nombre) const getV = (keys) => { const header = allCells.find(el => keys.some(k => el.innerText.trim().toUpperCase() === k.toUpperCase())); if (!header) return null; const idx = header.cellIndex; - const nextRow = header.parentElement.nextElementSibling; + const row = header.parentElement; + const nextRow = row.nextElementSibling; + // Manejo de THEAD a TBODY si es necesario + if (!nextRow) { + const table = header.closest('table'); + const tbody = table ? table.querySelector('tbody') : null; + const firstRow = tbody ? tbody.rows[0] : null; + return firstRow && firstRow.cells[idx] ? clean(firstRow.cells[idx].innerText) : null; + } return nextRow && nextRow.cells[idx] ? clean(nextRow.cells[idx].innerText) : null; }; - // Recolectamos todo con nombres estables para el mapeador - const data = {}; - data['Expediente'] = getH(["Número Reparación", "Expediente"]) || ""; - data['Nombre Cliente'] = getV(["Nombre Cliente", "Asegurado"]) || ""; - data['Dirección'] = getV(["Dirección", "Domicilio"]) || ""; - data['Población_CP'] = getV(["Distrito Postal", "C.P"]) || ""; - data['Compañía'] = getH(["Procedencia", "Compañía"]) || ""; - data['Descripción'] = getH(["Descripción de la Reparación", "Descripción"]) || ""; - data['Urgente'] = getH(["Urgente"]) || "No"; - data['Fecha Cita'] = getH(["Fecha realización"]) || ""; - data['Tramitador'] = getH(["Tramitador"]) || ""; + const d = {}; + // Extraemos usando los selectores que funcionan en tu código de éxito + d['Expediente'] = getH(["Número Reparación", "Número de Servicio"]) || ""; + d['Nombre Cliente'] = getV(["Nombre Cliente", "Asegurado"]) || ""; + d['Dirección'] = getV(["Dirección", "Domicilio"]) || ""; + d['Población_CP'] = getV(["Distrito Postal", "C.P", "Distrito"]) || ""; + d['Compañía'] = getH(["Procedencia", "Compañía"]) || "MULTI - MULTIASISTENCIA"; + + // Limpieza de descripción (evita capturar fechas basura) + let rawDesc = getH(["Descripción de la Reparación", "Descripción"]) || ""; + const idxDate = rawDesc.search(/\b\d{2}\/\d{2}\/\d{4}\b/); + d['Descripción'] = idxDate !== -1 ? rawDesc.substring(0, idxDate).trim() : rawDesc; - // Teléfono (Busca patrón de 9 dígitos) + d['Urgente'] = getH(["Urgente"]) || "No"; + d['Fecha Cita'] = getH(["Fecha realización", "Fecha de Realización"]) || ""; + + // Teléfono: Buscamos patrón de 9 dígitos const phoneMatch = body.match(/[6789]\d{8}/); - data['Teléfono'] = phoneMatch ? phoneMatch[0] : ""; + d['Teléfono'] = phoneMatch ? phoneMatch[0] : ""; - return data; + return d; }); - if (scrapData && scrapData['Nombre Cliente']) break; // Si ya encontramos los datos, salimos del bucle de frames + if (scrapData && scrapData['Nombre Cliente']) break; } catch (e) { continue; } } if (scrapData && scrapData['Nombre Cliente']) { + // Sincronizamos con tu función de guardado en Postgres await saveServiceToDB(ownerId, 'multiasistencia', ref, scrapData); + console.log(`✅ [Multi] Guardado: ${ref} - ${scrapData['Nombre Cliente']}`); } } } catch (e) { console.error("❌ Error Multi:", e.message); }