🎓 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.
Unidad 1 – Modelado
Conceptos POO, diagramas UML, patrones de diseño, Draw.io, PyCharm.
Unidad 2 – Sintaxis POO
Python orientado a objetos completo: clases, herencia, polimorfismo, encapsulamiento.
Unidad 3 – FLET + MySQL
Interfaces gráficas con FLET, conexión a MySQL, principios SOLID, clean code.
Unidad 4 – IDE, Git y Proyecto
PyCharm avanzado, Git, manejo de excepciones, pruebas unitarias y proyecto final.
🛠️ 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
📐 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.
📋 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.
🔧 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:
python --versionDeberí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
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
| Concepto | Sintaxis Python | Descripción |
|---|---|---|
| Definir clase | class NombreClase: | Crea un molde (plantilla) de objetos |
| Constructor | def __init__(self, param): | Se ejecuta al crear un objeto. self = el propio objeto |
| Atributo | self.nombre = nombre | Guarda un dato dentro del objeto |
| Método | def mi_metodo(self): | Función que pertenece a la clase |
| Crear objeto | obj = NombreClase(args) | Instancia un objeto de la clase |
| Usar objeto | obj.metodo() | Llama a un método del objeto |
| Herencia | class Hijo(Padre): | Hijo hereda todo de Padre |
| Llamar padre | super().__init__() | Llama al constructor del padre |
| Atrib. privado | self.__dato = x | Solo accesible dentro de la clase |
| Atrib. protegido | self._dato = x | Convención: no tocar desde afuera |
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.
__init__, crear objetos, usar self, escribir métodos, llamar métodos de un objeto desde otro, y usar __str__ para imprimir.# ═══════════════════════════════════════════════════════
# 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
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
Patrón Singleton — "Solo existe uno"
Útil cuando solo debe existir UN objeto de una clase. Ejemplo: la configuración del sistema.
ConfiguracionHotel(), Python siempre devuelve el mismo objeto único en memoria.cfg1 is cfg2 devuelve True — ambas variables apuntan al mismo objeto.__new__ en Python (se llama antes de __init__), y cuándo usar el Singleton: configuraciones, conexiones a BD, logs del sistema.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"
Habitacion(...).@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.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
| Criterio | Estratégico | Autónomo | Básico | Receptivo |
|---|---|---|---|---|
| Orden/Organización | Código impecable, comentado, estructura profesional | Bien organizado con comentarios clave | Organizado pero sin comentarios | Desorganizado o incompleto |
| Diagramas UML | Diagramas completos, correctos y bien etiquetados | Diagramas correctos con algunos detalles faltantes | Diagramas básicos incompletos | Intento de diagrama incorrecto |
| Conceptos POO | Domina todos los pilares y los aplica correctamente | Aplica correctamente la mayoría de conceptos | Aplica algunos conceptos básicos | Confunde los conceptos |
| Patrones Diseño | Implementa y explica Singleton, Factory, Observer | Implementa correctamente 2 patrones | Implementa 1 patrón con errores menores | No 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.
¿Qué es la POO? — Explicación Visual
Introducción a clases, objetos y los 4 pilares con animaciones didácticas
Diagramas UML — Clases y Relaciones paso a paso
Cómo dibujar diagramas de clases en Draw.io desde cero
Patrones de Diseño — Singleton y Factory
Implementación práctica de los patrones más usados en la industria
Python desde Cero — Instalación y Primer Programa
Instala Python y PyCharm, ejecuta tu primer script paso a paso
Los 4 Pilares de la POO — Explicación Completa
Abstracción, Encapsulamiento, Herencia y Polimorfismo explicados con ejemplos visuales y código Python
Ejecutar Python en Visual Studio Code — Explicación Completa
Aprende a configurar y ejecutar Python en Visual Studio Code paso a paso
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.
Clase 1 — Introducción a POO y Python
Conceptos básicos: clases, objetos, atributos, métodos
Clase 2 — Los 4 Pilares de la POO
Abstracción, Encapsulamiento, Herencia, Polimorfismo con ejemplos
Clase 3 — Diagramas UML con Draw.io
Diagramas de clases, casos de uso y secuencia
Clase 4 — Patrones de Diseño: Singleton, Factory, Observer
Implementación y casos de uso de los 3 patrones del curso
🐍 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.
📋 Temas de la Unidad
🏛️ LOS 4 PILARES DE LA POO — Orden de Aprendizaje
💡 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
| Elemento | Sintaxis | Descripción |
|---|---|---|
| Clase simple | class Producto: | Clase sin herencia |
| Clase con herencia | class Ropa(Producto): | Hereda de Producto |
| Constructor | def __init__(self, n, p): | Se llama al crear el objeto |
| Atributo de clase | iva = 0.16 (fuera de __init__) | Compartido por todos los objetos |
| Atributo de instancia | self.nombre = n | Único para cada objeto |
| Método regular | def 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.setter | Valida al asignar valor |
| Repr | def __repr__(self): | Representación técnica del objeto |
| Comparación | def __eq__(self, otro): | Define cuándo dos objetos son iguales |
| Longitud | def __len__(self): | Define len(objeto) |
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.
__precio_base), polimorfismo (descripcion()), abstracción (IVA como atributo de clase).super(), usar @property y setters para encapsular datos, y aprovechar el polimorfismo para que el mismo bucle for funcione con cualquier tipo de producto.# ═════════════════════════════════════════════════════════════════
# 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
•
self.dato — público: cualquiera puede leerlo y modificarlo•
self._dato — protegido: convención, no modificar desde afuera•
self.__dato — privado: Python lo "oculta" (name mangling), usa property para acceder__saldo, __historial) no se pueden modificar desde afuera — solo mediante métodos que validan la operación.@property para leer datos privados de forma segura, y cómo el setter valida antes de guardar.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
procesar(), tres comportamientos distintospago.procesar(total) # Cada uno responde diferente
cobrar() no necesita saber si el pago es efectivo, tarjeta o transferencia — simplemente llama procesar() y cada objeto responde diferente.cobrar() — solo creas una nueva subclase.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
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).
| Elemento | Sintaxis | Descripción |
|---|---|---|
| Importar ABC | from abc import ABC, abstractmethod | Módulo estándar de Python para abstracción |
| Clase abstracta | class Animal(ABC): | No se puede instanciar directamente |
| Método abstracto | @abstractmethoddef sonido(self): ... | Las subclases DEBEN implementarlo o Python lanza error |
| Método concreto | def respirar(self): return "inhala" | Puede coexistir con métodos abstractos |
| Subclase concreta | class Perro(Animal): | Debe implementar TODOS los métodos abstractos |
| Propiedad abstracta | @property@abstractmethoddef categoria(self): ... | Obliga a definir una propiedad en la subclase |
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.
ABC y @abstractmethod: la clase ReporteBase define QUÉ métodos deben existir sin implementarlos — cada subclase decide el CÓMO.for.ABC y abstractmethod, la diferencia entre métodos abstractos y concretos, y por qué Python lanza TypeError al intentar instanciar una clase abstracta.# ═══════════════════════════════════════════════════════════════
# 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!
• 🎭 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
pydoc."""
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
| Criterio | Estratégico | Autónomo | Básico | Receptivo |
|---|---|---|---|---|
| Clases y Objetos | Implementa correctamente con encapsulamiento, properties y validación | Clases correctas con encapsulamiento básico | Clases funcionales sin encapsulamiento | Errores de sintaxis o lógica |
| Herencia | Usa super(), herencia múltiple y MRO correctamente | Herencia simple con super() correcto | Herencia básica sin super() | Intenta herencia con errores |
| Encapsulamiento | Atributos privados, properties con validación completa | Atributos privados con getters/setters | Atributos privados sin properties | Sin encapsulamiento |
| Abstracción | Usa ABC, @abstractmethod, propiedades abstractas y método concreto heredado | ABC con @abstractmethod implementado en subclases | Simula abstracción con NotImplementedError | No implementa abstracción |
| Polimorfismo | Implementa override, interfaces y duck typing | Override correcto en subclases | Override básico en 1 método | No implementa polimorfismo |
| Documentación | Docstrings completos con parámetros, returns y ejemplos | Docstrings con descripción y parámetros | Docstrings básicos descriptivos | Sin 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.
Clases Abstractas y ABC en Python
abstractmethod, ABC, contratos entre clases con ejemplos reales
Encapsulamiento: @property, getters y setters
Atributos privados, protegidos y el decorador @property
Herencia Simple y Múltiple — super() y MRO
Cómo funciona la cadena de herencia y super() correctamente
Polimorfismo y Duck Typing en Python
Override de métodos, interfaces implícitas, polimorfismo en acción
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.
Clase 1 — Clases, Objetos y Constructor
__init__, self, atributos, primer objeto en Python
Clase 2 — Pilar 1: Abstracción con ABC
Clases abstractas, @abstractmethod, contrato entre clases
Clase 3 — Pilar 2: Encapsulamiento y @property
Atributos privados, getters, setters, validación
Clase 4 — Pilar 3: Herencia Simple y super()
Herencia, super(), reutilización de código
Clase 5 — Herencia Múltiple y MRO
Resolución de orden de método, diamond problem
Clase 6 — Pilar 4: Polimorfismo y Duck Typing
Override, mismo método, comportamientos distintos
Clase 7 — Documentación Profesional con Docstrings
pydoc, docstrings de módulos, clases y métodos
Clase 8 — Repaso y Ejercicio Integrador U2
Proyecto integrador con los 4 pilares aplicados
🎨 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.
📋 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.
Lógica del negocio
Eventos, Controles
DBeaver
diseño limpio
Completo y funcional
🔧 Instalación de WampServer 3.3.7
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:
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
Instalar FLET
Abre PyCharm → Terminal (menú View → Tool Windows → Terminal) y escribe:
pip install fletEspera a que termine. Verás mensajes de descarga. Al final aparece Successfully installed flet-X.X.X.
Instalar mysql-connector-python
pip install mysql-connector-pythonTu primera app FLET — "Hola Mundo"
Crea un archivo hola_flet.py y copia este código:
on_change
lógica...
page.update()
main(page) recibe la ventana como parámetro, y ft.app() es el motor que la hace funcionar.ft.app(target=main), los componentes ft.Text, ft.ElevatedButton y ft.Column, el parámetro on_click para manejar eventos, y page.add().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)
🧩 Componentes Principales de FLET
| Componente | Uso | Ejemplo |
|---|---|---|
ft.Text | Texto en pantalla | ft.Text("Hola", size=20) |
ft.TextField | Campo de entrada | ft.TextField(label="Nombre") |
ft.ElevatedButton | Botón principal | ft.ElevatedButton("OK", on_click=f) |
ft.TextButton | Botón texto | ft.TextButton("Cancelar") |
ft.Dropdown | Lista desplegable | ft.Dropdown(options=[ft.dropdown.Option("A")]) |
ft.DataTable | Tabla de datos | ft.DataTable(columns=[...], rows=[...]) |
ft.Column | Columna vertical | ft.Column([comp1, comp2]) |
ft.Row | Fila horizontal | ft.Row([comp1, comp2]) |
ft.Container | Contenedor con estilo | ft.Container(content=..., bgcolor=...) |
ft.AlertDialog | Cuadro de diálogo | ft.AlertDialog(title=..., content=...) |
ft.SnackBar | Mensaje temporal | ft.SnackBar(content=ft.Text("OK")) |
ft.AppBar | Barra de título | ft.AppBar(title=ft.Text("App")) |
🐬 Tema 3.2 — Base de Datos MySQL para el Proyecto
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.
CREATE TABLE, tipos de datos (INT, VARCHAR, DECIMAL), restricciones (NOT NULL, AUTO_INCREMENT), y relaciones con FOREIGN KEY.-- 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)
VentaDAO
localhost:3306
clientes
ventas
→ No se abren múltiples conexiones a la BD
Conexion reutilizable que centraliza el acceso a MySQL, con métodos ejecutar() para modificaciones y consultar() para lecturas que devuelven diccionarios.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.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
obtener_todos(), insertar(), actualizar(), eliminar() y buscar_por_nombre(), con protección contra SQL Injection.%s como marcadores seguros en consultas SQL, y el patrón JOIN para obtener datos de múltiples tablas.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}%",))
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.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 — Single Responsibility
Una clase = una responsabilidad. ProductoDAO solo accede a BD. ProductoService solo tiene lógica del negocio. No mezcles ambas.
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 — Liskov Substitution
Una subclase debe poder reemplazar a su padre. Si tienes Animal con método hablar(), todos sus hijos deben tenerlo funcionando.
I — Interface Segregation
Mejor muchas interfaces pequeñas que una grande. No obligues a una clase a implementar métodos que no usa.
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
| Criterio | Estratégico | Autónomo | Básico | Receptivo |
|---|---|---|---|---|
| GUI con FLET | Interfaz profesional, validación completa, UX cuidada | Interfaz funcional con validaciones básicas | Interfaz básica sin validaciones | No funciona o usa otra librería |
| MySQL OOP | DAO completo con CRUD, manejo de errores, patrón Singleton | CRUD funcional con clases bien definidas | Conexión y consultas básicas | Conexión sin OOP |
| Principios SOLID | Aplica los 5 principios visiblemente en el código | Aplica S y O claramente | Menciona SOLID en comentarios | No aplica SOLID |
| Documentación | Manual de usuario + guía de instalación profesional | Manual de usuario completo | README básico | Sin 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 — Apps de Escritorio con Python
Primeros pasos: controles, eventos, páginas y navegación
MySQL con Python — Conector y CRUD Completo
Instalar mysql-connector, conectar, INSERT, UPDATE, DELETE
Principios SOLID — Ejemplos en Python
SRP, OCP, LSP, ISP, DIP aplicados a código orientado a objetos
Instalar WampServer y configurar MySQL local
Guía paso a paso para tener MySQL en Windows con WampServer
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.
Clase 1 — Instalación FLET y Primera App
pip install flet, estructura de proyecto, primer ft.app()
Clase 2 — Controles FLET: Botones, Inputs, Listas
ElevatedButton, TextField, ListView, Row, Column
Clase 3 — Instalación MySQL y WampServer
Configuración de MySQL local, DBeaver, conexión inicial
Clase 4 — CRUD MySQL con Python OOP
Clase Conexion, DAOs, INSERT/SELECT/UPDATE/DELETE
Clase 5 — Conectar FLET con MySQL
DAO + interfaz gráfica: CRUD completo con FLET
Clase 6 — Principios SOLID en el Proyecto
Refactorizar aplicando SRP, OCP y LSP
Clase 7 — Documentación y Cierre U3
Manual de usuario, guía de instalación, entrega final
🚀 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.
📋 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.
Refactoring
Historial
Errores propios
TDD
Documentado
🔀 Guía de Instalación y Uso de 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 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
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 init | Crea un repositorio nuevo | git init mi-proyecto |
git status | Muestra qué archivos cambiaron | git status |
git add | Prepara archivos para commit | git add archivo.py o git add . |
git commit | Guarda una versión con mensaje | git commit -m "Agrega módulo de ventas" |
git log | Historial de commits | git log --oneline |
git branch | Lista o crea ramas | git branch nueva-feature |
git checkout | Cambia de rama | git checkout nueva-feature |
git merge | Une ramas | git merge nueva-feature |
git push | Sube cambios a GitHub | git push origin main |
git pull | Descarga cambios de GitHub | git pull origin main |
git clone | Clona repositorio remoto | git clone https://github.com/user/repo.git |
💡 Tema 4.1 — PyCharm: Atajos y Funciones Profesionales
| Acción | Atajo Windows | ¿Para qué? |
|---|---|---|
| Ejecutar programa | Shift+F10 | Corre el archivo actual |
| Depurador | Shift+F9 | Ejecuta en modo debug (con breakpoints) |
| Agregar breakpoint | F9 (clic en margen) | Pausa la ejecución en esa línea |
| Buscar en proyecto | Ctrl+Shift+F | Busca texto en todos los archivos |
| Ir a definición | Ctrl+B | Salta a donde está definida la función/clase |
| Renombrar símbolo | Shift+F6 | Renombra en todos los archivos a la vez |
| Autocompletar | Ctrl+Space | Muestra sugerencias de código |
| Comentar línea | Ctrl+/ | Comenta/descomenta la línea actual |
| Formatear código | Ctrl+Alt+L | Aplica el estilo PEP 8 automáticamente |
| Terminal integrada | Alt+F12 | Abre terminal dentro de PyCharm |
| Buscar clase | Ctrl+N | Busca una clase por nombre |
| Ver estructura | Alt+7 | Panel con métodos y atributos de la clase |
Virtual Environment (Entorno Virtual)
requirements.txt que permite reproducir el entorno exacto en cualquier otra computadora.pip freeze para documentar dependencias. Práctica usada en toda la industria.# 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
try/except puedes capturarla y responder de forma elegante.puede fallar
try/except/else/finally y cómo crear excepciones personalizadas que comunican errores específicos del negocio.ValueError, ZeroDivisionError heredan de Exception), cómo crear StockInsuficienteError personalizada y manejar errores de conexión MySQL.# ── 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
unittest de Python: cómo estructurar una suite de pruebas con setUp(), múltiples métodos test_*() y assertions que verifican el comportamiento esperado.assertEqual, assertIn, assertAlmostEqual, assertRaises e assertIsInstance — los 5 assertions más comunes. Y el ciclo: escribir prueba → ejecutar → corregir código.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
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.
✅ 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
models/, dao/, ui/, services/, tests/) siguiendo el principio de responsabilidad única.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
main.py.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."""
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
# 🏢 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
| Criterio | Estratégico | Autónomo | Básico | Receptivo |
|---|---|---|---|---|
| Git | Repositorio con ramas, commits descriptivos, README profesional | Commits regulares con mensajes descriptivos | Repositorio con commits iniciales | Entrega sin control de versiones |
| Excepciones | Excepciones propias, try/except en toda la app, logging | Maneja excepciones MySQL y de usuario | try/except básico en operaciones clave | Sin manejo de excepciones |
| Pruebas Unitarias | +15 pruebas con setUp, tearDown y casos límite | 10+ pruebas funcionales | 5+ pruebas básicas | Menos de 3 pruebas |
| Proyecto Final | Sistema completo, profesional, documentado, con pruebas y Git | Sistema funcional con la mayoría de requisitos | Sistema parcial con funcionalidad básica | Proyecto incompleto o no funcional |
| Documentación | UML + manual usuario + guía instalación + comentarios de código | UML y manual de usuario | README y comentarios básicos | Sin 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 y GitHub desde Cero — Guía Completa
init, add, commit, push, pull, ramas y flujo profesional
PyCharm — Configuración y Productividad
Atajos, depurador, refactorización y plugins esenciales
Pruebas Unitarias en Python con unittest
TestCase, assertEqual, setUp, tearDown — testing profesional
Manejo de Excepciones — try/except/finally
Excepciones propias, jerarquía de errores, logging
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.
Clase 1 — Git: Instalación y Primeros Commits
git init, git add, git commit, .gitignore, primer repositorio
Clase 2 — GitHub: Ramas y Pull Requests
git branch, merge, conflictos, GitHub Desktop
Clase 3 — PyCharm Profesional: Debugger y Refactor
Breakpoints, inspeccionar variables, refactorizar código
Clase 4 — Manejo de Excepciones Propias
try/except/finally, crear excepciones personalizadas
Clase 5 — Pruebas Unitarias con unittest
TestCase, assertEqual, setUp/tearDown, estructura de pruebas
Clase 6 — Proyecto Final: Planificación y Arquitectura
Selección del sistema, UML, estructura de carpetas
Clase 7 — Proyecto Final: Desarrollo Parte 1
Modelos, clases, base de datos — primeras pantallas FLET
Clase 8 — Proyecto Final: Entrega y Presentación
Demo del sistema, documentación final, rúbrica de evaluación
📎 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.
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.
Duda — Programación Orientada a Objetos — [Tu Nombre Completo] — [Grupo]