Mejoras y Desafíos: Evolución Empresarial
¡Felicidades! 🎉 Has construido un sistema empresarial completo y funcional. Pero como todo gran arquitecto sabe, la construcción nunca termina realmente. Siempre hay nuevos pisos que agregar, tecnologías que integrar y funcionalidades que perfeccionar.
Esta sección es tu hoja de ruta hacia la excelencia, donde transformarás tu buen sistema en un sistema extraordinario. Cada mejora que implementes te acercará más al nivel de un desarrollador profesional.
🚀 Niveles de Evolución: Tu Camino al Dominio
🥉 Nivel Bronce: Mejoras Fundamentales
“Perfeccionando los cimientos”
Mejora 1: Base de Datos Profesional (SQLite)
Evoluciona de archivos JSON a una base de datos real:
# archivo: database.py
"""
Sistema de base de datos profesional con SQLite
Reemplaza el almacenamiento JSON con una base de datos real
"""
import sqlite3
import json
from datetime import datetime
from typing import List, Dict, Optional, Tuple
from pathlib import Path
from config import config
from logger import logger
class GestorBaseDatos:
"""Gestor de base de datos SQLite para el sistema empresarial"""
def __init__(self, db_path: Path = None):
self.db_path = db_path or config.DATOS_DIR / "inventario.db"
self.init_database()
def init_database(self):
"""Inicializa la base de datos con todas las tablas necesarias"""
try:
conn = self.get_connection()
cursor = conn.cursor()
# Tabla de productos
cursor.execute('''
CREATE TABLE IF NOT EXISTS productos (
codigo TEXT PRIMARY KEY,
nombre TEXT NOT NULL,
precio REAL NOT NULL CHECK(precio > 0),
stock INTEGER NOT NULL CHECK(stock >= 0),
categoria TEXT DEFAULT 'General',
stock_minimo INTEGER DEFAULT 5 CHECK(stock_minimo >= 0),
proveedor TEXT,
ubicacion TEXT,
descripcion TEXT,
activo BOOLEAN DEFAULT 1,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
fecha_actualizacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Tabla de ventas
cursor.execute('''
CREATE TABLE IF NOT EXISTS ventas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
codigo_producto TEXT NOT NULL,
cantidad INTEGER NOT NULL CHECK(cantidad > 0),
precio_unitario REAL NOT NULL CHECK(precio_unitario > 0),
descuento REAL DEFAULT 0 CHECK(descuento >= 0 AND descuento <= 1),
impuesto REAL DEFAULT 0.16,
total REAL NOT NULL,
usuario TEXT DEFAULT 'sistema',
fecha_venta TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (codigo_producto) REFERENCES productos (codigo)
)
''')
# Tabla de categorías
cursor.execute('''
CREATE TABLE IF NOT EXISTS categorias (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT UNIQUE NOT NULL,
descripcion TEXT,
activa BOOLEAN DEFAULT 1,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Tabla de usuarios
cursor.execute('''
CREATE TABLE IF NOT EXISTS usuarios (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
nombre_completo TEXT NOT NULL,
email TEXT UNIQUE,
rol TEXT DEFAULT 'operador',
activo BOOLEAN DEFAULT 1,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ultimo_acceso TIMESTAMP
)
''')
# Tabla de configuración
cursor.execute('''
CREATE TABLE IF NOT EXISTS configuracion (
clave TEXT PRIMARY KEY,
valor TEXT NOT NULL,
descripcion TEXT,
fecha_actualizacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Índices para mejorar rendimiento
cursor.execute('CREATE INDEX IF NOT EXISTS idx_productos_categoria ON productos(categoria)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_productos_activo ON productos(activo)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ventas_fecha ON ventas(fecha_venta)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ventas_producto ON ventas(codigo_producto)')
conn.commit()
logger.info("Base de datos inicializada correctamente")
except Exception as e:
logger.error(f"Error inicializando base de datos: {e}")
raise
finally:
conn.close()
def get_connection(self) -> sqlite3.Connection:
"""Obtiene conexión a la base de datos"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row # Para acceso por nombre de columna
return conn
def migrar_desde_json(self, archivo_productos: Path):
"""Migra datos existentes desde JSON a SQLite"""
try:
if not archivo_productos.exists():
logger.info("No hay archivo JSON para migrar")
return
with open(archivo_productos, 'r', encoding='utf-8') as f:
productos_json = json.load(f)
conn = self.get_connection()
cursor = conn.cursor()
productos_migrados = 0
for codigo, datos in productos_json.items():
try:
cursor.execute('''
INSERT OR REPLACE INTO productos
(codigo, nombre, precio, stock, categoria, stock_minimo, fecha_creacion)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (
codigo,
datos['nombre'],
datos['precio'],
datos['stock'],
datos.get('categoria', 'General'),
datos.get('stock_minimo', 5),
datos.get('fecha_creacion', datetime.now().isoformat())
))
productos_migrados += 1
except Exception as e:
logger.error(f"Error migrando producto {codigo}: {e}")
conn.commit()
logger.info(f"Migración completada: {productos_migrados} productos")
except Exception as e:
logger.error(f"Error en migración: {e}")
finally:
conn.close()
def ejecutar_consulta(self, query: str, params: tuple = ()) -> List[sqlite3.Row]:
"""Ejecuta una consulta SELECT y retorna los resultados"""
try:
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(query, params)
resultados = cursor.fetchall()
return resultados
except Exception as e:
logger.error(f"Error ejecutando consulta: {e}")
return []
finally:
conn.close()
def ejecutar_comando(self, query: str, params: tuple = ()) -> bool:
"""Ejecuta un comando INSERT/UPDATE/DELETE"""
try:
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(query, params)
conn.commit()
return True
except Exception as e:
logger.error(f"Error ejecutando comando: {e}")
return False
finally:
conn.close()
# Ejemplo de uso del nuevo sistema de base de datos
class ProductoConDB:
"""Versión mejorada de la clase Producto que usa SQLite"""
def __init__(self, db_manager: GestorBaseDatos):
self.db = db_manager
def crear_producto(self, codigo: str, nombre: str, precio: float,
stock: int, categoria: str = "General") -> Tuple[bool, str]:
"""Crea un nuevo producto en la base de datos"""
query = '''
INSERT INTO productos (codigo, nombre, precio, stock, categoria)
VALUES (?, ?, ?, ?, ?)
'''
if self.db.ejecutar_comando(query, (codigo, nombre, precio, stock, categoria)):
logger.operacion("CREAR_PRODUCTO", detalles=f"Código: {codigo}")
return True, f"Producto {codigo} creado exitosamente"
else:
return False, "Error al crear el producto"
def obtener_producto(self, codigo: str) -> Optional[Dict]:
"""Obtiene un producto por su código"""
query = "SELECT * FROM productos WHERE codigo = ? AND activo = 1"
resultados = self.db.ejecutar_consulta(query, (codigo,))
if resultados:
return dict(resultados[0])
return None
def listar_productos(self, categoria: str = None, activos_solo: bool = True) -> List[Dict]:
"""Lista productos con filtros opcionales"""
query = "SELECT * FROM productos"
params = []
condiciones = []
if activos_solo:
condiciones.append("activo = 1")
if categoria:
condiciones.append("categoria = ?")
params.append(categoria)
if condiciones:
query += " WHERE " + " AND ".join(condiciones)
query += " ORDER BY nombre"
resultados = self.db.ejecutar_consulta(query, tuple(params))
return [dict(row) for row in resultados]
def actualizar_stock(self, codigo: str, nuevo_stock: int) -> Tuple[bool, str]:
"""Actualiza el stock de un producto"""
query = '''
UPDATE productos
SET stock = ?, fecha_actualizacion = CURRENT_TIMESTAMP
WHERE codigo = ? AND activo = 1
'''
if self.db.ejecutar_comando(query, (nuevo_stock, codigo)):
logger.operacion("ACTUALIZAR_STOCK", detalles=f"Código: {codigo}, Nuevo stock: {nuevo_stock}")
return True, f"Stock actualizado para {codigo}"
else:
return False, "Error al actualizar stock"
def productos_stock_bajo(self) -> List[Dict]:
"""Obtiene productos con stock bajo"""
query = '''
SELECT * FROM productos
WHERE stock <= stock_minimo AND activo = 1
ORDER BY (stock - stock_minimo), nombre
'''
resultados = self.db.ejecutar_consulta(query)
return [dict(row) for row in resultados]
Mejora 2: Sistema de Autenticación y Usuarios
# archivo: autenticacion.py
"""
Sistema de autenticación y gestión de usuarios
"""
import hashlib
import secrets
from datetime import datetime, timedelta
from typing import Optional, Dict, List, Tuple
class GestorUsuarios:
"""Gestor de usuarios y autenticación"""
def __init__(self, db_manager):
self.db = db_manager
self.usuario_actual = None
self.sesion_activa = False
def hash_password(self, password: str, salt: str = None) -> Tuple[str, str]:
"""Genera hash seguro de contraseña"""
if not salt:
salt = secrets.token_hex(16)
# Usar PBKDF2 para mayor seguridad
password_hash = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt.encode('utf-8'),
100000 # 100,000 iteraciones
)
return password_hash.hex(), salt
def crear_usuario(self, username: str, password: str, nombre_completo: str,
email: str = None, rol: str = "operador") -> Tuple[bool, str]:
"""Crea un nuevo usuario"""
# Verificar si el usuario ya existe
if self.obtener_usuario(username):
return False, "El usuario ya existe"
# Generar hash de contraseña
password_hash, salt = self.hash_password(password)
# Insertar usuario
query = '''
INSERT INTO usuarios (username, password_hash, salt, nombre_completo, email, rol)
VALUES (?, ?, ?, ?, ?, ?)
'''
if self.db.ejecutar_comando(query, (username, password_hash, salt, nombre_completo, email, rol)):
logger.operacion("CREAR_USUARIO", detalles=f"Usuario: {username}, Rol: {rol}")
return True, f"Usuario {username} creado exitosamente"
else:
return False, "Error al crear usuario"
def autenticar(self, username: str, password: str) -> Tuple[bool, str]:
"""Autentica un usuario"""
usuario = self.obtener_usuario(username)
if not usuario:
return False, "Usuario no encontrado"
if not usuario['activo']:
return False, "Usuario inactivo"
# Verificar contraseña
password_hash, _ = self.hash_password(password, usuario['salt'])
if password_hash == usuario['password_hash']:
self.usuario_actual = usuario
self.sesion_activa = True
# Actualizar último acceso
self.db.ejecutar_comando(
"UPDATE usuarios SET ultimo_acceso = CURRENT_TIMESTAMP WHERE username = ?",
(username,)
)
logger.operacion("LOGIN", usuario=username)
return True, f"Bienvenido, {usuario['nombre_completo']}"
else:
logger.operacion("LOGIN_FALLIDO", usuario=username)
return False, "Contraseña incorrecta"
def cerrar_sesion(self):
"""Cierra la sesión actual"""
if self.usuario_actual:
logger.operacion("LOGOUT", usuario=self.usuario_actual['username'])
self.usuario_actual = None
self.sesion_activa = False
def tiene_permiso(self, operacion: str) -> bool:
"""Verifica si el usuario actual tiene permiso para una operación"""
if not self.sesion_activa or not self.usuario_actual:
return False
rol = self.usuario_actual['rol']
# Definir permisos por rol
permisos = {
'admin': ['*'], # Todos los permisos
'gerente': ['ver_productos', 'crear_producto', 'actualizar_producto',
'procesar_venta', 'ver_reportes', 'gestionar_usuarios'],
'vendedor': ['ver_productos', 'procesar_venta', 'ver_reportes_basicos'],
'operador': ['ver_productos', 'actualizar_stock']
}
permisos_rol = permisos.get(rol, [])
return '*' in permisos_rol or operacion in permisos_rol
Mejora 3: Sistema de Reportes Avanzados
# archivo: reportes_avanzados.py
"""
Sistema de reportes empresariales avanzados con gráficos y análisis
"""
import matplotlib.pyplot as plt
import pandas as pd
from datetime import datetime, timedelta
import seaborn as sns
from typing import Dict, List, Optional
class GeneradorReportesAvanzados:
"""Generador de reportes empresariales con visualizaciones"""
def __init__(self, db_manager):
self.db = db_manager
# Configurar estilo de gráficos
plt.style.use('seaborn-v0_8')
sns.set_palette("husl")
def reporte_ventas_temporales(self, dias: int = 30) -> Dict:
"""Genera reporte de ventas por período con gráfico"""
fecha_inicio = datetime.now() - timedelta(days=dias)
query = '''
SELECT DATE(fecha_venta) as fecha,
COUNT(*) as num_ventas,
SUM(total) as total_ventas,
AVG(total) as promedio_venta
FROM ventas
WHERE fecha_venta >= ?
GROUP BY DATE(fecha_venta)
ORDER BY fecha
'''
resultados = self.db.ejecutar_consulta(query, (fecha_inicio.isoformat(),))
if not resultados:
return {"mensaje": "No hay datos de ventas para el período"}
# Convertir a DataFrame para análisis
df = pd.DataFrame([dict(row) for row in resultados])
df['fecha'] = pd.to_datetime(df['fecha'])
# Crear gráfico
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
# Gráfico de ventas diarias
ax1.plot(df['fecha'], df['total_ventas'], marker='o', linewidth=2, markersize=6)
ax1.set_title(f'Ventas Diarias - Últimos {dias} días', fontsize=14, fontweight='bold')
ax1.set_ylabel('Total Ventas ($)')
ax1.grid(True, alpha=0.3)
ax1.tick_params(axis='x', rotation=45)
# Gráfico de número de transacciones
ax2.bar(df['fecha'], df['num_ventas'], alpha=0.7, color='skyblue')
ax2.set_title('Número de Transacciones Diarias', fontsize=14, fontweight='bold')
ax2.set_ylabel('Número de Ventas')
ax2.set_xlabel('Fecha')
ax2.grid(True, alpha=0.3)
ax2.tick_params(axis='x', rotation=45)
plt.tight_layout()
# Guardar gráfico
archivo_grafico = config.REPORTES_DIR / f"ventas_temporales_{datetime.now().strftime('%Y%m%d')}.png"
plt.savefig(archivo_grafico, dpi=300, bbox_inches='tight')
plt.show()
# Estadísticas resumidas
estadisticas = {
"periodo": f"{fecha_inicio.strftime('%d/%m/%Y')} - {datetime.now().strftime('%d/%m/%Y')}",
"total_ventas": float(df['total_ventas'].sum()),
"promedio_diario": float(df['total_ventas'].mean()),
"mejor_dia": {
"fecha": df.loc[df['total_ventas'].idxmax(), 'fecha'].strftime('%d/%m/%Y'),
"ventas": float(df['total_ventas'].max())
},
"total_transacciones": int(df['num_ventas'].sum()),
"promedio_transacciones": float(df['num_ventas'].mean()),
"archivo_grafico": str(archivo_grafico)
}
return estadisticas
def analisis_productos_abc(self) -> Dict:
"""Análisis ABC de productos (80/20 rule)"""
query = '''
SELECT p.codigo, p.nombre, p.categoria,
COALESCE(SUM(v.cantidad * v.precio_unitario), 0) as ingresos_totales,
COALESCE(SUM(v.cantidad), 0) as unidades_vendidas,
p.stock, p.precio
FROM productos p
LEFT JOIN ventas v ON p.codigo = v.codigo_producto
WHERE p.activo = 1
GROUP BY p.codigo, p.nombre, p.categoria, p.stock, p.precio
ORDER BY ingresos_totales DESC
'''
resultados = self.db.ejecutar_consulta(query)
if not resultados:
return {"mensaje": "No hay datos suficientes para análisis ABC"}
df = pd.DataFrame([dict(row) for row in resultados])
# Calcular porcentajes acumulados
df['porcentaje_ingresos'] = (df['ingresos_totales'] / df['ingresos_totales'].sum()) * 100
df['porcentaje_acumulado'] = df['porcentaje_ingresos'].cumsum()
# Clasificar productos ABC
def clasificar_abc(porcentaje_acum):
if porcentaje_acum <= 80:
return 'A'
elif porcentaje_acum <= 95:
return 'B'
else:
return 'C'
df['clasificacion'] = df['porcentaje_acumulado'].apply(clasificar_abc)
# Crear gráfico de Pareto
fig, ax1 = plt.subplots(figsize=(14, 8))
# Barras de ingresos
bars = ax1.bar(range(len(df)), df['ingresos_totales'], alpha=0.7, color='steelblue')
ax1.set_xlabel('Productos (ordenados por ingresos)')
ax1.set_ylabel('Ingresos Totales ($)', color='steelblue')
ax1.tick_params(axis='y', labelcolor='steelblue')
# Línea de porcentaje acumulado
ax2 = ax1.twinx()
ax2.plot(range(len(df)), df['porcentaje_acumulado'], color='red', marker='o', linewidth=2)
ax2.set_ylabel('Porcentaje Acumulado (%)', color='red')
ax2.tick_params(axis='y', labelcolor='red')
ax2.set_ylim(0, 100)
# Líneas de referencia ABC
ax2.axhline(y=80, color='orange', linestyle='--', alpha=0.7, label='80% (A-B)')
ax2.axhline(y=95, color='green', linestyle='--', alpha=0.7, label='95% (B-C)')
plt.title('Análisis ABC de Productos (Principio de Pareto)', fontsize=16, fontweight='bold')
plt.legend()
plt.tight_layout()
# Guardar gráfico
archivo_grafico = config.REPORTES_DIR / f"analisis_abc_{datetime.now().strftime('%Y%m%d')}.png"
plt.savefig(archivo_grafico, dpi=300, bbox_inches='tight')
plt.show()
# Estadísticas por clasificación
resumen_abc = df.groupby('clasificacion').agg({
'codigo': 'count',
'ingresos_totales': 'sum',
'unidades_vendidas': 'sum'
}).round(2)
return {
"productos_a": int(resumen_abc.loc['A', 'codigo']) if 'A' in resumen_abc.index else 0,
"productos_b": int(resumen_abc.loc['B', 'codigo']) if 'B' in resumen_abc.index else 0,
"productos_c": int(resumen_abc.loc['C', 'codigo']) if 'C' in resumen_abc.index else 0,
"ingresos_a": float(resumen_abc.loc['A', 'ingresos_totales']) if 'A' in resumen_abc.index else 0,
"archivo_grafico": str(archivo_grafico),
"productos_detalle": df.to_dict('records')
}
self.crear_interfaz()
def crear_interfaz(self):
"""Crea la interfaz gráfica"""
# Frame principal
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Botones principales
ttk.Button(main_frame, text="Agregar Producto",
command=self.ventana_agregar_producto).grid(row=0, column=0, padx=5)
ttk.Button(main_frame, text="Listar Productos",
command=self.mostrar_productos).grid(row=0, column=1, padx=5)
command=self.listar_productos).grid(row=0, column=1, padx=5)
# Tabla de productos
self.tree = ttk.Treeview(main_frame, columns=("Código", "Nombre", "Precio", "Stock"))
self.tree.grid(row=1, column=0, columnspan=3, pady=10)
3. API REST
Crear una API con Flask:
from flask import Flask, jsonify, request
from inventario import GestorInventario
app = Flask(__name__)
gestor = GestorInventario()
@app.route("/api/productos", methods=["GET"])
def obtener_productos():
productos = gestor.listar_productos()
return jsonify([p.to_dict() for p in productos])
@app.route('/api/productos', methods=['POST'])
def agregar_producto():
datos = request.json
exito, mensaje = gestor.agregar_producto(
datos['codigo'], datos['nombre'],
datos['precio'], datos['stock']
)
return jsonify({'exito': exito, 'mensaje': mensaje})
if __name__ == '__main__':
app.run(debug=True)
app.run(debug=True)
Funcionalidades adicionales
1. Sistema de usuarios
class Usuario:
def __init__(self, username, password, rol="empleado"):
self.username = username
self.password = password # En producción, usar hash
self.rol = rol
self.fecha_creacion = datetime.now()
class GestorUsuarios:
def __init__(self):
self.usuarios = {}
self.usuario_actual = None
def autenticar(self, username, password):
"""Autentica un usuario"""
usuario = self.usuarios.get(username)
if usuario and usuario.password == password:
self.usuario_actual = usuario
return True
return False
2. Historial de movimientos
class MovimientoInventario:
def __init__(self, tipo, producto_codigo, cantidad, usuario, motivo=""):
self.tipo = tipo # "entrada", "salida", "ajuste"
self.producto_codigo = producto_codigo
self.cantidad = cantidad
self.usuario = usuario
self.motivo = motivo
self.fecha = datetime.now()
class GestorMovimientos:
def __init__(self):
self.movimientos = []
def registrar_movimiento(self, tipo, producto_codigo, cantidad, usuario, motivo=""):
"""Registra un movimiento de inventario"""
movimiento = MovimientoInventario(tipo, producto_codigo, cantidad, usuario, motivo)
self.movimientos.append(movimiento)
3. Códigos de barras
import barcode
from barcode.writer import ImageWriter
def generar_codigo_barras(codigo_producto):
"""Genera código de barras para un producto"""
code128 = barcode.get_barcode_class('code128')
codigo = code128(codigo_producto, writer=ImageWriter())
filename = f"codigos_barras/{codigo_producto}.png"
codigo.save(filename)
return filename
Desafíos de programación
Desafío 1: Predicción de demanda
Implementa un algoritmo simple para predecir cuándo se agotará un producto:
def predecir_agotamiento(producto, historial_ventas):
"""Predice cuándo se agotará un producto"""
if not historial_ventas:
return None
# Calcular promedio de ventas diarias
ventas_diarias = sum(historial_ventas) / len(historial_ventas)
if ventas_diarias <= 0:
return None
# Días hasta agotamiento
dias_restantes = producto.stock / ventas_diarias
fecha_agotamiento = datetime.now() + timedelta(days=dias_restantes)
return fecha_agotamiento
Desafío 2: Optimización de inventario
Calcula el punto de reorden óptimo:
def calcular_punto_reorden(demanda_promedio, tiempo_reposicion, stock_seguridad):
"""Calcula el punto de reorden óptimo"""
return (demanda_promedio * tiempo_reposicion) + stock_seguridad
def sugerir_cantidad_pedido(producto, costo_pedido, costo_almacenamiento, demanda_anual):
"""Sugiere cantidad óptima de pedido usando EOQ"""
import math
# Fórmula EOQ (Economic Order Quantity)
eoq = math.sqrt((2 * demanda_anual * costo_pedido) / costo_almacenamiento)
return round(eoq)
Desafío 3: Dashboard en tiempo real
Crea un dashboard que se actualice automáticamente:
import threading
import time
class DashboardTiempoReal:
def __init__(self, gestor):
self.gestor = gestor
self.ejecutando = False
def iniciar_monitoreo(self):
"""Inicia el monitoreo en tiempo real"""
self.ejecutando = True
thread = threading.Thread(target=self._monitorear)
thread.daemon = True
thread.start()
def _monitorear(self):
"""Monitorea el inventario continuamente"""
while self.ejecutando:
self._actualizar_dashboard()
time.sleep(30) # Actualizar cada 30 segundos
def _actualizar_dashboard(self):
"""Actualiza la información del dashboard"""
stats = self.gestor.obtener_estadisticas()
productos_bajo = self.gestor.obtener_productos_stock_bajo()
# Aquí actualizarías la interfaz gráfica
print(f"Dashboard actualizado: {len(productos_bajo)} productos con stock bajo")
Mejores prácticas implementadas
1. Logging
import logging
# Configurar logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('inventario.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class GestorInventario:
def agregar_producto(self, codigo, nombre, precio, stock):
logger.info(f"Agregando producto: {codigo} - {nombre}")
# ... resto del código
2. Configuración externa
# archivo: config.py
import json
class Configuracion:
def __init__(self, archivo_config="config.json"):
self.config = self.cargar_configuracion(archivo_config)
def cargar_configuracion(self, archivo):
"""Carga configuración desde archivo JSON"""
try:
with open(archivo, 'r') as f:
return json.load(f)
except FileNotFoundError:
return self.configuracion_por_defecto()
def configuracion_por_defecto(self):
"""Configuración por defecto"""
return {
"stock_minimo_global": 5,
"moneda": "USD",
"idioma": "es",
"backup_automatico": True,
"intervalo_backup": 3600 # segundos
}
3. Tests automatizados
import unittest
from inventario import GestorInventario, Producto
class TestInventario(unittest.TestCase):
def setUp(self):
"""Configuración antes de cada test"""
self.gestor = GestorInventario("test_db.json")
def test_agregar_producto(self):
"""Test agregar producto"""
exito, mensaje = self.gestor.agregar_producto("TEST01", "Test", 100, 10)
self.assertTrue(exito)
self.assertIn("TEST01", self.gestor.productos)
def test_procesar_venta(self):
"""Test procesar venta"""
self.gestor.agregar_producto("TEST02", "Test", 50, 20)
exito, mensaje, total = self.gestor.procesar_venta("TEST02", 5)
self.assertTrue(exito)
self.assertEqual(total, 250.0)
def tearDown(self):
"""Limpieza después de cada test"""
import os
if os.path.exists("test_db.json"):
os.remove("test_db.json")
if __name__ == '__main__':
unittest.main()
Próximos pasos
- Implementa una mejora: Elige una de las sugerencias y desarróllala
- Optimiza el rendimiento: Identifica cuellos de botella
- Añade más validaciones: Mejora la robustez del sistema
- Crea documentación: Escribe manuales de usuario
- Despliega el sistema: Ponlo en producción
¡El proyecto está listo para crecer y evolucionar según tus necesidades!