erp-core/docs/04-modelado/requerimientos-funcionales/mgn-015/RF-MGN-015-007-pricing-por-usuario.md

9.7 KiB
Raw Permalink Blame History

RF-MGN-015-007: Pricing por Usuario (Per-Seat Billing)

Módulo: MGN-015 - Billing y Suscripciones Prioridad: P0 (Core SaaS) Story Points: 13 Estado: Definido Fecha: 2025-12-05

Descripción

El sistema debe soportar un modelo de pricing híbrido donde los tenants pagan un precio base mensual más un cargo adicional por cada usuario activo. Esto permite un modelo SaaS escalable donde el tenant owner registra y gestiona usuarios, pagando según el número de asientos (seats) utilizados.

Modelo de Negocio

Estructura de Pricing

┌─────────────────────────────────────────────────────────────┐
│                    MODELO PER-SEAT                          │
├─────────────────────────────────────────────────────────────┤
│  Precio Total = Precio Base + (Usuarios × Precio por Seat) │
│                                                             │
│  Ejemplo Plan Professional:                                 │
│  - Base: $49/mes (incluye 1 usuario)                       │
│  - Por usuario adicional: $15/mes                          │
│  - 5 usuarios = $49 + (4 × $15) = $109/mes                 │
└─────────────────────────────────────────────────────────────┘

Planes con Per-Seat Pricing

Plan Precio Base Usuarios Incluidos Por Usuario Extra Max Usuarios
Starter $29/mes 1 $12/mes 10
Professional $49/mes 1 $15/mes 50
Business $99/mes 3 $18/mes 200
Enterprise $299/mes 10 $20/mes Ilimitado

Actores

  • Actor Principal: Tenant Owner (propietario de la cuenta)
  • Actores Secundarios:
    • Tenant Admin (gestiona usuarios)
    • Sistema (cálculo de facturación)
    • Gateway de Pagos

Precondiciones

  1. Tenant debe tener suscripción activa
  2. Tenant Owner debe tener método de pago válido
  3. Plan debe soportar per-seat billing (pricing_model = 'per_seat')

Flujo Principal - Agregar Usuario

  1. Tenant Owner/Admin accede a "Gestión de Usuarios"
  2. Sistema muestra usuarios actuales y seats disponibles/usados
  3. Usuario selecciona "Invitar nuevo usuario"
  4. Sistema verifica límite de usuarios del plan
  5. Sistema calcula costo adicional prorrateado
  6. Sistema muestra confirmación: "Agregar usuario: +$15/mes prorrateado"
  7. Usuario confirma
  8. Sistema crea invitación y actualiza conteo de seats
  9. Sistema genera cargo prorrateado si aplica
  10. Nuevo usuario recibe email de invitación

Flujos Alternativos

FA-1: Límite de Usuarios Alcanzado

  1. Usuario intenta agregar usuario y alcanza max_users del plan
  2. Sistema muestra: "Has alcanzado el límite de usuarios de tu plan"
  3. Sistema ofrece: "Upgrade a plan superior" o "Contactar ventas"
  4. Si upgrade: Redirige a flujo de cambio de plan

FA-2: Eliminar Usuario (Reducir Seats)

  1. Admin elimina/desactiva usuario
  2. Sistema reduce conteo de seats activos
  3. Sistema NO genera crédito inmediato (se refleja en próxima factura)
  4. Próxima factura se calcula con seats actuales

FA-3: Usuario Inactivo

  1. Sistema detecta usuario sin login por 30+ días
  2. Sistema notifica a Tenant Owner
  3. Owner puede: mantener, desactivar, o configurar auto-desactivación
  4. Si auto-desactivación: seat se libera automáticamente

FA-4: Periodo de Gracia para Nuevos Usuarios

  1. Nuevo usuario tiene 7 días de gracia sin cargo
  2. Si se elimina antes de 7 días: sin cargo
  3. Si permanece después de 7 días: cargo desde día 1

Reglas de Negocio

  • RN-1: El precio total = base_price + (active_seats - included_seats) × per_seat_price
  • RN-2: Usuarios incluidos en el plan no generan cargo adicional
  • RN-3: Cargos por nuevos usuarios se prorratean al día
  • RN-4: Reducción de usuarios se refleja en próxima factura (sin crédito inmediato)
  • RN-5: Usuarios desactivados no cuentan como seats activos
  • RN-6: Tenant Owner siempre cuenta como 1 seat (no eliminable)
  • RN-7: Límite máximo de usuarios según plan es hard limit
  • RN-8: Cambios de plan ajustan el per_seat_price inmediatamente

Criterios de Aceptación

  • Sistema calcula precio total basado en seats activos
  • Tenant Owner puede ver desglose de costos (base + seats)
  • Agregar usuario muestra costo adicional antes de confirmar
  • Sistema cobra prorrateado al agregar usuarios mid-cycle
  • Factura mensual detalla: cargo base + usuarios adicionales
  • Sistema respeta límite máximo de usuarios del plan
  • Dashboard muestra: seats usados / seats incluidos / max seats
  • Historial de cambios de seats disponible
  • Notificaciones cuando se acerca al límite (80%, 90%, 100%)

Entidades Involucradas

Modificaciones a billing.subscription_plans

ALTER TABLE billing.subscription_plans ADD COLUMN pricing_model VARCHAR(20) DEFAULT 'flat';
-- pricing_model: 'flat' (precio fijo), 'per_seat' (por usuario), 'usage' (por uso)

ALTER TABLE billing.subscription_plans ADD COLUMN base_price DECIMAL(10,2);
ALTER TABLE billing.subscription_plans ADD COLUMN included_seats INT DEFAULT 1;
ALTER TABLE billing.subscription_plans ADD COLUMN per_seat_price DECIMAL(10,2);
ALTER TABLE billing.subscription_plans ADD COLUMN max_seats INT;
-- max_seats NULL = ilimitado

Modificaciones a billing.subscriptions

ALTER TABLE billing.subscriptions ADD COLUMN current_seats INT DEFAULT 1;
ALTER TABLE billing.subscriptions ADD COLUMN seats_updated_at TIMESTAMPTZ;

Nueva tabla: billing.seat_changes

CREATE TABLE billing.seat_changes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES core_tenants.tenants(id),
    subscription_id UUID NOT NULL REFERENCES billing.subscriptions(id),
    user_id UUID REFERENCES core_users.users(id),
    change_type VARCHAR(20) NOT NULL, -- 'add', 'remove', 'activate', 'deactivate'
    previous_seats INT NOT NULL,
    new_seats INT NOT NULL,
    prorated_amount DECIMAL(10,2), -- Cargo/crédito prorrateado
    effective_date DATE NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_by UUID,

    CONSTRAINT chk_seat_change_type CHECK (change_type IN ('add', 'remove', 'activate', 'deactivate'))
);

Cálculos de Facturación

Fórmula de Cargo Mensual

function calculateMonthlyCharge(subscription: Subscription, plan: Plan): number {
  const extraSeats = Math.max(0, subscription.currentSeats - plan.includedSeats);
  return plan.basePrice + (extraSeats * plan.perSeatPrice);
}

Fórmula de Prorrateo (Agregar Usuario)

function calculateProratedCharge(
  perSeatPrice: number,
  addedDate: Date,
  periodEnd: Date
): number {
  const daysRemaining = differenceInDays(periodEnd, addedDate);
  const daysInPeriod = 30; // o calcular dinámicamente
  return (perSeatPrice * daysRemaining) / daysInPeriod;
}

Ejemplo de Factura

FACTURA #INV-2025-001234
Período: 01/01/2025 - 31/01/2025

Concepto                              Cantidad    Precio      Total
─────────────────────────────────────────────────────────────────────
Plan Professional (Base)                   1      $49.00     $49.00
Usuarios adicionales (4 × $15)             4      $15.00     $60.00
Usuario agregado 15/01 (prorrateado)       1       $8.00      $8.00
─────────────────────────────────────────────────────────────────────
                                         SUBTOTAL:          $117.00
                                         IVA (16%):          $18.72
                                         TOTAL:             $135.72

API Endpoints

GET /api/v1/billing/seats

Response: {
  currentSeats: 5,
  includedSeats: 1,
  extraSeats: 4,
  maxSeats: 50,
  perSeatPrice: 15.00,
  monthlySeatsCharge: 60.00,
  users: [
    { id: "uuid", email: "owner@...", role: "owner", addedAt: "2025-01-01" },
    { id: "uuid", email: "user1@...", role: "admin", addedAt: "2025-01-05" },
    ...
  ]
}

POST /api/v1/billing/seats/preview

Request: { action: "add", quantity: 2 }
Response: {
  currentSeats: 5,
  newSeats: 7,
  proratedCharge: 16.00,
  newMonthlyTotal: 139.00,
  effectiveDate: "2025-01-15"
}

Notificaciones

Evento Destinatario Canal
Usuario agregado Tenant Owner Email
80% de seats usados Tenant Owner Email + In-app
100% de seats alcanzado Tenant Owner Email + In-app
Cargo por nuevo seat Tenant Owner Email
Resumen mensual de seats Tenant Owner Email

Referencias

Notas Técnicas

  • Cache: Conteo de seats en Redis para validaciones rápidas
  • Trigger: Al crear/eliminar usuario, actualizar subscriptions.current_seats
  • Job: Reconciliación diaria de seats vs usuarios activos
  • Webhook: Notificar a sistemas externos cambios de seats

Dependencias

  • RF Requeridos: RF-MGN-015-001, RF-MGN-015-002
  • Bloqueante para: Ninguno (mejora al modelo existente)