diff --git a/server.js b/server.js index b08e9e9..ac96718 100644 --- a/server.js +++ b/server.js @@ -502,7 +502,70 @@ async function ensureInstance(instanceName) { // 🚀 RUTAS PÚBLICAS (MÓVIL OPERARIO) // ========================================== -app.get("/public/portal/:token +// 1. Cargar datos del cliente, logo y empresa +app.get("/public/portal/:token", async (req, res) => { + try { + const { token } = req.params; + const clientQ = await pool.query(` + SELECT c.id, c.full_name, c.phone, c.addresses, c.owner_id, + u.company_slug, u.full_name as company_name, u.company_logo + FROM clients c + JOIN users u ON c.owner_id = u.id + WHERE c.portal_token = $1 + `, [token]); + + if (clientQ.rowCount === 0) return res.status(404).json({ ok: false, error: "Enlace no válido o caducado" }); + const clientData = clientQ.rows[0]; + + const phoneRaw = clientData.phone.replace('+34', ''); + const scrapedQ = await pool.query(` + SELECT id, service_ref as title, raw_data->>'Descripción' as description, + raw_data->>'scheduled_date' as scheduled_date, + raw_data->>'scheduled_time' as scheduled_time, + raw_data->>'appointment_status' as appointment_status, + created_at, + is_urgent, + (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 + WHERE owner_id = $1 + AND (raw_data->>'Teléfono' ILIKE $2 OR raw_data->>'TELEFONO' ILIKE $2 OR raw_data->>'TELEFONOS' ILIKE $2) + ORDER BY created_at DESC + `, [clientData.owner_id, `%${phoneRaw}%`]); + + const services = scrapedQ.rows.map(s => { + // Evaluamos el nombre real de la base de datos + let stNameDb = (s.real_status_name || 'Pendiente de Asignar').toLowerCase(); + let finalStatusName = s.real_status_name || "Pendiente de Asignar"; + + if (stNameDb.includes('asignado') || stNameDb.includes('esperando')) { finalStatusName = "Asignado a Técnico"; } + if (stNameDb.includes('citado')) { finalStatusName = "Visita Agendada"; } + 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 { + id: s.id, + title: (s.is_urgent ? "🚨 URGENTE: " : "") + "Expediente #" + s.title, + description: s.description || "Avería reportada.", + scheduled_date: s.scheduled_date, + scheduled_time: s.scheduled_time, + appointment_status: s.appointment_status, + created_at: s.created_at, + status_name: finalStatusName, + assigned_worker: s.assigned_worker || "Pendiente" + }; + }); + + res.json({ + ok: true, + client: { name: clientData.full_name, phone: clientData.phone, addresses: clientData.addresses }, + company: { name: clientData.company_name, slug: clientData.company_slug, logo: clientData.company_logo }, + services: services + }); + } 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) app.get("/public/portal/:token/slots", async (req, res) => {