Apariencia
📙 Clase 18 — El gran refactor: tu app como clase
Fase 3 · Programación Orientada a Objetos · Clase integradora ⬅️ Volver al índice de clases
🎯 Qué aprendí
- Juntar TODA la Fase 3 en el patrón profesional de una app CTk.
- La anatomía estándar:
__init__→ estado →crear_widgets→ callbacks-método. - El plan paso a paso para refactorizar tus apps (el código lo escribes tú 💪).
📖 PARTE TEÓRICA — la anatomía estándar
Toda app CTk profesional sigue esta estructura. Memorízala como plantilla mental:
python
import customtkinter as ctk
class App(ctk.CTk): # ① herencia (Clase 15)
def __init__(self):
super().__init__() # ② construir la ventana
self.title("Mi app")
self.geometry("420x520")
self._tareas = [] # ③ ESTADO en self (Clases 14/16)
self._crear_widgets() # ④ UI separada en un método
# ---------- UI ----------
def _crear_widgets(self): # _ = interno (Clase 16)
self.entry = ctk.CTkEntry(self, placeholder_text="Nueva tarea…")
self.entry.pack(padx=12, pady=12, fill="x")
ctk.CTkButton(self, text="Agregar",
command=self._agregar).pack() # callback = MÉTODO
self.lista = ctk.CTkScrollableFrame(self)
self.lista.pack(fill="both", expand=True, padx=12, pady=12)
self.status = ctk.CTkLabel(self, text="")
self.status.pack()
# ---------- callbacks ----------
def _agregar(self):
ok, msg = self._validar(self.entry.get()) # lógica con return (Clase 08)
if not ok:
self.status.configure(text=msg)
return
self._tareas.append(Tarea(self.entry.get().strip()))
self.entry.delete(0, "end")
self._pintar()
# ---------- lógica (sin tocar widgets) ----------
@staticmethod
def _validar(texto):
if not texto.strip():
return False, "⚠️ Escribe algo primero"
return True, ""
# ---------- render ----------
def _pintar(self):
for w in self.lista.winfo_children(): # limpiar el frame
w.destroy()
for tarea in self._tareas: # y redibujar desde el estado
ctk.CTkCheckBox(self.lista, text=f"{tarea}", # __str__ (Clase 17)
command=lambda t=tarea: self._alternar(t) # (Clase 10)
).pack(anchor="w", pady=2)
self.status.configure(text=f"Pendientes: {self.pendientes}")
def _alternar(self, tarea):
tarea.alternar()
self._pintar()
@property
def pendientes(self): # property (Clase 16)
return sum(1 for t in self._tareas if not t.hecha)
class Tarea: # el MODELO (Clases 13/17)
def __init__(self, texto):
self.texto = texto
self.hecha = False
def alternar(self):
self.hecha = not self.hecha
def __str__(self):
return f"{'✅' if self.hecha else '⬜'} {self.texto}"
if __name__ == "__main__": # guardián (Clase 11)
App().mainloop()📌 Esta plantilla compila con CustomTkinter 6.0.0 (verificada por partes en el
.venv; la ventana la abres tú conpython app.py). Fíjate cuántas clases del curso se juntan aquí — cada línea la aprendiste en una clase concreta.
🗺️ El mapa de responsabilidades
class App(ctk.CTk)
├── __init__ configura ventana + estado + llama a _crear_widgets
├── _crear_widgets SOLO construye la UI (no decide reglas)
├── callbacks (_agregar…) orquestan: leen entrada → lógica → repintan
├── lógica (_validar…) reglas puras: reciben datos, DEVUELVEN resultado
├── _pintar borra y redibuja desde el estado (patrón de tu To-Do)
└── properties datos calculados para mostrar (pendientes)
class Tarea el MODELO: datos + comportamiento del registro💡 Cuando en la Fase 6 agreguemos SQLite, solo cambia una cosa: el estado
self._tareasse carga/guarda desde un módulodatos.py. La estructura ya no se mueve. Por eso este refactor es la inversión más rentable del curso.
💻 PARTE PRÁCTICA — tu misión (tú escribes el código)
Refactoriza tus dos apps reales a este patrón. Guía en modo guiado, sin solución — como tus ejercicios:
Refactor 1 — Formulario de registro (Ejercicio 1)
- Crea
class App(ctk.CTk)consuper().__init__(), título y tamaño. - Mueve la creación de widgets a
_crear_widgets(); guarda los entries comoself.entry_.... - Convierte cada función-callback en un método (
def _registrar(self):). - Extrae la validación a un método puro que devuelva
(ok, mensaje). - Arranca con
if __name__ == "__main__": App().mainloop().
Refactor 2 — Lista de tareas (Ejercicio 2)
- Crea la clase modelo
Tarea(texto,hecha,alternar(),__str__). class App(ctk.CTk)conself._tareas = [].- Tu función "redibujar" pasa a ser el método
_pintar(). - Reto: agrega la property
pendientesy muéstrala en un label de estado.
Checklist de éxito (márcalo al terminar):
- [ ] Cero variables globales (todo en
self) - [ ] Cero
globalen el archivo - [ ] La validación no toca widgets (recibe texto, devuelve
(ok, msg)) - [ ]
python app.pyabre y funciona igual (o mejor) que antes
🔐 Solución de referencia (ábrela solo DESPUÉS de intentarlo)
Ver solución · Refactor 1 — Formulario como clase
python
import customtkinter as ctk
class App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Registro")
self.geometry("380x300")
self._crear_widgets()
def _crear_widgets(self):
self.entry_nombre = ctk.CTkEntry(self, placeholder_text="Nombre")
self.entry_nombre.pack(padx=12, pady=6, fill="x")
self.entry_email = ctk.CTkEntry(self, placeholder_text="Email")
self.entry_email.pack(padx=12, pady=6, fill="x")
ctk.CTkButton(self, text="Registrar", command=self._registrar).pack(pady=10)
self.mensaje = ctk.CTkLabel(self, text="")
self.mensaje.pack()
def _registrar(self):
ok, msg = self._validar(self.entry_nombre.get(), self.entry_email.get())
if not ok:
self.mensaje.configure(text=f"⚠️ {msg}")
return
self.mensaje.configure(text="✅ Registrado")
self.entry_nombre.delete(0, "end")
self.entry_email.delete(0, "end")
@staticmethod
def _validar(nombre, email): # lógica pura: testeable sin ventana
if not nombre.strip():
return False, "El nombre está vacío"
if "@" not in email:
return False, "El email no es válido"
return True, ""
if __name__ == "__main__":
App().mainloop()Ver solución · Refactor 2 — To-Do como clase
La plantilla completa de la parte teórica de esta misma clase ES la solución: class App(ctk.CTk) con self._tareas, _crear_widgets, _agregar, _pintar, _alternar, la property pendientes y la clase modelo Tarea. Compárala con la tuya sección por sección usando el mapa de responsabilidades. Si tu versión hace lo mismo con otros nombres, está bien — lo que importa es el checklist: cero globals, validación pura, callbacks-método.
🧪 Tip de entrevista: "¿Por qué organizar una GUI como clase?" → El estado vive encapsulado en la instancia (no en globals), los callbacks comparten ese estado vía
self, y la app se vuelve testeable y componible (frames-clase reutilizables).
❓ Preguntas y respuestas (autoevaluación)
1. ¿Dónde vive el estado de la app en este patrón y qué reemplaza?
En atributos de la instancia (
self._tareas); reemplaza las variables globales +global.
2. ¿Qué hace _crear_widgets y qué NO debe hacer?
Construye la interfaz. No debe contener reglas de negocio ni validaciones.
3. ¿Por qué _validar es @staticmethod?
No usa
self: es lógica pura (recibe texto, devuelve resultado). Marcarla static lo documenta y permite probarla sin instanciar la app.
4. ¿Qué única pieza cambiará cuando llegue la base de datos en la Fase 6?
La carga/guardado del estado (
self._tareas↔datos.py/SQLite). La estructura queda igual.
📎 Apuntes relacionados
- Cada pieza en su clase: return · scope/lambda · módulos · herencia · properties · dunder
- Tus apps a refactorizar → Ejercicio 1 · Ejercicio 2
➡️ Siguiente
¡Fase 3 completa! 🎉 Fase 4: Clase 19 · Archivos — que tu app recuerde datos al cerrar.