from typing import Optional, List, Dict, Any import re from sqlalchemy import func from app.models.classroom import Classroom from app.models.building import Building from app.repositories.classroom_repository import ClassroomRepository from app.schemas.classroom_dto import ClassroomCreateDTO, ClassroomUpdateDTO from app import db class ClassroomService: """Capa de servicios para la lógica de negocio y validación de aulas.""" def __init__(self, repository: Optional[ClassroomRepository] = None): self.repository = repository or ClassroomRepository() def get_classroom(self, classroom_id: int) -> Optional[Classroom]: """Obtiene un aula por su identificador.""" return self.repository.get_by_id(classroom_id) def list_classrooms(self, building_id: Optional[int] = None, floor: Optional[str] = None, only_virtual: bool = False, only_physical: bool = False) -> List[Classroom]: """Lista aulas filtradas según criterios de ubicación o modalidad.""" return self.repository.get_active_classrooms( building_id=building_id, floor=floor, only_virtual=only_virtual, only_physical=only_physical ) def create_classroom(self, dto: ClassroomCreateDTO) -> Classroom: """ Crea una nueva aula aplicando reglas de negocio: - Resolución o creación dinámica del edificio. - Unicidad de código por piso y edificio. - Capacidad adaptativa para aulas virtuales. """ building = None if dto.building_id: building = Building.query.get(dto.building_id) if not building: raise ValueError(f"El edificio con ID {dto.building_id} no existe.") elif dto.building_name: b_name = dto.building_name.strip() building = Building.query.filter(func.lower(Building.name) == func.lower(b_name)).first() if not building: code_slug = re.sub(r'[^a-zA-Z0-9]+', '_', b_name.upper()).strip('_') building = Building(name=b_name, code=code_slug, is_active=True) db.session.add(building) db.session.flush() else: raise ValueError("Debe especificar un edificio válido o el nombre de un nuevo edificio.") # Verificar unicidad existing = self.repository.get_by_code_and_building(building.id, dto.code, dto.floor) if existing: raise ValueError(f"Ya existe un aula con el código '{dto.code}' en el piso {dto.floor} de {building.name}.") capacity = dto.capacity is_virtual = ('VIRTUAL' in dto.code.upper()) or ('VIRTUAL' in building.name.upper()) if is_virtual and capacity < 999: capacity = 9999 classroom = Classroom( building_id=building.id, building=building.name, code=dto.code, floor=dto.floor, capacity=capacity, description=dto.description, is_active=dto.is_active ) return self.repository.save(classroom) def update_classroom(self, classroom_id: int, dto: ClassroomUpdateDTO) -> Classroom: """Actualiza atributos de un aula existente.""" classroom = self.repository.get_by_id(classroom_id) if not classroom: raise ValueError(f"Aula con ID {classroom_id} no encontrada.") building_id = dto.building_id if dto.building_id is not None else classroom.building_id code = dto.code.strip() if dto.code is not None else classroom.code floor = dto.floor.strip() if dto.floor is not None else classroom.floor # Si cambia código, piso o edificio, validar colisión if (building_id != classroom.building_id or code != classroom.code or floor != classroom.floor): existing = self.repository.get_by_code_and_building(building_id, code, floor) if existing and existing.id != classroom.id: raise ValueError(f"Ya existe otra aula con el código '{code}' en el piso {floor}.") if dto.building_id is not None: building = Building.query.get(dto.building_id) if building: classroom.building_id = building.id classroom.building = building.name if dto.code is not None: classroom.code = code if dto.floor is not None: classroom.floor = floor if dto.capacity is not None: classroom.capacity = dto.capacity if dto.description is not None: classroom.description = dto.description if dto.is_active is not None: classroom.is_active = dto.is_active return self.repository.save(classroom) def delete_classroom(self, classroom_id: int) -> bool: """Elimina o desactiva un aula verificando dependencias.""" classroom = self.repository.get_by_id(classroom_id) if not classroom: return False # Si tiene reservas históricas, se desactiva lógicamente para preservar integridad if classroom.reservations: classroom.is_active = False self.repository.save(classroom) return True return self.repository.delete(classroom)