Saltar a contenido

Diagramas de datos e infraestructura

Verificados contra el código el 2026-10-10 (modelos SQLAlchemy, compose, nginx, auth y manager). Solo hechos del código; las dudas van anotadas. Convención de dinero: céntimos enteros (price, total, *_cents).

1. Modelo de datos (ER simplificado)

Tablas leídas de backend/app/modules/*/infrastructure/models/. Se dibujan las FK reales; bills.session_token no tiene FK (solo índice) y payment_idempotency_keys es standalone con unique (scope, key).

erDiagram
    RESTAURANTS ||--o{ ROOMS : tiene
    ROOMS ||--o{ TABLES : contiene
    TABLES ||--o{ TABLE_SESSIONS : abre
    RESTAURANTS ||--o{ TABLE_SESSIONS : opera
    RESTAURANTS ||--o{ MENUS : publica
    MENUS ||--o{ CATEGORIES : agrupa
    CATEGORIES ||--o{ PRODUCTS : lista
    TABLE_SESSIONS ||--o{ CART_ITEMS : acumula
    PRODUCTS ||--o{ CART_ITEMS : referencia
    TABLE_SESSIONS ||--o{ ORDERS : genera
    ORDERS ||--o{ ORDER_LINES : detalla
    PRODUCTS ||--o{ ORDER_LINES : referencia
    TABLE_SESSIONS ||--o{ COMANDA_COURSE_PLAN : planifica
    RESTAURANTS ||--o{ BILLS : factura
    TABLES ||--o{ BILLS : consume
    BILLS ||--o{ BILL_PAYMENTS : cobra
    RESTAURANTS ||--o{ CONVERSATIONS : conversa
    CONVERSATIONS ||--o{ CHAT_MESSAGES : guarda
    CONVERSATIONS ||--o{ CHAT_LOGS : audita
    RESTAURANTS ||--o{ CONVERSATION_TREES : versiona
    CUSTOMERS ||--o{ ORDERS : pide
    CUSTOMERS ||--o{ CONVERSATIONS : chatea
    TABLE_SESSIONS ||--o{ REVIEWS : valora
    TABLE_SESSIONS ||--o{ MANAGER_CALLS : avisa
    ADMIN_USERS ||--o{ MANAGER_CALLS : atiende
    ADMIN_USERS }|--|{ RESTAURANTS : asignado
    RESTAURANTS ||--|| RESTAURANT_KNOWLEDGE : describe
    RESTAURANTS {
        int id PK
        string slug_uk "unique"
        float tax_rate
        string currency
    }
    TABLES {
        int id PK
        string qr_code_uk "unique"
        int default_customers
    }
    TABLE_SESSIONS {
        int id PK
        string session_token_uk "unique"
        string lifecycle_state
        int version
    }
    PRODUCTS {
        int id PK
        json name "i18n"
        int price "céntimos"
        string course_type
    }
    ORDERS {
        int id PK
        string status "draft..."
        int total "céntimos"
        int version
    }
    BILLS {
        int id PK
        int base_total_cents
        int tip_cents
        string status
    }
    CONVERSATION_TREES {
        int id PK
        int version
        string status "active único"
        json nodes
    }

Notas:

  • TableLifecycleState (manager, sin tabla propia) persiste en table_sessions.lifecycle_state: READING → DRINKS_ORDERED → DRINKS_DELIVERED → FOOD_ORDERED → FOOD_DELIVERED → BILL_REQUESTED → CLOSED, más DELAY_ALERT transversal (manager/domain/lifecycle.py).
  • Comanda vive en la tabla orders; ComandaLine en order_lines; comanda_model.py es solo shim de re-export.

2. Despliegue con Docker (referencia para redesplegar)

Servicios leídos de backend/docker-compose.yml (prod), backend/docker-compose.dev.yml, backend/Dockerfile (+.dev), frontend/Dockerfile y nginx/*.conf. VPS histórico dado de baja: el diagrama sirve para provisionar de nuevo.

flowchart TB
    subgraph HOST["Host Docker (a provisionar por la Sociedad)"]
        subgraph NET["Red bridge de compose"]
            PG[("postgres:15-alpine\n5432 · vol pgdata\nhealthcheck pg_isready")]
            BE["backend · python:3.12-slim\nuvicorn 0.0.0.0:8000\ntorch CPU · entrypoint migra\nEMBEDDINGS → /hf-cache"]
            FE["frontend · node:20-alpine\n.next/standalone · :3000\nUSER nextjs · healthcheck wget"]
        end
        HFCACHE[("vol camarero_ia_hf_cache\n/hf-cache · ~390 MB MiniLM")]
        PGDATA[("vol camarero_ia_postgres_data")]
        NGINX["nginx (referencia)\nupstreams 127.0.0.1:3000/8000\nVIP 79.117.123.235 · flag mantenimiento"]
    end
    BE --> PG
    BE --- HFCACHE
    PG --- PGDATA
    NGINX --> FE
    NGINX --> BE
    BE -.->|lifespan| JOBS["jobs: lifecycle 60 s\nworker recompute-recs"]

Notas:

  • Dev (docker-compose.dev.yml): sin hf_cache ni EMBEDDINGS_*, volumen .:/app con reload, userns_mode: keep-id.
  • TLS histórico: Let's Encrypt wildcard en n0idea.app.conf (TLS 1.2/1.3, HSTS); caducado con la baja del VPS.

3. Auth y multitenancy

Leído de auth/interface/{admin_router,customer_router}.py, app/core/deps.py, jose_token_service.py, restaurant_access_checker.py y app/core/bootstrap.py.

flowchart TB
    C([Comensal]) --> CR["POST /api/v1/auth/register|login\nGET /oauth/google (PKCE)\nGET /oauth/apple (state-only)\nPOST /oauth/callback"]
    A([Admin]) --> AR["GET /api/v1/admin/auth/google|apple\nPOST /api/v1/admin/auth/callback\nGET /api/v1/admin/me"]
    CR --> JWT["JWT HS256 · Bearer\ncustomer 7 días (sub=email)"]
    AR --> JWT24["JWT HS256 · Bearer\nadmin 24 h (sub/admin_id/role)"]
    JWT --> API["endpoints cliente\n(sesión QR)"]
    JWT24 --> GATE["require_restaurant_access\n404 / 403"]
    GATE --> OPS["operación del restaurante"]
    GATE -.->|SUPER_ADMIN| BYPASS["bypass sin asignación"]
    BOOT["bootstrap ADMIN_SUPER_EMAIL\noauth_id=bootstrap (main.py:177)"] -.-> ADMINU[("admin_users")]

Notas:

  • Apple id_token se verifica RS256 contra JWKS (apple_id_token.py, timeout 5 s); el fallo cierra (500), no degrada.
  • SSE de manager no usa Authorization (EventSource no envía headers): autentica por ?token=<jwt>.

4. Secuencia de control de sala

Leído de manager/interface/admin_manager_router.py (prefijo /api/v1/admin/manager/...), manager/interface/background.py (poll 60 s, delay comida 20 min, mesa silenciosa 15 min) y frontend/lib/hooks/useManagerSessionSync.ts.

sequenceDiagram
    participant M as Manager (SalaClient)
    participant B as Backend /manager
    participant BG as lifecycle background 60 s
    participant S as SSE manager:{id}
    M->>B: GET /restaurants/{id}/sessions
    B-->>M: snapshot mesas + lifecycle_state
    M->>S: GET /restaurants/{id}/events?token=jwt
    S-->>M: sala_snapshot + table_state_changed / silent_table_alert
    M->>B: POST /sessions/{token}/deliver
    M->>B: POST /sessions/{token}/courtesy-round
    M->>B: POST|GET /restaurants/{id}/kitchen/overload
    BG->>B: FOOD_ORDERED + food_ordered_at > 20 min
    B->>S: DELAY_ALERT
    BG->>B: DRINKS_DELIVERED + quiet > 15 min
    B->>S: silent_table_alert

5. Migraciones y seed

Cadena Alembic en backend/alembic/versions/ (34 ficheros, dos merges). Se aplican con alembic upgrade head y se siembran con scripts/seed_amici.py (reseed idempotente del restaurante Amici Madrid).

flowchart LR
    A["20260416 orderlines_json … 20260521 product_decision_engine"] --> B["20260603 índices + safety01 …"]
    B --> M1["20260604_merge_heads"]
    M1 --> C["20260604 payment · course_plan · rate_limit · model01 · rd04 · rd07 · soft_delete"]
    C --> M2["20260605_merge_rd_heads"]
    M2 --> D["20260605 rdfix02 … 20260614 table_lifecycle"]
    D --> SEED["seed_amici.py\nmenú · salas · mesas · KB"]