92 lines
3.2 KiB
Markdown
92 lines
3.2 KiB
Markdown
# 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`
|