PCPOS CYRE 20261106F – Correcciones RTI002471
Fecha: 06/11/2026 · Versión entregada: 20261106F · Versión base: 20260916F (CYRE_20260916Fsrcs.ZIP) · Fuentes modificados: pcpos.h, pos_mis2.cpp, pos_mppo.cpp
1. Resumen ejecutivo
Se corrigen dos defectos reportados en el incidente RTI002471, ambos en la etapa de cobro (medios de pago) del POS:
| # | Defecto | Efecto en operación | Estado |
|---|---|---|---|
| 1 | Pérdida silenciosa de pagos al superar ~80 tickets Cordiez SAP en un mismo comprobante | El comprobante cerraba con menos pagos registrados que los ingresados: descuadre de caja y registros basura en el detalle de medios de pago. | Corregido y verificado |
| 2 | PAGO QR (medio 22) cerraba el ticket y abría la gaveta sin autorización del procesador | Venta cerrada como cobrada sin intención de pago generada: dinero no percibido. | Corregido y verificado |
POS_MAIN.EXE y las 16 DLL POS_*.DLL del paquete (versión 20261106F). Las DLL se enlazan contra la import library del ejecutable y se reconstruyen en cada versión; no deben mezclarse DLL 20260916F con el EXE 20261106F. No requiere cambios de perfiles ni bases. Compatible con la configuración actual de CAEA, impresión y PAGO QR.
2. Contenido del entregable
| Archivo | Descripción |
|---|---|
CYRE_20261106F_bin/POS_MAIN.EXE + POS_*.DLL (16) | Ejecutable y DLL compilados juntos (Borland C++ 3.1 / Phar Lap 286) como versión 20261106F, con ambas correcciones. Rebuild completo (co.bat). |
CYRE_20261106Fsrcs/pcpos.h | Versión PosVersion 20260916F → 20261106F (y 20260916P → 20261106P). |
CYRE_20261106Fsrcs/pos_mis2.cpp | Fuente corregido, defecto 1. |
CYRE_20261106Fsrcs/pos_mppo.cpp | Fuente corregido, defecto 2. |
pcpos_h_fix.diff, pos_mis2_fix.diff, pos_mppo_fix.diff | Diferencias unificadas contra los fuentes originales 20260916F. |
CYRE_modal_*.png, CYRE_qr_*.png, CYRE_qrmock.log | Evidencia de las pruebas (capturas y registro del simulador de procesador QR). |
SHA256SUMS.txt | Sumas de verificación. |
d3cb43378061451884dd59f9c719ff9a4e066560f028acee93c45e37a483d154 *CYRE_20261106Fsrcs/pcpos.h
f765f15a6a2d13047383619a85a10933dbbe65ecb619f48fe59c1f500397bbcc *CYRE_20261106Fsrcs/pos_mis2.cpp
e6ea765f908488b6e4cd10ab17844aec4d9b0762aabe50705881c7db957bfa9e *CYRE_20261106Fsrcs/pos_mppo.cpp
6d17fedf93dbbbcff572a09dbd4968cc7c1a3f32f13f509067c830871a53dd13 *pcpos_h_fix.diff
7a084ac3977d6b2e4ed2f2ac684cb3c9b7a117108233f58bd1190515926ccaba *pos_mis2_fix.diff
d203cdb371f905966c95451a35b0a04b6d56cc7cec472d138f984d30d4c931d0 *pos_mppo_fix.diff
# Binarios 20261106F (POS_MAIN.EXE + POS_*.DLL): sumas en CYRE_20261106F_bin/SHA256SUMS.txt al cerrar el build
3. Defecto 1 – Límite de pagos por comprobante (tickets Cordiez SAP)
3.1 Síntoma
Al cobrar con una gran cantidad de tickets Cordiez SAP (caso reportado: 247 tickets en un comprobante), el POS dejaba de registrar pagos a partir de aproximadamente el ticket 80 sin avisar al cajero. El comprobante cerraba con saldo cero en pantalla pero con faltante en el detalle de medios de pago (dE_DDMP) y registros corruptos en los archivos de transmisión.
3.2 Causa raíz
- Techo físico. El motor transaccional en memoria (
DDFMEM) direcciona los registros de cada base con un puntero far dentro de un único segmento de 64 KB. Con un registro de 378 bytes (dE_Ticke) el límite real es de ~173 registros; superado ese punto el puntero vuelve al inicio del segmento y sobreescribe registros anteriores. No es un problema de memoria disponible (hay más de 90 MB libres) ni de aritmética de 16 bits. - Control existente mal ubicado. En 2024 se había agregado un límite de 80 pagos, pero el control se ejecutaba después de
Ticket.Update, de la imputación de promociones y deTicket.Saldo -= Monto. Al rechazar el pago número 81, el saldo en pantalla ya había bajado aunque el pago no se registró. El cajero veía saldo cero y cerraba: ese es el mecanismo exacto del descuadre.
3.3 Corrección (pos_mis2.cpp)
- El control de límite se reubica antes de
Ticket.Update: un pago rechazado no modifica totales, promociones ni saldo. - Límite general elevado a 120 pagos por comprobante (
MAXMDEPDETALLEXTICKET), con margen de seguridad de 10 para el medio Cordiez SAP (MDEP_SAFETYNET): 110 efectivos. Ambos valores quedan por debajo del techo físico de ~173 registros. - Mensaje explícito al cajero en lugar del aviso genérico anterior.
--- a/pos_mis2.cpp (20260916F original)
+++ b/pos_mis2.cpp (fix 20261106)
@@ -18,7 +18,8 @@
#define MAXCOONRESUMEN 50
#define MAXCLIENTES 15
-#define MAXMDEPDETALLEXTICKET 80
+#define MAXMDEPDETALLEXTICKET 120
+#define MDEP_SAFETYNET 10
extern int POS_EXPORT GetMaxSaldoParaMdePNoBulto(int _id, int _idid, int _cuo, double& dSaldoAPagar);
@@ -3901,6 +3902,20 @@
return 1;
}
+
+MBrowse* br2= (MBrowse *)View::GetView(ID_BR_EMdeP);
+if (!br2 || !br2->ba)
+ return 1;
+
+//20240322++ (reubicado 20261106) Limite de mdep por comprobante. Va ANTES de Ticket.Update/Saldo:
+//si se rechaza despues, el saldo ya bajo sin registrar el mdep (descuadre RTI002471).
+//Techo fisico: segmento 64KB far por base (dE_Ticke reclen 378 -> ~173 regs).
+int _limMdeP=(xuId==profile.iIDMDEPTICKETCORDIEZ)?(MAXMDEPDETALLEXTICKET-MDEP_SAFETYNET):MAXMDEPDETALLEXTICKET;
+if(br2->ba->last >= _limMdeP){
+ static char _far _msgLimMdeP[]="LIMITE DE PAGOS POR COMPROBANTE\nEl ultimo pago NO se registro.\nComplete con otro medio o cierre\ny siga en otro comprobante.";
+ LMessage(_msgLimMdeP,BGM_OK_WAIT,BGM_HIDEATEXIT);
+ return 1;
+}
double xmonto=0.0L;
@@ -3958,15 +3973,6 @@
Ticket.SaldoBack= Ticket.Saldo ;
ViewSaldo(dRecDescMonto);
-MBrowse* br2= (MBrowse *)View::GetView(ID_BR_EMdeP);
-if (!br2 || !br2->ba)
- return 1;
-
-//20240322++ SINO ROMPE LA MEMORIA TRANSACCIONES DE_DMDEP DDFMEM* (354 es el reclen, mas de 32K es drama!)
-if(br2->ba->last > MAXMDEPDETALLEXTICKET){
- PError(msg_AvisoExcLimPermit_374);
- return 1;
-}
//
if(iAplicoAjusteMDEPPromoCuo){
3.4 Verificación
- Entorno de laboratorio (DOSBox-X con Btrieve 6.15) con los fuentes de producción y bases del cliente.
- Harness de carga (200 pagos sobre el motor transaccional real): el código original registra hasta su límite de 80 y pierde el resto; el corregido registra hasta 120 sin registros corruptos (
SIMLOG.BEFORE/SIMLOG.AFTER). - Margen Cordiez: 110 tickets Cordiez SAP, el 111 es rechazado; luego se admiten hasta 10 pagos de otro medio hasta el tope de 120 (
SIMLOG.SAFETYNET). - Corrida en el POS completo con límite reducido (compilación de prueba) para forzar el rechazo: con el código original el saldo bajaba igual al rechazar; con la corrección el saldo se mantiene y el pago rechazado no figura en el detalle.
4. Defecto 2 – PAGO QR cierra el ticket sin autorización
4.1 Síntoma
Con PAGO QR en línea (medio 22), en determinadas condiciones el POS imprimía, abría la gaveta y cerraba el ticket sin que el procesador hubiera generado la intención de pago. El cliente no pagaba y la venta quedaba cerrada como cobrada.
4.2 Causa raíz
En _ProcessKeyTOTAL (pos_mppo.cpp) la autorización QR se procesa así:
if(!PAGOQR_Op(trxpagoqr)){
if(trxpagoqr.lIdTrx){
... registra la autorizacion (iPeriodo = lIdTrx) ...
}
}
else {
return 6; // autorizacion fallida: no cierra
}
Si PAGOQR_Op devuelve OK pero sin lIdTrx (no se generó intención), no entra en ninguna de las dos ramas: el bucle continúa, no hay retorno de error y el ticket sigue su cierre normal con un pago QR sin autorizar. Los rechazos explícitos del procesador (denegado, sin id, sin respuesta) sí estaban cubiertos; el hueco es este caso intermedio.
4.3 Corrección (pos_mppo.cpp)
Antes de finalizar el ticket se agrega una verificación independiente: todo medio PAGO QR del comprobante debe tener autorización registrada (iPeriodo distinto de cero). Si alguno no la tiene, se registra en el log, se informa al cajero y se retorna por el mismo camino que una autorización fallida: el ticket queda abierto en medios de pago, sin impresión ni gaveta.
--- a/pos_mppo.cpp (20260916F original)
+++ b/pos_mppo.cpp (fix 20261106)
@@ -3687,6 +3687,26 @@
}
}
+//20261106++ BUG#2 RTI002471: verificar que TODO mdep PAGO QR tenga autorizacion real antes de finalizar.
+//Si PAGOQR_Op no cargo (DLL) o la autorizacion no dejo lIdTrx/iPeriodo, el QR NO esta pagado:
+//no se cierra el ticket ni se abre la gaveta. Mismo camino de retorno que un PAGOQR_Op fallido.
+if(mbase && !mbase->Top()){
+ do{
+ int _qId=0,_qIdId=0; long _qPe=0L;
+ sfield(mbase,str_uId ,_qId);
+ sfield(mbase,str_uIdId,_qIdId);
+ if(!EsPagoQR(_qId,_qIdId)) continue;
+ mbase->field(str_iPeriodo,_qPe);
+ if(!_qPe){
+ static char _far _lgQRNoAut[]="PAGOQR SIN AUTORIZAR al TOTAL uId=%d uIdId=%d PAGOQR_Op=%d hab=%d";
+ static char _far _msgQRNoAut[]="PAGO QR SIN AUTORIZACION\nEl pago QR NO fue autorizado.\nNo se cierra el ticket.\nReintente o cobre con otro medio.";
+ Log(LOGERROR,LOG_PAGOQR,GCmos.NroZ,_lgQRNoAut,_qId,_qIdId,PAGOQR_Op?1:0,profile.iPAGOQR_Habilitado);
+ LMessage(_msgQRNoAut,BGM_OK_WAIT,BGM_HIDEATEXIT);
+ return 6;
+ }
+ }while(!mbase->Skip());
+ }
+
//Si dio error, chau
if (ret){
View::Active( ID_BR_MdeP); //MBROWSE!!!
LGaammdd.LOG): PAGOQR SIN AUTORIZAR al TOTAL uId=22 uIdId=N PAGOQR_Op=1 hab=1. Permite auditar cuántas veces el control actuó en producción.
4.4 Verificación
Se construyó un simulador del procesador QR (protocolo HTTP de pos_ctoq.cpp: /api/trxid, /api/trxpago, /api/trxes/{id}) y se ejecutó el POS corregido contra él en el laboratorio, con red real (TCP/IP del POS).
| Escenario del procesador | Respuesta del POS | Ticket | Resultado |
|---|---|---|---|
Aprobado (PAGO_APROBADO) | Voucher QR, gaveta, cierre normal | Cerrado | Correcto, sin falso positivo |
Pendiente y aprobado en 2ª consulta (PAGO_ENVIADO → PAGO_APROBADO) | Verifica y cierra | Cerrado | Correcto |
Rechazado (HTTP 400 + REFUND_DENEGADO) | "PAGO-QR Denegado" | Abierto | Correcto |
Sin intención (id: 0) | "Denegada – SIN_ID" | Abierto | Correcto |
| Sin respuesta (timeout) | "No se recibio respuesta del Procesador" | Abierto | Correcto |
Autorización omitida (simulación del hueco: PAGOQR_Op OK sin lIdTrx) | Nuevo aviso "PAGO QR SIN AUTORIZACION"; log tipo 162 | Abierto, sin gaveta | Control efectivo |
El último escenario se reprodujo con una compilación de prueba que omite la llamada al procesador; el ejecutable entregado no contiene esa modificación.
5. Observaciones adicionales
- Dependencia de
POS_CTOQ.DLL. Si esa DLL no está presente, el POS falla con "General protection fault" al restaurar un ticket pendiente en el inicio de sesión (llamada a una función de promociones de la DLL no cargada). No es un escenario de producción, pero conviene verificar la presencia de la DLL en cada despliegue. - Límite de valores en caja. Los perfiles 75/76 (
MONTO_LIMITE_V1/V2) bloquean la caja y piden clave de supervisor al superar el efectivo acumulado. Comportamiento esperado; se menciona porque apareció durante las pruebas.
6. Plan de despliegue y prueba en sucursal
- Verificar
SHA256SUMS.txtdel ejecutable recibido. - Con el POS cerrado, resguardar
POS_MAIN.EXEy lasPOS_*.DLLactuales (20260916F) y copiar el paquete completo 20261106F. - Iniciar el POS y confirmar en el encabezado y en el log de arranque la versión 20261106F para el EXE y para cada DLL (líneas tipo 125
POS_XXX.DLL 20261106F, sin "No se encontro metodo"). - Prueba defecto 1: un comprobante con más de 110 tickets Cordiez SAP debe mostrar el aviso de límite en el pago 111 y el saldo no debe variar.
- Prueba defecto 2: una venta con PAGO QR aprobado debe cerrar normalmente; una venta con QR rechazado o sin respuesta debe quedar abierta en medios de pago.
- Rollback: restaurar el conjunto resguardado (EXE + DLL 20260916F). No hay cambios en bases ni perfiles que revertir.
Fuentes de referencia: pos_mis2.cpp (IngresoMdeP_ConPromoMdeP), pos_mppo.cpp (_ProcessKeyTOTAL), pos_ctoq.cpp (PAGOQR::TxRx/Op), libsp\BTRIEVE\DDFMEM.CPP.