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.
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 usanitems: [{ 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.