Apéndice B: Soluciones a Ejercicios
Aquí están las soluciones de referencia. Úsalas solo después de intentarlo por tu cuenta; ver la respuesta antes de intentarlo por tu cuenta no te enseña nada.
Dos tipos de respuesta conviven aquí:
- Solución canónica: ejercicios de código con una respuesta verificable. Todo el código de este apéndice se ejecuta automáticamente con las pruebas del proyecto (
make test-solutions), así que las soluciones que ves aquí corren de verdad. - Respuesta de ejemplo: ejercicios de razonamiento (sobre todo Capítulos 1 y 2) donde hay muchas respuestas válidas; la que mostramos es una vara para comparar, no la única correcta.
Capítulo 1: Introducción
Ejercicio 1.1: El Robot del Café (Básico)
Respuesta de ejemplo (no es la única válida):
- Toma una taza limpia y colócala sobre la mesa.
- Llena la tetera (o una olla) con agua hasta la marca de una taza.
- Pon la tetera a calentar.
- Espera hasta que el agua hierva.
- Abre el frasco de café instantáneo.
- Toma una cucharadita de café del frasco.
- Vierte la cucharadita de café dentro de la taza.
- Vierte el agua caliente en la taza, hasta dejar dos dedos libres del borde.
- Toma una cuchara limpia.
- Revuelve el contenido de la taza durante 10 segundos.
- SI quieres azúcar: agrega una cucharadita de azúcar y revuelve otros 5 segundos.
- Retira la cuchara. El café está listo.
¿Qué tenía que cumplir tu respuesta? Tres cosas: que cada paso sea ejecutable sin interpretación (el robot no “sabe” llenar una taza, hay que decirle cuánto), que el orden sea inequívoco (no puedes verter el agua antes de calentarla), y que no falte ningún paso “obvio” como revolver. Si tu lista cumple eso, está correcta aunque tenga más o menos pasos que esta.
Ejercicio 1.2: Instrucciones de Dirección (Lógica Espacial)
Respuesta de ejemplo (no es la única válida):
Para una habitación donde la silla está junto al escritorio, a la derecha de la puerta:
- Avanza 3 pasos.
- Gira 90 grados a la derecha.
- Avanza 4 pasos.
- Gira 90 grados a la izquierda.
- Avanza 1 paso.
- Detente.
Tu recorrido será distinto porque tu habitación es distinta, y eso está bien. Lo que tenía que cumplir tu respuesta: usar solo los tres comandos permitidos (nada de “camina hacia la silla”, la persona tiene los ojos vendados y no la ve), indicar cantidades concretas de pasos en lugar de distancias vagas, y que la secuencia, ejecutada al pie de la letra, deje a la persona frente a la silla sin chocar con nada. Si tuviste que agregar un giro extra para esquivar la cama, mejor todavía: eso es pensar como programador.
Ejercicio 1.3: La Lámpara no Funciona (Depuración)
Respuesta de ejemplo (no es la única válida):
- ¿Está enchufada la lámpara?
- NO: Enchúfala. ¿Prende? SÍ: fin. NO: Paso 2.
- SÍ: Paso 2.
- ¿Está el interruptor de la lámpara en posición de encendido?
- NO: Enciéndelo. ¿Prende? SÍ: fin. NO: Paso 3.
- SÍ: Paso 3.
- ¿Está quemado el foco? (Pruébalo en otra lámpara que sí funcione).
- SÍ: Reemplaza el foco. ¿Prende? SÍ: fin. NO: Paso 4.
- NO: Paso 4.
- ¿Hay electricidad en el enchufe? (Conecta otro aparato que sepas que funciona).
- NO: El problema es el enchufe o la instalación eléctrica, no la lámpara. Revisa el tablero de fusibles o llama a un electricista.
- SÍ: Paso 5.
- Si llegaste aquí, la falla está dentro de la lámpara (cable o portalámparas). Llévala a reparar o reemplázala.
Lo que tenía que cumplir tu respuesta: que cada pregunta tenga exactamente dos salidas (SÍ y NO) con una acción o un siguiente paso definido para cada una, que las pruebas vayan de lo más barato a lo más caro de verificar (mirar el enchufe antes de desarmar la lámpara), y que en cada rama verifiques si el problema se resolvió antes de seguir. Depurar es exactamente eso: aislar la causa descartando hipótesis en orden.
Ejercicio 1.4: Clasificación de Inventario (Abstracción)
Respuesta de ejemplo (no es la única válida):
Etiqueta las tres cajas así:
- Caja A: Comestibles
- Caja B: Herramientas
- Caja C: Textiles
Reglas para el empleado (las aplica en orden, una por objeto):
- Toma un objeto de la mesa.
- SI el objeto se puede comer: ponlo en la Caja A.
- SI NO, y el objeto sirve para reparar o construir: ponlo en la Caja B.
- SI NO: el objeto es prenda de vestir, ponlo en la Caja C.
- SI quedan objetos en la mesa: vuelve al paso 1.
Resultado: manzanas y peras en la Caja A; martillos y destornilladores en la Caja B; camisas y pantalones en la Caja C.
Lo que tenía que cumplir tu respuesta: que las reglas se basen en una propiedad del objeto (comestible, herramienta, prenda) y no en una lista de nombres, porque mañana llegan naranjas y llaves inglesas y tu sistema debe clasificarlas sin reescribir nada. Además, cada objeto debe caer en exactamente una caja: sin ambigüedad y sin objetos huérfanos. Abstraer es justamente eso: agrupar por lo que las cosas tienen en común, no por lo que son individualmente.
Ejercicio 1.5: El Algoritmo de tu Mañana (Complejidad Real)
Respuesta de ejemplo (no es la única válida):
- Suena la alarma. Abre los ojos.
- SI es fin de semana: apaga la alarma y vuelve a dormir (fin del algoritmo). SI NO: continúa.
- Levántate de la cama.
- Báñate y vístete.
- SI quedan menos de 20 minutos para salir: no desayunes, toma solo un café. SI NO: prepara pan tostado y desayuna sentado.
- Lávate los dientes.
- Toma las llaves, el teléfono y la cartera.
- Mira por la ventana. SI está lloviendo: toma el paraguas. SI NO: toma los lentes de sol.
- Sal de casa y cierra la puerta con llave.
Lo que tenía que cumplir tu respuesta: pasos en orden ejecutable (no puedes vestirte antes de bañarte), al menos dos decisiones con sus dos ramas explícitas (un SI sin su SI NO deja al programa sin saber qué hacer), y que cada condición sea verificable con un dato concreto: “queda poco tiempo” es vago, “quedan menos de 20 minutos” es comprobable. Tu rutina real seguro tiene más pasos y más decisiones; mientras estén explícitas, tu algoritmo está bien.
Capítulo 2: Configuración del Entorno
Ejercicio 2.1: Verificación de Identidad
Respuesta de ejemplo (no es la única válida):
En Windows (PowerShell):
py --version
En macOS/Linux:
python3 --version
La salida esperada es algo como:
Python 3.12.4
Lo que tenía que cumplir tu respuesta: que el comando devuelva una versión 3.x (la exacta no importa, mientras sea 3 punto algo) y que no aparezca un error tipo “comando no encontrado”. Si ves el error, Python no está instalado o no está en el PATH: vuelve a la guía de instalación de tu sistema operativo antes de continuar. En Windows también funciona python --version si dejaste que el gestor configurara el PATH.
Ejercicio 2.2: El Explorador de Archivos (Terminal)
Respuesta de ejemplo (no es la única válida):
En macOS/Linux:
cd ~/Desktop # ve al Escritorio (en algunos sistemas es ~/Escritorio)
mkdir practica_terminal
cd practica_terminal
ls # está vacía, no muestra nada
cd .. # vuelve atrás
rmdir practica_terminal
ls # comprueba que ya no existe
En Windows (PowerShell):
cd ~\Desktop
mkdir practica_terminal
cd practica_terminal
dir # está vacía
cd ..
rmdir practica_terminal
dir # comprueba que ya no existe
Lo que tenía que cumplir tu respuesta: crear, entrar, salir y borrar usando solo la terminal, en ese orden. El detalle que atrapa a la mayoría es que no puedes borrar una carpeta mientras estás dentro de ella: primero cd .., después rmdir. Si la carpeta tuviera contenido, rmdir se negaría; en ese caso usarías rm -r practica_terminal en macOS/Linux o rmdir -Recurse en PowerShell, con cuidado, porque no hay papelera de reciclaje en la terminal.
Ejercicio 2.3: Simulacro de Proyecto
Respuesta de ejemplo (no es la única válida):
En macOS/Linux:
mkdir proyecto_secreto
cd proyecto_secreto
python3 -m venv .venv_secreto
source .venv_secreto/bin/activate
# La terminal ahora muestra: (.venv_secreto) usuario@pc:~/proyecto_secreto$
deactivate
# El paréntesis desaparece
cd ..
rm -r proyecto_secreto
En Windows (PowerShell):
mkdir proyecto_secreto
cd proyecto_secreto
py -m venv .venv_secreto
.venv_secreto\Scripts\Activate
# La terminal ahora muestra: (.venv_secreto) PS C:\...\proyecto_secreto>
deactivate
cd ..
rmdir -Recurse proyecto_secreto
Lo que tenía que cumplir tu respuesta: el ciclo completo en orden (crear carpeta, crear entorno, activar, desactivar, limpiar) y la verificación visual del paréntesis, que es tu única señal de que estás “dentro de la habitación”. Dos errores clásicos: activar sin haber entrado antes a la carpeta del proyecto (la ruta no existe desde fuera) e intentar borrar la carpeta con el entorno todavía activo. Desactiva y sal antes de borrar.
Ejercicio 2.4: Instalación de Prueba
Respuesta de ejemplo (no es la única válida):
Con el entorno virtual ya activado (verifica el paréntesis):
pip install pyjokes
pip list
La salida de pip list debe incluir algo como:
Package Version
---------- -------
pip 24.0
pyjokes 0.8.3
Ahora desactiva y vuelve a preguntar:
deactivate
pip list
Esta vez pyjokes no aparece en la lista (o el comando muestra los paquetes globales de tu sistema, que son otros; o la terminal responde que pip ya ni existe, y esa también es la respuesta correcta). La librería quedó encerrada dentro de la habitación .venv, no contaminó el resto del almacén.
Lo que tenía que cumplir tu respuesta: instalar con el entorno activo, verificar con pip list que la librería está, y comprobar que al desactivar desaparece. Si pyjokes aparece también fuera del entorno, lo instalaste sin activar primero: es el error más común y la razón de ser de este ejercicio.
Ejercicio 2.5: Configuración del Editor
Respuesta de ejemplo (no es la única válida):
- Abre Visual Studio Code.
- Ve al panel de extensiones: ícono de bloques en la barra lateral izquierda, o atajo
Ctrl+Shift+X(Cmd+Shift+Xen macOS). - Busca “Python”, elige la extensión oficial publicada por Microsoft y pulsa Install.
- Abre la carpeta de un proyecto que tenga entorno virtual: menú File > Open Folder (Archivo > Abrir carpeta) y selecciona, por ejemplo,
mi-primer-almacen. - Abre la paleta de comandos con
Ctrl+Shift+P(Cmd+Shift+Pen macOS) y escribe Python: Select Interpreter. - En la lista, elige el intérprete cuya ruta pasa por tu carpeta
.venv:- macOS/Linux:
./.venv/bin/python - Windows:
.\.venv\Scripts\python.exe
- macOS/Linux:
- Verifica en la esquina inferior derecha de la ventana: al tener un archivo
.pyabierto, debe mostrar la versión de Python seguida de('.venv')o similar.
Lo que tenía que cumplir tu respuesta: extensión oficial de Python instalada, la carpeta del proyecto abierta (no un archivo suelto, porque VS Code detecta el .venv dentro de esa carpeta) y el intérprete apuntando a .venv y no al Python global. Si el intérprete apunta al global, VS Code no verá las librerías que instalaste en tu entorno y te subrayará imports que sí funcionan.
Capítulo 3: Sintaxis y Tu Primer Programa
Ejercicio 3.1: La Tarjeta de Presentación
print("##########################")
print("# Nombre: Tu Nombre #")
print("# Cargo: Aprendiz Python #")
print("##########################")
Ejercicio 3.2: El Arte del Error (Provocando Fallos)
# Intento 1: print(Hola)
# NameError: name 'Hola' is not defined
# Significado: sin comillas, Python no ve texto, ve el nombre de una
# variable llamada Hola que nunca fue creada.
# Intento 2: print("Hola"
# SyntaxError: '(' was never closed
# Significado: el paréntesis quedó abierto y Python no encuentra
# dónde termina la instrucción.
# La versión corregida, con comillas y paréntesis cerrado:
print("Hola")
Ejercicio 3.3: Indentación Correcta
# Código reparado
print("Iniciando sistema...")
print("Cargando módulos...") # ya no tiene espacios extra a la izquierda
if True:
print("Sistema listo") # ahora sí está adentro del if, con 4 espacios
Ejercicio 3.4: Comentarios Útiles
# Paso 1: preparar la base de la receta
print("Paso 1: Hervir dos tazas de agua en una olla")
# Paso 2: agregar el ingrediente principal
print("Paso 2: Agregar la pasta y cocinar por 10 minutos")
# Paso 3: terminar y servir
print("Paso 3: Escurrir, agregar salsa y servir caliente")
Ejercicio 3.5: Tu Primer Script Interactivo
# input() detiene el programa y espera a que escribas algo.
# Lo que escribas queda guardado en la variable nombre.
nombre = input("¿Cómo te llamas? ")
# print() acepta varios valores separados por comas
print("Hola,", nombre)
Capítulo 4: Variables y Tipos de Datos
Ejercicio 4.1: El Inventario Inicial
nombre_producto: str = "Laptop Gamer"
precio: float = 1250.50
cantidad_stock: int = 15
disponible: bool = True
print(nombre_producto)
print(precio)
print(cantidad_stock)
print(disponible)
Ejercicio 4.2: Corrección de Datos
# El tipo correcto no depende de cómo "se ve" el dato, sino de qué vas
# a hacer con él. Un código postal nunca entra en una suma y puede
# llevar ceros a la izquierda: texto.
edad_usuario: int = 25
codigo_postal: str = "55120"
altura_metros: float = 1.75
print(edad_usuario, codigo_postal, altura_metros)
Ejercicio 4.3: Operaciones Básicas de Tipos
precio_unitario : float = 49.90
cantidad : int = 3
total = precio_unitario * cantidad
print(f"El costo total es: ${total}")
Ejercicio 4.4: El Misterio del Tipo
print(type(3.14)) # <class 'float'>
print(type("True")) # <class 'str'> <- la sorpresa: las comillas lo vuelven texto
print(type(True)) # <class 'bool'>
print(type(100)) # <class 'int'>
Ejercicio 4.5: Conversión de Emergencia (Casting)
numero_texto = "100"
numero_entero = int(numero_texto)
resultado = numero_entero + 50
print(resultado) # 150
Ejercicio 4.6: Rebanando Códigos (Slicing)
codigo = "PRD-2026-TORNILLO"
print(codigo[:3]) # "PRD" (del inicio al índice 2)
print(codigo[4:8]) # "2026" (el índice 8 queda fuera)
print(codigo[-8:]) # "TORNILLO" (últimos 8, sin contar la longitud total)
print(codigo[-1]) # "O" (el último carácter)
Ejercicio 4.7: La Ficha de Recepción (Integrador)
nombre_crudo = input("Nombre del producto: ")
nombre = nombre_crudo.strip().lower()
precio_texto = input("Precio unitario: ")
precio = float(precio_texto)
print(f"Producto: {nombre} | Precio: ${precio:.2f}")
print(type(nombre)) # <class 'str'>
print(type(precio)) # <class 'float'>
Capítulo 5: Estructuras de Datos
Ejercicio 5.1: El Inventario Perdido (Listas)
frutas: list[str] = ["Manzana", "Pera", "Naranja"]
frutas.append("Uva") # agrega al final de la lista
frutas[1] = "Mango" # "Pera" está en la posición 1
print(frutas) # ['Manzana', 'Mango', 'Naranja', 'Uva']
Ejercicio 5.2: Catálogo de Precios (Diccionarios)
precios: dict[str, int] = {"Manzana": 10, "Mango": 15, "Uva": 20}
precios["Mango"] = 18 # la llave ya existe: se actualiza el valor
precios["Sandía"] = 30 # la llave no existe: se agrega el producto
print(precios["Uva"]) # 20
Ejercicio 5.3: Limpieza de Datos (Conjuntos)
ids = [101, 102, 101, 103, 102, 104]
# Al convertir la lista en conjunto, los duplicados desaparecen solos
ids_unicos: set[int] = set(ids)
# len() cuenta cuántos elementos quedaron
print(f"Usuarios únicos: {len(ids_unicos)}") # 4
Ejercicio 5.4: Datos Intocables (Tuplas)
colores_primarios: tuple[str, str, str] = ("Rojo", "Azul", "Amarillo")
# Intento de cambiar "Rojo" por "Verde":
# colores_primarios[0] = "Verde"
# TypeError: 'tuple' object does not support item assignment
# La tupla no admite asignar elementos: una vez creada, no se modifica.
print(colores_primarios)
Ejercicio 5.5: El Sistema Integrado (Reto Final)
estudiantes = [
{"nombre": "Ana", "calificacion": 90},
{"nombre": "Beto", "calificacion": 85}
]
# Cada estudiante es un diccionario; la lista los agrupa
estudiantes.append({"nombre": "Tu Nombre", "calificacion": 100})
# Primero se accede a la posición de la lista, luego a la llave
print(estudiantes[0]["nombre"]) # Ana
Ejercicio 5.6: El Diccionario Seguro (.get e .items)
producto = {"nombre": "Tornillos", "precio": 5.5}
# .get evita el KeyError cuando la clave puede no existir
print(producto.get("descuento", "sin descuento"))
# .items() entrega clave y valor en cada vuelta
for clave, valor in producto.items():
print(f"{clave}: {valor}")
# 'in' pregunta por la CLAVE, no por el valor
print("stock" in producto) # False
Ejercicio 5.7: Rebanando el Estante (Slicing de listas)
estante = ["Martillo", "Taladro", "Sierra", "Clavos", "Cinta"]
print(estante[:2]) # ['Martillo', 'Taladro'] (las dos primeras)
print(estante[-1]) # 'Cinta' (la última)
print(estante[-2:]) # ['Clavos', 'Cinta'] (las dos últimas)
print(estante[1:]) # todo menos la primera
Ejercicio 5.8: El Reparto (Unpacking)
envio = ("PRD-101", "Tornillos", 500)
codigo, nombre, cantidad = envio
print(codigo) # PRD-101
print(nombre) # Tornillos
print(cantidad) # 500
# Intercambio en una línea: se arma la tupla (b, a) y se desempaca
a = "Pasillo 1"
b = "Pasillo 2"
a, b = b, a
print(a) # Pasillo 2
print(b) # Pasillo 1
Ejercicio 5.9: Las Dos Etiquetas (Aliasing y .copy())
# Parte 1: dos etiquetas, un mismo estante
original = ["Martillo", "Sierra"]
respaldo = original # NO es una copia: es otra etiqueta al mismo estante
respaldo.append("Taladro")
print(original) # ['Martillo', 'Sierra', 'Taladro'] ← también cambió
print(respaldo is original) # True: son el mismo objeto
# Parte 2: ahora con una copia de verdad
original = ["Martillo", "Sierra"]
respaldo = original.copy() # estante nuevo con el mismo contenido
respaldo.append("Taladro")
print(original) # ['Martillo', 'Sierra'] ← intacto
print(respaldo is original) # False: dos estantes distintos
Capítulo 6: Operadores
Ejercicio 6.1: La Calculadora de Descuentos
precio = 100
descuento = 0.2 # 20%
monto_ahorrado = precio * descuento
precio_final = precio - monto_ahorrado
print(f"Monto ahorrado: ${monto_ahorrado}") # $20.0
print(f"Precio final: ${precio_final}") # $80.0
Ejercicio 6.2: ¿Par o Impar? (Módulo)
numero = 7
# El módulo entrega el sobrante de dividir entre 2:
# 0 significa par, 1 significa impar
sobrante = numero % 2
print(sobrante) # 1, así que 7 es impar
Ejercicio 6.3: El Portero Lógico
altura = 1.6
antiguedad = 10
restriccion = False
# Mide más de 1.5 O tiene más de 12 años de antigüedad,
# Y ADEMÁS no tiene restricción médica activa
puede_pasar = (altura > 1.5 or antiguedad > 12) and not restriccion
# Sí pasa: cumple por la altura (1.6 > 1.5) y no tiene
# restricción, aunque la antigüedad no alcance.
print(puede_pasar) # True
Ejercicio 6.4: Manipulación de Texto (in)
frase = "El veloz murciélago hindú comía feliz cardillo y kiwi"
print("murciélago" in frase) # True: la palabra sí está
print("manzana" not in frase) # True: la palabra NO está
Ejercicio 6.5: Asignación Rápida
puntos = 10
puntos += 5 # 10 + 5 = 15
puntos *= 2 # 15 * 2 = 30
puntos -= 4 # 30 - 4 = 26
print(puntos) # 26
Ejercicio 6.6: El Valor por Defecto (Cortocircuito)
entrada = "Ana"
nombre = entrada or "Anónimo"
print(nombre) # "Ana": la cadena con texto es "verdadera" y or se queda con ella
entrada = ""
nombre = entrada or "Anónimo"
# La cadena vacía cuenta como "falsa", así que or descarta 'entrada'
# y devuelve el segundo valor.
print(nombre) # "Anónimo"
Ejercicio 6.7: ¿La Misma Caja? (is e is None)
pedido = None
print(pedido is None) # True: no hay pedido todavía
pedido = ["Tornillos"]
print(pedido is None) # False: ya llegó algo
a = [1, 2]
b = [1, 2]
print(a == b) # True: mismo CONTENIDO
print(a is b) # False: dos cajas distintas que se parecen
c = a
print(a is c) # True: c es otra etiqueta de LA MISMA caja
Ejercicio 6.8: El Orden de la Maquinaria (Precedencia)
print(2 + 3 * 4) # 14: la multiplicación va primero
print((2 + 3) * 4) # 20: el paréntesis manda
print(10 - 4 - 3) # 3: la resta evalúa de izquierda a derecha
print(True or False and False) # True: and se evalúa antes que or
# Moraleja: cuando dudes de la precedencia, los paréntesis son gratis.
# Reto: precedencia entre operadores lógicos.
a = True
b = False
print(not (a or b)) # False: (a or b) es True, y not lo invierte
Capítulo 7: Control de Flujo
Ejercicio 7.1: El Portero del Club (Repetido y Mejorado)
edad: int = 21
tiene_pase: bool = True
# La misma regla del capítulo de operadores, pero ahora el programa decide
# y habla por sí mismo en lugar de solo imprimir True o False.
if edad >= 18 and tiene_pase:
print("Bienvenido al club")
else:
print("Lo siento, no puedes pasar")
Ejercicio 7.2: El Clasificador de Envíos
destino: str = "Europa"
# Python evalúa de arriba hacia abajo y se queda con la primera
# coincidencia. El else atrapa cualquier destino que no esté en la lista.
if destino == "Norteamérica":
costo = 50
elif destino == "Europa":
costo = 70
elif destino == "Asia":
costo = 90
else:
costo = 120
print(f"Costo de envío a {destino}: ${costo}")
Ejercicio 7.3: La Máquina de Café (Match-Case)
# input() siempre entrega texto; convertimos a int para comparar números.
opcion = int(input("Elige tu bebida (1, 2 o 3): "))
match opcion:
case 1:
print("Preparando Espresso...")
case 2:
print("Preparando Capuchino...")
case 3:
print("Preparando Latte...")
case _:
# El comodín atrapa el 4, el 5 o cualquier otro número.
print("Opción inválida")
Ejercicio 7.4: Calificación Escolar (Lógica de Rangos)
calificacion: int = 87
# Reto: la validación va primero. Si el dato es basura, no tiene caso
# seguir comparando rangos.
if calificacion < 0 or calificacion > 100:
print("Calificación inválida: debe estar entre 0 y 100")
elif calificacion >= 90:
letra = "A"
print(f"Calificación: {letra}")
elif calificacion >= 80:
# Si llegamos aquí, ya sabemos que es menor a 90. Por eso el orden
# de los elif no es negociable: de mayor a menor.
letra = "B"
print(f"Calificación: {letra}")
elif calificacion >= 70:
letra = "C"
print(f"Calificación: {letra}")
elif calificacion >= 60:
letra = "D"
print(f"Calificación: {letra}")
else:
letra = "F"
print(f"Calificación: {letra}")
Ejercicio 7.5: El Robot Explorador (Patrones)
coordenadas: tuple = (0, 7)
# match no solo compara valores: abre la tupla y examina su estructura.
# El orden importa: (0, 0) debe ir antes que (0, y), porque (0, y)
# también atraparía a la base.
match coordenadas:
case (0, 0):
print("En la base")
case (0, y):
# x es exactamente 0; el valor de y queda capturado en la variable
print("En el eje Y")
case (x, 0):
print("En el eje X")
case _:
print("En el cuadrante")
Ejercicio 7.6: El Cajero con Guardas (Truthiness)
def cobrar(carrito, tiene_dinero):
if not carrito: # lista vacía es falsa
return "Carrito vacío"
if not tiene_dinero:
return "Pago rechazado"
return "Venta completada"
print(cobrar([], True)) # "Carrito vacío"
print(cobrar(["pan"], False)) # "Pago rechazado"
print(cobrar(["pan"], True)) # "Venta completada"
Capítulo 8: Bucles y Automatización
Ejercicio 8.1: El Contador de Inventario (for)
productos = ["Manzana", "Pera", "Manzana", "Uva", "Manzana"]
# El diccionario nace vacío. .get(producto, 0) lee el conteo actual
# (0 si el producto es nuevo), le suma 1 y lo guarda de vuelta.
conteo = {}
for producto in productos:
conteo[producto] = conteo.get(producto, 0) + 1
print(conteo)
Ejercicio 8.2: La Cuenta Regresiva (while)
contador = 10
# La resta dentro del bucle es lo que evita el bucle infinito:
# tarde o temprano el contador llega a 0 y la condición se vuelve falsa.
while contador > 0:
print(contador)
contador = contador - 1
print("¡Despegue!")
Ejercicio 8.3: Tablas de Multiplicar
numero = int(input("¿De qué número quieres la tabla? "))
# range(1, 11) genera del 1 al 10: el límite superior nunca se incluye.
for multiplicador in range(1, 11):
resultado = numero * multiplicador
print(f"{numero} x {multiplicador} = {resultado}")
Ejercicio 8.4: Filtrando Basura (continue)
datos = [10, 50, -5, 20, -10, 30]
for numero in datos:
if numero < 0:
# continue salta el resto del bloque y pasa al siguiente número.
# El negativo nunca llega al print.
continue
print(numero)
Ejercicio 8.5: Prensa Industrial (Comprehension)
dolares = [10, 20, 50]
# Una sola línea: recorrer, transformar y construir la lista nueva.
pesos = [valor * 20 for valor in dolares]
print(pesos) # [200, 400, 1000]
Ejercicio 8.6: La Lista Numerada (enumerate)
productos = ["Martillo", "Taladro", "Sierra"]
# start=1 hace que la numeración empiece en 1 en vez de 0
for indice, producto in enumerate(productos, start=1):
print(f"{indice}. {producto}")
Ejercicio 8.7: Precios Emparejados (zip)
productos = ["Martillo", "Taladro", "Sierra"]
precios = [50, 120, 80]
# zip recorre las dos listas en paralelo
for producto, precio in zip(productos, precios):
print(f"{producto}: ${precio}")
Ejercicio 8.8: La Inspección con Bandera (break)
lote = ["ROJA", "AZUL", "DAÑADA", "VERDE", "DAÑADA"]
hay_danio = False
posicion = None
for indice, caja in enumerate(lote):
if caja == "DAÑADA":
hay_danio = True
posicion = indice
break # con una basta: no hace falta seguir buscando
if hay_danio:
print(f"Lote rechazado (primer daño en la posición {posicion})")
else:
print("Lote aprobado")
Ejercicio 8.9: El Mini Mostrador (while True + menú)
while True:
print("\n1. Saludar")
print("2. Salir")
opcion = input("Elige una opción: ")
if opcion == "1":
print("¡Hola, Gerente!")
elif opcion == "2":
print("Hasta pronto.")
break
else:
print("Esa opción no existe.")
Capítulo 9: Funciones
Ejercicio 9.1: El Saludo Corporativo
def bienvenida(nombre: str) -> None:
"""Imprime el saludo corporativo para un empleado."""
# Esta función no retorna nada (solo imprime), por eso -> None.
print(f"Hola {nombre}, empleado #1234")
# El mismo procedimiento sirve para cualquier empleado.
bienvenida("Ana")
bienvenida("Beto")
Ejercicio 9.2: La Calculadora de Áreas (return)
def area_caja(base: float, altura: float) -> float:
"""Retorna el área de una caja rectangular."""
# return entrega el valor a quien llama; imprimir aquí lo desperdiciaría.
return base * altura
resultado = area_caja(10, 5)
print(f"El área es: {resultado}")
Ejercicio 9.3: Conversor de Moneda (Parámetros por defecto)
def convertir_dolares(cantidad: float, tasa: float = 20.0) -> float:
"""Convierte dólares a pesos con la tasa indicada (20.0 por defecto)."""
return cantidad * tasa
# Sin tasa: entra el valor de respaldo (20.0).
print(convertir_dolares(10)) # 200.0
# Con tasa: el argumento de quien llama gana sobre el valor por defecto.
print(convertir_dolares(10, 18.5)) # 185.0
Ejercicio 9.4: Validador de Acceso (Lógica dentro de Funciones)
def es_mayor_edad(edad: int) -> bool:
"""Retorna True si la edad alcanza la mayoría de edad."""
# La comparación ya produce un bool: no hace falta un if/else aquí.
return edad >= 18
if es_mayor_edad(15):
print("Adelante")
else:
print("Fuera")
Ejercicio 9.5: El Formateador de Inventario
def formato_producto(nombre: str, precio: float, stock: int) -> str:
"""Retorna la ficha del producto lista para mostrar."""
# :.2f fuerza dos decimales para que el precio se vea como dinero.
return f"[PRODUCTO]: {nombre} | ${precio:.2f} | Disponibles: {stock}"
producto = {"nombre": "Laptop", "precio": 1500.00, "stock": 5}
print(formato_producto(producto["nombre"], producto["precio"], producto["stock"]))
Ejercicio 9.6: El Descuento con Default
def aplicar_descuento(precio: float, porcentaje: float = 10) -> float | None:
if precio < 0:
return None # el pedido no procede
return precio * (1 - porcentaje / 100)
print(aplicar_descuento(100)) # 90.0 (usa el 10% por defecto)
print(aplicar_descuento(100, 25)) # 75.0 (porcentaje propio)
print(aplicar_descuento(-5)) # None (precio inválido)
Ejercicio 9.7: La Orden Explícita (Argumentos por Nombre)
def etiquetar(nombre: str, pasillo: str = "A", refrigerado: bool = False) -> str:
if refrigerado:
clima = "Refrigerado"
else:
clima = "Ambiente"
return f"{nombre} | Pasillo {pasillo} | {clima}"
# 1. Solo el nombre: los defaults hacen el resto
print(etiquetar("Tornillos"))
# 2. Saltarse el pasillo exige nombrar el argumento
print(etiquetar("Helado", refrigerado=True))
# 3. Nombrados en desorden: el nombre manda, no la posición
print(etiquetar(refrigerado=False, nombre="Clavos", pasillo="C"))
Capítulo 10: Control de Daños (Excepciones)
Ejercicio 10.1: Diagnóstico de Accidentes
# Cada línea del ejercicio produce una excepción distinta. Aquí las
# provocamos una por una, cada una en su propio try/except, para
# confirmar el diagnóstico sin que el script muera en el intento.
try:
int("veinte")
except ValueError as e:
# El texto "veinte" no se puede convertir a número.
print(f"ValueError: {e}")
try:
"50" + 5
except TypeError as e:
# No se puede sumar texto con entero: tipos incompatibles.
print(f"TypeError: {e}")
try:
{"a": 1}["b"]
except KeyError as e:
# La llave "b" no existe en el diccionario.
print(f"KeyError: {e}")
try:
100 / 0
except ZeroDivisionError as e:
# Dividir entre cero no está definido.
print(f"ZeroDivisionError: {e}")
try:
open("no_existe.txt", "r")
except FileNotFoundError as e:
# El archivo no está en el disco.
print(f"FileNotFoundError: {e}")
Ejercicio 10.2: La División Protegida
dividendo = int(input("Primer número: "))
divisor = int(input("Segundo número: "))
try:
# La línea peligrosa va dentro del try.
print(f"Resultado: {dividendo / divisor}")
except ZeroDivisionError:
print("No se puede dividir entre cero")
Ejercicio 10.3: Doble Protección
# Las conversiones también van dentro del try: int() puede explotar
# si el usuario escribe texto.
try:
dividendo = int(input("Primer número: "))
divisor = int(input("Segundo número: "))
print(f"Resultado: {dividendo / divisor}")
except ValueError:
# Atrapa el texto que no se puede convertir a número.
print("Eso no es un número válido")
except ZeroDivisionError:
# Atrapa la división entre cero.
print("No se puede dividir entre cero")
Ejercicio 10.4: El Portero Robusto
def pedir_numero_seguro(mensaje: str) -> int:
"""Pide un número al usuario y repite hasta recibir uno válido."""
while True:
try:
# Si int() tiene éxito, return rompe el bucle y entrega el valor.
valor = int(input(mensaje))
except ValueError:
# Si explota, avisamos y el while vuelve a preguntar.
print("Eso no es un número. Intenta de nuevo.")
continue
if valor < 0:
print("No se aceptan números negativos. Intenta de nuevo.")
continue
return valor
cajas = pedir_numero_seguro("¿Cuántas cajas llegaron? ")
print(f"Registradas {cajas} cajas.")
Ejercicio 10.5: El Descuento Estricto (raise + finally)
def aplicar_descuento(precio: float, porcentaje: float = 10) -> float:
if precio < 0:
raise ValueError("El precio no puede ser negativo")
return precio * (1 - porcentaje / 100)
for precio in [100, -5]:
try:
print(f"Precio final: ${aplicar_descuento(precio):.2f}")
except ValueError as e:
print(f"Pedido rechazado: {e}")
finally:
print("Cotización terminada.")
Capítulo 11: Archivos y Datos
Ejercicio 11.1: El Explorador de Rutas (pathlib)
from pathlib import Path
# La ruta es solo una etiqueta: el archivo no necesita existir todavía.
ruta = Path("datos/usuarios.txt")
print(ruta.name) # usuarios.txt
print(ruta.parent) # datos
Ejercicio 11.2: La Nota de Bienvenida (Escritura simple)
from pathlib import Path
# Carpeta temporal para la prueba. (mkdir no es parte del
# ejercicio: solo prepara el terreno y se limpia al final.)
carpeta = Path("tmp_ejercicio")
carpeta.mkdir(exist_ok=True)
archivo = carpeta / "bienvenida.txt"
# "w" crea el archivo (o lo reescribe si ya existía).
with open(archivo, "w", encoding="utf-8") as f:
f.write("¡Bienvenido al sistema v1.0!")
# Lo abrimos de nuevo en modo lectura para verificar.
with open(archivo, "r", encoding="utf-8") as f:
print(f.read())
# Limpiar el almacén al salir: borramos lo que creamos.
archivo.unlink()
carpeta.rmdir()
Ejercicio 11.3: El Registro de Acceso (append)
from pathlib import Path
# Carpeta temporal para el log de esta práctica.
carpeta = Path("tmp_ejercicio")
carpeta.mkdir(exist_ok=True)
log = carpeta / "acceso.log"
nombres = ["Ana", "Beto", "Carla"]
for nombre in nombres:
# "a" agrega al final sin borrar lo que ya estaba.
with open(log, "a", encoding="utf-8") as f:
f.write(f"Usuario {nombre} entró al sistema.\n")
# Leemos el log completo para comprobar las tres entradas.
with open(log, "r", encoding="utf-8") as f:
print(f.read().rstrip())
# Limpiar el almacén al salir: borramos lo que creamos.
log.unlink()
carpeta.rmdir()
Ejercicio 11.4: Verificador de Existencia
from pathlib import Path
# Carpeta temporal para no dejar secretos regados en el disco.
carpeta = Path("tmp_ejercicio")
carpeta.mkdir(exist_ok=True)
secreto = carpeta / "secreto.txt"
# El ejercicio pide ejecutar el script dos veces. Aquí repetimos la
# comprobación dos veces para ver ambos comportamientos en una corrida:
# la primera pasada crea el archivo, la segunda lo encuentra y lo lee.
for pasada in [1, 2]:
print(f"Pasada {pasada}:")
if secreto.exists():
with open(secreto, "r", encoding="utf-8") as f:
print(f.read())
else:
print("No existe. Creando archivo...")
with open(secreto, "w", encoding="utf-8") as f:
f.write("Top Secret")
# Limpiar el almacén al salir: borramos lo que creamos.
secreto.unlink()
carpeta.rmdir()
Ejercicio 11.5: Limpieza de Archivos
from pathlib import Path
# Carpeta temporal para practicar el borrado sin riesgos.
carpeta = Path("tmp_ejercicio")
carpeta.mkdir(exist_ok=True)
basura = carpeta / "basura.tmp"
# 1. Crear el archivo.
with open(basura, "w", encoding="utf-8") as f:
f.write("datos temporales")
# 2. Verificar que existe.
print(f"¿Existe antes de borrar? {basura.exists()}") # True
# 3. Borrarlo desde el código. Precaución: unlink() no tiene deshacer.
basura.unlink()
# 4. Verificar que ya no existe.
print(f"¿Existe después de borrar? {basura.exists()}") # False
# Limpiar el almacén al salir: la carpeta temporal también se va.
carpeta.rmdir()
Ejercicio 11.6: Inventario en JSON (Round-trip)
import json
from pathlib import Path
productos = [
{"nombre": "Tornillos, 2mm", "precio": 5.5},
{"nombre": "Taladro", "precio": 89.0},
]
ruta = Path("productos.json")
with open(ruta, "w", encoding="utf-8") as f:
json.dump(productos, f, indent=2, ensure_ascii=False)
with open(ruta, encoding="utf-8") as f:
cargados = json.load(f)
print(cargados[0]["nombre"]) # 'Tornillos, 2mm' (la coma no rompe nada)
print(cargados[0]["precio"] + 1) # 6.5 (sigue siendo número, no texto)
ruta.unlink() # limpieza: borramos el archivo de prueba
Ejercicio 11.7: La Caja de Herramientas (import)
import random
from datetime import date
productos = ["Martillo", "Taladro", "Sierra"]
elegido = random.choice(productos)
print(f"Producto del día ({date.today()}): {elegido}")
Ejercicio 11.8: El Manifiesto en CSV (Round-trip)
import csv
from pathlib import Path
ruta = Path("manifiesto.csv")
filas = [
{"producto": "Tornillos, 2mm", "cantidad": 500},
{"producto": "Tuercas", "cantidad": 200},
{"producto": "Taladro", "cantidad": 12},
]
# Ida: escribir con DictWriter (protege la coma del nombre)
with open(ruta, "w", newline="", encoding="utf-8") as f:
escritor = csv.DictWriter(f, fieldnames=["producto", "cantidad"])
escritor.writeheader()
escritor.writerows(filas)
def total_manifiesto() -> int:
"""Lee manifiesto.csv y retorna la suma de las cantidades."""
total = 0
with open(ruta, encoding="utf-8") as f:
for fila in csv.DictReader(f):
print(f"{fila['producto']} x {fila['cantidad']}")
total += int(fila["cantidad"]) # del CSV todo sale como texto
return total
print(f"Total de piezas: {total_manifiesto()}")
ruta.unlink() # limpieza: el ejercicio no deja rastro
Capítulo 12: Proyecto Integrador
El proyecto integrador no tiene “una” solución. Las Guías de Referencia del propio Capítulo 12 contienen una implementación de referencia completa del SGI v1.0, del modelo de datos al ensamblaje final: el Mostrador (Guía 4) deja las opciones 3 y 4 como reto, y el archivo completo de la Guía 5 las incluye resueltas. Mejoras y Desafíos discute cómo extender el sistema. Compara tu sistema con esas guías: si tu versión hace lo mismo con otra estructura, también está bien.