# Quantum Nube API - Facturación Electrónica y Documento Soporte Electrónico

**Guía maestra de integración externa REST/JSON**  
**Versión:** 1.0.0  
**Fecha de referencia:** 07 de octubre de 2026  
**Base URL de producción:** `https://nomina.quantumpos.com.co/api/ext/v1`  
**Autenticación:** Bearer Token  
**Formato:** JSON UTF-8

> [!IMPORTANT]
> Este documento describe la API externa v1 de Quantum Nube para Facturación Electrónica (FE) y Documento Soporte Electrónico (DSE). Está diseñado para equipos de desarrollo, integradores ERP/POS/e-commerce y para publicar la documentación dentro del portal web de Quantum. Los ejemplos usan datos ficticios. Nunca publique tokens reales, contraseñas, certificados, claves técnicas ni credenciales DIAN.

---

## 1. Cómo se documenta profesionalmente una API

Una documentación profesional no debería vivir únicamente en Postman ni únicamente en una página escrita a mano. La recomendación para Quantum Nube es usar **cuatro capas complementarias**, con una sola fuente técnica de verdad:

| Capa | Propósito | Entregable recomendado |
|---|---|---|
| Contrato de API | Define rutas, métodos, seguridad, parámetros, esquemas y respuestas | `openapi/quantum-api-v1.yaml` |
| Portal humano | Explica conceptos, flujos, reglas fiscales, ejemplos y errores | Sección `/developers/api` en la web |
| Cliente de pruebas | Permite probar peticiones sin construir código desde cero | Colección y environment de Postman |
| Ejemplos y guías | Aceleran la integración en distintos lenguajes y sistemas | cURL, JavaScript, Python, PHP y JSON |

La recomendación es que **OpenAPI 3.1 sea la fuente contractual**, que el sitio web la renderice con un visor como Scalar, Redoc o Swagger UI, y que alrededor de esa referencia se publiquen guías manuales más explicativas. Postman debe mantenerse como complemento de pruebas y onboarding, no como única documentación.

### 1.1 Estructura recomendada del portal de desarrolladores

La web debería exponer una sección similar a:

```text
/developers
/developers/api
/developers/api/quickstart
/developers/api/authentication
/developers/api/fe
/developers/api/dse
/developers/api/errors
/developers/api/idempotency
/developers/api/reference
/developers/api/changelog
/developers/downloads/openapi.yaml
/developers/downloads/postman-collection.json
```

La navegación ideal tiene un menú lateral fijo en escritorio, buscador, contenido central, tabla de contenidos a la derecha y pestañas de código `cURL / JavaScript / Python / PHP`. En móvil, el menú lateral debe convertirse en drawer y el código debe permitir desplazamiento horizontal sin romper el layout.

### 1.2 Qué NO debe hacer la página pública

La documentación pública nunca debe guardar un Bearer Token real en HTML, JavaScript, variables públicas de build, `localStorage`, repositorios o ejemplos. Si se ofrece una función de **Try it**, debe estar detrás de un portal autenticado y, preferiblemente, ejecutarse mediante un backend/proxy de Quantum. Un token de integración de larga duración no debe viajar a un navegador público.

---

## 2. Resumen de la API externa v1

Todas las rutas descritas en este manual cuelgan de:

```text
https://nomina.quantumpos.com.co/api/ext/v1
```

El identificador de empresa forma parte de la URL:

```text
/companies/{company_id}/...
```

Cada token queda asociado a una empresa. Quantum valida que el `company_id` de la ruta corresponda a la empresa del token; intentar acceder a otra empresa produce una respuesta `403`.

### 2.1 Endpoints FE y DSE disponibles

| Método | Ruta | Scope | Uso |
|---|---|---|---|
| GET | `/companies/{company_id}/fe/docs` | `fe.read` | Lista documentos FE de producción |
| GET | `/companies/{company_id}/fe/docs/{doc_id}` | `fe.read` | Obtiene detalle de un documento FE |
| GET | `/companies/{company_id}/fe/terceros` | `fe.read` | Lista terceros/receptores registrados |
| GET | `/companies/{company_id}/fe/payment-methods` | `fe.read` | Consulta métodos de pago configurados para la empresa |
| POST | `/companies/{company_id}/dian/get-acquirer` | `fe.read` | Consulta datos de adquirente mediante el flujo DIAN soportado |
| POST | `/companies/{company_id}/fe/facturas` | `fe.write` | Crea, firma y procesa/envía una FE |
| POST | `/companies/{company_id}/dse/documentos` | `dse.write` | Crea, firma y transmite un DSE con idempotencia obligatoria |
| GET | `/companies/{company_id}/dse/documentos/{doc_id}` | `dse.read` | Consulta el estado y detalle de un DSE |

> [!NOTE]
> Este paquete se concentra deliberadamente en FE y DSE. La API externa también puede contener otros módulos/versiones, pero no forman parte del contrato documentado aquí.

---

## 3. Autenticación Bearer y aislamiento multiempresa

### 3.1 Header obligatorio

```http
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
Content-Type: application/json
```

Para solicitudes `GET` sin body, `Content-Type` puede omitirse. El token debe almacenarse en un gestor de secretos o variable de entorno del servidor consumidor.

### 3.2 Scopes

Los permisos se asignan al **cliente API** y los tokens de ese cliente heredan sus scopes. Los scopes relevantes son:

| Scope | Permite |
|---|---|
| `fe.read` | Consultar FE, terceros, métodos de pago y GetAcquirer |
| `fe.write` | Crear/procesar facturas electrónicas |
| `dse.read` | Consultar DSE por ID |
| `dse.write` | Crear, firmar y transmitir DSE |

Un token válido sin el scope necesario recibe `403`.

### 3.3 Respuestas típicas de seguridad

**Sin token o token inválido:**

```json
{
  "ok": false,
  "error": "Authorization Bearer faltante o inválido."
}
```

**Token de otra empresa:**

```json
{
  "ok": false,
  "error": "El token no tiene acceso a la empresa solicitada."
}
```

**Scope insuficiente:**

```json
{
  "ok": false,
  "error": "Scopes insuficientes. Faltan: dse.write"
}
```

### 3.4 Reglas operativas de seguridad

1. Trate el token como una contraseña de máquina a máquina.
2. No lo incluya en capturas, tickets, logs compartidos ni documentación pública.
3. No use un token de producción desde JavaScript del navegador.
4. Separe clientes API cuando existan terceros o integraciones con responsabilidades diferentes.
5. Rote/revoque credenciales cuando cambie el proveedor o exista sospecha de exposición.
6. Mantenga `company_id` configurable; no mezcle IDs entre clientes.
7. Aplique timeout, registro de respuesta y control de reintentos del lado integrador.

---

## 4. Convenciones generales

### 4.1 JSON

Las peticiones con body usan:

```http
Content-Type: application/json
```

Use JSON válido, números sin separadores de miles y fechas en ISO `YYYY-MM-DD` salvo que un campo indique otro formato.

### 4.2 Dinero y redondeo

Enviar valores monetarios como números JSON. Para conciliación, el sistema consumidor debe usar aritmética decimal, no `float` binario, y redondear a dos decimales cuando corresponda.

### 4.3 HTTP

| Código | Interpretación recomendada |
|---:|---|
| 200 | Consulta o operación exitosa; también puede representar replay idempotente |
| 201 | Documento nuevo creado/procesado exitosamente |
| 202 | Solicitud idempotente ya reservada y aún no terminal |
| 400 | Payload inválido/regla de negocio incumplida |
| 401 | Bearer ausente, inválido, expirado o revocado |
| 403 | Empresa incorrecta o scope insuficiente |
| 404 | Recurso no encontrado dentro de la empresa |
| 409 | Conflicto de idempotencia DSE: misma llave con payload diferente |
| 422 | DSE procesado pero clasificado como rechazado |
| 500 | Error interno inesperado |
| 502 | Resultado de transporte/DIAN incierto; revisar `pending` antes de reintentar |

### 4.4 Regla de reintentos

**Nunca asuma que un timeout equivale a “el documento no llegó”.** Una conexión puede cortarse después de que Quantum haya recibido o transmitido el documento. FE y DSE tienen mecanismos de trazabilidad diferentes y deben respetarse:

- FE: conservar y reutilizar el mismo `draft_key` para la operación original.
- DSE: conservar y reutilizar el mismo header `Idempotency-Key` y el mismo payload.
- En DSE, si la respuesta indica `pending: true`, no cree una segunda operación con una llave nueva.

---

## 5. Quickstart - validar conectividad sin emitir un documento

### 5.1 Probar autenticación con métodos de pago FE

```bash
curl -sS \
  -H "Authorization: Bearer $QUANTUM_API_TOKEN" \
  -H "Accept: application/json" \
  "https://nomina.quantumpos.com.co/api/ext/v1/companies/$COMPANY_ID/fe/payment-methods"
```

Esta petición permite comprobar token, empresa y `fe.read` sin crear un documento fiscal.

### 5.2 Variables mínimas recomendadas

```text
QUANTUM_API_BASE=https://nomina.quantumpos.com.co/api/ext/v1
QUANTUM_COMPANY_ID=<ID_EMPRESA>
QUANTUM_API_TOKEN=<SECRETO>
```

---

# PARTE I - FACTURACIÓN ELECTRÓNICA (FE)

## 6. Flujo FE de extremo a extremo

La ruta de creación FE reutiliza el flujo operativo probado de Quantum Nube: recibe la venta, crea la factura, genera XML, firma y ejecuta el procesamiento/envío DIAN según la configuración de la empresa.

Flujo recomendado del integrador:

```text
Venta confirmada en ERP/POS
        |
        v
Consultar/mantener mapeos de pago
        |
        v
Construir payload FE + draft_key
        |
        v
POST /fe/facturas
        |
        +---- 2xx ---> persistir factura_id / CUFE / estado
        |
        +---- timeout/error incierto ---> NO generar otra venta; conservar draft_key
        |
        v
GET /fe/docs/{doc_id} para detalle/seguimiento cuando se disponga del ID
```

---

## 7. Crear Factura Electrónica

### 7.1 Endpoint

```http
POST /api/ext/v1/companies/{company_id}/fe/facturas
Authorization: Bearer <token con fe.write>
Content-Type: application/json
```

### 7.2 Campos de primer nivel

| Campo | Tipo | Recomendación | Descripción |
|---|---|---|---|
| `receptor` | object | Requerido para integración | Datos del adquirente |
| `items` | array | Requerido | Líneas comerciales |
| `subtotal` | number | Recomendado | Base total antes de impuestos |
| `impuestos` | number | Recomendado | Total de impuestos |
| `total` | number | Recomendado | Total bruto del documento |
| `es_contingencia` | boolean | Opcional, default normal | `false` normal; `true` cuando aplique contingencia |
| `fecha_emision` | string/date | Recomendado | Fecha `YYYY-MM-DD` |
| `payment` | object | Recomendado | Forma y medio de pago |
| `aiu` | object | Opcional | AIU cuando aplique |
| `withholdings` | array | Opcional | Retenciones configuradas |
| `currency` | object | Opcional | Moneda comercial/tasa cuando aplique |
| `observaciones` | string | Opcional | Texto libre, UI actual admite hasta 4000 caracteres |
| `attachment_tokens` | array | Opcional | Tokens de adjuntos previamente cargados |
| `draft_key` | string | **Muy recomendado** | Identificador estable de la operación; máximo operativo actual 80 caracteres |

### 7.3 Receptor

Ejemplo mínimo de integración:

```json
{
  "receptor": {
    "tipo_doc": "13",
    "numero_doc": "1000000000",
    "nombre": "CLIENTE DE PRUEBA",
    "email": "cliente@example.com",
    "direccion": "Bogotá D.C."
  }
}
```

| Campo | Tipo | Uso |
|---|---|---|
| `tipo_doc` | string | Código de tipo de identificación |
| `numero_doc` | string | Documento/NIT del adquirente |
| `nombre` | string | Nombre o razón social |
| `email` | string | Correo del receptor |
| `direccion` | string | Dirección informada |

No use un tipo de documento únicamente porque aparece en un ejemplo; valide el tipo fiscal real del adquirente.

### 7.4 Ítems FE

Forma de payload actualmente utilizada por el flujo FE:

```json
{
  "desc": "Servicio de prueba",
  "cant": 1,
  "unit": 100000,
  "iva": 19,
  "iva_incluido": true,
  "unit_input": 119000
}
```

| Campo | Tipo | Descripción |
|---|---|---|
| `desc` | string | Descripción comercial |
| `cant` | number | Cantidad |
| `unit` | number | Valor unitario base |
| `iva` | number | Tarifa tributaria usada por el flujo FE |
| `iva_incluido` | boolean | Señala si el valor de entrada incluye impuesto |
| `unit_input` | number | Valor unitario original/de entrada |
| `codigo_impuesto` / `tax_code` | string | Campo avanzado para identificar el tributo cuando el flujo lo requiera |

### 7.5 Regla crítica FE: 8% se trata como INC

En el flujo FE actual, la tarifa exactamente `8%` se clasifica como **INC - Impuesto Nacional al Consumo**, código DIAN `04`. Otros porcentajes positivos sin código explícito se tratan como IVA código `01`; 0% no genera impuesto en esta normalización.

```text
Base                  100,000
INC 8%                  8,000
Total                  108,000
```

Ejemplo:

```json
{
  "desc": "Producto sujeto a INC 8%",
  "cant": 1,
  "unit": 100000,
  "iva": 8,
  "iva_incluido": true,
  "unit_input": 108000
}
```

> [!WARNING]
> No replique esta regla de FE en DSE. En DSE la semántica de impuestos es distinta y se documenta en su propia sección.

### 7.6 Totales

```json
{
  "subtotal": 100000,
  "impuestos": 19000,
  "total": 119000
}
```

El integrador debe calcular y validar que los totales sean consistentes con las líneas enviadas. No envíe totales de 19% cuando las líneas están marcadas con 8%, ni mezcle bases con valores impuestos incluidos sin normalización previa.

---

## 8. Formas y métodos de pago FE

`payment.form`, `method_id` y `means_code` son conceptos diferentes.

### 8.1 Forma de pago

| `payment.form` | Identificador fiscal resultante | Uso |
|---|---:|---|
| `contado` | 1 | Pago de contado |
| `credito` | 2 | Venta a crédito |

Para crédito, informe `due_date` en `YYYY-MM-DD` cuando corresponda.

### 8.2 Descubrir métodos configurados

```http
GET /api/ext/v1/companies/{company_id}/fe/payment-methods
Scope: fe.read
```

**No hardcodee `method_id` entre empresas.** Es un ID interno de Quantum y pertenece a la configuración de cada compañía. La integración debe consultar el catálogo y construir un mapeo estable en el ERP/POS.

Ejemplo conceptual de registro:

```json
{
  "id": 19,
  "payment_form": "contado",
  "label": "Efectivo",
  "payment_means_code": "10",
  "payment_means_name": "Efectivo",
  "is_active": true,
  "is_default": true
}
```

Los IDs del ejemplo no son portables a otra empresa.

### 8.3 Códigos observados en una configuración operativa

| `means_code` | Medio |
|---|---|
| `10` | Efectivo |
| `42` | Transferencia / consignación |
| `48` | Tarjeta crédito |
| `49` | Tarjeta débito |

Estos códigos sirven como referencia; la fuente operativa para cada cliente es `GET /fe/payment-methods`.

### 8.4 Pago contado

```json
{
  "payment": {
    "form": "contado",
    "method_id": 19,
    "means_code": "10",
    "due_date": ""
  }
}
```

### 8.5 Pago crédito

```json
{
  "payment": {
    "form": "credito",
    "method_id": 123,
    "means_code": "42",
    "due_date": "2026-11-06"
  }
}
```

Use un `method_id` real devuelto para la empresa; `123` es únicamente ilustrativo.

---

## 9. AIU, retenciones, moneda, observaciones y adjuntos FE

### 9.1 AIU

La UI vigente envía la siguiente estructura:

```json
{
  "aiu": {
    "enabled": false,
    "contract_base": 100000,
    "administracion_percent": 0,
    "imprevistos_percent": 0,
    "utilidad_percent": 0,
    "tax_rate": 19,
    "iva_base_mode": "AIU_TOTAL"
  }
}
```

`iva_base_mode` puede representar la base total AIU o, cuando corresponda al flujo, la utilidad. Si su integración no usa AIU, envíe `enabled: false`.

### 9.2 Retenciones FE

La estructura de la UI vigente utiliza:

```json
{
  "withholdings": [
    {
      "type": "<tipo-configurado>",
      "base_mode": "<base-configurada>",
      "percent": 2.5
    }
  ]
}
```

Los tipos/base válidos dependen de la configuración FE de la empresa. No invente códigos de retención en una integración externa. Si no aplican, envíe `[]`.

### 9.3 Moneda comercial

El flujo FE contempla una estructura como:

```json
{
  "currency": {
    "commercial_currency": "COP",
    "applied_rate": null,
    "official_trm": null,
    "rate_date": "2026-10-07"
  }
}
```

La implementación actual maneja `COP` y `USD` en el flujo comercial. Si usa USD, conserve la tasa aplicada y fecha como parte de la trazabilidad del sistema origen.

### 9.4 Observaciones

```json
{
  "observaciones": "Servicio correspondiente al período octubre de 2026."
}
```

### 9.5 Adjuntos

```json
{
  "attachment_tokens": []
}
```

La API externa v1 documentada aquí **no expone un endpoint público para subir adjuntos**. Por tanto, un integrador server-to-server debería enviar `[]` salvo que Quantum haya proporcionado específicamente un mecanismo compatible que genere esos tokens. No intente enviar rutas locales ni archivos base64 dentro de `attachment_tokens`.

---

## 10. draft_key e idempotencia operativa FE

`draft_key` identifica el borrador/operación de negocio en el flujo FE y la capa de metadatos mantiene unicidad por empresa para esa llave.

Recomendación:

```json
{
  "draft_key": "venta-ERP-2026-00018451"
}
```

Buenas prácticas:

1. Genere una llave estable a partir del ID inmutable de la venta o un UUID.
2. Persístala antes de llamar a Quantum.
3. Ante timeout, conserve la misma llave y el mismo contexto comercial.
4. No genere otra llave solo para “probar otra vez”.
5. Persista `factura_id`, CUFE, estado y respuesta cuando estén disponibles.

> [!IMPORTANT]
> FE usa `draft_key` en el body. DSE usa `Idempotency-Key` en el header. No son el mismo contrato y no deben mezclarse.

---

## 11. Ejemplo FE completo - contado IVA 19%

```json
{
  "receptor": {
    "tipo_doc": "13",
    "numero_doc": "1000000000",
    "nombre": "CLIENTE DE PRUEBA",
    "email": "cliente@example.com",
    "direccion": "Bogotá D.C."
  },
  "items": [
    {
      "desc": "Servicio de integración",
      "cant": 1,
      "unit": 100000,
      "iva": 19,
      "iva_incluido": true,
      "unit_input": 119000
    }
  ],
  "subtotal": 100000,
  "impuestos": 19000,
  "total": 119000,
  "es_contingencia": false,
  "fecha_emision": "2026-10-07",
  "payment": {
    "form": "contado",
    "method_id": 19,
    "means_code": "10",
    "due_date": ""
  },
  "aiu": {
    "enabled": false,
    "contract_base": 100000,
    "administracion_percent": 0,
    "imprevistos_percent": 0,
    "utilidad_percent": 0,
    "tax_rate": 19,
    "iva_base_mode": "AIU_TOTAL"
  },
  "withholdings": [],
  "currency": {
    "commercial_currency": "COP",
    "applied_rate": null,
    "official_trm": null,
    "rate_date": "2026-10-07"
  },
  "observaciones": "Ejemplo de integración FE",
  "attachment_tokens": [],
  "draft_key": "venta-demo-000001"
}
```

cURL:

```bash
curl -X POST \
  "https://nomina.quantumpos.com.co/api/ext/v1/companies/COMPANY_ID/fe/facturas" \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d @fe-contado.json
```

### 11.1 Respuesta de creación FE

La ruta externa reutiliza directamente el flujo de creación/procesamiento FE. El consumidor debe interpretar el contrato por `HTTP status`, `ok` y campos retornados. El campo `factura_id` es el identificador de factura utilizado por la UI actual después de crear/procesar.

Ejemplo representativo:

```json
{
  "ok": true,
  "factura_id": 12345,
  "cufe": "<CUFE>",
  "estado": "aceptado",
  "enviado": true
}
```

El response puede contener datos adicionales de envío/contabilidad según el resultado. No haga un parser que falle por campos adicionales.

---

## 12. Listar y consultar FE

### 12.1 Listado

```http
GET /api/ext/v1/companies/{company_id}/fe/docs
Scope: fe.read
```

Parámetros de consulta soportados por el listado actual:

| Parámetro | Regla |
|---|---|
| `page` | Página, mínimo 1 |
| `per_page` | Tamaño de página; el servidor limita el rango operativo |
| `tipo` | `01` factura, `91` nota crédito |
| `estado` | `aprobado`, `rechazado`, `pendiente` |
| `q` | Búsqueda por ID/número/CUFE/tercero/documento |

El listado externo reutiliza el listado FE de producción; no incluye documentos de habilitación.

### 12.2 Detalle FE por ID

```http
GET /api/ext/v1/companies/{company_id}/fe/docs/{doc_id}
Scope: fe.read
```

Respuesta estructural:

```json
{
  "ok": true,
  "doc": {
    "id": 12345,
    "company_id": 11,
    "tipo_dian": "01",
    "prefijo": "FV",
    "consecutivo": 123,
    "estado_dian": "aceptado",
    "cufe": "<CUFE>",
    "es_habilitacion": false,
    "subtotal": "100000.00",
    "impuestos": "19000.00",
    "total": "119000.00",
    "moneda": "COP",
    "fecha_emision": "2026-10-07T10:30:00",
    "fecha_vencimiento": null,
    "xml_path": "<ruta-interna>",
    "track_id": null,
    "zip_key": "<clave-si-aplica>",
    "tercero": {
      "id": 1,
      "nombre": "CLIENTE DE PRUEBA",
      "tipo_documento": "13",
      "numero_documento": "1000000000",
      "email": "cliente@example.com"
    },
    "items": [
      {
        "id": 1,
        "descripcion": "Servicio de integración",
        "cantidad": "1.00",
        "valor_unitario": "100000.00",
        "descuento": "0.00",
        "base_imponible": "100000.00",
        "impuesto": "19000.00",
        "tipo_impuesto": "IVA",
        "codigo_impuesto": "01"
      }
    ]
  }
}
```

`xml_path` es metadato de implementación y **no debe tratarse como URL pública de descarga** ni persistirse como contrato permanente del consumidor.

---

## 13. Terceros FE

```http
GET /api/ext/v1/companies/{company_id}/fe/terceros
Scope: fe.read
```

Respuesta:

```json
{
  "ok": true,
  "terceros": [
    {
      "id": 1,
      "nombre": "CLIENTE DE PRUEBA",
      "tipo_documento": "13",
      "numero_documento": "1000000000",
      "email": "cliente@example.com",
      "direccion": "Bogotá D.C."
    }
  ]
}
```

---

## 14. GetAcquirer DIAN

### 14.1 Endpoint

```http
POST /api/ext/v1/companies/{company_id}/dian/get-acquirer
Scope: fe.read
```

Request recomendado:

```json
{
  "identification_type": "31",
  "identification_number": "900000000",
  "force_refresh": false
}
```

Se aceptan alias de compatibilidad para identificación (`tipo_doc` / `tipo_documento` y `numero_doc` / `numero_documento`). Para nuevas integraciones use los nombres en inglés mostrados arriba para mantener un contrato claro.

Si el servicio DIAN/flujo subyacente no produce respuesta satisfactoria, el endpoint puede responder `502`.

---

# PARTE II - DOCUMENTO SOPORTE ELECTRÓNICO (DSE)

## 15. Objetivo del endpoint DSE externo

La API DSE externa v1 está diseñada como un flujo **create + sign + send** en una sola operación lógica, con control de idempotencia obligatorio y una barrera de envío antes de la llamada de red a DIAN.

```text
ERP / sistema cliente
      |
      | POST + Idempotency-Key
      v
Reserva idempotente
      |
      v
Crear DSE + numeración servidor
      |
      v
Firmar XML
      |
      v
Estado sending (fence)
      |
      v
Transmitir DIAN
      |
      +--> aceptado / rechazado
      |
      +--> pendiente / transporte incierto -> NO reenviar ciegamente
```

---

## 16. Crear, firmar y enviar DSE

### 16.1 Endpoint y headers

```http
POST /api/ext/v1/companies/{company_id}/dse/documentos
Authorization: Bearer <token con dse.write>
Idempotency-Key: <llave-estable>
Accept: application/json
Content-Type: application/json
```

### 16.2 Idempotency-Key es obligatorio

La llave:

- Es obligatoria para creación DSE externa.
- Máximo 120 caracteres.
- Se evalúa junto con empresa y cliente API.
- La API calcula un hash canónico del payload.
- Misma llave + mismo payload: devuelve el documento/reserva existente, no crea otro.
- Misma llave + payload diferente: `409`.

Ejemplo recomendado:

```http
Idempotency-Key: ERP-CXP-2026-00009182
```

Aunque existen aliases de compatibilidad `request_key` y `draft_key` en el body, las nuevas integraciones deben usar el **header estándar `Idempotency-Key`**.

### 16.3 Campos de primer nivel DSE

| Campo | Tipo | Regla |
|---|---|---|
| `fecha_documento` | string | `YYYY-MM-DD`; por operación debe ser la fecha actual |
| `fecha` | string | Alias compatible de `fecha_documento` |
| `hora` | string | Opcional; se genera hora `-05:00` si se omite |
| `moneda` | string | Default `COP` |
| `proveedor` | object | Datos reales del proveedor/no obligado |
| `items` | array | **Obligatorio**, mínimo 1 |
| `forma_generacion` | string | `1` por operación, `2` acumulado semanal |
| `fecha_inicio_periodo` | string | Requerido en modo 2 |
| `fecha_fin_periodo` | string | Requerido en modo 2 |
| `forma_pago` | string | `1` contado, `2` crédito |
| `medio_pago_codigo` | string | Código numérico 1 a 3 dígitos; default `10` |
| `fecha_vencimiento` | string | Fecha de pago; en contado se fuerza a emisión |
| `fecha_vencimiento_contable` | string | Opcional; no puede ser anterior a emisión |
| `retenciones` | array | Retenciones DSE normalizadas |
| `withholdings` | array | Alias compatible de `retenciones` |
| `tipo_dian` | string | No configurable; si se envía debe ser `05` |

Campos internos cuyo nombre empiece por `_` son rechazados por la API externa.

---

## 17. Proveedor DSE

Ejemplo recomendado con información explícita:

```json
{
  "proveedor": {
    "tipo_documento": "31",
    "nit": "900000001",
    "dv": "1",
    "nombre": "PROVEEDOR DE PRUEBA SAS",
    "nombre_comercial": "PROVEEDOR DE PRUEBA",
    "regimen": "O-49",
    "dian_tax_scheme_code": "ZZ",
    "direccion": "Calle 1 # 2-3",
    "municipio": "Bogotá",
    "codigo_municipio": "11001",
    "departamento": "Cundinamarca",
    "codigo_departamento": "11",
    "pais": "CO",
    "email": "proveedor@example.com",
    "telefono": "3000000000",
    "codigo_postal": "111711"
  }
}
```

| Campo | Observación |
|---|---|
| `nit` | Identificación principal; `numero_documento` funciona como alias |
| `dv` | Dígito de verificación cuando aplique |
| `nombre` | Nombre/razón social real |
| `nombre_comercial` | Opcional; si falta se deriva de `nombre` |
| `tipo_documento` | Default interno actual `13`; envíe el tipo real |
| `regimen` | Default técnico actual `O-49`; envíe el dato real cuando corresponda |
| `dian_tax_scheme_code` | Default técnico `ZZ`; debe corresponder al proveedor |
| `direccion` | Dirección real recomendada |
| `municipio` / `codigo_municipio` | Nombre y código |
| `departamento` / `codigo_departamento` | Nombre y código |
| `pais` | Default `CO` |
| `email` | Correo del proveedor |
| `telefono` | Teléfono |
| `codigo_postal` | Código postal |

> [!IMPORTANT]
> El backend tiene algunos defaults de compatibilidad para evitar campos vacíos, pero una integración profesional debe enviar **datos fiscales reales**, no depender de valores dummy/default.

---

## 18. Ítems DSE

### 18.1 Estructura recomendada

```json
{
  "items": [
    {
      "codigo": "81112100",
      "descripcion": "Servicio de prueba",
      "cantidad": 1,
      "unidad_medida": "EA",
      "precio_unitario": 100000,
      "porcentaje_iva": 19
    }
  ]
}
```

### 18.2 Reglas exactas relevantes

| Campo | Regla |
|---|---|
| `cantidad` | Debe ser mayor a 0 |
| `unidad_medida` | Default `EA`; `NIU` se normaliza a `EA` |
| `precio_unitario` | No negativo |
| `base_imponible` / `base` | Si falta `precio_unitario`, puede derivarse precio a partir de base/cantidad |
| `codigo` | **UNSPSC numérico de exactamente 8 dígitos** |
| `descripcion` | Si falta, el backend tiene fallback, pero debe enviarse una descripción real |
| `porcentaje_iva` | **Tarifa porcentual** recomendada en DSE |
| `impuesto` | Valor monetario absoluto del impuesto |
| `iva` | Alias histórico tratado como **valor monetario**, no como porcentaje |
| `cost_center_id` | Metadato contable opcional; debe pertenecer a la empresa |
| `cost_center_code` / `centro_costo_codigo` | Alternativa para resolver centro de costo |

### 18.3 Diferencia crítica entre FE y DSE respecto a `iva`

En FE, el payload operativo usa `"iva": 19` como tarifa. En DSE, la lógica actual interpreta `iva` como **valor monetario de impuesto** cuando viene informado.

Por eso, para DSE se recomienda:

```json
{
  "precio_unitario": 100000,
  "porcentaje_iva": 19
}
```

No:

```json
{
  "precio_unitario": 100000,
  "iva": 19
}
```

El segundo ejemplo se interpretaría como **19 unidades monetarias** de impuesto, no como 19%.

### 18.4 Totales DSE se calculan en servidor

Para cada ítem:

```text
base = cantidad * precio_unitario
impuesto = base * porcentaje_iva / 100      (si se usa porcentaje)
```

Y para el documento:

```text
subtotal = suma de bases
impuestos = suma de impuestos
total = subtotal + impuestos
neto_pagar = total - total_retenciones
```

No dependa de enviar `subtotal`, `impuestos` o `total` como autoridad en DSE; el backend los calcula desde las líneas.

---

## 19. Forma de generación DSE

### 19.1 Por operación - `forma_generacion: "1"`

- Es el default.
- `fecha_documento` debe ser la fecha actual.
- El período inicial y final queda igual a la fecha del documento.

```json
{
  "fecha_documento": "2026-10-07",
  "forma_generacion": "1"
}
```

### 19.2 Acumulado semanal - `forma_generacion: "2"`

Requiere:

```json
{
  "fecha_documento": "2026-10-07",
  "forma_generacion": "2",
  "fecha_inicio_periodo": "2026-10-01",
  "fecha_fin_periodo": "2026-10-07"
}
```

Reglas:

- Inicio no puede ser posterior al fin.
- Máximo 7 días calendario.
- Fin del período no puede ser posterior a `fecha_documento`.
- `period_start_date` y `period_end_date` son aliases compatibles.

---

## 20. Forma y medio de pago DSE

### 20.1 Contado

```json
{
  "forma_pago": "1",
  "medio_pago_codigo": "10"
}
```

En contado, la fecha de vencimiento fiscal se iguala a la fecha del documento.

### 20.2 Crédito

```json
{
  "forma_pago": "2",
  "medio_pago_codigo": "42",
  "fecha_vencimiento": "2026-11-06"
}
```

La fecha de vencimiento no puede ser anterior a `fecha_documento`.

Aliases soportados:

```text
forma_pago        <-> payment_type
medio_pago_codigo <-> payment_means_code
fecha_vencimiento <-> payment_due_date
```

`medio_pago_codigo` debe ser un código numérico de 1 a 3 dígitos.

---

## 21. Retenciones DSE

### 21.1 Códigos canónicos soportados

| Código DIAN | Nombre canónico |
|---|---|
| `05` | ReteIVA |
| `06` | ReteRenta |
| `07` | ReteICA |

### 21.2 Ejemplo por porcentaje

```json
{
  "retenciones": [
    {
      "retention_key": "rete_renta",
      "dian_tax_code": "06",
      "tax_name": "ReteRenta",
      "base_mode": "subtotal",
      "percent": 2.5
    }
  ]
}
```

### 21.3 Campos y aliases

| Concepto | Campos aceptados |
|---|---|
| Llave | `retention_key`, `type`, `tipo`, `tax_name`, `nombre` |
| Código DIAN | `dian_tax_code`, `codigo_dian` |
| Nombre | `tax_name`, `nombre`, `label` |
| Modo base | `base_mode`, `modo_base` |
| Base explícita | `base_amount`, `base` |
| Porcentaje | `percent`, `porcentaje` |
| Valor | `amount`, `valor` |
| Activación | `enabled`, `activa` |
| Orden | `display_order` |

### 21.4 Modos de base

| Valor normalizado | Aliases |
|---|---|
| `subtotal` | `subtotal`, `base` |
| `impuestos` | `impuestos`, `iva` |
| `total` | `total`, `bruto` |

### 21.5 Validaciones

- `retenciones` debe ser lista.
- Elementos con `enabled:false` o `activa:false` se omiten.
- No se permiten llaves de retención duplicadas.
- Debe existir código DIAN y nombre.
- La base debe ser mayor a cero y no superar el total.
- El porcentaje no puede ser negativo.
- Si se envía `amount` y `percent`, el valor debe coincidir con `base x porcentaje` dentro de tolerancia monetaria de un centavo.
- Si se envía amount sin porcentaje, el porcentaje puede derivarse.
- La suma de retenciones no puede superar el total del documento.

---

## 22. Numeración, certificado y secretos DSE

La API externa no necesita ni debe recibir:

```text
software_id
software_pin
clave_tecnica
resolucion
prefijo asignado manualmente
consecutivo manual
ruta de certificado
password P12
credenciales DIAN
```

Quantum obtiene numeración, configuración, certificado y datos técnicos desde la configuración segura de la empresa. El consumidor envía **datos de negocio**, no secretos fiscales internos.

`tipo_dian` está fijado a `05` por el servicio externo.

---

## 23. Ejemplo DSE completo - por operación, contado

Header:

```http
Idempotency-Key: ERP-CXP-2026-00009182
```

Body:

```json
{
  "fecha_documento": "2026-10-07",
  "forma_generacion": "1",
  "forma_pago": "1",
  "medio_pago_codigo": "10",
  "moneda": "COP",
  "proveedor": {
    "tipo_documento": "31",
    "nit": "900000001",
    "dv": "1",
    "nombre": "PROVEEDOR DE PRUEBA SAS",
    "regimen": "O-49",
    "dian_tax_scheme_code": "ZZ",
    "direccion": "Calle 1 # 2-3",
    "municipio": "Bogotá",
    "codigo_municipio": "11001",
    "departamento": "Cundinamarca",
    "codigo_departamento": "11",
    "pais": "CO",
    "email": "proveedor@example.com"
  },
  "items": [
    {
      "codigo": "81112100",
      "descripcion": "Servicio de soporte tecnológico",
      "cantidad": 1,
      "unidad_medida": "EA",
      "precio_unitario": 100000,
      "porcentaje_iva": 19
    }
  ],
  "retenciones": []
}
```

cURL:

```bash
curl -X POST \
  "https://nomina.quantumpos.com.co/api/ext/v1/companies/COMPANY_ID/dse/documentos" \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Idempotency-Key: ERP-CXP-2026-00009182" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d @dse-contado.json
```

---

## 24. Respuestas DSE e idempotencia

### 24.1 Documento nuevo procesado

HTTP `201` cuando el flujo nuevo se procesa sin clasificación `rechazado`:

```json
{
  "ok": true,
  "pending": false,
  "idempotent": false,
  "documento": {
    "id": 7423,
    "company_id": 30,
    "prefijo": "DS",
    "consecutivo": 4238,
    "document_date": "2026-10-07",
    "moneda": "COP",
    "subtotal": "100000.00",
    "impuestos": "19000.00",
    "total": "119000.00",
    "total_retenciones": "0.00",
    "estado_dian": "aceptado",
    "cuds": "<CUDS>"
  },
  "dian": {
    "is_valid": true,
    "status_code": "00",
    "status_description": "<descripcion>",
    "status_message": "<mensaje>"
  }
}
```

### 24.2 Replay idempotente

Misma llave + mismo payload, si ya existe documento:

```json
{
  "ok": true,
  "idempotent": true,
  "pending": false,
  "documento": {
    "id": 7423,
    "estado_dian": "aceptado"
  }
}
```

HTTP usual `200` (o `422` si el documento existente está rechazado).

### 24.3 Reserva aún sin documento terminal

```json
{
  "ok": true,
  "idempotent": true,
  "pending": true,
  "message": "La solicitud ya está reservada. No se creará ni enviará un segundo DSE.",
  "request_status": "creating"
}
```

HTTP `202`.

### 24.4 Misma llave, payload diferente

HTTP `409`:

```json
{
  "ok": false,
  "error": "Idempotency-Key ya fue usado con un payload diferente."
}
```

### 24.5 Transporte/DIAN incierto

HTTP `502` puede representar un **estado incierto, no un permiso para duplicar**:

```json
{
  "ok": false,
  "pending": true,
  "idempotent": false,
  "error": "La transmisión no confirmó un estado terminal. El DSE quedó reservado y no se reenviará automáticamente.",
  "documento": {
    "id": 7423,
    "estado_dian": "en_proceso_http"
  }
}
```

Acción del cliente:

1. Persistir el `doc_id`.
2. No crear otra llave.
3. Consultar `GET /dse/documentos/{doc_id}`.
4. Si requiere reintentar el POST por recuperación de conexión, usar **exactamente la misma `Idempotency-Key` y el mismo payload**.
5. Escalar a soporte si el estado permanece incierto según el SLA operativo acordado.

---

## 25. Consultar DSE por ID

```http
GET /api/ext/v1/companies/{company_id}/dse/documentos/{doc_id}
Scope: dse.read
```

Respuesta:

```json
{
  "ok": true,
  "pending": false,
  "documento": {
    "id": 7423,
    "company_id": 30,
    "prefijo": "DS",
    "consecutivo": 4238,
    "document_date": "2026-10-07",
    "accounting_due_date": null,
    "moneda": "COP",
    "subtotal": "100000.00",
    "impuestos": "19000.00",
    "total": "119000.00",
    "total_retenciones": "0.00",
    "es_habilitacion": 0,
    "xml_path": "<ruta-interna>",
    "estado_dian": "aceptado",
    "cuds": "<CUDS>",
    "zip_key": "<ZIP_KEY>",
    "last_status_error": null,
    "accounting_status": "posted",
    "third_party_id": 123,
    "cost_center_id": 1,
    "created_at": "2026-10-07 21:00:00",
    "updated_at": "2026-10-07 21:00:10"
  }
}
```

`xml_path` y `last_status_error` son metadatos de implementación/diagnóstico. No los use como URL pública ni como campos indispensables para que el ERP funcione.

---

## 26. Estados DSE que el integrador debe entender

| Estado | Significado operativo |
|---|---|
| `firmado` | XML firmado; etapa anterior a transmisión |
| `sending` | Quantum reservó el documento para transmisión; no duplicar |
| `enviado` | Enviado, resultado aún no terminal |
| `en_proceso` | DIAN/proceso aún pendiente |
| `en_proceso_http` | Resultado de transporte/correlación pendiente o incierto |
| `send_unknown` | Resultado de envío no concluyente |
| `aceptado` | Resultado terminal exitoso |
| `rechazado` | Resultado terminal rechazado |

El booleano `pending` resume los estados no terminales de transmisión en la API externa.

---

## 27. Ejemplo DSE acumulado semanal + crédito + retención

```json
{
  "fecha_documento": "2026-10-07",
  "forma_generacion": "2",
  "fecha_inicio_periodo": "2026-10-01",
  "fecha_fin_periodo": "2026-10-07",
  "forma_pago": "2",
  "medio_pago_codigo": "42",
  "fecha_vencimiento": "2026-11-06",
  "fecha_vencimiento_contable": "2026-11-06",
  "moneda": "COP",
  "proveedor": {
    "tipo_documento": "31",
    "nit": "900000001",
    "dv": "1",
    "nombre": "PROVEEDOR DE PRUEBA SAS",
    "regimen": "O-49",
    "dian_tax_scheme_code": "ZZ",
    "direccion": "Bogotá",
    "municipio": "Bogotá",
    "codigo_municipio": "11001",
    "departamento": "Cundinamarca",
    "codigo_departamento": "11",
    "pais": "CO"
  },
  "items": [
    {
      "codigo": "81112100",
      "descripcion": "Servicios semana 1",
      "cantidad": 1,
      "unidad_medida": "EA",
      "precio_unitario": 1000000,
      "porcentaje_iva": 0
    }
  ],
  "retenciones": [
    {
      "retention_key": "rete_renta",
      "dian_tax_code": "06",
      "tax_name": "ReteRenta",
      "base_mode": "subtotal",
      "percent": 2.5
    }
  ]
}
```

---

# PARTE III - DISEÑO DE LA INTEGRACIÓN

## 28. Patrón recomendado: Outbox/Queue

Para POS/ERP con transacciones críticas, no conviene que el cierre de una venta espere indefinidamente a servicios externos. El patrón recomendado es:

```text
Transacción de negocio local
        |
        +--> guardar venta/documento local
        +--> guardar trabajo OUTBOX con clave estable
                     |
                     v
               Worker asíncrono
                     |
                     +--> Quantum API
                     |
                     +--> persistir HTTP + respuesta
                     +--> reprogramar consultas/reintentos seguros
```

Esto desacopla el tiempo de respuesta del POS de la transmisión fiscal y permite trazabilidad, reintentos y recuperación controlada.

### 28.1 Registro mínimo por operación

| Campo local | Por qué guardarlo |
|---|---|
| `source_id` | ID de venta/compra en ERP/POS |
| `company_id` | Evita mezcla multiempresa |
| `document_type` | FE o DSE |
| `draft_key` / `idempotency_key` | Evita duplicados |
| `payload_hash` | Detecta cambios entre intentos |
| `attempt_count` | Auditoría de reintentos |
| `last_http_status` | Diagnóstico |
| `last_response` | Evidencia técnica |
| `quantum_document_id` | Consulta posterior |
| `cufe` / `cuds` | Identificador fiscal |
| `estado_dian` | Estado sincronizado |
| `created_at` / `updated_at` | Trazabilidad temporal |

---

## 29. Algoritmo de reintento recomendado

### 29.1 FE

```text
1. Generar/persistir draft_key antes del POST.
2. POST FE.
3. Si 2xx -> guardar factura_id/CUFE/estado.
4. Si 4xx de validación -> no reintentar automáticamente; corregir datos.
5. Si timeout/5xx -> conservar draft_key; no crear una venta nueva.
6. Aplicar revisión/consulta operativa antes de un reenvío destructivo.
```

### 29.2 DSE

```text
1. Generar/persistir Idempotency-Key antes del POST.
2. POST con la misma llave y payload.
3. 201 -> guardar documento.id y estado.
4. 200/202 con idempotent:true -> usar el documento/reserva existente.
5. 409 -> error de programación: se reutilizó una llave con otro payload.
6. 422 -> documento rechazado; no crear un duplicado para ocultar el rechazo.
7. 502 + pending:true -> estado incierto; consultar GET. NO cambiar llave.
8. Timeout -> repetir únicamente con misma llave y mismo payload o consultar por ID si ya se obtuvo.
```

Backoff sugerido del lado cliente para consultas no terminales: 5s, 15s, 30s, 60s, luego intervalos mayores según SLA. Evite loops de alta frecuencia.

---

## 30. Errores y diagnóstico

### 30.1 Matriz de tratamiento

| Clase | Ejemplo | Reintento automático |
|---|---|---|
| Autenticación | 401 | No; corregir token |
| Autorización | 403 | No; corregir empresa/scopes |
| Validación | 400 | No; corregir payload |
| No encontrado | 404 | No, salvo carrera de sincronización conocida |
| Idempotencia | 409 | No; investigar llave/payload |
| Rechazo fiscal DSE | 422 | No duplicar; revisar rechazo |
| Pendiente transporte | 502 + `pending:true` | Consultar; no crear nueva llave |
| Error interno | 500 | Reintento cauteloso manteniendo clave de idempotencia/trazabilidad |

### 30.2 Qué adjuntar a soporte

Sin exponer secretos:

- Fecha/hora y zona horaria.
- `company_id`.
- Endpoint y método.
- HTTP status.
- `draft_key` FE o `Idempotency-Key` DSE.
- `factura_id` / `documento.id` si existe.
- CUFE/CUDS si existe.
- `estado_dian`.
- Response body sanitizado.
- Hash del payload, no necesariamente el payload completo si contiene información sensible.

Nunca adjunte el Bearer Token.

---

## 31. Ejemplos de código

### 31.1 cURL FE

```bash
curl -X POST "$QUANTUM_API_BASE/companies/$COMPANY_ID/fe/facturas" \
  -H "Authorization: Bearer $QUANTUM_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d @fe-contado.json
```

### 31.2 cURL DSE

```bash
curl -X POST "$QUANTUM_API_BASE/companies/$COMPANY_ID/dse/documentos" \
  -H "Authorization: Bearer $QUANTUM_API_TOKEN" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d @dse-contado.json
```

### 31.3 JavaScript / Node.js

```javascript
const response = await fetch(
  `${process.env.QUANTUM_API_BASE}/companies/${companyId}/dse/documentos`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.QUANTUM_API_TOKEN}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify(payload),
  }
);

const body = await response.json();
if (response.status === 502 && body.pending) {
  // No crear otro DSE. Persistir ID/llave y consultar estado.
}
```

### 31.4 Python

```python
import os
import requests

url = f"{os.environ['QUANTUM_API_BASE']}/companies/{company_id}/dse/documentos"
headers = {
    "Authorization": f"Bearer {os.environ['QUANTUM_API_TOKEN']}",
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": idempotency_key,
}
response = requests.post(url, json=payload, headers=headers, timeout=60)
body = response.json()

if response.status_code == 502 and body.get("pending"):
    # Conservar la misma llave y consultar el documento; no duplicar.
    pass
```

### 31.5 PHP

```php
$ch = curl_init($baseUrl . "/companies/" . $companyId . "/dse/documentos");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("QUANTUM_API_TOKEN"),
        "Accept: application/json",
        "Content-Type: application/json",
        "Idempotency-Key: " . $idempotencyKey,
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
    CURLOPT_TIMEOUT => 60,
]);
$result = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
```

---

# PARTE IV - POSTMAN, OPENAPI Y WEB

## 32. Cómo usar la colección Postman incluida

El paquete incluye:

```text
postman/Quantum_Nube_FE_DSE_v1.postman_collection.json
postman/Quantum_Nube_FE_DSE_v1.postman_environment.json
```

Procedimiento:

1. Importar ambos archivos en Postman.
2. Seleccionar el environment.
3. Definir `company_id`.
4. Definir `api_token` localmente; no guardar el environment con el secreto en Git.
5. Ejecutar primero `FE > Listar métodos de pago`.
6. Guardar un `fe_payment_method_id` y `fe_payment_means_code` válidos.
7. Ejecutar las consultas de lectura.
8. Solo cuando se quiera emitir realmente, ejecutar los POST FE/DSE.
9. Para DSE, mantener la misma `dse_idempotency_key` si se repite la prueba de la misma operación.

La colección calcula por defecto la fecha de Colombia y genera claves solo cuando las variables están vacías, para que un retry conserve la misma llave.

---

## 33. OpenAPI 3.1 incluido

Archivo:

```text
openapi/quantum-api-v1.yaml
```

Debe convertirse en la fuente contractual del portal web. Beneficios:

- Referencia de endpoints navegable.
- Generación de clientes/SDKs si se decide más adelante.
- Validación automatizada del contrato.
- Importación directa en Postman/Insomnia/Bruno y herramientas de pruebas.
- Renderizado con Scalar, Redoc o Swagger UI.
- Control de versiones en Git.

### 33.1 Política recomendada de actualización

Cada cambio de API debe modificar en la misma entrega:

```text
1. Código backend
2. OpenAPI
3. Ejemplos/Postman
4. Changelog
5. Pruebas de contrato
```

No se debe permitir que la página web describa un payload distinto al que realmente acepta producción.

---

## 34. Integración dentro del sitio web de Quantum

### 34.1 Arquitectura propuesta

```text
Web Quantum
|
+-- /developers/api                  -> Landing / Quickstart
+-- /developers/api/fe               -> Guía FE
+-- /developers/api/dse              -> Guía DSE
+-- /developers/api/reference        -> OpenAPI renderizado
+-- /developers/api/errors           -> Errores / retries
+-- /developers/api/changelog        -> Historial
+-- /developers/downloads            -> OpenAPI + Postman
```

### 34.2 Componentes visuales

- Header del ecosistema Quantum.
- Sidebar por secciones.
- Buscador local.
- Breadcrumbs.
- Títulos con anclas copiables.
- Tablas responsivas.
- Bloques de código con botón copiar.
- Tabs por lenguaje.
- Callouts `Importante / Advertencia / Nota`.
- Etiquetas `GET`, `POST`, `fe.read`, `dse.write`.
- Sección “Respuesta” junto a cada request.
- Links descargables a OpenAPI y Postman.
- Changelog visible con versión y fecha.

### 34.3 Fuente de contenido

No duplique manualmente el contrato de endpoints en varias páginas. Use:

- Markdown para guías conceptuales.
- OpenAPI para referencia técnica.
- JSON de navegación para construir sidebar.
- Postman como descarga.

La carpeta incluida `docs/` ya está fragmentada de esa forma para que el proyecto web pueda copiarla o convertirla a MDX.

---

## 35. Seguridad específica del portal web

Si la documentación es pública:

```text
OpenAPI público         -> sí, sin secretos
Ejemplos JSON/cURL      -> sí, usando API_TOKEN ficticio
Postman collection      -> sí, sin token
Postman environment     -> sí, api_token vacío
Try it con token real   -> NO en página pública
```

Si se desea “Try it” real, cree un portal autenticado y un mecanismo seguro que no exponga credenciales persistentes al frontend.

---

## 36. Checklist de salida a producción para un integrador

### Credenciales y aislamiento

- [ ] `company_id` confirmado.
- [ ] Cliente API activo.
- [ ] Token guardado como secreto.
- [ ] Scopes mínimos asignados.
- [ ] Prueba de token contra endpoint de lectura.
- [ ] Prueba de empresa incorrecta produce 403.

### FE

- [ ] Mapeo de receptor validado.
- [ ] Mapeo de items e impuestos validado.
- [ ] Caso 0%, 19% y, si aplica, INC 8% probado.
- [ ] Métodos de pago obtenidos desde catálogo de la empresa.
- [ ] `draft_key` persistido antes de enviar.
- [ ] Timeout probado sin generar duplicidad.
- [ ] GET de detalle integrado.

### DSE

- [ ] Scope `dse.read` y/o `dse.write` confirmado.
- [ ] `Idempotency-Key` persistida antes de enviar.
- [ ] UNSPSC de 8 dígitos validado.
- [ ] `porcentaje_iva` usado correctamente.
- [ ] Proveedor real validado.
- [ ] Forma generación 1/2 probada según negocio.
- [ ] Retenciones probadas si aplican.
- [ ] Caso 409 probado.
- [ ] Caso retry misma llave probado.
- [ ] Caso `pending:true` tratado sin duplicar.
- [ ] GET DSE integrado.

### Observabilidad

- [ ] HTTP status persistido.
- [ ] Response body persistido/sanitizado.
- [ ] ID Quantum persistido.
- [ ] CUFE/CUDS persistido.
- [ ] Métricas de errores/reintentos.
- [ ] Alerta por pendientes prolongados.

---

## 37. Matriz mínima de QA / pruebas de contrato

| Caso | FE | DSE | Resultado esperado |
|---|---:|---:|---|
| Sin token | Sí | Sí | 401 |
| Token empresa distinta | Sí | Sí | 403 |
| Scope insuficiente | Sí | Sí | 403 |
| Payload mal formado | Sí | Sí | 400 |
| Documento inexistente | Sí | Sí | 404 |
| Efectivo/contado | Sí | Sí | Procesamiento válido |
| Crédito | Sí | Sí | Vencimiento válido |
| Impuesto 0% | Sí | Sí | Cálculo consistente |
| IVA 19% | Sí | Sí | Cálculo consistente |
| INC 8% | Sí | No aplicar regla FE | FE código 04 |
| UNSPSC inválido | N/A | Sí | 400 |
| DSE sin Idempotency-Key | N/A | Sí | 400 |
| DSE replay misma llave/payload | N/A | Sí | idempotent true / sin duplicado |
| DSE misma llave/payload distinto | N/A | Sí | 409 |
| DSE rechazo DIAN | N/A | Sí | 422 |
| DSE transporte incierto | N/A | Sí | 502 + pending true / sin duplicar |

---

## 38. Recomendaciones de evolución de la API

Para llevar el producto a un estándar de developer platform más completo, priorizar:

1. Añadir un `request_id`/correlation ID oficial en headers y respuestas.
2. Publicar endpoints externos de descarga XML/PDF mediante URLs autorizadas, en vez de exponer `xml_path` interno.
3. Publicar endpoint externo de adjuntos FE si el caso de uso lo exige.
4. Normalizar envelopes y nombres de estado FE/DSE.
5. Incorporar rate-limit explícito y headers de cuota si se requiere.
6. Publicar webhooks firmados para `aceptado/rechazado` y reducir polling.
7. Agregar sandbox/habilitación separado del endpoint productivo si se quiere onboarding autónomo.
8. Generar SDKs desde OpenAPI cuando el contrato se estabilice.
9. Ejecutar pruebas de contrato OpenAPI en CI.
10. Mantener changelog por versión y política de deprecación.

Estas son mejoras de plataforma; no cambian el contrato productivo documentado actualmente.

---

## 39. Changelog de documentación

### v1.0.0 - 2026-10-07

- Consolidación de API externa FE y DSE.
- Documentación de autenticación Bearer y scopes `fe.*` / `dse.*`.
- Documentación de aislamiento por empresa.
- FE: catálogo de pagos, creación, listado, detalle, terceros y GetAcquirer.
- FE: regla 8% -> INC código 04.
- DSE: create + sign + send externo.
- DSE: `Idempotency-Key` obligatorio, replay seguro y conflicto 409.
- DSE: estados pending y tratamiento de 502 incierto.
- DSE: proveedor, UNSPSC, impuestos, acumulado semanal y retenciones.
- Paquete OpenAPI + Postman + guías web.

---

## 40. Resumen ejecutivo para el equipo web

La implementación profesional recomendada para la página de Quantum es:

```text
OpenAPI 3.1                 = contrato técnico principal
Markdown/MDX                = guías humanas y tutoriales
Scalar/Redoc/Swagger UI     = referencia interactiva generada
Postman                     = colección descargable para pruebas
Changelog                   = control de versión visible
Backend/BFF                 = cualquier prueba autenticada real desde la web
```

Copie el contenido de este paquete al proyecto web, publique las guías Markdown dentro de `/developers/api`, renderice `openapi/quantum-api-v1.yaml` en la página de referencia y permita descargar los archivos Postman. No exponga credenciales reales en el frontend.
