Actualizar server.js
This commit is contained in:
63
server.js
63
server.js
@@ -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,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 = [];
|
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
|
|
||||||
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 takenTimes = dayData ? dayData.times : [];
|
||||||
const availMorning = morningBase.filter(t => !takenTimes.includes(t));
|
let availMorning = morningBase.filter(t => !takenTimes.includes(t));
|
||||||
const availAfternoon = afternoonBase.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) {
|
if (availMorning.length > 0 || availAfternoon.length > 0) {
|
||||||
availableDays.push({
|
availableDays.push({
|
||||||
date: dateStr,
|
date: dateStr,
|
||||||
@@ -1440,7 +1440,6 @@ app.get("/public/portal/:token/slots", async (req, res) => {
|
|||||||
daysAdded++;
|
daysAdded++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
d.setDate(d.getDate() + 1);
|
d.setDate(d.getDate() + 1);
|
||||||
}
|
}
|
||||||
res.json({ ok: true, days: availableDays });
|
res.json({ ok: true, days: availableDays });
|
||||||
|
|||||||
Reference in New Issue
Block a user