from app import db from datetime import datetime class Classroom(db.Model): __tablename__ = 'classrooms' id = db.Column(db.Integer, primary_key=True) campus_id = db.Column(db.Integer, nullable=True) # Maps to campus_id in DB 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.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_name}-{self.code}" @property def campus_location(self): """Placeholder for campus location""" return self.building_rel.address if self.building_rel else None # Virtual field for compatibility - combines building and code @property def room_number(self): return self.code @room_number.setter def room_number(self, value): self.code = value # Override code property for consistency @property def code_display(self): 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.is_virtual: return '∞' return str(self.capacity) @property def is_unlimited(self): return self.capacity == 0 or self.is_virtual def to_dict(self): """Convert classroom to dictionary""" return { 'id': self.id, '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, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None, 'resources': [resource.to_dict() for resource in self.resources] if hasattr(self, 'resources') else [] } def get_full_location(self): """Get full classroom 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): """Find the best suitable classroom for a commission based on capacity and availability""" from app.models.reservation import Reservation, ReservationStatus # Get all active classrooms with sufficient capacity suitable_classrooms = Classroom.query.filter( Classroom.is_active == True, Classroom.capacity >= max_students ).order_by(Classroom.capacity.asc()).all() # Start with smallest suitable # Check availability for each classroom available_classrooms = [] for classroom in suitable_classrooms: # Check for conflicts conflicts = Reservation.find_conflicts( classroom.id, start_time, end_time, exclude_reservation_id ) if not conflicts: # Calculate efficiency score (how well the capacity matches) efficiency = classroom.capacity / max_students if max_students > 0 else float('inf') available_classrooms.append({ 'classroom': classroom, 'efficiency': efficiency, 'wasted_capacity': classroom.capacity - max_students }) # Sort by efficiency (closest capacity match first) available_classrooms.sort(key=lambda x: x['efficiency']) return available_classrooms @staticmethod def get_availability_status(start_time, end_time, exclude_reservation_id=None): """Get all classrooms classified as available or occupied for a time slot""" from app.models.reservation import Reservation, ReservationStatus all_classrooms = Classroom.query.filter_by(is_active=True).all() available = [] occupied = [] for classroom in all_classrooms: conflicts = Reservation.find_conflicts( classroom.id, start_time, end_time, exclude_reservation_id ) classroom_info = { 'classroom': classroom, 'conflicts': conflicts } if conflicts: occupied.append(classroom_info) else: available.append(classroom_info) return { 'available': available, 'occupied': occupied, 'total_available': len(available), 'total_occupied': len(occupied) } def __repr__(self): return f'' class ClassroomResource(db.Model): __tablename__ = 'classroom_resources' id = db.Column(db.Integer, primary_key=True) classroom_id = db.Column(db.Integer, db.ForeignKey('classrooms.id'), nullable=False) resource_type = db.Column(db.String(50), nullable=False) # PROJECTOR, WHITEBOARD, COMPUTER, etc. brand = db.Column(db.String(100)) model = db.Column(db.String(100)) specifications = db.Column(db.Text) functional = db.Column(db.Boolean, default=True) last_maintenance_date = db.Column(db.DateTime) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Relationships classroom = db.relationship('Classroom', backref=db.backref('resources', lazy=True, cascade='all, delete-orphan')) def to_dict(self): """Convert resource to dictionary""" return { 'id': self.id, 'classroom_id': self.classroom_id, 'resource_type': self.resource_type, 'brand': self.brand, 'model': self.model, 'specifications': self.specifications, 'functional': self.functional, 'last_maintenance_date': self.last_maintenance_date.isoformat() if self.last_maintenance_date else None, 'created_at': self.created_at.isoformat() if self.created_at else None } def __repr__(self): return f''