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
+137 -32
View File
@@ -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>
+14 -2
View File
@@ -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>
+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 %}
+214 -69
View File
@@ -1,104 +1,217 @@
{% extends "base.html" %}
{% block title %}{% if user %}Edit User: {{ user.name }}{% else %}New User{% endif %} - Edu-Space Admin{% endblock %}
{% block title %}{% if user %}Editar Usuario: {{ user.name }}{% else %}Nuevo Usuario{% endif %} — Edu-Space Admin{% endblock %}
{% block content %}
<div class="container-fluid pt-5 mt-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-4">
<div>
<h1 class="h3 mb-1">
<h1 class="h3 mb-1 fw-bold">
<i class="bi bi-person-gear text-primary me-2"></i>
{% if user %}Edit User Account{% else %}Create User Account{% endif %}
{% if user %}Editar Cuenta de Usuario{% else %}Crear Nueva Cuenta de Usuario{% endif %}
</h1>
<p class="text-muted small mb-0">Configure user credentials, profile information, and role assignment.</p>
<p class="text-muted small mb-0">
Gestión integral de identidad, datos de contacto, credenciales y rol institucional para Bedelía, Docentes y Alumnos.
</p>
</div>
<div>
<a href="/admin/users_list" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>Back to Users
<i class="bi bi-arrow-left me-1"></i>Volver a Usuarios
</a>
</div>
</div>
<div class="row">
<div class="col-lg-6 mx-auto">
<div class="col-lg-8 mx-auto">
<div class="card border-0 shadow-sm">
<div class="card-header bg-body-tertiary py-3">
<h5 class="card-title h6 mb-0">
<i class="bi bi-person-vcard text-primary me-2"></i>Account Details
</h5>
<div class="card-header bg-body-tertiary py-3 d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-2">
<i class="bi bi-person-vcard text-primary fs-5"></i>
<h5 class="card-title h6 mb-0 fw-bold">
{% if user %}Ficha de Datos de: {{ user.name }}{% else %}Datos del Nuevo Usuario{% endif %}
</h5>
</div>
{% if user %}
<span class="badge bg-primary-subtle text-primary border font-monospace">ID #{{ user.id }}</span>
{% endif %}
</div>
<div class="card-body p-4">
<form method="POST">
{% if user %}
<input type="hidden" name="id" value="{{ user.id }}">
{% endif %}
<div class="mb-3">
<label for="name" class="form-label fw-semibold">Full Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="name" name="name"
value="{{ user.name if user else '' }}" required
placeholder="e.g. María González">
<!-- SECCIÓN 1: IDENTIDAD Y DOCUMENTACIÓN -->
<div class="mb-4">
<h6 class="text-uppercase text-primary fw-bold small border-bottom pb-2 mb-3">
<i class="bi bi-person-badge me-1"></i>1. Identidad y Documentación Personal
</h6>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="first_name" class="form-label small fw-semibold">Nombre(s) <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="first_name" name="first_name"
value="{{ user.first_name if user else '' }}" required
placeholder="Ej: María Belén">
</div>
<div class="col-md-6">
<label for="last_name" class="form-label small fw-semibold">Apellido(s) <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="last_name" name="last_name"
value="{{ user.last_name if user else '' }}" required
placeholder="Ej: González">
</div>
</div>
<div class="row g-3">
<div class="col-md-6">
<label for="document_type" class="form-label small fw-semibold">
Tipo de Documento <span class="text-muted">(Argentina / Extranjero)</span>
</label>
<select class="form-select" id="document_type" name="document_type">
<optgroup label="Documentos Nacionales (Argentina)">
<option value="DNI" {% if not user or user.document_type == 'DNI' %}selected{% endif %}>DNI — Documento Nacional de Identidad</option>
<option value="CUIL" {% if user and user.document_type == 'CUIL' %}selected{% endif %}>CUIL / CUIT — Código Laboral / Fiscal</option>
<option value="PAS" {% if user and user.document_type == 'PAS' %}selected{% endif %}>Pasaporte Argentino</option>
</optgroup>
<optgroup label="Documentos Extranjeros e Internacionales">
<option value="PASEXT" {% if user and user.document_type == 'PASEXT' %}selected{% endif %}>Pasaporte Extranjero (Internacional)</option>
<option value="DNIEXT" {% if user and user.document_type == 'DNIEXT' %}selected{% endif %}>DNI para Extranjeros (Radicación)</option>
<option value="CIEXT" {% if user and user.document_type == 'CIEXT' %}selected{% endif %}>Cédula Mercosur / Extranjera</option>
</optgroup>
<optgroup label="Documentos Históricos Nacionales">
<option value="CI" {% if user and user.document_type == 'CI' %}selected{% endif %}>Cédula de Identidad (Policía Federal)</option>
<option value="LC" {% if user and user.document_type == 'LC' %}selected{% endif %}>Libreta Cívica (LC)</option>
<option value="LE" {% if user and user.document_type == 'LE' %}selected{% endif %}>Libreta de Enrolamiento (LE)</option>
</optgroup>
</select>
</div>
<div class="col-md-6">
<label for="document_number" class="form-label small fw-semibold">Número de Documento</label>
<input type="text" class="form-control font-monospace" id="document_number" name="document_number"
value="{{ user.document_number if user else '' }}"
placeholder="Ej: 38123456">
<div class="form-text small" id="docHelpText">
Ingrese el número según la tipificación oficial seleccionada.
</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label fw-semibold">Email Address <span class="text-danger">*</span></label>
<input type="email" class="form-control" id="email" name="email"
value="{{ user.email if user else '' }}" required
placeholder="e.g. mgonzalez@edu-space.com">
<!-- SECCIÓN 2: DATOS DE CONTACTO Y DOMICILIO -->
<div class="mb-4">
<h6 class="text-uppercase text-primary fw-bold small border-bottom pb-2 mb-3">
<i class="bi bi-geo-alt me-1"></i>2. Contacto y Domicilio
</h6>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="phone" class="form-label small fw-semibold">
<i class="bi bi-telephone me-1 text-secondary"></i>Teléfono de Contacto
</label>
<input type="tel" class="form-control" id="phone" name="phone"
value="{{ user.phone if user else '' }}"
placeholder="Ej: +54 9 11 4455-6677">
<div class="form-text small">Móvil o fijo con código de área.</div>
</div>
<div class="col-md-6">
<label for="personal_email" class="form-label small fw-semibold">
<i class="bi bi-envelope-at me-1 text-secondary"></i>Email Personal / Particular
</label>
<input type="email" class="form-control" id="personal_email" name="personal_email"
value="{{ user.personal_email if user else '' }}"
placeholder="Ej: nombre.apellido@gmail.com">
<div class="form-text small">Correo personal alternativo para notificaciones y recuperación.</div>
</div>
</div>
<div class="row g-3">
<div class="col-12">
<label for="address" class="form-label small fw-semibold">
<i class="bi bi-house-door me-1 text-secondary"></i>Domicilio / Dirección
</label>
<input type="text" class="form-control" id="address" name="address"
value="{{ user.address if user else '' }}"
placeholder="Ej: Av. Córdoba 1850, Piso 4 B, CABA">
<div class="form-text small">Calle, número, piso/departamento y localidad.</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label fw-semibold">
Password {% if not user %}<span class="text-danger">*</span>{% endif %}
</label>
<input type="password" class="form-control" id="password" name="password"
{% if not user %}required{% endif %}
placeholder="{% if user %}Leave empty to keep current password{% else %}At least 6 characters{% endif %}">
{% if user %}
<small class="text-muted d-block mt-1">
Only fill this in if you want to reset this user's password.
</small>
{% endif %}
<!-- SECCIÓN 3: CUENTA, ACCESO Y ROL INSTITUCIONAL -->
<div class="mb-4">
<h6 class="text-uppercase text-primary fw-bold small border-bottom pb-2 mb-3">
<i class="bi bi-shield-lock me-1"></i>3. Cuenta de Acceso y Rol Institucional
</h6>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="email" class="form-label small fw-semibold">Email Institucional <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text bg-body-tertiary"><i class="bi bi-envelope"></i></span>
<input type="email" class="form-control" id="email" name="email"
value="{{ user.email if user else '' }}" required
placeholder="usuario@edu-space.com">
</div>
</div>
<div class="col-md-6">
<label for="password" class="form-label small fw-semibold">
Contraseña {% if not user %}<span class="text-danger">*</span>{% endif %}
</label>
<div class="input-group">
<span class="input-group-text bg-body-tertiary"><i class="bi bi-key"></i></span>
<input type="password" class="form-control" id="password" name="password"
{% if not user %}required{% endif %}
placeholder="{% if user %}Dejar en blanco para mantener{% else %}Mínimo 6 caracteres{% endif %}">
</div>
{% if user %}
<small class="text-muted d-block mt-1">
Solo complete este campo si desea restablecer la contraseña del usuario.
</small>
{% endif %}
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-8">
<label for="role_id" class="form-label small fw-semibold">Rol / Grupo de Acceso <span class="text-danger">*</span></label>
<select class="form-select" id="role_id" name="role_id" required>
<option value="" disabled {% if not user or not user.role_id %}selected{% endif %}>Seleccione un rol...</option>
{% for r in roles %}
<option value="{{ r.id }}" {% if user and user.role_id == r.id %}selected{% endif %}>
{{ r.name }} {% if r.description %}— {{ r.description }}{% endif %}
</option>
{% endfor %}
</select>
<small class="text-muted d-block mt-1">
Define los permisos para Bedelía, Profesores/Docentes, Alumnos o Administradores.
</small>
</div>
<div class="col-md-4 d-flex align-items-center">
<div class="form-check form-switch mt-3">
<input class="form-check-input" type="checkbox" role="switch" id="is_active" name="is_active"
{% if not user or user.is_active %}checked{% endif %}>
<label class="form-check-label fw-semibold small" for="is_active">
Cuenta Activa
</label>
<small class="text-muted d-block">
Permite iniciar sesión en el sistema.
</small>
</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="role_id" class="form-label fw-semibold">Role / Access Group <span class="text-danger">*</span></label>
<select class="form-select" id="role_id" name="role_id" required>
<option value="" disabled {% if not user or not user.role_id %}selected{% endif %}>Select a role...</option>
{% for r in roles %}
<option value="{{ r.id }}" {% if user and user.role_id == r.id %}selected{% endif %}>
{{ r.name }} - {{ r.description or '' }}
</option>
{% endfor %}
</select>
<small class="text-muted d-block mt-1">
<a href="/admin/roles_list" target="_blank" class="text-decoration-none">
<i class="bi bi-box-arrow-up-right me-1"></i>View or manage roles and permissions
</a>
</small>
</div>
<div class="mb-4 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="is_active" name="is_active"
{% if not user or user.is_active %}checked{% endif %}
{% if user and user.id == user.id %}disabled{% endif %}>
<label class="form-check-label fw-semibold" for="is_active">
Active Account
</label>
<small class="text-muted d-block">
Inactive users are prevented from signing in to the platform.
</small>
{% if user and user.id == user.id %}
<input type="hidden" name="is_active" value="on">
{% endif %}
</div>
<div class="d-flex justify-content-between align-items-center pt-2">
<div class="d-flex justify-content-between align-items-center pt-3 border-top">
<a href="/admin/users_list" class="btn btn-outline-secondary">
Cancel
Cancelar
</a>
<button type="submit" class="btn btn-primary px-4 fw-semibold">
<i class="bi bi-check-lg me-1"></i>Save User
<button type="submit" class="btn btn-primary px-4 fw-semibold shadow-sm">
<i class="bi bi-save me-1"></i>Guardar Usuario
</button>
</div>
</form>
@@ -107,4 +220,36 @@
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var docTypeSelect = document.getElementById('document_type');
var docNumInput = document.getElementById('document_number');
var docHelpText = document.getElementById('docHelpText');
var hints = {
'DNI': 'DNI Argentino: 7 u 8 dígitos numéricos sin puntos (ej: 38123456).',
'CUIL': 'CUIL/CUIT: 11 dígitos numéricos o con guiones (ej: 20-38123456-7).',
'PAS': 'Pasaporte Argentino: 3 letras y 6 números (ej: AAB123456).',
'PASEXT': 'Pasaporte Extranjero: código alfanumérico emitido por país de origen.',
'DNIEXT': 'DNI Extranjero: 7 a 10 dígitos o alfanumérico con residencia.',
'CI': 'Cédula de Identidad Nacional: 6 a 8 dígitos numéricos.',
'CIEXT': 'Cédula de Identidad Mercosur: formato de país emisor.',
'LC': 'Libreta Cívica: 6 a 7 dígitos numéricos.',
'LE': 'Libreta de Enrolamiento: 6 a 7 dígitos numéricos.'
};
function updateDocHint() {
var val = docTypeSelect.value;
if (hints[val]) {
docHelpText.textContent = hints[val];
}
}
if (docTypeSelect) {
docTypeSelect.addEventListener('change', updateDocHint);
updateDocHint();
}
});
</script>
{% endblock %}
+102 -40
View File
@@ -1,24 +1,39 @@
{% extends "base.html" %}
{% block title %}User Management - Edu-Space Admin{% endblock %}
{% block title %}Gestión de Usuarios — Edu-Space Admin{% endblock %}
{% block content %}
<div class="container-fluid pt-5 mt-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-4">
<div>
<h1 class="h3 mb-1">
<i class="bi bi-people text-primary me-2"></i>User Management
<h1 class="h3 mb-1 fw-bold">
<i class="bi bi-people text-primary me-2"></i>Gestión de Usuarios
</h1>
<p class="text-muted small mb-0">Manage user accounts, assign roles, and control active status.</p>
<p class="text-muted small mb-0">
Administración de cuentas, identidad y documentación, datos de contacto y asignación de roles institucionales (Bedelía, Docentes, Alumnos).
</p>
</div>
<div>
<a href="/admin/user_add" class="btn btn-primary">
<i class="bi bi-person-plus me-1"></i>New User
<a href="/admin/user_add" class="btn btn-primary shadow-sm">
<i class="bi bi-person-plus me-1"></i>Nuevo Usuario
</a>
</div>
</div>
<!-- Feedback Alerts -->
{% if msg == 'saved' %}
<div class="alert alert-success alert-dismissible fade show shadow-sm mb-4" role="alert">
<i class="bi bi-check-circle-fill me-2"></i>Los datos del usuario han sido guardados exitosamente.
<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 shadow-sm mb-4" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>{{ error }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<!-- Filters & Search -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-3">
@@ -29,13 +44,13 @@
<i class="bi bi-search text-muted"></i>
</span>
<input type="text" name="search" class="form-control border-start-0"
placeholder="Search by name or email..."
placeholder="Buscar por nombre, apellido, email o nro de documento..."
value="{{ search }}">
</div>
</div>
<div class="col-md-3">
<select name="role" class="form-select form-select-sm">
<option value="">All Roles</option>
<option value="">Todos los Roles</option>
{% for r in roles %}
<option value="{{ r.id }}" {% if role_filter == (r.id|string) %}selected{% endif %}>{{ r.name }}</option>
{% endfor %}
@@ -43,17 +58,17 @@
</div>
<div class="col-md-2">
<select name="status" class="form-select form-select-sm">
<option value="">All Statuses</option>
<option value="active" {% if status_filter == 'active' %}selected{% endif %}>Active</option>
<option value="inactive" {% if status_filter == 'inactive' %}selected{% endif %}>Inactive</option>
<option value="">Todos los Estados</option>
<option value="active" {% if status_filter == 'active' %}selected{% endif %}>Solo Activos</option>
<option value="inactive" {% if status_filter == 'inactive' %}selected{% endif %}>Solo Inactivos</option>
</select>
</div>
<div class="col-md-2 d-flex gap-2">
<button type="submit" class="btn btn-primary btn-sm w-100">
<i class="bi bi-filter me-1"></i>Filter
<i class="bi bi-filter me-1"></i>Filtrar
</button>
{% if search or role_filter or status_filter %}
<a href="/admin/users_list" class="btn btn-outline-secondary btn-sm" title="Clear filters">
<a href="/admin/users_list" class="btn btn-outline-secondary btn-sm" title="Limpiar filtros">
<i class="bi bi-x-lg"></i>
</a>
{% endif %}
@@ -68,12 +83,13 @@
<table class="table table-hover align-middle mb-0">
<thead class="bg-body-tertiary">
<tr>
<th class="ps-4">User</th>
<th>Email</th>
<th>Role / Group</th>
<th>Status</th>
<th>Last Login</th>
<th class="text-end pe-4">Actions</th>
<th class="ps-4">Usuario / Identidad</th>
<th>Documento</th>
<th>Contacto y Domicilio</th>
<th>Rol Institucional</th>
<th>Estado</th>
<th>Último Acceso</th>
<th class="text-end pe-4">Acciones</th>
</tr>
</thead>
<tbody>
@@ -81,17 +97,54 @@
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<div class="user-avatar-small me-2">
{{ (u.first_name[0] if u.first_name else 'U')|upper }}
<div class="user-avatar-small me-2 rounded-circle bg-primary-subtle text-primary d-flex align-items-center justify-content-center fw-bold" style="width: 36px; height: 36px;">
{{ (u.first_name[0] if u.first_name else (u.name[0] if u.name else 'U'))|upper }}
</div>
<div>
<div class="fw-bold">{{ u.name }}</div>
<small class="text-muted">ID: #{{ u.id }}</small>
<div class="text-muted small">
{% if u.first_name and u.last_name %}
<span class="opacity-75">{{ u.last_name }}, {{ u.first_name }}</span> ·
{% endif %}
<span class="font-monospace">#{{ u.id }}</span>
</div>
</div>
</div>
</td>
<td>
<span class="font-monospace small">{{ u.email }}</span>
{% if u.document_number %}
<div>
<span class="badge {% if u.document_type in ['PAS', 'PASEXT'] %}bg-info-subtle text-info border border-info-subtle{% elif u.document_type == 'CUIL' %}bg-primary-subtle text-primary border border-primary-subtle{% else %}bg-secondary-subtle text-secondary border{% endif %} py-1 px-2 font-monospace">
{{ u.document_type or 'DNI' }}: {{ u.document_formatted or u.document_number }}
</span>
</div>
{% else %}
<span class="text-muted small fst-italic"><i class="bi bi-dash"></i> Sin registrar</span>
{% endif %}
</td>
<td>
<div>
<i class="bi bi-building me-1 text-primary" title="Email Institucional"></i>
<span class="font-monospace small fw-semibold">{{ u.email }}</span>
</div>
{% if u.personal_email %}
<div class="text-muted small">
<i class="bi bi-envelope-at me-1 text-secondary" title="Email Personal"></i>
<span class="font-monospace" style="font-size: 0.8rem;">{{ u.personal_email }}</span>
</div>
{% endif %}
{% if u.phone or u.address %}
<div class="text-muted small mt-1 d-flex flex-column gap-0">
{% if u.phone %}
<span><i class="bi bi-telephone me-1 text-secondary"></i>{{ u.phone }}</span>
{% endif %}
{% if u.address %}
<span class="text-truncate" style="max-width: 220px;" title="{{ u.address }}">
<i class="bi bi-geo-alt me-1 text-secondary"></i>{{ u.address }}
</span>
{% endif %}
</div>
{% endif %}
</td>
<td>
{% if u.role_obj %}
@@ -100,40 +153,40 @@
</span>
{% else %}
<span class="badge bg-secondary">
{{ u.role or _('No Role') }}
{{ u.role or 'Docente' }}
</span>
{% endif %}
</td>
<td>
{% if u.is_active %}
<span class="badge bg-success-subtle text-success border border-success-subtle px-2 py-1">
<i class="bi bi-check-circle me-1"></i>Active
<i class="bi bi-check-circle me-1"></i>Activo
</span>
{% else %}
<span class="badge bg-danger-subtle text-danger border border-danger-subtle px-2 py-1">
<i class="bi bi-dash-circle me-1"></i>Inactive
<i class="bi bi-dash-circle me-1"></i>Inactivo
</span>
{% endif %}
</td>
<td>
<small class="text-muted">
{% if u.last_login %}
{{ format_datetime(u.last_login, format='short') }}
{{ u.last_login[:16] if u.last_login is string else u.last_login }}
{% else %}
Never
Nunca
{% endif %}
</small>
</td>
<td class="text-end pe-4">
<div class="btn-group btn-group-sm">
<a href="/admin/users/edit/{{ u.id }}" class="btn btn-outline-primary" title="Edit User">
<a href="/admin/users/edit/{{ u.id }}" class="btn btn-outline-primary" title="Editar Ficha de Usuario">
<i class="bi bi-pencil"></i>
</a>
{% if u.id != user.id %}
<form action="/admin/user_toggle" method="POST" class="d-inline">
<input type="hidden" name="id" value="{{ u.id }}">
<button type="submit" class="btn {% if u.is_active %}btn-outline-warning{% else %}btn-outline-success{% endif %}"
title="{% if u.is_active %}Deactivate User{% else %}Activate User{% endif %}">
title="{% if u.is_active %}Desactivar Usuario{% else %}Activar Usuario{% endif %}">
<i class="bi {% if u.is_active %}bi-person-x{% else %}bi-person-check{% endif %}"></i>
</button>
</form>
@@ -151,9 +204,9 @@
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<td colspan="7" class="text-center py-5 text-muted">
<i class="bi bi-people fs-1 d-block mb-2 text-secondary"></i>
No users found matching the criteria.
No se encontraron usuarios coincidentes con los criterios de búsqueda.
</td>
</tr>
{% endfor %}
@@ -162,11 +215,8 @@
</div>
<!-- Pagination -->
{% if pagination.pages > 1 %}
<div class="card-footer bg-body-tertiary d-flex justify-content-between align-items-center py-2 px-4">
<small class="text-muted">
Showing page {{ pagination.page }} of {{ pagination.pages }} ({{ pagination.total }} total)
</small>
{% if pagination and pagination.pages > 1 %}
<div class="card-footer bg-body-tertiary border-0 py-2 d-flex justify-content-center">
<ul class="pagination pagination-sm mb-0">
{% if pagination.has_prev %}
<li class="page-item"><a class="page-link" href="/admin/users_list">&laquo;</a></li>
@@ -189,7 +239,7 @@
</div>
</div>
<!-- Modal Global de Eliminación de Usuario (fuera de la tabla) -->
<!-- Modal Global de Eliminación de Usuario -->
<div class="modal fade" id="deleteUserModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg" style="border-radius: 14px;">
@@ -237,9 +287,21 @@ document.addEventListener('DOMContentLoaded', function() {
var name = this.getAttribute('data-user-name');
var email = this.getAttribute('data-user-email');
var actionUrl = this.getAttribute('data-action-url');
var userId = this.getAttribute('data-user-id');
if (nameEl) nameEl.textContent = name;
if (emailEl) emailEl.textContent = email;
if (formEl) formEl.action = actionUrl;
if (formEl) {
formEl.action = actionUrl;
// Add hidden input with id
var hiddenId = formEl.querySelector('input[name="id"]');
if (!hiddenId) {
hiddenId = document.createElement('input');
hiddenId.type = 'hidden';
hiddenId.name = 'id';
formEl.appendChild(hiddenId);
}
hiddenId.value = userId;
}
if (deleteModal) deleteModal.show();
});
});