Diagramas de arquitectura del backend¶
Verificados contra el código de backend/app/ el 2026-10-10. Todos los nombres de módulos, routers, endpoints y transiciones son los reales.
1. Mapa de módulos hexagonales¶
Los 16 módulos de backend/app/modules/ siguen la estructura interface / application / domain / infrastructure (verificado en chat, comanda y payment). No hay imports cross-module (ADR-004): se comunican por puertos y eventos de dominio. app/shared/ contiene código transversal, app/core/ el bootstrap y app/worker/ el runner recompute-recs.
flowchart TB
subgraph MOD["app/modules/ (16 módulos hexagonales)"]
direction LR
auth --- session
session --- table
table --- menu
menu --- chat
chat --- comanda
comanda --- payment
payment --- customer
customer --- restaurant
restaurant --- review
review --- recommendation
recommendation --- analytics
analytics --- manager
manager --- admin_user
admin_user --- audit
audit --- errors
end
CORE["app/core/<br/>config · database · deps<br/>event_wiring · permissions"]
SHARED["app/shared/<br/>domain · application<br/>infrastructure · identity"]
WORKER["app/worker/runner.py<br/>recompute-recs"]
API["app/api/ + app/main.py<br/>montaje de routers"]
MOD -.->|puertos / eventos| SHARED
MOD --> API
CORE --> MOD
CORE --> API
WORKER -->|usa| MOD
WORKER -->|usa| SHARED
2. State machine de comanda¶
Fuente de verdad: comanda/domain/state_machine.py (ComandaStateMachine, match/case exhaustivo). Existe un duplicado legacy en comanda/infrastructure/comanda_service.py (VALID_TRANSITIONS) que es más restrictivo (ver nota).
stateDiagram-v2
[*] --> DRAFT
DRAFT --> AI_REVIEWING
DRAFT --> PENDING : modo none RD04
DRAFT --> CANCELED
AI_REVIEWING --> AI_REJECTED
AI_REVIEWING --> PENDING_MANAGER : modo full
AI_REVIEWING --> PENDING : modo ai_only RD04
AI_REVIEWING --> CANCELED
AI_REJECTED --> DRAFT
AI_REJECTED --> CANCELED
PENDING_MANAGER --> MANAGER_REJECTED
PENDING_MANAGER --> PENDING : aprobada
PENDING_MANAGER --> CANCELED
MANAGER_REJECTED --> DRAFT
MANAGER_REJECTED --> CANCELED
PENDING --> IN_PROGRESS
PENDING --> CANCELED
IN_PROGRESS --> READY
IN_PROGRESS --> SERVED : entrega directa
IN_PROGRESS --> CANCELED
READY --> SERVED
READY --> CANCELED
SERVED --> CLOSED
CLOSED --> [*]
Nota:
VALID_TRANSITIONS(infraestructura) no incluye los atajos RD04DRAFT→PENDINGniAI_REVIEWING→PENDING, que sí existen en el dominio. El diagrama dibuja el dominio, que es la regla vigente.
3. Flujo de pago¶
Router payment/interface/router.py (prefijo /sessions): POST /{token}/bill (request), GET /{token}/bill (get), PATCH /{token}/bill (configure split+tip), POST /{token}/bill/payments (pay, con header Idempotency-Key, TTL 24h). Use cases en payment/application/use_cases/ y FakePaymentGateway en payment/infrastructure/gateways/fake_payment_gateway.py.
sequenceDiagram
participant C as Cliente
participant R as payment/router.py<br/>(prefix /sessions)
participant I as PaymentIdempotencyStore<br/>(claim/complete)
participant U as Use cases<br/>Request/Get/Configure/PayBill
participant G as FakePaymentGateway<br/>IPaymentGateway
participant B as SqlAlchemyBillRepository
C->>R: POST /{token}/bill (request_bill)
R->>U: RequestBillUseCase.execute()
U->>B: crea o refresca bill
C->>R: GET /{token}/bill (get_bill)
R->>U: GetBillUseCase.execute()
C->>R: PATCH /{token}/bill (split_mode + tip)
R->>U: ConfigureBillUseCase.execute()
Note over R,U: 422 tip inválido · 400 split inválido<br/>404 sin bill · 409 bill PAID
C->>R: POST /{token}/bill/payments + Idempotency-Key
R->>I: claim(scope=pay_bill, key)
alt duplicado en TTL
I-->>R: respuesta cacheada (replay)
else primera vez
R->>U: PayBillUseCase.execute()
U->>G: charge(amount_cents)
G-->>U: ref / error FAIL-CLOSED 502
U->>B: registra pago (402 si excede resto)
R->>I: complete(scope, key, 201, body)
end
R-->>C: 201 PayBillResponse + bill
4. Flujo QR → sesión → carrito → comanda¶
Routers reales: table/interface/qr_router.py (/admin/qr, POST /generate, POST /validate), session/interface/sessions_router.py (prefijo /sessions: POST /peek, POST /validate, GET /{token}, PATCH /{token}/guest-count), session/interface/cart_router.py (prefijo /sessions: GET /{token}/cart, POST /{token}/cart/items, PATCH/DELETE .../items/{item_id}), comanda/interface/customer_router.py (prefijo /sessions: GET /{token}/draft, POST /{token}/lines, POST /{token}/submit, POST /{token}/cancel).
sequenceDiagram
participant M as Mesa QR (JWT HMAC)
participant T as table/qr + public_tables<br/>admin/qr + GET /tables/{qr}
participant S as session/sessions_router<br/>POST /sessions/validate
participant K as session/cart_router<br/>/sessions/{token}/cart
participant O as comanda/customer_router<br/>/sessions/{token}/...
M->>T: escanea QR → POST /admin/qr/validate
T-->>M: table_id + restaurant_id
M->>S: POST /sessions/validate (qr_jwt, guest_count)
Note over S: idempotente natural:<br/>reintento reusa session_token<br/>is_new_session=false
S-->>M: session_token + expires_at
M->>K: POST /{token}/cart/items + Idempotency-Key
K-->>M: 201 CartItemResponse (id UUID str)
M->>K: PATCH /{token}/cart/items/{id} (cantidad)
M->>O: POST /{token}/lines (carrito → draft)
O-->>M: Comanda DRAFT
M->>O: POST /{token}/submit (revisión IA/manager)
M->>O: GET /{token}/ticket · GET /{token}/tab
5. Capas hexagonales de un módulo (comanda)¶
Ejemplo con ficheros reales de backend/app/modules/comanda/. Las dependencias apuntan siempre hacia dentro: interface → application → domain ← infrastructure.
flowchart LR
subgraph INT["interface/"]
CR["customer_router.py<br/>GET draft · POST lines/submit"]
AR["admin_orders_router.py"]
VR["order_validation_router.py"]
end
subgraph APP["application/"]
UC["use_cases/"]
CMD["commands.py · serializers.py"]
end
subgraph DOM["domain/"]
SM["state_machine.py<br/>ComandaStateMachine"]
EN["entities.py · value_objects.py<br/>ComandaStatus"]
PT["ports.py · repository.py"]
end
subgraph INF["infrastructure/"]
SVC["comanda_service.py<br/>VALID_TRANSITIONS legacy"]
REPO["repositories/ · orm.py · models/"]
SUB["subscribers.py"]
end
CR --> UC
AR --> UC
VR --> UC
UC --> CMD
UC --> PT
CMD --> EN
REPO -.->|implementa| PT
SVC -.->|usa| SM
SUB -.->|escucha| DOM