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) => {
try {
const { token } = req.params;
@@ -1319,23 +1319,21 @@ app.get("/public/portal/:token/slots", async (req, res) => {
const raw = service.raw_data || {};
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 workerZones = workerQ.rows[0]?.zones || [];
function getCityForCP(cp, fallbackPop) {
let cleanCP = String(cp || "").trim();
// Buscamos si el CP coincide con la tabla configurada del trabajador
const zone = workerZones.find(z => z.cps === cleanCP);
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();
}
// 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"]);
// 🧠 2. EXTRAER LA AGENDA TOTAL (FIRMADOS + PENDIENTES DE APROBAR)
// 🧠 2. EXTRAER LA AGENDA TOTAL
const agendaQ = await pool.query(`
SELECT raw_data->>'scheduled_date' as date,
raw_data->>'scheduled_time' as time,
@@ -1356,32 +1354,36 @@ app.get("/public/portal/:token/slots", async (req, res) => {
const agendaMap = {};
agendaQ.rows.forEach(row => {
// Determinar la fecha y hora REALES que ocupan sitio (Firmes o Pendientes)
let effectiveDate = row.date;
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) {
effectiveDate = row.req_date;
effectiveTime = row.req_time;
}
// Ignorar citas que ya pasaron
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)) {
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' && !agendaMap[effectiveDate].city) {
agendaMap[effectiveDate].city = getCityForCP(row.cp, row.poblacion);
if (row.provider !== 'SYSTEM_BLOCK' && effectiveTime) {
const 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);
if (effectiveTime) {
let [th, tm] = effectiveTime.split(':').map(Number);
@@ -1403,33 +1405,31 @@ 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 = [];
let d = new Date();
d.setDate(d.getDate() + 1);
let daysAdded = 0;
// 🛑 LÍMITE DE 5 DÍAS HÁBILES PARA EVITAR ERRORES CON HOMESERVE
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 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
if (dayData && dayData.city && targetCity) {
if (dayData.city !== targetCity) {
isDayAllowed = false;
}
}
// Si la ciudad coincide (o si el día está virgen):
if (isDayAllowed) {
const takenTimes = dayData ? dayData.times : [];
const availMorning = morningBase.filter(t => !takenTimes.includes(t));
const availAfternoon = afternoonBase.filter(t => !takenTimes.includes(t));
let availMorning = morningBase.filter(t => !takenTimes.includes(t));
let availAfternoon = afternoonBase.filter(t => !takenTimes.includes(t));
// 📍 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
}
}
// Solo añadimos el día si tiene AL MENOS una hora libre en su ciudad
if (availMorning.length > 0 || availAfternoon.length > 0) {
availableDays.push({
date: dateStr,
@@ -1440,7 +1440,6 @@ app.get("/public/portal/:token/slots", async (req, res) => {
daysAdded++;
}
}
}
d.setDate(d.getDate() + 1);
}
res.json({ ok: true, days: availableDays });