# Eliminar un Borrador de Factura [BETA]

<p class="callout warning">**Funcionalidad en desarrollo y pruebas:** este servicio está disponible en el ambiente de pruebas y todavía no debe usarse en producción. El funcionamiento, los campos y los mensajes de error pueden cambiar antes del despliegue definitivo.</p>

Este servicio **elimina un borrador de factura de venta** creado por el consumidor autenticado. Es la operación de compensación cuando el flujo local falla después de crear el borrador: nuby borra la factura y toda su cascada (detalle, impuestos, contabilidad y pagos/recibos) para no dejar documentos huérfanos.

Solo aplica a **facturas de venta en estado borrador** creadas por el propio consumidor. Una factura de contrato, una factura ya emitida o una factura de la UI se rechazan.

<p class="callout info">**¿Para qué sirve este servicio?**  
Úsalo como **compensación de tu propio flujo**: si después de crear el borrador algo falla en tu plataforma, eliminas el documento para no dejarlo colgado en nuby. Los casos de uso más frecuentes son:</p>

<div class="page-content" dir="auto" id="bkmrk-deshacer-una-creaci%C3%B3"><div dir="auto">- **Deshacer una creación que no pudo completarse** en tu sistema, después de recibir el `factura_id` del alta.
- **Limpiar borradores de prueba** generados por tus validaciones de integración.
- **Compensar un flujo distribuido fallido**: el borrador ya no va a emitirse ni a usarse.
- **Reintentar sin riesgo**: repetir la misma eliminación devuelve la respuesta original en lugar de un `404`.

---

  </div></div>#### **1. El Endpoint (La dirección web)**

##### **Método y dirección**

<div class="page-content" dir="auto" id="bkmrk-dato-valor-m%C3%A9todo-de"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 23%;"></col><col style="width: 77%;"></col></colgroup><thead><tr><th>Dato</th><th>Valor</th></tr></thead><tbody><tr><td>Método</td><td>`DELETE`</td></tr><tr><td>Ruta</td><td>`/invoices/{factura_id}`</td></tr><tr><td>Scope requerido</td><td>`invoices:update`</td></tr><tr><td>Autenticación</td><td>`Authorization: Bearer <jwt>`</td></tr><tr><td>Cuerpo de la petición</td><td>No se envía cuerpo</td></tr></tbody></table>

</div></div>```http
DELETE https://{{instancia}}/service/v2/public/invoices/{factura_id}
```

##### **¿Qué debo colocar en `{{instancia}}`?**

Es la **dirección web principal** con la que ingresas a tu plataforma, incluyendo la terminación `.nuby.app` o `.arrendasoft.co`. Si entras a nuby escribiendo `inmobiliaria.nuby.app` en el navegador, esa es tu instancia.

<p class="callout warning">**Importante:** no incluyas `https://` ni barras diagonales (`/`) al final. La dirección se compone así: `https://` + tu instancia + `/service/v2/public/invoices/` + el identificador de la factura.</p>

##### **Encabezados requeridos**

<div class="page-content" dir="auto" id="bkmrk-encabezado-%C2%BFobligato"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 22%;"></col><col style="width: 14%;"></col><col style="width: 64%;"></col></colgroup><thead><tr><th>Encabezado</th><th>¿Obligatorio?</th><th>Valor</th></tr></thead><tbody><tr><td>`Authorization`</td><td>Sí</td><td>`Bearer <token>`, con el token obtenido en el servicio **Login**.</td></tr><tr><td>`Content-Type`</td><td>No</td><td>`application/json` si tu cliente lo envía. Como la petición no lleva cuerpo, el servicio no lo necesita.</td></tr></tbody></table>

</div></div>##### **Permiso requerido (scope): `invoices:update`**

Este servicio exige el permiso `invoices:update` en el cliente OAuth. Si el token es válido pero al cliente le falta el permiso, la respuesta es `403` con el código `RECURSO_NO_AUTORIZADO`.

<p class="callout info">Si aún no tienes un token, consulta la página [*<span class="tw-font-bold tw-text-arrBlue hover:tw-text-nubyBlue tw-cursor-pointer">Login</span>*](https://docs.nuby.ai/books/api-nuby-v2/page/login) del libro **API nuby V2**. Allí se explica cómo identificarte y cómo se construye el encabezado `Authorization`.</p>

##### **Qué necesitas**

<div class="page-content" dir="auto" id="bkmrk-el-factura_id-del-bo"><div dir="auto">- El `factura_id` del borrador que quieres eliminar.
- Un token con el scope `invoices:update`.
- Que la factura haya sido creada por tu consumidor y siga en estado `5` (Borrador).

</div></div><p class="callout warning">**No hay soft delete.** La eliminación es física e irreversible. La trazabilidad de la operación queda en `api_solicitudes` y en los logs de nuby.</p>

<div class="page-content" dir="auto" id="bkmrk-"><div dir="auto">  </div></div>#### **2. La Petición (¿Qué debes enviarnos?)**

##### **Path parameter**

<div class="page-content" dir="auto" id="bkmrk-campo-tipo-requerido"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 22%;"></col><col style="width: 8%;"></col><col style="width: 9%;"></col><col style="width: 61%;"></col></colgroup><thead><tr><th>Campo</th><th>Tipo</th><th>Requerido</th><th>Descripción</th></tr></thead><tbody><tr><td>`factura_id`</td><td>entero &gt; 0</td><td>Sí</td><td>Identificador interno de la factura en nuby. Va en la dirección. Si no es un entero positivo, la respuesta es `400` con el mensaje `Debe indicar un factura_id entero mayor que 0 para operar sobre la factura.`</td></tr></tbody></table>

</div></div>##### **Body**

No se envía cuerpo. Tampoco se envía `referencia_externa`: la idempotencia usa la clave interna `eliminar:{factura_id}`.

##### **Orden de evaluación**

<div class="page-content" dir="auto" id="bkmrk-scope-invoices%3Aupdat"><div dir="auto">1. Scope `invoices:update`.
2. `factura_id` válido.
3. Idempotencia (replay si ya se eliminó).
4. Guard de contrato.
5. Existencia de la factura.
6. Pertenencia al consumidor.
7. Estado `5` (Borrador).

  </div></div>#### **3. La Respuesta (¿Qué te entregaremos?)**

##### **Respuesta exitosa**

```json
{
  "success": true,
  "status": 200,
  "message": "La factura de venta se eliminó correctamente.",
  "body": {
    "factura_id": 10231,
    "eliminada": true,
    "idempotent_replay": false
  }
}
```

##### **Estructura principal de la respuesta**

<div class="page-content" dir="auto" id="bkmrk-campo-tipo-descripci"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 22%;"></col><col style="width: 8%;"></col><col style="width: 70%;"></col></colgroup><thead><tr><th>Campo</th><th>Tipo</th><th>Descripción</th></tr></thead><tbody><tr><td>`success`</td><td>booleano</td><td>`true` cuando la eliminación se completó.</td></tr><tr><td>`status`</td><td>entero</td><td>Replica el código HTTP de la respuesta: `200` en el éxito y también en el reintento idempotente.</td></tr><tr><td>`message`</td><td>texto</td><td>Mensaje legible del resultado.</td></tr><tr><td>`body`</td><td>objeto</td><td>Identificador de la factura eliminada e indicador del reintento. En un error, este campo no existe.</td></tr></tbody></table>

</div></div><p class="callout info">Las respuestas de escritura **no** traen `pagination` ni `filters_applied`: esos campos pertenecen a las consultas de lectura.</p>

##### **Reintento idempotente**

Si repites el `DELETE` de una factura que ya eliminaste, nuby devuelve la respuesta original con `idempotent_replay: true`. No intenta borrar dos veces ni responde `404`.

##### **Campos de la respuesta**

<div class="page-content" dir="auto" id="bkmrk-campo-tipo-descripci-1"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 22%;"></col><col style="width: 8%;"></col><col style="width: 70%;"></col></colgroup><thead><tr><th>Campo</th><th>Tipo</th><th>Descripción</th></tr></thead><tbody><tr><td>`factura_id`</td><td>entero</td><td>Factura eliminada.</td></tr><tr><td>`eliminada`</td><td>booleano</td><td>`true` si la eliminación se completó.</td></tr><tr><td>`idempotent_replay`</td><td>booleano</td><td>`true` cuando la respuesta viene de un reintento idempotente.</td></tr></tbody></table>

</div></div>##### **Escenario alternativo: el reintento**

La segunda llamada sobre el mismo `factura_id` responde `200` con el `body` de la respuesta original y el indicador en `true`:

```json
{
  "factura_id": 10231,
  "eliminada": true,
  "idempotent_replay": true
}
```

<div class="page-content" dir="auto" id="bkmrk--1"><div dir="auto">  </div></div>#### **4. Idempotencia y reintentos**

Este servicio no usa `referencia_externa`. La clave de idempotencia es `eliminar:{factura_id}`, registrada en `api_solicitudes` con el recurso `factura` y la operación `eliminar`.

<div class="page-content" dir="auto" id="bkmrk-situaci%C3%B3n-qu%C3%A9-respon"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 34%;"></col><col style="width: 66%;"></col></colgroup><thead><tr><th>Situación</th><th>Qué responde nuby</th></tr></thead><tbody><tr><td>Reintento de una eliminación ya completada</td><td>`200` con la respuesta original y `idempotent_replay: true`.</td></tr><tr><td>Otra solicitud con la misma clave en curso</td><td>`409 IDEMPOTENCY_IN_PROGRESS`.</td></tr><tr><td>La fila de `api_solicitudes` ya fue purgada por retención</td><td>`404 FACTURA_NO_ENCONTRADA` en lugar del replay.</td></tr></tbody></table>

</div></div><p class="callout info">**Reintenta sin miedo.** Mientras la solicitud siga registrada, repetir el `DELETE` es seguro: nunca borra dos veces y nunca responde `404` por un borrado previo exitoso.</p>

<div class="page-content" dir="auto" id="bkmrk--2"><div dir="auto">  </div></div>#### **5. Seguridad y Posibles Errores**

##### **Tu pase tiene fecha de caducidad**

Por medidas de seguridad, el token que te entregamos **solo dura 1 hora**. Una vez transcurrido ese tiempo, el sistema te bloquea el acceso con un error `401` y tu sistema debe volver a consumir el servicio de **Login** para pedir un pase nuevo.

Esta respuesta la produce el **middleware de autenticación** y no el contrato de la operación, así que usa el envelope heredado (`statusCode` y `error.description`), no `success` ni `error_code`:

<div class="page-content" dir="auto" id="bkmrk-situaci%C3%B3n-http-detal"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 34%;"></col><col style="width: 14%;"></col><col style="width: 52%;"></col></colgroup><thead><tr><th>Situación</th><th>HTTP</th><th>Detalle</th></tr></thead><tbody><tr><td>No se envió el encabezado `Authorization`</td><td>`401`</td><td>`JWT Token required.`</td></tr><tr><td>El encabezado no tiene el formato `Bearer {token}`</td><td>`400`</td><td>`JWT Token not send.`</td></tr><tr><td>El token no fue encontrado en el sistema</td><td>`401`</td><td>`JWT Token not found.`</td></tr><tr><td>El token expiró</td><td>`401`</td><td>`JWT Token expired.`</td></tr></tbody></table>

</div></div>##### **Errores del contrato de la operación**

Los errores de la operación traen `success: false`, el código HTTP, un `message` legible y un `error_code` estable:

<div class="page-content" dir="auto" id="bkmrk-http-error_code-cu%C3%A1n"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 22%;"></col><col style="width: 14%;"></col><col style="width: 64%;"></col></colgroup><thead><tr><th>HTTP</th><th>`error_code`</th><th>Cuándo</th></tr></thead><tbody><tr><td>`400`</td><td>`VALIDATION_ERROR`</td><td>El `factura_id` del path no es un entero positivo.</td></tr><tr><td>`403`</td><td>`CONSUMIDOR_NO_AUTENTICADO`</td><td>El token es válido, pero no corresponde a un cliente OAuth habilitado para consumir la API.</td></tr><tr><td>`403`</td><td>`RECURSO_NO_AUTORIZADO`</td><td>El consumidor no tiene `invoices:update`.</td></tr><tr><td>`403`</td><td>`RECURSO_AJENO`</td><td>La factura fue creada por la UI u otro consumidor.</td></tr><tr><td>`404`</td><td>`FACTURA_NO_ENCONTRADA`</td><td>La factura no existe.</td></tr><tr><td>`409`</td><td>`FACTURA_CONTRATO_NO_SOPORTADA`</td><td>La factura es de contrato; se gestiona en el flujo de contratos.</td></tr><tr><td>`409`</td><td>`FACTURA_NO_ELIMINABLE`</td><td>La factura no está en estado `5` (Borrador). El mensaje incluye el estado actual.</td></tr><tr><td>`409`</td><td>`IDEMPOTENCY_IN_PROGRESS`</td><td>Otra solicitud con la misma clave está en proceso.</td></tr><tr><td>`500`</td><td>`INTERNAL_ERROR`</td><td>Fallo técnico; el detalle queda en el log `API_INTEGRACION`.</td></tr></tbody></table>

</div></div>##### **Forma del rechazo de contrato**

El rechazo por contrato llega con el código `FACTURA_CONTRATO_NO_SOPORTADA`, un mensaje que explica el motivo y una advertencia que identifica la factura y el alcance del endpoint:

<div class="page-content" dir="auto" id="bkmrk-campo-de-warnings%5B%5D-"><div dir="auto"><table style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 22%;"></col><col style="width: 8%;"></col><col style="width: 70%;"></col></colgroup><thead><tr><th>Campo de `warnings[]`</th><th>Tipo</th><th>Significado</th></tr></thead><tbody><tr><td>`code`</td><td>texto</td><td>Siempre `FACTURA_CONTRATO_NO_SOPORTADA`. Es el valor que debes evaluar en tu código.</td></tr><tr><td>`factura_id`</td><td>entero</td><td>Identificador de la factura rechazada.</td></tr><tr><td>`scope`</td><td>texto</td><td>Alcance del endpoint: confirma que el rechazo es por tipo de factura, no por permisos.</td></tr></tbody></table>

</div></div><p class="callout warning">**Trata el `404` como un caso de negocio**, no como una falla técnica: significa que el identificador no existe en nuby (o que nunca lo creó tu consumidor). No lo reintentes con el mismo valor.</p>

<p class="callout warning">**Trata el `409 FACTURA_NO_ELIMINABLE` como un caso de negocio**: la factura ya salió de borrador y no se elimina por esta vía. Revisa el estado actual en el mensaje antes de decidir qué hacer.</p>

<div class="page-content" dir="auto" id="bkmrk--3"><div dir="auto">  ---

</div></div>#### **6. Ejemplos de integración**

Aquí tienes ejemplos listos para adaptar a tu plataforma. Todos usan `{{instancia}}`, `Authorization: Bearer`, eliminan un borrador y tratan el éxito junto con los rechazos de negocio más frecuentes (`404` y `409`).

<div class="page-content" dir="auto" id="bkmrk-curl-eliminar-un-bor"><div dir="auto"><details><summary>cURL</summary>

**Eliminar un borrador**

```bash
TOKEN="TU_TOKEN_AQUI"
INSTANCIA="mi-inmobiliaria.nuby.app"
FACTURA_ID=10231

curl -X DELETE "https://${INSTANCIA}/service/v2/public/invoices/${FACTURA_ID}" \
  -H "Authorization: Bearer ${TOKEN}"
```

**Reintentar la misma eliminación**

```bash
curl -X DELETE "https://${INSTANCIA}/service/v2/public/invoices/${FACTURA_ID}" \
  -H "Authorization: Bearer ${TOKEN}"
```

La segunda llamada devuelve `200` con `idempotent_replay: true`.

</details><details><summary>PHP</summary>

```php
<?php
$instancia = 'mi-inmobiliaria.nuby.app'; // Tu dirección web completa
$token     = 'TU_TOKEN_AQUI';            // Token obtenido del servicio Login
$facturaId = 10231;

$ch = curl_init("https://{$instancia}/service/v2/public/invoices/{$facturaId}");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer {$token}",
]);

$respuesta = curl_exec($ch);
$httpCode  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$info = json_decode($respuesta, true);

if ($httpCode === 200) {
    echo "Factura eliminada: {$info['body']['factura_id']}\n";
    echo "Reintento idempotente: " . var_export($info['body']['idempotent_replay'], true) . "\n";
} elseif ($httpCode === 404 || $httpCode === 409) {
    echo "Rechazo de negocio {$info['error_code']}: {$info['message']}\n";
} else {
    echo "Error HTTP {$httpCode}: {$info['message']}\n";
}
```

</details><details><summary>Python</summary>

```python
import requests

# 1. Configura tus datos de acceso
instancia = "mi-inmobiliaria.nuby.app"  # Reemplaza con tu dirección web completa
token = "TU_TOKEN_AQUI"                 # Token obtenido del servicio Login
factura_id = 10231

# 2. Envía la petición
url = f"https://{instancia}/service/v2/public/invoices/{factura_id}"
headers = {"Authorization": f"Bearer {token}"}

respuesta = requests.delete(url, headers=headers, timeout=30)

# 3. Procesa la respuesta
if respuesta.status_code == 200:
    body = respuesta.json()["body"]
    print(f"Factura {body['factura_id']} eliminada: {body['eliminada']}")
    print(f"Reintento idempotente: {body['idempotent_replay']}")
elif respuesta.status_code in (404, 409):
    error = respuesta.json()
    print(f"Rechazo de negocio {error['error_code']}: {error['message']}")
else:
    print(f"Error HTTP {respuesta.status_code}: {respuesta.text}")
```

</details><details><summary>JavaScript (Fetch API)</summary>

```javascript
// 1. Configura tus datos de acceso
const instancia = 'mi-inmobiliaria.nuby.app'; // Reemplaza con tu dirección web completa
const token = 'TU_TOKEN_AQUI';                // Token obtenido del servicio Login

// 2. Función que elimina el borrador
async function eliminarBorrador(facturaId) {
  const url = `https://${instancia}/service/v2/public/invoices/${facturaId}`;

  const response = await fetch(url, {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  });

  const info = await response.json().catch(() => ({}));

  if (!response.ok) {
    console.error(`Error HTTP ${response.status} ${info.error_code ?? ''}: ${info.message ?? ''}`);
    return null;
  }

  return info.body;
}

// 3. Elimina el borrador y revisa si fue un reintento
eliminarBorrador(10231).then((body) => {
  if (!body) {
    return;
  }

  console.log(`Factura ${body.factura_id} eliminada: ${body.eliminada}`);
  console.log(`Reintento idempotente: ${body.idempotent_replay}`);
});
```

</details>  </div></div>#### **7. Buenas prácticas y límites conocidos**

##### **Recomendaciones**

<div class="page-content" dir="auto" id="bkmrk-elimina-solo-cuando-"><div dir="auto">- **Elimina solo cuando el flujo local falló.** Si la creación se completó, conserva el borrador para editarlo o emitirlo.
- **No reutilices la referencia de creación.** El DELETE usa su propia clave; no necesitas enviar `referencia_externa`.
- **Trata el `200` con `idempotent_replay: true` como éxito.** Significa que la factura ya estaba eliminada por tu consumidor.
- **No uses DELETE para anular una factura emitida.** La anulación tiene su propio flujo; el DELETE solo acepta borradores.
- **Registra el `factura_id` y el resultado.** La operación es irreversible y la trazabilidad queda en `api_solicitudes`.

</div></div>##### **Límites de este servicio**

<div class="page-content" dir="auto" id="bkmrk-borrado-f%C3%ADsico-e-irr"><div dir="auto">- **Borrado físico e irreversible.** No hay soft delete: no existe una operación para recuperar la factura eliminada.
- **Solo borradores.** Una factura en estado distinto de `5` se rechaza con `409 FACTURA_NO_ELIMINABLE`.
- **Solo facturas propias.** Una factura creada por la UI u otro consumidor se rechaza con `403 RECURSO_AJENO`.
- **No anula ni emite.** Este servicio no reemplaza el flujo de anulación ni el de emisión de la factura.
- **Depende de la retención de `api_solicitudes`.** Si la solicitud ya fue purgada, un `DELETE` repetido responde `404` en lugar del replay.

</div></div>