Actualizar server.js

This commit is contained in:
2026-03-20 15:13:36 +00:00
parent 2c1d8c0a87
commit e01cf938c5

View File

@@ -431,6 +431,44 @@ function signToken(user) { const accountId = user.owner_id || user.id; return jw
function authMiddleware(req, res, next) { const h = req.headers.authorization || ""; const token = h.startsWith("Bearer ") ? h.slice(7) : ""; if (!token) return res.status(401).json({ ok: false, error: "No token" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { return res.status(401).json({ ok: false, error: "Token inválido" }); } }
function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)); }
// ==========================================
// 🛡️ ESCUDO DE TITANIO: ANTI-SOLAPAMIENTOS BBDD
// ==========================================
async function comprobarDisponibilidad(ownerId, workerId, date, time, durationMin, excludeId = null) {
if (!workerId || !date || !time) return { choca: false };
let [newH, newM] = time.split(':').map(Number);
let newStart = newH * 60 + newM;
let newEnd = newStart + (parseInt(durationMin) || 60);
let query = `
SELECT id, service_ref, raw_data->>'scheduled_time' as time, raw_data->>'duration_minutes' as dur,
raw_data->>'requested_time' as req_time, raw_data->>'appointment_status' as app_status
FROM scraped_services
WHERE owner_id = $1 AND assigned_to = $2 AND status != 'archived'
AND (
raw_data->>'scheduled_date' = $3 OR
(raw_data->>'requested_date' = $3 AND raw_data->>'appointment_status' = 'pending')
)
`;
let params = [ownerId, workerId, date];
if (excludeId) { query += ` AND id != $4`; params.push(excludeId); }
const q = await pool.query(query, params);
for (const s of q.rows) {
let sTime = s.time || (s.app_status === 'pending' ? s.req_time : null);
if (sTime && sTime.includes(':')) {
let [sH, sM] = sTime.split(':').map(Number);
let sStart = sH * 60 + sM;
let sEnd = sStart + parseInt(s.dur || 60);
if (newStart < sEnd && newEnd > sStart) return { choca: true, ref: s.service_ref, time: sTime };
}
}
return { choca: false };
}
// ==========================================
// 🔐 SISTEMA DE AUTENTICACIÓN (LOGIN Y SESIÓN)
// ==========================================
@@ -1287,8 +1325,8 @@ app.get("/public/portal/:token/slots", async (req, res) => {
const targetGuildId = raw["guild_id"];
const agendaQ = await pool.query(`
SELECT raw_data->>'scheduled_date' as date,
raw_data->>'scheduled_time' as time,
SELECT COALESCE(NULLIF(raw_data->>'scheduled_date', ''), raw_data->>'requested_date') as date,
COALESCE(NULLIF(raw_data->>'scheduled_time', ''), raw_data->>'requested_time') as time,
raw_data->>'duration_minutes' as duration,
raw_data->>'Población' as poblacion,
raw_data->>'Código Postal' as cp,
@@ -1296,8 +1334,12 @@ app.get("/public/portal/:token/slots", async (req, res) => {
raw_data->>'blocked_guild_id' as blocked_guild_id
FROM scraped_services
WHERE assigned_to = $1
AND raw_data->>'scheduled_date' IS NOT NULL
AND raw_data->>'scheduled_date' >= CURRENT_DATE::text
AND status != 'archived'
AND (
(raw_data->>'scheduled_date' IS NOT NULL AND raw_data->>'scheduled_date' >= CURRENT_DATE::text)
OR
(raw_data->>'appointment_status' = 'pending' AND raw_data->>'requested_date' >= CURRENT_DATE::text)
)
`, [assignedTo]);
const agendaMap = {};
@@ -1432,6 +1474,14 @@ app.post("/public/portal/:token/book", async (req, res) => {
const srv = serviceQ.rows[0];
const raw = srv.raw_data || {};
// 🛡️ ESCUDO: Verificamos que el hueco siga libre (Por si 2 clientes hacen clic en el mismo milisegundo)
if (srv.assigned_to) {
const solapamiento = await comprobarDisponibilidad(ownerId, srv.assigned_to, date, time, 60, serviceId);
if (solapamiento.choca) {
return res.status(400).json({ ok: false, error: "Lo sentimos, alguien acaba de reservar ese mismo hueco hace unos instantes. Por favor, elige otro horario." });
}
}
// Grabamos la solicitud en el jsonb para que el admin la vea en agenda.html
raw.requested_date = date;
raw.requested_time = time;