diff --git a/server.js b/server.js index 4086d73..0b20703 100644 --- a/server.js +++ b/server.js @@ -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) => { 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; @@ -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]); 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') { const checkQ = await pool.query(` 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 || {}; 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) { - - // Solo guardamos el aviso y alertamos si no lo habíamos hecho ya antes if (!raw.cerrado_proveedor) { 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]); - // GUARDAMOS EN EL LOG EL INTENTO DE CIERRE + // LOG: INTENTO DE CIERRE await pool.query( "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.'] ); - // --- 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]); if (workerQ.rowCount > 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); - // Guardamos en el log que le hemos avisado await pool.query( "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" }); } } - // 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]); - - // GUARDAMOS EN EL LOG LA BAJA DEFINITIVA 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, 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 }); } - // 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]); 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; 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) { const statusQ = await pool.query("SELECT name FROM service_statuses WHERE id=$1", [newStatus]); const stName = (statusQ.rows[0]?.name || "").toLowerCase(); - // --- NUEVA REGLA: BORRADO DE FECHA SI RETROCEDE O SE ANULA --- - // 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 ((stName.includes('pendiente') && !stName.includes('cita')) || stName.includes('desasignado') || stName.includes('asignado') || stName.includes('anulado') || stName.includes('esperando')) { if (!extra.scheduled_date) { extra.scheduled_date = ""; extra.scheduled_time = ""; } } - if (stName.includes('asignado')) { + if (stName.includes('asignado') && finalAssignedTo) { const waEnviadoExito = await triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_assigned'); 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]); if(estadoEsperando.rowCount > 0) { 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 = { - ...current.rows[0].raw_data, - ...extra, - "Nombre Cliente": name || current.rows[0].raw_data["Nombre Cliente"], - "Teléfono": phone || current.rows[0].raw_data["Teléfono"], - "Dirección": address || current.rows[0].raw_data["Dirección"], - "Código Postal": cp || current.rows[0].raw_data["Código Postal"], - "Descripción": description || current.rows[0].raw_data["Descripción"], + ...rawActual, ...extra, + "Nombre Cliente": name || rawActual["Nombre Cliente"], + "Teléfono": phone || rawActual["Teléfono"], + "Dirección": address || rawActual["Dirección"], + "Código Postal": cp || rawActual["Código Postal"], + "Descripción": description || rawActual["Descripción"], "guild_id": guild_id, - "assigned_to": assigned_to, + "assigned_to": finalAssignedTo, "assigned_to_name": assigned_to_name, "internal_notes": internal_notes, "client_notes": client_notes, @@ -1359,29 +1345,15 @@ app.put('/providers/scraped/:id', authMiddleware, async (req, res) => { "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; - - // 2. Comprobamos la urgencia AQUÍ AL FINAL (donde ya todo existe) const finalIsUrgent = is_urgent !== undefined ? is_urgent : current.rows[0].is_urgent; - // 3. Guardamos finalmente en base de datos await pool.query( - `UPDATE scraped_services - SET raw_data = $1, - status = $2, - is_urgent = $3, - assigned_to = $4 - WHERE id = $5 AND owner_id = $6`, + `UPDATE scraped_services 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] ); - // --- INICIO TRAZABILIDAD --- - await registrarMovimiento(id, req.user.sub, "Edición Web", "El expediente ha sido actualizado manualmente desde el panel."); - // --- FIN TRAZABILIDAD --- + await registrarMovimiento(id, req.user.sub, "Edición / Asignación", "Expediente actualizado o asignado."); res.json({ ok: true }); } catch (error) {