# ANALISIS MODULO RT-006: PRECIOS Y PROMOCIONES

**Fecha:** 2025-12-18
**Fase:** 2 - Analisis por Modulo
**Modulo:** RT-006 Precios
**Herencia:** 30%
**Story Points:** 36
**Prioridad:** P0

---

## 1. DESCRIPCION GENERAL

### 1.1 Proposito
Motor de reglas de precios con listas de precios, promociones, descuentos por volumen y sistema de cupones.

### 1.2 Funcionalidades Principales

| Funcionalidad | Descripcion | Criticidad |
|---------------|-------------|------------|
| Listas de precios | Por canal/sucursal | Critica |
| Promociones | Multiples tipos | Alta |
| Descuentos volumen | Por cantidad | Media |
| Cupones | Generacion y canje | Media |
| Motor reglas | Evaluacion rapida | Critica |

---

## 2. HERENCIA DEL CORE

### 2.1 Componentes Heredados (30%)

| Componente Core | % Uso | Accion |
|-----------------|-------|--------|
| sales.pricelists | 100% | HEREDAR |
| sales.pricelist_items | 100% | HEREDAR |

### 2.2 Servicios a Heredar

```typescript
import { PricelistsService } from '@erp-core/sales';
```

### 2.3 Servicios a Extender

```typescript
class RetailPriceService extends PricelistsService {
  // Motor de precios
  async calculatePrice(productId: string, context: PriceContext): Promise<PriceResult>;

  // Promociones
  async getActivePromotions(branchId: string): Promise<Promotion[]>;
  async applyPromotion(orderId: string, promotionId: string): Promise<void>;
}
```

---

## 3. COMPONENTES NUEVOS

### 3.1 Entidades (TypeORM)

```typescript
// 1. Promotion - Promocion
@Entity('promotions', { schema: 'retail' })
export class Promotion {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

  @Column()
  code: string;

  @Column()
  name: string;

  @Column({ nullable: true })
  description: string;

  @Column({ type: 'enum', enum: PromotionType })
  promotionType: PromotionType;
  // percentage, fixed_amount, buy_x_get_y, bundle

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  discountValue: number;  // % o monto segun tipo

  @Column({ type: 'date' })
  startDate: Date;

  @Column({ type: 'date' })
  endDate: Date;

  @Column({ type: 'boolean', default: false })
  appliesToAll: boolean;

  @Column({ type: 'int', nullable: true })
  minQuantity: number;

  @Column({ type: 'decimal', precision: 12, scale: 2, nullable: true })
  minAmount: number;

  @Column({ type: 'uuid', array: true, nullable: true })
  branchIds: string[];  // null = todas

  @Column({ type: 'boolean', default: true })
  isActive: boolean;

  @Column({ type: 'int', nullable: true })
  maxUses: number;

  @Column({ type: 'int', default: 0 })
  currentUses: number;

  @OneToMany(() => PromotionProduct, pp => pp.promotion)
  products: PromotionProduct[];
}

// 2. PromotionProduct - Productos en promocion
@Entity('promotion_products', { schema: 'retail' })
export class PromotionProduct {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ManyToOne(() => Promotion)
  promotion: Promotion;

  @ManyToOne(() => Product)
  product: Product;
}

// 3. Coupon - Cupon
@Entity('coupons', { schema: 'retail' })
export class Coupon {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

  @Column()
  code: string;

  @Column({ type: 'enum', enum: CouponType })
  couponType: CouponType;  // percentage, fixed_amount

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  discountValue: number;

  @Column({ type: 'decimal', precision: 12, scale: 2, nullable: true })
  minPurchase: number;

  @Column({ type: 'decimal', precision: 12, scale: 2, nullable: true })
  maxDiscount: number;

  @Column({ type: 'date' })
  validFrom: Date;

  @Column({ type: 'date' })
  validUntil: Date;

  @Column({ type: 'int', default: 1 })
  maxUses: number;

  @Column({ type: 'int', default: 0 })
  timesUsed: number;

  @Column({ type: 'boolean', default: true })
  isActive: boolean;
}

// 4. CouponRedemption - Uso de cupon
@Entity('coupon_redemptions', { schema: 'retail' })
export class CouponRedemption {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ManyToOne(() => Coupon)
  coupon: Coupon;

  @ManyToOne(() => POSOrder)
  order: POSOrder;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  discountApplied: number;

  @Column({ type: 'timestamptz' })
  redeemedAt: Date;
}
```

### 3.2 Motor de Precios

```typescript
interface PriceContext {
  productId: string;
  quantity: number;
  customerId?: string;
  branchId: string;
  channel: 'pos' | 'ecommerce';
  date: Date;
}

interface PriceResult {
  basePrice: number;
  finalPrice: number;
  discounts: DiscountApplied[];
  taxes: TaxApplied[];
  total: number;
}

interface DiscountApplied {
  type: 'pricelist' | 'promotion' | 'volume' | 'coupon' | 'loyalty';
  name: string;
  amount: number;
}

class PriceEngine {
  async calculatePrice(context: PriceContext): Promise<PriceResult> {
    // 1. Obtener precio base del producto
    const basePrice = await this.getBasePrice(context.productId);

    // 2. Aplicar lista de precios del canal
    const pricelistPrice = await this.applyPricelist(basePrice, context);

    // 3. Evaluar promociones activas
    const promotionDiscount = await this.evaluatePromotions(pricelistPrice, context);

    // 4. Evaluar descuento por volumen
    const volumeDiscount = await this.evaluateVolumeDiscount(context);

    // 5. Calcular precio final
    const finalPrice = pricelistPrice - promotionDiscount - volumeDiscount;

    // 6. Calcular impuestos
    const taxes = await this.calculateTaxes(finalPrice, context);

    return {
      basePrice,
      finalPrice,
      discounts: [...],
      taxes,
      total: finalPrice + taxes.total
    };
  }
}
```

### 3.3 Servicios Backend

| Servicio | Metodos Principales |
|----------|-------------------|
| PriceEngineService | calculatePrice(), evaluatePromotions() |
| PromotionService | create(), activate(), deactivate(), getActive() |
| CouponService | generate(), validate(), redeem() |
| VolumeDiscountService | configure(), calculate() |

### 3.4 Controladores

```typescript
@Controller('pricing')
export class PricingController {
  // Calculo de precios
  @Post('calculate')
  calculatePrice(@Body() dto: PriceRequestDto): Promise<PriceResult>;

  // Promociones
  @Get('promotions')
  getPromotions(@Query() filters: PromotionFilters): Promise<Promotion[]>;

  @Get('promotions/active')
  getActivePromotions(@Query('branchId') branchId: string): Promise<Promotion[]>;

  @Post('promotions')
  createPromotion(@Body() dto: CreatePromotionDto): Promise<Promotion>;

  @Put('promotions/:id')
  updatePromotion(@Param('id') id: string, @Body() dto: UpdatePromotionDto): Promise<Promotion>;

  @Post('promotions/:id/activate')
  activatePromotion(@Param('id') id: string): Promise<Promotion>;

  @Post('promotions/:id/deactivate')
  deactivatePromotion(@Param('id') id: string): Promise<Promotion>;

  // Cupones
  @Post('coupons/generate')
  generateCoupons(@Body() dto: GenerateCouponsDto): Promise<Coupon[]>;

  @Get('coupons/:code/validate')
  validateCoupon(@Param('code') code: string): Promise<CouponValidation>;

  @Post('coupons/:code/redeem')
  redeemCoupon(@Param('code') code: string, @Body() dto: RedeemDto): Promise<CouponRedemption>;
}
```

---

## 4. TIPOS DE PROMOCIONES

### 4.1 Descuento Porcentual

```yaml
tipo: percentage
ejemplo:
  nombre: "20% en ropa"
  descuento: 20
  aplica_a: [categoria: "ROPA"]
  vigencia: "2025-12-01 al 2025-12-31"
```

### 4.2 Descuento Monto Fijo

```yaml
tipo: fixed_amount
ejemplo:
  nombre: "$100 en compras mayores a $500"
  descuento: 100
  minimo_compra: 500
```

### 4.3 Compra X Lleva Y (NxM)

```yaml
tipo: buy_x_get_y
ejemplo:
  nombre: "3x2 en bebidas"
  compra: 3
  paga: 2
  aplica_a: [categoria: "BEBIDAS"]
```

### 4.4 Descuento por Volumen

```yaml
tipo: volume
ejemplo:
  nombre: "Descuento por volumen"
  rangos:
    - cantidad_min: 5, descuento: 5%
    - cantidad_min: 10, descuento: 10%
    - cantidad_min: 20, descuento: 15%
```

---

## 5. REGLAS DE EVALUACION

### 5.1 Orden de Prioridad

```
1. Precio base del producto
2. Lista de precios del canal (si existe)
3. Promociones activas (por prioridad, NO acumulables)
4. Descuento por volumen (si aplica)
5. Cupon (si proporciona el cliente)
6. Puntos de lealtad (como descuento)
```

### 5.2 Conflicto de Promociones

```typescript
// Solo aplica la mejor promocion (no acumulables)
function selectBestPromotion(promotions: Promotion[], orderTotal: number): Promotion {
  return promotions
    .filter(p => isApplicable(p, orderTotal))
    .sort((a, b) => calculateDiscount(b, orderTotal) - calculateDiscount(a, orderTotal))
    [0];
}
```

---

## 6. TABLAS DDL

### 6.1 Tablas Definidas

```sql
-- Ya en 03-retail-tables.sql
CREATE TABLE retail.promotions (...);
CREATE TABLE retail.promotion_products (...);

-- Agregar cupones
CREATE TABLE retail.coupons (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
  code VARCHAR(20) NOT NULL,
  coupon_type VARCHAR(20) NOT NULL,
  discount_value DECIMAL(10,2) NOT NULL,
  min_purchase DECIMAL(12,2),
  max_discount DECIMAL(12,2),
  valid_from DATE NOT NULL,
  valid_until DATE NOT NULL,
  max_uses INT DEFAULT 1,
  times_used INT DEFAULT 0,
  is_active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(tenant_id, code)
);

CREATE TABLE retail.coupon_redemptions (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
  coupon_id UUID NOT NULL REFERENCES retail.coupons(id),
  order_id UUID NOT NULL REFERENCES retail.pos_orders(id),
  discount_applied DECIMAL(12,2) NOT NULL,
  redeemed_at TIMESTAMPTZ DEFAULT NOW()
);
```

### 6.2 Indices

```sql
-- Promociones activas
CREATE INDEX idx_promotions_active ON retail.promotions(is_active, start_date, end_date);
CREATE INDEX idx_promotions_branch ON retail.promotions USING GIN(branch_ids);

-- Cupones
CREATE UNIQUE INDEX idx_coupons_code ON retail.coupons(tenant_id, code);
CREATE INDEX idx_coupons_valid ON retail.coupons(valid_from, valid_until, is_active);
```

---

## 7. DEPENDENCIAS

### 7.1 Dependencias de Core

| Modulo | Estado | Requerido Para |
|--------|--------|---------------|
| MGN-013 Sales | 50% | Pricelists |
| SPEC-PRICING-RULES | Planificado | Motor de precios |

### 7.2 Dependencias de Retail

| Modulo | Tipo |
|--------|------|
| RT-001 Fundamentos | Prerequisito |

### 7.3 Bloquea a

| Modulo | Razon |
|--------|-------|
| RT-002 POS | Precios en ventas |
| RT-009 E-commerce | Precios online |

---

## 8. CRITERIOS DE ACEPTACION

### 8.1 Funcionales

- [ ] Crear lista de precios
- [ ] Asignar precios por producto
- [ ] Crear promocion porcentual
- [ ] Crear promocion monto fijo
- [ ] Crear promocion NxM
- [ ] Configurar descuento por volumen
- [ ] Generar cupones
- [ ] Validar cupon
- [ ] Canjear cupon
- [ ] Calcular precio con todas las reglas
- [ ] Motor < 100ms

### 8.2 Performance

- [ ] Calculo precio < 100ms
- [ ] Soportar 100+ promociones activas

---

## 9. ESTIMACION DETALLADA

| Componente | SP Backend | SP Frontend | Total |
|------------|-----------|-------------|-------|
| Entities + Migrations | 3 | - | 3 |
| PriceEngineService | 8 | - | 8 |
| PromotionService | 5 | - | 5 |
| CouponService | 5 | - | 5 |
| Controllers | 3 | - | 3 |
| Promotion Pages | - | 8 | 8 |
| Coupon Pages | - | 4 | 4 |
| **TOTAL** | **24** | **12** | **36** |

---

**Estado:** ANALISIS COMPLETO
**Bloqueado por:** RT-001, SPEC-PRICING-RULES
