Guía de Referencia 4: El Mostrador
Toda la maquinaria de las guías anteriores existe en el vacío hasta que alguien puede usarla. Esta guía construye el mostrador del almacén: el menú infinito, la entrada de datos a prueba de errores y la central de mando que conecta cada opción con su función.
Porteros Robustos para la Entrada (Bucle de Validación)
Recuerda la lección del Capítulo 10: nada de lo que teclea el usuario es de fiar. Va a escribir "caro" cuando le pidas un precio. Si pasas eso directo a float(), el programa colapsa. La solución es el patrón del portero robusto: un while True que insiste hasta recibir un dato válido.
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).")
El flujo tiene dos defensas. La primera es el try/except: si la conversión falla, el except atrapa el ValueError y el bucle vuelve a preguntar. La segunda es la regla de negocio: un almacén no maneja precios ni stocks negativos, así que el continue rechaza esos valores y repite la pregunta. La función solo termina por el return, y al return solo se llega con un dato sano.
El Reporte del Almacén
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']}")
Nada exótico: un for sobre la lista y un f-string por producto. El caso del almacén vacío merece su propio mensaje; una pantalla en blanco deja al usuario preguntándose si el programa se rompió.
El Menú
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")
La Central de Mando: main()
Aquí se conecta todo. Un bucle while True muestra el menú, lee la opción y la clasifica con match-case:
def main() -> None:
"""La central de mando 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":
print("Opción en construcción. Este reto es tuyo, Gerente.")
case "4":
print("Opción en construcción. Este reto es tuyo, Gerente.")
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.")
main()
Puntos clave del diseño:
- Cargar primero: la primera línea de
mainreconstruye el inventario desde el disco. Todo lo que pase después trabaja sobre esa lista. - Guardar antes de salir: la opción 5 llama a
guardar_datosy después hacebreak. Si rompes el bucle sin guardar, todo el trabajo de la sesión se evapora con la RAM. - El comodín
case _atrapa cualquier disparate que escriba el usuario, sin colapsar. - La última línea del archivo,
main(), es la que enciende el sistema. Sin ella, definiste todo y no ejecutaste nada. (En el ensamblaje final esa llamada gana un guardián; la Guía 5 explica por qué.)
Cuidado:
matchcompara texto.input()siempre devuelvestr, así que los patrones son"1","2"… con comillas. Si escribescase 1:sin comillas, ninguna opción coincidirá jamás.
Nota: te toca conectar las opciones 3 y 4. Las opciones “Buscar producto” y “Eliminar producto” aparecen en el menú, pero ahora mismo solo imprimen un aviso. Las funciones
buscar_producto()yeliminar_producto()ya existen desde la Guía 3, probadas y listas. Cablearlas es tu trabajo:
- Para buscar: pide el ID con
input(), llama abuscar_producto()y revisa el resultado conis None. Si existe, muéstralo; si no, dilo claramente.- Para eliminar: recuerda que
eliminar_producto()devuelve una lista nueva. Si no hacesinventario = eliminar_producto(...), el producto seguirá ahí.Pista extra: ¿cómo confirmas que la eliminación funcionó? Compara
len()antes y después, o busca el ID tras eliminar.Si te atascas, el archivo completo de la Guía 5 trae ambas opciones resueltas para comparar. Inténtalo tú primero.
Todas las piezas están sobre la mesa. Falta verlas juntas en un solo archivo ejecutable y ponerlo a prueba: sigue con la Guía de Referencia 5, donde ensamblas inventario.py completo y lo sometes a tres pruebas de funcionalidad.