Actualizar server.js

This commit is contained in:
2026-02-08 22:03:28 +00:00
parent 6a56ac2ec9
commit c66a0ae086

129
server.js
View File

@@ -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;