Aplicación Angular para la digitalización de menús, gestión de pedidos en tiempo real y recomendaciones nutricionales impulsadas por IA.
- Descripción
- Características Principales
- Stack Tecnológico
- Arquitectura del Proyecto
- Estructura de Carpetas
- Instalación y Configuración
- Scripts Disponibles
- Sistema de Roles y Rutas Protegidas
- Módulo de Recomendación IA
- Gestión de Estado
- Guía de Estilo
- Documentación Técnica
SmartMenu es una Single Page Application (SPA) desarrollada con Angular 20 que digitaliza la experiencia completa de un restaurante: desde la carta interactiva y el pedido en mesa, hasta la gestión en cocina/barra y la administración del catálogo de productos.
El sistema integra un motor de recomendación nutricional basado en IA que analiza los datos biométricos del cliente (edad, peso, altura, objetivo nutricional y tipo de dieta) para sugerir combinaciones de menú personalizadas, calculando la ingesta calórica óptima y los macronutrientes.
Proyecto académico desarrollado en equipo como parte del ciclo formativo DAW (Desarrollo de Aplicaciones Web), conectado al backend SmartMenu Backend (Spring Boot + MongoDB).
| Característica | Descripción |
|---|---|
| 🤖 Recomendaciones por IA | El cliente introduce sus datos biométricos y recibe menús personalizados adaptados a sus objetivos nutricionales (perder peso, mantener, ganar músculo) y tipo de dieta (normal, vegetariana, vegana). |
| 📋 Carta digital interactiva | Catálogo de productos filtrable por categoría (Entrantes, Principales, Postres, Bebidas) con búsqueda en tiempo real y contador de calorías. |
| 🛒 Carrito persistente | Estado del pedido almacenado en localStorage, con soporte de múltiples rondas de comandas por mesa y registro de historial local. |
| 🍳 Panel de barra / cocina | Vista en tiempo real de los pedidos activos con actualización automática por polling cada 10 segundos. Permite avanzar el estado de cada pedido (RECIBIDO → EN_PREPARACION → LISTO → ENTREGADO). |
| 🔧 Panel de administración | CRUD completo de productos con formulario reactivo validado: nombre, precio, IVA, kcal, macronutrientes, alérgenos, etiquetas, imagen y disponibilidad. |
| 🔐 Control de acceso por roles | Guard de rutas que valida autenticación (JWT) y autorización (CLIENTE, EMPLEADO, EMPRESA) en cada navegación. |
| 📱 Diseño responsive | Interfaz oscura con tema dorado-accent (--accent: #d6b15e) optimizada para tablets de mesa y dispositivos móviles. |
| Paquete | Versión | Propósito |
|---|---|---|
@angular/core |
^20.3.0 | Framework principal (standalone components) |
@angular/router |
^20.3.0 | Enrutado SPA con lazy loading |
@angular/forms |
^20.3.0 | Formularios reactivos (ReactiveFormsModule) |
@angular/material |
^20.2.14 | Componentes UI (botones, modales, inputs) |
@angular/cdk |
^20.2.14 | Primitivas de comportamiento UI |
bootstrap |
^5.3.8 | Grid system y utilidades CSS |
rxjs |
~7.8.0 | Programación reactiva (Observables, polling) |
zone.js |
~0.15.0 | Detección de cambios de Angular |
| Paquete | Versión | Propósito |
|---|---|---|
@angular/cli |
^20.3.6 | Toolchain de Angular |
typescript |
~5.9.2 | Lenguaje de tipado estático |
typedoc |
^0.28.16 | Generación de documentación API |
karma + jasmine |
~6.4 / ~5.9 | Suite de testing unitario |
SmartMenu sigue una arquitectura en capas basada en el patrón Servicio → Store → Componente, con separación clara de responsabilidades:
┌─────────────────────────────────────────────────────────┐
│ CAPA DE PRESENTACIÓN │
│ Pages (login, menu, pedir, barra, admin…) │
│ Components reutilizables (Nav, Footer) │
└──────────────────────┬──────────────────────────────────┘
│ inyección de dependencias
┌──────────────────────▼──────────────────────────────────┐
│ CAPA DE ESTADO LOCAL │
│ PedidoStore → localStorage (carrito, │
│ mesa, historial de rondas, ID de pedido) │
└──────────────────────┬──────────────────────────────────┘
│ cuando el usuario confirma
┌──────────────────────▼──────────────────────────────────┐
│ CAPA DE SERVICIOS │
│ AuthService · MenuService · PedidoService │
│ AdminProductosService · RecommendationService │
└──────────────────────┬──────────────────────────────────┘
│ usa
┌──────────────────────▼──────────────────────────────────┐
│ CAPA HTTP │
│ ApiClient (wrapper centralizado de HttpClient) │
│ authInterceptor → inyecta JWT en cada petición │
└──────────────────────┬──────────────────────────────────┘
│ HTTPS
┌────────▼────────┐
│ Backend :9002 │
│ Spring Boot │
│ + MongoDB │
└─────────────────┘
- Standalone Components: todos los componentes y páginas son standalone (sin NgModules), siguiendo el modelo moderno de Angular 17+.
- Lazy Loading: cada ruta carga su componente bajo demanda con
loadComponent(), reduciendo el bundle inicial. - Centralización de endpoints: el objeto
endpointsenconfig/endpoints.tses la única fuente de verdad para las rutas API. Ningún servicio escribe URLs literales. - Interceptor transparente:
authInterceptorañade automáticamente el headerAuthorization: Bearer <token>sin que los servicios lo gestionen individualmente. - Guard de roles compuesto:
roleGuardvalida tanto autenticación (¿hay token?) como autorización (¿el rol del usuario está endata.roles?).
smart-menu-front-main/
├── src/
│ ├── app/
│ │ │
│ │ ├── api/ # 🌐 Capa de comunicación HTTP
│ │ │ ├── api-client.ts # Wrapper de HttpClient (get/post/put/patch/delete)
│ │ │ ├── auth-service.ts # Login, logout, token JWT, rol del usuario
│ │ │ ├── menu-service.ts # GET catálogo de productos
│ │ │ ├── pedido-service.ts # CRUD de pedidos y cambio de estado
│ │ │ ├── admin-productos-service.ts # CRUD de productos (panel admin)
│ │ │ ├── recommendation-service.ts # POST al motor de IA nutricional
│ │ │ └── service-call-service.ts # Llamada al camarero desde mesa
│ │ │
│ │ ├── http/ # 🔒 Interceptores HTTP
│ │ │ └── auth.interceptor.ts # Añade Authorization: Bearer <token>
│ │ │
│ │ ├── components/ # 🧩 Componentes reutilizables
│ │ │ ├── nav-component/ # Barra de navegación (adaptativa por rol)
│ │ │ └── footer-component/ # Pie de página
│ │ │
│ │ ├── config/ # ⚙️ Configuración global
│ │ │ ├── endpoints.ts # Diccionario centralizado de rutas API
│ │ │ └── app.config.ts # Providers de Angular (router, HTTP + interceptor)
│ │ │
│ │ ├── guards/ # 🛡️ Protección de rutas
│ │ │ └── role.guard.ts # Valida autenticación y autorización por rol
│ │ │
│ │ ├── models/ # 📐 Interfaces y tipos TypeScript
│ │ │ ├── auth.models.ts # User, AuthResponse, Role
│ │ │ ├── menu.models.ts # Producto, MenuResponse
│ │ │ ├── order.models.ts # OrderItem, CreateOrderRequest, OrderResponse
│ │ │ ├── producto.model.ts # Modelo extendido con macros nutricionales
│ │ │ └── recomendation.models.ts # DietType, GoalType, RecommendationRequest/Response
│ │ │
│ │ ├── pages/ # 📄 Vistas principales (lazy loaded)
│ │ │ ├── login/ # Formulario de acceso con JWT
│ │ │ ├── inicio/ # Dashboard de bienvenida post-login
│ │ │ ├── menu/ # Carta digital + carrito (modo ver / modo armar IA)
│ │ │ ├── pedir/ # Revisión del carrito y confirmación de pedido
│ │ │ ├── formulario-ia/ # Formulario biométrico + sugerencias del motor IA
│ │ │ ├── barra/ # Panel kanban de cocina con polling automático
│ │ │ ├── admin/ # CRUD de productos con formulario reactivo
│ │ │ └── pagina404/ # Página de error para rutas no encontradas
│ │ │
│ │ ├── services/ # 🔧 Lógica de negocio adicional
│ │ │ └── producto.ts # Transformaciones y helpers de productos
│ │ │
│ │ ├── state/ # 🗄️ Gestión de estado (localStorage)
│ │ │ └── pedido.store.ts # Carrito, mesa, ID pedido, historial de rondas
│ │ │
│ │ ├── app.routes.ts # 🗺️ Configuración del enrutado principal
│ │ ├── app.ts # Componente raíz
│ │ └── app.html / app.css # Template y estilos del root
│ │
│ ├── environment/
│ │ └── environment.ts # 🌍 Variables de entorno (apiUrl)
│ ├── styles.css # 🎨 CSS global + custom properties de diseño
│ ├── custom-theme.scss # Tema personalizado de Angular Material
│ ├── index.html
│ └── main.ts # Bootstrap de la aplicación
│
├── typedoc.json # Configuración de TypeDoc
├── angular.json
├── package.json
└── tsconfig.json
Asegúrate de tener instalado en tu sistema:
- Node.js ≥ 20.x → nodejs.org
- npm ≥ 8.x (incluido con Node.js)
- Angular CLI ≥ 20.x
# Instalar Angular CLI globalmente
npm install -g @angular/cli
# Verificar versiones
node --version # v20.x o superior
ng version # Angular CLI: 20.x# 1. Clonar el repositorio
git clone <url-del-repositorio>
cd smart-menu-front-main
# 2. Instalar dependencias
npm install
# 3. Arrancar en modo desarrollo
npm startLa aplicación estará disponible en http://localhost:4200
⚠️ El backend SmartMenu debe estar corriendo en el puerto configurado (por defecto9002) para que las llamadas HTTP funcionen correctamente.
El archivo src/environment/environment.ts centraliza la URL base de la API. Actualízala según el entorno de despliegue:
// Desarrollo local
export const environment = {
production: false,
apiUrl: 'http://localhost:9002',
};
// Producción
export const environment = {
production: true,
apiUrl: 'https://tu-servidor.com/api',
};El
authInterceptorusaenvironment.apiUrlpara determinar a qué peticiones añadir el header de autenticación. Mantener esta variable actualizada es crítico para el correcto funcionamiento en todos los entornos.
Todos los scripts se ejecutan desde la raíz del proyecto con npm run <script>:
# Inicia el servidor de desarrollo con hot reload
npm start
# Equivale a: ng serve — disponible en http://localhost:4200
# Compila la aplicación para producción (output: /dist)
npm run build
# Activa optimizaciones: tree-shaking, minificación, AOT
# Compila en modo watch para desarrollo iterativo
npm run watch
# Equivale a: ng build --watch --configuration development
# Ejecuta la suite de tests unitarios con Karma + Jasmine
npm test
# Abre Chrome con el informe de cobertura en tiempo real
# Genera la documentación técnica del proyecto con TypeDoc
npm run generate-docs
# Lee los comentarios JSDoc del código fuente
# y genera una referencia API navegable en HTMLnpm run build
# El output en /dist/ incluye:
# ├── index.html
# ├── main-[hash].js (bundle principal + lazy chunks)
# ├── polyfills-[hash].js
# └── styles-[hash].cssSmartMenu implementa un control de acceso basado en roles (RBAC) mediante el roleGuard. El guard evalúa dos condiciones en cada navegación:
- Autenticación: ¿existe un token JWT válido en
localStorage? - Autorización: ¿el rol del usuario está incluido en el array
data.rolesde la ruta?
// src/app/guards/role.guard.ts
export const roleGuard: CanActivateFn = (route) => {
if (!auth.isLoggedIn()) {
router.navigateByUrl('/login'); // Sin sesión → redirige al login
return false;
}
const rolesPermitidos = route.data['roles'] as string[];
const userRole = auth.getRole();
if (rolesPermitidos && !rolesPermitidos.includes(userRole)) {
router.navigateByUrl('/inicio'); // Sesión válida pero sin rol → redirige a inicio
return false;
}
return true;
};| Ruta | CLIENTE | EMPLEADO | EMPRESA |
|---|---|---|---|
/login |
✅ | ✅ | ✅ |
/inicio |
✅ | ✅ | ✅ |
/menu |
✅ | ✅ | ✅ |
/pedir |
✅ | ✅ | ✅ |
/formulario-ia |
✅ | ✅ | ✅ |
/barra |
❌ | ✅ | ✅ |
/admin |
❌ | ❌ | ✅ |
POST /auth/login { email, password }
│
▼
{ token, user: { id, nombre, rol, mesaId? } }
│
├──► localStorage.setItem('sm_token', token)
└──► localStorage.setItem('sm_user', JSON.stringify(user))
│
▼
authInterceptor actúa en cada petición saliente
└──► Authorization: Bearer <token>
El formulario IA (/formulario-ia) es el componente diferenciador de SmartMenu. Recoge datos biométricos del cliente y obtiene del backend un conjunto de menús personalizados calculados por el motor de IA.
// Tipos de dieta soportados
export enum DietType {
NORMAL = 'NORMAL',
VEGETARIANA = 'VEGETARIANA',
VEGANA = 'VEGANA',
}
// Objetivos nutricionales del cliente
export enum GoalType {
PERDER_PESO = 'PERDER_PESO',
MANTENER = 'MANTENER',
GANAR_MUSCULO = 'GANAR_MUSCULO',
}
// Payload enviado al endpoint POST /recommendations
export interface RecommendationRequest {
restauranteId?: string;
edad: number;
pesoKg: number;
alturaCm: number;
dieta: DietType;
objetivo: GoalType;
kcalObjetivo: number;
incluirBebida: boolean;
}
// Respuesta con menús sugeridos y totales nutricionales
export interface RecommendationResponse {
kcalObjetivo: number;
menus: MenuSuggestion[];
}Usuario rellena formulario biométrico (/formulario-ia)
│
▼
RecommendationService → POST /recommendations
│
▼
Backend calcula menús óptimos (proximidad calórica)
│
▼
Frontend muestra sugerencias con kcal y proteínas totales
│
Usuario selecciona un menú → "Ver en carta"
│
▼
Router.navigate(['/menu'], {
queryParams: {
modo: 'armar',
recomendados: 'id1,id2,id3',
kcal: 1850
}
})
│
▼
/menu activa el modo 'armar': resalta visualmente los
productos sugeridos y permite añadirlos al carrito.
El estado de la sesión del cliente se gestiona con PedidoStore, un servicio singleton que persiste en localStorage sin dependencias externas:
Clave localStorage |
Contenido | Gestionada por |
|---|---|---|
sm_token |
JWT de autenticación | AuthService |
sm_user |
Objeto usuario { id, nombre, rol, mesaId } |
AuthService |
sm_carrito |
Array ItemCarrito[] (productos en selección) |
PedidoStore |
sm_mesa |
Identificador de mesa | PedidoStore |
sm_pedido_id |
ID único de la sesión de pedido | PedidoStore |
sm_historial_pedidos |
Array de rondas de comandas enviadas | PedidoStore |
estado_actual |
Estado del pedido activo | PedidoStore |
// Carrito
obtenerItems(): ItemCarrito[]
guardarItems(items: ItemCarrito[]): void
totalItems(): number // Cantidad total de unidades
totalEuros(): number // Importe total del carrito
// Mesa y sesión
guardarMesa(mesa: string): void
obtenerMesa(): string
guardarIdPedido(id: string): void
obtenerIdPedido(): string // Genera uno aleatorio si no existe
// Estado y historial de rondas
guardarEstado(estado: string): void
obtenerEstado(): string
agregarAlHistorial(comanda: any): void
obtenerHistorial(): any[]
// Reset completo de la sesión de mesa
vaciar(): voidEl proyecto usa Prettier para el formateo automático y consistente del código. La configuración está definida en package.json:
"prettier": {
"printWidth": 100,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": { "parser": "angular" }
}
]
}Reglas clave a tener en cuenta:
- Comilla simple (
singleQuote: true) en todos los archivos TypeScript. - Longitud de línea máxima: 100 caracteres.
- Parser
angularpara templates HTML: respeta la sintaxis de bindings[()], directivas*ngIfy pipes.
El sistema de diseño se controla desde src/styles.css. Para personalizar la identidad visual del restaurante, modificar únicamente estas variables:
:root {
/* Fondos */
--bg: #0f1115; /* Fondo principal oscuro */
--surface: #161a22; /* Superficie de tarjetas */
--surface-2: #1c2230; /* Superficie secundaria / hover */
/* Acento de marca (cambiar para personalizar el restaurante) */
--accent: #d6b15e; /* Color principal de acción — dorado */
--accent-2: #b8923b; /* Dorado para estados hover/pressed */
/* Estados semánticos */
--danger: #ff5a5f;
--success: #2ecc71;
/* Tipografía */
--text: rgba(255, 255, 255, 0.92);
--muted: rgba(255, 255, 255, 0.65);
--border: rgba(255, 255, 255, 0.08);
/* Layout */
--radius: 18px;
--radius-lg: 24px;
--shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}| Elemento | Convención | Ejemplo |
|---|---|---|
| Componentes y clases | PascalCase |
FormularioIa, NavComponent |
| Servicios | PascalCase + sufijo Service |
AuthService, PedidoService |
| Interfaces y tipos | PascalCase |
ItemCarrito, AdminProducto |
| Variables y métodos | camelCase |
obtenerItems(), totalEuros() |
| Archivos | kebab-case |
pedido-service.ts, role.guard.ts |
| Rutas URL | kebab-case |
/formulario-ia, /pagina404 |
| Enums | PascalCase (clave) + UPPER_SNAKE_CASE (valor) |
DietType.VEGETARIANA |
El proyecto está documentado con TypeDoc, que genera una referencia API navegable en HTML a partir de los comentarios JSDoc del código fuente.
# Generar la documentación
npm run generate-docsLos servicios, stores, guards y modelos incluyen comentarios /** */ con descripción de métodos y parámetros, siguiendo el estándar JSDoc:
/**
* Recupera el ID del pedido o genera uno nuevo si no existe.
* @returns Identificador único de la sesión de pedido.
*/
obtenerIdPedido(): string { ... }
/**
* Añade una nueva comanda confirmada al historial local.
* @param comanda Objeto con los datos de la ronda enviada.
*/
agregarAlHistorial(comanda: any): void { ... }SmartMenu · Proyecto académico DAW · Angular 20 + Spring Boot + MongoDB