Ir al contenido principal

Sistema de gestión de inventario

Introducción

Un sistema de gestión de inventario es una aplicación fundamental en el mundo empresarial que permite realizar un seguimiento detallado de los productos, sus cantidades, ubicaciones y movimientos. En este artículo, crearemos un sistema básico de inventario en Python que nos permitirá aplicar muchos de los conceptos aprendidos hasta ahora, especialmente la programación orientada a objetos, el manejo de archivos y las estructuras de datos. Este proyecto nos acerca a aplicaciones del mundo real, donde Python se utiliza frecuentemente para crear soluciones empresariales prácticas y eficientes.

Diseño del sistema

Modelado de clases

Comenzaremos definiendo las clases principales que necesitaremos para nuestro sistema:

class Producto:
    """Representa un producto individual en el inventario."""
    
    def __init__(self, codigo, nombre, descripcion, precio, cantidad=0):
        """
        Inicializa un nuevo producto.
        
        Args:
            codigo: Identificador único del producto
            nombre: Nombre del producto
            descripcion: Descripción detallada
            precio: Precio unitario
            cantidad: Cantidad disponible en inventario
        """
        self.codigo = codigo
        self.nombre = nombre
        self.descripcion = descripcion
        self.precio = precio
        self.cantidad = cantidad
    
    def __str__(self):
        """Representación en texto del producto."""
        return f"{self.codigo} - {self.nombre} (Disponibles: {self.cantidad})"
    
    def valor_total(self):
        """Calcula el valor total del producto en inventario."""
        return self.precio * self.cantidad
    
    def actualizar_cantidad(self, cantidad):
        """Actualiza la cantidad disponible."""
        nueva_cantidad = self.cantidad + cantidad
        if nueva_cantidad < 0:
            raise ValueError(f"Stock insuficiente. Solo hay {self.cantidad} disponibles.")
        self.cantidad = nueva_cantidad

A continuación, definiremos la clase para gestionar todo el inventario:

class Inventario:
    """Gestiona la colección de productos en el inventario."""
    
    def __init__(self):
        """Inicializa un inventario vacío."""
        self.productos = {}  # Diccionario con código como clave y producto como valor
    
    def agregar_producto(self, producto):
        """Añade un nuevo producto al inventario."""
        if producto.codigo in self.productos:
            raise ValueError(f"Ya existe un producto con el código {producto.codigo}")
        self.productos[producto.codigo] = producto
        return True
    
    def eliminar_producto(self, codigo):
        """Elimina un producto del inventario."""
        if codigo not in self.productos:
            raise ValueError(f"No existe un producto con el código {codigo}")
        del self.productos[codigo]
        return True
    
    def buscar_producto(self, codigo):
        """Busca un producto por su código."""
        return self.productos.get(codigo)
    
    def buscar_por_nombre(self, nombre):
        """Busca productos cuyo nombre contenga el texto buscado."""
        return [p for p in self.productos.values() if nombre.lower() in p.nombre.lower()]
    
    def actualizar_stock(self, codigo, cantidad):
        """Actualiza la cantidad de un producto (positivo para entrada, negativo para salida)."""
        producto = self.buscar_producto(codigo)
        if not producto:
            raise ValueError(f"No existe un producto con el código {codigo}")
        producto.actualizar_cantidad(cantidad)
        return True
    
    def valor_total_inventario(self):
        """Calcula el valor total de todo el inventario."""
        return sum(producto.valor_total() for producto in self.productos.values())
    
    def listar_productos(self):
        """Devuelve una lista con todos los productos."""
        return list(self.productos.values())
    
    def productos_con_bajo_stock(self, umbral=5):
        """Devuelve productos con stock por debajo del umbral."""
        return [p for p in self.productos.values() if p.cantidad <= umbral]

Almacenamiento de datos

Para persistir los datos del inventario, implementaremos métodos para guardar y cargar desde archivos:

import json
import os

class GestorAlmacenamiento:
    """Gestiona la persistencia de datos del inventario."""
    
    @staticmethod
    def guardar_inventario(inventario, archivo="inventario.json"):
        """Guarda el inventario en un archivo JSON."""
        datos = {}
        for codigo, producto in inventario.productos.items():
            datos[codigo] = {
                "nombre": producto.nombre,
                "descripcion": producto.descripcion,
                "precio": producto.precio,
                "cantidad": producto.cantidad
            }
        
        try:
            with open(archivo, 'w', encoding='utf-8') as f:
                json.dump(datos, f, indent=4, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"Error al guardar el inventario: {e}")
            return False
    
    @staticmethod
    def cargar_inventario(archivo="inventario.json"):
        """Carga el inventario desde un archivo JSON."""
        inventario = Inventario()
        
        if not os.path.exists(archivo):
            return inventario
        
        try:
            with open(archivo, 'r', encoding='utf-8') as f:
                datos = json.load(f)
            
            for codigo, info in datos.items():
                producto = Producto(
                    codigo,
                    info["nombre"],
                    info["descripcion"],
                    info["precio"],
                    info["cantidad"]
                )
                inventario.agregar_producto(producto)
            
            return inventario
        except Exception as e:
            print(f"Error al cargar el inventario: {e}")
            return Inventario()

Registro de transacciones

Añadiremos un sistema de registro para mantener un historial de movimientos:

import datetime

class Transaccion:
    """Representa una transacción de entrada o salida de productos."""
    
    ENTRADA = "ENTRADA"
    SALIDA = "SALIDA"
    
    def __init__(self, codigo_producto, tipo, cantidad, fecha=None):
        """
        Inicializa una nueva transacción.
        
        Args:
            codigo_producto: Código del producto afectado
            tipo: Tipo de transacción (ENTRADA o SALIDA)
            cantidad: Cantidad de productos
            fecha: Fecha de la transacción (por defecto, ahora)
        """
        self.codigo_producto = codigo_producto
        
        if tipo not in [self.ENTRADA, self.SALIDA]:
            raise ValueError("Tipo de transacción inválido")
        self.tipo = tipo
        
        self.cantidad = abs(cantidad)  # Siempre positivo
        self.fecha = fecha or datetime.datetime.now()
    
    def __str__(self):
        """Representación en texto de la transacción."""
        return f"{self.fecha.strftime('%Y-%m-%d %H:%M:%S')} - {self.tipo}: {self.cantidad} unidades del producto {self.codigo_producto}"
    
    def to_dict(self):
        """Convierte la transacción en un diccionario para almacenamiento."""
        return {
            "codigo_producto": self.codigo_producto,
            "tipo": self.tipo,
            "cantidad": self.cantidad,
            "fecha": self.fecha.strftime('%Y-%m-%d %H:%M:%S')
        }

class RegistroTransacciones:
    """Gestiona el registro de transacciones del inventario."""
    
    def __init__(self):
        """Inicializa un registro vacío."""
        self.transacciones = []
    
    def registrar(self, transaccion):
        """Añade una transacción al registro."""
        self.transacciones.append(transaccion)
    
    def obtener_transacciones_producto(self, codigo_producto):
        """Devuelve las transacciones de un producto específico."""
        return [t for t in self.transacciones if t.codigo_producto == codigo_producto]
    
    def guardar_registro(self, archivo="transacciones.json"):
        """Guarda el registro en un archivo JSON."""
        datos = [t.to_dict() for t in self.transacciones]
        
        try:
            with open(archivo, 'w', encoding='utf-8') as f:
                json.dump(datos, f, indent=4, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"Error al guardar el registro: {e}")
            return False
    
    @staticmethod
    def cargar_registro(archivo="transacciones.json"):
        """Carga el registro desde un archivo JSON."""
        registro = RegistroTransacciones()
        
        if not os.path.exists(archivo):
            return registro
        
        try:
            with open(archivo, 'r', encoding='utf-8') as f:
                datos = json.load(f)
            
            for info in datos:
                fecha = datetime.datetime.strptime(info["fecha"], '%Y-%m-%d %H:%M:%S')
                transaccion = Transaccion(
                    info["codigo_producto"],
                    info["tipo"],
                    info["cantidad"],
                    fecha
                )
                registro.registrar(transaccion)
            
            return registro
        except Exception as e:
            print(f"Error al cargar el registro: {e}")
            return RegistroTransacciones()

Interfaz para el usuario

Finalmente, crearemos una interfaz de texto para interactuar con el sistema:

class InterfazInventario:
    """Interfaz de texto para el sistema de inventario."""
    
    def __init__(self):
        """Inicializa la interfaz y carga los datos."""
        self.inventario = GestorAlmacenamiento.cargar_inventario()
        self.registro = RegistroTransacciones.cargar_registro()
    
    def mostrar_menu(self):
        """Muestra el menú principal."""
        print("\n=== SISTEMA DE GESTIÓN DE INVENTARIO ===")
        print("1. Ver todos los productos")
        print("2. Buscar producto")
        print("3. Añadir nuevo producto")
        print("4. Registrar entrada de stock")
        print("5. Registrar salida de stock")
        print("6. Eliminar producto")
        print("7. Ver valor total del inventario")
        print("8. Ver productos con bajo stock")
        print("9. Ver historial de transacciones")
        print("0. Guardar y salir")
        
        try:
            opcion = int(input("\nSeleccione una opción: "))
            return opcion
        except ValueError:
            print("Por favor, introduzca un número válido.")
            return None
    
    def ejecutar(self):
        """Ejecuta el bucle principal de la interfaz."""
        while True:
            opcion = self.mostrar_menu()
            
            if opcion == 1:
                self.listar_productos()
            elif opcion == 2:
                self.buscar_producto()
            elif opcion == 3:
                self.anadir_producto()
            elif opcion == 4:
                self.registrar_entrada()
            elif opcion == 5:
                self.registrar_salida()
            elif opcion == 6:
                self.eliminar_producto()
            elif opcion == 7:
                self.mostrar_valor_total()
            elif opcion == 8:
                self.mostrar_bajo_stock()
            elif opcion == 9:
                self.mostrar_transacciones()
            elif opcion == 0:
                self.guardar_y_salir()
                break
            else:
                print("Opción no válida. Inténtelo de nuevo.")
            
            input("\nPulse Enter para continuar...")
    
    def listar_productos(self):
        """Muestra todos los productos del inventario."""
        productos = self.inventario.listar_productos()
        if not productos:
            print("No hay productos en el inventario.")
            return
        
        print("\n--- LISTA DE PRODUCTOS ---")
        for producto in productos:
            print(f"{producto.codigo} - {producto.nombre} - {producto.cantidad} unidades - {producto.precio}€/u")
    
    # Aquí irían el resto de métodos de la interfaz...
    
    def guardar_y_salir(self):
        """Guarda los datos y termina el programa."""
        GestorAlmacenamiento.guardar_inventario(self.inventario)
        self.registro.guardar_registro()
        print("Datos guardados correctamente. ¡Hasta pronto!")

# Punto de entrada de la aplicación
if __name__ == "__main__":
    interfaz = InterfazInventario()
    interfaz.ejecutar()

Funcionalidades adicionales

Podemos añadir otras funcionalidades para completar nuestro sistema:

# Método para generar informes en formato CSV
def exportar_inventario_csv(inventario, archivo="inventario.csv"):
    """Exporta el inventario a un archivo CSV."""
    try:
        with open(archivo, 'w', encoding='utf-8') as f:
            # Cabecera
            f.write("Código,Nombre,Descripción,Precio,Cantidad,Valor Total\n")
            
            # Datos
            for producto in inventario.listar_productos():
                f.write(f'"{producto.codigo}","{producto.nombre}","{producto.descripcion}",'
                        f'{producto.precio},{producto.cantidad},{producto.valor_total()}\n')
        return True
    except Exception as e:
        print(f"Error al exportar a CSV: {e}")
        return False

# Método para buscar productos por rango de precio
def buscar_por_rango_precio(inventario, precio_min, precio_max):
    """Busca productos dentro de un rango de precios."""
    return [p for p in inventario.listar_productos() 
            if precio_min <= p.precio <= precio_max]

# Categorización de productos
class ProductoCategorizado(Producto):
    """Extensión de Producto que incluye categoría."""
    
    def __init__(self, codigo, nombre, descripcion, precio, cantidad=0, categoria="General"):
        super().__init__(codigo, nombre, descripcion, precio, cantidad)
        self.categoria = categoria
    
    def __str__(self):
        return f"{super().__str__()} - Categoría: {self.categoria}"

Resumen

En este artículo, hemos desarrollado un sistema básico pero funcional de gestión de inventario utilizando Python. Este proyecto integra varios conceptos fundamentales como la programación orientada a objetos (clases, herencia, encapsulamiento), el manejo de archivos JSON para la persistencia de datos, y la creación de una interfaz de usuario basada en texto. El sistema permite realizar operaciones esenciales como añadir productos, registrar entradas y salidas, buscar información y generar informes básicos. Este tipo de aplicación representa un ejemplo real de cómo Python puede utilizarse para resolver problemas empresariales prácticos, y sienta las bases para comprender sistemas más complejos que podrían incorporar interfaces gráficas o bases de datos relacionales.