Cómo evitar stock negativo en Laravel usando transacciones y lockForUpdate

Cómo evitar stock negativo en Laravel usando transacciones y lockForUpdate

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.

💣 Descarga sistemas PHP y Laravel COMPLETOS GRATIS
Recursos premium listos para usar en proyectos reales 💻
👥 +1000 desarrolladores · 🔥 Publicaciones diarias
🔥 Unirme al canal GRATIS ahora 👥 Grupo de Facebook
⏳ Enlaces disponibles por tiempo limitado
💻 Sistemas completos 🔌 Plugins PRO ⚡ Scripts listos 🔥 Recursos exclusivos

Cómo evitar stock negativo en Laravel usando transacciones y lockForUpdate
Cómo evitar stock negativo en Laravel 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:

  1. e-commerce
  2. sistemas ERP
  3. plataformas de reservas
  4. 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:

UsuarioStock leídoAcciónResultado
Usuario A1comprastock = 0
Usuario B1comprastock = -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

VentajaExplicación
Consistencia de datosEvita registros incompletos
Rollback automáticoSi algo falla, se revierte
IntegridadGarantiza operaciones completas

Desventajas

DesventajaExplicación
Bloqueos de recursosPuede bloquear registros temporalmente
Mayor complejidadRequiere entender concurrencia
Impacto en rendimientoEn 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

PasoAcción
1inicia la transacción
2bloquea la fila del producto
3verifica el stock
4descuenta el inventario
5finaliza la transacción

De esta manera ningún otro proceso puede modificar el mismo producto al mismo tiempo.

Ventajas de usar lockForUpdate

VentajaDescripción
Evita condiciones de carreraPreviene modificaciones simultáneas
Mayor consistenciaGarantiza inventario correcto
Control de concurrenciaManeja múltiples usuarios

Posibles desventajas

DesventajaDescripción
Bloqueos temporalesOtros procesos deben esperar
Mayor carga en DBEn sistemas muy concurridos
Requiere transaccionesNo 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:

SistemaUso
E-commerceevitar vender productos agotados
ERPcontrol de inventario
Reservasevitar doble reserva
Sistemas SaaSmanejo 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.

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuarlo!

Promedio de puntuación 5 / 5. Recuento de votos: 1

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.

Leave a comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *