- ET-SAAS-018-sales.md: Sales pipeline/CRM module - ET-SAAS-019-portfolio.md: Product catalog module - ET-SAAS-020-commissions.md: Commissions system - ET-SAAS-021-mlm.md: Multi-Level Marketing module - ET-SAAS-022-goals.md: Goals and objectives system - Update _MAP.md with all 9 specifications All specs document: - Data model (DDL, enums, tables) - Backend architecture (endpoints, services) - Security (RLS, RBAC permissions) - Frontend status and hooks - Module integrations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
376 lines
11 KiB
Markdown
376 lines
11 KiB
Markdown
---
|
||
id: "ET-SAAS-020"
|
||
title: "Especificacion Tecnica Commissions"
|
||
type: "TechnicalSpec"
|
||
status: "Implemented"
|
||
priority: "P1"
|
||
module: "commissions"
|
||
version: "1.0.0"
|
||
created_date: "2026-02-03"
|
||
updated_date: "2026-02-03"
|
||
story_points: 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:**
|
||
```json
|
||
[
|
||
{"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
|
||
|
||
```sql
|
||
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
|
||
|
||
```typescript
|
||
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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```sql
|
||
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`
|