Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/main/java/com/classes/controllers/ClassController.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,31 @@ public ResponseEntity<MonthlyCalendarDTO> 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<ClassResponse> 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<ClassResponse> 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<ClassResponse> cancelClass(@PathVariable UUID id) {
log.info("Cancelando clase: {}", id);
ClassResponse response = classService.cancelClass(id);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public ResponseEntity<ClassReservationResponse> 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);
}
Expand All @@ -45,7 +45,7 @@ public ResponseEntity<Void> 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();
}
Expand All @@ -59,7 +59,7 @@ public ResponseEntity<Void> 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();
Expand All @@ -73,7 +73,7 @@ public ResponseEntity<Void> 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();
Expand All @@ -87,7 +87,7 @@ public ResponseEntity<List<ClassReservationResponse>> 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<ClassReservationResponse> reservations = reservationService.getReservationsByMember(memberId, completed);
if (reservations.isEmpty()) {
return ResponseEntity.noContent().build();
Expand Down
35 changes: 18 additions & 17 deletions src/main/java/com/classes/controllers/ClassViewController.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,33 +58,34 @@ public ResponseEntity<ClassDetailResponse> 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<List<CalendarClassDTO>> 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<CalendarClassDTO> classes = classStatsService.getClassesForCalendar(startDate, endDate);
return ResponseEntity.ok(classes);
} else {
List<CalendarClassDTO> classes = classStatsService.getUpcomingClasses();
return ResponseEntity.ok(classes);
}
List<CalendarClassDTO> 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<List<CalendarClassDTO>> getUpcomingClasses() {
log.info("Consultando próximas clases");
List<CalendarClassDTO> classes = classStatsService.getUpcomingClasses();
@PreAuthorize("hasRole('TRAINER') or hasRole('ADMIN')")
public ResponseEntity<List<CalendarClassDTO>> 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<CalendarClassDTO> 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')")
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/classes/dtos/Class/CalendarClassDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ public class ClassDetailResponse {

private String schedule;
private boolean active;
private String status;
private List<StudentInClassDTO> students;
}
1 change: 1 addition & 0 deletions src/main/java/com/classes/dtos/Class/ClassResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ public class ClassResponse {
private String schedule;
private boolean active;
private String description;
private String status;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<StudentTrendDTO> studentTrends; // Tendencia semanal de estudiantes
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/classes/entities/ClassEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand Down Expand Up @@ -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")
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/classes/enums/ClassStatus.java
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions src/main/java/com/classes/mappers/ClassMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/**
Expand All @@ -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);

/**
Expand All @@ -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);
}
2 changes: 2 additions & 0 deletions src/main/java/com/classes/mappers/ClassStatsMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);


Expand All @@ -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<CalendarClassDTO> toCalendarDTOList(List<ClassEntity> entities);
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/classes/repositories/ClassRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ Page<ClassEntity> 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);
}
6 changes: 6 additions & 0 deletions src/main/java/com/classes/services/ClassService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
8 changes: 7 additions & 1 deletion src/main/java/com/classes/services/ClassStatsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ public interface ClassStatsService {

ClassDetailResponse getClassDetail(UUID classId);

// Métodos con filtros
List<CalendarClassDTO> getClassesForCalendar(LocalDate startDate, LocalDate endDate, String status, UUID locationId);

List<CalendarClassDTO> getUpcomingClasses(String status, UUID locationId);

// Métodos sin filtros (para uso interno y compatibilidad)
List<CalendarClassDTO> getClassesForCalendar(LocalDate startDate, LocalDate endDate);

List<CalendarClassDTO> getUpcomingClasses();
}
70 changes: 60 additions & 10 deletions src/main/java/com/classes/services/Impl/ClassServiceImpl.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}
Loading