diff --git a/src/main/java/com/classes/controllers/ClassController.java b/src/main/java/com/classes/controllers/ClassController.java index 54aaac1..11304ac 100644 --- a/src/main/java/com/classes/controllers/ClassController.java +++ b/src/main/java/com/classes/controllers/ClassController.java @@ -77,4 +77,31 @@ public ResponseEntity getMonthlyCalendar( MonthlyCalendarDTO calendar = classService.getMonthlyCalendar(year, month); return ResponseEntity.ok(calendar); } + + @Operation(summary = "Iniciar clase", description = "Cambia el estado de la clase de PROGRAMADA a EN_PROCESO") + @PatchMapping("/{id}/start") + @PreAuthorize("hasAnyRole('ADMIN', 'TRAINER')") + public ResponseEntity startClass(@PathVariable UUID id) { + log.info("Iniciando clase: {}", id); + ClassResponse response = classService.startClass(id); + return ResponseEntity.ok(response); + } + + @Operation(summary = "Completar clase", description = "Cambia el estado de la clase de EN_PROCESO a COMPLETADA") + @PatchMapping("/{id}/complete") + @PreAuthorize("hasAnyRole('ADMIN', 'TRAINER')") + public ResponseEntity completeClass(@PathVariable UUID id) { + log.info("Completando clase: {}", id); + ClassResponse response = classService.completeClass(id); + return ResponseEntity.ok(response); + } + + @Operation(summary = "Cancelar clase", description = "Cambia el estado de la clase a CANCELADA") + @PatchMapping("/{id}/cancel") + @PreAuthorize("hasAnyRole('ADMIN', 'TRAINER')") + public ResponseEntity cancelClass(@PathVariable UUID id) { + log.info("Cancelando clase: {}", id); + ClassResponse response = classService.cancelClass(id); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/classes/controllers/ClassReservationController.java b/src/main/java/com/classes/controllers/ClassReservationController.java index d9f58bb..672a287 100644 --- a/src/main/java/com/classes/controllers/ClassReservationController.java +++ b/src/main/java/com/classes/controllers/ClassReservationController.java @@ -32,7 +32,7 @@ public ResponseEntity reserveClass( @RequestBody ClassReservationRequest request, Authentication authentication) { UUID memberId = authorizationService.getUserId(authentication); - log.info("🎟️ Usuario {} intentando reservar clase {}", memberId, request.getClassId()); + log.info("Usuario {} intentando reservar clase {}", memberId, request.getClassId()); ClassReservationResponse response = reservationService.reserveClass(request, memberId); return ResponseEntity.ok(response); } @@ -45,7 +45,7 @@ public ResponseEntity cancelReservation( @PathVariable UUID reservationId, Authentication authentication) { UUID memberId = authorizationService.getUserId(authentication); - log.info("❌ Usuario {} cancelando reserva {}", memberId, reservationId); + log.info("Usuario {} cancelando reserva {}", memberId, reservationId); reservationService.cancelReservation(reservationId, memberId); return ResponseEntity.noContent().build(); } @@ -59,7 +59,7 @@ public ResponseEntity confirmAttendance( Authentication authentication) { UUID memberId = authorizationService.getUserId(authentication); - log.info("✅ Usuario {} confirmando asistencia para reserva {}", memberId, reservationId); + log.info("Usuario {} confirmando asistencia para reserva {}", memberId, reservationId); reservationService.confirmAttendance(reservationId, memberId); return ResponseEntity.noContent().build(); @@ -73,7 +73,7 @@ public ResponseEntity completeReservation( Authentication authentication) { UUID memberId = authorizationService.getUserId(authentication); - log.info("🏁 Usuario {} completando reserva {}", memberId, reservationId); + log.info("Usuario {} completando reserva {}", memberId, reservationId); reservationService.completeReservation(reservationId, memberId); return ResponseEntity.noContent().build(); @@ -87,7 +87,7 @@ public ResponseEntity> getMyReservations( Authentication authentication, @RequestParam(required = false) Boolean completed) { UUID memberId = authorizationService.getUserId(authentication); - log.info("📋 Usuario {} consultando sus reservas (completed={})", memberId, completed); + log.info("Usuario {} consultando sus reservas (completed={})", memberId, completed); List reservations = reservationService.getReservationsByMember(memberId, completed); if (reservations.isEmpty()) { return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/classes/controllers/ClassViewController.java b/src/main/java/com/classes/controllers/ClassViewController.java index 61574f4..898ee9b 100644 --- a/src/main/java/com/classes/controllers/ClassViewController.java +++ b/src/main/java/com/classes/controllers/ClassViewController.java @@ -58,33 +58,34 @@ public ResponseEntity getClassDetail(@PathVariable UUID id) return ResponseEntity.ok(detail); } - @Operation(summary = "Ver calendario de clases", description = "Vista de calendario para todos") + @Operation(summary = "Ver calendario de clases por rango de fechas", description = "Vista de calendario para todos con filtros opcionales. Requiere startDate y endDate.") @GetMapping("/calendar") - @PreAuthorize("hasRole('USER') or hasRole('TRAINER') or hasRole('ADMIN')") + @PreAuthorize("hasRole('TRAINER') or hasRole('ADMIN')") public ResponseEntity> getClassesForCalendar( - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate, + @RequestParam(required = false) String status, + @RequestParam(required = false) UUID locationId) { - log.info("📅 Consultando clases para calendario"); + log.info("📅 Consultando clases para calendario entre {} y {} con filtros - Estado: {}, Ubicación: {}", + startDate, endDate, status, locationId); - if (startDate != null && endDate != null) { - List classes = classStatsService.getClassesForCalendar(startDate, endDate); - return ResponseEntity.ok(classes); - } else { - List classes = classStatsService.getUpcomingClasses(); - return ResponseEntity.ok(classes); - } + List classes = classStatsService.getClassesForCalendar(startDate, endDate, status, locationId); + return ResponseEntity.ok(classes); } - @Operation(summary = "Ver próximas clases", description = "Lista de clases futuras") + @Operation(summary = "Ver próximas clases", description = "Lista de clases futuras con filtros opcionales") @GetMapping("/upcoming") - @PreAuthorize("hasRole('USER') or hasRole('TRAINER') or hasRole('ADMIN')") - public ResponseEntity> getUpcomingClasses() { - log.info("Consultando próximas clases"); - List classes = classStatsService.getUpcomingClasses(); + @PreAuthorize("hasRole('TRAINER') or hasRole('ADMIN')") + public ResponseEntity> getUpcomingClasses( + @RequestParam(required = false) String status, + @RequestParam(required = false) UUID locationId) { + log.info("Consultando próximas clases con filtros - Estado: {}, Ubicación: {}", status, locationId); + List classes = classStatsService.getUpcomingClasses(status, locationId); return ResponseEntity.ok(classes); } + @Operation(summary = "Ver calendario mensual", description = "Vista de calendario por mes con clases agrupadas por fecha") @GetMapping("/calendar/monthly") @PreAuthorize("hasRole('USER') or hasRole('TRAINER') or hasRole('ADMIN')") diff --git a/src/main/java/com/classes/dtos/Class/CalendarClassDTO.java b/src/main/java/com/classes/dtos/Class/CalendarClassDTO.java index b4f1998..0ab73ec 100644 --- a/src/main/java/com/classes/dtos/Class/CalendarClassDTO.java +++ b/src/main/java/com/classes/dtos/Class/CalendarClassDTO.java @@ -33,4 +33,5 @@ public class CalendarClassDTO { private int currentStudents; private int maxCapacity; private String action; // "Reservar", "Lista de espera", "Llena" + private String status; // "PROGRAMADA", "EN_PROCESO", "COMPLETADA", "CANCELADA" } diff --git a/src/main/java/com/classes/dtos/Class/ClassDetailResponse.java b/src/main/java/com/classes/dtos/Class/ClassDetailResponse.java index b0f45a7..31aef59 100644 --- a/src/main/java/com/classes/dtos/Class/ClassDetailResponse.java +++ b/src/main/java/com/classes/dtos/Class/ClassDetailResponse.java @@ -35,5 +35,6 @@ public class ClassDetailResponse { private String schedule; private boolean active; + private String status; private List students; } diff --git a/src/main/java/com/classes/dtos/Class/ClassResponse.java b/src/main/java/com/classes/dtos/Class/ClassResponse.java index baf76a4..7582eb8 100644 --- a/src/main/java/com/classes/dtos/Class/ClassResponse.java +++ b/src/main/java/com/classes/dtos/Class/ClassResponse.java @@ -25,4 +25,5 @@ public class ClassResponse { private String schedule; private boolean active; private String description; + private String status; } \ No newline at end of file diff --git a/src/main/java/com/classes/dtos/Dashboard/TrainerDashboardDTO.java b/src/main/java/com/classes/dtos/Dashboard/TrainerDashboardDTO.java index b826e85..bfa2111 100644 --- a/src/main/java/com/classes/dtos/Dashboard/TrainerDashboardDTO.java +++ b/src/main/java/com/classes/dtos/Dashboard/TrainerDashboardDTO.java @@ -12,8 +12,11 @@ @NoArgsConstructor @Builder public class TrainerDashboardDTO { - private int totalStudents; + private int totalClasses; // Total de clases asignadas al trainer + private int completedClasses; // Clases con status COMPLETADA + private int totalStudents; // Total de estudiantes únicos en todas las clases private double averageAttendance; // Porcentaje promedio + private int upcomingClasses; // Próximas clases (futuras) private int classesThisMonth; private double attendanceChange; // Cambio porcentual respecto al mes anterior private List studentTrends; // Tendencia semanal de estudiantes diff --git a/src/main/java/com/classes/entities/ClassEntity.java b/src/main/java/com/classes/entities/ClassEntity.java index a72e817..702a09b 100644 --- a/src/main/java/com/classes/entities/ClassEntity.java +++ b/src/main/java/com/classes/entities/ClassEntity.java @@ -2,6 +2,7 @@ import com.classes.config.audit.Audit; import com.classes.config.audit.AuditListener; +import com.classes.enums.ClassStatus; import jakarta.persistence.*; import lombok.*; @@ -31,6 +32,11 @@ public class ClassEntity { private LocalTime endTime; private boolean active; private String description; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, columnDefinition = "varchar(255) default 'PROGRAMADA'") + @Builder.Default + private ClassStatus status = ClassStatus.PROGRAMADA; @ManyToOne @JoinColumn(name = "location_id") diff --git a/src/main/java/com/classes/enums/ClassStatus.java b/src/main/java/com/classes/enums/ClassStatus.java new file mode 100644 index 0000000..8c773a5 --- /dev/null +++ b/src/main/java/com/classes/enums/ClassStatus.java @@ -0,0 +1,8 @@ +package com.classes.enums; + +public enum ClassStatus { + PROGRAMADA, // Clase creada y programada, esperando inicio + EN_PROCESO, // Clase iniciada por el trainer, en curso + COMPLETADA, // Clase finalizada + CANCELADA // Clase cancelada por admin o trainer +} diff --git a/src/main/java/com/classes/mappers/ClassMapper.java b/src/main/java/com/classes/mappers/ClassMapper.java index 665bd8d..4acd731 100644 --- a/src/main/java/com/classes/mappers/ClassMapper.java +++ b/src/main/java/com/classes/mappers/ClassMapper.java @@ -24,6 +24,7 @@ public interface ClassMapper { @Mapping(target = "trainer", ignore = true) @Mapping(target = "reservations", ignore = true) @Mapping(target = "audit", ignore = true) + @Mapping(target = "status", ignore = true) // se usa el valor por defecto PROGRAMADA ClassEntity toEntity(ClassRequest request); /** @@ -32,6 +33,7 @@ public interface ClassMapper { @Mapping(target = "locationName", source = "location.name") @Mapping(target = "trainerName", expression = "java(entity.getTrainer().getFirstName() + \" \" + entity.getTrainer().getLastName())") @Mapping(target = "schedule", expression = "java(entity.getStartTime() + \" - \" + entity.getEndTime())") + @Mapping(target = "status", expression = "java(entity.getStatus().name())") ClassResponse toResponse(ClassEntity entity); /** @@ -48,5 +50,6 @@ public interface ClassMapper { @Mapping(target = "location", ignore = true) @Mapping(target = "reservations", ignore = true) @Mapping(target = "audit", ignore = true) + @Mapping(target = "status", ignore = true) // el status se cambia con endpoints específicos void updateFromRequest(ClassRequest request, @MappingTarget ClassEntity entity); } diff --git a/src/main/java/com/classes/mappers/ClassStatsMapper.java b/src/main/java/com/classes/mappers/ClassStatsMapper.java index 64b8464..439611d 100644 --- a/src/main/java/com/classes/mappers/ClassStatsMapper.java +++ b/src/main/java/com/classes/mappers/ClassStatsMapper.java @@ -32,6 +32,7 @@ public interface ClassStatsMapper { @Mapping(target = "schedule", expression = "java(formatScheduleWithDay(entity))") @Mapping(target = "currentStudents", ignore = true) // se calcula en servicio @Mapping(target = "students", ignore = true) // se calcula en servicio + @Mapping(target = "status", expression = "java(entity.getStatus().name())") ClassDetailResponse toClassDetailResponse(ClassEntity entity); @@ -40,6 +41,7 @@ public interface ClassStatsMapper { @Mapping(target = "schedule", expression = "java(entity.getStartTime() + \" - \" + entity.getEndTime())") @Mapping(target = "currentStudents", ignore = true) // se calcula en servicio @Mapping(target = "action", ignore = true) // se calcula en servicio + @Mapping(target = "status", expression = "java(entity.getStatus().name())") CalendarClassDTO toCalendarDTO(ClassEntity entity); List toCalendarDTOList(List entities); diff --git a/src/main/java/com/classes/repositories/ClassRepository.java b/src/main/java/com/classes/repositories/ClassRepository.java index 434fe13..cf502b8 100644 --- a/src/main/java/com/classes/repositories/ClassRepository.java +++ b/src/main/java/com/classes/repositories/ClassRepository.java @@ -36,4 +36,10 @@ Page findByClassNameContainingIgnoreCaseOrTrainerFirstNameContainin @Query("SELECT COUNT(c) FROM ClassEntity c WHERE c.trainer.id = :trainerId AND YEAR(c.classDate) = :year AND MONTH(c.classDate) = :month") long countByTrainerIdAndMonth(@Param("trainerId") UUID trainerId, @Param("year") int year, @Param("month") int month); + + @Query("SELECT COUNT(c) FROM ClassEntity c WHERE c.trainer.id = :trainerId AND c.status = com.classes.enums.ClassStatus.COMPLETADA") + long countCompletedClassesByTrainerId(@Param("trainerId") UUID trainerId); + + @Query("SELECT COUNT(c) FROM ClassEntity c WHERE c.trainer.id = :trainerId AND c.classDate >= :currentDate") + long countUpcomingClassesByTrainerId(@Param("trainerId") UUID trainerId, @Param("currentDate") LocalDate currentDate); } diff --git a/src/main/java/com/classes/services/ClassService.java b/src/main/java/com/classes/services/ClassService.java index 64af71f..c4b463b 100644 --- a/src/main/java/com/classes/services/ClassService.java +++ b/src/main/java/com/classes/services/ClassService.java @@ -24,4 +24,10 @@ public interface ClassService { void deleteClass(UUID id); MonthlyCalendarDTO getMonthlyCalendar(int year, int month); + + ClassResponse startClass(UUID classId); + + ClassResponse completeClass(UUID classId); + + ClassResponse cancelClass(UUID classId); } diff --git a/src/main/java/com/classes/services/ClassStatsService.java b/src/main/java/com/classes/services/ClassStatsService.java index 7fae3a2..7dfdf87 100644 --- a/src/main/java/com/classes/services/ClassStatsService.java +++ b/src/main/java/com/classes/services/ClassStatsService.java @@ -16,7 +16,13 @@ public interface ClassStatsService { ClassDetailResponse getClassDetail(UUID classId); + // Métodos con filtros + List getClassesForCalendar(LocalDate startDate, LocalDate endDate, String status, UUID locationId); + + List getUpcomingClasses(String status, UUID locationId); + + // Métodos sin filtros (para uso interno y compatibilidad) List getClassesForCalendar(LocalDate startDate, LocalDate endDate); - + List getUpcomingClasses(); } diff --git a/src/main/java/com/classes/services/Impl/ClassServiceImpl.java b/src/main/java/com/classes/services/Impl/ClassServiceImpl.java index 6041ae1..6b56650 100644 --- a/src/main/java/com/classes/services/Impl/ClassServiceImpl.java +++ b/src/main/java/com/classes/services/Impl/ClassServiceImpl.java @@ -1,5 +1,6 @@ package com.classes.services.Impl; +import com.classes.enums.ClassStatus; import com.classes.services.ClassStatsService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -46,7 +47,7 @@ public class ClassServiceImpl implements ClassService { @Transactional @Override public ClassResponse createClass(ClassRequest request) { - validateTrainerAndLocation(request); + ClassEntity entity = classMapper.toEntity(request); TrainerEntity trainer = trainerRepository.findById(request.getTrainerId()) .orElseThrow(() -> new IllegalArgumentException("El trainer con ID " + request.getTrainerId() + " no existe")); @@ -112,15 +113,6 @@ public void deleteClass(UUID id) { repository.deleteById(id); } - private void validateTrainerAndLocation(ClassRequest request) { - if (!trainerRepository.existsById(request.getTrainerId())) { - throw new IllegalArgumentException("El trainer con ID " + request.getTrainerId() + " no existe"); - } - if (!locationRepository.existsById(request.getLocationId())) { - throw new IllegalArgumentException("La ubicación con ID " + request.getLocationId() + " no existe"); - } - } - @Override @Transactional(readOnly = true) public MonthlyCalendarDTO getMonthlyCalendar(int year, int month) { @@ -146,4 +138,62 @@ public MonthlyCalendarDTO getMonthlyCalendar(int year, int month) { .daysWithClasses(daysWithClasses) .build(); } + + @Override + @Transactional + public ClassResponse startClass(UUID classId) { + log.info("Iniciando clase con ID: {}", classId); + + ClassEntity classEntity = repository.findById(classId) + .orElseThrow(() -> new RuntimeException("Clase no encontrada")); + + if (classEntity.getStatus() != ClassStatus.PROGRAMADA) { + throw new RuntimeException("La clase debe estar en estado PROGRAMADA para poder iniciarla"); + } + + classEntity.setStatus(ClassStatus.EN_PROCESO); + ClassEntity saved = repository.save(classEntity); + + log.info("Clase {} cambiada a estado EN_PROCESO", classId); + return classMapper.toResponse(saved); + } + + @Override + @Transactional + public ClassResponse completeClass(UUID classId) { + log.info("Completando clase con ID: {}", classId); + + ClassEntity classEntity = repository.findById(classId) + .orElseThrow(() -> new RuntimeException("Clase no encontrada")); + + if (classEntity.getStatus() != ClassStatus.EN_PROCESO) { + throw new RuntimeException("La clase debe estar EN_PROCESO para poder completarla"); + } + + classEntity.setStatus(ClassStatus.COMPLETADA); + ClassEntity saved = repository.save(classEntity); + + log.info("Clase {} cambiada a estado COMPLETADA", classId); + return classMapper.toResponse(saved); + } + + @Override + @Transactional + public ClassResponse cancelClass(UUID classId) { + log.info("Cancelando clase con ID: {}", classId); + + ClassEntity classEntity = repository.findById(classId) + .orElseThrow(() -> new RuntimeException("Clase no encontrada")); + + if (classEntity.getStatus() == ClassStatus.COMPLETADA) { + throw new RuntimeException("No se puede cancelar una clase que ya fue completada"); + } + + classEntity.setStatus(ClassStatus.CANCELADA); + classEntity.setActive(false); + ClassEntity saved = repository.save(classEntity); + + log.info("Clase {} cambiada a estado CANCELADA", classId); + return classMapper.toResponse(saved); + } } diff --git a/src/main/java/com/classes/services/Impl/ClassStatsServiceImpl.java b/src/main/java/com/classes/services/Impl/ClassStatsServiceImpl.java index d0f2601..37858be 100644 --- a/src/main/java/com/classes/services/Impl/ClassStatsServiceImpl.java +++ b/src/main/java/com/classes/services/Impl/ClassStatsServiceImpl.java @@ -83,6 +83,26 @@ public ClassDetailResponse getClassDetail(UUID classId) { return response; } + @Override + @Transactional(readOnly = true) + public List getClassesForCalendar(LocalDate startDate, LocalDate endDate, String status, UUID locationId) { + log.info("Obteniendo clases para calendario entre {} y {} con filtros - Estado: {}, Ubicación: {}", + startDate, endDate, status, locationId); + + List classes = repository.findByClassDateBetween(startDate, endDate); + return mapToCalendarDTOs(classes, status, locationId); + } + + @Override + @Transactional(readOnly = true) + public List getUpcomingClasses(String status, UUID locationId) { + log.info("Obteniendo próximas clases con filtros - Estado: {}, Ubicación: {}", status, locationId); + + List classes = repository.findUpcomingClasses(LocalDate.now()); + return mapToCalendarDTOs(classes, status, locationId); + } + + // Métodos sobrecargados sin filtros (para uso interno) @Override @Transactional(readOnly = true) public List getClassesForCalendar(LocalDate startDate, LocalDate endDate) { @@ -144,16 +164,47 @@ private StudentInClassDTO buildStudentDTO(ClassReservation reservation, List mapToCalendarDTOs(List classes) { - return classes.stream().map(classEntity -> { - CalendarClassDTO dto = classStatsMapper.toCalendarDTO(classEntity); - long currentStudents = reservationRepository.countByClassEntityIdAndStatus( - classEntity.getId(), ReservationStatus.RESERVADO); - dto.setCurrentStudents((int) currentStudents); - String action = determineAction(classEntity, (int) currentStudents); - dto.setAction(action); - return dto; - }).collect(Collectors.toList()); + return classes.stream() + .map(classEntity -> { + CalendarClassDTO dto = classStatsMapper.toCalendarDTO(classEntity); + long currentStudents = reservationRepository.countByClassEntityIdAndStatus( + classEntity.getId(), ReservationStatus.RESERVADO); + dto.setCurrentStudents((int) currentStudents); + String action = determineAction(classEntity, (int) currentStudents); + dto.setAction(action); + return dto; + }).collect(Collectors.toList()); + } + + // Método con filtros (para endpoints con filtros) + private List mapToCalendarDTOs(List classes, String status, UUID locationId) { + return classes.stream() + .filter(classEntity -> { + // Filtrar por estado si se proporciona + if (status != null && !status.trim().isEmpty() && !status.equalsIgnoreCase("TODOS")) { + if (!classEntity.getStatus().name().equalsIgnoreCase(status)) { + return false; + } + } + // Filtrar por ubicación si se proporciona + if (locationId != null) { + if (!classEntity.getLocation().getId().equals(locationId)) { + return false; + } + } + return true; + }) + .map(classEntity -> { + CalendarClassDTO dto = classStatsMapper.toCalendarDTO(classEntity); + long currentStudents = reservationRepository.countByClassEntityIdAndStatus( + classEntity.getId(), ReservationStatus.RESERVADO); + dto.setCurrentStudents((int) currentStudents); + String action = determineAction(classEntity, (int) currentStudents); + dto.setAction(action); + return dto; + }).collect(Collectors.toList()); } private String determineClassStatus(ClassEntity classEntity, int currentStudents) { diff --git a/src/main/java/com/classes/services/Impl/TrainerDashboardServiceImpl.java b/src/main/java/com/classes/services/Impl/TrainerDashboardServiceImpl.java index 8667a71..fa6863c 100644 --- a/src/main/java/com/classes/services/Impl/TrainerDashboardServiceImpl.java +++ b/src/main/java/com/classes/services/Impl/TrainerDashboardServiceImpl.java @@ -29,7 +29,18 @@ public class TrainerDashboardServiceImpl implements TrainerDashboardService { @Transactional(readOnly = true) public TrainerDashboardDTO getDashboardForTrainer(UUID trainerId) { log.info("📊 Generando dashboard para el trainer {}", trainerId); + + // 🔹 Total de clases asignadas al trainer List trainerClasses = classRepository.findByTrainerId(trainerId); + int totalClasses = trainerClasses.size(); + + // 🔹 Clases completadas (status = COMPLETADA) + long completedClasses = classRepository.countCompletedClassesByTrainerId(trainerId); + + // 🔹 Próximas clases (futuras) + long upcomingClasses = classRepository.countUpcomingClassesByTrainerId(trainerId, LocalDate.now()); + + // 🔹 Total de estudiantes únicos en todas las clases del trainer Set uniqueStudents = new HashSet<>(); for (ClassEntity classEntity : trainerClasses) { List reservations = reservationRepository.findByClassEntityId(classEntity.getId()); @@ -56,8 +67,11 @@ public TrainerDashboardDTO getDashboardForTrainer(UUID trainerId) { List studentTrends = calculateStudentTrends(trainerClasses); return TrainerDashboardDTO.builder() + .totalClasses(totalClasses) + .completedClasses((int) completedClasses) .totalStudents(totalStudents) .averageAttendance(averageAttendance) + .upcomingClasses((int) upcomingClasses) .classesThisMonth((int) classesThisMonth) .attendanceChange(attendanceChange) .studentTrends(studentTrends)