# Suscripciones

Monta cuotas que UTILIA OS factura, envía y, si quieres, cobra solas cada periodo; consulta el próximo cobro y pausa, reanuda, da de baja o reactiva.

Una suscripción es una cuota que se repite: con su importe, su periodicidad y sus condiciones, UTILIA OS emite cada periodo la factura con su numeración y su fiscalidad, la envía por correo si lo pides y, con cobro automático, la cobra contra la tarjeta que el usuario autorizó. En tu organización aparecen en [Finanzas › Cobros recurrentes](https://os.utilia.ai/dashboard/finance/recurring-invoices), junto a las que crea el propio equipo. Funciona con la clave de tu aplicación externa, siempre desde tu servidor.

## Antes de empezar

- El plan necesita el aplicativo **Finanzas** con la capacidad de **cobros recurrentes**. Si falta, la respuesta es un 403 `MODULE_NOT_ENABLED` o `CAPABILITY_NOT_ENABLED` ([errores de plan](https://os.utilia.ai/docs/developers/sdk/plan-errors)).
- Scopes: `billing:subscriptions:read` para consultar, `billing:subscriptions:write` para el alta y los cambios, y `billing:subscriptions:cancel` para dar de baja (se concede aparte porque corta un ingreso). `retryCharge` mueve dinero y pide `billing:scheduled-charges:write`, además de Pagos con tarjeta (Stripe) y el cobro automático en el plan; `generateNow` sobre una cuota que se cobra sola pide además `billing:scheduled-charges:write` o `billing:charge`. Ver [Suscripciones](https://os.utilia.ai/docs/developers/api/subscriptions#plan-y-permisos).
- Para el **cobro automático** hace falta además una [autorización de cobro](https://os.utilia.ai/docs/developers/sdk/payment-authorizations) vigente del usuario, de alcance `RECURRING`.

| JavaScript | Python | Para qué |
| - | - | - |
| `create(datos)` | `create(datos)` | Dar de alta la cuota |
| `list(filtros)` / `get(id, { userId })` | `list(filtros)` / `get(id, user_id=...)` | Consultar |
| `update(id, datos)` | `update(id, datos)` | Cambiar condiciones |
| `pause`, `resume`, `cancel`, `reactivate` | `pause`, `resume`, `cancel`, `reactivate` | Cambiar de estado |
| `getUpcomingCharge(id, { userId })` | `get_upcoming_charge(...)` | Ver el próximo cobro |
| `generateNow(id, { userId })` | `generate_now(...)` | Emitir ya la factura del periodo |
| `listInvoices`, `listLogs` | `list_invoices`, `list_logs` | Facturas generadas e historial |
| `retryCharge(id, facturaId, { userId })` | `retry_charge(...)` | Reintentar el cobro de una factura |

Todos los métodos, salvo `list`, piden el `userId` del usuario dueño de la suscripción.

## Dar de alta la cuota

Cuatro campos definen cómo se comporta:

| JavaScript | Python | Qué decide |
| - | - | - |
| `frequency` | `frequency` | `MONTHLY_FIXED_DAY`, con `dayOfMonth` de 1 a 28 (o 99 para el último día del mes); o `INTERVAL_DAYS`, con `intervalDays`. Sin `dayOfMonth`, se toma el día de `startDate`, con 28 como máximo. |
| `automation` | `automation` | `AUTO` emite la factura de cada periodo; `DRAFT` la deja en borrador para que tu equipo la revise. |
| `chargeMode` | `charge_mode` | `MANUAL`: la paga el usuario. `AUTO_STRIPE`: se cobra sola con la tarjeta autorizada (necesita `paymentAuthorizationId`). |
| `sendEmail` | `send_email` | Si se envía cada factura por correo. Sin el campo, manda la preferencia de la aplicación. |

Desde la 4.4.0 decides también cómo se numeran sus facturas:

| JavaScript | Python | Qué decide |
| - | - | - |
| `type` | `type` | `STANDARD` (ordinarias, por defecto) o `SIMPLIFIED` (simplificadas, solo si cada cuota no pasa de 400 € impuestos incluidos; si no, `SIMPLIFIED_INVOICE_AMOUNT_EXCEEDED`). |
| `seriesCode` | `series_code` | Una serie de tu aplicación (de `invoices.listSeries()`) o `DEFAULT` para la de siempre. Sin el campo, la serie por defecto de tu aplicación para el tipo. La serie queda fijada en la suscripción. |

La respuesta trae `type` y `seriesCode` (`series_code`). Los errores de la serie son los mismos que al emitir una factura: ver [Series disponibles](https://os.utilia.ai/docs/developers/api/invoices#series-disponibles).

La ventanilla única del IVA (en «Próximamente») también llega a las suscripciones: `taxSystem: 'EU_OSS'`, `consumerResidence` con `evidenceItems` (`consumer_residence`, `evidence_items`) y líneas `EU_VAT` con `vatRateClass` y `ossSupplyType`. El detalle trae `consumerCountryCode`, y cada factura generada, su `taxSystem` y su `ossScheme`.

Además: `name`, `startDate` y `lines` (obligatorios), `endDate`, `taxSystem`, `discountPercent`, `daysUntilDue`, `notes`, `metadata` e `idempotencyKey`. Las líneas llevan los mismos campos fiscales que una factura; como la plantilla genera facturas mes tras mes sin que nadie la revise, una línea fiscalmente mal planteada produce facturas mal emitidas indefinidamente. Revisa el `igicOperationType` si operas en Canarias, y `exemptionReason` y `exemptionNote` en operaciones exentas.

### JavaScript

```typescript
const suscripcion = await sdk.subscriptions.create({
  userId: 'user_01HXYZ',
  name: 'Plan Profesional mensual',
  frequency: 'MONTHLY_FIXED_DAY',
  dayOfMonth: 1,
  startDate: '2026-11-01',
  automation: 'AUTO',
  chargeMode: 'AUTO_STRIPE',
  paymentAuthorizationId: 'ckauth1234567890',
  taxSystem: 'IGIC',
  daysUntilDue: 7,
  sendEmail: true,
  lines: [{ name: 'Plan Profesional', quantity: 1, unitPrice: 49.95, taxType: 'IGIC_7' }],
  idempotencyKey: 'sub_user_01HXYZ_pro',
});
```

### Python

```python
from utilia_sdk.models import (
    CreateExternalSubscriptionInput,
    CreateExternalSubscriptionLineInput,
)

suscripcion = await sdk.subscriptions.create(
    CreateExternalSubscriptionInput(
        user_id="user_01HXYZ",
        name="Plan Profesional mensual",
        frequency="MONTHLY_FIXED_DAY",
        day_of_month=1,
        start_date="2026-11-01",
        automation="AUTO",
        charge_mode="AUTO_STRIPE",
        payment_authorization_id="ckauth1234567890",
        tax_system="IGIC",
        days_until_due=7,
        send_email=True,
        lines=[CreateExternalSubscriptionLineInput(name="Plan Profesional", quantity=1, unit_price=49.95, tax_type="IGIC_7")],
        idempotency_key="sub_user_01HXYZ_pro",
    )
)
```

> **Importante: El cobro automático sin autorización falla, no se degrada**
>
> Pedir `AUTO_STRIPE` sin una autorización vigente del usuario responde 409 `PAYMENT_AUTHORIZATION_REQUIRED`, con un `nextStep` que dice las dos operaciones que faltan. No se convierte en silencio a cobro manual: te dejaría creyendo que tienes montado un cobro que nunca ocurriría. Un 409 `AUTO_CHARGE_DISABLED_FOR_ORGANIZATION` es otra cosa: la organización tiene apagado el cobro automático y solo puede encenderlo su equipo, en Finanzas › Ajustes › Cobro automático con tarjeta. Ese error no trae `nextStep`.

## Consultar y cambiar

`list` filtra por `userId` (sin él, lista las de todos los usuarios de tu aplicación), `status` (`ACTIVE`, `PAUSED`, `CANCELLED` o `COMPLETED`), `page` y `limit`, y devuelve un sobre plano con `data`, `total`, `page`, `limit` y `totalPages`; las filas son un resumen sin líneas. El detalle completo lo da `get`.

`update` cambia solo los campos que mandes. Si mandas `lines`, es la lista **completa** que quieres: las líneas que no aparezcan se eliminan. `type` y `seriesCode` valen para las facturas futuras; si cambias `type` sin `seriesCode`, la suscripción pasa a la serie por defecto de tu aplicación para el nuevo tipo.

`getUpcomingCharge` es la consulta que conviene enseñar en el perfil del usuario: dice cuándo y cuánto se cobrará y, con `autoChargeReady` en falso y su motivo (`NO_AUTHORIZATION`, `AUTHORIZATION_REVOKED`, `PAYMENT_METHOD_EXPIRED` o `AUTOCHARGE_DISABLED`), te avisa **antes** de que la renovación falle.

### JavaScript

```typescript
const pagina = await sdk.subscriptions.list({ userId: 'user_01HXYZ', status: 'ACTIVE' });

const proximo = await sdk.subscriptions.getUpcomingCharge(suscripcion.id, { userId: 'user_01HXYZ' });
if (!proximo.autoChargeReady) avisarAlUsuario(proximo.autoChargeBlockedReason);
```

### Python

```python
from utilia_sdk.models import ListExternalSubscriptionsFilters

pagina = await sdk.subscriptions.list(
    ListExternalSubscriptionsFilters(user_id="user_01HXYZ", status="ACTIVE")
)

proximo = await sdk.subscriptions.get_upcoming_charge(suscripcion.id, user_id="user_01HXYZ")
if not proximo.auto_charge_ready:
    avisar_al_usuario(proximo.auto_charge_blocked_reason)
```

## Pausar, reanudar, dar de baja y reactivar

**Estados de una suscripción**

1. **Activa** (`ACTIVE`). Genera la factura de cada periodo y, con cobro automático, la cobra.
   - **Pausada** (`PAUSED`). Se llega desde Activa cuando: pause. No genera facturas ni cobra; conserva todo.
   - **Cancelada** (`CANCELLED`). Se llega desde Activa cuando: cancel. Dada de baja, con su historial y sus facturas.
2. **Completada** (`COMPLETED`). Se llega desde Activa cuando: No quedan periodos antes de endDate. El generador la cierra solo al llegar a su fecha de fin.

Otras transiciones:

- Pausada → Activa: resume: vuelve a facturar desde la siguiente fecha prevista, sin recuperar los periodos saltados.
- Pausada → Cancelada: cancel también da de baja una suscripción pausada.
- Cancelada → Activa: reactivate la vuelve a poner en marcha.

_Con el nombre que ves en Finanzas › Cobros recurrentes y el valor que devuelve status._

No hay borrado: la contabilidad necesita su rastro.

`pause` y `cancel` admiten un motivo (`reason`). Las acciones que no caben en el estado actual (pausar una pausada, reanudar una dada de baja) responden 409 `SUBSCRIPTION_STATE_INVALID`.

### JavaScript

```typescript
await sdk.subscriptions.pause(suscripcion.id, { userId: 'user_01HXYZ', reason: 'Pausa de dos meses.' });
await sdk.subscriptions.resume(suscripcion.id, { userId: 'user_01HXYZ' });
await sdk.subscriptions.cancel(suscripcion.id, { userId: 'user_01HXYZ', reason: 'Baja solicitada.' });
await sdk.subscriptions.reactivate(suscripcion.id, { userId: 'user_01HXYZ' });
```

### Python

```python
from utilia_sdk.models import ExternalSubscriptionActionInput

await sdk.subscriptions.pause(
    suscripcion.id, ExternalSubscriptionActionInput(user_id="user_01HXYZ", reason="Pausa de dos meses.")
)
await sdk.subscriptions.resume(suscripcion.id, ExternalSubscriptionActionInput(user_id="user_01HXYZ"))
await sdk.subscriptions.cancel(
    suscripcion.id, ExternalSubscriptionActionInput(user_id="user_01HXYZ", reason="Baja solicitada.")
)
await sdk.subscriptions.reactivate(suscripcion.id, ExternalSubscriptionActionInput(user_id="user_01HXYZ"))
```

## Facturas, historial y cobros pendientes

`listInvoices` devuelve las facturas generadas con el periodo que cubre cada una (sin `pdfUrl`; para el detalle, [`invoices.get`](https://os.utilia.ai/docs/developers/sdk/invoices)). `listLogs` devuelve cada ejecución del generador con su resultado: `GENERATED`, `SKIPPED` o `FAILED`.

`generateNow` emite ya la factura del periodo, solo si la suscripción está activa. Si no toca, responde con `generated` en falso y el motivo en `reason`, sin error. `retryCharge` reintenta el cobro de una factura concreta de la suscripción; devuelve un acuse, y el resultado llega por los avisos de cobros.

## Errores y avisos

| `errorCode` | HTTP | Qué hacer |
| - | - | - |
| `PAYMENT_AUTHORIZATION_REQUIRED` | 409 | Recoge la autorización del usuario y vuelve a intentarlo (trae `nextStep`). |
| `AUTO_CHARGE_DISABLED_FOR_ORGANIZATION` | 409 | La organización tiene apagado el cobro automático. |
| `SUBSCRIPTION_NOT_ACCESSIBLE` | 404 | Es de otra aplicación o de otro usuario. |
| `SUBSCRIPTION_STATE_INVALID` | 409 | La acción no cabe en el estado actual. |
| `BILLING_AMOUNT_EXCEEDS_LIMIT` | 422 | El importe por periodo supera el tope de tu aplicación. |
| `SIMPLIFIED_INVOICE_AMOUNT_EXCEEDED` | 422 | Cuotas simplificadas de más de 400 € impuestos incluidos: usa `type: 'STANDARD'`. |
| `INVOICE_SERIES_NOT_AVAILABLE`, `INVOICE_SERIES_TYPE_MISMATCH` | 400 | La serie no es de tu aplicación o es de otro tipo. Consulta las tuyas con `invoices.listSeries()`. |
| `INVOICE_SERIES_INACTIVE` | 409 | La serie está desactivada. |
| `EXTERNAL_USER_NOT_FOUND` | 404 | [Identifica](https://os.utilia.ai/docs/developers/sdk/users) antes al usuario. |

Avisos que recibe tu servidor: `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_UPDATED` (con los campos cambiados), `SUBSCRIPTION_PAUSED`, `SUBSCRIPTION_RESUMED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_COMPLETED` (se llegó a la fecha de fin), `SUBSCRIPTION_INVOICE_GENERATED` (con `emailSent`) y `SUBSCRIPTION_PAYMENT_AUTHORIZATION_PENDING` (configurada para cobrar sola pero sin autorización vigente). Verifícalos con [`webhooks.parse`](https://os.utilia.ai/docs/developers/webhooks/hmac-signature).

## Siguiente paso

- [Autorizaciones de cobro](https://os.utilia.ai/docs/developers/sdk/payment-authorizations): Recoge el consentimiento que permite el cobro automático.
- [Endpoints de suscripciones](https://os.utilia.ai/docs/developers/api/subscriptions): El contrato REST completo, campo a campo.
