9.7 KiB
9.7 KiB
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
- Tenant debe tener suscripción activa
- Tenant Owner debe tener método de pago válido
- Plan debe soportar per-seat billing (
pricing_model = 'per_seat')
Flujo Principal - Agregar Usuario
- Tenant Owner/Admin accede a "Gestión de Usuarios"
- Sistema muestra usuarios actuales y seats disponibles/usados
- Usuario selecciona "Invitar nuevo usuario"
- Sistema verifica límite de usuarios del plan
- Sistema calcula costo adicional prorrateado
- Sistema muestra confirmación: "Agregar usuario: +$15/mes prorrateado"
- Usuario confirma
- Sistema crea invitación y actualiza conteo de seats
- Sistema genera cargo prorrateado si aplica
- Nuevo usuario recibe email de invitación
Flujos Alternativos
FA-1: Límite de Usuarios Alcanzado
- Usuario intenta agregar usuario y alcanza max_users del plan
- Sistema muestra: "Has alcanzado el límite de usuarios de tu plan"
- Sistema ofrece: "Upgrade a plan superior" o "Contactar ventas"
- Si upgrade: Redirige a flujo de cambio de plan
FA-2: Eliminar Usuario (Reducir Seats)
- Admin elimina/desactiva usuario
- Sistema reduce conteo de seats activos
- Sistema NO genera crédito inmediato (se refleja en próxima factura)
- Próxima factura se calcula con seats actuales
FA-3: Usuario Inactivo
- Sistema detecta usuario sin login por 30+ días
- Sistema notifica a Tenant Owner
- Owner puede: mantener, desactivar, o configurar auto-desactivación
- Si auto-desactivación: seat se libera automáticamente
FA-4: Periodo de Gracia para Nuevos Usuarios
- Nuevo usuario tiene 7 días de gracia sin cargo
- Si se elimina antes de 7 días: sin cargo
- 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 | |
| 80% de seats usados | Tenant Owner | Email + In-app |
| 100% de seats alcanzado | Tenant Owner | Email + In-app |
| Cargo por nuevo seat | Tenant Owner | |
| Resumen mensual de seats | Tenant Owner |
Referencias
- RF-MGN-015-001 - Planes
- RF-MGN-015-002 - Suscripciones
- RF-MGN-015-004 - Facturación
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)