# Phase 5 Implementation & Settlement Design Document **Variable Remuneration, Compensation, and Commissions System - Hoteles Estelar** --- This document outlines the detailed architectural decisions, logical workflows, API specifications, UI mockups, and E2E verification plans for **Phase 5: Settlement Engine & Approvals**. ## 1. Architectural Strategy & Logic Flows ### 1.1. Core Settlement Calculation Engine (US-COM-006) The system calculates commissions automatically based on defined rules. For a target period (`YYYY-MM`), the calculation steps are: 1. **Fetch Active Plans**: Resolve the active version of each `CompensationPlan` for target collaborators. 2. **Fetch Goals**: Resolve the goal amount for target users from the `Goal` table for the matching period. 3. **Fetch Sales Results**: Retrieve and sum the total `amount` and `salesCount` of `SalesResult` records generated by each collaborator in the period. 4. **Determine Achievement**: $$\text{Achievement \%} = \frac{\text{Sum of Sales Amount}}{\text{Goal Amount}}$$ 5. **Apply Rules**: - Compare the Achievement % against boundaries in the plan's `CalculationRule` list. - For `TIER` rules: Payout is calculated as: $$\text{Commission} = \text{Sum of Sales Amount} \times \text{Rule Rate}$$ - For `BONUS` rules: Payout is the fixed `payoutAmount` configured. 6. **Enforce Cap Limit**: If the sum of calculated payouts exceeds the plan's `maxCap`, the payout is capped at `maxCap`. ### 1.2. Retroactive Adjustments & PMS Clawbacks Hotel booking modifications, refunds, or cancellations frequently occur after past commissions have been paid. The engine processes retroactive adjustments as follows: ```mermaid graph TD A[Start Calculation for Month M] --> B[Retrieve Month M-1 and M-2 Sales Results] B --> C[Fetch Original Settlements for Month M-1 and M-2] C --> D{Any sales discrepancy found?} D -->|No| E[Continue with normal month M calculation] D -->|Yes| F[Recalculate past commission with live sales data] F --> G[Calculate Delta: Corrected Payout - Previously Paid Payout] G --> H[Create Pending Adjustment Settlement record in DB linked to Original ID] H --> I[Add Delta to Month M Total Payout: Total = Commission + Bonus + Sum(Deltas)] ``` - If $\Delta \text{Payout} < 0$, it is treated as a **Clawback** (reduction). - If $\Delta \text{Payout} > 0$, it is treated as an **Adjustment Credit** (addition). - A self-referencing relationship is stored in the `Settlement` table via `originalSettlementId` to preserve the historical audit trail. ### 1.3. Webhook Branching & Environment Isolation To prevent test calculation suites from polluting production records, the n8n workflows implement a strict routing architecture: 1. **Conditional Branching**: The n8n webhook endpoint evaluates the request URI path. 2. **Test/Staging calls**: Webhooks routed via `/webhook-test/calculate-settlements` route to `http://app-dev:3001` (running development stack, connected to `TEST_DATABASE_URL`) and validate signatures using `N8N_WEBHOOK_SECRET_DEV`. 3. **Production calls**: Webhooks routed via `/webhook/calculate-settlements` route to `http://app-prod:3000` and validate signatures using `N8N_WEBHOOK_SECRET`. ### 1.4. SOC 2 Log Redaction All Next.js API routes run console outputs through a filtering proxy that redacts base salaries, passwords, and custom compensation parameters before writing to standard streams: - Intercepts logs on settlement mutations. - Replaces values for keys: `password`, `passwordHash`, `baseSalary`, `bankAccount`, and `salary` with `[REDACTED]`. --- ## 2. API Specifications ### 2.1. `POST /api/settlements/calculate` (Process Settlements) - **Role Restriction**: `admin` | `analyst` - **Request Body**: ```json { "period": "2026-06", "simulateOnly": false } ``` - **Response**: - `201 Created` (Direct commit mode): ```json { "success": true, "code": "SETTLEMENTS_CALCULATED", "metadata": { "count": 14, "totalCommission": 85000.00, "totalAdjustment": -3500.00 } } ``` ### 2.2. `POST /api/settlements/[id]/approve` (Approve Payout) - **Role Restriction**: `commercial_leader` - **Logic**: Enforces RLS. A leader can only approve settlements belonging to collaborators of hotels in their region. Sets status to `APPROVED`, logs `approvedBy`, and triggers a callback notification. - **Response**: `200 OK`. ### 2.3. `POST /api/settlements/[id]/reject` (Reject Payout) - **Role Restriction**: `commercial_leader` - **Request Body**: ```json { "reason": "Venta reportada del colaborador no coincide con el cierre del hotel" } ``` - **Logic**: Sets status to `REJECTED`, stores the mandatory reason in the database, and sets the state to allow correction and recalculation. - **Response**: `200 OK`. --- ## 3. UI Design Specifications ### 3.1. Simulation Dashboard (`/sales/simulation`) Provides analysts with a dry-run environment: 1. **Period Selector**: Dropdown to select the target payout month. 2. **Comparison Matrix**: Grid displaying: - Collaborator | Plan Version | Goal | Confirmed Sales | Achievement % | Proposed Commission | Retroactive Adjustments | Total Payout (Simulated) 3. **Dry-Run Mode Toggle**: Execute calculations without writing data or triggering final callbacks. ### 3.2. Approvals Panel (`/settlements/approvals`) Allows leaders to sign off on payouts: 1. **Segregated List**: Displays pending settlements, restricted to the leader's region. 2. **Reason Modal**: Appears when selecting "Rechazar", requiring a text entry. 3. **Status Badges**: Shows simulated vs finalized payout progress. --- ## 4. Headless Puppeteer Verification Plan We will create a verification script `prisma/test-phase5-ui.js` that tests: 1. **Engine Accuracy**: Seed sales/goals, run calculation, verify output against expected mathematical tiers. 2. **Clawback Delta Logic**: Update a past month's sale amount, run the calculation again, and verify that the current month's proposed settlement contains a negative adjustment equal to the commission delta. 3. **Leader Isolation**: Confirm `lider_ctg` is blocked from approving a settlement for a collaborator in Antioquia (`EST-MDE`). 4. **Rejection Constraint**: Ensure a rejection request fails (400) if the reason is omitted.