Cuando desarrollamos sistemas de inventario en Laravel, uno de los problemas más comunes es el stock negativo. Este error suele aparecer cuando varios usuarios intentan comprar el mismo producto al mismo tiempo, provocando inconsistencias en la base de datos.
En este artículo aprenderás por qué ocurre el stock negativo en Laravel y cómo solucionarlo usando transacciones y lockForUpdate.

¿Qué es el stock negativo en un sistema de inventario?
El stock negativo ocurre cuando la cantidad disponible de un producto en el inventario se vuelve menor que cero.
Esto suele suceder cuando varios procesos modifican el mismo registro simultáneamente, algo común en sistemas de:
- e-commerce
- sistemas ERP
- plataformas de reservas
- sistemas de almacén
Si no se controla la concurrencia, el sistema puede vender más productos de los que realmente existen.
Problema: compras simultáneas en Laravel
En muchos sistemas el código para descontar inventario es similar a este:
$product = Product::find($id);
if ($product->stock > 0) {
$product->stock--;
$product->save();
}¿Qué ocurre aquí?
Si dos usuarios compran el último producto al mismo tiempo, ambos procesos pueden leer el mismo valor de stock antes de que se actualice.
Resultado:
| Usuario | Stock leído | Acción | Resultado |
|---|---|---|---|
| Usuario A | 1 | compra | stock = 0 |
| Usuario B | 1 | compra | stock = -1 |
Esto genera inconsistencia en la base de datos.
¿Por qué ocurre este problema?
Este error ocurre por un problema llamado condición de carrera (race condition), donde varios procesos intentan modificar el mismo registro simultáneamente.
En bases de datos como MySQL o PostgreSQL, este tipo de problemas se solucionan utilizando bloqueos de filas y transacciones.
Solución: usar transacciones en Laravel
Las transacciones garantizan que varias operaciones se ejecuten como una sola unidad. Si algo falla, todos los cambios se revierten.
En Laravel se utilizan mediante DB::transaction().
DB::transaction(function () use ($id) {
$product = Product::find($id);
if ($product->stock > 0) {
$product->decrement('stock');
}
});Ventajas de usar transacciones
| Ventaja | Explicación |
|---|---|
| Consistencia de datos | Evita registros incompletos |
| Rollback automático | Si algo falla, se revierte |
| Integridad | Garantiza operaciones completas |
Desventajas
| Desventaja | Explicación |
|---|---|
| Bloqueos de recursos | Puede bloquear registros temporalmente |
| Mayor complejidad | Requiere entender concurrencia |
| Impacto en rendimiento | En sistemas muy grandes puede afectar |
Qué es lockForUpdate en Laravel
El método lockForUpdate() permite bloquear una fila en la base de datos hasta que termine la transacción.
Esto evita que otro proceso modifique el mismo registro al mismo tiempo.
Ejemplo:
$product = Product::lockForUpdate()->find($id);Mientras la transacción esté activa, ningún otro proceso podrá modificar ese registro.
Solución completa con lockForUpdate
La forma correcta de evitar stock negativo es combinar transacciones + bloqueo de filas.
DB::transaction(function () use ($id) {
$product = Product::lockForUpdate()->find($id);
if ($product->stock > 0) {
$product->decrement('stock');
}
});Cómo funciona esta solución
| Paso | Acción |
|---|---|
| 1 | inicia la transacción |
| 2 | bloquea la fila del producto |
| 3 | verifica el stock |
| 4 | descuenta el inventario |
| 5 | finaliza la transacción |
De esta manera ningún otro proceso puede modificar el mismo producto al mismo tiempo.
Ventajas de usar lockForUpdate
| Ventaja | Descripción |
|---|---|
| Evita condiciones de carrera | Previene modificaciones simultáneas |
| Mayor consistencia | Garantiza inventario correcto |
| Control de concurrencia | Maneja múltiples usuarios |
Posibles desventajas
| Desventaja | Descripción |
|---|---|
| Bloqueos temporales | Otros procesos deben esperar |
| Mayor carga en DB | En sistemas muy concurridos |
| Requiere transacciones | No funciona fuera de ellas |
Buenas prácticas para sistemas de inventario en Laravel
Para evitar problemas de concurrencia en aplicaciones Laravel es recomendable:
1. Usar siempre transacciones
Las operaciones críticas deben ejecutarse dentro de DB::transaction().
2. Bloquear registros críticos
Cuando se trabaja con inventario o pagos, utilizar lockForUpdate().
3. Validar el stock antes de descontar
Siempre verificar que el stock sea mayor que cero.
4. Registrar operaciones
Guardar logs de movimientos de inventario para auditoría.
Cuándo deberías usar esta técnica
El uso de transacciones y bloqueo de filas es recomendable en sistemas donde existe alta concurrencia.
Ejemplos:
| Sistema | Uso |
|---|---|
| E-commerce | evitar vender productos agotados |
| ERP | control de inventario |
| Reservas | evitar doble reserva |
| Sistemas SaaS | manejo de recursos compartidos |
Conclusión
El stock negativo en Laravel es un problema común en sistemas con múltiples usuarios. Cuando varias solicitudes intentan modificar el mismo registro al mismo tiempo, pueden producirse inconsistencias en la base de datos.
La solución consiste en usar transacciones junto con lockForUpdate(), lo que permite bloquear temporalmente los registros y garantizar que las operaciones se ejecuten de forma segura.
Implementar esta técnica mejora la consistencia del inventario, evita errores en producción y protege la integridad de los datos en aplicaciones desarrolladas con Laravel.
