Arquitectura del sistema¶
Esta página explica cómo está organizado el backend de Camarero IA y por qué. Es una visión panorámica (Diátaxis: explanation): describe los principios, el mapa de módulos y los dos planos de comunicación, y enlaza a las páginas que detallan cada subsistema. Pensada tanto para personas como para LLMs que necesiten un modelo mental del sistema antes de tocar código.
Qué construimos¶
Camarero IA es un sistema de camarero virtual con IA. El recorrido del cliente:
- Escanea un QR en la mesa → se abre una sesión (
TableSession). - Conversa con la IA en un chat.
- La conversación produce una
Comanda(pedido) que pasa por un ciclo de vida con validación de IA y, según la política del restaurante, aprobación del manager. - Cocina la prepara, se sirve y se paga.
Stack: Python 3.12 · FastAPI · SQLAlchemy 2.x async · Alembic · Pydantic v2 ·
PostgreSQL (asyncpg).
Principio rector: hexagonal modular¶
El backend sigue arquitectura hexagonal (ports & adapters) aplicada por módulo
(decisión ADR-004). En lugar de una sola carpeta domain/ global, cada
módulo funcional es un subconjunto autocontenido con sus propias cuatro capas:
app/modules/{modulo}/
├── domain/ # Lógica de negocio pura: entidades, value objects,
│ # eventos de dominio, excepciones, puertos (ABCs)
├── application/ # Use cases, commands, queries, DTOs, puertos cross-module
├── infrastructure/ # Repos SQLAlchemy, modelos ORM, mappers, adapters externos
└── interface/ # Routers FastAPI + schemas Pydantic
Regla de dependencia¶
Cada capa solo importa de la capa inmediatamente interior. Es inviolable:
| Permitido | Prohibido |
|---|---|
| Interface → Application | Domain → cualquier capa exterior |
| Application → Domain | Application → Infrastructure (usa puertos, no implementaciones) |
| Infrastructure → Domain (implementa puertos) | Módulo A → Módulo B (import directo) |
Cómo detectar una violación
Si en cualquier archivo de domain/ aparece un import de sqlalchemy, fastapi,
pydantic o de otro módulo, es una violación de arquitectura. El gate de CI
make lint-arch (import-linter) la bloquea automáticamente.
Comunicación entre módulos¶
Los módulos no se importan entre sí directamente (ADR-004). Hay dos mecanismos:
- Puertos (síncrono, 1→1). El módulo que necesita algo declara un puerto en su propia
capa. La implementación (un gateway) vive en el módulo dueño del dato y se cablea en el
composition root (
app/main.py). Ejemplos reales:register_menu_catalog_gateway,register_session_cart_gateway,register_billing_gateway,register_restaurant_policy_gateway,register_coursing_gateway. - Eventos de dominio (asíncrono, 1→N) (ADR-005). Los use cases publican
eventos en
event_bus(app/shared/domain/event_bus.py); los subscribers reaccionan, registrados víaapp/core/event_wiring.py. Los eventos se publican después del commit.
Bootstrap autorizado a cruzar módulos
Solo tres archivos pueden importar de todos los módulos, porque su trabajo es
ensamblar la app, no contener lógica: app/main.py (routers + cableado de puertos),
app/core/event_wiring.py (subscribers) y el worker runner. Cualquier cuarto candidato
requiere un ADR.
Lo transversal sin dueño de dominio vive en app/shared/ (clases base, paginación, i18n,
money, identidad AdminUser, puertos infraestructurales, seam del POS/TPV) y el bootstrap
en app/core/ (config, BD, lifespan, middleware, observabilidad). Ver
Servicios de plataforma.
Mapa de módulos¶
Cada módulo en app/modules/ y su responsabilidad principal (todos registrados en
app/main.py). La columna Detalle enlaza a la página de arquitectura del subsistema
cuando existe.
| Módulo | Responsabilidad | Detalle |
|---|---|---|
auth |
Autenticación JWT de cliente y admin; autenticadores OAuth (Jose). | Seguridad |
admin_user |
Gestión de usuarios administradores, roles y permisos. | Usuarios admin y roles |
restaurant |
Restaurantes, salas (Room), conocimiento, políticas de aprobación. |
Mesas y salas |
table |
Mesas y códigos QR (acceso público y admin). | Mesas y salas |
session |
TableSession (sesión de mesa), carrito (CartItem), llamadas al manager. |
Sesiones y carrito |
menu |
Menús, categorías, productos, extras, promociones, conocimiento. | Catálogo de menú |
chat |
Conversación con IA (motor legacy + híbrido), árboles de decisión. | Chat híbrido |
comanda |
Pedido (Comanda/ComandaLine); state machine y ciclo de vida. |
Comanda |
recommendation |
Recomendador determinista (capas 0–2) con artefactos precalculados. | Recomendador |
manager |
"Control de sala": dashboard del manager en tiempo real (SSE). | Control de sala |
payment |
Facturas (Bill) y pagos; pasarela PSP. |
Pagos |
review |
Valoraciones del cliente y listado admin. | Clientes y reseñas |
analytics |
Eventos de comportamiento (capa de medición) y analítica admin. | Servicios de plataforma |
audit |
Registro de auditoría de operaciones admin. | Servicios de plataforma |
customer |
Datos del cliente final. | Clientes y reseñas |
errors |
Registro y consulta de logs de error. | Servicios de plataforma |
El detalle por módulo (símbolos, repos, puertos) está en el Mapa de módulos.
Los dos planos de comunicación¶
El sistema habla con el frontend por dos canales complementarios:
1. REST / OpenAPI (petición-respuesta)¶
Routers FastAPI bajo /api/v1 (cliente) y /api/v1/admin (admin), descritos por el
contrato OpenAPI. Es el plano de comandos y consultas síncronas: validar sesión, añadir
al carrito, enviar comanda, gestionar menú, aprobar pedidos, etc. Ver la
referencia de la API.
El contrato OpenAPI es un activo protegido
Existe una red de tests contract-lock (tests/contract/). Las adiciones aditivas
(campos nuevos) se permiten siempre que se cablee el frontend en el mismo cambio; los
removals / renames / cambios de tipo de campos existentes rompen clientes desplegados
y requieren cuidado. Cómo evolucionarlo: Evolucionar el contrato.
2. SSE (tiempo real, servidor → cliente)¶
Server-Sent Events (Content-Type: text/event-stream) para flujos unidireccionales que
el servidor empuja: el streaming de la respuesta del chat y las actualizaciones en vivo del
dashboard del manager. El contrato SSE (nombres de evento + forma del payload) también está
versionado y testeado. Ver Tiempo real (SSE) y el
inventario de eventos SSE.
Ciclo de vida de una petición de chat¶
Cuando el cliente envía un mensaje, la respuesta llega por streaming SSE:
- El cliente hace
POSTal router de chat (prefijo real/api/v1/chat/conversations/...). El handler deinterface/traduce la request a un command y delega en el use case. - El use case (
StreamAssistantReplyUseCase) orquesta el dominio y abre un stream SSE. - Un dispatcher por modo (
ModeDispatchingChatAiResponder) elige el motor segúnrestaurants.chat_mode:legacy(por defecto) ohybrid. Es fail-open a legacy y el rollback es instantáneo volviendo el flag. - El motor emite frames SSE:
content(texto),: thinking(keep-alive) y un framedoneconcart_actions+course_actions. El motor híbrido añade, de forma aditiva, un framerecommendationcon tarjetas estructuradas. - Las acciones de carrito/coursing detectadas se ejecutan vía puertos hacia
sessionycomanda(el chat no importa esos módulos directamente). - Tras el stream, los datos estructurados por-mensaje se persisten en
chat_messages.extra_datay se publican por el bus para que el frontend los reciba.
El detalle (motores, árbol de decisión, recomendador) está en Chat híbrido.
Ciclo de vida de una comanda¶
La Comanda avanza por una state machine explícita con guards en el dominio
(comanda/domain/state_machine.py). Camino feliz:
- DRAFT. Borrador editable mientras el cliente compone el pedido.
- AI_REVIEWING. Validación de IA. Es fail-closed: ante fallo se rechaza o se escala al manager, nunca se auto-aprueba.
- PENDING_MANAGER. Espera aprobación humana, según la política del restaurante (
full/ai_only/none, leída vía puerto derestaurant). - PENDING → IN_PROGRESS → READY → SERVED. La cocina la prepara, se marca lista y se sirve.
- CLOSED. Cerrada (alimenta el flujo de pago).
Los estados de rechazo (AI_REJECTED, MANAGER_REJECTED) y DRAFT son editables: el
cliente puede modificar y reenviar. La comanda usa optimistic locking (columna version)
para concurrencia. Los hijos (ComandaLine) se modifican solo vía la raíz del agregado.
Detalle completo en Comanda.
Resiliencia y seguridad (resumen)¶
- Errores tipados. Las excepciones de dominio heredan de
DomainException; los handlers globales deapp/main.pylas traducen a HTTP (404, 403, 409, 422, 400).HTTPExceptionsolo eninterface/. Hay handlers específicos para conflictos de comanda (ComandaConflict,InvalidComandaTransition→ 409) y de mesa. - Multitenancy. Todo endpoint admin con scope pasa por
require_restaurant_access;SUPER_ADMINlo bypassa. Nunca se confía en elrestaurant_iddel body. - Idempotencia.
Idempotency-Keyen POST críticos (validar sesión, añadir al carrito, llamada al manager, pago). - Rate limiting persistente. Middleware
RateLimitMiddlewarecon backend Redis/Valkey si está configurado, BD en su defecto (no in-memory). - Trazabilidad.
RequestIDMiddleware(capa más externa) acuña/propagaX-Request-ID. - Dinero en céntimos (int) en BD; value object
Moneyen el dominio.
Bootstrap y arranque¶
El composition root (app/main.py) ensambla la app: registra routers, cablea todos los
puertos cross-module y los handlers de excepción. En el lifespan:
- Crea las tablas y siembra el
SUPER_ADMINsi hace falta (bootstrap_superadmin_if_needed). - Cablea los subscribers de eventos (
wire_subscribers). - Si
embeddings_enabled, hace warmup de los embeddings Granite (fire-and-forget). - Lanza el monitor de fondo del "control de sala" (
run_lifecycle_background_task).
El seam del POS/TPV se inyecta aquí: por defecto LocalPOSAdapter responde el contrato
POSAdapter desde las propias gateways de la plataforma (sin TPV externo). Detalle en
Servicios de plataforma.
Páginas relacionadas¶
- Hexagonal (ADR-004 / ADR-005) — capas, puertos y eventos en detalle.
- Tiempo real (SSE) — el plano de eventos servidor → cliente.
- Sesiones y carrito —
TableSessionyCartItem. - Comanda — state machine y ciclo de vida del pedido.
- Chat híbrido — motores legacy/hybrid, árbol de decisión.
- Control de sala — dashboard del manager en tiempo real.
- Recomendador — capas y artefactos precalculados.
- Pagos — facturas y pasarela.
- Catálogo de menú — menús, categorías, productos, extras y promociones.
- Mesas y salas — restaurantes,
Room, mesas y códigos QR. - Clientes y reseñas — datos del cliente final y valoraciones.
- Usuarios admin y roles — gestión de administradores y permisos.
- Servicios de plataforma —
shared/core, analytics, audit, errors, POS, rate limiting. - Seguridad y multitenancy.
- Frontend — cómo consume el frontend ambos planos.
- Referencia: API REST · Eventos SSE · Mapa de módulos · Convenciones · Configuración.
- Cómo: Añadir un módulo · Evolucionar el contrato · Desplegar.