Actualizar server.js

This commit is contained in:
2026-03-03 22:33:13 +00:00
parent d2ab1c7ecf
commit 5237a6065a

138
server.js
View File

@@ -1199,8 +1199,9 @@ app.post("/providers/automate/:id", authMiddleware, async (req, res) => {
} }
}); });
// AÑADIDO Y BLINDADO: CAPTURA COMPLETA, REGLA WA MANUAL, DESASIGNACIÓN Y BORRADO DE FECHA // ==========================================
// 📝 ACTUALIZACIÓN DE EXPEDIENTES (Y ESCUDO ANTI-ARCHIVO)
// ==========================================
app.put('/providers/scraped/:id', authMiddleware, async (req, res) => { app.put('/providers/scraped/:id', authMiddleware, async (req, res) => {
const { id } = req.params; const { id } = req.params;
let { automation_status, status, name, phone, address, cp, description, guild_id, assigned_to, assigned_to_name, internal_notes, client_notes, is_urgent, ...extra } = req.body; let { automation_status, status, name, phone, address, cp, description, guild_id, assigned_to, assigned_to_name, internal_notes, client_notes, is_urgent, ...extra } = req.body;
@@ -1211,7 +1212,8 @@ app.put('/providers/scraped/:id', authMiddleware, async (req, res) => {
await pool.query(`UPDATE scraped_services SET automation_status = $1 WHERE id = $2 AND owner_id = $3`, [automation_status, id, req.user.accountId]); await pool.query(`UPDATE scraped_services SET automation_status = $1 WHERE id = $2 AND owner_id = $3`, [automation_status, id, req.user.accountId]);
return res.json({ ok: true }); return res.json({ ok: true });
} }
// ESCUDO ANTI-ARCHIVO Y LOG AUTOMÁTICO
// 2. ESCUDO ANTI-ARCHIVO Y LOG AUTOMÁTICO (CON WA AL OPERARIO)
if (status === 'archived') { if (status === 'archived') {
const checkQ = await pool.query(` const checkQ = await pool.query(`
SELECT raw_data, assigned_to, service_ref, SELECT raw_data, assigned_to, service_ref,
@@ -1226,132 +1228,116 @@ app.put('/providers/scraped/:id', authMiddleware, async (req, res) => {
let raw = row.raw_data || {}; let raw = row.raw_data || {};
const serviceRef = row.service_ref || raw["Referencia"] || id; const serviceRef = row.service_ref || raw["Referencia"] || id;
// SI tiene trabajador asignado Y NO está en un estado final operativo (Finalizado/Anulado) // SI tiene trabajador asignado Y NO está en un estado final operativo
if (hasWorker && !isFinal) { if (hasWorker && !isFinal) {
// Solo guardamos el aviso y alertamos si no lo habíamos hecho ya antes
if (!raw.cerrado_proveedor) { if (!raw.cerrado_proveedor) {
raw.cerrado_proveedor = true; raw.cerrado_proveedor = true;
await pool.query(`UPDATE scraped_services SET raw_data = $1 WHERE id = $2 AND owner_id = $3`, [JSON.stringify(raw), id, req.user.accountId]); await pool.query(`UPDATE scraped_services SET raw_data = $1 WHERE id = $2 AND owner_id = $3`, [JSON.stringify(raw), id, req.user.accountId]);
// GUARDAMOS EN EL LOG EL INTENTO DE CIERRE // LOG: INTENTO DE CIERRE
await pool.query( await pool.query(
"INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)", "INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)",
[id, 'Sistema (Robot)', 'Intento de Cierre', 'La compañía ha cerrado el aviso, pero se mantiene vivo en IntegraRepara.'] [id, 'Sistema (Robot)', 'Intento de Cierre', 'La compañía ha cerrado el aviso, pero se mantiene vivo en IntegraRepara.']
); );
// --- NUEVA ALERTA URGENTE AL OPERARIO POR WHATSAPP --- // ALERTA URGENTE AL OPERARIO POR WHATSAPP
const workerQ = await pool.query("SELECT full_name, phone FROM users WHERE id=$1", [row.assigned_to]); const workerQ = await pool.query("SELECT full_name, phone FROM users WHERE id=$1", [row.assigned_to]);
if (workerQ.rowCount > 0) { if (workerQ.rowCount > 0) {
const w = workerQ.rows[0]; const w = workerQ.rows[0];
const msg = `🚨 *¡ALERTA URGENTE!* 🚨\n\nHola ${w.full_name}, la compañía aseguradora acaba de *CERRAR/ANULAR* el expediente *#${serviceRef}* en su sistema.\n\n⚠️ *NO ACUDAS NI REALICES EL TRABAJO* si no lo has hecho ya, porque no se va a cobrar.\n\nPor favor, contacta con la oficina para aclarar este aviso.`; const msg = `🚨 *¡ALERTA URGENTE!* 🚨\n\nHola ${w.full_name}, la compañía aseguradora acaba de *CERRAR/ANULAR* el expediente *#${serviceRef}* en su sistema.\n\n⚠️ *NO ACUDAS NI REALICES EL TRABAJO* si no lo has hecho ya, porque no se va a cobrar.\n\nPor favor, contacta con la oficina.`;
sendWhatsAppAuto(w.phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error); sendWhatsAppAuto(w.phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error);
// Guardamos en el log que le hemos avisado
await pool.query( await pool.query(
"INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)", "INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)",
[id, 'Sistema (Robot)', 'Alerta de Cancelación', `WhatsApp de emergencia enviado a ${w.full_name} avisando de la anulación del proveedor.`] [id, 'Sistema (Robot)', 'Alerta de Cancelación', `WhatsApp de emergencia enviado a ${w.full_name}.`]
); );
} }
// -----------------------------------------------------
} }
return res.json({ ok: true, note: "Protegido de archivo automático y operario alertado" }); return res.json({ ok: true, note: "Protegido de archivo automático y operario alertado" });
} }
} }
// Si no tiene trabajador, o el técnico YA lo había finalizado, lo archivamos sin problema // Si no tiene trabajador, o ya está finalizado
await pool.query(`UPDATE scraped_services SET status = 'archived', automation_status = 'manual' WHERE id = $1 AND owner_id = $2`, [id, req.user.accountId]); await pool.query(`UPDATE scraped_services SET status = 'archived', automation_status = 'manual' WHERE id = $1 AND owner_id = $2`, [id, req.user.accountId]);
// GUARDAMOS EN EL LOG LA BAJA DEFINITIVA
await pool.query( await pool.query(
"INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)", "INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)",
[id, 'Sistema', 'Archivado', 'El expediente se ha cerrado definitivamente y movido al histórico.'] [id, 'Sistema', 'Archivado', 'El expediente se ha cerrado definitivamente y movido al histórico.']
); );
return res.json({ ok: true });
}
return res.json({ ok: true, note: "Protegido de archivo automático" });
}
}
// Si no tiene trabajador, o el técnico YA lo había finalizado, lo archivamos sin problema
await pool.query(`UPDATE scraped_services SET status = 'archived', automation_status = 'manual' WHERE id = $1 AND owner_id = $2`, [id, req.user.accountId]);
// GUARDAMOS EN EL LOG
await pool.query(
"INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)",
[id, 'Sistema', 'Archivado', 'El expediente se ha cerrado definitivamente y movido al histórico.']
);
return res.json({ ok: true }); return res.json({ ok: true });
} }
// 1. Buscamos los datos actuales (Asegurando traer 'is_urgent') // 3. ACTUALIZACIÓN MANUAL NORMAL DE LA FICHA
const current = await pool.query('SELECT raw_data, assigned_to, status, is_urgent FROM scraped_services WHERE id = $1 AND owner_id = $2', [id, req.user.accountId]); const current = await pool.query('SELECT raw_data, assigned_to, status, is_urgent FROM scraped_services WHERE id = $1 AND owner_id = $2', [id, req.user.accountId]);
if (current.rows.length === 0) return res.status(404).json({ error: 'No encontrado' }); if (current.rows.length === 0) return res.status(404).json({ error: 'No encontrado' });
let oldStatus = current.rows[0].raw_data.status_operativo || null; let rawActual = current.rows[0].raw_data || {};
let oldStatus = rawActual.status_operativo || null;
let newStatus = extra.status_operativo || oldStatus; let newStatus = extra.status_operativo || oldStatus;
if (newStatus === "") newStatus = null; if (newStatus === "") newStatus = null;
const oldWorkerId = current.rows[0].assigned_to || rawActual.assigned_to;
let finalAssignedTo = assigned_to !== undefined ? (assigned_to === "" ? null : assigned_to) : oldWorkerId;
// --- AVISO AL OPERARIO (ASIGNACIÓN NUEVA / DESASIGNACIÓN) ---
if (finalAssignedTo !== oldWorkerId) {
if (finalAssignedTo) {
const workerQ = await pool.query("SELECT full_name, phone FROM users WHERE id=$1", [finalAssignedTo]);
if (workerQ.rowCount > 0) {
const w = workerQ.rows[0];
const ref = rawActual.service_ref || rawActual["Referencia"] || id;
const dir = address || rawActual["Dirección"] || "Ver ficha";
const msg = `🛠️ *NUEVO SERVICIO ASIGNADO*\n\nHola ${w.full_name}, se te ha asignado el expediente *#${ref}*.\n📍 Población/Zona: ${cp || rawActual["Población"] || dir}\n\nRevisa tu panel de operario para agendar la cita.`;
sendWhatsAppAuto(w.phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error);
}
} else if (oldWorkerId && !finalAssignedTo) {
const workerQ = await pool.query("SELECT full_name, phone FROM users WHERE id=$1", [oldWorkerId]);
if (workerQ.rowCount > 0) {
const w = workerQ.rows[0];
const ref = rawActual.service_ref || rawActual["Referencia"] || id;
const msg = `⚠️ *AVISO DE DESASIGNACIÓN*\n\nHola ${w.full_name}, se te ha retirado el expediente *#${ref}*.\n\nYa no tienes que atender este servicio.`;
sendWhatsAppAuto(w.phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error);
}
}
}
// --- LÓGICA DE ESTADOS Y WHATSAPP AL CLIENTE ---
if (newStatus && newStatus !== oldStatus) { if (newStatus && newStatus !== oldStatus) {
const statusQ = await pool.query("SELECT name FROM service_statuses WHERE id=$1", [newStatus]); const statusQ = await pool.query("SELECT name FROM service_statuses WHERE id=$1", [newStatus]);
const stName = (statusQ.rows[0]?.name || "").toLowerCase(); const stName = (statusQ.rows[0]?.name || "").toLowerCase();
// --- NUEVA REGLA: BORRADO DE FECHA SI RETROCEDE O SE ANULA --- if ((stName.includes('pendiente') && !stName.includes('cita')) || stName.includes('desasignado') || stName.includes('asignado') || stName.includes('anulado') || stName.includes('esperando')) {
// EXCEPCIÓN: No borrar si es "Pendiente de Cita" o si se está enviando una fecha nueva en este momento
if (
(stName.includes('pendiente') && !stName.includes('cita')) ||
stName.includes('desasignado') ||
stName.includes('asignado') ||
stName.includes('anulado') ||
stName.includes('esperando')
) {
// Solo vaciamos la fecha si NO viene una fecha nueva en la misma petición
if (!extra.scheduled_date) { if (!extra.scheduled_date) {
extra.scheduled_date = ""; extra.scheduled_date = "";
extra.scheduled_time = ""; extra.scheduled_time = "";
} }
} }
if (stName.includes('asignado')) { if (stName.includes('asignado') && finalAssignedTo) {
const waEnviadoExito = await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_assigned'); const waEnviadoExito = await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_assigned');
if (waEnviadoExito) { if (waEnviadoExito) {
const estadoEsperando = await pool.query("SELECT id FROM service_statuses WHERE owner_id=$1 AND name='Esperando al Cliente' LIMIT 1", [req.user.accountId]); const estadoEsperando = await pool.query("SELECT id FROM service_statuses WHERE owner_id=$1 AND name='Esperando al Cliente' LIMIT 1", [req.user.accountId]);
if(estadoEsperando.rowCount > 0) { if(estadoEsperando.rowCount > 0) {
newStatus = estadoEsperando.rows[0].id; newStatus = estadoEsperando.rows[0].id;
extra.status_operativo = newStatus; extra.status_operativo = newStatus;
await registrarMovimiento(id, req.user.sub, "Robot WA", "WhatsApp de asignación entregado. Estado cambiado a Esperando al Cliente.");
} }
} else {
await registrarMovimiento(id, req.user.sub, "Robot WA", "WhatsApp de asignación falló o está desactivado.");
} }
} else if (stName.includes('pendiente de asignar') || stName.includes('desasignado')) {
const oldWorkerId = current.rows[0].assigned_to || current.rows[0].raw_data.assigned_to;
if (oldWorkerId) {
const workerQ = await pool.query("SELECT full_name, phone FROM users WHERE id=$1", [oldWorkerId]);
if (workerQ.rowCount > 0) {
const w = workerQ.rows[0];
const ref = current.rows[0].raw_data.service_ref || current.rows[0].raw_data["Referencia"] || id;
const msg = `⚠️ *AVISO DE DESASIGNACIÓN*\n\nHola ${w.full_name}, se te ha retirado el expediente *#${ref}*.\n\nYa no tienes que atender este servicio.`;
sendWhatsAppAuto(w.phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error);
}
}
assigned_to = null;
assigned_to_name = null;
extra.assigned_to = null;
extra.assigned_to_name = null;
} }
} }
// 4. UNIFICAR DATOS FINALES
const updatedRawData = { const updatedRawData = {
...current.rows[0].raw_data, ...rawActual, ...extra,
...extra, "Nombre Cliente": name || rawActual["Nombre Cliente"],
"Nombre Cliente": name || current.rows[0].raw_data["Nombre Cliente"], "Teléfono": phone || rawActual["Teléfono"],
"Teléfono": phone || current.rows[0].raw_data["Teléfono"], "Dirección": address || rawActual["Dirección"],
"Dirección": address || current.rows[0].raw_data["Dirección"], "Código Postal": cp || rawActual["Código Postal"],
"Código Postal": cp || current.rows[0].raw_data["Código Postal"], "Descripción": description || rawActual["Descripción"],
"Descripción": description || current.rows[0].raw_data["Descripción"],
"guild_id": guild_id, "guild_id": guild_id,
"assigned_to": assigned_to, "assigned_to": finalAssignedTo,
"assigned_to_name": assigned_to_name, "assigned_to_name": assigned_to_name,
"internal_notes": internal_notes, "internal_notes": internal_notes,
"client_notes": client_notes, "client_notes": client_notes,
@@ -1359,29 +1345,15 @@ app.put('/providers/scraped/:id', authMiddleware, async (req, res) => {
"status_operativo": newStatus "status_operativo": newStatus
}; };
let finalAssignedTo = assigned_to;
if (finalAssignedTo === "" || finalAssignedTo === null) finalAssignedTo = null;
else if (finalAssignedTo === undefined) finalAssignedTo = current.rows[0].assigned_to;
let currentDbStatus = current.rows[0].status; let currentDbStatus = current.rows[0].status;
// 2. Comprobamos la urgencia AQUÍ AL FINAL (donde ya todo existe)
const finalIsUrgent = is_urgent !== undefined ? is_urgent : current.rows[0].is_urgent; const finalIsUrgent = is_urgent !== undefined ? is_urgent : current.rows[0].is_urgent;
// 3. Guardamos finalmente en base de datos
await pool.query( await pool.query(
`UPDATE scraped_services `UPDATE scraped_services SET raw_data = $1, status = $2, is_urgent = $3, assigned_to = $4 WHERE id = $5 AND owner_id = $6`,
SET raw_data = $1,
status = $2,
is_urgent = $3,
assigned_to = $4
WHERE id = $5 AND owner_id = $6`,
[JSON.stringify(updatedRawData), currentDbStatus, finalIsUrgent, finalAssignedTo, id, req.user.accountId] [JSON.stringify(updatedRawData), currentDbStatus, finalIsUrgent, finalAssignedTo, id, req.user.accountId]
); );
// --- INICIO TRAZABILIDAD --- await registrarMovimiento(id, req.user.sub, "Edición / Asignación", "Expediente actualizado o asignado.");
await registrarMovimiento(id, req.user.sub, "Edición Web", "El expediente ha sido actualizado manualmente desde el panel.");
// --- FIN TRAZABILIDAD ---
res.json({ ok: true }); res.json({ ok: true });
} catch (error) { } catch (error) {