Documentación
Booqi, de punta a punta
Todo lo que hace la aplicación y cómo lo hace: cada pantalla para quien compra y para quien administra, los cuatro microservicios y su API, el recorrido de una compra entre servicios, la base de datos, el frontend, la infraestructura, las pruebas y las limitaciones conocidas.
Empezar
Qué es Booqi
Booqi es un sistema de gestión y reserva de entradas para eventos construido con microservicios. Permite explorar eventos, reservarlos, pagarlos con un pago simulado y descargar las entradas en PDF con su código QR. Es un trabajo de fin de grado de código abierto.
-
Para quien compra
Catálogo con filtros, detalle del evento, carrito, pago, entradas en PDF, historial de reservas y perfil.
-
Para quien administra
Gestión de usuarios y eventos, listado de ventas con filtros y un informe de ventas en PDF.
-
Cuatro microservicios
Usuarios, eventos, reservas y pagos en Spring Boot, cada uno con su propio esquema de MySQL.
-
Un solo comando
Todo el stack (seis contenedores) se levanta con Docker Compose o Podman.
Enlaces
- Código: github.com/RenzoRamosDEV/Booqui-TFG
- Demo sin backend: renzoramosdev.github.io/Booqui-TFG
- Repositorio original del grupo: gitlab.com/booqui
Empezar
Probar la demo
La demo publicada es la misma aplicación React, pero sin servidor: un backend simulado en el navegador responde a todas las llamadas a la API con 250 ms de latencia. Funciona igual que la real: errores con el mismo formato, aprobación del pago por el último dígito de la tarjeta, control de stock y administración completa.
| Rol | Contraseña | |
|---|---|---|
| Cliente | cliente@booqi.demo | demo1234 |
| Administrador | admin@booqi.demo | demo1234 |
- La pantalla de inicio de sesión de la demo tiene los botones «Entrar como cliente demo» y «Entrar como admin demo».
- Incluye 10 eventos en ciudades de España con fechas relativas al momento en que abres la demo, y algunas ventas previas para el panel de administración.
- Lo que hagas se guarda en
sessionStorage: sobrevive a una recarga y desaparece al cerrar la pestaña. - Una etiqueta fija «Demo · datos simulados» recuerda que nada es real.
- Usa rutas con
#porque GitHub Pages no reenvía las rutas de una SPA aindex.html.
Truco para probar pagos
Una tarjeta cuyo último dígito es par se aprueba; si es impar, se rechaza. PayPal siempre se aprueba.
Empezar
Ejecutar en local
Solo necesitas git, Docker o Podman con Compose v2, y make.
git clone https://github.com/RenzoRamosDEV/Booqui-TFG.git
cd Booqui-TFG
cp infra/.env.example infra/.env # cambia las contraseñas de ejemplo
make up # construye y levanta el stack
La primera vez construye las imágenes; el stack tarda unos 2–3 minutos en estar sano (unos 195 s medidos en frío). Después abre http://localhost.
| Componente | Dirección |
|---|---|
| Frontend | http://localhost |
| user-service | http://localhost:8080 |
| event-service | http://localhost:8081 |
| booking-service | http://localhost:8082 |
| payment-service | http://localhost:8083 |
| MySQL | localhost:3307 |
Cada servicio publica su documentación en /swagger-ui.html, el contrato en
/v3/api-docs y su estado en /actuator/health.
Usuarios de prueba
| Rol | Contraseña | |
|---|---|---|
| ADMIN | mel@booqi.com | admin_mel |
| CUSTOMER | andres.g@email.com | cust_pass_1 |
Podman rootless o puerto 80 ocupado
Si tu sistema no deja publicar el puerto 80 sin privilegios, pon
BOOQI_FRONTEND_PORT=8088 en infra/.env y abre
http://localhost:8088.
Para pararlo: make down. Para borrarlo todo, también los datos: make clean.
La aplicación
Inicio y catálogo
Inicio
- Un hero con el titular «Tus eventos favoritos, sin colas» y un buscador de artista, evento u organizador.
- «Explora por categoría», con accesos directos a cada categoría.
- 3 destacados: los próximos eventos que aún tienen entradas, por fecha.
- 6 próximos: los siguientes eventos futuros.
- Si no hay eventos futuros: «Todavía no hay eventos próximos».
Catálogo
El catálogo se descarga una sola vez y se filtra y pagina en el navegador. Todos los filtros se guardan en la URL, así que un listado filtrado se puede compartir o recargar.
| Filtro | Parámetro | Cómo filtra |
|---|---|---|
| Buscar | q | Texto en título, descripción u organizador, sin tildes ni mayúsculas |
| Categoría | category | Música, Arte, Deporte, Gastronomía, Tecnología, Educación u Otros |
| Ciudad | city | Texto contenido en la ubicación |
| Desde / Hasta | from, to | Días del calendario |
| Precio máximo | maxPrice | Euros |
| Página | page | 12 eventos por página, ordenados por fecha |
Los filtros se combinan entre sí. La paginación muestra como mucho 7 botones, con puntos suspensivos en medio.
La aplicación
Detalle de un evento
- Muestra categoría, inicio, fin, organizador y lugar; el lugar abre Google Maps en otra pestaña.
- El precio por entrada, o «Gratis» si vale 0.
- Un panel «Comprar entradas» con selector de cantidad y el aviso «Máximo 10 entradas por evento».
- Si el evento no existe: «Evento no encontrado».
| Entradas disponibles | Etiqueta |
|---|---|
| 0 | Agotado |
| 1 | Última entrada |
| 2 a 10 | Últimas N entradas |
| Más de 10 | N entradas disponibles |
Al añadir al carrito sin haber iniciado sesión, la app te lleva al inicio de sesión y después te devuelve al evento. Con sesión, aparece el aviso «Añadido al carrito».
La aplicación
Carrito y pago
Carrito
- Una línea por evento. La cantidad va de 1 al mínimo entre las entradas disponibles y 10.
- Los importes se calculan en céntimos y se muestran en euros, sin comisiones.
- El carrito vive en memoria: se vacía al recargar la página o al cerrar sesión.
- Vacío: «Tu carrito está vacío».
Pago
El pago tiene tres pasos: Revisión, Pago y Confirmación.
| Método | Datos que pide |
|---|---|
| Tarjeta de crédito | Titular, número, caducidad y CVV |
| Tarjeta de débito | Titular, número, caducidad y CVV |
| PayPal | Nada: no envía tarjeta |
Validación de la tarjeta antes de enviar:
- Titular obligatorio.
- Número de 13 a 19 dígitos (los espacios se ignoran).
- Caducidad en formato
MM/AAy no vencida. - CVV de 3 o 4 dígitos.
Al pagar, la app procesa cada línea del carrito por orden y de una en una: crea la reserva y, si sale bien, la paga. Un fallo en una línea no detiene las demás. Solo una línea aprobada cuenta como pagada: las aprobadas salen del carrito y las rechazadas se quedan con un aviso, para reintentarlas.
- Si todo se aprueba: «¡Pago completado!», con un check animado y confeti.
- Si no: «Resultado del pago», con el resultado de cada línea, su código de reserva, el QR y la descarga de entradas.
- No se envía ningún correo: las entradas se descargan desde la app.
La aplicación
Entradas en PDF con QR
- Se generan en el propio navegador, sin pasar por el servidor.
- Una página por entrada, numerada
n / N, en A5 apaisado, con la banda de color de la marca, el logo y el QR. - El QR codifica el número de la reserva.
- Las reservas canceladas no generan entradas.
- El archivo se llama
booqi-<evento>-<id>.pdf. - Las librerías de PDF y QR solo se descargan cuando hacen falta, para no pesar en el resto de la app.
La aplicación
Mis reservas
- Agrupa tus reservas por evento, del más reciente al más antiguo.
- Cada grupo muestra el total de entradas y el importe, sin contar las canceladas.
- Un panel lateral muestra el detalle de cada reserva.
- Se pueden descargar las entradas de nuevo en cualquier momento.
| Insignia del grupo | Cuándo |
|---|---|
| Confirmada | Todas sus reservas están confirmadas |
| Cancelada | Todas sus reservas están canceladas |
| Pendiente | Cualquier otro caso |
Cancelar
Un diálogo pide confirmación: «¿Cancelar la reserva?». La cancelación no se puede deshacer y no devuelve el importe pagado; tampoco devuelve las entradas al stock del evento.
La aplicación
Cuenta, perfil y contacto
Registro
- Nombre y apellidos: de 2 a 50 caracteres.
- Fecha de nacimiento en el pasado.
- Teléfono opcional, de 9 a 15 dígitos con
+opcional. - Email válido y único.
- Contraseña de al menos 8 caracteres, repetida para confirmar.
- Toda cuenta nueva es de cliente. Los errores del servidor aparecen bajo su campo.
Inicio de sesión
- Email y contraseña. Si fallan: «Email o contraseña incorrectos» (no se dice cuál de los dos).
- Después vuelve a la página de la que venías; si no venías de ninguna, un administrador va a Administración y un cliente al inicio.
Mi perfil
- Edita nombre, apellidos, fecha de nacimiento, teléfono y email.
- Contraseña nueva opcional (al menos 8 caracteres, repetida).
- Solo se envían los campos cambiados; sin cambios, avisa «No hay cambios que guardar».
- Si el email ya pertenece a otra cuenta: «Este email ya está registrado».
Contacto
Un formulario de nombre, email, asunto y mensaje (hasta 2000 caracteres). Es una simulación, marcada como «Demo»: muestra «¡Mensaje recibido!» pero no envía nada, porque el backend no tiene servicio de contacto.
La aplicación
Panel de administración
Solo para administradores. Su código ni siquiera se descarga hasta que la app comprueba que tienes ese rol.
Métricas
Cuatro tarjetas: número de administradores, número de clientes, precio medio de los eventos y total de ventas aprobadas.
Usuarios
- Listado de todos los usuarios con su rol.
- Borrar cualquier usuario excepto a ti mismo, con confirmación.
- No se crean ni editan usuarios desde aquí.
Eventos
Crear, editar y borrar eventos. El formulario valida:
| Campo | Regla |
|---|---|
| Título | 3 a 200 caracteres |
| Descripción | Hasta 2000 caracteres |
| Entradas | Número entero, 0 o más |
| Precio | 0 o más |
| Ubicación | 3 a 200 caracteres |
| Inicio | En el futuro (solo al crear) |
| Fin | No anterior al inicio |
| Organizador | 3 a 100 caracteres |
| Categoría | Una de la lista |
| Email de contacto | Válido y único entre eventos |
| Teléfono | 9 a 15 dígitos |
| Imagen | URL opcional |
Al editar solo se envían los cambios. Si hay un conflicto (por ejemplo, un email repetido), el error aparece en el propio diálogo, que no se cierra.
Ventas
- Cruza los pagos con sus reservas para mostrar fecha, cliente, evento, entradas, importe, método y estados.
- Filtros por estado del pago (todos, aprobados, rechazados, pendientes) y por evento.
- Totales aprobado, rechazado y pendiente.
Informe de ventas en PDF
- De un evento o de la selección filtrada, titulado «Informe de ventas · …».
- Columnas: Fecha, Cliente, Evento, Entradas, Importe, Método, Pago y Reserva, con los totales al final.
- Pie con la fecha de generación y quién lo exportó.
- Se puede previsualizar antes de descargar. Archivo:
booqi-informe-<ámbito>-<día>.pdf.
Por dentro
Arquitectura
Seis contenedores: un MySQL, cuatro microservicios Spring Boot y un nginx que sirve el frontend y hace de proxy de la API.
- El navegador siempre habla con un único origen: nginx reenvía
/api/v1/users,/events,/bookingsy/paymentsa su servicio. En desarrollo, Vite hace lo mismo. - Una base de datos por servicio: cada servicio es dueño de su esquema y nadie más lo toca.
- Los servicios se llaman entre sí por HTTP: reservas consulta a usuarios y eventos; pagos descuenta stock en eventos y confirma o cancela en reservas.
- Todos siguen la misma estructura de capas:
controller→service→repository, con DTO como records y MapStruct para convertir entidades. - Monorepo:
services/,frontend/,infra/ydocs/, con unMakefileen la raíz.
Común a los cuatro servicios
- Java 17 y Spring Boot 4.0.3, paquete
com.booqi.<servicio>. - Hibernate solo valida el esquema (
ddl-auto: validate); nunca lo modifica. - Errores en formato estándar RFC 9457 mediante un manejador global.
- Actuator con
healtheinfo, y OpenAPI con Swagger UI. - CORS configurable con
booqi.cors.allowed-origins.
Por dentro
Stack tecnológico
Todas las herramientas del proyecto, agrupadas por capa. Las versiones son las del repositorio.
Backend
- Java 17
- Spring Boot 4.0.3
- Spring Web (MVC)
- Spring Data JPA · Hibernate
- Spring Validation
- Spring Boot Actuator
- Spring Security Crypto (BCrypt)
- MapStruct 1.6.3
- Lombok
- springdoc-openapi 3.0.3
- Swagger UI
- MySQL Connector/J
- H2 tests
- Maven 3.9.12
- RFC 9457 Problem Details
Frontend
- React 19.3.1
- TypeScript 6.0.3
- Vite 8.3.5
- Tailwind CSS 4.3.3
- React Router 7.18.4
- TanStack Query 5.104.5
- Axios 1.20.0
- React Hook Form 7.91.0
- Zod 4.6.7
- Zustand 5.0.16
- Motion 14.6.0
- Lucide 1.61.0
- pdf-lib 1.17.1
- qrcode 1.5.4
- Inter · Space Grotesk
Infraestructura
- Docker Compose v2
- Podman (rootless)
- nginx alpine (proxy /api/v1)
- MySQL 8.0
- eclipse-temurin 17 (JRE alpine)
- maven:3.9 (build)
- node:22-alpine
- Makefile
- GitHub Pages (demo)
Pruebas y calidad
- JUnit 5
- Mockito
- @WebMvcTest · @DataJpaTest
- PIT 1.17.0
- Checkstyle 12.3.1
- Spotless
- google-java-format 1.35.0
- Vitest 5.0
- Testing Library (dom · react · user-event · jest-dom)
- jsdom 30
- Playwright 1.64
- Stryker 10.1.1
- ESLint 10
- typescript-eslint · react-hooks · react-refresh
- Prettier 3.9.10
- node --test
CI y método
- GitHub Actions (backend · frontend · compose · Pages)
- OpenSpec (19 capacidades)
- ADRs (MADR)
- Conventional Commits
- TDD
Por dentro
Microservicios y API
Toda la API cuelga de /api/v1. Crear devuelve 201 con la cabecera
Location. Los listados aceptan filtros por query string, pero solo uno a la vez
(en usuarios, nombre y apellido sí se combinan); los filtros vacíos se ignoran.
user-service · puerto 8080
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /users | Crea un usuario (CUSTOMER si no se indica rol) |
| GET | /users | Lista; filtros contactEmail, contactPhone, role, firstName, lastName |
| GET | /users/{id} | Obtiene un usuario |
| PUT | /users/{id} | Actualización parcial; si cambia la contraseña, la vuelve a cifrar |
| DELETE | /users/{id} | Borra |
| POST | /users/login | Inicia sesión con contactEmail y password |
El filtro por rol no distingue mayúsculas (un rol desconocido devuelve una lista vacía) y los de nombre tampoco.
event-service · puerto 8081
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /events | Crea un evento (409 si el email ya existe; 422 si el fin es anterior al inicio) |
| GET | /events | Lista; filtros title, location, organizer (contiene), category (igual) o contactEmail (exacto) |
| GET | /events/{id} | Obtiene un evento |
| PUT | /events/{id} | Actualización parcial, con las mismas comprobaciones de fechas y email |
| DELETE | /events/{id} | Borra |
| PATCH | /events/{id}/stock?quantity=n | Descuenta stock; 422 si la cantidad no es positiva o no hay bastantes entradas |
booking-service · puerto 8082
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /bookings | Crea una reserva pendiente con userId, eventId y ticketQuantity (1 o más) |
| GET | /bookings | Lista; filtros userId, eventId o status |
| GET | /bookings/{id} | Obtiene una reserva |
| PATCH | /bookings/{id}/confirm | La confirma (si ya lo estaba, la devuelve igual) |
| PATCH | /bookings/{id}/cancel | La cancela (si ya lo estaba, la devuelve igual) |
Al crear una reserva consulta el usuario y el evento y guarda una copia de sus datos (nombre, email, título, descripción, fecha y lugar), para que la reserva conserve cómo eran en ese momento. El precio total es el precio del evento por la cantidad. Las llamadas a otros servicios tienen 2 s de tiempo de conexión y 5 s de lectura, sin reintentos.
payment-service · puerto 8083
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /payments | Procesa un pago simulado; responde 201 aunque se rechace |
| GET | /payments | Lista; filtros userId, eventId, bookingId o status |
| GET | /payments/{id} | Obtiene un pago |
Un pago lleva bookingId, userId, eventId,
ticketQuantity, totalPrice, el método (CREDIT_CARD,
DEBIT_CARD, PAYPAL o BANK_TRANSFER, este último sin uso
en la interfaz) y, opcionalmente, tarjeta y CVV.
Errores al llamar a otro servicio
| Respuesta del otro servicio | Lo que devuelve el que llama |
|---|---|
| 5xx | 503 «X service is unavailable» |
| 404 | 404 con el mismo mensaje |
| Otro 4xx | 422 con el mismo mensaje |
| Sin respuesta o tiempo agotado | 503 |
Por dentro
Flujo de una compra
Lo que pasa, servicio a servicio, por cada línea del carrito:
-
Reserva
El frontend llama a
POST /bookings. booking-service consulta a user-service y a event-service, guarda la reserva como PENDING y responde 201. -
Pago
El frontend llama a
POST /paymentscon el número de reserva y el total. payment-service guarda el pago como PENDING y simula medio segundo de procesamiento. -
Decisión simulada
Se aprueba si no hay tarjeta (PayPal) o si el último dígito de la tarjeta es par; si es impar, se rechaza.
-
Aprobado
payment-service descuenta el stock en event-service y confirma la reserva en booking-service. El pago queda APPROVED con un identificador de transacción y su fecha.
-
Rechazado o con error
El pago queda REJECTED con el motivo («Payment rejected by simulation», falta de stock, un servicio caído…) y la reserva se cancela.
-
Resultado
El servicio responde siempre 201 con el pago. El frontend quita del carrito las líneas aprobadas y recarga eventos y reservas.
| Estado | Reserva | Pago |
|---|---|---|
| Inicial | PENDING | PENDING |
| Aprobado | CONFIRMED | APPROVED |
| Rechazado | CANCELLED | REJECTED |
| Cancelada por el usuario | CANCELLED | Sin cambios |
Por dentro
Errores y validación
Todos los errores siguen el estándar RFC 9457 (application/problem+json) con
type, title, status, detail e
instance (la ruta pedida). Los errores de validación añaden una lista
errors con el campo y el mensaje.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Validation failed",
"instance": "/api/v1/users",
"errors": [{ "field": "contactEmail", "message": "…" }]
}
| Código | Cuándo |
|---|---|
| 400 | Validación, varios filtros a la vez o un parámetro de tipo incorrecto |
| 401 | Email o contraseña incorrectos al iniciar sesión |
| 404 | El recurso no existe |
| 409 | Conflicto, como un email repetido |
| 422 | Regla de negocio: fechas incoherentes, stock insuficiente… |
| 503 | Otro servicio no responde |
| 500 | Error inesperado |
Los mensajes de validación están en español; los de negocio, en inglés. En el frontend, cada error se convierte en un mensaje para el usuario: primero el del servidor y, si no hay, uno por defecto en español según el código (por ejemplo, para 503: «El servicio no está disponible…»).
Por dentro
Base de datos
Un único MySQL 8.0 con cuatro esquemas, uno por servicio: user,
event, booking y payment. La aplicación usa un usuario
de base de datos con permisos solo sobre esos cuatro esquemas. No hay migraciones: el esquema
se crea con un script al inicializar el contenedor.
- users
- Nombre, apellidos, fecha de nacimiento, teléfono, email (único), contraseña cifrada con BCrypt, rol (ADMIN o CUSTOMER) y fechas de creación y actualización.
- events
- Título, descripción, entradas disponibles, ubicación, inicio, fin, organizador, precio, categoría, email (único), teléfono e imagen.
- bookings
- Usuario y evento (con la copia de sus datos), cantidad, precio base, precio total y estado.
- payments
- Reserva, usuario, evento, cantidad, total, método, estado, identificador de transacción, mensaje de error y fecha de pago.
Datos iniciales
- 17 usuarios: 2 administradores y 15 clientes.
- 36 eventos en Madrid y alrededores, de música, deporte, gastronomía, cultura, salud, educación, tecnología y arte.
- Sin reservas ni pagos: se crean al usarla.
Por dentro
Sesión y roles
- Las contraseñas se guardan cifradas con BCrypt (coste 10). Los registros nunca imprimen contraseñas, tarjetas ni CVV: se muestran como
***. - El inicio de sesión devuelve los datos del usuario sin la contraseña. No hay tokens ni cookies.
- El frontend guarda la sesión en el navegador, la valida al cargar (si está corrupta la descarta) y no caduca.
- Hay dos roles: CUSTOMER y ADMIN.
- Las rutas protegidas te mandan a iniciar sesión y te devuelven después. Si no tienes el rol necesario, ves «Página no encontrada» y el contenido protegido ni se carga.
- Las redirecciones tras iniciar sesión solo aceptan rutas internas, nunca otra web.
Ojo: es un proyecto académico
Los permisos se aplican en la interfaz, pero el backend no comprueba quién llama. Está documentado en las limitaciones conocidas; los tokens JWT quedan como trabajo futuro.
Por dentro
Frontend por dentro
Estructura
src/app- Router, proveedores, layout, guards y páginas genéricas.
src/features- Una carpeta por dominio (admin, auth, bookings, cart, checkout, contact, events, tickets), cada una con
api,components,hooksypages. src/components/ui- Componentes base: botones, campos, tarjetas, insignias, esqueletos, pestañas, paginación, diálogos, paneles laterales y avisos.
src/lib- Cliente HTTP, errores, dinero, tema, animaciones, paginación y contraste.
Las dependencias van en un solo sentido: app → features → components → lib → types.
Datos y estado
- Un único cliente Axios con rutas relativas; toda respuesta se valida con Zod antes de usarse.
- TanStack Query para leer de la API: reintenta una vez si no hay respuesta o el servidor falla, nunca reintenta escrituras y no recarga al volver a la pestaña.
- Zustand para la sesión (guardada en el navegador), el carrito (en memoria) y el tema.
Tema, accesibilidad y rendimiento
- Tema claro y oscuro con tokens de color; un test comprueba que todos los textos cumplen un contraste WCAG de al menos 4,5:1 en ambos temas.
- Foco visible, diálogos y paneles que atrapan el foco y lo devuelven al cerrar, pestañas navegables con flechas y avisos anunciados a los lectores de pantalla.
- Con «movimiento reducido» activado en el sistema, las animaciones se reducen a fundidos cortos.
- Fuentes Inter y Space Grotesk servidas desde la propia app, sin Google Fonts.
- Cada página y las librerías de PDF y QR se cargan por separado; un script hace fallar el build si algún fragmento supera 500 kB.
- La interfaz está solo en español.
Modo demo
npm run build:demo genera la demo en dist-demo/: cambia el
adaptador de Axios por un backend en memoria (el mismo que usan las pruebas de Playwright) y
las rutas por rutas con #. El build normal no se ve afectado; una prueba lo
comprueba.
Por dentro
Infraestructura
| Contenedor | Imagen | Puerto en tu equipo |
|---|---|---|
| booqi-mysql | mysql:8.0 | 3307 |
| booqi-user-service | booqi/user-service | 8080 (con el override) |
| booqi-event-service | booqi/event-service | 8081 (con el override) |
| booqi-booking-service | booqi/booking-service | 8082 (con el override) |
| booqi-payment-service | booqi/payment-service | 8083 (con el override) |
| booqi-frontend | booqi/frontend | 80 o BOOQI_FRONTEND_PORT |
- Arrancan en orden y cada uno espera a que el anterior esté sano: MySQL → usuarios y eventos → reservas → pagos → frontend.
- Cada contenedor tiene su comprobación de salud y se reinicia solo si se cae.
- Los servicios se construyen con Maven sobre Temurin 17 y se ejecutan con un JRE 17 mínimo como usuario sin privilegios. El frontend se construye con Node 22 y se sirve con nginx.
- El fichero
docker-compose.override.ymlpublica los puertos 8080–8083 y activa el perfil de desarrollo; elMakefilesiempre lo incluye. - Los datos de MySQL se guardan en el volumen
booqi-mysql-data.
Variables de entorno
| Variable | Para qué | Ejemplo |
|---|---|---|
MYSQL_ROOT_PASSWORD | Contraseña de root de MySQL | change-me-root |
BOOQI_DB_USER | Usuario de la aplicación | booqi |
BOOQI_DB_PASSWORD | Su contraseña (sin comillas simples) | change-me-app |
BOOQI_CORS_ALLOWED_ORIGINS | Orígenes permitidos | http://localhost,…:5173 |
BOOQI_FRONTEND_PORT | Puerto del frontend | 80 |
BOOQI_TAG | Etiqueta de las imágenes | latest |
Perfiles de Spring
- por defecto
- MySQL en
localhost:3307, para ejecutar un servicio fuera de Docker. docker- Usa los nombres de los contenedores.
dev- Muestra el SQL y sube el nivel de log de la app.
test- Base H2 en memoria que imita MySQL.
nginx
Sirve la app y reenvía cada prefijo de la API a su servicio conservando el host con su
puerto, para que CORS funcione también fuera del puerto 80. Cualquier otra ruta devuelve
index.html, de modo que se puede recargar cualquier página de la app. Swagger y
actuator no pasan por el proxy: se abren en el puerto de cada servicio.
Proyecto
Pruebas y calidad
Backend
- Unas 456 pruebas: de controladores, de repositorios con H2, de servicios con Mockito, y de clientes HTTP, configuración, errores y mappers.
- Pruebas de mutación con PIT 1.17.0 sobre controladores, servicios, errores, configuración y clientes, con un objetivo de más del 95 %.
- Formato con google-java-format 1.35.0 (Spotless) y estilo con Checkstyle 12.3.1: líneas de hasta 120 caracteres, sin imports con
*ni sin usar, y sinSystem.outniprintStackTrace.
Frontend
- Más de 1000 pruebas con Vitest y Testing Library.
- Playwright: 30 pruebas de extremo a extremo con la API simulada, 31 contra el stack real y pruebas de la demo tal como se publica en GitHub Pages.
- Pruebas de mutación con Stryker 10.1.1, con un mínimo del 95 %; la última ejecución dio un 99,77 %.
- ESLint y Prettier.
Proyecto
CI y publicación
Cada push y pull request ejecuta en GitHub Actions:
- backend
- Los cuatro servicios en paralelo con Java 17: compilar, probar, formato y estilo (
./mvnw verify). - frontend
- Con Node 22: lint, formato, build, build de la demo y pruebas.
- compose
- Comprueba que la configuración de Docker Compose es válida.
Cada cambio en main publica en GitHub Pages la demo, en la raíz de
renzoramosdev.github.io/Booqui-TFG. La antigua
dirección /demo/ redirige a ella.
Proyecto
Desarrollo
| Comando | Qué hace |
|---|---|
make up | Construye y levanta el stack |
make down | Lo para |
make clean | Lo para y borra los datos |
make logs / make ps | Logs y estado de los contenedores |
make build | Empaqueta los servicios y construye el frontend |
make test | Pruebas de todos los servicios |
make lint / make format | Comprueba o aplica el formato y el estilo |
Por partes
- Un servicio:
cd services/<servicio>-service && ./mvnw spring-boot:run(JDK 17). - Frontend:
cd frontend && npm ci && npm run dev; Vite reenvía la API a los puertos 8080–8083. - Pruebas:
npm test,npm run test:e2eynpm run test:e2e:real(contra el stack levantado). - Mutación en un servicio:
./mvnw test-compile org.pitest:pitest-maven:mutationCoverage. - Demo publicada: tras
npm run build:demo,npm run serve:pagesla sirve como en GitHub Pages enhttp://localhost:4190/Booqui-TFG/, ynpm run test:pagesla prueba.
Los cambios se gestionan con OpenSpec: hay 19 capacidades especificadas en openspec/specs.
Proyecto
Decisiones de arquitectura
-
0001 · Monorepo
Carpetas
services,frontend,infraydocs. Sin librería común: el poco código compartido se duplica y se compara. -
0002 · API y errores
Rutas
/api/v1/<recurso>, filtros excluyentes, acciones con PATCH y errores RFC 9457. -
0003 · Capas planas
Controller, service y repository con records y MapStruct, en lugar de una arquitectura hexagonal con modelos triplicados.
-
0004 · BCrypt y login
Contraseñas con BCrypt sin toda la cadena de Spring Security, y un 401 genérico al fallar. JWT queda para el futuro.
-
0005 · Proxy nginx y Vite
El navegador siempre llama al mismo origen; nginx y Vite reenvían cada prefijo a su servicio.
Proyecto
Limitaciones conocidas
Booqi es un proyecto académico. Estas son sus limitaciones, documentadas en el repositorio:
- Seguridad: el backend no verifica quién hace cada llamada, así que cualquiera podría usar la API directamente (por ejemplo, cambiar un rol o confirmar una reserva).
- Pagos: repetir un pago descuenta el stock dos veces; el total no se contrasta con la reserva; y si la confirmación falla después de descontar stock, este no se devuelve.
- Stock: no hay bloqueo, así que dos compras simultáneas podrían vender la misma entrada; crear una reserva no comprueba el stock.
- Cancelaciones: no devuelven el dinero ni las entradas, y los estados de la reserva no están protegidos contra transiciones incoherentes.
- Categorías: algunos eventos de los datos iniciales usan categorías («Deportes», «Cultura», «Salud») que no están en la lista del filtro.
- No existen: recuperación de contraseña, envío de correos, servicio de contacto ni paginación en el servidor.
Proyecto
Problemas frecuentes
- No abre en el puerto 80
- Pasa con Podman rootless. Pon
BOOQI_FRONTEND_PORT=8088eninfra/.envy abrehttp://localhost:8088. - Cambié el esquema y no arranca
- El script de la base de datos solo se ejecuta la primera vez. Haz
make cleany luegomake up. - Tildes raras en los datos
- Si ves textos como «GastronomÃa», la base se cargó con una codificación distinta de UTF-8; recrea el volumen con
make clean. - Swagger no abre desde el frontend
- No pasa por el proxy: ábrelo en el puerto de cada servicio, por ejemplo
http://localhost:8081/swagger-ui.html. - Podman avisa del HEALTHCHECK
- Podman ignora el de los Dockerfile, pero Compose define los suyos, así que no afecta.
Proyecto
Créditos y licencia
Trabajo de fin de grado de Renzo Iván Ramos de los Ríos (RenzoRamosDEV) y Melanie Gabriela Cárdenas Hidalgo (MelGabi), publicado con licencia MIT.
↑ Volver arriba