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
210no está en la carpeta; esa sección se construyó a partir de la explicación de la lección.
Tabla de contenidos
- Antes de empezar
- Un poco de historia: ¿qué es una GUI?
- Ventanas y etiquetas con Tkinter
- Argumentos con valores por defecto
*args: muchos argumentos posicionales**kwargs: muchos argumentos de palabra clave- Botones, entradas y opciones de los widgets
- Otros widgets: el "recetario"
- Gestores de diseño:
pack,placeygrid - 🚀 Proyecto del día: el conversor de millas a kilómetros
- 🌟 Proyecto extra: la calculadora de huella de carbono del transporte escolar
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 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 confloat()(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:
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.
minsizedefine un tamaño mínimo. La ventana crece si le pones muchos componentes.
2. La etiqueta (Label).
my_label = tkinter.Label(text="I Am a Label", font=("Arial", 24, "bold"))
my_label.pack()textes el texto que muestra.fontes 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 conside="bottom". Dibuja en tu cuaderno cómo quedan en cada caso. - 🟠 Reto 3: investiga en la documentación las opciones
fgybgdeLabel, 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:
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.
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=3Ya lo usabas sin saberlo. tim.write() de Turtle tiene esta forma: write(arg, move=False, align="left", font=(...)).
argno tiene=: es obligatorio. Si llamas atim.write()vacío, obtienes un error que dice que falta un argumento posicional requerido.move,alignyfontsí tienen=: son opcionales. Solo los escribes si quieres cambiarlos, por ejemplofont=("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:
def add(*args):
print(type(args)) # <class 'tuple'> (verificado)
...argses un nombre por convención; podrías llamarlonumbers. Lo indispensable es el*.- Dentro de la función,
argses 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
suma la variable del total. Funciona, pero tapa la función integradasum()de Python dentro de esa función. Mejor usatotal. De hecho, la función completa podría serreturn 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 usarmax(). ¿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 quenombrees obligatorio y los hobbies no. - 🔴 Reto 4: tienes
notas = [4.0, 3.5, 5.0]. ¿Cómo llamas apromedio(*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
**kwen 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:
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):
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
Carlos atributos opcionalescoloryseats. - 🟡 Reto 2: crea
crear_perfil(nombre, **datos)que imprima el nombre y luego cada dato extra:grado: 10,ciudad: Pasto. - 🟠 Reto 3: modifica
calculatepara que las operaciones sean opcionales: si no pasasmultiply, no multiplica. (Pista:.get()con un valor por defecto, comokwargs.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*argsy**kwargsen 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):
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 configEl 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.
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:
input = Entry(width=10)
input.pack()
print(input.get()) # imprime "" → en este momento el usuario aún no ha escrito nadawidthdefine 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
inputa la variable de la entrada. Eso tapa la función integradainput()en todo el archivo. En un programa gráfico no suele notarse, pero es una mala práctica. Usa nombres comouser_entryomiles_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
Radiobuttondel ejemplo aparecen con "valor 2" y "valor 2". Para que funcionen como opciones distintas, cada uno debe tener unvaluediferente (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
Scalede 0 a 100 cuyo valor aparezca en una etiqueta mientras la mueves. - 🟡 Reto 2: tres
Radiobuttonpara elegir la jornada (mañana, tarde, única) y un botón que muestre la elegida. - 🟠 Reto 3: una encuesta con una
Listboxde deportes, unCheckbutton"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:
_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).
window.config(padx=20, pady=20) # espacio alrededor de TODA la ventana
my_label.config(padx=50, pady=50) # espacio alrededor de UN widgetpadx 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
placeel 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
- Una entrada para escribir las millas.
- Etiquetas:
Miles,is equal to, el resultado (empieza en0) yKm. - Un botón
Calculateque haga la conversión. - Distribución en una cuadrícula de 3 columnas × 3 filas.
Restricciones técnicas
from tkinter import *,gridyconfig.- La conversión se hace en una función conectada con
command.
Planificación
- Entrada: el texto de la entrada (¡es un string!).
- Proceso: convertir a número y multiplicar por 1.609, el factor que el curso toma de una búsqueda en Google.
- Salida: el texto de la etiqueta del resultado.
- 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:
from tkinter import *
window = Tk()
window.title("Mile to Km Converter")
window.mainloop()Versión 2 — Crear los widgets.
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")sintext=, obtienesAttributeError: '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.
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 confloat()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
- Entrada vacía o con letras.
float("")yfloat("diez")producenValueError: 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 contry/except. - Decimales con coma. En Colombia escribimos
2,5, perofloat("2,5")también daValueError. ¿Cómo lo resolverías? (Pista:replace.) - Demasiados decimales.
0.1 * 3no da exactamente0.3en Python. Pruébalo y usaround(km, 2)para mostrar el resultado.
⭐ Extensión del proyecto
- Agregar
Radiobuttonpara elegir la conversión: millas → km o km → millas. - Convertir mientras se escribe, sin botón. (Investiga el evento
<KeyRelease>y el métodobind.) - Un conversor múltiple: litros ↔ galones, °C ↔ °F, libras ↔ kilogramos, usando una
Listboxo 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
- 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é.
- Interfaz:
Radiobuttonpara el medio de transporte.Entrypara la distancia (km de ida).Spinboxpara los días a la semana (1 a 6).- Un botón Calcular.
- Una etiqueta con el resultado semanal (ida y vuelta).
- Comparación: un
Checkbutton"Comparar con bicicleta" que, si está marcado, muestre también cuánto CO₂ se ahorraría. - Diseño con
grid, relleno en la ventana y colores del colegio. - 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 depack). - Al menos un widget de la sección 7 además de
Label,EntryyButton. - 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
- ¿Qué hace
mainloop()y por qué va al final? - ¿En qué se diferencian
*argsy**kwargs? ¿Qué tipo de dato es cada uno dentro de la función? - ¿Por qué PyCharm muestra
**kwcuando escribesLabel(?
Análisis de código
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
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.
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.
- 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".
- Antes de ejecutarlo, revisa:
- ¿Usa un solo gestor de diseño por ventana?
- ¿Los
commandvan sin paréntesis? - ¿Usa nombres que tapan funciones integradas (
input,sum,list)?
- 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.
- Elige dos opciones de widgets que haya usado (por ejemplo,
reliefosticky) y verifica en la documentación oficial detkinter(se abre en otra pestaña) que existan y hagan lo que la IA dice. - 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.pyconadd(*args),calculate(**kwargs)y la claseCar, 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:
mainloopolvidado, widget sin gestor de diseño,commandcon paréntesis,'str' object has no attribute 'tk',KeyErroren**kwargs,TclErrorpor mezclargridypack, yValueErroral convertir una entrada vacía.