Files
admin-edu-space/app/templates/dashboard.html
T

1112 lines
65 KiB
HTML

{% extends "base.html" %}
{% block title %}
{% if role == 'admin' %}
{% trans %}Panel de Administración - Edu-Space Admin{% endtrans %}
{% elif role == 'bedelia' %}
{% trans %}Panel Operativo de Bedelía - Edu-Space Admin{% endtrans %}
{% elif role == 'docente' %}
{% trans %}Panel Docente - Edu-Space Admin{% endtrans %}
{% else %}
{% trans %}Mi Portal de Cursada - Edu-Space Admin{% endtrans %}
{% endif %}
{% endblock %}
{% block content %}
{% if current_user.is_authenticated %}
<div class="container-fluid pt-5 mt-4">
{% endif %}
<!-- ========================================================================= -->
<!-- 1. HEADER & ROLE PERSPECTIVE SWITCHER -->
<!-- ========================================================================= -->
<div class="dashboard-role-banner mb-4 shadow-sm">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-3">
<div>
<div class="d-flex align-items-center gap-2 mb-1 flex-wrap">
<h1 class="h4 mb-0 fw-bold">
{% trans %}¡Hola{% endtrans %}, {{ current_user.first_name or current_user.name }}!
</h1>
<span class="badge {{ current_user.role_badge_display.class }} role-pill">
<i class="bi {{ current_user.role_badge_display.icon }}"></i>
{{ current_user.role_badge_display.label }}
</span>
{% if role != actual_role %}
<span class="badge bg-warning text-dark border font-monospace small">
<i class="bi bi-eye-fill me-1"></i>{% trans %}Modo Vista Previa{% endtrans %}: {{ role|capitalize }}
</span>
{% endif %}
</div>
<p class="text-muted small mb-0">
{% if role == 'admin' %}
<i class="bi bi-shield-check text-primary me-1"></i>{% trans %}Gestión centralizada de infraestructura, oferta académica, seguridad y trazabilidad institucional.{% endtrans %}
{% elif role == 'bedelia' %}
<i class="bi bi-building-gear text-warning me-1"></i>{% trans %}Control operativo de campus: asignación de aulas físicas y virtuales, cartelera del día y aprobaciones.{% endtrans %}
{% elif role == 'docente' %}
<i class="bi bi-person-video3 text-info me-1"></i>{% trans %}Espacio docente: gestión de cursadas, horarios de clase, solicitudes de aula y enlaces de videoconferencia.{% endtrans %}
{% else %}
<i class="bi bi-mortarboard text-success me-1"></i>{% trans %}Portal de cursada: consulta tu aula de hoy, cronograma semanal de clases y fechas de exámenes.{% endtrans %}
{% endif %}
</p>
</div>
<!-- Role Perspective Switcher for Admins & Managers -->
{% if current_user.is_admin() or current_user.can_manage() %}
<div class="d-flex flex-column align-items-end">
<small class="text-muted fw-semibold mb-1" style="font-size: 0.72rem; letter-spacing: 0.3px;">
<i class="bi bi-person-bounding-box me-1"></i>{% trans %}VER PANEL COMO ROL:{% endtrans %}
</small>
<div class="btn-group role-view-selector bg-body-tertiary p-1 rounded-pill border" role="group">
<a href="{{ url_for('main.dashboard', view_as='admin') }}"
class="btn btn-sm {% if role == 'admin' %}btn-primary active{% else %}btn-outline-secondary border-0{% endif %}">
<i class="bi bi-shield-check me-1"></i>Admin
</a>
<a href="{{ url_for('main.dashboard', view_as='bedelia') }}"
class="btn btn-sm {% if role == 'bedelia' %}btn-warning active text-dark{% else %}btn-outline-secondary border-0{% endif %}">
<i class="bi bi-building-gear me-1"></i>Bedelía
</a>
<a href="{{ url_for('main.dashboard', view_as='docente') }}"
class="btn btn-sm {% if role == 'docente' %}btn-info active text-white{% else %}btn-outline-secondary border-0{% endif %}">
<i class="bi bi-person-video3 me-1"></i>Docente
</a>
<a href="{{ url_for('main.dashboard', view_as='alumno') }}"
class="btn btn-sm {% if role == 'alumno' %}btn-success active text-white{% else %}btn-outline-secondary border-0{% endif %}">
<i class="bi bi-mortarboard me-1"></i>Alumno
</a>
</div>
</div>
{% endif %}
</div>
</div>
<!-- ========================================================================= -->
<!-- 2. ROLE SPECIFIC DASHBOARDS -->
<!-- ========================================================================= -->
{% if role == 'admin' %}
<!-- ========================= [A] ADMIN DASHBOARD ========================= -->
<!-- Admin KPI Cards -->
<div class="row mb-4 g-3">
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('classrooms.list_classrooms') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Aulas Totales{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-primary">{{ stats.total_classrooms }}</h2>
</div>
<div class="bg-primary bg-opacity-10 rounded-circle p-3 text-primary">
<i class="bi bi-door-open fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-primary small fw-semibold">
<span>{% trans %}Ver catálogo de espacios{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('admin.subjects_list') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Oferta de Materias{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-success">{{ stats.total_subjects }}</h2>
</div>
<div class="bg-success bg-opacity-10 rounded-circle p-3 text-success">
<i class="bi bi-book fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-success small fw-semibold">
<span>{% trans %}Gestión académica{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('admin.users_list') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Usuarios Activos{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-info">{{ stats.total_users or 1 }}</h2>
</div>
<div class="bg-info bg-opacity-10 rounded-circle p-3 text-info">
<i class="bi bi-people fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-info small fw-semibold">
<span>{% trans %}Control de usuarios y roles{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('schedule.today_schedule') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Reservas de Hoy{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-warning">{{ stats.today_reservations }}</h2>
</div>
<div class="bg-warning bg-opacity-10 rounded-circle p-3 text-warning">
<i class="bi bi-calendar-check fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-warning small fw-semibold">
<span>{% trans %}Cartelera en tiempo real{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
</div>
<!-- Admin Visual Menu Launchpad -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-body-tertiary border-0 py-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-grid-3x3-gap-fill text-primary me-2"></i>{% trans %}Módulos de Gestión & Visualización de Menús{% endtrans %}
</h5>
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">{% trans %}Vista Administrador{% endtrans %}</span>
</div>
</div>
<div class="card-body p-3">
<div class="row g-3">
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('classrooms.list_classrooms') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-primary-subtle text-primary me-3">
<i class="bi bi-door-open fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Espacios & Sedes{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Aulas físicas, virtuales y edificios{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.calendar_view') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-info-subtle text-info me-3">
<i class="bi bi-calendar-week fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Cronograma & Reservas{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Calendario, cartelera y reservas{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('genetic_algorithm_web.genetic_optimizer') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-warning-subtle text-warning me-3">
<i class="bi bi-cpu fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Optimizador IA{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Asignación genética inteligente{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('admin.audit_logs') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-danger-subtle text-danger me-3">
<i class="bi bi-shield-lock fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Auditoría & RBAC{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Trazabilidad y control de permisos{% endtrans %}</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<!-- Admin Analytics & Audit Row -->
<div class="row mb-4">
<!-- Monthly Chart -->
<div class="col-lg-8 mb-4 mb-lg-0">
<div class="card border-0 shadow-sm h-100">
<div class="card-header border-0 py-3 bg-body-tertiary d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-graph-up text-primary me-2"></i>{% trans %}Tendencia de Ocupación y Reservas Mensuales{% endtrans %}
</h5>
<a href="{{ url_for('admin.occupancy_metrics') }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-bar-chart me-1"></i>{% trans %}Métricas Detalladas{% endtrans %}
</a>
</div>
<div class="card-body">
<canvas id="monthlyChart" height="110"></canvas>
</div>
</div>
</div>
<!-- Recent Audit Logs -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header border-0 py-3 bg-body-tertiary d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-shield-check text-success me-2"></i>{% trans %}Últimos Eventos de Auditoría{% endtrans %}
</h5>
<a href="{{ url_for('admin.audit_logs') }}" class="btn btn-sm btn-outline-secondary py-0 px-2">
{% trans %}Ver todo{% endtrans %}
</a>
</div>
<div class="card-body p-0">
{% if role_data.recent_audits %}
<ul class="list-group list-group-flush small">
{% for audit in role_data.recent_audits %}
<li class="list-group-item d-flex justify-content-between align-items-center py-2 px-3">
<div>
<div class="fw-semibold text-truncate" style="max-width: 180px;">
{{ audit.user_email or 'Sistema' }}
</div>
<span class="text-muted font-monospace" style="font-size: 0.72rem;">
{{ audit.created_at.strftime('%d/%m %H:%M') }} · {{ audit.module }}
</span>
</div>
<div>
{% if audit.action == 'CREATE' %}
<span class="badge bg-success-subtle text-success border">{{ audit.action }}</span>
{% elif audit.action == 'UPDATE' %}
<span class="badge bg-primary-subtle text-primary border">{{ audit.action }}</span>
{% elif audit.action == 'DELETE' %}
<span class="badge bg-danger-subtle text-danger border">{{ audit.action }}</span>
{% else %}
<span class="badge bg-secondary-subtle text-secondary border">{{ audit.action }}</span>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
{% else %}
<div class="text-center py-4 text-muted">
<i class="bi bi-shield-check fs-2 d-block mb-1 text-secondary"></i>
<span class="small">{% trans %}No hay registros recientes.{% endtrans %}</span>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% elif role == 'bedelia' %}
<!-- ========================= [B] BEDELÍA DASHBOARD ========================= -->
<!-- Bedelía KPI Cards -->
<div class="row mb-4 g-3">
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('classrooms.list_classrooms', view_mode='physical') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Aulas Presenciales{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-primary">{{ stats.physical_classrooms }}</h2>
</div>
<div class="bg-primary bg-opacity-10 rounded-circle p-3 text-primary">
<i class="bi bi-door-open fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-primary small fw-semibold">
<span>{% trans %}Ver por piso y edificio{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('classrooms.list_classrooms', view_mode='virtual') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Salas Virtuales{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-info">{{ stats.virtual_classrooms }}</h2>
</div>
<div class="bg-info bg-opacity-10 rounded-circle p-3 text-info">
<i class="bi bi-camera-video fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-info small fw-semibold">
<span>{% trans %}Salas Meet / Teams / Zoom{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('schedule.today_schedule') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Clases de Hoy{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-success">{{ stats.today_reservations }}</h2>
</div>
<div class="bg-success bg-opacity-10 rounded-circle p-3 text-success">
<i class="bi bi-calendar-check fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-success small fw-semibold">
<span>{% trans %}Control operativo de hoy{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('schedule.list_reservations', status='PENDING') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all {% if stats.pending_count > 0 %}border-warning{% endif %}">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Pendientes de Confirmar{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 {% if stats.pending_count > 0 %}text-danger{% else %}text-muted{% endif %}">
{{ stats.pending_count }}
</h2>
</div>
<div class="{% if stats.pending_count > 0 %}bg-danger bg-opacity-10 text-danger{% else %}bg-secondary bg-opacity-10 text-secondary{% endif %} rounded-circle p-3">
<i class="bi bi-clock-history fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top small fw-semibold {% if stats.pending_count > 0 %}text-danger{% else %}text-muted{% endif %}">
<span>{% if stats.pending_count > 0 %}{% trans %}Requieren confirmación{% endtrans %}{% else %}{% trans %}Al día{% endtrans %}{% endif %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
</div>
<!-- Bedelía Operational Launchpad -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-body-tertiary border-0 py-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-lightning-charge-fill text-warning me-2"></i>{% trans %}Acciones Rápidas & Menús Operativos de Bedelía{% endtrans %}
</h5>
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">{% trans %}Vista Bedelía{% endtrans %}</span>
</div>
</div>
<div class="card-body p-3">
<div class="row g-3">
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.today_schedule') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-info-subtle text-info me-3">
<i class="bi bi-calendar-day fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Cartelera del Día{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Supervisión en vivo de aulas{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.add_reservation') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-success-subtle text-success me-3">
<i class="bi bi-calendar-plus fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Asignar / Nueva Reserva{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Cargar reserva de espacio{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('admin.google_sheets_import') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-primary-subtle text-primary me-3">
<i class="bi bi-file-earmark-spreadsheet fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Sincronizar Sheets{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Importación automática de cursadas{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('admin.occupancy_metrics') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-warning-subtle text-warning me-3">
<i class="bi bi-graph-up-arrow fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Métricas de Ocupación{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Monitoreo de saturación y picos{% endtrans %}</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<!-- Pending Approvals & Today's Schedule -->
{% if role_data.pending_reservations %}
<div class="card border-warning shadow-sm mb-4">
<div class="card-header bg-warning bg-opacity-10 border-warning py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold text-warning-emphasis">
<i class="bi bi-exclamation-triangle-fill me-2"></i>{% trans %}Solicitudes de Reserva Pendientes de Confirmación{% endtrans %} ({{ role_data.pending_reservations|length }})
</h5>
<a href="{{ url_for('schedule.list_reservations', status='PENDING') }}" class="btn btn-sm btn-warning">
{% trans %}Gestionar Todas{% endtrans %}
</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light small">
<tr>
<th class="ps-3">{% trans %}Horario / Fecha{% endtrans %}</th>
<th>{% trans %}Aula{% endtrans %}</th>
<th>{% trans %}Asignatura{% endtrans %}</th>
<th>{% trans %}Docente / Solicitante{% endtrans %}</th>
<th class="text-end pe-3">{% trans %}Acción{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for pres in role_data.pending_reservations %}
<tr>
<td class="ps-3 font-monospace small">
<strong>{{ pres.start_time.strftime('%d/%m') }}</strong> {{ pres.start_time.strftime('%H:%M') }} - {{ pres.end_time.strftime('%H:%M') }} hs
</td>
<td>
<span class="badge bg-secondary-subtle text-secondary border font-monospace">
{{ pres.classroom.code if pres.classroom else 'Sin Aula' }}
</span>
</td>
<td>
<div class="fw-semibold text-primary">{{ pres.subject_name }}</div>
</td>
<td class="small">{{ pres.user.name if pres.user else 'Docente' }}</td>
<td class="text-end pe-3">
<a href="{{ url_for('schedule.view_reservation', id=pres.id) }}" class="btn btn-sm btn-outline-primary py-0 px-2">
<i class="bi bi-check-circle me-1"></i>{% trans %}Revisar{% endtrans %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
{% elif role == 'docente' %}
<!-- ========================= [C] DOCENTE DASHBOARD ========================= -->
<!-- Docente KPI Cards -->
<div class="row mb-4 g-3">
<div class="col-md-6 col-xl-3">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Mis Clases Hoy{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-primary">{{ stats.my_today_count }}</h2>
</div>
<div class="bg-primary bg-opacity-10 rounded-circle p-3 text-primary">
<i class="bi bi-clock-history fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-primary small fw-semibold">
<span>{% trans %}Sesiones del día{% endtrans %}</span>
<i class="bi bi-check2-circle"></i>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('schedule.list_reservations') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Mis Reservas Activas{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-success">{{ stats.my_total_reservations }}</h2>
</div>
<div class="bg-success bg-opacity-10 rounded-circle p-3 text-success">
<i class="bi bi-calendar-check fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-success small fw-semibold">
<span>{% trans %}Ver todas mis reservas{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('classrooms.list_classrooms', view_mode='virtual') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small fw-semibold text-uppercase">{% trans %}Aulas Virtuales{% endtrans %}</span>
<h2 class="mb-0 fw-bold mt-1 text-info">{{ stats.virtual_rooms_count }}</h2>
</div>
<div class="bg-info bg-opacity-10 rounded-circle p-3 text-info">
<i class="bi bi-camera-video fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-info small fw-semibold">
<span>{% trans %}Acceso a salas virtuales{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="{{ url_for('schedule.add_reservation') }}" class="text-decoration-none text-reset d-block h-100">
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all bg-primary bg-opacity-10 border border-primary-subtle">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-primary small fw-bold text-uppercase">{% trans %}Nueva Solicitud{% endtrans %}</span>
<h4 class="mb-0 fw-bold mt-1 text-primary">{% trans %}Reservar Aula{% endtrans %}</h4>
</div>
<div class="bg-primary text-white rounded-circle p-3">
<i class="bi bi-plus-lg fs-3"></i>
</div>
</div>
<div class="d-flex align-items-center justify-content-between pt-3 mt-2 border-top text-primary small fw-bold">
<span>{% trans %}Solicitar espacio para clase{% endtrans %}</span>
<i class="bi bi-arrow-right"></i>
</div>
</div>
</div>
</a>
</div>
</div>
<!-- Docente Launchpad -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-body-tertiary border-0 py-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-compass text-info me-2"></i>{% trans %}Accesos Directos de Docencia & Visualización de Menús{% endtrans %}
</h5>
<span class="badge bg-info-subtle text-info border border-info-subtle">{% trans %}Vista Profesor{% endtrans %}</span>
</div>
</div>
<div class="card-body p-3">
<div class="row g-3">
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.add_reservation') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-success-subtle text-success me-3">
<i class="bi bi-calendar-plus fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Solicitar Aula{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Reserva de espacio o laboratorio{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.commissions_view') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-primary-subtle text-primary me-3">
<i class="bi bi-diagram-3 fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Mis Comisiones{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Ver comisiones y turnos asignados{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.today_schedule') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-info-subtle text-info me-3">
<i class="bi bi-calendar-day fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Cartelera del Día{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Consulta de aulas asignadas{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('classrooms.list_classrooms', view_mode='virtual') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-warning-subtle text-warning me-3">
<i class="bi bi-camera-video fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Aulas Virtuales{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Salas y enlaces de reunión{% endtrans %}</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<!-- Docente: Mis Clases de Hoy -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-body-tertiary border-0 py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-calendar-check text-primary me-2"></i>{% trans %}Mis Clases Programadas para Hoy{% endtrans %}
</h5>
<span class="badge bg-primary-subtle text-primary border">{{ role_data.my_today_classes|length }} {% trans %}hoy{% endtrans %}</span>
</div>
<div class="card-body p-0">
{% if role_data.my_today_classes %}
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light small">
<tr>
<th class="ps-3">{% trans %}Horario{% endtrans %}</th>
<th>{% trans %}Materia & Código{% endtrans %}</th>
<th>{% trans %}Comisión / Turno{% endtrans %}</th>
<th>{% trans %}Ubicación / Aula{% endtrans %}</th>
<th class="text-end pe-3">{% trans %}Acción{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for c in role_data.my_today_classes %}
<tr>
<td class="ps-3 font-monospace fw-bold text-primary">
<i class="bi bi-clock me-1 text-warning"></i>{{ c.start_time.strftime('%H:%M') }} - {{ c.end_time.strftime('%H:%M') }} hs
</td>
<td>
<div class="fw-semibold">{{ c.subject_name }}</div>
<small class="text-muted font-monospace">{{ c.subject_code }}</small>
</td>
<td>
<span class="badge bg-secondary-subtle text-secondary border">{{ c.commission.code if c.commission else 'C1' }}</span>
</td>
<td>
{% if c.classroom and c.classroom.is_virtual %}
<span class="badge bg-info-subtle text-info border">
<i class="bi bi-camera-video me-1"></i>{% trans %}Aula Virtual{% endtrans %}
</span>
{% elif c.classroom %}
<span class="badge bg-primary-subtle text-primary border">
<i class="bi bi-geo-alt me-1"></i>{{ c.classroom.location_display }}
</span>
{% else %}
<span class="text-muted small">{% trans %}Sin aula asignada{% endtrans %}</span>
{% endif %}
</td>
<td class="text-end pe-3">
{% if c.virtual_link %}
<a href="{{ c.virtual_link }}" target="_blank" class="btn btn-sm btn-info text-white py-1 px-2">
<i class="bi bi-box-arrow-up-right me-1"></i>{% trans %}Ingresar a Reunión{% endtrans %}
</a>
{% else %}
<a href="{{ url_for('schedule.view_reservation', id=c.id) }}" class="btn btn-sm btn-outline-secondary py-1 px-2">
<i class="bi bi-eye"></i>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4 text-muted">
<i class="bi bi-cup-hot fs-2 d-block mb-1 text-primary"></i>
<p class="mb-0 fw-semibold">{% trans %}No tienes clases programadas para hoy.{% endtrans %}</p>
<small class="text-muted">{% trans %}Puedes revisar tus reservas futuras en el calendario institucional.{% endtrans %}</small>
</div>
{% endif %}
</div>
</div>
{% else %}
<!-- ========================= [D] ALUMNO DASHBOARD ========================= -->
<!-- Alumno Welcome Compass: ¿Dónde curso hoy? -->
<div class="card border-primary border-opacity-25 shadow-sm mb-4">
<div class="card-header bg-primary bg-opacity-10 border-0 py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold text-primary">
<i class="bi bi-compass-fill me-2"></i>{% trans %}¿Dónde curso hoy? - Cartelera del Alumno{% endtrans %}
</h5>
<a href="{{ url_for('schedule.today_schedule') }}" class="btn btn-sm btn-primary">
<i class="bi bi-calendar-day me-1"></i>{% trans %}Cartelera Completa{% endtrans %}
</a>
</div>
<div class="card-body p-0">
{% if role_data.today_classes %}
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light small">
<tr>
<th class="ps-3">{% trans %}Horario{% endtrans %}</th>
<th>{% trans %}Asignatura / Carrera{% endtrans %}</th>
<th>{% trans %}Comisión{% endtrans %}</th>
<th>{% trans %}Edificio · Piso · Aula{% endtrans %}</th>
<th class="text-end pe-3">{% trans %}Acceso{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for cls in role_data.today_classes %}
<tr>
<td class="ps-3 font-monospace fw-bold text-primary">
<i class="bi bi-clock-fill text-warning me-1"></i>{{ cls.start_time.strftime('%H:%M') }} - {{ cls.end_time.strftime('%H:%M') }} hs
</td>
<td>
<div class="fw-semibold text-body">{{ cls.subject_name }}</div>
<small class="text-muted font-monospace">{{ cls.subject_code }}</small>
</td>
<td>
<span class="badge bg-secondary-subtle text-secondary border">
{{ cls.commission.code if cls.commission else 'C1' }}
</span>
</td>
<td>
{% if cls.classroom and cls.classroom.is_virtual %}
<span class="badge bg-info-subtle text-info border font-monospace">
<i class="bi bi-camera-video me-1"></i>{% trans %}Campus Virtual (Videollamada){% endtrans %}
</span>
{% elif cls.classroom %}
<span class="badge bg-success-subtle text-success border font-monospace">
<i class="bi bi-building me-1"></i>{{ cls.classroom.location_display }}
</span>
{% else %}
<span class="badge bg-warning-subtle text-warning border">{% trans %}A confirmar{% endtrans %}</span>
{% endif %}
</td>
<td class="text-end pe-3">
{% if cls.virtual_link %}
<a href="{{ cls.virtual_link }}" target="_blank" class="btn btn-sm btn-info text-white py-1 px-2">
<i class="bi bi-box-arrow-up-right me-1"></i>{% trans %}Unirse a Clase{% endtrans %}
</a>
{% else %}
<span class="badge bg-body-tertiary text-muted border">
<i class="bi bi-geo-alt"></i> {% trans %}Presencial{% endtrans %}
</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4 text-muted">
<i class="bi bi-calendar-check fs-2 d-block mb-1 text-success"></i>
<p class="mb-0 fw-semibold">{% trans %}¡No tienes clases asignadas para el día de hoy!{% endtrans %}</p>
<small class="text-muted">{% trans %}Consulta el cronograma semanal para ver tus próximas fechas y horarios.{% endtrans %}</small>
</div>
{% endif %}
</div>
</div>
<!-- Alumno Visual Menu Launchpad -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-body-tertiary border-0 py-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-mortarboard text-success me-2"></i>{% trans %}Menú del Alumno & Herramientas de Cursada{% endtrans %}
</h5>
<span class="badge bg-success-subtle text-success border border-success-subtle">{% trans %}Vista Alumno{% endtrans %}</span>
</div>
</div>
<div class="card-body p-3">
<div class="row g-3">
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.today_schedule') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-primary-subtle text-primary me-3">
<i class="bi bi-geo-alt fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Cartelera de Hoy{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Dónde cursas hoy, piso y aula{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('schedule.calendar_view') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-success-subtle text-success me-3">
<i class="bi bi-calendar3 fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Cronograma Semanal{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Calendario general de cursadas{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('classrooms.list_classrooms', view_mode='virtual') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-info-subtle text-info me-3">
<i class="bi bi-camera-video fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Aulas Virtuales{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Salas y videollamadas Meet/Teams{% endtrans %}</p>
</div>
</div>
</a>
</div>
<div class="col-md-6 col-lg-3">
<a href="{{ url_for('classrooms.list_classrooms', view_mode='physical') }}" class="card h-100 p-3 action-menu-card bg-body">
<div class="d-flex align-items-center">
<div class="quick-icon-circle bg-warning-subtle text-warning me-3">
<i class="bi bi-buildings fs-4"></i>
</div>
<div>
<h6 class="mb-1 fw-bold text-body">{% trans %}Espacios del Campus{% endtrans %}</h6>
<p class="text-muted small mb-0">{% trans %}Sedes, edificios y planos{% endtrans %}</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<!-- Alumno Upcoming Milestones (Exámenes y Entregas) -->
{% if role_data.upcoming_milestones %}
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-body-tertiary border-0 py-3">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-bell-fill text-warning me-2"></i>{% trans %}Próximos Hitos Académicos & Exámenes{% endtrans %}
</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light small">
<tr>
<th class="ps-3">{% trans %}Fecha{% endtrans %}</th>
<th>{% trans %}Tipo de Hito{% endtrans %}</th>
<th>{% trans %}Materia / Código{% endtrans %}</th>
<th>{% trans %}Aula / Enlace{% endtrans %}</th>
<th class="pe-3">{% trans %}Notas{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for m in role_data.upcoming_milestones %}
<tr>
<td class="ps-3 font-monospace fw-bold text-primary">
{{ m.date.strftime('%d/%m/%Y') }}
</td>
<td>
<span class="badge" style="background-color: {{ m.type_obj.color if m.type_obj else '#0d6efd' }}; color: #fff;">
{{ m.type_obj.name if m.type_obj else 'Evaluación' }}
</span>
</td>
<td>
<div class="fw-semibold">{{ m.title or m.materia_codigo }}</div>
<small class="text-muted font-monospace">{{ m.materia_codigo }}</small>
</td>
<td>
{% if m.link %}
<a href="{{ m.link }}" target="_blank" class="btn btn-sm btn-outline-info py-0 px-2">
<i class="bi bi-box-arrow-up-right me-1"></i>{% trans %}Enlace{% endtrans %}
</a>
{% else %}
<span class="text-muted small">{{ m.classroom or 'Sede Central' }}</span>
{% endif %}
</td>
<td class="pe-3 small text-muted">{{ m.notes or '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
{% endif %}
<!-- ========================================================================= -->
<!-- 3. UPCOMING RESERVATIONS (ALL ROLES GENERAL FEED) -->
<!-- ========================================================================= -->
<div class="row">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header border-0 py-3 bg-body-tertiary d-flex justify-content-between align-items-center">
<h5 class="mb-0 h6 fw-bold">
<i class="bi bi-clock-history text-info me-2"></i>{% trans %}Próximas Reservas del Campus (Próximos 7 Días){% endtrans %}
</h5>
<a href="{{ url_for('schedule.calendar_view') }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-calendar3 me-1"></i>{% trans %}Ver en Calendario{% endtrans %}
</a>
</div>
<div class="card-body p-0">
{% if upcoming_reservations %}
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light small text-uppercase">
<tr>
<th class="ps-3">{% trans %}Fecha & Horario{% endtrans %}</th>
<th>{% trans %}Aula{% endtrans %}</th>
<th>{% trans %}Asignatura / Código{% endtrans %}</th>
<th>{% trans %}Turno / Comisión{% endtrans %}</th>
<th>{% trans %}Estado{% endtrans %}</th>
<th class="text-end pe-3">{% trans %}Acciones{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for reservation in upcoming_reservations %}
<tr>
<td class="ps-3">
<div class="fw-bold font-monospace text-primary small">
<i class="bi bi-calendar-event text-secondary me-1"></i>{{ reservation.start_time.strftime('%d/%m') }}
<span class="ms-1 text-body">{{ reservation.start_time.strftime('%H:%M') }} - {{ reservation.end_time.strftime('%H:%M') }} hs</span>
</div>
</td>
<td>
<span class="badge bg-secondary-subtle text-secondary border font-monospace">
{{ reservation.classroom.code if reservation.classroom else 'Sin Aula' }}
</span>
</td>
<td>
<div class="fw-semibold text-primary">{{ reservation.subject_name }}</div>
<small class="text-muted font-monospace">{{ reservation.subject_code }}</small>
</td>
<td>
<span class="badge bg-info-subtle text-info border me-1">
{{ reservation.shift or 'Tarde' }}
</span>
<small class="text-muted">{{ reservation.commission.code if reservation.commission else 'C1' }}</small>
</td>
<td>
{% if reservation.status == 'CONFIRMED' or reservation.status.value == 'CONFIRMED' %}
<span class="badge bg-success-subtle text-success border border-success-subtle">
<i class="bi bi-check-circle me-1"></i>{% trans %}Confirmada{% endtrans %}
</span>
{% else %}
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">
<i class="bi bi-clock me-1"></i>{% trans %}Pendiente{% endtrans %}
</span>
{% endif %}
</td>
<td class="text-end pe-3">
<a href="{{ url_for('schedule.view_reservation', id=reservation.id) }}"
class="btn btn-sm btn-outline-secondary py-0 px-2" title="{% trans %}Ver Ficha{% endtrans %}">
<i class="bi bi-eye"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="bi bi-calendar-x text-muted fs-1 d-block mb-2"></i>
<h6 class="fw-bold">{% trans %}No hay reservas para los próximos 7 días{% endtrans %}</h6>
<p class="text-muted small">{% trans %}Puedes programar nuevas reservas desde el calendario.{% endtrans %}</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% if current_user.is_authenticated %}
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
$(document).ready(function() {
const chartElem = document.getElementById('monthlyChart');
if (chartElem) {
$.get('/api/dashboard-stats')
.done(function(data) {
const ctx = chartElem.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: data.monthly_data.map(item => item.month),
datasets: [{
label: 'Reservas',
data: data.monthly_data.map(item => item.reservations),
borderColor: '#B43E8E',
backgroundColor: 'rgba(180, 62, 142, 0.12)',
borderWidth: 2,
fill: true,
tension: 0.4,
pointBackgroundColor: '#B43E8E',
pointRadius: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
});
}
});
</script>
{% endblock %}