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
+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>