499 lines
12 KiB
Markdown
499 lines
12 KiB
Markdown
# ANALISIS MODULO RT-005: CLIENTES Y PROGRAMA DE LEALTAD
|
|
|
|
**Fecha:** 2025-12-18
|
|
**Fase:** 2 - Analisis por Modulo
|
|
**Modulo:** RT-005 Clientes
|
|
**Herencia:** 40%
|
|
**Story Points:** 34
|
|
**Prioridad:** P1
|
|
|
|
---
|
|
|
|
## 1. DESCRIPCION GENERAL
|
|
|
|
### 1.1 Proposito
|
|
Gestion de clientes con programa de fidelizacion basado en puntos, niveles de membresia y recompensas.
|
|
|
|
### 1.2 Funcionalidades Principales
|
|
|
|
| Funcionalidad | Descripcion | Criticidad |
|
|
|---------------|-------------|------------|
|
|
| Registro cliente | Datos minimos en POS | Alta |
|
|
| Programa puntos | Acumulacion y canje | Alta |
|
|
| Niveles membresia | Bronce, Plata, Oro | Media |
|
|
| Historial compras | 3 anos | Media |
|
|
| Segmentacion | Por comportamiento | Baja |
|
|
|
|
---
|
|
|
|
## 2. HERENCIA DEL CORE
|
|
|
|
### 2.1 Componentes Heredados (40%)
|
|
|
|
| Componente Core | % Uso | Accion |
|
|
|-----------------|-------|--------|
|
|
| core.partners | 100% | HEREDAR (clientes) |
|
|
| core.partner_contacts | 100% | HEREDAR |
|
|
| crm.leads | 20% | OPCIONAL |
|
|
|
|
### 2.2 Servicios a Heredar
|
|
|
|
```typescript
|
|
import { PartnersService } from '@erp-core/core';
|
|
import { ContactsService } from '@erp-core/core';
|
|
```
|
|
|
|
### 2.3 Servicios a Extender
|
|
|
|
```typescript
|
|
class RetailCustomerService extends PartnersService {
|
|
// Busqueda rapida
|
|
async quickSearch(phone: string): Promise<Customer>;
|
|
async quickSearch(email: string): Promise<Customer>;
|
|
|
|
// Programa lealtad
|
|
async getPoints(customerId: string): Promise<LoyaltyInfo>;
|
|
async getLoyaltyCard(customerId: string): Promise<LoyaltyCard>;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. COMPONENTES NUEVOS
|
|
|
|
### 3.1 Entidades (TypeORM)
|
|
|
|
```typescript
|
|
// 1. LoyaltyProgram - Programa de lealtad
|
|
@Entity('loyalty_programs', { schema: 'retail' })
|
|
export class LoyaltyProgram {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id: string;
|
|
|
|
@Column('uuid')
|
|
tenantId: string;
|
|
|
|
@Column()
|
|
code: string;
|
|
|
|
@Column()
|
|
name: string;
|
|
|
|
@Column({ nullable: true })
|
|
description: string;
|
|
|
|
@Column({ type: 'decimal', precision: 5, scale: 2, default: 1 })
|
|
pointsPerCurrency: number; // 1 punto por $10
|
|
|
|
@Column({ type: 'decimal', precision: 5, scale: 2, default: 0.01 })
|
|
currencyPerPoint: number; // $0.10 por punto
|
|
|
|
@Column({ type: 'int', default: 100 })
|
|
minPointsRedeem: number;
|
|
|
|
@Column({ type: 'int', nullable: true })
|
|
pointsExpiryDays: number; // null = no expiran
|
|
|
|
@Column({ type: 'boolean', default: true })
|
|
isActive: boolean;
|
|
}
|
|
|
|
// 2. LoyaltyCard - Tarjeta de cliente
|
|
@Entity('loyalty_cards', { schema: 'retail' })
|
|
export class LoyaltyCard {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id: string;
|
|
|
|
@Column('uuid')
|
|
tenantId: string;
|
|
|
|
@ManyToOne(() => LoyaltyProgram)
|
|
program: LoyaltyProgram;
|
|
|
|
@ManyToOne(() => Partner)
|
|
customer: Partner;
|
|
|
|
@Column()
|
|
cardNumber: string;
|
|
|
|
@Column({ type: 'date' })
|
|
issueDate: Date;
|
|
|
|
@Column({ type: 'int', default: 0 })
|
|
pointsBalance: number;
|
|
|
|
@Column({ type: 'int', default: 0 })
|
|
pointsEarned: number;
|
|
|
|
@Column({ type: 'int', default: 0 })
|
|
pointsRedeemed: number;
|
|
|
|
@Column({ type: 'int', default: 0 })
|
|
pointsExpired: number;
|
|
|
|
@Column({ type: 'boolean', default: true })
|
|
isActive: boolean;
|
|
}
|
|
|
|
// 3. LoyaltyTransaction - Movimiento de puntos
|
|
@Entity('loyalty_transactions', { schema: 'retail' })
|
|
export class LoyaltyTransaction {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id: string;
|
|
|
|
@Column('uuid')
|
|
tenantId: string;
|
|
|
|
@ManyToOne(() => LoyaltyCard)
|
|
card: LoyaltyCard;
|
|
|
|
@Column({ type: 'enum', enum: LoyaltyTransactionType })
|
|
transactionType: LoyaltyTransactionType; // earn, redeem, expire, adjust
|
|
|
|
@Column({ type: 'int' })
|
|
pointsAmount: number;
|
|
|
|
@Column({ nullable: true })
|
|
referenceType: string; // pos_order, promotion, manual
|
|
|
|
@Column({ type: 'uuid', nullable: true })
|
|
referenceId: string;
|
|
|
|
@Column({ nullable: true })
|
|
notes: string;
|
|
|
|
@Column({ type: 'timestamptz' })
|
|
createdAt: Date;
|
|
}
|
|
|
|
// 4. MembershipLevel - Niveles
|
|
@Entity('membership_levels', { schema: 'retail' })
|
|
export class MembershipLevel {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id: string;
|
|
|
|
@Column('uuid')
|
|
tenantId: string;
|
|
|
|
@ManyToOne(() => LoyaltyProgram)
|
|
program: LoyaltyProgram;
|
|
|
|
@Column()
|
|
name: string; // Bronce, Plata, Oro
|
|
|
|
@Column({ type: 'int' })
|
|
minPointsYear: number; // Puntos minimos para alcanzar
|
|
|
|
@Column({ type: 'decimal', precision: 3, scale: 2, default: 1 })
|
|
multiplier: number; // 1x, 1.5x, 2x
|
|
|
|
@Column({ type: 'jsonb', nullable: true })
|
|
benefits: object; // Beneficios adicionales
|
|
}
|
|
```
|
|
|
|
### 3.2 Servicios Backend
|
|
|
|
| Servicio | Metodos Principales |
|
|
|----------|-------------------|
|
|
| RetailCustomerService | quickSearch(), register(), getHistory() |
|
|
| LoyaltyProgramService | create(), configure(), getActive() |
|
|
| LoyaltyCardService | issue(), activate(), getBalance() |
|
|
| LoyaltyTransactionService | earnPoints(), redeemPoints(), expire() |
|
|
| MembershipService | calculateLevel(), upgrade(), downgrade() |
|
|
|
|
### 3.3 Controladores
|
|
|
|
```typescript
|
|
@Controller('customers')
|
|
export class RetailCustomerController {
|
|
// Clientes
|
|
@Get('search')
|
|
quickSearch(@Query('q') query: string): Promise<Customer[]>;
|
|
|
|
@Post('quick-register')
|
|
quickRegister(@Body() dto: QuickRegisterDto): Promise<Customer>;
|
|
|
|
@Get(':id/history')
|
|
getPurchaseHistory(@Param('id') id: string): Promise<PurchaseHistory>;
|
|
|
|
// Lealtad
|
|
@Get(':id/loyalty')
|
|
getLoyaltyInfo(@Param('id') id: string): Promise<LoyaltyInfo>;
|
|
|
|
@Get(':id/loyalty/card')
|
|
getLoyaltyCard(@Param('id') id: string): Promise<LoyaltyCard>;
|
|
|
|
@Post(':id/loyalty/earn')
|
|
earnPoints(@Param('id') id: string, @Body() dto: EarnPointsDto): Promise<LoyaltyTransaction>;
|
|
|
|
@Post(':id/loyalty/redeem')
|
|
redeemPoints(@Param('id') id: string, @Body() dto: RedeemPointsDto): Promise<LoyaltyTransaction>;
|
|
|
|
@Get(':id/loyalty/transactions')
|
|
getTransactions(@Param('id') id: string): Promise<LoyaltyTransaction[]>;
|
|
}
|
|
|
|
@Controller('loyalty')
|
|
export class LoyaltyController {
|
|
// Programas
|
|
@Get('programs')
|
|
getPrograms(): Promise<LoyaltyProgram[]>;
|
|
|
|
@Post('programs')
|
|
createProgram(@Body() dto: CreateProgramDto): Promise<LoyaltyProgram>;
|
|
|
|
// Tarjetas
|
|
@Post('cards/issue')
|
|
issueCard(@Body() dto: IssueCardDto): Promise<LoyaltyCard>;
|
|
|
|
@Get('cards/:number')
|
|
getCardByNumber(@Param('number') number: string): Promise<LoyaltyCard>;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. SISTEMA DE PUNTOS
|
|
|
|
### 4.1 Configuracion
|
|
|
|
```yaml
|
|
loyalty_config:
|
|
points:
|
|
earn_rate: 1 # 1 punto por cada $10
|
|
redeem_rate: 100 # 100 puntos = $10 descuento
|
|
min_redeem: 100 # Minimo para canjear
|
|
max_discount_percent: 50 # Maximo descuento por puntos
|
|
expiry_days: null # No expiran (o 365)
|
|
|
|
levels:
|
|
- name: "Bronce"
|
|
min_points_year: 0
|
|
multiplier: 1.0
|
|
benefits: []
|
|
|
|
- name: "Plata"
|
|
min_points_year: 1000
|
|
multiplier: 1.5
|
|
benefits:
|
|
- "Envio gratis e-commerce"
|
|
- "Ofertas exclusivas"
|
|
|
|
- name: "Oro"
|
|
min_points_year: 5000
|
|
multiplier: 2.0
|
|
benefits:
|
|
- "Envio gratis e-commerce"
|
|
- "Ofertas exclusivas"
|
|
- "Acceso VIP preventas"
|
|
- "Descuento cumpleaños 20%"
|
|
```
|
|
|
|
### 4.2 Calculo de Puntos
|
|
|
|
```typescript
|
|
// Al cerrar venta
|
|
function calculatePoints(order: POSOrder, card: LoyaltyCard): number {
|
|
const program = card.program;
|
|
const level = getMembershipLevel(card);
|
|
|
|
// Base: 1 punto por cada $10
|
|
const basePoints = Math.floor(order.total / program.pointsPerCurrency);
|
|
|
|
// Multiplicador por nivel
|
|
const multipliedPoints = Math.floor(basePoints * level.multiplier);
|
|
|
|
// Puntos extra por promociones
|
|
const bonusPoints = calculateBonusPoints(order);
|
|
|
|
return multipliedPoints + bonusPoints;
|
|
}
|
|
|
|
// Canje de puntos
|
|
function redeemPoints(points: number, program: LoyaltyProgram): number {
|
|
if (points < program.minPointsRedeem) {
|
|
throw new Error('Puntos insuficientes');
|
|
}
|
|
|
|
// Calcular descuento
|
|
const discount = points * program.currencyPerPoint;
|
|
return discount;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. FLUJOS DE NEGOCIO
|
|
|
|
### 5.1 Flujo en POS
|
|
|
|
```
|
|
1. Cliente se identifica (telefono/tarjeta)
|
|
↓
|
|
2. Buscar tarjeta de lealtad
|
|
↓
|
|
3. Mostrar puntos disponibles
|
|
↓
|
|
4. Realizar venta normal
|
|
↓
|
|
5. Preguntar si desea canjear puntos
|
|
↓
|
|
6. Si canjea:
|
|
a. Calcular descuento
|
|
b. Aplicar a orden
|
|
c. Registrar redeem
|
|
↓
|
|
7. Cerrar venta
|
|
↓
|
|
8. Calcular puntos ganados
|
|
↓
|
|
9. Registrar earn
|
|
↓
|
|
10. Imprimir puntos en ticket
|
|
```
|
|
|
|
### 5.2 Flujo de Registro
|
|
|
|
```
|
|
1. Cliente nuevo en POS
|
|
↓
|
|
2. Registro rapido (nombre, telefono)
|
|
↓
|
|
3. Crear partner en core
|
|
↓
|
|
4. Crear tarjeta lealtad
|
|
↓
|
|
5. Asignar nivel Bronce
|
|
↓
|
|
6. Opcional: puntos bienvenida
|
|
```
|
|
|
|
---
|
|
|
|
## 6. TABLAS DDL
|
|
|
|
### 6.1 Tablas Definidas
|
|
|
|
```sql
|
|
-- Ya en 03-retail-tables.sql
|
|
CREATE TABLE retail.loyalty_programs (...);
|
|
CREATE TABLE retail.loyalty_cards (...);
|
|
CREATE TABLE retail.loyalty_transactions (...);
|
|
|
|
-- Agregar niveles
|
|
CREATE TABLE retail.membership_levels (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
program_id UUID NOT NULL REFERENCES retail.loyalty_programs(id),
|
|
name VARCHAR(50) NOT NULL,
|
|
min_points_year INT NOT NULL DEFAULT 0,
|
|
multiplier DECIMAL(3,2) NOT NULL DEFAULT 1.00,
|
|
benefits JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
### 6.2 Indices
|
|
|
|
```sql
|
|
-- Busqueda rapida cliente
|
|
CREATE INDEX idx_partners_phone ON core.partners(phone);
|
|
CREATE INDEX idx_partners_email ON core.partners(email);
|
|
|
|
-- Tarjetas
|
|
CREATE UNIQUE INDEX idx_loyalty_cards_number ON retail.loyalty_cards(tenant_id, card_number);
|
|
CREATE INDEX idx_loyalty_cards_customer ON retail.loyalty_cards(customer_id);
|
|
|
|
-- Transacciones
|
|
CREATE INDEX idx_loyalty_transactions_card ON retail.loyalty_transactions(card_id);
|
|
CREATE INDEX idx_loyalty_transactions_date ON retail.loyalty_transactions(created_at);
|
|
```
|
|
|
|
---
|
|
|
|
## 7. DEPENDENCIAS
|
|
|
|
### 7.1 Dependencias de Core
|
|
|
|
| Modulo | Estado | Requerido Para |
|
|
|--------|--------|---------------|
|
|
| MGN-005 Catalogs | 0% | Partners (clientes) |
|
|
| MGN-014 CRM | 0% | Opcional |
|
|
|
|
### 7.2 Dependencias de Retail
|
|
|
|
| Modulo | Tipo |
|
|
|--------|------|
|
|
| RT-001 Fundamentos | Prerequisito |
|
|
|
|
### 7.3 Bloquea a
|
|
|
|
| Modulo | Razon |
|
|
|--------|-------|
|
|
| RT-002 POS | Integracion puntos |
|
|
| RT-009 E-commerce | Clientes y puntos |
|
|
|
|
---
|
|
|
|
## 8. CRITERIOS DE ACEPTACION
|
|
|
|
### 8.1 Funcionales
|
|
|
|
- [ ] Buscar cliente por telefono
|
|
- [ ] Buscar cliente por email
|
|
- [ ] Registro rapido de cliente
|
|
- [ ] Crear programa de lealtad
|
|
- [ ] Emitir tarjeta a cliente
|
|
- [ ] Consultar balance de puntos
|
|
- [ ] Acumular puntos al comprar
|
|
- [ ] Canjear puntos por descuento
|
|
- [ ] Ver historial de transacciones
|
|
- [ ] Calcular nivel de membresia
|
|
- [ ] Ver historial de compras (3 anos)
|
|
|
|
### 8.2 Performance
|
|
|
|
- [ ] Busqueda cliente < 500ms
|
|
- [ ] Consulta puntos < 200ms
|
|
|
|
---
|
|
|
|
## 9. RIESGOS
|
|
|
|
| Riesgo | Probabilidad | Impacto | Mitigacion |
|
|
|--------|--------------|---------|------------|
|
|
| Fraude de puntos | Media | Alto | Auditoria + limites |
|
|
| Performance busqueda | Baja | Medio | Indices |
|
|
|
|
---
|
|
|
|
## 10. ESTIMACION DETALLADA
|
|
|
|
| Componente | SP Backend | SP Frontend | Total |
|
|
|------------|-----------|-------------|-------|
|
|
| Entities + Migrations | 3 | - | 3 |
|
|
| RetailCustomerService | 5 | - | 5 |
|
|
| LoyaltyProgramService | 3 | - | 3 |
|
|
| LoyaltyCardService | 5 | - | 5 |
|
|
| LoyaltyTransactionService | 3 | - | 3 |
|
|
| MembershipService | 2 | - | 2 |
|
|
| Controllers | 3 | - | 3 |
|
|
| Customer Pages | - | 5 | 5 |
|
|
| Loyalty Pages | - | 5 | 5 |
|
|
| **TOTAL** | **24** | **10** | **34** |
|
|
|
|
---
|
|
|
|
## 11. REFERENCIAS
|
|
|
|
| Documento | Ubicacion |
|
|
|-----------|-----------|
|
|
| Epica RT-005 | docs/08-epicas/EPIC-RT-005-clientes.md |
|
|
| Modulo Core Partners | erp-core/backend/src/modules/partners/ |
|
|
|
|
---
|
|
|
|
**Estado:** ANALISIS COMPLETO
|
|
**Bloqueado por:** RT-001 Fundamentos
|