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.