Saltar al contenido
Inicio 100 Días de Código en Python — Día 16: Programación Orientada a Objetos (POO)

100 Días de Código en Python — Día 16: Programación Orientada a Objetos (POO)

Nivel: Grado décimo Área: Tecnología e Informática Modalidad: Tutorial práctico Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 16 (nivel intermedio).

Este tutorial adapta pedagógicamente el Día 16. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso. Los retos 🟢🟡🟠🔴, el proyecto extra, el proyecto integrador y el kit de reemplazo de la máquina de café (Anexo A) son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.

⚠️ Prerequisito importante: este día retoma el proyecto de la máquina de café del Día 15, que se hizo sin objetos (con programación por procedimientos). Ese proyecto se desarrolla en el tutorial del Día 15 (tutorial_dia15_100_dias_de_python.md). Si tus estudiantes no lo hicieron, basta con explicarles sus requisitos, que se resumen en la Sección 5.


Tabla de contenidos

  1. Antes de empezar: ¿por qué objetos?
  2. Clases y objetos: atributos y métodos
  3. Construir objetos y usarlos: la librería Turtle
  4. Paquetes externos: PyPI e instalación
  5. Práctica con PrettyTable: atributos y métodos
  6. 🚀 Proyecto del día: la Máquina de Café con POO
  7. 🌟 Proyecto extra: Tabla del Intercolegiado
  8. 🏆 Proyecto integrador del Día 16: Panel de la Cafetería Escolar
  9. 🤖 Programar con apoyo de IA
  10. 🛠️ Correcciones técnicas al material original
  11. 📋 Matriz de trazabilidad
  12. 📦 Evidencias de aprendizaje del Día 16
  13. 📎 Anexo A: Kit de reemplazo de la Máquina de Café

0. Antes de empezar: ¿por qué objetos?

(Fuentes: 108 - Why do we need OOP and how does it work-.md, 115 - Don't forget to review occasionally.md)

El problema: el código "espagueti"

Hasta ahora hemos programado de forma procedimental: funciones que se llaman unas a otras, de arriba hacia abajo. Es uno de los paradigmas más antiguos: lenguajes como Fortran y COBOL se basaban casi exclusivamente en él. Funciona bien en programas pequeños, pero cuando crecen, las relaciones entre funciones y variables se enredan como un plato de espaguetis. El curso lo compara, en broma, con una red de relaciones en la que "todos han estado con alguien más".

La idea: dividir en piezas que saben hacer su trabajo

Ejemplo 1: un carro autónomo. Es un proyecto enorme, pero se puede dividir en módulos: cámara, detección de carril, navegación y manejo del combustible.

  • Cada equipo trabaja en su módulo al mismo tiempo que los demás.
  • Los módulos son reutilizables: la cámara y la navegación servirían también para un dron de entregas.

Ejemplo 2: el restaurante. Si heredas un restaurante y lo manejas solo, tienes que ser recepcionista, mesero, chef y encargado de la limpieza a la vez. Solo funciona si el local es muy pequeño. Si contratas personal con roles definidos, tú solo eres el gerente: le dices al mesero que atienda la mesa 4 sin explicarle cómo atender, y al chef que cocine sin enseñarle a freír un huevo. Cada uno sabe hacer su trabajo.

Esa es la Programación Orientada a Objetos (POO): dividir el programa en "personajes" (objetos) que guardan sus propios datos y saben hacer sus propias tareas.

Consejo de cierre del curso: ver videos da la sensación de avanzar. Repasa con frecuencia: cambia los proyectos, rehaz los retos y escribe tú el código de los tutoriales.

Prerequisito general: funciones, módulos e import (Días 4, 7 y 10), diccionarios (Día 9) y la Máquina de Café del Día 15.


1. Clases y objetos: atributos y métodos

(Fuente: 109 - How to use OOP- Classes and Objects.md)

🎯 Objetivo de aprendizaje

  • Explicar qué son una clase, un objeto, un atributo y un método.
  • Modelar un objeto de la vida real identificando qué tiene y qué hace.

📖 Explicación conceptual

Para modelar un mesero virtual nos preguntamos dos cosas:

Pregunta Nombre en POO Se programa como… Ejemplo
¿Qué tiene? Atributos Variables unidas al objeto sosteniendo_plato = True, mesas = [4, 5, 6]
¿Qué hace? Métodos Funciones unidas al objeto tomar_pedido(), recibir_pago()

Un atributo es "una palabra elegante para una variable asociada a un objeto". Un método es "una función que puede hacer un objeto en particular".

Clase frente a objeto:

  • La clase es el plano o molde: define qué tiene y qué hace un mesero.
  • Los objetos son los meseros concretos creados a partir de ese plano, por ejemplo Henry y Betty. Se pueden crear tantos como se quiera.
Texto
flowchart LR
    C["Clase Mesero<br/>(plano)"] --> O1["Objeto: henry"]
    C --> O2["Objeto: betty"]
    C --> O3["Objeto: ..."]

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: para una licuadora, escribe 3 atributos y 3 métodos.
  • 🟡 Reto 2: para un estudiante del sistema académico del colegio, haz la tabla de atributos y métodos. ¿Cuáles serían los objetos concretos?
  • 🟠 Reto 3: modela el restaurante completo: ¿qué clases hay? ¿Qué atributos y métodos tiene cada una? ¿Qué objeto "le pide cosas" a cuál?
  • 🔴 Reto 4: toma tu Blackjack del Día 11 y propón cómo lo dividirías en clases (por ejemplo, Baraja, Mano, Jugador y Crupier). No escribas código todavía: solo el diseño con atributos y métodos.

2. Construir objetos y usarlos: la librería Turtle

(Fuente: 110 - Constructing Objects and Accessing their Attributes and Methods.md)

🎯 Objetivo de aprendizaje

  • Construir un objeto a partir de una clase.
  • Leer y modificar atributos, y llamar métodos, con la notación de punto.
  • Usar la documentación para descubrir qué puede hacer un objeto.

📖 Explicación conceptual

Sintaxis para construir un objeto:

Python
car = CarBlueprint()
  • La clase se escribe en PascalCase: cada palabra empieza con mayúscula y no hay guiones bajos (CarBlueprint, PrettyTable). Así se distingue de las variables y funciones, que van en snake_case (my_screen).
  • Los paréntesis activan la construcción del objeto, igual que activan una función.

Notación de punto:

Para… Sintaxis Ejemplo
Leer un atributo objeto.atributo car.speed
Llamar un método objeto.metodo() car.stop()

Turtle. La librería de "gráficos de tortuga" viene incluida con Python: no hay que instalarla. Imagina una tortuga con un pincel en la espalda a la que le das órdenes para que dibuje en la pantalla.

Python
from turtle import Turtle, Screen

timmy = Turtle()               # construir un objeto Turtle
print(timmy)                   # <turtle.Turtle object at 0x...>: es un objeto, no un texto
timmy.shape("turtle")          # método: cambiar la forma
timmy.color("coral")           # método: cambiar el color
timmy.forward(100)             # método: avanzar 100 pasos

my_screen = Screen()
print(my_screen.canvheight)    # atributo: la altura del lienzo (300 por defecto)
my_screen.exitonclick()        # método: mantener la ventana abierta hasta que se haga clic

Consejo del curso: en la documentación oficial de Turtle (se abre en otra pestaña) están todos los métodos para moverse, dibujar y cambiar colores. Los colores se pueden escribir por su nombre en inglés, por ejemplo "coral". Hay una lista de nombres de colores en la documentación de Tk.

Repaso de módulos (Días 4 y 7). import turtle y luego turtle.Turtle() es equivalente a from turtle import Turtle y luego Turtle().

⚠️ Errores frecuentes

  • Olvidar los paréntesis al construir: timmy = Turtle guarda la clase, no un objeto. Después, timmy.forward(100) falla con un TypeError.
  • Llamar forward sin argumento: TypeError: missing 1 required positional argument.
  • La ventana se cierra sola: falta screen.exitonclick() al final.
  • Llamar tu archivo turtle.py: "tapa" la librería. Es el mismo problema de random.py del Día 4.

📝 Ejercicio original del curso: tu primera tortuga

  1. Crea el objeto timmy y el objeto my_screen.
  2. Imprime el atributo canvheight de la pantalla.
  3. Cambia la forma a "turtle" y el color a uno de la lista de la documentación.
  4. Busca en la documentación cómo hacer que la tortuga avance 100 pasos.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: crea dos tortugas de colores distintos y haz que avancen distancias diferentes.
  • 🟡 Reto 2: busca en la documentación los métodos left, right, penup y pendown, y úsalos para escribir la inicial de tu nombre.
  • 🟠 Reto 3: cambia el atributo título de la ventana. (Pista: busca "title" en la sección de Screen.)
  • 🔴 Reto 4: usando solo la documentación, dibuja una "estrella" de 5 puntas. ¿Qué ángulo necesitas? Explica el razonamiento geométrico. (El Día 18 se dedica completamente a Turtle.)

3. Paquetes externos: PyPI e instalación

(Fuente: 111 - How to Add Python Packages and use PyPi.md)

📖 Explicación conceptual

Término Qué es
Módulo Un archivo .py. Cada archivo que creas es un módulo
Paquete Un conjunto de muchos archivos escritos por otras personas para lograr un objetivo
PyPI (pypi.org (se abre en otra pestaña)) El Python Package Index: el gran repositorio de paquetes que comparte la comunidad de Python

A diferencia de Turtle, los paquetes de PyPI hay que instalarlos.

Formas de instalar prettytable:

Método Pasos
PyCharm (el que usa el curso) File → Settings (Windows) o PyCharm → Preferences/Settings (Mac) → Project → Python Interpreter → botón + → buscar prettytable → Install Package
Terminal (cualquier editor) pip install prettytable o python -m pip install prettytable

Ver el código de un paquete: en PyCharm, clic derecho sobre el nombre → Go To → Implementation. El curso muestra que no necesitas entender ese código para usarlo; basta con leer la documentación.

🛠️ Nota técnica: el curso dice que la documentación de PrettyTable está alojada en Google Code Archive. Hoy la documentación actualizada está en la página del paquete en PyPI (se abre en otra pestaña) y en su repositorio de GitHub. Los ejemplos de este tutorial se probaron con PrettyTable 3.18.


4. Práctica con PrettyTable

(Fuente: 112 - Practice Modifying Object Attributes and Calling Methods.md)

🎯 Objetivo de aprendizaje

  • Construir un objeto de una librería externa, llamar sus métodos y modificar sus atributos, guiándose por la documentación.

📝 Ejercicio original del curso: tabla de Pokémon

Enunciado:

  1. Construye un objeto table a partir de la clase PrettyTable.
  2. Con el método add_column, agrega la columna "Pokemon" con Pikachu, Squirtle y Charmander, y la columna "Type" con Electric, Water y Fire, en el mismo orden para que cada tipo corresponda a su Pokémon.
  3. Imprime la tabla.
  4. Cambia el atributo align a "l" para alinear a la izquierda.
Python
from prettytable import PrettyTable

table = PrettyTable()
table.add_column("Pokemon", ["Pikachu", "Squirtle", "Charmander"])
table.add_column("Type", ["Electric", "Water", "Fire"])
table.align = "l"
print(table)

Resultado (verificado):

Texto
+------------+----------+
| Pokemon    | Type     |
+------------+----------+
| Pikachu    | Electric |
| Squirtle   | Water    |
| Charmander | Fire     |
+------------+----------+

Lección clave: table.add_column(...) es un método (lleva paréntesis y hace algo). table.align = "l" modifica un atributo (se asigna con =, como una variable).

🛠️ Nota técnica: en la versión actual de la librería, print(table.align) muestra también una clave interna 'base_align_value' además de las columnas. Es normal y no afecta el ejercicio.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: crea una tabla de tus 3 materias favoritas con su profesor usando add_row en lugar de add_column. (Pista: primero table.field_names = ["Materia", "Profesor"].)
  • 🟡 Reto 2: ordena una tabla por una columna usando los atributos sortby y reversesort.
  • 🟠 Reto 3: busca en la documentación cómo cambiar el estilo de los bordes (por ejemplo set_style) y prueba al menos dos estilos.
  • 🔴 Reto 4: investiga qué otros paquetes de PyPI podrían servir en el colegio (por ejemplo para generar códigos QR o gráficas). Elige uno, lee su documentación e identifica una clase, un atributo y un método.

5. 🚀 Proyecto del día: la Máquina de Café con POO

(Fuentes: 113 - Building the Coffee Machine in OOP.md, 114 - Walkthrough and Solution for the OOP Coffee Machine.md, proyecto oficial del curso original)

Contexto: la analogía del hotel (Steve Jobs)

El curso cita una explicación de Steve Jobs. Llegas a Japón con la camiseta manchada: no conoces la ciudad, no tienes yenes y no hablas japonés. Pero el hotel sí sabe dónde está la lavandería, tiene la moneda local y habla el idioma. Tú solo le entregas la camiseta y recibes una limpia. Así se trabaja con objetos: llamas a hotel.limpiar(camiseta) y confías en que el objeto sabe hacerlo, sin preocuparte por los detalles.

Requisitos (los mismos del Día 15)

  1. Preguntar "¿Qué quieres? (latte/espresso/cappuccino/)".
  2. off apaga la máquina.
  3. report imprime los recursos (agua, leche y café) y el dinero acumulado.
  4. Verificar que haya suficientes recursos para la bebida.
  5. Procesar las monedas y verificar que el pago sea suficiente, entregando el cambio.
  6. Preparar el café, descontando los ingredientes.

La novedad

El curso entrega cuatro clases ya escritas, con su documentación. No debes modificarlas: trátalas "como si fueran una librería externa" y trabaja solo en main.py.

🛠️ Nota: el código inicial del curso (archivos menu.py, coffee_maker.py, money_machine.py) y su sitio de documentación no están incluidos entre los materiales suministrados. En el Anexo A encontrarás un kit de reemplazo creado para esta adaptación, con la misma interfaz que documenta el curso, en pesos colombianos y probado.

Documentación de las clases (interfaz)

Clase Atributos Métodos
MenuItem name, cost, ingredients (diccionario) —
Menu — get_items() → texto "latte/espresso/cappuccino/"
find_drink(order_name) → un MenuItem, o None si no existe
CoffeeMaker — report()
is_resource_sufficient(drink) → True/False
make_coffee(order)
MoneyMachine — report()
make_payment(cost) → True/False (pide las monedas y entrega el cambio)

🔍 Antes de programar

  • ¿Qué objetos necesitas crear? ¿A partir de qué clases?
  • Para cada requisito (1 a 6), ¿qué método de qué objeto lo resuelve? Haz una tabla.
  • is_resource_sufficient espera un MenuItem, no un texto. ¿Cómo obtienes el MenuItem a partir de lo que escribió el usuario?
  • ¿De dónde sale el costo que necesita make_payment? (Pista: es un atributo.)

Construcción paso a paso (según la solución del curso)

Paso 1: el informe. Crea money_machine = MoneyMachine() y coffee_maker = CoffeeMaker(). La convención es nombrar el objeto como la clase, pero en snake_case. Llama a report() en los dos.

Paso 2: el menú y el bucle. Crea menu = Menu() y un while is_on:. Muestra las opciones con menu.get_items() dentro de una f-string. Maneja off y report.

Paso 3: encontrar la bebida. drink = menu.find_drink(choice). Imprímela para comprobar que es un objeto MenuItem.

Paso 4: recursos, pago y café. El curso llega a una solución muy compacta:

Python
if coffee_maker.is_resource_sufficient(drink) and money_machine.make_payment(drink.cost):
    coffee_maker.make_coffee(drink)

Gracias al and, si no hay recursos, ni siquiera se piden las monedas: Python no evalúa la segunda condición si la primera es False.

"Incluso si nunca hubieras visto cómo funcionan los métodos por dentro, puedes entender con facilidad lo que hace este código."

Pruebas (con el kit del Anexo A)

Acción Resultado esperado (verificado con el kit)
report al inicio Agua 300 ml, Leche 200 ml, Café 100 g, Dinero $0
latte pagando $5000 "Tu cambio es $0" y "Aquí está tu latte ☕"
Otro latte "No hay suficiente agua" y "No hay suficiente leche", sin pedir monedas
espresso pagando $2000 + $1000 Se prepara
report Agua 50, Leche 50, Café 58, Dinero $8000
mocha (no existe) ⚠️ AttributeError: 'NoneType' object has no attribute 'ingredients'

🛠️ Nota técnica (verificada con el kit): find_drink devuelve None si la bebida no existe. La solución del curso le pasa ese None a is_resource_sufficient, y el programa se detiene. Corrección: verificar antes con if drink: o con if drink is not None:. Es otro caso del "camino que devuelve None" (Días 10 y 12).

⭐ Extensión del proyecto (sin modificar las clases)

  • Mostrar un mensaje de bienvenida con el menú y los precios (recorriendo los objetos del menú y leyendo name y cost).
  • Contar cuántas bebidas de cada tipo se vendieron (un diccionario en main.py).
  • Pregunta para el Día 17: ¿qué tendrías que cambiar en las clases para agregar una bebida "tinto"?

6. 🌟 Proyecto extra: Tabla del Intercolegiado

Situación

El comité deportivo necesita mostrar en consola la tabla de posiciones del torneo de microfútbol.

Requisitos

  1. Una lista de diccionarios con los equipos: nombre, partidos ganados (PG), empatados (PE) y perdidos (PP), goles a favor (GF) y goles en contra (GC).
  2. Calcular para cada equipo los puntos (3 por ganar, 1 por empatar) y la diferencia de gol (GF − GC).
  3. Construir un objeto PrettyTable con field_names y agregar cada equipo con add_row dentro de un for.
  4. Ordenar por puntos, de mayor a menor, con los atributos sortby y reversesort.
  5. Alinear a la izquierda la columna del nombre del equipo (table.align["Equipo"] = "l").

Casos de prueba

Con 10A (3 PG, 1 PE, 0 PP) y 10B (2 PG, 2 PE, 0 PP): 10A tiene 10 puntos y aparece primero; 10B tiene 8.

⭐ Extensión

¿Qué pasa si dos equipos tienen los mismos puntos? Investiga si PrettyTable permite ordenar por varios criterios (puntos y luego diferencia de gol). Si no lo permite, ordena la lista tú mismo antes de construir la tabla.


7. 🏆 Proyecto integrador del Día 16: Panel de la Cafetería Escolar

Proyecto integrador creado para esta adaptación. Combina objetos de dos librerías distintas: el kit de la máquina de café (Anexo A) y PrettyTable. Esa es la gran ventaja de la POO: combinar piezas que otros construyeron.

Situación problema

El administrador de la cafetería quiere que la máquina muestre la información en tablas claras y lleve un historial de ventas.

Pregunta orientadora

¿Cómo lograr funcionalidades nuevas combinando objetos existentes, sin modificar su código?

Requerimientos

  1. No modificar menu.py, coffee_maker.py ni money_machine.py. Todo el trabajo va en main.py.
  2. Al iniciar, mostrar el menú como tabla (bebida, precio y los ingredientes: agua, leche y café), leyendo los atributos de los MenuItem. (Pista: menu.menu es la lista de objetos del kit.)
  3. report muestra los recursos y el dinero en una tabla. (Pista: el kit guarda los recursos en coffee_maker.resources y el dinero en money_machine.profit.)
  4. Una lista ventas en la que cada venta exitosa agregue un diccionario {"bebida": ..., "precio": ...}.
  5. Una opción ventas que muestre el historial en una tabla, con el total vendido y la bebida más vendida.
  6. Corregir el problema del None (bebida inexistente).

Reflexión obligatoria

Los puntos 2 y 3 leen atributos internos del kit (menu.menu, resources, profit) que no aparecen en la documentación de la interfaz. ¿Es buena idea depender de ellos? ¿Qué pasaría si el autor del kit los renombrara? (Este dilema se retoma en el Día 17, cuando crees tus propias clases.)

Entregables

  • main.py funcional.
  • Capturas de las tres tablas.
  • Tabla de pruebas.
  • El párrafo de reflexión.

Criterios técnicos

  • Los archivos del kit quedan intactos: se puede comparar con el Anexo A.
  • Ninguna entrada detiene el programa.
  • El total de ventas coincide con money_machine.profit.

8. 🤖 Programar con apoyo de IA

El proyecto de hoy enseña a confiar en una interfaz documentada sin leer el código interno. Una IA puede ayudarte a leer documentación más rápido, pero verifica lo que te diga contra la fuente.

Actividad: la IA y la documentación.

  1. Pregunta a una IA: "¿Cómo alineo a la izquierda una columna en PrettyTable?".
  2. Prueba su respuesta en Python y contrástala con la documentación de PyPI (se abre en otra pestaña).
  3. Pregúntale por un método de Turtle que no exista (por ejemplo timmy.jump()). ¿Te dice que no existe o inventa una respuesta? Verifícalo en la documentación oficial.

9. 🛠️ Correcciones técnicas al material original

Archivo Afirmación o comportamiento del curso Observación Tratamiento
113, 114 Código inicial de la máquina de café y su sitio de documentación No están incluidos entre los materiales suministrados Kit de reemplazo con la misma interfaz (Anexo A), probado
114 drink = menu.find_drink(choice) sin verificar Con una bebida inexistente → AttributeError (verificado con el kit) Nota técnica y pruebas del proyecto
114 Método "_resource_efficient" (error de dictado) El nombre correcto es is_resource_sufficient Tabla de la interfaz
111 La documentación de PrettyTable está en Google Code Archive Hoy está en PyPI y GitHub Nota técnica del Tema 3
111 Ruta de instalación en PyCharm: "Preferences" En Windows es File → Settings. Alternativa: pip install Tabla del Tema 3
112 print(table.align) muestra solo las columnas En PrettyTable 3.x aparece también base_align_value (verificado) Nota técnica del Tema 4

10. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Por qué POO: espagueti, carro autónomo, restaurante 108 Sección 0 — — Secciones 5 y 7
Clases, objetos, atributos, métodos (mesero) 109 Tema 1 — Retos 1–4 del Tema 1 (diseño del Blackjack) Todos
Construir objetos, PascalCase, notación de punto, Turtle, documentación 110 Tema 2 Timmy: forma, color, forward(100) Retos 1–4 del Tema 2 Día 18
Módulo frente a paquete, PyPI, instalación 111 Tema 3 Instalar PrettyTable Reto 4 del Tema 4 Secciones 6 y 7
PrettyTable: add_column, align 112 Tema 4 Tabla de Pokémon Retos 1–4 del Tema 4 Secciones 6 y 7
Analogía del hotel, máquina de café con POO 113 Sección 5 Planteamiento del proyecto — Sección 5
Solución: objetos, get_items, find_drink, and 114 Sección 5 Proyecto oficial Extensiones y corrección del None Secciones 5 y 7
Repasar de vez en cuando 115 Sección 0 — — —
(No existe en el curso original) — — — Tabla del Intercolegiado Sección 6
(No existe en el curso original) — — — Panel de la Cafetería Escolar Sección 7
(No existe en el curso original) — — — Kit de reemplazo Anexo A

11. 📦 Evidencias de aprendizaje del Día 16

  • Tabla de atributos y métodos de al menos dos objetos de la vida real (Tema 1).
  • Captura de la tortuga Timmy (forma, color y movimiento).
  • Tabla de Pokémon con PrettyTable, alineada a la izquierda.
  • Máquina de Café con POO funcionando con el kit, incluida la corrección del None.
  • Proyecto extra (Intercolegiado) o integrador (Panel de la Cafetería), con su reflexión.
  • Vocabulario del día en tu cuaderno: clase, objeto, atributo, método, construir, PascalCase, paquete y PyPI, cada uno con un ejemplo propio.

12. 📎 Anexo A: Kit de reemplazo de la Máquina de Café

Creado para esta adaptación, porque el código inicial del curso no está incluido entre los materiales suministrados. Respeta la interfaz documentada por el curso (los mismos nombres de clases, atributos y métodos), usa pesos colombianos y los mensajes están en español. Se probó con las entradas de la tabla de pruebas de la Sección 5.

Para los estudiantes: igual que en el curso, no necesitas entender este código todavía. La sintaxis class, __init__ y self se estudia en el Día 17. Hoy solo lo usas desde main.py.

Crea estos tres archivos en la misma carpeta de tu main.py:

menu.py

Python
class MenuItem:
    """Modela una bebida del menú."""
    def __init__(self, name, water, milk, coffee, cost):
        self.name = name
        self.cost = cost
        self.ingredients = {"water": water, "milk": milk, "coffee": coffee}


class Menu:
    """Modela el menú de bebidas."""
    def __init__(self):
        self.menu = [
            MenuItem(name="latte", water=200, milk=150, coffee=24, cost=5000),
            MenuItem(name="espresso", water=50, milk=0, coffee=18, cost=3000),
            MenuItem(name="cappuccino", water=250, milk=100, coffee=24, cost=6000),
        ]

    def get_items(self):
        """Devuelve los nombres de las bebidas disponibles, separados por /."""
        options = ""
        for item in self.menu:
            options += f"{item.name}/"
        return options

    def find_drink(self, order_name):
        """Busca una bebida por su nombre. Devuelve el MenuItem, o None si no existe."""
        for item in self.menu:
            if item.name == order_name:
                return item
        print("Lo siento, esa bebida no está disponible.")
        return None

coffee_maker.py

Python
class CoffeeMaker:
    """Modela la máquina que prepara el café."""
    NOMBRES = {"water": "agua", "milk": "leche", "coffee": "café"}

    def __init__(self):
        self.resources = {"water": 300, "milk": 200, "coffee": 100}

    def report(self):
        """Imprime un informe de los recursos."""
        print(f"Agua: {self.resources['water']} ml")
        print(f"Leche: {self.resources['milk']} ml")
        print(f"Café: {self.resources['coffee']} g")

    def is_resource_sufficient(self, drink):
        """Devuelve True si se puede preparar la bebida, False si falta algún ingrediente."""
        can_make = True
        for item in drink.ingredients:
            if drink.ingredients[item] > self.resources[item]:
                print(f"Lo siento, no hay suficiente {self.NOMBRES[item]}.")
                can_make = False
        return can_make

    def make_coffee(self, order):
        """Descuenta los ingredientes de los recursos."""
        for item in order.ingredients:
            self.resources[item] -= order.ingredients[item]
        print(f"Aquí está tu {order.name} ☕. ¡Disfrútalo!")

money_machine.py

Python
class MoneyMachine:
    """Modela la máquina que recibe el dinero (pesos colombianos)."""
    COINS = {"1000": 1000, "500": 500, "200": 200, "100": 100}

    def __init__(self):
        self.profit = 0
        self.money_received = 0

    def report(self):
        """Imprime las ganancias acumuladas."""
        print(f"Dinero: ${self.profit}")

    def process_coins(self):
        """Pide las monedas y devuelve el total insertado."""
        print("Inserta monedas.")
        for coin in self.COINS:
            self.money_received += int(input(f"¿Cuántas monedas de ${coin}?: ")) * self.COINS[coin]
        return self.money_received

    def make_payment(self, cost):
        """Devuelve True si el pago es suficiente, False si no."""
        self.process_coins()
        if self.money_received >= cost:
            change = self.money_received - cost
            print(f"Tu cambio es ${change}.")
            self.profit += cost
            self.money_received = 0
            return True
        else:
            print("Dinero insuficiente. Se te devuelve el dinero.")
            self.money_received = 0
            return False