From 4d4dee84be3ba172d5cd3b756a756c6179d94231 Mon Sep 17 00:00:00 2001 From: marsalva Date: Thu, 12 Feb 2026 21:56:13 +0000 Subject: [PATCH] Actualizar server.js --- server.js | 82 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/server.js b/server.js index 7f34cee..ef0a44d 100644 --- a/server.js +++ b/server.js @@ -16,6 +16,7 @@ const { JWT_SECRET, EVOLUTION_BASE_URL, EVOLUTION_API_KEY, + EVOLUTION_INSTANCE, // <--- ¡FALTABA ESTO! (La instancia que envía los códigos) } = process.env; // --- DIAGNÓSTICO DE INICIO --- @@ -24,17 +25,19 @@ console.log("🔧 INICIANDO SERVIDOR INTEGRA REPARA"); console.log("------------------------------------------------"); if (!DATABASE_URL) console.error("❌ FALTA: DATABASE_URL"); if (!JWT_SECRET) console.error("❌ FALTA: JWT_SECRET"); + if (!EVOLUTION_BASE_URL) { - console.error("⚠️ AVISO: No has puesto EVOLUTION_BASE_URL (WhatsApp no funcionará)"); + console.error("⚠️ AVISO: No has puesto EVOLUTION_BASE_URL"); } else { - console.log("✅ Evolution URL detectada:", EVOLUTION_BASE_URL); + console.log("✅ Evolution URL:", EVOLUTION_BASE_URL); } -if (!EVOLUTION_API_KEY) { - console.error("⚠️ AVISO: No has puesto EVOLUTION_API_KEY (WhatsApp no funcionará)"); + +if (!EVOLUTION_INSTANCE) { + console.error("⚠️ AVISO: No has puesto EVOLUTION_INSTANCE (No se enviarán códigos de registro)"); } else { - // Mostramos solo los primeros 4 caracteres por seguridad - console.log("✅ Evolution API Key detectada:", EVOLUTION_API_KEY.substring(0, 4) + "..."); + console.log("✅ Instancia de Notificaciones:", EVOLUTION_INSTANCE); } + console.log("------------------------------------------------"); if (!DATABASE_URL || !JWT_SECRET) { @@ -207,26 +210,61 @@ async function autoUpdateDB() { // HELPERS function normalizePhone(phone) { let p = String(phone || "").trim().replace(/\s+/g, "").replace(/-/g, ""); if (!p) return ""; if (!p.startsWith("+") && /^[6789]\d{8}$/.test(p)) return "+34" + p; return p; } +function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)); } function signToken(user) { const accountId = user.owner_id || user.id; return jwt.sign({ sub: user.id, email: user.email, phone: user.phone, role: user.role || 'operario', accountId }, JWT_SECRET, { expiresIn: "30d" }); } function authMiddleware(req, res, next) { const h = req.headers.authorization || ""; const token = h.startsWith("Bearer ") ? h.slice(7) : ""; if (!token) return res.status(401).json({ ok: false, error: "No token" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { return res.status(401).json({ ok: false, error: "Token inválido" }); } } -function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)); } -// --- WHATSAPP HELPER (Automatización) --- +// --- FUNCIÓN DE ENVÍO DE CÓDIGO (REGISTRO) --- +async function sendWhatsAppCode(phone, code) { + if (!EVOLUTION_BASE_URL || !EVOLUTION_API_KEY || !EVOLUTION_INSTANCE) { + console.error("❌ ERROR: Faltan variables para enviar WhatsApp (URL, APIKEY o INSTANCE)"); + return; + } + + // Aseguramos que la URL no tenga barra al final y añadimos la instancia + const url = `${EVOLUTION_BASE_URL.replace(/\/$/, "")}/message/sendText/${EVOLUTION_INSTANCE}`; + const number = phone.replace("+", ""); // Quitar el + para la API + + console.log(`📤 Enviando código a ${number} desde instancia ${EVOLUTION_INSTANCE}...`); + + try { + const res = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "apikey": EVOLUTION_API_KEY + }, + body: JSON.stringify({ + number: number, + text: `🔐 Código de verificación IntegraRepara: *${code}*` + }) + }); + + if (!res.ok) { + const err = await res.text(); + console.error("❌ Error enviando WhatsApp:", res.status, err); + } else { + console.log("✅ WhatsApp enviado correctamente."); + } + } catch (e) { + console.error("❌ Excepción enviando WhatsApp:", e.message); + } +} + +// --- AUTOMATIZACIÓN DE INSTANCIAS (CLIENTES) --- async function ensureInstance(instanceName) { if (!EVOLUTION_BASE_URL || !EVOLUTION_API_KEY) throw new Error("Faltan variables EVOLUTION en el servidor"); const baseUrl = EVOLUTION_BASE_URL.replace(/\/$/, ""); const headers = { "Content-Type": "application/json", - "apikey": EVOLUTION_API_KEY.trim() // Trim por si se copió con espacios + "apikey": EVOLUTION_API_KEY.trim() }; - // 1. Verificar si existe (intentando pedir estado) const checkRes = await fetch(`${baseUrl}/instance/connectionState/${instanceName}`, { headers }); if (checkRes.status === 404) { console.log(`🚀 Creando instancia automática: ${instanceName}`); - // 2. Si no existe, crear const createRes = await fetch(`${baseUrl}/instance/create`, { method: 'POST', headers, @@ -238,24 +276,22 @@ async function ensureInstance(instanceName) { }); if (!createRes.ok) { - // Si falla, leemos el error para mostrarlo en el log const errText = await createRes.text(); - console.error("❌ Error Evolution API al crear:", createRes.status, errText); - - if (createRes.status === 401) { - throw new Error("API KEY INCORRECTA: Revisa EVOLUTION_API_KEY en las variables de entorno."); - } + if (createRes.status === 401) throw new Error("API KEY INCORRECTA"); throw new Error(`Error creando instancia: ${errText}`); } } else if (checkRes.status === 401) { - throw new Error("API KEY INCORRECTA: Revisa EVOLUTION_API_KEY en las variables de entorno."); + throw new Error("API KEY INCORRECTA"); } - return { baseUrl, headers }; } // RUTAS AUTH -app.post("/auth/register", async (req, res) => { const client = await pool.connect(); try { const { fullName, phone, address, dni, email, password } = req.body; const p = normalizePhone(phone); if (!fullName || !p || !email || !password) return res.status(400).json({ ok: false }); const passwordHash = await bcrypt.hash(password, 10); await client.query('BEGIN'); const insert = await client.query("INSERT INTO users (full_name, phone, address, dni, email, password_hash, role, owner_id) VALUES ($1, $2, $3, $4, $5, $6, 'admin', NULL) RETURNING id", [fullName, p, address, dni, email, passwordHash]); const userId = insert.rows[0].id; const code = genCode6(); const codeHash = await bcrypt.hash(code, 10); const expiresAt = new Date(Date.now() + 10 * 60 * 1000); await client.query("INSERT INTO login_codes (user_id, phone, code_hash, expires_at) VALUES ($1, $2, $3, $4)", [userId, p, codeHash, expiresAt]); await client.query('COMMIT'); res.json({ ok: true, phone: p }); } catch (e) { await client.query('ROLLBACK'); res.status(500).json({ ok: false }); } finally { client.release(); } }); +app.post("/auth/register", async (req, res) => { const client = await pool.connect(); try { const { fullName, phone, address, dni, email, password } = req.body; const p = normalizePhone(phone); if (!fullName || !p || !email || !password) return res.status(400).json({ ok: false }); const passwordHash = await bcrypt.hash(password, 10); await client.query('BEGIN'); const insert = await client.query("INSERT INTO users (full_name, phone, address, dni, email, password_hash, role, owner_id) VALUES ($1, $2, $3, $4, $5, $6, 'admin', NULL) RETURNING id", [fullName, p, address, dni, email, passwordHash]); const userId = insert.rows[0].id; const code = genCode6(); const codeHash = await bcrypt.hash(code, 10); const expiresAt = new Date(Date.now() + 10 * 60 * 1000); await client.query("INSERT INTO login_codes (user_id, phone, code_hash, expires_at) VALUES ($1, $2, $3, $4)", [userId, p, codeHash, expiresAt]); + // ENVÍO DE WHATSAPP + await sendWhatsAppCode(p, code); + await client.query('COMMIT'); res.json({ ok: true, phone: p }); } catch (e) { await client.query('ROLLBACK'); console.error(e); res.status(500).json({ ok: false }); } finally { client.release(); } }); + app.post("/auth/verify", async (req, res) => { try { const { phone, code } = req.body; const p = normalizePhone(phone); const q = await pool.query(`SELECT lc.*, u.id as uid, u.email, u.role, u.owner_id FROM login_codes lc JOIN users u ON lc.user_id = u.id WHERE lc.phone=$1 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 }); await pool.query("UPDATE login_codes SET consumed_at=NOW() WHERE id=$1", [row.id]); await pool.query("UPDATE users SET is_verified=TRUE WHERE id=$1", [row.uid]); res.json({ ok: true, token: signToken({ id: row.uid, email: row.email, phone: p, role: row.role, owner_id: row.owner_id }) }); } catch (e) { res.status(500).json({ ok: false }); } }); app.post("/auth/login", async (req, res) => { try { const { email, password } = req.body; const q = await pool.query("SELECT * FROM users WHERE email=$1", [email]); if (q.rowCount === 0) return res.status(401).json({ ok: false }); let user = null; for (const u of q.rows) { if (await bcrypt.compare(password, u.password_hash)) { user = u; break; } } if (!user) return res.status(401).json({ ok: false }); res.json({ ok: true, token: signToken(user) }); } catch(e) { res.status(500).json({ ok: false }); } }); @@ -264,23 +300,17 @@ app.post("/auth/login", async (req, res) => { try { const { email, password } = // ========================================== app.get("/whatsapp/status", authMiddleware, async (req, res) => { try { - // Generar nombre único para la instancia: "cliente_123" const instanceName = `cliente_${req.user.accountId}`; const { baseUrl, headers } = await ensureInstance(instanceName); - - // 1. Obtener estado const stateRes = await fetch(`${baseUrl}/instance/connectionState/${instanceName}`, { headers }); const stateData = await stateRes.json(); const state = stateData.instance?.state || "close"; - - // 2. Si no conectado, obtener QR let qr = null; if (state !== "open") { const qrRes = await fetch(`${baseUrl}/instance/connect/${instanceName}`, { headers }); const qrData = await qrRes.json(); qr = qrData.code || qrData.base64; } - res.json({ ok: true, state, qr, instanceName }); } catch (e) { console.error("Error WhatsApp EndPoint:", e.message);