# 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; // Promociones async getActivePromotions(branchId: string): Promise; async applyPromotion(orderId: string, promotionId: string): Promise; } ``` --- ## 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 { // 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; // Promociones @Get('promotions') getPromotions(@Query() filters: PromotionFilters): Promise; @Get('promotions/active') getActivePromotions(@Query('branchId') branchId: string): Promise; @Post('promotions') createPromotion(@Body() dto: CreatePromotionDto): Promise; @Put('promotions/:id') updatePromotion(@Param('id') id: string, @Body() dto: UpdatePromotionDto): Promise; @Post('promotions/:id/activate') activatePromotion(@Param('id') id: string): Promise; @Post('promotions/:id/deactivate') deactivatePromotion(@Param('id') id: string): Promise; // Cupones @Post('coupons/generate') generateCoupons(@Body() dto: GenerateCouponsDto): Promise; @Get('coupons/:code/validate') validateCoupon(@Param('code') code: string): Promise; @Post('coupons/:code/redeem') redeemCoupon(@Param('code') code: string, @Body() dto: RedeemDto): Promise; } ``` --- ## 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