Skill v1.0.1
currentAutomated scan100/100+31 new
name: facturas description: "Emití facturas electrónicas, notas de crédito y notas de débito con ARCA (ex AFIP) desde TypeScript con el paquete facturas. Usala siempre que el usuario quiera facturar en Argentina, obtener un CAE, integrar ARCA o AFIP, WSFE, WSAA o WSMTXCA, emitir como monotributista o responsable inscripto, corregir o anular una factura, reintentar sin duplicar comprobantes o consultar un CUIT en el Padrón, aunque no nombre el paquete. También aplica a pedidos en inglés como Argentina e-invoicing, electronic invoice, AFIP integration o CAE authorization." license: Apache-2.0 metadata: version: "1.0.1"
facturas
facturas es el SDK de TypeScript para emitir comprobantes electrónicos con ARCA (ex AFIP). Se conecta directo desde tu servidor, sin SOAP ni XML a la vista y sin intermediarios. Requiere Node.js 22 o posterior y es solo ESM.
- npm:
facturas - Documentación: https://facturas-sdk.dev
- Repositorio: https://github.com/LaPyme/facturas
No confíes en tu memoria
El paquete está antes de la 1.0 y su API cambia entre versiones menores. Por ejemplo, el CLI pasó de --tax-id a --cuit y varios métodos se renombraron o se quitaron. Lo que recuerdes de versiones anteriores, o de otras librerías de ARCA, probablemente no aplique.
- Mirá la versión instalada en
node_modules/facturas/package.jsony la
última con npm view facturas version. Si el proyecto está atrasado, avisale al usuario y leé el changelog.
- Los tipos en
node_modules/facturas/dist/son la verdad para la versión
instalada. Empezá por index.d.ts, que reexporta el resto, y buscá ahí cada campo antes de usarlo.
- Cualquier página de la documentación se lee en Markdown agregando
.mda
la URL. El índice está en https://facturas-sdk.dev/llms.txt y el texto completo en https://facturas-sdk.dev/llms-full.txt.
- Si no encontrás respaldo en los tipos ni en la documentación, decilo. No
inventes campos ni códigos de ARCA.
La primera factura no necesita infraestructura
Sin base de datos, sin tabla y sin servicio externo. El cliente lee cuatro variables de entorno y guarda el ticket de WSAA en memoria:
| Variable | Valor | |
|---|---|---|
ARCA_TAX_ID | CUIT de 11 dígitos | |
ARCA_CERTIFICATE_PEM | El certificado PEM completo | |
ARCA_PRIVATE_KEY_PEM | La clave privada PEM completa | |
ARCA_ENVIRONMENT | test (homologación) o production. No hay valor por defecto |
import { createArcaClient } from "facturas";const arca = createArcaClient();const factura = await arca.issue({issuer: "monotributo",salesPoint: 3,to: { condition: "consumidor_final" },items: [{ amount: 150_000 }], // ARS 1.500,00 en centavos});
El certificado, la autorización del servicio y el punto de venta se habilitan en ARCA. El SDK no lo hace por el usuario, pero el CLI lo guía:
npx facturas init --cuit 20123456786 --env test # clave privada y CSRnpx facturas cert # guarda el certificado de ARCAnpx facturas check # prueba configuración, WSAA, WSFE y puntos de ventanpx facturas issue --sales-point 3 --issuer monotributo # factura de ARS 1 en homologación
Cuando algo falle antes de emitir, sugerí npx facturas check: nombra el paso que falla y dónde corregirlo en ARCA. En ejemplos usá siempre el CUIT de muestra 20123456786, nunca uno real.
El input es de negocio
Pasá datos de la venta y dejá que el SDK derive la clase, el tipo de comprobante, el IVA de cabecera y el pedido para ARCA. No armes esos valores a mano.
issueres la condición fiscal real del emisor:"monotributo",
"responsable_inscripto", "exento" o "no_alcanzado", o su código de ARCA (6, 1, 4, 15). Es una afirmación legal: preguntala si no la sabés, nunca la infieras.
- Un responsable inscripto emite A a receptores RI o monotributistas y B al
resto. Los demás emisores emiten C.
- Los importes son enteros en centavos.
150_000es ARS 1.500,00. Nunca uses
pesos con decimales.
- Clase C:
items: [{ amount }]. Clase A o B:items: [{ gross, vat }]o
[{ net, vat }], con vat en 0, 2.5, 5, 10.5, 21, 27, "exempt" o "untaxed".
toes el receptor:{ condition: "consumidor_final" }, o una condición
con cuit. Desde ARS 10.000.000 el consumidor final también necesita cuit o dni.
- Por defecto la fecha es hoy en Buenos Aires, el concepto es productos y la
moneda es ARS. Para servicios pasá service: { from, to, dueDate }.
- ARCA acepta una fecha de hasta 5 días antes o después de hoy para productos,
sin pasar al mes siguiente, y 10 para servicios. preview() e issue() lanzan ArcaInputError con code: "ARCA_INPUT_DATE_OUTSIDE_WINDOW" y window: { from, to } fuera de esa ventana. voucherDateWindow() la calcula antes, sin I/O.
arca.preview(input) es sincrónico y no hace I/O. Devuelve la clase, el tipo, los importes y el pedido exacto. Compará preview(input).amounts.sentTotal con el total de la venta antes de llamar a issue().
Para campos menos comunes, como moneda extranjera, tributos, FCE o optionalFields, leé Emitir facturas.
Tratá los cuatro resultados
issue(), issueCreditNote(), issueDebitNote() y recover() devuelven el resultado fiscal en vez de lanzarlo. El código tiene que tratar los cuatro:
switch (factura.kind) {case "authorized":// Guardá number, cae, caeExpiry, header, amounts y qr.await guardar(factura.voucher);break;case "rejected":// ARCA rechazó. El input corregido va con una clave nueva.console.error(factura.issues);break;case "indeterminate":// No se sabe si ARCA emitió. Conservá el número y conciliá.console.error(factura.attempted, factura.lookup);break;case "conflict":// Otro comprobante ocupa el número. Detené el flujo y avisá a una persona.console.error(factura.attempted, factura.found);break;default:factura satisfies never;}
voucher.qr es la URL que codifica el QR del comprobante impreso y voucher.qrPayload su JSON. Para el PDF, no armes letra, leyendas ni IVA a mano: buildVoucherDocument({ voucher, items, issuer, saleConditions }) deriva todo lo que el comprobante impreso tiene que mostrar, con los mismos items de la emisión. Está en Comprobante impreso. Lo que sí se lanza son los errores de input (ArcaInputError, con un code como ARCA_INPUT_INVALID_AMOUNT) y de configuración (ArcaConfigurationError), siempre antes de escribir en ARCA. Un ArcaInputError significa que ARCA no emitió nada por esa llamada: corregí el input y reintentá.
Reintentos sin duplicados
ARCA no acepta una clave de idempotencia. Si la respuesta se pierde, reintentar a ciegas puede emitir dos facturas. Para producción, agregá un store persistente y pasá el ID estable de la venta como idempotencyKey:
import { createArcaClient, createPostgresStore } from "facturas";import { Pool } from "pg";const pool = new Pool({ connectionString: process.env.DATABASE_URL });const arca = createArcaClient({store: createPostgresStore({ query: (text, params) => pool.query(text, params) }),});const factura = await arca.issue(input, { idempotencyKey: venta.id });
La tabla se crea una vez:
CREATE TABLE arca_store (key text PRIMARY KEY,value text NOT NULL,updated_at timestamptz NOT NULL DEFAULT now());
También hay createRedisStore(redis), createFileStore(dir) para un único servidor y createMemoryStore(), que solo sirve para pruebas porque no sobrevive a un reinicio. Reglas de la clave:
- Una clave por operación de negocio, de 1 a 255 caracteres, sin CUIT, DNI ni
datos personales. Nunca un UUID nuevo por intento.
- Una
idempotencyKeysinstorelanza antes de cualquier I/O. - Mismo input con la misma clave. Otro input lanza
ARCA_INPUT_IDEMPOTENCY_MISMATCH.
- Después de
rejected, el input corregido va con una clave nueva. - Nunca borres ni hagas vencer los registros del store: guardan el número que
un reintento tiene que consultar.
Ante indeterminate, recover(clave) consulta la reserva y nunca emite. Hacé lo que dice lookup.kind:
lookup.kind | Qué hacer | |
|---|---|---|
not_found | ARCA confirmó que el número está vacío. Llamá otra vez al método original con la misma clave y el mismo input | |
failed, aborted | La consulta no terminó. Esperá y volvé a llamar a recover() o repetí la llamada original con la misma clave | |
incomplete | ARCA tiene un comprobante en el número reservado, pero la consulta no trae los datos para probar que es este. No emitas otro. Probá recover() una vez más y, si sigue igual, que lo revise una persona con reason y attempted | |
blocked | Otra reserva frena la secuencia. Conciliá primero la clave by con recover() desde el emisor byTaxId y después repetí | |
superseded | La secuencia siguió sin esta clave. Emití con una clave nueva |
recover() sobre una clave sin reserva lanza ARCA_INPUT_RESERVATION_NOT_FOUND. Detalles en Evitar comprobantes duplicados.
Notas de crédito y débito
ARCA no anula comprobantes. Una corrección es una nota de crédito, que también es un documento fiscal real. Nombrá la factura original con for y elegí el modo de forma explícita:
const nota = await arca.issueCreditNote({for: { salesPoint: 3, voucherType: 11, number: 41 }, // o factura.voucheritems: [{ amount: 50_000 }], // devolución parcial, ARS 500,00},{ idempotencyKey: `nc:${devolucion.id}` },);
itemsacredita líneas y es el caso habitual.all: trueacredita el
original completo. Uno de los dos, o un desglose amounts, es obligatorio.
- La forma de los ítems sigue la clase del original:
{ amount }en C,
{ gross | net, vat } en A y B.
- La clase, el receptor, la moneda y el concepto salen del original. La nota
no lleva issuer ni currency. Si la consulta del original no informa la condición de IVA del receptor, pasá to.condition. Si ya la informa, podés omitir to o aportar la misma condición. to solo admite condition. El tipo y el número de documento del receptor siempre salen del original.
issueDebitNote()tiene el mismo contrato sinall: true.previewCreditNote()ypreviewDebitNote()son asincrónicas porque
consultan el original.
arca.lookup({ salesPoint, voucherType, number })consulta un comprobante
autorizado, en centavos y fechas YYYY-MM-DD. Devuelve null si ARCA no lo tiene.
arca.lastAuthorized({ salesPoint, voucherType })devuelve el último número
autorizado, o 0, en WSFE o WSMTXCA.
associatedPeriod: { from, to }en lugar deforajusta un período. Es la
alternativa cuando no hay un comprobante puntual.
Detalles en Notas de crédito y débito.
Producción
- Homologación y producción tienen certificados y puntos de venta propios. El
par de producción se genera con npx facturas init --cuit 20123456786 --env production.
ARCA_ENVIRONMENT=productionemite documentos reales. Una prueba de ARS 1
también lo es y se corrige con una nota de crédito. Usá items: [{ amount: 100 }] en clase C y items: [{ gross: 100, vat: 21 }] en A y B para una operación con IVA del 21%.
- Store persistente compartido por todos los procesos que emiten, e
idempotencyKey en cada emisión.
abortSignal: AbortSignal.timeout(20_000)evita llamadas colgadas. Un corte
después del envío devuelve indeterminate con lookup.kind === "aborted". Un corte mientras espera el lock de la secuencia lanza ArcaLockTimeoutError sin reservar nada: repetí la llamada.
- Los logs quedan en
warn. Endebugse loguean los pedidos SOAP con datos
de clientes. Nunca loguees los PEM.
La lista completa está en Pasar a producción.
Qué evitar
- Armar SOAP o XML a mano, o sumar otra librería de ARCA en paralelo.
- Usar
client.wsfepara emitir. Es el módulo de transporte. La emisión va por
issue(), issueCreditNote() e issueDebitNote().
- Calcular el IVA, la clase o el tipo de comprobante a mano.
- Importes en pesos con decimales en lugar de centavos.
- Reintentar una emisión sin
storeniidempotencyKey. - Pasar
service: "wsmtxca"en las opciones sin que el contribuyente o el
punto de venta lo requiera. WSFE es el valor por defecto.
- Probar en producción antes de homologación.