Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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. Ese if __name__ == "__main__": es un guardián: ejecuta el menú solo cuando corres el archivo directo con python inventario.py. Sin él, cualquier import 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:

  1. 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_datos antes del break, y que cargar_datos se ejecute al arrancar main.

  2. Validación de tipos: al agregar un producto, escribe caro cuando te pida el precio y -5 cuando 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ún input() numérico está esquivando a los porteros pedir_decimal y pedir_entero.

  3. Integridad de los IDs: agrega tres productos (IDs 1, 2, 3), elimina el 2 con la opción 4 y agrega un producto nuevo. El recién llegado debe recibir el ID 4, no el 3. Si recibe el 3, tu generar_id está contando elementos con len() en lugar de buscar el mayor ID existente. Abre inventario.json con 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.