Files
admin-edu-space/frontend/views/base.html
T

1137 lines
52 KiB
HTML

{% set auth_user = current_user if (current_user and current_user.is_authenticated) else user %}
<!DOCTYPE html>
<html lang="{{ current_lang }}" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Edu-Space Admin{% endblock %}</title>
<!-- Favicon -->
<link rel="icon" type="image/png" href="/img/favicon.png">
<!-- Script Anti-Flicker para Modo Claro / Oscuro -->
<script>
(function() {
let userTheme = "{{ auth_user.theme_preference if (auth_user and auth_user.is_authenticated) else '' }}";
let theme = userTheme;
if (!theme || theme === 'auto') {
const storedTheme = localStorage.getItem('theme');
theme = storedTheme || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
}
document.documentElement.setAttribute('data-bs-theme', theme);
})();
</script>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Design System & Custom Theme -->
<link href="/css/theme.css" rel="stylesheet">
<link href="/css/style.css" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
min-height: 100vh;
transition: background-color 0.25s ease, color 0.25s ease;
}
/* --- MODO CLARO ([data-bs-theme="light"]) - ALTO CONTRASTE & CONFORT VISUAL --- */
[data-bs-theme="light"] body {
background-color: #EEF2F6 !important;
color: #0F172A !important;
}
.navbar-logo,
[data-bs-theme="light"] .navbar-logo,
[data-bs-theme="dark"] .navbar-logo {
filter: none !important;
}
/* Sidebar en Modo Claro */
[data-bs-theme="light"] .sidebar {
background-color: #FFFFFF !important;
border-right: 1px solid #CBD5E1 !important;
box-shadow: 2px 0 12px rgba(15, 23, 42, 0.04) !important;
}
[data-bs-theme="light"] .sidebar-header {
background-color: #FFFFFF !important;
border-bottom: 1px solid #E2E8F0 !important;
}
[data-bs-theme="light"] .sidebar-header .brand-text {
color: #0F172A !important;
font-weight: 700;
}
[data-bs-theme="light"] .sidebar-toggle-btn {
background-color: #F1F5F9 !important;
border: 1px solid #CBD5E1 !important;
color: #1E293B !important;
}
[data-bs-theme="light"] .sidebar-toggle-btn:hover {
background-color: rgba(158, 45, 119, 0.1) !important;
border-color: #9E2D77 !important;
color: #9E2D77 !important;
}
[data-bs-theme="light"] .sidebar-nav .nav-link {
color: #1E293B !important;
font-weight: 600;
}
[data-bs-theme="light"] .sidebar-nav .nav-link:hover,
[data-bs-theme="light"] .sidebar-nav .nav-link[aria-expanded="true"] {
background-color: rgba(158, 45, 119, 0.1) !important;
color: #9E2D77 !important;
}
[data-bs-theme="light"] .sidebar-submenu .nav-link {
color: #334155 !important;
font-weight: 500;
}
[data-bs-theme="light"] .sidebar-submenu .nav-link:hover {
background-color: rgba(158, 45, 119, 0.08) !important;
color: #9E2D77 !important;
}
[data-bs-theme="light"] .sidebar-footer {
background-color: #FFFFFF !important;
border-top: 1px solid #E2E8F0 !important;
}
[data-bs-theme="light"] .sidebar.collapsed .nav-item:hover > .collapse,
[data-bs-theme="light"] .sidebar.collapsed .nav-item > .collapse.show,
[data-bs-theme="light"] .sidebar.collapsed .nav-item.show-flyout > .collapse {
background-color: #FFFFFF !important;
border: 1px solid #CBD5E1 !important;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.15) !important;
}
[data-bs-theme="light"] .sidebar.collapsed .flyout-header {
background-color: #F8FAFC !important;
border-bottom: 1px solid #E2E8F0 !important;
}
/* Navbar & Dropdowns */
[data-bs-theme="light"] .glass-navbar {
background: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid #CBD5E1 !important;
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.05) !important;
}
[data-bs-theme="light"] .glass-navbar .nav-link {
color: #1E293B !important;
font-weight: 600;
}
[data-bs-theme="light"] .glass-navbar .nav-link:hover,
[data-bs-theme="light"] .glass-navbar .nav-link.active {
color: #9E2D77 !important;
}
[data-bs-theme="light"] .dropdown-menu {
background-color: #FFFFFF !important;
border: 1px solid #CBD5E1 !important;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12) !important;
}
[data-bs-theme="light"] .dropdown-item {
color: #1E293B !important;
font-weight: 500;
}
[data-bs-theme="light"] .dropdown-item:hover {
background-color: #F1F5F9 !important;
color: #9E2D77 !important;
}
[data-bs-theme="light"] .dropdown-divider {
border-color: #E2E8F0 !important;
}
[data-bs-theme="light"] .dropdown-header {
color: #475569 !important;
font-weight: 700;
}
/* Cards & Contenedores con Definición y Relieve */
[data-bs-theme="light"] .glass-card,
[data-bs-theme="light"] .card {
background: #FFFFFF !important;
color: #0F172A !important;
border: 1px solid #D8E0E9 !important;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.05), 0 1px 2px rgba(15, 23, 42, 0.03) !important;
}
[data-bs-theme="light"] .card-header {
background-color: #F8FAFC !important;
border-bottom: 1px solid #E2E8F0 !important;
color: #0F172A !important;
}
[data-bs-theme="light"] .card-footer {
background-color: #F8FAFC !important;
border-top: 1px solid #E2E8F0 !important;
}
[data-bs-theme="light"] .dashboard-role-banner {
background: #FFFFFF !important;
border: 1px solid #D8E0E9 !important;
border-left: 5px solid #9E2D77 !important;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05) !important;
}
[data-bs-theme="light"] .dashboard-stat-card:hover {
border-color: #9E2D77 !important;
box-shadow: 0 6px 20px rgba(158, 45, 119, 0.15) !important;
}
/* Tablas con Alto Contraste */
[data-bs-theme="light"] .table {
--bs-table-color: #0F172A;
--bs-table-bg: transparent;
border-color: #E2E8F0 !important;
}
[data-bs-theme="light"] .table thead th {
background-color: #F1F5F9 !important;
color: #0F172A !important;
font-weight: 700 !important;
border-bottom: 2px solid #CBD5E1 !important;
font-size: 0.82rem;
letter-spacing: 0.03em;
}
[data-bs-theme="light"] .table tbody td {
color: #1E293B !important;
border-bottom: 1px solid #E2E8F0 !important;
vertical-align: middle;
}
[data-bs-theme="light"] .table tbody tr:hover td {
background-color: #F8FAFC !important;
}
/* Formularios, Inputs y Selects */
[data-bs-theme="light"] .form-label {
color: #0F172A !important;
font-weight: 600 !important;
}
[data-bs-theme="light"] .form-control,
[data-bs-theme="light"] .form-select {
background-color: #FFFFFF !important;
border: 1px solid #94A3B8 !important;
color: #0F172A !important;
font-weight: 500;
}
[data-bs-theme="light"] .form-control:focus,
[data-bs-theme="light"] .form-select:focus {
border-color: #9E2D77 !important;
box-shadow: 0 0 0 0.2rem rgba(158, 45, 119, 0.2) !important;
background-color: #FFFFFF !important;
color: #0F172A !important;
}
[data-bs-theme="light"] .form-control::placeholder {
color: #64748B !important;
}
[data-bs-theme="light"] .input-group-text {
background-color: #F1F5F9 !important;
border: 1px solid #94A3B8 !important;
color: #334155 !important;
}
[data-bs-theme="light"] .form-text {
color: #475569 !important;
}
/* Jerarquía Tipográfica y Contraste de Texto */
[data-bs-theme="light"] .bg-body-tertiary {
background-color: #F1F5F9 !important;
color: #1E293B !important;
}
[data-bs-theme="light"] .text-body {
color: #0F172A !important;
}
[data-bs-theme="light"] .text-muted {
color: #475569 !important;
}
[data-bs-theme="light"] h1, [data-bs-theme="light"] h2,
[data-bs-theme="light"] h3, [data-bs-theme="light"] h4,
[data-bs-theme="light"] h5, [data-bs-theme="light"] h6 {
color: #0F172A !important;
}
/* Colores Semánticos de Alto Contraste en Modo Claro */
[data-bs-theme="light"] .text-primary {
color: #9E2D77 !important;
}
[data-bs-theme="light"] .text-warning {
color: #B45309 !important;
}
[data-bs-theme="light"] .text-info {
color: #0369A1 !important;
}
[data-bs-theme="light"] .text-success {
color: #15803D !important;
}
[data-bs-theme="light"] .text-danger {
color: #B91C1C !important;
}
[data-bs-theme="light"] .text-secondary {
color: #334155 !important;
}
/* Badges de Estado con Bordes Nítidos y Excelente Contraste */
[data-bs-theme="light"] .badge.bg-primary-subtle {
background-color: #FCE7F3 !important;
color: #831843 !important;
border: 1px solid #F472B6 !important;
}
[data-bs-theme="light"] .badge.bg-success-subtle {
background-color: #DCFCE7 !important;
color: #14532D !important;
border: 1px solid #86EFAC !important;
}
[data-bs-theme="light"] .badge.bg-warning-subtle {
background-color: #FEF3C7 !important;
color: #78350F !important;
border: 1px solid #FCD34D !important;
}
[data-bs-theme="light"] .badge.bg-info-subtle {
background-color: #E0F2FE !important;
color: #0C4A6E !important;
border: 1px solid #7DD3FC !important;
}
[data-bs-theme="light"] .badge.bg-danger-subtle {
background-color: #FEE2E2 !important;
color: #7F1D1D !important;
border: 1px solid #FCA5A5 !important;
}
[data-bs-theme="light"] .badge.bg-secondary-subtle {
background-color: #F1F5F9 !important;
color: #1E293B !important;
border: 1px solid #CBD5E1 !important;
}
/* Fondos con opacidad suave para KPIs */
[data-bs-theme="light"] .bg-primary.bg-opacity-10 {
background-color: rgba(158, 45, 119, 0.12) !important;
color: #9E2D77 !important;
}
[data-bs-theme="light"] .bg-success.bg-opacity-10 {
background-color: rgba(21, 128, 61, 0.12) !important;
color: #15803D !important;
}
[data-bs-theme="light"] .bg-info.bg-opacity-10 {
background-color: rgba(3, 105, 161, 0.12) !important;
color: #0369A1 !important;
}
[data-bs-theme="light"] .bg-warning.bg-opacity-10 {
background-color: rgba(180, 83, 9, 0.12) !important;
color: #B45309 !important;
}
/* Botones de Contorno en Modo Claro */
[data-bs-theme="light"] .btn-outline-secondary {
border-color: #94A3B8 !important;
color: #1E293B !important;
background-color: #FFFFFF !important;
}
[data-bs-theme="light"] .btn-outline-secondary:hover {
background-color: #F1F5F9 !important;
border-color: #64748B !important;
color: #0F172A !important;
}
[data-bs-theme="light"] .btn-outline-primary {
color: #9E2D77 !important;
border-color: #9E2D77 !important;
background-color: #FFFFFF !important;
}
[data-bs-theme="light"] .btn-outline-primary:hover {
background-color: #9E2D77 !important;
border-color: #832062 !important;
color: #FFFFFF !important;
}
[data-bs-theme="light"] .role-view-selector {
background-color: #E2E8F0 !important;
border: 1px solid #CBD5E1 !important;
}
[data-bs-theme="light"] .role-view-selector .btn-outline-secondary {
color: #334155 !important;
background-color: transparent !important;
}
/* Bordes y Líneas Divisorias */
[data-bs-theme="light"] .border,
[data-bs-theme="light"] .border-top,
[data-bs-theme="light"] .border-bottom,
[data-bs-theme="light"] .border-start,
[data-bs-theme="light"] .border-end {
border-color: #CBD5E1 !important;
}
[data-bs-theme="light"] .border-secondary-subtle {
border-color: #CBD5E1 !important;
}
/* Botón de Alternancia de Tema */
[data-bs-theme="light"] .theme-toggle-btn {
background: rgba(15, 23, 42, 0.08);
color: #0F172A;
border: 1px solid #CBD5E1;
border-radius: 50%;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
[data-bs-theme="light"] .theme-toggle-btn:hover {
background: rgba(15, 23, 42, 0.15);
transform: scale(1.05);
}
/* --- MODO OSCURO ([data-bs-theme="dark"]) --- */
[data-bs-theme="dark"] body {
background: linear-gradient(135deg, #0F172A 0%, #1E1B4B 100%) !important;
color: #F8FAFC !important;
}
[data-bs-theme="dark"] .glass-navbar {
background: rgba(15, 23, 42, 0.78) !important;
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3) !important;
}
[data-bs-theme="dark"] .glass-navbar .nav-link {
color: #CBD5E1 !important;
}
[data-bs-theme="dark"] .glass-navbar .nav-link:hover,
[data-bs-theme="dark"] .glass-navbar .nav-link.active {
color: #FF8AB6 !important;
}
[data-bs-theme="dark"] .glass-navbar .dropdown-menu {
background: #1E293B !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5) !important;
}
[data-bs-theme="dark"] .glass-navbar .dropdown-item {
color: #CBD5E1 !important;
}
[data-bs-theme="dark"] .glass-navbar .dropdown-item:hover {
background-color: rgba(255, 255, 255, 0.08) !important;
color: #FF8AB6 !important;
}
[data-bs-theme="dark"] .glass-card,
[data-bs-theme="dark"] .card {
background: rgba(30, 41, 59, 0.7) !important;
color: #F8FAFC !important;
border: 1px solid rgba(255, 255, 255, 0.08) !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
}
[data-bs-theme="dark"] .table {
--bs-table-color: #E2E8F0;
--bs-table-bg: transparent;
}
[data-bs-theme="dark"] .bg-body-tertiary {
background-color: rgba(255, 255, 255, 0.05) !important;
color: #CBD5E1 !important;
}
[data-bs-theme="dark"] .text-body {
color: #F8FAFC !important;
}
[data-bs-theme="dark"] .text-muted {
color: #94A3B8 !important;
}
[data-bs-theme="dark"] .theme-toggle-btn {
background: rgba(255, 255, 255, 0.1);
color: #FFD54F;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 50%;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
[data-bs-theme="dark"] .theme-toggle-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
}
.navbar-brand span {
background: linear-gradient(90deg, #B43E8E, #682CAD);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
}
/* =======================================================
SIDEBAR & TOGGLE BUTTON DESIGN (720p, 1360x768 & 1080p)
======================================================= */
.app-wrapper {
min-height: 100vh;
display: flex;
}
.sidebar {
width: 250px;
transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
flex-shrink: 0;
z-index: 1040;
display: flex;
flex-direction: column;
border-right: 1px solid rgba(0, 0, 0, 0.08);
background: var(--bs-body-bg);
position: sticky;
top: 0;
height: 100vh;
}
[data-bs-theme="dark"] .sidebar {
border-right: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(24, 32, 47, 0.98);
}
/* Header del Sidebar */
.sidebar-header {
height: 54px;
padding: 0.5rem 0.85rem;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
transition: all 0.25s ease;
}
[data-bs-theme="dark"] .sidebar-header {
border-bottom-color: rgba(255, 255, 255, 0.07);
}
/* Botón de Toggle Limpio & Estilizado */
.sidebar-toggle-btn {
width: 34px;
height: 34px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.08);
background: rgba(0, 0, 0, 0.03);
color: #475569;
transition: all 0.2s ease-in-out;
cursor: pointer;
padding: 0;
outline: none !important;
box-shadow: none !important;
}
.sidebar-toggle-btn:hover {
background: rgba(180, 62, 142, 0.1);
border-color: rgba(180, 62, 142, 0.4);
color: #B43E8E !important;
transform: scale(1.05);
}
[data-bs-theme="dark"] .sidebar-toggle-btn {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.1);
color: #94A3B8;
}
[data-bs-theme="dark"] .sidebar-toggle-btn:hover {
background: rgba(255, 138, 182, 0.15);
border-color: rgba(255, 138, 182, 0.4);
color: #FF8AB6 !important;
transform: scale(1.05);
}
/* Sidebar Colapsado (Compacto, ultra limpio para pantallas chicas) */
.sidebar.collapsed {
width: 68px;
overflow: visible !important;
}
.sidebar.collapsed .brand-link {
display: none !important;
}
.sidebar.collapsed .sidebar-header {
justify-content: center !important;
padding: 0.5rem !important;
}
.sidebar.collapsed .brand-text,
.sidebar.collapsed .nav-text,
.sidebar.collapsed .nav-caret,
.sidebar.collapsed .sidebar-footer-text,
.sidebar.collapsed .dropdown-header {
display: none !important;
}
.sidebar.collapsed .sidebar-body {
overflow: visible !important;
}
.sidebar.collapsed .nav-item {
position: relative;
}
.sidebar.collapsed .nav-link {
justify-content: center !important;
padding: 0.55rem 0 !important;
position: relative;
}
.sidebar.collapsed .nav-link i {
margin-right: 0 !important;
font-size: 1.25rem !important;
}
.sidebar.collapsed .sidebar-footer {
padding: 0.5rem !important;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.sidebar.collapsed .sidebar-footer .theme-toggle-btn {
margin-bottom: 0.5rem !important;
}
/* Submenús flotantes (Flyout) cuando el sidebar está contraído */
.sidebar.collapsed .collapse {
display: none;
}
.sidebar.collapsed .nav-item:hover > .collapse,
.sidebar.collapsed .nav-item > .collapse.show,
.sidebar.collapsed .nav-item.show-flyout > .collapse {
display: block !important;
position: absolute;
left: 100%;
top: 0;
margin-left: 6px;
min-width: 220px;
background: var(--bs-body-bg);
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 12px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
z-index: 1070;
padding: 0.5rem 0;
animation: flyoutSlideIn 0.15s cubic-bezier(0.16, 1, 0.3, 1);
}
[data-bs-theme="dark"] .sidebar.collapsed .nav-item:hover > .collapse,
[data-bs-theme="dark"] .sidebar.collapsed .nav-item > .collapse.show,
[data-bs-theme="dark"] .sidebar.collapsed .nav-item.show-flyout > .collapse {
background: #1e293b;
border-color: #334155;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
}
@keyframes flyoutSlideIn {
from { opacity: 0; transform: translateX(-6px); }
to { opacity: 1; transform: translateX(0); }
}
.sidebar.collapsed .flyout-header {
display: block !important;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.35rem 1rem 0.45rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
margin-bottom: 0.35rem;
}
[data-bs-theme="dark"] .sidebar.collapsed .flyout-header {
border-bottom-color: rgba(255, 255, 255, 0.08);
}
.sidebar.collapsed .nav-item:hover .sidebar-submenu,
.sidebar.collapsed .nav-item > .collapse.show .sidebar-submenu,
.sidebar.collapsed .nav-item.show-flyout .sidebar-submenu {
padding-left: 0 !important;
margin: 0 !important;
list-style: none;
}
.sidebar.collapsed .nav-item:hover .sidebar-submenu .nav-link,
.sidebar.collapsed .nav-item > .collapse.show .sidebar-submenu .nav-link,
.sidebar.collapsed .nav-item.show-flyout .sidebar-submenu .nav-link {
padding: 0.5rem 1rem !important;
font-size: 0.85rem;
white-space: nowrap;
display: flex;
align-items: center;
justify-content: flex-start !important;
}
.sidebar.collapsed .nav-item:hover .sidebar-submenu .nav-link:hover,
.sidebar.collapsed .nav-item > .collapse.show .sidebar-submenu .nav-link:hover,
.sidebar.collapsed .nav-item.show-flyout .sidebar-submenu .nav-link:hover {
background-color: rgba(180, 62, 142, 0.08);
color: #B43E8E !important;
}
[data-bs-theme="dark"] .sidebar.collapsed .nav-item:hover .sidebar-submenu .nav-link:hover,
[data-bs-theme="dark"] .sidebar.collapsed .nav-item > .collapse.show .sidebar-submenu .nav-link:hover,
[data-bs-theme="dark"] .sidebar.collapsed .nav-item.show-flyout .sidebar-submenu .nav-link:hover {
background-color: rgba(255, 138, 182, 0.1);
color: #FF8AB6 !important;
}
/* Navegación del Sidebar */
.sidebar-nav {
padding: 0;
list-style: none;
width: 100%;
}
.sidebar-nav .nav-item {
margin-bottom: 2px;
width: 100%;
}
.sidebar-nav .nav-link {
display: flex;
align-items: center;
padding: 0.5rem 0.85rem;
color: var(--bs-body-color);
border-radius: 8px;
text-decoration: none;
white-space: nowrap;
font-size: 0.88rem;
font-weight: 500;
transition: all 0.18s ease;
width: 100%;
}
.sidebar-nav .nav-link:hover, .sidebar-nav .nav-link[aria-expanded="true"] {
background-color: rgba(180, 62, 142, 0.08);
color: #B43E8E !important;
}
[data-bs-theme="dark"] .sidebar-nav .nav-link:hover, [data-bs-theme="dark"] .sidebar-nav .nav-link[aria-expanded="true"] {
background-color: rgba(255, 138, 182, 0.1);
color: #FF8AB6 !important;
}
.sidebar-nav .nav-link i {
font-size: 1.15rem;
min-width: 26px;
text-align: center;
margin-right: 10px;
}
.sidebar-submenu {
list-style: none;
padding-left: 2.2rem;
margin-top: 0.2rem;
}
.sidebar-submenu .nav-link {
padding: 0.35rem 0.75rem;
font-size: 0.83rem;
color: var(--bs-secondary-color);
}
/* Contenido Principal & Eliminación de Desperdicio Superior */
.main-content {
flex-grow: 1;
min-width: 0;
display: flex;
flex-direction: column;
background: transparent;
}
.app-main-container {
padding-top: 0.85rem !important;
padding-bottom: 1.5rem !important;
}
/* Anular margen excesivo heredado de templates antiguos */
.app-main-container > .container-fluid.pt-5.mt-4,
.app-main-container > .container.pt-5.mt-4,
.app-main-container .pt-5.mt-4,
.main-content .container-fluid.pt-5.mt-4,
.main-content .container.pt-5.mt-4 {
padding-top: 0 !important;
margin-top: 0 !important;
}
/* Mobile Header */
.mobile-header {
display: none;
}
@media (max-width: 991.98px) {
.sidebar {
position: fixed;
left: -260px;
}
.sidebar.mobile-open {
left: 0;
}
.mobile-header {
display: flex;
background: var(--bs-body-bg);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
padding: 0.5rem 1rem;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 1030;
height: 52px;
}
[data-bs-theme="dark"] .mobile-header {
border-bottom-color: rgba(255, 255, 255, 0.08);
}
}
/* =======================================================
OPTIMIZACIONES PARA 720p / 1360x768 (Mercado Argentino / Bedelía)
======================================================= */
@media (max-height: 820px), (max-width: 1400px) {
.sidebar-header {
height: 48px;
padding: 0.4rem 0.65rem;
}
.sidebar-header .brand-text {
font-size: 1.05rem !important;
}
.sidebar-header .navbar-logo {
height: 24px;
}
.sidebar-nav .nav-link {
padding: 0.4rem 0.75rem;
font-size: 0.83rem;
}
.sidebar-nav .nav-link i {
font-size: 1.05rem;
min-width: 22px;
margin-right: 8px;
}
.sidebar-submenu .nav-link {
padding: 0.28rem 0.65rem;
font-size: 0.8rem;
}
.sidebar-footer {
padding: 0.4rem 0.65rem !important;
}
.app-main-container {
padding-top: 0.5rem !important;
padding-left: 0.85rem !important;
padding-right: 0.85rem !important;
}
/* Títulos y márgenes más compactos para ganar viewport útil */
h1.h3, .h3, h1.h4, .h4 {
font-size: 1.25rem !important;
margin-bottom: 0.2rem !important;
}
.dashboard-role-banner {
padding: 0.75rem 1rem !important;
margin-bottom: 0.85rem !important;
}
.card {
margin-bottom: 0.85rem !important;
}
.card-body {
padding: 0.85rem !important;
}
}
/* =======================================================
OPTIMIZACIONES PARA 1080p (1920x1080 / Monitores Grandes)
======================================================= */
@media (min-width: 1600px) and (min-height: 900px) {
.sidebar {
width: 270px;
}
.sidebar-header {
height: 60px;
padding: 0.75rem 1rem;
}
.sidebar-nav .nav-link {
padding: 0.58rem 1rem;
font-size: 0.92rem;
}
.app-main-container {
padding-top: 1.25rem !important;
padding-left: 2rem !important;
padding-right: 2rem !important;
}
}
.brand-link span {
background: linear-gradient(90deg, #B43E8E, #682CAD);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body data-user-authenticated="{{ 'true' if (auth_user and auth_user.is_authenticated) else 'false' }}">
{% if auth_user and auth_user.is_authenticated %}
<div class="app-wrapper">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header border-bottom border-secondary-subtle">
<a href="/dashboard" class="text-decoration-none d-flex align-items-center brand-link">
<img src="/img/logo_unicaba_mini.png" alt="Logo UniCABA" class="navbar-logo me-2" height="26">
<span class="brand-text fs-5">Edu-Space</span>
</a>
<button class="sidebar-toggle-btn d-none d-lg-inline-flex" id="sidebarToggle" type="button" aria-label="Alternar menú lateral" title="Alternar menú lateral">
<i class="bi bi-layout-sidebar-inset fs-5"></i>
</button>
<button class="sidebar-toggle-btn d-lg-none" id="sidebarCloseMobile" type="button" aria-label="Cerrar menú">
<i class="bi bi-x-lg fs-5"></i>
</button>
</div>
<div class="sidebar-body flex-grow-1 overflow-y-auto p-2">
<ul class="sidebar-nav" id="sidebarNav">
{% set user_role = role if role else (auth_user.role if auth_user else 'admin') %}
{% if user_role == 'admin' %}
<!-- ADMIN -->
<li class="nav-item">
<a class="nav-link" href="/dashboard"><i class="bi bi-speedometer2 text-primary"></i> <span class="nav-text">Panel General</span></a>
</li>
<li class="nav-item">
<a class="nav-link collapsed" href="#menuEspacios" data-bs-toggle="collapse" role="button" aria-expanded="false">
<i class="bi bi-door-open text-info"></i> <span class="nav-text flex-grow-1">Espacios & Sedes</span> <i class="bi bi-chevron-down nav-caret ms-auto fs-6"></i>
</a>
<div class="collapse" id="menuEspacios" data-bs-parent="#sidebarNav">
<div class="flyout-header text-info d-none"><i class="bi bi-door-open me-1"></i>Espacios & Sedes</div>
<ul class="sidebar-submenu">
<li><a class="nav-link" href="/classrooms/list_classrooms?view_mode=physical">Presenciales</a></li>
<li><a class="nav-link" href="/classrooms/list_classrooms?view_mode=virtual">Virtuales</a></li>
<li><a class="nav-link" href="/classrooms/list_classrooms">Todas las Aulas</a></li>
<li><a class="nav-link text-success" href="/classrooms/add_classroom"><i class="bi bi-plus-circle me-1" style="min-width:auto; font-size:1rem;"></i>Nueva Aula</a></li>
<li><a class="nav-link" href="/buildings/list_buildings">Edificios</a></li>
<li><a class="nav-link" href="/admin/occupancy_metrics">Métricas</a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link collapsed" href="#menuReservas" data-bs-toggle="collapse" role="button" aria-expanded="false">
<i class="bi bi-calendar3 text-warning"></i> <span class="nav-text flex-grow-1">Reservas</span> <i class="bi bi-chevron-down nav-caret ms-auto fs-6"></i>
</a>
<div class="collapse" id="menuReservas" data-bs-parent="#sidebarNav">
<div class="flyout-header text-warning d-none"><i class="bi bi-calendar3 me-1"></i>Reservas</div>
<ul class="sidebar-submenu">
<li><a class="nav-link" href="/schedule/calendar_view">Calendario</a></li>
<li><a class="nav-link" href="/schedule/today_schedule">Cartelera</a></li>
<li><a class="nav-link" href="/schedule/list_reservations">Gestión</a></li>
<li><a class="nav-link text-success" href="/schedule/add_reservation"><i class="bi bi-plus-circle me-1" style="min-width:auto; font-size:1rem;"></i>Nueva</a></li>
<li><a class="nav-link" href="/schedule/commissions_view">Comisiones</a></li>
<li><a class="nav-link text-info fw-semibold" href="/genetic_algorithm_web/genetic_optimizer"><i class="bi bi-cpu me-1" style="min-width:auto; font-size:1rem;"></i>IA Optimizer</a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link collapsed" href="#menuAcademica" data-bs-toggle="collapse" role="button" aria-expanded="false">
<i class="bi bi-mortarboard text-success"></i> <span class="nav-text flex-grow-1">Académica</span> <i class="bi bi-chevron-down nav-caret ms-auto fs-6"></i>
</a>
<div class="collapse" id="menuAcademica" data-bs-parent="#sidebarNav">
<div class="flyout-header text-success d-none"><i class="bi bi-mortarboard me-1"></i>Académica</div>
<ul class="sidebar-submenu">
<li><a class="nav-link" href="/admin/careers_list">Carreras</a></li>
<li><a class="nav-link" href="/admin/subjects_list">Asignaturas</a></li>
<li><a class="nav-link" href="/admin/commissions_list">Comisiones</a></li>
<li><a class="nav-link" href="/admin/academic_terms_list">Ciclo Lectivo</a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link collapsed" href="#menuAdmin" data-bs-toggle="collapse" role="button" aria-expanded="false">
<i class="bi bi-shield-lock text-secondary"></i> <span class="nav-text flex-grow-1">Administración</span> <i class="bi bi-chevron-down nav-caret ms-auto fs-6"></i>
</a>
<div class="collapse" id="menuAdmin" data-bs-parent="#sidebarNav">
<div class="flyout-header text-secondary d-none"><i class="bi bi-shield-lock me-1"></i>Administración</div>
<ul class="sidebar-submenu">
<li><a class="nav-link" href="/admin/users_list">Usuarios</a></li>
<li><a class="nav-link" href="/admin/roles_list">Roles RBAC</a></li>
<li><a class="nav-link" href="/admin/milestone_types_list">Tipos Hitos</a></li>
<li><a class="nav-link" href="/admin/google_sheets_import">Sincro Sheets</a></li>
<li><a class="nav-link" href="/admin/audit_logs">Auditoría</a></li>
<li><a class="nav-link text-primary fw-semibold" href="/admin/settings"><i class="bi bi-gear-wide-connected me-1" style="min-width:auto; font-size:1rem;"></i>Config. Global</a></li>
</ul>
</div>
</li>
{% elif user_role == 'bedelia' %}
<!-- BEDELIA -->
<li class="nav-item"><a class="nav-link" href="/dashboard"><i class="bi bi-speedometer2 text-warning"></i> <span class="nav-text">Panel Bedelía</span></a></li>
<li class="nav-item"><a class="nav-link" href="/classrooms/list_classrooms"><i class="bi bi-door-open text-primary"></i> <span class="nav-text">Aulas y Espacios</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/today_schedule"><i class="bi bi-calendar-day text-info"></i> <span class="nav-text">Cartelera Diaria</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/list_reservations"><i class="bi bi-list-check text-success"></i> <span class="nav-text">Gestión Reservas</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/add_reservation"><i class="bi bi-plus-circle text-success"></i> <span class="nav-text">Nueva Reserva</span></a></li>
<li class="nav-item"><a class="nav-link" href="/admin/commissions_list"><i class="bi bi-diagram-3 text-primary"></i> <span class="nav-text">Comisiones</span></a></li>
{% elif user_role == 'docente' %}
<!-- DOCENTE -->
<li class="nav-item"><a class="nav-link" href="/dashboard"><i class="bi bi-speedometer2 text-info"></i> <span class="nav-text">Panel Docente</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/list_reservations"><i class="bi bi-calendar2-check text-primary"></i> <span class="nav-text">Mis Reservas</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/add_reservation"><i class="bi bi-plus-circle text-success"></i> <span class="nav-text">Solicitar Reserva</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/commissions_view"><i class="bi bi-diagram-3 text-info"></i> <span class="nav-text">Mis Comisiones</span></a></li>
<li class="nav-item"><a class="nav-link" href="/my_subjects/my_subjects_list"><i class="bi bi-mortarboard text-warning"></i> <span class="nav-text">Mis Materias</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/today_schedule"><i class="bi bi-geo-alt text-danger"></i> <span class="nav-text">Cartelera Hoy</span></a></li>
{% else %}
<!-- ALUMNO -->
<li class="nav-item"><a class="nav-link" href="/dashboard"><i class="bi bi-house-door text-success"></i> <span class="nav-text">Mi Portal</span></a></li>
<li class="nav-item"><a class="nav-link" href="/my_subjects/my_subjects_list"><i class="bi bi-mortarboard text-primary"></i> <span class="nav-text">Mis Materias</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/today_schedule"><i class="bi bi-geo-alt text-danger"></i> <span class="nav-text">Cartelera Hoy</span></a></li>
<li class="nav-item"><a class="nav-link" href="/schedule/calendar_view"><i class="bi bi-calendar-week text-info"></i> <span class="nav-text">Cronograma</span></a></li>
<li class="nav-item"><a class="nav-link" href="/classrooms/list_classrooms"><i class="bi bi-camera-video text-secondary"></i> <span class="nav-text">Aulas Virtuales</span></a></li>
{% endif %}
</ul>
</div>
<div class="sidebar-footer p-3 border-top border-secondary-subtle">
<div class="d-flex align-items-center justify-content-between mb-3 sidebar-footer-text">
{% include 'partials/language_switcher.html' %}
<button class="theme-toggle-btn" type="button" aria-label="Cambiar tema" title="Modo Claro/Oscuro">
<i class="bi bi-moon-stars-fill"></i>
</button>
</div>
<div class="dropdown dropup">
<a href="#" class="d-flex align-items-center text-decoration-none dropdown-toggle text-body w-100" data-bs-toggle="dropdown" aria-expanded="false">
<div class="user-avatar-small bg-primary text-white rounded-circle d-flex align-items-center justify-content-center" style="width:32px; height:32px; font-weight:bold; flex-shrink:0;">
{{ auth_user.name[0]|upper if auth_user.name else 'U' }}
</div>
<div class="ms-2 sidebar-footer-text text-truncate flex-grow-1" style="min-width:0;">
<small class="d-block fw-semibold text-truncate">{{ auth_user.first_name or auth_user.name }}</small>
<small class="text-muted d-block text-truncate" style="font-size:0.7rem;">{{ auth_user.role_badge_display.label if (auth_user and auth_user.role_badge_display) else (auth_user.role|capitalize if auth_user else 'Usuario') }}</small>
</div>
</a>
<ul class="dropdown-menu shadow-sm border-0 mb-2">
<li><h6 class="dropdown-header">{{ auth_user.email if auth_user else '' }}</h6></li>
<li><a class="dropdown-item" href="/auth/profile"><i class="bi bi-person me-2"></i>Mi Perfil</a></li>
<li><a class="dropdown-item" href="/auth/change_password"><i class="bi bi-key me-2"></i>Cambiar Contraseña</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="/auth/logout"><i class="bi bi-box-arrow-right me-2"></i>Cerrar Sesión</a></li>
</ul>
</div>
</div>
</aside>
<!-- Main Content Wrapper -->
<div class="main-content">
<!-- Mobile Header -->
<div class="mobile-header shadow-sm">
<a href="/dashboard" class="text-decoration-none brand-link d-flex align-items-center">
<img src="/img/logo_unicaba_mini.png" alt="Logo UniCABA" class="navbar-logo me-2" height="24">
<span class="brand-text fw-bold fs-6">Edu-Space</span>
</a>
<button class="sidebar-toggle-btn" id="mobileMenuBtn" type="button" aria-label="Abrir menú">
<i class="bi bi-list fs-4"></i>
</button>
</div>
{% if is_impersonating %}
<!-- Banner Impersonación Activa -->
<div class="impersonation-banner px-3 py-2 d-flex align-items-center justify-content-between text-white shadow-sm" style="background: linear-gradient(90deg, #d97706 0%, #b45309 100%); z-index: 1045; border-bottom: 2px solid #f59e0b;">
<div class="d-flex align-items-center gap-2">
<div class="spinner-grow spinner-grow-sm text-warning" role="status" style="width: 14px; height: 14px;">
<span class="visually-hidden">Impersonating...</span>
</div>
<div>
<strong class="me-1"><i class="bi bi-incognito me-1"></i>Modo Impersonación Activo:</strong>
<span>Estás navegando como</span>
<strong class="text-warning-emphasis bg-white bg-opacity-25 px-2 py-0.5 rounded">{{ impersonated_user.name or impersonated_user.first_name or impersonated_user.username }}</strong>
<span class="badge bg-dark bg-opacity-50 text-white ms-1">{{ impersonated_user.role|capitalize }}</span>
<small class="ms-2 opacity-75 d-none d-md-inline">· Registro de auditoría activo (todas las acciones se trazan a tu usuario admin)</small>
</div>
</div>
<div>
<a href="/auth/stop-impersonating" class="btn btn-sm btn-light text-dark fw-bold shadow-sm d-inline-flex align-items-center">
<i class="bi bi-box-arrow-left me-1"></i> Salir de Impersonación
</a>
</div>
</div>
{% endif %}
<main class="container-fluid app-main-container px-3 px-xl-4">
<!-- Flash Messages -->
<div class="toast-container position-fixed bottom-0 end-0 p-3" style="z-index: 1080;">
</div>
<!-- Main Content -->
{% block content %}{% endblock %}
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- Theme Toggle JS -->
<script src="/js/theme-toggle.js"></script>
<!-- Custom JS -->
<script src="/js/main.js"></script>
<!-- View Switcher JS -->
<script src="/js/view_switcher.js"></script>
<!-- Show flash messages -->
<script>
$(document).ready(function() {
$('.toast').toast('show');
});
</script>
{% block extra_js %}{% endblock %}
</main>
</div><!-- main-content -->
</div><!-- app-wrapper -->
{% endif %}
<!-- Sidebar Toggle JS -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const sidebar = document.getElementById("sidebar");
const toggleBtn = document.getElementById("sidebarToggle");
const mobileBtn = document.getElementById("mobileMenuBtn");
const closeMobileBtn = document.getElementById("sidebarCloseMobile");
if (!sidebar) return;
function updateToggleIcon() {
if (!toggleBtn) return;
const isCollapsed = sidebar.classList.contains("collapsed");
const icon = toggleBtn.querySelector("i");
if (icon) {
icon.className = isCollapsed ? "bi bi-layout-sidebar fs-5" : "bi bi-layout-sidebar-inset fs-5";
}
toggleBtn.setAttribute("title", isCollapsed ? "Expandir menú" : "Colapsar menú");
toggleBtn.setAttribute("aria-label", isCollapsed ? "Expandir menú" : "Colapsar menú");
}
// Restore state from localStorage (desktop only)
if (window.innerWidth >= 992) {
const isCollapsed = localStorage.getItem("sidebarCollapsed") === "true";
if (isCollapsed) {
sidebar.classList.add("collapsed");
}
}
updateToggleIcon();
if (toggleBtn) {
toggleBtn.addEventListener("click", function() {
sidebar.classList.toggle("collapsed");
localStorage.setItem("sidebarCollapsed", sidebar.classList.contains("collapsed"));
updateToggleIcon();
// Limpiar flyouts abiertos al alternar modo
document.querySelectorAll('.nav-item.show-flyout').forEach(function(item) {
item.classList.remove('show-flyout');
});
});
}
// Manejo de clicks en submenús cuando el sidebar está colapsado (pantallas táctiles o click explícito)
document.querySelectorAll('#sidebarNav > .nav-item').forEach(function(item) {
const trigger = item.querySelector('[data-bs-toggle="collapse"]');
if (trigger) {
trigger.addEventListener('click', function(e) {
if (sidebar.classList.contains('collapsed')) {
e.preventDefault();
e.stopPropagation();
const isAlreadyOpen = item.classList.contains('show-flyout');
document.querySelectorAll('#sidebarNav > .nav-item.show-flyout').forEach(function(other) {
other.classList.remove('show-flyout');
});
if (!isAlreadyOpen) {
item.classList.add('show-flyout');
}
}
});
}
});
// Cerrar flyouts flotantes al hacer click fuera del sidebar
document.addEventListener('click', function(e) {
if (!sidebar.contains(e.target)) {
document.querySelectorAll('#sidebarNav > .nav-item.show-flyout').forEach(function(item) {
item.classList.remove('show-flyout');
});
}
});
if (mobileBtn) {
mobileBtn.addEventListener("click", function() {
sidebar.classList.add("mobile-open");
});
}
if (closeMobileBtn) {
closeMobileBtn.addEventListener("click", function() {
sidebar.classList.remove("mobile-open");
});
}
});
</script>
</body>
</html>