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
import { PartnersService } from '@erp-core/core';
import { ContactsService } from '@erp-core/core';
2.3 Servicios a Extender
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)
// 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
@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
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
// 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
-- 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
-- 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
8.2 Performance
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