feat: completar Fase 2 del Roadmap MVP (Co-docencia, Matriz de Conflictos y Regla Diaria) y auditoria de seguridad
This commit is contained in:
@@ -13,8 +13,35 @@
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Flash Messages -->
|
||||
|
||||
<!-- Flash & Action Alerts -->
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show d-flex align-items-center gap-2 shadow-sm mb-4" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill fs-5 text-danger flex-shrink-0"></i>
|
||||
<div class="small fw-semibold flex-grow-1">{{ error }}</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if success %}
|
||||
<div class="alert alert-success alert-dismissible fade show d-flex align-items-center gap-2 shadow-sm mb-4" role="alert">
|
||||
<i class="bi bi-check-circle-fill fs-5 text-success flex-shrink-0"></i>
|
||||
<div class="small fw-semibold flex-grow-1">Alumno matriculado exitosamente en la comisión.</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if unregistered %}
|
||||
<div class="alert alert-info alert-dismissible fade show d-flex align-items-center gap-2 shadow-sm mb-4" role="alert">
|
||||
<i class="bi bi-info-circle-fill fs-5 text-info flex-shrink-0"></i>
|
||||
<div class="small fw-semibold flex-grow-1">El alumno ha sido desvinculado de la comisión.</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if updated %}
|
||||
<div class="alert alert-info alert-dismissible fade show d-flex align-items-center gap-2 shadow-sm mb-4" role="alert">
|
||||
<i class="bi bi-info-circle-fill fs-5 text-info flex-shrink-0"></i>
|
||||
<div class="small fw-semibold flex-grow-1">Estado de matrícula actualizado correctamente.</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-4">
|
||||
<!-- Left: Commission Info + Teacher Assignment -->
|
||||
@@ -77,47 +104,96 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Teacher Assignment -->
|
||||
<!-- Co-Docencia / Equipo Docente -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header border-0 bg-transparent py-2 d-flex align-items-center gap-2">
|
||||
<i class="bi bi-person-video3 text-info"></i>
|
||||
<span class="fw-semibold">Docente Asignado</span>
|
||||
<div class="card-header border-0 bg-transparent py-2 d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-people-fill text-info"></i>
|
||||
<span class="fw-semibold">Equipo Docente / Cátedra</span>
|
||||
</div>
|
||||
<span class="badge bg-info-subtle text-info border border-info-subtle">
|
||||
{{ commission.teachers|length if commission.teachers else (1 if commission.teacher else 0) }} docente(s)
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if commission.teacher %}
|
||||
<div class="d-flex align-items-center gap-3 mb-3 p-2 rounded bg-info-subtle border border-info-subtle">
|
||||
<div class="avatar-sm bg-info rounded-circle d-flex align-items-center justify-content-center" style="width:40px;height:40px;flex-shrink:0;">
|
||||
<i class="bi bi-person-fill text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-semibold small">{{ commission.teacher.name }}</div>
|
||||
<div class="text-muted" style="font-size:.75rem;">{{ commission.teacher.email }}</div>
|
||||
{% if commission.teachers and commission.teachers|length > 0 %}
|
||||
<div class="d-flex flex-column gap-2 mb-3">
|
||||
{% for t in commission.teachers %}
|
||||
<div class="d-flex align-items-center justify-content-between p-2 rounded bg-body-tertiary border">
|
||||
<div class="d-flex align-items-center gap-2 overflow-hidden">
|
||||
<div class="avatar-sm {% if t.role == 'Titular' %}bg-primary{% elif t.role == 'Adjunto' %}bg-info{% else %}bg-secondary{% endif %} rounded-circle text-white d-flex align-items-center justify-content-center" style="width:34px;height:34px;flex-shrink:0;">
|
||||
<i class="bi bi-person-fill small"></i>
|
||||
</div>
|
||||
<div class="text-truncate">
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<span class="fw-semibold small text-truncate">{{ t.name }}</span>
|
||||
<span class="badge {% if t.role == 'Titular' %}bg-primary-subtle text-primary border border-primary-subtle{% elif t.role == 'Adjunto' %}bg-info-subtle text-info border border-info-subtle{% elif t.role == 'JTP' %}bg-warning-subtle text-warning border border-warning-subtle{% else %}bg-secondary-subtle text-secondary border{% endif %} py-0 px-1" style="font-size: 0.65rem;">
|
||||
{{ t.role or 'Docente' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-muted text-truncate" style="font-size:.75rem;">{{ t.email or 'Sin email' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if can_edit %}
|
||||
<form method="POST" action="/admin/commission_remove_teacher" class="ms-2 flex-shrink-0" onsubmit="return confirm('¿Quitar a {{ t.name }} de la comisión?');">
|
||||
<input type="hidden" name="id" value="{{ commission.id }}">
|
||||
<input type="hidden" name="teacher_id" value="{{ t.user_id }}">
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm py-0 px-1" title="Quitar de la cátedra">
|
||||
<i class="bi bi-trash" style="font-size: 0.75rem;"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif commission.teacher %}
|
||||
<div class="d-flex align-items-center justify-content-between p-2 rounded bg-body-tertiary border mb-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="avatar-sm bg-primary rounded-circle text-white d-flex align-items-center justify-content-center" style="width:34px;height:34px;flex-shrink:0;">
|
||||
<i class="bi bi-person-fill small"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-semibold small">{{ commission.teacher.name }} <span class="badge bg-primary-subtle text-primary border py-0 px-1" style="font-size:0.65rem;">Titular</span></div>
|
||||
<div class="text-muted" style="font-size:.75rem;">{{ commission.teacher.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning py-2 small mb-3">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>Sin docente asignado
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>Sin docentes asignados a la comisión.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if can_edit %}
|
||||
<form method="POST" action="/admin/commission_assign_teacher" id="assign-teacher-form">
|
||||
<input type="hidden" name="id" value="{{ commission.id }}">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small fw-semibold mb-1">Cambiar / Asignar Docente</label>
|
||||
<select name="teacher_id" class="form-select form-select-sm" id="teacher-select">
|
||||
<option value="">— Sin asignar —</option>
|
||||
{% for teacher in teachers %}
|
||||
<option value="{{ teacher.id }}" {% if commission.teacher_id == teacher.id %}selected{% endif %}>
|
||||
{{ teacher.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-info btn-sm w-100" id="assign-teacher-btn">
|
||||
<i class="bi bi-person-check me-1"></i>Guardar Docente
|
||||
</button>
|
||||
</form>
|
||||
<div class="border-top pt-3 mt-2">
|
||||
<h6 class="small fw-bold text-muted text-uppercase mb-2">
|
||||
<i class="bi bi-person-plus me-1"></i>Agregar Docente a la Cátedra
|
||||
</h6>
|
||||
<form method="POST" action="/admin/commission_add_teacher" id="add-teacher-form">
|
||||
<input type="hidden" name="id" value="{{ commission.id }}">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small fw-semibold mb-1">Docente</label>
|
||||
<select name="teacher_id" class="form-select form-select-sm" required id="teacher-select">
|
||||
<option value="">— Seleccionar Docente —</option>
|
||||
{% for teacher in teachers %}
|
||||
<option value="{{ teacher.id }}">{{ teacher.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold mb-1">Rol en la Cátedra</label>
|
||||
<select name="role" class="form-select form-select-sm" required>
|
||||
<option value="Titular">Profesor Titular</option>
|
||||
<option value="Adjunto" selected>Profesor Adjunto</option>
|
||||
<option value="JTP">Jefe de Trabajos Prácticos (JTP)</option>
|
||||
<option value="Ayudante">Ayudante Diplomado / Alumno</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100" id="assign-teacher-btn">
|
||||
<i class="bi bi-plus-circle me-1"></i>Asignar a Comisión
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -147,6 +223,7 @@
|
||||
<div class="collapse" id="enroll-form">
|
||||
<div class="card-body border-top bg-success-subtle py-3">
|
||||
<form method="POST" action="/admin/commission_enroll_student" id="enroll-student-form">
|
||||
<input type="hidden" name="id" value="{{ commission.id }}">
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label small fw-semibold mb-1">Seleccionar Alumno</label>
|
||||
@@ -168,6 +245,20 @@
|
||||
<i class="bi bi-plus-circle me-1"></i>Inscribir
|
||||
</button>
|
||||
</div>
|
||||
<!-- Regla de Restricción Diaria - Excepción de Bedelía (Fase 2 MVP) -->
|
||||
<div class="col-12 mt-2 pt-2 border-top border-success-subtle">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="allow_same_day_exception" value="1" id="sameDayCheck"
|
||||
onchange="document.getElementById('reasonRow').classList.toggle('d-none', !this.checked)">
|
||||
<label class="form-check-label small fw-semibold text-success-emphasis" for="sameDayCheck">
|
||||
<i class="bi bi-shield-check me-1 text-primary"></i>Autorizar excepción de cursada en el mismo día (Bedelía)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mt-1 d-none" id="reasonRow">
|
||||
<input type="text" name="exception_reason" class="form-control form-control-sm border-warning"
|
||||
placeholder="Motivo o expediente de excepción de Bedelía (ej: Autorizado por Bedelía Central)..." id="exception-reason-input">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -184,6 +275,7 @@
|
||||
<th>Alumno</th>
|
||||
<th class="text-center">Estado</th>
|
||||
<th class="text-center">Fecha Inscripción</th>
|
||||
<th class="text-center">Excepciones</th>
|
||||
<th class="text-center">Notas</th>
|
||||
{% if can_edit %}<th class="text-end pe-3">Acciones</th>{% endif %}
|
||||
</tr>
|
||||
@@ -205,12 +297,23 @@
|
||||
<td class="text-center small text-muted">
|
||||
{{ e.enrolled_at if e.enrolled_at else '—' }}
|
||||
</td>
|
||||
<td class="text-center small">
|
||||
{% if e.allow_same_day_exception %}
|
||||
<span class="badge bg-warning-subtle text-warning border border-warning-subtle py-1 px-2" title="{{ e.exception_reason or 'Excepción autorizada de Bedelía' }}">
|
||||
<i class="bi bi-shield-check me-1"></i>Excepción Diaria
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted opacity-50">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center small text-muted">{{ e.notes or '—' }}</td>
|
||||
{% if can_edit %}
|
||||
<td class="text-end pe-3">
|
||||
<div class="d-flex gap-1 justify-content-end">
|
||||
<!-- Quick status update -->
|
||||
<form method="POST" action="/admin/commission_update_enrollment" class="d-inline">
|
||||
<input type="hidden" name="id" value="{{ commission.id }}">
|
||||
<input type="hidden" name="student_id" value="{{ e.student_id }}">
|
||||
<select name="status" class="form-select form-select-sm d-inline-block w-auto"
|
||||
onchange="this.form.submit()" title="Cambiar estado"
|
||||
id="status-select-{{ e.student_id }}">
|
||||
@@ -223,6 +326,8 @@
|
||||
{% set st_display = e.student.name if e.student else 'este alumno' %}
|
||||
<form method="POST" action="/admin/commission_unenroll_student"
|
||||
onsubmit="return confirm('¿Dar de baja a {{ st_display }} de la comisión?')">
|
||||
<input type="hidden" name="id" value="{{ commission.id }}">
|
||||
<input type="hidden" name="student_id" value="{{ e.student_id }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Dar de baja"
|
||||
id="unenroll-btn-{{ e.student_id }}">
|
||||
<i class="bi bi-person-dash"></i>
|
||||
|
||||
@@ -126,7 +126,18 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if comm.teacher %}
|
||||
{% if comm.teachers and comm.teachers | length > 0 %}
|
||||
<div class="d-flex flex-column gap-1">
|
||||
{% for t in comm.teachers %}
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<span class="badge {% if t.role == 'Titular' %}bg-primary-subtle text-primary border border-primary-subtle{% elif t.role == 'Adjunto' %}bg-info-subtle text-info border border-info-subtle{% elif t.role == 'JTP' %}bg-warning-subtle text-warning border border-warning-subtle{% else %}bg-secondary-subtle text-secondary border{% endif %} py-0 px-1" style="font-size: 0.65rem;">
|
||||
{{ t.role or 'Docente' }}
|
||||
</span>
|
||||
<span class="small text-truncate" style="max-width: 150px;" title="{{ t.name }}">{{ t.name }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif comm.teacher %}
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="avatar-xs bg-info-subtle rounded-circle d-flex align-items-center justify-content-center" style="width:28px;height:28px;">
|
||||
<i class="bi bi-person-video3 small text-info"></i>
|
||||
@@ -295,7 +306,7 @@
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-8">
|
||||
<label for="add_teacher_id" class="form-label fw-semibold small">
|
||||
<i class="bi bi-person-video3 text-info me-1"></i>Docente Responsable
|
||||
<i class="bi bi-person-video3 text-info me-1"></i>Docente Responsable (Titular)
|
||||
</label>
|
||||
<select class="form-select form-select-sm" id="add_teacher_id" name="teacher_id">
|
||||
<option value="">-- Sin asignar (se puede asignar más tarde) --</option>
|
||||
@@ -303,6 +314,7 @@
|
||||
<option value="{{ t.id }}">{{ t.name }} ({{ t.email }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text small">Docente titular principal. Podrás agregar más docentes (co-docencia, adjuntos, JTP) desde el detalle de la comisión.</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="add_max_students" class="form-label fw-semibold small">Cupo Máximo de Alumnos</label>
|
||||
|
||||
Reference in New Issue
Block a user