# Recordatorios de cobro

Consultar los recordatorios de cobro de una factura, pausarlos, reanudarlos y registrar una promesa de pago desde /external/v1/invoices/:id/reminders.

Tu organización decide en UTILIA OS cuándo y cuántas veces se recuerda cada factura pendiente, con qué tono y por dónde (correo y, si lo tiene, WhatsApp). Esos recordatorios salen solos y se paran en cuanto la factura consta como pagada.

Desde tu aplicación puedes ver los recordatorios de una factura tuya y hacer lo mismo que el equipo desde la factura: pausarlos, reanudarlos o registrar que el cliente ha prometido pagar un día. Configurar las reglas no se hace por la API: se hace en UTILIA OS, y la guía de uso está en [Recordatorios de cobro de tus facturas](https://os.utilia.ai/docs/guides/finance/payment-reminders).

Las cuatro rutas buscan la factura por su identificador, tu aplicación y el usuario indicado. Si es de otra aplicación o de otro usuario, la respuesta es un `404 INVOICE_NOT_ACCESSIBLE`, igual que el de una factura que no existe.

## Plan y permisos

| Endpoint | Aplicativo del plan | Permiso de la API (scope) |
| - | - | - |
| `GET /invoices/:id/reminders` | Finanzas | `crm:invoices:read` |
| `POST /invoices/:id/reminders/pause` | Finanzas | `crm:invoices:reminders` |
| `POST /invoices/:id/reminders/resume` | Finanzas | `crm:invoices:reminders` |
| `POST /invoices/:id/reminders/promise` | Finanzas | `crm:invoices:reminders` |

Además, la aplicación actúa en nombre de su **persona responsable**, que tiene que poder ver esa factura en UTILIA OS y, para pausar, reanudar o registrar una promesa, también enviarla. Si no puede, la respuesta es `403 APP_RESPONSIBLE_PERMISSION_DENIED`: lo arregla un administrador de tu organización, dándole ese permiso o nombrando otra persona responsable.

Estas rutas no tienen periodo de gracia: sin el permiso de la API, `403 INSUFFICIENT_SCOPE`. Exigen también la facturación de la aplicación activada (`403 BILLING_NOT_ENABLED`).

## Consultar los recordatorios

**`GET /api/external/v1/invoices/:id/reminders`**

Estado de los recordatorios de la factura, la política que se le aplica, los próximos envíos con los canales que de verdad saldrían y el historial con su canal. Es la misma previsión que ve el equipo en la factura.

Parámetro de ruta: `id`.

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `userId` | `query` | Sí | Identificador del usuario en tu aplicación (externalId) al que pertenece la factura. |

### Respuesta (200)

```json
{
  "invoiceId": "ckinv1234567890",
  "policy": { "id": "ckpol01", "name": "Por defecto", "source": "DEFAULT" },
  "status": "PAUSED",
  "reason": "PAUSED",
  "reasonText": "Recordatorios en pausa hasta el martes, 3 de noviembre.",
  "pendingAmount": "1200.00",
  "currency": "EUR",
  "state": {
    "status": "PAUSED",
    "pausedUntil": "2026-11-03T00:00:00.000Z",
    "pauseReason": "PROMISE_TO_PAY",
    "promisedPaymentDate": "2026-11-01T00:00:00.000Z",
    "promisedAmount": null,
    "sentCount": 2,
    "lastSentAt": "2026-10-27T09:00:00.000Z",
    "escalated": false,
    "stopReason": null
  },
  "upcoming": [
    {
      "plannedDate": "2026-11-03",
      "sendAt": "2026-11-03T09:00:00.000Z",
      "kind": "AFTER_DUE",
      "occurrence": 1,
      "channels": ["EMAIL"],
      "tone": "NEUTRAL",
      "shifted": false
    }
  ],
  "history": [
    {
      "id": "cklog01",
      "sentAt": "2026-10-27T09:00:00.000Z",
      "channel": "EMAIL",
      "success": true,
      "outcome": "SENT",
      "reason": null,
      "kind": "REMINDER",
      "stepKind": "ON_DUE",
      "tone": "FRIENDLY",
      "manual": false,
      "fallbackFromWhatsApp": false
    }
  ]
}
```

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `status` | `string` | Sí | ACTIVE (en seguimiento), PAUSED, STOPPED (parados: pagada, anulada...), COMPLETED (ya salieron todos) o NONE (a esta factura no le tocan recordatorios). |
| `reason / reasonText` | `string` | No | Por qué no va a salir nada, como código estable y como frase lista para enseñar. |
| `state` | `objeto` | No | Pausa, promesa de pago, envíos hechos, si el seguimiento pasó a una persona del equipo (escalated) y el motivo de la parada. null si la factura todavía no ha entrado en los recordatorios. |
| `upcoming` | `objeto[]` | Sí | Hasta 12 próximos envíos, con el día, la hora, el momento (antes, el día o después del vencimiento), el tono y los canales que de verdad saldrían. |
| `history` | `objeto[]` | Sí | Hasta 50 recordatorios enviados o intentados, del más reciente al más antiguo, con su canal y su resultado. |

La respuesta nunca trae a quién se escribió, su correo, su teléfono ni el enlace de pago.

**Estado de los recordatorios de una factura**

1. **En seguimiento** (`ACTIVE`). Los recordatorios salen según la política.
   - **En pausa** (`PAUSED`). Se llega desde En seguimiento cuando: pause, promise, el cliente dice que pagó o responde. No sale ninguno mientras dura.
   - **Parados** (`STOPPED`, estado final). Se llega desde En seguimiento cuando: La factura se cobra, se anula o se rectifica. Por cualquier vía de cobro.
2. **Ya salieron todos** (`COMPLETED`, estado final). Se llega desde En seguimiento cuando: Sale el último paso. No queda ninguno por salir.

Otras transiciones:

- En pausa → En seguimiento: resume, o llega el día de until o el de la promesa más su margen.

_Fuera de este ciclo, NONE: a la factura no le tocan recordatorios, y reason dice por qué._

## Pausar

**`POST /api/external/v1/invoices/:id/reminders/pause`**

Deja de recordar la factura hasta el día indicado o, sin él, hasta que se reanuden. No envía nada al cliente.

Parámetro de ruta: `id`.

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `userId` | `string` | Sí | Identificador del usuario en tu aplicación (externalId). |
| `reason` | `enum` | No | MANUAL (por defecto) o DISPUTE, si la factura está en discusión con el cliente. |
| `until` | `string` | No | Día (AAAA-MM-DD, en la zona horaria de tu organización) en que se reanudan solos: desde mañana y como mucho dentro de 180 días. Sin él, siguen en pausa hasta que llames a resume. |
| `note` | `string` | No | Nota para el equipo, hasta 1.000 caracteres. La ve en la factura; el cliente no. |

## Reanudar

**`POST /api/external/v1/invoices/:id/reminders/resume`**

Quita la pausa, también la de una promesa de pago. Los recordatorios siguen con el siguiente paso que toque.

Parámetro de ruta: `id`.

Los días que pasaron en pausa no se recuperan: no sale de golpe lo que no salió. Si los recordatorios no estaban en pausa, `409 PAYMENT_REMINDER_NOT_PAUSED`.

## Registrar una promesa de pago

**`POST /api/external/v1/invoices/:id/reminders/promise`**

Anota el día en que el cliente dice que pagará. Los recordatorios se pausan hasta ese día más el margen que fija tu organización; si vence sin pago, se reanudan y se avisa al equipo.

Parámetro de ruta: `id`.

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `userId` | `string` | Sí | Identificador del usuario en tu aplicación (externalId). |
| `promisedPaymentDate` | `string` | Sí | Día prometido (AAAA-MM-DD, en la zona horaria de tu organización), de hoy a 180 días. |
| `promisedAmount` | `number` | No | Importe prometido, si no es todo lo pendiente. No puede superar lo pendiente. |
| `note` | `string` | No | Nota para el equipo, hasta 1.000 caracteres. |

Las tres acciones responden `200` con la misma forma que la consulta, ya con el cambio aplicado, y ninguna envía nada al cliente.

**Límites de los recordatorios en la API**

- Peticiones por aplicación: 60 al minuto. Se cuentan por aplicación, no por dirección IP.
- Plazo máximo de una pausa o una promesa: 180 días
- Nota para el equipo: 1.000 caracteres

## Errores

| Situación | HTTP | `errorCode` |
| - | - | - |
| La factura es de otra aplicación o de otro usuario | 404 | `INVOICE_NOT_ACCESSIBLE` |
| El usuario no existe en tu aplicación | 404 | `EXTERNAL_USER_NOT_FOUND` |
| La persona responsable no puede ver o enviar esa factura | 403 | `APP_RESPONSIBLE_PERMISSION_DENIED` |
| La factura ya no está pendiente de cobro | 409 | `PAYMENT_REMINDER_INVOICE_NOT_REMINDABLE` |
| Reanudar unos recordatorios que no están en pausa | 409 | `PAYMENT_REMINDER_NOT_PAUSED` |
| Fecha fuera de plazo o importe mayor que lo pendiente | 400 | `PAYMENT_REMINDER_PROMISE_INVALID` |

## Avisos a tu servidor

Si tu aplicación se suscribe, recibe dos avisos de cada factura suya (o de todas las de tu organización, si son facturas del equipo):

- `INVOICE_REMINDER_SENT`: ha salido un recordatorio. Llega uno por canal y dice cuál (`EMAIL` o `WHATSAPP`), el momento del paso, el tono y cuántos lleva la factura.
- `INVOICE_REMINDER_STATE_CHANGED`: han cambiado sus recordatorios. `reminders.change` vale `PAUSED`, `RESUMED`, `PROMISED`, `STOPPED` o `ESCALATED`.

Ninguno lleva el destinatario, su teléfono, su correo ni el enlace de pago. Su forma está en [Eventos de los avisos](https://os.utilia.ai/docs/developers/webhooks/events).

## Ejemplo: registrar una promesa de pago

### curl

```bash
curl -X POST 'https://os.utilia.ai/api/external/v1/invoices/ckinv1234567890/reminders/promise' \
  -H "X-Api-Key: utl_live_..." \
  -H "Content-Type: application/json" \
  -d '{"userId": "user_01HXYZ", "promisedPaymentDate": "2026-11-20"}'
```

### TypeScript

```typescript
import { UtiliaSDK } from '@utilia-os/sdk-js';

const sdk = new UtiliaSDK({ baseURL: 'https://os.utilia.ai/api', apiKey: process.env.UTILIA_API_KEY! });

const estado = await sdk.invoices.reminders.get('ckinv1234567890', { userId: 'user_01HXYZ' });
await sdk.invoices.reminders.promise('ckinv1234567890', {
  userId: 'user_01HXYZ',
  promisedPaymentDate: '2026-11-20',
});
```

### Python

```python
import asyncio
import os

from utilia_sdk import RegisterPaymentPromiseInput, UtiliaSDK


async def main() -> None:
    sdk = UtiliaSDK(base_url="https://os.utilia.ai/api", api_key=os.environ["UTILIA_API_KEY"])
    estado = await sdk.invoices.reminders.get("ckinv1234567890", user_id="user_01HXYZ")
    print(estado.status)
    await sdk.invoices.reminders.promise(
        "ckinv1234567890",
        RegisterPaymentPromiseInput(user_id="user_01HXYZ", promised_payment_date="2026-11-20"),
    )


asyncio.run(main())
```

`sdk.invoices.reminders` está en los dos SDK oficiales desde la versión 4.8.0.
