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.
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.
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.
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).
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, exponeisAuthenticated,user,login,logout,changePasswordyhasRole. -
useCart.js: gestiona el carrito de la compra. Persiste los items enlocalStoragey exponeitems,addToCart,incrementQty,decrementQty,removeItem,clearCartysubtotal.
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.
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.
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
metasi requieren sesión (requiresAuth) y qué roles pueden acceder (roles). - El guard
router.beforeEachcomprueba 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.
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).
El flujo típico de un dato es:
View → Component → Composable / Service → API
- La vista carga los datos que necesita. Puede hacerlo directamente o a través de un componente.
- 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.
- El servicio hace la petición HTTP y devuelve la respuesta. Lanza un error con el
statussi la respuesta no es correcta. - 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.vuellama aaddToCartdeuseCart. El estado del carrito cambia.MainHeader.vue, que también consumeuseCart, actualiza el contador automáticamente. -
Login:
LoginView.vuellama alogindeuseAuth. El composable usaAuthServicepara hacer la petición, guarda el token enlocalStoragey actualiza el estado. El router y el header reaccionan al cambio. -
Cargar productos:
MenuSection.vuellama agetProductsdeProductService. El servicio hace elfetchal backend y devuelve los productos. La vista los renderiza como tarjetas.
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
watchylocalStorage, como haceuseCart). - Si el estado crece mucho, el composable se vuelve difícil de manejar. En ese caso, se migra a Pinia.
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
fetchcon el método HTTP correspondiente. - Comprueban
response.ok. Si no lo es, lanzan unErrorcon elstatusadjunto, 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 delocalStorage.
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.
-
Token JWT en
localStoragebajo la clavegiacobello-token. -
useAuthdecodifica el payload del token para extraersub(username),rolesyexp. -
isAuthenticatedes uncomputedque 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
requiresAuthyrolesen elmetade la ruta y redirige si no se cumplen. -
Header dinámico:
MainHeader.vuemuestra "Login" o "Logout" y el icono de perfil segúnisAuthenticated. El icono de perfil apunta a/admino/kitchensegún el rol del usuario.
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 yfont-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.
-
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,useAuthyvue-routersegún lo que se esté probando. - Los tests e2e mockean el backend con
page.route()para no depender de Spring Boot corriendo.