🍳 NTLPROG2026-1 · Fundamentos de Programación

Construyamos el programa
por capas

Aprende a separar tu código como un profesional — una carpeta a la vez, con preguntas y respuestas de verdad

Trabajo en grupo · 5 estudiantes
Duración: 1 a 2 clases
IDE: Spyder · Windows / Ubuntu
🎯 Intro
📁 Paso 0
🧊 Paso 1
🔪 Paso 2
👨‍🍳 Paso 3
🧑‍🍳 Paso 4
🔌 Paso 5
🗺️ Mapa
🧯 Errores
🔁 Cierre
🎯
¿Por qué separamos el código en carpetas?
D
DANI
Profe… ¿para qué tanto rollos con carpetas? ¿No es más fácil escribir todo en un solo archivo main.py?
P
Profe Diego
Claro que funciona al principio. Pero imagínate un restaurante donde el mesero también cocina, lava los platos, va al mercado y lleva la caja... todo al mismo tiempo. ¿Qué pasa si ese señor se enferma?
D
DANI
Jajaja se cae el restaurante entero 😬
P
Profe Diego
Exacto. En programación pasa igual: si un solo archivo hace TODO, cuando algo falla no sabes dónde buscar. Separar en capas = repartir responsabilidades. Cada parte tiene un solo trabajo, y si algo se rompe, sabes exactamente en qué "estación" buscar.

Esta tabla es tu mapa durante toda la actividad. Úsala como referencia en cada paso:

En el restaurante… En nuestro programa… Carpeta
🧑‍🍳 El mesero te atiende y muestra el menú Lo que el usuario ve y elige en pantalla menus/
👨‍🍳 La cocina prepara el plato (sabe las recetas) La lógica: qué hacer con los datos services/
🔪 Los utensilios que la cocina usa Herramientas reutilizables para todos utils/
🧊 La despensa / nevera donde se guarda la comida Donde viven los datos del programa data/
0
Paso 0 — Preparar el "esqueleto" del proyecto
Estructura
D
DANI
Entonces… ¿cuántas carpetas tenemos que crear y cómo se llaman?
P
Profe Diego
Cuatro: data/, utils/, services/ y menus/. Más el archivo main.py en la raíz. Piensen en esto como montar las estaciones del restaurante antes de cocinar: primero organizan el espacio, después viene la comida.
D
DANI
¿Y cómo las creo en Windows? ¿Hay que instalar algo?
P
Profe Diego
No instalan nada. En Windows: clic derecho → Nueva carpeta, la renombrán y listo. Para main.py crean un documento de texto vacío y lo renombran a main.py. Ojo: verifiquen que no quede main.py.txt — Windows a veces esconde la extensión.

Esta es la estructura completa que deben tener al final de este paso:

mi_mini_market/

├── main.pyHOY
├── data/Paso 1
├── utils/Paso 2
├── services/Paso 3
└── menus/Paso 4
⚠️
Respeten exactamente los nombres de carpetas y archivos. Una mayúscula de más (Data/ en vez de data/) puede romper los import en Ubuntu.
1
Paso 1 — La capa de DATOS (data/)
data/
D
DANI
Profe, cuando cierro un programa y lo vuelvo a abrir, ¿por qué los datos que escribí antes siguen ahí? ¿Acaso las variables no se borran?
P
Profe Diego
¡Excelente pregunta! Sí, las variables viven en la RAM y se borran al cerrar. Pero un archivo en disco permanece. A eso se le llama persistencia: los datos sobreviven al cierre del programa porque están escritos en un archivo real.
D
DANI
Ah, ¡como cuando guardo un documento de Word! ¿Y qué tipo de archivo usamos para que Python lo pueda leer fácil?
P
Profe Diego
Exactamente esa analogía. Y para Python usamos JSON: es texto plano que se parece mucho a las listas y diccionarios que ya conocen de Python. El programa lo puede leer y escribir fácilmente con la librería json que ya viene instalada.
D
DANI
¿Y qué le pongo adentro si todavía no tenemos productos?
P
Profe Diego
Solo dos corchetes: []. Eso es una lista vacía en JSON. El archivo ya existe y está listo para recibir datos, pero por ahora no tiene nada adentro. La despensa está abierta, simplemente vacía.

Creen este archivo dentro de la carpeta data/:

JSON data/productos.json
[]
💡
La carpeta data/ no tiene código Python. Solo archivos de datos. Ninguna capa del programa "vive" aquí — solo se guarda información.
2
Paso 2 — La capa de UTILIDADES (utils/)
utils/
D
DANI
Para guardar productos voy a necesitar abrir un archivo, leer el JSON… y lo mismo para usuarios, para ventas… ¿eso no me toca escribirlo en cada uno?
P
Profe Diego
¡No! Eso se llama DRY: Don't Repeat Yourself. Si escribís lo mismo en 10 archivos y un día hay que cambiarlo, tenés que corregirlo en 10 lugares — y siempre olvidás alguno. La solución: escribirlo una sola vez en utils/ y que todo el mundo lo use desde allá.
D
DANI
¿Y cuántas funciones necesito para manejar archivos JSON?
P
Profe Diego
Solo dos: una para leer (leer_json) y una para guardar (guardar_json). Como el cuchillo y la sartén en una cocina — son las herramientas base que todos usan.
D
DANI
¿Y qué pasa si llamo leer_json con un archivo que todavía no existe?
P
Profe Diego
Sin manejo de errores, Python explotaría con un FileNotFoundError. Por eso le ponemos un chequeo: si el archivo no existe, devolvemos [] en lugar de caerse. El programa puede seguir trabajando normalmente aunque sea la primera vez que corre. A eso se llama manejar errores con cuidado.

Creen el archivo utils/file_manager.py:

Python utils/file_manager.py
import  json
import  os


def leer_json(ruta):
    # Si el archivo no existe todavía, devolvemos lista vacía
    if not os.path.exists(ruta):
        return []

    with open(ruta, "r", encoding="utf-8") as archivo:
        try:
            return json.load(archivo)
        except json.JSONDecodeError:
            return []


def guardar_json(ruta, datos):
    with open(ruta, "w", encoding="utf-8") as archivo:
        json.dump(datos, archivo, indent=4, ensure_ascii=False)
🧠
¿Por qué ensure_ascii=False? Para que los acentos y la ñ se guarden correctamente en el archivo. Sin eso, "Café" quedaría guardado como \u00e9.
⚠️
utils/file_manager.py todavía no se puede probar solo — es una herramienta, y alguien tiene que usarla. Ese alguien es la siguiente capa.
3
Paso 3 — La capa de SERVICIOS (services/)
services/
D
DANI
"Publicar un producto" y "ver la lista" son cosas que el usuario va a hacer. ¿Ese código va en menus/ o en services/?
P
Profe Diego
En services/, la cocina. El menú solo pide la acción; el servicio la prepara. El menú no debería saber cómo se guarda un producto en el JSON — solo debería decirle al servicio "guardá esto" y el servicio sabe qué hacer.
D
DANI
¿Y para guardar un producto nuevo, qué pasos son?
P
Profe Diego
Tres: (1) leer los productos que ya existen, (2) agregar el nuevo a la lista, (3) guardar la lista completa de nuevo. Y para los pasos 1 y 3 ya tenemos herramientas listas en utils/. ¡Reutilizamos lo que ya construimos!
D
DANI
O sea que producto_service.py va a importar de utils/file_manager.py?
P
Profe Diego
Exactamente. Esa es la dependencia: services/ depende de utils/. Y eso está bien — la cocina depende de los utensilios. Lo que NO debe pasar es que los utensilios dependan de la cocina o del menú. El flujo siempre va en una sola dirección.

Creen services/producto_service.py:

Python services/producto_service.py
from utils.file_manager import leer_json, guardar_json

RUTA_PRODUCTOS = "data/productos.json"


def listar_productos():
    productos = leer_json(RUTA_PRODUCTOS)

    print("\n===== PRODUCTOS DISPONIBLES =====")

    if len(productos) == 0:
        print("No hay productos publicados todavía.")
        return

    for producto in productos:
        print("--------------------------------")
        print(f"ID: {producto['id']}")
        print(f"Nombre: {producto['nombre']}")
        print(f"Precio: ${producto['precio']}")


def crear_producto():
    productos = leer_json(RUTA_PRODUCTOS)

    print("\n===== PUBLICAR PRODUCTO =====")
    nombre = input("Nombre del producto: ")

    try:
        precio = float(input("Precio: "))
    except ValueError:
        print("El precio debe ser un número. Intente de nuevo.")
        return

    nuevo_producto = {
        "id": len(productos) + 1,
        "nombre": nombre,
        "precio": precio
    }

    productos.append(nuevo_producto)
    guardar_json(RUTA_PRODUCTOS, productos)

    print("✅ Producto publicado correctamente.")
🧠
¿Por qué try / except ValueError? Porque si el usuario escribe "cinco" en vez de 5000, la conversión float() falla. El try atrapa ese error y muestra un mensaje amable en vez de que el programa explote.
4
Paso 4 — La capa de MENÚS (menus/)
menus/
D
DANI
¿El menú también debería tener código que guarda en el JSON?
P
Profe Diego
No. El menú es "tonto a propósito". Solo pregunta, muestra opciones y delega. Toda la inteligencia está en services/. El mesero no cocina — solo anota el pedido y se lo pasa a la cocina.
D
DANI
¿Y para que el usuario pueda elegir varias veces sin que el programa se cierre, qué uso?
P
Profe Diego
Un bucle while True. Muestra el menú una y otra vez, hasta que el usuario elige "Salir" y el programa hace break para salir del bucle. Sin el break, el programa nunca pararía.

Creen menus/menu_principal.py:

Python menus/menu_principal.py
from services.producto_service import listar_productos, crear_producto


def mostrar_menu_principal():
    while True:
        print("\n====================================")
        print("        MI MINI MARKET")
        print("====================================")
        print("1. Ver productos")
        print("2. Publicar producto")
        print("3. Salir")

        opcion = input("Seleccione una opción: ")

        if opcion == "1":
            listar_productos()
        elif opcion == "2":
            crear_producto()
        elif opcion == "3":
            print("¡Gracias por usar Mi Mini Market!")
            break
        else:
            print("Opción no válida. Intente de nuevo.")
🧠
Fíjense en el import de la primera línea. El menú solo sabe que existen esas dos funciones — no sabe cómo funcionan por dentro. Eso es encapsulación.
5
Paso 5 — main.py: encender el programa
main.py
D
DANI
Tenemos las 4 capas pero el programa no arranca. ¿Quién da la orden inicial?
P
Profe Diego
main.py es el botón de encendido. Su único trabajo es llamar al menú principal. Por eso es cortísimo — con 3 líneas basta. Toda la magia ya está repartida en las capas.
D
DANI
¿Y qué es ese if __name__ == "__main__" tan raro?
P
Profe Diego
Es la forma estándar de Python de decir: "ejecuta esto solo cuando corras ESTE archivo directamente". Si otro archivo importa main.py por algún motivo, el menú no se activa solo. No es obligatorio entenderlo a fondo hoy, pero es buena práctica acostumbrarse a verlo.

Abren el archivo main.py que ya crearon y escriben esto:

Python main.py
from menus.menu_principal import mostrar_menu_principal

if __name__ == "__main__":
    mostrar_menu_principal()
🔌
Ahora ejecuten desde la terminal, dentro de la carpeta mi_mini_market/:

python main.py
🗺️
Mapa completo: ¿quién depende de quién?
D
DANI
Profe, me confundí. ¿Cómo sé qué archivo puede usar qué? ¿Cualquiera puede importar a cualquiera?
P
Profe Diego
No, hay reglas. Siempre fluye hacia abajo en las capas. El menú puede llamar al servicio. El servicio puede usar los utils. Los utils acceden a los archivos de data. Pero nunca al revés. Si el servicio intentara importar del menú, algo está muy mal diseñado.

Este es el flujo exacto de dependencias en tu proyecto:

main.py
importa de
menus/menu_principal.py
menus/menu_principal.py
importa de
services/producto_service.py
services/producto_service.py
importa de
utils/file_manager.py
utils/file_manager.py
lee y escribe
data/productos.json
data/productos.json
no importa nada —
es solo un archivo de texto
D
DANI
O sea que si mañana decidimos guardar en una base de datos en vez de JSON, ¿solo cambiamos file_manager.py?
P
Profe Diego
¡Exactamente! Y el menú, el servicio y el main… no se enteran del cambio. Eso es lo que en programación llamamos desacoplamiento: las capas están separadas, independientes. Cambiar una no rompe las otras.

Así queda la estructura final completa con sus archivos:

mi_mini_market/

├── main.py ← botón de encendido

├── data/
│ └── productos.json ← lista vacía [] al inicio

├── utils/
│ └── file_manager.py ← leer_json, guardar_json

├── services/
│ └── producto_service.py ← listar, crear producto

└── menus/
└── menu_principal.py ← lo que ve el usuario
🧯
Si algo falla — errores comunes
D
DANI
Profe, me sale un error horrible rojo en la terminal. ¿Qué hago?
P
Profe Diego
Regla de oro: lean el error de abajo hacia arriba. La última línea casi siempre dice qué pasó, y la línea con File "..." dice dónde. Nunca borren todo — solo busquen el punto exacto que falla.
Error en pantalla Causa más probable Solución
ModuleNotFoundError:
No module named 'menus'
Estás corriendo Python desde la carpeta equivocada Asegúrate de ejecutar python main.py dentro de mi_mini_market/
FileNotFoundError:
data/productos.json
La carpeta o el archivo no existe o está mal escrito Verifica que se llame exactamente data/productos.json (todo en minúscula)
El programa se cierra de inmediato Falta el while True o la sangría está mal Revisa la indentación en menu_principal.py — Python es muy estricto con los espacios
KeyError: 'nombre' Un producto en el JSON no tiene esa "llave" Borra el contenido de productos.json y déjalo en []
Acentos y ñ se ven raros Falta encoding="utf-8" Ya viene en file_manager.py; verifica que no lo hayas borrado
🔁
Cierre: conecten con su propio proyecto
D
DANI
Profe, cerramos el programa, lo volvimos a abrir y el producto seguía ahí. ¿Por qué?
P
Profe Diego
Porque guardar_json escribió el producto en data/productos.json. Las variables se borran al cerrar, pero los archivos permanecen. Eso es la persistencia, y la lograron ustedes con sus propias capas. 🏆
D
DANI
Entonces… si mi proyecto guarda usuarios en vez de productos, ¿hago lo mismo pero con usuarios.json y usuario_service.py?
P
Profe Diego
¡Exactamente! Es el mismo patrón. Los nombres cambian, la estructura no. Esa es la magia de las capas: una vez que entiendes el esquema, lo puedes repetir para cualquier tipo de dato.

Antes de entregar, respondan estas preguntas en grupo. Anótenlas para la exposición:

📤 Reflexión de cierre

Scroll to Top