Actualizar server.js

This commit is contained in:
2026-03-20 20:42:04 +00:00
parent 578130c9f3
commit 57dd284848

View File

@@ -1277,7 +1277,7 @@ app.get("/public/portal/:token", async (req, res) => {
} }
}); });
// 2. Obtener huecos disponibles inteligentes (CON HORARIOS DINÁMICOS, TRAMOS Y ENRUTAMIENTO PRO) // 2. Obtener huecos disponibles inteligentes (CON ENRUTAMIENTO POR TURNOS: Mañana y Tarde)
app.get("/public/portal/:token/slots", async (req, res) => { app.get("/public/portal/:token/slots", async (req, res) => {
try { try {
const { token } = req.params; const { token } = req.params;
@@ -1319,23 +1319,21 @@ app.get("/public/portal/:token/slots", async (req, res) => {
const raw = service.raw_data || {}; const raw = service.raw_data || {};
const targetGuildId = raw["guild_id"]; const targetGuildId = raw["guild_id"];
// 🧠 1. EXTRAER LAS ZONAS DEL OPERARIO PARA EL ALGORITMO DE ENRUTAMIENTO // 🧠 1. EXTRAER LAS ZONAS DEL OPERARIO
const workerQ = await pool.query("SELECT zones FROM users WHERE id = $1", [assignedTo]); const workerQ = await pool.query("SELECT zones FROM users WHERE id = $1", [assignedTo]);
const workerZones = workerQ.rows[0]?.zones || []; const workerZones = workerQ.rows[0]?.zones || [];
function getCityForCP(cp, fallbackPop) { function getCityForCP(cp, fallbackPop) {
let cleanCP = String(cp || "").trim(); let cleanCP = String(cp || "").trim();
// Buscamos si el CP coincide con la tabla configurada del trabajador
const zone = workerZones.find(z => z.cps === cleanCP); const zone = workerZones.find(z => z.cps === cleanCP);
if (zone && zone.city) return zone.city.toUpperCase().trim(); if (zone && zone.city) return zone.city.toUpperCase().trim();
// Si el CP no existe en la tabla o no hay, usamos la población escrita como respaldo
return String(fallbackPop || "").toUpperCase().trim(); return String(fallbackPop || "").toUpperCase().trim();
} }
// Bautizamos al cliente actual que intenta coger cita con su Ciudad Principal // Bautizamos al cliente actual
const targetCity = getCityForCP(raw["Código Postal"] || raw["C.P."], raw["Población"] || raw["POBLACION-PROVINCIA"]); const targetCity = getCityForCP(raw["Código Postal"] || raw["C.P."], raw["Población"] || raw["POBLACION-PROVINCIA"]);
// 🧠 2. EXTRAER LA AGENDA TOTAL (FIRMADOS + PENDIENTES DE APROBAR) // 🧠 2. EXTRAER LA AGENDA TOTAL
const agendaQ = await pool.query(` const agendaQ = await pool.query(`
SELECT raw_data->>'scheduled_date' as date, SELECT raw_data->>'scheduled_date' as date,
raw_data->>'scheduled_time' as time, raw_data->>'scheduled_time' as time,
@@ -1356,32 +1354,36 @@ app.get("/public/portal/:token/slots", async (req, res) => {
const agendaMap = {}; const agendaMap = {};
agendaQ.rows.forEach(row => { agendaQ.rows.forEach(row => {
// Determinar la fecha y hora REALES que ocupan sitio (Firmes o Pendientes)
let effectiveDate = row.date; let effectiveDate = row.date;
let effectiveTime = row.time; let effectiveTime = row.time;
// 🛡️ REGLA: Si está pendiente de aprobar por la oficina, ¡BLOQUEA EL HUECO PARA EL RESTO!
if (row.appt_status === 'pending' && row.req_date && row.req_time) { if (row.appt_status === 'pending' && row.req_date && row.req_time) {
effectiveDate = row.req_date; effectiveDate = row.req_date;
effectiveTime = row.req_time; effectiveTime = row.req_time;
} }
// Ignorar citas que ya pasaron
if (!effectiveDate || new Date(effectiveDate) < new Date(new Date().toISOString().split('T')[0])) return; if (!effectiveDate || new Date(effectiveDate) < new Date(new Date().toISOString().split('T')[0])) return;
// Ignorar bloqueos de sistema que pertenezcan a otros gremios
if (row.provider === 'SYSTEM_BLOCK' && row.blocked_guild_id && String(row.blocked_guild_id) !== String(targetGuildId)) { if (row.provider === 'SYSTEM_BLOCK' && row.blocked_guild_id && String(row.blocked_guild_id) !== String(targetGuildId)) {
return; return;
} }
if (!agendaMap[effectiveDate]) agendaMap[effectiveDate] = { times: [], city: null }; // 🛑 NUEVO: Ahora guardamos la ciudad de la MAÑANA y de la TARDE por separado
if (!agendaMap[effectiveDate]) agendaMap[effectiveDate] = { times: [], morningCity: null, afternoonCity: null };
// 🏙️ Bautizamos el día con la ciudad del PRIMER servicio agendado if (row.provider !== 'SYSTEM_BLOCK' && effectiveTime) {
if (row.provider !== 'SYSTEM_BLOCK' && !agendaMap[effectiveDate].city) { const city = getCityForCP(row.cp, row.poblacion);
agendaMap[effectiveDate].city = getCityForCP(row.cp, row.poblacion); let [th, tm] = effectiveTime.split(':').map(Number);
let [afternoonStartH, afternoonStartM] = pSet.a_start.split(':').map(Number); // Ej: 16:00
// Si la cita empieza antes del turno de tarde, bautiza la mañana. Si no, la tarde.
if (th < afternoonStartH) {
if (!agendaMap[effectiveDate].morningCity) agendaMap[effectiveDate].morningCity = city;
} else {
if (!agendaMap[effectiveDate].afternoonCity) agendaMap[effectiveDate].afternoonCity = city;
}
} }
// Bloqueamos la franja de tiempo específica calculando duraciones
const dur = parseInt(row.duration || 60); const dur = parseInt(row.duration || 60);
if (effectiveTime) { if (effectiveTime) {
let [th, tm] = effectiveTime.split(':').map(Number); let [th, tm] = effectiveTime.split(':').map(Number);
@@ -1403,42 +1405,39 @@ app.get("/public/portal/:token/slots", async (req, res) => {
} }
}); });
// 🧠 3. GENERAR LOS DÍAS DISPONIBLES PARA ESTE CLIENTE // 🧠 3. GENERAR LOS DÍAS DISPONIBLES (CON CORTAFUEGOS POR TURNOS)
const availableDays = []; const availableDays = [];
let d = new Date(); let d = new Date();
d.setDate(d.getDate() + 1); d.setDate(d.getDate() + 1);
let daysAdded = 0; let daysAdded = 0;
// 🛑 LÍMITE DE 5 DÍAS HÁBILES PARA EVITAR ERRORES CON HOMESERVE
while(daysAdded < 5) { while(daysAdded < 5) {
if (d.getDay() !== 0 && d.getDay() !== 6) { // Ignora Sábado y Domingo if (d.getDay() !== 0 && d.getDay() !== 6) {
const dateStr = d.toISOString().split('T')[0]; const dateStr = d.toISOString().split('T')[0];
const dayData = agendaMap[dateStr]; const dayData = agendaMap[dateStr];
let isDayAllowed = true;
// 📍 REGLA CRÍTICA DE RUTAS: Si el día está bautizado con otra ciudad que NO es la del cliente, DENEGADO const takenTimes = dayData ? dayData.times : [];
if (dayData && dayData.city && targetCity) { let availMorning = morningBase.filter(t => !takenTimes.includes(t));
if (dayData.city !== targetCity) { let availAfternoon = afternoonBase.filter(t => !takenTimes.includes(t));
isDayAllowed = false;
// 📍 REGLA CRÍTICA DE TURNOS: Bloqueamos de forma independiente
if (dayData) {
if (dayData.morningCity && dayData.morningCity !== targetCity) {
availMorning = []; // Mañana ocupada en otra ciudad
}
if (dayData.afternoonCity && dayData.afternoonCity !== targetCity) {
availAfternoon = []; // Tarde ocupada en otra ciudad
} }
} }
// Si la ciudad coincide (o si el día está virgen): if (availMorning.length > 0 || availAfternoon.length > 0) {
if (isDayAllowed) { availableDays.push({
const takenTimes = dayData ? dayData.times : []; date: dateStr,
const availMorning = morningBase.filter(t => !takenTimes.includes(t)); displayDate: d.toLocaleDateString('es-ES', { weekday: 'long', day: 'numeric', month: 'long' }),
const availAfternoon = afternoonBase.filter(t => !takenTimes.includes(t)); morning: availMorning,
afternoon: availAfternoon
// Solo añadimos el día si tiene AL MENOS una hora libre en su ciudad });
if (availMorning.length > 0 || availAfternoon.length > 0) { daysAdded++;
availableDays.push({
date: dateStr,
displayDate: d.toLocaleDateString('es-ES', { weekday: 'long', day: 'numeric', month: 'long' }),
morning: availMorning,
afternoon: availAfternoon
});
daysAdded++;
}
} }
} }
d.setDate(d.getDate() + 1); d.setDate(d.getDate() + 1);