import { chromium } from 'playwright'; import pg from 'pg'; const { DATABASE_URL } = process.env; if (!DATABASE_URL) { console.error("❌ Error: No hay DATABASE_URL."); process.exit(1); } const pool = new pg.Pool({ connectionString: DATABASE_URL, ssl: false }); const HEADLESS = true; // Función auxiliar para reintentar la navegación async function gotoWithRetry(page, url, retries = 3) { for (let i = 0; i < retries; i++) { try { await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 }); return; } catch (e) { if (i === retries - 1) throw e; console.log(`⚠️ Fallo de red. Reintentando (${i + 1}/${retries})...`); await page.waitForTimeout(3000); } } } async function main() { console.log("🤖 INICIANDO ROBOT AUXILIAR (Buscador de Detalles)"); while (true) { const client = await pool.connect(); try { // Obtenemos las credenciales activas const res = await client.query("SELECT * FROM provider_credentials WHERE status = 'active' AND provider = 'homeserve'"); for (const cred of res.rows) { let password = Buffer.from(cred.password_hash, 'base64').toString('utf-8'); console.log(`\n🔄 [AUXILIAR] Procesando HOMESERVE para owner_id: ${cred.owner_id}...`); await runHomeserveAux(cred.owner_id, cred.username, password); } } catch (e) { console.error("❌ Error ciclo Auxiliar:", e.message); } finally { client.release(); } console.log("\n💤 [AUXILIAR] Durmiendo 10 minutos..."); await new Promise(r => setTimeout(r, 10 * 60 * 1000)); } } // ========================================== // 🧹 HOMESERVE AUXILIAR (Caza-Iconos y Detalles) // ========================================== async function runHomeserveAux(ownerId, user, pass) { const browser = await chromium.launch({ headless: HEADLESS, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] }); const page = await browser.newPage(); try { console.log("🌍 [HomeServe AUX] Iniciando sesión..."); await gotoWithRetry(page, 'https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=PROF_PASS'); 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(5000); } // Vamos a la lista total de servicios await gotoWithRetry(page, 'https://www.clientes.homeserve.es/cgi-bin/fccgi.exe?w3exec=lista_servicios_total'); await page.waitForTimeout(3000); console.log("🔍 [HomeServe AUX] Escaneando iconos (Candados y Ojos)..."); // 🧠 LA MAGIA: Extraemos la referencia y verificamos si hay imágenes específicas junto a ella const serviciosExtraidos = await page.evaluate(() => { const results = []; // Buscamos todas las filas de la tabla de expedientes const rows = Array.from(document.querySelectorAll('table[bgcolor="#FCF4D6"] tr')); rows.forEach(tr => { const firstTd = tr.querySelector('td'); if (!firstTd) return; // Intentamos cazar el número de expediente (empieza por 15 y tiene 8 cifras) const textMatch = firstTd.innerText.trim().match(/^15\d{6}$/); const aMatch = firstTd.querySelector('a') ? firstTd.querySelector('a').innerText.trim().match(/^15\d{6}$/) : null; const ref = textMatch ? textMatch[0] : (aMatch ? aMatch[0] : null); if (ref) { // Miramos si dentro de esa celda existe la imagen del candado o de los ojos const hasLock = firstTd.querySelector('img[src*="candado.gif"]') !== null; const hasEyes = firstTd.querySelector('img[src*="ojos.gif"]') !== null; results.push({ ref, hasLock, hasEyes }); } }); return results; }); console.log(`✅ [HomeServe AUX] Se han escaneado ${serviciosExtraidos.length} expedientes.`); // Guardamos los datos en la base de datos de forma segura for (const data of serviciosExtraidos) { await saveIconStatusToDB(ownerId, 'homeserve', data.ref, data.hasLock, data.hasEyes); } console.log("🏁 [HomeServe AUX] Tarea completada."); } catch (e) { console.error("❌ [HomeServe AUX] Error:", e.message); } finally { await browser.close(); } } // ========================================== // 💾 GUARDADO SEGURO EN BD (Inyección en raw_data) // ========================================== async function saveIconStatusToDB(ownerId, provider, ref, hasLock, hasEyes) { try { // Actualizamos inyectando los valores booleanos dentro del JSON raw_data. // Solo lo hacemos si el expediente existe. await pool.query(` UPDATE scraped_services SET raw_data = jsonb_set( jsonb_set(COALESCE(raw_data, '{}'::jsonb), '{has_lock}', $1::jsonb), '{has_eyes}', $2::jsonb ) WHERE owner_id = $3 AND provider = $4 AND service_ref = $5 `, [ hasLock ? 'true' : 'false', hasEyes ? 'true' : 'false', ownerId, provider, ref ]); } catch (e) { console.error(`❌ Error inyectando iconos en ${ref}:`, e.message); } } main();