const express = require('express'); const router = express.Router(); const bcrypt = require('bcryptjs'); const User = require('../models/User'); const Company = require('../models/Company'); const Department = require('../models/Department'); router.get('/login', (req, res) => { if (req.session.user) return res.redirect('/'); const tab = req.query.tab || 'client'; const googleClientId = process.env.GOOGLE_CLIENT_ID || '171735739774-4vcvuf86nd17ndaloob48r49t50tvers.apps.googleusercontent.com'; res.render('auth/login', { tab, googleClientId }); }); router.post('/login', async (req, res) => { const { email, password, login_type } = req.body; if (!email || !password) { req.flash('error_msg', 'Por favor ingresa tu correo y contraseña'); return res.redirect('/login'); } const cleanEmail = email.trim().toLowerCase(); // ========================================================================= // 1. MODO PERSONAL TÉCNICO & STAFF (Superadmin, Admins y Agentes en MariaDB) // ========================================================================= if (login_type === 'staff') { try { const user = await User.findByEmail(cleanEmail); if (!user) { req.flash('error_msg', 'Credenciales de personal técnico incorrectas'); return res.redirect('/login?tab=staff'); } if (!user.is_active) { req.flash('error_msg', 'Cuenta técnica desactivada por el administrador'); return res.redirect('/login?tab=staff'); } // Si es un cliente regular, no debe entrar por la consola técnica if (user.rol === 'client') { req.flash('error_msg', 'Esta sección es exclusiva para agentes y técnicos. Los clientes deben ingresar en la pestaña de Clientes.'); return res.redirect('/login?tab=client'); } const company = await Company.findById(user.company_id); if (company && !company.is_active) { req.flash('error_msg', 'Tu organización se encuentra suspendida'); return res.redirect('/login?tab=staff'); } const match = await bcrypt.compare(password, user.password); if (!match) { req.flash('error_msg', 'Credenciales de personal técnico incorrectas'); return res.redirect('/login?tab=staff'); } req.session.user = { id: user.id, company_id: user.company_id, nombre: user.nombre, email: user.email, rol: user.rol, provider: user.provider || 'local' }; req.flash('success_msg', `Bienvenido al panel operativo, ${user.nombre}`); return res.redirect('/'); } catch (error) { console.error('Error login staff:', error); req.flash('error_msg', 'Error al procesar el acceso del personal técnico'); return res.redirect('/login?tab=staff'); } } // ========================================================================= // 2. MODO CLIENTES DE LA PLATAFORMA (API-SIGMA-WEBCONTROL) // ========================================================================= const webcontrolUrl = process.env.WEBCONTROL_API_URL || 'http://192.168.10.160:3002'; try { let wcRes; try { wcRes = await fetch(`${webcontrolUrl}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: cleanEmail, paswd: password }) }); } catch (fetchErr) { console.warn('[Auth WebControl] Conexión interna falló, reintentando por dominio público:', fetchErr.message); wcRes = await fetch('https://api.siax-system.net/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: cleanEmail, paswd: password }) }); } const wcData = await wcRes.json(); if (wcRes.ok && wcData.success) { const wcUser = wcData.usuario || {}; const userName = wcUser.nombre || wcUser.usuario || cleanEmail.split('@')[0]; const providerId = String(wcUser.id || cleanEmail); // Sincronizar o aprovisionar usuario en help_desk.users con rol 'client' const user = await User.upsertExternal(userName, cleanEmail, 'webcontrol', providerId, null, 1); req.session.user = { id: user.id, company_id: user.company_id, nombre: user.nombre, email: user.email, rol: user.rol || 'client', provider: 'webcontrol', webcontrol_token: wcData.token || null }; req.flash('success_msg', `Bienvenido a Help Desk, ${user.nombre}`); return res.redirect('/client/dashboard'); } else { // Verificación de cortesía: si un técnico intentó ingresar en la pestaña de clientes const localStaff = await User.findByEmail(cleanEmail); if (localStaff && localStaff.rol !== 'client') { const matchLocal = await bcrypt.compare(password, localStaff.password); if (matchLocal) { req.session.user = { id: localStaff.id, company_id: localStaff.company_id, nombre: localStaff.nombre, email: localStaff.email, rol: localStaff.rol, provider: 'local' }; req.flash('success_msg', `Bienvenido al panel técnico, ${localStaff.nombre}`); return res.redirect('/'); } } req.flash('error_msg', wcData.message || 'Credenciales de Telcotronics / WebControl incorrectas'); return res.redirect('/login?tab=client'); } } catch (error) { console.error('Error login WebControl:', error); req.flash('error_msg', 'Error de comunicación con el servicio de autenticación central WebControl'); return res.redirect('/login?tab=client'); } }); // ============================================================================= // 3. SOCIAL LOGIN — GOOGLE OAUTH // ============================================================================= router.post('/auth/google', async (req, res) => { const { credential } = req.body; if (!credential) { return res.status(400).json({ success: false, message: 'Token de Google requerido' }); } try { const verifyRes = await fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${encodeURIComponent(credential)}`); const payload = await verifyRes.json(); if (!verifyRes.ok || !payload.email) { return res.status(401).json({ success: false, message: 'Token de Google no válido o expirado' }); } const cleanEmail = payload.email.trim().toLowerCase(); const userName = payload.name || cleanEmail.split('@')[0]; const avatarUrl = payload.picture || null; const providerId = payload.sub; // Auto-provisioning automático como cliente const user = await User.upsertExternal(userName, cleanEmail, 'google', providerId, avatarUrl, 1); req.session.user = { id: user.id, company_id: user.company_id, nombre: user.nombre, email: user.email, rol: user.rol || 'client', provider: 'google', avatar_url: avatarUrl }; return res.json({ success: true, redirect: '/client/dashboard' }); } catch (err) { console.error('Error Google OAuth:', err.message); return res.status(500).json({ success: false, message: 'Error al verificar autenticación con Google' }); } }); // ============================================================================= // 4. SOCIAL LOGIN — FACEBOOK OAUTH // ============================================================================= router.post('/auth/facebook', async (req, res) => { const { accessToken } = req.body; if (!accessToken) { return res.status(400).json({ success: false, message: 'Token de Facebook requerido' }); } try { const fbRes = await fetch( `https://graph.facebook.com/me?fields=id,name,email,picture.type(large)&access_token=${encodeURIComponent(accessToken)}` ); const fbData = await fbRes.json(); if (fbData.error || !fbData.id) { return res.status(401).json({ success: false, message: 'Token de Facebook inválido' }); } const cleanEmail = fbData.email ? fbData.email.trim().toLowerCase() : `fb_${fbData.id}@sigmac.app`; const userName = fbData.name || 'Usuario Facebook'; const avatarUrl = fbData.picture?.data?.url || null; const providerId = fbData.id; // Auto-provisioning automático como cliente const user = await User.upsertExternal(userName, cleanEmail, 'facebook', providerId, avatarUrl, 1); req.session.user = { id: user.id, company_id: user.company_id, nombre: user.nombre, email: user.email, rol: user.rol || 'client', provider: 'facebook', avatar_url: avatarUrl }; return res.json({ success: true, redirect: '/client/dashboard' }); } catch (err) { console.error('Error Facebook OAuth:', err.message); return res.status(500).json({ success: false, message: 'Error al verificar autenticación con Facebook' }); } }); router.get('/register', (req, res) => { if (req.session.user) return res.redirect('/'); res.render('auth/register'); }); router.post('/register', async (req, res) => { const { nombre, email, password, confirm_password, company_name } = req.body; if (!nombre || !email || !password || !confirm_password) { req.flash('error_msg', 'Todos los campos son obligatorios'); return res.redirect('/register'); } if (password !== confirm_password) { req.flash('error_msg', 'Las contraseñas no coinciden'); return res.redirect('/register'); } if (password.length < 6) { req.flash('error_msg', 'La contraseña debe tener al menos 6 caracteres'); return res.redirect('/register'); } try { const existing = await User.findByEmail(email); if (existing) { req.flash('error_msg', 'El email ya está registrado'); return res.redirect('/register'); } // Create company or use default let companyId = 1; if (company_name) { const slug = company_name.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').slice(0, 100); const slugExists = await Company.findBySlug(slug); if (slugExists) { req.flash('error_msg', 'El nombre de empresa ya está registrado'); return res.redirect('/register'); } companyId = await Company.create(company_name, slug, 'free'); // Seed default departments for new company await Department.create(companyId, 'Soporte Técnico', 'Problemas técnicos y de software'); await Department.create(companyId, 'Facturación', 'Problemas de facturación y pagos'); await Department.create(companyId, 'General', 'Consultas generales'); } const hashedPassword = await bcrypt.hash(password, 10); await User.create(companyId, nombre, email, hashedPassword, company_name ? 'admin' : 'client'); req.flash('success_msg', 'Registro exitoso, ahora puedes iniciar sesión'); res.redirect('/login'); } catch (error) { console.error(error); req.flash('error_msg', 'Error al registrarse'); res.redirect('/register'); } }); router.get('/invite/:token', async (req, res) => { try { const invite = await User.findByInviteToken(req.params.token); if (!invite) { req.flash('error_msg', 'Invitación inválida o expirada'); return res.redirect('/login'); } res.render('auth/accept-invite', { token: req.params.token, email: invite.email }); } catch (error) { console.error(error); req.flash('error_msg', 'Error al procesar invitación'); res.redirect('/login'); } }); router.post('/invite/:token', async (req, res) => { const { nombre, password, confirm_password } = req.body; if (!nombre || !password || !confirm_password) { req.flash('error_msg', 'Todos los campos son obligatorios'); return res.redirect(`/invite/${req.params.token}`); } if (password !== confirm_password) { req.flash('error_msg', 'Las contraseñas no coinciden'); return res.redirect(`/invite/${req.params.token}`); } try { const invite = await User.findByInviteToken(req.params.token); if (!invite) { req.flash('error_msg', 'Invitación inválida o expirada'); return res.redirect('/login'); } const hashedPassword = await bcrypt.hash(password, 10); await User.update(invite.id, { nombre, password: hashedPassword, is_active: 1, invite_token: null, invite_token_expires: null }); req.flash('success_msg', 'Cuenta activada, ahora puedes iniciar sesión'); res.redirect('/login'); } catch (error) { console.error(error); req.flash('error_msg', 'Error al aceptar invitación'); res.redirect(`/invite/${req.params.token}`); } }); router.get('/logout', (req, res) => { req.session.destroy(); res.redirect('/login'); }); module.exports = router;