Actualizar server.js
This commit is contained in:
66
server.js
66
server.js
@@ -895,12 +895,12 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
|||||||
if (agendaQ.rowCount > 0) {
|
if (agendaQ.rowCount > 0) {
|
||||||
const ocupaciones = {};
|
const ocupaciones = {};
|
||||||
agendaQ.rows.forEach(r => {
|
agendaQ.rows.forEach(r => {
|
||||||
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] = [];
|
||||||
|
|
||||||
// Calculamos la hora de fin exacta sumando la duración (Ej: 60, 120, 180 min)
|
// Calculamos la hora de fin exacta sumando la duración (Ej: 60, 120, 180 min)
|
||||||
let [h, m] = r.time.split(':').map(Number);
|
let [h, m] = r.time.split(':').map(Number);
|
||||||
let dur = parseInt(r.duration || 60);
|
let dur = parseInt(r.duration || 60, 10);
|
||||||
let endMin = (h * 60 + m) + dur;
|
let endMin = (h * 60 + m) + dur;
|
||||||
let endH = String(Math.floor(endMin / 60) % 24).padStart(2, '0');
|
let endH = String(Math.floor(endMin / 60) % 24).padStart(2, '0');
|
||||||
let endM = String(endMin % 60).padStart(2, '0');
|
let endM = String(endMin % 60).padStart(2, '0');
|
||||||
@@ -912,8 +912,17 @@ async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const lineas = Object.keys(ocupaciones).map(d => `- Día ${d}:\n * ${ocupaciones[d].join("\n * ")}`);
|
const lineas = Object.keys(ocupaciones).sort().map(d => {
|
||||||
if(lineas.length > 0) {
|
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 `- Día ${fechaHumana} (${d}):\n * ${ocupaciones[d].join("\n * ")}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lineas.length > 0) {
|
||||||
agendaOcupadaTexto = "Ocupaciones actuales del técnico (Citas confirmadas, pendientes y bloqueos):\n" + lineas.join("\n") +
|
agendaOcupadaTexto = "Ocupaciones actuales del técnico (Citas confirmadas, pendientes y bloqueos):\n" + lineas.join("\n") +
|
||||||
"\n\n👉 IMPORTANTE: Todas las horas que NO se solapen con esos tramos exactos ESTÁN LIBRES." +
|
"\n\n👉 IMPORTANTE: Todas las horas que NO se solapen con esos tramos exactos ESTÁN LIBRES." +
|
||||||
"\n🚨 REGLA LOGÍSTICA ESTRICTA: El técnico necesita tiempo para viajar. Si el cliente actual es de una localidad distinta a la cita anterior o posterior (ej: Algeciras vs La Línea), ES OBLIGATORIO dejar un margen de al menos 45-60 minutos de viaje entre el final de una cita y el inicio de la siguiente. NUNCA ofrezcas horas pegadas si hay desplazamiento.";
|
"\n🚨 REGLA LOGÍSTICA ESTRICTA: El técnico necesita tiempo para viajar. Si el cliente actual es de una localidad distinta a la cita anterior o posterior (ej: Algeciras vs La Línea), ES OBLIGATORIO dejar un margen de al menos 45-60 minutos de viaje entre el final de una cita y el inicio de la siguiente. NUNCA ofrezcas horas pegadas si hay desplazamiento.";
|
||||||
@@ -4054,27 +4063,44 @@ app.post("/webhook/evolution", async (req, res) => {
|
|||||||
// 🛡️ REGEX BLINDADO: Pilla la etiqueta aunque la IA meta espacios raros
|
// 🛡️ REGEX BLINDADO: Pilla la etiqueta aunque la IA meta espacios raros
|
||||||
const matchPropuesta = respuestaIA.match(/\[PROPUESTA:\s*(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})\]/i);
|
const matchPropuesta = respuestaIA.match(/\[PROPUESTA:\s*(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})\]/i);
|
||||||
|
|
||||||
|
// 🧹 BORRAMOS EL TEXTO DEL CÓDIGO PARA QUE EL CLIENTE NO LO VEA NUNCA
|
||||||
|
let textoLimpio = respuestaIA.replace(/\[PROPUESTA:.*?\]/gi, "").replace(/código:/gi, "").trim();
|
||||||
|
|
||||||
if (matchPropuesta) {
|
if (matchPropuesta) {
|
||||||
const fechaSugerida = matchPropuesta[1];
|
const fechaSugerida = matchPropuesta[1];
|
||||||
const horaSugerida = matchPropuesta[2];
|
const horaSugerida = matchPropuesta[2];
|
||||||
|
|
||||||
// 🚀 GUARDADO COMO PENDIENTE (Espera a que el Técnico la apruebe en la App o en la Oficina)
|
|
||||||
await pool.query(`
|
|
||||||
UPDATE scraped_services
|
|
||||||
SET raw_data = raw_data || jsonb_build_object(
|
|
||||||
'requested_date', $1::text,
|
|
||||||
'requested_time', $2::text,
|
|
||||||
'appointment_status', 'pending'
|
|
||||||
) WHERE id = $3
|
|
||||||
`, [fechaSugerida, horaSugerida, service.id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🧹 BORRAMOS EL TEXTO DEL CÓDIGO PARA QUE EL CLIENTE NO LO VEA NUNCA
|
// 🛡️ ESCUDO ANTI-SOLAPE: comprobamos antes de guardar la propuesta
|
||||||
const textoLimpio = respuestaIA.replace(/\[PROPUESTA:.*?\]/gi, "").replace(/código:/gi, "").trim();
|
const disponibilidad = await comprobarDisponibilidad(
|
||||||
|
ownerId,
|
||||||
|
service.assigned_to,
|
||||||
|
fechaSugerida,
|
||||||
|
horaSugerida,
|
||||||
|
60,
|
||||||
|
service.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (disponibilidad.choca) {
|
||||||
|
console.log(`⛔ [DOBLE-BOOKING EVITADO] Exp ${service.service_ref} chocaba con ${disponibilidad.ref} a las ${disponibilidad.time}`);
|
||||||
|
textoLimpio = "Uy, perdona, se me acaban de cruzar los cables y justo me han bloqueado ese hueco por el sistema interno. 😅 ¿Me dices otra hora que te venga bien?";
|
||||||
|
} else {
|
||||||
|
// 🚀 GUARDADO COMO PENDIENTE (Espera a que el Técnico la apruebe en la App o en la Oficina)
|
||||||
|
await pool.query(`
|
||||||
|
UPDATE scraped_services
|
||||||
|
SET raw_data = raw_data || jsonb_build_object(
|
||||||
|
'requested_date', $1::text,
|
||||||
|
'requested_time', $2::text,
|
||||||
|
'appointment_status', 'pending'
|
||||||
|
) WHERE id = $3
|
||||||
|
`, [fechaSugerida, horaSugerida, service.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await sendWhatsAppAuto(telefonoCliente, textoLimpio, instanceName, true);
|
await sendWhatsAppAuto(telefonoCliente, textoLimpio, instanceName, true);
|
||||||
await pool.query(`INSERT INTO service_communications (scraped_id, owner_id, sender_name, sender_role, message) VALUES ($1, $2, $3, $4, $5)`,
|
await pool.query(
|
||||||
[service.id, ownerId, "Asistente IA", "ia", textoLimpio]);
|
`INSERT INTO service_communications (scraped_id, owner_id, sender_name, sender_role, message) VALUES ($1, $2, $3, $4, $5)`,
|
||||||
|
[service.id, ownerId, "Asistente IA", "ia", textoLimpio]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
candadosIA.delete(service.id);
|
candadosIA.delete(service.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user