PistaGO

Sistema completo de reservas de pistas de tenis: app Android nativa, backend REST en la nube y panel de administración.

Tipo Proyecto fin de ciclo DAM
Duración ~4 meses (2026)
Estado En producción
Rol Diseño, backend y frontend

El problema

En muchos clubes de tenis pequeños, la reserva de pistas todavía se gestiona por WhatsApp, en una libreta detrás del mostrador, o por llamada telefónica al recepcionista. Esto genera fricciones reales: dos personas reservando la misma franja sin saberlo, no haber dejado claro si la pista era de tierra o dura, dificultad para saber qué pistas están disponibles sin ir físicamente.

PistaGO ataca ese problema con una aplicación móvil que pone la disponibilidad en tiempo real al alcance de cualquier socio del club, evita reservas duplicadas a nivel de servidor, y da al administrador del club una vista completa de uso y métricas.

Stack tecnológico

Kotlin Jetpack Compose Material 3 Hilt Retrofit Coil DataStore Spring Boot 3 Spring Security JPA / Hibernate PostgreSQL 16 JWT Firebase Cloud Messaging JUnit 5 Mockito Kotlin Nginx Ubuntu (VPS) Let's Encrypt Docker (dev)

Cliente Android

Aplicación nativa escrita 100% en Kotlin con Jetpack Compose. Arquitectura MVVM + Clean (capas de presentación, dominio y datos). Inyección de dependencias con Hilt. Llamadas HTTP con Retrofit + Gson. Carga asíncrona de imágenes con Coil. Persistencia local de sesión con DataStore.

Backend

API REST en Kotlin con Spring Boot 3.5. Autenticación con JWT firmado con HS512 y Spring Security. Datos en PostgreSQL 16 con JPA/Hibernate. Notificaciones push integradas con Firebase Cloud Messaging para avisar a los usuarios de lista de espera cuando una pista se libera. Validaciones de entrada con Bean Validation. Manejo global de errores con un @RestControllerAdvice que devuelve respuestas JSON consistentes.

Infraestructura

Desplegado en un VPS de DigitalOcean con Ubuntu 24. Nginx como proxy inverso con HTTPS gestionado por Let's Encrypt. PostgreSQL local en el VPS. Backend ejecutado como JAR de Spring Boot (perfectamente apto para systemd; en este caso por sencillez se usa nohup). APK firmada distribuida públicamente desde nacimiento.me/pistago/.

Por los números

12
Pantallas en la app
30
Pruebas unitarias
7
Tablas en BD
~3500
Líneas de Kotlin

Funcionalidades destacadas

Reservas por franjas de 1:30

9 franjas diarias con bloqueo automático de horas pasadas (usando zona horaria de Madrid, independiente del dispositivo). Estados visuales claros: disponible, seleccionada, ocupada o pasada.

Una reserva activa por usuario

Para repartir el uso equitativamente, un usuario solo puede tener una reserva activa simultánea. Los administradores quedan exentos. Validado en el servidor y cubierto con pruebas unitarias.

Lista de espera con notificaciones push

Si una franja está ocupada, el usuario puede apuntarse en lista de espera. Cuando alguien cancela, el primer usuario en la lista recibe una notificación push de Firebase con 10 minutos para confirmar.

Panel de administración con estadísticas

Métricas de uso del club: usuarios, reservas totales, reservas del día, tasa de cancelación, ranking de pistas y usuarios más activos, distribución por día de la semana. Gráfico de barras dibujado con Canvas, sin librerías externas.

Sistema unificado de errores

El servidor responde a los errores con un JSON consistente (status, error, message, path, errores de validación). El cliente extrae el mensaje real y lo muestra al usuario, no un genérico "Error 400".

Identidad visual propia

Logo, icono de la app, paleta cromática verde y acento lima, tipografía consistente. La marca aparece coherente en todas las pantallas: login, home, listado, perfil con iniciales como avatar...

Capturas

Tres pantallas representativas del producto final.

Pantalla principal de PistaGO

Pantalla principal de la app: acceso a reservar, mis reservas, lista de espera y panel de administración.

Panel de estadísticas del administrador

Panel de estadísticas del administrador: tarjetas resumen, ranking de pistas y usuarios, y gráfico de barras por día de la semana dibujado con Canvas de Compose.

Decisiones técnicas interesantes

JWT con HS512 en lugar de OAuth

Como la app es de un único cliente confiable, no había necesidad de la complejidad de OAuth. Un JWT firmado con HS512 cumple los requisitos de autenticación y autorización con menos infraestructura. Spring Security gestiona el filtro de autenticación y la verificación del token.

Gráfico de barras sin librerías

En la pantalla de estadísticas, en lugar de añadir una dependencia como Vico o MPAndroidChart, dibujé el gráfico de barras directamente con Canvas de Jetpack Compose. Para 7 datos (días de la semana) era más limpio y reducía el tamaño de la APK.

Zona horaria fijada en el cliente

Al detectar que los emuladores Android arrancan en UTC por defecto, el bloqueo de franjas pasadas falla si confías en LocalTime.now() sin más. Solución: forzar ZoneId.of("Europe/Madrid") en los cálculos del cliente, así funciona correctamente independientemente de la configuración del dispositivo.

Diseño de errores en una sola pieza

Después de detectar que los repositorios del cliente repetían el mismo patrón de parseo de errores, lo extraje a una función de extensión común Response<T>.extractErrorMessage(). Esto desbloquea que cuando el servidor añade nuevas validaciones, los mensajes aparecen automáticamente en la app sin tocar ningún cliente.

Pruebas

El backend tiene 30 pruebas unitarias propias escritas en JUnit 5 con Mockito Kotlin, distribuidas en los tres servicios principales:

Presentación

Slides utilizadas en la defensa del proyecto el 15 de junio de 2026. Puedes verlas online en PDF o descargar el archivo editable de PowerPoint.

Lo que aprendí

Más allá del código, este proyecto fue un ejercicio completo de gestión de un sistema desde cero: tomar decisiones de arquitectura, equilibrar pragmatismo y limpieza, desplegar a producción y mantener una API estable mientras el cliente evoluciona. También me obligó a ser disciplinado con Git (Conventional Commits), tests y documentación, en vez de ir parcheando sobre la marcha.

La parte que más disfruté fue la del diseño visual: trabajar la identidad de marca, dibujar el gráfico de estadísticas con Canvas, y conseguir que la app se sintiera cuidada hasta en los detalles pequeños (placeholder de avatar con iniciales, transiciones suaves entre estados, mensajes de error útiles).

Probarlo tú mismo

La aplicación está en producción y puedes instalarla en cualquier Android.

Usuario de prueba: usuarioprueba@pistago.com · pistago2026