Actualizar server.js

This commit is contained in:
2026-02-08 11:37:51 +00:00
parent cc815213d4
commit fce443c93e

View File

@@ -40,7 +40,7 @@ async function autoUpdateDB() {
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
full_name TEXT NOT NULL,
phone TEXT UNIQUE NOT NULL,
phone TEXT NOT NULL, -- ELIMINADO 'UNIQUE' EN DEFINICIÓN (Se gestiona abajo)
email TEXT UNIQUE NOT NULL,
dni TEXT,
address TEXT,
@@ -80,6 +80,15 @@ async function autoUpdateDB() {
);
`);
// --- CORRECCIÓN MULTITENANT ---
// Eliminamos la restricción global de teléfono único si existe para permitir
// que el mismo teléfono esté en varias empresas.
try {
await client.query(`ALTER TABLE users DROP CONSTRAINT IF EXISTS users_phone_key`);
} catch (e) {
// Ignoramos si no existe o hay error menor
}
// Parches
await client.query(`
DO $$ BEGIN
@@ -156,16 +165,18 @@ app.post("/auth/register", async (req, res) => {
const passwordHash = await bcrypt.hash(password, 10);
await client.query('BEGIN');
const checkUser = await client.query("SELECT * FROM users WHERE email = $1 OR phone = $2", [email, p]);
// Verificamos Email (Que debe ser único globalmente para login)
const checkUser = await client.query("SELECT * FROM users WHERE email = $1", [email]);
let userId;
if (checkUser.rowCount > 0) {
const existing = checkUser.rows[0];
// Si existe y ya está verificado, error.
if (existing.is_verified) {
await client.query('ROLLBACK');
return res.status(409).json({ ok: false, error: "Usuario ya registrado." });
return res.status(409).json({ ok: false, error: "El email ya está registrado." });
}
await client.query("UPDATE users SET full_name=$1, address=$2, dni=$3, password_hash=$4 WHERE id=$5", [fullName, address, dni, passwordHash, existing.id]);
await client.query("UPDATE users SET full_name=$1, address=$2, dni=$3, password_hash=$4, phone=$5 WHERE id=$6", [fullName, address, dni, passwordHash, p, existing.id]);
userId = existing.id;
} else {
const insert = await client.query(
@@ -217,15 +228,11 @@ app.post("/auth/login", async (req, res) => {
} catch(e) { res.status(500).json({ ok: false, error: "Error login" }); }
});
// 🆕 NUEVO: RECUPERAR CONTRASEÑA 🆕
// Paso 1: Solicitar código con DNI y Teléfono
// RECUPERAR CONTRASEÑA
app.post("/auth/forgot-password", async (req, res) => {
try {
const { dni, phone } = req.body;
const p = normalizePhone(phone);
// Buscamos usuario por DNI y Teléfono
const userQuery = await pool.query("SELECT id FROM users WHERE dni = $1 AND phone = $2", [dni, p]);
if (userQuery.rowCount === 0) {
@@ -235,9 +242,8 @@ app.post("/auth/forgot-password", async (req, res) => {
const userId = userQuery.rows[0].id;
const code = genCode6();
const codeHash = await bcrypt.hash(code, 10);
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 min
const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
// Guardamos con propósito 'password_reset'
await pool.query("INSERT INTO login_codes (user_id, phone, code_hash, purpose, expires_at) VALUES ($1, $2, $3, 'password_reset', $4)",
[userId, p, codeHash, expiresAt]);
@@ -250,14 +256,12 @@ app.post("/auth/forgot-password", async (req, res) => {
}
});
// Paso 2: Resetear contraseña
app.post("/auth/reset-password", async (req, res) => {
const client = await pool.connect();
try {
const { phone, code, newPassword } = req.body;
const p = normalizePhone(phone);
// Buscar código válido para reset
const codeQuery = await client.query(`
SELECT lc.*, u.id as uid
FROM login_codes lc
@@ -275,7 +279,6 @@ app.post("/auth/reset-password", async (req, res) => {
const valid = await bcrypt.compare(String(code), row.code_hash);
if (!valid) return res.status(400).json({ ok: false, error: "Código incorrecto" });
// Cambiar contraseña
const newHash = await bcrypt.hash(newPassword, 10);
await client.query('BEGIN');
@@ -339,6 +342,7 @@ app.get("/admin/users", authMiddleware, async (req, res) => {
} catch (e) { res.status(500).json({ ok: false, error: "Error listar users" }); }
});
// CREAR USUARIO (CORREGIDO MULTITENANT)
app.post("/admin/users", authMiddleware, async (req, res) => {
const client = await pool.connect();
try {
@@ -346,6 +350,16 @@ app.post("/admin/users", authMiddleware, async (req, res) => {
if (!email || !password || !fullName || !phone) return res.status(400).json({ ok: false, error: "Faltan datos" });
const p = normalizePhone(phone);
const passwordHash = await bcrypt.hash(password, 10);
// NUEVO: Verificamos duplicado SOLO dentro de mi empresa
const checkDup = await client.query(
"SELECT id FROM users WHERE (phone=$1 OR email=$2) AND owner_id=$3",
[p, email, req.user.accountId]
);
if (checkDup.rowCount > 0) {
return res.status(400).json({ ok: false, error: "❌ Este empleado ya existe en tu empresa (Email o Teléfono)." });
}
await client.query('BEGIN');
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`,
@@ -359,9 +373,9 @@ app.post("/admin/users", authMiddleware, async (req, res) => {
res.json({ ok: true, msg: "Usuario creado" });
} catch (e) {
await client.query('ROLLBACK');
if (e.code === '23505') {
if (e.constraint && e.constraint.includes('email')) return res.status(400).json({ ok: false, error: "❌ Email existe." });
if (e.constraint && e.constraint.includes('phone')) return res.status(400).json({ ok: false, error: "❌ Teléfono existe." });
// Mantenemos el check global de email único, pero permitimos teléfonos duplicados en distintas empresas
if (e.code === '23505' && e.constraint.includes('email')) {
return res.status(400).json({ ok: false, error: "❌ El Email ya está usado en el sistema." });
}
res.status(500).json({ ok: false, error: "Error creando usuario" });
} finally { client.release(); }