Files
mercadodevida/work/artifacts/CLUB-003/architect.md

3.2 KiB

Arquitectura — CLUB-003 · Integración TPV del Club

Objetivo

Conectar la caja TPV con el módulo Club para que el dependiente pueda identificar a un socio y acumular cashback en cada venta. El ledger registra la transacción de forma idempotente.

Análisis existente

Lo que ya existe

  • Módulo Club con club_members, club_devices, club_transactions (CLUB-001).
  • PWA Club con join y tarjeta digital (CLUB-002).
  • Terminal POS en project/apps/pos/ con búsqueda de cliente por email.
  • Flujo de venta: añadir artículos → cobrar → POST /pos/sales.

Patrones a reutilizar

  • La misma transacción de base de datos que usa el POS para ventas.
  • El ledger idempotente de CLUB-001 con idempotencyKey.
  • Configuración de Club en store_settings (club_cashback_bps).

Diseño propuesto

1) Endpoint de resolución de socio por código

GET /club/resolve?memberCode=MDV-XXXXXXXX

Devuelve: { member, config } si existe y está activo, o 404.

Esto permite al TPV resolver un socio desde el código que el cliente presenta en la tarjeta digital.

2) Extender creación de venta POS

POST /pos/sales acepta un campo opcional:

clubMemberId?: string;

Si está presente y el Club está habilitado:

  1. Resolver el socio por ID.
  2. Consultar club_cashback_bps de settings.
  3. Calcular cashbackCents = round(totalCents * cashbackBps / 10_000).
  4. Crear transacción de ledger idempotente dentro de la misma transacción DB que la venta:
    idempotencyKey: `club-earn-${orderId}`
    type: 'earn'
    amountCents: totalCents
    balanceDeltaCents: cashbackCents
    saleId: orderId
    
  5. Devolver en la respuesta de venta: { ..., clubEarnedCents: cashbackCents }.

Si la venta es PENDING (no se cobra aún), no se acumula cashback. Solo cuando orderState === 'COMPLETED'.

3) Receipt del TPV incluye cashback

El receipt ya incluye campos libres. Se añade:

clubMemberCode: string (si hay socio)
clubEarnedCents: number

4) UI del TPV: búsqueda de socio Club

En la barra superior del terminal, рядом con la info de cajero:

  • Campo de texto para código de socio Club.
  • Botón "Club" que abre un diálogo simple de búsqueda.
  • Muestra: código, saldo actual.
  • Al confirmar, asocia el socio a la venta en curso.
  • Al borrar/limpiar caja, también se limpia el socio.

Alcance

Sí entra

  • GET /club/resolve
  • integración de clubMemberId en POST /pos/sales
  • cálculo de cashback configurable desde settings
  • ledger idempotente por orderId
  • UI simple en TPV para buscar socio Club
  • receipt con cashback ganado

No entra

  • UI de reintegro de cashback en devolución TPV (CLUB-004)
  • canjeo de saldo Club en TPV
  • vínculo con usuario registrado en TPV
  • lógica de recovery codes
  • admin UI del Club

Estrategia de tests

  • Test unitario del cálculo de cashback en create-pos-sale.ts.
  • Test de integración del endpoint /club/resolve.
  • Test del flujo completo: venta POS + Club earn ledger (con itest real si el pool de test lo soporta).

Validación prevista

  • cd project && npm run typecheck
  • cd project && npm run build
  • cd project/apps/pos && npm run build
  • cd project && npx vitest run src/modules/club/tests/boundary.test.ts
  • ./scripts/verify.sh