semillero-special-hotel/docs/ARCHITECTURE.md

12 KiB

Architecture and System Design

Variable Remuneration, Compensation, and Commissions System - Hoteles Estelar


1. Technical Stack

  • Frontend: Next.js App Router (React + TypeScript).
  • Styling: Vanilla CSS utilizing CSS Modules (*.module.css) for component-level style isolation, paired with a global variables system (globals.css) defining the design tokens.
  • Backend: Next.js API Routes (Node.js runtime).
  • ORM: Prisma ORM (providing type-safe database queries and automated schema migrations).
  • Database: PostgreSQL (decoupled, configured via environment variables to run on any host/network).
  • Workflow Engine: n8n (external service triggered via API Webhooks). Runs the actual calculation steps, anomaly checks, AI-assisted audits, and notifications. This allows live monitoring of tasks and flexible AI model switching (e.g., swapping OpenAI/Anthropic/Ollama models within n8n nodes without rebuilding the Next.js codebase).
  • Auditing: Ready-to-use, community-proven @explita/prisma-audit-log Prisma Client Extension. It automatically intercepts database mutations (create, update, delete), tracks changes (old vs new state), handles sensitive field masking (e.g. passwords), and records logs into the AuditLog table. This approach is database-agnostic, requires no native OS dependencies, and runs purely inside the runtime.

2. Decoupled Network & Environment Independence

The project is structured to run in any isolated Docker environment. All parameters are fed via environment variables to cleanly partition production and development configurations:

2.1. Environment Variables

  • Core Variables (Both Env):
    • DATABASE_URL: Connection string for the active database (Production or Development).
    • NEXT_PUBLIC_APP_URL: The domain or local host path of the running application.
    • NEXTAUTH_SECRET: Secret key for JWT session validation.
  • Development-Exclusive Test Variables:
    • TEST_DATABASE_URL: Connection string to the secondary sandbox/test database.
    • N8N_TEST_WEBHOOK_URL: The n8n testing webhook entry point. Used by development services and test runners.
    • N8N_WEBHOOK_SECRET: Token to authorize and verify n8n webhook payload signatures locally.

2.2. Component Interaction:

graph TD
    User([User Client]) -->|HTTPS| WebApp[Next.js App Router]
    
    subgraph Isolated Network
        WebApp -->|Prisma Client| DB[(PostgreSQL Main)]
        WebApp -.->|Prisma Client - Test Env| DBTest[(PostgreSQL Test)]
        WebApp -->|HTTP POST Webhook /webhook-test| n8n[n8n Workflow Engine]
        n8n -->|IF Webhook Path Match| DBTest
        n8n -->|Else| DB
        n8n -->|HTTP POST Callback| WebApp
    end

3. Database Schema Design (Entity-Relationship Diagram)

erDiagram
    REGIONS ||--o{ HOTELS : contains
    HOTELS ||--o{ USERS : houses
    USERS ||--o{ GOALS : achieves
    USERS ||--o{ SALES_RESULTS : generates
    USERS ||--o{ SETTLEMENTS : receives
    
    COMPENSATION_PLANS ||--o{ CALCULATION_RULES : dictates
    COMPENSATION_PLANS ||--o{ SETTLEMENTS : calculates
    
    USERS ||--o{ AUDIT_LOGS : executes
    USERS ||--o{ NOTIFICATIONS : receives

    REGIONS {
        int id PK
        string name
        string code
    }

    HOTELS {
        int id PK
        string name
        string code
        int region_id FK
        string status
    }

    USERS {
        int id PK
        string username
        string email
        string password_hash
        string role "admin | director | hotel_manager | commercial_leader | analyst | auditor | collaborator"
        int hotel_id FK
        string area
        string status "ACTIVE | INACTIVE"
        datetime created_at
    }

    COMPENSATION_PLANS {
        int id PK
        string name
        string code UK
        datetime validity_start
        datetime validity_end
        string type "PERCENTAGE | SCALE | CONDITIONAL | FIXED"
        string formula "JSON or string representation"
        decimal meta_amount
        decimal percentage_rate
        decimal max_cap
        string status "DRAFT | ACTIVE | INACTIVE"
        int version
        int created_by FK
        datetime created_at
    }

    CALCULATION_RULES {
        int id PK
        int plan_id FK
        string type "TIER | BONUS"
        decimal min_achievement "percentage"
        decimal max_achievement "percentage"
        decimal rate "multiplier or percentage"
        decimal payout_amount "fixed payment"
    }

    GOALS {
        int id PK
        string target_type "INDIVIDUAL | TEAM | HOTEL"
        int target_id "user_id, team_id, or hotel_id"
        string period "YYYY-MM"
        decimal amount
        datetime created_at
    }

    SALES_RESULTS {
        int id PK
        string source "EXCEL | API"
        int hotel_id FK
        int user_id FK "colaborador"
        string period "YYYY-MM"
        decimal amount
        int sales_count
        string status "PENDING | PROCESSED"
        string idempotency_key UK
        string transaction_id
        int uploaded_by FK
        datetime created_at
    }

    SETTLEMENTS {
        int id PK
        string period "YYYY-MM"
        int plan_id FK "References specific version of the plan"
        int user_id FK
        decimal sales_amount
        decimal goal_amount
        decimal achievement_percentage
        decimal calculated_commission
        decimal calculated_bonus
        decimal adjustment_amount "Clawback or adjustment delta"
        decimal total_payout "calculated_commission + calculated_bonus + adjustment_amount"
        string status "SIMULATED | PENDING | APPROVED | REJECTED"
        int approved_by FK
        datetime approved_at
        string rejection_reason
        int original_settlement_id FK "Self-references the settlement adjusted, if any"
        string adjustment_notes
        datetime created_at
    }

    AUDIT_LOGS {
        int id PK
        int user_id FK
        string action "CREATE | UPDATE | DELETE | APPROVE | REJECT | LOGIN"
        string target_table
        int target_id
        json previous_value
        json new_value
        string ip_address
        datetime created_at
    }

    NOTIFICATIONS {
        int id PK
        int user_id FK
        string title
        string message
        string status "UNREAD | READ"
        string type "EMAIL | PUSH"
        datetime sent_at
    }

4. Workflows & n8n Integration Model

By offloading calculations and integrations to n8n, we achieve a highly visual, modular, and editable workflow architecture.

4.1. Sales Data Import Workflow

  1. Trigger: Next.js calls POST /api/n8n/import-sales which forwards the parsed Excel payload to n8n's webhook URL.
  2. n8n Processing:
    • Iterates through sales records.
    • Queries Next.js APIs to validate collaborator codes and hotel IDs.
    • Cleanses data and identifies duplicates.
    • Uses an LLM node (with configurable models: GPT-4, Claude 3.5, Gemini, etc.) to perform semantic anomaly checks (e.g., flag sales values that deviate more than 2.5 standard deviations from the collaborator's monthly average).
  3. Response: n8n POSTs the sanitized/flagged list back to POST /api/sales/batch-save in Next.js to update the database.

4.2. Settlement Calculation Workflow

  1. Trigger: Next.js calls n8n to execute the calculation for period YYYY-MM.
  2. n8n Processing:
    • Pulls active plans, individual goals, and actual sales from the Next.js API.
    • Evaluates the mathematical formulas.
    • Evaluates rule scales and applies caps.
    • Generates notifications (via email or push notifications) using n8n integrations.
  3. Response: Updates database records via the Next.js API and completes the task.

4.3. Test Webhook Branching & Database Isolation in n8n

To ensure complete isolation of production data, all n8n workflows must follow a strict testing branch architecture:

  1. Webhook Entry Node: n8n listens on two webhook path variants:
    • Production calls hit: /webhook/calculate-commissions
    • Test suite calls hit: /webhook-test/calculate-commissions
  2. Conditional Path Routing:
    • An IF node immediately checks if the webhook request path contains webhook-test.
    • True (Test Mode): The workflow overrides its database credential node configurations to connect to TEST_DATABASE_URL (the secondary test sandbox database) and makes API callbacks back to the Next.js test instance.
    • False (Prod Mode): The workflow executes against the main DATABASE_URL and interacts with the production Next.js instance.

5. Security & Access Control Model (RBAC)

We define role-based access restrictions as follows:

Role Access Level Restrictions
Administrador Full system write & read. None.
Director Comercial Reads all dashboards & reports. Can configure metadata. Cannot calculate or approve.
Gerente Hotel Reads data, sales, and settlements for their specific Hotel. Restricted to hotel_id.
Líder Comercial Triggers simulations, views dashboards. Approves/Rejects settlements. Restricted to their region/team.
Analista Financiero Reviews calculations. Exports consolidated PDF/Excel reports. Cannot approve.
Consulta Read-only. No mutations allowed.
Colaborador Consults own history & dashboard. Restricted to user_id.

5.1. PostgreSQL Row-Level Security (RLS) & Data Isolation

To ensure absolute segregation of sensitive compensation data, the database implements Row-Level Security (RLS). RLS is enforced at the database layer (or via Prisma client middleware setting transaction context parameters), guaranteeing security even if application queries omit filters.

  • Tenant Isolation Rules:
    • Colaboradores: Can only select rows from SETTLEMENTS, SALES_RESULTS, and GOALS where user_id = current_setting('app.current_user_id').
    • Gerentes: Can only select rows where hotel_id = current_setting('app.current_hotel_id').
    • Líderes: Can select rows within their assigned regions or teams (region_id = current_setting('app.current_region_id')).
    • Administradores / Analistas: RLS is bypassed to allow system-wide computations and consolidated reporting.
  • Audit Trail Immutability:
    • The AUDIT_LOGS table has RLS policies that prevent UPDATE or DELETE actions for all users, including administrators. It is strictly write-only (INSERT operations only).

6. Dual-Environment Docker Deployment Model

We design the containerized architecture to run two distinct instances of the application concurrently from the same codebase, ensuring clean division.

6.1. Service Configurations

  • Production Container (app-prod):
    • Network Port: Exposed on port 3000 (mapped via Caddy to special-hotel.yourdomain.com).
    • Database: Bound to the production DATABASE_URL.
    • Logs: Prefixed with [PROD] inside the container engine.
  • Development Container (app-dev):
    • Network Port: Exposed on port 3001 (mapped via Caddy to special-hotel-dev.yourdomain.com).
    • Database: Bound to TEST_DATABASE_URL (acting as its main DATABASE_URL for test isolated migrations).
    • Logs: Prefixed with [DEV] for easy debugging contrast.

6.2. Graceful Dev Failure Model

The development instance utilizes a validation startup hook. If any development-exclusive environment variables (like TEST_DATABASE_URL) are omitted:

  1. The app-dev container logs a clear notification: [DEV] Missing required development variables. Gracefully shutting down development service.
  2. The entrypoint script exits with exit code 0.
  3. Docker or the compose orchestrator registers the container as cleanly stopped (not crashed). The production stack is completely unaffected, avoiding restart-loop penalties or deployment failures.