Ir al contenido

IDEAS PRÁCTICAS · ASIMOV INGENIERÍA

Construyendo la aplicación final en Tkinter – Parte 1

Guía paso a paso para crear la ventana principal, el menú y una pantalla funcional (listado de notas) usando clases y módulos en Tkinter.

Imagen ilustrativa · AltumCode / Unsplash.

Table of contents

No titles in the blog post

Construyendo la aplicación final en Tkinter – Parte 1

En el post anterior definimos la arquitectura de nuestra aplicación de gestión académica (alumnos, notas, profesores) y diseñamos la base de datos SQLite. En esta entrega, pasaremos a la capa de presentación con Tkinter, creando la ventana principal, un menú completo y la primera pantalla funcional: el listado de notas.

Representación tridimensional del logotipo de Python
Imagen ilustrativa · Rubaitul Azad / Unsplash.

El objetivo es que cualquier estudiante de programación pueda seguir el proceso, entender la organización en paquetes y aplicar buenas prácticas de POO y modularidad.

Requisitos previos

  • Python 3.9+ instalado.
  • Conocimientos básicos de Tkinter (ventanas, widgets).
  • SQLite3 (viene con Python).
  • Editor de código (VS Code, PyCharm, etc.).

Instala la librería ttkbootstrap para mejorar la estética con Bootstrap‑like themes:

pip install ttkbootstrap

Estructura del proyecto

my_school_app/
├─ app.py                # Punto de entrada
├─ config.py             # Configuración global
├─ db/
│   └─ database.py      # Acceso a SQLite
├─ ui/
│   ├─ __init__.py
│   ├─ main_window.py   # Clase MainWindow
│   ├─ menu_bar.py      # Clase MenuBar
│   └─ notes_view.py    # Vista de listado de notas
└─ models/
    ├─ __init__.py
    ├─ student.py        # Entidad Student
    └─ note.py           # Entidad Note

Esta organización permite:

  • Separación de responsabilidades: UI, lógica de negocio y acceso a datos están aislados.
  • Escalabilidad: Añadir nuevas vistas (alumnos, profesores) sin tocar el núcleo.
  • Testabilidad: Cada módulo puede ser testeado de forma independiente.

config.py

"""Configuración central de la aplicación"""

DB_PATH = "db/school.db"
APP_TITLE = "Gestor Académico"
WINDOW_SIZE = "900x600"
THEME = "flatly"  # Tema ttkbootstrap (Bootstrap 4 like)

models/note.py

from dataclasses import dataclass

@dataclass
class Note:
    id: int
    student_id: int
    subject: str
    grade: float
    date: str  # ISO format YYYY-MM-DD

Utilizamos dataclass para simplificar la definición de la entidad.

db/database.py

import sqlite3
from pathlib import Path
from ..config import DB_PATH

class Database:
    def __init__(self):
        self.conn = sqlite3.connect(DB_PATH)
        self.conn.row_factory = sqlite3.Row
        self._ensure_schema()

    def _ensure_schema(self):
        """Crea tablas si no existen – idempotente"""
        cur = self.conn.cursor()
        cur.execute('''
            CREATE TABLE IF NOT EXISTS notes (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                student_id INTEGER NOT NULL,
                subject TEXT NOT NULL,
                grade REAL NOT NULL,
                date TEXT NOT NULL
            )
        ''')
        self.conn.commit()

    def fetch_notes(self):
        cur = self.conn.cursor()
        cur.execute('SELECT * FROM notes ORDER BY date DESC')
        rows = cur.fetchall()
        return [dict(row) for row in rows]

    def insert_note(self, student_id, subject, grade, date):
        cur = self.conn.cursor()
        cur.execute(
            'INSERT INTO notes (student_id, subject, grade, date) VALUES (?,?,?,?)',
            (student_id, subject, grade, date)
        )
        self.conn.commit()
        return cur.lastrowid

    def close(self):
        self.conn.close()

El método _ensure_schema garantiza que la tabla exista siempre que la app se inicie, facilitando despliegues en máquinas nuevas.

ui/main_window.py

import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from .menu_bar import MenuBar
from .notes_view import NotesView

class MainWindow(ttk.Window):
    def __init__(self, db, *args, **kwargs):
        super().__init__(themename=kwargs.pop('theme', 'flatly'), *args, **kwargs)
        self.title(kwargs.get('title', 'Aplicación'))
        self.geometry(kwargs.get('size', '800x600'))
        self.db = db

        # --- Menú superior ---
        self.menu = MenuBar(self)
        self.config(menu=self.menu)

        # --- Contenedor principal (frame) ---
        self.container = ttk.Frame(self)
        self.container.pack(fill=BOTH, expand=YES, padx=10, pady=10)

        # Vista por defecto: listado de notas
        self.show_notes_view()

    def clear_container(self):
        for widget in self.container.winfo_children():
            widget.destroy()

    def show_notes_view(self):
        self.clear_container()
        NotesView(self.container, self.db).pack(fill=BOTH, expand=YES)

Observa cómo MainWindow delega la creación del menú a MenuBar y la lógica de la pantalla a NotesView. La función clear_container permite cambiar de vista sin crear una nueva ventana.

ui/menu_bar.py

import ttkbootstrap as ttk
from ttkbootstrap.constants import *

class MenuBar(ttk.Menu):
    def __init__(self, master):
        super().__init__(master)
        self.master = master
        self._build_menus()

    def _build_menus(self):
        # Archivo
        file_menu = ttk.Menu(self, tearoff=0)
        file_menu.add_command(label='Salir', command=self.master.quit, accelerator='Ctrl+Q')
        self.add_cascade(label='Archivo', menu=file_menu)

        # Ver
        view_menu = ttk.Menu(self, tearoff=0)
        view_menu.add_command(label='Notas', command=self.master.show_notes_view)
        # Futuro: view_menu.add_command(label='Alumnos', command=self.master.show_students_view)
        self.add_cascade(label='Ver', menu=view_menu)

        # Ayuda
        help_menu = ttk.Menu(self, tearoff=0)
        help_menu.add_command(label='Acerca de', command=self._show_about)
        self.add_cascade(label='Ayuda', menu=help_menu)

        # Atajos de teclado
        self.master.bind_all('', lambda e: self.master.quit())

    def _show_about(self):
        ttk.Messagebox.showinfo('Acerca de', 'Gestor Académico – Versión 1.0')

ui/notes_view.py

import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.tableview import Tableview

class NotesView(ttk.Frame):
    def __init__(self, master, db, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.db = db
        self._build_ui()
        self._load_data()

    def _build_ui(self):
        # Título
        ttk.Label(self, text='Listado de Notas', font=('Helvetica', 16, 'bold')).pack(pady=10)

        # Tabla (Tableview de ttkbootstrap)
        columns = ('id', 'student_id', 'subject', 'grade', 'date')
        self.table = Tableview(self,
                               paginated=False,
                               searchable=False,
                               bootstyle=PRIMARY,
                               columns=columns,
                               show='headings')
        for col in columns:
            self.table.heading(col, text=col.title().replace('_', ' '))
            self.table.column(col, width=100, anchor=CENTER)
        self.table.pack(fill=BOTH, expand=YES, padx=5, pady=5)

        # Botón de refresco
        ttk.Button(self, text='Refrescar', command=self._load_data, bootstyle=SUCCESS).pack(pady=5)

    def _load_data(self):
        self.table.delete(*self.table.get_children())
        notes = self.db.fetch_notes()
        for note in notes:
            self.table.insert('', END, values=(note['id'], note['student_id'], note['subject'], note['grade'], note['date']))

La tabla utiliza Tableview de ttkbootstrap, que ofrece paginación y búsqueda integradas (desactivadas aquí para simplificar). Cada fila muestra los campos de la tabla notes.

app.py (punto de entrada)

#!/usr/bin/env python3
"""Gestor Académico – Entrypoint"""

import sys
from pathlib import Path

# Añadir el directorio raíz al PYTHONPATH (para imports relativos)
BASE_DIR = Path(__file__).resolve().parent
sys.path.append(str(BASE_DIR))

from config import APP_TITLE, WINDOW_SIZE, THEME
from db.database import Database
from ui.main_window import MainWindow

def main():
    db = Database()
    try:
        root = MainWindow(db, title=APP_TITLE, size=WINDOW_SIZE, theme=THEME)
        root.mainloop()
    finally:
        db.close()

if __name__ == '__main__':
    main()

El bloque try/finally garantiza que la conexión SQLite se cierre adecuadamente, evitando bloqueos de archivo.

Ejecutar la aplicación

  1. Clona o descarga el repositorio.
  2. Desde la raíz del proyecto, instala dependencias:
    pip install -r requirements.txt  # (contendrá ttkbootstrap)
  3. Ejecuta:
    python app.py
  4. La ventana mostrará el listado de notas (vacío la primera vez).

Solución de problemas (troubleshooting)

  • ImportError: No module named 'ttkbootstrap' – Asegúrate de haber ejecutado pip install ttkbootstrap dentro del mismo entorno virtual.
  • Database locked – Cierra correctamente la aplicación (Ctrl+Q) o verifica que no haya otro proceso manteniendo la conexión.
  • La tabla no se muestra – Revisa que la versión de ttkbootstrap sea >=1.10; versiones antiguas no incluyen Tableview.

Mejores prácticas y seguridad

  • Validación de datos de entrada: antes de llamar a insert_note valida que grade esté entre 0 y 10 y que date sea una fecha ISO válida.
  • Principio de menor privilegio: el archivo school.db debe tener permisos 600 (solo el propietario).
  • Separación de capas: si la aplicación crece, considera mover la lógica de negocio a un paquete services/ y exponer una API REST con FastAPI para futuras versiones.
  • Internacionalización (i18n): utiliza gettext para traducir los textos del menú y mensajes.

Tkinter vs. alternativas modernas (PyQt5 / Kivy)

Tkinter + ttkbootstrap
  • Incluido en la instalación estándar de Python (no dependencias externas).
  • Ligero, bajo consumo de memoria.
  • Facilidad de despliegue en entornos corporativos con restricciones.
  • Estética mejorada mediante ttkbootstrap (temas Bootstrap).
PyQt5 / PySide2
  • Widgets más ricos y soporte nativo para estilos avanzados.
  • Mayor curva de aprendizaje y licencia GPL/comercial.
  • Mayor peso del ejecutable, pero mejor para aplicaciones con gráficos complejos.
  • Soporte integrado para internacionalización y accesibilidad.

Para proyectos educativos y prototipos rápidos, Tkinter + ttkbootstrap ofrece la mejor relación simplicidad‑funcionalidad.

¿Qué sigue?

En la Parte 2 continuaremos:

  • Implementando la vista de Listado de Alumnos con edición en línea.
  • Añadiendo validaciones de formulario y manejo de errores.
  • Integrando un módulo de services para separar lógica de negocio.
  • Empaquetando la aplicación con PyInstaller para distribución.