from flask import Blueprint, request, jsonify, g from datetime import datetime, timedelta, date from pydantic import ValidationError from app.services.reservation_service import ReservationService from app.schemas.reservation_dto import ReservationCreateDTO, ReservationUpdateDTO from app.utils.jwt_decorators import jwt_required, jwt_role_required api_reservations_bp = Blueprint('api_reservations', __name__, url_prefix='/api/v1/reservations') reservation_service = ReservationService() @api_reservations_bp.route('', methods=['GET']) @jwt_required def list_reservations(): """Consulta de reservas por rango de fechas y/o aula.""" start_str = request.args.get('start_date') end_str = request.args.get('end_date') classroom_id = request.args.get('classroom_id', type=int) now = datetime.utcnow() try: start_dt = datetime.strptime(start_str, '%Y-%m-%d') if start_str else now.replace(hour=0, minute=0, second=0) except ValueError: return jsonify({'error': 'BadRequest', 'message': 'Formato inválido de start_date (debe ser YYYY-MM-DD).'}), 400 try: end_dt = datetime.strptime(end_str, '%Y-%m-%d').replace(hour=23, minute=59, second=59) if end_str else start_dt + timedelta(days=7) except ValueError: return jsonify({'error': 'BadRequest', 'message': 'Formato inválido de end_date (debe ser YYYY-MM-DD).'}), 400 reservations = reservation_service.list_reservations(start_dt, end_dt, classroom_id=classroom_id) result = [] for r in reservations: result.append({ 'id': r.id, 'classroom_id': r.classroom_id, 'classroom_name': r.classroom.code if r.classroom else None, 'commission_id': r.commission_id, 'teacher_name': r.user.name if r.user else None, 'start_time': r.start_time.isoformat(), 'end_time': r.end_time.isoformat(), 'purpose': r.purpose, 'status': str(r.status), 'shift': r.shift, 'virtual_link': r.virtual_link, 'expected_attendees': r.expected_attendees }) return jsonify({ 'total': len(result), 'start_date': start_dt.strftime('%Y-%m-%d'), 'end_date': end_dt.strftime('%Y-%m-%d'), 'reservations': result }), 200 @api_reservations_bp.route('/', methods=['GET']) @jwt_required def get_reservation(reservation_id: int): """Obtiene el detalle de una reserva específica.""" r = reservation_service.get_reservation(reservation_id) if not r: return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404 return jsonify({ 'id': r.id, 'classroom_id': r.classroom_id, 'classroom_name': r.classroom.code if r.classroom else None, 'location_display': r.classroom.location_display if r.classroom else None, 'commission_id': r.commission_id, 'teacher_id': r.user_id, 'teacher_name': r.user.name if r.user else None, 'start_time': r.start_time.isoformat(), 'end_time': r.end_time.isoformat(), 'purpose': r.purpose, 'status': str(r.status), 'shift': r.shift, 'virtual_link': r.virtual_link, 'expected_attendees': r.expected_attendees, 'notes': r.notes }), 200 @api_reservations_bp.route('', methods=['POST']) @jwt_required def create_reservation(): """Crea una reserva aplicando validación DTO y detección de colisiones.""" data = request.get_json(silent=True) or {} # Inyectar usuario autenticado si no se especifica if 'user_id' not in data: data['user_id'] = g.jwt_user.id try: dto = ReservationCreateDTO(**data) except ValidationError as e: return jsonify({'error': 'ValidationError', 'details': e.errors()}), 400 try: reservation = reservation_service.create_reservation(dto) return jsonify({ 'message': 'Reserva creada exitosamente.', 'id': reservation.id, 'classroom_id': reservation.classroom_id, 'start_time': reservation.start_time.isoformat(), 'end_time': reservation.end_time.isoformat(), 'status': str(reservation.status) }), 201 except ValueError as e: return jsonify({'error': 'ConflictOrValidation', 'message': str(e)}), 409 @api_reservations_bp.route('/', methods=['PUT']) @jwt_required def update_reservation(reservation_id: int): """Modifica una reserva existente.""" data = request.get_json(silent=True) or {} try: dto = ReservationUpdateDTO(**data) except ValidationError as e: return jsonify({'error': 'ValidationError', 'details': e.errors()}), 400 try: reservation = reservation_service.update_reservation(reservation_id, dto) return jsonify({ 'message': 'Reserva actualizada exitosamente.', 'id': reservation.id, 'start_time': reservation.start_time.isoformat(), 'end_time': reservation.end_time.isoformat(), 'status': str(reservation.status) }), 200 except ValueError as e: return jsonify({'error': 'ConflictOrValidation', 'message': str(e)}), 409 @api_reservations_bp.route('/', methods=['DELETE']) @jwt_required def cancel_reservation(reservation_id: int): """Cancela una reserva.""" cancelled = reservation_service.cancel_reservation(reservation_id) if not cancelled: return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404 return jsonify({'message': f'Reserva {reservation_id} cancelada correctamente.'}), 200