Actualizar server.js
This commit is contained in:
146
server.js
146
server.js
@@ -904,38 +904,23 @@ async function registrarMovimiento(serviceId, userId, action, details) {
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 🧠 CEREBRO IA (WHATSAPP) CON MODO DEBUG
|
||||
// 🧠 CEREBRO IA (WHATSAPP)
|
||||
// ==========================================
|
||||
async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
||||
// 🎚️ INTERRUPTOR DE LOGS (Cambia a false para apagar los mensajes en la consola)
|
||||
const DEBUG_IA = true;
|
||||
|
||||
if (DEBUG_IA) {
|
||||
console.log("\n=======================================================");
|
||||
console.log(`🤖 [DEBUG IA] INICIANDO CEREBRO PARA EXP: #${datosExpediente.ref}`);
|
||||
console.log(`💬 Mensaje del cliente: "${mensajeCliente}"`);
|
||||
console.log("=======================================================\n");
|
||||
}
|
||||
|
||||
try {
|
||||
const userQ = await pool.query(
|
||||
"SELECT wa_settings, full_name, portal_settings FROM users WHERE id=$1",
|
||||
[ownerId]
|
||||
);
|
||||
|
||||
if (userQ.rowCount === 0) return null; // 🛡️ Protección de seguridad
|
||||
|
||||
const userData = userQ.rows[0];
|
||||
const settings = userData.wa_settings || {};
|
||||
const settings = userData?.wa_settings || {};
|
||||
const instruccionesExtra = settings.ai_custom_prompt || "";
|
||||
const empresaNombre = userData.full_name || "nuestra empresa";
|
||||
const empresaNombre = userData?.full_name || "nuestra empresa";
|
||||
|
||||
if (!settings.wa_ai_enabled) {
|
||||
if (DEBUG_IA) console.log("❌ [DEBUG IA] La IA está apagada en los ajustes del usuario. Abortando.");
|
||||
return null;
|
||||
}
|
||||
if (!settings.wa_ai_enabled) return null;
|
||||
|
||||
const pSettings = userData.portal_settings || {};
|
||||
const pSettings = userData?.portal_settings || {};
|
||||
const horarios = {
|
||||
m_start: pSettings.m_start || "09:00",
|
||||
m_end: pSettings.m_end || "14:00",
|
||||
@@ -951,7 +936,9 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
||||
day: "numeric"
|
||||
}).format(new Date());
|
||||
|
||||
// 👇 Historial del chat
|
||||
// 👇 IMPORTANTE:
|
||||
// Aquí leemos el historial REAL, ya incluyendo el mensaje actual del cliente
|
||||
// que el webhook guardará antes de llamar a OpenAI.
|
||||
const historyQ = await pool.query(`
|
||||
SELECT sender_role, message
|
||||
FROM service_communications
|
||||
@@ -968,9 +955,10 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
||||
content: row.message
|
||||
}));
|
||||
|
||||
// Si solo existe el mensaje actual del cliente, es primer mensaje
|
||||
const esPrimerMensaje = historialRows.length <= 1;
|
||||
|
||||
let agendaOcupadaTexto = "AGENDA LIBRE. Puedes proponer cualquier hora dentro del horario laboral.";
|
||||
let agendaOcupadaTexto = "✅ El técnico tiene la agenda libre en horario laboral.";
|
||||
|
||||
if (datosExpediente.worker_id) {
|
||||
const agendaQ = await pool.query(`
|
||||
@@ -978,6 +966,7 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
||||
COALESCE(NULLIF(raw_data->>'scheduled_date', ''), raw_data->>'requested_date') as date,
|
||||
COALESCE(NULLIF(raw_data->>'scheduled_time', ''), raw_data->>'requested_time') as time,
|
||||
raw_data->>'duration_minutes' as duration,
|
||||
COALESCE(raw_data->>'Población', raw_data->>'POBLACION-PROVINCIA') as pob,
|
||||
provider
|
||||
FROM scraped_services
|
||||
WHERE owner_id = $1
|
||||
@@ -1001,32 +990,33 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
||||
|
||||
let [h, m] = r.time.split(':').map(Number);
|
||||
let dur = parseInt(r.duration || 60, 10);
|
||||
|
||||
// 🚀 TRUCO: 45 min extra de desplazamiento
|
||||
let startMin = h * 60 + m;
|
||||
let endMin = startMin + dur + 45;
|
||||
|
||||
let endH = Math.floor(endMin / 60) % 24;
|
||||
let endM = endMin % 60;
|
||||
let endMin = (h * 60 + m) + dur;
|
||||
let endH = String(Math.floor(endMin / 60) % 24).padStart(2, '0');
|
||||
let endM = String(endMin % 60).padStart(2, '0');
|
||||
|
||||
let horasAfectadas = [];
|
||||
for(let i = h; i <= endH; i++) {
|
||||
horasAfectadas.push(`${String(i).padStart(2,'0')}:00`);
|
||||
horasAfectadas.push(`${String(i).padStart(2,'0')}:30`);
|
||||
}
|
||||
const tipo = r.provider === 'SYSTEM_BLOCK' ? 'BLOQUEO/AUSENCIA' : 'CITA';
|
||||
const lugar = r.pob || 'Otra zona';
|
||||
|
||||
ocupaciones[r.date].push(`De ${r.time} a ${String(endH).padStart(2,'0')}:${String(endM).padStart(2,'0')} (Afecta a: ${horasAfectadas.join(", ")})`);
|
||||
ocupaciones[r.date].push(`❌ OCUPADO de ${r.time} a ${endH}:${endM} (${tipo} en ${lugar})`);
|
||||
}
|
||||
});
|
||||
|
||||
const lineas = Object.keys(ocupaciones).sort().map(d => {
|
||||
const [y, m, day] = d.split('-').map(Number);
|
||||
const fechaHumana = new Date(y, m - 1, day, 12, 0, 0).toLocaleDateString('es-ES', { weekday: 'long', day: 'numeric', month: 'long' });
|
||||
return `FECHA: ${d} (${fechaHumana})\nBLOQUEOS:\n - ${ocupaciones[d].join("\n - ")}`;
|
||||
const fechaHumana = new Date(y, m - 1, day, 12, 0, 0).toLocaleDateString('es-ES', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long'
|
||||
});
|
||||
return `- Día ${fechaHumana} (${d}):\n * ${ocupaciones[d].join("\n * ")}`;
|
||||
});
|
||||
|
||||
if (lineas.length > 0) {
|
||||
agendaOcupadaTexto = lineas.join("\n\n");
|
||||
agendaOcupadaTexto =
|
||||
"⚠️ ¡ATENCIÓN! LA SIGUIENTE LISTA SON LOS HORARIOS QUE YA ESTÁN OCUPADOS Y NO PUEDES OFRECER:\n" +
|
||||
lineas.join("\n") +
|
||||
"\n\n👉 INSTRUCCIÓN VITAL: Revisa bien la lista de arriba. Tienes que proponerle al cliente CUALQUIER OTRA HORA que esté totalmente libre y no pise esos tramos." +
|
||||
"\n🚨 REGLA LOGÍSTICA: Si la zona de la avería actual no es la misma que la de la cita anterior o posterior, DEBES dejar al menos 45-60 minutos libres para el viaje.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1070,53 +1060,53 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
||||
let directivaEstricta = "";
|
||||
|
||||
if (esEstadoFinal) {
|
||||
directivaEstricta = `ESTADO: CERRADO. Informa al cliente que el servicio está finalizado. NO AGENDAR.`;
|
||||
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) {
|
||||
directivaEstricta = `ESTADO: SIN TÉCNICO. Solo informa que hemos recibido el aviso y estamos buscando técnico. PROHIBIDO AGENDAR.`;
|
||||
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.`;
|
||||
} else if (citaYaPaso) {
|
||||
directivaEstricta = `ESTADO: CITA PASADA. Informa que estamos tramitando su avería. NO AGENDAR.`;
|
||||
directivaEstricta = `🛑 ESTADO ACTUAL: LA CITA YA PASÓ (${datosExpediente.cita}). Informa que estamos tramitando su avería (${datosExpediente.averia}). NO AGENDES NADA.`;
|
||||
} else if (esUrgencia) {
|
||||
directivaEstricta = `ESTADO: URGENCIA. Tranquiliza al cliente, 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) {
|
||||
directivaEstricta = `ESTADO: CITA PENDIENTE DE APROBACIÓN el día ${datosExpediente.cita_pendiente_fecha} ${tramoPendiente}. Informa que esperamos confirmación del técnico. Si el cliente quiere cambiar, ofrece otra hora.`;
|
||||
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.`;
|
||||
} else if (tieneCitaConfirmada) {
|
||||
directivaEstricta = `ESTADO: CITA CONFIRMADA el ${datosExpediente.cita} ${tramoConfirmado}. Recuerda la cita. Si el cliente quiere cambiar, ofrece otra hora.`;
|
||||
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.`;
|
||||
} else {
|
||||
directivaEstricta = `ESTADO: PENDIENTE DE AGENDAR. Acuerda fecha y hora para reparar su avería (${datosExpediente.averia}). Nunca ofrezcas horas ocupadas.`;
|
||||
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.`;
|
||||
}
|
||||
|
||||
const promptSistema = \`
|
||||
Eres el coordinador de la empresa "\${empresaNombre}". Hablas de tú, de forma amable y directa por WhatsApp (máximo 2 frases).
|
||||
const promptSistema = `
|
||||
Eres el coordinador humano de "${empresaNombre}". Hablas de tú, de forma muy natural, empática y con buen tono por WhatsApp.
|
||||
|
||||
<DATOS_ACTUALES>
|
||||
- Fecha de Hoy: \${fechaHoyTexto}
|
||||
- Horario de Trabajo: L-V de \${horarios.m_start} a \${horarios.m_end} y de \${horarios.a_start} a \${horarios.a_end}. NUNCA propongas horas fuera de aquí ni en fin de semana.
|
||||
</DATOS_ACTUALES>
|
||||
--- 📋 CONTEXTO BÁSICO ---
|
||||
- Hoy es: ${fechaHoyTexto}.
|
||||
- Horario de la empresa: L-V de ${horarios.m_start} a ${horarios.m_end} y de ${horarios.a_start} a ${horarios.a_end}.
|
||||
- ⛔ REGLA DE ORO DEL HORARIO: NUNCA propongas horas fuera de ese horario ni pises el tramo de comer. Fines de semana prohibidos salvo urgencias.
|
||||
- Localidad del cliente actual: ${datosExpediente.poblacion || 'Localidad no especificada'}.
|
||||
|
||||
<AGENDA_PROHIBIDA>
|
||||
\${agendaOcupadaTexto}
|
||||
</AGENDA_PROHIBIDA>
|
||||
--- 📅 REVISIÓN DE AGENDA (EVITAR SOLAPES) ---
|
||||
${agendaOcupadaTexto}
|
||||
|
||||
<OBJETIVO>
|
||||
\${directivaEstricta}
|
||||
</OBJETIVO>
|
||||
--- 🎯 DIRECTIVA ESTRICTA PARA ESTE MENSAJE ---
|
||||
${directivaEstricta}
|
||||
|
||||
<REGLAS_ESTRICTAS>
|
||||
1. LEE LA AGENDA PROHIBIDA: Si el cliente propone una hora que coincide o choca con las horas prohibidas, DÍSELO ("A esa hora el técnico está ocupado") y propón una alternativa libre.
|
||||
2. NUNCA propongas una fecha y hora que esté dentro de la AGENDA PROHIBIDA.
|
||||
3. Si llegáis a un acuerdo y tú o el cliente proponéis una hora LIBRE, añade al FINAL de tu respuesta exactamente: [PROPUESTA:YYYY-MM-DD HH:mm]
|
||||
4. JAMÁS uses la etiqueta [PROPUESTA] si la hora solicitada choca con la AGENDA PROHIBIDA.
|
||||
5. Nunca digas "¿En qué te puedo ayudar?". Ve al grano.
|
||||
\${esPrimerMensaje ? '6. Es tu primer mensaje: saluda, di quién eres y menciona el aviso (#' + datosExpediente.ref + ').' : ''}
|
||||
\${instruccionesExtra ? '7. REGLA EXTRA: ' + instruccionesExtra : ''}
|
||||
</REGLAS_ESTRICTAS>
|
||||
\`;
|
||||
--- ⚡ REGLA CRÍTICA DE AGENDA (COMANDO SECRETO) ---
|
||||
Si (y solo si) has propuesto un hueco y el cliente ACEPTA FIRMEMENTE, DEBES añadir AL FINAL ABSOLUTO de tu respuesta este texto literal:
|
||||
[PROPUESTA:YYYY-MM-DD HH:mm]
|
||||
|
||||
if (DEBUG_IA) {
|
||||
console.log("📝 [DEBUG IA] PROMPT GENERADO Y ENVIADO A OPENAI:");
|
||||
console.log("\x1b[36m%s\x1b[0m", promptSistema); // Se imprime en color cian para verlo mejor
|
||||
console.log("-------------------------------------------------------\n");
|
||||
}
|
||||
Ejemplo:
|
||||
"Perfecto, le paso la nota al técnico para que te confirme el miércoles entre las 10:00 y las 11:00 aprox. ¡Te decimos algo pronto! [PROPUESTA:2026-03-25 10:00]"
|
||||
|
||||
⛔ PROHIBICIÓN: NUNCA le digas "te agendo" ni "cita confirmada". El cliente debe saber que dependemos del técnico. NUNCA menciones las palabras "código" o "etiqueta".
|
||||
|
||||
--- ⚙️ REGLAS DE COMUNICACIÓN ---
|
||||
1. MÁXIMO 2 FRASES. Mensajes cortos y directos.
|
||||
2. NUNCA uses fechas frías si puedes decir "el martes". NUNCA des una hora exacta si puedes decir "entre las 10:00 y las 11:00 aprox".
|
||||
3. NO TE PRESENTES si ya habéis intercambiado mensajes.
|
||||
4. ⛔ MULETILLAS PROHIBIDAS: NUNCA digas "¿En qué más te puedo ayudar?".
|
||||
${esPrimerMensaje ? `5. Primer mensaje: preséntate y menciona el aviso (#${datosExpediente.ref}).` : ''}
|
||||
${instruccionesExtra ? `6. Instrucción extra de la empresa: ${instruccionesExtra}` : ''}
|
||||
`;
|
||||
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: OPENAI_MODEL || "gpt-4o-mini",
|
||||
@@ -1124,18 +1114,10 @@ Eres el coordinador de la empresa "\${empresaNombre}". Hablas de tú, de forma a
|
||||
{ role: "system", content: promptSistema },
|
||||
...historialChat
|
||||
],
|
||||
temperature: 0.0 // 🚀 Máxima obediencia
|
||||
temperature: 0.1
|
||||
});
|
||||
|
||||
const respuestaFinal = completion.choices?.[0]?.message?.content || null;
|
||||
|
||||
if (DEBUG_IA) {
|
||||
console.log("💡 [DEBUG IA] RESPUESTA RECIBIDA DE OPENAI:");
|
||||
console.log("\x1b[32m%s\x1b[0m", respuestaFinal); // Se imprime en color verde
|
||||
console.log("=======================================================\n");
|
||||
}
|
||||
|
||||
return respuestaFinal;
|
||||
return completion.choices?.[0]?.message?.content || null;
|
||||
} catch (e) {
|
||||
console.error("❌ Error OpenAI:", e.message);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user