Actualizar server.js
This commit is contained in:
133
server.js
133
server.js
@@ -610,6 +610,139 @@ app.get("/whatsapp/status", authMiddleware, (req, res, next) => requirePlan(req,
|
||||
} catch (e) { res.status(500).json({ ok: false, error: e.message }); }
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 🧠 MOTOR INTELIGENTE DE AGENDAMIENTO (RUTAS NUEVAS)
|
||||
// ==========================================
|
||||
|
||||
// 1. Obtener huecos disponibles inteligentes
|
||||
app.get("/public/portal/:token/slots", async (req, res) => {
|
||||
try {
|
||||
const { token } = req.params;
|
||||
const { serviceId } = req.query;
|
||||
|
||||
// 1. Validar cliente y servicio
|
||||
const clientQ = await pool.query("SELECT id, owner_id FROM clients WHERE portal_token = $1", [token]);
|
||||
if (clientQ.rowCount === 0) return res.status(404).json({ ok: false, error: "Token inválido" });
|
||||
|
||||
const serviceQ = await pool.query("SELECT * FROM scraped_services WHERE id=$1", [serviceId]);
|
||||
if (serviceQ.rowCount === 0) return res.status(404).json({ ok: false, error: "Servicio no encontrado" });
|
||||
|
||||
const service = serviceQ.rows[0];
|
||||
const assignedTo = service.assigned_to;
|
||||
if (!assignedTo) return res.status(400).json({ ok: false, error: "No hay operario asignado" });
|
||||
|
||||
// 2. Extraer "Zona" del servicio actual (Población o CP) para agrupar
|
||||
const raw = service.raw_data || {};
|
||||
const targetZone = (raw["Población"] || raw["POBLACION-PROVINCIA"] || raw["Código Postal"] || "").toLowerCase().trim();
|
||||
|
||||
// 3. Buscar la agenda del operario para los próximos 14 días
|
||||
const agendaQ = await pool.query(`
|
||||
SELECT raw_data->>'scheduled_date' as date,
|
||||
raw_data->>'scheduled_time' as time,
|
||||
raw_data->>'Población' as poblacion,
|
||||
raw_data->>'Código Postal' as cp
|
||||
FROM scraped_services
|
||||
WHERE assigned_to = $1
|
||||
AND raw_data->>'scheduled_date' IS NOT NULL
|
||||
AND raw_data->>'scheduled_date' >= CURRENT_DATE::text
|
||||
`, [assignedTo]);
|
||||
|
||||
// Mapa de días ocupados y sus zonas "Ancla"
|
||||
const agendaMap = {};
|
||||
agendaQ.rows.forEach(row => {
|
||||
if (!agendaMap[row.date]) agendaMap[row.date] = { times: [], zone: (row.poblacion || row.cp || "").toLowerCase().trim() };
|
||||
agendaMap[row.date].times.push(row.time);
|
||||
});
|
||||
|
||||
// 4. Generar próximos 10 días laborables
|
||||
const availableDays = [];
|
||||
let d = new Date();
|
||||
d.setDate(d.getDate() + 1); // Empezamos desde mañana
|
||||
|
||||
let daysAdded = 0;
|
||||
while(daysAdded < 10) {
|
||||
// Saltamos domingos
|
||||
if (d.getDay() !== 0) {
|
||||
const dateStr = d.toISOString().split('T')[0]; // YYYY-MM-DD
|
||||
const dayData = agendaMap[dateStr];
|
||||
|
||||
let isDayAllowed = true;
|
||||
|
||||
// REGLA DE AGRUPACIÓN (EL CEREBRO):
|
||||
// Si el día ya tiene servicios, comprobamos si la zona coincide.
|
||||
if (dayData && dayData.zone && targetZone) {
|
||||
// Si el día está anclado a una zona diferente, bloqueamos el día para este cliente
|
||||
if (!dayData.zone.includes(targetZone) && !targetZone.includes(dayData.zone)) {
|
||||
isDayAllowed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDayAllowed) {
|
||||
// Generar Huecos. Mañana: 9 a 13 | Tarde: 16 a 19
|
||||
const morningSlots = ["09:00", "10:00", "11:00", "12:00", "13:00"];
|
||||
const afternoonSlots = ["16:00", "17:00", "18:00", "19:00"];
|
||||
|
||||
const takenTimes = dayData ? dayData.times : [];
|
||||
|
||||
const availMorning = morningSlots.filter(t => !takenTimes.includes(t));
|
||||
const availAfternoon = afternoonSlots.filter(t => !takenTimes.includes(t));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
res.json({ ok: true, days: availableDays });
|
||||
} catch (e) { console.error("Error Slots:", e); res.status(500).json({ ok: false }); }
|
||||
});
|
||||
|
||||
// 2. Guardar la cita elegida por el cliente
|
||||
app.post("/public/portal/:token/book", async (req, res) => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const { token } = req.params;
|
||||
const { serviceId, date, time } = req.body;
|
||||
|
||||
await client.query('BEGIN');
|
||||
|
||||
const clientQ = await client.query("SELECT owner_id FROM clients WHERE portal_token = $1", [token]);
|
||||
if (clientQ.rowCount === 0) throw new Error("Token inválido");
|
||||
const ownerId = clientQ.rows[0].owner_id;
|
||||
|
||||
const serviceQ = await client.query("SELECT raw_data FROM scraped_services WHERE id=$1 AND owner_id=$2", [serviceId, ownerId]);
|
||||
if (serviceQ.rowCount === 0) throw new Error("Servicio no encontrado");
|
||||
|
||||
const raw = serviceQ.rows[0].raw_data;
|
||||
|
||||
// Buscamos el estado "Citado"
|
||||
const statusQ = await client.query("SELECT id FROM service_statuses WHERE owner_id=$1 AND name ILIKE '%citado%' LIMIT 1", [ownerId]);
|
||||
const idCitado = statusQ.rows[0]?.id || raw.status_operativo;
|
||||
|
||||
const updatedRaw = { ...raw, scheduled_date: date, scheduled_time: time, status_operativo: idCitado };
|
||||
|
||||
await client.query("UPDATE scraped_services SET raw_data = $1 WHERE id = $2", [JSON.stringify(updatedRaw), serviceId]);
|
||||
|
||||
// Disparamos el WhatsApp informando de la cita
|
||||
await triggerWhatsAppEvent(ownerId, serviceId, 'wa_evt_date');
|
||||
|
||||
await client.query('COMMIT');
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error("Error Booking:", e);
|
||||
res.status(500).json({ ok: false, error: e.message });
|
||||
} finally { client.release(); }
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// ⚙️ MOTOR AUTOMÁTICO DE WHATSAPP
|
||||
// ==========================================
|
||||
|
||||
Reference in New Issue
Block a user