feat: implementar ABM completo de comisiones, carreras y asignaturas con proteccion de integridad y corregir edificios y campus virtual
This commit is contained in:
@@ -26,6 +26,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Alerts -->
|
||||
{% if msg == 'created' %}
|
||||
<div class="alert alert-success alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>La carrera ha sido registrada exitosamente.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'updated' %}
|
||||
<div class="alert alert-success alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>Los datos de la carrera se actualizaron correctamente.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'deleted' %}
|
||||
<div class="alert alert-success alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>La carrera ha sido eliminada permanentemente del sistema.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error == 'has_subjects' %}
|
||||
<div class="alert alert-warning alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>No es posible eliminar esta carrera porque tiene asignaturas vinculadas. Debe reasignar o eliminar primero sus materias.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error == 'conflict' %}
|
||||
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill me-2"></i>Ya existe una carrera registrada con ese nombre o código institucional.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill me-2"></i>Ocurrió un error al procesar la solicitud en el catálogo de carreras.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Search & Filter Bar -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body py-2">
|
||||
@@ -114,10 +147,15 @@
|
||||
<form method="POST" action="/admin/career_delete" class="d-inline"
|
||||
onsubmit="return confirm('¿Confirma que desea eliminar esta carrera permanentemente?');">
|
||||
<input type="hidden" name="id" value="{{ career.id }}">
|
||||
<button type="submit" class="btn btn-outline-danger py-0 px-2" title="Eliminar">
|
||||
<button type="submit" class="btn btn-outline-danger py-0 px-2" title="Eliminar Carrera">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-outline-secondary py-0 px-2 opacity-50"
|
||||
disabled title="No se puede eliminar: posee {{ c_stat.subjects_count }} asignatura(s) vinculada(s)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
<!-- Commission Card -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header border-0 bg-primary text-white rounded-top py-3">
|
||||
<div class="card-header border-0 bg-primary text-white rounded-top py-3 d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-diagram-3 fs-4"></i>
|
||||
<div>
|
||||
@@ -57,6 +57,11 @@
|
||||
<small class="opacity-75">{{ commission.semester }} {{ commission.year }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if can_edit %}
|
||||
<button type="button" class="btn btn-sm btn-light py-1 px-2 fw-semibold text-primary" data-bs-toggle="modal" data-bs-target="#modalEditCommissionDetail">
|
||||
<i class="bi bi-pencil me-1"></i>Editar
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="fw-bold mb-1">{{ commission.subject.name }}</h5>
|
||||
@@ -389,4 +394,80 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Editar Datos de la Comisión -->
|
||||
{% if can_edit %}
|
||||
<div class="modal fade" id="modalEditCommissionDetail" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<form action="/admin/commission_edit" method="POST">
|
||||
<input type="hidden" name="id" value="{{ commission.id }}">
|
||||
<div class="modal-header bg-body-tertiary border-bottom py-3">
|
||||
<h5 class="modal-title fw-bold">
|
||||
<i class="bi bi-pencil-square text-primary me-2"></i>Editar Datos de la Comisión
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold small">Identificador / Código</label>
|
||||
<input type="text" class="form-control form-control-sm font-monospace" name="code" value="{{ commission.code }}" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold small">Cuatrimestre</label>
|
||||
<select class="form-select form-select-sm" name="semester">
|
||||
<option value="1C" {% if commission.semester == '1C' %}selected{% endif %}>1C (Primer Cuatrimestre)</option>
|
||||
<option value="2C" {% if commission.semester == '2C' %}selected{% endif %}>2C (Segundo Cuatrimestre)</option>
|
||||
<option value="Anual" {% if commission.semester == 'Anual' %}selected{% endif %}>Anual</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold small">Año</label>
|
||||
<input type="number" class="form-control form-control-sm" name="year" value="{{ commission.year }}" min="2020" max="2035">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold small">Turno</label>
|
||||
<select class="form-select form-select-sm" name="shift">
|
||||
<option value="Mañana" {% if commission.shift == 'Mañana' %}selected{% endif %}>Mañana (08:00 - 12:00)</option>
|
||||
<option value="Tarde" {% if commission.shift == 'Tarde' %}selected{% endif %}>Tarde (17:30 - 21:30)</option>
|
||||
<option value="Vespertino" {% if commission.shift == 'Vespertino' %}selected{% endif %}>Vespertino</option>
|
||||
<option value="Noche" {% if commission.shift == 'Noche' %}selected{% endif %}>Noche (19:00 - 23:00)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold small">Cupo Máximo de Alumnos</label>
|
||||
<input type="number" class="form-control form-control-sm" name="max_students" value="{{ commission.max_students }}" min="1" max="300">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold small">Días y Horarios</label>
|
||||
<input type="text" class="form-control form-control-sm" name="schedule" value="{{ commission.schedule or '' }}" placeholder="Ej: Lun y Mié 17:30 a 21:30 hs">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold small">Enlace Aula Virtual (Meet, Teams, Zoom)</label>
|
||||
<input type="url" class="form-control form-control-sm" name="virtual_link" value="{{ commission.virtual_link or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="active" id="detail_edit_active" value="on" {% if commission.active %}checked{% endif %}>
|
||||
<label class="form-check-label small fw-semibold" for="detail_edit_active">
|
||||
Comisión Activa
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-body-tertiary border-top py-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm px-4">Guardar Cambios</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Flash Messages & Feedback -->
|
||||
{% if created %}
|
||||
{% if msg == 'created' or created %}
|
||||
<div class="alert alert-success alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill fs-5 text-success"></i>
|
||||
<div>
|
||||
@@ -30,6 +30,62 @@
|
||||
</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'updated' %}
|
||||
<div class="alert alert-success alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill fs-5 text-success"></i>
|
||||
<div>
|
||||
<strong>¡Comisión actualizada!</strong> Los datos de la comisión, cupos y horarios fueron guardados correctamente.
|
||||
</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'toggled' %}
|
||||
<div class="alert alert-info alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-info-circle-fill fs-5 text-info"></i>
|
||||
<div>
|
||||
El estado de actividad de la comisión ha sido modificado.
|
||||
</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'deleted' %}
|
||||
<div class="alert alert-success alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill fs-5 text-success"></i>
|
||||
<div>
|
||||
La comisión ha sido eliminada permanentemente del sistema.
|
||||
</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'deactivated' %}
|
||||
<div class="alert alert-warning alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill fs-5 text-warning"></i>
|
||||
<div>
|
||||
La comisión posee reservas horarias o alumnos inscriptos. Por integridad del historial académico fue <strong>desactivada</strong>.
|
||||
</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error == 'create' %}
|
||||
<div class="alert alert-danger alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill fs-5 text-danger"></i>
|
||||
<div>No se pudo crear la comisión. Verifique que no exista otra comisión con el mismo código en el mismo período.</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error == 'edit' %}
|
||||
<div class="alert alert-danger alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill fs-5 text-danger"></i>
|
||||
<div>Ocurrió un error al actualizar los datos de la comisión.</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error == 'delete' %}
|
||||
<div class="alert alert-danger alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill fs-5 text-danger"></i>
|
||||
<div>Ocurrió un error al intentar eliminar la comisión.</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show d-flex align-items-center gap-2 mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill fs-5 text-danger"></i>
|
||||
<div>Ocurrió una eventualidad procesando la comisión.</div>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -170,10 +226,44 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<a href="/admin/commission_detail?id={{ comm.id }}"
|
||||
class="btn btn-sm btn-outline-primary" title="Ver y gestionar comisión">
|
||||
<i class="bi bi-gear me-1"></i>Gestionar
|
||||
</a>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="/admin/commission_detail?id={{ comm.id }}"
|
||||
class="btn btn-outline-primary py-0 px-2" title="Gestionar e Inscribir Alumnos">
|
||||
<i class="bi bi-gear"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-secondary py-0 px-2"
|
||||
data-bs-toggle="modal" data-bs-target="#modalEditCommission"
|
||||
data-id="{{ comm.id }}"
|
||||
data-code="{{ comm.code }}"
|
||||
data-subject-id="{{ comm.subject_id }}"
|
||||
data-academic-term-id="{{ comm.academic_term_id or '' }}"
|
||||
data-semester="{{ comm.semester }}"
|
||||
data-year="{{ comm.year }}"
|
||||
data-shift="{{ comm.shift }}"
|
||||
data-teacher-id="{{ comm.teacher_id or '' }}"
|
||||
data-max-students="{{ comm.max_students }}"
|
||||
data-schedule="{{ comm.schedule or '' }}"
|
||||
data-virtual-link="{{ comm.virtual_link or '' }}"
|
||||
data-active="{{ 'true' if comm.active else 'false' }}"
|
||||
title="Editar Comisión">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<form method="POST" action="/admin/commission_toggle" class="d-inline">
|
||||
<input type="hidden" name="id" value="{{ comm.id }}">
|
||||
<button type="submit" class="btn btn-outline-warning py-0 px-2"
|
||||
title="{% if comm.active %}Desactivar comisión{% else %}Activar comisión{% endif %}">
|
||||
<i class="bi {% if comm.active %}bi-pause{% else %}bi-play{% endif %}"></i>
|
||||
</button>
|
||||
</form>
|
||||
<button type="button" class="btn btn-outline-danger py-0 px-2"
|
||||
data-bs-toggle="modal" data-bs-target="#modalDeleteCommission"
|
||||
data-id="{{ comm.id }}"
|
||||
data-code="{{ comm.get_full_code() }}"
|
||||
data-subject-name="{{ comm.subject.name }}"
|
||||
title="Eliminar Comisión">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -366,13 +456,171 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Editar Comisión -->
|
||||
<div class="modal fade" id="modalEditCommission" tabindex="-1" aria-labelledby="modalEditCommissionLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header bg-body-tertiary border-bottom py-3">
|
||||
<h5 class="modal-title fw-bold" id="modalEditCommissionLabel">
|
||||
<i class="bi bi-pencil-square text-primary me-2"></i>Editar Comisión de Cursada
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="/admin/commission_edit" method="POST" id="formEditCommission">
|
||||
<input type="hidden" name="id" id="edit_commission_id">
|
||||
<div class="modal-body p-4">
|
||||
<!-- 1. Asignatura y Código -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-7">
|
||||
<label for="edit_subject_id" class="form-label fw-semibold small">
|
||||
<i class="bi bi-book text-primary me-1"></i>Asignatura Asociada <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select class="form-select form-select-sm" id="edit_subject_id" name="subject_id" required>
|
||||
{% for subj in subjects %}
|
||||
<option value="{{ subj.id }}">{{ subj.code }} — {{ subj.name }} ({{ subj.career_name or 'General' }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label for="edit_code" class="form-label fw-semibold small">
|
||||
<i class="bi bi-tag text-primary me-1"></i>Identificador / Código <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm font-monospace" id="edit_code" name="code" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Ciclo Lectivo, Semestre y Año -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-5">
|
||||
<label for="edit_academic_term_id" class="form-label fw-semibold small">
|
||||
<i class="bi bi-calendar-event text-info me-1"></i>Ciclo Lectivo
|
||||
</label>
|
||||
<select class="form-select form-select-sm" id="edit_academic_term_id" name="academic_term_id">
|
||||
<option value="">-- Sin ciclo específico --</option>
|
||||
{% for term in academic_terms %}
|
||||
<option value="{{ term.id }}" data-year="{{ term.year }}" data-period="{{ term.period_type or '' }}" data-code="{{ term.code }}">
|
||||
{{ term.name }} ({{ term.code }}) - {{ term.year }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="edit_semester" class="form-label fw-semibold small">Período / Cuatrimestre</label>
|
||||
<select class="form-select form-select-sm" id="edit_semester" name="semester">
|
||||
<option value="1C">1C (Primer Cuatrimestre)</option>
|
||||
<option value="2C">2C (Segundo Cuatrimestre)</option>
|
||||
<option value="Anual">Anual</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="edit_year" class="form-label fw-semibold small">Año Académico</label>
|
||||
<input type="number" class="form-control form-control-sm" id="edit_year" name="year" min="2020" max="2035">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="edit_shift" class="form-label fw-semibold small">Turno de Cursada</label>
|
||||
<select class="form-select form-select-sm" id="edit_shift" name="shift">
|
||||
<option value="Mañana">Mañana (08:00 - 12:00)</option>
|
||||
<option value="Tarde">Tarde (17:30 - 21:30)</option>
|
||||
<option value="Vespertino">Vespertino</option>
|
||||
<option value="Noche">Noche (19:00 - 23:00)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="edit_max_students" class="form-label fw-semibold small">Cupo Máximo de Alumnos</label>
|
||||
<input type="number" class="form-control form-control-sm" id="edit_max_students" name="max_students" min="1" max="300">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Docente Responsable -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-12">
|
||||
<label for="edit_teacher_id" class="form-label fw-semibold small">
|
||||
<i class="bi bi-person-video3 text-info me-1"></i>Docente Responsable (Titular)
|
||||
</label>
|
||||
<select class="form-select form-select-sm" id="edit_teacher_id" name="teacher_id">
|
||||
<option value="">-- Sin asignar (se puede asignar más tarde) --</option>
|
||||
{% for t in teachers %}
|
||||
<option value="{{ t.id }}">{{ t.name }} ({{ t.email }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. Días y Horarios -->
|
||||
<div class="mb-3">
|
||||
<label for="edit_schedule" class="form-label fw-semibold small">
|
||||
<i class="bi bi-calendar-week text-warning me-1"></i>Días y Horarios de Cursada
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm" id="edit_schedule" name="schedule" placeholder="Ej: Lun y Mié 17:30 a 21:30 hs">
|
||||
</div>
|
||||
|
||||
<!-- 5. Enlace Virtual -->
|
||||
<div class="mb-3">
|
||||
<label for="edit_virtual_link" class="form-label fw-semibold small">
|
||||
<i class="bi bi-camera-video text-primary me-1"></i>Enlace a Aula Virtual (Meet, Zoom, Teams)
|
||||
</label>
|
||||
<input type="url" class="form-control form-control-sm" id="edit_virtual_link" name="virtual_link">
|
||||
</div>
|
||||
|
||||
<!-- 6. Estado Activo -->
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="active" id="edit_active" value="on">
|
||||
<label class="form-check-label small fw-semibold" for="edit_active">
|
||||
Comisión Activa para Cursada y Reservas
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-body-tertiary border-top py-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm px-4 shadow-sm">
|
||||
<i class="bi bi-save me-1"></i>Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Eliminar Comisión -->
|
||||
<div class="modal fade" id="modalDeleteCommission" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<form action="/admin/commission_delete" method="POST">
|
||||
<input type="hidden" name="id" id="delete_commission_id">
|
||||
<div class="modal-header bg-danger text-white py-3">
|
||||
<h5 class="modal-title h6 fw-bold">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>Eliminar Comisión
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body py-4">
|
||||
<p class="mb-2">¿Estás seguro de que deseas eliminar la comisión <strong id="delete_commission_code" class="font-monospace"></strong> vinculada a <strong id="delete_commission_subject"></strong>?</p>
|
||||
<div class="alert alert-warning small mb-0">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
<strong>Integridad Académica:</strong> Si la comisión posee reservas en aulas o alumnos inscriptos, el sistema la <strong>desactivará automáticamente</strong> para salvaguardar las actas y calificaciones.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-body-tertiary border-top py-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-danger btn-sm px-3">
|
||||
<i class="bi bi-trash me-1"></i>Confirmar Eliminación
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const termSelect = document.getElementById('add_academic_term_id');
|
||||
const yearInput = document.getElementById('add_year');
|
||||
const semSelect = document.getElementById('add_semester');
|
||||
// Sincronización Ciclo Lectivo en Alta
|
||||
const addTermSelect = document.getElementById('add_academic_term_id');
|
||||
const addYearInput = document.getElementById('add_year');
|
||||
const addSemSelect = document.getElementById('add_semester');
|
||||
|
||||
function syncTerm() {
|
||||
function syncTerm(termSelect, yearInput, semSelect) {
|
||||
if (!termSelect) return;
|
||||
const opt = termSelect.options[termSelect.selectedIndex];
|
||||
if (!opt || !opt.value) return;
|
||||
@@ -391,9 +639,51 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
|
||||
if (termSelect) {
|
||||
termSelect.addEventListener('change', syncTerm);
|
||||
if (termSelect.value) syncTerm();
|
||||
if (addTermSelect) {
|
||||
addTermSelect.addEventListener('change', () => syncTerm(addTermSelect, addYearInput, addSemSelect));
|
||||
if (addTermSelect.value) syncTerm(addTermSelect, addYearInput, addSemSelect);
|
||||
}
|
||||
|
||||
// Sincronización Ciclo Lectivo en Edición
|
||||
const editTermSelect = document.getElementById('edit_academic_term_id');
|
||||
const editYearInput = document.getElementById('edit_year');
|
||||
const editSemSelect = document.getElementById('edit_semester');
|
||||
|
||||
if (editTermSelect) {
|
||||
editTermSelect.addEventListener('change', () => syncTerm(editTermSelect, editYearInput, editSemSelect));
|
||||
}
|
||||
|
||||
// Poblar Modal Editar Comisión
|
||||
const modalEdit = document.getElementById('modalEditCommission');
|
||||
if (modalEdit) {
|
||||
modalEdit.addEventListener('show.bs.modal', function(event) {
|
||||
const btn = event.relatedTarget;
|
||||
if (!btn) return;
|
||||
document.getElementById('edit_commission_id').value = btn.getAttribute('data-id') || '';
|
||||
document.getElementById('edit_code').value = btn.getAttribute('data-code') || '';
|
||||
document.getElementById('edit_subject_id').value = btn.getAttribute('data-subject-id') || '';
|
||||
document.getElementById('edit_academic_term_id').value = btn.getAttribute('data-academic-term-id') || '';
|
||||
document.getElementById('edit_semester').value = btn.getAttribute('data-semester') || '1C';
|
||||
document.getElementById('edit_year').value = btn.getAttribute('data-year') || '2026';
|
||||
document.getElementById('edit_shift').value = btn.getAttribute('data-shift') || 'Mañana';
|
||||
document.getElementById('edit_teacher_id').value = btn.getAttribute('data-teacher-id') || '';
|
||||
document.getElementById('edit_max_students').value = btn.getAttribute('data-max-students') || '40';
|
||||
document.getElementById('edit_schedule').value = btn.getAttribute('data-schedule') || '';
|
||||
document.getElementById('edit_virtual_link').value = btn.getAttribute('data-virtual-link') || '';
|
||||
document.getElementById('edit_active').checked = (btn.getAttribute('data-active') === 'true');
|
||||
});
|
||||
}
|
||||
|
||||
// Poblar Modal Eliminar Comisión
|
||||
const modalDel = document.getElementById('modalDeleteCommission');
|
||||
if (modalDel) {
|
||||
modalDel.addEventListener('show.bs.modal', function(event) {
|
||||
const btn = event.relatedTarget;
|
||||
if (!btn) return;
|
||||
document.getElementById('delete_commission_id').value = btn.getAttribute('data-id') || '';
|
||||
document.getElementById('delete_commission_code').textContent = btn.getAttribute('data-code') || '';
|
||||
document.getElementById('delete_commission_subject').textContent = btn.getAttribute('data-subject-name') || '';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -27,7 +27,22 @@
|
||||
</div>
|
||||
|
||||
<!-- Notification Alerts -->
|
||||
{% if msg == 'deleted' %}
|
||||
{% if msg == 'created' %}
|
||||
<div class="alert alert-success alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>La asignatura ha sido registrada exitosamente en el catálogo académico.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'updated' %}
|
||||
<div class="alert alert-success alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>Los datos de la asignatura fueron actualizados correctamente.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'toggled' %}
|
||||
<div class="alert alert-info alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-info-circle-fill me-2"></i>El estado de actividad de la asignatura ha sido modificado.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif msg == 'deleted' %}
|
||||
<div class="alert alert-success alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>La asignatura ha sido eliminada permanentemente del sistema.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
@@ -37,11 +52,21 @@
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>La asignatura posee comisiones históricas asociadas. Por integridad de los registros académicos fue <strong>desactivada</strong>.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error == 'conflict' %}
|
||||
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill me-2"></i>Ya existe una asignatura con ese código o denominación.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error == 'delete' %}
|
||||
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill me-2"></i>Ocurrió un error al procesar la eliminación de la asignatura.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% elif error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow-sm" role="alert">
|
||||
<i class="bi bi-x-circle-fill me-2"></i>Ocurrió un error al procesar la operación sobre la asignatura.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Filters Bar -->
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form method="POST" novalidate>
|
||||
<form method="POST" action="{% if building %}/buildings/edit/{{ building.id }}{% else %}/buildings/add_building{% endif %}" novalidate>
|
||||
{% if building %}
|
||||
<input type="hidden" name="id" value="{{ building.id }}">
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-8">
|
||||
@@ -47,7 +50,7 @@
|
||||
|
||||
<div class="col-md-8">
|
||||
{{ form.address.label(class="form-label fw-semibold") }}
|
||||
{{ form.address(class="form-control" + (" is-invalid" if form.address.errors else ""), placeholder="ej. Av. Paseo Colón 255, CABA") }}
|
||||
{{ form.address(class="form-control" + (" is-invalid" if form.address.errors else ""), placeholder="ej. Av. Paseo Colón 255, CABA o campus.unicaba.edu.ar") }}
|
||||
{% if form.address.errors %}
|
||||
<div class="invalid-feedback">{{ form.address.errors[0] }}</div>
|
||||
{% endif %}
|
||||
@@ -55,11 +58,11 @@
|
||||
|
||||
<div class="col-md-4">
|
||||
{{ form.floors.label(class="form-label fw-semibold") }}
|
||||
{{ form.floors(class="form-control" + (" is-invalid" if form.floors.errors else ""), min=1, max=100) }}
|
||||
{{ form.floors(class="form-control" + (" is-invalid" if form.floors.errors else ""), min=0, max=100) }}
|
||||
{% if form.floors.errors %}
|
||||
<div class="invalid-feedback">{{ form.floors.errors[0] }}</div>
|
||||
{% endif %}
|
||||
<small class="text-muted">Cantidad de plantas/pisos</small>
|
||||
<small class="text-muted">Plantas físicas (0 para Campus Virtual)</small>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
@@ -24,6 +24,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alertas de estado -->
|
||||
{% if success or updated or created %}
|
||||
<div class="alert alert-success alert-dismissible fade show border-0 shadow-sm mb-4" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
{% if created %}El edificio ha sido creado exitosamente.{% else %}Los datos del edificio se actualizaron correctamente.{% endif %}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-sm-6 col-xl-3">
|
||||
@@ -34,8 +43,9 @@
|
||||
<i class="bi bi-buildings fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small fw-semibold">Total Edificios</div>
|
||||
<div class="text-muted small fw-semibold">Sedes y Edificios</div>
|
||||
<h3 class="fw-bold mb-0">{{ buildings|length }}</h3>
|
||||
<small class="text-muted" style="font-size: 0.72rem;">Físicas y Digitales</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,8 +60,9 @@
|
||||
<i class="bi bi-door-open fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small fw-semibold">Aulas Activas</div>
|
||||
<div class="text-muted small fw-semibold">Aulas Totales</div>
|
||||
<h3 class="fw-bold mb-0 text-success">{{ total_classrooms }}</h3>
|
||||
<small class="text-muted" style="font-size: 0.72rem;">Activas en el sistema</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,8 +77,11 @@
|
||||
<i class="bi bi-people fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small fw-semibold">Capacidad Total</div>
|
||||
<h3 class="fw-bold mb-0 text-info">{{ total_capacity }}</h3>
|
||||
<div class="text-muted small fw-semibold">Capacidad Física Instalada</div>
|
||||
<h3 class="fw-bold mb-0 text-info">{{ total_physical_capacity or total_capacity }}</h3>
|
||||
<small class="text-info fw-semibold" style="font-size: 0.72rem;">
|
||||
<i class="bi bi-cloud-check me-1"></i>+ Campus Virtual (Ilimitado)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,8 +96,9 @@
|
||||
<i class="bi bi-layers fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small fw-semibold">Pisos Disponibles</div>
|
||||
<div class="text-muted small fw-semibold">Pisos Presenciales</div>
|
||||
<h3 class="fw-bold mb-0 text-warning">{{ buildings|sum(attribute='floors') }}</h3>
|
||||
<small class="text-muted" style="font-size: 0.72rem;">Plantas físicas activas</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,14 +141,14 @@
|
||||
<div class="row g-3">
|
||||
{% for b in buildings %}
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-0 shadow-sm h-100 position-relative">
|
||||
<div class="card border-0 shadow-sm h-100 position-relative {% if b.is_virtual %}border-start border-4 border-info{% endif %}">
|
||||
<div class="card-header bg-body-tertiary border-0 py-3 d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-secondary-subtle text-secondary font-monospace border mb-1">
|
||||
{{ b.code or 'EDIF' }}
|
||||
<span class="badge {% if b.is_virtual %}bg-info-subtle text-info border border-info-subtle{% else %}bg-secondary-subtle text-secondary border{% endif %} font-monospace mb-1">
|
||||
{% if b.is_virtual %}<i class="bi bi-cloud-fill me-1"></i>{% endif %}{{ b.code or 'EDIF' }}
|
||||
</span>
|
||||
<h5 class="card-title fw-bold mb-0">
|
||||
<a href="/buildings/view_building" class="text-decoration-none text-primary">
|
||||
<a href="/buildings/view/{{ b.id }}" class="text-decoration-none text-primary">
|
||||
{{ b.name }}
|
||||
</a>
|
||||
</h5>
|
||||
@@ -153,7 +168,7 @@
|
||||
<div class="card-body">
|
||||
{% if b.address %}
|
||||
<p class="text-muted small mb-3">
|
||||
<i class="bi bi-geo-alt-fill text-danger me-1"></i>{{ b.address }}
|
||||
<i class="bi {% if b.is_virtual %}bi-globe2 text-info{% else %}bi-geo-alt-fill text-danger{% endif %} me-1"></i>{{ b.address }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
@@ -165,7 +180,11 @@
|
||||
<div class="col-4">
|
||||
<div class="p-2 rounded bg-body-tertiary">
|
||||
<small class="text-muted d-block">Pisos</small>
|
||||
{% if b.is_virtual %}
|
||||
<span class="badge bg-info-subtle text-info border border-info-subtle mt-1">Digital</span>
|
||||
{% else %}
|
||||
<span class="fw-bold fs-6">{{ b.floors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@@ -177,22 +196,28 @@
|
||||
<div class="col-4">
|
||||
<div class="p-2 rounded bg-body-tertiary">
|
||||
<small class="text-muted d-block">Capacidad</small>
|
||||
<span class="fw-bold fs-6 text-success">{{ b.total_capacity }}</span>
|
||||
{% if b.is_virtual %}
|
||||
<span class="badge bg-primary-subtle text-primary border border-primary-subtle mt-1" title="Capacidad de concurrencia virtual ilimitada">
|
||||
<i class="bi bi-infinity me-1"></i>Ilimitada
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="fw-bold fs-6 text-success">{{ b.physical_capacity or b.total_capacity }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent border-0 py-3 d-flex justify-content-between align-items-center">
|
||||
<a href="/buildings/view_building" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-eye me-1"></i>Ver Distribución de Aulas
|
||||
<a href="/buildings/view/{{ b.id }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-eye me-1"></i>Ver Aulas
|
||||
</a>
|
||||
|
||||
{% if user.has_permission('classrooms', 'read_write') %}
|
||||
{% if user.has_permission('classrooms', 'read_write') or user.is_admin() or user.is_bedelia() %}
|
||||
<div class="btn-group">
|
||||
<a href="/buildings/edit_building" class="btn btn-outline-secondary btn-sm" title="Editar Edificio">
|
||||
<i class="bi bi-pencil"></i>
|
||||
<a href="/buildings/edit/{{ b.id }}" class="btn btn-outline-secondary btn-sm" title="Editar Edificio">
|
||||
<i class="bi bi-pencil me-1"></i>Editar
|
||||
</a>
|
||||
<form method="POST" action="/buildings/toggle_building" class="d-inline">
|
||||
<form method="POST" action="/buildings/toggle/{{ b.id }}" class="d-inline">
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm" title="{% if b.is_active %}Desactivar{% else %}Activar{% endif %}">
|
||||
<i class="bi bi-power"></i>
|
||||
</button>
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-md-5 text-md-end mt-3 mt-md-0">
|
||||
{% if user.has_permission('classrooms', 'read_write') %}
|
||||
<a href="/buildings/edit_building" class="btn btn-outline-secondary me-2">
|
||||
{% if user.has_permission('classrooms', 'read_write') or user.is_admin() or user.is_bedelia() %}
|
||||
<a href="/buildings/edit/{{ building.id }}" class="btn btn-outline-secondary me-2">
|
||||
<i class="bi bi-pencil me-1"></i>Editar Edificio
|
||||
</a>
|
||||
<a href="/classrooms/add_classroom" class="btn btn-primary">
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="card-body p-3">
|
||||
<div class="text-muted small fw-semibold">Ubicación / Dirección</div>
|
||||
<p class="h6 fw-bold mb-2">
|
||||
<i class="bi bi-geo-alt text-danger me-1"></i>{{ building.address or 'Sin dirección especificada' }}
|
||||
<i class="bi {% if building.is_virtual %}bi-globe2 text-info{% else %}bi-geo-alt text-danger{% endif %} me-1"></i>{{ building.address or 'Sin dirección especificada' }}
|
||||
</p>
|
||||
{% if building.description %}
|
||||
<small class="text-secondary">{{ building.description }}</small>
|
||||
@@ -59,7 +59,11 @@
|
||||
<div class="col-sm-4">
|
||||
<div class="card border-0 shadow-sm text-center p-3">
|
||||
<small class="text-muted text-uppercase fw-bold">Pisos</small>
|
||||
{% if building.is_virtual %}
|
||||
<span class="fs-5 fw-bold text-info d-block mt-2">Digital / Nube</span>
|
||||
{% else %}
|
||||
<span class="display-6 fw-bold text-primary">{{ building.floors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
@@ -71,7 +75,13 @@
|
||||
<div class="col-sm-4">
|
||||
<div class="card border-0 shadow-sm text-center p-3">
|
||||
<small class="text-muted text-uppercase fw-bold">Capacidad Total</small>
|
||||
<span class="display-6 fw-bold text-info">{{ building.total_capacity }}</span>
|
||||
{% if building.is_virtual %}
|
||||
<span class="fs-5 fw-bold text-primary d-block mt-2" title="Capacidad de concurrencia virtual ilimitada">
|
||||
<i class="bi bi-infinity me-1"></i>Ilimitada
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="display-6 fw-bold text-info">{{ building.physical_capacity or building.total_capacity }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user