Cómo evitar vender inventario inexistente: actualización condicional en SQLite
Una venta debería descontar existencias solo si queda cantidad suficiente. El clásico patrón «leer stock, restar y guardar» puede fallar cuando dos operaciones se ejecutan casi a la vez.
El problema y el alcance del ejemplo
La ficha de POSManager: TPV y Caja Ventas resume su función así: «TPV y caja para comercios: controla ventas, stock y tickets desde el móvil.» POSManager describe cobros, ventas, tickets y control de stock. Para estudiar una decisión de diseño vinculada al inventario, usamos una actualización SQL condicionada dentro de una transacción. No estamos describiendo la base de datos real del producto.
Cómo reproducir la idea
Puedes ejecutarlo con Python 3, que incluye SQLite. Guarda el archivo como venta.py y ejecútalo. La base está en memoria para que cada prueba comience desde cero.
import sqlite3 db = sqlite3.connect(":memory:") db.execute("CREATE TABLE productos(id INTEGER PRIMARY KEY, stock INTEGER NOT NULL)") db.execute("CREATE TABLE ventas(producto INTEGER, cantidad INTEGER)") db.execute("INSERT INTO productos VALUES(1, 3)") db.commit() def vender(producto, cantidad): if cantidad <= 0: raise ValueError("Cantidad inválida") with db: # transacción y rollback automático ante error cur = db.execute( "UPDATE productos SET stock=stock-? WHERE id=? AND stock>=?", (cantidad, producto, cantidad), ) if cur.rowcount != 1: raise ValueError("Stock insuficiente o producto inexistente") db.execute("INSERT INTO ventas VALUES(?, ?)", (producto, cantidad)) vender(1, 2) print("Stock restante:", db.execute("SELECT stock FROM productos").fetchone()[0]) try: vender(1, 2) except ValueError as e: print("Venta rechazada:", e) Qué demuestra el código
La condición stock >= ? se verifica en la misma instrucción que descuenta inventario. Si ninguna fila fue modificada, la venta no se inserta. La transacción relaciona los dos efectos: movimiento de stock y registro de venta.
Pruebas recomendadas
Deberías obtener stock 1 tras la primera venta y rechazo de la segunda. Prueba vender 0 unidades y un identificador desconocido. Comprueba que la tabla de ventas contiene únicamente una fila.
Límites que conviene reconocer
La prueba no modela devoluciones, cobros, sincronización offline, precios, impuestos ni múltiples dispositivos con inventarios divergentes. En un sistema distribuido la transacción local no resuelve por sí sola conflictos entre terminales.
Qué aprendemos
El stock es una invariante de negocio; conviene protegerla en la operación de escritura, no solo mediante validaciones en la interfaz.
Contexto y enlaces del autor
El tema se relaciona con las funciones declaradas públicamente de POSManager: TPV y Caja Ventas, una aplicación Android de AppInitDev. El código anterior es un ejemplo independiente: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.
- Google Play: POSManager: TPV y Caja Ventas
- Página de la aplicación: https://posmanager.appinitdev.com/
- Sitio del desarrollador: AppInitDev

