npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@folyo/sdk

v0.2.1

Published

SDK oficial de Folyo para facturación electrónica chilena (DTE + SII). Del código al SII.

Readme

@folyo/sdk

SDK oficial de Folyo para facturación electrónica chilena (DTE + SII). Del código al SII. Sin escalas.

  • TypeScript nativo, tipos derivados del OpenAPI oficial de la API.
  • ESM + CJS, sin dependencias de runtime (usa el fetch global de Node 18+).
  • Manejo de errores tipado, reintentos con backoff y emisión asíncrona con polling.
  • Redacción automática de credenciales: el cliente y los errores nunca exponen tu API key, tu JWT ni material sensible (clave SII, .pfx, CAF, secretos).

Instalación

pnpm add @folyo/sdk
# o
npm install @folyo/sdk
# o
yarn add @folyo/sdk
# o
bun add @folyo/sdk

Requiere Node.js >= 18.

Quickstart: emitir una factura electrónica (DTE 33)

import { Folyo } from "@folyo/sdk";

const folyo = new Folyo({ apiKey: process.env.FOLYO_API_KEY! });

// Emisión asíncrona + espera del resultado por polling.
const job = await folyo.dte.emitirYEsperar(
  {
    tipo_dte: 33, // Factura Electrónica
    receptor: {
      rut: "12.345.678-9",
      razon_social: "Cliente SpA",
      giro: "Comercio",
      direccion: "Av. Siempre Viva 123",
      comuna: "Santiago",
    },
    detalle: [
      {
        nombre: "Servicio de consultoría",
        cantidad: 1,
        precio: 100000,
        monto: 100000, // requerido
      },
    ],
    // Referencias opcionales. `tipo_doc_ref` acepta códigos del SII (ej. 801 =
    // orden de compra) o un código de texto de hasta 3 caracteres acordado con
    // el receptor, como "HES" (Hoja de Entrada de Servicios). Para facturar al
    // MOP usa `tipo_doc_ref` "UDP" (equivale al 802), el código de la unidad de
    // pago que entrega el mandante en `folio_ref` y su descripción en `razon_ref`.
    referencia: [
      { tipo_doc_ref: 801, folio_ref: "OC-2026-1487" },
      { tipo_doc_ref: "HES", folio_ref: "4500123456" },
      { tipo_doc_ref: "UDP", folio_ref: "2020", razon_ref: "NIVEL_CENTRAL_SUBSECRETARIA" },
    ],
  },
  { idempotencyKey: crypto.randomUUID() },
);

console.log(job.estado); // "completed"
console.log(job.result?.track_id, job.folio);

Nota: completed significa que el SII recibió el documento (tienes folio y track_id). El veredicto (aceptación o rechazo) llega después: el documento queda en estado enviado y Folyo lo consulta automáticamente hasta resolverlo. El SII puede demorar desde minutos hasta más de una hora; no reemitas por eso — suscríbete al webhook de cambio de estado.

Emisión cruda (solo encolar) + polling manual

const encolada = await folyo.dte.emitir(body, { idempotencyKey: crypto.randomUUID() });
console.log(encolada.job_id, encolada.folio);

// más tarde, o desde un webhook `dte.emitido`:
const job = await folyo.dte.getEmision(encolada.job_id!);

Impuestos adicionales y retenciones por línea

Una línea puede llevar impuestos con el código del impuesto adicional del DL 825 (art. 42). Tú mandas el código; Folyo resuelve la tasa desde su catálogo, calcula el monto sobre la misma base que el IVA y lo desglosa en el documento.

import type { CodigoImpuestoAdicional } from "@folyo/sdk";

const cervezas: CodigoImpuestoAdicional = "26";

const job = await folyo.dte.emitirYEsperar(
  {
    tipo_dte: 33,
    receptor: { rut: "12.345.678-9", razon_social: "Distribuidora SpA", giro: "Comercio" },
    detalle: [
      {
        nombre: "Cerveza lager 330 ml",
        cantidad: 120,
        precio: 12500,
        monto: 1500000,
        impuestos: [{ codigo: cervezas }], // cervezas y otras bebidas alcohólicas: 20,5%
      },
    ],
  },
  { idempotencyKey: crypto.randomUUID() },
);
// Neto 1.500.000 + IVA 285.000 + impuesto 307.500 = total 2.092.500
for (const imp of job.result?.impuestos ?? []) {
  console.log(imp.codigo, imp.glosa, imp.tasa, imp.monto, imp.retencion);
  // "26" "Cervezas y otras bebidas alcohólicas" 20.5 307500 false
}

| Código | Impuesto | Tasa | |---|---|---| | 24 | Licores, piscos y whisky (incluye aguardientes y vinos licorosos) | 31,5% | | 25 | Vinos | 20,5% | | 26 | Cervezas y otras bebidas alcohólicas | 20,5% | | 27 | Bebidas analcohólicas y minerales | 10% | | 271 | Bebidas analcohólicas con azúcar elevada | 18% | | 15 | IVA retenido total genérico por cambio de sujeto | 19% | | 30/32/33/34/36/37/48 | Retención parcial: legumbres, ganado, madera, trigo, arroz, hidrobiológicos y frambuesas | 10/8/8/4/10/10/14% | | 31/38/39/41/47 | Retención total: silvestres, chatarra, PPA, construcción y cartones | 19% IVA | | 23/44/45 | Suntuarios | 15/15/50% | | 28/35/51/52 | Específicos de diésel, gasolina y gases | monto fijo, sin tasa | | 17 | IVA anticipado de faenamiento | 5% de la base especial faenamiento | | 18/19 | IVA anticipado de carne y harina | 5/12% | | 46 | IVA retenido oro | 100% del IVA, solo 33 y NC/ND 56/61 referenciarias |

Los códigos se validan por tipo de DTE. Las retenciones parciales van en una factura de compra 46 o en su NC/ND 56/61 de referencia. Para que una de ellas retenga el IVA completo, manda el código base y ndf: true: Folyo usa la variante total (30→301, 32→321, 33→331, 34→341, 36→361, 37→371, 48→481) y omite iva_no_retenido. La API no consulta nóminas SII; quien emite debe cumplir la calidad de agente retenedor que exija el régimen.

Envía normalmente el código parcial con ndf: true, sin tasa ni monto: Folyo lo normaliza a la variante total. También puede enviarse la variante total canónica directamente con ndf: true; sólo para revalidación admite tasa: 19 y un monto que cuadre exactamente con la base del código.

Una línea exenta no puede llevar impuesto. Con impuestos o retenciones, un descuento/recargo global sólo puede ser porcentual: se prorratea en cada base antes de calcular los montos. Un descuento global en pesos responde 400 INVALID_REQUEST antes de reservar folio. El tipo 43 sólo admite adicionales, nunca retenciones ni iva_no_retenido. Los códigos 17 y 46 siguen cerrados. Mientras una familia no esté validada en certificación para tu empresa, producción responde 422 IMPUESTO_PENDIENTE_CERT.

El código 15 conserva su compatibilidad histórica sin descuento ni recargo global. Si se combina con uno porcentual, producción exige que el código 15 esté habilitado explícitamente; de lo contrario responde 422 IMPUESTO_PENDIENTE_CERT.

El código 17 exige faenamiento en cada línea afectada: codigo_cpcs (1701 a 1706), cantidad_cabezas positiva y monto_base_faena positivo. Ambas cantidades usan hasta 6 decimales y se normalizan a micro-unidades cuyo valor escalado no excede 9007199254740991 (máximo nominal 9007199254.740991); la base y su suma no superan 9007199254740991. La línea usa cantidad en KG; Folyo deriva el CPCS, Retenedor, QtyRef UN, el impuesto 17 y monto_base del resultado. Solo aplica a 33 y a 56/61 que referencian una 33 compatible; no admite descuentos o recargos globales ni mezcla con otros impuestos. El emisor debe contar previamente con acreditación operativa como agente retenedor: Folyo no hace lookup automático ni afirma una aprobación digital persistida; producción se habilita después de certificación. El código 46 es una retención total de oro para 33 y sus 56/61 referenciarias; no se usa en la Factura de Compra DTE 46.

tasa y monto son opcionales y normalmente no se envían: el catálogo de Folyo resuelve la tasa vigente. Si entregas una tasa, debe ser finita, estar entre 0,01% y 100% y tener como máximo dos decimales; debes enviarla con el mismo valor en todas las líneas que llevan ese código, o en ninguna. Si entregas monto, debes entregarlo en todas esas líneas y la suma debe ser exactamente round(baseDelCodigo * tasa / 100): no se acepta redondear cada línea por separado. monto es obligatorio para los específicos fijos 28, 35, 51 y 52, que no llevan tasa.

Para la retención total del IVA en una factura de compra (46), la forma recomendada es impuestos: [{ codigo: "15" }] en cada línea afecta, sin tasa ni monto: el código 15 siempre retiene el IVA total al 19% y produce el mismo XML que retencion_iva_total: true, que queda obsoleto pero sigue funcionando.

Para los servicios agrícolas de la Res. Ex. SII 83/2026, el uso del código 15 es una inferencia pendiente de confirmación tributaria y certificación. La capacidad técnica no acredita que el emisor cumpla los requisitos de ese régimen; la API no consulta ni acredita esa condición ante el SII.

El desglose también vuelve en impuestos del listado de documentos, junto con monto_neto, monto_exento, monto_iva e iva_no_retenido cuando la retención fue parcial:

const [doc] = await folyo.dte.listDocumentos({ limite: 1 });
for (const imp of doc?.impuestos ?? []) {
  console.log(imp.codigo, imp.glosa, imp.tasa, imp.monto);
}

Autenticación

Dos esquemas, mutuamente excluyentes:

// API key (recomendado server-side; no expira; fija tenant y empresa).
const folyo = new Folyo({ apiKey: "tu-api-key" });

// JWT (sesión de usuario).
const folyo = new Folyo({ token: "access-token-jwt" });

El SDK envía la API key tal cual en el header X-API-Key (no asume prefijo). Con API key la empresa queda fijada por la key.

Configuración

const folyo = new Folyo({
  apiKey: process.env.FOLYO_API_KEY!,
  baseURL: "https://api.folyo.cl", // por defecto; usa http://localhost:8080 en local
  timeoutMs: 30000, // timeout por request
  maxRetries: 2, // reintentos ante 429 / 503 idempotentes
  userAgent: "mi-app/1.0", // sufijo opcional del User-Agent
  // fetch: customFetch,      // inyectable (tests, proxies)
});

Recursos disponibles

| Namespace | Métodos | |---|---| | folyo.dte | emitir, emitirYEsperar, getEmision, listDocumentos, downloadXml, downloadPdf, regeneratePdf, getEstado, getEstadoEnvio, getEmitidos, getRecibidos, getContribuyente, getSituacionTributaria | | folyo.folios | info, cargarCaf, solicitar | | folyo.rcv | periodos, get, sync, resumenIva | | folyo.clientes | list, upsert, importar, buscarPorRut, update, delete | | folyo.empresa | list, seleccionar | | folyo.acuse | registrar, pendientes, estado | | folyo.rcof | enviar, resumen | | folyo.webhooks | list, create, update, delete | | folyo.apiKeys | list, create, delete |

Algunos endpoints (clientes, RCV, plantillas, listado de documentos) requieren "panel operativo" y pueden devolver 403 en planes solo-API.

Idempotencia

Para dte.emitir / dte.emitirYEsperar, pasa una idempotencyKey (8-64 chars [a-zA-Z0-9_-], UUID v4 recomendado). Un reenvío con la misma key y el mismo cuerpo devuelve el mismo job_id sin quemar un folio nuevo. Además habilita el reintento seguro ante 503 del lado del SDK.

const key = crypto.randomUUID();
await folyo.dte.emitir(body, { idempotencyKey: key });
// reintento seguro con la misma key → mismo job_id
await folyo.dte.emitir(body, { idempotencyKey: key });

Una misma key con un cuerpo distinto produce 409 IDEMPOTENCY_KEY_CONFLICT (FolyoValidationError).

Manejo de errores

Todos los errores heredan de FolyoError y exponen status, code y requestId (nunca el cuerpo de la request).

import {
  FolyoError,
  FolyoAuthError, // 401
  FolyoRateLimitError, // 429 (.retryAfter en segundos)
  FolyoQuotaError, // 402 / 403 (PLAN_LIMIT, PAYMENT_REQUIRED, ...)
  FolyoValidationError, // 400 / 409 / 422
  FolyoConnectionError, // red / timeout
  FolyoSiiUnavailableError, // 502 / 503 / 504 (.retryAfter)
} from "@folyo/sdk";

try {
  await folyo.dte.emitir(body, { idempotencyKey: crypto.randomUUID() });
} catch (err) {
  if (err instanceof FolyoRateLimitError) {
    console.warn(`Rate limit; reintenta en ${err.retryAfter}s`);
  } else if (err instanceof FolyoQuotaError) {
    console.error(`Cuota/plan: ${err.code}`); // PLAN_LIMIT, PAYMENT_REQUIRED...
  } else if (err instanceof FolyoSiiUnavailableError) {
    console.error("El SII no está disponible, reintenta más tarde.");
  } else if (err instanceof FolyoError) {
    console.error(`${err.code ?? err.status}: ${err.message} (req ${err.requestId})`);
  }
}

Reintentos automáticos

El SDK reintenta con backoff exponencial (respetando Retry-After) ante 429 y 503 solo en operaciones idempotentes: cualquier GET, y POST /dte/emitir solo si entregaste una Idempotency-Key. Ajusta con maxRetries.

Seguridad

  • El cliente y todos los objetos de error redactan la credencial a *** al serializar (JSON.stringify) o al imprimirse (util.inspect).
  • Los errores nunca incluyen el cuerpo de la request (que puede traer la clave del SII o un .pfx).
  • Sin telemetría ni logging por defecto.

Licencia

MIT — Folyo Technologies SpA.