Skip to main content
issue() es la fachada: derivá el comprobante desde datos de negocio, reservá el número y recuperá los reintentos. Ese input de negocio es el único input. Los campos que el SDK no deriva, optionalFields, buyers y activities, pasan tal cual a ARCA. preview().request te muestra el comprobante armado antes de mandarlo.

Emitir

Definí las variables de entorno. No hace falta store ni ningún servicio externo. Este bloque es examples/primera-factura.ts:

Evitar duplicados al reintentar

ARCA no recibe una clave de idempotencia. Si la conexión se corta después del envío, repetir la llamada sin guardar el intento puede duplicar la factura. facturas facilita la deduplicación con un store: guarda el número reservado, el input y el resultado. Pasale el ID estable de tu venta como idempotencyKey. Este ejemplo supone que venta es tu venta y que ya creaste la tabla Postgres.
Una clave tiene de 1 a 255 caracteres. Usá el ID de la venta o del pedido, nunca un UUID nuevo por intento. No pongas CUIT, DNI ni otros datos personales en las claves. Reusá la misma clave y el mismo input en los reintentos. Si el input cambia, se lanza ARCA_INPUT_IDEMPOTENCY_MISMATCH. Las claves están alcanzadas al CUIT del cliente y al entorno. representedTaxId también se controla como parte de la identidad del input. Una clave sin store lanza antes de cualquier I/O con ARCA. Claves distintas identifican operaciones de negocio distintas. Con un store que provee withLock, como Postgres, Redis, archivos, memoria o el tuyo, las llamadas con clave sobre el mismo punto de venta y tipo de comprobante se serializan: cada una toma el número siguiente y escribe una sola vez. La coordinación alcanza a los procesos que comparten ese store, no a otros escritores del mismo punto de venta. El detalle, con la tabla de garantías por adaptador, está en Evitar comprobantes duplicados. abortSignal marca el límite de tiempo con un AbortSignal, por ejemplo AbortSignal.timeout(20_000). Aborta el login WSAA, la escritura y las consultas de esa llamada, y también la espera del lock de la secuencia: si vence antes de tomarlo, la llamada lanza ArcaLockTimeoutError sin reservar ni enviar nada, y la podés repetir. Si se corta después de haber enviado la escritura, el resultado es indeterminate con lookup.kind === "aborted": la reserva queda y recover() la concilia. No hay un timeoutMs aparte: un solo abortSignal compone con el que ya tenga tu aplicación. El ejemplo completo, con el tratamiento de los cuatro resultados, está en examples/issue-invoice.ts. recover(clave, opciones) concilia sin emitir. Solo consulta la reserva guardada, con el servicio y el número que quedaron registrados: nunca autoriza ni reserva un número nuevo. Si ARCA confirma que el número está vacío, el resultado es indeterminate con lookup.kind === "not_found", no una autorización. Para emitir, llamá a issue() con la misma clave. Si no hay reserva para esa clave, lanza ArcaInputError con code === "ARCA_INPUT_RESERVATION_NOT_FOUND". Esa ausencia no es el lookup.kind === "not_found" de una reserva consultada. Tampoco prueba que otro proceso todavía no esté por guardarla. Acepta representedTaxId, forceRefresh, include y abortSignal.

Revisá antes de emitir

preview() deriva exactamente lo que enviaría issue() sin hacer I/O. No usa el store, WSAA, SOAP ni lee el próximo número. Lanza los mismos errores de input que lanza issue() antes de su primera llamada, así que un input que previsualiza limpio no genera ningún error local nuevo al emitir. Este bloque es examples/preview.ts:
preview() es sincrónico y devuelve la voucherClass derivada, el voucherType, la date del comprobante en YYYY-MM-DD, los mismos amounts que informa un resultado autorizado, y request, el pedido que enviaría issue(). El número de comprobante no aparece porque recién se conoce cuando se reserva al emitir. Previsualizar una nota sí necesita el original, así que previewCreditNote() y previewDebitNote() son asincrónicas y hacen esa consulta. Están en Notas de crédito.

Datos de la factura

El emisor es tu afirmación legal en cada llamada. El SDK nunca lo infiere de los ítems ni del Padrón. Un emisor RI produce A para receptores RI o Monotributo y B para las demás condiciones soportadas. Los emisores Monotributo, Exento y No Alcanzado producen C y usan items: [{ amount: 10_000 }]. ARCA valida la habilitación real. to es el receptor fiscal, el tipo y el número de documento y la condición de IVA que viajan en el comprobante, no un registro de cliente. Los importes son enteros en centavos. Para ítems de RI, elegí net o gross en cada ítem y uno de 0 | 2.5 | 5 | 10.5 | 21 | 27 | "exempt" | "untaxed" para vat. El cero numérico es una alícuota de IVA. Los importes exentos y no gravados tienen campos fiscales propios. Los ítems se agrupan por alícuota antes del redondeo Round Half Even. total, cuando lo pasás, afirma el total enviado. El SDK ajusta el IVA de cabecera solo dentro de un centavo por cada alícuota numérica emitida, y mantiene el IVA no negativo. Los totales de clase C tienen que coincidir exactamente. Los resultados autorizados exponen computedTotal, sentTotal y vatAdjustment en voucher.amounts, todos en centavos. Los valores por defecto son la fecha de hoy en Buenos Aires, productos (concepto 1) y ARS con tipo de cambio 1. Usá currency: "USD" con un exchangeRate decimal positivo en string, o service: { from, to, dueDate } para el concepto 2. Las fechas aceptan YYYY-MM-DD o YYYYMMDD. El fin del servicio tiene que ser igual o posterior a su inicio y el vencimiento de pago igual o posterior a la fecha de la factura. ARCA solo acepta una fecha cercana al día en que recibe el comprobante, y preview() e issue() la controlan antes de enviar nada, contando desde hoy en Buenos Aires: Una fecha fuera de esa ventana lanza ArcaInputError con code: "ARCA_INPUT_DATE_OUTSIDE_WINDOW", field: "date" y window: { from, to }, las fechas que acepta ARCA ese día en YYYY-MM-DD. Un reintento con la misma idempotencyKey devuelve lo que ARCA ya autorizó aunque la ventana se haya movido. Para mostrar la ventana antes de emitir, por ejemplo como límites de un selector de fecha, voucherDateWindow() la calcula sin I/O con la misma regla:
concept es "products" si lo omitís, como en issue(). service: "wsmtxca" aplica la ventana de WSMTXCA y now fija el momento del envío, que por defecto es ahora. ARCA igual rechaza una fecha anterior al último comprobante del mismo tipo y punto de venta. Los receptores que no son consumidor final requieren un cuit de 11 dígitos. Un consumidor final acepta un cuit o un dni, o ninguno por debajo del umbral de identificación. Desde ARS 10.000.000 (incluidos los USD convertidos al tipo de cambio informado), la identificación es obligatoria según la RG 5866/2026. Cuando el cliente pide el CUIT para deducir en Ganancias, informalo sin importar el monto. Los controles de forma del documento no verifican la inscripción ante ARCA. family elige la familia del comprobante y por defecto es "ordinary" (1, 6, 11). "retention_legend" es A con leyenda y existe solo en clase A (51, 52, 53). "fce" es Factura de Crédito Electrónica MiPyME (201 a 213): requiere dueDate y fce: { cbu, alias?, transfer?, reference? }, con un CBU de 22 dígitos. ARCA valida la cuenta bancaria real y tu habilitación. taxes son tributos y percepciones. Cada fila es { id, description?, base, rate, amount }, con base y amount en centavos y rate en porcentaje. Los tributos quedan fuera de la aritmética de los ítems y suman al total. amounts es un desglose fiscal ya revisado y es excluyente con items: el SDK no recalcula el IVA ni lo ajusta. Toma { net, vat, exempt?, untaxed?, vatRates? } en centavos, con filas vatRates de la forma { id, base, amount }. El total opcional afirma el total revisado. Se controla contra las reglas de conciliación de importes de ARCA y no se reescribe en silencio. concept: "products_and_services" es el concepto 3, junto a "products" y "services". dueDate informa el vencimiento de pago. paidInForeignCurrency marca la cancelación en la misma moneda extranjera y no se acepta en ARS. Para monedas que no son ARS ni USD, pasá currency: { id: "060" } con un exchangeRate explícito. ARCA controla la elegibilidad. issuer también acepta el código de ARCA de la condición del emisor: 1 responsable inscripto, 6 monotributo, 4 exento y 15 no alcanzado, como en ARCA_ISSUER_CONDITION_IDS. Del mismo modo, to.condition acepta el número de la condición de IVA del receptor del catálogo de ARCA, con cuit, dni o document: { type, number }. Los campos optionalFields, buyers y activities pasan tal cual a ARCA. No dupliques ahí lo que ya informa fce. Cada ítem puede además describir su línea con description, quantity, unit, unitPrice, discount, code, matrixCode y matrixUnits. La emisión normal no usa ese detalle. Si el contribuyente o el punto de venta opera a través de WSMTXCA, esos campos forman las filas que requiere ese servicio. Revisá el caso especial de WSMTXCA. El ejemplo compilado de todo esto es examples/emision-completa.ts.

Qué hace issue() al emitir

Sin clave, una llamada lee un próximo número y autoriza una vez, con a lo sumo una consulta de identidad después de una respuesta indeterminada. Una primera llamada con clave reserva ese número antes de escribir. Una repetición con clave consulta la reserva: solo not_found habilita una autorización del número guardado. Un comprobante encontrado nunca se reenvía. Las escrituras indeterminadas y los rechazos 10016 con clave pueden agregar una consulta. issueCreditNote() agrega la consulta del original solo cuando crea una reserva nueva. Un 10016 sobre un número que esta misma llamada reservó no se resuelve por coincidencia de campos: si la consulta encuentra un comprobante el resultado es conflict, y si no encuentra nada sigue siendo rejected. Dos ventas con datos fiscales idénticos, por ejemplo dos ventas a consumidor final por el mismo importe el mismo día, coinciden en todos los campos. Por eso, la coincidencia probaría consistencia y nunca autoría. La comparación de identidad queda para una reserva que ya existía, el único caso en el que el número puede ser una escritura propia anterior. Todo conflict se anota en el store antes de responder, salvo el que encuentra una repetición o un recover() después de un rechazo en un store sin withLock. Una repetición con esa clave, o un recover(), devuelve el mismo conflict sin ninguna llamada a ARCA. voucher habla en las unidades del input: date y caeExpiry vienen como YYYY-MM-DD y amounts en centavos. voucher.totals trae el dinero autorizado en centavos: total, neto gravado, no gravado, exento, IVA, tributos, una fila por alícuota de IVA en vatRates y el detalle de taxes. Es lo que el comprobante impreso muestra. voucher.header trae concept, la identidad y condición del receptor, currencyId, exchangeRate y, cuando corresponden, las fechas de servicio y paymentDueDate. Las fechas son YYYY-MM-DD, el documento del receptor es un string y el tipo, la condición y el concepto conservan sus identificadores numéricos de ARCA. exchangeRate es un string decimal exacto. Puede faltar solo cuando ARCA permite omitir la cotización de una cancelación en la misma moneda extranjera. Las fechas de servicio faltan para un comprobante de productos y paymentDueDate falta cuando ese comprobante no tiene vencimiento fiscal. preview().header tiene el mismo contrato antes de emitir. Un resultado authorized lo conserva en voucher.header tanto si recoveredByMatch es false como si es true. La consulta y el request siguen disponibles como evidencia, pero no hace falta ramificar por el modo de recuperación para guardar la cabecera. voucher trae también qr, la URL que tiene que codificar el código QR del comprobante impreso según la especificación de ARCA, armada con el CUIT emisor, el total, la moneda, el CAE y el documento del receptor que se envió de verdad. Falta solo si ARCA devolvió un CAE que la especificación no puede codificar: una autorización nunca se pierde por su QR. qrPayload trae el JSON que esa URL lleva en base64, para guardarlo o mostrarlo sin decodificar la URL. Para reimprimir un comprobante guardado sin volver a emitir, arcaQrUrl() arma la misma URL a partir de esos datos. Solo request conserva la forma de ARCA, con importes en pesos y fechas YYYYMMDD. Si ARCA rechaza el ticket WSAA antes de procesar el pedido, issue() pide un ticket nuevo y reenvía una sola vez. Es seguro porque ese rechazo se clasifica solo cuando la respuesta no trae CAE, resultado ni número. Con forceRefresh: true no hay segundo intento. Un indeterminate informa en lookup por qué quedó abierto: not_found, incomplete, failed, aborted cuando venció tu abortSignal, blocked cuando otra reserva sin resolver todavía frena la secuencia y superseded cuando la secuencia siguió sin esta clave. En blocked no se reservó ningún número: by nombra la clave a conciliar con recover(). En superseded la clave by se llevó el número: esta clave nunca va a escribir, así que emití bajo una clave nueva. El segundo argumento acepta idempotencyKey, abortSignal, representedTaxId, forceRefresh, service, number e include: { rawResponse: true, request: true }. Los resultados no traen la respuesta cruda ni el request fiscal por defecto. Si pedís request, aparece en cualquiera de los cuatro resultados y representa lo que el SDK envió o preparó para ese intento. Una repetición sin un resultado de escritura observado usa un intento indeterminado con reason: "incomplete_response". La consulta aporta la evidencia de autorización. No pases service en el uso normal. La emisión usa WSFE por defecto. Usá service: "wsmtxca" solo cuando el contribuyente o el punto de venta opere a través de ese servicio de ARCA. El SDK no lo descubre ni lo cambia solo. La guía de WSMTXCA cubre ese caso especial. number es un número reservado por fuera. Cuando lo pasás, la llamada no lee el próximo número, así que la aplicación sigue siendo dueña de la secuencia del punto de venta. issue() cubre la emisión de CAE para facturas y notas, también las de exportación, que siguen la guía de Factura E. No cubre CAEA ni los demás servicios de ARCA. El comparador de identidad compara coordenadas, fecha, concepto, receptor, moneda, todos los importes de cabecera, alícuotas de IVA, fechas de servicio, tributos, campos opcionales, compradores, actividades, las asociaciones de notas (incluidos el CUIT emisor y la fecha informados) y el flag de pago en moneda extranjera. Los campos faltantes quedan incompletos y nunca cuentan como prueba de coincidencia. Las diferencias son conflictos. Los campos del servicio de ARCA que quedan fuera de ese conjunto se consideran incompletos.
Last modified on October 3, 2026