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
(formato llmstxt.org) apunta a llms-full.txt y
llms-small.txt con el contenido completo y abreviado del sitio, y el
repo del SDK trae su propio
AGENTS.md y llms.txt en markdown puro.
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.
Instalar y configurar
Sección titulada «Instalar y configurar»npm install @simplohq/sdk # o pnpm add / yarn add / bun add- Requiere Node ≥ 18.17 (
fetchglobal). Cero dependencias en runtime. - Credencial: variable de entorno
SIMPLO_API_KEY(formatosk_simplo_...). Solo backend — nunca expongas la key en un bundle de navegador, nunca la commitees. Agrégala al.env.exampledel proyecto como nombre, sin valor. - La API de Simplo está en beta privada; si el usuario no tiene key, dile que pida acceso en hello@simplo.cl. 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 faltaCrea el cliente una vez (module scope / contenedor DI), no por request.
El flujo canónico de emisión
Sección titulada «El flujo canónico de emisión»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:
- Los montos son pesos chilenos (CLP) enteros, netos de IVA. Sin centavos, sin floats para dinero. La API calcula el IVA (19%).
emitretorna antes del veredicto del SII. Persistedte.id, tratafirmadocomo “en vuelo”, y resuelve el estado final vía webhooks (preferido) o polling desimplo.dtes.retrieve(id)hasta queestadoseaaceptado,rechazadoocon_reparos.- Guarda
dte.idydte.folioen tu lado (ej. en la fila de la orden) inmediatamente después del emit. - No bloquees el flujo del usuario en la aceptación del SII — puede tardar. Emite, responde, reconcilia asíncronamente.
Manejo de errores — por clase
Sección titulada «Manejo de errores — por clase»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.
Reglas de idempotencia
Sección titulada «Reglas de idempotencia»dtes.emitauto-genera unaIdempotency-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
ValidationErrorconcode: 'IDEMPOTENCY_BODY_MISMATCH'.
Multi-empresa
Sección titulada «Multi-empresa»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 anteWebhookVerificationError. - 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.
Testing
Sección titulada «Testing»- 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:
- 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. - Inyecta un
fetchmock en el cliente (new Simplo({ apiKey, fetch: mockFetch })) cuando quieras ejercitar la serialización real.
- Mockea el cliente del SDK (
- Asegura con asserts:
tipo_dtecorrecto, montos CLP enteros, RUT pasado sin tocar,dte.id/foliopersistidos, y los caminos de error deValidationErroryConflictError. - 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/sdkinstalado; cliente creado una vez, key desdeSIMPLO_API_KEY;.env.exampleactualizado; sin key en código ni frontend. - Camino de emisión implementado con montos CLP enteros netos y el
tipo_dtecorrecto para el caso de uso (33 factura B2B / 39 boleta). -
dte.idyfoliopersistidos 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
estadoterminal. - Existe el camino
rechazado: notificación al operador o flujo correctivo — nunca silencioso. - Los reintentos entre procesos llevan una
idempotencyKeyestable. - Clases de error manejadas según la tabla;
requestIdincluido 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.