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)684af93
PRs #78 · #79 · #80 · #82
jar: usar qa-20260808c
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 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 · main 684af93 · suite 549/0/16 ·
PRs #78 (build-info) · #79 (yml) · #80 (fix huérfanos R) · #82 (bot versión + /nc + ABM 409) · jar qa-20260808c ·
§6 marca de relación NC↔CAE + dedup (sin cambio de código; comportamiento vigente de PR#76/#80) ·
anterior: reconciliacion-cae-caea-nc-nd.html (PRs #73–#77)