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() { async function autoUpdateDB() {
const client = await pool.connect(); const client = await pool.connect();
@@ -76,6 +76,10 @@ 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 (
@@ -97,6 +101,22 @@ async function autoUpdateDB() {
is_final BOOLEAN DEFAULT FALSE, is_final BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW() 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 ( CREATE TABLE IF NOT EXISTS services (
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,
@@ -133,33 +153,10 @@ async function autoUpdateDB() {
); );
`); `);
// 2. NUEVAS TABLAS GEOGRÁFICAS (PROVINCIAS Y ZONAS) // SEEDING PROVINCIAS
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"); const provCheck = await client.query("SELECT COUNT(*) FROM provinces");
if (parseInt(provCheck.rows[0].count) === 0) { 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 = [ const spainProvinces = [
"Álava", "Albacete", "Alicante", "Almería", "Asturias", "Ávila", "Badajoz", "Barcelona", "Burgos", "Cáceres", "Á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", "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", "Santa Cruz de Tenerife", "Segovia", "Sevilla", "Soria", "Tarragona", "Teruel", "Toledo", "Valencia",
"Valladolid", "Vizcaya", "Zamora", "Zaragoza", "Ceuta", "Melilla" "Valladolid", "Vizcaya", "Zamora", "Zaragoza", "Ceuta", "Melilla"
]; ];
for (const provName of spainProvinces) { for (const provName of spainProvinces) {
const resProv = await client.query("INSERT INTO provinces (name) VALUES ($1) RETURNING id", [provName]); await client.query("INSERT INTO provinces (name) VALUES ($1)", [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 // PARCHE REPARACIÓN (Fuerza columnas)
await client.query(` await client.query(`
DO $$ DO $$ BEGIN
BEGIN
-- 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;
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='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;
@@ -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='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='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; 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_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; BEGIN ALTER TABLE users DROP CONSTRAINT IF EXISTS users_email_key; EXCEPTION WHEN OTHERS THEN NULL; END;
END $$; END $$;
`); `);
console.log("✅ DB Sincronizada."); console.log("✅ DB Sincronizada.");
} catch (e) { } catch (e) { console.error("❌ Error DB:", e); } finally { client.release(); }
console.error("❌ Error DB:", e);
} finally {
client.release();
}
} }
// HELPERS // HELPERS
@@ -301,6 +276,51 @@ app.get("/zones", authMiddleware, async (req, res) => {
} catch (e) { res.status(500).json({ ok: false }); } } 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 // DATOS MAESTROS
app.get("/statuses", authMiddleware, async (req, res) => { app.get("/statuses", authMiddleware, async (req, res) => {
try { 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.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 // OBTENER OPERARIOS POR GREMIO (Y ZONA EN EL FUTURO)
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;
@@ -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 { 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 // SANITIZAR ENTEROS (Evitar error invalid input syntax for type integer: "")
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;
@@ -437,6 +457,7 @@ 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;