Saltar a contenido

Documento histórico

Este documento se conserva solo para trazabilidad. Consulte el índice maestro para la fuente vigente.

Documento histórico — no usar como fuente de decisiones actuales

Reemplazado por la versión canónica del 2026-07-11. Conservar solo como trazabilidad histórica.


Diagrama de secuencia — Pago y despacho de envío

Diagrama UML de secuencia para el flujo completo de pago y despacho, que incluye al usuario (pago) y al operador (despacho).


Diagrama completo

sequenceDiagram
    autonumber
    actor U as Usuario
    participant App as App
    participant API as FastAPI
    participant Auth as Auth Service
    participant ShipSvc as Shipment Service
    participant QuoteSvc as Quote Service
    participant PaySvc as Payment Service
    participant LockSvc as Redis Lock
    participant DB as PostgreSQL
    participant Notif as Notification Service

    actor OP as Operador
    participant Back as Backoffice

    Note over U,OP: Parte A: Usuario paga
    U->>App: Abre cotización pendiente
    App->>API: GET /quotes/{id}
    API->>Auth: Validar JWT
    API->>QuoteSvc: getQuote(quote_id, user_id)
    QuoteSvc->>DB: SELECT * FROM quotes WHERE id = ?
    DB-->>QuoteSvc: quote
    QuoteSvc-->>API: quote
    API-->>App: quote (vigente?)

    alt Cotización expirada
        App-->>U: "Cotización expirada, se recalculará"
        App->>API: POST /quotes/{id}/recalculate
        API->>QuoteSvc: recalculate
        QuoteSvc-->>API: nueva quote
    end

    U->>App: Toca "Pagar"
    App->>API: POST /payments {quote_id, method}
    API->>PaySvc: createPayment(quote_id, method)
    PaySvc->>LockSvc: acquire lock payment:{quote_id}
    LockSvc-->>PaySvc: lock acquired

    PaySvc->>DB: SELECT * FROM shipments WHERE id = ? AND status = 'pendiente_pago'
    DB-->>PaySvc: shipment
    PaySvc->>DB: SELECT * FROM kyc_records WHERE user_id = ? AND status = 'aprobado'
    DB-->>PaySvc: KYC

    alt Sin KYC aprobado
        PaySvc-->>API: error KYC_REQUIRED
        API-->>App: 403 KYC required
    end

    PaySvc->>DB: BEGIN TRANSACTION
    PaySvc->>DB: INSERT payments (status='completado' manual)
    PaySvc->>DB: UPDATE quotes SET status='aceptada'
    PaySvc->>DB: UPDATE shipments SET status='pagado'
    PaySvc->>DB: UPDATE consolidations SET status='pagada'
    PaySvc->>DB: UPDATE physical_packages SET status='listo_para_consolidar' WHERE consolidation_id = ?
    PaySvc->>DB: INSERT charges (desglose)
    PaySvc->>DB: INSERT invoices
    PaySvc->>DB: COMMIT

    PaySvc->>LockSvc: release lock
    PaySvc-->>API: payment OK
    API-->>App: 201 {payment, invoice}
    App-->>U: "Pago confirmado"

    par Notificaciones
        API->>Notif: sendPush "Pago confirmado, en preparación"
        API->>DB: INSERT audit_logs
    end

    Note over U,OP: Parte B: Operador despacha
    OP->>Back: Accede a cola "Consolidaciones por preparar"
    Back->>API: GET /consolidations?status=pagada
    API->>DB: SELECT consolidations WHERE status='pagada'
    DB-->>API: list
    API-->>Back: list

    OP->>Back: Selecciona consolidación
    OP->>Back: Verifica paquetes (escaneo)
    OP->>Back: Ingresa master_guide y carrier
    OP->>Back: Toca "Marcar como despachado"

    Back->>API: POST /shipments/{id}/dispatch {master_guide, carrier}
    API->>ShipSvc: dispatchShipment(shipment_id, master_guide, carrier, operator_id)
    ShipSvc->>DB: BEGIN TRANSACTION
    ShipSvc->>DB: UPDATE shipments SET status='despachado_desde_almacen', master_guide, carrier, shipped_at
    ShipSvc->>DB: UPDATE consolidations SET status='despachada'
    ShipSvc->>DB: UPDATE physical_packages SET status='en_transito' (batch)
    ShipSvc->>DB: INSERT package_events (batch)
    ShipSvc->>DB: INSERT shipment_events
    ShipSvc->>DB: COMMIT
    DB-->>ShipSvc: OK

    ShipSvc-->>API: shipment despachado
    API-->>Back: 200 OK
    Back-->>OP: "Envío despachado"

    par Notificación
        API->>Notif: sendPush "Tu envío ha sido despachado. Guía: {master_guide}"
    end

    Note over U,OP: Parte C: Tracking en tránsito
    OP->>Back: Actualiza estado: en_transito_a_cuba
    Back->>API: PATCH /shipments/{id}/status {status: 'en_transito_a_cuba'}
    API->>DB: UPDATE shipments + INSERT event
    API->>Notif: sendPush "En camino a Cuba"
    API-->>Back: 200 OK

    OP->>Back: Actualiza estado: en_aduana
    Back->>API: PATCH status: 'en_aduana'
    API->>DB: UPDATE
    API->>Notif: sendPush "En proceso de aduana"
    API-->>Back: 200 OK

    OP->>Back: Actualiza estado: en_reparto
    Back->>API: PATCH status: 'en_reparto'
    API->>DB: UPDATE
    API->>Notif: sendPush "Próximo a recibir"
    API-->>Back: 200 OK

    OP->>Back: Actualiza estado: entregado
    Back->>API: PATCH status: 'entregado'
    API->>DB: UPDATE shipments SET delivered_at, status='entregado'
    API->>DB: UPDATE physical_packages SET status='entregado'
    API->>Notif: sendPush "¡Tu paquete fue entregado!"
    API-->>Back: 200 OK

Descripción del flujo

Parte A: Pago (Usuario)

  1. Usuario abre la cotización pendiente
  2. Si está expirada, se recalcula automáticamente
  3. Usuario toca "Pagar"
  4. Backend adquiere lock distribuido en Redis (evita doble pago)
  5. Valida KYC aprobado
  6. Transacción ACID: crea Payment, actualiza Quote, Shipment, Consolidation, PhysicalPackage, crea Charges e Invoice
  7. Libera lock
  8. Push de confirmación

Parte B: Despacho (Operador)

  1. Operador ve la cola de consolidaciones pagadas
  2. Selecciona una y verifica los paquetes
  3. Ingresa master_guide (de la agencia) y carrier
  4. Toca "Marcar como despachado"
  5. Transacción: actualiza estados de Shipment, Consolidation, PhysicalPackage; crea eventos
  6. Push al usuario con el número de guía

Parte C: Tracking (Operador + Sistema)

  1. El operador actualiza el estado del envío a medida que avanza
  2. Cada cambio crea un shipment_event con timestamp
  3. Cada cambio genera push al usuario
  4. Cuando llega a entregado, se cierra el ciclo

Lock distribuido (detalle)

El lock sobre payment:{quote_id} previene:

  • Doble pago por clics concurrentes
  • Pago y cancelación simultáneos
  • Pago de una cotización que se está recalculando
# Pseudocódigo simplificado
with redis.lock(f"payment:{quote_id}", timeout=10):
    if shipment.status != 'pendiente_pago':
        raise InvalidStateError
    # transacción ACID

Si el lock no se adquiere en 3 reintentos, se devuelve LOCK_NOT_ACQUIRED y se le pide al usuario reintentar.


Manejo de errores

Etapa Error Manejo
Pago Cotización expirada Recalcular y notificar
Pago Sin KYC Bloqueo con CTA a KYC
Pago Lock no adquirido Mensaje de reintento
Pago DB error Rollback automático + log
Despacho Master guide inválido Validación en front
Despacho Estado incorrecto No permite despachar si no está pagado
Tracking Transición inválida Validar con máquina de estados

Referencias