Arquitectura - FactoriaF5-Asturias/project-p5-digital-academy-team3-restaurant-frontend GitHub Wiki

Descripción de la organización del frontend, las capas que lo componen y cómo se comunican entre ellas.


Visión general

El frontend es una aplicación Single Page Application (SPA) construida con Vue 3 y Vite. La navegación entre vistas se gestiona con Vue Router sin recargar la página, y la comunicación con el backend se realiza mediante peticiones HTTP a una API REST.

El código se organiza por capas con responsabilidades separadas:

  • Las vistas representan páginas completas asociadas a una ruta.
  • Los componentes son piezas de interfaz reutilizables que se componen dentro de las vistas.
  • Los composables concentran lógica y estado compartido entre varios componentes.
  • Los servicios encapsulan las peticiones a la API.
  • El router define las rutas y controla el acceso según la sesión y el rol del usuario.

Capas

Views

Las vistas representan páginas completas y se asocian a una ruta. Orquestan componentes, gestionan la carga inicial de datos y manejan la lógica de presentación de su página. Viven en src/views/ y terminan en View.

Ejemplos: HomeView.vue, LoginView.vue, CartPageView.vue, AdminView.vue, KitchenDashboardView.vue.

Components

Los componentes son piezas de interfaz reutilizables. Se organizan por área funcional:

  • components/common/: compartidos entre varias vistas. Ej: MainHeader.vue, MainFooter.vue.
  • components/home/: específicos de la home. Ej: HeroSection.vue, ProductCard.vue, MenuSection.vue.
  • components/admin/: específicos del panel de administración.
  • components/kitchen/: específicos del dashboard de cocina.

Se comunican con el exterior mediante props (datos de padre a hijo) y emits (eventos de hijo a padre).

Composables

Los composables encapsulan lógica y estado que se comparte entre varios componentes. Viven en src/composables/ y usan el prefijo use.

  • useAuth.js: gestiona la sesión del usuario. Guarda y decodifica el token JWT, expone isAuthenticated, user, login, logout, changePassword y hasRole.
  • useCart.js: gestiona el carrito de la compra. Persiste los items en localStorage y expone items, addToCart, incrementQty, decrementQty, removeItem, clearCart y subtotal.

El estado a nivel de módulo (refs declaradas fuera de la función) hace que el estado sea compartido entre todos los componentes que usan el composable, sin necesidad de un store global.

Services

Los servicios encapsulan las peticiones HTTP a la API. Viven en src/services/ y cada servicio agrupa las operaciones de un dominio.

  • AuthService.js: login, changePassword.
  • ProductService.js: getProducts, getAdminProducts, getCategories, createProduct, updateProducts, deleteProduct.
  • CartService.js: createOrder.
  • OrderService.js: fetchOrders, updateOrderStatus.
  • ReportService.js: descarga de informes de ventas.

Los servicios usan fetch con VITE_API_URL como prefijo común. Cuando el endpoint requiere autenticación, incluyen la cabecera Authorization: Bearer <token> leída de localStorage.

Router

El router vive en src/router/. Define las rutas de la aplicación, asocia cada ruta a una vista y aplica guardas de navegación para controlar el acceso.

  • Las rutas protegidas declaran en su meta si requieren sesión (requiresAuth) y qué roles pueden acceder (roles).
  • El guard router.beforeEach comprueba la sesión y los roles del usuario antes de permitir la navegación.
  • Si no hay sesión, redirige a /login. Si el usuario no tiene el rol necesario, redirige a la home.

Stores

src/stores/ está reservada para stores de Pinia. Pinia está instalado y configurado, pero el estado global se gestiona actualmente con composables. La carpeta se usará si en el futuro se necesita un estado global más complejo (por ejemplo, con devtools o persistencia avanzada).


Comunicación entre capas

El flujo típico de un dato es:

View  →  Component  →  Composable / Service  →  API
  1. La vista carga los datos que necesita. Puede hacerlo directamente o a través de un componente.
  2. El componente muestra la información. Si necesita modificar el estado global (carrito, sesión), llama a un composable. Si necesita datos del backend, llama a un servicio.
  3. El servicio hace la petición HTTP y devuelve la respuesta. Lanza un error con el status si la respuesta no es correcta.
  4. El composable actualiza el estado global y notifica a los componentes que lo consumen. Los cambios se propagan por reactividad.

Ejemplos concretos:

  • Añadir producto al carrito: ProductCard.vue llama a addToCart de useCart. El estado del carrito cambia. MainHeader.vue, que también consume useCart, actualiza el contador automáticamente.
  • Login: LoginView.vue llama a login de useAuth. El composable usa AuthService para hacer la petición, guarda el token en localStorage y actualiza el estado. El router y el header reaccionan al cambio.
  • Cargar productos: MenuSection.vue llama a getProducts de ProductService. El servicio hace el fetch al backend y devuelve los productos. La vista los renderiza como tarjetas.

Estado global

El estado global se gestiona con composables a nivel de módulo, no con Pinia. Las refs se declaran fuera de la función que exporta el composable, así que son singleton y compartidas entre todos los componentes que las importan.

Ventajas de este patrón para el proyecto:

  • Menos configuración que Pinia.
  • Reactividad compartida directamente.
  • La lógica y el estado viven en el mismo archivo, fácil de seguir.

Limitaciones:

  • No hay devtools dedicadas como las de Pinia.
  • La persistencia hay que gestionarla a mano (por ejemplo, con watch y localStorage, como hace useCart).
  • Si el estado crece mucho, el composable se vuelve difícil de manejar. En ese caso, se migra a Pinia.

Servicios y comunicación con la API

Cada servicio encapsula las peticiones a un dominio funcional. Todos siguen el mismo patrón:

  • Leen la URL base de import.meta.env.VITE_API_URL.
  • Usan fetch con el método HTTP correspondiente.
  • Comprueban response.ok. Si no lo es, lanzan un Error con el status adjunto, para que el componente pueda diferenciar entre 401, 404, 409, etc.
  • Devuelven el resultado como JSON.
  • Añaden la cabecera Authorization: Bearer <token> cuando el endpoint lo requiere, leyendo el token de localStorage.

Este patrón mantiene los componentes ajenos al transporte: no saben si se usa fetch, axios u otro cliente. Solo llaman a una función y reciben datos o un error.


Autenticación y roles

  • Token JWT en localStorage bajo la clave giacobello-token.
  • useAuth decodifica el payload del token para extraer sub (username), roles y exp.
  • isAuthenticated es un computed que comprueba si hay token. No comprueba la expiración.
  • hasRole(role) comprueba si el usuario tiene un rol concreto.
  • Router guards: antes de cada navegación, el guard comprueba requiresAuth y roles en el meta de la ruta y redirige si no se cumplen.
  • Header dinámico: MainHeader.vue muestra "Login" o "Logout" y el icono de perfil según isAuthenticated. El icono de perfil apunta a /admin o /kitchen según el rol del usuario.

Estilos

Los estilos se construyen con Tailwind CSS v4. Los tokens de marca (colores y tipografías) se definen en src/styles/theme.css dentro de un bloque @theme.

  • Colores de marca: bg-bg-brand, text-text-brand, bg-bg-brand-darker, text-text-muted, etc.
  • Tipografías: font-display (Playfair Display) para títulos y font-sans (Inter) para texto de interfaz.

En los componentes se prefieren utilidades de Tailwind en el <template>. Solo se escribe CSS propio cuando hace falta y, en ese caso, se usa <style scoped> con @reference "../../main.css"; para que Tailwind resuelva los tokens.


Testing

  • Tests unitarios con Vitest y Vue Test Utils en tests/.
  • Tests end-to-end con Playwright en e2e/.
  • Cobertura mínima exigida: 70%.
  • Los tests unitarios mockean servicios, fetch, useAuth y vue-router según lo que se esté probando.
  • Los tests e2e mockean el backend con page.route() para no depender de Spring Boot corriendo.
⚠️ **GitHub.com Fallback** ⚠️