# 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
