feat: completar Fase 2 del Roadmap MVP (Co-docencia, Matriz de Conflictos y Regla Diaria) y auditoria de seguridad

This commit is contained in:
2026-09-19 14:05:16 -03:00
parent 05e197e49f
commit ada39248d7
36 changed files with 3037 additions and 443 deletions
+155 -60
View File
@@ -20,12 +20,30 @@
<i class="bi bi-plus-lg me-1"></i>Nueva Asignatura
</button>
{% endif %}
<a href="/schedule/commissions_view" class="btn btn-outline-secondary">
<a href="/admin/commissions_list" class="btn btn-outline-secondary">
<i class="bi bi-diagram-3 me-1"></i>Ver Comisiones
</a>
</div>
</div>
<!-- Notification Alerts -->
{% if 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>
</div>
{% elif msg == 'deactivated' %}
<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>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 == '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>
{% endif %}
<!-- Filters Bar -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body py-2">
@@ -133,7 +151,15 @@
<td class="text-end">
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary py-0 px-2"
data-bs-toggle="modal" data-bs-target="#modalEditSubject{{ s.id }}"
data-bs-toggle="modal" data-bs-target="#modalEditSubject"
data-id="{{ s.id }}"
data-code="{{ s.code }}"
data-name="{{ s.name }}"
data-credits="{{ s.credits }}"
data-career-id="{{ s.career_id or '' }}"
data-department="{{ s.department or '' }}"
data-description="{{ s.description or '' }}"
data-active="{{ 'true' if s.is_active else 'false' }}"
title="Editar Asignatura">
<i class="bi bi-pencil"></i>
</button>
@@ -144,64 +170,14 @@
<i class="bi {% if s.is_active %}bi-pause{% else %}bi-play{% endif %}"></i>
</button>
</form>
</div>
<!-- Modal Editar Asignatura -->
<div class="modal fade text-start" id="modalEditSubject{{ s.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="/admin/subject_edit">
<input type="hidden" name="id" value="{{ s.id }}">
<div class="modal-header">
<h5 class="modal-title h6 fw-bold">
<i class="bi bi-pencil-square text-primary me-2"></i>Editar Asignatura
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row g-2 mb-3">
<div class="col-md-5">
<label class="form-label small fw-semibold">Código *</label>
<input type="text" name="code" class="form-control form-control-sm font-monospace" value="{{ s.code }}" required>
</div>
<div class="col-md-7">
<label class="form-label small fw-semibold">Créditos</label>
<input type="number" name="credits" class="form-control form-control-sm" value="{{ s.credits }}" min="1" max="20">
</div>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Nombre de la Asignatura *</label>
<input type="text" name="name" class="form-control form-control-sm" value="{{ s.name }}" required>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Carrera Vinculada</label>
<select name="career_id" class="form-select form-select-sm">
<option value="">Sin carrera específica (General)</option>
{% for c in careers %}
<option value="{{ c.id }}" {% if s.career_id == c.id %}selected{% endif %}>{{ c.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Departamento / Área</label>
<input type="text" name="department" class="form-control form-control-sm" value="{{ s.department or '' }}">
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Descripción / Objetivos</label>
<textarea name="description" class="form-control form-control-sm" rows="2">{{ s.description or '' }}</textarea>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="is_active" id="activeSubj{{ s.id }}" {% if s.is_active %}checked{% endif %}>
<label class="form-check-label small" for="activeSubj{{ s.id }}">Asignatura Activa</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-sm btn-primary">Guardar Cambios</button>
</div>
</form>
</div>
</div>
<button type="button" class="btn btn-outline-danger py-0 px-2"
data-bs-toggle="modal" data-bs-target="#modalDeleteSubject"
data-id="{{ s.id }}"
data-code="{{ s.code }}"
data-name="{{ s.name }}"
title="Eliminar Asignatura">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
{% endif %}
@@ -304,5 +280,124 @@
</div>
</div>
</div>
<!-- Modal Editar Asignatura (Global fuera de la tabla para evitar problemas de visualización) -->
<div class="modal fade" id="modalEditSubject" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="/admin/subject_edit" id="formEditSubject">
<input type="hidden" name="id" id="editSubjectId">
<div class="modal-header">
<h5 class="modal-title h6 fw-bold">
<i class="bi bi-pencil-square text-primary me-2"></i>Editar Asignatura
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row g-2 mb-3">
<div class="col-md-5">
<label class="form-label small fw-semibold">Código *</label>
<input type="text" name="code" id="editSubjectCode" class="form-control form-control-sm font-monospace" required>
</div>
<div class="col-md-7">
<label class="form-label small fw-semibold">Créditos</label>
<input type="number" name="credits" id="editSubjectCredits" class="form-control form-control-sm" min="1" max="20">
</div>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Nombre de la Asignatura *</label>
<input type="text" name="name" id="editSubjectName" class="form-control form-control-sm" required>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Carrera Vinculada</label>
<select name="career_id" id="editSubjectCareer" class="form-select form-select-sm">
<option value="">Sin carrera específica (General)</option>
{% for c in careers %}
<option value="{{ c.id }}">{{ c.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Departamento / Área</label>
<input type="text" name="department" id="editSubjectDept" class="form-control form-control-sm">
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Descripción / Objetivos</label>
<textarea name="description" id="editSubjectDesc" class="form-control form-control-sm" rows="2"></textarea>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="is_active" id="editSubjectActive">
<label class="form-check-label small" for="editSubjectActive">Asignatura Activa</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-sm btn-primary">Guardar Cambios</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal Eliminar Asignatura -->
<div class="modal fade" id="modalDeleteSubject" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form method="POST" action="/admin/subject_delete">
<input type="hidden" name="id" id="deleteSubjectId">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title h6 fw-bold">
<i class="bi bi-exclamation-triangle-fill me-2"></i>Eliminar Asignatura
</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 asignatura <strong id="deleteSubjectName"></strong> (<span class="font-monospace" id="deleteSubjectCode"></span>)?</p>
<div class="alert alert-warning small mb-0">
<i class="bi bi-info-circle me-1"></i>
<strong>Aviso de integridad:</strong> Si la materia no posee comisiones vinculadas se eliminará permanentemente. Si ya posee comisiones históricas o cursadas, el sistema la <strong>desactivará automáticamente</strong> para resguardar las actas académicas.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-sm btn-danger">Confirmar Eliminación</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Populate Edit Subject Modal
var modalEdit = document.getElementById('modalEditSubject');
if (modalEdit) {
modalEdit.addEventListener('show.bs.modal', function(event) {
var btn = event.relatedTarget;
if (!btn) return;
document.getElementById('editSubjectId').value = btn.getAttribute('data-id') || '';
document.getElementById('editSubjectCode').value = btn.getAttribute('data-code') || '';
document.getElementById('editSubjectName').value = btn.getAttribute('data-name') || '';
document.getElementById('editSubjectCredits').value = btn.getAttribute('data-credits') || '4';
document.getElementById('editSubjectCareer').value = btn.getAttribute('data-career-id') || '';
document.getElementById('editSubjectDept').value = btn.getAttribute('data-department') || '';
document.getElementById('editSubjectDesc').value = btn.getAttribute('data-description') || '';
document.getElementById('editSubjectActive').checked = (btn.getAttribute('data-active') === 'true');
});
}
// Populate Delete Subject Modal
var modalDel = document.getElementById('modalDeleteSubject');
if (modalDel) {
modalDel.addEventListener('show.bs.modal', function(event) {
var btn = event.relatedTarget;
if (!btn) return;
document.getElementById('deleteSubjectId').value = btn.getAttribute('data-id') || '';
document.getElementById('deleteSubjectCode').textContent = btn.getAttribute('data-code') || '';
document.getElementById('deleteSubjectName').textContent = btn.getAttribute('data-name') || '';
});
}
});
</script>
{% endif %}
{% endblock %}