Actualizar server.js

This commit is contained in:
2026-04-03 11:00:53 +00:00
parent 6b212d2e65
commit f42124cb45

View File

@@ -913,6 +913,8 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
[ownerId] [ownerId]
); );
if (userQ.rowCount === 0) return null;
const userData = userQ.rows[0]; const userData = userQ.rows[0];
const settings = userData?.wa_settings || {}; const settings = userData?.wa_settings || {};
const instruccionesExtra = settings.ai_custom_prompt || ""; const instruccionesExtra = settings.ai_custom_prompt || "";
@@ -936,9 +938,8 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
day: "numeric" day: "numeric"
}).format(new Date()); }).format(new Date());
// 👇 IMPORTANTE: // Historial REAL del chat (ya incluye el último mensaje del cliente
// Aquí leemos el historial REAL, ya incluyendo el mensaje actual del cliente // si el webhook lo ha guardado antes de llamar a esta función)
// que el webhook guardará antes de llamar a OpenAI.
const historyQ = await pool.query(` const historyQ = await pool.query(`
SELECT sender_role, message SELECT sender_role, message
FROM service_communications FROM service_communications
@@ -955,7 +956,7 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
content: row.message content: row.message
})); }));
// Si solo existe el mensaje actual del cliente, es primer mensaje // Si el historial solo tiene el mensaje actual del cliente, es primer contacto
const esPrimerMensaje = historialRows.length <= 1; const esPrimerMensaje = historialRows.length <= 1;
let agendaOcupadaTexto = "✅ El técnico tiene la agenda libre en horario laboral."; let agendaOcupadaTexto = "✅ El técnico tiene la agenda libre en horario laboral.";
@@ -988,11 +989,11 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
if (r.date && r.time && r.time.includes(':')) { if (r.date && r.time && r.time.includes(':')) {
if (!ocupaciones[r.date]) ocupaciones[r.date] = []; if (!ocupaciones[r.date]) ocupaciones[r.date] = [];
let [h, m] = r.time.split(':').map(Number); const [h, m] = r.time.split(':').map(Number);
let dur = parseInt(r.duration || 60, 10); const dur = parseInt(r.duration || 60, 10);
let endMin = (h * 60 + m) + dur; const endMin = (h * 60 + m) + dur;
let endH = String(Math.floor(endMin / 60) % 24).padStart(2, '0'); const endH = String(Math.floor(endMin / 60) % 24).padStart(2, '0');
let endM = String(endMin % 60).padStart(2, '0'); const endM = String(endMin % 60).padStart(2, '0');
const tipo = r.provider === 'SYSTEM_BLOCK' ? 'BLOQUEO/AUSENCIA' : 'CITA'; const tipo = r.provider === 'SYSTEM_BLOCK' ? 'BLOQUEO/AUSENCIA' : 'CITA';
const lugar = r.pob || 'Otra zona'; const lugar = r.pob || 'Otra zona';
@@ -1008,6 +1009,7 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
day: 'numeric', day: 'numeric',
month: 'long' month: 'long'
}); });
return `- Día ${fechaHumana} (${d}):\n * ${ocupaciones[d].join("\n * ")}`; return `- Día ${fechaHumana} (${d}):\n * ${ocupaciones[d].join("\n * ")}`;
}); });
@@ -1021,21 +1023,27 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
} }
} }
const hayCitaPendiente = datosExpediente.appointment_status === 'pending' && datosExpediente.cita_pendiente_fecha; const hayCitaPendiente =
const tieneCitaConfirmada = datosExpediente.cita && datosExpediente.cita !== 'Ninguna'; datosExpediente.appointment_status === 'pending' &&
datosExpediente.cita_pendiente_fecha;
const tieneCitaConfirmada =
datosExpediente.cita &&
datosExpediente.cita !== 'Ninguna';
const esUrgencia = datosExpediente.is_urgent; const esUrgencia = datosExpediente.is_urgent;
let tramoPendiente = datosExpediente.cita_pendiente_hora || ""; let tramoPendiente = datosExpediente.cita_pendiente_hora || "";
if (tramoPendiente && tramoPendiente.includes(":")) { if (tramoPendiente && tramoPendiente.includes(":")) {
let [h, m] = tramoPendiente.split(':'); const [h, m] = tramoPendiente.split(':');
let hEnd = String((parseInt(h) + 1) % 24).padStart(2, '0'); const hEnd = String((parseInt(h, 10) + 1) % 24).padStart(2, '0');
tramoPendiente = `entre las ${h}:${m} y las ${hEnd}:${m} aprox`; tramoPendiente = `entre las ${h}:${m} y las ${hEnd}:${m} aprox`;
} }
let tramoConfirmado = datosExpediente.hora_cita || ""; let tramoConfirmado = datosExpediente.hora_cita || "";
if (tramoConfirmado && tramoConfirmado.includes(":")) { if (tramoConfirmado && tramoConfirmado.includes(":")) {
let [h, m] = tramoConfirmado.split(':'); const [h, m] = tramoConfirmado.split(':');
let hEnd = String((parseInt(h) + 1) % 24).padStart(2, '0'); const hEnd = String((parseInt(h, 10) + 1) % 24).padStart(2, '0');
tramoConfirmado = `entre las ${h}:${m} y las ${hEnd}:${m} aprox`; tramoConfirmado = `entre las ${h}:${m} y las ${hEnd}:${m} aprox`;
} else { } else {
tramoConfirmado = 'una hora por confirmar'; tramoConfirmado = 'una hora por confirmar';
@@ -1049,7 +1057,9 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
if (citaTime < hoyTime) citaYaPaso = true; if (citaTime < hoyTime) citaYaPaso = true;
} }
const esEstadoFinal = datosExpediente.estado && ( const esEstadoFinal =
datosExpediente.estado &&
(
datosExpediente.estado.toLowerCase().includes('finalizado') || datosExpediente.estado.toLowerCase().includes('finalizado') ||
datosExpediente.estado.toLowerCase().includes('terminado') || datosExpediente.estado.toLowerCase().includes('terminado') ||
datosExpediente.estado.toLowerCase().includes('anulado') datosExpediente.estado.toLowerCase().includes('anulado')
@@ -1062,17 +1072,25 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
if (esEstadoFinal) { if (esEstadoFinal) {
directivaEstricta = `🛑 ESTADO ACTUAL: SERVICIO CERRADO. Informa al cliente que el servicio por su avería (${datosExpediente.averia}) está finalizado. NO AGENDES NADA.`; directivaEstricta = `🛑 ESTADO ACTUAL: SERVICIO CERRADO. Informa al cliente que el servicio por su avería (${datosExpediente.averia}) está finalizado. NO AGENDES NADA.`;
} else if (noTieneTecnico) { } else if (noTieneTecnico) {
directivaEstricta = `🛑 ESTADO ACTUAL: SIN TÉCNICO ASIGNADO.\nTU ÚNICO OBJETIVO: Informar al cliente que hemos recibido el aviso de su avería (${datosExpediente.averia}) y que estamos coordinando para asignarle un técnico en su zona.\n⛔ PROHIBICIÓN ABSOLUTA: NO ofrezcas citas, NO des horas, NO agendes nada hasta que se le asigne un técnico.`; directivaEstricta = `🛑 ESTADO ACTUAL: SIN TÉCNICO ASIGNADO.
TU ÚNICO OBJETIVO: Informar al cliente que hemos recibido el aviso de su avería (${datosExpediente.averia}) y que estamos coordinando para asignarle un técnico en su zona.
⛔ PROHIBICIÓN ABSOLUTA: NO ofrezcas citas, NO des horas, NO agendes nada hasta que se le asigne un técnico.`;
} else if (citaYaPaso) { } else if (citaYaPaso) {
directivaEstricta = `🛑 ESTADO ACTUAL: LA CITA YA PASÓ (${datosExpediente.cita}). Informa que estamos tramitando su avería (${datosExpediente.averia}). NO AGENDES NADA.`; directivaEstricta = `🛑 ESTADO ACTUAL: LA CITA YA PASÓ (${datosExpediente.cita}). Informa que estamos tramitando su avería (${datosExpediente.averia}). NO AGENDES NADA.`;
} else if (esUrgencia) { } else if (esUrgencia) {
directivaEstricta = `🛑 ESTADO ACTUAL: URGENCIA. Tranquiliza al cliente sobre su avería (${datosExpediente.averia}) y dile que el técnico está avisado. NO PROPONGAS HORAS.`; directivaEstricta = `🛑 ESTADO ACTUAL: URGENCIA. Tranquiliza al cliente sobre su avería (${datosExpediente.averia}) y dile que el técnico está avisado. NO PROPONGAS HORAS.`;
} else if (hayCitaPendiente) { } else if (hayCitaPendiente) {
directivaEstricta = `🛑 ESTADO ACTUAL: CITA PENDIENTE DE APROBACIÓN.\n📅 Propuesta actual: El día ${datosExpediente.cita_pendiente_fecha} ${tramoPendiente}.\nTU OBJETIVO: Informar que esperamos confirmación del técnico para reparar su avería (${datosExpediente.averia}).\n⚠️ EXCEPCIÓN: Si el cliente pide CAMBIAR o CANCELAR, ofrécele un hueco nuevo.`; directivaEstricta = `🛑 ESTADO ACTUAL: CITA PENDIENTE DE APROBACIÓN.
📅 Propuesta actual: El día ${datosExpediente.cita_pendiente_fecha} ${tramoPendiente}.
TU OBJETIVO: Informar que esperamos confirmación del técnico para reparar su avería (${datosExpediente.averia}).
⚠️ EXCEPCIÓN: Si el cliente pide CAMBIAR o CANCELAR, ofrécele un hueco nuevo.`;
} else if (tieneCitaConfirmada) { } else if (tieneCitaConfirmada) {
directivaEstricta = `🛑 ESTADO ACTUAL: CITA CONFIRMADA para el ${datosExpediente.cita} ${tramoConfirmado}. Recuerda la cita para su avería (${datosExpediente.averia}).\n⚠️ EXCEPCIÓN: Si el cliente pide CAMBIARLA o CANCELARLA, ofrécele un hueco nuevo.`; directivaEstricta = `🛑 ESTADO ACTUAL: CITA CONFIRMADA para el ${datosExpediente.cita} ${tramoConfirmado}. Recuerda la cita para su avería (${datosExpediente.averia}).
⚠️ EXCEPCIÓN: Si el cliente pide CAMBIARLA o CANCELARLA, ofrécele un hueco nuevo.`;
} else { } else {
directivaEstricta = `🟢 ESTADO ACTUAL: PENDIENTE DE AGENDAR CITA.\nTU OBJETIVO: Acordar fecha y hora para reparar su avería (${datosExpediente.averia}). NUNCA ofrezcas horas ocupadas. Fines de semana solo URGENCIAS.\n⚠️ MUY IMPORTANTE: Cuando el cliente elija un hueco, NO le digas que la cita está confirmada. Dile que le pasas la nota al técnico para que él lo valide.`; directivaEstricta = `🟢 ESTADO ACTUAL: PENDIENTE DE AGENDAR CITA.
TU OBJETIVO: Acordar fecha y hora para reparar su avería (${datosExpediente.averia}). NUNCA ofrezcas horas ocupadas. Fines de semana solo URGENCIAS.
⚠️ MUY IMPORTANTE: Cuando el cliente elija un hueco, NO le digas que la cita está confirmada. Dile que le pasas la nota al técnico para que él lo valide.`;
} }
const promptSistema = ` const promptSistema = `
@@ -1124,6 +1142,7 @@ ${instruccionesExtra ? `6. Instrucción extra de la empresa: ${instruccionesExtr
} }
} }
// ========================================== // ==========================================
// 📱 OTP PARA PORTAL DEL CLIENTE (ACCESO WEB) // 📱 OTP PARA PORTAL DEL CLIENTE (ACCESO WEB)
// ========================================== // ==========================================