Volver al Inicio

Scoutcamp: reservas y gestión de campamentos scout

Scoutcamp: reservas y gestión de campamentos scout

Scoutcamp es el proyecto de fin de grado (TFG) con el que digitalicé el ciclo completo de reserva y gestión de campamentos scout: descubrir alojamientos, comprobar disponibilidad por fechas, reservar parcelas o bungalows, y que el propietario gestione solicitudes desde un panel propio. Sustituye hojas de cálculo, correos sueltos y confirmaciones manuales por un flujo centralizado en la web.

Demo en producción: scoutcamp.alejandroquintana.dev. Código en frontend y backend.

Dominio y actores

El modelo de datos gira en torno a:

  • Camping — ficha del campamento (ubicación con índice 2dsphere, imágenes, normas, horarios, métodos de pago, propietario).
  • CampingLodging — tipos de alojamiento: parcela (campsite), bungalow u other, con capacidad y tarifa por noche.
  • CampingUnit — unidades reservables concretas dentro de cada lodging.
  • Booking — reserva con fechas, unidades, responsable, coste total, método de pago y estado (pending, accepted, rejected, cancelled).
  • CampingRelation — favoritos y reseñas por usuario/camping.

Roles de usuario (RBAC sencillo pero efectivo):

  • user — busca campings, reserva, consulta sus bookings, conversaciones y perfil.
  • manager — crea y edita sus campings, gestiona lodgings/unidades, acepta o rechaza reservas, mensajería.
  • admin — panel global: usuarios, campings, reservas y conversaciones.
Scoutcamp — listado y búsqueda de campamentos scout

El reto técnico

  • Búsqueda contextual: filtrar por fechas de entrada/salida, ubicación geográfica ($geoNear) y disponibilidad real de unidades en ese rango.
  • Evitar sobreventa: al crear una reserva, validar que hay suficientes unidades libres antes de persistir.
  • Listados ricos: mostrar campings con alojamientos, capacidad agregada y valoraciones sin disparar consultas N+1 ingenuas.
  • Autorización por propietario: solo el owner del camping modifica fichas o cambia el estado de reservas ajenas.
  • Internacionalización: API con i18n (es, en, fr, de) para emails; frontend con @ngx-translate.
  • Medios: subida de imágenes a Google Cloud Storage vía modelo Document y Multer.

Arquitectura del sistema

SPA Angular 18 contra API REST Express 5 con prefijo /api/v1. Autenticación JWT: middleware checkUser decodifica el token en cada petición; authMiddleware protege rutas que exigen sesión.

flowchart TB
  subgraph client [Angular 18 SPA]
    Public[Listado y reserva]
    Manager[Panel manager]
    Admin[Panel admin]
  end
  subgraph api [Node Express 5]
    Auth[JWT checkUser]
    Routes[api/v1 routes]
    Ctrl[Controllers y validators]
  end
  subgraph data [Persistencia]
    Mongo[(MongoDB Mongoose 9)]
    GCS[Google Cloud Storage]
    Mail[Nodemailer]
  end
  Public --> Routes
  Manager --> Routes
  Admin --> Routes
  Routes --> Auth
  Routes --> Ctrl
  Ctrl --> Mongo
  Ctrl --> GCS
  Ctrl --> Mail
        

Stack tecnológico

  • Frontend: Angular 18, TypeScript 5.4, Angular Material, Bootstrap 5, RxJS, @ngx-translate, Google Maps, JWT con @auth0/angular-jwt.
  • Backend: Express 5, Mongoose 9, JWT, bcrypt, express-validator, Multer, @google-cloud/storage, i18n, Morgan, CORS.

Backend: API y lógica de negocio

Rutas principales montadas en index.js:

  • /api/v1/ — login y signup (auth.routes).
  • /api/v1/campings — CRUD de campings, lodgings, bookings, reseñas (público y autenticado según endpoint).
  • /api/v1/users — perfiles y reservas de usuario.
  • /api/v1/conversations — mensajería entre usuarios.
  • /api/v1/documents — subida y descarga de ficheros en GCS.

Todos los modelos extienden un databaseSchema base con método estático search(): paginación, ordenación, filtros y populate reutilizables en listados de admin y usuario.

Flujo de reserva (createBooking)

  1. El cliente envía fechas, lodgings solicitados, datos del responsable y método de pago.
  2. El servidor obtiene lodgings disponibles con CampingLodging.getAvailableLodgings y unidades libres con CampingUnit.getAvailableUnits.
  3. Si no hay stock suficiente para algún lodging, responde con error (no se guarda la reserva).
  4. Calcula totalCost (noches × tarifa × unidades) y crea el booking en estado pending.
  5. Envía email al propietario del camping en su idioma (i18n.setLocale + Nodemailer).
Scoutcamp — flujo de reserva y gestión de alojamientos

Agregaciones en listado de campings

Camping.getCampings construye un pipeline de agregación con $lookup a lodgings, units y relations; calcula valoración media; opcionalmente aplica $geoNear si hay coordenadas; y filtra campings con bookings que solapan las fechas buscadas. Así el listado público llega enriquecido en una sola ida a la base de datos.

Frontend: módulos y rutas

La app Angular se organiza por dominios:

  • campingcampings-list, ficha camping-view (galería, mapa, reseñas, booking), paso final camping-booking.
  • manager — mis campings, crear/editar camping, gestión de alojamientos y conversaciones.
  • admin — usuarios, campings, reservas y conversaciones globales.
  • auth / user — registro, login, perfil y área de usuario.

Componentes compartidos: buscador de campings, tablas reutilizables, panel lateral (left-menu), selector de idioma y barra de navegación.

Scoutcamp — panel de administración y gestión

Retos resueltos

1. Disponibilidad sin sobreventa

Problema: dos usuarios podrían intentar reservar la última parcela en las mismas fechas.

Solución: antes de booking.save(), comprobar que cada lodging solicitado tiene availables mayor o igual a la cantidad pedida; asignar solo unidades devueltas por getAvailableUnits. Si falla, lanzar Unauthorized sin escribir en base de datos.

2. Listados sin N+1 naive

Problema: mostrar campings con alojamientos, unidades y ratings en listados paginados con muchas consultas encadenadas.

Solución: aggregation pipeline en getCampings con $lookup, $addFields para capacidad total y media de reseñas, y filtro de bookings solapados por fechas.

3. Autorización y ownership

Problema: un usuario no debe modificar campings o reservas ajenas.

Solución: JWT en cabecera Authorization; comprobaciones camping.owner.equals(req.user._id) en gestión de bookings y edición de fichas; rol admin con acceso ampliado en listados de usuario.

4. Multilenguaje

Problema: propietarios y usuarios con distintos idiomas, especialmente en notificaciones por email.

Solución: paquete i18n en el backend (locales es, en, fr, de) para asuntos y cuerpos de correo; frontend con @ngx-translate y selector de idioma en la UI.

5. Imágenes y documentos

Problema: cada camping necesita galería de fotos almacenada de forma fiable.

Solución: subida con Multer al buffer, stream a bucket GCS (scoutcamp_bucket), metadatos en colección Document referenciados desde el camping.

Conclusión

Scoutcamp consolidó el diseño de una API REST con Mongoose, agregaciones para consultas complejas y una SPA Angular modular por roles. Como TFG, demostró que se puede cubrir búsqueda, reserva, gestión del propietario y administración global en un solo producto coherente, sin inflar el modelo con jerarquías organizativas que el dominio no requería.

Aprendizajes clave: modelar disponibilidad en el servidor, enriquecer listados con pipelines en lugar de joins manuales en el cliente, y separar claramente los módulos público, manager y admin en el frontend.