From eb7cdcdaa2a4cf0acb4f73ee73b09527b0c4327f Mon Sep 17 00:00:00 2001 From: marsalva Date: Wed, 18 Feb 2026 21:50:00 +0000 Subject: [PATCH] Actualizar server.js --- server.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/server.js b/server.js index 94cf0af..90afa74 100644 --- a/server.js +++ b/server.js @@ -263,6 +263,13 @@ async function autoUpdateDB() { IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='scraped_services' AND column_name='automation_status') THEN ALTER TABLE scraped_services ADD COLUMN automation_status TEXT DEFAULT 'manual'; END IF; + -- AÑADIDO: Token mágico para el Portal del Cliente + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='clients' AND column_name='portal_token') THEN + ALTER TABLE clients ADD COLUMN portal_token TEXT UNIQUE; + UPDATE clients SET portal_token = substr(md5(random()::text || id::text), 1, 12) WHERE portal_token IS NULL; + ALTER TABLE clients ALTER COLUMN portal_token SET DEFAULT substr(md5(random()::text || clock_timestamp()::text), 1, 12); + END IF; + BEGIN ALTER TABLE users DROP CONSTRAINT IF EXISTS users_phone_key; EXCEPTION WHEN OTHERS THEN NULL; END; BEGIN ALTER TABLE users DROP CONSTRAINT IF EXISTS users_email_key; EXCEPTION WHEN OTHERS THEN NULL; END; END $$; @@ -457,6 +464,68 @@ app.post("/public/assignment/respond", async (req, res) => { } }); +// ========================================== +// 🌐 RUTAS PÚBLICAS: PORTAL DEL CLIENTE (SIN FRICCIÓN) +// ========================================== + +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 + 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 servicesQ = await pool.query(` + SELECT s.id, s.title, s.description, s.scheduled_date, s.scheduled_time, s.created_at, + st.name as status_name, st.color as status_color, st.is_final, + u.full_name as assigned_worker + FROM services s + LEFT JOIN service_statuses st ON s.status_id = st.id + LEFT JOIN users u ON s.assigned_to = u.id + WHERE s.client_id = $1 + ORDER BY s.created_at DESC + `, [clientData.id]); + + res.json({ + ok: true, + client: { name: clientData.full_name, phone: clientData.phone, addresses: clientData.addresses }, + company: { name: clientData.company_name, slug: clientData.company_slug }, + services: servicesQ.rows + }); + } catch (e) { res.status(500).json({ ok: false, error: "Error de servidor" }); } +}); + +app.post("/public/portal/:token/request", async (req, res) => { + const client = await pool.connect(); + try { + const { token } = req.params; + const { description, address } = req.body; + await client.query('BEGIN'); + const clientQ = await client.query("SELECT id, owner_id, full_name, phone FROM clients WHERE portal_token = $1", [token]); + if (clientQ.rowCount === 0) throw new Error("Token inválido"); + const cData = clientQ.rows[0]; + const statusQ = await client.query("SELECT id FROM service_statuses WHERE owner_id=$1 AND is_default=TRUE LIMIT 1", [cData.owner_id]); + const statusId = statusQ.rows[0]?.id; + const insertSvc = await client.query(` + INSERT INTO services (owner_id, client_id, status_id, contact_name, contact_phone, address, description, title, import_source) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'PORTAL_CLIENTE') RETURNING id + `, [cData.owner_id, cData.id, statusId, cData.full_name, cData.phone, address, description, "Nuevo Aviso desde App Cliente"]); + await client.query("INSERT INTO service_logs (service_id, new_status_id, comment) VALUES ($1, $2, 'Aviso reportado por el cliente desde su portal')", [insertSvc.rows[0].id, statusId]); + await client.query('COMMIT'); + res.json({ ok: true, message: "Aviso recibido", service_id: insertSvc.rows[0].id }); + } catch (e) { + await client.query('ROLLBACK'); + res.status(500).json({ ok: false, error: e.message }); + } finally { client.release(); } +}); + // ========================================== // 🔐 RUTAS AUTH Y PRIVADAS ( CRM ORIGINAL ) // ==========================================