Actualizar server.js
This commit is contained in:
129
server.js
129
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;
|
||||
|
||||
Reference in New Issue
Block a user