Saltar al contenido
Inicio 100 Días de Código en Python — Día 27: Tkinter, *args, **kwargs y programas con interfaz gráfica

100 Días de Código en Python — Día 27: Tkinter, *args, **kwargs y programas con interfaz gráfica

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 27 (nivel intermedio).

Este tutorial adapta pedagógicamente el Día 27. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso. Los retos 🟢🟡🟠🔴 y el proyecto extra son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.

Nota sobre las verificaciones: los ejemplos se ejecutaron con Python 3.14 y Tk 8.6 en Windows. Los mensajes de error de Tkinter que aparecen aquí son los obtenidos en esa versión. La "demo de widgets" que el curso enlaza en la lección 210 no está en la carpeta; esa sección se construyó a partir de la explicación de la lección.


Tabla de contenidos

  1. Antes de empezar
  2. Un poco de historia: ¿qué es una GUI?
  3. Ventanas y etiquetas con Tkinter
  4. Argumentos con valores por defecto
  5. *args: muchos argumentos posicionales
  6. **kwargs: muchos argumentos de palabra clave
  7. Botones, entradas y opciones de los widgets
  8. Otros widgets: el "recetario"
  9. Gestores de diseño: pack, place y grid
  10. 🚀 Proyecto del día: el conversor de millas a kilómetros
  11. 🌟 Proyecto extra: la calculadora de huella de carbono del transporte escolar
  12. 🧩 Comprueba lo aprendido
  13. 🤖 Programar con apoyo de IA
  14. 🛠️ Correcciones técnicas al material original
  15. 📋 Matriz de trazabilidad
  16. 📦 Evidencias de aprendizaje del Día 27

0. Antes de empezar

(Fuente: 203 - Day 27 Goals- what we will make by the end of the day.md)

Hasta ahora nuestros programas funcionaban en la consola o dentro del "corral" de Turtle. Hoy crearemos programas con ventanas, etiquetas, botones y cajas de texto usando Tkinter, el módulo de interfaces gráficas que viene incluido con Python.

Al mismo tiempo aprenderemos características avanzadas de las funciones: valores por defecto, *args y **kwargs. No es casualidad: son la clave para entender cómo está construido Tkinter.

El proyecto del día será un conversor de millas a kilómetros con interfaz gráfica.

Prerequisito general: funciones con parámetros y argumentos de palabra clave (Día 8), funciones con return (Día 10), tuplas (Día 18), diccionarios y .get() (Día 9), clases y __init__ (Días 16 y 17), eventos y funciones como argumento en Turtle (Día 19) y conversión de tipos con float() (Día 2).


1. Un poco de historia: ¿qué es una GUI?

(Fuente: 204 - History of GUI and Introduction to Tkinter.md)

Una GUI (Graphical User Interface, interfaz gráfica de usuario) permite usar un computador señalando y haciendo clic, en lugar de escribir comandos de texto como en MS-DOS o en la consola de Python.

La historia que cuenta el curso:

  • Xerox PARC (un centro de investigación en Palo Alto) creó la primera interfaz gráfica, el primer ratón para usarla, Ethernet y uno de los primeros lenguajes orientados a objetos.
  • Apple lanzó la Lisa, uno de los primeros computadores con este tipo de interfaz.
  • Microsoft lanzó después Windows. Apple demandó a Microsoft por infringir sus derechos.
  • Según la biografía de Steve Jobs escrita por Walter Isaacson, cuando Jobs acusó a Bill Gates de robarle la idea, Gates respondió: "Es como si ambos tuviéramos un vecino rico llamado Xerox; yo entré a su casa a robar el televisor y descubrí que tú ya lo habías robado".

El curso recomienda la película Piratas de Silicon Valley para conocer más esta historia.

💡 Para discutir en clase: ¿quién merece el crédito por una idea: quien la inventa o quien la convierte en un producto que todos usan? ¿Qué relación tiene esto con la propiedad intelectual y con el software libre?


2. Ventanas y etiquetas con Tkinter

(Fuente: 205 - Creating Windows and Labels with Tkinter.md)

🎯 Objetivo de aprendizaje

  • Crear una ventana, cambiar su título y su tamaño mínimo.
  • Crear una etiqueta y mostrarla en la ventana.

📖 Explicación conceptual

1. La ventana. Es el equivalente a la Screen de Turtle. Se crea a partir de la clase Tk:

Python
import tkinter

window = tkinter.Tk()
window.title("My First GUI Program")
window.minsize(width=500, height=300)

# ... aquí van todos los componentes ...

window.mainloop()

¿Por qué mainloop()? Sin esa línea, Tkinter crea la ventana, ve que no hay más instrucciones y termina el programa de inmediato. mainloop() es un bucle que mantiene la ventana abierta y escucha lo que hace el usuario (clics, teclas…). Es parecido a un while True que ya viene incluido en Tkinter.

Regla del curso: mainloop() va siempre al final. Todo lo demás se escribe entre la creación de la ventana y esa línea.

  • minsize define un tamaño mínimo. La ventana crece si le pones muchos componentes.

2. La etiqueta (Label).

Python
my_label = tkinter.Label(text="I Am a Label", font=("Arial", 24, "bold"))
my_label.pack()
  • text es el texto que muestra.
  • font es una tupla: nombre de la fuente, tamaño y estilo ("bold" para negrita, "italic" para cursiva, o se omite).
  • Crear un componente no basta para verlo. Hay que indicar cómo se ubica en la ventana. La forma más sencilla es pack(), que lo centra.

3. Opciones de pack. La documentación de Tkinter (enlazada en los recursos del curso) muestra opciones como side ("left", "right", "top", "bottom") y expand (True para ocupar todo el espacio disponible).

El misterio que abre el siguiente tema. Cuando escribes tim.write( en Turtle, PyCharm te muestra todos los parámetros posibles (arg, move, align, font). Pero cuando escribes my_label.pack(, solo aparece **kw. ¿Cómo es posible que side y expand funcionen si no aparecen? La respuesta está en los argumentos avanzados de Python.

⚠️ Errores frecuentes

Error Qué ocurre Solución
Olvidar mainloop() La ventana aparece y desaparece al instante, o no aparece Agrega window.mainloop() al final
Olvidar pack() La etiqueta no aparece Todo widget necesita un gestor de diseño
Escribir código después de mainloop() Ese código no se ejecuta mientras la ventana esté abierta Todo va antes de mainloop()
font=("Arial", "24") Puede funcionar, pero no es lo esperado El tamaño es un número entero

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: crea una ventana con el nombre de tu colegio como título y una etiqueta de bienvenida.
  • 🟡 Reto 2: pon tres etiquetas con pack(side="left") y luego con side="bottom". Dibuja en tu cuaderno cómo quedan en cada caso.
  • 🟠 Reto 3: investiga en la documentación las opciones fg y bg de Label, y crea un letrero con los colores de tu colegio.
  • 🔴 Reto 4: ¿qué pasa si creas dos ventanas con Tk() en el mismo programa? Investígalo y explica por qué no se recomienda.

3. Argumentos con valores por defecto

(Fuente: 206 - Setting Default Values for Optional Arguments inside a Function Header.md)

🎯 Objetivo de aprendizaje

  • Definir funciones con argumentos opcionales.
  • Diferenciar un argumento obligatorio de uno opcional.

📖 Explicación conceptual

Recuerda (Día 8): con argumentos de palabra clave puedes pasar los valores en cualquier orden:

Python
def my_function(a, b, c):
    ...

my_function(c=3, a=1, b=2)

El problema: si 9 de cada 10 veces usas a=1, b=2 y c=3, escribirlos siempre es un esfuerzo inútil.

La solución: valores por defecto en la definición.

Python
def my_function(a=1, b=2, c=3):
    ...

my_function()        # usa a=1, b=2, c=3
my_function(b=5)     # usa a=1, b=5, c=3

Ya lo usabas sin saberlo. tim.write() de Turtle tiene esta forma: write(arg, move=False, align="left", font=(...)).

  • arg no tiene =: es obligatorio. Si llamas a tim.write() vacío, obtienes un error que dice que falta un argumento posicional requerido.
  • move, align y font sí tienen =: son opcionales. Solo los escribes si quieres cambiarlos, por ejemplo font=("Times New Roman", 80, "bold").

En las ayudas de PyCharm, los opcionales aparecen con = ....

⚠️ Errores frecuentes

Error Qué ocurre Solución
def f(a=1, b): SyntaxError: un parámetro sin valor por defecto no puede ir después de uno con valor Los obligatorios van primero
Creer que el valor por defecto se "guarda" si lo cambias en una llamada La siguiente llamada vuelve a usar el valor por defecto Cada llamada es independiente

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: crea saludar(nombre, saludo="Hola") y llámala de dos formas.
  • 🟡 Reto 2: crea calcular_nota_final(nota, bonificacion=0, nota_maxima=5.0) que no deje pasar la nota de la nota máxima.
  • 🟠 Reto 3: crea precio_con_iva(precio, iva=0.19). Investiga en una fuente oficial si en Colombia hay productos con otras tarifas de IVA y pruébala con ellas.
  • 🔴 Reto 4: investiga por qué def agregar(item, lista=[]) es una trampa famosa de Python. Escribe un ejemplo que muestre el problema y la solución habitual (lista=None).

4. *args: muchos argumentos posicionales

(Fuente: 207 - -args- Many Positional Arguments.md)

🎯 Objetivo de aprendizaje

  • Crear funciones que acepten cualquier cantidad de argumentos.
  • Recorrer y usar esos argumentos dentro de la función.

📖 Explicación conceptual

add(n1, n2) solo suma dos números. ¿Y si quieres sumar 3, 4 o 7? Con un asterisco antes del nombre del parámetro, la función acepta cualquier número de argumentos:

Python
def add(*args):
    print(type(args))   # <class 'tuple'>  (verificado)
    ...
  • args es un nombre por convención; podrías llamarlo numbers. Lo indispensable es el *.
  • Dentro de la función, args es una tupla con todos los valores recibidos, en orden.
  • Por eso se llaman argumentos posicionales ilimitados: la posición importa. args[0] es el primero, args[1] el segundo…

📝 Ejercicio original del curso: sumar cualquier cantidad de números

Enunciado: en un archivo nuevo, playground.py, crea add(*args) que sume todos los números que reciba y devuelva el total.

🔍 Antes de programar

  • ¿Qué tipo de dato es args? ¿Cómo lo recorres?
  • ¿Con qué valor debe empezar la suma?
  • ¿Dónde va el return: dentro o fuera del bucle?

Resultado esperado: add(3, 5, 6) → 14; add(3, 5, 6, 2, 1) → 17 (verificado).

Solución del curso (en palabras): crear una variable sum en 0, recorrer for n in args, sumar cada n y devolver el total fuera del bucle.

🛠️ Nota técnica: el curso llama sum a la variable del total. Funciona, pero tapa la función integrada sum() de Python dentro de esa función. Mejor usa total. De hecho, la función completa podría ser return sum(args). Escribirla con el bucle tiene valor pedagógico; la versión corta muestra que Python ya trae esa herramienta.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: crea promedio(*notas).
  • 🟡 Reto 2: crea mayor(*numeros) sin usar max(). ¿Qué debería pasar si se llama sin argumentos?
  • 🟠 Reto 3: crea presentar(nombre, *hobbies) que imprima "Ana disfruta: leer, fútbol y ajedrez". Observa que nombre es obligatorio y los hobbies no.
  • 🔴 Reto 4: tienes notas = [4.0, 3.5, 5.0]. ¿Cómo llamas a promedio(*notas) para pasarle la lista? Investiga qué hace el * en la llamada (no en la definición) y explica la diferencia.

5. **kwargs: muchos argumentos de palabra clave

(Fuente: 208 - --kwargs- Many Keyword Arguments.md)

🎯 Objetivo de aprendizaje

  • Crear funciones y clases que acepten argumentos de palabra clave opcionales e ilimitados.
  • Entender por qué Tkinter muestra **kw en lugar de la lista de opciones.

📖 Explicación conceptual

Con dos asteriscos, la función recibe cualquier cantidad de argumentos con nombre, y los guarda en un diccionario:

Python
def calculate(n, **kwargs):
    print(kwargs)            # {'add': 3, 'multiply': 5}
    n += kwargs["add"]
    n *= kwargs["multiply"]
    print(n)

calculate(2, add=3, multiply=5)   # 25  (verificado)

Traza: n empieza en 2; se le suma 3 → 5; se multiplica por 5 → 25.

Como kwargs es un diccionario, puedes recorrerlo con for (key, value) in kwargs.items(): o leer una clave con kwargs["add"].

Una clase con **kw (ejemplo del curso):

Python
class Car:
    def __init__(self, **kw):
        self.make = kw.get("make")
        self.model = kw.get("model")

my_car = Car(make="Nissan", model="GT-R")
print(my_car.model)    # GT-R

my_car = Car(make="Nissan")
print(my_car.model)    # None

¿Por qué .get() y no corchetes? Con kw["model"], si no pasas model, el programa se detiene con KeyError: 'model'. Con kw.get("model"), simplemente obtienes None. Así los argumentos quedan realmente opcionales.

El misterio resuelto. Tkinter es una adaptación a Python de otra tecnología llamada Tk, que tiene una sintaxis muy distinta. Para traducirla, sus creadores convirtieron todas las opciones de Tk en **kw. Por eso Label( y pack( solo muestran **kw en las ayudas de PyCharm, y por eso hay que buscar las opciones en la documentación. Módulos pensados desde el principio para Python, como Turtle, sí muestran sus parámetros uno por uno.

⚠️ Errores frecuentes

Error Qué ocurre Solución
kwargs["model"] cuando no se pasó KeyError: 'model' kwargs.get("model")
Escribir mal la clave: Car(modle="GT-R") No hay error, pero model queda en None Con **kw no hay aviso de nombres mal escritos: revisa la ortografía
Olvidar los asteriscos: def f(kwargs): TypeError al llamar con argumentos de palabra clave **kwargs

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: agrega a Car los atributos opcionales color y seats.
  • 🟡 Reto 2: crea crear_perfil(nombre, **datos) que imprima el nombre y luego cada dato extra: grado: 10, ciudad: Pasto.
  • 🟠 Reto 3: modifica calculate para que las operaciones sean opcionales: si no pasas multiply, no multiplica. (Pista: .get() con un valor por defecto, como kwargs.get("multiply", 1).)
  • 🔴 Reto 4: diseña registrar_estudiante(nombre, *materias, **contacto). Llámala así: registrar_estudiante("Ana", "Física", "Arte", correo="ana@colegio.edu.co"). Explica qué recibe cada parámetro y en qué orden deben ir *args y **kwargs en la definición.

6. Botones, entradas y opciones de los widgets

(Fuente: 209 - Buttons, Entry, and Setting Component Options.md)

🎯 Objetivo de aprendizaje

  • Cambiar las opciones de un widget después de crearlo.
  • Conectar un botón con una función.
  • Leer el texto que escribe el usuario en una entrada.

📖 Explicación conceptual

Tres formas de cambiar una opción (según la documentación que muestra el curso):

Python
my_label = Label(text="I Am a Label")   # 1. al crear el widget
my_label["text"] = "New Text"           # 2. como si fuera un diccionario
my_label.config(text="New Text")        # 3. con el método config

El curso prefiere config porque "se lee con más sentido". La documentación de Tcl/Tk enumera todas las opciones: las estándar (comunes a todos los widgets, como el color de fondo) y las específicas de cada widget (como el alto y el ancho de una etiqueta).

from tkinter import *. En lugar de escribir tkinter.Label, tkinter.Button… puedes importar todas las clases y escribir Label, Button. El consejo del curso: si usas una sola clase, conserva el nombre del módulo para saber de dónde viene; si usas muchas, el asterisco ahorra escritura.

Botones (Button) y el argumento command.

Python
def button_clicked():
    print("I got clicked")

button = Button(text="Click Me", command=button_clicked)
button.pack()

¡Atención! command=button_clicked, sin paréntesis. Le entregas el nombre de la función para que Tkinter la llame cuando haya un clic. Es la misma idea de los event listeners de Turtle (Día 19).

📝 Ejercicio original del curso 1: el botón cambia la etiqueta

Enunciado: haz que al hacer clic en el botón, my_label diga "Button Got Clicked".

Solución del curso (en palabras): dentro de button_clicked, usar my_label.config(text="Button Got Clicked").

Entradas (Entry). Son cajas de texto de una línea:

Python
input = Entry(width=10)
input.pack()
print(input.get())   # imprime "" → en este momento el usuario aún no ha escrito nada
  • width define el ancho.
  • get() devuelve el texto como string.
  • Si llamas a get() justo después de crear la entrada, obtienes una cadena vacía, porque ese código se ejecuta antes de que el usuario escriba.

🛠️ Nota técnica: el curso llama input a la variable de la entrada. Eso tapa la función integrada input() en todo el archivo. En un programa gráfico no suele notarse, pero es una mala práctica. Usa nombres como user_entry o miles_input.

📝 Ejercicio original del curso 2: mostrar lo que escribió el usuario

Enunciado: al hacer clic en el botón, la etiqueta debe mostrar el texto que el usuario escribió en la entrada.

🔍 Antes de programar

  • ¿Cuándo debe ejecutarse get(): al crear la entrada o al hacer clic?
  • Entonces, ¿dentro de qué función debe ir?

⚠️ Errores frecuentes

Error Qué ocurre Solución
command=button_clicked() La función se ejecuta una vez al crear el botón y el clic no hace nada (verificado: el command queda vacío) Sin paréntesis
Llamar a get() fuera de la función del botón Siempre obtienes "" Llama a get() dentro de la función que se ejecuta con el clic
Label("Miles") sin text= AttributeError: 'str' object has no attribute 'tk' (verificado) El primer argumento posicional es la ventana "padre". Escribe text="Miles"

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: un botón que cuente cuántas veces se ha hecho clic y lo muestre en una etiqueta.
  • 🟡 Reto 2: una entrada para el nombre y un botón que muestre "¡Hola, <nombre>!". Si el nombre está vacío, debe mostrar "Escribe tu nombre".
  • 🟠 Reto 3: dos entradas con números y cuatro botones (+, −, ×, ÷). El resultado aparece en una etiqueta. ¿Qué pasa si divides por cero o escribes letras? Anota los errores; los resolveremos en el Día 30.
  • 🔴 Reto 4: crea un "adivina el número" gráfico: el programa elige un número del 1 al 100 y, en cada clic, la etiqueta dice "más alto", "más bajo" o "¡acertaste!" (retoma el Día 12).

7. Otros widgets: el "recetario"

(Fuente: 210 - Other Tkinter Widgets- Radiobuttons, Scales, Checkbuttons and more.md)

📖 Explicación conceptual

El curso ofrece una demo de widgets (un enlace en sus recursos, no incluido en la carpeta) para usar como recetario: no hace falta memorizar cada línea, sino saber buscar el ejemplo que necesitas y adaptarlo.

Widget Para qué sirve Detalles que menciona el curso
Entry Texto de una línea insert() pone un texto inicial, como una pista: "Email"
Text Texto de varias líneas height (líneas) y width; focus() pone el cursor ahí; get("1.0", END) lee todo desde la línea 1, carácter 0
Spinbox Un contador con flechas arriba y abajo Se conecta a una función con command
Scale Un control deslizante Su command recibe el valor actual como argumento
Checkbutton Casilla de activar o desactivar Se enlaza a una IntVar(): 1 si está marcada, 0 si no
Radiobutton Elegir una opción entre varias Todos comparten la misma IntVar; cada uno tiene su propio value
Listbox Lista de opciones Se llena con un bucle; se enlaza con bind("<<ListboxSelect>>", función)

Sobre IntVar: es una clase de Tkinter. Crea un objeto que "vigila" el valor de un widget. Lo lees con .get().

Sobre END y "1.0": son índices que usa Tk para ubicar posiciones dentro del texto. El curso dice que puedes copiarlos tal cual; no tienes que cambiarlos.

🛠️ Nota técnica: en la transcripción de la lección, los dos Radiobutton del ejemplo aparecen con "valor 2" y "valor 2". Para que funcionen como opciones distintas, cada uno debe tener un value diferente (por ejemplo, 1 y 2). Si comparten valor, al marcar uno se marcan los dos.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: una Scale de 0 a 100 cuyo valor aparezca en una etiqueta mientras la mueves.
  • 🟡 Reto 2: tres Radiobutton para elegir la jornada (mañana, tarde, única) y un botón que muestre la elegida.
  • 🟠 Reto 3: una encuesta con una Listbox de deportes, un Checkbutton "Quiero recibir información" y un botón "Enviar" que muestre un resumen.
  • 🔴 Reto 4: busca en la documentación oficial (tkinter (se abre en otra pestaña)) un widget que no esté en la tabla y crea un ejemplo mínimo con él.

8. Gestores de diseño: pack, place y grid

(Fuente: 211 - Tkinter Layout Managers- pack(), place() and grid().md)

🎯 Objetivo de aprendizaje

  • Comparar los tres gestores de diseño de Tkinter.
  • Ubicar widgets en una cuadrícula y agregar relleno.

📖 Explicación conceptual

Regla del curso: si un widget no tiene gestor de diseño, no se muestra.

Gestor Cómo funciona Ventaja Desventaja
pack() Pone los widgets uno tras otro, de arriba abajo (o según side) Muy fácil Es difícil lograr posiciones precisas
place(x=, y=) Coordenadas exactas en píxeles; (0, 0) es la esquina superior izquierda Precisión total Con muchos widgets, calcular coordenadas se vuelve una pesadilla
grid(column=, row=) Divide la ventana en filas y columnas, empezando en 0 Flexible y fácil de imaginar Hay que planear la cuadrícula

grid es el favorito del curso. Recuerda: las filas van en horizontal y las columnas en vertical.

grid es relativo. Si pones un widget en column=5, row=5 y no hay nada en las columnas y filas anteriores, igual queda en la esquina superior izquierda: las filas y columnas vacías no ocupan espacio.

⚠️ No se pueden mezclar grid y pack en la misma ventana. Verificado:

Texto
_tkinter.TclError: cannot use geometry manager pack inside . which already has slaves managed by grid

📝 Ejercicio original del curso: agregar un botón a la cuadrícula

Situación inicial: etiqueta en (columna 0, fila 0), botón en (1, 1) y entrada en (2, 2), en diagonal.

Enunciado: agrega un nuevo botón en la esquina superior derecha, a la derecha del primer botón y encima de la entrada, según la imagen del curso.

Lo que descubre el curso: al poner el nuevo botón en (columna 2, fila 0), la entrada queda debajo de él, en la misma columna. Para que quede a la derecha, hay que mover la entrada a la columna 3.

Lección: en grid, agregar un widget puede obligarte a mover otros. Planea tu cuadrícula en papel antes de programar.

Relleno (padding).

Python
window.config(padx=20, pady=20)      # espacio alrededor de TODA la ventana
my_label.config(padx=50, pady=50)    # espacio alrededor de UN widget

padx es el relleno horizontal (eje X) y pady el vertical (eje Y).

⚠️ Errores frecuentes

Error Qué ocurre Solución
Mezclar grid y pack TclError (ver arriba) Elige un solo gestor por ventana
Pensar que column=5 deja espacio vacío El widget queda "pegado" a los demás Las columnas vacías no ocupan espacio
Confundir filas y columnas Los widgets quedan donde no esperabas Filas = horizontal (row); columnas = vertical (column)

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: con grid, arma un teclado numérico de 3 × 3 botones (del 1 al 9).
  • 🟡 Reto 2: agrega el 0 debajo del 5 y un botón "Borrar" debajo del 7. Dibuja primero la cuadrícula en papel.
  • 🟠 Reto 3: haz que al pulsar un número se agregue a una etiqueta, como en un cajero automático, y que "Borrar" la deje vacía.
  • 🔴 Reto 4: reproduce con place el mismo teclado. Compara cuántas líneas escribiste y qué tan fácil sería agregar una fila más en cada versión. Escribe tu conclusión.

9. 🚀 Proyecto del día: el conversor de millas a kilómetros

(Fuente: 212 - Mile to Kilometers Converter Project.md)

Problema

Construir un programa gráfico en el que el usuario escribe una cantidad de millas, pulsa Calculate y ve el equivalente en kilómetros.

Contexto

Angela lo usa porque corre en millas y quiere saber cuántos kilómetros son. En Colombia usamos kilómetros, pero muchas aplicaciones y marcas de ropa deportiva, así como las series de EE. UU., usan millas.

Requisitos funcionales

  1. Una entrada para escribir las millas.
  2. Etiquetas: Miles, is equal to, el resultado (empieza en 0) y Km.
  3. Un botón Calculate que haga la conversión.
  4. Distribución en una cuadrícula de 3 columnas × 3 filas.

Restricciones técnicas

  • from tkinter import *, grid y config.
  • La conversión se hace en una función conectada con command.

Planificación

  1. Entrada: el texto de la entrada (¡es un string!).
  2. Proceso: convertir a número y multiplicar por 1.609, el factor que el curso toma de una búsqueda en Google.
  3. Salida: el texto de la etiqueta del resultado.
  4. Diseño: dibuja la cuadrícula antes de programar.

Construcción paso a paso

Versión 1 — La ventana vacía. En un archivo nuevo, mile_to_kilo_converter.py:

Python
from tkinter import *

window = Tk()
window.title("Mile to Km Converter")

window.mainloop()

Versión 2 — Crear los widgets.

Python
miles_input = Entry()
miles_label = Label(text="Miles")
is_equal_label = Label(text="is equal to")
kilometer_result_label = Label(text="0")
kilometer_label = Label(text="Km")
calculate_button = Button(text="Calculate")

Si ejecutas ahora, no aparece nada: ningún widget tiene gestor de diseño.

Error muy común que advierte el curso: si escribes Label("Miles") sin text=, obtienes AttributeError: 'str' object has no attribute 'tk'. El mensaje confunde, pero la causa es que falta el nombre del argumento.

Versión 3 — La cuadrícula. El curso indica las dos primeras posiciones:

Widget Columna Fila
miles_input 1 0
miles_label 2 0
is_equal_label ? ?
kilometer_result_label ? ?
kilometer_label ? ?
calculate_button ? ?

🧩 Tu turno: completa la tabla para que el resultado se lea como una frase: "[entrada] Miles / is equal to [resultado] Km / [Calculate]". El botón va centrado debajo.

Ajustes del curso: la entrada es demasiado ancha, así que se reduce con width=7, y la ventana se rellena con window.config(padx=20, pady=20).

Versión final — La función de conversión.

Python
def miles_to_km():
    miles = float(miles_input.get())
    km = miles * 1.609
    kilometer_result_label.config(text=f"{km}")

Conéctala al botón con command=miles_to_km (sin paréntesis).

Cuidado con los tipos, como explica el curso:

  • get() devuelve un string → se convierte con float() porque el usuario puede escribir decimales.
  • config(text=...) necesita texto → se usa un f-string.

Puedes redondear el resultado con round(km): se ve más limpio, pero pierde precisión. Tú decides cuántos decimales mostrar.

Pruebas

Entrada Resultado esperado
10 16.09 (el curso lo muestra redondeado como 16)
20 32.18
0.5 0.8045
(vacío) ⚠️ (ver 🧪)
diez ⚠️ (ver 🧪)

🧪 Prueba y depuración

  1. Entrada vacía o con letras. float("") y float("diez") producen ValueError: could not convert string to float (verificado). El error aparece en la consola y la etiqueta no cambia: el usuario no sabe qué pasó. Por ahora, antes de convertir, revisa si la entrada está vacía y muestra un mensaje en la etiqueta del resultado. En el Día 30 aprenderás a manejar este error con try/except.
  2. Decimales con coma. En Colombia escribimos 2,5, pero float("2,5") también da ValueError. ¿Cómo lo resolverías? (Pista: replace.)
  3. Demasiados decimales. 0.1 * 3 no da exactamente 0.3 en Python. Pruébalo y usa round(km, 2) para mostrar el resultado.

⭐ Extensión del proyecto

  • Agregar Radiobutton para elegir la conversión: millas → km o km → millas.
  • Convertir mientras se escribe, sin botón. (Investiga el evento <KeyRelease> y el método bind.)
  • Un conversor múltiple: litros ↔ galones, °C ↔ °F, libras ↔ kilogramos, usando una Listbox o un menú.

10. 🌟 Proyecto extra: la calculadora de huella de carbono del transporte escolar

Situación

El comité ambiental del colegio quiere que cada estudiante estime cuánto CO₂ emite en una semana según cómo llega al colegio, y que vea cuánto ahorraría si cambiara de medio de transporte.

Requisitos

  1. Investiga los datos. Busca en una fuente confiable los factores de emisión aproximados (kg de CO₂ por km por pasajero) de: carro particular, moto, bus o buseta, y bicicleta o a pie. Guárdalos en un diccionario y cita la fuente en un comentario. Si encuentras valores distintos en fuentes distintas, elige uno y justifica por qué.
  2. Interfaz:
    • Radiobutton para el medio de transporte.
    • Entry para la distancia (km de ida).
    • Spinbox para los días a la semana (1 a 6).
    • Un botón Calcular.
    • Una etiqueta con el resultado semanal (ida y vuelta).
  3. Comparación: un Checkbutton "Comparar con bicicleta" que, si está marcado, muestre también cuánto CO₂ se ahorraría.
  4. Diseño con grid, relleno en la ventana y colores del colegio.
  5. Función de cálculo con valores por defecto, por ejemplo calcular_emision(km, dias, factor, ida_y_vuelta=True).

Restricciones técnicas

  • Solo grid (nada de pack).
  • Al menos un widget de la sección 7 además de Label, Entry y Button.
  • Al menos una función con parámetros por defecto.

Casos de prueba (con tus factores)

Caso Esperado
Bicicleta, 3 km, 5 días 0 kg de CO₂
Carro, 10 km, 5 días 10 × 2 × 5 × factor del carro
Distancia vacía Mensaje en la etiqueta, sin error en la consola
Cambiar de medio y volver a calcular El resultado se actualiza

Reflexión

Los factores de emisión son estimaciones. ¿De qué dependen? (Tipo de vehículo, ocupación, combustible…) ¿Qué tan confiable es tu resultado? Escribe un párrafo para el comité ambiental que explique sus limitaciones.


11. 🧩 Comprueba lo aprendido

Conceptuales

  1. ¿Qué hace mainloop() y por qué va al final?
  2. ¿En qué se diferencian *args y **kwargs? ¿Qué tipo de dato es cada uno dentro de la función?
  3. ¿Por qué PyCharm muestra **kw cuando escribes Label(?

Análisis de código

Python
def pedido(plato, *adiciones, bebida="agua", **notas):
    print(plato, adiciones, bebida, notas)

pedido("arepa", "queso", "hogao", bebida="avena", picante="poco")

¿Qué recibe cada parámetro? Escribe lo que imprime.

Predicción

Python
def f(a, b=2, *args, **kwargs):
    return a + b + len(args) + len(kwargs)

print(f(1))
print(f(1, 1))
print(f(1, 1, 1, 1, x=1))

Depuración

Este programa debería mostrar en la etiqueta lo que escribe el usuario. Encuentra cuatro problemas.

Python
from tkinter import *

window = Tk()
entrada = Entry(width=10)
entrada.grid(column=0, row=0)
etiqueta = Label("Aquí aparecerá el texto")
etiqueta.pack()

def mostrar():
    etiqueta.config(text=entrada.get())

boton = Button(text="Mostrar", command=mostrar())
boton.grid(column=0, row=2)

Modificación

Modifica el conversor de millas para que también muestre el resultado en metros, en una etiqueta nueva ubicada en la fila 3.

Creación

Crea una ventana "Semáforo": tres botones (Rojo, Amarillo, Verde) que cambien el color de fondo de una etiqueta grande y su texto ("Pare", "Precaución", "Siga").


12. 🤖 Programar con apoyo de IA

El curso lo dice sin rodeos: Tkinter no está muy bien documentado. Eso hace tentador pedirle todo a una IA. Pero las IA a veces inventan opciones de widgets que no existen o mezclan grid y pack sin darse cuenta.

Actividad: auditar código generado.

  1. Pídele a una IA: "Haz en Tkinter un formulario de inscripción al club de robótica con nombre, grado, jornada y un botón Enviar".
  2. Antes de ejecutarlo, revisa:
    • ¿Usa un solo gestor de diseño por ventana?
    • ¿Los command van sin paréntesis?
    • ¿Usa nombres que tapan funciones integradas (input, sum, list)?
  3. Ejecútalo. Si hay errores, copia el mensaje y búscalo en la documentación de Python o en la documentación de Tcl/Tk antes de volver a preguntarle a la IA.
  4. Elige dos opciones de widgets que haya usado (por ejemplo, relief o sticky) y verifica en la documentación oficial de tkinter (se abre en otra pestaña) que existan y hagan lo que la IA dice.
  5. Escribe qué cambiaste y por qué.

13. 🛠️ Correcciones técnicas al material original

Archivo Afirmación o comportamiento del curso Observación Tratamiento
207 La variable del total se llama sum Tapa la función integrada sum(); total es mejor. La función completa equivale a return sum(args) (verificado) Nota técnica del Tema 4
208 "Entonces 5 más 5, y obtenemos 25" Es 5 por 5 = 25 (verificado); probablemente un error de transcripción Traza del Tema 5
209 La entrada se guarda en una variable llamada input Tapa la función integrada input() Nota técnica del Tema 6
209 command= debe recibir la función sin paréntesis Correcto; con paréntesis, el command queda vacío (verificado) Errores frecuentes del Tema 6
210 Los dos Radiobutton del ejemplo aparecen con "valor 2" Deben tener valores distintos para funcionar como opciones separadas Nota técnica del Tema 7
210 Demo de widgets enlazada en los recursos No está en la carpeta Tabla construida desde la explicación de la lección
211 No se pueden mezclar grid y pack Correcto; el mensaje exacto es TclError: cannot use geometry manager pack inside . which already has slaves managed by grid (verificado) Tema 8
212 Olvidar text= produce "el objeto String no tiene el atributo tk" Correcto: AttributeError: 'str' object has no attribute 'tk' (verificado) Versión 2 del proyecto
212 La conversión con float(miles_input.get()) Con la entrada vacía o con letras produce ValueError (verificado) 🧪 del proyecto; se retoma en el Día 30

14. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Objetivos: Tkinter, argumentos, conversor 203 Sección 0 — — Sección 9
Historia de las GUI, Xerox PARC, Apple y Microsoft 204 Tema 1 — Discusión sobre propiedad intelectual —
Tk, title, minsize, mainloop, Label, font, pack 205 Tema 2 Primera ventana y etiqueta Retos 1–4 del Tema 2 Secciones 9 y 10
Argumentos con valores por defecto 206 Tema 3 Ejemplo de tim.write Retos 1–4 del Tema 3 Sección 10
*args 207 Tema 4 add(*args) Retos 1–4 del Tema 4 —
**kwargs, .get(), clase Car, origen de Tkinter en Tk 208 Tema 5 calculate, Car Retos 1–4 del Tema 5 —
config, Button, command, Entry, get 209 Tema 6 Botón que cambia la etiqueta; mostrar lo escrito Retos 1–4 del Tema 6 Secciones 9 y 10
Text, Spinbox, Scale, Checkbutton, Radiobutton, Listbox 210 Tema 7 Explorar la demo Retos 1–4 del Tema 7 Sección 10
pack, place, grid, padx/pady 211 Tema 8 Nuevo botón en la cuadrícula Retos 1–4 del Tema 8 Secciones 9 y 10
Conversor de millas a kilómetros 212 Sección 9 El proyecto completo Depuración y extensiones —
(No existe en el curso original) — — — Huella de carbono del transporte escolar Sección 10

15. 📦 Evidencias de aprendizaje del Día 27

  • playground.py con add(*args), calculate(**kwargs) y la clase Car, con comentarios que expliquen cada uno.
  • Captura del ejercicio de la cuadrícula con el botón nuevo en su lugar.
  • El conversor de millas a kilómetros funcionando, con la tabla de la cuadrícula completa y las pruebas.
  • La calculadora de huella de carbono (proyecto extra), con la fuente de los factores de emisión citada.
  • Cuaderno de errores: mainloop olvidado, widget sin gestor de diseño, command con paréntesis, 'str' object has no attribute 'tk', KeyError en **kwargs, TclError por mezclar grid y pack, y ValueError al convertir una entrada vacía.