El dashboard decía 8 donde vendimos 4
El portal interno marcaba 8 unidades vendidas de un modelo de cámara que en la tienda habíamos vendido 4 veces. Conté los comprobantes del mes a mano y ganaron los comprobantes: lo que iba de septiembre reportaba 21 unidades contra 14 vendidas, 50% de aire en el número que uso para decidir qué repongo. El dashboard contaba la misma cámara dos veces y lo hacía con total convicción.
Con ese número negocio cuotas y pedidos con las marcas. Un 50% inflado me hace comprar stock que no voy a mover. Nadie había tocado el código de ventas en meses y los duplicados venían apareciendo todos los meses desde abril.
El índice único nunca rechazó una fila
Mi primera teoría fue la obvia: que la restricción de unicidad, la regla que impide guardar dos veces la misma fila, se había caído en alguna migración. Fui a verificarla y estaba activa. No había rechazado una sola fila desde abril, y por eso nadie se había dado cuenta de nada.
El dashboard no lee la base del ERP, el sistema de inventario y facturación de la tienda. Lee una hoja de cálculo que el equipo llena a mano y que un script sincroniza cada noche. Ese script armaba la llave de cada venta con la posición de la fila dentro de la hoja.
Cuando alguien volvía a pegar un bloque de ventas más abajo, porque se movió el orden o porque copió de otra pestaña, esas filas recibían posiciones nuevas. Con posiciones nuevas el script calculaba llaves nuevas, el índice único comparaba llaves distintas y las dejaba pasar sin un error en los logs.
Es como contar a los invitados de un evento anotando la silla donde se sentó cada uno: si alguien se levanta y se cambia de sitio, lo cuentas dos veces, porque la silla no te dice quién estuvo ahí. Para contar bien hay que pedirle el documento a cada invitado.
Espera, ¿la llave era la posición de la fila?
Siempre lo fue
El documento de identidad de una venta
Lo único que identifica a una cámara es su número de serie. Una cámara física no puede aparecer dos veces en el mismo comprobante, así que la llave pasó a ser un índice único sobre serie más comprobante.
Lo hice parcial a propósito: las ventas sin número de serie no colisionan entre sí, y una cámara que vuelve por cambio y se revende con otro comprobante sigue siendo una venta válida. Con la serie sola habría bloqueado casos reales del mostrador.
Las correas no tienen serie
Baterías, filtros y correas entran a la hoja sin número de serie. Ahí probé primero la regla amplia: borrar cualquier fila repetida con el mismo desplazamiento respecto de otra. La corrí en seco y habría borrado unas 50 ventas reales, de comprobantes donde el cliente sí compró tres correas iguales. Fue la regla de borrado más prolija que escribí y también la más equivocada.
La cambié por un corte conservador: borrar un bloque repetido solo si las filas son contiguas y abarcan al menos 2 comprobantes distintos. Un cliente puede comprar tres correas; que tres comprobantes seguidos se repitan idénticos y en el mismo orden es un bloque repegado. Prefiero dejar un duplicado dudoso en la tabla antes que borrar una venta buena.
76 filas menos y un auditor que no borra
Salieron 76 filas duplicadas, de 3,171 a 3,095, revisadas una por una contra los comprobantes antes del borrado y sin un falso positivo. Lo que va de septiembre cuadra ahora con la planilla de ventas que llevamos aparte: las 14 unidades reales son las que muestra el dashboard.
El script auditor quedó corriendo: recorre la tabla, reporta duplicados candidatos y no borra nada por su cuenta, porque el borrado exige una confirmación escrita a mano. La regla de bloques contiguos es un criterio aproximado y no identifica la venta, así que mientras los accesorios se registren sin serie el control depende de que alguien lea ese reporte.
El respaldo con cp salió sin las últimas ventas
Antes de borrar las 76 filas copié la base SQLite con cp, como copio cualquier archivo. SQLite en modo WAL (write-ahead log: un archivo aparte donde la base anota los cambios recientes antes de integrarlos) mantiene las últimas transacciones fuera del archivo principal, así que el respaldo salió sin justo lo que me importaba. Ahora saco los respaldos con VACUUM INTO, el comando de la propia base.
En la documentación del módulo anoté tres reglas. Antes de aceptar una deduplicación reviso si la llave describe el objeto del mundo real o el lugar donde estaba escrito. Toda regla de borrado se corre en seco, contando cuántas filas buenas se habría llevado. Y si un índice único pasa meses sin rechazar nada, voy a ver qué está comparando. El dashboard sigue contando con la misma convicción, pero ahora le pide el número de serie a cada cámara antes de sumarla.
