subscriptions
Especificaciones de comportamiento
⚡
Spec minado automáticamente desde el código existente.
Esta es la línea base (baseline) del módulo
subscriptions.
Los futuros cambios usarán bloques ADDED / MODIFIED / REMOVED sobre estos requisitos.
Anclas de delta:
Requisito
Crear suscripción en modo trial
SubscriptionService.create_subscription
Una clínica sin suscripción activa SHALL poder crear una nueva suscripción.
Si el plan tiene
trial_days > 0, la suscripción MUST comenzar en estado
TRIALING con trial_ends_at = now + trial_days.
Si trial_days == 0, inicia directamente en ACTIVE.
El sistema MUST enviar el email de bienvenida tras guardar la suscripción.
Escenarios
Plan con periodo de prueba (14 días por defecto)
test_create_subscription_starts_trialing
WHEN
La clínica no tiene suscripción activa y se crea con un plan cuyo
trial_days = 14
THEN
La suscripción se persiste con
status = TRIALING, trial_ends_at = now + 14 days, seats_dietitians = 1, seats_patients = 0
Plan sin periodo de prueba
WHEN
La clínica no tiene suscripción activa y se crea con un plan cuyo
trial_days = 0
THEN
La suscripción se persiste con
status = ACTIVE y trial_ends_at = None
Clínica ya tiene suscripción activa
test_create_subscription_duplicate_raises
WHEN
La clínica ya posee una suscripción en estado
ACTIVE o TRIALING
THEN
Se lanza
SubscriptionError("Clinic already has an active subscription")
Requisito
Cobrar factura
SubscriptionService.charge_invoice
El sistema SHALL intentar cobrar una factura impagada a través del
payment_gateway.
La operación es idempotente: si invoice.paid = True MUST retornar sin cargo adicional.
Cada intento incrementa attempt_count. Si el cobro tiene éxito, el periodo avanza 30 días y la suscripción
vuelve a ACTIVE si estaba en PAST_DUE.
Si el cobro falla y attempt_count >= 3, la suscripción pasa a PAST_DUE y se envía notificación.
Escenarios
Cobro exitoso — primer intento
test_charge_invoice_success
WHEN
invoice.paid = False y el payment_gateway retorna result.success = True
THEN
invoice.paid = True, invoice.paid_at = now; current_period_start y current_period_end avanzan 30 días
Cobro fallido — máximo de intentos alcanzado
test_charge_invoice_max_attempts_past_due
WHEN
El payment_gateway falla y
invoice.attempt_count alcanza 3
THEN
sub.status = PAST_DUE y se envía email de pago fallido a la clínica
Factura ya cobrada (idempotencia)
test_charge_invoice_idempotent
WHEN
invoice.paid = True
THEN
Se retorna la factura sin llamar al
payment_gateway ni modificar nada
Requisito
Upgrade de plan
SubscriptionService.upgrade_plan
Una suscripción activa SHALL poder cambiar a un plan de tier superior
(
STARTER < PRO < CLINIC). El nuevo plan MUST tener un índice de tier
estrictamente mayor que el actual. Tras el cambio se envía email de confirmación.
Escenarios
Upgrade válido (STARTER → PRO)
test_upgrade_plan_ok
WHEN
La suscripción está en
STARTER y se solicita cambio a PRO
THEN
sub.plan_id se actualiza, se persiste y se envía send_plan_upgraded
Upgrade inválido — mismo tier o inferior
test_upgrade_plan_same_tier_raises
WHEN
El nuevo plan tiene tier igual o inferior al actual
THEN
Se lanza
SubscriptionError("upgrade_plan requires a higher-tier plan")
Invariante
Suscripción activa única por clínica
SubscriptionService.create_subscription
Una clínica MUST tener como máximo una suscripción en estado
ACTIVE o TRIALING en cualquier momento. El sistema impone esta restricción verificando db.get_active_subscription(clinic_id) antes de crear.
✓ Última verificación:
2026-06-18 · a3f91bc
Invariante
Factura no cobra dos veces
SubscriptionService.charge_invoice
charge_invoice es idempotente: si invoice.paid = True, el payment_gateway MUST NOT ser llamado y invoice.attempt_count MUST NOT incrementarse.
✓ Última verificación:
2026-06-18 · a3f91bc
Invariante
Periodo de facturación siempre avanza
SubscriptionService.charge_invoice
Tras un cobro exitoso,
sub.current_period_end MUST ser siempre mayor que el current_period_end anterior. El sistema incrementa en exactamente 30 días; nunca retrocede ni se resetea a una fecha pasada.
✓ Última verificación:
2026-06-18 · a3f91bc