Volver al portfolio
  • Java
  • Spring Boot
  • PostgreSQL
  • Docker
  • Angular

SportReserve

SaaS multi-tenant para la gestión de reservas de espacios en clubes y asociaciones.

Demo en vivo

Puedes probar el sistema con las siguientes credenciales:

Rol Email Contraseña
Administrador [email protected] Demo1234!

Demo de Club de Tenis Valencia: https://sportreserve.andresgranerosala.com/club/club-tenis-valencia

Qué es y a quién va dirigido

SportReserve es un software para clubes y asociaciones cuyos socios reservan espacios de uso común (pistas de tenis, salas de reuniones de un coworking, aulas…) sin tener que recibir llamadas ni gestionar muchos mensajes en un grupo de WhatsApp. Es un SaaS multi-tenant: un mismo sistema sirve a muchos clubs a la vez, de forma modular y automatizada, y cada club se adapta a su negocio con personalización de colores y textos, su página pública, su horario, sus espacios y sus anuncios.

Cómo funciona

Login y sesión

Se entra con email, contraseña y club, porque un mismo email puede ser socio de varios clubs como cuentas independientes. Al acertar, el sistema emite un token firmado que acompaña a cada petición (y se renueva), para no tener que pedir la contraseña continuamente.

Permisos y seguridad

Hay tres roles jerárquicos que actúan dentro de su zona: PROVIDER (superadministrador: crea clubs y a otros administradores), TENANT (administrador de su club) y USER (socio que reserva). El sistema no confía en lo que dice la petición: valida la identidad, fuerza el club correcto y comprueba la propiedad de cada dato; así nadie puede actuar fuera de su club.

El viaje de una petición

DTO request → filtro JWT → bind → endpoint → servicio → repositorio → PostgreSQL → DTO response

Separar la entrada, la lógica y la salida hace el sistema seguro, claro y fácil de mantener.

Reservas y errores

Todo se valida en el servidor, no solo en la pantalla: horario del club, sin solapes, duración en múltiplos del intervalo del club, tope diario/semanal por socio y avisos de cierre. Cualquier error responde siempre con el mismo formato.

Frontend, con honestidad

El proyecto tiene dos aplicaciones web (la página pública del club y el panel de administración) construidas casi íntegras con IA generativa; mis conocimientos de frontend son básicos y los potencio con IA. El valor técnico sólido está en el backend.

Despliegue

Alojado en un servidor virtual propio con Dockploy: el backend y el frontend van en imágenes Docker separadas que Dockploy instala y actualiza en el servidor, cada una con un solo Dockerfile.

Crear un club desde JSON

Un club completo (datos, espacios, horario, anuncios y personalización) se crea pegando un JSON que se valida entero antes de escribir nada; si algo falla, no se crea nada y se puede reintentar.

Decisiones de diseño

El flujo request → bind → service → repository → response es deliberado y no casual. Mientras creaba SportReserve me di cuenta de que la parte más crítica de un SaaS multi-tenant no es la CRUD, sino quiénes pueden tocar qué y cuándo. Por eso separé la defensa en capas en lugar de confiarla a un único punto:

  • JwtAuthFilter resuelve quién eres (userId, zona y rol) solo con el token.
  • BindingAdvice obliga a que el zoneId/userId que llega del cliente se amarre al contexto autenticado antes de validar el formulario. Así, un socio no puede escribir en el club de otro aunque manipule el JSON de la petición.
  • El servicio vuelve a comprobar la propiedad con findByIdAndZoneId (si no, AccessDenied) y aplica las reglas de negocio: horario, solapes, cupos y duración múltiplo de slotMinutes.
  • ExceptionManager centraliza todas las respuestas de error en { "error": "…" } con el código HTTP correcto.

Lo hice así porque he pensado en el producto, no solo en el código: si una demo en vivo usa los colores, los nombres y el horario reales de la empresa, el cliente se ve a sí mismo en la pantalla y entiende el valor en 10 segundos. Y porque no oculto que el proceso está asistido por IA: controlar el prompt, el formato y la verificación también es parte del trabajo de ingeniería.

Cómo se creó

  • Agentes de IA (OpenCode): configuré AGENTS.md como arnés de convenciones y reglas del proyecto, y lo he usado también para depurar y arreglar errores a lo largo de todo el desarrollo.
  • De la web al club, en tres líneas: OpenCode busca en internet la información real de la empresa (personas, colores, logo, horario), la valida contra las reglas del sistema y genera el JSON del club; de ese mismo JSON se genera el CV, de forma determinista.
  • Frontend asistido por IA: la parte Angular (widget y panel) está construida en su práctica totalidad con IA generativa.
  • Rigor negocio: la demo usa colores, nombres y horarios reales de la empresa para que el cliente «se vea» en el producto.
  • Transparencia: la demo se ha generado con asistencia de IA; el código y las decisiones son propios.

Stack

Backend

Java 21, Spring Boot, Spring Security, REST API, JWT, JPA/Hibernate

Base de datos

PostgreSQL 16, SQL

Frontend

Angular 22 (standalone, signals), TypeScript, HTML/CSS

Infraestructura

Docker, nginx, servidor virtual propio con Dockploy

Metodología

Multi-tenant, defensa en profundidad, arquitectura por capas, DTOs, Bean Validation

Herramientas

Git, Gradle, npm, OpenCode (IA)