feat: acople de schedule.view_reservation al BFF y creacion/asociacion de comisiones a materias

This commit is contained in:
2026-09-19 10:23:50 -03:00
parent 1080acd43a
commit 500aeeb9df
12 changed files with 597 additions and 55 deletions
+66 -6
View File
@@ -103,27 +103,66 @@ def list_reservations():
@api_reservations_bp.route('/<int:reservation_id>', methods=['GET'])
@jwt_required
def get_reservation(reservation_id: int):
"""Obtiene el detalle de una reserva específica."""
"""Obtiene el detalle de una reserva específica con sus relaciones completas."""
r = reservation_service.get_reservation(reservation_id)
if not r:
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
duration_min = int((r.end_time - r.start_time).total_seconds() / 60) if (r.start_time and r.end_time) else 120
is_virt = bool(r.is_virtual or (r.classroom and r.classroom.is_virtual))
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,
'location_display': getattr(r.classroom, 'location_display', r.classroom.code if r.classroom else None),
'classroom': {
'id': r.classroom.id,
'code': r.classroom.code,
'code_display': getattr(r.classroom, 'code_display', r.classroom.code),
'building_name': getattr(r.classroom, 'building_name', getattr(r.classroom, 'building', 'Edificio Central')),
'floor': r.classroom.floor,
'floor_display': getattr(r.classroom, 'floor_display', f"Piso {r.classroom.floor}"),
'capacity': r.classroom.capacity,
'capacity_display': getattr(r.classroom, 'capacity_display', str(r.classroom.capacity)),
'description': getattr(r.classroom, 'description', '') or '',
'is_virtual': getattr(r.classroom, 'is_virtual', False)
} if r.classroom else None,
'commission_id': r.commission_id,
'commission': {
'id': r.commission.id,
'code': r.commission.code,
'full_code': r.commission.get_full_code() if hasattr(r.commission, 'get_full_code') else r.commission.code,
'virtual_link': r.commission.virtual_link,
'max_students': r.commission.max_students,
'current_students': getattr(r.commission, 'current_students', 0),
'subject': {
'id': r.commission.subject.id,
'name': r.commission.subject.name,
'code': r.commission.subject.code
} if (r.commission and r.commission.subject) else None
} if r.commission else None,
'teacher_id': r.user_id,
'teacher_name': r.user.name if r.user else None,
'user': {
'id': r.user.id,
'name': r.user.name,
'first_name': getattr(r.user, 'first_name', r.user.name.split()[0] if r.user.name else 'Usuario'),
'last_name': getattr(r.user, 'last_name', ' '.join(r.user.name.split()[1:]) if r.user.name and len(r.user.name.split()) > 1 else ''),
'email': r.user.email
} if r.user else None,
'start_time': r.start_time.isoformat(),
'end_time': r.end_time.isoformat(),
'duration': duration_min,
'purpose': r.purpose,
'status': str(r.status),
'shift': r.shift,
'status': str(r.status.value if hasattr(r.status, 'value') else r.status),
'shift': r.shift or 'Mañana',
'virtual_link': r.virtual_link,
'expected_attendees': r.expected_attendees,
'notes': r.notes
'effective_virtual_link': r.effective_virtual_link,
'is_virtual': is_virt,
'expected_attendees': r.expected_attendees or 30,
'notes': r.notes or '',
'can_be_cancelled': r.can_be_cancelled() if hasattr(r, 'can_be_cancelled') else True
}), 200
@api_reservations_bp.route('', methods=['POST'])
@@ -185,3 +224,24 @@ def cancel_reservation(reservation_id: int):
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
return jsonify({'message': f'Reserva {reservation_id} cancelada correctamente.'}), 200
@api_reservations_bp.route('/<int:reservation_id>/cancel', methods=['POST'])
@jwt_required
def cancel_reservation_post(reservation_id: int):
"""Cancela una reserva (endpoint POST compatible con formularios)."""
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
@api_reservations_bp.route('/<int:reservation_id>/confirm', methods=['POST'])
@jwt_required
def confirm_reservation(reservation_id: int):
"""Confirma una reserva cambiando su estado a CONFIRMED."""
confirmed = reservation_service.confirm_reservation(reservation_id)
if not confirmed:
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
return jsonify({'message': f'Reserva {reservation_id} confirmada correctamente.'}), 200