Guía de Referencia 5: Ensamblaje Final y Pruebas
Llegó el momento de ver el sistema completo. Aquí no hay piezas nuevas: este es el archivo inventario.py con todo lo construido en las guías 1 a 4, en el orden correcto y listo para ejecutarse.
Una advertencia antes de mirar: este archivo trae resueltas las opciones 3 y 4 del menú, las mismas que el Mostrador te dejó como reto. Si todavía estás resolviéndolas, no leas los case "3" y case "4". Regresa cuando tengas tu versión y compara.
El Archivo Completo: inventario.py
"""SGI v1.0: Sistema de Gestión de Inventario del Almacén Digital."""
import json
from pathlib import Path
RUTA_INVENTARIO = Path("inventario.json")
# --- Modelo de datos ---
def crear_producto(id_producto: str, nombre: str,
precio: float, stock: int) -> dict:
"""Crea el diccionario que representa una caja del almacén."""
return {
"id": id_producto,
"nombre": nombre,
"precio": precio,
"stock": stock,
}
# --- Lógica de negocio ---
def generar_id(inventario: list[dict]) -> str:
"""Genera un ID nuevo: el mayor ID existente más uno."""
if len(inventario) == 0:
return "1"
ids = [int(producto["id"]) for producto in inventario]
return str(max(ids) + 1)
def agregar_producto(inventario: list[dict], nombre: str,
precio: float, stock: int) -> dict:
"""Crea un producto con ID nuevo y lo añade al estante."""
producto = crear_producto(generar_id(inventario), nombre, precio, stock)
inventario.append(producto)
return producto
def buscar_producto(inventario: list[dict], id_producto: str) -> dict | None:
"""Busca un producto por su ID. Devuelve None si no existe."""
for producto in inventario:
if producto["id"] == id_producto:
return producto
return None
def eliminar_producto(inventario: list[dict], id_producto: str) -> list[dict]:
"""Devuelve una lista nueva sin el producto indicado."""
return [
producto for producto in inventario if producto["id"] != id_producto
]
# --- Persistencia ---
def cargar_datos(ruta: Path) -> list[dict]:
"""Lee el archivo JSON y devuelve la lista de productos."""
if not ruta.exists():
return []
with open(ruta, encoding="utf-8") as archivo:
return json.load(archivo)
def guardar_datos(ruta: Path, inventario: list[dict]) -> None:
"""Sobrescribe el archivo JSON con los datos actuales."""
with open(ruta, mode="w", encoding="utf-8") as archivo:
json.dump(inventario, archivo, indent=2, ensure_ascii=False)
# --- Interfaz de usuario ---
def pedir_decimal(mensaje: str) -> float:
"""Insiste hasta recibir un número decimal válido y no negativo."""
while True:
try:
numero = float(input(mensaje))
if numero < 0:
print("No se permiten cantidades negativas.")
continue
return numero
except ValueError:
print("Eso no es un número. Intenta de nuevo (p. ej. 5.50).")
def pedir_entero(mensaje: str) -> int:
"""Insiste hasta recibir un número entero válido y no negativo."""
while True:
try:
numero = int(input(mensaje))
if numero < 0:
print("No se permiten cantidades negativas.")
continue
return numero
except ValueError:
print("Eso no es un número entero. Intenta de nuevo (p. ej. 100).")
def mostrar_reporte(inventario: list[dict]) -> None:
"""Imprime el estado actual del almacén."""
print(f"\n--- INVENTARIO ({len(inventario)} productos) ---")
if len(inventario) == 0:
print("El almacén está vacío.")
for producto in inventario:
ficha = f"[{producto['id']}] {producto['nombre']}"
print(f"{ficha} | ${producto['precio']} | stock: {producto['stock']}")
def mostrar_menu() -> None:
"""Imprime las opciones disponibles."""
print("\n=== SGI v1.0: ALMACÉN DIGITAL ===")
print("1. Mostrar inventario")
print("2. Agregar producto")
print("3. Buscar producto")
print("4. Eliminar producto")
print("5. Guardar y salir")
def main() -> None:
"""El cerebro del sistema: carga los datos, atiende y guarda."""
inventario = cargar_datos(RUTA_INVENTARIO)
print(f"Sistema iniciado: {len(inventario)} productos en el almacén.")
while True:
mostrar_menu()
opcion = input("Elige una opción: ")
match opcion:
case "1":
mostrar_reporte(inventario)
case "2":
nombre = input("Nombre del producto: ")
precio = pedir_decimal("Precio unitario: ")
stock = pedir_entero("Stock inicial: ")
producto = agregar_producto(inventario, nombre, precio, stock)
print(
f"Registrado '{producto['nombre']}' "
f"con ID {producto['id']}."
)
case "3":
id_buscado = input("ID del producto a buscar: ")
producto = buscar_producto(inventario, id_buscado)
if producto is None:
print(f"No hay ningún producto con ID {id_buscado}.")
else:
ficha = f"[{producto['id']}] {producto['nombre']}"
detalle = (
f"${producto['precio']} | stock: {producto['stock']}"
)
print(f"{ficha} | {detalle}")
case "4":
id_eliminar = input("ID del producto a eliminar: ")
if buscar_producto(inventario, id_eliminar) is None:
print(f"No hay ningún producto con ID {id_eliminar}.")
else:
inventario = eliminar_producto(inventario, id_eliminar)
print(f"Producto {id_eliminar} retirado del estante.")
case "5":
guardar_datos(RUTA_INVENTARIO, inventario)
print("Inventario guardado. Hasta pronto, Gerente.")
break
case _:
print("Opción no válida. Elige un número del 1 al 5.")
if __name__ == "__main__":
main()
Nota: el archivo ya no termina en
main()a secas. Eseif __name__ == "__main__":es un guardián: ejecuta el menú solo cuando corres el archivo directo conpython inventario.py. Sin él, cualquierimport inventario(el de las pruebas de la Guía 6, por ejemplo) arrancaría el menú y se quedaría esperando a un usuario que no existe.
Ejecución
Desde la terminal, en la carpeta donde guardaste el archivo:
python inventario.py
La primera vez verás 0 productos en el almacén: el JSON todavía no existe y cargar_datos devolvió la lista vacía. Es el comportamiento correcto, no un error.
Pruebas de Funcionalidad
No basta con que el programa arranque. Hay que empujarlo un poco para ver dónde cede. Somete tu sistema a estas tres inspecciones:
-
Persistencia de datos: agrega un producto con la opción 2, sal con la opción 5 y vuelve a ejecutar el programa. El producto debe seguir ahí y el contador de arranque debe reflejarlo. Si el inventario aparece vacío, revisa que la opción 5 llame a
guardar_datosantes delbreak, y quecargar_datosse ejecute al arrancarmain. -
Validación de tipos: al agregar un producto, escribe
carocuando te pida el precio y-5cuando te pida el stock. El programa no debe colapsar en ningún caso: debe rechazar el dato, explicar el problema y volver a preguntar. Si ves un Traceback, algúninput()numérico está esquivando a los porterospedir_decimalypedir_entero. -
Integridad de los IDs: agrega tres productos (IDs
1,2,3), elimina el2con la opción 4 y agrega un producto nuevo. El recién llegado debe recibir el ID4, no el3. Si recibe el3, tugenerar_idestá contando elementos conlen()en lugar de buscar el mayor ID existente. Abreinventario.jsoncon un editor de texto y confirma que no haya IDs repetidos.
Si las tres pruebas pasan, el SGI v1.0 está oficialmente en operación. Inauguraste tu almacén digital con código que tú entiendes línea por línea. Falta blindarlo con pruebas que se queden contigo en vez de repetirlas a mano cada vez que toques algo: sigue con la Guía de Referencia 6.