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
200 changes: 200 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Microservicio de Gestión de Clases - FitDesk

## 📋 Descripción

Microservicio para la gestión de clases de gimnasio, incluyendo reservas, asistencia, estadísticas y dashboards para trainers y miembros.

## 🚀 Nuevas Funcionalidades Implementadas

### 1. **Vista de Gestión de Clases con Estadísticas**
- Endpoint: `GET /classes/my-classes/stats`
- Rol requerido: `TRAINER` o `ADMIN`
- Retorna lista de clases con:
- Estudiantes actuales vs capacidad máxima
- Porcentaje de asistencia promedio
- Estado de la clase (Activa, Llena, Cancelada)
- Información del trainer y ubicación

### 2. **Dashboard del Trainer**
- Endpoint: `GET /dashboard/trainer`
- Rol requerido: `TRAINER` o `ADMIN`
- Métricas incluidas:
- Total de estudiantes únicos
- Porcentaje de asistencia promedio
- Clases impartidas este mes
- Cambio porcentual respecto al mes anterior
- Tendencia semanal de estudiantes activos/inactivos

### 3. **Detalle de Clase con Estudiantes**
- Endpoint: `GET /classes/{id}/detail`
- Rol requerido: `TRAINER` o `ADMIN`
- Información detallada:
- Datos completos de la clase
- Lista de estudiantes inscritos con:
- Información del miembro (integración con msvc-members)
- Porcentaje de asistencia individual
- Total de clases del estudiante
- Estado de membresía
- Último acceso

### 4. **Calendario de Clases**
- Endpoint: `GET /classes/calendar?startDate=2025-01-01&endDate=2025-01-31`
- Endpoint: `GET /classes/upcoming` (próximas clases)
- Rol requerido: `USER`, `TRAINER` o `ADMIN`
- Muestra:
- Clases por rango de fechas
- Horarios y ubicaciones
- Capacidad disponible
- Acción sugerida (Reservar, Lista de espera, Llena)

### 5. **Dashboard del Miembro** (existente, mejorado)
- Endpoint: `GET /dashboard/member`
- Rol requerido: `USER` o `ADMIN`
- Información incluida:
- Estado actual (en clase o no)
- Clases restantes del plan
- Próxima clase programada
- Días consecutivos de asistencia
- Actividad semanal
- Lista de próximas clases

## 📦 Nuevos DTOs Creados

### Gestión de Clases
- `ClassWithStatsResponse`: Clase con estadísticas agregadas
- `ClassDetailResponse`: Detalle completo de clase
- `StudentInClassDTO`: Información de estudiante en clase
- `CalendarClassDTO`: Clase para vista de calendario

### Dashboard y Métricas
- `TrainerDashboardDTO`: Métricas del trainer
- `StudentTrendDTO`: Tendencia de estudiantes por semana
- `MemberDashboardDTO`: Dashboard del miembro (mejorado)
- `WeeklyActivityDTO`: Actividad semanal
- `UpcomingClassDTO`: Próximas clases

### Integración Externa
- `MemberInfoDTO`: Información de miembro desde msvc-members

## 🔧 Componentes Técnicos

### Servicios
- `ClassService`: Extendido con métodos de estadísticas y calendario
- `TrainerDashboardService`: Nuevo servicio para métricas del trainer
- `MemberClientService`: Cliente HTTP para integración con msvc-members
- `DashboardServiceImpl`: Dashboard del miembro (existente)

### Repositorios Extendidos
- `ClassRepository`: Consultas por trainer, fechas, y estadísticas mensuales
- `ClassReservationRepository`: Cálculos de asistencia y conteo de reservas

### Controladores
- `ClassController`: Extendido con endpoints de estadísticas y calendario
- `TrainerDashboardController`: Nuevo controlador para métricas del trainer
- `DashboardController`: Dashboard del miembro (existente)
- `ClassReservationController`: Gestión de reservas (existente)

## 🔐 Roles y Permisos

- **USER**: Puede ver calendario, reservar clases, ver su dashboard
- **TRAINER**: Puede ver sus clases con estadísticas, ver detalles de estudiantes, ver su dashboard
- **ADMIN**: Acceso completo a todas las funcionalidades

## 🌐 Integración con Otros Microservicios

### msvc-members
- Obtención de información detallada de miembros
- Estado de membresía
- Último acceso
- URL: `http://msvc-members/members/{id}`

### Configuración necesaria
- Eureka Client configurado para service discovery
- RestTemplate con LoadBalancer para comunicación entre microservicios

## 📊 Endpoints Principales

| Método | Endpoint | Descripción | Rol |
|--------|----------|-------------|-----|
| GET | `/classes` | Listar todas las clases | ANY |
| POST | `/classes` | Crear nueva clase | ADMIN/TRAINER |
| GET | `/classes/my-classes/stats` | Clases con estadísticas del trainer | TRAINER/ADMIN |
| GET | `/classes/{id}/detail` | Detalle completo de clase | TRAINER/ADMIN |
| GET | `/classes/calendar` | Calendario de clases | ANY |
| GET | `/classes/upcoming` | Próximas clases | ANY |
| GET | `/dashboard/trainer` | Dashboard del trainer | TRAINER/ADMIN |
| GET | `/dashboard/member` | Dashboard del miembro | USER/ADMIN |
| POST | `/reservations` | Reservar clase | USER/ADMIN |
| GET | `/reservations/my` | Mis reservas | USER/ADMIN |

## 🚦 Cómo Probar

### 1. Obtener clases con estadísticas (como Trainer)
```bash
curl -X GET "http://localhost:8083/classes/my-classes/stats" \
-H "Authorization: Bearer {token_trainer}"
```

### 2. Ver dashboard del trainer
```bash
curl -X GET "http://localhost:8083/dashboard/trainer" \
-H "Authorization: Bearer {token_trainer}"
```

### 3. Ver calendario de clases
```bash
curl -X GET "http://localhost:8083/classes/calendar?startDate=2025-01-01&endDate=2025-01-31"
```

### 4. Ver detalle de clase
```bash
curl -X GET "http://localhost:8083/classes/{classId}/detail" \
-H "Authorization: Bearer {token_trainer}"
```

## 📝 Notas de Implementación

1. **Cálculo de Asistencia**: Se basa en el campo `attended` de `ClassReservation`
2. **Tendencias Semanales**: Analiza las últimas 4 semanas de datos
3. **Estado de Clase**: Se determina por capacidad y estado activo
4. **Integración Resiliente**: Si msvc-members no responde, se usan datos por defecto
5. **Optimización**: Uso de transacciones read-only para consultas

## 🔄 Próximas Mejoras

- [ ] Cache de información de miembros para reducir llamadas HTTP
- [ ] Websockets para actualizaciones en tiempo real del dashboard
- [ ] Exportación de estadísticas a PDF/Excel
- [ ] Notificaciones automáticas cuando una clase está por llenarse
- [ ] Sistema de lista de espera automatizado
- [ ] Análisis predictivo de asistencia

## 🛠️ Stack Tecnológico

- **Framework**: Spring Boot 3.5.5
- **Base de Datos**: PostgreSQL
- **ORM**: Spring Data JPA
- **Mapeo**: MapStruct 1.5.5 ⭐ (usado en todos los DTOs)
- **Service Discovery**: Eureka Client
- **Config**: Spring Cloud Config
- **Seguridad**: Spring Security + OAuth2
- **Documentación**: Swagger/OpenAPI 3
- **Logging**: SLF4J + Logback

### 🔄 MapStruct Integration
Los mappers están completamente implementados:
- `ClassMapper`: Mapeo CRUD básico (4 métodos)
- `ClassStatsMapper`: Mapeo de estadísticas y vistas (4 métodos)
- `ClassReservationMapper`: Mapeo de reservas
- Mapeo Entity ↔ DTO sin código manual
- Ver: `MAPSTRUCT_USAGE.md` para detalles

### 🏗️ Arquitectura de Controladores
Controladores especializados por responsabilidad:
- `AdminClassController`: CRUD (solo ADMIN)
- `ClassViewController`: Vistas y consultas (TRAINER/USER)
- Ver: `ARQUITECTURA_CONTROLADORES.md` para detalles

---

✨ **Desarrollado para FitDesk Gym Management System**
10 changes: 8 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<version>3.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.members</groupId>
<artifactId>msvc-members</artifactId>
<groupId>com.classes</groupId>
<artifactId>msvc-classes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>msvc-classes</name>
<description>msvc-classes</description>
Expand Down Expand Up @@ -40,6 +40,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.19.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/com/classes/config/AzureConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.classes.config;

import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.PublicAccessType;
import com.azure.storage.common.StorageSharedKeyCredential;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AzureConfig {

@Value("${azure.storage.account-name}")
private String accountName;

@Value("${azure.storage.account-key}")
private String accountKey;

@Value("${azure.storage.container-name}")
private String containerName;

@Bean
public BlobContainerClient blobContainerClient() {
String endpoint = String.format("https://%s.blob.core.windows.net", accountName);
StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountName, accountKey);

BlobServiceClient serviceClient = new BlobServiceClientBuilder()
.endpoint(endpoint)
.credential(credential)
.buildClient();

BlobContainerClient containerClient = serviceClient.getBlobContainerClient(containerName);

// Crear el contenedor si no existe y configurar acceso público
if (!containerClient.exists()) {
containerClient.create();
containerClient.setAccessPolicy(PublicAccessType.BLOB, null);
}

return containerClient;
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/classes/config/CloudinaryConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.classes.config;

import com.cloudinary.Cloudinary;
import com.cloudinary.utils.ObjectUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class CloudinaryConfig {
@Value("${cloudinary.cloud_name}")
private String cloudName;
@Value("${cloudinary.api_key}")
private String apikey;
@Value("${cloudinary.api_secret}")
private String apiSecret;


@Bean
public Cloudinary cloudinary() {
if (cloudName.isBlank() || apikey.isBlank() || apiSecret.isBlank()) {
throw new IllegalStateException("Faltan propiedades de Cloudinary: configura cloudinary.cloud_name, cloudinary.api_key y cloudinary.api_secret");
}
return new Cloudinary(ObjectUtils.asMap(
"cloud_name", cloudName,
"api_key", apikey,
"api_secret", apiSecret,
"secure", true
));
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/classes/config/RestTemplateConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.classes.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
25 changes: 20 additions & 5 deletions src/main/java/com/classes/controllers/ClassController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,32 @@
import com.classes.dtos.Class.ClassRequest;
import com.classes.dtos.Class.ClassResponse;
import com.classes.services.ClassService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.data.domain.Page;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/classes")
@RequestMapping("/classes")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "Admin - Clases", description = "Gestión administrativa de clases (CRUD)")
public class ClassController {

private final ClassService classService;



@PostMapping
@PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)")
public ResponseEntity<ClassResponse> createClass(@RequestBody ClassRequest request) {
log.info("Admin creando nueva clase: {}", request.getClassName());
ClassResponse created = classService.createClass(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
Expand All @@ -36,6 +40,15 @@ public ResponseEntity<List<ClassResponse>> findAllClasses() {
return ResponseEntity.ok(list);
}

@GetMapping("/paginated")
public ResponseEntity<Page<ClassResponse>> findAllClassesPaginated(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String search) {
Page<ClassResponse> result = classService.findAllPaginated(page, size, search);
return ResponseEntity.ok(result);
}


@PutMapping("/{id}")
@PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)")
Expand All @@ -44,9 +57,11 @@ public ResponseEntity<ClassResponse> updateClass(@PathVariable UUID id, @Request
return ResponseEntity.ok(updated);
}


@DeleteMapping("/{id}")
@PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)")
public ResponseEntity<String> deleteClass(@PathVariable UUID id) {
log.info("🗑Admin eliminando clase: {}", id);
classService.deleteClass(id);
return ResponseEntity.ok("Clase eliminada correctamente");
}
Expand Down
Loading