# 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 ```sql 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 ```sql 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 ```sql 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 ```typescript 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) ```typescript 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 ```typescript 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 ```typescript 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 - [RF-MGN-015-001](./RF-MGN-015-001-gestion-planes-suscripcion.md) - Planes - [RF-MGN-015-002](./RF-MGN-015-002-gestion-suscripciones-tenant.md) - Suscripciones - [RF-MGN-015-004](./RF-MGN-015-004-facturacion-cobros.md) - 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)