Documentación

Booqi, de punta a punta

Documentación de Booqi, un proyecto de .

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.

  • Java 17
  • Spring Boot 4.0.3
  • React 19
  • MySQL 8.0
  • Licencia MIT

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

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.

RolEmailContraseña
Clientecliente@booqi.demodemo1234
Administradoradmin@booqi.demodemo1234
  • 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 a index.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.

ComponenteDirección
Frontendhttp://localhost
user-servicehttp://localhost:8080
event-servicehttp://localhost:8081
booking-servicehttp://localhost:8082
payment-servicehttp://localhost:8083
MySQLlocalhost: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

RolEmailContraseña
ADMINmel@booqi.comadmin_mel
CUSTOMERandres.g@email.comcust_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

Detalle de un evento

Detalle de un evento en tema oscuro con precio, selector de cantidad y botón para añadir al carrito
Detalle de un evento en tema oscuro.
  • 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 disponiblesEtiqueta
0Agotado
1Última entrada
2 a 10Últimas N entradas
Más de 10N 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étodoDatos que pide
Tarjeta de créditoTitular, número, caducidad y CVV
Tarjeta de débitoTitular, número, caducidad y CVV
PayPalNada: 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/AA y 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 grupoCuándo
ConfirmadaTodas sus reservas están confirmadas
CanceladaTodas sus reservas están canceladas
PendienteCualquier 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

Panel de administración con métricas y la tabla de usuarios
Panel de administración: métricas y usuarios.

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:

CampoRegla
Título3 a 200 caracteres
DescripciónHasta 2000 caracteres
EntradasNúmero entero, 0 o más
Precio0 o más
Ubicación3 a 200 caracteres
InicioEn el futuro (solo al crear)
FinNo anterior al inicio
Organizador3 a 100 caracteres
CategoríaUna de la lista
Email de contactoVálido y único entre eventos
Teléfono9 a 15 dígitos
ImagenURL 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, /bookings y /payments a 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/ y docs/, con un Makefile en 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 health e info, 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étodoRutaQué hace
POST/usersCrea un usuario (CUSTOMER si no se indica rol)
GET/usersLista; 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/loginInicia 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étodoRutaQué hace
POST/eventsCrea un evento (409 si el email ya existe; 422 si el fin es anterior al inicio)
GET/eventsLista; 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=nDescuenta stock; 422 si la cantidad no es positiva o no hay bastantes entradas

booking-service · puerto 8082

MétodoRutaQué hace
POST/bookingsCrea una reserva pendiente con userId, eventId y ticketQuantity (1 o más)
GET/bookingsLista; filtros userId, eventId o status
GET/bookings/{id}Obtiene una reserva
PATCH/bookings/{id}/confirmLa confirma (si ya lo estaba, la devuelve igual)
PATCH/bookings/{id}/cancelLa 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étodoRutaQué hace
POST/paymentsProcesa un pago simulado; responde 201 aunque se rechace
GET/paymentsLista; 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 servicioLo que devuelve el que llama
5xx503 «X service is unavailable»
404404 con el mismo mensaje
Otro 4xx422 con el mismo mensaje
Sin respuesta o tiempo agotado503

Por dentro

Flujo de una compra

Lo que pasa, servicio a servicio, por cada línea del carrito:

  1. 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.

  2. Pago

    El frontend llama a POST /payments con el número de reserva y el total. payment-service guarda el pago como PENDING y simula medio segundo de procesamiento.

  3. 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.

  4. 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.

  5. 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.

  6. Resultado

    El servicio responde siempre 201 con el pago. El frontend quita del carrito las líneas aprobadas y recarga eventos y reservas.

EstadoReservaPago
InicialPENDINGPENDING
AprobadoCONFIRMEDAPPROVED
RechazadoCANCELLEDREJECTED
Cancelada por el usuarioCANCELLEDSin 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ódigoCuándo
400Validación, varios filtros a la vez o un parámetro de tipo incorrecto
401Email o contraseña incorrectos al iniciar sesión
404El recurso no existe
409Conflicto, como un email repetido
422Regla de negocio: fechas incoherentes, stock insuficiente…
503Otro servicio no responde
500Error 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, hooks y pages.
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

ContenedorImagenPuerto en tu equipo
booqi-mysqlmysql:8.03307
booqi-user-servicebooqi/user-service8080 (con el override)
booqi-event-servicebooqi/event-service8081 (con el override)
booqi-booking-servicebooqi/booking-service8082 (con el override)
booqi-payment-servicebooqi/payment-service8083 (con el override)
booqi-frontendbooqi/frontend80 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.yml publica los puertos 8080–8083 y activa el perfil de desarrollo; el Makefile siempre lo incluye.
  • Los datos de MySQL se guardan en el volumen booqi-mysql-data.

Variables de entorno

VariablePara quéEjemplo
MYSQL_ROOT_PASSWORDContraseña de root de MySQLchange-me-root
BOOQI_DB_USERUsuario de la aplicaciónbooqi
BOOQI_DB_PASSWORDSu contraseña (sin comillas simples)change-me-app
BOOQI_CORS_ALLOWED_ORIGINSOrígenes permitidoshttp://localhost,…:5173
BOOQI_FRONTEND_PORTPuerto del frontend80
BOOQI_TAGEtiqueta de las imágeneslatest

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 sin System.out ni printStackTrace.

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

ComandoQué hace
make upConstruye y levanta el stack
make downLo para
make cleanLo para y borra los datos
make logs / make psLogs y estado de los contenedores
make buildEmpaqueta los servicios y construye el frontend
make testPruebas de todos los servicios
make lint / make formatComprueba 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:e2e y npm 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:pages la sirve como en GitHub Pages en http://localhost:4190/Booqui-TFG/, y npm run test:pages la prueba.

Los cambios se gestionan con OpenSpec: hay 19 capacidades especificadas en openspec/specs.

Proyecto

Decisiones de arquitectura

  • 0001 · Monorepo

    Carpetas services, frontend, infra y docs. 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=8088 en infra/.env y abre http://localhost:8088.
Cambié el esquema y no arranca
El script de la base de datos solo se ejecuta la primera vez. Haz make clean y luego make 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