feat(auth): estrategia multicanal (WebControl + Google/Facebook + MariaDB Staff) y rediseño de login con mascota bot
This commit is contained in:
+291
-94
@@ -1,6 +1,9 @@
|
||||
<%- include('../partials/header') %>
|
||||
<% title = 'Iniciar Sesión'; %>
|
||||
|
||||
<!-- SDK de Google Identity Services -->
|
||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||
|
||||
<div class="login-container">
|
||||
<div class="login-card-wrapper row g-0">
|
||||
|
||||
@@ -15,10 +18,10 @@
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="fw-bold text-white mb-2">Help Desk & FSM</h3>
|
||||
<p class="text-white-50 small mb-0">Gestión inteligente de incidencias y visitas técnicas de campo en terreno.</p>
|
||||
<p class="text-white-50 small mb-0">Gestión centralizada de soporte, incidencias y operaciones de campo.</p>
|
||||
</div>
|
||||
|
||||
<!-- Mascota Bot de Soporte con Animación -->
|
||||
<!-- Mascota Bot de Soporte con Animación Flotante -->
|
||||
<div class="bot-avatar-wrapper text-center my-4">
|
||||
<img src="/img/bot-soporte.svg" alt="Bot de Soporte Help Desk" class="bot-avatar-img mb-3">
|
||||
<div>
|
||||
@@ -35,7 +38,7 @@
|
||||
<div class="login-feature-icon">
|
||||
<i class="fas fa-bolt"></i>
|
||||
</div>
|
||||
<div>Triaje digital y tickets en tiempo real</div>
|
||||
<div>Tickets y triaje inteligente en tiempo real</div>
|
||||
</div>
|
||||
<div class="login-feature-item">
|
||||
<div class="login-feature-icon">
|
||||
@@ -47,7 +50,7 @@
|
||||
<div class="login-feature-icon">
|
||||
<i class="fas fa-building"></i>
|
||||
</div>
|
||||
<div>Aislamiento multi-tenant 100% seguro</div>
|
||||
<div>Integración nativa con SIGMA WebControl</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-3 border-top border-white border-opacity-10 d-flex justify-content-between align-items-center text-white-50" style="font-size: 0.75rem;">
|
||||
@@ -58,7 +61,7 @@
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- Columna Derecha: Formulario de Login -->
|
||||
<!-- Columna Derecha: Selector y Formularios -->
|
||||
<!-- ========================================== -->
|
||||
<div class="col-lg-7 login-form-side">
|
||||
|
||||
@@ -87,83 +90,188 @@
|
||||
<% }) %>
|
||||
<% } %>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<img src="/img/logo.svg" alt="Help Desk" width="32" height="32" class="rounded-2 shadow-sm">
|
||||
<h4 class="fw-bold text-dark mb-0">¡Bienvenido de vuelta!</h4>
|
||||
<!-- Cabecera de Bienvenida -->
|
||||
<div class="mb-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-1">
|
||||
<img src="/img/logo.svg" alt="Help Desk" width="30" height="30" class="rounded-2 shadow-sm">
|
||||
<h4 class="fw-bold text-dark mb-0">Acceso al Sistema</h4>
|
||||
</div>
|
||||
<p class="text-muted small">Ingresa tus credenciales autorizadas para acceder a la plataforma.</p>
|
||||
<p class="text-muted small mb-0">Selecciona tu perfil de usuario para ingresar a la plataforma correspondiente.</p>
|
||||
</div>
|
||||
|
||||
<form action="/login" method="POST" id="loginForm">
|
||||
|
||||
<!-- Campo de Correo Electrónico -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-secondary small fw-semibold">Correo Electrónico</label>
|
||||
<div class="input-group login-input-group">
|
||||
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
|
||||
<input type="email" name="email" id="inputEmail" class="form-control" placeholder="ejemplo@helpdesk.com" required autocomplete="email">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Campo de Contraseña -->
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label class="form-label text-secondary small fw-semibold">Contraseña</label>
|
||||
</div>
|
||||
<div class="input-group login-input-group">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="••••••••" required autocomplete="current-password" style="border-top-right-radius: 0; border-bottom-right-radius: 0;">
|
||||
<button class="btn btn-outline-secondary password-toggle-btn px-3" type="button" id="togglePasswordBtn" title="Mostrar/Ocultar contraseña">
|
||||
<i class="fas fa-eye" id="togglePasswordIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opciones Adicionales -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="rememberMe">
|
||||
<label class="form-check-label text-muted small" for="rememberMe">Recordar credenciales</label>
|
||||
</div>
|
||||
<a href="/register" class="text-decoration-none small text-primary fw-semibold">Registrar empresa</a>
|
||||
</div>
|
||||
|
||||
<!-- Botón Principal de Acceso -->
|
||||
<button type="submit" class="btn btn-login w-100 d-flex justify-content-center align-items-center shadow-sm">
|
||||
<span>Ingresar a la Plataforma</span>
|
||||
<i class="fas fa-arrow-right ms-2"></i>
|
||||
<!-- Segmented Control / Tabs de Selección de Perfil -->
|
||||
<div class="login-segmented-tabs">
|
||||
<button type="button" class="login-tab-btn <%= tab !== 'staff' ? 'active' : '' %>" id="tabBtnClient" onclick="switchLoginTab('client')">
|
||||
<i class="fas fa-user-tag text-primary"></i>
|
||||
<span>Clientes & Empresas</span>
|
||||
</button>
|
||||
</form>
|
||||
<button type="button" class="login-tab-btn <%= tab === 'staff' ? 'active staff-active' : '' %>" id="tabBtnStaff" onclick="switchLoginTab('staff')">
|
||||
<i class="fas fa-user-shield text-indigo" style="color: #4f46e5;"></i>
|
||||
<span>Personal Técnico</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Acceso Rápido / Credenciales Demo del Ecosistema -->
|
||||
<div class="mt-4 pt-3 border-top">
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<span class="text-muted fw-semibold" style="font-size: 0.76rem;">
|
||||
<i class="fas fa-key me-1 text-primary"></i> Acceso Rápido Demo:
|
||||
</span>
|
||||
<small class="text-muted" style="font-size: 0.72rem;">(1 clic para rellenar)</small>
|
||||
<!-- ============================================================= -->
|
||||
<!-- PESTAÑA 1: CLIENTES & EMPRESAS (WebControl + Redes Sociales) -->
|
||||
<!-- ============================================================= -->
|
||||
<div id="sectionClient" style="<%= tab === 'staff' ? 'display: none;' : '' %>">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3 p-2.5 rounded-3 bg-light border border-slate-200">
|
||||
<div class="client-badge-pill">
|
||||
<i class="fas fa-building"></i> Clientes SIGMA
|
||||
</div>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Autenticación vía <strong>SIGMA WebControl</strong></small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="demo-account-chip" onclick="fillCredentials('super@helpdesk.com', '123456')">
|
||||
<i class="fas fa-crown text-warning me-1"></i> Superadmin
|
||||
</span>
|
||||
<span class="demo-account-chip" onclick="fillCredentials('admin@helpdesk.com', '123456')">
|
||||
<i class="fas fa-user-shield text-info me-1"></i> Admin
|
||||
</span>
|
||||
<span class="demo-account-chip" onclick="fillCredentials('agent@helpdesk.com', '123456')">
|
||||
<i class="fas fa-headset text-primary me-1"></i> Agente
|
||||
</span>
|
||||
<span class="demo-account-chip" onclick="fillCredentials('client@helpdesk.com', '123456')">
|
||||
<i class="fas fa-user text-secondary me-1"></i> Cliente
|
||||
</span>
|
||||
|
||||
<form action="/login" method="POST" id="clientLoginForm">
|
||||
<input type="hidden" name="login_type" value="client">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-secondary small fw-semibold">Correo de Cliente / Empresa</label>
|
||||
<div class="input-group login-input-group">
|
||||
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
|
||||
<input type="email" name="email" id="clientEmail" class="form-control" placeholder="tu-correo@empresa.com" required autocomplete="email">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-secondary small fw-semibold">Contraseña WebControl</label>
|
||||
<div class="input-group login-input-group">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
<input type="password" name="password" id="clientPassword" class="form-control" placeholder="••••••••" required autocomplete="current-password" style="border-top-right-radius: 0; border-bottom-right-radius: 0;">
|
||||
<button class="btn btn-outline-secondary password-toggle-btn px-3" type="button" onclick="togglePassVisibility('clientPassword', 'clientPassIcon')" title="Mostrar/Ocultar contraseña">
|
||||
<i class="fas fa-eye" id="clientPassIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="clientRememberMe">
|
||||
<label class="form-check-label text-muted small" for="clientRememberMe">Recordarme</label>
|
||||
</div>
|
||||
<a href="https://siax-system.net" target="_blank" class="text-decoration-none small text-primary fw-semibold">
|
||||
¿Olvidaste tu contraseña?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-login w-100 d-flex justify-content-center align-items-center shadow-sm">
|
||||
<span>Ingresar con Cuenta SIGMA</span>
|
||||
<i class="fas fa-arrow-right ms-2"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Divisor de Social Auth -->
|
||||
<div class="login-divider">
|
||||
<span>O regístrate / accede con</span>
|
||||
</div>
|
||||
|
||||
<!-- Google Identity Services Button -->
|
||||
<div class="mb-2">
|
||||
<div id="g_id_onload"
|
||||
data-client_id="<%= googleClientId %>"
|
||||
data-context="signin"
|
||||
data-ux_mode="popup"
|
||||
data-callback="handleGoogleCredentialResponse"
|
||||
data-auto_prompt="false">
|
||||
</div>
|
||||
<div class="g_id_signin w-100 d-flex justify-content-center"
|
||||
data-type="standard"
|
||||
data-shape="rectangular"
|
||||
data-theme="outline"
|
||||
data-text="continue_with"
|
||||
data-size="large"
|
||||
data-logo_alignment="left"
|
||||
data-width="360">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botón de Facebook OAuth -->
|
||||
<div class="mt-2">
|
||||
<button type="button" class="btn btn-social-fb w-100 d-flex align-items-center justify-content-center gap-2 shadow-sm" onclick="handleFacebookLogin()">
|
||||
<i class="fab fa-facebook fs-5"></i>
|
||||
<span>Continuar con Facebook</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3 pt-2">
|
||||
<span class="text-muted small">¿Nueva empresa o sucursal?</span>
|
||||
<a href="/register" class="text-decoration-none small text-primary fw-semibold ms-1">Crear registro institucional</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pie del Formulario -->
|
||||
<div class="text-center mt-4">
|
||||
<!-- ============================================================= -->
|
||||
<!-- PESTAÑA 2: PERSONAL TÉCNICO & STAFF (Consola Local MariaDB) -->
|
||||
<!-- ============================================================= -->
|
||||
<div id="sectionStaff" style="<%= tab !== 'staff' ? 'display: none;' : '' %>">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3 p-2.5 rounded-3 bg-light border border-slate-200">
|
||||
<div class="staff-badge-pill">
|
||||
<i class="fas fa-shield-alt"></i> Consola Operativa
|
||||
</div>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Exclusivo <strong>Agentes & Técnicos</strong></small>
|
||||
</div>
|
||||
|
||||
<form action="/login" method="POST" id="staffLoginForm">
|
||||
<input type="hidden" name="login_type" value="staff">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-secondary small fw-semibold">Correo Corporativo de Operaciones</label>
|
||||
<div class="input-group login-input-group">
|
||||
<span class="input-group-text"><i class="fas fa-id-badge"></i></span>
|
||||
<input type="email" name="email" id="staffEmail" class="form-control" placeholder="agente@helpdesk.com" required autocomplete="email">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-secondary small fw-semibold">Contraseña de Acceso Interno</label>
|
||||
<div class="input-group login-input-group">
|
||||
<span class="input-group-text"><i class="fas fa-key"></i></span>
|
||||
<input type="password" name="password" id="staffPassword" class="form-control" placeholder="••••••••" required autocomplete="current-password" style="border-top-right-radius: 0; border-bottom-right-radius: 0;">
|
||||
<button class="btn btn-outline-secondary password-toggle-btn px-3" type="button" onclick="togglePassVisibility('staffPassword', 'staffPassIcon')" title="Mostrar/Ocultar contraseña">
|
||||
<i class="fas fa-eye" id="staffPassIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="staffRememberMe">
|
||||
<label class="form-check-label text-muted small" for="staffRememberMe">Mantener sesión técnica</label>
|
||||
</div>
|
||||
<span class="text-muted small">help_desk.users DB</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-login w-100 d-flex justify-content-center align-items-center shadow-sm" style="background: linear-gradient(135deg, #4f46e5 0%, #2563eb 100%);">
|
||||
<span>Acceder a Consola Técnica</span>
|
||||
<i class="fas fa-shield-alt ms-2"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Acceso Rápido / Credenciales Demo Exclusivas para Personal Técnico -->
|
||||
<div class="mt-4 pt-3 border-top">
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<span class="text-muted fw-semibold" style="font-size: 0.76rem;">
|
||||
<i class="fas fa-bolt me-1 text-primary"></i> Acceso Rápido Personal de Guardia:
|
||||
</span>
|
||||
<small class="text-muted" style="font-size: 0.72rem;">(1 clic para autocompletar)</small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="demo-account-chip" onclick="fillStaffCredentials('super@helpdesk.com', '123456')">
|
||||
<i class="fas fa-crown text-warning me-1"></i> Superadmin
|
||||
</span>
|
||||
<span class="demo-account-chip" onclick="fillStaffCredentials('admin@helpdesk.com', '123456')">
|
||||
<i class="fas fa-user-shield text-info me-1"></i> Admin
|
||||
</span>
|
||||
<span class="demo-account-chip" onclick="fillStaffCredentials('agent@helpdesk.com', '123456')">
|
||||
<i class="fas fa-headset text-primary me-1"></i> Agente Técnico
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pie de Soporte -->
|
||||
<div class="text-center mt-4 pt-2">
|
||||
<small class="text-muted">
|
||||
¿Problemas de acceso? Contacta a <a href="mailto:soporte@telcotronics.com" class="text-decoration-none text-secondary fw-semibold">Soporte Telcotronics</a>
|
||||
¿Problemas de conectividad? Contacta a <a href="mailto:soporte@telcotronics.com" class="text-decoration-none text-secondary fw-semibold">NOC Telcotronics</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -172,34 +280,52 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Toggle de visibilidad de contraseña
|
||||
const togglePasswordBtn = document.getElementById('togglePasswordBtn');
|
||||
const inputPassword = document.getElementById('inputPassword');
|
||||
const togglePasswordIcon = document.getElementById('togglePasswordIcon');
|
||||
// Conmutador interactivo de pestañas
|
||||
function switchLoginTab(tab) {
|
||||
const btnClient = document.getElementById('tabBtnClient');
|
||||
const btnStaff = document.getElementById('tabBtnStaff');
|
||||
const secClient = document.getElementById('sectionClient');
|
||||
const secStaff = document.getElementById('sectionStaff');
|
||||
|
||||
if (togglePasswordBtn && inputPassword && togglePasswordIcon) {
|
||||
togglePasswordBtn.addEventListener('click', function() {
|
||||
if (inputPassword.type === 'password') {
|
||||
inputPassword.type = 'text';
|
||||
togglePasswordIcon.classList.remove('fa-eye');
|
||||
togglePasswordIcon.classList.add('fa-eye-slash');
|
||||
} else {
|
||||
inputPassword.type = 'password';
|
||||
togglePasswordIcon.classList.remove('fa-eye-slash');
|
||||
togglePasswordIcon.classList.add('fa-eye');
|
||||
}
|
||||
});
|
||||
if (tab === 'staff') {
|
||||
btnClient.classList.remove('active');
|
||||
btnStaff.classList.add('active', 'staff-active');
|
||||
secClient.style.display = 'none';
|
||||
secStaff.style.display = 'block';
|
||||
window.history.replaceState(null, null, '/login?tab=staff');
|
||||
} else {
|
||||
btnStaff.classList.remove('active', 'staff-active');
|
||||
btnClient.classList.add('active');
|
||||
secStaff.style.display = 'none';
|
||||
secClient.style.display = 'block';
|
||||
window.history.replaceState(null, null, '/login?tab=client');
|
||||
}
|
||||
}
|
||||
|
||||
// Función para autocompletar credenciales demo
|
||||
function fillCredentials(email, password) {
|
||||
const emailInput = document.getElementById('inputEmail');
|
||||
const passInput = document.getElementById('inputPassword');
|
||||
// Toggle de visibilidad para contraseña
|
||||
function togglePassVisibility(inputId, iconId) {
|
||||
const input = document.getElementById(inputId);
|
||||
const icon = document.getElementById(iconId);
|
||||
if (input && icon) {
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
icon.classList.remove('fa-eye');
|
||||
icon.classList.add('fa-eye-slash');
|
||||
} else {
|
||||
input.type = 'password';
|
||||
icon.classList.remove('fa-eye-slash');
|
||||
icon.classList.add('fa-eye');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Autocompletado para el personal técnico
|
||||
function fillStaffCredentials(email, password) {
|
||||
const emailInput = document.getElementById('staffEmail');
|
||||
const passInput = document.getElementById('staffPassword');
|
||||
if (emailInput && passInput) {
|
||||
emailInput.value = email;
|
||||
passInput.value = password;
|
||||
|
||||
// Efecto visual de feedback
|
||||
emailInput.classList.add('is-valid');
|
||||
passInput.classList.add('is-valid');
|
||||
setTimeout(() => {
|
||||
@@ -208,6 +334,77 @@
|
||||
}, 1200);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback de Google Identity Services
|
||||
window.handleGoogleCredentialResponse = async function(response) {
|
||||
if (!response || !response.credential) return;
|
||||
try {
|
||||
const res = await fetch('/auth/google', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ credential: response.credential })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.location.href = data.redirect || '/client/dashboard';
|
||||
} else {
|
||||
alert(data.message || 'No fue posible iniciar sesión con Google');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error Google Sign-In:', err);
|
||||
alert('Error al conectar con el servidor de autenticación');
|
||||
}
|
||||
};
|
||||
|
||||
// Callback de Facebook Login
|
||||
window.handleFacebookLogin = function() {
|
||||
// Si el SDK de Facebook está cargado, invocarlo, o guiar al usuario
|
||||
if (typeof FB !== 'undefined') {
|
||||
FB.login(function(response) {
|
||||
if (response.authResponse && response.authResponse.accessToken) {
|
||||
fetch('/auth/facebook', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accessToken: response.authResponse.accessToken })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.href = data.redirect || '/client/dashboard';
|
||||
} else {
|
||||
alert(data.message || 'Error con Facebook Login');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error FB:', err);
|
||||
alert('Error al verificar credenciales con Facebook');
|
||||
});
|
||||
}
|
||||
}, { scope: 'public_profile,email' });
|
||||
} else {
|
||||
// Mensaje amigable si FB SDK aún no tiene APP_ID público configurado
|
||||
const email = prompt('Inicio con Facebook:\nIngresa tu correo de cuenta para simular el acceso social o usa Google Sign-In:');
|
||||
if (email && email.includes('@')) {
|
||||
fetch('/auth/facebook', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accessToken: 'mock_fb_token_' + btoa(email) })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.href = data.redirect || '/client/dashboard';
|
||||
} else {
|
||||
// Si falla la verificación con Graph API real, informar
|
||||
alert('Para inicio social directo en este entorno, por favor utiliza "Continuar con Google".');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
alert('Por favor utiliza el botón de "Continuar con Google" o tus credenciales WebControl.');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<%- include('../partials/footer') %>
|
||||
|
||||
Reference in New Issue
Block a user