Reportado en varias sucursales. En San Vicente (PV 841 / POS 15) se emitieron las Y 12504–12506 y al pedir la 12507 se volvía a emitir la 12506. En RB Vesta (POS 75) al pedir la Y 763 salía la 762. En ambos casos la Y contenía la facturación de una cuenta corriente.
Verificación previa: en el servidor, los archivos de dos Y consecutivas tenían el mismo tamaño, y el log del POS mostraba el contenido correcto de la Y nueva mientras el archivo quedaba con el de la anterior.
El cierre de cajero arma la Y en un archivo spool y luego lo copia a YLOG\NNNNNNNN.y y .2se,
que viajan al servidor y a la base CierresImpresosPOS. El mecanismo del fallo:
logprnti.$X$ (LogPrnTICKET).TICKET::DeleteAll
(pos_vnta.cpp:2249), para tickets de factura (ImprimeTicket==1) los dos
StopLog2File() están comentados (líneas 2250 y 2253). El spool queda abierto.INFORME::DoInforme llama a StaLogPrn2File(LogPrnCIEZCAJR) e ignora el retorno.
Esa función devuelve BAD sin hacer nada cuando el spool ya está ocupado (fpl || StaLog2File).Tres archivos de fuente. El binario resultante es POS_MAIN.EXE (no cambian DLLs).
pos_info.cpp — guarda al iniciar el cierre + validación de NroYAntes de abrir el spool del cierre: si quedó ocupado, se loguea y se cierra a la fuerza; si aun así no abre, se aborta el cierre con aviso, antes de tocar contadores. Y antes de copiar la Y, se verifica que el spool contenga el NroY actual.
if (Modo==MODO_Z){ NoPrintPorFacturacion=1; // si el spool quedó ocupado por otro flujo, lo cerramos a la fuerza if (fpl || StaLog2File){ Log(LOGERROR, LOG_ZETAMANUAL, GCmos.NroZ, fs_SpoolBusyAtClose, CurrentLogFName); StopLog2File(); } if (StaLogPrn2File(Alcance==aPOS ? LogPrnCIEZCAJA : LogPrnCIEZCAJR)!=OK){ Log(LOGERROR, LOG_ZETAMANUAL, GCmos.NroZ, fs_SpoolOpenFailed, ...); NoPrintPorFacturacion=0; PError(msg_ErrorImpresEnCurso_141); return BAD; // cierre abortado, sin generar Y falsa }
// antes de copiar el spool a YLOG: sprintf(cNroYEsperado, "Y%08ld", NroY); while (!feof(fp)){ if(!fgets(cLineaSpool,sizeof(cLineaSpool),fp)) break; if(strstr(cLineaSpool, cNroYEsperado)!=NULL){ iNroYEncontrado=1; break; } } if(!iNroYEncontrado) // avisa si el spool no trae el NroY del cierre Log(LOGERROR, LOG_ZETAMANUAL, GCmos.NroZ, fs_SpoolMissingNroY, LogPrnCIEZCAJR, cNroYEsperado);
pos_misc.cpp — EndLog2File resetea el flagDejaba StaLog2File en "ocupado" con fpl en NULL, marcando el spool como ocupado para siempre.
void POS_EXPORT EndLog2File(char* FileLog){ if (fpl!=NULL) fclose(fpl); remove(FileLog); fpl=NULL; StaLog2File=0; // mantiene el flag consistente con fpl }
pos_main.cpp — SaveYNoImpresa (recuperación tras apagón)En la ruta de recuperación al reiniciar el POS, no genera la Y si el spool no contiene su NroY (evita publicar una Y
equivocada). Además corrige una fuga del handle fpx.
sprintf(cNroYEsperado, "Y%08ld", lNroY); while (!feof(fp)){ ... if(strstr(cLineaSpool, cNroYEsperado)) { iNroYEncontrado=1; break; } } if(!iNroYEncontrado){ Log(LOGERROR, LOG_ZETAMANUAL, GCmos.NroZ, fs_SpoolNoNroYNoGen, LogPrnCIEZCAJR, cNroYEsperado); Error(fs_WarnSpoolNoNroYNoGen, lNroY); fclose(fp); return; // NO genera Y }
Nota técnica: los mensajes nuevos se declararon como static char _far fs_*[N] porque el segmento
DGROUP (datos near de 16 bits) tenía sólo 190 bytes libres. No se tocó ninguna cadena preexistente.
Decisión: NO se descomentan los StopLog2File de DeleteAll. Estaban deshabilitados a
propósito por un historial de "doble impresión de Y". El fix neutraliza la fuga en el cierre, sin tocar esa rama.
Escenario del reclamo: caja sin fiscal / CAEA, bases del kit. Secuencia: habilitar cajero → vender ítem → Factura A (cuenta corriente) → anular el ticket → cierre de cajero.
'20260914 17:09:21','1','165',...,'Spool ocupado al iniciar cierre (logprnti.$X$), se fuerza cierre'
El spool culpable es logprnti.$X$ (LogPrnTICKET): confirma que la anulación de la factura lo dejó abierto.
YLOG\00004960.Y contiene únicamente Y00004960
(ninguna Y anterior). La Y salió con sus propios datos.
| Sin el fix | Con el fix |
|---|---|
El cierre encuentra el spool ocupado, StaLogPrn2File falla en silencio, se copia el spool viejo como la Y nueva → Y con datos de la anterior. |
Detecta el spool ocupado, lo cierra a la fuerza, genera la Y correcta y loguea el evento → Y correcta + traza para diagnóstico. |
| Archivo | Ubicación | Cambio |
|---|---|---|
| pos_info.cpp | C:\Work\PCPOS\20260914_DINO\srcs | Guarda de spool al iniciar cierre + validación de NroY |
| pos_misc.cpp | C:\Work\PCPOS\20260914_DINO\srcs | EndLog2File resetea StaLog2File |
| pos_main.cpp | C:\Work\PCPOS\20260914_DINO\srcs | SaveYNoImpresa: no publica Y sin su NroY + fix de handle |
| POS_MAIN.EXE | C:\Work\PCPOS\20260914_DINO\build_20260914 | Binario compilado con el fix (3.060.461 bytes, 14/09) |
Despliegue: copiar POS_MAIN.EXE a las cajas (afecta sólo al EXE; las DLLs no cambian).
En producción, la nueva línea Spool ocupado al iniciar cierre (<archivo>) permite identificar qué
operación deja el spool abierto, para corregirlo también en el origen si se decide.