Fill ARCA's RCEL invoice wizard (Factura C and the other C comprobantes) end to end and stop at the summary screen. It never presses Confirmar, so it never generates a CAE.
{"tipoComprobante":"string optional, default \"Factura C\". Also \"Nota de Credito C\", \"Nota de Debito C\", \"Recibo C\"","puntoVenta":"int|string optional, e.g. 1 or \"00001\". Auto-selected when the CUIT has exactly one","fecha":"DD/MM/AAAA optional, defaults to the date RCEL prefills (today)","concepto":"\"productos\" | \"servicios\" | \"ambos\" (default \"servicios\")","periodoDesde":"DD/MM/AAAA, REQUIRED when concepto is servicios or ambos","periodoHasta":"DD/MM/AAAA, same","vtoPago":"DD/MM/AAAA, same","actividad":"string optional, e.g. \"620200\". Only needed when the CUIT has several","receptor":"{condicionIva:\"Consumidor Final\"|..., docTipo:\"DNI\"|\"CUIT\"|..., docNro:string, razonSocial, domicilio, email}","formasDePago":"array of strings optional, e.g. [\"Transferencia Bancaria\"]. Defaults to whatever RCEL prechecks","items":"REQUIRED array of {descripcion, cantidad, unidadMedida, precioUnitario, bonificacion}","referencia":"string optional, Referencia Comercial"}{"step":"string: empresa|menu|ptovta|emision|receptor|operacion|resumen","done":"bool, true ONLY on the summary page","wait":"bool, true means call again","stop":"\"MANUAL_CONFIRM\" on the final call","resumen":"{razonSocial,puntoDeVenta,concepto,periodoFacturado,vtoPago,condicionIva,condicionesVenta,importeTotal}","texto":"string, the full summary screen as plain text","error":"string|null","alerts":"array of RCEL validation messages when error is \"validacion\""}Drives ARCA's RCEL (Regimen de Comprobantes en Linea) wizard at fe.afip.gob.ar/rcel/, filling the four steps from the params and stopping on the Resumen de Datos screen.
IT DOES NOT ISSUE THE COMPROBANTE. The extractor has a hard block on the confirm control (
#btngenerar,onclick="observarOConfirmar()") and refuses to click it or anything wired to that handler. Pressing Confirmar generates a CAE, which is a legal fiscal document: it cannot be undone, edited or deleted, only offset with a Nota de Credito. That press belongs to a human looking at the screen. Report the returnedresumento the user and tell them to click "Confirmar Datos..." themselves.
1. You cannot navigate to RCEL. Ever. fe.afip.gob.ar/rcel/* returns 403 to any direct navigation, including menu_ppal.jsp, including with a live session, including to go back to the menu for a second comprobante. The only way in is the portal SSO handoff, so arca-open-service always runs first and the service lands in a new tab. To restart the wizard, close the RCEL tab and redo the handoff; do not goto the menu. (Verified twice on 27/07/2026: once cold, once mid-session.)
2. Never transcribe evaluate.js into a tool parameter. Copying the source by hand to fill the <FN> placeholder in the store snippet mangled the ̀-ͯ range in norm() into literal combining characters on 27/07/2026. It happened to keep working because those are the same two code points, which is exactly why it is dangerous: it fails silently. Move the source only with JSON.stringify from a script that reads the file. Generate the store snippet with a script (node -e + JSON.stringify) and paste its output, never the file.
Unlike the other skills, this one is not a single call. RCEL is a 2008-era JSP app: each step is a full page POST and each navigation destroys the JS context, so the extractor runs once per screen. That is why it lives in localStorage, which survives the navigations.
The extractor is fully synchronous, no await anywhere. Every wait (the AJAX that fills the comprobante types, each page load) is expressed as {wait: true} and resolved by calling again.
ARCA (ex AFIP) keeps its service URLs on
afip.gob.ar. The catalog domain reads ARCA but the tab must be onfe.afip.gob.ar.
Default action: just call (step 1), in a loop. The call reports NEEDS_STORE when the extractor is missing or out of date; only then store (step 2) and resume the loop.
Versioning. Gated by VER (must match version in the frontmatter). When evaluate.js changes, bump both.
done or error)Get into RCEL with arca-open-service and switch to the tab it opens (never navigate to menu_ppal.jsp, see rule 1). Then run this same snippet repeatedly with the same params every time:
await (() => {
const KEY = "arca-prepare-invoice", VER = "2";
const s = localStorage.getItem(KEY);
if (!s || localStorage.getItem(KEY + ":ver") !== VER) return 'NEEDS_STORE';
const fn = eval('(' + s + ')');
return fn(document, {
tipoComprobante: "Factura C",
concepto: "servicios",
periodoDesde: "27/07/2026", periodoHasta: "27/07/2026", vtoPago: "27/07/2026",
receptor: { condicionIva: "Consumidor Final" },
formasDePago: ["Transferencia Bancaria"],
items: [{ descripcion: "Servicios de consultoria en informatica",
cantidad: 1, unidadMedida: "unidades", precioUnitario: 15000 }]
});
})()
Run this snippet verbatim. Never add an await before the eval. Only the outer await is allowed.
The loop. Each call advances one screen and returns {step, done: false, wait: true}. Wait for the page to settle, then call again with identical params. Stop when:
done: true -> you are on the Resumen screen. Finish here. Read resumen back to the user and hand off the Confirmar click.error present -> stop and report it. Do not retry blindly.done -> stop, something is wrong.The extractor is idempotent per screen: it decides what to do from location.pathname, so a repeated call on an unchanged page is harmless.
Typical run for one item: empresa (only if RCEL asks) -> menu -> ptovta (twice, the second waits on the ajax) -> emision -> receptor -> operacion -> resumen. Six to eight calls.
NEEDS_STORE)From a fe.afip.gob.ar tab, evaluate the following with the bridge, with <FN> replaced by the contents of evaluate.js (the ( (root, params) => { ... } ) function). Keep VER equal to the call snippet's. Build that substitution with a script (node -e + JSON.stringify) and paste its output: retyping the source is rule 2, and it fails silently.
(() => {
const KEY = "arca-prepare-invoice", VER = "2";
const fn = (<FN>);
localStorage.setItem(KEY, String(fn));
localStorage.setItem(KEY + ":ver", VER);
return 'stored';
})()
Returns "stored". Then go back to step 1.
Everything is matched by the visible option text, case and accent insensitive, so write params the way they read on screen. Numeric codes also work as a fallback.
tipoComprobante: Factura C (2), Nota de Debito C (3), Nota de Credito C (4), Recibo C (5), plus the FCE MiPyMEs variants (120/121/122). (These are the EMISSION codes, different from the consulta in arca-issued-invoices where Factura C is 11.)
receptor.condicionIva: Consumidor Final (5), IVA Responsable Inscripto (1), Responsable Monotributo (6), IVA Sujeto Exento (4), Sujeto No Categorizado (7), Cliente del Exterior (9), Proveedor del Exterior (8).
receptor.docTipo: CUIT (80), CUIL (86), CDI (87), DNI (96), Pasaporte (94), LE (89), LC (90).
formasDePago (checkboxes, several at once): Contado (1), Cuenta Corriente (96), Cheque (97), Transferencia Bancaria (91), Otros medios de pago electronico (90, RCEL default), Otra (99). Omit the param to keep RCEL's default.
items[].unidadMedida: unidades is the normal one for services. Also kilogramos, metros, litros, docenas, packs, otras unidades and the rest of the RCEL list.
Same-origin only. RCEL has no CORS and authenticates with JSESSIONID, so the tab must be on fe.afip.gob.ar.
Latin-1. RCEL is served as iso-8859-1, so the browser encodes the form body in Latin-1. The extractor folds curly quotes, long dashes and ellipses to ASCII and strips anything outside Latin-1. Accents and the letter n-tilde are fine. Do not put long dashes or emoji in descripcion.
Selects are matched by text, not value. RCEL reuses option values; the extractor sets selectedIndex, which is what the page's own handlers read.
Totals are computed by the page. After filling a row the extractor calls RCEL's own calcularSubtotalDetalle(n) and verifies #imptotal is greater than zero before submitting.
Validation alerts are captured, not swallowed. validarCampos() reports problems with alert(), which would hang the bridge. The extractor temporarily replaces alert/confirm, collects the messages and returns them as {error: "validacion", alerts: [...]}.
Not supported: foreign currency, credit/debit card payment, comprobantes asociados, and the RG 3749 / RG 4004 datos adicionales. Those must be loaded by hand.
OPEN BUG, no known fix: RCEL starts writing Condiciones de Venta: null and stays that way. It is server-side ARCA behaviour, not a filling bug. A fresh Clave Fiscal login does NOT reset it (tested 27/07/2026: first wizard on a brand-new session, still null). Whatever the trigger is, it outlives the session, so the only reliable defence is to read Condiciones de Venta on the summary before confirming.
What the day looked like: the first handful of wizards recorded the payment method correctly, and from some point on every single one wrote null, across new sessions and new logins. The only variable that tracks it is how many wizards had been abandoned on the summary screen without confirming, which suggests something accumulating per taxpayer rather than per session, but that is a correlation and it is not proven. Measured 27/07/2026 by running the wizard once per variant and lining up what the extractor ticked, what the browser POSTed and what the summary showed:
| run | asked | ticked | POST leaving receptor | summary |
|---|---|---|---|---|
| 1 | Contado | 1 Contado | formaDePago=1 | Contado |
| 2 | Transferencia Bancaria | 91 | formaDePago=91 | Transferencia Bancaria |
| 3 | Cuenta Corriente | 96 | formaDePago=96 | Cuenta Corriente |
| 4 | Contado (receptor con DNI) | 1 | formaDePago=1 | null |
| 5 | Contado (by code) | 1 | formaDePago=1 | null |
Runs 1 and 5 ask for exactly the same thing and disagree, so it does not depend on the payment method, the receptor, or how the value is named. Five identical Contado wizards run right after gave null five times out of five, and so did the first wizard of a later, freshly logged-in session. Redoing the SSO handoff per run does not reset it either.
Why the client is exonerated, by POST capture: the extractor ticks the right box and the POST leaving the receptor carries the right formaDePago=N in every single run, degraded or not. datosOperacionForm (the next screen) has no formaDePago field at all and the string appears nowhere in its HTML, so RCEL holds the value in the session and loses it there. It is also not a race: at 6 s per screen with waitForLoadState('load') the result is the same null with byte-identical POST bodies.
Practical consequences:
Condiciones de Venta before confirming, and compare it against what was asked. RCEL swallows the field without erroring, so nothing else will warn you.NEEDS_STORE -> extractor missing or version-mismatched; run step 2 once, then retry.session_expired / login_required -> ARCA bounced you to login. The Clave Fiscal is typed by the user, by hand; there is nothing to retry until they do. Then restart from the portal. Report it immediately and stop, no retries and no waiting. Detect it before any long wait (~2 s) by matching ARCA's own /portal/app/expiredSession route and by counting service cards on the portal, which is zero when logged out. Matching only the auth.afip host misses expiredSession entirely, which on 27/07/2026 turned this into a 25 s wait times three.wrong_site -> the tab is not on fe.afip.gob.ar/rcel/. Do not navigate there (403, rule 1); re-run arca-open-service.no_abrio_el_servicio -> the portal card was clicked but no tab appeared. Usually an expired session that bounces after the click; check whether the portal is now on auth.afip.gob.ar.validacion -> RCEL rejected the screen. Read alerts, fix the params, restart from the menu.punto_de_venta_no_encontrado / tipo_comprobante_no_encontrado / condicion_iva_no_encontrada / forma_de_pago_no_encontrada / unidad_de_medida_no_encontrada -> the return includes disponibles with the exact option texts; pick one from there.faltan_fechas_de_servicio -> concepto is servicios or ambos, so periodoDesde, periodoHasta and vtoPago are mandatory.refused_to_click_confirm -> something asked the extractor to press Confirmar. It will not. By design.{ "type": "json", "jsonPath": "done" }