Algoritmia de Programación del Software

Domina los pilares de la algoritmia para diseñar y construir aplicaciones de software eficientes y robustas. Este curso te guiará desde los conceptos básicos de cálculo y lógica hasta el manejo avanzado de datos, errores, archivos, bases de datos y la creación de interfaces gráficas intuitivas.

Nivel: Fundamentos a Intermedio Duración Estimada: 40 horas Lenguaje Principal: Python

Módulo 1: Desarrollo de Aplicaciones de Cálculo

Las aplicaciones de cálculo son esenciales en innumerables campos, desde la ciencia y la ingeniería hasta las finanzas y la vida cotidiana. En este módulo, exploraremos cómo construir estas aplicaciones de manera efectiva, prestando atención a la precisión, el rendimiento y la lógica subyacente.

Precisión Numérica

Aprende a seleccionar los tipos de datos adecuados para evitar errores de redondeo y garantizar precisión en cálculos críticos.

Rendimiento Optimizado

Implementa algoritmos eficientes que minimicen el uso de recursos y maximicen la velocidad de ejecución.

Lógica Estructurada

Diseña flujos de control claros que faciliten el mantenimiento y la comprensión del código.

Componentes Clave

Para desarrollar una aplicación de cálculo robusta, es crucial considerar:

  • Selección de Tipos de Datos: La elección correcta de tipos de datos (enteros, flotantes de precisión simple o doble, decimales) es vital. Un tipo de dato inadecuado puede llevar a errores de precisión o a un uso ineficiente de la memoria.
    • int: Para números enteros (ej: contadores, índices).
    • float: Para números con decimales (ej: mediciones científicas, cálculos financieros básicos). Cuidado con la precisión limitada.
    • Decimal (en Python, del módulo decimal): Para cálculos financieros que requieren alta precisión y evitar errores de representación de los flotantes binarios.
  • Estructuras de Control de Flujo: Permiten dirigir la lógica de la aplicación.
    • Condicionales (if, elif, else; switch en otros lenguajes): Para tomar decisiones basadas en ciertos criterios.
    • Bucles (for, while): Para repetir operaciones, como iterar sobre conjuntos de datos o realizar cálculos sucesivos.
  • Optimización y Eficiencia: No solo se trata de obtener el resultado correcto, sino de obtenerlo rápidamente y con un uso razonable de recursos. Esto implica:
    • Seleccionar algoritmos eficientes (ej: evitar bucles anidados innecesarios).
    • Minimizar operaciones costosas dentro de bucles.
    • Considerar el perfilado de código para identificar cuellos de botella.
  • Interfaz de Usuario (UI/UX): Decidir si la aplicación necesita una Interfaz Gráfica de Usuario (GUI) para facilitar la interacción, o si una Interfaz de Línea de Comandos (CLI) es suficiente (común para herramientas de backend o scripts).
  • Pruebas y Validaciones Exhaustivas: Probar la aplicación con un rango diverso de entradas, incluyendo valores límite y casos erróneos, es fundamental para asegurar su fiabilidad.
¿Sabías que?

Los errores de redondeo en cálculos financieros con valores float pueden llevar a diferencias significativas en grandes volúmenes de transacciones. Por eso en aplicaciones bancarias y financieras se usa casi exclusivamente el tipo Decimal.

Ejemplo Práctico: Calculadora de Interés Simple

Veamos un ejemplo sencillo en Python para calcular el interés simple. Este es un componente fundamental en aplicaciones financieras.

calculador_interes.py

# calculador_interes.py
def calcular_interes_simple(capital, tasa_anual, tiempo_anios):
    """Calcula el interés simple y el monto total."""
    if not all(isinstance(arg, (int, float)) for arg in [capital, tasa_anual, tiempo_anios]):
        raise TypeError("Todos los argumentos deben ser numéricos.")
    if capital < 0 or tasa_anual < 0 or tiempo_anios < 0:
        raise ValueError("Capital, tasa y tiempo no pueden ser negativos.")
    
    interes = (capital * tasa_anual * tiempo_anios) / 100
    monto_total = capital + interes
    return interes, monto_total

# Ejemplo de uso
try:
    cap = 1000.0  # Capital inicial
    tasa = 5.0    # Tasa de interés anual (%)
    tiempo = 2    # Tiempo en años

    interes_generado, total_final = calcular_interes_simple(cap, tasa, tiempo)
    print(f"Capital Inicial: ${cap:,.2f}")
    print(f"Tasa Anual: {tasa}%")
    print(f"Tiempo: {tiempo} años")
    print(f"Interés Generado: ${interes_generado:,.2f}")
    print(f"Monto Total: ${total_final:,.2f}")

except (TypeError, ValueError) as e:
    print(f"Error: {e}")
                        
$ python calculador_interes.py
Capital Inicial: $1,000.00
Tasa Anual: 5.0%
Tiempo: 2 años
Interés Generado: $100.00
Monto Total: $1,100.00

Lenguajes Comunes: Python (con bibliotecas como NumPy para cálculo científico), Java, C++, y R (para estadística) son excelentes opciones para desarrollar aplicaciones de cálculo.

Calculadora Interactiva de Interés

$100 $1,000 $10,000
0.1% 5.0% 20%
1 año 2 años 30 años
Capital Inicial: $1,000.00
Interés Generado: $100.00
Monto Total: $1,100.00

Ejercicio Propuesto

Modifica la función calcular_interes_simple para que calcule el interés compuesto. Investiga la fórmula del interés compuesto y aplícala.

Pista: La fórmula del interés compuesto es $A = P (1 + r/n)^{nt}$, donde $P$ es el capital principal, $r$ la tasa de interés anual, $n$ el número de veces que el interés se compone por año, y $t$ el número de años. Para simplificar, puedes asumir que $n=1$ (composición anual).

Solución

def calcular_interes_compuesto(capital, tasa_anual, tiempo_anios, composiciones_anuales=1):
    # Validaciones similares al anterior
    if not all(isinstance(arg, (int, float)) for arg in [capital, tasa_anual, tiempo_anios, composiciones_anuales]):
        raise TypeError("Todos los argumentos deben ser numéricos.")
    if capital < 0 or tasa_anual < 0 or tiempo_anios < 0 or composiciones_anuales <= 0:
        raise ValueError("Los valores no pueden ser negativos y el número de composiciones debe ser positivo.")
    
    tasa_decimal = tasa_anual / 100
    monto_total = capital * (1 + tasa_decimal / composiciones_anuales)**(composiciones_anuales * tiempo_anios)
    interes_generado = monto_total - capital
    return interes_generado, monto_total

# Ejemplo de uso
cap = 1000.0  # Capital inicial
tasa = 5.0    # Tasa de interés anual (%)
tiempo = 2    # Tiempo en años
composiciones = 12  # Mensual

interes, total = calcular_interes_compuesto(cap, tasa, tiempo, composiciones)
print(f"Capital Inicial: ${cap:,.2f}")
print(f"Tasa Anual: {tasa}%")
print(f"Tiempo: {tiempo} años")
print(f"Composiciones anuales: {composiciones}")
print(f"Interés Generado: ${interes:,.2f}")
print(f"Monto Total: ${total:,.2f}")
                            

Módulo 2: Programación Lógica y Manejo de Ficheros

Este módulo se adentra en dos áreas cruciales: cómo aplicar principios de programación lógica para la toma de decisiones y cómo interactuar con el sistema de archivos para leer y escribir datos de manera persistente.

Programación Lógica

La programación lógica se basa en la idea de definir un conjunto de hechos y reglas, y luego realizar consultas para deducir nueva información. Prolog es el lenguaje paradigmático, pero sus conceptos pueden aplicarse en otros lenguajes.

Componentes de la Programación Lógica

Hechos: Afirmaciones que se consideran verdaderas (ej: "Juan es padre de Ana").
Reglas: Implicaciones que permiten inferir nuevos hechos (ej: "Si X es padre de Y, y Y es padre de Z, entonces X es abuelo de Z").
Consultas: Preguntas al sistema basadas en los hechos y reglas establecidos.

En lenguajes imperativos como Python, simulamos la programación lógica mediante:

  • Funciones que encapsulan reglas de decisión.
  • Estructuras de datos (listas, diccionarios) para almacenar "hechos" o estados.
  • Condicionales complejos y recursión para emular el proceso de inferencia.
sistema_recomendacion_simple.py

# sistema_recomendacion_simple.py
def recomendar_actividad(clima, dia_semana):
    """Recomienda una actividad basada en el clima y el día."""
    if clima == "soleado":
        if dia_semana in ["sabado", "domingo"]:
            return "Perfecto para ir a la playa o un picnic."
        else:
            return "Buen día para un paseo corto después del trabajo."
    elif clima == "lluvioso":
        return "Ideal para una tarde de películas o leer un libro en casa."
    elif clima == "nublado":
        return "Podrías visitar un museo o una cafetería."
    else:
        return "No tengo una recomendación para ese clima."

print(f"Clima: soleado, Día: sabado -> Recomendación: {recomendar_actividad('soleado', 'sabado')}")
print(f"Clima: lluvioso, Día: lunes -> Recomendación: {recomendar_actividad('lluvioso', 'lunes')}")
                        

Sistema de Recomendación Interactivo

Recomendación: Perfecto para ir a la playa o un picnic.

Manejo de Ficheros

Las aplicaciones a menudo necesitan almacenar y recuperar datos. El manejo de ficheros es fundamental para la persistencia de datos.

Operaciones Básicas

Aprende a abrir, leer, escribir y cerrar ficheros de manera segura y eficiente.

Modos de Apertura

Domina los diferentes modos ('r', 'w', 'a', 'r+', 'b') para controlar cómo interactúas con los archivos.

Formatos de Archivo

Trabaja con diferentes formatos como TXT, CSV, JSON y XML según las necesidades de tu aplicación.

Buena Práctica

Siempre usa with open(...) as file: en Python para manejar ficheros. Este enfoque de "context manager" garantiza que el archivo se cierre adecuadamente incluso si ocurren excepciones durante su procesamiento.

  • Modos de Apertura:
    • 'r': Lectura (predeterminado). Error si el archivo no existe.
    • 'w': Escritura. Crea el archivo si no existe, lo sobrescribe si existe.
    • 'a': Añadir. Crea el archivo si no existe, añade contenido al final si existe.
    • 'r+': Lectura y escritura.
    • 'b': Modo binario (ej: 'rb', 'wb'), para archivos no textuales como imágenes o ejecutables.
  • Context Manager (with open(...)): La forma recomendada en Python, ya que asegura que el fichero se cierre automáticamente, incluso si ocurren errores.
manejo_ficheros.py

# manejo_ficheros.py
# Escribir en un fichero
try:
    with open("datos_usuarios.txt", "w", encoding="utf-8") as archivo:
        archivo.write("Usuario1:Ana:30\n")
        archivo.write("Usuario2:Luis:25\n")
    print("Archivo 'datos_usuarios.txt' escrito correctamente.")
except IOError:
    print("Error: No se pudo escribir en el archivo.")

# Leer de un fichero
try:
    with open("datos_usuarios.txt", "r", encoding="utf-8") as archivo:
        print("\nContenido de 'datos_usuarios.txt':")
        for linea in archivo:
            print(linea.strip()) # .strip() elimina espacios en blanco y saltos de línea
except FileNotFoundError:
    print("Error: El archivo 'datos_usuarios.txt' no fue encontrado.")
except IOError:
    print("Error: No se pudo leer el archivo.")
                        
$ python manejo_ficheros.py
Archivo 'datos_usuarios.txt' escrito correctamente.
Contenido de 'datos_usuarios.txt':
Usuario1:Ana:30
Usuario2:Luis:25

Formatos de Ficheros Comunes:

Texto Plano (.txt)

Simple, legible por humanos, sin estructura definida.

Uso: Datos simples, logs, notas.

CSV (.csv)

Valores separados por comas, ideal para datos tabulares.

Uso: Datos estructurados en filas y columnas, compatible con hojas de cálculo.

JSON (.json)

Formato ligero de intercambio de datos, legible por humanos.

Uso: APIs web, configuraciones, datos jerárquicos.

XML (.xml)

Formato extensible y autodescriptivo.

Uso: Configuraciones complejas, intercambio de datos entre sistemas heterogéneos.

Comparación de Formatos de Archivo

Ejercicio Propuesto

Crea un script que lea un archivo CSV llamado productos.csv con columnas (ID,Nombre,Precio,Stock). Luego, el script debe generar un archivo JSON llamado productos_oferta.json que contenga solo los productos cuyo precio sea menor a un valor X (ej: $50) y tengan stock mayor a 0.

convertidor_csv_json.py

import csv
import json

def convertir_csv_a_json(archivo_csv, archivo_json, precio_maximo=50):
    """
    Lee un archivo CSV de productos y crea un JSON con productos en oferta.
    Solo incluye productos con precio < precio_maximo y stock > 0.
    """
    productos_oferta = []
    
    try:
        # Leer el archivo CSV
        with open(archivo_csv, 'r', encoding='utf-8') as f:
            lector_csv = csv.DictReader(f)
            for fila in lector_csv:
                # Convertir valores a tipos adecuados
                producto = {
                    'id': int(fila['ID']),
                    'nombre': fila['Nombre'],
                    'precio': float(fila['Precio']),
                    'stock': int(fila['Stock'])
                }
                
                # Filtrar productos
                if producto['precio'] < precio_maximo and producto['stock'] > 0:
                    productos_oferta.append(producto)
        
        # Escribir el archivo JSON
        with open(archivo_json, 'w', encoding='utf-8') as f:
            json.dump({'productos_oferta': productos_oferta}, f, indent=4, ensure_ascii=False)
        
        print(f"Archivo '{archivo_json}' creado exitosamente con {len(productos_oferta)} productos en oferta.")
        return True
    
    except FileNotFoundError:
        print(f"Error: No se encontró el archivo '{archivo_csv}'.")
    except (KeyError, ValueError) as e:
        print(f"Error en el formato del archivo CSV: {e}")
    except Exception as e:
        print(f"Error inesperado: {e}")
    
    return False

# Programa principal
if __name__ == "__main__":
    archivo_entrada = "productos.csv"
    archivo_salida = "productos_oferta.json"
    precio_limite = 50
    
    convertir_csv_a_json(archivo_entrada, archivo_salida, precio_limite)
                            

Ejemplo de contenido para productos.csv:


ID,Nombre,Precio,Stock
1,Teclado Mecánico,89.99,15
2,Mouse Inalámbrico,45.50,8
3,Auriculares Bluetooth,65.75,0
4,Cable USB 2m,12.99,42
5,Adaptador HDMI,19.50,12
                            

Módulo 3: Manejo de Errores y Expresiones Regulares

Construir software robusto implica anticipar y gestionar problemas. Este módulo cubre el manejo efectivo de errores y el uso de expresiones regulares para la validación y procesamiento de texto avanzado.

Manejo de Errores (Excepciones)

Un error no gestionado puede detener abruptamente una aplicación. El manejo de excepciones permite controlar estos fallos de forma elegante.

Estructura del Manejo de Excepciones

try: Envuelve el código que podría generar una excepción.
except TipoError: Captura y maneja tipos específicos de errores.
else: Se ejecuta si no ocurre ninguna excepción en el bloque try.
finally: Se ejecuta siempre, ocurra o no una excepción.

ZeroDivisionError

Se produce al intentar dividir por cero.

ValueError

Argumento de tipo correcto pero valor inapropiado.

TypeError

Operación aplicada a un objeto de tipo inapropiado.

IndexError

Índice fuera de rango en una secuencia.

KeyError

Clave no encontrada en un diccionario.

FileNotFoundError

El archivo especificado no existe.

manejo_errores_avanzado.py

# manejo_errores_avanzado.py
def dividir_numeros():
    try:
        num_str = input("Introduce el numerador: ")
        den_str = input("Introduce el denominador: ")
        
        numerador = float(num_str)
        denominador = float(den_str)
        
        if denominador == 0:
            raise ZeroDivisionError("El denominador no puede ser cero (lanzado manualmente).")
            
        resultado = numerador / denominador
        
    except ValueError:
        print("Error: Ambos deben ser números válidos.")
    except ZeroDivisionError as zde:
        print(f"Error de división: {zde}")
    except Exception as e: # Captura genérica para otros errores inesperados
        print(f"Ocurrió un error inesperado: {type(e).__name__} - {e}")
    else:
        print(f"El resultado de la división es: {resultado:.2f}")
    finally:
        print("Bloque 'finally': Esta parte se ejecuta siempre.")

dividir_numeros()
                        

Simulador de Errores

Prueba diferentes escenarios para ver cómo se comporta el manejo de excepciones:

$ python manejo_errores_avanzado.py
Importante

Evita usar bloques except: sin especificar un tipo de excepción (captura genérica) en código de producción, ya que puede capturar errores inesperados y dificultar la depuración. Siempre es mejor capturar tipos específicos de excepciones.

Expresiones Regulares (Regex)

Las expresiones regulares son secuencias de caracteres que definen un patrón de búsqueda. Son increíblemente poderosas para validar datos, buscar y reemplazar texto, y extraer información.

Literales

Caracteres que se buscan a sí mismos.

python busca exactamente "python".

Metacaracteres

Caracteres con significado especial.

. (cualquier caracter), ^ (inicio), $ (fin).

Clases de caracteres

Conjuntos predefinidos de caracteres.

\d (dígito), \w (alfanumérico), \s (espacio).

Cuantificadores

Indican cuántas veces debe aparecer un elemento.

* (0+), + (1+), ? (0-1), {n} (exacto).

validador_correo_regex.py

# validador_correo_regex.py
import re

def validar_correo(email):
    # Patrón simplificado para demostración. Un patrón robusto es más complejo.
    patron = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    if re.fullmatch(patron, email): # fullmatch para coincidencia completa de la cadena
        return True
    else:
        return False

correos_prueba = ["usuario@dominio.com", "test.user+tag@sub.example.co.uk", "invalido", "usuario@dominio", "@dominio.com"]
for correo in correos_prueba:
    if validar_correo(correo):
        print(f"'{correo}' es un correo VÁLIDO.")
    else:
        print(f"'{correo}' es un correo INVÁLIDO.")
                        
$ python validador_correo_regex.py
'usuario@dominio.com' es un correo VÁLIDO.
'test.user+tag@sub.example.co.uk' es un correo VÁLIDO.
'invalido' es un correo INVÁLIDO.
'usuario@dominio' es un correo INVÁLIDO.
'@dominio.com' es un correo INVÁLIDO.

Probador de Expresiones Regulares

Ejercicio Propuesto

Escribe una función que use expresiones regulares para extraer todos los números de teléfono con formato (XXX) XXX-XXXX o XXX-XXX-XXXX de un texto dado.

extractor_telefonos.py

import re

def extraer_telefonos(texto):
    """
    Extrae todos los números de teléfono con formato (XXX) XXX-XXXX o XXX-XXX-XXXX de un texto.
    """
    # Patrón para ambos formatos de teléfono
    patron = r'(\(\d{3}\)\s\d{3}-\d{4}|\d{3}-\d{3}-\d{4})'
    
    # Buscar todas las coincidencias
    telefonos = re.findall(patron, texto)
    
    return telefonos

# Texto de prueba
texto_ejemplo = """
Contactos:
- Juan Pérez: (555) 123-4567
- María García: 789-456-1230
- Carlos López: carlos@example.com
- Ana Martínez: (999) 888-7777
- Teléfono de la oficina: 555-123-4567
- Formato incorrecto: 5551234567
"""

# Extraer y mostrar teléfonos
telefonos_encontrados = extraer_telefonos(texto_ejemplo)
print("Números de teléfono encontrados:")
for telefono in telefonos_encontrados:
    print(f"- {telefono}")
                            

Módulo 4: Bases de Datos y Entornos Gráficos (GUI)

Este módulo final combina el almacenamiento persistente de datos mediante bases de datos con la creación de interfaces de usuario gráficas para hacer las aplicaciones más interactivas y amigables.

Bases de Datos (BD)

Las bases de datos permiten almacenar, organizar y recuperar grandes volúmenes de información de manera eficiente y estructurada.

Relacionales (SQL)

Datos organizados en tablas con filas y columnas, relacionadas mediante claves.

Ejemplos: MySQL, PostgreSQL, SQLite, SQL Server, Oracle.

No Relacionales (NoSQL)

Modelos más flexibles: documentos, clave-valor, grafos, columnares.

Ejemplos: MongoDB, Redis, Cassandra, Neo4j.

Operaciones CRUD

Las operaciones fundamentales que se realizan sobre los datos en una base de datos:

Create (Crear): Insertar nuevos registros en la base de datos.
Read (Leer): Consultar y recuperar información existente.
Update (Actualizar): Modificar registros existentes en la base de datos.
Delete (Eliminar): Borrar registros de la base de datos.
SQLite

SQLite es una base de datos SQL ligera, basada en ficheros, ideal para aplicaciones de escritorio, móviles o para desarrollo y prototipado. No requiere un servidor separado y Python tiene soporte integrado mediante el módulo sqlite3.

crud_sqlite.py

# crud_sqlite.py
import sqlite3

def conectar_bd(nombre_bd="empresa.db"):
    """Conecta a la BD y devuelve la conexión y el cursor."""
    conexion = sqlite3.connect(nombre_bd)
    cursor = conexion.cursor()
    return conexion, cursor

def crear_tabla_empleados(cursor):
    """Crea la tabla de empleados si no existe."""
    cursor.execute("""
    CREATE TABLE IF NOT EXISTS empleados (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        nombre TEXT NOT NULL,
        puesto TEXT,
        salario REAL
    )
    """)
    print("Tabla 'empleados' verificada/creada.")

def insertar_empleado(conexion, cursor, nombre, puesto, salario):
    """Inserta un nuevo empleado."""
    try:
        cursor.execute("INSERT INTO empleados (nombre, puesto, salario) VALUES (?, ?, ?)", 
                       (nombre, puesto, salario))
        conexion.commit() # Guardar cambios
        print(f"Empleado '{nombre}' insertado con ID: {cursor.lastrowid}")
    except sqlite3.Error as e:
        print(f"Error al insertar empleado: {e}")

def consultar_empleados(cursor):
    """Consulta y muestra todos los empleados."""
    print("\n--- Listado de Empleados ---")
    cursor.execute("SELECT id, nombre, puesto, salario FROM empleados")
    for fila in cursor.fetchall():
        print(f"ID: {fila[0]}, Nombre: {fila[1]}, Puesto: {fila[2]}, Salario: ${fila[3]:.2f}")
    print("---------------------------")

# --- Script Principal ---
conn, curr = conectar_bd()
crear_tabla_empleados(curr)

# Insertar algunos datos (ejecutar solo una vez o añadir lógica para evitar duplicados)
# insertar_empleado(conn, curr, "Ana Torres", "Desarrolladora", 60000.0)
# insertar_empleado(conn, curr, "Luis Vera", "Diseñador UX", 55000.0)
# insertar_empleado(conn, curr, "Carlos Solis", "Gerente de Proyecto", 75000.0)

consultar_empleados(curr)

# Cerrar conexión
conn.close()
print("Conexión a la BD cerrada.")
                        
Seguridad en Bases de Datos

Es crucial prevenir la inyección SQL usando consultas parametrizadas (como en el ejemplo (?, ?, ?)) en lugar de formatear cadenas SQL con datos del usuario. La inyección SQL es uno de los vectores de ataque más comunes en aplicaciones web.

Tabla de Empleados

ID Nombre Puesto Salario Acciones
1 Ana Torres Desarrolladora $60,000.00
2 Luis Vera Diseñador UX $55,000.00
3 Carlos Solis Gerente de Proyecto $75,000.00

Entornos Gráficos (GUI)

Una GUI hace que la interacción con la aplicación sea más intuitiva para el usuario final.

Bibliotecas GUI

Python: Tkinter, PyQt, Kivy, Flet.
Java: Swing, JavaFX.
JavaScript: Electron.

Eventos

Las GUIs reaccionan a acciones del usuario (clics, teclas) mediante funciones callback.

Widgets

Componentes visuales como botones, etiquetas, campos, menús.

Layout Managers

Organizan los widgets en la ventana (pack, grid, place en Tkinter).

gui_simple_tkinter.py

# gui_simple_tkinter.py
import tkinter as tk
from tkinter import messagebox

def saludar():
    nombre = campo_nombre.get()
    if nombre:
        messagebox.showinfo("Saludo", f"¡Hola, {nombre}!\nBienvenido a MoriDevEdu.")
    else:
        messagebox.showwarning("Entrada Vacía", "Por favor, introduce tu nombre.")

# Crear la ventana principal
ventana = tk.Tk()
ventana.title("MoriDevEdu GUI Simple")
ventana.geometry("350x150") # Ancho x Alto

# Etiqueta
etiqueta_nombre = tk.Label(ventana, text="Introduce tu nombre:")
etiqueta_nombre.pack(pady=5)

# Campo de entrada
campo_nombre = tk.Entry(ventana, width=30)
campo_nombre.pack(pady=5)

# Botón
boton_saludar = tk.Button(ventana, text="Saludar", command=saludar, bg="#4361ee", fg="white")
boton_saludar.pack(pady=10)

# Iniciar el bucle principal de la GUI
ventana.mainloop()
                        

Demo de Interfaz de Usuario

MoriDevEdu GUI Simple
Introduce tu nombre:

Ejercicio Propuesto

Expande la GUI de Tkinter para incluir campos para "Puesto" y "Salario". Al hacer clic en un botón "Guardar Empleado", los datos ingresados deben insertarse en la tabla empleados de la base de datos SQLite. Muestra un mensaje de confirmación o error.

gestor_empleados_gui.py

# gestor_empleados_gui.py
import tkinter as tk
from tkinter import messagebox, ttk
import sqlite3
import re

class GestorEmpleadosApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Gestor de Empleados - MoriDevEdu")
        self.root.geometry("500x400")
        self.root.configure(padx=20, pady=20)
        
        # Conectar a la BD
        self.conn = sqlite3.connect("empresa.db")
        self.cursor = self.conn.cursor()
        self.crear_tabla_empleados()
        
        # Variables
        self.var_nombre = tk.StringVar()
        self.var_puesto = tk.StringVar()
        self.var_salario = tk.StringVar()
        
        # Marco principal
        main_frame = ttk.Frame(root)
        main_frame.pack(fill="both", expand=True)
        
        # Formulario
        form_frame = ttk.LabelFrame(main_frame, text="Datos del Empleado")
        form_frame.pack(fill="x", padx=10, pady=10)
        
        # Campos del formulario
        ttk.Label(form_frame, text="Nombre:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
        ttk.Entry(form_frame, textvariable=self.var_nombre, width=30).grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(form_frame, text="Puesto:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
        ttk.Entry(form_frame, textvariable=self.var_puesto, width=30).grid(row=1, column=1, padx=5, pady=5)
        
        ttk.Label(form_frame, text="Salario:").grid(row=2, column=0, padx=5, pady=5, sticky="w")
        ttk.Entry(form_frame, textvariable=self.var_salario, width=30).grid(row=2, column=1, padx=5, pady=5)
        
        # Botones
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill="x", padx=10, pady=10)
        
        ttk.Button(btn_frame, text="Guardar Empleado", command=self.guardar_empleado).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="Limpiar Campos", command=self.limpiar_campos).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="Ver Empleados", command=self.mostrar_empleados).pack(side="left", padx=5)
        
        # Tabla de empleados
        self.tree = ttk.Treeview(main_frame, columns=("ID", "Nombre", "Puesto", "Salario"), show="headings")
        self.tree.pack(fill="both", expand=True, padx=10, pady=10)
        
        # Configurar columnas
        self.tree.heading("ID", text="ID")
        self.tree.heading("Nombre", text="Nombre")
        self.tree.heading("Puesto", text="Puesto")
        self.tree.heading("Salario", text="Salario")
        
        self.tree.column("ID", width=50)
        self.tree.column("Nombre", width=150)
        self.tree.column("Puesto", width=150)
        self.tree.column("Salario", width=100)
        
        # Cargar datos iniciales
        self.cargar_empleados()
    
    def crear_tabla_empleados(self):
        self.cursor.execute("""
        CREATE TABLE IF NOT EXISTS empleados (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nombre TEXT NOT NULL,
            puesto TEXT,
            salario REAL
        )
        """)
        self.conn.commit()
    
    def validar_datos(self):
        nombre = self.var_nombre.get().strip()
        puesto = self.var_puesto.get().strip()
        salario = self.var_salario.get().strip()
        
        if not nombre:
            messagebox.showerror("Error", "El nombre es obligatorio.")
            return False
        
        if not puesto:
            messagebox.showerror("Error", "El puesto es obligatorio.")
            return False
        
        if not salario:
            messagebox.showerror("Error", "El salario es obligatorio.")
            return False
        
        # Validar que salario sea un número
        if not re.match(r'^\d+(\.\d+)?$', salario):
            messagebox.showerror("Error", "El salario debe ser un número válido.")
            return False
        
        return True
    
    def guardar_empleado(self):
        if not self.validar_datos():
            return
        
        nombre = self.var_nombre.get().strip()
        puesto = self.var_puesto.get().strip()
        salario = float(self.var_salario.get().strip())
        
        try:
            self.cursor.execute("INSERT INTO empleados (nombre, puesto, salario) VALUES (?, ?, ?)", 
                               (nombre, puesto, salario))
            self.conn.commit()
            
            messagebox.showinfo("Éxito", f"Empleado '{nombre}' guardado correctamente.")
            self.limpiar_campos()
            self.cargar_empleados()
        except sqlite3.Error as e:
            messagebox.showerror("Error en la Base de Datos", f"No se pudo guardar el empleado: {e}")
    
    def limpiar_campos(self):
        self.var_nombre.set("")
        self.var_puesto.set("")
        self.var_salario.set("")
    
    def cargar_empleados(self):
        # Limpiar tabla
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # Cargar datos de la BD
        self.cursor.execute("SELECT id, nombre, puesto, salario FROM empleados ORDER BY id DESC")
        for row in self.cursor.fetchall():
            self.tree.insert("", "end", values=(row[0], row[1], row[2], f"${row[3]:,.2f}"))
    
    def mostrar_empleados(self):
        self.cargar_empleados()
    
    def __del__(self):
        if hasattr(self, 'conn') and self.conn:
            self.conn.close()

# Iniciar aplicación
if __name__ == "__main__":
    root = tk.Tk()
    app = GestorEmpleadosApp(root)
    root.mainloop()
                            
Fin del curso