Ir al contenido
SimploSimplo Docs

Para coding agents

Playbook autocontenido para la tarea "integra la facturación electrónica de Simplo en esta app" — flujo canónico de emisión, errores, idempotencia, testing y checklist.

Instrucciones para un coding agent cuya tarea es “integra la facturación electrónica de Simplo (DTEs chilenos) en esta aplicación”. Esta página es autocontenida; los docs enlazados agregan profundidad, no prerrequisitos.

Estas docs también existen en formato máquina: /llms.txt es un índice real en formato llmstxt.org — anota cada página del sitio y cada operación de la API con su enlace markdown. Toda página HTML tiene su espejo en markdown puro agregando index.md a la URL (ej. /comienza/quickstart/index.md), la referencia completa de la API vive en un solo archivo en /api-reference.md, y el repo del SDK trae su propio AGENTS.md y llms.txt.

Este sitio publica el Agent Skill simplo-invoicing en /.well-known/skills/:

Ventana de terminal
npx skills add https://docs.simplo.cl

La skill le entrega a tu agente este mismo playbook (instalación, flujo canónico de emisión, errores por clase, idempotencia, testing y checklist) más referencias de tipos de DTE, errores, multi-empresa y webhooks — con los mismos límites honestos: la escritura vía API key está en beta privada ([email protected]) y ninguna key ni endpoint debe inventarse.

Vocabulario de dominio: DTE = Documento Tributario Electrónico. SII = la autoridad tributaria chilena. RUT = identificador tributario chileno (76543210-K). Folio = número de documento autorizado por el SII. CAF = el archivo del SII que autoriza un rango de folios.

Ventana de terminal
npm install @simplohq/sdk # o pnpm add / yarn add / bun add
  • Requiere Node ≥ 18.17 (fetch global). Cero dependencias en runtime.
  • Credencial: variable de entorno SIMPLO_API_KEY (formato sk_simplo_...). Solo backend — nunca expongas la key en un bundle de navegador, nunca la commitees. Agrégala al .env.example del proyecto como nombre, sin valor.
  • La API de Simplo está en beta privada; si el usuario no tiene key, dile que pida acceso en [email protected]. No inventes una key ni caigas en endpoints falsos.
import Simplo from '@simplohq/sdk';
export const simplo = new Simplo(); // lee SIMPLO_API_KEY, lanza si falta

Crea el cliente una vez (module scope / contenedor DI), no por request.

Toda integración de facturación se reduce a este flujo. Adapta los nombres, conserva la forma:

import Simplo, { ValidationError, type DteSummary } from '@simplohq/sdk';
const simplo = new Simplo();
export async function emitInvoice(order: {
customerRut: string; // ej. '76543210-K'
customerName: string;
items: Array<{ name: string; quantity: number; unitPriceClp: number }>;
}): Promise<DteSummary> {
const dte = await simplo.dtes.emit({
tipo_dte: 33, // 33 factura (B2B) — 39 para boletas a consumidor
receptor: {
rut: order.customerRut,
razon_social: order.customerName,
},
detalle: order.items.map((item) => ({
nombre: item.name,
cantidad: item.quantity,
precio: item.unitPriceClp, // CLP entero, NETO (el IVA lo agrega la API)
})),
metadata: { order_id: String(order.customerRut) }, // tu correlación propia
});
// dte.estado === 'firmado': firmado y en cola. La aceptación del SII es ASÍNCRONA.
return dte;
}

Reglas que hacen esto correcto:

  1. Los montos son pesos chilenos (CLP) enteros, netos de IVA. Sin centavos, sin floats para dinero. La API calcula el IVA (19%).
  2. emit retorna antes del veredicto del SII. Persiste dte.id, trata firmado como “en vuelo”, y resuelve el estado final vía webhooks (preferido) o polling de simplo.dtes.retrieve(id) hasta que estado sea aceptado, rechazado o con_reparos.
  3. Guarda dte.id y dte.folio en tu lado (ej. en la fila de la orden) inmediatamente después del emit.
  4. No bloquees el flujo del usuario en la aceptación del SII — puede tardar. Emite, responde, reconcilia asíncronamente.
import {
AuthenticationError,
ConflictError,
RateLimitError,
ServerError,
ConnectionError,
ValidationError,
} from '@simplohq/sdk';
Capturaste Haz esto
ValidationError Bug o input malo del usuario. Muestra error.details (field/code/message). Nunca reintentes tal cual. Común: INVALID_RUT — valida el dígito verificador del RUT en tu capa de formularios.
AuthenticationError Key mal configurada. Falla rápido con una pista de setup; no reintentes.
PermissionError La key no tiene el permiso/rol. Avísale al operador; no reintentes.
NotFoundError ID equivocado o recurso de otra empresa. Trátalo como bug de datos.
ConflictError Si code === 'IDEMPOTENCY_IN_PROGRESS': la misma emisión ya corre — espera y re-consulta en vez de re-emitir. En otro caso (ej. folio duplicado), muéstralo.
RateLimitError El SDK ya reintentó. Encola/frena al llamador; usa error.retryAfter.
ServerError / ConnectionError El SDK ya reintentó las requests seguras. Loguea con error.requestId y falla el job para que tu cola reintente después.

Tabla completa: Errores.

  • dtes.emit auto-genera una Idempotency-Key (UUID) por llamada — un reintento en vuelo nunca puede duplicar una factura. No construyas una capa extra de dedup alrededor de llamadas individuales.
  • Para dedup entre procesos (job reintentado por tu cola), pasa tu propia key estable: simplo.dtes.emit(params, { idempotencyKey: order-${orderId} }). La misma key + el mismo body dentro de 24h devuelve el resultado original.
  • Nunca reutilices una idempotency key con un body distinto — eso es un ValidationError con code: 'IDEMPOTENCY_BODY_MISMATCH'.

Una API key = una empresa. Si la app atiende varias empresas, mantén una key por empresa (variables de entorno / entradas de secretos separadas) e instancia un cliente por empresa. No multiplexes mutando un cliente compartido. La opción companyId existe para credenciales de plataforma, que están en el roadmap — no dependas de ella con keys normales. Detalles: Multi-empresa.

Webhooks (cuando la tarea incluye actualizaciones de estado)

Sección titulada «Webhooks (cuando la tarea incluye actualizaciones de estado)»

Sigue Webhooks al pie de la letra. No negociables:

  • Verifica cada entrega con simplo.webhooks.verify(rawBody, headers, secret) antes de confiar en ella; rechaza con 401 ante WebhookVerificationError.
  • Verifica contra el body crudo de la request (ej. express.raw), nunca JSON re-serializado.
  • Maneja reentregas (deduplica por webhook-id) y eventos fuera de orden.
  • Guarda el secreto de firma de webhooks.create — se muestra una sola vez.
  • Nunca llames a la API real desde tests. No existe una key de prueba que lo haga seguro — los documentos emitidos son documentos tributarios reales.
  • Testea tu integración mockeando en una de dos costuras:
    1. Mockea el cliente del SDK (vi.mock('@simplohq/sdk') / inyecta un fake con las mismas formas de método) — preferido para lógica de negocio.
    2. Inyecta un fetch mock en el cliente (new Simplo({ apiKey, fetch: mockFetch })) cuando quieras ejercitar la serialización real.
  • Asegura con asserts: tipo_dte correcto, montos CLP enteros, RUT pasado sin tocar, dte.id/folio persistidos, y los caminos de error de ValidationError y ConflictError.
  • Para handlers de webhook, firma payloads de prueba localmente (receta en Webhooks).

Definition of done — checklist de integración

Sección titulada «Definition of done — checklist de integración»
  • @simplohq/sdk instalado; cliente creado una vez, key desde SIMPLO_API_KEY; .env.example actualizado; sin key en código ni frontend.
  • Camino de emisión implementado con montos CLP enteros netos y el tipo_dte correcto para el caso de uso (33 factura B2B / 39 boleta).
  • dte.id y folio persistidos en el registro de dominio al emitir.
  • Veredicto asíncrono manejado: endpoint de webhook (verificado, body crudo, 401 ante firma mala) o un job de polling hasta un estado terminal.
  • Existe el camino rechazado: notificación al operador o flujo correctivo — nunca silencioso.
  • Los reintentos entre procesos llevan una idempotencyKey estable.
  • Clases de error manejadas según la tabla; requestId incluido en logs.
  • Tests: SDK/fetch mockeados, sin llamadas a la API real, caminos de error cubiertos.
  • Si es multi-empresa: un cliente por key de empresa; sin cliente mutable compartido.