# Arquitectura — CLUB-001 · Fase 1 Core backend ## Análisis de arquitectura existente ### Superficies del proyecto - **Backend API**: `project/src/app/build-app.ts` registra módulos Fastify desacoplados bajo `project/src/modules/*`. - **Frontend tienda**: `project/frontend/` consume la API vía rutas proxy Next.js. - **Admin panel**: `project/apps/admin/` usa endpoints backoffice/admin ya existentes. - **TPV/POS**: `project/apps/pos/` usa backend POS y órdenes como fuente de ventas. ### Patrones que debemos reutilizar - **Módulo aislado por carpeta**: `api/`, `application/`, `domain/`, `infrastructure/`, `index.ts`. - **Rutas finas**: validación con `zod` + `parseJson`, errores con `AppError`, Swagger con `errorSchema`. - **Persistencia PostgreSQL**: migraciones `project/migrations/*.js` y repositorios `Pg*Repository`. - **Auth desacoplada por inyección**: módulos reciben `authenticate` desde `build-app.ts`; no importan internals de identity. - **Configuración editable**: `store_settings` ya actúa como KV-store para ajustes globales del negocio. - **Tests reales de integración**: `project/src/app/tests/*.itest.ts` recrean DB, aplican migraciones y prueban la app completa. ## Decisiones técnicas para Fase 1 ### 1) Nuevo módulo `club` Se crea `project/src/modules/club/` con registro de rutas propio desde `build-app.ts`. ### 2) Ledger como fuente de verdad - `club_transactions` será el **source of truth**. - `club_members.current_balance_cents` existirá solo como **cache/optimización**. - Cada escritura de ledger actualizará ambos dentro de la misma transacción. - El balance podrá reconstruirse con `SUM(balance_delta_cents)`. ### 3) Configuración reutilizando `store_settings` No se crea un sistema nuevo de configuración. Se añaden claves: - `club_enabled` - `club_cashback_bps` - `club_allow_anonymous_members` - `club_allow_recovery_codes` - `club_minimum_redeem_cents` Esto mantiene consistencia con la arquitectura actual y simplifica futura UI admin. ### 4) Dispositivo anónimo con token opaco hasheado - El backend genera `device_token` opaco. - Solo se persiste `device_token_hash` en `club_devices`. - El raw token se devuelve al cliente una sola vez en `POST /club/join`. - Las rutas de lectura de Club aceptarán el token mediante header/cookie para no acoplar la PWA todavía. ### 5) Modelo preparado para fases futuras Aunque Fase 1 solo activa core backend, la migración deja base para próximas fases: - `club_members` - `club_devices` - `club_transactions` - `club_recovery_codes` - `club_rewards` - `club_campaigns` ### 6) Cashback configurable, no hardcoded La lógica core leerá `club_cashback_bps` desde settings. El default inicial será **200 bps = 2%**. ## Alcance funcional de CLUB-001 ### Sí entra en Fase 1 - Crear socio anónimo. - Emitir token de dispositivo. - Consultar tarjeta/resumen del socio por token. - Consultar movimientos del ledger. - Configuración backend del Club. - Infraestructura de migraciones y tests. - Helper backend para registrar transacciones idempotentes sobre ledger. ### No entra en Fase 1 - PWA visual `/club/*`. - QR visual y endpoint TPV de identificación. - Recovery codes funcionales. - Vinculación a cuenta de usuario. - Admin UI. - Integración TPV completa de earn/redeem/refund. ## Esquema inicial propuesto ### `club_members` - `id uuid pk` - `user_id uuid null -> identity_users(id)` - `member_code text unique` - `status text` (`active|blocked|merged`) - `tier_code text default 'base'` - `current_balance_cents integer default 0` - `created_at`, `updated_at` ### `club_devices` - `id uuid pk` - `member_id uuid fk -> club_members(id)` - `device_token_hash text unique` - `last_used_at timestamptz` - `created_at timestamptz` - `revoked_at timestamptz null` ### `club_transactions` - `id uuid pk` - `member_id uuid fk -> club_members(id)` - `sale_id uuid null -> orders_orders(id)` - `store_id uuid null -> pos_stores(id)` - `type text` (`earn|redeem|refund|bonus|adjustment`) - `amount_cents integer` - `balance_delta_cents integer` - `idempotency_key text unique null` - `metadata jsonb not null default '{}'` - `created_at` ### `club_recovery_codes` - Tabla preparada para Fase 4. - Guardará hash(es) del código, no plaintext. ### `club_rewards`, `club_campaigns` - Tablas scaffold para evolución posterior sin activar motor complejo aún. ## Endpoints backend de Fase 1 ### Públicos / cliente Club - `GET /club/config` - Devuelve flags públicos del módulo. - `POST /club/join` - Crea socio anónimo + device token. - `GET /club/me` - Resuelve socio por device token. - `GET /club/movements` - Lista movimientos del socio actual. ### Admin / configuración - `GET /admin/club/settings` - `PATCH /admin/club/settings` ### Aplicación interna - Servicio backend para registrar ledger idempotente y recalcular balance. - Se deja listo para ser usado por TPV en CLUB-003. ## Validaciones clave - Rechazar `join` si `club_enabled=false` o `club_allow_anonymous_members=false`. - No aceptar tokens sin hash coincidente o revocados. - `member_code` único y corto, formato `MDV-XXXXXXXX`. - `type` del ledger restringido por CHECK. - `current_balance_cents` nunca por debajo de 0 en operaciones que no lo permitan. - `idempotency_key` único para evitar dobles registros. ## Estrategia de tests - **Unit tests** para helpers de token/member code/config parsing. - **Boundary test** para evitar imports indebidos del módulo. - **Integration test real PostgreSQL** para: - migraciones del Club - `POST /club/join` - `GET /club/me` - `GET /club/movements` - `GET/PATCH /admin/club/settings` - escritura idempotente de ledger ## Riesgos / deuda controlada - La PWA aún no existe; por eso Fase 1 devolverá el `deviceToken` al cliente y además dejará la ruta preparada para header/cookie. - El QR opaco persistente se implementará en la fase TPV/PWA, sin bloquear el core del ledger. - Recovery codes se dejan modelados pero no activados todavía para evitar complejidad prematura.