Actualizar index.js

This commit is contained in:
marsalva 2025-12-26 09:29:54 +00:00
parent 2c0a1d02c3
commit d11f4e44ad
1 changed files with 259 additions and 91 deletions

350
index.js
View File

@ -1,124 +1,292 @@
// robot-multiasistencia/index.js
const { chromium } = require('playwright');
const admin = require('firebase-admin');
if (process.env.FIREBASE_PRIVATE_KEY) {
try {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
}),
});
console.log("✅ Firebase inicializado correctamente");
} catch (err) {
console.error("❌ Error inicializando Firebase:", err);
process.exit(1);
}
} else {
console.error("❌ Falta FIREBASE_PRIVATE_KEY en las variables de entorno");
process.exit(1);
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
}),
});
}
const db = admin.firestore();
const COLLECTION_PENDIENTES = "multiasistencia_pendientes";
const MULTI_USER = process.env.MULTI_USER;
const MULTI_PASS = process.env.MULTI_PASS;
async function runMultiasistencia() {
console.log(`\n🕒 [${new Date().toLocaleTimeString()}] Iniciando ciclo...`);
let user, pass;
if (!MULTI_USER || !MULTI_PASS) {
console.error("❌ Faltan MULTI_USER o MULTI_PASS en ENV");
process.exit(1);
}
try {
const credSnap = await db.collection("providerCredentials").doc("multiasistencia").get();
if (!credSnap.exists) return console.error("❌ No hay credenciales.");
user = credSnap.data().user;
pass = credSnap.data().pass;
} catch (e) {
return console.error("❌ Error Firestore:", e.message);
}
async function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main() {
const browser = await chromium.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const context = await browser.newContext();
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();
try {
console.log("🌐 Entrando a Multiasistencia...");
await page.goto('https://web.multiasistencia.com/', { waitUntil: 'domcontentloaded', timeout: 120000 });
console.log("🌍 Entrando a la web...");
await page.goto('https://web.multiasistencia.com/w3multi/acceso.php', { waitUntil: 'networkidle', timeout: 60000 });
console.log("🔐 Login...");
await page.waitForSelector('input[type="text"]', { timeout: 60000 });
await page.fill('input[type="text"]', MULTI_USER);
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;
}
return false;
}, user);
await page.waitForSelector('input[type="password"]', { timeout: 60000 });
await page.fill('input[type="password"]', MULTI_PASS);
if (!userFilled) await page.fill('input[name="usuario"]', user);
await page.fill('input[type="password"]', pass);
await page.click('input[type="submit"]');
await page.waitForTimeout(4000);
const btn = await page.$('button[type="submit"]');
if (btn) {
await btn.click();
} else {
await page.keyboard.press('Enter');
console.log("🔄 Cargando listado...");
for (let i = 1; i <= 3; i++) {
await page.goto('https://web.multiasistencia.com/w3multi/frepasos_new.php?refresh=1', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2000);
}
await page.waitForLoadState('networkidle', { timeout: 120000 });
console.log("✅ Logueado");
await delay(3000);
let tieneSiguiente = true;
let paginaActual = 1;
// Ir a servicios / pendientes (según portal)
const pendientesLink = await page.$('text=Pendientes');
if (pendientesLink) {
await pendientesLink.click();
await page.waitForLoadState('networkidle', { timeout: 120000 });
await delay(2000);
}
while (tieneSiguiente && paginaActual <= 3) {
console.log(`📄 Página ${paginaActual}...`);
console.log("📥 Extrayendo servicios...");
const servicios = await page.evaluate(() => {
const rows =
Array.from(document.querySelectorAll('table tbody tr')) ||
Array.from(document.querySelectorAll('tbody tr'));
return rows.map(r => {
const cells = Array.from(r.querySelectorAll('td')).map(td => td.innerText.trim());
return { cells };
}).filter(x => x && x.cells && x.cells.length > 0);
});
console.log(`🧾 Encontrados ${servicios.length} servicios (filas)`);
const batch = db.batch();
const col = db.collection('multiasistencia_pendientes');
let saved = 0;
for (const item of servicios) {
const ref = col.doc();
batch.set(ref, {
raw: item,
createdAt: admin.firestore.FieldValue.serverTimestamp(),
source: 'multiasistencia',
const expedientes = await page.evaluate(() => {
const links = Array.from(document.querySelectorAll('a[href*="reparacion="]'));
return Array.from(new Set(links.map(a => a.href.match(/reparacion=(\d+)/)?.[1]).filter(Boolean)));
});
saved++;
if (saved % 450 === 0) {
await batch.commit();
if (expedientes.length === 0) {
for (const frame of page.frames()) {
const frameLinks = await frame.evaluate(() => {
const links = Array.from(document.querySelectorAll('a[href*="reparacion="]'));
return Array.from(new Set(links.map(a => a.href.match(/reparacion=(\d+)/)?.[1]).filter(Boolean)));
});
if (frameLinks.length > 0) expedientes.push(...frameLinks);
}
}
console.log(`🔍 Encontrados ${expedientes.length} expedientes.`);
for (const ref of expedientes) {
const detalleUrl = `https://web.multiasistencia.com/w3multi/repasos1.php?reparacion=${ref}`;
try {
await page.goto(detalleUrl, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1500);
let foundData = false;
for (const frame of page.frames()) {
try {
const scrapData = await frame.evaluate(() => {
const clean = (text) => text ? text.replace(/\s+/g, ' ').trim() : "";
const bodyText = document.body?.innerText || "";
if (!bodyText.includes("Nombre Cliente") && !bodyText.includes("Asegurado")) return null;
// Buscar valor por fila: "Etiqueta" | "Valor"
const findRowValue = (labels) => {
const rows = Array.from(document.querySelectorAll('tr'));
for (const tr of rows) {
const tds = Array.from(tr.querySelectorAll('td'));
if (tds.length >= 2) {
const key = clean(tds[0].innerText).toUpperCase();
if (labels.some(l => key === l.toUpperCase())) {
const val = clean(tds[1].innerText);
if (val) return val;
}
}
}
return "";
};
const allCells = Array.from(document.querySelectorAll('td, th'));
const getVertical = (keywords) => {
const header = allCells.find(el => keywords.some(k => (el.innerText || "").trim().toUpperCase() === k.toUpperCase()));
if (!header) return null;
const cellIndex = header.cellIndex;
const row = header.parentElement;
const tbody = row.parentElement;
let nextRow = row.nextElementSibling;
if (!nextRow && tbody.tagName === 'THEAD') {
const table = header.closest('table');
const realBody = table ? table.querySelector('tbody') : null;
if (realBody && realBody.rows && realBody.rows[0]) nextRow = realBody.rows[0];
}
if (nextRow && nextRow.cells && nextRow.cells[cellIndex]) {
return clean(nextRow.cells[cellIndex].innerText);
}
return null;
};
const getHorizontal = (keywords) => {
const header = allCells.find(el => keywords.some(k => (el.innerText || "").toUpperCase().includes(k.toUpperCase())));
if (header && header.nextElementSibling) {
return clean(header.nextElementSibling.innerText);
}
return null;
};
// ✅ DESCRIPTION: "Descripción de la Reparación" y cortar antes de la primera fecha dd/mm/yyyy
const getDescription = () => {
let text =
findRowValue(["Descripción de la Reparación"]) ||
getHorizontal(["Descripción de la Reparación", "Descripción", "Daños"]) ||
"";
text = clean(text);
// Cortar en la primera fecha dd/mm/yyyy (da igual si va con paréntesis o no)
const idxDate = text.search(/\b\d{2}\/\d{2}\/\d{4}\b/);
if (idxDate !== -1) {
text = text.substring(0, idxDate).trim();
}
return text;
};
// ✅ multiStatus: guardar el ESTADO tal cual aparece (incluyendo "(27/07/2025 - 13:13)")
const getStatus = () => {
const st = findRowValue(["Estado", "Situación"]);
if (st) return st; // guardamos todo el texto
return getHorizontal(["Estado", "Situación"]) || "PENDIENTE";
};
const getDate = () => {
let dt = getHorizontal(["Fecha/Hora Apertura", "Fecha Apertura"]);
if (dt) dt = dt.replace('/', '').replace(/\s+/g, ' ').trim();
return dt || "";
};
const getPhone = () => {
let rawText = "";
const titleDiv = Array.from(document.querySelectorAll('div.subtitulo'))
.find(d => (d.innerText || "").includes("Teléfono del Cliente"));
if (titleDiv) {
const table = titleDiv.closest('table') || titleDiv.parentElement.querySelector('table');
if (table) rawText = table.innerText || "";
}
if (!rawText) rawText = bodyText;
const match = rawText.match(/[6789]\d{8}/);
return match ? match[0] : "Sin teléfono";
};
const getCompany = () => {
let text = getHorizontal(["Procedencia", "Compañía"]);
if (!text) {
const regex = /Procedencia\s*[:\-]?\s*([^\n]+)/i;
const match = bodyText.match(regex);
if (match) text = match[1];
}
if (!text) return "MULTI - MULTIASISTENCIA";
return `MULTI - ${clean(text)}`;
};
const rawAddress = getVertical(["Dirección", "Domicilio"]);
const rawZip = getVertical(["Distrito Postal", "C.P", "Distrito"]);
let fullAddress = rawAddress || "Sin dirección";
if (rawZip) fullAddress = `${fullAddress} ${rawZip}`;
return {
clientName: getVertical(["Nombre Cliente", "Asegurado"]),
address: fullAddress,
company: getCompany(),
phone: getPhone(),
description: getDescription(),
multiStatus: getStatus(),
dateString: getDate(),
serviceNumber: "",
hasContent: true
};
});
if (scrapData && scrapData.clientName) {
scrapData.serviceNumber = ref;
console.log(`✅ EXITO ${ref}: ${scrapData.clientName} | Estado: ${scrapData.multiStatus}`);
await db.collection(COLLECTION_PENDIENTES).doc(ref).set({
...scrapData,
status: "pendiente_validacion",
updatedAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true });
foundData = true;
break;
}
} catch (e) { /* ignore frame errors */ }
}
if (!foundData) {
console.log(`⚠️ ALERTA: No se pudo leer ${ref}`);
await db.collection(COLLECTION_PENDIENTES).doc(ref).set({
serviceNumber: ref,
status: "error_formato",
clientName: "ERROR - REVISAR MANUAL",
updatedAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true });
}
} catch (errDetail) {
console.error(`❌ Error en ${ref}:`, errDetail.message);
}
}
if (paginaActual >= 3) break;
const siguientePaginaNum = paginaActual + 1;
console.log(`➡️ Pasando a pág ${siguientePaginaNum}...`);
await page.goto(
`https://web.multiasistencia.com/w3multi/frepasos_new.php?refresh=1&paginasiguiente=${siguientePaginaNum}`,
{ waitUntil: 'domcontentloaded' }
);
const hayResultados = await page.evaluate(() => document.querySelectorAll('a[href*="reparacion="]').length > 0);
if (hayResultados) {
paginaActual++;
await page.waitForTimeout(2000);
} else {
tieneSiguiente = false;
}
}
await batch.commit();
console.log(`✅ Guardados ${saved} registros en Firestore (multiasistencia_pendientes)`);
} catch (err) {
console.error("❌ Error en robot Multiasistencia:", err);
try {
await page.screenshot({ path: '/tmp/error-multiasistencia.png', fullPage: true });
console.log("📸 Screenshot guardada en /tmp/error-multiasistencia.png");
} catch (e) {}
process.exitCode = 1;
} catch (e) {
console.error("❌ Error General:", e.message);
} finally {
await browser.close();
console.log("🧹 Browser cerrado");
}
}
main();
async function start() {
while (true) {
await runMultiasistencia();
console.log("💤 Durmiendo 15 minutos...");
await new Promise(r => setTimeout(r, 15 * 60 * 1000));
}
}
start();