# Reactivar suscripción cancelada dentro del período pagado

## Resumen

Cuando un cliente cancela su suscripción, el acceso continúa hasta `currentPeriodEnd` (política existente). Antes, durante esa ventana no había manera de revertir la cancelación — el cliente tenía que esperar a que expirara para re-contratar. Ahora puede reactivarla con un botón desde `/configuracion/suscripcion`.

## Motivación

UX real: cliente cancela por error o cambia de opinión, quiere volver. Forzar que espere a fin de período + re-contratar desde cero es fricción innecesaria (y posible pérdida si se van con la competencia en esa ventana).

## Mecánica MercadoPago

Un preapproval cancelado en MP es **terminal** — no se puede reactivar. Reactivar = crear un preapproval **nuevo** con los mismos parámetros (plan, amount, frequency) y `auto_recurring.start_date` apuntando al final del período actual para evitar doble cobro.

```
T0 (hoy): cancelled, currentPeriodEnd = T0+15d
T0: Usuario click "Reactivar"
    → backend crea NUEVO preapproval
      reason:   "Reactivación Plan X (frequency) - Tenant"
      amount:   mismo
      frequency: mismo
      start_date: T0+15d ← clave para no cobrar doble
    → Subscription.status = 'pending'
    → Subscription.mpPreapprovalId = nuevo ID
    → Retorna paymentUrl
T0..T0+14d: Usuario sigue teniendo acceso (período ya pagado)
T0+15d: MP ejecuta primer cobro → webhook → status 'authorized'
T0+15d+30d (o +365d): siguiente cobro
```

## Reglas y validaciones

| Condición | Resultado |
|-----------|-----------|
| Tenant no tiene suscripción cancelada | 400 "No hay suscripción cancelada para reactivar" |
| Suscripción cancelled pero `currentPeriodEnd` ya venció | 400 "El período pagado ya venció — contrata un nuevo plan desde el selector" (UI ya lo sabe: `isCancelledExpired` muestra el picker) |
| Plan cancelado era custom | 400 "Reactivación de plan custom requiere coordinación con el admin global" |
| MP no configurado | 503 con mensaje claro |
| MP rechaza (token inválido) | 503 "MercadoPago rechazó la solicitud" |
| Reactivación exitosa | Limpia `pendingPlan`, `upgradeTargetPlan`, etc. El estado queda limpio como una sub recién contratada |

## Archivos

### Backend
- `apps/api/src/services/payment/mercadopago.service.ts` — `createPreapproval` ahora acepta `startDate?: Date` opcional (traduce a `auto_recurring.start_date` ISO si es fecha futura; MP rechaza fechas pasadas)
- `apps/api/src/services/payment/subscription.service.ts` — `reactivateSubscription({ tenantId, payerEmail })`
- `apps/api/src/controllers/subscription.controller.ts` — `reactivateMe` handler
- `apps/api/src/routes/subscription.routes.ts` — `POST /api/subscriptions/me/reactivate`

### Frontend
- `apps/web/lib/api/subscription.ts` — `reactivateMe()`
- `apps/web/lib/hooks/use-subscription.ts` — `useReactivateMe()`
- `apps/web/app/(dashboard)/configuracion/suscripcion/page.tsx`:
  - Handler `handleReactivate` — llama mutation, abre `paymentUrl` en nueva pestaña
  - Banner naranja `isCancelledInPeriod` incluye botón "Reactivar suscripción"
  - Card "Tu Suscripción" muestra "Reactivar" como acción principal (primary) en vez de "Cancelar"
  - Limpia la condición redundante `(isActive || ... || isCancelledInPeriod) && !isCancelledInPeriod`

## Flow UI

```
Estado: isCancelledInPeriod (status='cancelled', currentPeriodEnd en el futuro)
   ↓
Banner naranja "Suscripción cancelada — tienes acceso hasta X"
  + botón inline "Reactivar suscripción"
   ↓
Card "Tu Suscripción" con botón primary "Reactivar suscripción"
   ↓
Click → POST /me/reactivate → { paymentUrl }
   ↓
window.open(paymentUrl, '_blank') → MP checkout
   ↓
Usuario autoriza en MP
   ↓
Webhook `subscription_preapproval` → status='authorized'
   ↓
UI refresca (React Query invalidation) → muestra "Activa"
```

## Decisiones descartadas

### Reactivar con cambio de plan en el mismo flow
**Tentación:** mostrar modal con picker en vez de botón "Reactivar" plano.

**Por qué no:** complica el flujo (reactivar + cambiar plan = dos semánticas mezcladas). MVP = simple: reactiva con mismo plan. Si quiere otro plan, usa "Cambiar plan" después de reactivar (ya existe).

### "Undo cancel" retentivo (sin re-autorización de MP)
**Tentación:** si la cancelación fue hace < 24h y el preapproval MP aún está `paused` (no `cancelled`), teóricamente podríamos re-activarlo sin crear uno nuevo.

**Por qué no:** `cancelSubscription` llama `cancelPreapproval` inmediatamente → MP lo marca `cancelled` terminal. Recuperarlo requeriría: (a) no cancelar el preapproval al cancelar, (b) cron que lo cancele 7 días después. Complejidad innecesaria para un caso marginal.

## Pendientes

1. **Email confirmación de reactivación** — análogo al de cancelación, para cerrar el loop de comunicación.
2. **Métrica de reactivaciones** — útil para el admin global: cuántos clientes cancelan pero reactivan antes del fin de período. Indicador de UX y de churn real.
