20260808/nc read-only, config de reconciliación en el yml, mensaje claro del 409 en el ABM, y el fix crítico de huérfanos R (pre-requisito para F1)qa-20260810
WSDL sin cambios
qa-20260808c (main 684af93).
nc.reconciliacion (§3) y revisar el checklist §6.Badge en el header del cockpit, junto a PRODUCCIÓN / PROD·monitoreo:
El jar registra su propia fecha de compilación (META-INF/build-info.properties, goal build-info del plugin de Spring Boot — sin dependencias nuevas). Sale también:
GET /api/cockpit/config (version, buildTime) — útil para el futuro tablero central./status y el mensaje de arranque (🟢 TiFactura INICIADO — PRODUCCION — 🏷 2.0.0-SNAPSHOT · build …) — referencia inmediata de qué jar corre en cada server.La hora se muestra en la zona del server (build UTC convertido). En dev sin package muestra dev. Se acabó el "¿qué versión corre acá?".
El bloque va en el application.yml del server (config externa) — sin NSSM ni rebuild:
Precedencia: si la env var existe, gana; si no, vale el default del yml. Para prender F1 editás el default a ...:true} y reiniciás. Modo "solo avisar por Telegram" = F1 true + F2 false.
Tareas:
Detectado con data real de prod (10 casos en 3 semanas): comparando el conteo de tickets duplicados (50) contra la detección de dobles (40), los 10 excluidos eran todos el mismo patrón: CAE rechazado por ARCA (R) + CAEA aprobada. Eso es la contingencia sana — ARCA rechazó el CAE, el POS facturó por CAEA, el único comprobante válido es el CAEA. No corresponde NC.
La rama de "huérfanos" del job (CRÍTICO-4) incluía los R además de los NULL. El problema: tras un rechazo, ARCA no consume el número → la venta siguiente lo reusa. Con F1 prendida el job habría consultado ARCA por ese número, recibido el comprobante aprobado de OTRA venta, pisado la fila R con un CAE ajeno (se persistía con F1 sola), y con F2 emitido una NC contra el comprobante equivocado — anulando fiscalmente una venta legítima.
Huérfano = resultado IS NULL únicamente (respuesta de ARCA perdida — la intención original de CRÍTICO-4). Una R persistida es respuesta definitiva: ni se consulta, ni se pisa, ni se emite. Test dedicado que fija la invariante con la fila R intacta.
qa-20260808b: el "a" (y anteriores) tienen la rama vieja. No prender F1 sin este fix en ningún server con rechazos CAE en la ventana.Read-only, la misma lógica del job F1. Cada fila = CAE aprobado con doble CAEA, candidato a NC:
Cómo leerla: NO incluye los CAE rechazados (R) — contingencia sana, sin NC. Los huérfanos (resultado IS NULL) los confirma solo el job contra ARCA (CRÍTICO-4). Necesita que las filas CAEA tengan el trío suc/pos/ticket (notificador nuevo).
/nc PR#82Read-only puro: no ejecuta CRÍTICO-4, no consulta ARCA, no persiste — y funciona con el job deshabilitado o sin la fila en Tareas. Ideal para mirar antes de prender F1/F2.
Cuando F2 emite el comprobante compensatorio sobre un CAE que también tuvo CAEA, la marca de la relación vive en la fila de reverso, NO en el CAE original. El CAE de venta queda intacto (resultado='A', su número) — no se le escribe nada. El vínculo es direccional: reverso → CAE (el hijo apunta al padre).
El tipo que anula fiscalmente depende del comprobante original (MAPA_REVERSO, por tipoComprobante):
Es decir: el reverso es una ND cuando el comprobante original es una NC (típicamente una NC que el POS emitió online, se quedó sin respuesta y se terminó facturando por CAEA). Anular esa NC de más es una ND — nunca una "NC de la NC" (eso sería re-cobrarle al cliente).
tipofacturacion='NC' es un MARCADOR de "esto es un reverso", no el tipo fiscal. El tipo fiscal real —NC (3/8/13/53) o ND (2/7/12/52)— va en tipoComprobante. Por eso hasta una ND queda tagueada tipofacturacion='NC', y un solo guard de dedup cubre las dos.| Campo en la fila de reverso | Qué es | Para qué sirve |
|---|---|---|
trxoriginal = cae.id | link interno al id de la Trx del CAE original | es lo que usa el dedup del job |
tipofacturacion = 'NC' | marca de "reverso de reconciliación" (el tipo fiscal real —NC o ND— va en tipoComprobante) | un mismo guard cubre NC y ND |
ComprobanteAsociado (CbtesAsoc) | link fiscal que exige AFIP: tipo/pv/nro/fecha del CAE acreditado | la relación del lado ARCA |
El finder (y la query de §5) cierra con un NOT EXISTS que descarta todo CAE que ya tenga un reverso apuntándolo. Recordá que el marcador tipofacturacion='NC' vale para NC y ND, así que este mismo guard cubre los dos casos:
O sea: la existencia de la fila de reverso ES la señal de "ya reconciliado" — no hay un flag aparte en el CAE. En cuanto existe el reverso con trxoriginal = cae.id, el CAE deja de ser candidato.
trxoriginal ya seteado) se comitea ANTES de emitir a AFIP (persist-before-emit, tx REQUIRES_NEW). Si la emisión a ARCA o el commit posterior fallan, la fila ya quedó grabada → la corrida siguiente la ve por el NOT EXISTS → nunca se emite un segundo reverso fiscal (NC o ND) contra el mismo CAE.Trx, el CAE vuelve a ser candidato y el sistema la re-emitiría. Para el job está perfecto; solo importa si hacés limpieza manual de Trx.| Ítem | Qué | Prioridad |
|---|---|---|
telegram.alert-check-ms | Son milisegundos: con 60 el chequeo corre cada 60 ms (~16 veces/seg). Debe ser 60000. | 🔴 corregir ya |
cockpit.stats-window-days | Si falta, el default es 0 = sin límite → full scan del histórico en cada apertura. Poner 90. | 🟠 |
nc.reconciliacion | Bloque de §3 (apagado por default, visible y manejable). | 🟡 |
archivado-trx | Bloque del archivado (enabled/retencion/batch/max-filas), apagado por default. | 🟡 |
afip.tls-insecure | El valor true = trust-all (NO verifica); que el comentario no diga lo contrario. Con certs válidos de ARCA prod, false es lo deseable. | 🟡 decidir |
email: + spring.mail: | Solo si van a usar el envío de facturas (default del código: apagado). | ⚪ opcional |
| Secretos | Todos por env var sin default (${DB_USER}, ${TELEGRAM_BOT_TOKEN}, …), nunca literales. | 🟡 |
Tras editar: reiniciar y verificar con el bloque CONFIG EFECTIVA AL ARRANQUE del log.
Tareas| JobProcessor | Qué hace | ¿Gated? | Nota |
|---|---|---|---|
TokenManagerJob | TA de WSAA (renueva cada 5') | — | legacy, ya presente |
CaeaManagerJob (6 filas) | obtención de CAEA por quincena | — | legacy, ya presente |
ComprobantesEmitidosJob | informe batch de CAEA sin informar (resultado=null) | no | opcional si TODO el CAEA entra por el notificador REST (verificar: findstr saveTrx traffic.log sin resultados) |
PvSinMovimientosJob | informa PV CAEA sin movimientos por quincena | no | sí va — nadie más lo hace (obligación del régimen) |
NotaCreditoReconciliacionJob | F1 identifica dobles + Telegram / F2 emite NC-ND | sí (OFF) | prender F1 recién con qa-20260808b |
ArchivadoTrxJob | archiva Trx viejas a bak_ | sí (OFF) | requiere add-bak-tables-Trx.sql antes de habilitar |
qa-20260808c (pre-requisito §4). Desde ya podés espiar los candidatos con /nc en el bot (§5), sin activar nada.SELECT COUNT(*) FROM Trx WHERE tipofacturacion='CAEA' AND nroTicketPos IS NOT NULL > 0.NC_RECONCILIACION en Tareas (§3).identificar-enabled: ${NC_RECON_IDENTIFICAR:true} (F2 queda false) + reiniciar.emitir-enabled → true y F2 emite el reverso (NC/ND) solo.Al prender F2 en un comercio de Servicios, AFIP rechazó la NC (CAE id=762, tipo=8) con dos observaciones. Las dos eran bugs de cómo el servicio armaba el pedido — no un problema de plazo (NO hay límite de 10 días para emitir la NC):
| Obs AFIP | Causa | Fix |
|---|---|---|
10016 CbteFch fuera de rango N±10 | La NC salía con la fecha del CAE original (vieja). AFIP valida que el CbteFch de la NC (comprobante nuevo) esté dentro de HOY±10 días. | CbteFch de la NC = hoy. El vínculo con el original va por CbtesAsoc, no por la fecha. |
10049 faltan FchServDesde/Hasta/VtoPago | El original es Concepto 2/3 (Servicios); la NC hereda el concepto pero no copiaba las fechas de servicio → obligatorias. | Con Concepto 2/3: FchServDesde/Hasta = las del original (período real); FchVtoPago = hoy (≥ CbteFch). |
(ente,ticket,suc,pos,tipo,comprobanteFecha,tipofacturacion). Como CbteFch ahora es hoy, un segundo fallo el mismo día chocaría con la fila R de hoy (se loguea y salta, se reintenta al otro día). El primer reintento —que es el caso real— usa fecha distinta a la fila R vieja, así que entra limpio.Tests nuevos: NC con CbteFch=hoy + FchServ copiadas (Concepto 3), y reverso R que no bloquea y se reintenta. Suite 551/0/16.
Al guardar/crear un PV que colisiona, el cockpit mostraba Error al guardar PV: HTTP 409 pelado. El backend ya mandaba el motivo en el body — ahora el front lo muestra:
TiFactura · release técnico 20260808 · suite 551/0/16 ·
PRs #78 (build-info) · #79 (yml) · #80 (fix huérfanos R) · #82 (bot versión + /nc + ABM 409) · §10 fix emisión NC/ND (CbteFch + Concepto 2/3 + retry R) · jar qa-20260810 ·
§6 marca de relación NC↔CAE + dedup ·
anterior: reconciliacion-cae-caea-nc-nd.html (PRs #73–#77)