PCPOS · Informe técnico consolidado

Cinco mejoras de integridad para facturación y operación POS

Fecha: 13/07/2026. Documento autocontenido de alcance, causa, corrección y evidencia reproducible para numeración fiscal, cálculo de IVA, cierre de gaveta, recuperación de pagos QR y totalización/sincronización de cierres FINZD.

Resumen ejecutivo

Las cinco mejoras protegen puntos donde un dato correcto podía perderse, quedar obsoleto o publicarse como exitoso sin estar sincronizado entre el cálculo, la memoria de trabajo, la persistencia y el servidor. Se entregan como paquetes separados, compatibles con nombres DOS 8.3 y verificados con Borland C++ 3.1/BCC286.

Persistencia

iNroComprob

Conserva el número fiscal no nulo desde bT_TResu hasta dE_TResu y evita sustituirlo por el ticket POS.

Ver mejora 1

Cálculo fiscal

Neto e IVA 10,5%

Incluye el neto negativo de la bonificación mayorista en la estructura enviada a autorizar.

Ver mejora 2

Periférico

Cierre de gaveta

Actualiza el estado físico durante la espera, con frecuencia limitada y validación estricta del byte recibido.

Ver mejora 3

Recuperación QR

Terminal REFUND_DENEGADO

Evita que el retry omita un estado terminal y repita consultas innecesarias.

Ver mejora 4

Cierre FINZD

Total de venta y sincronización

Calcula el total por ventas de la Y cerrada, realiza el upsert sobre dE_FinZd y distingue fallo local de publicación remota pendiente.

Ver mejora 5

Resultado común. Los originales del árbol no fueron editados por estos entregables. Cada paquete contiene fuente completa modificada o parche, harness y evidencia de compilación legacy; FINZDTV agrega además fixtures RED inmutables y verificadas por hash.

1. Protección de iNroComprob entre memoria y disco

Incidente
Ticket POS 371637; autorización CAE con comprobante fiscal 1.
Síntoma
dE_TResu.iNroComprob podía recibir el número incremental del ticket en lugar del número fiscal ya obtenido.
Causa
En F2Disk_TResu, el fallback a CurrTick no expresaba ni verificaba la procedencia del dato antes de persistir.
Corrección
Resolver por procedencia y exigir una postcondición antes de SAppend: una fuente fiscal no nula debe conservarse sin cambios.

Regla de decisión

La protección de una fuente no nula se aplica de igual manera a comprobantes obtenidos por CAE o CAEA; el guard no decide el método de autorización.

Flujo protegido de iNroComprobLa fuente no nula se conserva; el fallback CurrTick sólo se permite en el perfil no fiscal sin CAEA; una discordancia bloquea la persistencia. De bT_TResu a dE_TResu: procedencia + postcondición Fuente iNroComprobmbase / ETres ¿Fuente != 0?Sí: preservar exacto Asignar al destinosin usar CurrTick Postcondiciónigualdad → append No: fuente cero¿No fiscal y CAEA off?Sí: fallback CurrTick Caso fiscallog + rechazo

Paquete: nroguard.zip. El harness cubre siete escenarios y la fuente completa fue compilada con la configuración histórica POSTCPNU.CFG: BUILD_RC=0, cero errores y tres warnings históricos en la compilación completa.

2. Neto e IVA con bonificación mayorista

Incidente
Ticket 371649, transacción 280006.
Respuesta AFIP
Errores 10048 (ImpTotal no coincide con la suma de componentes) y 10051 (alícuotas IVA inconsistentes).
Causa puntual
En pos_inve.cpp, línea investigada 4646, ImprimirIVAS acumulaba _dNetoIVA105 en vez del neto mayorista devuelto en dMayNeto105.
Corrección
Cambiar una selección de variable: dNetoIVA105 += dMayNeto105.
Neto positivo 7287,9511 Bonificación mayorista neta -634,8869 ---------- ImpNeto corregido 6653,0642 ImpIVA corregido 1019,3858 ---------- ImpTotal 7672,4500
Cálculo de neto e IVA corregidoLos importes positivos y la bonificación mayorista negativa convergen en el neto y el IVA corregidos, cuya suma coincide con el total. La bonificación participa en base e impuesto Items positivosNeto 7287,9511 BON.MAY. (10,5%)Neto -634,8869 · IVA -66,6631 Acumuladores correctosImpNeto 6653,0642ImpIVA 1019,3858 ImpTotal 7672,4500Neto + IVA = Total
Ruteo fiscal fuera de alcance. El caso reproducido y corregido pertenece al flujo principal CAE. CAEA continúa como fallback o plan B gobernado por perfiles y lógica existente; esta mejora no modifica punto de venta, numeración, cCAEA ni decisión CAE/CAEA.

Paquete: IVA105.ZIP. RED reproduce el neto incorrecto; GREEN confirma 6653,0642 + 1019,3858 = 7672,45. La fuente completa compila con BCC286/POSTCPNU.CFG, BUILD_RC=0.

3. Confirmación física de cierre de gaveta

Comparación
Versión de referencia 20251227CF.
Evolución observada
Polling de eventos de 0,31 s a 30 s; parámetro idle de 5 a 10; rotación de 4 ciclos, aproximadamente 40 s, con dependencia del caché.
Problema
WaitGavetaClosed podía esperar sobre poscard.drawer sin refrescar el estado físico con la frecuencia necesaria.
Corrección
Polling físico inmediato y luego como máximo cada 500 ms, limitado a perfiles compatibles y con validación del status antes de publicar el estado.

Gates y protección de estado

Ciclo de detección de gaveta antes y despuésAntes la espera dependía de un caché actualizado por una rotación lenta. Después se inicia la consulta como máximo una vez cada 500 ms; el intervalo efectivo puede ser mayor por timeout. Sólo se confirma si el byte es válido. AntesWaitGavetaClosedlee cachéIdle / rotación x4≈ 40 s para refrescoEstado potencialmente obsoletola espera no confirma el cierre físico DespuésGate estrictoperfil + SERIALPoll físicoinmediato / 500 msreaded + statusStatusValid0x12 abierto · 0x16 cerradoConfirmarambos caches miss / inválido 0x04Restaurarsin falso cierre

Paquete: GAVFIX.ZIP. RED demuestra que readed=1/status=0x04 podía producir falso cierre; GREEN confirma rechazo de 0x04, 0x12 abierto, 0x16 cerrado, miss, gates, polaridad y wrap. Harness y fuente completa: BCC286 BUILD_RC=0.

4. Estado terminal QR antes del retry

Incidente
En el log de la transacción idTrx:1955485, el primer REFUND_DENEGADO no cerró la recuperación: se observaron 37 ciclos y 236.067s.
Causa
El control del código terminal 1020 estaba después del deadline, los prompts de retry y un continue; elegir reintentar evitaba ejecutar el cierre terminal.
Regla confirmada
En esta recuperación QR, REFUND_DENEGADO es terminal y no aprobado.
Corrección
Mover el bloque inmediatamente después de pagoqr->Op(...), antes de if(x) y del retry; corregir además el formato del mensaje a %s.

Comportamiento preservado

Máquina de estados QR antes y despuésPAGO_ENVIADO continúa consultando y PAGO_APROBADO finaliza con éxito. Antes, REFUND_DENEGADO atravesaba deadline, retry y continue; ahora finaliza inmediatamente como no aprobado. Estados preservados PAGO_ENVIADOtransitorio Nueva consultamantiene el ciclo PAGO_APROBADOéxito REFUND_DENEGADO: antes REFUND_DENEGADOdeadlinevencidoRetryprompt operadorcontinueomite terminal REFUND_DENEGADO: después REFUND_DENEGADOTerminal inmediato1 consulta · 0 prompts · no aprobado
Cleanup único. El control terminal se ejecuta una sola vez, antes de la rama genérica. No se modificaron callers ni el reconocimiento de los demás estados.

Paquete: QRTERM.ZIP (47BB2413052238849CF9FE1D36764D6AE075EBD33CF32C4F02B7A3F172C2DD3C). Revisar README.TXT, POS_CTOQ.CPP, PATCH.DIF, RED.LOG, GREEN.LOG y FULL.LOG.

5. Total de venta y sincronización del cierre FINZD

Resultado: el cierre manual con iMODOZ_CAEA ya no puede dejar silenciosamente dE_FinZd.iZ_TotalVenta en cero por una escritura dirigida al handle equivocado ni informar éxito cuando la publicación remota falla.

Problema local
El total de ventas se calculaba desde dE_TMdeP, pero la decisión update/append de dE_FinZd consultaba un handle cerrado y los retornos de persistencia se ignoraban.
Problema remoto
SincronizarZeta incrementaba el contador aun si TCP_Append fallaba; otros callers ignoraban ese retorno y podían reportar un OK falso.
Invariante local
Un fallo de SUpdate/SAppend bloquea avance de Z y sincronización. Un commit local exitoso avanza Z antes de intentar publicar.
Invariante remota
Si el sync falla después del commit local, no se simula rollback: se retorna BAD, se registra pendiente y se conserva el registro para retry.

Causa raíz comprobada

Solución implementada

Persistencia local

  • Filtra dE_TMdeP por iNroY y suma sólo idVENTA.
  • Registra locate, cantidad de registros, ventas y total.
  • Localiza FINZD con baz->locate(str_iNroZ).
  • Tras AppendStart, reasigna claves y campos.
  • Usa SUpdate/SAppend; el fallo retorna BAD antes de avanzar Z.

Publicación y retry

  • El commit local exitoso actualiza Z y luego captura el retorno de ProcessSavePoint.
  • SincronizarZeta es fail-fast y cuenta sólo appends exitosos.
  • El fallo remoto deja estado local confirmado y publicación pendiente.
  • Los savepoints exteriores se omiten cuando zret!=OK, evitando publicar un FINZD viejo.
  • xxret se acumula sólo en la rama FINZD.

Matriz de estados

Escritura local FINZDSync remotoAvance Z localResultado observableAcción posterior
FallaNo se ejecutaNoBAD; log FINZD WRITE ERRCorregir almacenamiento local y reintentar el cierre.
OKOKOK; contador incluye sólo envíos exitososSin pendiente.
OKFalla total o parcialSí; no hay rollback ficticioBAD; log LOCAL OK SYNC PENDINGEl registro queda pendiente y bTStatus no confirmado para retry.
Flujo de cálculo, persistencia y sincronización FINZDLas ventas de DE TMDEP se totalizan y se intenta un upsert local de DE FINZD. Un fallo local bloquea el avance. Si el commit local es exitoso, avanza Z y se sincroniza; un fallo remoto queda pendiente para reintento sin revertir el estado local. Cierre manual FINZD: commit local antes de publicación dE_TMdePY cerrada · idVENTA Totalizar ventasregistros + importe Upsert dE_FinZdbaz locate + SUpdateo AppendStart + SAppend ¿Local OK?retorno persistencia NoBloquear avanceBAD · sin sync Avanzar Z localMCoun confirmado Sincronizar FINZDTCP_Append fail-fast Remoto OKcierre publicado Fallo remotoLocal confirmado; sync pendienteBAD + retry · sin rollback local

Evidencia reproducible

Límite honesto. No se ejecutó integración live con Btrieve/DdfBase ni con servidor TCP real. El seam puro modela las transiciones de fallo y es usado por producción; los fixtures/checker fijan handle y orden concretos, pero no sustituyen una prueba integrada de almacenamiento y red.

Paquete: FINZDTV.ZIP (3CF9412F9F3725E7E39FFE56F5B48BAD028CCDF48C543B89335623CC3833F587). Revisar también README.TXT, RED.LOG, GREEN.LOG y RUN.LOG.

Evidencia y artefactos

Ruta de revisión recomendada: leer primero el README del paquete, luego RED/GREEN y finalmente la compilación completa.

MejoraPaquete y SHA-256RED / GREENCompilación completa
iNroComprobnroguard.zip
4A00EA05FB107755C9F2E320DD7CEF4693A2A059C41890D40270883347C62C4F
BUILD.LOG
RUN.LOG: 7 escenarios
FULL.LOG: BCC286, BUILD_RC=0
Neto/IVA 10,5%IVA105.ZIP
3C2119D47F099967F628A16B63D839C82301A1B23A370A3814D8DB7C41E778AE
RED.LOG
GREEN.LOG
FULL.LOG: BCC286, BUILD_RC=0
GavetaGAVFIX.ZIP
A63920723E59374ED194EE14A9B0CA9916C9931ABF534EFA124FFEDB8EDFA76B
RED.LOG
GREEN.LOG
FULL.LOG: BCC286, BUILD_RC=0
QR terminalQRTERM.ZIP
47BB2413052238849CF9FE1D36764D6AE075EBD33CF32C4F02B7A3F172C2DD3C
RED.LOG
GREEN.LOG: terminal pre/post-deadline
FULL.LOG: BCC286, BUILD_RC=0
FINZD total/syncFINZDTV.ZIP
3CF9412F9F3725E7E39FFE56F5B48BAD028CCDF48C543B89335623CC3833F587
RED.LOG
GREEN.LOG: fixtures hash-bound + transiciones
FULL.LOG: POS_MAIN_RC=0, POS_SERV_RC=0

Revisión independiente

Hallazgo documentado y cerrado en GAVFIX. Una revisión independiente detectó que RequestStatusTM950 podía modificar poscard._drawer antes de validar el byte. El HIGH fue remediado guardando/restaurando el caché físico y exigiendo iReaded && StatusValid; RED y GREEN enlazados arriba prueban la regresión y su cierre.
Revisión FINZDTV: PASS posterior a remediación. Se preservan todos los retornos posteriores a zret, se bloquean savepoints exteriores en error y el resultado remoto pendiente no se confunde con rollback local.
Revisión QRTERM: PASS. La revisión independiente confirmó el orden terminal antes del retry, el cleanup único, el formato %s, la evidencia RED reproducible y la compilación completa BCC286.

Los paquetes de iNroComprob e IVA incluyen harness y logs reproducibles, pero no contienen un archivo separado de revisión independiente. Este informe no atribuye una aprobación externa que no está materializada en los entregables.

Alcance, no alcance y riesgos residuales

Incluido

  • Guard de numeración al persistir.
  • Neto mayorista IVA 10,5% en CAE.
  • Polling directo de gaveta bajo gates.
  • Terminal QR antes del retry.
  • Upsert FINZD, avance local y sync con retry.
  • Harness y compilación BCC286.

No incluido

  • Cambio de ruteo CAE/CAEA.
  • Cambio de numerador POS o AFIP.
  • No se modifican el ABI ni los temporizadores globales; los originales raíz permanecen intactos. POS_MAIN.CPP se entrega solamente como copia/parche aislado dentro de FINZDTV.
  • Cambio de callers u otros estados QR.
  • Integración live Btrieve/TCP para FINZD.
  • Link de ejecutable POS completo.

Riesgo residual principal

La gaveta requiere validación HITL con impresora TM950, cable, puerto serie y hardware reales. Los harness verifican política y estado, no reemplazan la prueba física. QRTERM requiere además una prueba integrada contra el backend QR real. FINZDTV requiere validar Btrieve y un fallo TCP parcial en un entorno POS/servidor controlado.

Condición de aceptación operativa. Antes del despliegue, ejecutar un ciclo real de abrir/cerrar gaveta, simular timeout o desconexión y comprobar que un status inválido no cierre la espera. Para facturación, validar tickets controlados por CAE y el fallback CAEA sin alterar su ruteo. Para QR, reproducir PAGO_ENVIADO, PAGO_APROBADO y REFUND_DENEGADO contra el backend integrado. Para FINZD, verificar write local fallido, commit local exitoso y sync remoto fallido con posterior retry.