fix(classrooms): blindar vistas presenciales y virtuales sin aulas + feat(admin): reinicializacion de sistema con clave de admin

This commit is contained in:
2026-09-23 21:03:40 -03:00
parent 95d2aac323
commit 1e6f493358
8 changed files with 536 additions and 63 deletions
@@ -88,6 +88,11 @@
<i class="bi bi-window-desktop me-2 text-primary"></i>Interfaz & Dashboard
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link text-danger" id="tab-maintenance-btn" data-bs-toggle="tab" data-bs-target="#tab-maintenance" type="button" role="tab">
<i class="bi bi-radioactive me-2 text-danger"></i>Mantenimiento &amp; Reinicio
</button>
</li>
</ul>
</div>
@@ -549,6 +554,93 @@
</div>
</div>
<!-- ========================================== -->
<!-- PESTAÑA 5: MANTENIMIENTO & REINICIO -->
<!-- ========================================== -->
<div class="tab-pane fade" id="tab-maintenance" role="tabpanel">
<div class="row g-4">
<div class="col-lg-8">
<!-- Danger Zone Card -->
<div class="card border border-danger shadow-sm rounded-3 overflow-hidden">
<div class="card-header bg-danger text-white py-3">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
<div class="d-flex align-items-center">
<i class="bi bi-exclamation-triangle-fill fs-4 me-2"></i>
<div>
<h5 class="h6 fw-bold mb-0 text-white">Zona de Peligro: Reinicialización Total del Sistema</h5>
<small class="text-white-50">Depuración integral de datos operacionales para inicio de ciclo o migración en limpio</small>
</div>
</div>
<span class="badge bg-white text-danger font-monospace px-2 py-1 small">Requiere Clave Admin</span>
</div>
</div>
<div class="card-body p-4 bg-body">
<p class="mb-3 text-body">
Esta herramienta permite vaciar completamente los datos operacionales generados por la actividad académica y de uso diario en la plataforma, restableciendo el sistema a su estado inicial.
</p>
<div class="row g-3 mb-4">
<div class="col-md-6">
<div class="p-3 rounded-3 bg-danger-subtle border border-danger-subtle h-100">
<h6 class="fw-bold text-danger mb-2 d-flex align-items-center">
<i class="bi bi-trash3-fill me-2"></i>Datos que se eliminarán:
</h6>
<ul class="small mb-0 ps-3 text-danger-emphasis">
<li><strong>Historiales:</strong> Toda la bitácora histórica y registros previos de auditoría.</li>
<li><strong>Reservas:</strong> Todas las reservas de aulas, asignaciones de espacios y estados.</li>
<li><strong>Espacios y Sedes:</strong> Todas las aulas físicas, virtuales, recursos y edificios/sedes.</li>
<li><strong>Académica:</strong> Carreras, materias, comisiones, docentes asignados, matrículas, notas y ciclos lectivos.</li>
<li><strong>Colas de sincronización:</strong> Tareas pendientes o fallidas hacia sistemas externos.</li>
</ul>
</div>
</div>
<div class="col-md-6">
<div class="p-3 rounded-3 bg-success-subtle border border-success-subtle h-100">
<h6 class="fw-bold text-success mb-2 d-flex align-items-center">
<i class="bi bi-shield-check me-2"></i>Datos que se PRESERVAN intactos:
</h6>
<ul class="small mb-0 ps-3 text-success-emphasis">
<li><strong>Cuentas de Usuarios:</strong> Todos los usuarios (Administradores, Bedeles, Docentes, Alumnos) con sus contraseñas activas.</li>
<li><strong>Roles y Permisos (RBAC):</strong> Estructura completa de perfiles institucionales.</li>
<li><strong>Configuración Global:</strong> Preferencias de correo SMTP, Single Sign-On, Moodle y opciones de interfaz.</li>
<li><strong>Tipos de Hitos:</strong> Tipos maestros configurados (Parciales, Finales, TP, etc.).</li>
<li><strong>Módulo de Administración:</strong> Todos los componentes de la pestaña Administración.</li>
</ul>
</div>
</div>
</div>
<div class="alert alert-warning py-2 px-3 small d-flex align-items-center mb-4">
<i class="bi bi-shield-lock-fill text-warning fs-5 me-2 flex-shrink-0"></i>
<div>
Por estrictas razones de seguridad institucional, para ejecutar esta acción deberá ingresar la <strong>contraseña del usuario Administrador</strong> actualmente autenticado.
</div>
</div>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-danger btn-sm px-4 fw-semibold shadow-sm" data-bs-toggle="modal" data-bs-target="#modalResetSystem">
<i class="bi bi-radioactive me-2"></i>Reinicializar Sistema...
</button>
</div>
</div>
</div>
</div>
<!-- Info / Good Practices -->
<div class="col-lg-4">
<div class="card border-0 bg-body-tertiary p-3 rounded-3 mb-3">
<h6 class="fw-bold mb-2"><i class="bi bi-info-circle text-primary me-1"></i>¿Cuándo usar esta función?</h6>
<p class="small text-muted mb-2">
Utilice la reinicialización cuando desee comenzar un nuevo ciclo lectivo desde cero, tras finalizar períodos de prueba o capacitación, o para cargar una nueva grilla de sedes y carreras sin arrastrar información residual.
</p>
<p class="small text-muted mb-0">
Al conservar intactos los usuarios y las configuraciones de seguridad, el personal administrativo y docente mantiene su acceso ininterrumpido a la plataforma.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -580,6 +672,56 @@
</div>
</div>
<!-- Modal Confirmar Reinicialización del Sistema -->
<div class="modal fade" id="modalResetSystem" tabindex="-1" aria-labelledby="modalResetSystemLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-danger text-white py-2">
<h6 class="modal-title fw-bold" id="modalResetSystemLabel">
<i class="bi bi-shield-exclamation me-2"></i>Confirmación Crítica: Reinicio Total del Sistema
</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="alert alert-danger py-2 px-3 small mb-3">
<i class="bi bi-exclamation-triangle-fill me-1"></i>
<strong>¡Atención!</strong> Esta acción es irreversible. Se eliminarán todas las reservas, aulas, sedes, materias, carreras, comisiones, notas y registros de auditoría previa.
</div>
<p class="small text-muted mb-3">
Las cuentas de usuario y la configuración del sistema permanecerán intactas. Para autorizar la purga definitiva, ingrese su <strong>contraseña de Administrador</strong>:
</p>
<div id="resetFeedbackAlert"></div>
<div class="mb-3">
<label for="admin_reset_password" class="form-label small fw-bold">Contraseña del Administrador en sesión <span class="text-danger">*</span></label>
<div class="input-group input-group-sm">
<span class="input-group-text bg-body-tertiary"><i class="bi bi-key-fill text-danger"></i></span>
<input type="password" class="form-control" id="admin_reset_password" placeholder="Ingrese su contraseña institucional" required autocomplete="current-password">
<button class="btn btn-outline-secondary pass-toggle-btn" type="button" onclick="togglePassVisibility('admin_reset_password')" title="Mostrar/ocultar contraseña">
<i class="bi bi-eye"></i>
</button>
</div>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="confirm_reset_checkbox" onchange="document.getElementById('btnConfirmSystemReset').disabled = !this.checked">
<label class="form-check-label small text-body" for="confirm_reset_checkbox">
Entiendo las consecuencias irreversibles de esta purga y confirmo la reinicialización de los datos.
</label>
</div>
</div>
<div class="modal-footer py-2 bg-light">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal" id="btnCancelReset">Cancelar</button>
<button type="button" class="btn btn-sm btn-danger fw-semibold px-3" id="btnConfirmSystemReset" disabled onclick="executeSystemReset()">
<i class="bi bi-radioactive me-1"></i>Confirmar Reinicio Completo
</button>
</div>
</div>
</div>
</div>
<script>
function showAlert(message, type = 'success') {
const container = document.getElementById('settingsAlertContainer');
@@ -1109,6 +1251,63 @@ async function saveUiConfig(e) {
}
}
// -------------------------------------------------------------
// Reinicialización Total del Sistema (Requiere Contraseña Admin)
// -------------------------------------------------------------
async function executeSystemReset() {
const passwordInput = document.getElementById('admin_reset_password');
const feedback = document.getElementById('resetFeedbackAlert');
const btnConfirm = document.getElementById('btnConfirmSystemReset');
const btnCancel = document.getElementById('btnCancelReset');
const check = document.getElementById('confirm_reset_checkbox');
const password = (passwordInput ? passwordInput.value : '').trim();
if (!password) {
feedback.innerHTML = '<div class="alert alert-warning py-2 px-3 small mb-3"><i class="bi bi-exclamation-circle me-1"></i>Por favor ingrese su contraseña de administrador.</div>';
if (passwordInput) passwordInput.focus();
return;
}
btnConfirm.disabled = true;
btnCancel.disabled = true;
btnConfirm.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Reinicializando sistema...';
feedback.innerHTML = '<div class="alert alert-info py-2 px-3 small mb-3"><span class="spinner-border spinner-border-sm me-1"></span>Procesando purga de datos operacionales...</div>';
try {
const res = await fetch('/admin/settings/reset_system', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password })
});
const data = await res.json();
if (res.ok && data.status === 'success') {
feedback.innerHTML = `<div class="alert alert-success py-2 px-3 small mb-3"><i class="bi bi-check-circle-fill me-1"></i>${data.message}</div>`;
showAlert(data.message, 'success');
passwordInput.value = '';
if (check) check.checked = false;
setTimeout(() => {
const modalEl = document.getElementById('modalResetSystem');
const modalInstance = bootstrap.Modal.getInstance(modalEl);
if (modalInstance) modalInstance.hide();
window.location.reload();
}, 2000);
} else {
feedback.innerHTML = `<div class="alert alert-danger py-2 px-3 small mb-3"><i class="bi bi-x-circle-fill me-1"></i>${data.message || 'Error al autorizar la reinicialización.'}</div>`;
btnConfirm.disabled = false;
btnCancel.disabled = false;
btnConfirm.innerHTML = '<i class="bi bi-radioactive me-1"></i>Confirmar Reinicio Completo';
if (passwordInput) passwordInput.focus();
}
} catch (err) {
feedback.innerHTML = `<div class="alert alert-danger py-2 px-3 small mb-3"><i class="bi bi-exclamation-triangle-fill me-1"></i>Error de comunicación: ${err.message}</div>`;
btnConfirm.disabled = false;
btnCancel.disabled = false;
btnConfirm.innerHTML = '<i class="bi bi-radioactive me-1"></i>Confirmar Reinicio Completo';
}
}
// Cargar estadísticas y tareas al iniciar
document.addEventListener('DOMContentLoaded', () => {
refreshQueueStats();