3.2 KiB
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:
- Resolver el socio por ID.
- Consultar
club_cashback_bpsde settings. - Calcular
cashbackCents = round(totalCents * cashbackBps / 10_000). - 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 - 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
clubMemberIdenPOST /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 typecheckcd project && npm run buildcd project/apps/pos && npm run buildcd project && npx vitest run src/modules/club/tests/boundary.test.ts./scripts/verify.sh