Saltar a contenido

Comunicación POS ↔ Backend

Cómo se hablan la terminal y el backend hoy, qué cambió respecto del pasado, y qué patrones de resiliencia hacen que el checkout no se rompa cuando la red tiembla.

El canal: REST sobre /autocompras/v1

La terminal habla con el backend por HTTP REST. Todas las mutaciones del ticket son POST bajo el context-path /autocompras/v1. No hay WebSocket ni STOMP en el flujo actual.

sequenceDiagram
    participant POS as Terminal (BackendClientBloc)
    participant API as Backend (tickets)

    Note over POS,API: Mutaciones de ticket (REST)
    POS->>API: POST /openTicket
    API-->>POS: TicketDto
    POS->>API: POST /agregarArticulo
    API-->>POS: TicketDto recalculado
    POS->>API: POST /payTicket
    API-->>POS: TicketDto (PAGADO / VOUCHERPENDING)
    POS->>API: POST /closeTicket
    API-->>POS: TicketDto (CLOSE)

    Note over POS,API: Salud de servicios (polling)
    loop cada 60 s (sano) / 15 s (degradado)
        POS->>API: GET /status
        API-->>POS: { servicesOnline, offlineServices }
    end

Quién orquesta esto en la terminal

El BackendClientBloc (en lib/viewmodels/backend_client/) es el centro de la comunicación:

  • Mutaciones: el evento SendMessage(destination, request, …) despacha cada operación de ticket al TicketService, que hace el POST correspondiente.
  • Salud: un Timer adaptativo dispara CheckStatusServices, que consulta /status vía StatusRepository. Si los servicios están sanos, el timer se reprograma a 60 s; si están degradados, baja a 15 s para detectar la recuperación más rápido.

Identidad de la terminal en cada request

El HttpCliente (en lib/services/http/http_cliente.dart) inyecta automáticamente tres headers en cada llamada:

Header Para qué
X-Cod-Terminal Identifica la terminal.
X-Terminal-Uuid UUID del device; el backend lo valida contra la tabla pos.
X-Device-Health Última salud de los dispositivos de pago (pinpad/Point), insumo del Cockpit.

Resiliencia: por qué no se rompe el checkout

La terminal opera en un comercio real, con red que se cae. Los mecanismos que la sostienen:

  • Polling adaptativo de salud. La terminal siempre sabe si el backend está sano o degradado, y reacciona (60 s ↔ 15 s).
  • ConnectionRecoveryMixin (en las pantallas de pago QR y Point Smart): maneja timeout + reintento automático de la consulta de transacción, y navega al resultado correcto sin dejar la pantalla colgada.
  • Idempotencia de pago (paymentAttemptId). Se genera una vez por intención de pago y se reusa en los reintentos del mismo intento. Así, si la red corta entre "cobré" y "recibí la confirmación", reintentar no genera un doble cobro: el backend deduplica.
  • Recuperación de pagos in-flight. Al arrancar, la terminal revisa la entidad IsarPendingPayment (persistida en Isar) para recuperar pagos que quedaron sin resolver en la sesión anterior. Un corte de luz en medio de un cobro no deja la plata en el limbo.
  • Manejo de errores de transporte. El HttpCliente traduce SocketException (sin conexión), TimeoutException y HandshakeException (mismatch http/https) a fallas de dominio claras, en vez de propagar excepciones crudas.

El detalle más delicado está en los pagos

La idempotencia y la máquina de estados de Point Smart (created → at_terminal → processed/failed/action_required/expired/...) son lo más fácil de romper. Antes de tocar cualquier flujo de cobro, leé Referencia → Pagos entero.

Lo que cambió: de STOMP a REST

Históricamente la terminal usaba STOMP/WebSocket (un broker, suscripciones a tópicos como /topic/status, mutaciones por mensajes). Ese modelo se reemplazó por REST por la fase del cutover descrita en MIGRACION_BLOC_REST.md:

  • Las mutaciones pasaron de mensajes STOMP a POST REST (uno por operación).
  • El estado de servicios pasó de una suscripción push a un polling adaptativo.
graph LR
    subgraph Antes["Antes (STOMP)"]
        A1["mensajes a /app/*"] --> A2["broker"]
        A2 --> A3["suscripción /topic/status"]
    end
    subgraph Ahora["Ahora (REST)"]
        B1["POST /autocompras/v1/*"] --> B2["respuesta directa"]
        B3["GET /status (polling)"]
    end

Vestigios que vas a encontrar

  • Backend: TerminalSessionService existe pero está muerto; no hay @MessageMapping ni config de broker.
  • Terminal: quedan enums y comentarios que nombran STOMP, y la dependencia stomp_dart_client todavía está en pubspec.yaml. La Fase 3 de la migración es eliminarlos.

Ninguno de esos vestigios participa del flujo real. La verdad operativa de hoy es REST.