Actualizar server.js

This commit is contained in:
2026-02-08 15:10:17 +00:00
parent be4cdb11c3
commit 6a56ac2ec9

View File

@@ -29,14 +29,14 @@ const pool = new Pool({
}); });
// ========================================== // ==========================================
// 🧠 AUTO-ACTUALIZACIÓN DB // 🧠 AUTO-ACTUALIZACIÓN DB Y CARGA DE DATOS GEOGRÁFICOS
// ========================================== // ==========================================
async function autoUpdateDB() { async function autoUpdateDB() {
const client = await pool.connect(); const client = await pool.connect();
try { try {
console.log("🔄 Verificando salud de la base de datos..."); console.log("🔄 Verificando salud de la base de datos...");
// 1. CREAR TABLAS (Si no existen) // 1. ESTRUCTURA BÁSICA
await client.query(` await client.query(`
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
@@ -76,10 +76,6 @@ async function autoUpdateDB() {
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
owner_id INT REFERENCES users(id) ON DELETE CASCADE, owner_id INT REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL, name TEXT NOT NULL,
cif TEXT,
email TEXT,
phone TEXT,
address TEXT,
created_at TIMESTAMP DEFAULT NOW() created_at TIMESTAMP DEFAULT NOW()
); );
CREATE TABLE IF NOT EXISTS clients ( CREATE TABLE IF NOT EXISTS clients (
@@ -137,31 +133,80 @@ async function autoUpdateDB() {
); );
`); `);
// 2. PARCHE DE REPARACIÓN // 2. NUEVAS TABLAS GEOGRÁFICAS (PROVINCIAS Y ZONAS)
await client.query(`
CREATE TABLE IF NOT EXISTS provinces (
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS zones (
id SERIAL PRIMARY KEY,
province_id INT REFERENCES provinces(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- Ej: Madrid Capital, Zona Sur, etc.
owner_id INT, -- NULL = Zona del sistema, ID = Zona personalizada
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS user_zones (
user_id INT REFERENCES users(id) ON DELETE CASCADE,
zone_id INT REFERENCES zones(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, zone_id)
);
`);
// 3. SEEDING (POBLAR DATOS DE ESPAÑA SI ESTÁ VACÍO)
const provCheck = await client.query("SELECT COUNT(*) FROM provinces");
if (parseInt(provCheck.rows[0].count) === 0) {
console.log("🇪🇸 Poblando base de datos con Provincias y Zonas de España...");
const spainProvinces = [
"Álava", "Albacete", "Alicante", "Almería", "Asturias", "Ávila", "Badajoz", "Barcelona", "Burgos", "Cáceres",
"Cádiz", "Cantabria", "Castellón", "Ciudad Real", "Córdoba", "Cuenca", "Girona", "Granada", "Guadalajara",
"Guipúzcoa", "Huelva", "Huesca", "Illes Balears", "Jaén", "La Coruña", "La Rioja", "Las Palmas", "León",
"Lleida", "Lugo", "Madrid", "Málaga", "Murcia", "Navarra", "Ourense", "Palencia", "Pontevedra", "Salamanca",
"Santa Cruz de Tenerife", "Segovia", "Sevilla", "Soria", "Tarragona", "Teruel", "Toledo", "Valencia",
"Valladolid", "Vizcaya", "Zamora", "Zaragoza", "Ceuta", "Melilla"
];
for (const provName of spainProvinces) {
const resProv = await client.query("INSERT INTO provinces (name) VALUES ($1) RETURNING id", [provName]);
const provId = resProv.rows[0].id;
// Zonas automáticas básicas para seguros
const zones = ["Capital", "Provincia (Resto)"];
// Zonas detalladas para grandes capitales
if (["Madrid", "Barcelona", "Valencia", "Sevilla", "Málaga", "Alicante", "Vizcaya"].includes(provName)) {
zones.push("Área Metropolitana", "Zona Norte", "Zona Sur", "Zona Este", "Zona Oeste");
}
for (const z of zones) {
await client.query("INSERT INTO zones (province_id, name, owner_id) VALUES ($1, $2, NULL)", [provId, z]);
}
}
console.log("✅ Datos geográficos cargados.");
}
// 4. PARCHE DE REPARACIÓN
await client.query(` await client.query(`
DO $$ DO $$
BEGIN BEGIN
-- Client ID -- Columnas faltantes previas
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;
-- Status ID
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;
-- Guild & Assigned
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='guild_id') THEN ALTER TABLE services ADD COLUMN guild_id INT REFERENCES guilds(id) ON DELETE SET NULL; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='guild_id') THEN ALTER TABLE services ADD COLUMN guild_id INT REFERENCES guilds(id) ON DELETE SET NULL; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='assigned_to') THEN ALTER TABLE services ADD COLUMN assigned_to INT REFERENCES users(id) ON DELETE SET NULL; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='assigned_to') THEN ALTER TABLE services ADD COLUMN assigned_to INT REFERENCES users(id) ON DELETE SET NULL; END IF;
-- Contact
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;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='contact_name') THEN ALTER TABLE services ADD COLUMN contact_name TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='contact_name') THEN ALTER TABLE services ADD COLUMN contact_name TEXT; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='address') THEN ALTER TABLE services ADD COLUMN address TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='address') THEN ALTER TABLE services ADD COLUMN address TEXT; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='email') THEN ALTER TABLE services ADD COLUMN email TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='email') THEN ALTER TABLE services ADD COLUMN email TEXT; END IF;
-- Details
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='title') THEN ALTER TABLE services ADD COLUMN title TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='title') THEN ALTER TABLE services ADD COLUMN title TEXT; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='description') THEN ALTER TABLE services ADD COLUMN description TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='description') THEN ALTER TABLE services ADD COLUMN description TEXT; END IF;
-- Schedule
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='scheduled_date') THEN ALTER TABLE services ADD COLUMN scheduled_date DATE DEFAULT CURRENT_DATE; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='scheduled_date') THEN ALTER TABLE services ADD COLUMN scheduled_date DATE DEFAULT CURRENT_DATE; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='scheduled_time') THEN ALTER TABLE services ADD COLUMN scheduled_time TIME DEFAULT CURRENT_TIME; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='scheduled_time') THEN ALTER TABLE services ADD COLUMN scheduled_time TIME DEFAULT CURRENT_TIME; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='duration_minutes') THEN ALTER TABLE services ADD COLUMN duration_minutes INT DEFAULT 30; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='duration_minutes') THEN ALTER TABLE services ADD COLUMN duration_minutes INT DEFAULT 30; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='is_urgent') THEN ALTER TABLE services ADD COLUMN is_urgent BOOLEAN DEFAULT FALSE; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='is_urgent') THEN ALTER TABLE services ADD COLUMN is_urgent BOOLEAN DEFAULT FALSE; END IF;
-- Company & Notes
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='company_id') THEN ALTER TABLE services ADD COLUMN company_id INT REFERENCES companies(id) ON DELETE SET NULL; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='company_id') THEN ALTER TABLE services ADD COLUMN company_id INT REFERENCES companies(id) ON DELETE SET NULL; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='is_company') THEN ALTER TABLE services ADD COLUMN is_company BOOLEAN DEFAULT FALSE; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='is_company') THEN ALTER TABLE services ADD COLUMN is_company BOOLEAN DEFAULT FALSE; END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='company_ref') THEN ALTER TABLE services ADD COLUMN company_ref TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='company_ref') THEN ALTER TABLE services ADD COLUMN company_ref TEXT; END IF;
@@ -173,7 +218,7 @@ async function autoUpdateDB() {
END $$; END $$;
`); `);
console.log("✅ DB Sincronizada y Reparada."); console.log("✅ DB Sincronizada.");
} catch (e) { } catch (e) {
console.error("❌ Error DB:", e); console.error("❌ Error DB:", e);
} finally { } finally {
@@ -237,6 +282,25 @@ app.post("/auth/reset-password", async (req, res) => {
try { const { phone, code, newPassword } = req.body; const p = normalizePhone(phone); const q = await client.query(`SELECT lc.*, u.id as uid FROM login_codes lc JOIN users u ON lc.user_id=u.id WHERE lc.phone=$1 AND lc.purpose='password_reset' AND lc.consumed_at IS NULL AND lc.expires_at>NOW() ORDER BY lc.created_at DESC LIMIT 1`, [p]); if(q.rowCount===0) return res.status(400).json({ok:false}); const row=q.rows[0]; if(!(await bcrypt.compare(String(code), row.code_hash))) return res.status(400).json({ok:false}); const hash=await bcrypt.hash(newPassword, 10); await client.query('BEGIN'); await client.query("UPDATE users SET password_hash=$1 WHERE id=$2",[hash, row.uid]); await client.query("UPDATE login_codes SET consumed_at=NOW() WHERE id=$1",[row.id]); await client.query('COMMIT'); res.json({ok:true}); } catch(e){await client.query('ROLLBACK'); res.status(500).json({ok:false});} finally{client.release();} try { const { phone, code, newPassword } = req.body; const p = normalizePhone(phone); const q = await client.query(`SELECT lc.*, u.id as uid FROM login_codes lc JOIN users u ON lc.user_id=u.id WHERE lc.phone=$1 AND lc.purpose='password_reset' AND lc.consumed_at IS NULL AND lc.expires_at>NOW() ORDER BY lc.created_at DESC LIMIT 1`, [p]); if(q.rowCount===0) return res.status(400).json({ok:false}); const row=q.rows[0]; if(!(await bcrypt.compare(String(code), row.code_hash))) return res.status(400).json({ok:false}); const hash=await bcrypt.hash(newPassword, 10); await client.query('BEGIN'); await client.query("UPDATE users SET password_hash=$1 WHERE id=$2",[hash, row.uid]); await client.query("UPDATE login_codes SET consumed_at=NOW() WHERE id=$1",[row.id]); await client.query('COMMIT'); res.json({ok:true}); } catch(e){await client.query('ROLLBACK'); res.status(500).json({ok:false});} finally{client.release();}
}); });
// =========================
// NUEVAS RUTAS GEOGRÁFICAS
// =========================
app.get("/provinces", authMiddleware, async (req, res) => {
try { const q = await pool.query("SELECT * FROM provinces ORDER BY name ASC"); res.json({ ok: true, provinces: q.rows }); } catch (e) { res.status(500).json({ ok: false }); }
});
app.get("/zones", authMiddleware, async (req, res) => {
try {
const { province_id } = req.query;
let query = "SELECT * FROM zones WHERE (owner_id IS NULL OR owner_id=$1)";
const params = [req.user.accountId];
if(province_id) { query += " AND province_id=$2"; params.push(province_id); }
query += " ORDER BY name ASC";
const q = await pool.query(query, params);
res.json({ ok: true, zones: q.rows });
} catch (e) { res.status(500).json({ ok: false }); }
});
// DATOS MAESTROS // DATOS MAESTROS
app.get("/statuses", authMiddleware, async (req, res) => { app.get("/statuses", authMiddleware, async (req, res) => {
try { try {
@@ -255,6 +319,7 @@ app.get("/clients/search", authMiddleware, async (req, res) => {
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 }); } });
app.post("/companies", authMiddleware, async (req, res) => { try { const { name } = req.body; await pool.query("INSERT INTO companies (name, owner_id) VALUES ($1, $2)", [name, req.user.accountId]); res.json({ ok: true }); } catch (e) { res.status(500).json({ ok: false }); } }); app.post("/companies", authMiddleware, async (req, res) => { try { const { name } = req.body; await pool.query("INSERT INTO companies (name, owner_id) VALUES ($1, $2)", [name, req.user.accountId]); res.json({ ok: true }); } catch (e) { res.status(500).json({ ok: false }); } });
// OBTENER OPERARIOS POR GREMIO
app.get("/operators", authMiddleware, async (req, res) => { app.get("/operators", authMiddleware, async (req, res) => {
try { try {
const { guild_id } = req.query; const { guild_id } = req.query;
@@ -313,7 +378,7 @@ app.post("/services", authMiddleware, async (req, res) => {
const { phone, name, address, email, description, scheduled_date, scheduled_time, duration, is_urgent, is_company, company_id, company_ref, internal_notes, client_notes, status_id, guild_id, assigned_to } = req.body; const { phone, name, address, email, description, scheduled_date, scheduled_time, duration, is_urgent, is_company, company_id, company_ref, internal_notes, client_notes, status_id, guild_id, assigned_to } = req.body;
const p = normalizePhone(phone); const p = normalizePhone(phone);
// SANITIZAR ENTEROS (Evitar error invalid input syntax for type integer: "") // SANITIZAR
const safeDuration = (duration === "" || duration === null) ? 30 : duration; const safeDuration = (duration === "" || duration === null) ? 30 : duration;
const safeCompanyId = (company_id === "" || company_id === null) ? null : company_id; const safeCompanyId = (company_id === "" || company_id === null) ? null : company_id;
const safeGuildId = (guild_id === "" || guild_id === null) ? null : guild_id; const safeGuildId = (guild_id === "" || guild_id === null) ? null : guild_id;
@@ -372,7 +437,6 @@ app.put("/services/:id", authMiddleware, async (req, res) => {
guild_id, assigned_to guild_id, assigned_to
} = req.body; } = req.body;
// SANITIZAR ENTEROS
const safeDuration = (duration === "" || duration === null) ? 30 : duration; const safeDuration = (duration === "" || duration === null) ? 30 : duration;
const safeCompanyId = (company_id === "" || company_id === null) ? null : company_id; const safeCompanyId = (company_id === "" || company_id === null) ? null : company_id;
const safeGuildId = (guild_id === "" || guild_id === null) ? null : guild_id; const safeGuildId = (guild_id === "" || guild_id === null) ? null : guild_id;