Este tutorial adapta pedagógicamente el Día 29. 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 archivos iniciales (
logo.png,main.py) y el código del generador de contraseñas que enlaza el curso no están en la carpeta; este tutorial los reconstruye a partir de lo que explican las lecciones. La libreríapyperclipno viene con Python y no estaba instalada en el equipo de verificación, así que esa parte se revisó con la documentación y con una alternativa de Tkinter que sí se verificó.
🔐 Advertencia de seguridad para el aula
El programa de hoy guarda las contraseñas en un archivo de texto sin cifrar. Cualquier persona que abra la carpeta puede leerlas. Es un excelente proyecto para aprender, pero no para guardar contraseñas reales.
En clase, usa únicamente sitios, correos y contraseñas inventados (por ejemplo,
sitio-prueba.comyestudiante@ejemplo.com). Nunca escribas tus contraseñas reales, las de tu familia ni las del colegio. En la sección 7 analizamos por qué y qué hacen los gestores de contraseñas profesionales.
Tabla de contenidos
- Antes de empezar
- Desafío 1: la ventana y el logo
- Desafío 2: la cuadrícula y
columnspan - Desafío 3: guardar los datos en un archivo
- Cuadros de diálogo y ventanas emergentes
- Generar la contraseña y copiarla al portapapeles
- 🚀 Proyecto del día: MyPass, el gestor de contraseñas
- 🔐 ¿Qué tan seguro es MyPass?
- 🌟 Proyecto extra: el registro de préstamos de la biblioteca escolar
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 Evidencias de aprendizaje del Día 29
0. Antes de empezar
(Fuente: 220 - Day 29 Goals- what we will make by the end of the day.md)
La inspiración del proyecto. Angela intentaba registrarse en un sitio web que exigía una contraseña larguísima, con muchos tipos de caracteres y distinta a las anteriores. Cuando por fin encontró una que cumplía todas las reglas… ya la había olvidado.
El curso cita el blog Coding Horror de Jeff Atwood (cofundador de Stack Overflow), que critica estas reglas, y un cómic de XKCD que resume el problema: "Tras 20 años de esfuerzo, hemos entrenado a todo el mundo para usar contraseñas difíciles de recordar para los humanos, pero fáciles de adivinar para los computadores".
Lo que construiremos: MyPass, una aplicación que:
- Guarda el sitio web, el correo o usuario y la contraseña en un archivo local (no en internet).
- Trae el correo ya escrito, porque la mayoría usamos casi siempre el mismo.
- Genera contraseñas largas y complejas con un botón y las copia al portapapeles para pegarlas de inmediato.
- Valida que no haya campos vacíos y pide confirmación antes de guardar.
Prerequisito general: el generador de contraseñas (Día 5), escribir en archivos con
openywith(Día 24), comprensiones de listas (Día 26) y todo lo de Tkinter de los Días 27 y 28 (Canvas,PhotoImage,grid,Entry,Button,command).
1. Desafío 1: la ventana y el logo
(Fuente: 221 - Challenge 1 - Working with Images and Setting up the Canvas.md)
📝 Ejercicio original del curso
Enunciado: crea una ventana con el título "Password Manager" que tenga solo un lienzo de 200 × 200 píxeles con la imagen logo.png en el centro, y un relleno de 20 píxeles entre el lienzo y el borde de la ventana en los cuatro lados.
Pista del curso: repasa el Canvas del Día 28 y la documentación de create_image (enlazada en los recursos).
🔍 Antes de programar
- Si el lienzo mide 200 × 200, ¿cuáles son las coordenadas del centro?
- ¿Qué clase necesitas para leer el archivo de la imagen?
- ¿El relleno se configura en el lienzo o en la ventana?
Solución del curso (en palabras): from tkinter import *; ventana con title; Canvas(height=200, width=200); logo_img = PhotoImage(file="logo.png"); canvas.create_image(100, 100, image=logo_img); canvas.pack() por ahora; window.config(padx=20, pady=20); y mainloop() al final.
El error que muestra el curso: si olvidas las coordenadas, obtienes IndexError: tuple index out of range (verificado en el Día 28). Tkinter esperaba una tupla con x e y.
2. Desafío 2: la cuadrícula y columnspan
(Fuentes: 222 - Challenge 2 - Use grid() and columnspan to Complete the User Interface.md, 223 - Solution to the Creating the Grid Layout.md)
🎯 Objetivo de aprendizaje
- Hacer que un widget ocupe varias columnas con
columnspan. - Diferenciar las opciones del widget de las opciones de
grid.
📖 Explicación conceptual
El problema. En el diseño final, la entrada del sitio web ocupa todo el ancho, pero la de la contraseña ocupa solo una parte, porque a su lado va el botón "Generate Password". Es decir: algunas entradas ocupan dos columnas.
El experimento del curso (una aplicación "sandbox").
- Una etiqueta roja en (fila 0, columna 0) y una verde en (fila 1, columna 1), ambas de ancho 20 y alto 5: forman un "tablero de ajedrez".
- Se agrega una etiqueta azul en (fila 2, columna 0). ¿Cómo hacer que abarque el ancho de la roja y de la verde?
- Primer intento: duplicar su ancho a 40. ❌ La azul sigue en la columna 0; lo único que logra es ensanchar esa columna y la roja queda centrada en ella.
- Solución:
columnspan=2, que indica cuántas columnas abarca el widget. Empieza en la columna 0 y ocupa la 0 y la 1. ✅
b.grid(row=2, column=0, columnspan=2)📝 Ejercicio original del curso: completar la interfaz
Enunciado: recrea la interfaz de MyPass: 3 columnas × 5 filas, con etiquetas, entradas y botones. El curso ya calculó los anchos "casi óptimos".
🔍 Antes de programar
Completa la cuadrícula en tu cuaderno antes de ver la solución:
| Fila | Columna 0 | Columna 1 | Columna 2 |
|---|---|---|---|
| 0 | Logo (lienzo) | ||
| 1 | "Website:" | Entrada del sitio web (¿cuántas columnas?) | |
| 2 | "Email/Username:" | Entrada del correo (¿cuántas columnas?) | |
| 3 | "Password:" | Entrada de la contraseña | Botón "Generate Password" |
| 4 | Botón "Add" (¿cuántas columnas?) |
Anchos que da el curso: entradas del sitio web y del correo, width=35; entrada de la contraseña, width=21; botón Add, width=36.
Solución del curso (resumen):
- Crear las tres etiquetas y ubicarlas en la columna 0, filas 1, 2 y 3.
- Cambiar el
pack()del lienzo porgrid(row=0, column=1): no se pueden mezclar gestores. - Crear las entradas en la columna 1. La del sitio web y la del correo, con
columnspan=2. - El botón Generate en (fila 3, columna 2); el botón Add en (fila 4, columna 1) con
columnspan=2ywidth=36. - Detalles finales: mayúscula en "Password" y dos puntos al final de cada etiqueta. Si quieres más aire, sube el relleno de la ventana a 50.
Observación del curso: la alineación no queda perfecta porque el botón Generate solo ocupa el ancho de su texto. Puedes ajustar los anchos por prueba y error.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
entrada.grid(column=1, row=0, width=10) |
TclError: bad option "-width": must be -column, -columnspan, -in, -ipadx, -ipady, -padx, -pady, -row, -rowspan, or -sticky (verificado) |
width es una opción del widget: Entry(width=10). El curso lo señala como un error muy común |
| Ensanchar un widget para que "cruce" columnas | Solo se ensancha su columna | Usa columnspan |
Dejar el pack() del lienzo al pasar a grid |
TclError por mezclar gestores |
Todo con grid |
Crear el botón Add como Radiobutton por error |
Aparece un círculo de selección | Button (el curso comete y corrige este error en vivo) |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: con el sandbox de colores, haz una etiqueta que ocupe dos filas (investiga
rowspan). - 🟡 Reto 2: investiga la opción
sticky="EW"degridy úsala para que todas las entradas se estiren hasta el borde de su celda. ¿Sigue siendo necesario ajustar los anchos a mano? - 🟠 Reto 3: diseña en papel y luego en Tkinter el formulario de inscripción a un torneo intercursos: nombre del equipo, grado, capitán, deporte (
Radiobutton) y un botón que ocupe todo el ancho. - 🔴 Reto 4: toma una captura de la pantalla de inicio de sesión de una aplicación que uses. Dibuja su cuadrícula (filas, columnas y
columnspan) y recréala con Tkinter.
3. Desafío 3: guardar los datos en un archivo
(Fuente: 224 - Challenge 3 - Saving Data to File.md)
🎯 Objetivo de aprendizaje
- Poner el cursor y un texto inicial en una entrada.
- Guardar lo escrito en un archivo sin borrar lo anterior.
- Borrar el contenido de una entrada.
📖 Explicación conceptual
Detalles de usabilidad que agrega el curso:
website_entry.focus() # el cursor empieza en el sitio web
email_entry.insert(0, "estudiante@ejemplo.com") # correo ya escrito (¡ficticio!)focus()pone el cursor en esa entrada al abrir la aplicación.insert(indice, texto)inserta texto en una posición.0= al principio;END= al final, después del texto que ya exista.ENDes una constante de Tkinter.
Borrar el contenido de una entrada:
website_entry.delete(0, END) # borra desde el carácter 0 hasta el finalSegún la documentación, si omites el segundo índice, delete borra un solo carácter.
📝 Ejercicio original del curso
Enunciado: al pulsar Add, guarda el sitio web, el correo y la contraseña en data.txt, en una línea con este formato (o el que te resulte cómodo):
Amazon | estudiante@ejemplo.com | Clave-De-Prueba-123Cada registro nuevo va en una línea nueva, al final del archivo. Después de guardar, borra el sitio web y la contraseña (pero no el correo).
🔍 Antes de programar
- ¿Con qué modo abres el archivo para agregar al final sin borrar lo anterior? (Repasa:
"r","w","a".) - ¿Qué pasa si el archivo no existe todavía?
- ¿Cómo obtienes el texto de cada entrada?
- ¿Qué carácter hace falta para que cada registro quede en su propia línea?
Solución del curso (en palabras):
- Conectar el botón Add con
command=save. - En
save(), obtener los tres textos con.get(). with open("data.txt", "a") as data_file:ydata_file.write(f"{website} | {email} | {password}\n").- Borrar el sitio web y la contraseña con
delete(0, END).
Recordatorios del curso:
- En modo
"a"o"w", si el archivo no existe, se crea. withcierra el archivo automáticamente; sinwith, tendrías que llamar aclose().- En la primera prueba, los registros quedaron pegados en la misma línea: faltaba el
\n.
💬 Comentario del curso sobre las contraseñas de ejemplo: Angela usa contraseñas malas a propósito ("qwerty", "password") y dice: "Si adiviné alguna de sus contraseñas, cámbienla, por su propia seguridad".
4. Cuadros de diálogo y ventanas emergentes
(Fuente: 225 - Dialog Boxes and Pop-Ups in Tkinter.md)
🎯 Objetivo de aprendizaje
- Mostrar mensajes y pedir confirmaciones con
messagebox. - Validar que el usuario no deje campos vacíos.
📖 Explicación conceptual
El problema de experiencia de usuario: al pulsar Add, los campos se borran, pero el usuario no sabe si se guardó ni tuvo la oportunidad de revisar los datos.
Los diálogos estándar. Tkinter trae el módulo messagebox:
from tkinter import messagebox¿Por qué hay que importarlo aparte si ya escribí
from tkinter import *? Porque*importa las clases y constantes detkinter, peromessageboxes otro módulo dentro del paquete. El curso lo muestra con clic derecho → Go To → Implementation, que abre el archivomessagebox.py.
| Función | Botones | Devuelve |
|---|---|---|
showinfo, showwarning, showerror |
Aceptar | — |
askokcancel |
Aceptar / Cancelar | True / False |
askyesno |
Sí / No | True / False |
askretrycancel |
Reintentar / Cancelar | True / False |
askyesnocancel |
Sí / No / Cancelar | True / False / None |
Todas reciben title= y message=. (Las capturas de la documentación que muestra el curso están en sueco, pero en tu equipo aparecerán en el idioma del sistema).
Confirmar antes de guardar (solución del curso):
is_ok = messagebox.askokcancel(
title=website,
message=f"These are the details entered: \nEmail: {email} "
f"\nPassword: {password} \nIs it ok to save?"
)
if is_ok:
# escribir en el archivo y borrar las entradasSi el usuario cancela, no pasa nada y puede corregir los datos.
📝 Ejercicio original del curso: validación
Enunciado: si el sitio web o la contraseña están vacíos, muestra una ventana emergente: "Oops" — "Please make sure you haven't left any fields empty." y no guardes nada.
🔍 Antes de programar
- ¿Cómo sabes si un string está vacío? (El curso usa
len(...) == 0.) - ¿Debes usar
anduor? Piensa: ¿qué pasa si solo uno de los dos está vacío? - ¿Por qué el curso no valida el correo? (Pista: ya viene escrito.)
- ¿Dónde va la confirmación de guardar: antes o después de la validación?
Solución del curso (en palabras): después de obtener los datos, if len(website) == 0 or len(password) == 0: → showinfo con el aviso; else: → todo lo demás (confirmación, escritura y borrado) indentado dentro del else.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
Olvidar from tkinter import messagebox |
NameError: name 'messagebox' is not defined |
Impórtalo aparte |
Validar con and |
Solo avisa si ambos están vacíos | Usa or |
Olvidar el else |
Muestra el aviso y además guarda los datos vacíos | Todo lo que guarda va dentro del else |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: después de guardar, muestra un
showinfoque diga "Guardado correctamente". - 🟡 Reto 2: un campo lleno solo de espacios (
" ") pasa la validación del curso. Corrígelo. (Pista:strip().) - 🟠 Reto 3: si la contraseña tiene menos de 8 caracteres, usa
askyesnopara advertir: "Esta contraseña es débil. ¿Guardar de todas formas?". - 🔴 Reto 4: valida que el correo tenga exactamente una
@y al menos un punto después de ella. Escribe 5 casos de prueba, incluidos casos límite comoa@b.o@ejemplo.com. ¿Tu validación acepta algún correo inválido? ¿Rechaza alguno válido?
5. Generar la contraseña y copiarla al portapapeles
(Fuente: 226 - Generate a Password & Copy it to the Clipboard.md)
🎯 Objetivo de aprendizaje
- Reescribir el generador del Día 5 con comprensiones de listas y
join. - Insertar la contraseña en la entrada y copiarla al portapapeles.
📖 Explicación conceptual
El generador del Día 5, modificado por el curso. El curso enlaza una versión que ya no pide datos por consola: elige al azar entre 8 y 10 letras, 2 a 4 símbolos y 2 a 4 números. Cópiala en la sección del generador y sube los import al principio del archivo.
Parte del código original (con bucles):
password_list = []
for char in range(nr_letters):
password_list.append(random.choice(letters))
# ... otros dos bucles iguales para símbolos y números ...
random.shuffle(password_list)
password = ""
for char in password_list:
password += char📝 Ejercicio original del curso: tres bucles, tres comprensiones
Enunciado: reemplaza los tres bucles que llenan la lista por tres comprensiones de listas. El programa debe seguir funcionando igual.
🔍 Antes de programar
- En el patrón
[new_item for item in list], ¿qué es lalistaquí? (Pista: no es una lista, es unrange.) - ¿Usas el
itempara algo? Si no, ¿cómo se nombra una variable que no se usa? - ¿Qué es el
new_item?
Solución del curso (en palabras):
password_letters = [choice(letters) for _ in range(nr_letters)], y lo mismo con símbolos y números.- El guion bajo
_es la convención para "esta variable no me importa". - Se unen las tres listas con
+y se mezclan conshuffle. - Se cambia
import randomporfrom random import choice, randint, shufflepara escribir menos.
El método join. Todo string tiene el método join, que une los elementos de un iterable usando ese string como separador:
"#".join(["John", "Peter", "Vicky"]) # 'John#Peter#Vicky' (verificado)
"".join(["a", "b", "c"]) # 'abc'Con un string vacío como separador, reemplaza las tres líneas del bucle: password = "".join(password_list).
Conectarlo a la interfaz:
- Mete todo en una función
generate_password()y conéctala al botón concommand=generate_password. - En lugar de imprimir la contraseña, ponla en la entrada:
password_entry.insert(0, password).
Copiar al portapapeles con pyperclip. Es una librería externa y multiplataforma. Hay que instalarla (PyCharm la ofrece con la bombilla, o en la terminal: pip install pyperclip):
import pyperclip
pyperclip.copy(password) # queda lista para pegar con Ctrl + V🛠️ Nota técnica (verificada): Tkinter puede hacer lo mismo sin instalar nada:
window.clipboard_clear()y luegowindow.clipboard_append(password). Conwindow.clipboard_get()se comprobó que el texto quedó en el portapapeles. Diferencia: con la opción de Tkinter, en algunos sistemas el contenido se pierde al cerrar la aplicación;pyperclipno depende de la ventana. Las dos son válidas.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
ModuleNotFoundError: No module named 'pyperclip' |
La librería no está instalada en el entorno del proyecto (verificado) | Instálala (Día 15) o usa la alternativa de Tkinter |
| Pulsar "Generate Password" dos veces | La nueva contraseña se pega delante de la anterior: insert(0, 'xyz') sobre 'abc' da 'xyzabc' (verificado) |
Borra la entrada con delete(0, END) antes de insertar |
" ".join(lista) con un espacio |
La contraseña queda con espacios entre los caracteres | "".join(...), string vacío |
join con una lista de números |
TypeError: sequence item 0: expected str instance, int found |
Todos los elementos deben ser strings |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: agrega una
Scalepara elegir la longitud de la contraseña (de 8 a 32). - 🟡 Reto 2: agrega
Checkbuttonpara incluir o no símbolos y números. - 🟠 Reto 3: muestra junto a la entrada una etiqueta con la fortaleza de la contraseña (débil, media, fuerte) según su longitud y la variedad de caracteres. Define y escribe tus criterios.
- 🔴 Reto 4 — Pensamiento computacional: el cómic de XKCD que cita el curso propone usar frases de varias palabras comunes ("caballo correcto batería grapa"). Crea un generador de frases de contraseña a partir de una lista de palabras en español. Calcula cuántas combinaciones hay con 4 palabras de una lista de 2000 y compáralo con una contraseña de 8 caracteres aleatorios. ¿Cuál es más fácil de recordar? ¿Cuál tiene más combinaciones?
6. 🚀 Proyecto del día: MyPass, el gestor de contraseñas
(Fuentes: 220 a 226)
Las secciones 1 a 5 construyen el proyecto paso a paso. Aquí lo organizamos como una secuencia de versiones para que puedas comprobar tu avance.
Requisitos funcionales
- Interfaz con el logo, tres campos, el botón Generate y el botón Add (sección 2).
- El cursor empieza en el sitio web y el correo ya está escrito (sección 3).
- Validación de campos vacíos (sección 4).
- Confirmación antes de guardar (sección 4).
- Guardar en
data.txt, un registro por línea (sección 3). - Generar contraseñas y copiarlas al portapapeles (sección 5).
Versiones
| Versión | Qué agrega | Cómo sabes que funciona |
|---|---|---|
| 1 | Ventana y logo | Se ve el logo centrado con relleno |
| 2 | Interfaz completa con grid y columnspan |
Se parece a la imagen del curso |
| 3 | Guardar en archivo | data.txt aparece en la carpeta, un registro por línea |
| 4 | Validación y confirmación | Con un campo vacío aparece "Oops"; al cancelar no se guarda nada |
| 5 | Generador con comprensiones y join |
La contraseña aparece en el campo |
| Final | Portapapeles | Puedes pegar la contraseña con Ctrl + V en otro programa |
Pruebas
| Caso | Esperado |
|---|---|
| Todo lleno → Add → OK | Se guarda y se borran el sitio web y la contraseña |
| Todo lleno → Add → Cancelar | No se guarda nada; los datos siguen en los campos |
| Sitio web vacío → Add | "Oops"; no se guarda |
| Contraseña con solo espacios → Add | ⚠️ (ver 🧪) |
| Generate dos veces | ⚠️ (ver 🧪) |
| Guardar el mismo sitio web dos veces | ⚠️ (ver 🧪) |
🧪 Prueba y depuración
- Generate dos veces: las contraseñas se concatenan (verificado). Corrígelo.
- Espacios:
" "tiene longitud 3 y pasa la validación. Corrígelo constrip(). - Sitio web repetido: si guardas "Amazon" dos veces, quedan dos líneas y no sabes cuál es la contraseña vigente. No lo corrijas todavía: en el Día 30 cambiaremos el formato del archivo a JSON y este problema desaparecerá. Anota por qué ocurre.
- El separador
|: ¿qué pasa si una contraseña generada contiene el símbolo|? ¿Sigue siendo fácil separar los datos de esa línea? Revisa si la lista de símbolos del generador lo incluye.
⭐ Extensión del proyecto
- Un botón "Mostrar/Ocultar" contraseña (investiga la opción
show="*"deEntry). - Guardar también la fecha de creación de cada registro.
- Atajo de teclado: Enter equivale a pulsar Add (investiga
window.bind("<Return>", ...)).
7. 🔐 ¿Qué tan seguro es MyPass?
Esta sección es una adaptación pedagógica. El curso presenta MyPass como una alternativa local a servicios como LastPass y dice que "algunas personas creen que es mucho más seguro". Aquí analizamos esa afirmación con más cuidado.
Tres problemas de seguridad
1. Las contraseñas se guardan en texto plano. Cualquiera que abra data.txt (otra persona que use el computador, un programa malicioso, una copia de seguridad en la nube) ve todas las contraseñas. Los gestores profesionales cifran el archivo con una contraseña maestra.
2. random no es seguro para generar contraseñas. La documentación oficial de random (se abre en otra pestaña) advierte que sus generadores no deben usarse con fines de seguridad y remite al módulo secrets, diseñado para eso. El cambio es pequeño:
import secrets
password_letters = [secrets.choice(letters) for _ in range(nr_letters)]3. El portapapeles lo lee cualquier programa. La contraseña queda ahí hasta que copies otra cosa.
Entonces, ¿"local" es más seguro que "en la nube"?
No hay una respuesta única. Un archivo local sin cifrar es menos seguro que un buen gestor en la nube cifrado. Uno local cifrado evita depender de una empresa, pero si pierdes el archivo, pierdes todo. Lo importante es qué tan bien se protegen los datos, no solo dónde están.
Actividad
- Cambia
random.choiceporsecrets.choiceen tu generador. ¿Cuántas líneas tuviste que cambiar? - Investiga qué es una contraseña maestra y qué es la autenticación en dos pasos. Escribe un párrafo que explique cada una a alguien de tu familia.
- Investiga qué dice el ColCERT (el Grupo de Respuesta a Emergencias Cibernéticas de Colombia) o alguna fuente oficial sobre buenas prácticas con contraseñas, y cita la fuente.
8. 🌟 Proyecto extra: el registro de préstamos de la biblioteca escolar
Situación
La biblioteca del colegio registra los préstamos de libros en un cuaderno. Quieren una aplicación sencilla para registrarlos en el computador de la biblioteca.
Requisitos
- Interfaz con
grid: el logo o escudo del colegio; campos para el código del libro, el título, el código del estudiante y el grado; unSpinboxcon los días de préstamo (1 a 15); y un botón "Registrar préstamo" que ocupe todo el ancho. - El grado se elige con un menú desplegable o con
Radiobutton(6.º a 11.º), no se escribe. - Validación con ventanas emergentes: ningún campo vacío (ni solo con espacios); el código del libro debe ser solo dígitos (
isdigit()). - Confirmación con
askyesno, mostrando un resumen del préstamo y la fecha de devolución calculada. (Investigadatetime.date.today()ydatetime.timedelta(days=...).) - Guardar en
prestamos.csvcon encabezados en la primera línea (solo si el archivo es nuevo) y un préstamo por línea. - Un botón "Ver préstamos de hoy" que muestre en una ventana emergente cuántos préstamos se registraron hoy.
Decisiones que debes tomar y justificar
- ¿Guardas el nombre del estudiante o solo su código? Piensa en la protección de datos personales (en Colombia, la Ley 1581 de 2012). ¿Qué dato es realmente necesario?
- ¿Qué haces si alguien escribe una coma en el título del libro, sabiendo que el archivo es CSV?
Casos de prueba
| Caso | Esperado |
|---|---|
| Todo correcto, 8 días | Se guarda con la fecha de devolución correcta |
Código del libro A12 |
Aviso: el código debe ser numérico |
| Primer préstamo del día | Se crea el CSV con encabezados |
| Segundo préstamo | Se agrega sin repetir los encabezados |
Título con coma: Cien años de soledad, edición conmemorativa |
Se guarda y se puede leer bien con pandas (Día 25) |
9. 🧩 Comprueba lo aprendido
Conceptuales
- ¿Para qué sirve
columnspan? ¿Por qué no basta con aumentar elwidth? - ¿Qué diferencia hay entre
insert(0, texto)einsert(END, texto)? - ¿Por qué hay que importar
messageboxaparte?
Análisis de código
simbolos = [choice(symbols) for _ in range(randint(2, 4))]Explica cada parte. ¿Por qué se usa _? ¿Cuántos elementos puede tener simbolos?
Predicción
print("-".join(["2026", "09", "25"]))
print("".join([c.upper() for c in "abc"]))
print(len("".join(["ab", "cd", "ef"])))Depuración
Encuentra cuatro problemas en esta función.
def save():
website = website_entry.get()
password = password_entry.get()
if len(website) == 0 and len(password) == 0:
messagebox.showinfo(title="Oops", message="No dejes campos vacíos")
with open("data.txt", "w") as data_file:
data_file.write(f"{website} | {password}")
website_entry.delete(0)Modificación
Cambia MyPass para que el mensaje de confirmación no muestre la contraseña completa, sino solo sus dos primeros caracteres seguidos de asteriscos: Cl********.
Creación
Crea una función generar_codigo_verificacion(longitud=6) que devuelva un código de solo dígitos usando secrets y una comprensión de listas con join.
10. 🤖 Programar con apoyo de IA
Actividad: la IA como auditora de seguridad (y tú como auditor de la IA).
- Pega en una IA tu código de MyPass (¡sin contraseñas reales!) y pregúntale: "¿Qué problemas de seguridad tiene este programa?".
- Compara su lista con la sección 7. ¿Encontró los tres problemas? ¿Encontró otros? ¿Alguno de los que menciona no aplica a tu código?
- Pregúntale: "¿Es seguro usar
randompara generar contraseñas?". Luego verifica su respuesta con la documentación oficial derandom(se abre en otra pestaña) y la desecrets(se abre en otra pestaña). Copia la frase exacta de la documentación que confirma o contradice lo que dijo la IA. - Pídele que te ayude a cifrar
data.txt. Probablemente te sugiera una librería externa. No la instales todavía: investiga quién la mantiene, cuántas descargas tiene y si es la recomendada en fuentes confiables. Escribe tus hallazgos. - Regla de oro: nunca pegues en una IA contraseñas, claves de acceso o datos personales reales.
11. 🛠️ Correcciones técnicas al material original
| Archivo | Afirmación o comportamiento del curso | Observación | Tratamiento |
|---|---|---|---|
220 |
Guardar localmente "algunas personas creen que es mucho más seguro" que un servicio de terceros | Un archivo local sin cifrar no es más seguro que un gestor cifrado; lo importante es cómo se protegen los datos | Sección 7 |
221–226 |
Archivos iniciales y código del generador enlazados en los recursos | No están en la carpeta | Nota inicial; reconstrucción desde las lecciones |
223 |
width dentro de grid produce un error |
Correcto; mensaje exacto verificado | Errores frecuentes del Tema 2 |
224 |
Las contraseñas se guardan en data.txt en texto plano |
Riesgo de seguridad; en clase solo deben usarse datos ficticios | Advertencia inicial y sección 7 |
225 |
Validación con len(...) == 0 |
Un campo con solo espacios la pasa | Reto 2 del Tema 4 y 🧪 |
226 |
Generador con el módulo random |
La documentación oficial de random desaconseja usarlo con fines de seguridad; se recomienda secrets |
Sección 7 |
226 |
password_entry.insert(0, password) |
Si se pulsa Generate dos veces, las contraseñas se concatenan (verificado) | 🧪 del proyecto |
226 |
pyperclip para el portapapeles |
Requiere instalación; Tkinter trae clipboard_clear y clipboard_append (verificado) |
Nota técnica del Tema 5 |
12. 📋 Matriz de trazabilidad
| Contenido original | Archivo de origen | Concepto desarrollado | Ejercicio original | Reto adicional | Proyecto relacionado |
|---|---|---|---|---|---|
| Objetivos, Coding Horror, XKCD, MyPass | 220 |
Sección 0 | — | Reto 4 del Tema 5 (frases de contraseña) | Sección 6 |
Ventana, Canvas y logo |
221 |
Tema 1 | Desafío 1 | — | Secciones 6 y 8 |
columnspan, sandbox de colores |
222 |
Tema 2 | Desafío 2 | Retos 1–4 del Tema 2 | Secciones 6 y 8 |
Solución de la cuadrícula, anchos, width en grid |
223 |
Tema 2 | Solución del desafío 2 | — | Sección 6 |
focus, insert, END, modo "a", delete, \n |
224 |
Tema 3 | Desafío 3 | — | Secciones 6 y 8 |
messagebox, askokcancel, validación |
225 |
Tema 4 | Validación de campos vacíos | Retos 1–4 del Tema 4 | Secciones 6 y 8 |
Comprensiones en el generador, _, join, pyperclip |
226 |
Tema 5 | Tres bucles → tres comprensiones | Retos 1–4 del Tema 5 | Secciones 6 y 7 |
| (No existe en el curso original) | — | — | — | Análisis de seguridad y secrets |
Sección 7 |
| (No existe en el curso original) | — | — | — | Préstamos de la biblioteca escolar | Sección 8 |
13. 📦 Evidencias de aprendizaje del Día 29
- Tu dibujo de la cuadrícula de MyPass y la captura de la interfaz terminada.
- MyPass funcionando con datos ficticios, con la tabla de pruebas y los problemas 1 y 2 de 🧪 corregidos.
- La versión del generador con
secrets. - El párrafo sobre contraseña maestra y autenticación en dos pasos, con la fuente citada.
- El registro de préstamos de la biblioteca (proyecto extra) con tu justificación sobre los datos personales.
- Cuaderno de errores:
IndexErrorencreate_image,TclErrorporwidthengrid,NameErrordemessagebox, validación conanden lugar deor, registros en la misma línea por falta de\n, contraseñas concatenadas yModuleNotFoundErrordepyperclip.