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:

#DefectoEfecto en operaciónEstado
1Pérdida silenciosa de pagos al superar ~80 tickets Cordiez SAP en un mismo comprobanteEl 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
2PAGO QR (medio 22) cerraba el ticket y abría la gaveta sin autorización del procesadorVenta cerrada como cobrada sin intención de pago generada: dinero no percibido.Corregido y verificado
Despliegue: reemplazar 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

ArchivoDescripció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.hVersión PosVersion 20260916F → 20261106F (y 20260916P → 20261106P).
CYRE_20261106Fsrcs/pos_mis2.cppFuente corregido, defecto 1.
CYRE_20261106Fsrcs/pos_mppo.cppFuente corregido, defecto 2.
pcpos_h_fix.diff, pos_mis2_fix.diff, pos_mppo_fix.diffDiferencias unificadas contra los fuentes originales 20260916F.
CYRE_modal_*.png, CYRE_qr_*.png, CYRE_qrmock.logEvidencia de las pruebas (capturas y registro del simulador de procesador QR).
SHA256SUMS.txtSumas 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

3.3 Corrección (pos_mis2.cpp)

--- 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

Aviso de límite
Aviso al cajero al superar el límite de pagos por comprobante.
Saldo intacto
Tras el rechazo: sólo los pagos admitidos figuran en el detalle y el saldo sigue correcto.
Operativa: cuando aparece el aviso "LIMITE DE PAGOS POR COMPROBANTE", el cajero completa el saldo con otro medio o cierra el comprobante y continúa en uno nuevo. El límite es por comprobante, no por cliente.

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!!!
Línea de log (tipo 162, 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 procesadorRespuesta del POSTicketResultado
Aprobado (PAGO_APROBADO)Voucher QR, gaveta, cierre normalCerradoCorrecto, sin falso positivo
Pendiente y aprobado en 2ª consulta (PAGO_ENVIADO → PAGO_APROBADO)Verifica y cierraCerradoCorrecto
Rechazado (HTTP 400 + REFUND_DENEGADO)"PAGO-QR Denegado"AbiertoCorrecto
Sin intención (id: 0)"Denegada – SIN_ID"AbiertoCorrecto
Sin respuesta (timeout)"No se recibio respuesta del Procesador"AbiertoCorrecto
Autorización omitida (simulación del hueco: PAGOQR_Op OK sin lIdTrx)Nuevo aviso "PAGO QR SIN AUTORIZACION"; log tipo 162Abierto, sin gavetaControl 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.

Aviso PAGO QR SIN AUTORIZACION
Control actuando al TOTAL: el ticket permanece en medios de pago.
QR aprobado
Aprobado contra el simulador: cierre normal (sin falso positivo).
QR rechazado
Rechazado: el ticket queda abierto con el pago QR sin autorizar.
Operativa: ante el aviso "PAGO QR SIN AUTORIZACION" el cajero vuelve a intentar el TOTAL (el POS reintenta la autorización del QR pendiente) o cancela el medio QR y cobra con otro medio.

5. Observaciones adicionales

6. Plan de despliegue y prueba en sucursal

  1. Verificar SHA256SUMS.txt del ejecutable recibido.
  2. Con el POS cerrado, resguardar POS_MAIN.EXE y las POS_*.DLL actuales (20260916F) y copiar el paquete completo 20261106F.
  3. 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").
  4. 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.
  5. 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.
  6. 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.