diff --git a/server.js b/server.js index 2162032..553fa50 100644 --- a/server.js +++ b/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) => { 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,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 = []; 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; + const takenTimes = dayData ? dayData.times : []; + 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 } } - // 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)); - - // 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, - displayDate: d.toLocaleDateString('es-ES', { weekday: 'long', day: 'numeric', month: 'long' }), - morning: availMorning, - afternoon: availAfternoon - }); - daysAdded++; - } + if (availMorning.length > 0 || availAfternoon.length > 0) { + 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);