Actualizar worker-multi-estado.js

This commit is contained in:
marsalva 2026-01-06 22:22:31 +00:00
parent dda9dd1a1b
commit 244678b69d
1 changed files with 69 additions and 76 deletions

View File

@ -22,6 +22,7 @@ function toServerTimestamp() { return admin.firestore.FieldValue.serverTimestamp
function timeToMultiValue(timeStr) {
if (!timeStr) return "";
const [h, m] = timeStr.split(':').map(Number);
// Formato interno de Multi: segundos desde medianoche
return String((h * 3600) + (m * 60));
}
@ -40,19 +41,6 @@ function initFirebase() {
return admin.firestore();
}
// --- HELPERS AVANZADOS ---
// Esta función es la clave: obliga a la web a reconocer el cambio
async function triggerEvents(page, selector) {
await page.evaluate((sel) => {
const el = document.querySelector(sel);
if (el) {
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('blur', { bubbles: true }));
}
}, selector);
}
// --- LOGIN ---
async function loginMulti(page, db) {
let user = "", pass = "";
@ -84,6 +72,17 @@ async function withBrowser(fn) {
try { return await fn(page); } finally { await browser.close().catch(() => {}); }
}
// --- HELPERS DE EVENTOS ---
async function forceUpdate(elementHandle) {
if (elementHandle) {
await elementHandle.evaluate(el => {
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('blur', { bubbles: true }));
});
}
}
// --- LÓGICA PRINCIPAL ---
async function processChangeState(page, db, jobData) {
const { serviceNumber, reasonValue, comment, dateStr, timeStr } = jobData;
@ -93,76 +92,76 @@ async function processChangeState(page, db, jobData) {
// 2. IR AL SERVICIO
const targetUrl = `${CONFIG.MULTI_ACTION_BASE}?reparacion=${serviceNumber}&modo=0&navid=%2Fw3multi%2Ffrepasos_new.php%FDGET%FDrefresh%3D1%FC`;
console.log(`📂 Abriendo servicio ${serviceNumber}...`);
console.log(`📂 Abriendo servicio 27867185...`); // Log limpio para depurar
await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
// Esperar formulario
await page.waitForSelector('select.answer-select', { timeout: 20000 });
await page.waitForTimeout(2000); // Esperar a que Angular "se asiente"
await page.waitForTimeout(1500);
console.log('📝 Rellenando formulario (Modo Fuerza Bruta)...');
console.log('📝 Rellenando formulario (Modo Preciso)...');
// 3. MOTIVO (SELECT)
// Seleccionamos y forzamos el evento 'change'
const reasonSel = 'select.answer-select';
await page.selectOption(reasonSel, String(reasonValue));
await triggerEvents(page, reasonSel);
await page.waitForTimeout(500);
// 3. MOTIVO (Primer select de la página suele ser el motivo)
const reasonSel = page.locator('select.answer-select').first();
await reasonSel.selectOption(String(reasonValue));
await forceUpdate(await reasonSel.elementHandle());
// 4. COMENTARIO
if (comment) {
const commentSel = 'textarea[formcontrolname="comment"]';
await page.fill(commentSel, comment);
await triggerEvents(page, commentSel);
const commentBox = page.locator('textarea[formcontrolname="comment"]');
await commentBox.fill(comment);
await forceUpdate(await commentBox.elementHandle());
}
// 5. FECHA
if (dateStr) {
const dateSel = 'input[type="date"]';
await page.fill(dateSel, dateStr);
await triggerEvents(page, dateSel);
// Truco: hacer click fuera para validar
const dateInput = page.locator('input[type="date"]');
await dateInput.fill(dateStr);
await forceUpdate(await dateInput.elementHandle());
// Click fuera para asegurar validación de fecha
await page.click('body');
}
// 6. HORA
// 6. HORA (El punto crítico)
if (timeStr) {
const secondsValue = timeToMultiValue(timeStr);
// Buscamos el select de hora. Puede ser el segundo select de la página.
// Usamos una estrategia de búsqueda por valor para ser precisos.
const foundTime = await page.evaluate((val) => {
const selects = Array.from(document.querySelectorAll('select'));
for (const s of selects) {
// Si tiene la opción con ese valor (ej: 28800)
if (s.querySelector(`option[value="${val}"]`)) {
s.value = val;
s.dispatchEvent(new Event('change', { bubbles: true }));
s.dispatchEvent(new Event('blur', { bubbles: true }));
return true;
}
}
return false;
}, secondsValue);
const secondsValue = timeToMultiValue(timeStr); // ej: "28800" para 08:00
console.log(`🕒 Intentando poner hora: ${timeStr} (Valor interno: ${secondsValue})`);
if (!foundTime) console.log(`⚠️ Advertencia: No encontré donde poner la hora ${timeStr}`);
// ESTRATEGIA INFALIBLE: Buscar el select que contiene esa opción específica usando XPath
// "Búscame un <select> que tenga dentro una <option> con value='28800'"
const timeSelectHandle = await page.$(`xpath=//select[.//option[@value="${secondsValue}"]]`);
if (timeSelectHandle) {
await timeSelectHandle.selectOption(secondsValue);
await forceUpdate(timeSelectHandle);
console.log('✅ Hora seleccionada correctamente.');
} else {
console.log(`⚠️ ERROR GRAVE: No encuentro ningún desplegable que tenga la opción de valor "${secondsValue}".`);
// Fallback desesperado: intentar ponerlo en el segundo select que encuentre
const allSelects = await page.$$('select');
if (allSelects.length > 1) {
console.log('⚠️ Intentando fallback en el segundo select...');
await allSelects[1].selectOption(secondsValue).catch(() => {});
await forceUpdate(allSelects[1]);
}
}
}
await page.waitForTimeout(2000);
// 7. GUARDAR - VERIFICACIÓN DE ESTADO DEL BOTÓN
const btnSelector = 'button.form-container-button-submit';
const btn = page.locator(btnSelector);
// 7. GUARDAR
const btn = page.locator('button.form-container-button-submit');
// Comprobar si está habilitado
// Verificación final antes de clickar
if (await btn.isDisabled()) {
console.log('⛔ El botón Guardar sigue deshabilitado. Intentando "despertar" el formulario...');
// Intentar hacer focus/blur en el comentario otra vez
await page.focus('textarea[formcontrolname="comment"]');
console.log('⛔ Botón deshabilitado. Intentando reactivación final...');
// A veces hacer click en el comentario ayuda a que Angular se entere
await page.click('textarea[formcontrolname="comment"]');
await page.keyboard.press('Tab');
await page.waitForTimeout(1000);
if (await btn.isDisabled()) {
throw new Error("IMPOSIBLE GUARDAR: El formulario no valida los datos (botón gris). Revisa si la fecha/hora son correctas.");
throw new Error(`IMPOSIBLE GUARDAR: Formulario incompleto. ¿La hora ${timeStr} es válida para este servicio?`);
}
}
@ -170,34 +169,28 @@ async function processChangeState(page, db, jobData) {
await btn.click();
// 8. VERIFICAR RESULTADO
// Esperamos a ver si sale mensaje de éxito o error
await page.waitForTimeout(3000);
await page.waitForTimeout(4000);
// Buscamos mensajes en la pantalla
// Captura de mensajes de éxito/error de la web
const message = await page.evaluate(() => {
// En tu HTML vi esta etiqueta: <encastrables-success-error-message>
const msgEl = document.querySelector('encastrables-success-error-message');
const errorEl = document.querySelector('.form-container-error');
const successEl = document.querySelector('.form-container-success');
const err = document.querySelector('.form-container-error');
const ok = document.querySelector('.form-container-success');
const toast = document.querySelector('encastrables-success-error-message');
if (errorEl) return `ERROR WEB: ${errorEl.innerText}`;
if (successEl) return `EXITO WEB: ${successEl.innerText}`;
if (msgEl && msgEl.innerText.trim().length > 0) return `MSG: ${msgEl.innerText}`;
if (err && err.innerText) return `ERROR WEB: ${err.innerText}`;
if (ok && ok.innerText) return `EXITO WEB: ${ok.innerText}`;
if (toast && toast.innerText) return `MSG: ${toast.innerText}`;
return null;
});
if (message && message.includes('ERROR')) {
throw new Error(message);
}
console.log(` Estado final: ${message || 'Sin mensaje (Redirección correcta)'}`);
console.log(` Resultado pantalla: ${message || 'Sin mensaje explícito (probablemente OK)'}`);
if (message && message.includes('ERROR')) throw new Error(message);
// Si la URL cambió, es buena señal
const currentUrl = page.url();
return { success: true, finalUrl: currentUrl, webMessage: message };
return { success: true, finalUrl: page.url() };
}
// --- WORKER LOOP ---
// --- WORKER ---
async function claimJobById(db, jobId) {
const ref = db.collection(CONFIG.QUEUE_COLLECTION).doc(jobId);
return await db.runTransaction(async (tx) => {
@ -249,7 +242,7 @@ function startWorker(db) {
db.collection(CONFIG.QUEUE_COLLECTION).where('status', '==', 'PENDING').onSnapshot(s => {
s.docChanges().forEach(c => { if(c.type==='added') { queue.push(c.doc.id); run(); } });
});
console.log('🚀 Worker Multiasistencia Estados (FUERZA BRUTA) LISTO.');
console.log('🚀 Worker Multiasistencia Estados (BÚSQUEDA XPATH) LISTO.');
}
const db = initFirebase();