97707e4)Los tres PRs endurecen y simplifican la reconciliación CAE → CAEA de TsTicket contra el server de facturación, y llevan ese código al nivel de calidad de money path: tests directos, mutation testing y un chequeo de titularidad que antes no existía.
getCaea por suc/pos y feCompUltimoAutorizado antes de emitir.nroTicketPos; el CAE se reutiliza solo si es del ticket y está aprobado.CAE,CAEA, cualquier falla del lado CAE termina en CAEA. La reconciliación existe solo para reutilizar un CAE que AFIP realmente otorgó y evitar notas de crédito después.Son PRs encadenados. Mergear en este orden, borrando la rama base en cada paso para que GitHub reapunte el siguiente a main:
feCompConsultar devuelve nroTicketPos; el CAE recuperado se aplica solo si coincide con el del ticket (solo dígitos, ceros a la izquierda ignorados). Si falta o difiere: warn y CAEA.fecaeSolicitar (claves V2, sin comprobante/ptoVta), parseo con XML golden, rechazo por trxErrors, y las cuatro ramas de clasificación de fallos HTTP. Usa un WebClient con exchangeFunction stub, sin dependencias nuevas.consultarComprobante, propagación de nroTicketPos, casos fallback-safe de resolverPtoVtaCae, campos obligatorios de autorizarCae.CaeService.validateInputs.TicketService.caeBelongsToTicket / normalizeTicketNumber: la política "ausente = ajeno" es intencional.TicketServiceTest modificados: el caso "own ticket" ahora setea nroTicketPos en ambos lados.git add -f: .gitignore línea 3 (test/) también tapa src/test/. Pendiente anclarlo a /test/ en otro cambio.feCompConsultar: enteFacturador, nroTicketPos, nroSuc, nroPos, tipoComprobante.id, comprobanteFecha. Misma clave que findByNextGenKey en el server. No manda ptoVta ni comprobante (el server rechaza las dos claves juntas).resolverPtoVtaCae, getCaea(cuit, suc, pos), consultarUltimoAutorizado y sus DTOs. El refresco diario de CAEA sigue usando getCaea(cuit) sin cambios.and not(*)). La respuesta del server tiene <cae> como contenedor y como hoja; una respuesta no aprobada devolvía el texto concatenado del contenedor en vez de null.resolveComprobanteFecha, buildTipoComprobanteRef).resultado=null se lee como no aprobada y el POS va a CAEA, igual que antes; el job diario de notas de crédito del server limpia ese caso.mutation (PIT 1.30.0 + plugin JUnit 5) no ligado al ciclo de vida, y JaCoCo como reporte sin gate. Mismo criterio que el server TIFacturaOnlineNext.CaeService, CaeSoapClient, el nuevo FacturacionServiceTest, y los métodos fiscales de TicketService. Todos afirman comportamiento: valores parseados, tipos de excepción, contenido del JSON de auditoría, forma del envelope XML.facturarConCAEA armaba el JSON de auditoría con Map.of("error", e.getMessage()). Map.of lanza NullPointerException con un valor null, así que un fallo de CAEA con excepción sin mensaje reventaba con un NPE ajeno en vez de salir como TicketException. Corregido con Collections.singletonMap y sustitución de null por cadena vacía, con test que falla primero.37a0caf: que el mismo patrón Map.of con valor posiblemente null no quede en otro lado del camino fiscal.mutation NO corra en mvn test ni mvn verify, y que JaCoCo no tenga goal check que rompa CI.Estado en main antes de los PRs. Dos viajes antes de cada emisión, y recuperación por el último número autorizado del punto de venta, sin verificar de quién es.
Cero viajes previos. Ante una falla, una única consulta por la identidad del ticket. El server responde desde su base; el POS reutiliza el CAE solo si es propio y aprobado.
PIT 1.30.0 con mutadores por defecto, perfil Maven mutation no ligado al ciclo de vida (igual que en TIFacturaOnlineNext). Corrida: ./mvnw -Pmutation test-compile org.pitest:pitest-maven:mutationCoverage, reporte en target/pit-reports/index.html.
| Clase | Mutantes | Score antes | Score después |
|---|---|---|---|
CaeService | 112 | 87.5% | 93.8% |
CaeSoapClient | 110 | 83.6% | 93.6% |
FacturacionService | 19 | 94.7% | 94.7% |
TicketService (solo métodos fiscales) | 61 | 66.7% | 86.9% |
Los 8 mutantes que quedan vivos están documentados como equivalentes o código inalcanzable (guardas de null duplicadas aguas arriba, ternarios que su callee ya neutraliza, o artefactos de bytecode duplicado de PIT sobre condiciones compuestas ya cubiertas). Varios se verificaron forzando la condición en producción y confirmando que la suite completa sigue pasando. Suite completa: 228 tests, 0 fallos, 1 skip preexistente. El perfil mutation no corre en el build normal; la corrida de PIT toma ~170 s.
NullPointerException que enmascaraba un fallo de CAEA sin mensaje. Fue corregido en este PR (fix(cae): 37a0caf). Justamente lo que el mutation testing busca: código que la cobertura de línea daba por probado pero ningún assert miraba.| Hecho | Dónde está en TIFacturaOnlineNext |
|---|---|
El ruteo V1/V2 es por datos: nroTicketPos presente → V2; comprobante ≠ 0 → V1; ambos → fault. | ws/endpoint/TfomWsEndpoint.java L132-153 |
La consulta V2 es solo base de datos, nunca AFIP. No encontrado → trx sin cae, bloque <cae> omitido. | action/ws/FacturacionService.feCompomprobanteFacturadoNextGen |
caeFromTrx no copia resultado al bloque <cae>; la aprobación se lee de <trx><resultado>. | ParentEntityManager#caeFromTrx |
Un punto de venta por POS: índice único UQ_SucPosPV_pvcae. | db/migration/add-unique-indexes-SucPosPV.sql |
La emisión V2 persiste el número reservado ANTES de llamar a AFIP; respuesta perdida → resultado=null. Reintento idempotente por nroTicketPos (ramas 2 y 3). | action/ws/FacturacionService.java ~L392-460 |
| Job diario CAE↔CAEA: detecta doble facturación por identidad de ticket y emite la NC del CAE; consulta ARCA para huérfanos. | action/ws/NotaCreditoReconciliacionService.java |
Desde b7beec3 (2026-09-08) los rechazos V2 llegan con la observación real de ARCA; el texto genérico "Error al emitir CAE en AFIP" queda solo sin observación. | git show b7beec3 |
nroTicketPos es xs:string en el WSDL: el server devuelve lo que se envió. | wsdl/TiFacturaOnlineManagerWS.wsdl L120 |
Documento de handoff del server para exactamente esta revisión: docs/handoff-clientes-gateway.md.
fecaeSolicitar con el mismo nroTicketPos haría que el server consulte ARCA por el número reservado y recupere el CAE de una respuesta perdida, pero también puede re-emitir. Hoy el cliente solo consulta la base. Es una decisión de producto.app.ticket.facturacion.mode es una propiedad por JVM, no por comercio ni terminal.trx/cae preceda al contenedor <cae> en el propOrder del server. No está fijado por ningún test de contrato en TsTicket.java.version=21 del pom).