Desarrolle una API REST con .NET 8 aplicando Clean Architecture (4 capas: Domain, Application, Infrastructure, API), CQRS con MediatR para separar comandos de queries, JWT con Refresh Token rotation para sesiones seguras, soft delete para auditoria sin perdida de datos, y 25 tests unitarios con xUnit + FluentAssertions que validan todas las reglas de negocio.
Clean Architecture con 4 proyectos separados: Domain (entidades puras sin dependencias), Application (use cases con MediatR), Infrastructure (EF Core, JWT, repositories), API (controllers y middleware)
CQRS con MediatR: cada operacion es un Command o Query independiente con su Handler — CreateCourtCommand, GetAvailableSlotsQuery, BookCourtCommand, CancelBookingCommand, etc.
Repository Pattern con interfaces en Domain e implementaciones en Infrastructure — el dominio nunca referencia Entity Framework directamente
Dependency Injection configurado en la capa API con extension methods por capa: AddApplication(), AddInfrastructure() — cada capa registra sus propios servicios
JWT Authentication con access token de corta duracion (15 min) + refresh token de larga duracion (7 dias) almacenado en base de datos
Refresh Token rotation: cada vez que se usa un refresh token, se invalida y se emite uno nuevo — mitiga el riesgo de token robado
Revocacion de refresh tokens: endpoint de logout que invalida el token actual. Si se detecta reuso de un token ya rotado, se revocan todos los tokens del usuario (compromiso detectado)
Registro y login con hash BCrypt + salt automatico — las contrasenas nunca se almacenan en texto plano
Sistema de reservas con validacion de disponibilidad: verifica que la cancha exista, que el slot horario este libre, y que no haya conflicto con reservas existentes en el mismo rango horario
Cancelacion de reservas con validacion de ownership — solo el usuario que creo la reserva puede cancelarla
Consulta de disponibilidad por cancha y fecha: retorna los slots libres calculados a partir de las reservas existentes
Tipos de cancha configurables: futbol 5, futbol 7, futbol 11, paddle, tenis — cada tipo con duracion de turno y precio por hora
Soft delete implementado con columna IsDeleted + DeletedAt en todas las entidades — los registros nunca se eliminan fisicamente de la base de datos
Query filters globales en EF Core: todas las queries excluyen automaticamente registros con IsDeleted = true, sin necesidad de agregar WHERE en cada consulta
Auditoria completa: cada entidad tiene CreatedAt, UpdatedAt, DeletedAt y CreatedBy para trazabilidad de cambios
FluentValidation para validacion de requests: cada Command tiene su Validator con reglas tipadas (nombre requerido, email valido, fecha futura, duracion positiva)
Pipeline behavior de MediatR para validacion automatica: el ValidationBehavior intercepta cada request, ejecuta sus validators, y lanza ValidationException antes de llegar al handler
Exception handling middleware global que captura excepciones de dominio y las transforma en respuestas HTTP tipadas (400, 401, 404, 409)
Entity Framework Core con PostgreSQL, migraciones code-first y configuracion fluent API para constraints, indices y relaciones
Seed data con canchas de ejemplo y usuario admin para testing inmediato tras migrar
25 tests unitarios con xUnit + FluentAssertions: cobertura de handlers de commands y queries, validadores, servicios de autenticacion y reglas de negocio
Tests puros sin base de datos: mocks de repositorios con NSubstitute para aislar la logica de negocio de la infraestructura
Tests de edge cases: reserva en slot ocupado, refresh token expirado, cancelacion por usuario no autorizado, cancha inexistente
Swagger/OpenAPI con documentacion de todos los endpoints, schemas de request/response, y autorizacion JWT configurable desde la UI
Versionado de API preparado con prefijo /api/v1/ para futuras iteraciones sin romper clientes existentes
FLUJO — Reserva de cancha: [Cliente] POST /api/v1/bookings {courtId, date, startTime, endTime} → [ValidationBehavior] FluentValidation: fecha futura, startTime < endTime, courtId valido → [BookCourtHandler] carga Court del repositorio → verifica Court.IsAvailable(date, startTime, endTime) contra reservas existentes → si hay conflicto: 409 Conflict → si esta libre: crea Booking con status=Confirmed, CreatedBy=userId → persiste via IBookingRepository → response 201 con bookingId
FLUJO — Login y token rotation: [Cliente] POST /api/v1/auth/login {email, password} → [LoginHandler] busca usuario por email → BCrypt.Verify(password, hash) → si falla: 401 → si pasa: genera JWT (15min) + RefreshToken (7 dias) → persiste RefreshToken en DB con UserId, Token, ExpiresAt, IsRevoked=false → response {accessToken, refreshToken}
FLUJO — Refresh token: [Cliente] POST /api/v1/auth/refresh {refreshToken} → [RefreshHandler] busca token en DB → valida: no expirado, no revocado → marca token actual como IsRevoked=true → genera nuevo JWT + nuevo RefreshToken → persiste nuevo token → response {accessToken, refreshToken} (el anterior ya no sirve)
FLUJO — Deteccion de compromiso: [Atacante] usa RefreshToken ya rotado → [RefreshHandler] busca token → IsRevoked=true → ALERTA: token reusado = posible robo → revoca TODOS los tokens del usuario (UPDATE SET IsRevoked=true WHERE UserId=X) → response 401 → el usuario legitimo debe re-loguearse
FLUJO — Consulta de disponibilidad: [Cliente] GET /api/v1/courts/{id}/availability?date=2025-06-15 → [GetAvailableSlotsHandler] carga cancha con su configuracion (apertura, cierre, duracion turno) → genera todos los slots posibles del dia (ej: 08:00-09:00, 09:00-10:00...) → carga bookings existentes para esa cancha y fecha → filtra los slots que no tienen booking → response [{slot: "08:00-09:00", available: true}, {slot: "09:00-10:00", available: false, bookedBy: "..."}]
FLUJO — Cancelacion con soft delete: [Cliente] DELETE /api/v1/bookings/{id} → [CancelBookingHandler] carga booking del repositorio → verifica booking.CreatedBy == userId actual (ownership) → si no: 403 Forbidden → si si: booking.IsDeleted=true, booking.DeletedAt=now → persiste → el slot queda libre automaticamente (query filter excluye deleted) → response 204
FLUJO — Request completo por capas: [HTTP Request] → [API: Controller] extrae JWT del header → [Auth Middleware] valida token y setea ClaimsPrincipal → [MediatR.Send(Command)] → [ValidationBehavior] ejecuta FluentValidation → [Handler en Application] ejecuta logica de negocio → [Repository en Infrastructure] persiste via EF Core → [PostgreSQL] → response sube las capas en orden inverso
FLUJO — Dependencias entre capas: [API] → depende de → [Application] → depende de → [Domain] ← no depende de nada. [Infrastructure] → implementa interfaces de → [Domain]. La regla de dependencia apunta siempre hacia adentro: las capas externas dependen de las internas, nunca al reves