Actualizar server.js

This commit is contained in:
2026-02-20 17:03:36 +00:00
parent 5a3b18d231
commit 10efc22fa2

109
server.js
View File

@@ -131,6 +131,7 @@ async function autoUpdateDB() {
color TEXT DEFAULT 'gray', color TEXT DEFAULT 'gray',
is_default BOOLEAN DEFAULT FALSE, is_default BOOLEAN DEFAULT FALSE,
is_final BOOLEAN DEFAULT FALSE, is_final BOOLEAN DEFAULT FALSE,
is_system BOOLEAN DEFAULT FALSE, -- AÑADIDO: Identificador de estados imborrables
created_at TIMESTAMP DEFAULT NOW() created_at TIMESTAMP DEFAULT NOW()
); );
CREATE TABLE IF NOT EXISTS message_templates ( CREATE TABLE IF NOT EXISTS message_templates (
@@ -258,6 +259,11 @@ async function autoUpdateDB() {
ALTER TABLE guilds ADD COLUMN ia_keywords JSONB DEFAULT '[]'; ALTER TABLE guilds ADD COLUMN ia_keywords JSONB DEFAULT '[]';
END IF; END IF;
-- AÑADIDO: Columna para marcar estados imborrables del sistema
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='service_statuses' AND column_name='is_system') THEN
ALTER TABLE service_statuses ADD COLUMN is_system BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='client_id') THEN ALTER TABLE services ADD COLUMN client_id INT REFERENCES clients(id) ON DELETE SET NULL; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='client_id') THEN ALTER TABLE services ADD COLUMN client_id INT REFERENCES clients(id) ON DELETE SET NULL; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='status_id') THEN ALTER TABLE services ADD COLUMN status_id INT REFERENCES service_statuses(id) ON DELETE SET NULL; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='status_id') THEN ALTER TABLE services ADD COLUMN status_id INT REFERENCES service_statuses(id) ON DELETE SET NULL; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='contact_phone') THEN ALTER TABLE services ADD COLUMN contact_phone TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='contact_phone') THEN ALTER TABLE services ADD COLUMN contact_phone TEXT; END IF;
@@ -831,18 +837,13 @@ app.get("/discovery/keys/:provider", authMiddleware, async (req, res) => {
} catch (e) { res.status(500).json({ ok: false }); } } catch (e) { res.status(500).json({ ok: false }); }
}); });
// AÑADIDO: Ruta para el Panel Operativo (Muestra TODOS los activos, incluidos los de Proveedores) // AÑADIDO Y MEJORADO: Ruta para el Panel Operativo (Muestra TODOS los activos)
app.get("/services/active", authMiddleware, async (req, res) => { app.get("/services/active", authMiddleware, async (req, res) => {
try { try {
const q = await pool.query(` const q = await pool.query(`
SELECT SELECT
s.*, s.*,
u.full_name as assigned_name, u.full_name as assigned_name
CASE
WHEN s.assigned_to IS NULL THEN 'sin_asignar'
WHEN (s.raw_data->>'scheduled_date') IS NULL OR (s.raw_data->>'scheduled_date') = '' THEN 'asignado_operario'
ELSE 'citado'
END as estado_operativo
FROM scraped_services s FROM scraped_services s
LEFT JOIN users u ON s.assigned_to = u.id LEFT JOIN users u ON s.assigned_to = u.id
WHERE s.owner_id = $1 WHERE s.owner_id = $1
@@ -869,7 +870,7 @@ app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => {
...extra, ...extra,
"scheduled_date": date || "", "scheduled_date": date || "",
"scheduled_time": time || "", "scheduled_time": time || "",
"status_operativo": status_operativo || "citado" "status_operativo": status_operativo
}; };
// 3. Guardamos el JSON completo de vuelta // 3. Guardamos el JSON completo de vuelta
@@ -976,9 +977,70 @@ app.put("/clients/:id", authMiddleware, async (req, res) => {
} catch (e) { res.status(500).json({ ok: false }); } } catch (e) { res.status(500).json({ ok: false }); }
}); });
app.get("/statuses", authMiddleware, async (req, res) => { try { let q = await pool.query("SELECT * FROM service_statuses WHERE owner_id=$1 ORDER BY id ASC", [req.user.accountId]); if (q.rowCount === 0) { const defaults = [{name:'Pendiente',c:'gray',d:true,f:false},{name:'En Proceso',c:'blue',d:false,f:false},{name:'Terminado',c:'green',d:false,f:true},{name:'Cancelado',c:'red',d:false,f:true}]; for (const s of defaults) await pool.query("INSERT INTO service_statuses (owner_id,name,color,is_default,is_final) VALUES ($1,$2,$3,$4,$5)", [req.user.accountId,s.name,s.c,s.d,s.f]); q = await pool.query("SELECT * FROM service_statuses WHERE owner_id=$1 ORDER BY id ASC", [req.user.accountId]); } res.json({ ok: true, statuses: q.rows }); } catch (e) { res.status(500).json({ ok: false }); } }); // ==========================================
app.post("/statuses", authMiddleware, async (req, res) => { try { const { name, color } = req.body; await pool.query("INSERT INTO service_statuses (owner_id, name, color) VALUES ($1, $2, $3)", [req.user.accountId, name, color || 'gray']); res.json({ ok: true }); } catch(e) { res.status(500).json({ ok: false }); } }); // 🎨 RUTAS DE ESTADOS DEL SISTEMA (SAAS COMPLETO)
app.delete("/statuses/:id", authMiddleware, async (req, res) => { const client = await pool.connect(); try { const statusId = req.params.id; const check = await client.query("SELECT COUNT(*) FROM services WHERE status_id = $1 AND owner_id = $2", [statusId, req.user.accountId]); if (parseInt(check.rows[0].count) > 0) return res.status(400).json({ ok: false, error: "En uso" }); await client.query("DELETE FROM service_statuses WHERE id=$1 AND owner_id=$2", [statusId, req.user.accountId]); res.json({ ok: true }); } catch(e) { res.status(500).json({ ok: false }); } finally { client.release(); } }); // ==========================================
app.get("/statuses", authMiddleware, async (req, res) => {
try {
let q = await pool.query("SELECT * FROM service_statuses WHERE owner_id=$1 ORDER BY id ASC", [req.user.accountId]);
// 🚀 MAGIA SAAS: Si el usuario es nuevo, inyectamos los 9 estados base obligatorios.
if (q.rowCount === 0) {
const defaults = [
{name:'Pendiente de Asignar', c:'gray', d:true, f:false, sys:true},
{name:'Asignado', c:'blue', d:false, f:false, sys:true},
{name:'Pendiente de Cita', c:'amber', d:false, f:false, sys:true},
{name:'Citado', c:'emerald', d:false, f:false, sys:true},
{name:'De Camino', c:'indigo', d:false, f:false, sys:true},
{name:'Trabajando', c:'amber', d:false, f:false, sys:true},
{name:'Incidencia', c:'red', d:false, f:false, sys:true},
{name:'Terminado', c:'purple', d:false, f:true, sys:true},
{name:'Anulado', c:'gray', d:false, f:true, sys:true}
];
for (const s of defaults) {
await pool.query("INSERT INTO service_statuses (owner_id,name,color,is_default,is_final,is_system) VALUES ($1,$2,$3,$4,$5,$6)", [req.user.accountId,s.name,s.c,s.d,s.f,s.sys]);
}
q = await pool.query("SELECT * FROM service_statuses WHERE owner_id=$1 ORDER BY id ASC", [req.user.accountId]);
}
res.json({ ok: true, statuses: q.rows });
} catch (e) { res.status(500).json({ ok: false }); }
});
app.post("/statuses", authMiddleware, async (req, res) => {
try {
const { name, color } = req.body;
await pool.query("INSERT INTO service_statuses (owner_id, name, color, is_system) VALUES ($1, $2, $3, false)", [req.user.accountId, name, color || 'gray']);
res.json({ ok: true });
} catch(e) { res.status(500).json({ ok: false }); }
});
app.delete("/statuses/:id", authMiddleware, async (req, res) => {
const client = await pool.connect();
try {
const statusId = req.params.id;
// 1. Comprobamos si es del sistema
const sysCheck = await client.query("SELECT is_system FROM service_statuses WHERE id = $1 AND owner_id = $2", [statusId, req.user.accountId]);
if (sysCheck.rowCount > 0 && sysCheck.rows[0].is_system) {
return res.status(400).json({ ok: false, error: "Este es un estado esencial del sistema y no se puede borrar." });
}
// 2. Comprobamos si está en uso (esto hay que cruzarlo con scraped_services ahora que guardamos el ID ahí)
const checkSvc = await client.query("SELECT COUNT(*) FROM services WHERE status_id = $1 AND owner_id = $2", [statusId, req.user.accountId]);
const checkScrap = await client.query("SELECT COUNT(*) FROM scraped_services WHERE raw_data->>'status_operativo' = $1 AND owner_id = $2", [statusId, req.user.accountId]);
if (parseInt(checkSvc.rows[0].count) > 0 || parseInt(checkScrap.rows[0].count) > 0) {
return res.status(400).json({ ok: false, error: "No puedes borrar un estado que tiene servicios asignados." });
}
await client.query("DELETE FROM service_statuses WHERE id=$1 AND owner_id=$2", [statusId, req.user.accountId]);
res.json({ ok: true });
} catch(e) {
res.status(500).json({ ok: false, error: e.message });
} finally {
client.release();
}
});
app.get("/clients/search", authMiddleware, async (req, res) => { try { const { phone } = req.query; const p = normalizePhone(phone); if(!p) return res.json({ok:true,client:null}); const q = await pool.query("SELECT * FROM clients WHERE phone=$1 AND owner_id=$2 LIMIT 1", [p, req.user.accountId]); res.json({ ok: true, client: q.rows[0] || null }); } catch (e) { res.status(500).json({ ok: false }); } }); app.get("/clients/search", authMiddleware, async (req, res) => { try { const { phone } = req.query; const p = normalizePhone(phone); if(!p) return res.json({ok:true,client:null}); const q = await pool.query("SELECT * FROM clients WHERE phone=$1 AND owner_id=$2 LIMIT 1", [p, req.user.accountId]); res.json({ ok: true, client: q.rows[0] || null }); } catch (e) { res.status(500).json({ ok: false }); } });
app.get("/companies", authMiddleware, async (req, res) => { try { const q = await pool.query("SELECT * FROM companies WHERE owner_id=$1 ORDER BY name ASC", [req.user.accountId]); res.json({ ok: true, companies: q.rows }); } catch (e) { res.status(500).json({ ok: false }); } }); app.get("/companies", authMiddleware, async (req, res) => { try { const q = await pool.query("SELECT * FROM companies WHERE owner_id=$1 ORDER BY name ASC", [req.user.accountId]); res.json({ ok: true, companies: q.rows }); } catch (e) { res.status(500).json({ ok: false }); } });
@@ -988,33 +1050,14 @@ app.delete("/companies/:id", authMiddleware, async (req, res) => { try { await p
// AÑADIDO: Filtro estricto para que solo devuelva operarios que estén en estado 'active' // AÑADIDO: Filtro estricto para que solo devuelva operarios que estén en estado 'active'
app.get("/operators", authMiddleware, async (req, res) => { app.get("/operators", authMiddleware, async (req, res) => {
try { try {
// Si nos pasan un guild_id, filtramos también por gremio
const guildId = req.query.guild_id; const guildId = req.query.guild_id;
let query = ` let query = `SELECT u.id, u.full_name, u.zones FROM users u WHERE u.owner_id=$1 AND u.role='operario' AND u.status='active'`;
SELECT u.id, u.full_name, u.zones
FROM users u
WHERE u.owner_id=$1 AND u.role='operario' AND u.status='active'
`;
const params = [req.user.accountId]; const params = [req.user.accountId];
if (guildId) { query = `SELECT u.id, u.full_name, u.zones FROM users u JOIN user_guilds ug ON u.id = ug.user_id WHERE u.owner_id=$1 AND u.role='operario' AND u.status='active' AND ug.guild_id=$2`; params.push(guildId); }
if (guildId) {
query = `
SELECT u.id, u.full_name, u.zones
FROM users u
JOIN user_guilds ug ON u.id = ug.user_id
WHERE u.owner_id=$1 AND u.role='operario' AND u.status='active' AND ug.guild_id=$2
`;
params.push(guildId);
}
query += ` ORDER BY u.full_name ASC`; query += ` ORDER BY u.full_name ASC`;
const q = await pool.query(query, params); const q = await pool.query(query, params);
res.json({ ok: true, operators: q.rows }); res.json({ ok: true, operators: q.rows });
} catch (e) { } catch (e) { res.status(500).json({ ok: false }); }
console.error("Error al cargar operarios:", e);
res.status(500).json({ ok: false });
}
}); });
app.get("/zones", authMiddleware, async (req, res) => { try { const q = await pool.query("SELECT * FROM zones WHERE owner_id=$1 ORDER BY name ASC", [req.user.accountId]); res.json({ ok: true, zones: q.rows }); } catch (e) { res.status(500).json({ ok: false }); } }); app.get("/zones", authMiddleware, async (req, res) => { try { const q = await pool.query("SELECT * FROM zones WHERE owner_id=$1 ORDER BY name ASC", [req.user.accountId]); res.json({ ok: true, zones: q.rows }); } catch (e) { res.status(500).json({ ok: false }); } });