# dte-sv — Referencia completa de integración (para humanos y IA)

API REST para emitir Documentos Tributarios Electrónicos (DTE) firmados ante el
Ministerio de Hacienda (MH) de El Salvador. Normativa de Cumplimiento DTE v2.0.

- Base URL producción: https://dte-sv.tequio.work
- OpenAPI 3.1 machine-readable: https://dte-sv.tequio.work/openapi.json
- Referencia interactiva (Scalar): https://dte-sv.tequio.work/developers/reference

================================================================================
## 1. Autenticación
================================================================================
Header único en cada request:

    Authorization: Bearer <api_key>     # la key del Consumer, formato tqo_...

La API key se entrega UNA sola vez al registrarte (vive como sha256 en BD).
Si falla → 401. No se valida host/IP; la única defensa es la API key (rotala si se filtra).

================================================================================
## 2. Endpoints
================================================================================
### Emisión por tipo (síncrono)
  POST /api/dte/factura               Factura (01) — consumidor final. receptor opcional.
  POST /api/dte/ccf                   CCF (03) — B2B, receptor obligatorio (NIT/NRC/dirección).
  POST /api/dte/nota-credito          Nota de Crédito (05) — requiere documentoRelacionado.
  POST /api/dte/nota-debito           Nota de Débito (06) — mismo shape que NC.
  POST /api/dte/factura-exportacion   Factura de Exportación (11) — receptor extranjero, IVA 0%.

### Emisión con IA (lenguaje natural)
  POST /api/dte/ai                    Mandás la venta en texto libre; la IA elige el tipo,
                                      arma el payload, lo valida y (autoEmit, default true)
                                      lo emite. Ver §3-bis.

### Eventos sobre DTEs emitidos
  GET  /api/dte/{codigoGeneracion}              Consultar estado + assets.
  POST /api/dte/{codigoGeneracion}/invalidar    Invalidar (tipos 01/03/11).
  POST /api/dte/{codigoGeneracion}/retorno      Reportar devolución B2C (01/11/14).

### Lote (asíncrono)
  POST /api/dte/lote                  Hasta 100 DTEs → devuelve codigoLote.
  GET  /api/dte/lote/{codigoLote}     Consultar resultado del lote (idempotente).

================================================================================
## 3. Request — campos comunes a toda emisión
================================================================================
    {
      "ambiente": "00" | "01",         // OBLIGATORIO. 00=pruebas, 01=producción. Sin default: envialo siempre, aun con un solo ambiente. No configurado → 422 ambiente-not-configured.
      "issuerId": "<uuid>",            // opcional. Default: primero permitido al Consumer.
      "codActividad": "62020",         // opcional. Actividad (CAT-019) del EMISOR; debe ser una registrada en el Issuer. Default: la principal.
      "items": [                        // mínimo 1
        {
          "descripcion": "string",
          "cantidad": 1,                // default 1
          "precioUni": 9.99,            // Factura: INCLUYE IVA. CCF/NC/ND: neto (se agrega 13%).
          "uniMedida": 59,              // CAT-014, default 59 (unidad)
          "tipoItem": 2,                // 1=bien, 2=servicio, 3=ambos, 4=otros
          "codigo": "SKU-123"           // opcional, tu SKU
        }
      ],
      "condicionOperacion": 1,          // 1=contado, 2=crédito, 3=otro
      "formaPagoCodigo": "01",          // CAT-017: 01=billetes, 02=tarjeta déb, 03=tarjeta créd, 05=transferencia
      "notify": ["email","whatsapp"],   // opcional, ver §5
      "notifyOnContingencyResolution": true,
      "fechaEmision": "2026-06-11T19:02" // opcional, ver §3.1 (default: ahora)
    }

### 3.1 fechaEmision — fecha-hora de generación (opcional)
    Por defecto el DTE se fecha en el momento de la emisión. Mandá fechaEmision
    (hora El Salvador, UTC-6) solo si querés otra. Formatos aceptados:
      "YYYY-MM-DD"                 → ese día 00:00:00 SV
      "YYYY-MM-DDTHH:mm[:ss]"      → naive = hora SV
      ISO 8601 con offset/Z        → instante absoluto
    Reglas del MH que validamos ANTES de transmitir (si falla → 422
    fecha-emision-invalida con code + el porqué, sin ir al MH):
      • Retro-fechar: LIBRE. Cualquier fecha que anteceda a la transmisión, hasta
        el 2000-01-01 (Normativa, Identificación campo 10.2). La fecha de
        PROCESAMIENTO del MH es automática e informativa — no afecta la validez.
      • Post-fechar: hasta 5 días en el futuro, PERO sin cruzar al siguiente
        período tributario (mensual). Ej.: transmitís 27-jun → podés fechar
        28/29/30-jun, no 01-jul (campo 10.4).
    En POST /api/dte/lote el campo va POR ITEM (clave para backfills: cada DTE a
    su fecha real). Distinto de documentoRelacionado.fechaEmision (la fecha del
    DTE relacionado en NC/ND), no la de este documento.

### Factura (01) — receptor opcional
    "receptor": null    // consumidor final (ventas ≤ $25,000)
    // o, identificado:
    "receptor": { "tipoDocumento":"36","numDocumento":"...","nombre":"...","correo":"..." }
    // tipoDocumento (CAT-022): 13=DUI, 36=NIT, 37=Otro, 02=Carnet, 03=Pasaporte

### CCF (03) — receptor OBLIGATORIO
    "receptor": {
      "nit":"05282111241019","nrc":"3523746","nombre":"EMPRESA S.A. DE C.V.",
      "codActividad":"62020","descActividad":"Consultorías",
      "direccion":{"departamento":"05","municipio":"21","distrito":"14","complemento":"..."},
      "correo":"facturacion@empresa.com"
    }

### Notas de Crédito/Débito (05/06)
    "receptor": { ...igual que CCF, debe matchear el documento original... },
    "documentoRelacionado": {
      "tipoDocumento":"03",            // 03=CCF, 07=Retención, 11=FEX
      "tipoGeneracion":2,              // 2=electrónico
      "numeroDocumento":"<codGen del original, UUID MAYÚSCULAS>",
      "fechaEmision":"2026-05-01"      // YYYY-MM-DD del original
    }

### Factura de Exportación (11)
    "receptor": {
      "nombre":"Foreign Buyer LLC","tipoDocumento":"36","numDocumento":"98-7654321",
      "codPais":"US",                  // CAT-020: ISO 3166-1 alpha-2 (US, CR, ES, GT)
      "complemento":"123 Main St, Miami","tipoPersona":2,
      "descActividad":"Importadora"    // REQUIRED, 5-150 chars
    },
    "tipoMoneda":"USD","codIncoterms":"01","flete":0,"seguro":0,
    "observaciones":"Contrato 2026-014, fase 2 de 3"   // opcional ≤3000 chars; queda en el DTE oficial (JSON/MH)
    // ⚠ codPais DEBE ser alpha-2 (el catálogo viejo de 4 dígitos da error 096).
    // ⚠ tributos ["C3"] (IVA export 0%) se inyecta automáticamente — no lo mandes.

================================================================================
## 3-bis. Emisión con IA — POST /api/dte/ai
================================================================================
Mandás la operación en LENGUAJE NATURAL y el flujo de IA (Generador → validación
determinística contra el esquema oficial del MH → Revisor) la convierte en DTE.

### Request
    {
      "input": "Vendí una lámpara en $100 pero le di $15 de descuento, en efectivo.",
      "ambiente": "00" | "01",     // OBLIGATORIO, igual que en emisión manual
      "issuerId": "<uuid>",        // opcional, default: primero del ACL
      "autoEmit": true,            // default true — ver abajo
      "codActividad": "62020",     // opcional, actividad del EMISOR
      "notify": ["email"]          // opcional, si el DTE llega a emitirse
    }

Incluí en el texto TODO lo que tengas (NIT/NRC, giro, dirección, montos, forma
de pago): la IA NUNCA inventa un dato que no diste — si falta algo, responde
accion CONFIRMAR en vez de emitir.

El texto debe describir una operación real: si tras descartar emojis no llega a
4 caracteres alfanuméricos (vacío, solo emojis, solo signos), se rechaza con 422
invalid-payload antes de llegar a la IA y sin cobrar la petición. Límite superior:
8500 bytes UTF-8.

### Response (la accion manda)
    {
      "id": "<id de la petición, auditoría>",
      "accion": "EMITIR" | "CONFIRMAR" | "NO_EMITIR",
      "tipoDte": "01" | "03" | ... | "NONE",
      "resumenHumano": "Factura a consumidor final: 1 lámpara, $85.00, efectivo.",
      "observaciones": ["supuestos que hizo la IA sobre ESTE DTE"],
      "consejos": ["acciones a futuro para otras operaciones"],
      "datoQueFalta": ["RECEPTOR_NIT", ...],   // solo con CONFIRMAR
      "fechaEmision": null,        // o "YYYY-MM-DDTHH:mm:ss" SV si la detectó del mensaje (ver abajo)
      "payload": { ... },          // input del builder, listo para POST /api/dte/<tipo>
      "autoEmit": true,
      "emitido": true|false,       // true = se transmitió a MH (mirá dte.estado)
      "dte": { ...mismo body que la emisión manual (§4)... } | null,
      "ruta": "emitir" | "confirmar-api" | ...,
      "ms": 3200
    }

- autoEmit true (default) + accion EMITIR → se emite en la misma llamada;
  HTTP 200 solo si MH dijo PROCESADO (RECHAZADO/PENDIENTE_CONTINGENCIA → 422
  con el body completo, igual que la emisión manual).
- autoEmit false → el flujo corre COMPLETO (incluido el Revisor) pero NUNCA
  transmite. Usá "payload" para emitir por el endpoint manual del tipo, o
  repetí la llamada con autoEmit true. Siempre HTTP 200.
- accion CONFIRMAR/NO_EMITIR → nunca se emite; HTTP 200.
- Fecha de generación: si el mensaje menciona una fecha ("ayer", "el 11 de junio
  a las 7pm", "el martes pasado", "hace 3 días") la resuelve un parser
  determinístico (no el LLM; incluye días de la semana) a hora El Salvador y vuelve
  en "fechaEmision" + una observación. Se valida igual que en los endpoints
  manuales (retro libre; futuro ≤5d sin cruzar el período). Sin mención →
  "fechaEmision": null = momento de emisión. Ver §3.1.

### Precio y límites
- $0.02 por PETICIÓN, liquidado a fin de mes con el uso del plan. Se cobra por
  petición (no por resultado): CONFIRMAR, flujo a medias o MH caído cobran igual.
  Excepciones que NO cobran: 503 ai-metering-unavailable (no se pudo registrar
  el uso — reintento gratis) y cualquier 4xx previo al flujo (auth/validación).
  Un 502 ai-unavailable SÍ se cobró (el flujo ya había sido aceptado).
- El DTE emitido cuenta ADEMÁS como un DTE normal del plan (PROCESADO o
  contingencia, y otra vez al resolverse la contingencia — igual que manual).
- Disponible sin opt-in en cualquier issuer con plan/suscripción activa (402 si no).
- Rate limit: bucket PROPIO, separado del manual, mismo límite por minuto del
  Consumer (default 60/min). El DTE que la IA emite no consume el bucket manual.

================================================================================
## 4. Response
================================================================================
### PROCESADO (HTTP 200)
    {
      "dteId":"019e...","codigoGeneracion":"C6D61720-...","numeroControl":"DTE-01-M001P001-000000000000186",
      "estado":"PROCESADO","selloRecibido":"20262F05...","fhProcesamiento":"26/05/2026 11:56:38",
      "assets":{"portalUrl":"...","pdfUrl":"<firmada 30d>","jwsUrl":"...","jsonUrl":"..."},
      "notifications":{"email":{"status":"sent"}}
    }

### PENDIENTE_CONTINGENCIA (HTTP 422, estado en el body)
MH estaba caído. El DTE queda firmado y se retransmite solo (cron, ventana legal 72h).
El PDF lleva banda roja "REPRESENTACIÓN PROVISIONAL". Cuando se sella, llega un webhook.

### RECHAZADO (HTTP 422, estado en el body)
MH rechazó el JSON. Mirá mhResponse. NO reintentes con el mismo codigoGeneracion
(error 004 "ya existe") — corregí y hacé POST de nuevo (se genera codGen nuevo).

### Errores HTTP
  400 no-issuer-resolved   · 401 missing/invalid-api-key, consumer-inactive
  402 (sin suscripción activa de la org)   · 403 issuer-not-permitted
  422 invalid-payload (mirá issues[])      · 429 rate-limit-exceeded (header Retry-After)
  422 dte-schema-invalid (el DTE construido no cumple el schema oficial MH;
      se rechaza local SIN firmar ni enviar — friendlyIssues[] trae el campo,
      el porqué y el valor en español; issues[] trae los paths crudos)
  502 pipeline-failed

================================================================================
## 5. Notificaciones (notify)
================================================================================
- notify ausente/[] → solo emite; vos notificás con las URLs de assets.
- ["email"] → email con PDF + Archivo DTE firmado adjuntos (si el Issuer habilita email y el receptor tiene correo).
- ["whatsapp"] → template con botón al portal (sin PDF; el receptor descarga del portal).
- Cada canal devuelve {status: sent|not_allowed|skipped|failed}. Un fallo NO revierte la emisión.
- receptor.correo vacío/inválido o receptor.telefono no reconocido → ese canal = skipped (NO bloquea la emisión).
- Teléfono: 8 dígitos = El Salvador (con o sin separadores); para otro país usá + y código (+504…), o el codPais del receptor (exportación) define el default. Se interpreta a E.164 para WhatsApp.

================================================================================
## 6. Webhook (contingencia → resuelta)
================================================================================
Si registraste webhookUrl, recibís POST cuando un DTE pasa de PENDIENTE_CONTINGENCIA
a definitivo:
    { "event":"contingencia.processed"|"contingencia.rejected", "timestamp":"...",
      "data":{ "codigoGeneracion":"...","estado":"PROCESADO","selloRecibido":"..." } }
Best-effort (timeout 10s, sin reintento). Sé idempotente. Sin webhook → poll GET /api/dte/{codGen}.
El webhookUrl DEBE ser https:// y apuntar a un host PÚBLICO (no localhost ni IPs privadas);
se valida al registrarlo/editarlo y de nuevo justo antes de cada envío (anti-SSRF).

================================================================================
## 7. Ambientes, rate limits, catálogos
================================================================================
- Ambiente 00=pruebas (apitest.dtes.mh.gob.sv, sello "NO TIENE VALIDEZ"), 01=producción.
- Rate limit default 60 req/min por Consumer (429 + Retry-After). ¿Necesitás más?
  Escribinos: WhatsApp +503 6955-3668 (wa.me/50369553668) o dte-sv@tequio.work.
- Catálogos clave: CAT-012 departamentos, CAT-013 municipios, CAT-008 distritos,
  CAT-014 unidad, CAT-015 tributos (20=IVA 13%, C3=export 0%), CAT-017 forma pago,
  CAT-019 actividad económica, CAT-020 países (ISO alpha-2), CAT-022 tipo documento.

================================================================================
## 8. Ejemplos curl
================================================================================
# Factura a consumidor final
curl -X POST https://dte-sv.tequio.work/api/dte/factura -H "Authorization: Bearer tqo_xxx" -H "Content-Type: application/json" \
  -d '{"receptor":null,"items":[{"descripcion":"Pago único","precioUni":1.00,"tipoItem":2}],"condicionOperacion":1,"formaPagoCodigo":"05","notify":["email"]}'

# CCF B2B
curl -X POST https://dte-sv.tequio.work/api/dte/ccf -H "Authorization: Bearer tqo_xxx" -H "Content-Type: application/json" \
  -d '{"receptor":{"nit":"05282111241019","nrc":"3523746","nombre":"INGENIO360, S.A.S. DE C.V.","codActividad":"62020","descActividad":"Consultorías","direccion":{"departamento":"05","municipio":"21","distrito":"14","complemento":"Santa Tecla"},"correo":"x@y.com"},"items":[{"descripcion":"Consultoría","precioUni":100,"tipoItem":2}],"notify":["email"]}'

# Emisión con IA (lenguaje natural → DTE sellado)
curl -X POST https://dte-sv.tequio.work/api/dte/ai -H "Authorization: Bearer tqo_xxx" -H "Content-Type: application/json" \
  -d '{"input":"Vendí una lámpara en $100 pero le di $15 de descuento, en efectivo.","ambiente":"01","notify":["email"]}'

================================================================================
## 9. Hand-off a una IA (Claude Code / Codex / Cursor)
================================================================================
Pegale esto a tu IA y que implemente el cliente completo:

Quiero integrar **dte-sv** (facturación electrónica de El Salvador, Ministerio de Hacienda) a mi sistema. Implementá un cliente completo y robusto.

Recursos (leélos antes de empezar):
- OpenAPI 3.1 (machine-readable): https://dte-sv.tequio.work/openapi.json
- Referencia completa para IA: https://dte-sv.tequio.work/llms-full.txt
- Guía humana: https://dte-sv.tequio.work/developers

Mis credenciales:
- API key: tqo_TU_API_KEY
- Base URL: https://dte-sv.tequio.work

Implementá:
1. Un cliente con auth Bearer (header `Authorization: Bearer tqo_TU_API_KEY`).
2. Emisión de Factura (01) a consumidor final y CCF (03) B2B, con los shapes exactos del OpenAPI.
3. Manejo de los 3 estados de respuesta: PROCESADO (200), PENDIENTE_CONTINGENCIA (422 con estado), RECHAZADO (422). NO reintentar con el mismo codigoGeneracion en RECHAZADO.
4. Manejo de errores HTTP (401, 402 sin suscripción, 422 invalid-payload con `issues`, 429 con `Retry-After`).
5. Tipado fuerte a partir del OpenAPI (generá tipos/cliente con tu herramienta preferida).
6. Tests con el ambiente de pruebas (`"ambiente": "00"`) antes de producción.
7. (Opcional) Emisión con IA: `POST /api/dte/ai` recibe la venta en lenguaje natural
   (`{ input, ambiente, autoEmit? }`) y devuelve `accion` EMITIR/CONFIRMAR/NO_EMITIR
   con el DTE emitido o el payload listo — útil como atajo de captura.

Empezá generando el cliente desde https://dte-sv.tequio.work/openapi.json y mostrame un ejemplo de emisión de Factura.
