📘 Programación Orientada a Objetos

Universidad Tecnológica de Hermosillo  |  TIID  |  Docente: Bernardo Prado Díaz  |  Mayo–Agosto 2026

🎓 3° Cuatrimestre ⚡ 59 Horas 🐍 Python + MySQL

🎓 Bienvenido al Curso de POO

Esta guía digital cubre los 4 bloques de la asignatura de Programación Orientada a Objetos con explicaciones paso a paso, ejemplos reales de negocios, referencias de sintaxis y guías de instalación. Todo asumiendo que empiezas desde cero.

📅 Mayo–Agosto 2026 🏫 UTH – TIID 👨‍🏫 Bernardo Prado Díaz 💻 Python & MySQL
📐

Unidad 1 – Modelado

Conceptos POO, diagramas UML, patrones de diseño, Draw.io, PyCharm.

14 hrs • 10% de la calificación
🐍

Unidad 2 – Sintaxis POO

Python orientado a objetos completo: clases, herencia, polimorfismo, encapsulamiento.

28 hrs • 10% de la calificación
🎨

Unidad 3 – FLET + MySQL

Interfaces gráficas con FLET, conexión a MySQL, principios SOLID, clean code.

28 hrs • 10% de la calificación
🚀

Unidad 4 – IDE, Git y Proyecto

PyCharm avanzado, Git, manejo de excepciones, pruebas unitarias y proyecto final.

35 hrs • 10% de la calificación

🛠️ Herramientas que usaremos

🐍
Python

Lenguaje principal del curso. Descarga desde python.org

💡
PyCharm Community Edition

IDE profesional para Python. Descarga en jetbrains.com/pycharm

📊
Draw.io

Diagramas UML online. Entra en app.diagrams.net — sin instalación.

🐬
WampServer 3.3.7

Servidor MySQL local para Windows. Descarga en wampserver.com

🦦
DBeaver 25.1.0

Editor visual de bases de datos. Descarga en dbeaver.io

🎨
FLET (librería Python)

Librería para GUIs modernas. Se instala con: pip install flet

🔀
Git

Control de versiones. Descarga en git-scm.com

💡 Tip inicial Si ves un error en Python que dice "command not found", reinicia tu computadora después de instalar Python y asegúrate de haber marcado "Add Python to PATH" durante la instalación.

📐 Unidad 1 — Modelado Orientado a Objetos

Aprenderás los conceptos fundamentales de la Programación Orientada a Objetos (POO), cómo representarlos con diagramas UML y los patrones de diseño más usados en la industria.

⏱ 17 horas📊 Exámen y Prácticas en clase - Calificación🛠 Draw.io + PyCharm

📋 Temas de la Unidad

🏛️

1.1 Conceptos POO

Clases, objetos, atributos, métodos, herencia, polimorfismo, encapsulamiento, abstracción.

📊

1.2 Diagramas UML

Casos de uso, clases y secuencia usando Draw.io.

🧩

1.3 Patrones de Diseño

Singleton, Factory Method y Observer.

🗺️ Mapa Conceptual — Unidad 1: Del mundo real al código
🌍 Mundo Real
🏨 Hotel Fiesta Inn
🛏️ Habitaciones
👤 Clientes
📋 Reservaciones
modelar con
POO
🐍 Código Python
📦 class Habitacion
📦 class Cliente
📦 class Reservacion
+ herencia, métodos
documentar con
UML
📊 Diagramas UML
👤 Casos de Uso
📦 Clases
📋 Secuencia
en Draw.io
soluciones con
Patrones
🧩 Design Patterns
🔂 Singleton
🏭 Factory
👁️ Observer

🔧 Guía de Instalación: Python y PyCharm

Descargar Python

Ve a python.org/downloads y haz clic en el botón amarillo "Download Python 3.x.x". Se descargará un archivo .exe.

Instalar Python — ¡IMPORTANTE!

Ejecuta el instalador. En la primera pantalla, marca la casilla "Add Python to PATH" antes de hacer clic en "Install Now". Esto es crucial para que Python funcione desde cualquier lugar.

Verificar instalación

Abre CMD (tecla Windows + R, escribe cmd, Enter) y escribe:

CMD
python --version

Deberías ver algo como Python 3.12.x. Si aparece error, reinicia y repite la instalación marcando PATH.

Descargar PyCharm Community

Ve a jetbrains.com/pycharm/download. Selecciona Community Edition (es gratuita). Descarga el instalador Windows.

Instalar PyCharm

Ejecuta el instalador. En "Installation Options" marca:

  • ✅ Create Desktop Shortcut
  • ✅ Add "Open Folder as Project"
  • ✅ Add launchers dir to PATH
Crear tu primer proyecto

Abre PyCharm → New Project → ponle nombre (ej: poo-unidad1) → asegúrate de que el intérprete sea el Python que instalaste → clic Create.

Configurar Draw.io (sin instalación)

Entra a app.diagrams.net en tu navegador. Elige "Device" para guardar en tu computadora. ¡Listo, no se instala nada!

🏛️ Tema 1.1 — Conceptos Fundamentales de POO

🤔 ¿Qué es la POO? Es una forma de programar que organiza el código igual que el mundo real: en objetos. Un objeto tiene características (atributos) y puede hacer cosas (métodos). Por ejemplo, un Carro tiene color, marca y puede arrancar, frenar.

Los 4 pilares de la POO

🎭

1. Abstracción

Mostrar solo lo necesario y ocultar los detalles complejos. Como un control remoto: presionas un botón y funciona, sin saber la electrónica detrás.

🔒

2. Encapsulamiento

Esconder los datos internos del objeto. Solo accedes a ellos mediante métodos controlados. Como la caja negra de un avión: no ves el mecanismo interno, solo el resultado.

🧬

3. Herencia

Una clase puede heredar atributos y métodos de otra clase padre. Como que un Empleado hereda de Persona (nombre, edad) y agrega su propio sueldo.

🎪

4. Polimorfismo

El mismo método puede comportarse diferente según el objeto. calcular_pago() funciona distinto para un empleado fijo que para un freelance.

📝 Referencia de Sintaxis — Clases en Python

ConceptoSintaxis PythonDescripción
Definir claseclass NombreClase:Crea un molde (plantilla) de objetos
Constructordef __init__(self, param):Se ejecuta al crear un objeto. self = el propio objeto
Atributoself.nombre = nombreGuarda un dato dentro del objeto
Métododef mi_metodo(self):Función que pertenece a la clase
Crear objetoobj = NombreClase(args)Instancia un objeto de la clase
Usar objetoobj.metodo()Llama a un método del objeto
Herenciaclass Hijo(Padre):Hijo hereda todo de Padre
Llamar padresuper().__init__()Llama al constructor del padre
Atrib. privadoself.__dato = xSolo accesible dentro de la clase
Atrib. protegidoself._dato = xConvención: no tocar desde afuera
🏨 Ejemplo de Negocio: Sistema de Reservaciones de Hotel

Modelaremos un hotel con POO. El hotel tiene Habitaciones (número, tipo, precio) y Clientes (nombre, email). Una Reservación conecta ambos con fechas de entrada y salida.

🖼️ La Clase como Molde — Un molde, múltiples objetos
📦 CLASE: Habitacion
─── Atributos ───
• numero: int
• tipo: str
• precio_noche: float
• disponible: bool
─── Métodos ───
+ reservar() → str
+ liberar() → str
+ __str__() → str
⚙️
instanciar
hab101 = Habitacion(101, "Suite", 1200)
hab102 = Habitacion(102, "Doble", 650)
hab103 = Habitacion(103, "Sencilla", 400)
📦 CLASE: Cliente
• nombre: str
• email: str
+ agregar_reservacion()
↔️
📦 CLASE: Reservacion
• cliente: Cliente
• habitacion: Habitacion
+ calcular_costo()
+ confirmar()
↔️
📦 CLASE: Habitacion
• numero: int
• precio_noche: float
+ reservar()
💡 Analogía: Como el plano arquitectónico de las habitaciones del Hotel Fiesta Inn de Hermosillo — un plano, muchas habitaciones reales
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
Que una clase es un molde reutilizable: definimos Habitacion, Cliente y Reservacion como plantillas, y luego creamos múltiples objetos de cada una.
🚀 ¿QUÉ LOGRAMOS?
Un sistema funcional de reservaciones de hotel donde los objetos interactúan entre sí: la reserva confirma la habitación y se agrega al cliente automáticamente.
🧠 ¿QUÉ APRENDEMOS?
Definir clases con __init__, crear objetos, usar self, escribir métodos, llamar métodos de un objeto desde otro, y usar __str__ para imprimir.
hotel_modelo.py — Ejemplo de negocio: Hotel
# ═══════════════════════════════════════════════════════
# SISTEMA DE HOTEL — Unidad 1, ejemplo de negocio
# Aprende a leer: cada línea tiene su explicación ↓
# ═══════════════════════════════════════════════════════

class Habitacion:
    """
    Esta es la CLASE Habitacion.
    Una clase es como un MOLDE para crear objetos.
    Imagina que el molde define cómo es cada habitación.
    """

    def __init__(self, numero, tipo, precio_noche):
        # __init__ es el CONSTRUCTOR: se ejecuta al crear cada habitación.
        # 'self' representa a ESTA habitación específica (el objeto).
        # Los parámetros son datos que recibimos al crear la habitación.
        self.numero = numero              # Guardamos el número (ej: 101)
        self.tipo = tipo                  # Guardamos el tipo (ej: "suite")
        self.precio_noche = precio_noche  # Precio por noche (ej: 850.0)
        self.disponible = True           # Al inicio, siempre disponible

    def reservar(self):
        # Este es un MÉTODO: una acción que puede hacer la habitación.
        if self.disponible:
            self.disponible = False      # Marcamos como NO disponible
            return f"✅ Habitación {self.numero} reservada con éxito."
        else:
            return f"❌ Habitación {self.numero} ya está ocupada."

    def liberar(self):
        self.disponible = True
        return f"🔓 Habitación {self.numero} disponible nuevamente."

    def __str__(self):
        # __str__ define cómo se muestra el objeto cuando usamos print()
        estado = "Disponible" if self.disponible else "Ocupada"
        return f"Hab. {self.numero} | {self.tipo} | ${self.precio_noche}/noche | {estado}"


class Persona:
    # CLASE BASE (padre). Contiene lo que tienen en común clientes y empleados.
    def __init__(self, nombre, email):
        self.nombre = nombre
        self.email = email


class Cliente(Persona):
    """
    HERENCIA: Cliente HEREDA de Persona.
    Escrito como: class Cliente(Persona)
    Esto significa que Cliente tiene TODO lo de Persona
    (nombre y email), MÁS sus propias cosas.
    """
    def __init__(self, nombre, email, telefono):
        super().__init__(nombre, email)  # Llama al constructor de Persona
        self.telefono = telefono           # Agrega atributo propio
        self.reservaciones = []            # Lista vacía de reservaciones

    def agregar_reservacion(self, reservacion):
        self.reservaciones.append(reservacion)


class Reservacion:
    def __init__(self, cliente, habitacion, fecha_entrada, fecha_salida):
        self.cliente = cliente
        self.habitacion = habitacion
        self.fecha_entrada = fecha_entrada
        self.fecha_salida = fecha_salida

    def calcular_costo(self, noches):
        return self.habitacion.precio_noche * noches

    def confirmar(self):
        resultado = self.habitacion.reservar()   # Llama al método de Habitacion
        self.cliente.agregar_reservacion(self)   # Agrega a la lista del cliente
        return resultado


# ══════════════════ PROGRAMA PRINCIPAL ══════════════════
# Aquí creamos objetos (instancias) de nuestras clases

hab101 = Habitacion(101, "Suite Ejecutiva", 1200.0)
hab102 = Habitacion(102, "Doble Estándar", 650.0)

cliente1 = Cliente("Ana García", "ana@mail.com", "662-100-2000")

reserva = Reservacion(cliente1, hab101, "2026-06-01", "2026-06-04")
print(reserva.confirmar())                 # ✅ Habitación 101 reservada
print(hab101)                              # Muestra estado de la habitación
print(f"Costo: ${reserva.calcular_costo(3)}")  # Costo por 3 noches

📊 Tema 1.2 — Diagramas UML con Draw.io

⚠️ ¿Por qué UML? UML (Unified Modeling Language) es como el plano de una construcción: antes de escribir código, dibujamos cómo se verá la estructura del programa. Los reclutadores de empresas te pedirán diagramas UML.

Tipos de diagramas que usaremos

👤

Casos de Uso

Muestra quién usa el sistema (actores) y qué puede hacer (casos). Se dibujan con óvalos y actores palito.

📦

Diagrama de Clases

Muestra las clases, sus atributos, métodos y las relaciones entre ellas (herencia, composición, asociación).

📋

Diagrama de Secuencia

Muestra cómo interactúan los objetos en el tiempo. Perfecto para entender el flujo de una operación.

Pasos para crear diagrama de clases en Draw.io

Abrir Draw.io

Ve a app.diagrams.net → "Create New Diagram" → selecciona plantilla en blanco.

Agregar una clase

En la barra de búsqueda izquierda busca "Class". Arrastra el símbolo de clase al lienzo. Doble clic para editar el nombre.

Agregar atributos

En la segunda sección del rectángulo escribe los atributos: - numero: int, - precio: float. El signo - = privado, + = público.

Agregar métodos

En la tercera sección: + reservar(): str, + liberar(): void.

Conectar clases

Para herencia: dibuja una flecha con triángulo hueco desde la clase hija a la clase padre. Para asociación: línea simple con etiqueta.

Exportar

File → Export as → PNG (para incluir en documentos) o XML (para editar después).

🧩 Tema 1.3 — Patrones de Diseño

💡 ¿Qué es un patrón de diseño? Son soluciones probadas a problemas comunes de programación. En lugar de inventar soluciones desde cero, usas recetas que ya funcionan en miles de proyectos.

Patrón Singleton — "Solo existe uno"

Útil cuando solo debe existir UN objeto de una clase. Ejemplo: la configuración del sistema.

🖼️ Patrón Singleton — Solo puede existir UNA instancia
⛔ cfg1 = ConfiguracionHotel()
⛔ cfg2 = ConfiguracionHotel()
🏨 UNA sola instancia
cfg1 is cfg2
→ True ✅
Mismo objeto en memoria
💡 Analogía: Como el Director General del Hotel UTH Grand — solo puede haber uno en toda la empresa
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El patrón Singleton: que sin importar cuántas veces llames a ConfiguracionHotel(), Python siempre devuelve el mismo objeto único en memoria.
🚀 ¿QUÉ LOGRAMOS?
Una clase de configuración del hotel que garantiza que cfg1 is cfg2 devuelve True — ambas variables apuntan al mismo objeto.
🧠 ¿QUÉ APRENDEMOS?
Cómo funciona __new__ en Python (se llama antes de __init__), y cuándo usar el Singleton: configuraciones, conexiones a BD, logs del sistema.
patron_singleton.py
class ConfiguracionHotel:
    """
    PATRÓN SINGLETON: garantiza que solo exista
    UNA instancia de esta clase en todo el programa.
    Útil para configuraciones, conexiones a BD, etc.
    """
    _instancia = None   # Almacena la única instancia (empieza vacía)

    def __new__(cls):
        # __new__ se llama ANTES de __init__, al crear el objeto.
        # Si no existe instancia, la creamos; si ya existe, devolvemos la misma.
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
        return cls._instancia

    def __init__(self):
        self.nombre_hotel = "Hotel UTH Grand"
        self.ciudad = "Hermosillo, Sonora"
        self.max_habitaciones = 50

# Probamos que solo hay UNA instancia:
cfg1 = ConfiguracionHotel()
cfg2 = ConfiguracionHotel()
print(cfg1 is cfg2)   # True — ¡son el mismo objeto!
print(cfg1.nombre_hotel)

Patrón Factory — "Fábrica de objetos"

🖼️ Patrón Factory — Una fábrica que crea objetos según el tipo
HabitacionFactory.crear("suite")
🛏️ Suite Ejecutiva $1,500
HabitacionFactory.crear("doble")
🛏️🛏️ Doble Estándar $700
💡 Analogía: Como la cocina de un restaurante de Hermosillo — pides "hamburguesa" y la cocina sabe exactamente cómo prepararla
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El patrón Factory: delegar la creación de objetos a una clase especializada, en lugar de crear objetos directamente con Habitacion(...).
🚀 ¿QUÉ LOGRAMOS?
Una fábrica de habitaciones: pasas el tipo ("suite", "doble", "sencilla") y obtienes un objeto ya configurado con sus valores correctos.
🧠 ¿QUÉ APRENDEMOS?
Cómo usar @staticmethod para métodos que no necesitan self, y el principio de encapsular la lógica de creación para que el resto del código sea más limpio.
patron_factory.py
class HabitacionFactory:
    """
    PATRÓN FACTORY: una clase que se encarga de CREAR otros objetos.
    En lugar de crear objetos directamente con Habitacion(...),
    le pedimos a la fábrica que lo haga según el tipo.
    """
    @staticmethod
    def crear(tipo):
        # Según el tipo, creamos y configuramos la habitación
        if tipo == "suite":
            return Habitacion(0, "Suite Ejecutiva", 1500.0)
        elif tipo == "doble":
            return Habitacion(0, "Doble Estándar", 700.0)
        elif tipo == "sencilla":
            return Habitacion(0, "Sencilla", 450.0)
        else:
            raise ValueError(f"Tipo desconocido: {tipo}")

# Uso: le pedimos a la fábrica
suite = HabitacionFactory.crear("suite")
doble = HabitacionFactory.crear("doble")
print(suite)
print(doble)

📋 Criterios de Evaluación — Unidad 1

CriterioEstratégicoAutónomoBásicoReceptivo
Orden/OrganizaciónCódigo impecable, comentado, estructura profesionalBien organizado con comentarios claveOrganizado pero sin comentariosDesorganizado o incompleto
Diagramas UMLDiagramas completos, correctos y bien etiquetadosDiagramas correctos con algunos detalles faltantesDiagramas básicos incompletosIntento de diagrama incorrecto
Conceptos POODomina todos los pilares y los aplica correctamenteAplica correctamente la mayoría de conceptosAplica algunos conceptos básicosConfunde los conceptos
Patrones DiseñoImplementa y explica Singleton, Factory, ObserverImplementa correctamente 2 patronesImplementa 1 patrón con errores menoresNo implementa o implementa incorrectamente
🎬

Videos de Apoyo — Unidad 1: Modelado Orientado a Objetos

Videos seleccionados para reforzar los conceptos de esta unidad. Míralos después de clase para afianzar lo aprendido.

POO
¿Qué es la POO? — Explicación Visual

Introducción a clases, objetos y los 4 pilares con animaciones didácticas

🕐 ~15 minES✓ Recomendado
UML
Diagramas UML — Clases y Relaciones paso a paso

Cómo dibujar diagramas de clases en Draw.io desde cero

🕐 ~20 minES✓ Recomendado
Patrones
Patrones de Diseño — Singleton y Factory

Implementación práctica de los patrones más usados en la industria

🕐 ~25 minES✓ Recomendado
Python
Python desde Cero — Instalación y Primer Programa

Instala Python y PyCharm, ejecuta tu primer script paso a paso

🕐 ~18 minES✓ Recomendado
🏛 4 Pilares
Los 4 Pilares de la POO — Explicación Completa

Abstracción, Encapsulamiento, Herencia y Polimorfismo explicados con ejemplos visuales y código Python

🕐 ~22 minES✓ Recomendado
🏛 VS Code
Ejecutar Python en Visual Studio Code — Explicación Completa

Aprende a configurar y ejecutar Python en Visual Studio Code paso a paso

🕐 ~22 minES✓ Recomendado
🎥

Videos de Clase — Unidad 1

Grabaciones de las sesiones presenciales. Si faltaste o quieres repasar, estas grabaciones son tu mejor aliado. Se actualizan después de cada clase.

Sesión 1
Clase 1 — Introducción a POO y Python

Conceptos básicos: clases, objetos, atributos, métodos

S1📅 Por confirmar🕐 Próximamente
Sesión 2
Clase 2 — Los 4 Pilares de la POO

Abstracción, Encapsulamiento, Herencia, Polimorfismo con ejemplos

S2📅 Por confirmar🕐 Próximamente
Sesión 3
Clase 3 — Diagramas UML con Draw.io

Diagramas de clases, casos de uso y secuencia

S3📅 Por confirmar🕐 Próximamente
Sesión 4
Clase 4 — Patrones de Diseño: Singleton, Factory, Observer

Implementación y casos de uso de los 3 patrones del curso

S4📅 Por confirmar🕐 Próximamente
💡 ¿Cómo aprovecharlos? Pausa el video cada vez que el profesor escribe código, escríbelo tú mismo y luego compara. Los errores que cometes y resuelves son los que más aprendes.

🐍 Unidad 2 — Sintaxis y Documentación POO en Python

Aprenderás la sintaxis completa de Python orientado a objetos: clases, objetos, herencia múltiple, polimorfismo, encapsulamiento y manejo de eventos con ejemplos de negocios reales.

⏱ 14 horas📊 Exámen y Prácticas en clase - Calificación🛠 PyCharm + Python

📋 Temas de la Unidad

🏛️ LOS 4 PILARES DE LA POO — Orden de Aprendizaje

🎭
PILAR 1
Abstracción
Define QUÉ debe hacer un objeto
🔒
PILAR 2
Encapsulamiento
Protege los datos internos
🧬
PILAR 3
Herencia
Reutiliza y extiende clases
🎪
PILAR 4
Polimorfismo
Un método, múltiples formas

💡 Cada pilar se apoya en el anterior. No saltes pasos — el orden importa.

🧱

2.1 Clases y Objetos

Fundamento de la POO: definición, constructores, atributos de clase vs instancia.

🎭

2.2 Abstracción

Pilar 1 — Clases abstractas, ABC, métodos abstractos. Define el contrato.

🔒

2.3 Encapsulamiento

Pilar 2 — Atributos privados, getters, setters, @property.

🧬

2.4 Herencia

Pilar 3 — Herencia simple y múltiple, super(), reutilización de código.

🎪

2.5 Polimorfismo

Pilar 4 — Override, duck typing, mismo método con diferente comportamiento.

📖

2.6 Documentación

Docstrings, comentarios profesionales, pydoc.

📝 Referencia Completa — Sintaxis de Clases

ElementoSintaxisDescripción
Clase simpleclass Producto:Clase sin herencia
Clase con herenciaclass Ropa(Producto):Hereda de Producto
Constructordef __init__(self, n, p):Se llama al crear el objeto
Atributo de claseiva = 0.16 (fuera de __init__)Compartido por todos los objetos
Atributo de instanciaself.nombre = nÚnico para cada objeto
Método regulardef calcular(self):Necesita self
Método de clase@classmethod / def m(cls):Accede a atributos de clase
Método estático@staticmethod / def m():No necesita self ni cls
Propiedad@property / def precio(self):Accede como atributo
Setter@precio.setterValida al asignar valor
Reprdef __repr__(self):Representación técnica del objeto
Comparacióndef __eq__(self, otro):Define cuándo dos objetos son iguales
Longituddef __len__(self):Define len(objeto)
🏪 Ejemplo de Negocio: Sistema de Inventario — Tienda de Ropa

Modelaremos un inventario para una tienda de ropa de Hermosillo. Tendremos Productos con categorías (Ropa, Calzado, Accesorios) usando herencia. Cada producto calcula su precio con IVA usando encapsulamiento y propiedades.

🖼️ Diagrama de Herencia — Jerarquía de Clases del Inventario
📦 CLASE BASE: Producto
• nombre, __precio_base, __stock
• iva = 0.16 (atributo de clase)
+ precio_con_iva() + vender() + descripcion()
┬───────────┬
👔 CLASE HIJA: Ropa
+ talla, color
+ descripcion() ← sobreescribe
👟 CLASE HIJA: Calzado
+ numero, material
+ descripcion() ← sobreescribe
💡 Analogía: Como en "Moda Hermosillo" — todos los productos comparten precio e IVA, pero ropa tiene talla y calzado tiene número
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
Los 4 pilares en un solo sistema: herencia (Ropa/Calzado heredan de Producto), encapsulamiento (__precio_base), polimorfismo (descripcion()), abstracción (IVA como atributo de clase).
🚀 ¿QUÉ LOGRAMOS?
Un inventario real para "Moda Hermosillo" con productos de distintos tipos, catálogo en pantalla y operaciones de venta con validación de stock.
🧠 ¿QUÉ APRENDEMOS?
Cómo combinar herencia con super(), usar @property y setters para encapsular datos, y aprovechar el polimorfismo para que el mismo bucle for funcione con cualquier tipo de producto.
inventario_tienda.py — Sistema de Inventario POO completo
# ═════════════════════════════════════════════════════════════════
# SISTEMA DE INVENTARIO — Tienda de Ropa "Moda Hermosillo"
# Unidad 2: Herencia, Encapsulamiento, Polimorfismo
# ═════════════════════════════════════════════════════════════════

class Producto:
    """
    CLASE BASE para todos los productos de la tienda.
    Define lo que tienen en común: nombre, precio, stock.
    """
    iva = 0.16   # Atributo de CLASE: compartido por todos los productos

    def __init__(self, nombre, precio_base, stock):
        self.nombre = nombre
        self.__precio_base = precio_base   # __ = privado (encapsulado)
        self.__stock = stock               # __ = privado

    # ── PROPIEDADES (acceso controlado a atributos privados) ──

    @property
    def precio_base(self):
        # @property permite acceder como atributo: producto.precio_base
        return self.__precio_base

    @precio_base.setter
    def precio_base(self, valor):
        # El setter VALIDA el dato antes de guardarlo
        if valor < 0:
            raise ValueError("El precio no puede ser negativo.")
        self.__precio_base = valor

    @property
    def stock(self):
        return self.__stock

    def precio_con_iva(self):
        # Calcula el precio final sumando el IVA
        return self.__precio_base * (1 + self.iva)

    def vender(self, cantidad):
        # Verifica stock antes de vender
        if cantidad > self.__stock:
            return f"❌ Stock insuficiente. Solo hay {self.__stock} unidades."
        self.__stock -= cantidad
        return f"✅ Venta de {cantidad} '{self.nombre}' registrada. Stock: {self.__stock}"

    def descripcion(self):
        # Método que CADA subclase puede reemplazar (polimorfismo)
        return f"Producto genérico: {self.nombre}"

    def __str__(self):
        return (
            f"📦 {self.nombre}\n"
            f"   Precio base: ${self.__precio_base:.2f}\n"
            f"   Precio con IVA: ${self.precio_con_iva():.2f}\n"
            f"   Stock: {self.__stock} unidades"
        )


class Ropa(Producto):
    """
    SUBCLASE de Producto. Agrega talla y color.
    Hereda TODO de Producto y le añade sus propias cosas.
    """
    def __init__(self, nombre, precio_base, stock, talla, color):
        super().__init__(nombre, precio_base, stock)  # Inicializa la parte de Producto
        self.talla = talla
        self.color = color

    def descripcion(self):
        # SOBREESCRITURA del método (polimorfismo): Ropa tiene su propia descripción
        return f"👔 Ropa: {self.nombre} | Talla: {self.talla} | Color: {self.color}"


class Calzado(Producto):
    def __init__(self, nombre, precio_base, stock, numero, material):
        super().__init__(nombre, precio_base, stock)
        self.numero = numero
        self.material = material

    def descripcion(self):
        return f"👟 Calzado: {self.nombre} | No.{self.numero} | {self.material}"


class Inventario:
    """
    Clase que CONTIENE una lista de productos (Composición).
    El inventario tiene productos, no hereda de ellos.
    """
    def __init__(self, nombre_tienda):
        self.nombre_tienda = nombre_tienda
        self.productos = []

    def agregar(self, producto):
        self.productos.append(producto)
        print(f"➕ '{producto.nombre}' añadido al inventario.")

    def mostrar_catalogo(self):
        print(f"\n{'='*50}")
        print(f"🏪 {self.nombre_tienda} — Catálogo")
        print(f"{'='*50}")
        for p in self.productos:
            print(p.descripcion())  # POLIMORFISMO: cada p llama su propia descripcion()
            print(f"   Precio con IVA: ${p.precio_con_iva():.2f}")


# ══════════════════ PROGRAMA PRINCIPAL ══════════════════
inv = Inventario("Moda Hermosillo")

inv.agregar(Ropa("Playera Polo", 250.0, 30, "M", "Azul marino"))
inv.agregar(Ropa("Jeans Skinny", 480.0, 15, "28", "Negro"))
inv.agregar(Calzado("Tenis Running", 890.0, 10, 27, "Sintético"))

inv.mostrar_catalogo()

# Probamos venta:
print("\n🛒 Realizando venta...")
print(inv.productos[0].vender(3))   # Vende 3 playeras
print(inv.productos[0].vender(50))  # Intenta vender 50 (stock insuficiente)

🔒 Tema 2.3 — Encapsulamiento Detallado

🔑 Regla de Encapsulamiento en Python
self.datopúblico: cualquiera puede leerlo y modificarlo
self._datoprotegido: convención, no modificar desde afuera
self.__datoprivado: Python lo "oculta" (name mangling), usa property para acceder
🖼️ Encapsulamiento — Los datos privados solo se tocan con métodos controlados
🏦 CuentaBancaria
🔒 PRIVADO (nadie accede directo)
• __saldo = 5000
• __historial = []
✅ PÚBLICO (interfaz controlada)
+ saldo → @property
+ depositar(monto)
+ retirar(monto)
❌ cuenta.__saldo = 999999 → Error
✅ cuenta.depositar(2000) → OK
✅ cuenta.saldo → 7000
💡 Analogía: Como un cajero automático — puedes sacar dinero con tu tarjeta, pero no puedes abrir la máquina y agarrar los billetes directamente
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El encapsulamiento real: los atributos privados (__saldo, __historial) no se pueden modificar desde afuera — solo mediante métodos que validan la operación.
🚀 ¿QUÉ LOGRAMOS?
Una cuenta bancaria segura: los depósitos y retiros validan el monto antes de ejecutarse, y el historial se lleva automáticamente sin que nadie lo pueda borrar.
🧠 ¿QUÉ APRENDEMOS?
La diferencia entre atributos públicos, protegidos y privados, cómo usar @property para leer datos privados de forma segura, y cómo el setter valida antes de guardar.
encapsulamiento.py — Cuenta Bancaria con validación
class CuentaBancaria:
    def __init__(self, titular, saldo_inicial):
        self.titular = titular            # Público
        self.__saldo = saldo_inicial      # Privado — nadie lo toca directamente
        self.__historial = []             # Privado — registro de movimientos

    @property
    def saldo(self):
        # Getter: permite LEER el saldo de forma controlada
        return self.__saldo

    def depositar(self, monto):
        if monto <= 0:
            return "❌ Monto debe ser mayor a 0."
        self.__saldo += monto
        self.__historial.append(f"+${monto}")
        return f"✅ Depósito de ${monto}. Saldo: ${self.__saldo}"

    def retirar(self, monto):
        if monto > self.__saldo:
            return "❌ Fondos insuficientes."
        self.__saldo -= monto
        self.__historial.append(f"-${monto}")
        return f"✅ Retiro de ${monto}. Saldo: ${self.__saldo}"

    def ver_historial(self):
        return self.__historial

# Prueba:
cuenta = CuentaBancaria("Juan López", 5000)
print(cuenta.depositar(2000))
print(cuenta.retirar(800))
print(f"Saldo actual: ${cuenta.saldo}")  # Usamos la property
# cuenta.__saldo = 999999  ← ERROR: no se puede acceder directamente

🎭 Polimorfismo — Mismo método, diferente comportamiento

🖼️ Polimorfismo — El mismo método procesar(), tres comportamientos distintos
🔴 MetodoPago (base)
procesar(monto)
← define el contrato
💵 Efectivo: "Pago en efectivo de $350"
💳 Tarjeta: "Cargo a tarjeta ****4521"
🏦 BBVA: "Transferencia vía BBVA"
def cobrar(pago, total): # No sabe el tipo exacto
  pago.procesar(total) # Cada uno responde diferente
💡 Analogía: Como en una ferretería de Hermosillo — "cobrar" funciona igual con efectivo, tarjeta o transferencia; la caja no cambia su proceso
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El polimorfismo: la función cobrar() no necesita saber si el pago es efectivo, tarjeta o transferencia — simplemente llama procesar() y cada objeto responde diferente.
🚀 ¿QUÉ LOGRAMOS?
Un sistema de pagos donde puedes agregar nuevas formas de pago (ej: PayPal, CoDi) sin cambiar la función cobrar() — solo creas una nueva subclase.
🧠 ¿QUÉ APRENDEMOS?
Cómo sobreescribir métodos en subclases (override), el concepto de "duck typing" de Python, y por qué el polimorfismo hace el código más flexible y extensible.
polimorfismo.py — Sistema de Pagos
class MetodoPago:
    # Clase abstracta base para métodos de pago
    def procesar(self, monto):
        raise NotImplementedError("Cada pago debe implementar procesar()")

class PagoEfectivo(MetodoPago):
    def procesar(self, monto):
        return f"💵 Pago en efectivo de ${monto:.2f} recibido."

class PagoTarjeta(MetodoPago):
    def __init__(self, ultimos4):
        self.ultimos4 = ultimos4
    def procesar(self, monto):
        return f"💳 Cargo de ${monto:.2f} a tarjeta ****{self.ultimos4}."

class PagoTransferencia(MetodoPago):
    def __init__(self, banco):
        self.banco = banco
    def procesar(self, monto):
        return f"🏦 Transferencia de ${monto:.2f} vía {self.banco}."

# POLIMORFISMO en acción:
# La función no necesita saber QUÉ tipo de pago es, solo llama procesar()
def cobrar(pago: MetodoPago, total: float):
    resultado = pago.procesar(total)   # Cada objeto responde diferente
    print(resultado)

cobrar(PagoEfectivo(), 350.00)
cobrar(PagoTarjeta("4521"), 1280.50)
cobrar(PagoTransferencia("BBVA"), 9500.00)

🎭 Tema 2.4 — Abstracción

🧠 ¿Qué es la Abstracción?
La abstracción consiste en definir QUÉ debe hacer una clase sin especificar CÓMO lo hace. Es como un contrato: la clase abstracta exige a sus subclases que implementen ciertos métodos. En Python se logra con el módulo abc (Abstract Base Classes).
ElementoSintaxisDescripción
Importar ABCfrom abc import ABC, abstractmethodMódulo estándar de Python para abstracción
Clase abstractaclass Animal(ABC):No se puede instanciar directamente
Método abstracto@abstractmethod
def sonido(self): ...
Las subclases DEBEN implementarlo o Python lanza error
Método concretodef respirar(self): return "inhala"Puede coexistir con métodos abstractos
Subclase concretaclass Perro(Animal):Debe implementar TODOS los métodos abstractos
Propiedad abstracta@property
@abstractmethod
def categoria(self): ...
Obliga a definir una propiedad en la subclase
🏢 Ejemplo de Negocio: Sistema de Reportes — Empresa de Logística Hermosillo

Una empresa de logística necesita generar reportes en distintos formatos (PDF, Excel, correo electrónico). Todos los reportes comparten la misma estructura base pero cada uno se genera diferente. Usaremos abstracción para definir el contrato y que cada formato lo cumpla a su manera.

🖼️ Abstracción — Clase abstracta define el contrato, subclases lo implementan
🎭 CLASE ABSTRACTA: ReporteBase (ABC)
⛔ No se puede instanciar directamente
@abstractmethod generar() → obligatorio
@abstractmethod enviar() → obligatorio
encabezado() → ya implementado (hereda)
↓ hereda y cumple el contrato
📄 ReportePDF
generar() → PDF
enviar() → ruta
📊 ReporteExcel
generar() → .xlsx
enviar() → carpeta
✉️ ReporteEmail
generar() → texto
enviar() → correo
💡 Analogía: Como el contrato laboral de "Envíos Sonora" — define lo que DEBE hacer cada empleado, pero cada uno lo hace a su manera
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
La abstracción con ABC y @abstractmethod: la clase ReporteBase define QUÉ métodos deben existir sin implementarlos — cada subclase decide el CÓMO.
🚀 ¿QUÉ LOGRAMOS?
Un sistema de reportes para la empresa "Envíos Sonora" que puede generar y enviar reportes en PDF, Excel o correo usando exactamente el mismo bucle for.
🧠 ¿QUÉ APRENDEMOS?
Importar y usar ABC y abstractmethod, la diferencia entre métodos abstractos y concretos, y por qué Python lanza TypeError al intentar instanciar una clase abstracta.
abstraccion.py — Sistema de Reportes con Clases Abstractas
# ═══════════════════════════════════════════════════════════════
# ABSTRACCIÓN — Sistema de Reportes
# Empresa de Logística "Envíos Sonora"
# Unidad 2 · Programación Orientada a Objetos
# ═══════════════════════════════════════════════════════════════

from abc import ABC, abstractmethod   # Paso 1: importar el módulo abc


# ── CLASE ABSTRACTA BASE ────────────────────────────────────────
# No se puede instanciar: ReporteBase() lanzaría TypeError
# Solo define el CONTRATO que deben cumplir las subclases
class ReporteBase(ABC):
    """
    Contrato base para todos los tipos de reporte.
    Cualquier clase que herede de ReporteBase DEBE implementar:
        - generar()
        - enviar()
        - nombre_formato (propiedad)
    """

    def __init__(self, titulo, datos):
        self.titulo = titulo          # Común a todos los reportes
        self.datos  = datos           # Lista de registros a reportar

    # ── Métodos ABSTRACTOS: cada subclase los implementa a su manera ──

    @abstractmethod
    def generar(self):
        """Genera el contenido del reporte en el formato específico."""
        ...   # Los '...' indican que el cuerpo lo define la subclase

    @abstractmethod
    def enviar(self, destino):
        """Envía el reporte generado al destino indicado."""
        ...

    @property
    @abstractmethod
    def nombre_formato(self):
        """Devuelve el nombre del formato (PDF, Excel, Email...)."""
        ...

    # ── Método CONCRETO: ya tiene implementación, se hereda tal cual ──
    def encabezado(self):
        # Este método funciona igual para TODOS los formatos
        sep = "═" * 48
        return (
            f"\n{sep}\n"
            f"  📊 {self.titulo}\n"
            f"  Formato : {self.nombre_formato}\n"
            f"  Registros: {len(self.datos)}\n"
            f"{sep}"
        )


# ── SUBCLASE CONCRETA 1: Reporte en PDF ─────────────────────────
class ReportePDF(ReporteBase):
    """Implementación concreta para reportes en formato PDF."""

    @property
    def nombre_formato(self):
        return "PDF"

    def generar(self):
        # Aquí iría la librería reportlab/fpdf en un proyecto real
        lineas = [f"  • {r['paquete']} → {r['destino']} [{r['estado']}]"
                  for r in self.datos]
        return self.encabezado() + "\n" + "\n".join(lineas)

    def enviar(self, destino):
        return f"📤 PDF enviado a la ruta: {destino}/reporte.pdf"


# ── SUBCLASE CONCRETA 2: Reporte en Excel ───────────────────────
class ReporteExcel(ReporteBase):
    """Implementación concreta para reportes en formato Excel."""

    @property
    def nombre_formato(self):
        return "Excel (.xlsx)"

    def generar(self):
        # En proyecto real usaría openpyxl o pandas
        encabezado_cols = "  PAQUETE          | DESTINO       | ESTADO"
        filas = [f"  {r['paquete']:<18}| {r['destino']:<14}| {r['estado']}"
                 for r in self.datos]
        return self.encabezado() + f"\n{encabezado_cols}\n" + "\n".join(filas)

    def enviar(self, destino):
        return f"📊 Excel guardado en: {destino}/reporte.xlsx"


# ── SUBCLASE CONCRETA 3: Reporte por Correo ─────────────────────
class ReporteEmail(ReporteBase):
    """Implementación concreta para envío por correo electrónico."""

    @property
    def nombre_formato(self):
        return "Correo Electrónico"

    def generar(self):
        resumen = f"Total de envíos: {len(self.datos)}\n"
        pendientes = sum(1 for r in self.datos if r['estado'] == "Pendiente")
        return self.encabezado() + f"\n  {resumen}  Pendientes: {pendientes}"

    def enviar(self, destino):
        return f"✉️  Correo enviado a: {destino}"


# ══════════════════ PROGRAMA PRINCIPAL ══════════════════
paquetes = [
    {"paquete": "PKT-001", "destino": "Hermosillo", "estado": "Entregado"},
    {"paquete": "PKT-002", "destino": "Nogales",    "estado": "En tránsito"},
    {"paquete": "PKT-003", "destino": "Guaymas",    "estado": "Pendiente"},
]

# Creamos los tres tipos de reporte
reportes = [
    ReportePDF  ("Envíos Sonora — Semana 18", paquetes),
    ReporteExcel("Envíos Sonora — Semana 18", paquetes),
    ReporteEmail("Envíos Sonora — Semana 18", paquetes),
]

# ABSTRACCIÓN + POLIMORFISMO: el mismo código funciona con cualquier tipo
for rep in reportes:
    print(rep.generar())
    print(rep.enviar("C:/reportes"))
    print()

# ── ¿Qué pasa si intentamos instanciar la clase abstracta? ──
# reporte = ReporteBase("X", [])   ← TypeError: Can't instantiate abstract class
# Python NO lo permite. ¡Eso es justamente la abstracción!
💡 Los 4 pilares en el orden correcto de aprendizaje:
• 🎭 1. Abstracción — Define QUÉ debe hacer un objeto (el contrato, la interfaz)
• 🔒 2. Encapsulamiento — Controla CÓMO se accede a los datos internos
• 🧬 3. Herencia — Reutiliza y extiende código de una clase padre en una clase hija
• 🎪 4. Polimorfismo — El mismo método se comporta diferente según el objeto

📖 Tema 2.5 — Documentación Profesional

🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El estándar profesional de documentación de código Python: docstrings en módulos, clases y métodos con secciones de parámetros, tipos de retorno y ejemplos de uso.
🚀 ¿QUÉ LOGRAMOS?
Código auto-documentado que cualquier desarrollador puede entender sin necesidad de explicaciones adicionales, y que permite generar documentación HTML con pydoc.
🧠 ¿QUÉ APRENDEMOS?
La estructura de un docstring profesional: encabezado del módulo (autor, fecha, versión), docstring de clase (atributos, ejemplo), docstring de método (Args, Returns, Raises).
documentacion.py — Estándar de documentación UTH
"""
inventario_tienda.py
====================
Sistema de Inventario para Tienda de Ropa.
Módulo principal — Versión 1.0

Autor: Nombre del Alumno
Materia: Programación Orientada a Objetos
Docente: Bernardo Prado Díaz
Fecha: Mayo 2026
"""

class Producto:
    """
    Representa un producto en el inventario de la tienda.

    Atributos:
        nombre (str): Nombre descriptivo del producto.
        precio_base (float): Precio antes de impuestos.
        stock (int): Unidades disponibles.

    Ejemplo:
        >>> p = Producto("Playera", 200.0, 10)
        >>> p.precio_con_iva()
        232.0
    """

    def precio_con_iva(self) -> float:
        """
        Calcula el precio total incluyendo IVA del 16%.

        Returns:
            float: Precio base más 16% de IVA.
        """
        return self.precio_base * 1.16

📋 Criterios de Evaluación — Unidad 2

CriterioEstratégicoAutónomoBásicoReceptivo
Clases y ObjetosImplementa correctamente con encapsulamiento, properties y validaciónClases correctas con encapsulamiento básicoClases funcionales sin encapsulamientoErrores de sintaxis o lógica
HerenciaUsa super(), herencia múltiple y MRO correctamenteHerencia simple con super() correctoHerencia básica sin super()Intenta herencia con errores
EncapsulamientoAtributos privados, properties con validación completaAtributos privados con getters/settersAtributos privados sin propertiesSin encapsulamiento
AbstracciónUsa ABC, @abstractmethod, propiedades abstractas y método concreto heredadoABC con @abstractmethod implementado en subclasesSimula abstracción con NotImplementedErrorNo implementa abstracción
PolimorfismoImplementa override, interfaces y duck typingOverride correcto en subclasesOverride básico en 1 métodoNo implementa polimorfismo
DocumentaciónDocstrings completos con parámetros, returns y ejemplosDocstrings con descripción y parámetrosDocstrings básicos descriptivosSin documentación
🎬

Videos de Apoyo — Unidad 2: Sintaxis y Pilares POO

Videos seleccionados para reforzar los conceptos de esta unidad. Míralos después de clase para afianzar lo aprendido.

Abstracción
Clases Abstractas y ABC en Python

abstractmethod, ABC, contratos entre clases con ejemplos reales

🕐 ~18 minES✓ Recomendado
Encapsulamiento
Encapsulamiento: @property, getters y setters

Atributos privados, protegidos y el decorador @property

🕐 ~22 minES✓ Recomendado
Herencia
Herencia Simple y Múltiple — super() y MRO

Cómo funciona la cadena de herencia y super() correctamente

🕐 ~26 minES✓ Recomendado
Polimorfismo
Polimorfismo y Duck Typing en Python

Override de métodos, interfaces implícitas, polimorfismo en acción

🕐 ~20 minES✓ Recomendado
🎥

Videos de Clase — Unidad 2 — 8 Sesiones

Grabaciones de las sesiones presenciales. Si faltaste o quieres repasar, estas grabaciones son tu mejor aliado. Se actualizan después de cada clase.

Sesión 1
Clase 1 — Clases, Objetos y Constructor

__init__, self, atributos, primer objeto en Python

S1📅 Por confirmar🕐 Próximamente
Sesión 2
Clase 2 — Pilar 1: Abstracción con ABC

Clases abstractas, @abstractmethod, contrato entre clases

S2📅 Por confirmar🕐 Próximamente
Sesión 3
Clase 3 — Pilar 2: Encapsulamiento y @property

Atributos privados, getters, setters, validación

S3📅 Por confirmar🕐 Próximamente
Sesión 4
Clase 4 — Pilar 3: Herencia Simple y super()

Herencia, super(), reutilización de código

S4📅 Por confirmar🕐 Próximamente
Sesión 5
Clase 5 — Herencia Múltiple y MRO

Resolución de orden de método, diamond problem

S5📅 Por confirmar🕐 Próximamente
Sesión 6
Clase 6 — Pilar 4: Polimorfismo y Duck Typing

Override, mismo método, comportamientos distintos

S6📅 Por confirmar🕐 Próximamente
Sesión 7
Clase 7 — Documentación Profesional con Docstrings

pydoc, docstrings de módulos, clases y métodos

S7📅 Por confirmar🕐 Próximamente
Sesión 8
Clase 8 — Repaso y Ejercicio Integrador U2

Proyecto integrador con los 4 pilares aplicados

S8📅 Por confirmar🕐 Próximamente
💡 ¿Cómo aprovecharlos? Pausa el video cada vez que el profesor escribe código, escríbelo tú mismo y luego compara. Los errores que cometes y resuelves son los que más aprendes.

🎨 Unidad 3 — FLET + MySQL: Interfaces Gráficas y Bases de Datos

Aprenderás a crear aplicaciones con interfaz gráfica usando FLET, conectar Python con MySQL, aplicar los principios SOLID y documentar tu proyecto profesionalmente.

⏱ 14 horas📊 Exámen y Prácticas en clase - Calificación🛠 FLET + MySQL + WampServer + DBeaver

📋 Temas de la Unidad

🎨

3.1 FLET — GUI Moderna

Instalación, componentes, eventos, diseño de interfaces.

🐬

3.2 MySQL con WampServer

Instalación, creación de BD, tablas, consultas SQL.

🔌

3.3 Python + MySQL OOP

Conexión, CRUD completo en clases, patrón DAO.

🏗️

3.4 Principios SOLID

5 principios para código limpio y mantenible.

📄

3.5 Documentación

Manual de usuario y guía de instalación profesional.

🗺️ Stack Tecnológico — Unidad 3: Lo que vamos a conectar
🐍
Python + POO
Clases, DAO
Lógica del negocio
+
🎨
FLET
Interfaz gráfica
Eventos, Controles
+
🐬
MySQL
WampServer
DBeaver
+
🏗️
SOLID
Principios de
diseño limpio
=
🏪
Sistema POS
Tienda Hermosillo
Completo y funcional

🔧 Instalación de WampServer 3.3.7

⚠️ Antes de instalar WampServer Asegúrate de tener instalado Microsoft Visual C++ Redistributable (x64). Si no lo tienes, WampServer no iniciará. Descárgalo desde la página oficial de Microsoft.
Descargar WampServer 3.3.7

Ve a wampserver.com → "Download" → selecciona la versión 64-bit para Windows 10/11. El archivo pesa aprox. 300MB.

Instalar WampServer

Ejecuta el instalador como Administrador (clic derecho → "Ejecutar como administrador"). Acepta la carpeta de destino predeterminada: C:\wamp64\. Completa el asistente.

Iniciar WampServer

Busca WampServer en el menú de inicio y ábrelo. En la barra de tareas (esquina inferior derecha) aparecerá un icono W. Espera a que cambie de rojo a naranja y finalmente a verde.

Verificar que funciona

Abre tu navegador y ve a http://localhost. Deberías ver la página de bienvenida de WampServer con links a phpMyAdmin y las versiones instaladas.

Configurar contraseña de MySQL

Haz clic en el icono W (barra de tareas) → MySQL → MySQL console. Se abrirá un CMD. Escribe:

MySQL Console
ALTER USER 'root'@'localhost' IDENTIFIED BY 'tu_password';
FLUSH PRIVILEGES;

Reemplaza tu_password por tu contraseña. Anótala bien.

🦦 Instalación de DBeaver 25.1.0

Descargar DBeaver

Ve a dbeaver.io/download → selecciona Community Edition → Windows installer (.exe).

Instalar DBeaver

Ejecuta el instalador. Acepta todos los valores por defecto. Se instalará en C:\Program Files\DBeaver\.

Conectar a MySQL local

Abre DBeaver → clic en "New Database Connection" (ícono de enchufe) → selecciona MySQL → configura:

  • Server Host: localhost
  • Port: 3306
  • Username: root
  • Password: (la que configuraste)

Clic en "Test Connection" → si aparece verde, ¡éxito!

Crear base de datos del proyecto

En el panel izquierdo, clic derecho en la conexión → "Create New Database" → nombre: pos_tienda → charset: utf8mb4 → OK.

🎨 Instalación y Primeros Pasos con FLET

🤔 ¿Qué es FLET? FLET es una librería Python que permite crear aplicaciones de escritorio y web con una interfaz visual moderna, usando el mismo código. Es mucho más fácil y moderno que Tkinter. Creado por el equipo de Flutter/Google.
Instalar FLET

Abre PyCharm → Terminal (menú View → Tool Windows → Terminal) y escribe:

Terminal PyCharm
pip install flet

Espera a que termine. Verás mensajes de descarga. Al final aparece Successfully installed flet-X.X.X.

Instalar mysql-connector-python
Terminal PyCharm
pip install mysql-connector-python
Tu primera app FLET — "Hola Mundo"

Crea un archivo hola_flet.py y copia este código:

🖼️ Arquitectura FLET — Cómo fluye una aplicación
👆
EVENTO
on_click
on_change
⚙️
FUNCIÓN
def guardar(e):
lógica...
🖥️
PÁGINA
page.add()
page.update()
ft.Page
├── ft.Column
├── ft.Text("¡Hola!")
└── ft.ElevatedButton("Click")
💡 ft.app(target=main) inicia todo — "page" es la ventana que administras
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
La estructura mínima de una aplicación FLET: la función main(page) recibe la ventana como parámetro, y ft.app() es el motor que la hace funcionar.
🚀 ¿QUÉ LOGRAMOS?
Una ventana de escritorio real con título, color de fondo, texto y un botón que responde al clic agregando mensajes dinámicamente en pantalla.
🧠 ¿QUÉ APRENDEMOS?
La estructura ft.app(target=main), los componentes ft.Text, ft.ElevatedButton y ft.Column, el parámetro on_click para manejar eventos, y page.add().
hola_flet.py — Tu primera aplicación con FLET
import flet as ft   # Importamos FLET con el alias "ft"

def main(page: ft.Page):
    """
    'page' es la VENTANA de la aplicación.
    Todo lo que pongas aquí aparecerá en pantalla.
    """
    page.title = "Mi Primera App FLET"    # Título de la ventana
    page.bgcolor = ft.colors.BLUE_GREY_50  # Color de fondo
    page.window_width = 500              # Ancho de la ventana en pixeles
    page.window_height = 400             # Alto de la ventana en pixeles

    # ft.Text crea un texto visible
    titulo = ft.Text(
        value="¡Hola, UTH! 🎓",           # El texto a mostrar
        size=32,                          # Tamaño de fuente
        color=ft.colors.BLUE_900,         # Color del texto
        weight=ft.FontWeight.BOLD         # Negrita
    )

    # ft.ElevatedButton crea un botón
    boton = ft.ElevatedButton(
        text="Presióname 👋",
        on_click=lambda e: page.add(   # on_click = qué hacer al presionar
            ft.Text("¡Botón presionado! 🎉", color=ft.colors.GREEN_700)
        )
    )

    # page.add() agrega componentes a la ventana
    page.add(
        ft.Column(                        # Column organiza elementos verticalmente
            controls=[titulo, boton],     # Lista de componentes
            horizontal_alignment=ft.CrossAxisAlignment.CENTER,
            spacing=20                   # Espacio entre elementos
        )
    )

# ft.app() inicia la aplicación y llama a la función main
ft.app(target=main)
▶️ Para ejecutar: Haz clic derecho en el archivo en PyCharm → "Run hola_flet". Se abrirá una ventana de escritorio con tu aplicación.

🧩 Componentes Principales de FLET

ComponenteUsoEjemplo
ft.TextTexto en pantallaft.Text("Hola", size=20)
ft.TextFieldCampo de entradaft.TextField(label="Nombre")
ft.ElevatedButtonBotón principalft.ElevatedButton("OK", on_click=f)
ft.TextButtonBotón textoft.TextButton("Cancelar")
ft.DropdownLista desplegableft.Dropdown(options=[ft.dropdown.Option("A")])
ft.DataTableTabla de datosft.DataTable(columns=[...], rows=[...])
ft.ColumnColumna verticalft.Column([comp1, comp2])
ft.RowFila horizontalft.Row([comp1, comp2])
ft.ContainerContenedor con estiloft.Container(content=..., bgcolor=...)
ft.AlertDialogCuadro de diálogoft.AlertDialog(title=..., content=...)
ft.SnackBarMensaje temporalft.SnackBar(content=ft.Text("OK"))
ft.AppBarBarra de títuloft.AppBar(title=ft.Text("App"))

🐬 Tema 3.2 — Base de Datos MySQL para el Proyecto

🏪 Ejemplo de Negocio: Sistema de Punto de Venta (POS) — Tienda de Abarrotes

Crearemos un sistema POS para una tienda de abarrotes en Hermosillo. Tendrá gestión de productos, clientes y ventas con interfaz gráfica FLET y base de datos MySQL.

🖼️ Diagrama ER — Relaciones entre tablas del POS
🗂️ categorias
🔑 id (PK)
nombre
descripcion
1↔N
📦 productos
🔑 id (PK)
nombre, precio, stock
🔗 id_categoria (FK)
N↔N
🧾 detalle_venta
🔑 id (PK)
🔗 id_venta, id_producto
cantidad, precio_unit
N↔1
🛒 ventas
🔑 id (PK)
🔗 id_cliente (FK)
fecha, total
💡 PK = Llave Primaria (identificador único) | FK = Llave Foránea (referencia a otra tabla)
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
Cómo diseñar una base de datos relacional para un sistema POS: tablas normalizadas con llaves primarias, llaves foráneas y relaciones entre entidades del negocio.
🚀 ¿QUÉ LOGRAMOS?
La base de datos completa del sistema POS para la tienda de abarrotes con 5 tablas relacionadas, datos de prueba incluidos, lista para conectar con Python.
🧠 ¿QUÉ APRENDEMOS?
Sintaxis SQL: CREATE TABLE, tipos de datos (INT, VARCHAR, DECIMAL), restricciones (NOT NULL, AUTO_INCREMENT), y relaciones con FOREIGN KEY.
crear_bd.sql — Script SQL para crear la base de datos
-- Ejecuta este script en DBeaver o phpMyAdmin
-- Selecciona la BD 'pos_tienda' antes de ejecutar

USE pos_tienda;

-- Tabla de Categorías
CREATE TABLE categorias (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    nombre     VARCHAR(100) NOT NULL,
    descripcion TEXT
);

-- Tabla de Productos
CREATE TABLE productos (
    id           INT AUTO_INCREMENT PRIMARY KEY,
    nombre       VARCHAR(150) NOT NULL,
    precio       DECIMAL(10,2) NOT NULL,
    stock        INT DEFAULT 0,
    id_categoria INT,
    codigo_barra VARCHAR(50),
    FOREIGN KEY (id_categoria) REFERENCES categorias(id)
);

-- Tabla de Clientes
CREATE TABLE clientes (
    id       INT AUTO_INCREMENT PRIMARY KEY,
    nombre   VARCHAR(150) NOT NULL,
    telefono VARCHAR(20),
    email    VARCHAR(100)
);

-- Tabla de Ventas (encabezado)
CREATE TABLE ventas (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    id_cliente INT,
    fecha      DATETIME DEFAULT NOW(),
    total      DECIMAL(10,2),
    FOREIGN KEY (id_cliente) REFERENCES clientes(id)
);

-- Tabla de Detalle de Venta
CREATE TABLE detalle_venta (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    id_venta    INT,
    id_producto INT,
    cantidad    INT,
    precio_unit DECIMAL(10,2),
    FOREIGN KEY (id_venta) REFERENCES ventas(id),
    FOREIGN KEY (id_producto) REFERENCES productos(id)
);

-- Datos de prueba
INSERT INTO categorias (nombre) VALUES
    ('Abarrotes'), ('Bebidas'), ('Lácteos');

INSERT INTO productos (nombre, precio, stock, id_categoria) VALUES
    ('Arroz 1kg', 22.50, 100, 1),
    ('Refresco 600ml', 18.00, 60, 2),
    ('Leche 1L', 28.00, 40, 3);

🔌 Tema 3.3 — Conexión Python + MySQL con Clases (Patrón DAO)

💡 ¿Qué es el patrón DAO? Data Access Object. Es una clase dedicada SOLO a hablar con la base de datos. Así separamos la lógica del negocio del acceso a datos — uno de los principios SOLID.
🖼️ Arquitectura Python ↔ MySQL con Patrón Singleton
🐍
Python
ProductoDAO
VentaDAO
mysql-connector
Clase Conexion
🐬
MySQL
WampServer
localhost:3306
SQL queries
resultados dict
🗄️
BD: pos_tienda
productos
clientes
ventas
Singleton: Conexion() siempre devuelve la misma conexión
→ No se abren múltiples conexiones a la BD
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
Cómo aplicar el patrón Singleton a una clase de conexión MySQL: garantizar que toda la aplicación comparta una sola conexión, evitando sobrecarga en el servidor.
🚀 ¿QUÉ LOGRAMOS?
Una clase Conexion reutilizable que centraliza el acceso a MySQL, con métodos ejecutar() para modificaciones y consultar() para lecturas que devuelven diccionarios.
🧠 ¿QUÉ APRENDEMOS?
Usar mysql.connector.connect(), el parámetro dictionary=True para resultados como dicts, la importancia de commit() para guardar cambios, y cómo combinar Singleton con BD.
database.py — Clase de conexión a MySQL
import mysql.connector   # Importamos el conector de MySQL

class Conexion:
    """
    Clase que maneja la conexión a MySQL.
    Usamos el patrón Singleton para que solo haya UNA conexión.
    """
    _instancia = None

    def __new__(cls):
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
            cls._instancia._conectar()
        return cls._instancia

    def _conectar(self):
        # Establece la conexión con MySQL
        self.conn = mysql.connector.connect(
            host="localhost",      # El servidor (WampServer)
            user="root",           # Usuario MySQL
            password="tu_password",# Tu contraseña configurada
            database="pos_tienda"  # Nombre de la BD
        )
        self.cursor = self.conn.cursor(dictionary=True)
        # dictionary=True → los resultados vienen como dicts {col: valor}

    def ejecutar(self, sql, params=None):
        # Ejecuta una instrucción SQL (INSERT, UPDATE, DELETE)
        self.cursor.execute(sql, params)
        self.conn.commit()         # Guarda los cambios en la BD

    def consultar(self, sql, params=None):
        # Ejecuta un SELECT y retorna los resultados
        self.cursor.execute(sql, params)
        return self.cursor.fetchall()  # Lista de diccionarios

    def cerrar(self):
        self.cursor.close()
        self.conn.close()
        Conexion._instancia = None   # Limpia el singleton
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El patrón DAO (Data Access Object): una clase cuya única responsabilidad es hablar con la base de datos — CRUD completo (Create, Read, Update, Delete) para productos.
🚀 ¿QUÉ LOGRAMOS?
Métodos listos para usar: obtener_todos(), insertar(), actualizar(), eliminar() y buscar_por_nombre(), con protección contra SQL Injection.
🧠 ¿QUÉ APRENDEMOS?
Separar la lógica de negocio del acceso a datos (Principio S de SOLID), usar %s como marcadores seguros en consultas SQL, y el patrón JOIN para obtener datos de múltiples tablas.
producto_dao.py — DAO para CRUD de Productos
from database import Conexion

class ProductoDAO:
    """
    DAO (Data Access Object) para la tabla 'productos'.
    TODA la lógica de base de datos de productos va aquí.
    Principio de Responsabilidad Única (SOLID-S):
    esta clase solo se encarga de acceso a datos.
    """

    def __init__(self):
        self.db = Conexion()   # Obtenemos la conexión (singleton)

    def obtener_todos(self):
        # SELECT: obtiene todos los productos
        sql = "SELECT p.id, p.nombre, p.precio, p.stock, c.nombre AS categoria FROM productos p LEFT JOIN categorias c ON p.id_categoria = c.id"
        return self.db.consultar(sql)

    def insertar(self, nombre, precio, stock, id_categoria):
        # INSERT: agrega un nuevo producto
        # Usamos %s como marcadores de posición (evita SQL Injection)
        sql = "INSERT INTO productos (nombre, precio, stock, id_categoria) VALUES (%s, %s, %s, %s)"
        self.db.ejecutar(sql, (nombre, precio, stock, id_categoria))

    def actualizar(self, id_prod, nombre, precio, stock):
        # UPDATE: modifica un producto existente
        sql = "UPDATE productos SET nombre=%s, precio=%s, stock=%s WHERE id=%s"
        self.db.ejecutar(sql, (nombre, precio, stock, id_prod))

    def eliminar(self, id_prod):
        # DELETE: elimina un producto por ID
        sql = "DELETE FROM productos WHERE id = %s"
        self.db.ejecutar(sql, (id_prod,))   # Nota la coma: es una tupla

    def buscar_por_nombre(self, texto):
        sql = "SELECT * FROM productos WHERE nombre LIKE %s"
        return self.db.consultar(sql, (f"%{texto}%",))
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
La integración completa de FLET + MySQL + POO: una interfaz gráfica que se conecta a la base de datos y permite gestionar productos con formularios y tabla de datos.
🚀 ¿QUÉ LOGRAMOS?
Una app POS real con ventana de 900×650 px, tabla que carga productos de MySQL, formulario de alta/edición, y botones de Guardar/Eliminar que persisten datos en la BD.
🧠 ¿QUÉ APRENDEMOS?
Usar ft.DataTable con filas dinámicas, capturar eventos de selección de fila para prellenar formularios, y el flujo INSERT vs UPDATE condicionado al estado de selección.
pos_app.py — Aplicación POS completa con FLET + MySQL
import flet as ft
from producto_dao import ProductoDAO

def main(page: ft.Page):
    page.title = "🏪 POS Tienda Hermosillo"
    page.bgcolor = ft.colors.GREY_50
    page.window_width = 900
    page.window_height = 650
    page.window_center()

    dao = ProductoDAO()   # Creamos el DAO para acceder a la BD

    # ── Campos del formulario ──
    txt_nombre  = ft.TextField(label="Nombre del producto", expand=True)
    txt_precio  = ft.TextField(label="Precio ($)", width=150)
    txt_stock   = ft.TextField(label="Stock", width=100)
    id_seleccionado = [None]   # Lista para guardar el ID seleccionado

    # ── Tabla de datos ──
    tabla = ft.DataTable(
        columns=[
            ft.DataColumn(ft.Text("ID")),
            ft.DataColumn(ft.Text("Nombre")),
            ft.DataColumn(ft.Text("Precio")),
            ft.DataColumn(ft.Text("Stock")),
            ft.DataColumn(ft.Text("Categoría")),
        ],
        rows=[],   # Se llenará con datos de BD
        border=ft.border.all(1, ft.colors.GREY_300),
    )

    def cargar_productos(e=None):
        # Lee productos de MySQL y llena la tabla
        tabla.rows.clear()
        productos = dao.obtener_todos()
        for p in productos:
            tabla.rows.append(ft.DataRow(
                cells=[
                    ft.DataCell(ft.Text(str(p["id"]))),
                    ft.DataCell(ft.Text(p["nombre"])),
                    ft.DataCell(ft.Text(f"${p['precio']:.2f}")),
                    ft.DataCell(ft.Text(str(p["stock"]))),
                    ft.DataCell(ft.Text(p["categoria"] or "—")),
                ],
                on_select_changed=lambda e, prod=p: seleccionar(prod)
            ))
        page.update()   # Refresca la interfaz

    def seleccionar(prod):
        # Rellena el formulario con los datos del producto seleccionado
        txt_nombre.value  = prod["nombre"]
        txt_precio.value  = str(prod["precio"])
        txt_stock.value   = str(prod["stock"])
        id_seleccionado[0] = prod["id"]
        page.update()

    def guardar(e):
        if id_seleccionado[0]:
            # Si hay ID seleccionado → UPDATE
            dao.actualizar(id_seleccionado[0], txt_nombre.value,
                           float(txt_precio.value), int(txt_stock.value))
        else:
            # Si no → INSERT
            dao.insertar(txt_nombre.value, float(txt_precio.value),
                         int(txt_stock.value), 1)
        limpiar(None)
        cargar_productos()

    def eliminar(e):
        if id_seleccionado[0]:
            dao.eliminar(id_seleccionado[0])
            limpiar(None)
            cargar_productos()

    def limpiar(e):
        txt_nombre.value = txt_precio.value = txt_stock.value = ""
        id_seleccionado[0] = None
        page.update()

    # ── Botones ──
    btns = ft.Row([
        ft.ElevatedButton("💾 Guardar",  on_click=guardar,
                          bgcolor=ft.colors.BLUE_700, color=ft.colors.WHITE),
        ft.ElevatedButton("🗑 Eliminar", on_click=eliminar,
                          bgcolor=ft.colors.RED_700,  color=ft.colors.WHITE),
        ft.ElevatedButton("🔄 Refrescar",on_click=cargar_productos),
        ft.TextButton("✖ Limpiar", on_click=limpiar),
    ])

    # ── AppBar (barra superior) ──
    page.appbar = ft.AppBar(
        title=ft.Text("Sistema POS — Tienda Hermosillo", color=ft.colors.WHITE),
        bgcolor=ft.colors.BLUE_900
    )

    # ── Layout principal ──
    page.add(
        ft.Column([
            ft.Text("📦 Gestión de Productos", size=18, weight=ft.FontWeight.BOLD),
            ft.Row([txt_nombre, txt_precio, txt_stock]),
            btns,
            ft.Divider(),
            ft.Text("Haz clic en una fila para seleccionar", size=12, color=ft.colors.GREY_600),
            tabla,
        ], scroll=ft.ScrollMode.AUTO)
    )

    cargar_productos()   # Carga datos al iniciar la app

ft.app(target=main)

🏗️ Tema 3.4 — Principios SOLID

S

S — Single Responsibility

Una clase = una responsabilidad. ProductoDAO solo accede a BD. ProductoService solo tiene lógica del negocio. No mezcles ambas.

O

O — Open/Closed

Abierto para extender, cerrado para modificar. Si necesitas un nuevo tipo de descuento, agrega una subclase, no modifiques la clase existente.

L

L — Liskov Substitution

Una subclase debe poder reemplazar a su padre. Si tienes Animal con método hablar(), todos sus hijos deben tenerlo funcionando.

I

I — Interface Segregation

Mejor muchas interfaces pequeñas que una grande. No obligues a una clase a implementar métodos que no usa.

D

D — Dependency Inversion

Depende de abstracciones, no de implementaciones. Tu app no debe depender de MySQL específicamente, sino de una interfaz genérica de BD.

📋 Criterios de Evaluación — Unidad 3

CriterioEstratégicoAutónomoBásicoReceptivo
GUI con FLETInterfaz profesional, validación completa, UX cuidadaInterfaz funcional con validaciones básicasInterfaz básica sin validacionesNo funciona o usa otra librería
MySQL OOPDAO completo con CRUD, manejo de errores, patrón SingletonCRUD funcional con clases bien definidasConexión y consultas básicasConexión sin OOP
Principios SOLIDAplica los 5 principios visiblemente en el códigoAplica S y O claramenteMenciona SOLID en comentariosNo aplica SOLID
DocumentaciónManual de usuario + guía de instalación profesionalManual de usuario completoREADME básicoSin documentación
🎬

Videos de Apoyo — Unidad 3: FLET + MySQL

Videos seleccionados para reforzar los conceptos de esta unidad. Míralos después de clase para afianzar lo aprendido.

FLET
FLET — Apps de Escritorio con Python

Primeros pasos: controles, eventos, páginas y navegación

🕐 ~30 minEN+SUB✓ Recomendado
MySQL
MySQL con Python — Conector y CRUD Completo

Instalar mysql-connector, conectar, INSERT, UPDATE, DELETE

🕐 ~35 minES✓ Recomendado
SOLID
Principios SOLID — Ejemplos en Python

SRP, OCP, LSP, ISP, DIP aplicados a código orientado a objetos

🕐 ~28 minES✓ Recomendado
WampServer
Instalar WampServer y configurar MySQL local

Guía paso a paso para tener MySQL en Windows con WampServer

🕐 ~12 minES✓ Recomendado
🎥

Videos de Clase — Unidad 3 — 7 Sesiones

Grabaciones de las sesiones presenciales. Si faltaste o quieres repasar, estas grabaciones son tu mejor aliado. Se actualizan después de cada clase.

Sesión 1
Clase 1 — Instalación FLET y Primera App

pip install flet, estructura de proyecto, primer ft.app()

S1📅 Por confirmar🕐 Próximamente
Sesión 2
Clase 2 — Controles FLET: Botones, Inputs, Listas

ElevatedButton, TextField, ListView, Row, Column

S2📅 Por confirmar🕐 Próximamente
Sesión 3
Clase 3 — Instalación MySQL y WampServer

Configuración de MySQL local, DBeaver, conexión inicial

S3📅 Por confirmar🕐 Próximamente
Sesión 4
Clase 4 — CRUD MySQL con Python OOP

Clase Conexion, DAOs, INSERT/SELECT/UPDATE/DELETE

S4📅 Por confirmar🕐 Próximamente
Sesión 5
Clase 5 — Conectar FLET con MySQL

DAO + interfaz gráfica: CRUD completo con FLET

S5📅 Por confirmar🕐 Próximamente
Sesión 6
Clase 6 — Principios SOLID en el Proyecto

Refactorizar aplicando SRP, OCP y LSP

S6📅 Por confirmar🕐 Próximamente
Sesión 7
Clase 7 — Documentación y Cierre U3

Manual de usuario, guía de instalación, entrega final

S7📅 Por confirmar🕐 Próximamente
💡 ¿Cómo aprovecharlos? Pausa el video cada vez que el profesor escribe código, escríbelo tú mismo y luego compara. Los errores que cometes y resuelves son los que más aprendes.

🚀 Unidad 4 — Entornos de Desarrollo, Git y Proyecto Final

Dominarás PyCharm como IDE profesional, aprenderás Git para control de versiones, manejarás excepciones correctamente, harás pruebas unitarias y entregarás tu Proyecto Final POO completo.

⏱ 14 horas📊 Exposición de Conocimiento ante Maestros - Calificación🛠 PyCharm + Git + MySQL

📋 Temas de la Unidad

💡

4.1 PyCharm Avanzado

Atajos, depurador, refactorización, virtual environments.

🔀

4.2 Git y Control de Versiones

init, add, commit, branches, push, GitHub.

🛡️

4.3 Manejo de Excepciones

try/except/finally, excepciones personalizadas.

🧪

4.4 Pruebas Unitarias

unittest, assert, casos de prueba.

🏆

4.5 Proyecto Final

Sistema completo: GUI + CRUD + reportes + excepciones + documentación.

🗺️ Flujo de Trabajo Profesional — Unidad 4
💡
PyCharm Pro
Debug, atajos
Refactoring
🔀
Git + GitHub
Commits, ramas
Historial
🛡️
Excepciones
try/except
Errores propios
🧪
Pruebas
unittest
TDD
🏆
Proyecto Final
Sistema completo
Documentado

🔀 Guía de Instalación y Uso de Git

🤔 ¿Qué es Git? Git es un sistema que guarda el historial de cambios de tu código. Es como un "Ctrl+Z" infinito con comentarios. Todas las empresas de software lo usan. GitHub es la plataforma web que almacena tus repositorios Git.
Instalar Git

Ve a git-scm.com/download/win → descarga el instalador → ejecuta → acepta todas las opciones por defecto (son las recomendadas).

Configurar tu identidad

Abre Git Bash (se instala con Git) y configura tu nombre y email:

Git Bash
git config --global user.name "Tu Nombre"
git config --global user.email "tu@email.com"
Crear cuenta en GitHub

Ve a github.com → "Sign up" → usa tu email institucional UTH.

Iniciar repositorio en tu proyecto
Terminal (carpeta de tu proyecto)
git init                    # Inicia el repositorio
git add .                   # Agrega TODOS los archivos
git commit -m "Primer commit del proyecto POO"   # Guarda el estado

📋 Comandos Git Esenciales

Comando¿Qué hace?Ejemplo
git initCrea un repositorio nuevogit init mi-proyecto
git statusMuestra qué archivos cambiarongit status
git addPrepara archivos para commitgit add archivo.py o git add .
git commitGuarda una versión con mensajegit commit -m "Agrega módulo de ventas"
git logHistorial de commitsgit log --oneline
git branchLista o crea ramasgit branch nueva-feature
git checkoutCambia de ramagit checkout nueva-feature
git mergeUne ramasgit merge nueva-feature
git pushSube cambios a GitHubgit push origin main
git pullDescarga cambios de GitHubgit pull origin main
git cloneClona repositorio remotogit clone https://github.com/user/repo.git

💡 Tema 4.1 — PyCharm: Atajos y Funciones Profesionales

AcciónAtajo Windows¿Para qué?
Ejecutar programaShift+F10Corre el archivo actual
DepuradorShift+F9Ejecuta en modo debug (con breakpoints)
Agregar breakpointF9 (clic en margen)Pausa la ejecución en esa línea
Buscar en proyectoCtrl+Shift+FBusca texto en todos los archivos
Ir a definiciónCtrl+BSalta a donde está definida la función/clase
Renombrar símboloShift+F6Renombra en todos los archivos a la vez
AutocompletarCtrl+SpaceMuestra sugerencias de código
Comentar líneaCtrl+/Comenta/descomenta la línea actual
Formatear códigoCtrl+Alt+LAplica el estilo PEP 8 automáticamente
Terminal integradaAlt+F12Abre terminal dentro de PyCharm
Buscar claseCtrl+NBusca una clase por nombre
Ver estructuraAlt+7Panel con métodos y atributos de la clase

Virtual Environment (Entorno Virtual)

💡 ¿Para qué sirve un venv? Un entorno virtual aísla las librerías de tu proyecto. Así dos proyectos pueden usar versiones diferentes de la misma librería sin conflictos. ¡Siempre usa uno!
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
Por qué los entornos virtuales son esenciales: aislar las dependencias de cada proyecto para que no interfieran entre sí, como si cada proyecto tuviera su propia "cajita" de librerías.
🚀 ¿QUÉ LOGRAMOS?
Un entorno virtual aislado con FLET y mysql-connector instalados, y un archivo requirements.txt que permite reproducir el entorno exacto en cualquier otra computadora.
🧠 ¿QUÉ APRENDEMOS?
Los 5 pasos del flujo de trabajo profesional: crear venv → activar → instalar librerías → programar → pip freeze para documentar dependencias. Práctica usada en toda la industria.
Terminal — Crear y usar entorno virtual
# 1. Crear el entorno virtual (ejecuta una sola vez)
python -m venv venv

# 2. Activarlo (cada vez que abras el proyecto)
venv\Scripts\activate          # Windows

# Verás el prefijo (venv) en el terminal, significa que está activo

# 3. Instalar librerías dentro del venv
pip install flet mysql-connector-python

# 4. Guardar lista de dependencias
pip freeze > requirements.txt

# 5. Para instalar en otro equipo:
pip install -r requirements.txt

🛡️ Tema 4.3 — Manejo de Excepciones

⚠️ ¿Qué es una excepción? Es un error que ocurre en tiempo de ejecución (cuando el programa ya está corriendo). Si no la manejas, el programa se detiene con un mensaje de error feo. Con try/except puedes capturarla y responder de forma elegante.
🖼️ Flujo de Manejo de Excepciones
try:
Código que
puede fallar
except ValueError: texto en lugar de número
except ZeroDivisionError: división entre 0
except Exception as e: cualquier otro error
else: solo si no hubo error
finally: SIEMPRE se ejecuta
💡 Analogía: Como el protocolo de emergencias de un hospital en Hermosillo — hay un procedimiento para cada tipo de problema, y la limpieza (finally) se hace siempre
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
Cómo capturar errores en tiempo de ejecución con try/except/else/finally y cómo crear excepciones personalizadas que comunican errores específicos del negocio.
🚀 ¿QUÉ LOGRAMOS?
Un programa que nunca colapsa inesperadamente: responde con mensajes claros al usuario, registra el error, y continúa o cierra de forma ordenada sin mostrar trazas técnicas.
🧠 ¿QUÉ APRENDEMOS?
La jerarquía de excepciones de Python (ValueError, ZeroDivisionError heredan de Exception), cómo crear StockInsuficienteError personalizada y manejar errores de conexión MySQL.
excepciones.py — Manejo completo de excepciones
# ── Estructura básica de try/except ──
try:
    # Código que PUEDE fallar va aquí
    numero = int(input("Ingresa un número: "))
    resultado = 100 / numero
    print(f"Resultado: {resultado}")

except ValueError:
    # Se ejecuta si el usuario escribe texto en lugar de número
    print("❌ Error: debes ingresar un número entero.")

except ZeroDivisionError:
    # Se ejecuta si el usuario escribe 0
    print("❌ Error: no se puede dividir entre cero.")

except Exception as e:
    # Captura CUALQUIER otro error inesperado
    print(f"❌ Error inesperado: {e}")

else:
    # Se ejecuta SOLO si NO hubo error
    print("✅ Operación exitosa.")

finally:
    # Se ejecuta SIEMPRE, haya o no error (útil para cerrar BD, archivos)
    print("ℹ️ Fin de la operación.")


# ── Excepciones personalizadas ──
class StockInsuficienteError(Exception):
    """Error propio de nuestra aplicación."""
    def __init__(self, producto, cantidad_pedida, stock_actual):
        self.producto = producto
        mensaje = (
            f"Stock insuficiente para '{producto}'. "
            f"Pedido: {cantidad_pedida}, Disponible: {stock_actual}"
        )
        super().__init__(mensaje)


class ProductoService:
    def vender(self, producto, cantidad, stock):
        if cantidad > stock:
            raise StockInsuficienteError(producto, cantidad, stock)
        return stock - cantidad


# Uso con manejo de la excepción propia:
svc = ProductoService()
try:
    nuevo_stock = svc.vender("Arroz 1kg", 50, 10)
except StockInsuficienteError as e:
    print(f"❌ {e}")   # Muestra el mensaje personalizado


# ── Excepciones en conexión MySQL ──
import mysql.connector

def conectar_bd():
    try:
        conn = mysql.connector.connect(
            host="localhost", user="root",
            password="pass", database="pos_tienda"
        )
        return conn
    except mysql.connector.Error as e:
        print(f"❌ No se pudo conectar a MySQL: {e}")
        return None

🧪 Tema 4.4 — Pruebas Unitarias con unittest

💡 ¿Para qué sirven las pruebas? Una prueba unitaria verifica que cada función/método haga lo que se supone. Así cuando cambias código, las pruebas te avisan si algo dejó de funcionar. Es como un sistema de alarma para tu código.
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El framework unittest de Python: cómo estructurar una suite de pruebas con setUp(), múltiples métodos test_*() y assertions que verifican el comportamiento esperado.
🚀 ¿QUÉ LOGRAMOS?
5 pruebas automatizadas que verifican: cálculo de IVA, venta exitosa, stock insuficiente, validación de precio negativo y herencia de Ropa. Se ejecutan con un solo comando.
🧠 ¿QUÉ APRENDEMOS?
assertEqual, assertIn, assertAlmostEqual, assertRaises e assertIsInstance — los 5 assertions más comunes. Y el ciclo: escribir prueba → ejecutar → corregir código.
test_producto.py — Pruebas unitarias completas
import unittest
from inventario_tienda import Producto, Ropa   # Importamos las clases a probar

class TestProducto(unittest.TestCase):
    """
    Clase de pruebas. Hereda de unittest.TestCase.
    Cada método que empiece con 'test_' es una prueba.
    """

    def setUp(self):
        # setUp() se ejecuta ANTES de cada prueba
        # Aquí creamos objetos que necesitaremos en las pruebas
        self.prod = Producto("Arroz", 20.0, 10)

    def test_precio_con_iva(self):
        # Verifica que el cálculo de IVA sea correcto
        resultado = self.prod.precio_con_iva()
        self.assertAlmostEqual(resultado, 23.2, places=2)

    def test_vender_exitoso(self):
        # Verifica que vender 3 unidades funcione
        resultado = self.prod.vender(3)
        self.assertIn("✅", resultado)         # Debe contener ✅
        self.assertEqual(self.prod.stock, 7)  # Stock debe quedar en 7

    def test_vender_stock_insuficiente(self):
        # Verifica que vender más del stock muestre error
        resultado = self.prod.vender(100)
        self.assertIn("❌", resultado)

    def test_precio_negativo_lanza_error(self):
        # Verifica que precio negativo lance ValueError
        with self.assertRaises(ValueError):
            self.prod.precio_base = -100   # Debe lanzar excepción

    def test_herencia_ropa(self):
        # Verifica que Ropa herede correctamente
        ropa = Ropa("Playera", 250.0, 5, "M", "Blanco")
        self.assertIsInstance(ropa, Producto)  # Ropa ES un Producto
        self.assertEqual(ropa.talla, "M")

    def tearDown(self):
        # tearDown() se ejecuta DESPUÉS de cada prueba (limpieza)
        pass


# Ejecutar pruebas:
if __name__ == "__main__":
    unittest.main(verbosity=2)   # verbosity=2 muestra detalle de cada prueba

🏆 Tema 4.5 — Proyecto Final POO

🏢 Ejemplo de Negocio: Sistema de Administración para Empresa Local de Hermosillo

El proyecto final integra TODO lo del curso: clases POO, FLET, MySQL, SOLID, excepciones y pruebas. Elige un negocio local (restaurant, farmacia, ferretería, etc.) y crea un sistema completo de administración.

📋 Requisitos del Proyecto Final
✅ Mínimo 5 clases con POO (herencia, encapsulamiento, polimorfismo)
✅ Interfaz gráfica con FLET (pantallas de login, menú y módulos)
✅ Base de datos MySQL con mínimo 4 tablas relacionadas
✅ CRUD completo para al menos 2 entidades
✅ Manejo de excepciones en toda la app
✅ Mínimo 10 pruebas unitarias
✅ Documentación: diagrama de clases UML + manual de usuario
✅ Repositorio Git con historial de commits

Estructura recomendada del proyecto

🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
La estructura profesional de un proyecto Python POO: cómo organizar el código en carpetas (models/, dao/, ui/, services/, tests/) siguiendo el principio de responsabilidad única.
🚀 ¿QUÉ LOGRAMOS?
Una plantilla de proyecto lista para usar en tu proyecto final: cada archivo tiene su lugar específico, lo que facilita encontrar código, trabajar en equipo y agregar nuevas funciones.
🧠 ¿QUÉ APRENDEMOS?
El patrón de arquitectura en capas: UI → Services → DAO → BD. Cada capa tiene una responsabilidad y solo se comunica con la capa adyacente — base del diseño de software profesional.
estructura_proyecto.txt — Organización de archivos
mi_sistema_poo/
│
├── main.py              ← Punto de entrada de la aplicación
│
├── database/
│   ├── __init__.py
│   ├── conexion.py      ← Clase Singleton de conexión MySQL
│   └── script.sql       ← Script para crear la BD
│
├── models/              ← Clases del negocio (POO)
│   ├── __init__.py
│   ├── producto.py
│   ├── cliente.py
│   └── venta.py
│
├── dao/                 ← Clases DAO (acceso a BD)
│   ├── __init__.py
│   ├── producto_dao.py
│   ├── cliente_dao.py
│   └── venta_dao.py
│
├── ui/                  ← Vistas FLET
│   ├── __init__.py
│   ├── login_view.py
│   ├── menu_view.py
│   ├── productos_view.py
│   └── ventas_view.py
│
├── services/            ← Lógica del negocio (SOLID-S)
│   ├── __init__.py
│   └── venta_service.py
│
├── tests/               ← Pruebas unitarias
│   ├── __init__.py
│   ├── test_producto.py
│   └── test_venta.py
│
├── docs/                ← Documentación
│   ├── manual_usuario.pdf
│   ├── guia_instalacion.pdf
│   └── diagrama_clases.drawio
│
├── requirements.txt     ← pip freeze > requirements.txt
└── README.md            ← Descripción del proyecto
🎯 Objetivo del Ejercicio
📌 ¿QUÉ DEMOSTRAMOS?
El punto de entrada completo del proyecto final: integración de FLET, POO, MySQL, manejo de excepciones y navegación entre pantallas (Login → Menú) en un solo archivo main.py.
🚀 ¿QUÉ LOGRAMOS?
Una aplicación robusta que verifica la conexión a MySQL al arrancar, muestra la pantalla de login, y navega al menú principal — con manejo elegante de errores de conexión.
🧠 ¿QUÉ APRENDEMOS?
Cómo estructurar el main.py de una app real: encabezado de documentación, importaciones, función main(page) con navegación, y try/except para errores de inicio.
main.py — Punto de entrada de la aplicación final
"""
Sistema de Administración — Proyecto Final POO
Universidad Tecnológica de Hermosillo
Materia: Programación Orientada a Objetos
Docente: Bernardo Prado Díaz
Alumno: [Tu Nombre]
Fecha: Mayo–Agosto 2026
"""

import flet as ft
from ui.login_view import LoginView
from ui.menu_view import MenuView
from database.conexion import Conexion

def main(page: ft.Page):
    page.title = "Sistema de Administración UTH"
    page.bgcolor = ft.colors.BLUE_GREY_50
    page.window_width = 1200
    page.window_height = 750
    page.window_center()

    def ir_a_menu(usuario):
        # Función para navegar del login al menú principal
        page.clean()
        page.add(MenuView(page, usuario))
        page.update()

    try:
        db = Conexion()    # Verificamos la conexión a BD al iniciar
        page.add(LoginView(page, ir_a_menu))
    except Exception as e:
        # Si no hay conexión, mostramos error en vez de colapsar
        page.add(ft.Column([
            ft.Icon(ft.icons.ERROR_OUTLINE, size=60, color=ft.colors.RED),
            ft.Text("No se pudo conectar a la base de datos", size=20),
            ft.Text(str(e), size=13, color=ft.colors.GREY_600),
            ft.ElevatedButton("Reintentar", on_click=lambda _: page.clean()),
        ], horizontal_alignment=ft.CrossAxisAlignment.CENTER))

    page.update()

ft.app(target=main)

📄 Plantilla de README.md Profesional

README.md — Plantilla para tu proyecto final
# 🏢 Sistema de Administración [Nombre del Negocio]

## Descripción
Sistema de gestión desarrollado como proyecto final de la asignatura
**Programación Orientada a Objetos** en la Universidad Tecnológica de Hermosillo.

**Alumno:** [Tu Nombre]
**Docente:** Bernardo Prado Díaz
**Periodo:** Mayo–Agosto 2026
**Carrera:** TIID

## Tecnologías
- Python 3.12+
- FLET (GUI)
- MySQL / WampServer 3.3.7
- DBeaver 25.1.0
- Git

## Características
- ✅ CRUD completo de productos y clientes
- ✅ Módulo de ventas con reportes
- ✅ Autenticación de usuarios
- ✅ Manejo de excepciones personalizado
- ✅ Pruebas unitarias

## Instalación
1. Clonar: `git clone [URL]`
2. Activar venv: `venv\Scripts\activate`
3. Instalar: `pip install -r requirements.txt`
4. Ejecutar SQL: abrir `database/script.sql` en DBeaver
5. Configurar contraseña en `database/conexion.py`
6. Ejecutar: `python main.py`

## Estructura del Proyecto
[ver documentación técnica en /docs]

📋 Criterios de Evaluación — Unidad 4 y Proyecto Final

CriterioEstratégicoAutónomoBásicoReceptivo
GitRepositorio con ramas, commits descriptivos, README profesionalCommits regulares con mensajes descriptivosRepositorio con commits inicialesEntrega sin control de versiones
ExcepcionesExcepciones propias, try/except en toda la app, loggingManeja excepciones MySQL y de usuariotry/except básico en operaciones claveSin manejo de excepciones
Pruebas Unitarias+15 pruebas con setUp, tearDown y casos límite10+ pruebas funcionales5+ pruebas básicasMenos de 3 pruebas
Proyecto FinalSistema completo, profesional, documentado, con pruebas y GitSistema funcional con la mayoría de requisitosSistema parcial con funcionalidad básicaProyecto incompleto o no funcional
DocumentaciónUML + manual usuario + guía instalación + comentarios de códigoUML y manual de usuarioREADME y comentarios básicosSin documentación
🎬

Videos de Apoyo — Unidad 4: Git, PyCharm y Proyecto Final

Videos seleccionados para reforzar los conceptos de esta unidad. Míralos después de clase para afianzar lo aprendido.

Git
Git y GitHub desde Cero — Guía Completa

init, add, commit, push, pull, ramas y flujo profesional

🕐 ~40 minES✓ Recomendado
PyCharm
PyCharm — Configuración y Productividad

Atajos, depurador, refactorización y plugins esenciales

🕐 ~22 minES✓ Recomendado
unittest
Pruebas Unitarias en Python con unittest

TestCase, assertEqual, setUp, tearDown — testing profesional

🕐 ~25 minES✓ Recomendado
Excepciones
Manejo de Excepciones — try/except/finally

Excepciones propias, jerarquía de errores, logging

🕐 ~20 minES✓ Recomendado
🎥

Videos de Clase — Unidad 4 — 8 Sesiones

Grabaciones de las sesiones presenciales. Si faltaste o quieres repasar, estas grabaciones son tu mejor aliado. Se actualizan después de cada clase.

Sesión 1
Clase 1 — Git: Instalación y Primeros Commits

git init, git add, git commit, .gitignore, primer repositorio

S1📅 Por confirmar🕐 Próximamente
Sesión 2
Clase 2 — GitHub: Ramas y Pull Requests

git branch, merge, conflictos, GitHub Desktop

S2📅 Por confirmar🕐 Próximamente
Sesión 3
Clase 3 — PyCharm Profesional: Debugger y Refactor

Breakpoints, inspeccionar variables, refactorizar código

S3📅 Por confirmar🕐 Próximamente
Sesión 4
Clase 4 — Manejo de Excepciones Propias

try/except/finally, crear excepciones personalizadas

S4📅 Por confirmar🕐 Próximamente
Sesión 5
Clase 5 — Pruebas Unitarias con unittest

TestCase, assertEqual, setUp/tearDown, estructura de pruebas

S5📅 Por confirmar🕐 Próximamente
Sesión 6
Clase 6 — Proyecto Final: Planificación y Arquitectura

Selección del sistema, UML, estructura de carpetas

S6📅 Por confirmar🕐 Próximamente
Sesión 7
Clase 7 — Proyecto Final: Desarrollo Parte 1

Modelos, clases, base de datos — primeras pantallas FLET

S7📅 Por confirmar🕐 Próximamente
Sesión 8
Clase 8 — Proyecto Final: Entrega y Presentación

Demo del sistema, documentación final, rúbrica de evaluación

S8📅 Por confirmar🕐 Próximamente
💡 ¿Cómo aprovecharlos? Pausa el video cada vez que el profesor escribe código, escríbelo tú mismo y luego compara. Los errores que cometes y resuelves son los que más aprendes.

📎 Apoyo, Requisitos y Herramientas

Documentos oficiales de la asignatura: planeación didáctica, plan académico, calendarios de grupos y escolar, y el reglamento del curso. Consúltalos antes de cada actividad.

🏫 UTH – TIID📅 2026-2👨‍🏫 Bernardo Prado Díaz
Planeacion Didactica

📘 Planeación Didáctica POO 2026-2

Documento oficial que define objetivos, contenidos, estrategias de enseñanza, recursos y criterios de evaluación de la asignatura para el periodo Mayo–Agosto 2026.

📄 Documento oficial🎓 4 Unidades
Contenido: Datos de identificación, propósito, competencias por unidad, actividades de aprendizaje, recursos didácticos y bibliografía.
Plan Academico

🎓 Plan Académico POO

Plan académico oficial de la asignatura Programación Orientada a Objetos para la carrera TIID. Define horas, créditos, prerrequisitos y competencias a desarrollar.

⚡ 105 horas totales💎 6.56 créditos
Distribución: 18 horas teoría + 42 horas práctica. 3° cuatrimestre de la carrera TIID.
Calendario DSM3-1

📅 Calendario POO — Grupo DSM3-1

Calendario de actividades, prácticas, entregas y evaluaciones del grupo DSM3-1. Consulta fechas de entrega y actividades para Mayo–Agosto 2026.

👥 Grupo DSM3-1📆 Mayo–Ago 2026
Importante: Revisa este calendario semanalmente para no perder fechas de entrega de prácticas o evaluaciones.
Calendario DSM3-2

📅 Calendario POO — Grupo DSM3-2

Calendario de actividades, prácticas, entregas y evaluaciones del grupo DSM3-2. Consulta fechas de entrega y actividades para Mayo–Agosto 2026.

👥 Grupo DSM3-2📆 Mayo–Ago 2026
Importante: Revisa este calendario semanalmente para no perder fechas de entrega de prácticas o evaluaciones.
Calendario Escolar UTH

🏫 Calendario Escolar 2025–2026 UTH

Calendario oficial de la Universidad Tecnológica de Hermosillo para el ciclo escolar 2025–2026. Incluye periodos de clases, asuetos, exámenes y eventos institucionales.

🏛️ Oficial UTH📆 2025–2026
Incluye: Inicio y fin de cuatrimestre, vacaciones, días inhábiles y fechas de exámenes finales de todos los cuatrimestres.
Reglas de la Asignatura

📜 Reglas de la Asignatura

Normas y acuerdos del curso de Programación Orientada a Objetos. Léelas con atención: definen dinámica, entregas, asistencia y conducta esperada del alumno.

📋 Principales reglas: ✅ Entrega puntual — sin excepciones salvo causa justificada
✅ Código propio — plagio = calificación 0
✅ Mínimo 80% de asistencia para tener derecho a calificación
✅ Computadora solo para actividades de la clase
✅ Respeto al docente y compañeros en todo momento
✅ Herramientas instaladas desde la 1ª sesión práctica
✅ Participación activa suma al desempeño general
📬

Atención y Soporte

Profr. Bernardo Prado Díaz — POO 2026-2

✉️ Correo de contacto

tenor_prado@yahoo.com.mx

⏱️ Tiempo de respuesta

Máximo 24 horas, aunque generalmente es mucho menor.

🔄 Sin respuesta en 24 hrs

Reenvía tu correo. A veces los mensajes se desvían a SPAM. Revisa también tu bandeja de no deseados.

💡
Consejo para evitar que tu correo vaya al SPAM Los filtros de correo de Yahoo y Hotmail suelen marcar como basura los mensajes sin asunto claro. Al escribir, usa este formato en el campo Asunto:
Duda  —  Programación Orientada a Objetos  —  [Tu Nombre Completo]  —  [Grupo]
Cargando...
🎬
Video no disponible aún El profesor publicará esta grabación después de la sesión.
Revisa esta sección más tarde o consulta el grupo de clase.
📄 ▶ Usa los controles del video para pausar y retroceder ▮▮ ESC para cerrar