feat(club-003): club POS integration: identify member and register cashback from sales
This commit is contained in:
91
work/artifacts/CLUB-003/architect.md
Normal file
91
work/artifacts/CLUB-003/architect.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 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:
|
||||
```ts
|
||||
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`
|
||||
Reference in New Issue
Block a user