TiFacturaOnline · Gateway de facturación electrónica AFIP
Registro de releases QA/PROD desde qa-20260825 hasta qa-20260908, y el anexo con los pasos de actualización acumulados.
Un review adversarial externo (Codex/Astra) encontró seis defectos en el money-path (F01–F06). Se corrigieron de a uno, cada uno con tests de caracterización sobre la transacción real, review fresco de dos jueces y suite completa, y se cerraron además los temas que aparecieron durante las correcciones y en dos tandas de review holístico. Orden de trabajo: F01 → paquete de tests de paranoia → F05 → F02+F06 → F03 → F04 → composición. Todo sale en una sola release: un cierre parcial dejaba el sistema en un estado intermedio peor que el anterior.
AfipCaeService.emitirCae marcaba resultado='R' ante CUALQUIER excepción del cliente WSFE; como GeneralException es checked, Spring commiteaba y la R quedaba persistida. Un timeout, un HTTP 5xx, un HTML de mantenimiento o un fault se registraban igual que un rechazo genuino, y ARCA podía haber autorizado ese CAE: la fila salía del radar del barrido, de la reconciliación (CRÍTICO-4) y del reintento de rama-3 (todos filtran resultado IS NULL) → doble numeración contra el Libro de IVA de ARCA. Ahora null = estado desconocido y 'R' solo con veredicto de ARCA: FeCabResp/Resultado presente, trimeado, distinto de 'A' y sin código 10016. Son desconocido (null): <Errors> de nivel request (600 token, 501 interno, estructura), cuerpo sin FeCabResp o con <Resultado/> vacío, fault SOAP 1.1 y 1.2, no-XML, HTTP 5xx, timeout, 'A' sin CAE, y el 10016 (correlatividad) venga en Obs o en Err, porque es justamente el síntoma de un intento anterior autorizado con respuesta perdida. El texto que recibe el POS no cambia; el WSDL tampoco.resultado IS NULL OR 'A') ahora bloquea re-emitir una NC cuya emisión falló por transporte o por <Errors>. Antes la 'R' habilitaba el reintento → doble anulación por un parpadeo de red. La contracara: esas NC quedan trabadas hasta que corra el barrido.null nueva (CAE y NC). Este jar se despliega con Fase A + Fase B activadas (ver anexo, paso 4). Cambio visible en el cockpit: los fallos de transporte pasan de “rechazados” a “pendientes”.sys.indexes (12 índices + “ningún UNIQUE legacy en SucPosPV”, normaliza brackets y literales N'…'); tests de concurrencia contra SQL Server real que encarnan F02 y F03 (nacen rojos a propósito, gateados con -Drealdb=true, se ponen verdes con sus fixes); test H2 de F06 deshabilitado con motivo hasta su fix; tabla de transiciones de resultado (null→A, null→R, R→A por rama-3 y CAEA; ‘A’ terminal; incluye los dos escritores del job CAEA); perfil Maven mutation (PIT: 62 mutantes, 85 % muertos en 8 s sobre el cliente WSFE). Cómo correrlo: docs/testing-paranoia-pack.md. Hallazgo: el test de concurrencia que existía no cubría F02 (cubría “aprobó localmente”, no “aprobó en ARCA con respuesta perdida”).ultimoAutorizado fresco: si N ya fue consumido, se recupera el CAE que ARCA emitió para N en vez de pedir N+1 (antes: dos facturas de la misma venta). Antes de adoptar un CAE consultado se verifica titularidad: guarda local (otra fila del comercio ya aprobada con ese número → renumerar, nunca trabar el ticket), doble huérfano (dos filas sin respuesta con el mismo número → ambiguo, adjudicación manual) y comparación con lo que ARCA devuelve en FECompConsultar (fecha, importe, documento del receptor, número). La reserva del número nuevo se commitea en transacción propia antes de llamar a ARCA, como en rama-4; el verificador revirtió ese cambio y la suite contra SQL Server se colgó indefinidamente (lock de fila entre conexiones), prueba de que era necesario. Métrica nueva en cockpit NUMERACION para los rechazos por titularidad. Sin cambio de WSDL ni de texto al POS.NC_RECON_MAX_REINTENTOS, 3) con alerta Telegram al agotarse; los fallos de transporte no cuentan. Tope por corrida (50) y ventana de 92 días (NC_RECON_VENTANA_DIAS_REVERSOS). Una fila envenenada se saltea, no mata la corrida; una transacción de job envenenada corta la corrida sin seguir llamando a ARCA. KPIs nuevos en /nc de Telegram: reversos rechazados pendientes, trabados y fuera de ventana. Riesgo aceptado nuevo en docs/riesgos-aceptados-hardening.md §10. Sin DDL.saltados). La guarda A.1 excluye la propia fila (el bug de raíz). Ninguna llamada a ARCA se hace con un lock tomado (antes la consulta de numeración iba dentro del applock con timeout de 60 s contra un presupuesto de 30 s de los emisores: un ARCA colgado en el job nocturno podía mandar tickets vivos del POS a contingencia). Tope por corrida cobrado por llamada real a ARCA (casos A y B). Aislamiento por fila: un timeout de lock o un ARCA caído no descarta el resto del comercio (fallidos). El test rojo del paquete de paranoia está verde; se sumó uno contra SQL Server con el commit del competidor en vuelo, que mata al mutante sin lock pesimista (probado con RCSI ON). Sin DDL. Incluye del backlog: test del UNIQUE de NextGen arreglado y asserts del resultado devuelto por emitirCae/consultarComprobante.RecuperacionCaeGuard, y se aplican también a reconciliarHuerfano de la reconciliación, que adoptaba cualquier CAE consultado sin verificar dueño (podía terminar emitiendo una NC contra un comprobante ajeno). Rama-3 ya no deja persistir la ‘R’ de una consulta no aprobada sobre el huérfano. Rechazos de la reconciliación: una sola alerta Telegram agregada por corrida (tope 15 líneas), sin re-consultar a ARCA el mismo número dos veces en la corrida. Fila borrada a mano durante un reintento: cierre curado SIN_FILA al POS en vez de un error interno (la existencia se pregunta antes de tocar la fila, por la misma conexión). Riesgo aceptado §11: espera de lock infinita por default del driver, acotada por el applock de 30 s para los demás emisores. Sin DDL, sin cambio de WSDL.tipofacturacion='CAE'; el V2 marca las NC del POS como ‘NC’ y el lado CAEA acepta cualquier tipo, así que una NC emitida online y también por CAEA nunca se revertía. Ahora IN ('CAE','NC') AND trxoriginal IS NULL (el segundo predicado evita tomar un reverso generado como original: sin reverso-de-reverso, probado con mutante en las dos queries). El reverso de una NC es una ND de la misma letra, con la NC como comprobante asociado e importes 1:1. Script DBA obligatorio con este jar: replace-IX-Trx-v2-cae-fecha-con-nc.sql (el índice filtrado de la reconciliación estaba definido con ‘CAE’ literal; con el predicado nuevo el plan pasaba a scan completo, medido). El gate de paridad de índices falla a propósito nombrando el script hasta que se aplique. Alertas y logs dicen el tipo de comprobante. Riesgo aceptado §12: el reverso puede chocar con una NC del POS del mismo ticket y día (salto silencioso, backlog).NC_RECON_EMITIR_ND (nc.reconciliacion.emitir-notas-debito, default false). Con el gate apagado, F1 sigue detectando las NC del POS doble-facturadas y las lista en la alerta y en /nc como “requiere ND (emisión deshabilitada)”, con KPI de ND pendientes; F2 las omite y alerta cuántas quedaron sin revertir; los reversos ND ya creados y rechazados tampoco se re-emiten. Factura→NC y ND→NC sin cambio. Qué chequear antes de prenderlo: docs/qa-pendientes-activar.md.resultado (cada transición con escritor, lock, commit propio y archivo:línea), ciclo de un ticket V2, y respuestas SOAP al POS qa-20260825 vs actual contra el mismo simulador de ARCA (éxito, WSDL y headers idénticos; cambia solo lo decidido: null en vez de R ante ARCA caído, faults a 80 caracteres).registrarCaea). Se alerta una vez, con nombre del motivo (rechazo genuino vs correlatividad) y el tamaño del backlog detrás del bloqueante; antirruido entre corridas. El review probó que la primera versión (copiada de F01 con el 10016) marcaba ‘R’ en cascada toda la cola pendiente de un PV ante un hueco en la secuencia: BLOQUEADA y corregida. (2) La reparación de “informadas sin resultado” marcaba ‘A’ por rango (comprobante ≤ ultimoAutorizado) sin consultar ARCA; como el PV CAEA cae al PV CAE, un CAE online podía adelantar el rango y dar por informada una CAEA nunca informada. Ahora consulta FECompConsultar por fila (fuera de toda transacción, tope por corrida), con guardas locales (otra fila aprobada / doble huérfano), identidad cerrada (número + fecha + titularidad) y discriminador CAE/CAEA; solo si ARCA dice EmisionTipo≠CAEA cierra en ‘R’ terminal (“número ocupado por un CAE: regularización manual”); cualquier duda queda null. Escritura de ARCA fuera de la transacción del job; relectura con lock antes de escribir. Sin DDL. Precondición para confiar en la reparación: smoke contra homologación de FECompConsultar sobre un comprobante CAEA (ver docs/qa-pendientes-activar.md).incluir-rechazadas para las ‘R’ históricas. Defecto latente encontrado por review: con flush AUTO, la query nativa del applock (sp_getapplock) flusheaba la mutación que consultarComprobante deja en la entidad ANTES del refresh con lock y de las guardas de titularidad: en SQL Server el CAE (incluso ajeno) se escribía antes de verificarlo, y el test real-DB daba verde falso porque solo miraba el estado final. Invisible en H2 (lock en memoria). Fix: detach tras la consulta, find+refresh fresco bajo el lock, hint de no-flush en las queries del applock (defensa en profundidad), tests real-DB que asertan el desenlace y guard en H2 con un lock falso que hace SQL. Riesgo §13. Modo incluir-rechazadas (BARRIDO_NUM_INCLUIR_RECHAZADAS, OFF; requiere detectar): adjudica contra ARCA las ‘R’ escritas por el código viejo ante fallos de transporte. Solo detección por default (el veredicto sale del mismo camino de escritura con escribir=false: “R falsa” = exactamente lo que Fase B escribiría); con recuperar, R→A por el mismo helper. Excluye reversos NC (los adjudica F05) y CAEA. Titularidad fail-closed para las ‘R’. Ventana con cota superior para barrer la historia por tajadas; la pasada de una tajada termina solo con pendientes(cap)=0; tope de escrituras aparte (20/corrida); WARN por corrida mientras el flag esté prendido (es una herramienta de una sola pasada, apagar al terminar). Runbook en docs/qa-pendientes-activar.md. KPI rechazadasPendientes en la tarjeta del barrido.[10015] …. Ahora V2 devuelve las observaciones de ARCA curadas (código primero, ≤80 caracteres), transporte sigue con su señal + ref:. TrxErrorLog.referencia (antes null en todo lo AFIP) lleva trx:<id>;req:<reqId>: error ↔ comprobante ↔ línea del traffic.log. Sin DDL.@LockOwner='Transaction'. Antes el lock era de sesión (conexión JDBC): si la transacción reventaba al commitear, la conexión volvía al pool con el lock tomado y el siguiente emisor de ese PV comía 30 s de timeout en cada request hasta reiniciar el jar (reproducido en la comparación SOAP); y el finally lo liberaba antes del commit. Con owner de transacción SQL Server lo libera solo al COMMIT/ROLLBACK por cualquier camino. Gotcha medido: el driver abre la transacción de SQL Server recién en la primera sentencia que toca una tabla y un EXEC no cuenta (@@TRANCOUNT valía 0); se abre explícitamente antes del sp_getapplock. Evidencia real-DB rojo→verde: lock dentro de la tx, sin fuga tras un release fallido, sostenido hasta el commit. Riesgo §14 cerrado. Sin DDL.AfipCaeService.consultarComprobante copiaba siempre el resultado de ARCA sobre la entidad, y tres consumidores dependían de un detach para no adoptarlo (raíz de dos bugs de hoy). Ahora la consulta es pura y devuelve datos; la adopción es explícita con AfipCaeService.adoptar (estática, para que los tests que mockean el servicio no la anulen) en cinco sitios: emisión, V1 feCompConsultar, rama-3, reconciliación, barrido caso B. Cada sitio con su mutante. Invariante fijado por test: consultar sobre una entidad managed y correr una query nativa después no cambia la fila. Riesgo §13 actualizado: el detach ya no es de carga.CbteFchHsGen que el reenvío traía (loop 1441 irrompible). Los tres en corrección. Mergeado ya: jobs sin ejecución concurrente (@DisallowConcurrentExecution en los 6 jobs Quartz que escriben; la guarda previa salteaba la corrida tardía, la anotación la difiere, que es lo correcto para barrido y reconciliación; test con scheduler real y control negativo) y el javadoc del applock corregido: el lock colgado en el pool está CERRADO, el “release antes del commit” está COMPENSADO por la relectura con lock de cada escritor, que pasa a ser invariante obligatoria.--spring.config.location, que reemplaza el yml embebido: seis claves nuevas no estaban en deploy/application.yml y sus variables de entorno eran inertes en producción (el rebanado de las ‘R’ históricas, declarado obligatorio, hubiera sido un no-op silencioso). Agregadas, más el bloque de archivado; nuevo DeployYmlParityTest que exige que toda clave operativa del yml embebido exista en el de deploy con la misma variable y el mismo default. StartupConfigLogger imprime ahora los flags de barrido, reconciliación, reparación CAEA y archivado. Aviso (WARN + Telegram, máximo 1/día) cuando hay huérfanos con el barrido apagado (precondición del jar). La comparación SOAP declara el caso B como distinto desde el motivo V2. Índice del cockpit pasa a requerido; checklist DBA con orden explícito (el de mensajeCurado antes del jar; los índices en cualquier momento; los INSERT de Tareas al activar). Sección de rollback: flags OFF y reinicio antes de bajar el jar. ci-realdb.bat encadena la suite H2 y la capa real-DB. Lista de vigilancia en QA con el loop silencioso de §12.CbteFchHsGen y del trío suc/pos/ticket que el reenvío traía (RG 5782): la alerta pedía “reenviá corregido” y el reenvío no persistía → loop de obs 1441 irrompible. Ahora la corrección se commitea antes de llamar a ARCA (rojo→verde: expected "20260101120000" but was "19990101000000"), el desenlace se persiste aparte, y el lock de fila no se sostiene durante la llamada (el applock sí: es el mutex del dedup, soltarlo mandaba dos informes del mismo comprobante; acotado a 30 s). Respuesta al notificador byte-idéntica. Sin DDL.sp_getapplock). (2) Reconciliación al estándar F03/F05: nuevo NcEscrituraHelper, cada escritura en transacción propia con applock, relectura con lock y re-verificación (número y estado); el resultado de una NC nueva también sale de la transacción del job (rojo medido: la fila volvía al número viejo con ‘A’ y el CAE de ese número). Una falla de persistencia de una fila ya no envenena la corrida: se salta y sigue. (3) Rama-3: relectura con lock y re-verificación antes de adoptar; si la renumeraron mientras consultaba, cierra con error curado en vez de pisar. Riesgos §15 nuevo: las cinco reglas que todo escritor de resultado cumple y el catálogo de escritores. Real-DB completa: solo las 5 fallas conocidas, sin esperas de 30 s.AfipCaeService real y el límite transaccional real, asertando releyendo la fila (no el objeto en memoria); mutation-tested a mano: restaurar el setResultado("R") viejo rompe 5 tests. Suite 894/0/43. Se quitó también un setResultado("R") redundante en rama-3 y el agrupador CAEA filtra caea is not null para que las filas null nuevas no disparen consultas extra a ARCA.incluir-rechazadas del barrido (OFF por default) para adjudicarlas; es una herramienta de una sola pasada y se apaga al terminar. Runbook en docs/qa-pendientes-activar.md.GROUP BY en vez de traer todas las filas CAE V2; rate configurable. Índice opcional para el DBA.ref: del traffic.log, cap 80 caracteres, errores de mail también curados. Anexo de respuestas de error en este documento.resultado=null local), cuando el POS no reintentó ni facturó por CAEA. Cierra el hueco que ni rama-3 ni CRÍTICO-4 cubrían (sub/sobre-reporte fiscal). Guarda contra el número reusado y el doble-huérfano. Dos fases gateadas OFF (detectar / recuperar); corre antes del job de NC. KPI y alerta Telegram. (Plan 027)CondicionIVAReceptorId (que ARCA rechaza desde el 01/12/2026), con desglose por comercio/POS. Solo mide; no frena la emisión.resultado=null a propósito, condición necesaria para la detección de huérfanos.'R' solo con veredicto de ARCA; transporte, faults, Errors de request, 10016 y 'A' sin CAE quedan null = estado desconocido y los adjudica el barrido. Cierra de paso la doble anulación de NC por parpadeo de red.IN ('CAE','NC') AND trxoriginal IS NULL). Requiere el índice replace-IX-Trx-v2-cae-fecha-con-nc.sql.'R'; la reparación consulta FECompConsultar antes de marcar 'A'; el registro del POS commitea la corrección de CbteFchHsGen antes de llamar a ARCA (rompe el loop de obs 1441).@LockOwner='Transaction', consulta a ARCA no mutante, jobs Quartz sin ejecución concurrente, guardas de titularidad compartidas.TrxErrorLog.referencia, bloque de flags en StartupConfigLogger, DeployYmlParityTest y ci-realdb.bat. Gate de Notas de Débito OFF por default.Aplicar: scripts del DBA en orden (add-TrxErrorLog-mensajeCurado.sql antes del jar) + deploy/application.yml de esta release + swap de jar + activar el barrido Fase A y B (precondición dura, no opcional). Sin cambio de WSDL. Rollback: flags OFF y reinicio ANTES de tocar el jar.
@Scheduled (cada 5 min) que persiste el KPI en CockpitStat traía todas las filas CAE V2 crudas y agregaba en Java. Ahora agrega en SQL con GROUP BY (igual que posStats()); mismos números. Rate configurable: COCKPIT_SNAPSHOT_RATE_MS (default 300000).add-Trx-cockpit-posstats-index.sql (índice filtrado, SQL Server 2008+); vuelve el agregado index-only. No lo corre la app.Aplicar: swap de jar. Sin DDL obligatorio.
AFIP error BD: ORA-01033, AFIP HTTP 503, …) más un ref: con el reqId del traffic.log.resultado='R') y diagrama del flujo de email, en este mismo documento.Aplicar: swap de jar. Sin DDL. Sin cambio de WSDL.
Aplicar: swap de jar. KPI y PDF/email quedan activos solos; el barrido, inerte hasta cargar su fila en Tareas y prender los flags.
GET /api/libro-iva (X-Api-Key): todos los comprobantes autorizados del período (CAE + CAEA + NC/ND). Formatos arca (RG 4597), csv y json./api/cockpit/libro-iva.Aplicar: swap de jar. Sin DDL. Endpoints read-only.
resultado='A' y cae != null); si no, ERROR terminal EMAIL_NO_APROBADO. Candado único de las 4 vías de envío.management.tracing.enabled=false).Aplicar: swap de jar. Sin DDL. Config: agrega management.tracing.enabled=false y logging.pattern.correlation="" (ver anexo).
/api/ajustes-contingencia: detalle impositivo completo por comprobante (impuestos + alícuotas + tributos + datos del receptor). Aditivo.?formato=libro-iva — registros de ancho fijo del Libro de IVA Digital (RG 4597), verificados contra el PDF de AFIP.Aplicar: swap de jar. Sin DDL. Endpoints read-only.
/api/ajustes-contingencia acepta cuit y nroSucursal como filtros. Log más limpio (se vacía logging.pattern.correlation).setFlushMode(COMMIT) NO-OP. Sin cambio de comportamiento.Aplicar: swap de jar. Sin DDL. Punto de partida de esta guía.
Todas read-only, no tocan el money-path de emisión. Fechas en YYYYMMDD. Reemplazá
HOST y <api-key> por los de tu entorno.
Los comprobantes que el gateway emitió por su cuenta y el BackOffice necesita para cerrar su Libro de IVA: el CAE original (duplicado), la NC/ND que lo anula, y el CAEA que es la venta real impresa. El neteo cierra (CAE + CAEA − NC = CAEA); el valor es de registro. Cada comprobante trae su detalle impositivo completo.
Auth: header X-Api-Key: <api-key> · ventana máx. 92 días.
| Parámetro | Req. | Descripción |
|---|---|---|
desde | sí | Inicio del período (YYYYMMDD). |
hasta | sí | Fin del período (YYYYMMDD). |
cuit | — | Filtra por comercio (con o sin guiones; se normaliza a dígitos). |
nroSucursal | — | Filtra por sucursal. |
ente | — | Filtra por id interno de EnteFacturador (compatibilidad). |
formato | — | json (default) · libro-iva (registros ancho fijo RG 4597). |
curl -H "X-Api-Key: <api-key>" \ "https://HOST/api/ajustes-contingencia?desde=20260901&hasta=20260930&cuit=30712434763"
[
{
"ticket": { "suc": 1, "pos": 2, "nroTicket": "T-0001" },
"caeOriginal": {
"ptoVta": 559, "nro": 301882, "fecha": "20260901",
"cae": "75130212345601", "tipoComprobante": 6, "importeTotal": 1234.56,
"docTipo": 99, "docNro": 0, "condicionIvaReceptorId": 5,
"impuestos": {
"netoGravado": 1020.30, "noGravado": 0.0, "exento": 0.0,
"iva": 214.26, "tributos": 0.0, "total": 1234.56,
"ivaAlicuotas": [ { "id": 5, "baseImponible": 1020.30, "importe": 214.26 } ],
"tributosDetalle": []
}
},
"ncAnulacion": { "ptoVta": 559, "nro": 45, "fecha": "20260902", "cae": "75130298765401",
"tipoComprobante": 8, "importeTotal": 1234.56, "docTipo": 99, "docNro": 0,
"condicionIvaReceptorId": 5, "impuestos": { "…": "idem estructura" } },
"caeaImpreso": { "ptoVta": 560, "nro": 12000, "caea": "26123456789012",
"fchTope": "20260910", "estadoInforme": "INFORMADO",
"impuestos": { "…": "idem estructura" } },
"motivo": "doble facturación CAE+CAEA de contingencia",
"crossPeriodo": false,
"estado": "CERRADO"
}
]
Ids AFIP: tipoComprobante 6=Fac B, 8=NC B (1/3=A, 11/13=C, 51/53=M) · alícuota IVA 3=0%, 4=10,5%, 5=21%, 6=27%, 8=5%, 9=2,5% · docTipo 80=CUIT, 96=DNI, 99=Cons.Final.
?formato=libro-ivaMismos filtros; devuelve los registros de ancho fijo del Libro de IVA Digital (RG 4597):
curl -H "X-Api-Key: <api-key>" \
"https://HOST/api/ajustes-contingencia?desde=20260901&hasta=20260930&formato=libro-iva"
→ { "ventasCbte": [ "…registro de 266 caracteres por comprobante…" ],
"ventasAlicuotas": [ "…registro de 62 caracteres por alícuota…" ] }
A diferencia de (A), devuelve todos los comprobantes autorizados por AFIP (CAE online +
CAEA offline + NC/ND), no solo la tripla de contingencia. Fuente: la tabla Trx
(canal único).
Auth: header X-Api-Key: <api-key> · ventana máx. 366 días.
| Parámetro | Req. | Descripción |
|---|---|---|
desde | sí | Inicio del período (YYYYMMDD). |
hasta | sí | Fin del período (YYYYMMDD). |
cuit | — | Filtra por comercio (con o sin guiones). |
nroSucursal | — | Filtra por sucursal. |
ente | — | Filtra por id interno de EnteFacturador. |
formato | — | Ver opciones abajo. |
formatoarca (default) — JSON con las líneas de ancho fijo RG 4597 (ventasCbte 266 / ventasAlicuotas 62).json — un objeto estructurado por comprobante (ver ejemplo).csv — adjunto .zip con ventas_cbte.csv + ventas_alicuotas.csv.formato=jsoncurl -H "X-Api-Key: <api-key>" \
"https://HOST/api/libro-iva?desde=20260901&hasta=20260930&formato=json"
→ [
{
"fecha": "20260901", "tipoComprobante": 6, "ptoVta": 559, "nroComprobante": 301882,
"tipofacturacion": "CAE", "cae": "75130212345601", "caea": null,
"docTipo": 99, "docNro": 0, "razonSocial": "Consumidor Final",
"importeNeto": 1020.30, "importeNoGravado": 0.0, "importeExento": 0.0,
"importeIva": 214.26, "importeTributos": 0.0, "importeTotal": 1234.56,
"iva": [ { "id": 5, "baseImponible": 1020.30, "importe": 214.26 } ]
}
]
formato=csv (baja un .zip)curl -H "X-Api-Key: <api-key>" -OJ \ "https://HOST/api/libro-iva?desde=20260901&hasta=20260930&formato=csv" # → libro-iva-20260901-20260930.zip (ventas_cbte.csv + ventas_alicuotas.csv)
Mismo contenido que (B) pero con la auth normal del cockpit (sesión), sin la
X-Api-Key. Siempre devuelve un archivo descargable. Es lo que usa el botón
“Descargar” de la sección Libro de IVA (ventas).
Auth: sesión del cockpit (no requiere X-Api-Key ni el secret del ABM).
Iguales a (B): desde, hasta (req.), cuit, nroSucursal, ente, formato.
arca / csv → descarga .zip.json → descarga .json.
Actualización acumulada qa-20260825 → qa-20260908. Hasta qa-20260907e todas las
releases eran swap de jar sin DDL. Con qa-20260908 el orden cambia: hay un script del DBA que
bloquea al jar y hay que correrlo antes, el resto son índices aditivos, y la activación del barrido de
numeración pasa a ser precondición dura. Sigue sin haber ningún cambio de WSDL y la app no ejecuta DDL.
Todos los scripts están en src/main/resources/db/migration/. El primero bloquea al jar; los otros cuatro son aditivos y pueden correrse con el jar ya arriba (conviene igual una ventana de bajo tráfico por el costo de construir cada índice).
add-TrxErrorLog-mensajeCurado.sql — ANTES de desplegar el jar (bloqueante). El jar escribe esa columna al curar el error que ve el POS; sin ella el INSERT del log de errores falla.add-EntesFacturadores-cuit-unique.sql — sostiene el contrato POS-por-cuit. Requisito: sin cuits duplicados (el script trae la query de chequeo).add-Trx-hot-query-indexes.sql.replace-IX-Trx-v2-cae-fecha-con-nc.sql — obligatorio con este jar (F04): supersede a IX_Trx_v2_cae_fecha y sin él la consulta de reconciliación pasa a escanear el clustered de Trx.add-Trx-cockpit-posstats-index.sql — apoyo del snapshot del cockpit y de posStats. Ya no es opcional.Los cuatro índices los verifica por nombre RealDbIndexParityTest (-Drealdb=true): mientras falte alguno esa clase queda roja nombrándolo, así que el checklist es ejecutable. Los INSERT en Tareas (barrido y NC) van recién al activar los jobs, no acá.
Usar el deploy/application.yml de esta release. En producción el arranque usa --spring.config.location, que reemplaza al yml embebido en el jar: una clave que falte ahí no toma el default del jar, y su variable de entorno queda inerte. Claves nuevas de esta release (todas OFF o conservadoras por default):
BARRIDO_NUM_INCLUIR_RECHAZADAS # backfill de las 'R' históricas (una pasada) BARRIDO_NUM_RECHAZADAS_HASTA_DIAS # cota superior de la rebanada de 'R' BARRIDO_NUM_MAX_RECUPERACIONES_R # tope de escrituras R -> 'A' por corrida BARRIDO_NUM_AVISO_HUERFANOS_MS # aviso mientras el barrido esté apagado NC_RECON_EMITIR_ND # gate de Notas de Débito (false) NC_RECON_VENTANA_DIAS_REVERSOS # ventana de re-emisión de reversos 'R' (92) ARCHIVADO_TRX_* # bloque de archivado recurrente de Trx
Si mantenés tu YAML externo anterior (de qa-20260825), agregá además estas dos claves que entraron con BootUI 1.16 (qa-20260907):
management:
tracing:
enabled: false # BootUI 1.16 trae OpenTelemetry; lo dejamos inerte
logging:
pattern:
correlation: "" # evita el bloque [ ] vacío en cada línea de log
DeployYmlParityTest exige que toda clave operativa del yml embebido exista en deploy/application.yml con la misma variable y el mismo default, así que este desfasaje no se repite.
Descargar el asset tifactura-spring-20260908.jar de la release y renombrarlo a tifactura-spring.jar.
nssm stop TipreTiFactura copy /y D:\...\tifactura-spring.jar D:\...\backup\tifactura-spring.PREV-<fecha>.jar :: reemplazar el jar por el nuevo nssm start TipreTiFactura
Al arrancar, leer el bloque “CONFIG EFECTIVA AL ARRANQUE” del log — incluye ahora la sección “JOBS / FLAGS OPERATIVOS” (barrido, reconciliación, reparación CAEA, archivado). Confirmar que los flags leídos son los esperados antes de seguir.
Hasta qa-20260907e era opcional. Con F01, el barrido es el único mecanismo que resuelve las filas que quedan en resultado=null (CAE y NC) cuando ARCA no dio veredicto; sin él se acumulan y las NC afectadas quedan trabadas. Mientras esté apagado y haya huérfanos, el jar avisa (WARN + Telegram, máximo 1 por día). Se activa en orden:
a. Correr el INSERT en Tareas (una vez). El script está en el repo o dentro del jar:
jar xf tifactura-spring.jar BOOT-INF/classes/db/migration/schedule-barrido-numeracion-job.sql
b. La sección de config es numeracion.barrido en application.yml — así viene por default (cada clave enlazada a una variable de entorno con su valor por default):
numeracion:
barrido:
detectar-enabled: ${BARRIDO_NUM_DETECTAR:false} # Fase A: detecta + alerta (solo lectura)
recuperar-enabled: ${BARRIDO_NUM_RECUPERAR:false} # Fase B: recupera (escribe) — requiere Fase A
ventana-dias: ${BARRIDO_NUM_VENTANA_DIAS:35} # días hacia atrás por comprobanteFecha
max-por-corrida: ${BARRIDO_NUM_MAX:200} # tope de consultas a ARCA por corrida
Cada clave se puede prender de dos formas equivalentes (elegí una):
BARRIDO_NUM_DETECTAR=true, luego BARRIDO_NUM_RECUPERAR=true.detectar-enabled: true y luego recuperar-enabled: true en ese bloque.c. Secuencia: primero Fase A (detectar-enabled=true, recuperar en false) → reiniciar → mirar la tarjeta “Barrido de numeración” del cockpit y las alertas unos días. Después Fase B (recuperar-enabled=true) → reiniciar.
d. Backfill de las 'R' históricas (aparte y posterior): BARRIDO_NUM_INCLUIR_RECHAZADAS=true adjudica contra ARCA las 'R' que el código viejo escribió ante fallos de transporte. Es una herramienta de una sola pasada —prender, leer el resumen, recuperar una rebanada con BARRIDO_NUM_RECHAZADAS_HASTA_DIAS, apagar—, con su propio tope de escrituras (BARRIDO_NUM_MAX_RECUPERACIONES_R, default 20) y un WARN por corrida mientras esté prendida. Runbook completo en docs/qa-pendientes-activar.md.
a. INSERT en Tareas con schedule-nc-job.sql (02:30, después del barrido).
b. F1 primero (NC_RECON_IDENTIFICAR=true): detecta y alerta, no emite. Con F04 hay que esperar más candidatos que antes — las NC del POS doble-facturadas ya existían y no se estaban contando.
c. F2 después (NC_RECON_EMITIR=true), cuando los números de F1 cierren. NC_RECON_EMITIR_ND queda en false (decisión del titular: la reconciliación no emite Notas de Débito sola). Antes de prender F2, contar los reversos 'R' más viejos que NC_RECON_VENTANA_DIAS_REVERSOS (92 días): esos no se re-emiten solos y hay que mirarlos a mano.
d. Antes de confiar en la reparación CAEA: smoke contra homologación de FECompConsultar sobre un comprobante informado con CAEA, confirmando que CodAutorizacion sea el CAEA y EmisionTipo diga CAEA. No se puede validar contra el mock.
GET /api/cockpit/barrido-numeracion y GET /api/cockpit/condicion-iva-receptor devuelven JSON.ci-realdb.bat contra la base real: se esperan 5 rojos ambientales conocidos (4 del cockpit por falta de certificado y el gate de paridad de índices mientras falte el índice de F04).1. Primero los flags, y reiniciar. BARRIDO_NUM_DETECTAR, BARRIDO_NUM_RECUPERAR, BARRIDO_NUM_INCLUIR_RECHAZADAS, NC_RECON_IDENTIFICAR, NC_RECON_EMITIR, NC_RECON_EMITIR_ND en false. Los jobs quedan agendados pero no escriben ni llaman a ARCA; en la mayoría de los casos el rollback termina acá y el jar se queda.
2. Recién después, si de verdad hace falta, revertir el jar. El jar viejo no tiene F02/F03 y su barrido escribe sin re-verificar la clasificación contra el estado fresco de la fila: ponerlo a correr contra la población de huérfanos —más grande— que deja esta release es la peor combinación posible. Con los flags ya apagados entra inerte y el orden deja de importar.
3. El DDL no se revierte. Las migraciones son aditivas y el jar viejo convive con ellas: el filtro del índice nuevo es un superconjunto del anterior. Los comprobantes ya recuperados quedan (son válidos).
hbm2ddl=none, el esquema lo administra el cliente. Los .sql de db/migration/ los aplica el DBA. Novedad de qa-20260908: uno de ellos (add-TrxErrorLog-mensajeCurado.sql) bloquea al jar y va antes del deploy; el resto son índices aditivos.Catálogo de las respuestas con error que recibe el cliente (SOAP faults + Response con resultado=R), el faultstring de cada caso (CAE/AFIP + mail, con el fix de claridad aplicado) y el diagrama del flujo de envío de email. Documento self-contained; también suelto en docs/errores-respuesta-cliente.html.