--- id: "ET-SAAS-021" title: "Especificacion Tecnica MLM (Multi-Level Marketing)" type: "TechnicalSpec" status: "Implemented" priority: "P1" module: "mlm" version: "1.0.0" created_date: "2026-02-03" updated_date: "2026-02-03" story_points: 13 --- # ET-SAAS-021: Especificacion Tecnica - MLM (Multi-Level Marketing) ## Metadata - **Codigo:** ET-SAAS-021 - **Modulo:** MLM - **Version:** 1.0.0 - **Estado:** Implementado (Backend) - **Fecha:** 2026-02-03 - **Basado en:** SAAS-021 --- ## 1. Resumen Ejecutivo ### 1.1 Estado Actual Sistema MLM con backend implementado, UI pendiente. | Capacidad | Estado | Notas | |-----------|--------|-------| | Structures | SI | Unilevel, Binary, Matrix | | Ranks | SI | Rangos con requisitos | | Nodes | SI | Arbol de distribuidores | | Commissions | SI | Comisiones multinivel | | Bonuses | SI | Bonos de rango | | LTREE | SI | Consultas eficientes | | RLS | SI | Row Level Security | | UI | NO | Frontend pendiente | ### 1.2 Funcionalidades v1.0 Sistema completo con: - **6 Entidades**: Structures, Ranks, Nodes, Commissions, Bonuses, RankHistory - **4 Tipos de estructura**: unilevel, binary, matrix, hybrid - **5 Tipos de comision**: level, matching, infinity, leadership, pool - **4 Tipos de bono**: rank_achievement, rank_maintenance, fast_start, pool_share - **LTREE**: Para consultas eficientes de upline/downline --- ## 2. Modelo de Datos ### 2.1 Schema: mlm #### Tabla: structures | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | name | VARCHAR(100) | Nombre estructura | | description | TEXT | Descripcion | | type | ENUM | unilevel, binary, matrix, hybrid | | config | JSONB | Configuracion especifica | | level_rates | JSONB | Tasas por nivel | | matching_rates | JSONB | Tasas de matching bonus | | is_active | BOOLEAN | Estado | **Configuracion por tipo:** ```json // Unilevel {"max_width": null, "max_depth": 10} // Binary {"spillover": "left_first" | "weak_leg" | "balanced"} // Matrix {"width": 3, "depth": 7} ``` **Level rates:** ```json [ {"level": 1, "rate": 0.10}, {"level": 2, "rate": 0.05}, {"level": 3, "rate": 0.03} ] ``` #### Tabla: ranks | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | structure_id | UUID | FK structures | | name | VARCHAR(100) | "Silver", "Gold", etc. | | level | INT | 1, 2, 3... (orden) | | badge_url | VARCHAR(500) | Imagen de badge | | color | VARCHAR(7) | Color hex | | requirements | JSONB | Requisitos | | bonus_rate | DECIMAL(10,4) | Tasa de bono adicional | | benefits | JSONB | Beneficios del rango | | is_active | BOOLEAN | Estado | **Requisitos ejemplo:** ```json { "personal_volume": 1000, "group_volume": 10000, "direct_referrals": 3, "active_legs": 2, "rank_in_legs": {"rank_level": 2, "count": 1} } ``` #### Tabla: nodes | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | structure_id | UUID | FK structures | | user_id | UUID | FK users | | parent_id | UUID | FK nodes (padre directo) | | sponsor_id | UUID | FK nodes (patrocinador) | | position | INT | 1=left, 2=right (binary) | | path | LTREE | Ruta materializada | | depth | INT | Profundidad en el arbol | | rank_id | UUID | FK ranks (rango actual) | | highest_rank_id | UUID | FK ranks (maximo alcanzado) | | personal_volume | DECIMAL(15,2) | Volumen personal | | group_volume | DECIMAL(15,2) | Volumen de grupo | | direct_referrals | INT | Referidos directos | | total_downline | INT | Total de linea descendente | | total_earnings | DECIMAL(15,2) | Ganancias totales | | status | ENUM | pending, active, inactive, suspended | | joined_at | TIMESTAMPTZ | Fecha de ingreso | | invite_code | VARCHAR(20) | Codigo de invitacion | #### Tabla: commissions | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | node_id | UUID | FK nodes (receptor) | | source_node_id | UUID | FK nodes (origen) | | type | ENUM | level, matching, infinity, leadership, pool | | level | INT | Nivel de profundidad | | source_amount | DECIMAL(15,2) | Monto origen | | rate_applied | DECIMAL(10,4) | Tasa aplicada | | commission_amount | DECIMAL(15,2) | Comision final | | currency | VARCHAR(3) | Moneda | | period_id | UUID | FK commissions.periods | | source_reference | VARCHAR(200) | Referencia a transaccion | | status | ENUM | pending, approved, paid, cancelled | | paid_at | TIMESTAMPTZ | Fecha de pago | #### Tabla: bonuses | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | node_id | UUID | FK nodes | | rank_id | UUID | FK ranks | | type | ENUM | rank_achievement, rank_maintenance, fast_start, pool_share | | amount | DECIMAL(15,2) | Monto del bono | | currency | VARCHAR(3) | Moneda | | period_id | UUID | FK commissions.periods | | status | ENUM | pending, approved, paid, cancelled | | paid_at | TIMESTAMPTZ | Fecha de pago | | achieved_at | TIMESTAMPTZ | Fecha de logro | #### Tabla: rank_history | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | node_id | UUID | FK nodes | | rank_id | UUID | FK ranks (nuevo rango) | | previous_rank_id | UUID | FK ranks (rango anterior) | | personal_volume_at | DECIMAL(15,2) | Snapshot | | group_volume_at | DECIMAL(15,2) | Snapshot | | direct_referrals_at | INT | Snapshot | | achieved_at | TIMESTAMPTZ | Fecha de logro | ### 2.2 Enums ```sql mlm.structure_type: unilevel, binary, matrix, hybrid mlm.node_status: pending, active, inactive, suspended mlm.commission_type: level, matching, infinity, leadership, pool mlm.commission_status: pending, approved, paid, cancelled mlm.bonus_type: rank_achievement, rank_maintenance, fast_start, pool_share ``` --- ## 3. LTREE para Consultas Eficientes ### 3.1 Estructura del Path ``` Ejemplo: root.node1.node2.node3 └── Ruta desde la raiz hasta el nodo ``` ### 3.2 Consultas Comunes ```sql -- Obtener todos los ancestros (upline) SELECT * FROM mlm.nodes WHERE path @> 'root.a.b.c'; -- Obtener todos los descendientes (downline) SELECT * FROM mlm.nodes WHERE path <@ 'root.a.b.c'; -- Obtener descendientes hasta N niveles SELECT * FROM mlm.nodes WHERE path <@ 'root.a.b.c' AND nlevel(path) - nlevel('root.a.b.c') <= 3; -- Obtener hermanos SELECT * FROM mlm.nodes WHERE subpath(path, 0, -1) = 'root.a.b'; ``` --- ## 4. Arquitectura Backend ### 4.1 Estructura de Archivos ``` backend/src/modules/mlm/ ├── mlm.module.ts ├── controllers/ │ ├── structures.controller.ts │ ├── ranks.controller.ts │ ├── nodes.controller.ts │ ├── commissions.controller.ts │ └── my-network.controller.ts ├── services/ │ ├── structures.service.ts │ ├── ranks.service.ts │ ├── nodes.service.ts │ ├── commissions.service.ts │ ├── calculation.service.ts │ └── genealogy.service.ts ├── entities/ │ ├── structure.entity.ts │ ├── rank.entity.ts │ ├── node.entity.ts │ ├── commission.entity.ts │ ├── bonus.entity.ts │ └── rank-history.entity.ts └── dto/ ``` ### 4.2 Endpoints API #### Structures | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /mlm/structures | Listar | | GET | /mlm/structures/:id | Obtener | | POST | /mlm/structures | Crear | | PUT | /mlm/structures/:id | Actualizar | | DELETE | /mlm/structures/:id | Eliminar | #### Ranks | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /mlm/ranks | Listar | | GET | /mlm/ranks/:id | Obtener | | POST | /mlm/ranks | Crear | | PUT | /mlm/ranks/:id | Actualizar | | DELETE | /mlm/ranks/:id | Eliminar | | POST | /mlm/ranks/evaluate/:structureId | Evaluar todos | #### Nodes | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /mlm/nodes | Listar | | GET | /mlm/nodes/:id | Obtener | | POST | /mlm/nodes | Crear (registrar en red) | | PUT | /mlm/nodes/:id | Actualizar | | PATCH | /mlm/nodes/:id/status | Cambiar status | | GET | /mlm/nodes/:id/downline | Linea descendente | | GET | /mlm/nodes/:id/upline | Linea ascendente | | GET | /mlm/nodes/:id/tree | Arbol visual | #### My Network (Usuario autenticado) | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /mlm/my/dashboard | Mi dashboard | | GET | /mlm/my/network | Mi red | | GET | /mlm/my/earnings | Mis ganancias | | GET | /mlm/my/rank | Mi rango actual | | POST | /mlm/my/invite | Generar link de invitacion | #### Commissions | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /mlm/commissions | Listar | | GET | /mlm/commissions/:id | Obtener | | POST | /mlm/commissions/calculate | Calcular | | PATCH | /mlm/commissions/:id/status | Cambiar status | | GET | /mlm/commissions/by-level | Por nivel | --- ## 5. Logica de Comisiones ### 5.1 Algoritmo de Calculo por Nivel ```typescript async calculateLevelCommissions(sale: Sale): Promise { const commissions: Commission[] = []; // Obtener nodo del vendedor const sourceNode = await this.getNodeByUserId(sale.userId); // Obtener estructura y tasas const structure = await this.getStructure(sourceNode.structureId); const levelRates = structure.levelRates; // Recorrer upline let currentNode = await this.getParentNode(sourceNode.id); let level = 1; while (currentNode && level <= levelRates.length) { const rate = levelRates.find(r => r.level === level)?.rate || 0; if (rate > 0 && currentNode.status === 'active') { commissions.push({ nodeId: currentNode.id, sourceNodeId: sourceNode.id, type: 'level', level, sourceAmount: sale.amount, rateApplied: rate, commissionAmount: sale.amount * rate }); } currentNode = await this.getParentNode(currentNode.id); level++; } return commissions; } ``` ### 5.2 Ejemplo ``` Estructura: 3 niveles (10%, 5%, 3%) Venta: $1,000 Usuario D hace venta → Nivel 1 (Usuario C): $1,000 × 10% = $100 Nivel 2 (Usuario B): $1,000 × 5% = $50 Nivel 3 (Usuario A): $1,000 × 3% = $30 ``` --- ## 6. Frontend (Pendiente) ### 6.1 Paginas Propuestas | Ruta | Componente | Estado | |------|------------|--------| | /dashboard/mlm | MLMDashboard | Pendiente | | /dashboard/mlm/network | NetworkPage | Pendiente | | /dashboard/mlm/genealogy | GenealogyPage | Pendiente | | /dashboard/mlm/ranks | RanksPage | Pendiente | | /dashboard/mlm/commissions | MLMCommissionsPage | Pendiente | | /dashboard/mlm/my-network | MyNetworkPage | Pendiente | ### 6.2 Hooks Existentes ```typescript // frontend/src/hooks/useMlm.ts useStructures, useStructure, useCreateStructure, useUpdateStructure useRanks, useRank, useCreateRank, useEvaluateRanks useNodes, useNode, useNodeDownline, useNodeUpline, useNodeTree useMyDashboard, useMyNetwork, useMyEarnings, useMyRank, useGenerateInviteLink useMLMCommissions, useCalculateCommissions, useCommissionsByLevel ``` --- ## 7. Seguridad ### 7.1 RLS ```sql CREATE POLICY nodes_tenant_isolation ON mlm.nodes USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY commissions_tenant_isolation ON mlm.commissions USING (tenant_id = current_setting('app.tenant_id')::uuid); ``` ### 7.2 Permisos RBAC | Permiso | Descripcion | |---------|-------------| | mlm:read | Ver red y comisiones | | mlm:write | Gestionar nodos | | mlm:admin | Administrar estructuras | | mlm:calculate | Ejecutar calculos | --- ## 8. Integraciones | Modulo | Integracion | |--------|-------------| | sales | Trigger en ventas para comisiones | | commissions | Periodos compartidos | | goals | Metas de volumen | | notifications | Alertas de rango | | audit | Log de cambios | --- ## 9. Referencias - DDL: `database/ddl/schemas/mlm/` - Backend: `backend/src/modules/mlm/` - Frontend Services: `frontend/src/services/mlm/` - Frontend Hooks: `frontend/src/hooks/useMlm.ts`