> ## Documentation Index
> Fetch the complete documentation index at: https://developers.ipsofactura.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Complemento de pago

> Cómo referenciar la factura PPD que liquidas y por qué no se puede aplicar un REP a un CFDI timbrado con otro PAC.

`POST /cfdi/complemento-pago` timbra un CFDI tipo **P** (Recibo Electrónico de Pago, o REP)
contra una o varias facturas **PPD** que ya timbraste con Ipsofactura. Cada elemento de
`pagos[].documentos_relacionados` apunta a la factura que ese pago liquida.

## `id_documento` y CFDI de otro PAC

Un documento relacionado se referencia de una de dos formas, y sólo una es necesaria:

| Campo          | Qué valor lleva                                                     |
| -------------- | ------------------------------------------------------------------- |
| `id_documento` | El **folio fiscal** (UUID del SAT) de la factura PPD.               |
| `factura_id`   | El `id` interno que te devolvió Ipsofactura al timbrar esa factura. |

```json Un pago que liquida una PPD por folio fiscal theme={null}
{
  "rfc_emisor": "EKU9003173C9",
  "pagos": [
    {
      "fecha_pago": "2026-07-21T09:55:00",
      "forma_pago_p": "03",
      "moneda_p": "MXN",
      "monto": 1160.00,
      "num_operacion": "OP-0001",
      "documentos_relacionados": [
        { "id_documento": "B0440BB7-1F2C-4A51-9C33-5F2AD1A9B7E4", "imp_pagado": 1160.00 }
      ]
    }
  ]
}
```

Si no envías ninguno de los dos, la respuesta es `400 VALIDATION_ERROR`
(*"Cada documento relacionado requiere factura\_id o id\_documento"*). Si `id_documento` no es
un UUID, también `400 VALIDATION_ERROR`.

### Sólo facturas timbradas con Ipsofactura

`id_documento` **no** es un folio fiscal cualquiera: se busca entre los CFDIs de tu cuenta.
Un folio fiscal que no timbramos nosotros no existe para este endpoint y la respuesta es
`400 VALIDATION_ERROR` (*"CFDI relacionado no encontrado: …"*).

<Warning>
  Un complemento de pago sólo puede aplicarse a una factura que timbraste **a través de
  Ipsofactura**. Es la misma regla que la cancelación: operamos sobre los CFDIs que están en
  tu cuenta, porque el complemento necesita el documento —su total, su moneda y su receptor—
  para armar el REP, y su histórico de pagos para derivar `num_parcialidad`, `imp_saldo_ant`
  e `imp_saldo_insoluto`. Ese histórico no existe para un CFDI emitido en otra plataforma, y
  mandar los saldos [explícitos](#parcialidad-y-saldos-derivados-o-explícitos) no levanta la
  regla: seguimos necesitando el documento para lo demás.
</Warning>

Además de existir en tu cuenta, la factura relacionada debe cumplir (si no, `400
VALIDATION_ERROR` con el detalle en `message`):

* ser de `tipo_comprobante` **I** o **E**;
* tener `metodo_pago` **PPD** — una PUE no lleva complemento;
* estar en estatus **timbrado**;
* tener el mismo RFC emisor que el complemento.

<Note>
  Esto se diferencia de `cfdi_relacionados` al timbrar (nodo `CfdiRelacionados`), donde sí
  puedes declarar folios fiscales que no pasaron por Ipsofactura: ahí el UUID viaja tal cual
  al XML y el SAT lo valida. En un complemento de pago no basta con arrastrar el UUID: hay que
  calcular saldos.
</Note>

### Migración desde otro PAC

Si llegas a Ipsofactura con facturas **PPD abiertas** timbradas con tu proveedor anterior,
esos saldos no se migran. Las opciones son dos:

1. **Liquidarlas en el proveedor anterior**: timbra ahí los complementos de pago que falten,
   y usa Ipsofactura para las facturas nuevas.
2. **Refacturar**: cancela la PPD abierta y vuelve a emitirla con Ipsofactura. La
   cancelación sí la puedes hacer desde aquí, por `folio_fiscal` — ver
   [Cancelar un CFDI timbrado con otro proveedor](/cancelacion#cancelar-un-cfdi-timbrado-con-otro-proveedor)
   y [Refacturación](/cancelacion#refacturación-la-cadena-completa).

No existe una importación de CFDIs externos en esta versión de la API. Si tu operación la
necesita, escríbenos: sería una funcionalidad aparte, y depende de poder declarar saldos
iniciales explícitos sobre un documento que nosotros no timbramos.

## Parcialidad y saldos: derivados o explícitos

Por omisión no mandas `num_parcialidad`, `imp_saldo_ant` ni `imp_saldo_insoluto`: los
derivamos del histórico de pagos que ya timbramos contra esa factura. El primer pago arranca
del total de la factura y cada siguiente arrastra el `imp_saldo_insoluto` del anterior. Si tu
sistema no lleva contabilidad de saldos, no tienes que hacer nada — este es el camino de
siempre y no cambió.

Si tu sistema **sí** lleva su propia contabilidad, puedes declararlos por documento
relacionado y entonces **gana lo que mandas**:

| Campo                | Opcional | Qué hace                                                                                      |
| -------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `num_parcialidad`    | sí       | Número de esta parcialidad. Debe ser **mayor** a la última que ya timbramos para esa factura. |
| `imp_saldo_ant`      | sí       | Saldo insoluto antes de este pago, en la moneda del documento relacionado.                    |
| `imp_saldo_insoluto` | sí       | Saldo después del pago. Debe cumplir exactamente `imp_saldo_ant − imp_pagado`.                |
| `serie_dr`           | sí       | Serie del documento relacionado.                                                              |
| `folio_dr`           | sí       | Folio del documento relacionado.                                                              |

```json Un pago que declara su propia parcialidad y saldos theme={null}
{
  "rfc_emisor": "EKU9003173C9",
  "pagos": [
    {
      "fecha_pago": "2026-07-21T09:55:00",
      "forma_pago_p": "03",
      "moneda_p": "MXN",
      "monto": 2000.00,
      "documentos_relacionados": [
        {
          "id_documento": "B0440BB7-1F2C-4A51-9C33-5F2AD1A9B7E4",
          "imp_pagado": 2000.00,
          "num_parcialidad": 3,
          "imp_saldo_ant": 5000.00,
          "imp_saldo_insoluto": 3000.00
        }
      ]
    }
  ]
}
```

### Qué validamos

Puedes mandar los tres, algunos o ninguno; el que omitas se deriva. Lo que no puedes mandar es
una cadena incoherente, y son tres reglas — todas `400 VALIDATION_ERROR` nombrando el campo,
**sin consumir timbre**:

* **La identidad, con tolerancia cero:** `imp_saldo_ant − imp_pagado = imp_saldo_insoluto`. Se
  verifica sobre los valores a **dos decimales** que realmente vamos a timbrar, así que una
  request que aceptamos no puede después ser rechazada por el SAT (`CRP20244`) por un redondeo
  nuestro.
* **`num_parcialidad` monótona:** tiene que ser mayor a la última que timbramos para esa
  factura. Un **hueco sí se permite** — tu contabilidad puede contar parcialidades que nosotros
  nunca timbramos, por ejemplo pagos registrados fuera de Ipsofactura. Repetir o ir hacia atrás
  no, porque dejaría dos REPs reclamando la misma parcialidad del mismo documento.
* **`serie_dr` / `folio_dr` no pueden contradecir:** sólo se aceptan si la factura resuelta no
  trae serie o folio propios, o si coinciden. Un valor distinto timbraría un REP apuntando a una
  factura que no existe bajo esa serie y folio.

<Note>
  Estos campos son de `POST /cfdi/complemento-pago`. En
  [timbrado por XML](/timbrado-por-xml#ruteo-por-tipo-de-comprobante) la parcialidad y el saldo
  se siguen recalculando a partir de nuestro histórico, no de lo que el XML declare.
</Note>

### Egreso aplicado antes del REP

Si aplicaste una **nota de crédito** (CFDI tipo **E**) contra la PPD antes de timbrar el
complemento, el saldo que tú traes ya viene disminuido y el nuestro no: nosotros derivamos del
histórico de *pagos*, y un egreso no es un pago. Ahí es donde `imp_saldo_ant` explícito importa
— mándalo con el saldo real después del egreso y el REP se timbra con la cadena correcta. Sin
él, derivaríamos el saldo previo al egreso y la suma que verías en el XML no cuadraría con tu
contabilidad.

## Límite del periodo de prueba

Un complemento de pago consume un timbre igual que una factura, así que el límite de tu
periodo de prueba también se aplica aquí: agotado el cupo, `POST /cfdi/complemento-pago`
responde `402 TRIAL_LIMIT_EXCEEDED` antes de resolver nada del request y sin llamar al PAC. Es
el mismo código y el mismo mensaje que en `POST /cfdi/timbrar`.

## Si el timbrado del REP no responde

`POST /cfdi/complemento-pago` tiene la misma red de seguridad que el timbrado de facturas.
Cuando el PAC no confirma a tiempo, la respuesta es `503 STAMPING_OUTCOME_UNKNOWN` con
`retry_after`, y el REP **no** se pierde:

1. Ipsofactura vuelve a preguntar por ese timbrado en segundo plano.
2. Cuando el resultado se confirma, el complemento queda registrado completo —su árbol de
   pagos y documentos relacionados incluidos— y se emite el webhook
   [`pago.timbrado`](/webhooks#eventos-de-cancelación-y-timbrado-tardío) **tardío**, idéntico
   al de un REP timbrado a tiempo.
3. Un reintento con la misma `idempotency_key` responde `409 DUPLICATE_IN_PROGRESS` mientras la
   reconciliación sigue pendiente, y el `201` original del complemento —con sus
   `documentos_relacionados`— en cuanto se resuelve.

Esto importa más en un REP que en una factura: la cadena de parcialidades de la PPD se deriva
de los pagos que registramos, así que un REP perdido dejaría cada parcialidad posterior sobre
un saldo equivocado. El patrón completo está en
[Recuperación tras timeout](/recuperacion-timeouts).

<Warning>
  Igual que en el timbrado de facturas: **no reintentes con otra `idempotency_key`**. Un timeout
  no es evidencia de que no se timbró.
</Warning>

## `rfc_receptor`: sólo para el sandbox

El receptor de un CFDI tipo P lo determina la factura que liquida —el SAT exige que coincidan—,
así que el REP nunca lleva un receptor propio. Por eso el campo opcional `rfc_receptor` de
`POST /cfdi/complemento-pago` **no se timbra**: existe para que en el
[sandbox](/sandbox#rfcs-mágicos) puedas forzar un escenario con los mismos RFC reservados que
usas en `POST /cfdi/timbrar`. En producción se ignora.

```json Forzar un escenario del sandbox en el REP theme={null}
{
  "rfc_emisor": "EKU9003173C9",
  "rfc_receptor": "SBX010101LAT",
  "pagos": [ { "...": "el pago normal" } ]
}
```
