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) { function timeToMultiValue(timeStr) {
if (!timeStr) return ""; if (!timeStr) return "";
const [h, m] = timeStr.split(':').map(Number); const [h, m] = timeStr.split(':').map(Number);
// Formato interno de Multi: segundos desde medianoche
return String((h * 3600) + (m * 60)); return String((h * 3600) + (m * 60));
} }
@ -40,19 +41,6 @@ function initFirebase() {
return admin.firestore(); 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 --- // --- LOGIN ---
async function loginMulti(page, db) { async function loginMulti(page, db) {
let user = "", pass = ""; let user = "", pass = "";
@ -84,6 +72,17 @@ async function withBrowser(fn) {
try { return await fn(page); } finally { await browser.close().catch(() => {}); } 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 --- // --- LÓGICA PRINCIPAL ---
async function processChangeState(page, db, jobData) { async function processChangeState(page, db, jobData) {
const { serviceNumber, reasonValue, comment, dateStr, timeStr } = jobData; const { serviceNumber, reasonValue, comment, dateStr, timeStr } = jobData;
@ -93,76 +92,76 @@ async function processChangeState(page, db, jobData) {
// 2. IR AL SERVICIO // 2. IR AL SERVICIO
const targetUrl = `${CONFIG.MULTI_ACTION_BASE}?reparacion=${serviceNumber}&modo=0&navid=%2Fw3multi%2Ffrepasos_new.php%FDGET%FDrefresh%3D1%FC`; 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 }); await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
// Esperar formulario // Esperar formulario
await page.waitForSelector('select.answer-select', { timeout: 20000 }); 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) // 3. MOTIVO (Primer select de la página suele ser el motivo)
// Seleccionamos y forzamos el evento 'change' const reasonSel = page.locator('select.answer-select').first();
const reasonSel = 'select.answer-select'; await reasonSel.selectOption(String(reasonValue));
await page.selectOption(reasonSel, String(reasonValue)); await forceUpdate(await reasonSel.elementHandle());
await triggerEvents(page, reasonSel);
await page.waitForTimeout(500);
// 4. COMENTARIO // 4. COMENTARIO
if (comment) { if (comment) {
const commentSel = 'textarea[formcontrolname="comment"]'; const commentBox = page.locator('textarea[formcontrolname="comment"]');
await page.fill(commentSel, comment); await commentBox.fill(comment);
await triggerEvents(page, commentSel); await forceUpdate(await commentBox.elementHandle());
} }
// 5. FECHA // 5. FECHA
if (dateStr) { if (dateStr) {
const dateSel = 'input[type="date"]'; const dateInput = page.locator('input[type="date"]');
await page.fill(dateSel, dateStr); await dateInput.fill(dateStr);
await triggerEvents(page, dateSel); await forceUpdate(await dateInput.elementHandle());
// Truco: hacer click fuera para validar // Click fuera para asegurar validación de fecha
await page.click('body'); await page.click('body');
} }
// 6. HORA // 6. HORA (El punto crítico)
if (timeStr) { if (timeStr) {
const secondsValue = timeToMultiValue(timeStr); const secondsValue = timeToMultiValue(timeStr); // ej: "28800" para 08:00
// Buscamos el select de hora. Puede ser el segundo select de la página. console.log(`🕒 Intentando poner hora: ${timeStr} (Valor interno: ${secondsValue})`);
// 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);
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); await page.waitForTimeout(2000);
// 7. GUARDAR - VERIFICACIÓN DE ESTADO DEL BOTÓN // 7. GUARDAR
const btnSelector = 'button.form-container-button-submit'; const btn = page.locator('button.form-container-button-submit');
const btn = page.locator(btnSelector);
// Comprobar si está habilitado // Verificación final antes de clickar
if (await btn.isDisabled()) { if (await btn.isDisabled()) {
console.log('⛔ El botón Guardar sigue deshabilitado. Intentando "despertar" el formulario...'); console.log('⛔ Botón deshabilitado. Intentando reactivación final...');
// Intentar hacer focus/blur en el comentario otra vez // A veces hacer click en el comentario ayuda a que Angular se entere
await page.focus('textarea[formcontrolname="comment"]'); await page.click('textarea[formcontrolname="comment"]');
await page.keyboard.press('Tab'); await page.keyboard.press('Tab');
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
if (await btn.isDisabled()) { 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(); await btn.click();
// 8. VERIFICAR RESULTADO // 8. VERIFICAR RESULTADO
// Esperamos a ver si sale mensaje de éxito o error await page.waitForTimeout(4000);
await page.waitForTimeout(3000);
// Buscamos mensajes en la pantalla // Captura de mensajes de éxito/error de la web
const message = await page.evaluate(() => { const message = await page.evaluate(() => {
// En tu HTML vi esta etiqueta: <encastrables-success-error-message> const err = document.querySelector('.form-container-error');
const msgEl = document.querySelector('encastrables-success-error-message'); const ok = document.querySelector('.form-container-success');
const errorEl = document.querySelector('.form-container-error'); const toast = document.querySelector('encastrables-success-error-message');
const successEl = document.querySelector('.form-container-success');
if (errorEl) return `ERROR WEB: ${errorEl.innerText}`; if (err && err.innerText) return `ERROR WEB: ${err.innerText}`;
if (successEl) return `EXITO WEB: ${successEl.innerText}`; if (ok && ok.innerText) return `EXITO WEB: ${ok.innerText}`;
if (msgEl && msgEl.innerText.trim().length > 0) return `MSG: ${msgEl.innerText}`; if (toast && toast.innerText) return `MSG: ${toast.innerText}`;
return null; return null;
}); });
if (message && message.includes('ERROR')) { console.log(` Estado final: ${message || 'Sin mensaje (Redirección correcta)'}`);
throw new Error(message);
}
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 return { success: true, finalUrl: page.url() };
const currentUrl = page.url();
return { success: true, finalUrl: currentUrl, webMessage: message };
} }
// --- WORKER LOOP --- // --- WORKER ---
async function claimJobById(db, jobId) { async function claimJobById(db, jobId) {
const ref = db.collection(CONFIG.QUEUE_COLLECTION).doc(jobId); const ref = db.collection(CONFIG.QUEUE_COLLECTION).doc(jobId);
return await db.runTransaction(async (tx) => { return await db.runTransaction(async (tx) => {
@ -249,7 +242,7 @@ function startWorker(db) {
db.collection(CONFIG.QUEUE_COLLECTION).where('status', '==', 'PENDING').onSnapshot(s => { db.collection(CONFIG.QUEUE_COLLECTION).where('status', '==', 'PENDING').onSnapshot(s => {
s.docChanges().forEach(c => { if(c.type==='added') { queue.push(c.doc.id); run(); } }); 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(); const db = initFirebase();