| <!doctype html> |
| <html lang="es"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
| <meta name="description" content="Gestión de Usuarios"> |
| <meta name="author" content="Sistema Médico"> |
| <link rel="icon" href="favicon.ico"> |
| <title>Usuarios</title> |
|
|
| <link rel="stylesheet" href="css/simplebar.css"> |
| <link href="https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,100;0,200;0,300;0,400;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="css/feather.css"> |
| <link rel="stylesheet" href="css/app-light.css" id="lightTheme"> |
| <link rel="stylesheet" href="css/app-dark.css" id="darkTheme" disabled> |
| </head> |
|
|
| <body class="vertical light"> |
| <div class="wrapper"> |
| |
| <nav class="topnav navbar navbar-light"> |
| <button type="button" class="navbar-toggler text-muted mt-2 p-0 mr-3 collapseSidebar"> |
| <i class="fe fe-menu navbar-toggler-icon"></i> |
| </button> |
|
|
| <ul class="nav"> |
| <li class="nav-item dropdown"> |
| <a class="nav-link dropdown-toggle text-muted pr-0" href="#" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> |
| <span class="avatar avatar-sm mt-2"> |
| <img src="./assets/images/persona.png" alt="..." class="avatar-img rounded-circle"> |
| </span> |
| </a> |
| <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink"> |
| <a class="dropdown-item" href="#" onclick="confirmLogout()">Cerrar Sesión</a> |
| </div> |
| </li> |
| </ul> |
| </nav> |
|
|
| |
| <aside class="sidebar-left border-right bg-white shadow" id="leftSidebar" data-simplebar> |
| <a href="#" class="btn collapseSidebar toggle-btn d-lg-none text-muted ml-2 mt-3" data-toggle="toggle"> |
| <i class="fe fe-x"><span class="sr-only"></span></i> |
| </a> |
| <nav class="vertnav navbar navbar-light"> |
| |
| <div class="w-100 mb-4 d-flex"> |
| <a class="navbar-brand mx-auto mt-2 flex-fill text-center"> |
| <img src="assets/images/LOGO.png" alt="Logo"> |
| </a> |
| </div> |
|
|
| <ul class="navbar-nav flex-fill w-100 mb-2"> |
| <li class="nav-item"> |
| <a class="nav-link" href="./index.html"> |
| <i class="fe fe-home fe-16"></i> |
| <span class="ml-3 item-text">Dashboard</span> |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="./diagnosis.html"> |
| <i class="fe fe-eye fe-16"></i> |
| <span class="ml-3 item-text">Diagnóstico</span> |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="./patients.html"> |
| <i class="fe fe-users fe-16"></i> |
| <span class="ml-3 item-text">Pacientes</span> |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="./historial-consultas.html"> |
| <i class="fe fe-file-text fe-16"></i> |
| <span class="ml-3 item-text">Consultas</span> |
| </a> |
| </li> |
| <li class="nav-item active"> |
| <a class="nav-link" href="./users.html"> |
| <i class="fe fe-user fe-16"></i> |
| <span class="ml-3 item-text">Usuarios</span> |
| </a> |
| </li> |
| </ul> |
| </nav> |
| </aside> |
|
|
| |
| <main role="main" class="main-content"> |
| <div class="container-fluid"> |
| <div class="row justify-content-center"> |
| <div class="col-12"> |
| |
| <div class="row align-items-center mb-2"> |
| <div class="col"> |
| <h2 class="h5 page-title">Gestión de Usuarios</h2> |
| </div> |
| <div class="col-auto"> |
| <button class="btn btn-primary" onclick="loadUsers()"> |
| <i class="fe fe-refresh-cw fe-16 mr-2"></i>Actualizar |
| </button> |
| <button class="btn btn-success ml-2" onclick="showAddUserModal()"> |
| <i class="fe fe-plus fe-16 mr-2"></i>Nuevo Usuario |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="card shadow"> |
| <div class="card-header"> |
| <h6 class="card-title mb-0"> |
| <i class="fe fe-users mr-2"></i>Lista de Usuarios |
| </h6> |
| </div> |
| <div class="card-body"> |
| <div id="loadingSpinner" class="text-center py-5"> |
| <div class="spinner-border text-primary" role="status"> |
| <span class="sr-only">Cargando...</span> |
| </div> |
| <p class="text-muted mt-2">Cargando usuarios...</p> |
| </div> |
|
|
| <div id="usersTableContainer" style="display: none;"> |
| <div class="table-responsive"> |
| <table class="table table-striped table-hover"> |
| <thead class="thead-light"> |
| <tr> |
| <th>ID</th> |
| <th>Nombre de Usuario</th> |
| <th>Rol</th> |
| <th>Fecha de Creación</th> |
| <th class="text-center">Acciones</th> |
| </tr> |
| </thead> |
| <tbody id="usersTableBody"> |
| |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| <div id="emptyState" class="text-center py-5" style="display: none;"> |
| <i class="fe fe-users fe-48 text-muted mb-3"></i> |
| <h6 class="text-muted">No se encontraron usuarios</h6> |
| <p class="text-muted">No hay usuarios registrados en el sistema.</p> |
| <button class="btn btn-primary" onclick="showAddUserModal()"> |
| <i class="fe fe-plus mr-1"></i>Crear Primer Usuario |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </main> |
| </div> |
|
|
| |
| <div class="modal fade" id="userModal" tabindex="-1" role="dialog" aria-labelledby="userModalLabel" aria-hidden="true"> |
| <div class="modal-dialog" role="document"> |
| <div class="modal-content"> |
| <div class="modal-header"> |
| <h5 class="modal-title" id="userModalLabel"> |
| <i class="fe fe-user mr-2"></i>Nuevo Usuario |
| </h5> |
| <button type="button" class="close" data-dismiss="modal" aria-label="Close"> |
| <span aria-hidden="true">×</span> |
| </button> |
| </div> |
| <div class="modal-body"> |
| <form id="userForm"> |
| <input type="hidden" id="userId"> |
| <div class="form-group"> |
| <label for="username">Nombre de Usuario</label> |
| <input type="text" class="form-control" id="username" required> |
| </div> |
| <div class="form-group"> |
| <label for="password">Contraseña</label> |
| <input type="password" class="form-control" id="password" required> |
| </div> |
| <div class="form-group"> |
| <label for="confirmPassword">Confirmar Contraseña</label> |
| <input type="password" class="form-control" id="confirmPassword" required> |
| </div> |
| <div class="form-group"> |
| <label for="role">Rol</label> |
| <select class="form-control" id="role" required> |
| <option value="Doctor">Doctor</option> |
| <option value="Admin">Administrador</option> |
| </select> |
| </div> |
| </form> |
| </div> |
| <div class="modal-footer"> |
| <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button> |
| <button type="button" class="btn btn-primary" id="saveUserBtn" onclick="saveUser()">Guardar</button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script src="js/jquery.min.js"></script> |
| <script src="js/popper.min.js"></script> |
| <script src="js/bootstrap.min.js"></script> |
| <script src="js/simplebar.min.js"></script> |
| <script src="js/config.js"></script> |
| <script src="api.js"></script> |
|
|
| <script> |
| $(document).ready(function() { |
| checkAuthentication(); |
| |
| loadUsers(); |
| |
| $('#logoutBtn').on('click', function(e) { |
| e.preventDefault(); |
| if (confirm('¿Estás seguro que deseas cerrar sesión?')) { |
| api.logout().then(() => { window.location.href = 'auth-login.html'; }); |
| } |
| }); |
| }); |
| |
| function checkAuthentication() { |
| api.getSession().then(r => { if (!r.success) window.location.href = 'auth-login.html'; }).catch(() => { window.location.href = 'auth-login.html'; }); |
| } |
| |
| function loadUsers() { |
| $('#loadingSpinner').show(); |
| $('#usersTableContainer').hide(); |
| $('#emptyState').hide(); |
| |
| api.getUsers().then(function(response) { |
| $('#loadingSpinner').hide(); |
| |
| if (response.success) { |
| if (response.users.length > 0) { |
| renderUsersTable(response.users); |
| $('#usersTableContainer').show(); |
| } else { |
| $('#emptyState').show(); |
| } |
| } else { |
| console.error('Error cargando usuarios:', response.message); |
| showAlert('error', 'Error cargando usuarios: ' + response.message); |
| $('#emptyState').show(); |
| } |
| }); |
| } |
| |
| function renderUsersTable(users) { |
| const tbody = $('#usersTableBody'); |
| tbody.empty(); |
| |
| users.forEach(user => { |
| const row = ` |
| <tr> |
| <td>${user.userID}</td> |
| <td>${user.username}</td> |
| <td> |
| <span class="badge ${user.role === 'Admin' ? 'badge-primary' : 'badge-secondary'}"> |
| ${user.role} |
| </span> |
| </td> |
| <td>${user.creationDate}</td> |
| <td class="text-center"> |
| <div class="btn-group" role="group"> |
| <button class="btn btn-sm btn-outline-primary" |
| onclick="editUser(${user.userID})" |
| title="Editar"> |
| <i class="fe fe-edit fe-12"></i> |
| </button> |
| <button class="btn btn-sm btn-outline-danger" |
| onclick="confirmDeleteUser(${user.userID})" |
| title="Eliminar"> |
| <i class="fe fe-trash-2 fe-12"></i> |
| </button> |
| </div> |
| </td> |
| </tr> |
| `; |
| tbody.append(row); |
| }); |
| } |
| |
| function showAddUserModal() { |
| $('#userForm')[0].reset(); |
| $('#userId').val(''); |
| $('#userModalLabel').html('<i class="fe fe-user-plus mr-2"></i>Nuevo Usuario'); |
| $('#userModal').modal('show'); |
| } |
| |
| function editUser(userId) { |
| api.getUsers().then(function(response) { |
| if (response.success) { |
| const user = response.users.find(u => u.userID == userId); |
| if (user) { |
| $('#userId').val(user.userID); |
| $('#username').val(user.username); |
| $('#role').val(user.role); |
| $('#password').val(''); |
| $('#confirmPassword').val(''); |
| |
| $('#userModalLabel').html('<i class="fe fe-edit mr-2"></i>Editar Usuario'); |
| $('#userModal').modal('show'); |
| } |
| } |
| }); |
| } |
| |
| function saveUser() { |
| const userId = $('#userId').val(); |
| const username = $('#username').val().trim(); |
| const password = $('#password').val(); |
| const confirmPassword = $('#confirmPassword').val(); |
| const role = $('#role').val(); |
| |
| if (!username) { |
| showAlert('error', 'El nombre de usuario es requerido'); |
| return; |
| } |
| |
| if (!userId && (!password || !confirmPassword)) { |
| showAlert('error', 'La contraseña es requerida para nuevos usuarios'); |
| return; |
| } |
| |
| if (password !== confirmPassword) { |
| showAlert('error', 'Las contraseñas no coinciden'); |
| return; |
| } |
| |
| if (userId) { |
| const userData = { |
| userId: userId, |
| username: username, |
| role: role |
| }; |
| |
| if (password) { |
| userData.password = password; |
| } |
| |
| api.updateUser(userId, userData).then(function(response) { |
| if (response.success) { |
| showAlert('success', 'Usuario actualizado exitosamente'); |
| $('#userModal').modal('hide'); |
| loadUsers(); |
| } else { |
| showAlert('error', response.message || 'Error actualizando usuario'); |
| } |
| }); |
| } else { |
| api.createUser(username, password, role).then(function(response) { |
| if (response.success) { |
| showAlert('success', 'Usuario creado exitosamente'); |
| $('#userModal').modal('hide'); |
| loadUsers(); |
| } else { |
| showAlert('error', response.message || 'Error creando usuario'); |
| } |
| }); |
| } |
| } |
| |
| function confirmDeleteUser(userId) { |
| if (confirm('¿Estás seguro que deseas eliminar este usuario? Esta acción no se puede deshacer.')) { |
| deleteUser(userId); |
| } |
| } |
| |
| function deleteUser(userId) { |
| api.deleteUser(userId).then(function(response) { |
| if (response.success) { |
| showAlert('success', 'Usuario eliminado exitosamente'); |
| loadUsers(); |
| } else { |
| showAlert('error', response.message || 'Error eliminando usuario'); |
| } |
| }); |
| } |
| |
| function showAlert(type, message) { |
| const alertClass = { |
| 'success': 'alert-success', |
| 'error': 'alert-danger', |
| 'warning': 'alert-warning', |
| 'info': 'alert-info' |
| }[type] || 'alert-info'; |
| |
| const alertHtml = ` |
| <div class="alert ${alertClass} alert-dismissible fade show" role="alert"> |
| ${message} |
| <button type="button" class="close" data-dismiss="alert"> |
| <span aria-hidden="true">×</span> |
| </button> |
| </div> |
| `; |
| |
| $('.container-fluid').prepend(alertHtml); |
| |
| setTimeout(() => { |
| $('.alert').first().alert('close'); |
| }, 5000); |
| } |
| </script> |
|
|
| <script> |
| function confirmLogout() { |
| executeLogout(); |
| } |
| |
| function executeLogout() { |
| api.logout().then(() => { window.location.href = 'auth-login.html'; }); |
| } |
| </script> |
|
|
| <script> |
| function checkAdminAccess() { |
| api.getSession().then(r => { if (!r.success) { window.location.href = 'auth-login.html'; } else if (r.user.role !== 'Admin') { alert('Acceso denegado: Solo administradores'); window.location.href = 'index.html'; } }).catch(() => { window.location.href = 'auth-login.html'; }); |
| } |
| |
| function renderUsersTableSecure(users) { |
| const tbody = $('#usersTableBody'); |
| tbody.empty(); |
| |
| api.getSession().then(function(currentUserResponse) { if (!currentUserResponse.success) return; const currentUserId = currentUserResponse.user.userID; |
| |
| users.forEach(user => { |
| const canDelete = user.userID !== currentUserId; |
| |
| const deleteButton = canDelete ? |
| `<button class="btn btn-sm btn-outline-danger" |
| onclick="confirmDeleteUser(${user.userID})" |
| title="Eliminar"> |
| <i class="fe fe-trash-2 fe-12"></i> |
| </button>` : |
| `<button class="btn btn-sm btn-outline-secondary" |
| disabled |
| title="No puedes eliminar tu propia cuenta"> |
| <i class="fe fe-user-x fe-12"></i> |
| </button>`; |
| |
| const row = ` |
| <tr> |
| <td>${user.userID}</td> |
| <td> |
| ${user.username} |
| ${user.userID === currentUserId ? '<span class="badge badge-info ml-2">Tú</span>' : ''} |
| </td> |
| <td> |
| <span class="badge ${user.role === 'Admin' ? 'badge-primary' : 'badge-secondary'}"> |
| ${user.role} |
| </span> |
| </td> |
| <td>${user.creationDate}</td> |
| <td class="text-center"> |
| <div class="btn-group" role="group"> |
| <button class="btn btn-sm btn-outline-primary" |
| onclick="editUser(${user.userID})" |
| title="Editar"> |
| <i class="fe fe-edit fe-12"></i> |
| </button> |
| ${deleteButton} |
| </div> |
| </td> |
| </tr> |
| `; |
| tbody.append(row); |
| }); |
| }); |
| } |
| |
| function confirmDeleteUserSecure(userId) { |
| api.getSession().then(function(response) { if (!response.success) { showAlert('error', 'Error de autenticación'); return; } if (response.user.userID === userId) { |
| showAlert('error', 'No puedes eliminar tu propia cuenta'); |
| return; |
| } |
| |
| const confirmation = confirm( |
| 'ELIMINAR USUARIO\n\n' + |
| '¿Estás seguro que deseas eliminar este usuario?\n\n' + |
| '• Esta acción NO se puede deshacer\n' + |
| '• Se perderán todos los datos asociados\n' + |
| '• El usuario no podrá acceder al sistema\n\n' + |
| 'Escribe "CONFIRMAR" para continuar:' |
| ); |
| |
| if (confirmation) { |
| const secondConfirmation = prompt( |
| 'Para confirmar la eliminación, escribe: CONFIRMAR' |
| ); |
| |
| if (secondConfirmation === 'CONFIRMAR') { |
| deleteUser(userId); |
| } else { |
| showAlert('info', 'Eliminación cancelada - Confirmación incorrecta'); |
| } |
| } |
| }); |
| } |
| |
| $(document).ready(function() { |
| checkAdminAccess(); |
| |
| setTimeout(() => { |
| checkAuthentication(); |
| |
| loadUsers(); |
| |
| $('#logoutBtn').on('click', function(e) { |
| e.preventDefault(); |
| if (confirm('¿Estás seguro que deseas cerrar sesión?')) { |
| api.logout().then(() => { window.location.href = 'auth-login.html'; }); |
| } |
| }); |
| }, 500); |
| }); |
| |
| window.renderUsersTable = renderUsersTableSecure; |
| |
| window.confirmDeleteUser = confirmDeleteUserSecure; |
| </script> |
|
|
| <script> |
| (function() { |
| function centerWindow() { |
| try { |
| const screenWidth = window.screen.availWidth; |
| const screenHeight = window.screen.availHeight; |
| const x = Math.floor((screenWidth - 1200) / 2); |
| const y = Math.floor((screenHeight - 800) / 2); |
| |
| if (window.moveTo) { |
| window.moveTo(Math.max(0, x), Math.max(0, y)); |
| } |
| } catch (e) { |
| console.warn('No se pudo centrar ventana:', e); |
| } |
| } |
| |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', centerWindow); |
| } else { |
| centerWindow(); |
| } |
| |
| window.addEventListener('load', centerWindow); |
| window.addEventListener('focus', centerWindow); |
| })(); |
| </script> |
| </body> |
| </html> |