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