Caso de estudio

GoPoli

App web de viajes compartidos para los estudiantes de una universidad pública de Medellín

Los conductores publican viajes con su ruta y sus cupos, los pasajeros se unen y cada viaje tiene su propio chat. La llevé de una app en Flutter dentro de un solo repositorio a una app web, una API y una imagen de base de datos, cada una probada y desplegada por su cuenta.

El problema

Los estudiantes del Politécnico Colombiano Jaime Isaza Cadavid necesitaban una forma de ofrecer y tomar viajes compartidos hacia el campus. GoPoli empezó como un proyecto de clase, con una app en Flutter y su backend en un solo repositorio.

Restricciones

  • Cuatro personas trabajando a medio tiempo.
  • Un solo servidor para la app web, la API y la base de datos.
  • Mapas y rutas sin claves de API pagas.

Lo que construí

  • Dividí el repositorio original en uno por componente, reescribiendo la historia para que cada uno conserve sus propios commits, y revisé toda la historia en busca de secretos filtrados.
  • Una imagen de PostgreSQL 16 que es dueña del esquema, con scripts de inicio numerados y datos de demostración opcionales. La API se valida contra ella y no arranca si algo no coincide.
  • Una API en Spring Boot 4 con JWT y BCrypt, con las reglas de negocio en clases de políticas pequeñas y fáciles de probar.
  • Una PWA en Next.js 16 con mapas en Leaflet y rutas de OSRM, que cae a una línea recta cuando el enrutador falla. El JWT vive solo en memoria.
  • La dirección de la API se inyecta al arrancar el contenedor, así que una sola imagen sirve para todos los entornos.
  • Cada imagen se publica en GHCR con SBOM y procedencia, y un solo workflow central despliega toda la app por SSH y verifica /health desde afuera.

Arquitectura

Cómo viaja una petición

La app web habla con la API a través de Nginx; a la base de datos solo se llega por la red interna.

  1. Navegador

    • PWA en Next.js 16
    • Leaflet con OpenStreetMap
    • Rutas de OSRM
  2. Servidor

    • Nginx con TLS
  3. Servicios

    • API en Spring Boot 4 con JWT y BCrypt
    • PostgreSQL 16, dueña del esquema

Entrega

  • Imágenes en GHCR con SBOM y procedencia
  • Un workflow despliega toda la app por SSH
  • Verificación pública de /health tras cada despliegue

Decisiones y sus costos

  1. La imagen de la base es dueña del esquema

    Cuando JPA crea las tablas, el esquema no queda en un archivo que se pueda revisar.

  2. El token solo en memoria

    Un token guardado en el almacenamiento del navegador queda al alcance de los scripts de la página.

  3. OpenStreetMap y OSRM público

    Los mapas y las rutas no podían depender de claves de API pagas.

Resultados

  • 41 pruebas en la API y 18 en la app web corren en CI, junto con CodeQL y la revisión de dependencias.
  • Una prueba de humo de la API con 31 pasos, del registro al historial de viajes, pasa en desarrollo y en la configuración de producción.
  • Comprobaciones de producción sin iniciar sesión, que revisan TLS, la redirección a HTTPS, /health, el 401 sin token y un CORS que solo acepta la app web.

Pendientes

  • Calificaciones entre compañeros de viaje. La primera app las tenía y la API todavía no las soporta, así que la app web las deja fuera.

Stack

Elige una tecnología para ver qué proyectos la usan.