diff --git a/server.js b/server.js index 1feb88e..c14c5b0 100644 --- a/server.js +++ b/server.js @@ -61,7 +61,7 @@ async function autoUpdateDB() { ); CREATE TABLE IF NOT EXISTS guilds ( id SERIAL PRIMARY KEY, - name TEXT NOT NULL, -- Ya no es UNIQUE globalmente, solo por dueño + name TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS user_guilds ( @@ -92,17 +92,16 @@ async function autoUpdateDB() { END $$; `); - // Parche B: DUEÑO (owner_id) en Usuarios -> Para saber quién es tu jefe + // Parche B: DUEÑO (owner_id) en Usuarios await client.query(` DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='owner_id') THEN ALTER TABLE users ADD COLUMN owner_id INT; - -- Si owner_id es NULL, el usuario es su propio jefe (Cuenta Principal) END IF; END $$; `); - // Parche C: DUEÑO (owner_id) en Gremios -> Para que los gremios sean privados + // Parche C: DUEÑO en Gremios await client.query(` DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='guilds' AND column_name='owner_id') THEN @@ -111,7 +110,7 @@ async function autoUpdateDB() { END $$; `); - // Parche D: DUEÑO (owner_id) en Servicios -> Para que los servicios sean de la empresa + // Parche D: DUEÑO en Servicios await client.query(` DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='services' AND column_name='owner_id') THEN @@ -120,8 +119,7 @@ async function autoUpdateDB() { END $$; `); - // 3. LIMPIEZA DE RESTRICCIONES VIEJAS (Si existían) - // Esto permite que dos empresas diferentes tengan un gremio con el mismo nombre (ej: "FONTANERIA") + // 3. LIMPIEZA DE RESTRICCIONES VIEJAS try { await client.query(`ALTER TABLE guilds DROP CONSTRAINT IF EXISTS guilds_name_key`); } catch (e) {} @@ -147,8 +145,6 @@ function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)) function signToken(user) { // LÓGICA DE AISLAMIENTO: - // Si el usuario tiene owner_id (es empleado), usamos ese ID como "Cuenta Principal". - // Si no tiene (es el jefe), usamos su propio ID. const accountId = user.owner_id || user.id; return jwt.sign( @@ -157,7 +153,7 @@ function signToken(user) { email: user.email, phone: user.phone, role: user.role || 'operario', - accountId: accountId // <--- ESTO ES LA CLAVE DE TODO + accountId: accountId }, JWT_SECRET, { expiresIn: "30d" } @@ -221,7 +217,7 @@ app.post("/auth/register", async (req, res) => { await client.query("DELETE FROM login_codes WHERE user_id = $1", [userId]); await client.query("INSERT INTO login_codes (user_id, phone, code_hash, expires_at) VALUES ($1, $2, $3, $4)", [userId, p, codeHash, expiresAt]); - // Enviar WhatsApp (Simulado si no hay API) + // Enviar WhatsApp if (EVOLUTION_BASE_URL && EVOLUTION_API_KEY) { const url = `${EVOLUTION_BASE_URL.replace(/\/$/, "")}/message/sendText/${EVOLUTION_INSTANCE}`; await fetch(url, { @@ -281,13 +277,12 @@ app.post("/auth/login", async (req, res) => { // GET SERVICIOS (Solo los de MI empresa) app.get("/services", authMiddleware, async (req, res) => { try { - // Filtramos por owner_id (Mi empresa) const q = await pool.query("SELECT * FROM services WHERE owner_id=$1 ORDER BY created_at DESC", [req.user.accountId]); res.json({ ok: true, services: q.rows }); } catch (e) { res.status(500).json({ ok: false, error: "Error servicios" }); } }); -// GET GREMIOS (Solo los de MI empresa) +// GET GREMIOS app.get("/guilds", authMiddleware, async (req, res) => { try { const q = await pool.query("SELECT * FROM guilds WHERE owner_id=$1 ORDER BY name ASC", [req.user.accountId]); @@ -295,13 +290,12 @@ app.get("/guilds", authMiddleware, async (req, res) => { } catch (e) { res.status(500).json({ ok: false, error: e.message }); } }); -// POST GREMIOS (Se guardan en MI empresa) +// POST GREMIOS app.post("/guilds", authMiddleware, async (req, res) => { try { const { name } = req.body; if (!name) return res.status(400).json({ ok: false, error: "Falta nombre" }); - // Guardamos con el accountId (Mi ID o el de mi jefe) const q = await pool.query("INSERT INTO guilds (name, owner_id) VALUES ($1, $2) RETURNING *", [name.toUpperCase(), req.user.accountId]); res.json({ ok: true, guild: q.rows[0] }); } catch (e) { res.status(500).json({ ok: false, error: "Gremio duplicado" }); } @@ -309,7 +303,6 @@ app.post("/guilds", authMiddleware, async (req, res) => { app.delete("/guilds/:id", authMiddleware, async (req, res) => { try { - // Solo borro si el gremio es MÍO await pool.query("DELETE FROM guilds WHERE id = $1 AND owner_id = $2", [req.params.id, req.user.accountId]); res.json({ ok: true }); } catch (e) { res.status(500).json({ ok: false, error: e.message }); } @@ -328,7 +321,6 @@ app.post("/admin/users", authMiddleware, async (req, res) => { await client.query('BEGIN'); // AQUÍ ESTÁ LA MAGIA: owner_id = req.user.accountId - // El nuevo usuario pertenece a mi cuenta (yo soy su jefe) const insert = await client.query( `INSERT INTO users (full_name, email, password_hash, role, phone, is_verified, owner_id) VALUES ($1, $2, $3, $4, $5, TRUE, $6) RETURNING id`, @@ -346,6 +338,15 @@ app.post("/admin/users", authMiddleware, async (req, res) => { res.json({ ok: true, msg: "Usuario creado" }); } catch (e) { await client.query('ROLLBACK'); + + // --- NUEVO: GESTIÓN DE ERRORES MEJORADA --- + if (e.code === '23505') { // Código de clave duplicada + if (e.constraint && e.constraint.includes('email')) { + return res.status(400).json({ ok: false, error: "❌ El email ya está registrado." }); + } + } + // ------------------------------------------- + console.error(e); res.status(500).json({ ok: false, error: "Error creando usuario" }); } finally {