diff --git a/.agente/proyecto_estructura.md b/.agente/proyecto_estructura.md index 13460e4..8260668 100644 --- a/.agente/proyecto_estructura.md +++ b/.agente/proyecto_estructura.md @@ -77,7 +77,13 @@ help_desk/ │ └── settings.ejs # Configuración general de la empresa └── public/ ├── css/ - │ └── style.css # Estilos personalizados de la interfaz + │ └── style.css # Estilos personalizados, responsive, tabs y botones sociales + ├── img/ + │ ├── logo.svg # Isotipo y logotipo base + │ ├── favicon* # Favicons multi-resolución + │ ├── bot-soporte.svg # Mascota oficial vectorizada del Bot de Soporte + │ ├── bot-soporte.png # Mascota en raster alta resolución (192x192) + │ └── bot-soporte-512.png # Mascota en raster alta resolución (512x512) └── js/ └── main.js # Lógica JavaScript en navegador ``` @@ -89,8 +95,10 @@ help_desk/ | Método | Ruta | Middleware de Acceso | Descripción | |---|---|---|---| | **GET** | `/` | Sesión / Redirección | Redirige al dashboard correspondiente según el rol del usuario | -| **GET** | `/login` | Público | Renderiza formulario de login | -| **POST** | `/login` | Público | Valida credenciales e inicializa sesión | +| **GET** | `/login` | Público | Renderiza formulario de login con selector de perfil (Clientes vs Staff) | +| **POST** | `/login` | Público | Valida credenciales (Clientes vía WebControl, Personal técnico vía MariaDB local) | +| **POST** | `/auth/google` | Público | Autenticación social con Google OAuth (GIS ID Token) para clientes | +| **POST** | `/auth/facebook` | Público | Autenticación social con Facebook OAuth (Graph API) para clientes | | **GET** | `/register` | Público | Formulario de registro de cliente | | **POST** | `/register` | Público | Crea usuario nuevo con rol `client` | | **GET** | `/logout` | `isAuthenticated` | Cierra la sesión y redirige a login | diff --git a/.agente/proyecto_memoria.md b/.agente/proyecto_memoria.md index 18db062..53cdd8b 100644 --- a/.agente/proyecto_memoria.md +++ b/.agente/proyecto_memoria.md @@ -56,6 +56,12 @@ - **Dominio Público y Proxy Reverso:** Dominio oficial `https://soporte.sigmac.app`. El tráfico público ingresa por la WAN hacia `Servidor-web` CT 109 (`192.168.10.109` Apache 2.4.66), que enruta hacia el upstream interno `http://192.168.10.160:3008/` confirmado por SIAX Monitor (`/api/ports`). - **Inventario MCP de Infraestructura:** Registrado oficialmente como **Servicio ID 35** (`category: application`, `type: webapp`, `status: ACTIVE`, `url: https://soporte.sigmac.app`). +10. **Estrategia Multicanal de Autenticación y Mascota Bot de Soporte:** + - **Clientes de la Plataforma:** Autentican directamente contra la API central de Telcotronics `API-SIGMA-WEBCONTROL` (`http://192.168.10.160:3002/api/auth/login` con fallback a `https://api.siax-system.net/api/auth/login`). Al validar credenciales, se realiza aprovisionamiento o actualización transparente en `help_desk.users` (`provider='webcontrol'`) y se inicia sesión con rol `client`. + - **Nuevos Clientes (Social OAuth):** Integración nativa con Google Identity Services (GIS) verificando ID Tokens contra `https://oauth2.googleapis.com/tokeninfo`, y Facebook OAuth vía Graph API. Auto-aprovisiona usuarios como clientes sin fricción. + - **Personal Técnico y Staff:** La consola de inicio de sesión local MariaDB queda reservada exclusivamente para el personal operativo (`superadmin`, `admin`, `agent`). Se bloquea el acceso de clientes comunes por la consola técnica. + - **Identidad Visual y Mascota:** Rediseño integral de la página de inicio de sesión (`/login`) con control segmentado de pestañas (Clientes vs Personal Técnico) y la mascota oficial robot de soporte (`/img/bot-soporte.svg`, `/img/bot-soporte.png` y `/img/bot-soporte-512.png`) con animación de levitación y estado activo 24/7. + --- ## Base de Datos @@ -65,7 +71,7 @@ - **Base de datos:** `help_desk` - **Tablas:** - `companies`: Gestión de empresas/organizaciones (id, name, slug, plan, max_users, max_tickets, is_active, timestamps). - - `users`: Usuarios de cada empresa (id, company_id, nombre, email, password, rol, is_active, invite_token, invited_by, timestamps). + - `users`: Usuarios de cada empresa (id, company_id, nombre, email, password [nullable], rol, provider, provider_id, avatar_url, is_active, invite_token, invited_by, timestamps). - `departments`: Departamentos de soporte por empresa (id, company_id, nombre, descripcion, created_at). - `tickets`: Tickets de soporte (id, company_id, user_id, department_id, asunto, mensaje, status, prioridad, assigned_to, timestamps). - `ticket_respuestas`: Mensajes e hilo de conversación de cada ticket (id, ticket_id, user_id, mensaje, created_at). diff --git a/.agente/reglas.md b/.agente/reglas.md index 324d091..3829a07 100644 --- a/.agente/reglas.md +++ b/.agente/reglas.md @@ -34,3 +34,13 @@ - **Alcance:** Despliegues de la aplicación a producción (`desplegar.sh`, `servers.conf`). - **Restricción:** El despliegue debe utilizar `servers.conf` (copiado desde `servers.conf.example`, nunca commiteado). No utilizar `sudo` interactivo ni contraseñas fijas en el servidor; el ciclo de vida, reinicio y auditoría de salud debe coordinarse a través de la API REST de **SIAX Monitor** en `http://$HOST:8080` (`POST /api/apps/HELPDESK/restart` y `GET /api/apps/HELPDESK/status`). - **Motivo:** Cumplimiento de la política oficial de despliegue del ecosistema Telcotronics (Prompt 8 y Feedback #25). + +## 8. Estrategia Multicanal de Autenticación +- **Alcance:** Autenticación de usuarios, modelos de acceso y vistas de login (`/login`, `/auth/google`, `/auth/facebook`, `User.js`, `views/auth/login.ejs`). +- **Restricción:** + 1. **Clientes de la Plataforma:** Autentican contra la API central `API-SIGMA-WEBCONTROL` (`http://192.168.10.160:3002/api/auth/login` con fallback a `https://api.siax-system.net/api/auth/login`), aprovisionándose automáticamente con rol `client`. + 2. **Nuevos Clientes:** Autenticación social federada vía Google OAuth (GIS tokeninfo) y Facebook OAuth (Graph API), aprovisionándose como clientes. + 3. **Personal Técnico y Staff:** Acceso local sobre MariaDB `help_desk.users` reservado exclusivamente para roles operativos (`superadmin`, `admin`, `agent`). Queda prohibido el ingreso de clientes por la consola técnica. + 4. **Seguridad de Credenciales:** La tabla `users` soporta contraseñas opcionales (`NULL`) para cuentas federadas, rastreando `provider`, `provider_id` y `avatar_url`. +- **Motivo:** Integración sin fisuras con el ecosistema de cuentas SIGMA WebControl y simplificación de onboarding para clientes externos. + diff --git a/.agente/tareas.md b/.agente/tareas.md index 971624f..31f6075 100644 --- a/.agente/tareas.md +++ b/.agente/tareas.md @@ -10,6 +10,7 @@ | Fecha | Tarea / Entregable | Responsable | Archivos Afectados | |---|---|---|---| +| 2026-10-20 | **Autenticación Multicanal (WebControl + Google/Facebook + Local Staff) y Rediseño de Login con Mascota Bot** | Agente IA | `views/auth/login.ejs`, `public/css/style.css`, `public/img/bot-soporte.*`, `routes/auth.js`, `models/User.js`, `config/setup.js`, `.env*` | | 2026-10-20 | **Configuración de VirtualHost Apache y SSL para `soporte.sigmac.app`** | Pablinux / Agente IA | Servidor-web (`192.168.10.109`), Apache 2.4.66 | | 2026-10-18 | **Integración de Despliegue con SIAX Monitor y Dominio `soporte.sigmac.app`** | Agente IA | `desplegar.sh`, `servers.conf.example`, `init.sh`, `.gitignore`, `README.md`, `agents.md`, MCP ID 35 | | 2026-10-17 | **Despliegue a Producción en CT 132 (`server-webapps`)** | Agente IA | `desplegar.sh`, `siax-app-HELPDESK.service`, `/home/user_apps/apps/help_desk` | diff --git a/.env.example b/.env.example index 6dbb153..1be8a78 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,11 @@ JWT_SECRET=jwt_secreto_helpdesk ERROR_REPORTING_URL=https://api.siax-system.net/api/errores WEBCONTROL_API_KEY= +# Autenticación Central WebControl & Social Auth +WEBCONTROL_API_URL=http://192.168.10.160:3002 +GOOGLE_CLIENT_ID=171735739774-4vcvuf86nd17ndaloob48r49t50tvers.apps.googleusercontent.com + + # Hub de Tareas FSM (tareas.telcotronics.com) HUB_TAREAS_URL=https://tareas.telcotronics.com/api/external HUB_TAREAS_API_KEY=sigma_crm_test_key_123 diff --git a/README.md b/README.md index 834233f..afa0d0f 100644 --- a/README.md +++ b/README.md @@ -154,14 +154,34 @@ npm run check:errors --- -## 🔑 Credenciales por Defecto (Seed) +## 🔑 Estrategia Multicanal de Autenticación & Acceso -| Rol | Correo Electrónico | Contraseña | -|---|---|---| -| **Superadmin** | `super@helpdesk.com` | `123456` | -| **Admin** | `admin@helpdesk.com` | `123456` | -| **Agente** | `agent@helpdesk.com` | `123456` | -| **Cliente** | `client@helpdesk.com` | `123456` | +La plataforma cuenta con un sistema de acceso segmentado con interfaz moderna y el **Bot de Soporte** oficial del ecosistema (`/img/bot-soporte.svg`): + +1. **🏢 Clientes de la Plataforma (SIGMA WebControl):** + - Los clientes del ecosistema Telcotronics inician sesión utilizando sus credenciales corporativas habituales de **SIGMA WebControl** (`api.siax-system.net`). + - No requieren registro previo en Help Desk; la cuenta se aprovisiona y sincroniza automáticamente con rol `client` y aislamiento multi-tenant. +2. **🌐 Nuevos Clientes (Social Auth):** + - Acceso con un solo clic mediante **Google Identity Services (GIS)** y **Facebook Login**. + - Permite a usuarios nuevos crear y consultar tickets de soporte inmediatamente sin formularios extensos. +3. **🛡️ Personal Técnico & Staff de Guardia (Consola Local):** + - Reservada exclusivamente para el equipo operativo interno (`superadmin`, `admin`, `agent`). + - Autenticación sobre MariaDB local con contraseñas seguras bajo `bcrypt`. + +--- + +## 🔑 Credenciales para Personal Técnico (Entorno Local & Demo) + +Acceso exclusivo desde la pestaña **"Personal Técnico"** en `/login?tab=staff`: + +| Rol | Correo Electrónico | Contraseña | Perfil Operativo | +|---|---|---|---| +| **Superadmin** | `super@helpdesk.com` | `123456` | Administrador general de organizaciones y SaaS | +| **Admin** | `admin@helpdesk.com` | `123456` | Administrador técnico de empresa | +| **Agente** | `agent@helpdesk.com` | `123456` | Agente de soporte de guardia y despacho FSM | + +> [!NOTE] +> La interfaz incluye botones de acceso rápido (1 clic) para autocompletar credenciales en ambientes de desarrollo. --- diff --git a/config/setup.js b/config/setup.js index 128e38f..259e7b8 100644 --- a/config/setup.js +++ b/config/setup.js @@ -62,6 +62,16 @@ async function setup() { await migrateColumn('users', 'invite_token', 'invite_token VARCHAR(100) NULL'); await migrateColumn('users', 'invite_token_expires', 'invite_token_expires TIMESTAMP NULL'); await migrateColumn('users', 'invited_by', 'invited_by INT NULL'); + await migrateColumn('users', 'provider', "provider ENUM('local', 'webcontrol', 'google', 'facebook') DEFAULT 'local'"); + await migrateColumn('users', 'provider_id', "provider_id VARCHAR(150) NULL"); + await migrateColumn('users', 'avatar_url', "avatar_url VARCHAR(255) NULL"); + + try { + await conn.query("ALTER TABLE users MODIFY COLUMN password VARCHAR(255) NULL"); + console.log(' + users.password modificado a nullable'); + } catch (e) { + // Ignorar si ya es nullable + } // Alter users.rol to include superadmin try { diff --git a/models/User.js b/models/User.js index 55c211b..6081515 100644 --- a/models/User.js +++ b/models/User.js @@ -23,13 +23,27 @@ const User = { ); return rows[0]; }, - create: async (companyId, nombre, email, password, rol = 'client') => { + create: async (companyId, nombre, email, password, rol = 'client', provider = 'local', providerId = null, avatarUrl = null) => { const [result] = await db.query( - 'INSERT INTO users (company_id, nombre, email, password, rol) VALUES (?, ?, ?, ?, ?)', - [companyId, nombre, email, password, rol] + 'INSERT INTO users (company_id, nombre, email, password, rol, provider, provider_id, avatar_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + [companyId, nombre, email, password, rol, provider, providerId, avatarUrl] ); return result.insertId; }, + upsertExternal: async (nombre, email, provider, providerId, avatarUrl = null, companyId = 1) => { + const existing = await User.findByEmail(email); + if (existing) { + await User.update(existing.id, { + nombre: nombre || existing.nombre, + provider: provider || existing.provider, + provider_id: providerId || existing.provider_id, + avatar_url: avatarUrl || existing.avatar_url + }); + return await User.findById(existing.id); + } + const insertId = await User.create(companyId, nombre, email, null, 'client', provider, providerId, avatarUrl); + return await User.findById(insertId); + }, update: async (id, data) => { const fields = []; const values = []; diff --git a/public/css/style.css b/public/css/style.css index 6e6c7a4..663e74b 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -249,3 +249,118 @@ main { transform: translateY(-1px); } +/* ============================================================================== + Segmented Tabs & Social Buttons for Multi-Channel Login + ============================================================================== */ +.login-segmented-tabs { + display: flex; + background: #f1f5f9; + border-radius: 14px; + padding: 5px; + margin-bottom: 1.5rem; + gap: 6px; +} + +.login-tab-btn { + flex: 1; + border: none; + background: transparent; + padding: 0.65rem 0.85rem; + border-radius: 10px; + font-size: 0.88rem; + font-weight: 600; + color: #64748b; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; +} + +.login-tab-btn:hover { + color: #0f172a; +} + +.login-tab-btn.active { + background: #ffffff; + color: #0284c7; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -2px rgba(0, 0, 0, 0.04); +} + +.login-tab-btn.active.staff-active { + color: #4f46e5; +} + +.login-divider { + display: flex; + align-items: center; + text-align: center; + margin: 1.5rem 0 1rem; + color: #94a3b8; + font-size: 0.76rem; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 600; +} + +.login-divider::before, +.login-divider::after { + content: ''; + flex: 1; + border-bottom: 1px solid #e2e8f0; +} + +.login-divider:not(:empty)::before { + margin-right: .75em; +} + +.login-divider:not(:empty)::after { + margin-left: .75em; +} + +.btn-social-fb { + border: 1px solid #cbd5e1; + background: #ffffff; + color: #1877f2; + font-weight: 600; + font-size: 0.9rem; + border-radius: 12px; + padding: 0.65rem 1rem; + transition: all 0.2s ease; + text-decoration: none; +} + +.btn-social-fb:hover { + background: #f0f6ff; + border-color: #1877f2; + color: #0c56be; +} + +.staff-badge-pill { + display: inline-flex; + align-items: center; + gap: 6px; + background: rgba(79, 70, 229, 0.1); + color: #4f46e5; + border: 1px solid rgba(79, 70, 229, 0.2); + border-radius: 9999px; + padding: 0.25rem 0.65rem; + font-size: 0.75rem; + font-weight: 600; +} + +.client-badge-pill { + display: inline-flex; + align-items: center; + gap: 6px; + background: rgba(2, 132, 199, 0.1); + color: #0284c7; + border: 1px solid rgba(2, 132, 199, 0.2); + border-radius: 9999px; + padding: 0.25rem 0.65rem; + font-size: 0.75rem; + font-weight: 600; +} + + diff --git a/routes/auth.js b/routes/auth.js index 106a8e2..64e0dfe 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -7,44 +7,219 @@ const Department = require('../models/Department'); router.get('/login', (req, res) => { if (req.session.user) return res.redirect('/'); - res.render('auth/login'); + 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 } = req.body; + 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 { - const user = await User.findByEmail(email); - if (!user) { - req.flash('error_msg', 'Email o contraseña incorrectos'); - return res.redirect('/login'); + 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 }) + }); } - if (!user.is_active) { - req.flash('error_msg', 'Cuenta desactivada'); - return res.redirect('/login'); + + 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'); } - const company = await Company.findById(user.company_id); - if (company && !company.is_active) { - req.flash('error_msg', 'Tu empresa está desactivada'); - return res.redirect('/login'); - } - const match = await bcrypt.compare(password, user.password); - if (!match) { - req.flash('error_msg', 'Email o contraseña incorrectos'); - return res.redirect('/login'); + } 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 + rol: user.rol || 'client', + provider: 'google', + avatar_url: avatarUrl }; - req.flash('success_msg', 'Inicio de sesión exitoso'); - res.redirect('/'); - } catch (error) { - console.error(error); - req.flash('error_msg', 'Error al iniciar sesión'); - res.redirect('/login'); + + 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' }); } }); diff --git a/views/auth/login.ejs b/views/auth/login.ejs index 8ce2cdf..425b1a4 100644 --- a/views/auth/login.ejs +++ b/views/auth/login.ejs @@ -1,6 +1,9 @@ <%- include('../partials/header') %> <% title = 'Iniciar Sesión'; %> + + +
Gestión inteligente de incidencias y visitas técnicas de campo en terreno.
+Gestión centralizada de soporte, incidencias y operaciones de campo.
Ingresa tus credenciales autorizadas para acceder a la plataforma.
+Selecciona tu perfil de usuario para ingresar a la plataforma correspondiente.