Actualizar server.js
This commit is contained in:
117
server.js
117
server.js
@@ -488,96 +488,6 @@ async function ensureInstance(instanceName) {
|
|||||||
// 🚀 RUTAS PÚBLICAS (MÓVIL OPERARIO)
|
// 🚀 RUTAS PÚBLICAS (MÓVIL OPERARIO)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
app.get("/public/assignment/:token", async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { token } = req.params;
|
|
||||||
|
|
||||||
// Comprobación MODO BLINDADO (Extrae todo, exista o no)
|
|
||||||
const q = await pool.query(`
|
|
||||||
SELECT ap.*, s.raw_data, u.full_name as worker_name, CURRENT_TIMESTAMP as db_now
|
|
||||||
FROM assignment_pings ap
|
|
||||||
JOIN scraped_services s ON ap.scraped_id = s.id
|
|
||||||
JOIN users u ON ap.user_id = u.id
|
|
||||||
WHERE ap.token = $1
|
|
||||||
`, [token]);
|
|
||||||
|
|
||||||
if (q.rowCount === 0) return res.status(404).json({ ok: false, error: "Enlace caducado o inexistente" });
|
|
||||||
|
|
||||||
const data = q.rows[0];
|
|
||||||
const isExpired = data.status !== 'pending' || new Date(data.expires_at) <= new Date(data.db_now);
|
|
||||||
|
|
||||||
if (isExpired) {
|
|
||||||
return res.status(404).json({ ok: false, error: "Este enlace ha caducado o ha sido reasignado." });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
ok: true,
|
|
||||||
service: data.raw_data,
|
|
||||||
worker: data.worker_name,
|
|
||||||
debug: { hora_limite_bd: data.expires_at, hora_actual_bd: data.db_now }
|
|
||||||
});
|
|
||||||
} catch (e) { res.status(500).json({ ok: false }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/public/assignment/respond", async (req, res) => {
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
const { token, action } = req.body;
|
|
||||||
await client.query('BEGIN');
|
|
||||||
|
|
||||||
const q = await client.query(
|
|
||||||
"SELECT *, CURRENT_TIMESTAMP as db_now FROM assignment_pings WHERE token = $1 FOR UPDATE",
|
|
||||||
[token]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (q.rowCount === 0) throw new Error("Enlace no válido");
|
|
||||||
const ping = q.rows[0];
|
|
||||||
|
|
||||||
if (action === 'accept') {
|
|
||||||
await client.query("UPDATE assignment_pings SET status = 'accepted' WHERE id = $1", [ping.id]);
|
|
||||||
|
|
||||||
// 1. Obtenemos el owner para poder enviar el WA
|
|
||||||
const ownerQ = await client.query("SELECT owner_id FROM scraped_services WHERE id = $1", [ping.scraped_id]);
|
|
||||||
const ownerId = ownerQ.rows[0].owner_id;
|
|
||||||
|
|
||||||
// 2. DISPARAMOS EL WHATSAPP Y ESPERAMOS EL RESULTADO
|
|
||||||
const waEnviadoExito = await triggerWhatsAppEvent(ownerId, ping.scraped_id, 'wa_evt_assigned');
|
|
||||||
|
|
||||||
// 3. REGLA ESTRICTA: Decidimos el estado según el éxito del mensaje
|
|
||||||
const estadoFinalNombre = waEnviadoExito ? 'Esperando al Cliente' : 'Asignado';
|
|
||||||
|
|
||||||
const statusQ = await client.query(
|
|
||||||
"SELECT id FROM service_statuses WHERE owner_id = $1 AND name = $2 LIMIT 1",
|
|
||||||
[ownerId, estadoFinalNombre]
|
|
||||||
);
|
|
||||||
const idFinal = statusQ.rows[0]?.id;
|
|
||||||
|
|
||||||
// 4. Actualizamos el servicio con el operario y el estado correcto
|
|
||||||
await client.query(`
|
|
||||||
UPDATE scraped_services
|
|
||||||
SET status = 'imported',
|
|
||||||
automation_status = 'completed',
|
|
||||||
assigned_to = $1,
|
|
||||||
raw_data = raw_data || jsonb_build_object('assigned_to', $1::int, 'status_operativo', $3::text)
|
|
||||||
WHERE id = $2
|
|
||||||
`, [ping.user_id, ping.scraped_id, idFinal]);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
await client.query("UPDATE assignment_pings SET status = 'rejected', expires_at = CURRENT_TIMESTAMP WHERE id = $1", [ping.id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ ok: true });
|
|
||||||
} catch (e) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
res.status(400).json({ ok: false, error: e.message });
|
|
||||||
} finally { client.release(); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 🌐 RUTAS PÚBLICAS: PORTAL DEL CLIENTE (SIN FRICCIÓN)
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
// 1. Cargar datos del cliente, logo y empresa
|
// 1. Cargar datos del cliente, logo y empresa
|
||||||
app.get("/public/portal/:token", async (req, res) => {
|
app.get("/public/portal/:token", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -598,10 +508,11 @@ app.get("/public/portal/:token", async (req, res) => {
|
|||||||
SELECT id, service_ref as title, raw_data->>'Descripción' as description,
|
SELECT id, service_ref as title, raw_data->>'Descripción' as description,
|
||||||
raw_data->>'scheduled_date' as scheduled_date,
|
raw_data->>'scheduled_date' as scheduled_date,
|
||||||
raw_data->>'scheduled_time' as scheduled_time,
|
raw_data->>'scheduled_time' as scheduled_time,
|
||||||
|
raw_data->>'appointment_status' as appointment_status,
|
||||||
created_at,
|
created_at,
|
||||||
raw_data->>'status_operativo' as estado_operativo,
|
|
||||||
is_urgent,
|
is_urgent,
|
||||||
(SELECT full_name FROM users WHERE id = scraped_services.assigned_to) as assigned_worker
|
(SELECT full_name FROM users WHERE id = scraped_services.assigned_to) as assigned_worker,
|
||||||
|
(SELECT name FROM service_statuses WHERE id::text = raw_data->>'status_operativo') as real_status_name
|
||||||
FROM scraped_services
|
FROM scraped_services
|
||||||
WHERE owner_id = $1
|
WHERE owner_id = $1
|
||||||
AND (raw_data->>'Teléfono' ILIKE $2 OR raw_data->>'TELEFONO' ILIKE $2 OR raw_data->>'TELEFONOS' ILIKE $2)
|
AND (raw_data->>'Teléfono' ILIKE $2 OR raw_data->>'TELEFONO' ILIKE $2 OR raw_data->>'TELEFONOS' ILIKE $2)
|
||||||
@@ -609,13 +520,16 @@ app.get("/public/portal/:token", async (req, res) => {
|
|||||||
`, [clientData.owner_id, `%${phoneRaw}%`]);
|
`, [clientData.owner_id, `%${phoneRaw}%`]);
|
||||||
|
|
||||||
const services = scrapedQ.rows.map(s => {
|
const services = scrapedQ.rows.map(s => {
|
||||||
let statusName = "Pendiente de Asignar"; let color = "gray";
|
// Evaluamos el nombre real de la base de datos
|
||||||
if (s.estado_operativo === 'asignado_operario') { statusName = "Asignado a Técnico"; color = "blue"; }
|
let stNameDb = (s.real_status_name || 'Pendiente de Asignar').toLowerCase();
|
||||||
if (s.estado_operativo === 'citado') { statusName = "Visita Agendada"; color = "emerald"; }
|
let finalStatusName = s.real_status_name || "Pendiente de Asignar";
|
||||||
if (s.estado_operativo === 'de_camino') { statusName = "Técnico de Camino"; color = "indigo"; }
|
|
||||||
if (s.estado_operativo === 'trabajando') { statusName = "En Reparación"; color = "amber"; }
|
if (stNameDb.includes('asignado') || stNameDb.includes('esperando')) { finalStatusName = "Asignado a Técnico"; }
|
||||||
if (s.estado_operativo === 'incidencia') { statusName = "Pausado / Incidencia"; color = "red"; }
|
if (stNameDb.includes('citado')) { finalStatusName = "Visita Agendada"; }
|
||||||
if (s.estado_operativo === 'terminado') { statusName = "Terminado"; color = "purple"; }
|
if (stNameDb.includes('camino')) { finalStatusName = "Técnico de Camino"; }
|
||||||
|
if (stNameDb.includes('trabajando')) { finalStatusName = "En Reparación"; }
|
||||||
|
if (stNameDb.includes('incidencia')) { finalStatusName = "Pausado / Incidencia"; }
|
||||||
|
if (stNameDb.includes('terminado') || stNameDb.includes('finalizado') || stNameDb.includes('anulado') || stNameDb.includes('desasignado')) { finalStatusName = "Terminado"; }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: s.id,
|
id: s.id,
|
||||||
@@ -623,9 +537,9 @@ app.get("/public/portal/:token", async (req, res) => {
|
|||||||
description: s.description || "Avería reportada.",
|
description: s.description || "Avería reportada.",
|
||||||
scheduled_date: s.scheduled_date,
|
scheduled_date: s.scheduled_date,
|
||||||
scheduled_time: s.scheduled_time,
|
scheduled_time: s.scheduled_time,
|
||||||
|
appointment_status: s.appointment_status,
|
||||||
created_at: s.created_at,
|
created_at: s.created_at,
|
||||||
status_name: statusName,
|
status_name: finalStatusName,
|
||||||
status_color: color,
|
|
||||||
assigned_worker: s.assigned_worker || "Pendiente"
|
assigned_worker: s.assigned_worker || "Pendiente"
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -639,7 +553,6 @@ app.get("/public/portal/:token", async (req, res) => {
|
|||||||
} catch (e) { res.status(500).json({ ok: false, error: "Error de servidor" }); }
|
} catch (e) { res.status(500).json({ ok: false, error: "Error de servidor" }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Obtener huecos disponibles inteligentes (CON HORARIOS DINÁMICOS Y TRAMOS DE 1 HORA)
|
|
||||||
// 2. Obtener huecos disponibles inteligentes (CON HORARIOS DINÁMICOS Y TRAMOS DE 1 HORA)
|
// 2. Obtener huecos disponibles inteligentes (CON HORARIOS DINÁMICOS Y TRAMOS DE 1 HORA)
|
||||||
app.get("/public/portal/:token/slots", async (req, res) => {
|
app.get("/public/portal/:token/slots", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user