Aceptación de dólares por encima del saldo, con vuelto en pesos

POS Becerra, versión 20261014P. Documento para el equipo POS de Tipre y para quienes implementan en Becerra.

Rama: vuelto-dolar Base: port multimoneda 0dd9d2a (20261006P) Pruebas: 2026-10-14/15, laboratorio

En una mirada

1. Qué hace y para qué

Hasta la versión 20261006P, el medio de pago multimoneda DOLAR solo admitía USD hasta cubrir el saldo, con tope floor(saldo / cotización). Si el cliente traía un billete de más, el cajero no podía aceptarlo y el resto se pagaba en pesos.

Desde 20261014P, cuando los datos lo habilitan, el POS acepta más USD que el saldo. La diferencia se entrega como vuelto en pesos, con el medio EFECTIVO. El dólar nunca da vuelto en dólares.

Ejemplo resuelto (ticket de prueba 504)

Total del ticket$ 12.490,00
Cotización DOLAR (Pizarra)1520,00
Entrega el clienteUSD 10 = $ 15.200,00
Vuelto en efectivo$ 2.710,00

Cálculo: 10 × 1520 − 12.490 = 2.710.

Rango de USD aceptado

ValorFórmulaEjemplo (saldo 12.490, cot. 1520)
Mínimo que cubre el saldoceil(saldo / cot)ceil(8,217) = 9 → USD 9 = $ 13.680, vuelto $ 1.190
Máximo aceptadofloor((saldo + límite) / cot), con tope técnico 99.999floor((12.490 + 1.236.510) / 1520) = 821
Tope anterior (sin habilitar)floor(saldo / cot)floor(8,217) = 8 → máx. $ 12.160
¿De dónde sale el límite? El límite es el vuelto máximo en pesos. Es el menor de dos valores:
  • la fila dM_VmdeP DOLAR→EFECTIVO: su dLimite (o el porcentaje dPorcentaje sobre el total, si no es 0, acotado por dLimite). Es la misma regla que ya usa el POS para el vuelto (VerCruceMdeP);
  • la holgura de dM_MdeP(DOLAR).dPorcCobrar: total × dPorcCobrar/100 − DOLAR ya cobrado − saldo. Evita que al cerrar aparezca el mensaje 388.
En el laboratorio, con dLimite = 99.999.999,99 y dPorcCobrar = 10000, gana la holgura: 12.490 × 100 − 12.490 = 1.236.510. El log lo muestra como MaxVuelto:1236510.00. En la práctica es "sin límite". Para limitar el vuelto, se baja dM_VmdeP.dLimite (ver §3).

Cuándo está activo

El sobrepago se habilita solo si se cumple todo; si falta algo, rige el tope anterior:

Además, con la versión nueva los USD deben ser enteros (billetes) en el tipo billete, haya o no sobrepago.

2. Flujo del cajero paso a paso

Teclas Las teclas citadas son las de los datos de prueba (remapeo en EVENTOS4.DAT del laboratorio): F6 Medios de Pago, F7 Total, Ctrl+F9 vuelto automático (evento 8303, kbVUELTO_AUTOMA). El mapa de teclas de producción puede ser distinto: confirmarlo en cada sucursal.
  1. Registrar los artículos como siempre. Total del ejemplo: $ 12.490.
  2. Medios de Pago (F6 en los datos de prueba). Si aparecen promociones, se continúa. Se baja hasta DOLAR y se confirma con Enter.
  3. Consulta de cotización. El POS consulta a Pizarra; la pantalla muestra [MULTIMON] Procesando c/servidor remoto... [0 de 10 seg] Si Pizarra no responde o la cotización está suspendida, el medio no se puede usar: No se puede usar el Medio de Pago
  4. Recuadro de ayuda. Con el sobrepago habilitado, muestra una línea con la cotización y el saldo, y la línea de ingreso indica los USD mínimos para cubrir, el vuelto en pesos que darían y el máximo. Por ejemplo, con saldo $2.750: Cotiz.DOLAR:1520.00 Saldo $: 2750.00 y DOLAR cubre 2 (vto $290) max 180:
  5. Ingreso de USD. El prompt indica el mínimo y el máximo: Ingrese DOLAR (cubre con 9, max 821): El cajero tipea los billetes recibidos, por ejemplo 10, y Enter. El saldo queda negativo (SaldT:-2710.00 en el log).
    • Con decimales (por ejemplo 10.5): se rechaza y vuelve a pedir el monto. DOLAR sin decimales: ingrese solo billetes enteros!
    • Por encima del máximo: Supera monto maximo permitido, reingrese!
    • Vacío: cancela el ingreso de DOLAR.
  6. Vuelto. Se presiona la tecla de vuelto automático (Ctrl+F9 en los datos de prueba). El POS asigna el vuelto a EFECTIVO: diálogo EFECTIVO 2710.00.
  7. Cierre del ticket. Se cierra el ticket (con MODO_TECLA_VUELTO activo, la tecla de vuelto pasa directo al cierre). El ticket impreso muestra:
    TOTAL :                         12490.00
    DOLAR 10.00          15200.00
    SU PAGO:                        15200.00
    EFECTIVO             2710.00
    VUELTO:                          2710.00
  8. Entregar $ 2.710 en efectivo al cliente.

Si el sobrepago está deshabilitado (o los datos no están completos)

El POS vuelve al comportamiento anterior (prueba V5):

Cotiz.DOLAR:1520.00 Maximo monto a cubrir en $: 12160 Ingrese DOLAR (max 8.00): 10 Supera monto maximo permitido, reingrese!

El cajero cobra USD 8 ($ 12.160) y el resto ($ 330) en efectivo u otro medio. Si el saldo es menor que una unidad de USD, sin sobrepago aparece El saldo a pagar es menor al permitido con DOLAR. Con sobrepago, ese caso se acepta con USD 1 y vuelto.

Combinaciones admitidas

3. Configuración: tablas, perfiles y Pizarra

Tablas de medios de pago

Los ids son los de Becerra en laboratorio: DOLAR grupo 9 (detalle 9/10), EFECTIVO 1.

Tabla / registroCampoAntesDespuésNota
dM_MdeP uId 9 (DOLAR)iVuelto10El dólar no da vuelto propio; el vuelto sale por EFECTIVO.
dPorcCobrar10010000100 veces el total. Evita el mensaje 388 al cerrar (CheckLimites).
dM_MdeP uId 9dLimite, dUnidad, dMaxVuelto99999999.99 / 0 / 0sin cambiosNo tocar. Bajar dLimite provoca el mensaje 373. dUnidad y dMaxVuelto deben quedar en 0.
dM_VmdePfila uId 9 → uIdv 1no existe1 fila: dLimite 99999999.99, dPorcentaje 0Es el límite del vuelto. Única fila con uId=9. La fila 3→1 existente no se toca.
dM_DmdeP 9/10iTaxnumber11Requisito: 1 = billetes enteros. Grupo dM_MdeP 9 "DOLAR".
dM_MdeP uId 1 (EFECTIVO)dUnidad, dMaxVuelto0 / 0sin cambiosEn 0, el vuelto en efectivo es exacto (si la cotización tiene centavos, el vuelto también).
¿Por qué no se usa la unidad monetaria para exigir USD enteros? dM_MdeP.dUnidad y dM_UniMo validan el importe en pesos (USD × cotización), que cambia con cada cotización. No pueden expresar "USD entero". Esa regla vive en POS_MMON.DLL. En DOLAR y EFECTIVO, dUnidad y dMaxVuelto deben quedar en 0.

Después de cambiar dM_VmdeP hay que reiniciar el POS: la tabla se carga en memoria una sola vez al iniciar.

Perfiles

PerfilNombre / usoValor
554MULTIMONEDA_HABILITADO1. El valor -999 es simulación (cotización fija 1200): solo para laboratorio.
555IP del servidor PizarraIP de la sucursal / central (en laboratorio 10.0.2.2)
556Puerto de Pizarra8084
557Timeout de recepción (seg)en laboratorio 10
558Timeout de conexión (seg)en laboratorio 5
237NRO_SUCURSALnúmero de sucursal que se envía a Pizarra (BranchNumber; en laboratorio 13)
200ID_MDEP_VUELTO_DEF0 = efectivo (toma el perfil 22)
22ID_EFECTIVO1
171PERMITE_PROMO_MDEP1

Ningún perfil cambia con esta funcionalidad; la tabla sirve para verificar que estén bien.

Servidor Pizarra

4. Cómo habilitar y deshabilitar

Las tablas se distribuyen desde el servidor dM_MdeP y dM_VmdeP figuran en dE_Tranf. Cuando el servidor publica una versión nueva, el POS baja el archivo y reemplaza el .dat entero. Un cambio local hecho con SETVUELT se pierde con la próxima versión del maestro. El camino definitivo es el maestro central.

A. Camino recomendado: maestro central

  1. En el maestro central de Becerra, confirmar que existe el grupo dM_MdeP 9 "DOLAR" y que dM_DmdeP 9/10 tiene iTaxnumber = 1.
  2. En dM_MdeP uId 9: iVuelto 1 → 0 y dPorcCobrar 100 → 10000. No tocar dLimite, dUnidad ni dMaxVuelto.
  3. En dM_VmdeP: agregar la fila uId 9, uIdv 1, dLimite 99999999.99, dPorcentaje 0 (o el límite que defina el cliente). Que sea la única fila con uId 9.
  4. Publicar ambas tablas con versión nueva para que los POS las descarguen.
  5. Reiniciar cada POS y verificar con SETVUELT 9 1 (solo lectura) y un ticket real tipo V1.

Para deshabilitar: publicar iVuelto = 1, dPorcCobrar = 100 y sin la fila 9→1, con versión nueva.

B. SETVUELT.EXE: piloto, laboratorio o emergencia

Herramienta de consola (misma familia que SETMDTAX e INSMDEP). Se ejecuta con el POS detenido, con DDFDIR y DATADIR definidos y el motor Btrieve cargado.

SETVUELT <uIdMMon> <uIdVuelto>                       ver (solo lectura)
SETVUELT <uIdMMon> <uIdVuelto> /APPLY [lim] [porc]   habilitar (lim 99999999.99, porc 10000)
SETVUELT <uIdMMon> <uIdVuelto> /UNDO                 deshabilitar

Salidas reales (laboratorio)

/APPLY:

---- SETVUELT 9 1 /APPLY
ANTES:
  dM_MdeP uId=9 (DOLAR): iVuelto=1 dPorcCobrar=100.00 dLimite=99999999.99 dUnidad=0.00 dMaxVuelto=0.00
  dM_VmdeP 9->1: 0 fila(s)   otras filas uId=9: 0
  dE_Tranf dM_MdeP    iVersion=1 (una version nueva del server pisa este cambio)
  dE_Tranf dM_VmdeP   iVersion=1 (una version nueva del server pisa este cambio)
DESPUES (relectura):
  dM_MdeP uId=9 (DOLAR): iVuelto=0 dPorcCobrar=10000.00 dLimite=99999999.99 dUnidad=0.00 dMaxVuelto=0.00
  dM_VmdeP 9->1: 1 fila(s) dLimite=99999999.99 dPorcentaje=0.00   otras filas uId=9: 0
OK - GRABADO Y RELEIDO

/UNDO repetido (idempotencia):

---- SETVUELT 9 1 /UNDO
ANTES:
  dM_MdeP uId=9 (DOLAR): iVuelto=1 dPorcCobrar=100.00 dLimite=99999999.99 dUnidad=0.00 dMaxVuelto=0.00
  dM_VmdeP 9->1: 0 fila(s)   otras filas uId=9: 0
  ...
SIN CAMBIOS: ya esta deshabilitado

Línea de SETVUELT.BAK:

Wed Oct 14 23:29:47 2026 accion=APPLY uIdMMon=9 uIdVuelto=1 | dM_MdeP existe=1 iVuelto=1 dPorcCobrar=100.00 dLimite=99999999.99 dUnidad=0.00 dMaxVuelto=0.00 | dM_VmdeP filas=0 dLimite=0.00 dPorcentaje=0.00 otras=0
Corrección incluida (d306e1e) La primera versión de /UNDO borraba con DDF::del(), que solo marca el registro, y la fila 9→1 seguía presente. Ahora borra con Btrieve Remove(). Usar el SETVUELT.EXE de md5 5c36d23a… o posterior.

5. Binarios y fuentes modificados

Commits

CommitFechaDescripción
0dd9d2a2026-10-06Port multimoneda (Pizarra/USD) desde DINO_20260902; versión 20261006P/F. Base de este cambio.
31f714e2026-10-14feat(multimoneda): sobrepago en USD con vuelto en efectivo (20261014P).
d306e1e2026-10-15fix(setvuelt): borra filas de dM_VmdeP con Btrieve Remove.
0172b802026-10-15docs: plan y estado.
ab5a6542026-10-15fix(multimoneda): recuadro de ayuda compactado (primer intento).
a2e772c2026-10-15fix(multimoneda): el vuelto pasa a la línea de ingreso y el recuadro queda en una línea, porque la ventana de ingreso tapaba las líneas 2 y 3. Verificado en pantalla.

Cambios por archivo (0dd9d2a..vuelto-dolar, +385/−13)

ArchivoMóduloQué cambió
pos_mmon.cppPOS_MMON.DLLRecibe de POS_MAIN el vuelto máximo en pesos (en multimon.dMontoResPesos; 0 = sin sobrepago). Si es mayor que 0 y el tipo es billete, calcula mínimo ceil y máximo floor((saldo+límite)/cot) (tope 99.999). Nuevo recuadro de ayuda y prompt Ingrese DOLAR (cubre con N, max M). Rechaza USD con decimales en tipo billete. El log [MULTIMON] agrega MaxVuelto.
pos_mppo.cppPOS_MAIN.EXENueva función MMonMaxVueltoPesos(): devuelve el límite solo si el tipo es billete, iVuelto=0 y existe la fila dM_VmdeP DOLAR → vuelto por defecto; usa la regla de VerCruceMdeP. VerificarDMP pasa ese límite a la DLL, acotado por la holgura de dPorcCobrar. No cambia ninguna estructura.
pos_mis2.cppPOS_MAIN.EXEExcepción al mensaje 373 (cerca de la línea 3905, IngresoMdeP_ConPromoMdeP): el medio multimoneda puede dejar saldo negativo dentro del mismo límite.
pcpos.hPOS_MAIN.EXEVersión 20261014P / 20261014F (las DLL informan la versión exportada por POS_MAIN).
setvuelt.cpp, _CSETVLT.BATSETVUELT.EXEHerramienta nueva para ver, habilitar o deshabilitar (§4). Se compila con _CSETVLT.BAT (bcc286 + btr.lib).

El port previo (dff468b..0dd9d2a, +77/−23) tocó pos_mmon.cpp, pos_mppo.cpp, pos_valo.cpp, pos_defi.h y pcpos.h: quitó la cotización simulada fija, agregó el modo de redondeo por iTaxnumber, el orden de vData con 61= primero y el arqueo en USD.

md5 de los binarios (build completo CO.BAT, 20261014P)

Binariomd5Contiene el cambio
POS_MAIN.EXE2b3923b0a4809be3b2322c3a4005bf07Sí
POS_MMON.DLLf5e3a87d14aefc09f423f5de3aa725efSí
SETVUELT.EXE5c36d23a1fcdf62349bdaa06a05d42f7Herramienta

La distribución mínima es POS_MAIN.EXE + POS_MMON.DLL. Se recomienda distribuir el set completo de un mismo CO.BAT (las 16 DLL, listadas con md5 en built_binaries.md5 de la evidencia) para que coincidan versiones y md5. Al arrancar, el log debe mostrar Inicia POS=20261014P y todas las DLL en 20261014P.

Matriz de compatibilidad

POS_MAIN.EXEPOS_MMON.DLLDatosResultado
NuevoNuevaHabilitadosSobrepago con vuelto en efectivo; USD enteros.
NuevoNuevaViejosTope anterior; USD enteros obligatorios.
ViejoNuevaCualquieraTope anterior: POS_MAIN viejo limpia la estructura (multimon.Clear()) y la DLL recibe 0. USD enteros obligatorios (lo valida la DLL).
NuevoViejaCualquieraTope anterior: la DLL vieja ignora el valor de entrada. Admite decimales como antes.
ViejoViejaHabilitadosTope anterior; seguro (la DLL no deja superar el saldo).
CualquieraCualquieraA medio aplicarTope anterior (falta alguna condición). No dejar datos a medio aplicar.

6. Registro y arqueo

Cada pago genera filas en dE_DDMP. El vuelto es una fila EFECTIVO negativa (marcada como recibo de vuelto, bRecVuelto) sin vData. La fila DOLAR guarda el importe en pesos y, en vData, los USD, la cotización y el transactionId de Pizarra.

Ejemplo: ticket 504 (volcado de dE_DDMP)

uIduIdIdcDescripciondImportecComprobantevData
910DOLAR15200.00136861=10.00;60=1520.00;62=13687374;
110EFECTIVO−2710.0013001000017(vacío)

Cómo cuenta el arqueo por moneda

Cálculo sobre las filas volcadas de los tickets 504–510 (no se imprimió cierre X/Y en laboratorio):

DOLAR (USD)10+9+5+9+10+8 = 51
EFECTIVO de esos tickets ($)−2.580
Por qué existe la condición iVuelto = 0 Con iVuelto = 1 el vuelto saldría en DOLAR, en pesos y sin 61=, y el arqueo restaría pesos del acumulador de USD. Por eso el código exige iVuelto = 0 para habilitar el sobrepago.

7. Pruebas realizadas

Laboratorio con DOSBox-X y Pizarra local, cotización 1520 (versión 10345). Ticket base: 4 × COCA COLA + 1 PINZA = $ 12.490. Todas las consultas a Pizarra quedaron auditadas con HTTP 200.

#TicketCasoObtenidoResultado
V1504USD 10 sobre 12.490Prompt cubre con 9, max 821; SaldT:-2710.00; vuelto $ 2.710 en efectivo; filas DOLAR 15200 + EFECTIVO −2710.PASS
V2505USD 9 = mínimo que cubreVuelto $ 1.190; EFECTIVO −1190.PASS
V3506USD 5 + efectivo 4.890Sin vuelto, igual que antes.PASS
V3B507Efectivo 2.000 primero, después USD 9Su pago 15.680, vuelto $ 3.190; sin mensaje 388.PASS
V4504Tipear USD 10.5DOLAR sin decimales: ingrese solo billetes enteros! y vuelve a pedir.PASS
V5510Deshabilitado con SETVUELT /UNDOIngrese DOLAR (max 8.00); USD 10 → Supera monto maximo permitido; log MaxVuelto:0.00; pagó USD 8 + $ 330.PASS
V6B509Sobrepago ingresado y anulación del ticket en curso (autoriza supervisor)Ticket anulado; sin filas en dE_DDMP.PASS
V6508Anular un ticket ya cerradoLa operación no existe en Becerra: un ticket cerrado no se cancela.No aplica

Pendientes sugeridos para el piloto: reimpresión del ticket, cierre X/Y/Z por moneda y Pizarra caída con la versión nueva (con la versión 20261006P ya se probó, T4).

Pruebas previas del port multimoneda (20261006P)

#TicketCasoResultado
T1499Camino feliz, cotización 1520: USD 10 = $ 15.200 + efectivo; vData 61=10.00;60=1520.00;62=…; auditado.PASS
T2500Cotización nueva 1530 publicada en Pizarra; el POS la toma en la siguiente consulta.PASS
T3501Cotización suspendida: HTTP 404, el POS rechaza el medio; se paga en efectivo.PASS
T4502Servidor inalcanzable: error de recepción, el medio no se usa; no hay cobro con cotización fija.PASS
T5503Simulación (perfil 554 = −999): cotización 1200 aun con el servidor caído; sin auditoría.PASS

Evidencia: SistemaBiMonetarismo/docs/evidence/pos-becerra-vuelto-20261014/ (logs, tickets impresos, volcados, salidas de SETVUELT, auditorías) y …/pos-becerra-mmon-20261006/ (port, T1–T5).

8. Despliegue en un cliente

Orden: primero el código en todos los POS (es inerte con los datos viejos), después los datos, que funcionan como interruptor.

Checklist

Rollback

  1. Primera opción, por datos: publicar en el maestro iVuelto=1, dPorcCobrar=100 y sin la fila 9→1, y reiniciar. En emergencia, SETVUELT 9 1 /UNDO en el POS (con el POS detenido). El código queda con el tope anterior.
  2. Solo si hace falta, por binarios: volver a POS_MAIN.EXE + DLL de 20261006P. Es seguro con cualquier estado de datos (matriz §5).

9. Notas, límites y riesgos

Regulatorio Entregar vuelto en pesos por un pago en divisas puede considerarse operación cambiaria. Validarlo con el contador del cliente antes de habilitar.
TemaDetalle
Tablas distribuidas centralmentedM_MdeP y dM_VmdeP se reemplazan completas cuando el servidor publica una versión nueva. El cambio se hace en el maestro; SETVUELT local es transitorio.
Mapa de teclasF6, F7 y Ctrl+F9 son de los datos de prueba. Confirmar las teclas de producción (eventos 2011, 8301 y 8303).
Cierre inmediato tras el vueltoCon MODO_TECLA_VUELTO activo, la tecla de vuelto cierra el ticket. Un ticket cerrado no se puede cancelar en Becerra (esperado); antes de esa tecla, el ticket en curso sí se puede anular.
Impresora fiscalNo se usa en Becerra: no hay riesgo fiscal asociado al pago mayor que el total.
Solo billetes enterosCon iTaxnumber=1 no se aceptan USD con decimales (también sin sobrepago). El tipo 2 (centavos) no admite sobrepago.
Vuelto exactoEl vuelto en efectivo no se redondea: con cotización con centavos, el vuelto tiene centavos.
Límite del vueltoSe regula con dM_VmdeP.dLimite (fila 9→1). No con dM_MdeP(9).dLimite: bajarlo provoca el mensaje 373. dUnidad y dMaxVuelto deben quedar en 0.
Máximo de ingresoEl campo admite hasta 99.999 USD por ingreso.
Recuadro de ayudaCorregido y verificado en pantalla: la ventana de ingreso tapaba las líneas 2 y 3 del recuadro, así que el recuadro quedó en una línea (cotización y saldo) y el vuelto pasó a la línea de ingreso.
Mensajes 357 / 377Si al dar el vuelto aparecen, la fórmula del límite y la del vuelto divergieron. Son una red de defensa: no deberían aparecer. Revisar los datos de la fila 9→1.
Defectos previos (no introducidos por este cambio)ControlDeMdeP::CheckLimites indexa vueltos[n] en lugar de vueltos[m]; por eso se sube dPorcCobrar. Ticket.Update acumula el importe antes del chequeo del mensaje 373 y no lo revierte si lo rechaza (igual que en DolarEAN).
Versiones viejas20260807P, 20260824P y 20260917P simulan la cotización (1200) y no consultan Pizarra. Requisito: 20261006P o posterior.

Preguntas abiertas para el cliente