1318 lines
76 KiB
HTML
1318 lines
76 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}
|
|
{% if not role or role == 'admin' %}
|
|
Panel de Administración - Edu-Space Admin
|
|
{% elif role == 'bedelia' %}
|
|
Panel Operativo de Bedelía - Edu-Space Admin
|
|
{% elif role == 'docente' %}
|
|
Panel Docente - Edu-Space Admin
|
|
{% else %}
|
|
Mi Portal de Cursada - Edu-Space Admin
|
|
{% endif %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
{% if 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">
|
|
¡Hola, {{ user.first_name or user.name }}!
|
|
</h1>
|
|
<span class="badge {{ user.role_badge_display.class }} role-pill">
|
|
<i class="bi {{ user.role_badge_display.icon }}"></i>
|
|
{{ 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>Modo Vista Previa: {{ role|capitalize }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<p class="text-muted small mb-0">
|
|
{% if not role or role == 'admin' %}
|
|
<i class="bi bi-shield-check text-primary me-1"></i>Gestión centralizada de infraestructura, oferta académica, seguridad y trazabilidad institucional.
|
|
{% elif role == 'bedelia' %}
|
|
<i class="bi bi-building-gear text-warning me-1"></i>Control operativo de campus: asignación de aulas físicas y virtuales, cartelera del día y aprobaciones.
|
|
{% elif role == 'docente' %}
|
|
<i class="bi bi-person-video3 text-info me-1"></i>Espacio docente: gestión de cursadas, horarios de clase, solicitudes de aula y enlaces de videoconferencia.
|
|
{% else %}
|
|
<i class="bi bi-mortarboard text-success me-1"></i>Portal de cursada: consulta tu aula de hoy, cronograma semanal de clases y fechas de exámenes.
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Role Perspective Switcher for Admins & Managers -->
|
|
{% if user.is_admin() or 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>VER PANEL COMO ROL:
|
|
</small>
|
|
<div class="btn-group role-view-selector bg-body-tertiary p-1 rounded-pill border" role="group">
|
|
<a href="/dashboard?role=admin"
|
|
class="btn btn-sm {% if not role or 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="/dashboard?role=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="/dashboard?role=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="/dashboard?role=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 not role or 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="/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 glass-card">
|
|
<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">Aulas Totales</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>Ver catálogo de espacios</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-xl-3">
|
|
<a href="/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 glass-card">
|
|
<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">Oferta de Materias</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>Gestión académica</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-xl-3">
|
|
<a href="/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 glass-card">
|
|
<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">Usuarios Activos</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>Control de usuarios y roles</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-xl-3">
|
|
<a href="/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 glass-card">
|
|
<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">Reservas de Hoy</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>Cartelera en tiempo real</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Panel de Balance y Percentiles de Asignación de Aulas (Admin) -->
|
|
<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 flex-wrap gap-2">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<i class="bi bi-pie-chart-fill text-primary fs-5"></i>
|
|
<div>
|
|
<h5 class="mb-0 h6 fw-bold">Estado y Percentiles de Asignación de Aulas Físicas</h5>
|
|
<small class="text-muted">Balance de capacidad instalada en Edificio Central (Total: {{ stats.physical_classrooms }} aulas físicas)</small>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2 align-items-center flex-wrap">
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle px-2 py-1">
|
|
<i class="bi bi-check2-circle me-1"></i>{{ stats.assigned_classrooms }} Asignadas ({{ stats.assigned_classrooms_pct }}%)
|
|
</span>
|
|
<span class="badge bg-warning-subtle text-warning-emphasis border border-warning-subtle px-2 py-1">
|
|
<i class="bi bi-door-open me-1"></i>{{ stats.unassigned_classrooms }} Sin Asignar ({{ stats.unassigned_classrooms_pct }}%)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<div class="row g-3 align-items-center">
|
|
<!-- Tarjeta 1: Asignadas -->
|
|
<div class="col-md-3">
|
|
<a href="/classrooms/list_classrooms?view_mode=physical" class="text-decoration-none text-reset d-block h-100">
|
|
<div class="p-3 rounded bg-success bg-opacity-10 border border-success-subtle h-100 text-center transition-all">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="text-success small fw-bold text-uppercase">Aulas Asignadas</span>
|
|
<span class="badge bg-success text-white font-monospace">{{ stats.assigned_classrooms_pct }}%</span>
|
|
</div>
|
|
<div class="d-flex align-items-baseline justify-content-center gap-2 my-2">
|
|
<h2 class="mb-0 fw-bold text-success">{{ stats.assigned_classrooms }}</h2>
|
|
<span class="text-muted small">/ {{ stats.physical_classrooms }} aulas</span>
|
|
</div>
|
|
<div class="text-success small pt-2 border-top border-success-subtle d-flex align-items-center justify-content-between">
|
|
<span>Con cursadas activas</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Tarjeta 2: Sin Asignar -->
|
|
<div class="col-md-3">
|
|
<a href="/classrooms/list_classrooms?view_mode=physical" class="text-decoration-none text-reset d-block h-100">
|
|
<div class="p-3 rounded bg-warning bg-opacity-10 border border-warning-subtle h-100 text-center transition-all">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="text-warning-emphasis small fw-bold text-uppercase">Aulas Sin Asignar</span>
|
|
<span class="badge bg-warning text-dark font-monospace">{{ stats.unassigned_classrooms_pct }}%</span>
|
|
</div>
|
|
<div class="d-flex align-items-baseline justify-content-center gap-2 my-2">
|
|
<h2 class="mb-0 fw-bold text-warning-emphasis">{{ stats.unassigned_classrooms }}</h2>
|
|
<span class="text-muted small">/ {{ stats.physical_classrooms }} aulas</span>
|
|
</div>
|
|
<div class="text-warning-emphasis small pt-2 border-top border-warning-subtle d-flex align-items-center justify-content-between">
|
|
<span>Espacios disponibles</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Tarjeta 3: Barras Comparativas de Percentil (Cuatrimestre y Hoy) -->
|
|
<div class="col-md-6">
|
|
<div class="p-3 rounded bg-body-tertiary border h-100 d-flex flex-column justify-content-center">
|
|
<!-- Nivel 1: Cuatrimestre -->
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small fw-semibold text-body">
|
|
<i class="bi bi-calendar-check me-1 text-primary"></i>Planificación Cuatrimestral:
|
|
</span>
|
|
<span class="small font-monospace text-muted">
|
|
<strong class="text-success">{{ stats.assigned_classrooms_pct }}%</strong> Asignado ({{ stats.assigned_classrooms }} aulas) · <strong class="text-warning-emphasis">{{ stats.unassigned_classrooms_pct }}%</strong> Libre ({{ stats.unassigned_classrooms }} aula)
|
|
</span>
|
|
</div>
|
|
<div class="progress mb-3" style="height: 14px;">
|
|
<div class="progress-bar bg-success progress-bar-striped" role="progressbar" style="width: {{ stats.assigned_classrooms_pct }}%;"
|
|
aria-valuenow="{{ stats.assigned_classrooms_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="Asignadas: {{ stats.assigned_classrooms }} ({{ stats.assigned_classrooms_pct }}%)">
|
|
{{ stats.assigned_classrooms_pct }}%
|
|
</div>
|
|
<div class="progress-bar bg-warning" role="progressbar" style="width: {{ stats.unassigned_classrooms_pct }}%;"
|
|
aria-valuenow="{{ stats.unassigned_classrooms_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="Sin Asignar: {{ stats.unassigned_classrooms }} ({{ stats.unassigned_classrooms_pct }}%)">
|
|
{{ stats.unassigned_classrooms_pct }}%
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nivel 2: Día Operativo -->
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small fw-semibold text-body">
|
|
<i class="bi bi-clock-history me-1 text-info"></i>Ocupación Operativa de Hoy:
|
|
</span>
|
|
<span class="small font-monospace text-muted">
|
|
<strong class="text-info">{{ stats.assigned_today_classrooms }}</strong> en uso ({{ stats.assigned_today_pct }}%) · <strong class="text-secondary">{{ stats.unassigned_today_classrooms }}</strong> libres ({{ stats.unassigned_today_pct }}%)
|
|
</span>
|
|
</div>
|
|
<div class="progress" style="height: 10px;">
|
|
<div class="progress-bar bg-info" role="progressbar" style="width: {{ stats.assigned_today_pct }}%;"
|
|
aria-valuenow="{{ stats.assigned_today_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="En uso hoy: {{ stats.assigned_today_classrooms }}">
|
|
</div>
|
|
<div class="progress-bar bg-secondary-subtle" role="progressbar" style="width: {{ stats.unassigned_today_pct }}%;"
|
|
aria-valuenow="{{ stats.unassigned_today_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="Libres hoy: {{ stats.unassigned_today_classrooms }}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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>Módulos de Gestión & Visualización de Menús
|
|
</h5>
|
|
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">Vista Administrador</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<div class="row g-3">
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Espacios & Sedes</h6>
|
|
<p class="text-muted small mb-0">Aulas físicas, virtuales y edificios</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Cronograma & Reservas</h6>
|
|
<p class="text-muted small mb-0">Calendario, cartelera y reservas</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Optimizador IA</h6>
|
|
<p class="text-muted small mb-0">Asignación genética inteligente</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Auditoría & RBAC</h6>
|
|
<p class="text-muted small mb-0">Trazabilidad y control de permisos</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 glass-card">
|
|
<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>Tendencia de Ocupación y Reservas Mensuales
|
|
</h5>
|
|
<a href="/admin/occupancy_metrics" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-bar-chart me-1"></i>Métricas Detalladas
|
|
</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 glass-card">
|
|
<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>Últimos Eventos de Auditoría
|
|
</h5>
|
|
<a href="/admin/audit_logs" class="btn btn-sm btn-outline-secondary py-0 px-2">
|
|
Ver todo
|
|
</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 }} · {{ 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">No hay registros recientes.</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="/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 glass-card">
|
|
<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">Aulas Presenciales</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>Ver por piso y edificio</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-xl-3">
|
|
<a href="/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 glass-card">
|
|
<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">Salas Virtuales</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>Salas Meet / Teams / Zoom</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-xl-3">
|
|
<a href="/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 glass-card">
|
|
<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">Clases de Hoy</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>Control operativo de hoy</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-xl-3">
|
|
<a href="/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 {% 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">Pendientes de Confirmar</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 %}Requieren confirmación{% else %}Al día{% endif %}</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Panel de Balance y Percentiles de Asignación de Aulas (Bedelía) -->
|
|
<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 flex-wrap gap-2">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<i class="bi bi-pie-chart-fill text-warning fs-5"></i>
|
|
<div>
|
|
<h5 class="mb-0 h6 fw-bold">Gestión y Percentiles de Asignación de Aulas (Bedelía)</h5>
|
|
<small class="text-muted">Control de asignación de espacios físicos (Total: {{ stats.physical_classrooms }} aulas físicas en Sede Central)</small>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2 align-items-center flex-wrap">
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle px-2 py-1">
|
|
<i class="bi bi-check2-circle me-1"></i>{{ stats.assigned_classrooms }} Asignadas ({{ stats.assigned_classrooms_pct }}%)
|
|
</span>
|
|
<span class="badge bg-warning-subtle text-warning-emphasis border border-warning-subtle px-2 py-1">
|
|
<i class="bi bi-door-open me-1"></i>{{ stats.unassigned_classrooms }} Sin Asignar ({{ stats.unassigned_classrooms_pct }}%)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<div class="row g-3 align-items-center">
|
|
<!-- Tarjeta 1: Asignadas -->
|
|
<div class="col-md-3">
|
|
<a href="/classrooms/list_classrooms?view_mode=physical" class="text-decoration-none text-reset d-block h-100">
|
|
<div class="p-3 rounded bg-success bg-opacity-10 border border-success-subtle h-100 text-center transition-all">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="text-success small fw-bold text-uppercase">Aulas Asignadas</span>
|
|
<span class="badge bg-success text-white font-monospace">{{ stats.assigned_classrooms_pct }}%</span>
|
|
</div>
|
|
<div class="d-flex align-items-baseline justify-content-center gap-2 my-2">
|
|
<h2 class="mb-0 fw-bold text-success">{{ stats.assigned_classrooms }}</h2>
|
|
<span class="text-muted small">/ {{ stats.physical_classrooms }} aulas</span>
|
|
</div>
|
|
<div class="text-success small pt-2 border-top border-success-subtle d-flex align-items-center justify-content-between">
|
|
<span>Ver aulas con cursada</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Tarjeta 2: Sin Asignar -->
|
|
<div class="col-md-3">
|
|
<a href="/schedule/add_reservation" class="text-decoration-none text-reset d-block h-100">
|
|
<div class="p-3 rounded bg-warning bg-opacity-10 border border-warning-subtle h-100 text-center transition-all">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="text-warning-emphasis small fw-bold text-uppercase">Aulas Sin Asignar</span>
|
|
<span class="badge bg-warning text-dark font-monospace">{{ stats.unassigned_classrooms_pct }}%</span>
|
|
</div>
|
|
<div class="d-flex align-items-baseline justify-content-center gap-2 my-2">
|
|
<h2 class="mb-0 fw-bold text-warning-emphasis">{{ stats.unassigned_classrooms }}</h2>
|
|
<span class="text-muted small">/ {{ stats.physical_classrooms }} aulas</span>
|
|
</div>
|
|
<div class="text-warning-emphasis small pt-2 border-top border-warning-subtle d-flex align-items-center justify-content-between">
|
|
<span>Asignar espacio libre</span>
|
|
<i class="bi bi-plus-circle"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Tarjeta 3: Barras Comparativas de Percentil (Cuatrimestre y Hoy) -->
|
|
<div class="col-md-6">
|
|
<div class="p-3 rounded bg-body-tertiary border h-100 d-flex flex-column justify-content-center">
|
|
<!-- Nivel 1: Cuatrimestre -->
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small fw-semibold text-body">
|
|
<i class="bi bi-calendar-check me-1 text-primary"></i>Asignación Cuatrimestral:
|
|
</span>
|
|
<span class="small font-monospace text-muted">
|
|
<strong class="text-success">{{ stats.assigned_classrooms_pct }}%</strong> Asignado ({{ stats.assigned_classrooms }} aulas) · <strong class="text-warning-emphasis">{{ stats.unassigned_classrooms_pct }}%</strong> Disponible ({{ stats.unassigned_classrooms }} aula)
|
|
</span>
|
|
</div>
|
|
<div class="progress mb-3" style="height: 14px;">
|
|
<div class="progress-bar bg-success progress-bar-striped" role="progressbar" style="width: {{ stats.assigned_classrooms_pct }}%;"
|
|
aria-valuenow="{{ stats.assigned_classrooms_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="Asignadas: {{ stats.assigned_classrooms }} ({{ stats.assigned_classrooms_pct }}%)">
|
|
{{ stats.assigned_classrooms_pct }}%
|
|
</div>
|
|
<div class="progress-bar bg-warning" role="progressbar" style="width: {{ stats.unassigned_classrooms_pct }}%;"
|
|
aria-valuenow="{{ stats.unassigned_classrooms_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="Sin Asignar: {{ stats.unassigned_classrooms }} ({{ stats.unassigned_classrooms_pct }}%)">
|
|
{{ stats.unassigned_classrooms_pct }}%
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nivel 2: Día Operativo -->
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small fw-semibold text-body">
|
|
<i class="bi bi-clock-history me-1 text-info"></i>Ocupación Operativa de Hoy:
|
|
</span>
|
|
<span class="small font-monospace text-muted">
|
|
<strong class="text-info">{{ stats.assigned_today_classrooms }}</strong> en uso ({{ stats.assigned_today_pct }}%) · <strong class="text-secondary">{{ stats.unassigned_today_classrooms }}</strong> libres ({{ stats.unassigned_today_pct }}%)
|
|
</span>
|
|
</div>
|
|
<div class="progress" style="height: 10px;">
|
|
<div class="progress-bar bg-info" role="progressbar" style="width: {{ stats.assigned_today_pct }}%;"
|
|
aria-valuenow="{{ stats.assigned_today_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="En uso hoy: {{ stats.assigned_today_classrooms }}">
|
|
</div>
|
|
<div class="progress-bar bg-secondary-subtle" role="progressbar" style="width: {{ stats.unassigned_today_pct }}%;"
|
|
aria-valuenow="{{ stats.unassigned_today_pct }}" aria-valuemin="0" aria-valuemax="100"
|
|
title="Libres hoy: {{ stats.unassigned_today_classrooms }}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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>Acciones Rápidas & Menús Operativos de Bedelía
|
|
</h5>
|
|
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">Vista Bedelía</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<div class="row g-3">
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Cartelera del Día</h6>
|
|
<p class="text-muted small mb-0">Supervisión en vivo de aulas</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Asignar / Nueva Reserva</h6>
|
|
<p class="text-muted small mb-0">Cargar reserva de espacio</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Sincronizar Sheets</h6>
|
|
<p class="text-muted small mb-0">Importación automática de cursadas</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Métricas de Ocupación</h6>
|
|
<p class="text-muted small mb-0">Monitoreo de saturación y picos</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>Solicitudes de Reserva Pendientes de Confirmación ({{ role_data.pending_reservations|length }})
|
|
</h5>
|
|
<a href="/schedule/list_reservations" class="btn btn-sm btn-warning">
|
|
Gestionar Todas
|
|
</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">Horario / Fecha</th>
|
|
<th>Aula</th>
|
|
<th>Asignatura</th>
|
|
<th>Docente / Solicitante</th>
|
|
<th class="text-end pe-3">Acción</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for pres in role_data.pending_reservations %}
|
|
<tr>
|
|
<td class="ps-3 font-monospace small">
|
|
<strong>{{ pres.start_time }}</strong> {{ pres.start_time }} - {{ pres.end_time }} 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="/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>Revisar
|
|
</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-4 col-xl-4">
|
|
<div class="card border-0 shadow-sm h-100 dashboard-stat-card transition-all glass-card">
|
|
<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">Mis Clases Hoy</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>Sesiones del día</span>
|
|
<i class="bi bi-check2-circle"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4 col-xl-4">
|
|
<a href="/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 glass-card">
|
|
<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">Mis Reservas Activas</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>Ver todas mis reservas</span>
|
|
<i class="bi bi-arrow-right"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-md-4 col-xl-4">
|
|
<a href="/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 glass-card">
|
|
<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">Aulas Virtuales</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>Acceso a salas virtuales</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>Accesos Directos de Docencia & Visualización de Menús
|
|
</h5>
|
|
<span class="badge bg-info-subtle text-info border border-info-subtle">Vista Profesor</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<div class="row g-3">
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Solicitar Aula</h6>
|
|
<p class="text-muted small mb-0">Reserva de espacio o laboratorio</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Mis Comisiones</h6>
|
|
<p class="text-muted small mb-0">Ver comisiones y turnos asignados</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Cartelera del Día</h6>
|
|
<p class="text-muted small mb-0">Consulta de aulas asignadas</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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-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">Aulas Virtuales</h6>
|
|
<p class="text-muted small mb-0">Salas y enlaces de reunión</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>Mis Clases Programadas para Hoy
|
|
</h5>
|
|
<span class="badge bg-primary-subtle text-primary border">{{ role_data.my_today_classes|length }} hoy</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">Horario</th>
|
|
<th>Materia & Código</th>
|
|
<th>Comisión / Turno</th>
|
|
<th>Ubicación / Aula</th>
|
|
<th class="text-end pe-3">Acción</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 }} - {{ c.end_time }} 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>Aula Virtual
|
|
</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">Sin aula asignada</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>Ingresar a Reunión
|
|
</a>
|
|
{% else %}
|
|
<a href="/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">No tienes clases programadas para hoy.</p>
|
|
<small class="text-muted">Puedes revisar tus reservas futuras en el calendario institucional.</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>¿Dónde curso hoy? - Cartelera del Alumno
|
|
</h5>
|
|
<a href="/schedule/today_schedule" class="btn btn-sm btn-primary">
|
|
<i class="bi bi-calendar-day me-1"></i>Cartelera Completa
|
|
</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">Horario</th>
|
|
<th>Asignatura / Carrera</th>
|
|
<th>Comisión</th>
|
|
<th>Edificio · Piso · Aula</th>
|
|
<th class="text-end pe-3">Acceso</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 }} - {{ cls.end_time }} 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>Campus Virtual (Videollamada)
|
|
</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">A confirmar</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>Unirse a Clase
|
|
</a>
|
|
{% else %}
|
|
<span class="badge bg-body-tertiary text-muted border">
|
|
<i class="bi bi-geo-alt"></i> Presencial
|
|
</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">¡No tienes clases asignadas para el día de hoy!</p>
|
|
<small class="text-muted">Consulta el cronograma semanal para ver tus próximas fechas y horarios.</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>Menú del Alumno & Herramientas de Cursada
|
|
</h5>
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle">Vista Alumno</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<div class="row g-3">
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Cartelera de Hoy</h6>
|
|
<p class="text-muted small mb-0">Dónde cursas hoy, piso y aula</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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">Cronograma Semanal</h6>
|
|
<p class="text-muted small mb-0">Calendario general de cursadas</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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-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">Aulas Virtuales</h6>
|
|
<p class="text-muted small mb-0">Salas y videollamadas Meet/Teams</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6 col-lg-3">
|
|
<a href="/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-warning-subtle text-warning me-3">
|
|
<i class="bi bi-buildings fs-4"></i>
|
|
</div>
|
|
<div>
|
|
<h6 class="mb-1 fw-bold text-body">Espacios del Campus</h6>
|
|
<p class="text-muted small mb-0">Sedes, edificios y planos</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>Próximos Hitos Académicos & Exámenes
|
|
</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">Fecha</th>
|
|
<th>Tipo de Hito</th>
|
|
<th>Materia / Código</th>
|
|
<th>Aula / Enlace</th>
|
|
<th class="pe-3">Notas</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for m in role_data.upcoming_milestones %}
|
|
<tr>
|
|
<td class="ps-3 font-monospace fw-bold text-primary">
|
|
{{ m.date }}
|
|
</td>
|
|
<td>
|
|
<span class="badge" data-color="{{ m.type_obj.color if m.type_obj else '#0d6efd' }}" style="background-color:var(--badge-bg,#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>Enlace
|
|
</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>Próximas Reservas del Campus (Próximos 7 Días)
|
|
</h5>
|
|
<a href="/schedule/calendar_view" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-calendar3 me-1"></i>Ver en Calendario
|
|
</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">Fecha & Horario</th>
|
|
<th>Aula</th>
|
|
<th>Asignatura / Código</th>
|
|
<th>Turno / Comisión</th>
|
|
<th>Estado</th>
|
|
<th class="text-end pe-3">Acciones</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 }}
|
|
<span class="ms-1 text-body">{{ reservation.start_time }} - {{ reservation.end_time }} 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>Confirmada
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">
|
|
<i class="bi bi-clock me-1"></i>Pendiente
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-end pe-3">
|
|
<a href="/schedule/view_reservation?id={{ res.id }}"
|
|
class="btn btn-sm btn-outline-secondary py-0 px-2" title="Ver Ficha">
|
|
<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">No hay reservas para los próximos 7 días</h6>
|
|
<p class="text-muted small">Puedes programar nuevas reservas desde el calendario.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if 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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
// Apply dynamic colors from data-color attributes (milestone type badges)
|
|
document.querySelectorAll('[data-color]').forEach(function(el) {
|
|
const color = el.getAttribute('data-color');
|
|
if (color) el.style.setProperty('background-color', color, 'important');
|
|
});
|
|
</script>
|
|
{% endblock %} |