import { Aside } from "@astrojs/starlight/components";

Un rechazo de la DGII no siempre es definitivo. A veces el problema está en
el propio comprobante y hay que emitir uno nuevo. Otras veces la DGII falla
de forma pasajera, por ejemplo durante un mantenimiento, y basta con enviar
de nuevo el mismo comprobante. NovaFE distingue ambos casos para que no
tengas que hacerlo tú.

## Qué indica un rechazo

Un comprobante rechazado queda con `status: "rejected"` y el motivo en
`dgii.messages`, una lista de objetos `{ code, value }`. El campo
`dgii.sequenceUsed` responde la pregunta más importante: si la DGII
consumió el e-NCF.

| `dgii.sequenceUsed` | Significado                                                                                    |
| ------------------- | ---------------------------------------------------------------------------------------------- |
| `false`             | La DGII no consumió el e-NCF. El número sigue asociado a este comprobante y se puede reenviar. |
| `true` o ausente    | La DGII consumió el e-NCF. Queda invalidado de forma permanente.                               |

## Qué hace NovaFE en cada caso

| Situación                                                                      | Qué hace NovaFE                                            | Qué haces tú                                                     |
| ------------------------------------------------------------------------------ | ---------------------------------------------------------- | ---------------------------------------------------------------- |
| Falla pasajera de la DGII (por ejemplo, el código 004) y el número quedó libre | Reenvía el mismo comprobante por su cuenta, en intervalos. | Nada. Espera el resultado final por webhook.                     |
| Rechazo por otro motivo y el número quedó libre                                | Lo deja en `rejected`.                                     | Reenviarlo con `POST /ecf/{id}/retry` si lo consideras adecuado. |
| Rechazo con el número consumido                                                | Lo deja en `rejected`. `/retry` responde `409`.            | Emitir un comprobante nuevo, que recibirá otro e-NCF.            |

<Aside type="caution" title="Reenviar no corrige el contenido">
  Un reenvío envía exactamente el mismo XML ya firmado. Sirve cuando el fallo
  fue de la DGII, no cuando el motivo del rechazo está en los datos del
  comprobante, como un monto, un RNC o una firma inválidos. En ese caso el
  reenvío volvería a ser rechazado. Un número que la DGII dejó libre tampoco se
  reasigna a un comprobante nuevo: se reenvía únicamente el comprobante al que
  pertenece.
</Aside>

## Reintento automático

Cuando la DGII rechaza un comprobante **y** dice que no consumió el e-NCF, y
además el motivo es uno de los que NovaFE reconoce como falla pasajera de la
DGII, el comprobante no llega a quedar `rejected`. En su lugar:

1. Vuelve a `signed`. En `dgii.messages` puedes ver el último motivo que
   informó la DGII mientras se espera el reenvío.
2. NovaFE lo reenvía más tarde, con el mismo XML, el mismo e-NCF, el mismo
   código de seguridad y el mismo QR. Por eso la representación impresa que ya
   hayas entregado sigue siendo válida.
3. Si la DGII lo acepta, pasa a `accepted` como cualquier otro comprobante.

No se emite `ecf.rejected` mientras dure el reintento. Solo se emite si los
reintentos se agotan y el rechazo pasa a ser definitivo. En cambio, cada
reenvío que la DGII recibe emite de nuevo `ecf.submitted`, porque es una
recepción nueva.

Por defecto, NovaFE reintenta hasta 6 veces, esperando 2 minutos, 10
minutos, 30 minutos, 2 horas, 6 horas y 12 horas, unas 20 horas en total.
Es tiempo suficiente para un mantenimiento largo de la DGII sin dejar un
comprobante pendiente de forma indefinida.

## Reenvío manual

```
POST /api/v1/ecf/{id}/retry
```

Acepta un comprobante en estado `failed`, `review` o `rejected`. En el caso
de `rejected` exige que la DGII haya dejado libre el e-NCF.

| Respuesta | Cuándo                                                                                         |
| --------- | ---------------------------------------------------------------------------------------------- |
| `202`     | El comprobante volvió a `signed` y se reenviará. La respuesta trae el comprobante actualizado. |
| `404`     | El comprobante no existe o no es de tu tenant.                                                 |
| `409`     | El estado no admite reintento, o la DGII consumió el e-NCF de un comprobante `rejected`.       |

## Historial de envíos

`GET /api/v1/ecf/{id}/attempts` devuelve un renglón por cada resultado que
la DGII dio para el comprobante, del más antiguo al más reciente. Es útil
porque el comprobante solo muestra el último resultado: sin el historial, un
rechazo anterior a un reenvío exitoso dejaría de verse.

```json
[
  {
    "attempt": 1,
    "outcome": "rejected",
    "trackId": "d64c95e4-69c3-4f6f-bd9b-cd3da8b9284f",
    "statusCode": 2,
    "statusText": "Rechazado",
    "sequenceUsed": false,
    "messages": [
      {
        "code": 4,
        "value": "Ha ocurrido un error validando en eCF, favor intentar nueva vez."
      }
    ],
    "retryDecision": "auto:4",
    "xmlSha256": "3f8a1c9e2b7d4f60a5c8e1d2b3a4f5061c7e8d9f0a1b2c3d4e5f60718293a4b5",
    "recordedAt": "2026-09-25T15:32:42-04:00"
  },
  {
    "attempt": 2,
    "outcome": "accepted",
    "trackId": "a12b34c5-6d7e-4f80-9a1b-2c3d4e5f6a7b",
    "statusCode": 1,
    "statusText": "Aceptado",
    "sequenceUsed": true,
    "messages": [],
    "retryDecision": null,
    "xmlSha256": "3f8a1c9e2b7d4f60a5c8e1d2b3a4f5061c7e8d9f0a1b2c3d4e5f60718293a4b5",
    "recordedAt": "2026-09-25T15:34:50-04:00"
  }
]
```

| Campo           | Nota                                                                                                                                                        |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attempt`       | 1, 2, 3, dentro del comprobante.                                                                                                                            |
| `outcome`       | El estado en que quedó el comprobante: `accepted`, `accepted_conditional`, `rejected`, `review` o `failed`.                                                 |
| `retryDecision` | En un rechazo: `auto:<código>` si NovaFE lo reenvió por su cuenta, o `exhausted` si era reintentable pero se agotaron los reintentos. Ausente en los demás. |
| `xmlSha256`     | Huella del XML enviado. Si dos renglones repiten el valor, se enviaron exactamente los mismos bytes.                                                        |
| `recordedAt`    | Cuándo se registró el resultado.                                                                                                                            |

Un comprobante que aún no tiene ningún resultado devuelve una lista vacía.
Los reintentos de transporte que no llegan a producir un resultado, como un
tiempo de espera agotado, no generan renglón.

## Recomendaciones

- **No emitas un comprobante nuevo ante un rechazo sin revisar
  `dgii.sequenceUsed`.** Si es `false`, el comprobante original todavía puede
  completarse. Emitir otro dejaría un e-NCF sin usar y un comprobante duplicado.
- **Suscríbete a los webhooks** en lugar de consultar en bucle. Un rechazo
  reintentable no genera `ecf.rejected`, así que recibir ese evento indica que
  el resultado ya es definitivo.
- **Manda siempre `Idempotency-Key`** en `POST /ecf` (es obligatorio), para que
  un reintento de tu lado no emita dos comprobantes (ver
  [Endpoints y semántica](/emision/endpoints-y-semantica/)).