| id |
title |
type |
status |
priority |
module |
version |
created_date |
updated_date |
story_points |
| ET-SAAS-020 |
Especificacion Tecnica Commissions |
TechnicalSpec |
Implemented |
P1 |
commissions |
1.0.0 |
2026-02-03 |
2026-02-03 |
8 |
ET-SAAS-020: Especificacion Tecnica - Sistema de Comisiones
Metadata
- Codigo: ET-SAAS-020
- Modulo: Commissions
- Version: 1.0.0
- Estado: Implementado
- Fecha: 2026-02-03
- Basado en: SAAS-020
1. Resumen Ejecutivo
1.1 Estado Actual
Sistema de comisiones completamente implementado.
| Capacidad |
Estado |
Notas |
| Schemes |
SI |
Esquemas configurables |
| Assignments |
SI |
Asignacion usuario-esquema |
| Entries |
SI |
Entradas de comision |
| Periods |
SI |
Periodos de pago |
| Dashboard |
SI |
Metricas y reportes |
| RLS |
SI |
Row Level Security |
1.2 Funcionalidades v1.0
Sistema completo con:
- 4 Entidades: Schemes, Assignments, Entries, Periods
- 3 Tipos de esquema: percentage, fixed, tiered
- Workflow completo: pending → approved → paid
- Periodos de pago: Ciclos configurables
- Dashboard: Metricas por usuario, periodo, esquema
2. Modelo de Datos
2.1 Schema: commissions
Tabla: schemes
| Campo |
Tipo |
Descripcion |
| id |
UUID |
PK |
| tenant_id |
UUID |
FK tenants |
| name |
VARCHAR(100) |
Nombre del esquema |
| description |
TEXT |
Descripcion |
| type |
ENUM |
percentage, fixed, tiered |
| rate |
DECIMAL(5,2) |
Tasa porcentual (0-100) |
| fixed_amount |
DECIMAL(15,2) |
Monto fijo |
| tiers |
JSONB |
[{from, to, rate}] |
| applies_to |
ENUM |
all, products, categories |
| product_ids |
UUID[] |
IDs de productos |
| category_ids |
UUID[] |
IDs de categorias |
| min_amount |
DECIMAL(15,2) |
Monto minimo para calificar |
| max_amount |
DECIMAL(15,2) |
Cap maximo por comision |
| is_active |
BOOLEAN |
Estado |
Ejemplo tiers:
[
{"from": 0, "to": 1000, "rate": 5},
{"from": 1001, "to": 5000, "rate": 7},
{"from": 5001, "to": null, "rate": 10}
]
Tabla: assignments
| Campo |
Tipo |
Descripcion |
| id |
UUID |
PK |
| tenant_id |
UUID |
FK tenants |
| user_id |
UUID |
FK users |
| scheme_id |
UUID |
FK schemes |
| starts_at |
TIMESTAMPTZ |
Inicio de vigencia |
| ends_at |
TIMESTAMPTZ |
Fin de vigencia (null = sin fin) |
| custom_rate |
DECIMAL(5,2) |
Override de tasa para este usuario |
| is_active |
BOOLEAN |
Estado |
Tabla: entries
| Campo |
Tipo |
Descripcion |
| id |
UUID |
PK |
| tenant_id |
UUID |
FK tenants |
| user_id |
UUID |
FK users (quien gana) |
| scheme_id |
UUID |
FK schemes |
| assignment_id |
UUID |
FK assignments |
| reference_type |
VARCHAR(50) |
sale, opportunity, order |
| reference_id |
UUID |
ID del registro origen |
| base_amount |
DECIMAL(15,2) |
Monto base de la venta |
| rate_applied |
DECIMAL(5,2) |
Tasa aplicada |
| commission_amount |
DECIMAL(15,2) |
Comision calculada |
| currency |
VARCHAR(3) |
Moneda |
| status |
ENUM |
pending, approved, rejected, paid, cancelled |
| period_id |
UUID |
FK periods |
| paid_at |
TIMESTAMPTZ |
Fecha de pago |
| payment_reference |
VARCHAR(255) |
Referencia externa |
| notes |
TEXT |
Notas |
| metadata |
JSONB |
Datos adicionales |
| approved_by |
UUID |
FK users |
| approved_at |
TIMESTAMPTZ |
Fecha aprobacion |
Tabla: periods
| Campo |
Tipo |
Descripcion |
| id |
UUID |
PK |
| tenant_id |
UUID |
FK tenants |
| name |
VARCHAR(100) |
"January 2026" |
| starts_at |
TIMESTAMPTZ |
Inicio del periodo |
| ends_at |
TIMESTAMPTZ |
Fin del periodo |
| total_entries |
INT |
Total de entradas |
| total_amount |
DECIMAL(15,2) |
Monto total |
| currency |
VARCHAR(3) |
Moneda |
| status |
ENUM |
open, closed, processing, paid |
| closed_at |
TIMESTAMPTZ |
Fecha cierre |
| closed_by |
UUID |
FK users |
| paid_at |
TIMESTAMPTZ |
Fecha pago |
| paid_by |
UUID |
FK users |
| payment_reference |
VARCHAR(255) |
Referencia |
| payment_notes |
TEXT |
Notas de pago |
2.2 Enums
commissions.scheme_type: percentage, fixed, tiered
commissions.applies_to: all, products, categories
commissions.entry_status: pending, approved, rejected, paid, cancelled
commissions.period_status: open, closed, processing, paid
3. Arquitectura Backend
3.1 Estructura de Archivos
backend/src/modules/commissions/
├── commissions.module.ts
├── controllers/
│ ├── schemes.controller.ts
│ ├── assignments.controller.ts
│ ├── entries.controller.ts
│ ├── periods.controller.ts
│ └── dashboard.controller.ts
├── services/
│ ├── schemes.service.ts
│ ├── assignments.service.ts
│ ├── entries.service.ts
│ ├── periods.service.ts
│ ├── calculation.service.ts
│ └── dashboard.service.ts
├── entities/
│ ├── scheme.entity.ts
│ ├── assignment.entity.ts
│ ├── entry.entity.ts
│ └── period.entity.ts
└── dto/
3.2 Endpoints API
Schemes
| Metodo |
Endpoint |
Descripcion |
| GET |
/commissions/schemes |
Listar esquemas |
| GET |
/commissions/schemes/active |
Solo activos |
| GET |
/commissions/schemes/:id |
Obtener |
| POST |
/commissions/schemes |
Crear |
| PUT |
/commissions/schemes/:id |
Actualizar |
| DELETE |
/commissions/schemes/:id |
Eliminar |
| POST |
/commissions/schemes/:id/duplicate |
Duplicar |
| POST |
/commissions/schemes/:id/toggle |
Activar/desactivar |
Assignments
| Metodo |
Endpoint |
Descripcion |
| GET |
/commissions/assignments |
Listar |
| GET |
/commissions/assignments/user/:userId |
Por usuario |
| GET |
/commissions/assignments/user/:userId/active |
Esquema activo |
| GET |
/commissions/assignments/scheme/:schemeId |
Por esquema |
| POST |
/commissions/assignments |
Asignar |
| PUT |
/commissions/assignments/:id |
Actualizar |
| DELETE |
/commissions/assignments/:id |
Remover |
| POST |
/commissions/assignments/:id/deactivate |
Desactivar |
Entries
| Metodo |
Endpoint |
Descripcion |
| GET |
/commissions/entries |
Listar |
| GET |
/commissions/entries/pending |
Pendientes |
| GET |
/commissions/entries/:id |
Obtener |
| GET |
/commissions/entries/user/:userId |
Por usuario |
| GET |
/commissions/entries/period/:periodId |
Por periodo |
| POST |
/commissions/entries/calculate |
Calcular comision |
| POST |
/commissions/entries/simulate |
Simular (sin guardar) |
| PATCH |
/commissions/entries/:id/status |
Cambiar status |
| POST |
/commissions/entries/bulk-approve |
Aprobar multiples |
| POST |
/commissions/entries/bulk-reject |
Rechazar multiples |
Periods
| Metodo |
Endpoint |
Descripcion |
| GET |
/commissions/periods |
Listar |
| GET |
/commissions/periods/open |
Periodo abierto |
| GET |
/commissions/periods/:id |
Obtener |
| GET |
/commissions/periods/:id/summary |
Resumen |
| POST |
/commissions/periods |
Crear |
| POST |
/commissions/periods/:id/close |
Cerrar |
| POST |
/commissions/periods/:id/reopen |
Reabrir |
| POST |
/commissions/periods/:id/mark-paid |
Marcar pagado |
Dashboard
| Metodo |
Endpoint |
Descripcion |
| GET |
/commissions/dashboard |
Resumen general |
| GET |
/commissions/dashboard/by-user |
Por usuario |
| GET |
/commissions/dashboard/by-period |
Por periodo |
| GET |
/commissions/dashboard/top-earners |
Top ganadores |
| GET |
/commissions/dashboard/me |
Mis ganancias |
| GET |
/commissions/dashboard/user/:userId |
Ganancias de usuario |
| GET |
/commissions/dashboard/scheme/:schemeId |
Performance de esquema |
4. Logica de Calculo
4.1 Algoritmo de Calculo
async calculateCommission(dto: CalculateCommissionDto): Promise<CommissionEntry> {
// 1. Obtener asignacion activa del usuario
const assignment = await this.getActiveAssignment(dto.userId);
// 2. Obtener esquema
const scheme = await this.getScheme(assignment.schemeId);
// 3. Verificar monto minimo
if (dto.amount < scheme.minAmount) {
throw new Error('Amount below minimum threshold');
}
// 4. Calcular tasa segun tipo
let rate: number;
switch (scheme.type) {
case 'percentage':
rate = assignment.customRate ?? scheme.rate;
break;
case 'fixed':
return this.createEntry({
...dto,
rateApplied: 0,
commissionAmount: scheme.fixedAmount
});
case 'tiered':
rate = this.getTieredRate(scheme.tiers, dto.amount);
break;
}
// 5. Calcular comision
let commissionAmount = dto.amount * (rate / 100);
// 6. Aplicar cap si existe
if (scheme.maxAmount && commissionAmount > scheme.maxAmount) {
commissionAmount = scheme.maxAmount;
}
// 7. Crear entrada
return this.createEntry({
...dto,
schemeId: scheme.id,
assignmentId: assignment.id,
rateApplied: rate,
commissionAmount
});
}
4.2 Ejemplo Tiered
Tiers: [{from:0, to:1000, rate:5}, {from:1001, to:5000, rate:7}, {from:5001, to:null, rate:10}]
Venta: $3,500
Calculo:
- Primeros $1,000: $1,000 × 5% = $50
- Siguientes $2,500: $2,500 × 7% = $175
- Total comision: $225
5. Frontend
5.1 Paginas
| Ruta |
Componente |
Estado |
| /dashboard/commissions |
CommissionsDashboard |
Pendiente |
| /dashboard/commissions/schemes |
SchemesPage |
Pendiente |
| /dashboard/commissions/entries |
EntriesPage |
Pendiente |
| /dashboard/commissions/periods |
PeriodsPage |
Pendiente |
| /dashboard/commissions/my-earnings |
MyEarningsPage |
Pendiente |
5.2 Hooks
// frontend/src/hooks/useCommissions.ts
useSchemes, useScheme, useCreateScheme, useUpdateScheme, useDeleteScheme
useAssignments, useUserAssignments, useCreateAssignment
useEntries, usePendingEntries, useCalculateCommission, useUpdateEntryStatus
usePeriods, useOpenPeriod, useClosePeriod, useMarkPeriodPaid
useCommissionsDashboard, useMyEarnings, useTopEarners
6. Seguridad
6.1 RLS
CREATE POLICY schemes_tenant_isolation ON commissions.schemes
USING (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE POLICY entries_tenant_isolation ON commissions.entries
USING (tenant_id = current_setting('app.tenant_id')::uuid);
6.2 Permisos RBAC
| Permiso |
Descripcion |
| commissions:read |
Ver comisiones |
| commissions:write |
Crear/editar esquemas |
| commissions:approve |
Aprobar comisiones |
| commissions:pay |
Marcar como pagadas |
| commissions:manage |
Administrar todo |
7. Integraciones
| Modulo |
Integracion |
| sales |
Trigger en OpportunityWon |
| goals |
Tracking de metas de comision |
| mlm |
Comisiones MLM |
| notifications |
Alertas de aprobacion/pago |
| audit |
Log de cambios |
8. Referencias
- DDL:
database/ddl/schemas/commissions/
- Backend:
backend/src/modules/commissions/
- Frontend:
frontend/src/services/commissions/
- Hooks:
frontend/src/hooks/useCommissions.ts