From 0f8b9e48983a875e449c407476716adcbd7a51b5 Mon Sep 17 00:00:00 2001 From: marsalva Date: Sun, 8 Mar 2026 08:53:27 +0000 Subject: [PATCH] Actualizar server.js --- server.js | 102 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/server.js b/server.js index 19fb6c1..1ae7c3e 100644 --- a/server.js +++ b/server.js @@ -448,7 +448,7 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) { const ahora = new Date(); const fechaHoyTexto = ahora.toLocaleDateString('es-ES', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); - // 🧠 MEMORIA: Traemos los 8 ÚLTIMOS mensajes + // 🧠 MEMORIA: Traemos los últimos 8 mensajes const historyQ = await pool.query(` SELECT sender_role, message FROM service_communications @@ -463,19 +463,14 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) { const esPrimerMensaje = historialChat.length === 0; - // 🗓️ LECTURA DE AGENDA ANTI-SOLAPAMIENTOS - let agendaOcupadaTexto = "El técnico tiene la agenda libre. Puedes sugerir cualquier hora dentro del horario laboral."; + // 🗓️ LECTURA DE AGENDA (Solo la usamos si realmente necesita agendar) + let agendaOcupadaTexto = "El técnico tiene la agenda libre en horario laboral."; if (datosExpediente.worker_id) { const agendaQ = await pool.query(` - SELECT raw_data->>'scheduled_date' as date, - raw_data->>'scheduled_time' as time, - raw_data->>'Población' as pob + SELECT raw_data->>'scheduled_date' as date, raw_data->>'scheduled_time' as time, raw_data->>'Población' as pob FROM scraped_services - WHERE assigned_to = $1 - AND raw_data->>'scheduled_date' >= CURRENT_DATE::text - AND status != 'archived' - AND id != $2 - ORDER BY date ASC, time ASC + WHERE assigned_to = $1 AND raw_data->>'scheduled_date' >= CURRENT_DATE::text + AND status != 'archived' AND id != $2 ORDER BY date ASC, time ASC `, [datosExpediente.worker_id, datosExpediente.dbId]); if (agendaQ.rowCount > 0) { @@ -486,48 +481,71 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) { ocupaciones[r.date].push(`${r.time} (en ${r.pob || 'otra zona'})`); } }); - const lineas = Object.keys(ocupaciones).map(d => `- Día ${d}: Ocupado a las ${ocupaciones[d].join(", ")}`); if(lineas.length > 0) agendaOcupadaTexto = lineas.join("\n "); } } - // 🚦 ESTADO PENDIENTE + // 🚦 ANÁLISIS DEL ESTADO (LA MAGIA EMPIEZA AQUÍ) const hayCitaPendiente = datosExpediente.appointment_status === 'pending' && datosExpediente.cita_pendiente_fecha; - const textoCitaPendiente = hayCitaPendiente - ? `SÍ. El cliente solicitó cita para el ${datosExpediente.cita_pendiente_fecha} a las ${datosExpediente.cita_pendiente_hora || ''}. Está PENDIENTE de que el técnico la apruebe.` - : `NO.`; + const tieneCitaConfirmada = datosExpediente.cita && datosExpediente.cita !== 'Ninguna'; + const esUrgencia = datosExpediente.is_urgent; + // 🎯 INSTRUCCIÓN DINÁMICA: Le ponemos la camisa de fuerza a la IA + let directivaEstricta = ""; + + if (esUrgencia) { + directivaEstricta = ` + 🛑 ESTADO ACTUAL: SERVICIO DE URGENCIA. + TU ÚNICO OBJETIVO: Tranquilizar al cliente. Dile que al ser una urgencia, el técnico está avisado y contactará/acudirá lo antes posible. + PROHIBICIÓN ABSOLUTA: Bajo ningún concepto intentes dar cita, ni preguntes por fechas, ni propongas horarios. + `; + } else if (hayCitaPendiente) { + directivaEstricta = ` + 🛑 ESTADO ACTUAL: CITA PENDIENTE DE APROBACIÓN POR EL TÉCNICO. + Datos de la propuesta actual: Día ${datosExpediente.cita_pendiente_fecha} a las ${datosExpediente.cita_pendiente_hora}. + TU ÚNICO OBJETIVO: Informar al cliente que ya le hemos pasado su propuesta al técnico y que estamos esperando a que él la valide en su aplicación. + PROHIBICIÓN ABSOLUTA: No agendes de nuevo. No ofrezcas más huecos. Si el cliente dice "vale", despídete amablemente y fin. + `; + } else if (tieneCitaConfirmada) { + directivaEstricta = ` + 🛑 ESTADO ACTUAL: CITA 100% CONFIRMADA. + Fecha de la cita: ${datosExpediente.cita}. + TU ÚNICO OBJETIVO: Resolver cualquier duda del cliente y recordarle que su cita es el ${datosExpediente.cita}. + PROHIBICIÓN ABSOLUTA: No intentes agendar. No menciones huecos libres. El trabajo ya está programado. + `; + } else { + directivaEstricta = ` + 🟢 ESTADO ACTUAL: PENDIENTE DE AGENDAR CITA. + TU OBJETIVO: Acordar una fecha y hora con el cliente. + + REGLAS DE AGENDAMIENTO: + 1. OFRECE HUECOS: Mira la "AGENDA DEL TÉCNICO". Nunca ofrezcas horas ocupadas (deja 1 hora de margen). + 2. RUTAS INTELIGENTES: El cliente está en ${datosExpediente.poblacion || 'su domicilio'}. Si el técnico ya va a esa población un día concreto, ofrécele ese día para aprovechar el viaje. + 3. FINES DE SEMANA CERRADO. Ofrece solo de L-V. + 4. CÓDIGO DE CIERRE (VITAL): Si el cliente ACEPTA una propuesta (ej: tú dices "el lunes a las 10" y él dice "sí, perfecto"), confírmale que le pasas la nota al técnico y AÑADE AL FINAL DE TU MENSAJE: [PROPUESTA:YYYY-MM-DD HH:mm] + `; + } + + // 🧠 EL PROMPT MAESTRO const promptSistema = ` - Eres el asistente humano de "${empresaNombre}". Hablas de tú, de forma muy natural, corta y directa por WhatsApp. + Eres el coordinador humano de "${empresaNombre}". Hablas de tú, de forma muy natural, empática y al grano por WhatsApp. Eres resolutivo y no suenas como un contestador automático. - --- CONTEXTO Y HORARIOS --- - - Hoy es: ${fechaHoyTexto}. - - Horario L-V: Mañanas (${horarios.m_start}-${horarios.m_end}) y Tardes (${horarios.a_start}-${horarios.a_end}). - - ⛔ FIN DE SEMANA CERRADO. Ofrece solo de Lunes a Viernes. + --- CONTEXTO BÁSICO --- + - Hoy es: ${fechaHoyTexto}. (Año 2026). + - Horario de la empresa: Lunes a Viernes de ${horarios.m_start} a ${horarios.m_end} y de ${horarios.a_start} a ${horarios.a_end}. - --- DATOS DEL AVISO #${datosExpediente.ref} --- - - Estado: ${datosExpediente.estado} - - Población: ${datosExpediente.poblacion} - - Urgencia: ${datosExpediente.is_urgent ? 'SÍ (Prioridad)' : 'No'} - - Cita Confirmada: ${datosExpediente.cita || 'Ninguna'} - - Cita Solicitada PENDIENTE: ${textoCitaPendiente} - - --- ⚠️ CALENDARIO DEL TÉCNICO Y RUTAS --- + --- AGENDA DEL TÉCNICO ASIGNADO --- ${agendaOcupadaTexto} - - REGLA 1 (ANTI-SOLAPAMIENTO): NUNCA sugieras ni aceptes una hora que ya esté ocupada en la lista anterior. Deja margen de al menos 1 hora entre citas. - - REGLA 2 (RUTAS): El cliente actual está en ${datosExpediente.poblacion}. Si el técnico ya va a ${datosExpediente.poblacion} un día concreto, PRIORIZA ofrecerle ese mismo día (en una hora libre) para aprovechar el viaje. - --- 🛠️ MANUAL DE ACTUACIÓN (SIGUE EL ORDEN) --- - 1. 🚨 SI ES URGENCIA: No agendes. Di que al ser urgente, el técnico contactará y acudirá lo antes posible. - 2. ⏳ SI TIENE CITA PENDIENTE: Dile que su propuesta ya ha sido enviada al técnico y que le avisaréis en cuanto él la confirme en su agenda. NO busques otra fecha. - 3. ✅ SI TIENE CITA CONFIRMADA: Recuérdale cuándo es, no agendes otra. - 4. 📅 PARA AGENDAR (Si no aplica lo anterior): - - Sugiere un hueco libre (usando la Regla 2 de Rutas si es posible). - - ⚠️ CIERRE: Si el cliente ACEPTA, dile que la anotas para pasársela al técnico y que cuando él la vea, se confirmará. - - ⚠️ OBLIGATORIO: En el momento del cierre, añade AL FINAL tu respuesta el código oculto: [PROPUESTA:YYYY-MM-DD HH:mm] - 5. SALUDO: ${esPrimerMensaje ? 'Preséntate diciendo que eres de ' + empresaNombre + ' y da el nº de aviso.' : 'NO TE PRESENTES. Ya habéis hablado, ve al grano.'} - 6. FORMATO: Máximo 2 frases. Corto y al pie. + --- 🎯 DIRECTIVA ESTRICTA PARA ESTE MENSAJE --- + ${directivaEstricta} + + --- REGLAS DE ORO DE COMUNICACIÓN --- + 1. Máximo 2 frases. Los mensajes de WhatsApp deben ser cortos. + 2. Lee el historial de la conversación. Si el cliente solo responde "Ok" o "Gracias", dile "De nada, aquí estamos para lo que necesites" y cierra la charla. No le des la chapa. + 3. NO TE PRESENTES si ya estáis conversando. + ${esPrimerMensaje ? '4. Como es el primer mensaje del chat, preséntate brevemente diciendo que eres de ' + empresaNombre + ' y da su número de aviso (#' + datosExpediente.ref + ').' : ''} `; const mensajesParaIA = [ @@ -539,7 +557,7 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) { const completion = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: mensajesParaIA, - temperature: 0.2, // Creatividad baja para obedecer reglas y horarios + temperature: 0.1, // Congelado. No queremos que invente, queremos que obedezca. }); return completion.choices[0].message.content;