ALMACOR - Fix del doble descuento en promociones por medio de pago

POS_MAIN.EXE sobre las fuentes de produccion 20260928P - 6 de noviembre de 2026

1. Resumen ejecutivo

En la sucursal 9 (Salsipuedes), POS 3, Z 12, el ticket 110 imprimio la linea PROMOMDEP[EFT/DEB]-124800.00 cuando el cliente esperaba un descuento de 92.100. La diferencia proviene de que el cajero cargo medios de pago en efectivo, los borro y los volvio a cargar: el acumulador del descuento conservo el descuento de los medios borrados y lo sumo de nuevo.

La causa es un contador de indices que se desfasa respecto de los registros de control cuando un medio de pago no alcanza el minimo de la promocion. El fix ubica el registro a revertir por promocion (el ultimo registro de esa promo) en lugar de usar el contador global. Solo cambian pos_prom.cpp y pcpos.h; no hay cambios de base ni de perfiles.

El error se reprodujo en una VM con el EXE de produccion (descuento de 13.299,98 en lugar de 9.699,98) y quedo corregido con el EXE nuevo (9.699,98); ver seccion 6.

corregidovalidado en VM

2. Evidencia del cliente

Nota del cliente: "Deberia ser $DTO 92.100 y fue $124.800". Log del POS: Ticket_N°110_00031104.26L. El ticket impreso tiene la linea PROMOMDEP[EFT/DEB]-124800.00 (promo 2250000, promocion por medio de pago tipo 2, 10 % en efectivo/debito).

Linea de tiempo (ticket 110, subtotal 921.000,00)

Hora (04/11/2026)EventoIngresadoDescuento
21:21:07MDEP ADD efectivo300.000-30.000
21:21:10MDEP ADD efectivo30.000-3.000
21:21:12MDEP ADD efectivo3.0000 (sin descuento)
21:21:383 x MDEP DEL (3.000, 27.000 y 270.000 netos)-0 / -3.000 / -30.000
21:22:14MDEP ADD efectivo300.000-30.000
21:22:16MDEP ADD efectivo30.000-3.000
21:22:18MDEP ADD efectivo3.0000
21:23:12MDEP ADD transferencia529.200-58.800
21:23:14Impresion PROMOMDEP[EFT/DEB]--124.800

Importes

ConceptoImporte
Descuento vigente al final (30.000 + 3.000 + 58.800)91.800
Descuento esperado segun el cliente92.100
Descuento impreso124.800
Exceso sobre lo vigente (124.800 - 91.800)33.000

El exceso de 33.000 coincide exactamente con los descuentos de los medios borrados a las 21:21:38 (30.000 + 3.000): se contaron dos veces. La diferencia de 300 entre los 91.800 vigentes y los 92.100 indicados por el cliente no se explica con este log y no forma parte del fix.

3. Causa raiz

Funciones PromocionesMdeP y TICKET::PromocionesMdeP_DeleteMdeP (pos_prom.cpp, compilada con #define DELETESPECIALMODEPROMO).

Escenario reproducido en VM (ticket de 99.999,75: 25 items de 3.999,99)

PasoContadorRegistrosacupro.MontoDesc
Alta efectivo 20.000 (-2.000)1idx1-2.000
Alta efectivo 16.000 (-1.600)2idx1, idx2-3.600
Alta efectivo 3.000 (evaluada, no alcanza el minimo)3idx1, idx2-3.600
Borrar 3.000: GetLast(3) no encuentra, Del() no corre3idx1, idx2-3.600
Borrar 16.000: GetLast(3) no encuentra, sin reversion3idx1, idx2-3.600 (deberia ser -2.000)
Borrar 20.000: igual3idx1, idx2-3.600 (deberia ser 0)
Recargar 20.000 y 16.000, cerrar con 61.000 (-6.099,98)6...-13.299,98 (correcto: -9.699,98)

Por que el sintoma es intermitente. Si el medio de pago pequeno se agrega cuando el saldo pendiente ya es menor que 15.000 (MRefSinD:0.00), PromocionesMdeP retorna antes de getNextIndexIn() y el error no aparece. Depende del orden y de los importes de los medios de pago.

4. Fix

pcpos.h - metodo nuevo GetLastIndexIn

Devuelve el indexin mas alto registrado para la promo (0 si no hay). Los medios de pago se borran siempre de atras para adelante, por lo que el ultimo registro de la promo es el del medio que se borra. Igual que el resto de la estructura, depende de profile.iPagoOnlineEMV.

unsigned POS_EXPORT GetLastIndexIn(unsigned long idpromo)
{
if( (!profile.iPagoOnlineEMV))
    return 0;
unsigned uMax=0;
for(int i=iidx;i>=0;i--){
    if(pmdep[i].idpromo!=idpromo)
        continue;
    if(pmdep[i].indexin>uMax)
        uMax=pmdep[i].indexin;
    }
return uMax;
}

pos_prom.cpp - PromocionesMdeP_DeleteMdeP

Se guardan los valores entrantes antes del lazo por promocion:

double dRecDescIng=dRecDesc;   //20261106
double dImporteIng=dImporte;   //20261106
unsigned uIdxDel=0;            //20261106 indice del registro de control a borrar

Codigo original (reemplazado):

double _dRecDesc=0L;
double _dImporte=0L;
globalarrayPromMdepControl.GetLast(globalarrayPromMdepControl.getCurrentIndexIn(), Id, iIdMdeP, _dRecDesc, _dImporte);
dImporte=_dImporte;
dRecDesc=_dRecDesc;
if(!VALIDAR(dRecDesc))
    continue;

Codigo nuevo:

if(!VALIDAR(dRecDescIng))
    continue;
dImporte=dImporteIng;
dRecDesc=dRecDescIng;
{
unsigned uIdx=globalarrayPromMdepControl.GetLastIndexIn(Id);
if(uIdx){
    double _dRecDesc=0L;
    double _dImporte=0L;
    globalarrayPromMdepControl.GetLast(uIdx, Id, iIdMdeP, _dRecDesc, _dImporte);
    if(VALIDAR(_dRecDesc)){
        dImporte=_dImporte;
        dRecDesc=_dRecDesc;
        uIdxDel=uIdx;
        }
    }
}
Log(LOGOK, LOG_PROMOMDEP, GCmos.NroZ, "[DelMdeP] promo %ld idx %u rec %.2lf imp %.2lf", Id, uIdxDel, dRecDesc, dImporte);

El borrado del registro de control usa el indice encontrado, con el contador como respaldo:

globalarrayPromMdepControl.Del(uIdxDel ? uIdxDel : globalarrayPromMdepControl.getCurrentIndexIn(),iIdMdeP);

Comportamiento resultante:

5. Compilacion y paquete

Compilado con make -f postcpnu y implib pos_main.lib pos_main.exe bajo Phar Lap RUN286: 0 errores, 20 modulos recompilados (pcpos.h cambio). EXE anterior: 3.041.012 bytes, MD5 4358638d... (respaldos POS_MAIN.EXE.bak_20261106 y POS_MAIN.LIB.bak_20261106). Respaldos de fuentes: pos_prom.cpp.bak_promodel y pcpos.h.bak_promodel.

ArchivoQue esBytesMD5
POS_MAIN.EXEBinario a instalar. Reemplaza al EXE de produccion 20260928P (3.041.012 bytes, MD5 4358638d...).3.041.647e1aae4c0a4e5015d13130e72da153c0a
src_modificado/pos_prom.cppFuente con el fix en PromocionesMdeP_DeleteMdeP.155.20037038897a365555caf021e132a56dcba
src_modificado/pcpos.hFuente con el metodo nuevo GetLastIndexIn.90.618bff39602903fa66397a4e7900464e273
pos_prom.diffDiferencias contra pos_prom.cpp.bak_promodel.2.1407ccdc01ffbd41d609878cbf3af33bad0
pcpos_h.diffDiferencias contra pcpos.h.bak_promodel.748b6db1b620536959d7e73d843c9616641
LG261104_VM_DOBLEDTO.LOGLog completo de la VM con las corridas A y B (seccion 6).1.460.585433c6e2e28a7154d7b85529c478c68f0

6. Validacion en VM

VM VirtualBox Almacor_CAE_v260928P_test (MS-DOS 6.22, Btrieve 6.15, pcPOS rama ALMACOR) con los dM_Promo.dat y dM_ProAr.dat del cliente instalados. Promo 2250000 EFT/DEB: global, -10 %, minimo 15.000, valida solo los miercoles, con efectivo (1,1) entre los medios de pago. Fecha de la VM: miercoles 04/11/2026.

Misma secuencia de caja en las dos corridas: 25 x EAN 7790895000430 (3.999,99) = 99.999,75; efectivo 20.000, 16.000 y 3.000; borrar los tres (tecla - del teclado numerico); recargar los tres; cerrar con efectivo 61.000.

Corrida A - EXE de produccion 20260928PCorrida B - EXE corregido
Tamano / MD53.041.012 bytes
4358638d4e46e4ee72173c8f709aaf36
3.041.647 bytes
e1aae4c0a4e5015d13130e72da153c0a
Ticket / hora (reloj de la VM)130, 16:11 a 16:13131, 16:18 a 16:20
Lineas [DelMdeP]no existen en este EXEdos: idx 2 (-1.600) e idx 1 (-2.000); ninguna para el medio de 3.000, que no tenia descuento
PROMOMDEP[EFT/DEB] impreso-13.299,98-9.699,98
Esperado (2.000 + 1.600 + 6.099,98)-9.699,98-9.699,98
Diferencia3.600 de mas (los dos descuentos borrados), el patron del cliente0

Corrida A (EXE de produccion), lineas del log

MDEP ADD:EFECTIVO MRefSinD:67599.75 MRefConD:57599.77 MIngr:3000.00 MNetRes:3000.00 RecD:0.00 SaldT:60999.75
MDEP DEL:EFECTIVO TotDel:3000.00 RecDes:0.00 SaldTi:63999.75
MDEP DEL:EFECTIVO TotDel:14400.00 RecDes:-1600.00 SaldTi:79999.75
MDEP DEL:EFECTIVO TotDel:18000.00 RecDes:-2000.00 SaldTi:99999.75
(recarga de 20.000, 16.000 y 3.000)
MDEP ADD:EFECTIVO MRefSinD:60999.75 MRefConD:54899.77 MIngr:61000.00 MNetRes:61000.00 RecD:-6099.98 SaldT:-6100.23
PROMOMDEP[             EFT/DEB]-13299.98 (TOTAL)

Corrida B (EXE corregido), lineas del log

MDEP ADD:EFECTIVO MRefSinD:67599.75 MRefConD:57599.77 MIngr:3000.00 MNetRes:3000.00 RecD:0.00 SaldT:60999.75
MDEP DEL:EFECTIVO TotDel:3000.00 RecDes:0.00 SaldTi:63999.75
[DelMdeP] promo 2250000 idx 2 rec -1600.00 imp 16000.00
MDEP DEL:EFECTIVO TotDel:14400.00 RecDes:-1600.00 SaldTi:79999.75
[DelMdeP] promo 2250000 idx 1 rec -2000.00 imp 20000.00
MDEP DEL:EFECTIVO TotDel:18000.00 RecDes:-2000.00 SaldTi:99999.75
(recarga de 20.000, 16.000 y 3.000)
MDEP ADD:EFECTIVO MRefSinD:60999.75 MRefConD:54899.77 MIngr:61000.00 MNetRes:61000.00 RecD:-6099.98 SaldT:-6100.23
PROMOMDEP[             EFT/DEB]-9699.98 (TOTAL)

Prueba de la condicion de saldo. Un primer intento con un ticket de 39.999,90 imprimio el -3.600 correcto con el EXE anterior, porque el medio de 3.000 se agrego con el saldo pendiente ya por debajo del minimo (MRefSinD:0.00) y no avanzo el contador. Esto confirma la condicion descrita en la seccion 3.

El log completo esta en el paquete como LG261104_VM_DOBLEDTO.LOG.

7. Despliegue y pendientes

Despliegue

  1. Cerrar la POS (modo tecnico).
  2. Respaldar C:\PCPOS\POS_MAIN.EXE (por ejemplo como POS_MAIN.EXE.bak).
  3. Copiar el POS_MAIN.EXE del paquete a C:\PCPOS de cada POS y reiniciar.

No hay cambios de base de datos ni de perfiles. Perfiles involucrados en el cliente: 472 PAGOONLINE_EMV=1 y 256 MODOCANCELAMDEPPROMO=2. Rollback: restaurar el EXE respaldado.

Con una promo por medio de pago activa, el log de la POS mostrara la linea [DelMdeP] en cada borrado de medio de pago; sirve para el diagnostico en campo.

observacion pendiente Posible segunda fuga

En pos_mis2.cpp (aprox. lineas 3800-3935), PromocionesMdeP podria aplicar la promo antes de algunos return 1 que rechazan el medio de pago. No esta confirmado y no se cubre con este fix.