# Gestiona una suscripción

Una suscripción es una compra de un producto y plan; es lo que se renueva y factura, y la [instancia de servicio](/es/concepts/model) es lo que corre. Comprar está en [Suscripciones y facturación](/es/concepts/billing). Esta página cubre todo lo que haces con una suscripción después. Los ejemplos usan el ayudante de [Usar la API de Zaroz](/es/guides/api/overview).

## Listar y leer

**curl**

```bash
curl -sS "$ZAROZ_API/subscriptions?project_id=$PROJECT&page_size=50" -H "$AUTH"
curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION" -H "$AUTH"
curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/resources" -H "$AUTH"      # lo que ejecuta
```

**TypeScript**

```ts
import { call } from "./zaroz";

const list = await call("GET", `/subscriptions?project_id=${PROJECT}`);
for (const s of list.body.data) console.log(s.id, s.status, s.auto_renew, s.cancels_at, s.upcoming_charge);
```

**Python**

```python
from zaroz import call

_, subs = call("GET", f"/subscriptions?project_id={PROJECT}")
for s in subs["data"]:
    print(s["id"], s["status"], s["auto_renew"], s["cancels_at"], s["upcoming_charge"])
```

`status` es `pending`, `active`, `suspended`, `pending_termination`, `terminated`, `cancelled` o `failed`. Una suscripción **suspendida** (normalmente por impago) permanece detenida hasta liquidar la factura; pagarla levanta la suspensión.

## Detener o reanudar la renovación

Renovación y cancelación son cosas distintas a propósito.

- **`PUT /subscriptions/{id}/renewal`** decide si se emite la **próxima factura**. El servicio sigue en marcha y no se fija fecha de fin.
- **`PUT /subscriptions/{id}/cancellation`** decide si el servicio **termina** cuando termina el periodo pagado.

**curl**

```bash
curl -sS -X PUT "$ZAROZ_API/subscriptions/$SUBSCRIPTION/renewal" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"auto_renew": false}'

curl -sS -X PUT "$ZAROZ_API/subscriptions/$SUBSCRIPTION/cancellation" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"cancel_at_period_end": true}'
# {"subscription_id":"...","status":"active","cancels_at":"2026-11-30T00:00:00Z","auto_renew":true,"changed":true}

# si cambias de opinión antes de ese instante:
curl -sS -X PUT "$ZAROZ_API/subscriptions/$SUBSCRIPTION/cancellation" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"cancel_at_period_end": false}'
```

**TypeScript**

```ts
await call("PUT", `/subscriptions/${SUB}/renewal`, { auto_renew: false });

const c = await call("PUT", `/subscriptions/${SUB}/cancellation`, { cancel_at_period_end: true });
console.log(c.body.cancels_at, c.body.changed); // changed:false significa que ya era así; no es un error
```

**Python**

```python
call("PUT", f"/subscriptions/{SUB}/renewal", {"auto_renew": False})

_, c = call("PUT", f"/subscriptions/{SUB}/cancellation", {"cancel_at_period_end": True})
print(c["cancels_at"], c["changed"])   # changed False significa que ya era así; no es un error
```

Programar un fin **no mueve dinero**: no se emite ni anula ninguna factura y no se acredita nada. Enviar la decisión que ya está en vigor funciona y responde `changed: false`. Una suscripción suspendida no se puede cancelar al final del periodo (`409`) porque devolvería al servicio un servicio detenido por impago; liquida antes lo adeudado. Estas llamadas requieren el rol que puede comprar por la organización (Owner o Admin); un token de organización necesita `subscriptions.renewal.manage` o `subscriptions.cancel`.

## Cancelar ya y recuperar el tiempo sin usar

Si prefieres parar de inmediato y convertir la parte sin usar del periodo pagado en crédito de monedero, pide un presupuesto y canjéalo. El presupuesto va firmado y vale diez minutos.

**curl**

```bash
curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/wallet-credit-quote" -H "$AUTH"
# {"eligible": true, "total_cents": 1840, "minimum_cents": 100, "below_minimum": false,
#  "quote_id": "...", "expires_at": "...", "period_remaining_seconds": 1234567, ...}

curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/cancel-with-wallet-credit" -H "$AUTH" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" -d '{"quote_id": "<quote_id>"}'
# {"status": "cancelled", "total_credited_cents": 1840, "credit_note_ids": ["..."], ...}
```

**TypeScript**

```ts
import { randomUUID } from "node:crypto";

const q = await call("GET", `/subscriptions/${SUB}/wallet-credit-quote`);
if (q.body.eligible && !q.body.below_minimum) {
  const done = await call("POST", `/subscriptions/${SUB}/cancel-with-wallet-credit`,
    { quote_id: q.body.quote_id }, { "Idempotency-Key": randomUUID() });
  console.log("acreditado", done.body.total_credited_cents);
} else {
  console.log("no elegible:", q.body.reason);
}
```

**Python**

```python
import uuid

_, q = call("GET", f"/subscriptions/{SUB}/wallet-credit-quote")
if q["eligible"] and not q["below_minimum"]:
    _, done = call("POST", f"/subscriptions/{SUB}/cancel-with-wallet-credit",
                   {"quote_id": q["quote_id"]}, headers={"Idempotency-Key": str(uuid.uuid4())})
    print("acreditado", done["total_credited_cents"])
else:
    print("no elegible:", q["reason"])
```

Esto **cancela el servicio ya y no se puede deshacer**. Se rechaza con `422` si el presupuesto está alterado o es de otra suscripción, si la suscripción no es elegible o si el crédito no llega al mínimo, y con `409` si el presupuesto caducó o su importe cambió desde que lo pediste (pide uno nuevo). Puedes pedir el presupuesto tantas veces como quieras: no escribe nada.

## Redimensionar

Un redimensionado indica la **forma final completa** (cpu, memoria en GiB y almacenamiento en GiB), no un cambio. Envía la dimensión que no quieres cambiar con su valor actual. El almacenamiento solo puede crecer. Presupuesta siempre primero.

**curl**

```bash
BODY='{"resources": {"cpu": 2, "memory_gb": 4, "storage_gb": 40}}'

curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/resize/quote" -H "$AUTH" -H "Content-Type: application/json" -d "$BODY"
# {"billing": {"kind": "growth", "subtotal_cents": ..., "tax_cents": ..., "total_cents": ..., "currency": "EUR"},
#  "dimensions": [{"dimension": "cpu", "current": 1, "requested": 2, "growing": true, "available": true}, ...]}

curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/resize" -H "$AUTH" -H "Content-Type: application/json" -d "$BODY"
# {"outcome": "...", "invoice": {"id": "...", "amount_cents": ..., "status": "pending"}, "scheduled_downsize": null}

curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/resize/cancel" -H "$AUTH"      # 204, retira un redimensionado pendiente
```

**TypeScript**

```ts
const target = { resources: { cpu: 2, memory_gb: 4, storage_gb: 40 } };
const quote = await call("POST", `/subscriptions/${SUB}/resize/quote`, target);
if (quote.body.dimensions.some((d: any) => d.growing && !d.available)) throw new Error("sin capacidad ahora");
console.log(quote.body.billing); // { kind: "growth", total_cents, ... } o { kind: "downsize" }

const resize = await call("POST", `/subscriptions/${SUB}/resize`, target);
if (resize.body.invoice) {
  // págala: ver Monedero, recargas, códigos y facturas
  await call("POST", `/billing/invoices/${resize.body.invoice.id}/pay-with-wallet`, undefined, { "Idempotency-Key": randomUUID() });
}
```

**Python**

```python
target = {"resources": {"cpu": 2, "memory_gb": 4, "storage_gb": 40}}
_, quote = call("POST", f"/subscriptions/{SUB}/resize/quote", target)
if any(d["growing"] and not d["available"] for d in quote["dimensions"]):
    raise RuntimeError("sin capacidad ahora")
print(quote["billing"])  # {"kind": "growth", "total_cents": ...} o {"kind": "downsize"}

_, resize = call("POST", f"/subscriptions/{SUB}/resize", target)
if resize.get("invoice"):
    call("POST", f"/billing/invoices/{resize['invoice']['id']}/pay-with-wallet",
         headers={"Idempotency-Key": str(uuid.uuid4())})
```

Cómo se factura:

- **Crecer** una dimensión reserva capacidad durante **30 minutos** y emite una factura pendiente de *resize* por la diferencia prorrateada. El aumento se aplica cuando esa factura se paga. Págala dentro de la reserva o el bloqueo se libera.
- **Reducir** nunca se factura ni se abona. Se programa (`scheduled_downsize`) y se aplica al final del periodo de facturación actual, justo antes de que la próxima renovación se calcule con la forma más pequeña. Una nueva petición sustituye a una reducción programada anterior.
- La comprobación de existencias del presupuesto es una vista previa en vivo y puede quedar desfasada cuando redimensionas.
- `POST .../resize/cancel` retira un redimensionado pendiente (anulando su factura sin pagar) y responde `204` incluso si no había nada pendiente. Se rechaza con `409` solo cuando el aumento pendiente ya está pagado.

Redimensionar requiere el rol Owner o Admin; un token de organización necesita `subscriptions.resize.manage`. Se aplican el mínimo y el máximo del producto.

## Cambiar de plan

Donde el producto lo ofrece, puedes pasar a otro de sus planes del catálogo con la misma forma de presupuesto y confirmación. Obtén los ids de plan en `GET /catalog/products/{id}/plans`.

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/resize/plan-change/quote" -H "$AUTH" \
  -H "Content-Type: application/json" -d '{"plan_id": 7}'
curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/resize/plan-change" -H "$AUTH" \
  -H "Content-Type: application/json" -d '{"plan_id": 7}'
```

**TypeScript**

```ts
const plans = await call("GET", `/catalog/products/${PRODUCT}/plans`);
const q = await call("POST", `/subscriptions/${SUB}/resize/plan-change/quote`, { plan_id: plans.body[0].id });
```

**Python**

```python
_, plans = call("GET", f"/catalog/products/{PRODUCT}/plans")
_, q = call("POST", f"/subscriptions/{SUB}/resize/plan-change/quote", {"plan_id": plans[0]["id"]})
```

Se rechaza si el producto no ofrece cambio de plan por cuenta propia, o si `plan_id` no es uno de sus planes activos y públicos. La facturación es la de un redimensionado: el aumento se factura ahora y un plan menor se aplica al final del periodo.

## Pruebas gratuitas

Una prueba es un periodo gratuito en un producto. `GET /me/trials` lista lo que puedes reclamar, incluidas las que ya usaste (como `claimable: false`, para que no desaparezcan en silencio).

**curl**

```bash
curl -sS "$ZAROZ_API/me/trials" -H "$AUTH"
curl -sS -X POST "$ZAROZ_API/me/trials" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"trial_price_id": "<id de la lista>", "name": "Mi prueba", "slug": "mi-prueba"}'
```

**TypeScript**

```ts
const trials = await call("GET", "/me/trials");
const t = trials.body.trials.find((x: any) => x.claimable);
if (t) {
  const claimed = await call("POST", "/me/trials", { trial_price_id: t.id, name: "Mi prueba", slug: "mi-prueba" });
  console.log(claimed.body.subscription.id);
}
```

**Python**

```python
_, trials = call("GET", "/me/trials")
t = next((x for x in trials["trials"] if x["claimable"]), None)
if t:
    _, claimed = call("POST", "/me/trials", {"trial_price_id": t["id"], "name": "Mi prueba", "slug": "mi-prueba"})
    print(claimed["subscription"]["id"])
```

- Reclamar crea la suscripción en tu **organización personal** (no puedes nombrar otra) y la activa al instante. No se cobra nada ni hace falta método de pago.
- **`auto_renew` empieza en `false`**: si no haces nada, la prueba se suspende en `trial_ends_at` en lugar de facturarte. Para conservar el servicio, activa la renovación (`PUT .../renewal`) antes; la facturación empieza en `trial_ends_at`.
- **Una prueba por producto y cuenta, para siempre.** Un segundo intento se rechaza con `already_claimed`; `capacity_reached` significa que lo intentes más tarde; `not_available` que la prueba está desactivada.
- Cuando `region_is_fixed` es verdadero la prueba fija su propio hardware e ignora la región que envíes.
- Donde el despliegue usa un captcha para pruebas, la reclamación necesita además un token de captcha.
