# 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`](/llms.txt) es un
índice real en formato [llmstxt.org](https://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`](/comienza/quickstart/index.md)), la
referencia completa de la API vive en un solo archivo en
[`/api-reference.md`](/api-reference.md), y el
[repo del SDK](https://github.com/SimploHQ/simplo-node-sdk) trae su propio
`AGENTS.md` y `llms.txt`.

## Instala la skill

Este sitio publica el Agent Skill `simplo-invoicing` en
[`/.well-known/skills/`](/.well-known/skills/index.json):

```bash
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
([hola@simplo.cl](mailto:hola@simplo.cl)) 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.

## Instalar y configurar

```bash
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 [hola@simplo.cl](mailto:hola@simplo.cl). No inventes una
  key ni caigas en endpoints falsos.

```ts
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.

## El flujo canónico de emisión

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

```ts
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.

## Manejo de errores — por clase

```ts
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](/referencia/errores/).

## Reglas de idempotencia

- `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'`.

## 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](/comienza/multi-empresa/).

## Webhooks (cuando la tarea incluye actualizaciones de estado)

Sigue [Webhooks](/referencia/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.

## 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:
  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](/referencia/webhooks/#probar-localmente)).

## 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.

---

Versión HTML: https://docs.simplo.cl/agentes/para-coding-agents/ · Índice para agentes: https://docs.simplo.cl/llms.txt
