feat: refactorizar espacios fisicos desacoplando Edificio, Piso y Aula con selectores en cascada y filtros

This commit is contained in:
Carlos Tello
2026-09-03 05:18:01 -03:00
parent 5e0b905e55
commit 6ccc9e8c87
11 changed files with 1093 additions and 522 deletions
+52 -13
View File
@@ -9,23 +9,57 @@ class Classroom(db.Model):
building_id = db.Column(db.Integer, db.ForeignKey('buildings.id'), nullable=True)
building = db.Column(db.String(100), nullable=False)
code = db.Column('room_number', db.String(50), nullable=False) # Maps to room_number column in DB
floor = db.Column(db.Integer, nullable=False)
floor = db.Column(db.String(50), nullable=False, default='1')
capacity = db.Column(db.Integer, nullable=False)
description = db.Column(db.Text)
is_active = db.Column('active', db.Boolean, default=True, nullable=False) # Maps to active column in DB
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relación con Building (mapeada vía backref 'building_entity' en Building.classrooms)
@property
def building_rel(self):
return self.building_entity
@property
def building_name(self):
"""Nombre del edificio desacoplado"""
if self.building_rel:
return self.building_rel.name
return self.building or 'Edificio Central'
@property
def floor_display(self):
"""Formato amigable para el piso: PB, Planta Baja, Piso X"""
f = str(self.floor).strip() if self.floor is not None else ''
if f in ['0', 'PB', 'pb', 'Planta Baja', 'planta baja']:
return 'Planta Baja'
if f.lower().startswith('piso') or '°' in f:
return f
return f"Piso {f}"
@property
def is_virtual(self):
"""Indica si el aula es virtual"""
return (self.code and 'VIRTUAL' in self.code.upper()) or (self.building and 'VIRTUAL' in self.building.upper())
@property
def location_display(self):
"""Ubicación completa y limpia: Edificio · Piso · Aula"""
if self.is_virtual:
return '🌐 Campus Virtual'
return f"{self.building_name} · {self.floor_display} · {self.code}"
# Virtual properties for compatibility
@property
def name(self):
"""Generate name from building and code"""
return f"{self.building}-{self.code}"
return f"{self.building_name}-{self.code}"
@property
def campus_location(self):
"""Placeholder for campus location - can be expanded when campus model is implemented"""
return None
"""Placeholder for campus location"""
return self.building_rel.address if self.building_rel else None
# Virtual field for compatibility - combines building and code
@property
@@ -39,28 +73,34 @@ class Classroom(db.Model):
# Override code property for consistency
@property
def code_display(self):
return f"{self.building}-{self.code}"
return f"{self.building_name} - {self.code}"
@property
def capacity_display(self):
"""Retorna '∞' para aulas virtuales o capacidad ilimitada, o el número entero."""
if self.capacity == 0 or (self.code and 'VIRTUAL' in self.code.upper()):
if self.capacity == 0 or self.is_virtual:
return '∞'
return str(self.capacity)
@property
def is_unlimited(self):
return self.capacity == 0 or (self.code and 'VIRTUAL' in self.code.upper())
return self.capacity == 0 or self.is_virtual
def to_dict(self):
"""Convert classroom to dictionary"""
return {
'id': self.id,
'code': self.code_display,
'building': self.building,
'building_id': self.building_id,
'building': self.building_name,
'code': self.code,
'code_display': self.code_display,
'name': self.name,
'floor': self.floor,
'floor_display': self.floor_display,
'location_display': self.location_display,
'capacity': self.capacity,
'capacity_display': self.capacity_display,
'is_virtual': self.is_virtual,
'description': self.description,
'campus_location': self.campus_location,
'is_active': self.is_active,
@@ -71,10 +111,9 @@ class Classroom(db.Model):
def get_full_location(self):
"""Get full classroom location"""
location = f"{self.building} - Floor {self.floor}"
if self.campus_location:
location += f" ({self.campus_location})"
return location
if self.is_virtual:
return 'Campus Virtual (Remoto)'
return f"{self.building_name} - {self.floor_display}"
@staticmethod
def find_best_classroom(max_students, start_time, end_time, exclude_reservation_id=None):