Skip to main content

Movimientos de Inventario

El módulo de movimientos de inventario permite registrar las entradas (compras, devoluciones de clientes) y salidas (ventas, devoluciones a proveedores) de productos, actualizando automáticamente el stock disponible.
Los movimientos de inventario son la única forma de modificar el stock de productos en el sistema. Esto garantiza trazabilidad completa de todos los cambios de inventario.

Tipos de Movimientos

El sistema soporta dos tipos de movimientos:

Entrada

Aumenta el stock de un productoEjemplos:
  • Compra a proveedores
  • Devoluciones de clientes
  • Producción interna
  • Ajustes por conteo físico (positivos)

Salida

Disminuye el stock de un productoEjemplos:
  • Ventas a clientes
  • Devoluciones a proveedores
  • Productos dañados/pérdidas
  • Ajustes por conteo físico (negativos)

Registrar Entrada

Las entradas aumentan el stock de un producto y se registran en la tabla movimientos para trazabilidad.

Proceso de Entrada

1

Acceder al formulario de entrada

Desde la lista de productos, haz clic en Entrada en la barra de navegación superior, o navega a http://localhost/inventario/movimientos/entrada.php.
2

Seleccionar producto

Usa el menú desplegable para seleccionar el producto al que deseas agregar stock. El sistema muestra todos los productos disponibles.
3

Ingresar cantidad

Especifica la cantidad de unidades a agregar. Debe ser un número entero positivo mayor o igual a 1.
4

Registrar entrada

Haz clic en Registrar Entrada. El sistema:
  • Creará un registro en la tabla movimientos con tipo “entrada”
  • Aumentará el stock del producto seleccionado
  • Te redirigirá a la lista de productos con el stock actualizado

Formulario de Entrada

El formulario en movimientos/entrada.php:27-41 permite seleccionar el producto y cantidad:
movimientos/entrada.php
<form method="POST">
    Producto:<br>
    <select name="producto_id" required>
        <?php while ($p = $productos->fetch_assoc()) { ?>
            <option value="<?= $p['id'] ?>">
                <?= $p['nombre'] ?>
            </option>
        <?php } ?>
    </select><br><br>

    Cantidad:<br>
    <input type="number" name="cantidad" min="1" required><br><br>

    <button name="registrar">Registrar Entrada</button>
</form>

Procesamiento de la Entrada

Cuando se envía el formulario, el código en movimientos/entrada.php:7-22 procesa la entrada:
movimientos/entrada.php
if (isset($_POST['registrar'])) {
    $producto_id = $_POST['producto_id'];
    $cantidad = $_POST['cantidad'];

    // Registrar movimiento
    $conn->query("INSERT INTO movimientos (producto_id, tipo, cantidad)
                  VALUES ($producto_id, 'entrada', $cantidad)");

    // Aumentar stock
    $conn->query("UPDATE productos 
                  SET stock = stock + $cantidad 
                  WHERE id = $producto_id");

    header("Location: ../productos/listar.php");
    exit();
}
Transacciones atómicas: El sistema realiza dos operaciones separadas (INSERT en movimientos y UPDATE en productos). En producción, considera usar transacciones SQL para garantizar que ambas operaciones se completen o ninguna se ejecute:
$conn->begin_transaction();
try {
    // INSERT y UPDATE aquí
    $conn->commit();
} catch (Exception $e) {
    $conn->rollback();
    // Manejar error
}

Registrar Salida

Las salidas disminuyen el stock de un producto, con validación automática de stock disponible.

Proceso de Salida

1

Acceder al formulario de salida

Desde la lista de productos, haz clic en Salida en la barra de navegación superior, o navega a http://localhost/inventario/movimientos/salida.php.
2

Seleccionar producto

Usa el menú desplegable para seleccionar el producto del que deseas retirar stock.
3

Ingresar cantidad

Especifica la cantidad de unidades a retirar. Debe ser un número entero positivo.
El sistema validará que haya suficiente stock disponible antes de procesar la salida.
4

Registrar salida

Haz clic en Registrar Salida. Si hay stock suficiente, el sistema:
  • Creará un registro en la tabla movimientos con tipo “salida”
  • Disminuirá el stock del producto seleccionado
  • Te redirigirá a la lista de productos
Si no hay stock suficiente, mostrará un error “Stock insuficiente”.

Formulario de Salida

El formulario en movimientos/salida.php:36-50 es similar al de entrada:
movimientos/salida.php
<form method="POST">
    Producto:<br>
    <select name="producto_id" required>
        <?php while ($p = $productos->fetch_assoc()) { ?>
            <option value="<?= $p['id'] ?>">
                <?= $p['nombre'] ?>
            </option>
        <?php } ?>
    </select><br><br>

    Cantidad:<br>
    <input type="number" name="cantidad" min="1" required><br><br>

    <button name="registrar">Registrar Salida</button>
</form>

Validación de Stock Disponible

La característica clave de las salidas es la validación de stock (movimientos/salida.php:7-29):
movimientos/salida.php
if (isset($_POST['registrar'])) {
    $producto_id = $_POST['producto_id'];
    $cantidad = $_POST['cantidad'];

    // Obtener stock actual
    $producto = $conn->query(
        "SELECT stock FROM productos WHERE id=$producto_id"
    )->fetch_assoc();

    if ($producto['stock'] >= $cantidad) {
        // Registrar movimiento
        $conn->query("INSERT INTO movimientos (producto_id, tipo, cantidad)
                      VALUES ($producto_id, 'salida', $cantidad)");

        // Disminuir stock
        $conn->query("UPDATE productos 
                      SET stock = stock - $cantidad 
                      WHERE id = $producto_id");

        header("Location: ../productos/listar.php");
        exit();
    } else {
        $error = "Stock insuficiente";
    }
}
Prevención de stock negativo: La validación de stock asegura que nunca tendrás stock negativo en tu inventario, lo cual es esencial para mantener datos confiables.

Estructura de la Tabla Movimientos

Cada movimiento registrado contiene la siguiente información:
id
integer
required
Identificador único del movimiento. Clave primaria auto-incremental.
producto_id
integer
required
ID del producto relacionado. Clave foránea que referencia productos.id.
tipo
enum('entrada','salida')
required
Tipo de movimiento: “entrada” (aumenta stock) o “salida” (disminuye stock).
cantidad
integer
required
Cantidad de unidades del movimiento. Siempre es un número positivo.
fecha
timestamp
Fecha y hora en que se registró el movimiento. Se establece automáticamente al valor actual con DEFAULT CURRENT_TIMESTAMP.

Actualización Automática de Stock

El stock de productos se actualiza automáticamente con cada movimiento:
UPDATE productos 
SET stock = stock + [cantidad] 
WHERE id = [producto_id]
Ejemplo: Si un producto tiene stock de 10 y registras una entrada de 5 unidades:
  • Stock anterior: 10
  • Entrada: +5
  • Stock nuevo: 15

Consultar Historial de Movimientos

Aunque el sistema actualmente no incluye una interfaz para consultar el historial, puedes ver todos los movimientos directamente en la base de datos:
SELECT 
    m.id,
    p.nombre AS producto,
    p.codigo,
    m.tipo,
    m.cantidad,
    m.fecha
FROM movimientos m
INNER JOIN productos p ON m.producto_id = p.id
ORDER BY m.fecha DESC;
Esta consulta te mostrará:
  • Qué producto fue movido
  • Si fue entrada o salida
  • Cuántas unidades
  • Cuándo ocurrió el movimiento
Mejora recomendada: Crear una página movimientos/historial.php que muestre esta información en una tabla amigable, con filtros por producto, tipo de movimiento y rango de fechas.

Casos de Uso Comunes

  1. Navega a Entrada
  2. Selecciona el producto comprado
  3. Ingresa la cantidad recibida
  4. Registra la entrada
El stock del producto aumentará automáticamente.
  1. Navega a Salida
  2. Selecciona el producto vendido
  3. Ingresa la cantidad vendida
  4. Registra la salida
Si hay stock suficiente, se procesará la salida. Si no, verás un error.
Si realizas un conteo físico y encuentras discrepancias:Si encontraste más stock del registrado:
  • Usa Entrada para ajustar la diferencia positiva
Si encontraste menos stock del registrado:
  • Usa Salida para ajustar la diferencia negativa
Ejemplo: Sistema muestra 50 unidades, conteo físico encuentra 47
  • Registra una salida de 3 unidades para ajustar
Si tienes productos dañados o perdidos que no se pueden vender:
  1. Navega a Salida
  2. Selecciona el producto afectado
  3. Ingresa la cantidad de unidades dañadas/perdidas
  4. Registra la salida
Considera agregar un campo “motivo” en la tabla movimientos para distinguir ventas de pérdidas.

Flujo de Movimientos de Inventario

Mejoras Recomendadas

Página de historial

Crear una interfaz para consultar y filtrar todos los movimientos registrados con detalles completos.

Campo de motivo/notas

Agregar un campo opcional para documentar el motivo de cada movimiento (venta, devolución, ajuste, etc.).

Usuario que registró

Registrar qué usuario ($_SESSION['usuario']) realizó cada movimiento para auditoría completa.

Mostrar stock actual

En los formularios de entrada/salida, mostrar el stock actual de cada producto junto a su nombre.

Movimientos masivos

Permitir registrar múltiples productos en un solo movimiento (útil para compras con varios artículos).

Alertas de stock bajo

Mostrar una alerta cuando el stock de un producto caiga por debajo de un mínimo configurado.

Solución de Problemas

Verifica:
  • Que el stock en la base de datos coincida con el mostrado en la interfaz
  • Que otro usuario no haya registrado una salida simultáneamente
  • Ejecuta SELECT stock FROM productos WHERE id=X directamente en MySQL para confirmar el stock real
Si hay inconsistencia, puede deberse a:
  • Edición manual de la tabla productos (sin pasar por movimientos)
  • Errores en transacciones anteriores que no completaron ambas operaciones
Esto puede ocurrir si:
  • La consulta UPDATE tiene un error de sintaxis (revisa $conn->error)
  • El usuario MySQL no tiene permisos UPDATE en la tabla productos
  • La columna stock no existe o tiene un nombre diferente
  • Hay un trigger en la base de datos que interfiere con el UPDATE
Ejecuta las consultas manualmente en phpMyAdmin para identificar el problema.
Si la tabla movimientos no registra los movimientos:
  • Verifica que la tabla movimientos exista en la base de datos
  • Confirma que la consulta INSERT no tiene errores (revisa $conn->error)
  • Verifica permisos INSERT del usuario MySQL
  • Revisa que producto_id sea una clave foránea válida
Si logras registrar una salida que deja stock negativo:
  • La validación de stock puede tener un error lógico
  • Puede haber una condición de carrera (race condition) con múltiples usuarios
Solución temporal: Ejecuta un UPDATE para corregir:
UPDATE productos SET stock = 0 WHERE stock < 0;
Solución permanente: Agrega una restricción CHECK en MySQL 8.0+:
ALTER TABLE productos ADD CONSTRAINT stock_no_negativo CHECK (stock >= 0);

Reportes e Informes

Puedes generar reportes útiles consultando la tabla de movimientos:
SELECT 
    p.nombre,
    p.codigo,
    COUNT(*) as total_movimientos,
    SUM(CASE WHEN m.tipo = 'entrada' THEN m.cantidad ELSE 0 END) as total_entradas,
    SUM(CASE WHEN m.tipo = 'salida' THEN m.cantidad ELSE 0 END) as total_salidas
FROM movimientos m
INNER JOIN productos p ON m.producto_id = p.id
GROUP BY p.id
ORDER BY total_movimientos DESC
LIMIT 10;

Seguridad

Validación del lado del servidor: Aunque el formulario HTML tiene min="1", un atacante puede enviar valores negativos directamente. Siempre valida en el servidor:
$cantidad = intval($_POST['cantidad']);
if ($cantidad <= 0) {
    die("Cantidad inválida");
}
Prevención de inyección SQL: Usa consultas preparadas en todas las operaciones:
$stmt = $conn->prepare(
    "INSERT INTO movimientos (producto_id, tipo, cantidad) VALUES (?, ?, ?)"
);
$stmt->bind_param("isi", $producto_id, $tipo, $cantidad);
$stmt->execute();

Próximos Pasos

Gestión de Productos

Aprende a crear y editar productos

Estructura de Base de Datos

Entiende las relaciones entre tablas

Roles de Usuario

Configura permisos para movimientos

Solución de Problemas

Resuelve problemas comunes

Build docs developers (and LLMs) love