429 lines
24 KiB
HTML
429 lines
24 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Gestión de Asignaturas - Edu-Space Admin{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid pt-5 mt-4">
|
|
<!-- Header -->
|
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-4">
|
|
<div>
|
|
<h1 class="h3 fw-bold mb-1">
|
|
<i class="bi bi-book text-primary me-2"></i>Gestión de Asignaturas y Materias
|
|
</h1>
|
|
<p class="text-muted mb-0 small">
|
|
Catálogo institucional de asignaturas, códigos, créditos académicos y carreras vinculadas.
|
|
</p>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
{% if can_edit %}
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAddSubject">
|
|
<i class="bi bi-plus-lg me-1"></i>Nueva Asignatura
|
|
</button>
|
|
{% endif %}
|
|
<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 == '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>
|
|
</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 == '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 -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body py-2">
|
|
<form method="GET" class="row g-2 align-items-center">
|
|
<!-- Buscador -->
|
|
<div class="col-md-4">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-body-tertiary"><i class="bi bi-search"></i></span>
|
|
<input type="text" name="search" class="form-control"
|
|
placeholder="Buscar por código, nombre o departamento..."
|
|
value="{{ search }}">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtro Carrera -->
|
|
<div class="col-md-4">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-body-tertiary"><i class="bi bi-mortarboard text-primary"></i></span>
|
|
<select name="career_id" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
<option value="">Todas las carreras</option>
|
|
{% for c in careers %}
|
|
<option value="{{ c.id }}" {% if career_filter == c.id %}selected{% endif %}>{{ c.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtro Estado -->
|
|
<div class="col-md-2">
|
|
<select name="active" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
<option value="">Todos los estados</option>
|
|
<option value="true" {% if active_filter == 'true' %}selected{% endif %}>Solo activas</option>
|
|
<option value="false" {% if active_filter == 'false' %}selected{% endif %}>Solo inactivas</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-2 text-md-end">
|
|
<button type="submit" class="btn btn-sm btn-primary me-1">
|
|
<i class="bi bi-funnel me-1"></i>Filtrar
|
|
</button>
|
|
<a href="/admin/subjects_list" class="btn btn-sm btn-outline-secondary">
|
|
<i class="bi bi-x-circle me-1"></i>Limpiar
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Subjects Table -->
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Código</th>
|
|
<th>Nombre de la Asignatura</th>
|
|
<th>Carrera</th>
|
|
<th>Departamento</th>
|
|
<th>Créditos</th>
|
|
<th>Comisiones</th>
|
|
<th>Estado</th>
|
|
{% if can_edit %}
|
|
<th class="text-end">Acciones</th>
|
|
{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for s in subjects %}
|
|
<tr>
|
|
<td>
|
|
<span class="badge font-monospace bg-primary-subtle text-primary border">{{ s.code }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="fw-bold text-body">{{ s.name }}</span>
|
|
{% if s.description %}
|
|
<small class="text-muted d-block text-truncate" style="max-width: 300px;">{{ s.description }}</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if s.career_obj %}
|
|
<span class="badge bg-secondary-subtle text-secondary border">
|
|
<i class="bi bi-mortarboard me-1"></i>{{ s.career_obj.name }}
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted small">General</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="small text-muted">{{ s.department or '-' }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-light text-dark border">{{ s.credits }} cr.</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info-subtle text-info border">
|
|
<i class="bi bi-diagram-3 me-1"></i>{{ s.commissions|length if s.commissions else 0 }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge rounded-pill {% if s.is_active %}bg-success-subtle text-success border border-success-subtle{% else %}bg-secondary-subtle text-secondary border{% endif %}">
|
|
{% if s.is_active %}Activa{% else %}Inactiva{% endif %}
|
|
</span>
|
|
</td>
|
|
{% if can_edit %}
|
|
<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"
|
|
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>
|
|
<form method="POST" action="/admin/subject_toggle" class="d-inline">
|
|
<input type="hidden" name="id" value="{{ s.id }}">
|
|
<button type="submit" class="btn btn-outline-warning py-0 px-2"
|
|
title="{% if s.is_active %}Desactivar{% else %}Activar{% endif %}">
|
|
<i class="bi {% if s.is_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="#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 %}
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="8" class="text-center py-5 text-muted">
|
|
<i class="bi bi-book fs-1 d-block mb-2"></i>
|
|
No se encontraron asignaturas con los criterios indicados.
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Paginación -->
|
|
{% if pagination.pages > 1 %}
|
|
<div class="card-footer bg-body-tertiary border-0 py-2">
|
|
<nav aria-label="Subjects pagination">
|
|
<ul class="pagination pagination-sm justify-content-center mb-0">
|
|
{% if pagination.has_prev %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="/admin/subjects_list">Anterior</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for p in pagination.iter_pages(left_edge=2, right_edge=2, left_current=2, right_current=2) %}
|
|
{% if p %}
|
|
<li class="page-item {% if p == pagination.page %}active{% endif %}">
|
|
<a class="page-link" href="/admin/subjects_list">{{ p }}</a>
|
|
</li>
|
|
{% else %}
|
|
<li class="page-item disabled"><span class="page-link">…</span></li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if pagination.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="/admin/subjects_list">Siguiente</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Nueva Asignatura -->
|
|
{% if can_edit %}
|
|
<div class="modal fade" id="modalAddSubject" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="POST" action="/admin/subject_add">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title h6 fw-bold">
|
|
<i class="bi bi-book text-primary me-2"></i>Nueva 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" placeholder="ej. ASIG0015" 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="4" 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" placeholder="ej. Álgebra Lineal" 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 }}">{{ 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" placeholder="ej. Ciencias Exactas, Informática">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-semibold">Descripción</label>
|
|
<textarea name="description" class="form-control form-control-sm" rows="2" placeholder="Programa de la materia..."></textarea>
|
|
</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">Crear Asignatura</button>
|
|
</div>
|
|
</form>
|
|
</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 %}
|