diff --git a/worker-multiasistencia.js b/worker-multiasistencia.js index 56a067d..100278f 100644 --- a/worker-multiasistencia.js +++ b/worker-multiasistencia.js @@ -1,4 +1,4 @@ -// worker-multiasistencia.js (Versión PostgreSQL SaaS) +// worker-multiasistencia.js (Versión PostgreSQL SaaS + Escáner de Campos Dinámicos) import { chromium } from 'playwright'; import pg from 'pg'; @@ -114,7 +114,6 @@ async function loginMulti(page, creds) { await page.click('input[type="submit"]'); await page.waitForTimeout(4000); - // Verificación básica de login (Si sigue estando el input de password, falló) const isStillLogin = await page.locator('input[type="password"]').count(); if (isStillLogin > 0) throw new Error("Credenciales rechazadas por Multiasistencia."); } @@ -122,7 +121,7 @@ async function loginMulti(page, creds) { // --- PROCESO PRINCIPAL --- async function processChangeState(page, creds, jobData) { const serviceNumber = jobData.service_number; - const reasonValue = jobData.new_status; // Asumimos que la API ya le pasa el ID numérico web + const reasonValue = jobData.new_status; const comment = jobData.observation; const dateStr = normalizeDate(jobData.appointment_date); @@ -142,7 +141,7 @@ async function processChangeState(page, creds, jobData) { await page.waitForSelector('select.answer-select', { timeout: 20000 }); await page.waitForTimeout(1000); - // SELECCIONAR MOTIVO + // 1. SELECCIONAR MOTIVO console.log(` [3] Seleccionando estado ${reasonValue}...`); const reasonSel = page.locator('select.answer-select').first(); const options = await reasonSel.evaluate(s => Array.from(s.options).map(o => o.value)); @@ -154,14 +153,14 @@ async function processChangeState(page, creds, jobData) { await reasonSel.selectOption(String(reasonValue)); await forceUpdate(await reasonSel.elementHandle()); - // COMENTARIO + // 2. COMENTARIO if (comment) { const commentBox = page.locator('textarea[formcontrolname="comment"]'); await commentBox.fill(comment); await forceUpdate(await commentBox.elementHandle()); } - // FECHA SIGUIENTE ACCIÓN (Solo si existe fecha y es válida) + // 3. FECHA SIGUIENTE ACCIÓN (TXTFACCION) - La de la cita real if (dateStr) { const actionBlock = page.locator('encastrables-date-hour-field[label="TXTFACCION"]'); if (await actionBlock.count() > 0) { @@ -173,52 +172,62 @@ async function processChangeState(page, creds, jobData) { if (timeStr) { const seconds = timeToMultiValue(timeStr); const timeSel = actionBlock.locator('select.answer-select'); - await timeSel.selectOption(seconds).catch(()=>{ console.log(' ⚠️ No se pudo poner la hora exacta') }); + await timeSel.selectOption(seconds).catch(()=>{}); await forceUpdate(await timeSel.elementHandle()); } } else { - // Fallback + // Fallback genérico por si cambia la etiqueta const genDate = page.locator('input[type="date"]').first(); - await genDate.fill(dateStr); - await forceUpdate(await genDate.elementHandle()); + if (await genDate.count() > 0) { + await genDate.fill(dateStr); + await forceUpdate(await genDate.elementHandle()); + } } } - // FECHA CONTACTO (AUTOMÁTICA - HOY) - const contactBlock = page.locator('encastrables-date-hour-field[label="TXTFCONTACTO"]'); - if (await contactBlock.count() > 0 && await contactBlock.isVisible()) { - console.log(' [INFO] Rellenando fecha de contacto (Auto Hoy)...'); - const now = getCurrentDateTime(); - - const cDate = contactBlock.locator('input[type="date"]'); - await cDate.fill(now.dateStr); - await forceUpdate(await cDate.elementHandle()); + // 4. AUTO-RELLENADOR INTELIGENTE (Detecta campos obligatorios dinámicos) + const extraDynamicFields = ['TXTFCONTACTO', 'TXTFPRIMERAV']; + + for (const label of extraDynamicFields) { + const block = page.locator(`encastrables-date-hour-field[label="${label}"]`); + if (await block.count() > 0 && await block.isVisible()) { + console.log(` [INFO] Detectado campo obligatorio "${label}". Auto-rellenando...`); + const now = getCurrentDateTime(); + + // Rellenar Fecha + const cDate = block.locator('input[type="date"]'); + await cDate.fill(now.dateStr); + await forceUpdate(await cDate.elementHandle()); - const selects = contactBlock.locator('select.answer-select-time'); - if (await selects.count() >= 2) { - await selects.nth(0).selectOption(now.hourStr).catch(()=>{}); - await forceUpdate(await selects.nth(0).elementHandle()); - await selects.nth(1).selectOption(now.minStr).catch(()=>{}); - await forceUpdate(await selects.nth(1).elementHandle()); + // Rellenar Hora y Minutos (Multiasistencia usa 2 selects separados para esto) + const selects = block.locator('select.answer-select-time'); + if (await selects.count() >= 2) { + await selects.nth(0).selectOption(now.hourStr).catch(()=>{}); + await forceUpdate(await selects.nth(0).elementHandle()); + + await selects.nth(1).selectOption(now.minStr).catch(()=>{}); + await forceUpdate(await selects.nth(1).elementHandle()); + } } } await page.waitForTimeout(2000); - // GUARDAR (CON INTELIGENCIA ANTI-BLOQUEO) + // 5. GUARDAR (CON INTELIGENCIA ANTI-BLOQUEO) const btn = page.locator('button.form-container-button-submit'); if (await btn.isDisabled()) { console.log(' [INFO] Botón bloqueado. Forzando actualización de inputs...'); await page.locator('textarea[formcontrolname="comment"]').click(); await page.keyboard.press('Tab'); await page.waitForTimeout(1000); - if (await btn.isDisabled()) throw new Error(`El formulario está bloqueado (falta algún dato obligatorio).`); + + if (await btn.isDisabled()) throw new Error(`El formulario está bloqueado. Multiasistencia pide más datos de los previstos.`); } console.log(' [4] Guardando cambios en Multiasistencia...'); await btn.click(); - // GESTIÓN DE ALERTAS (Popups de confirmación) + // GESTIÓN DE ALERTAS (Popups de confirmación que a veces lanza la web) await page.waitForTimeout(3000); const confirmBtn = page.locator('button.form-container-button-submit-toast').filter({ hasText: 'Sí' }); if (await confirmBtn.count() > 0 && await confirmBtn.isVisible()) {