feat: implementar ABM completo de comisiones, carreras y asignaturas con proteccion de integridad y corregir edificios y campus virtual

This commit is contained in:
2026-09-23 19:53:27 -03:00
parent 195bfd915e
commit 165a0b65b8
13 changed files with 1033 additions and 154 deletions
+39 -1
View File
@@ -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>
+82 -1
View File
@@ -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 %}
+302 -12
View File
@@ -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>
+26 -1
View File
@@ -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 -->
+7 -4
View File
@@ -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">
+42 -17
View File
@@ -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>
+14 -4
View File
@@ -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>