From c66a0ae08612ffb4f7f640cdf432616f3877164b Mon Sep 17 00:00:00 2001 From: marsalva Date: Sun, 8 Feb 2026 22:03:28 +0000 Subject: [PATCH] Actualizar server.js --- server.js | 129 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 54 deletions(-) diff --git a/server.js b/server.js index ff1b1d9..eb110c9 100644 --- a/server.js +++ b/server.js @@ -29,7 +29,7 @@ const pool = new Pool({ }); // ========================================== -// 🧠 AUTO-ACTUALIZACIÓN DB Y CARGA DE DATOS GEOGRÁFICOS +// 🧠 AUTO-ACTUALIZACIÓN DB // ========================================== async function autoUpdateDB() { const client = await pool.connect(); @@ -76,6 +76,10 @@ async function autoUpdateDB() { id SERIAL PRIMARY KEY, owner_id INT REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, + cif TEXT, + email TEXT, + phone TEXT, + address TEXT, created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS clients ( @@ -97,6 +101,22 @@ async function autoUpdateDB() { is_final BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW() ); + 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, + owner_id INT, + 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) + ); CREATE TABLE IF NOT EXISTS services ( id SERIAL PRIMARY KEY, owner_id INT REFERENCES users(id) ON DELETE CASCADE, @@ -133,33 +153,10 @@ async function autoUpdateDB() { ); `); - // 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) + // SEEDING PROVINCIAS 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..."); - + console.log("🇪🇸 Poblando Provincias..."); 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", @@ -168,31 +165,14 @@ async function autoUpdateDB() { "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]); - } + await client.query("INSERT INTO provinces (name) VALUES ($1)", [provName]); } - console.log("✅ Datos geográficos cargados."); } - // 4. PARCHE DE REPARACIÓN + // PARCHE REPARACIÓN (Fuerza columnas) await client.query(` - DO $$ - BEGIN - -- Columnas faltantes previas + DO $$ BEGIN 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='guild_id') THEN ALTER TABLE services ADD COLUMN guild_id INT REFERENCES guilds(id) ON DELETE SET NULL; END IF; @@ -212,18 +192,13 @@ async function autoUpdateDB() { 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='internal_notes') THEN ALTER TABLE services ADD COLUMN internal_notes TEXT; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='client_notes') THEN ALTER TABLE services ADD COLUMN client_notes TEXT; 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 $$; `); console.log("✅ DB Sincronizada."); - } catch (e) { - console.error("❌ Error DB:", e); - } finally { - client.release(); - } + } catch (e) { console.error("❌ Error DB:", e); } finally { client.release(); } } // HELPERS @@ -301,6 +276,51 @@ app.get("/zones", authMiddleware, async (req, res) => { } catch (e) { res.status(500).json({ ok: false }); } }); +app.post("/zones", authMiddleware, async (req, res) => { + try { + const { province_id, name } = req.body; + if (!province_id || !name) return res.status(400).json({ ok: false, error: "Faltan datos" }); + const q = await pool.query("INSERT INTO zones (province_id, name, owner_id) VALUES ($1, $2, $3) RETURNING *", [province_id, name, req.user.accountId]); + res.json({ ok: true, zone: q.rows[0] }); + } catch (e) { res.status(500).json({ ok: false, error: e.message }); } +}); + +app.delete("/zones/:id", authMiddleware, async (req, res) => { + try { + const q = await pool.query("DELETE FROM zones WHERE id=$1 AND owner_id=$2", [req.params.id, req.user.accountId]); + if (q.rowCount === 0) return res.status(403).json({ ok: false, error: "No puedes borrar esta zona" }); + res.json({ ok: true }); + } catch (e) { res.status(500).json({ ok: false }); } +}); + +app.get("/zones/:id/operators", authMiddleware, async (req, res) => { + try { + const q = await pool.query("SELECT user_id FROM user_zones WHERE zone_id=$1", [req.params.id]); + const assignedIds = q.rows.map(r => r.user_id); + res.json({ ok: true, assignedIds }); + } catch (e) { res.status(500).json({ ok: false }); } +}); + +app.post("/zones/:id/assign", authMiddleware, async (req, res) => { + const client = await pool.connect(); + try { + const { operator_ids } = req.body; + const zoneId = req.params.id; + await client.query('BEGIN'); + await client.query("DELETE FROM user_zones WHERE zone_id=$1 AND user_id IN (SELECT id FROM users WHERE owner_id=$2)", [zoneId, req.user.accountId]); + if (operator_ids && Array.isArray(operator_ids)) { + for (const uid of operator_ids) { + const check = await client.query("SELECT id FROM users WHERE id=$1 AND owner_id=$2", [uid, req.user.accountId]); + if (check.rowCount > 0) { + await client.query("INSERT INTO user_zones (user_id, zone_id) VALUES ($1, $2)", [uid, zoneId]); + } + } + } + await client.query('COMMIT'); + res.json({ ok: true }); + } catch (e) { await client.query('ROLLBACK'); res.status(500).json({ ok: false }); } finally { client.release(); } +}); + // DATOS MAESTROS app.get("/statuses", authMiddleware, async (req, res) => { try { @@ -319,7 +339,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.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 +// OBTENER OPERARIOS POR GREMIO (Y ZONA EN EL FUTURO) app.get("/operators", authMiddleware, async (req, res) => { try { const { guild_id } = req.query; @@ -378,7 +398,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 p = normalizePhone(phone); - // SANITIZAR + // SANITIZAR ENTEROS (Evitar error invalid input syntax for type integer: "") const safeDuration = (duration === "" || duration === null) ? 30 : duration; const safeCompanyId = (company_id === "" || company_id === null) ? null : company_id; const safeGuildId = (guild_id === "" || guild_id === null) ? null : guild_id; @@ -437,6 +457,7 @@ app.put("/services/:id", authMiddleware, async (req, res) => { guild_id, assigned_to } = req.body; + // SANITIZAR ENTEROS const safeDuration = (duration === "" || duration === null) ? 30 : duration; const safeCompanyId = (company_id === "" || company_id === null) ? null : company_id; const safeGuildId = (guild_id === "" || guild_id === null) ? null : guild_id;