¡Hola! Me da mucho gusto que hayas decidido comenzar tu aventura en el mundo de la programación con Python. Este libro está diseñado especialmente para personas que nunca han programado antes, así que no te preocupes si todo esto te parece completamente nuevo.
Imagínate que vas a aprender a gestionar tu propio almacén digital. Al principio puede parecer abrumador, pero con las herramientas correctas y una buena guía, pronto estarás organizando datos, automatizando tareas y creando soluciones increíbles.
Durante mis años enseñando programación, me he dado cuenta de que muchos recursos asumen que ya sabes ciertas cosas o usan un lenguaje muy técnico desde el principio. Este libro es diferente. Aquí vamos a ir paso a paso, construyendo tu conocimiento como si estuvieras organizando un almacén desde cero.
Mi promesa: Al final de este libro, no solo sabrás programar en Python, sino que entenderás por qué funciona cada cosa y cómo aplicarlo en situaciones reales.
Aquí preparamos tu “espacio de trabajo digital”. Aprenderás qué es Python, por qué es tan poderoso, y cómo configurar tu computadora para programar como un profesional.
Esta es donde aprendes a manejar tu almacén. Descubrirás cómo usar “cajas” (variables) para guardar información, “herramientas” (operadores) para procesarla, y “gerentes inteligentes” (condicionales) y “robots automatizadores” (bucles) para tomar decisiones y repetir tareas.
Aquí las cosas se ponen más sofisticadas. Aprenderás diferentes sistemas de organización (listas, diccionarios, conjuntos), cómo crear herramientas especializadas (funciones), y cómo acceder al “almacén central” de herramientas de Python (módulos).
¡La parte más emocionante! Construirás un sistema completo de gestión de inventario que integra todo lo aprendido, y descubrirás hacia dónde dirigir tu aprendizaje futuro.
Estás a punto de embarcarte en un viaje increíble. La programación no es solo sobre escribir código - es sobre resolver problemas, crear soluciones y hacer que las computadoras trabajen para ti.
Cada experto fue una vez principiante. Yo empecé exactamente donde estás tú ahora, y he visto a cientos de estudiantes hacer esta misma transición exitosamente.
He diseñado este libro basándome en mi experiencia enseñando Python a principiantes. He visto qué funciona, qué confunde, y qué realmente ayuda a las personas a “hacer clic” con la programación.
Mi objetivo no es solo enseñarte Python, sino convertirte en un programador que piensa como programador. Alguien que puede ver un problema y visualizar cómo resolverlo con código.
Tu almacén digital te está esperando. Las herramientas están listas. Tu aventura en Python comienza ahora.
¡Bienvenido al increíble mundo de la programación! 🐍✨
💡 Consejo inicial: Mantén una actitud de curiosidad y experimentación. Los mejores programadores no son los que nunca cometen errores, sino los que aprenden de cada error y siguen intentando. ¡Tú puedes hacerlo!
Requisitos para este viaje:
Paciencia contigo mismo (todos empezamos desde cero)
Cuando empecé a programar, me sentía completamente perdido. Todo parecía muy complicado y pensé muchas veces en rendirme. Pero la programación es como aprender un nuevo idioma: al principio cuesta trabajo, pero una vez que empiezas a “pensar” en ese idioma, todo se vuelve más natural.
Python es especialmente amigable para principiantes porque su sintaxis es muy parecida al inglés. Vas a ver que muchas cosas se leen casi como oraciones normales.
¡Perfecto! En el siguiente capítulo vamos a hablar sobre qué es la programación y por qué Python es una excelente opción para empezar tu viaje como programador.
Recuerda: todos los programadores expertos fueron principiantes alguna vez. Lo importante es dar el primer paso.
¡Bienvenido a tu primer capítulo! Aquí vamos a responder las preguntas más importantes: ¿qué es la programación? y ¿por qué Python es perfecto para empezar?
Imagínate que tienes un amigo muy obediente pero que necesita instrucciones súper específicas para hacer cualquier cosa. Si le dices “haz café”, no va a saber qué hacer. Pero si le dices:
Ve a la cocina
Llena la cafetera con agua
Pon el filtro
Agrega dos cucharadas de café
Enciende la cafetera
Espera 5 minutos
¡Ahí sí va a poder hacerte un café perfecto!
La programación es exactamente eso: darle instrucciones muy específicas a una computadora para que haga lo que queremos.
Piensa en tu rutina matutina. Probablemente haces algo así:
# Pseudocódigo de rutina matutina
def rutina_matutina():
suena_alarma()
if dia in ["lunes", "martes", "miércoles", "jueves", "viernes"]:
levantarse_inmediatamente()
else: # fin de semana
dormir_30_minutos_mas()
ir_al_baño()
lavarse_dientes()
if hay_tiempo():
```python
def preparar_para_trabajo():
if revisar_hora() < "8:00":
desayunar_en_casa()
else:
comprar_algo_en_camino()
¡Felicidades! Acabas de ver tu primer “algoritmo”. Un algoritmo es simplemente una serie de pasos para resolver un problema.
Existen muchos lenguajes de programación. Algunos son como hablar en código militar (muy precisos pero difíciles), otros son como hablar en jerga médica (muy específicos pero complicados).
Python es como hablar con un amigo inteligente: claro, directo y fácil de entender.
En el siguiente capítulo vamos a preparar tu computadora para programar. Vas a instalar Python y configurar tu entorno de desarrollo. ¡Es hora de ensuciarse las manos!
💡 Consejo del capítulo: No trates de memorizar todo. En programación es más importante entender los conceptos que recordar sintaxis específica. ¡Para eso está Google y la documentación!
¡Excelente! Ya sabes qué es la programación y por qué Python es genial. Ahora viene la parte práctica: vamos a preparar tu computadora para que puedas empezar a programar.
No te preocupes si esto te parece técnico al principio. Te voy a guiar paso a paso, y al final de este capítulo vas a tener todo listo para escribir tu primer programa.
Para instalar Python necesitas permisos de administrador en tu computadora. Si es tu computadora personal, probablemente los tienes. Si es del trabajo o escuela, tal vez necesites pedirle ayuda al departamento de IT.
Si ves un número que empiece con 3 (como 3.8, 3.9, 3.10, 3.11, etc.), ¡perfecto! Tienes Python 3 instalado.
⚠️ Nota importante: Si ves algo como “Python 2.7.x”, significa que tienes una versión muy antigua. En algunos sistemas, necesitas usar python3 en lugar de python.
Ahora vamos a probar que Python funciona. En la terminal, escribe:
python
Deberías ver algo así:
# Ejemplo de salida:
Python 3.11.5 (main, Aug 24 2023, 15:18:16) [Clang 14.0.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
¡Felicidades! Estás dentro del intérprete interactivo de Python. Esos tres símbolos >>> significan que Python está esperando que le des una instrucción.
Prueba escribir:
print("¡Hola, mundo!")
Y presiona Enter. Deberías ver:
# Ejemplo de salida:
¡Hola, mundo!
¡Acabas de ejecutar tu primer programa en Python! 🎉
Ahora que tienes Python funcionando, necesitas un buen editor para escribir tu código. Es como elegir un buen cuaderno para escribir: técnicamente puedes usar cualquier cosa, pero uno bueno hace la diferencia.
En el siguiente capítulo vamos a profundizar en la sintaxis de Python. Aprenderás sobre:
Las reglas básicas de escritura en Python
Cómo Python entiende tu código
La importancia de la indentación
Cómo escribir comentarios útiles
¡Ya tienes las herramientas, ahora vamos a aprender el idioma! 🐍
💡 Consejo del capítulo: No te preocupes si algo no funciona a la primera. La configuración del entorno es la parte más técnica de todo el proceso. Una vez que esté lista, ¡todo lo demás será mucho más divertido!
# Ejemplo de salida:
Python 3.11.5 (tags/v3.11.5:cce6ba9, Aug 24 2023, 14:38:34) [MSC v.1936 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
print("¡Python funciona perfectamente en Windows!")
import sys
print(f"Versión de Python: {sys.version}")
print(f"Ubicación de Python: {sys.executable}")
Si ves Python 3.8 o superior, técnicamente puedes usar esa versión. Pero te recomiendo instalar la más reciente para tener todas las características nuevas.
print("¡Python funciona perfectamente en macOS!")
import sys
print(f"Versión de Python: {sys.version}")
print(f"Ubicación de Python: {sys.executable}")
# Verificar versión de Python
python3 --version
# Verificar versión de pip
pip3 --version
# Abrir intérprete interactivo
python3
# Ejecutar un archivo Python
python3 mi_archivo.py
# Instalar un paquete
pip3 install nombre_paquete
# Ver paquetes instalados
pip3 list
🍎 Resumen para macOS: Usa Homebrew (brew install python) o descarga desde python.org. Recuerda usar python3 y considera crear aliases para mayor comodidad.
# Descargar Python 3.11.5
cd /tmp
wget https://www.python.org/ftp/python/3.11.5/Python-3.11.5.tgz
tar -xf Python-3.11.5.tgz
cd Python-3.11.5
# Configurar la compilación
./configure --enable-optimizations --with-ensurepip=install
# Compilar (esto puede tomar un rato)
make -j 8
# Instalar
sudo make altinstall
Nota: Usamos altinstall en lugar de install para no sobrescribir el Python del sistema.
🐧 Resumen para Linux: Usa el gestor de paquetes de tu distribución, considera compilar desde fuente para versiones más nuevas, y siempre usa entornos virtuales para proyectos.
¡Perfecto! Ya tienes Python instalado. Ahora necesitas un buen editor para escribir tu código. Es como elegir un buen cuaderno para escribir: técnicamente puedes usar cualquier cosa, pero uno bueno hace toda la diferencia.
¡Perfecto! Ya tienes Python instalado y un editor configurado. En el siguiente capítulo vamos a aprender la sintaxis básica de Python y escribir nuestros primeros programas reales.
💡 Consejo final: No te obsesiones con elegir el editor “perfecto”. Cualquiera de los recomendados te servirá bien. Lo importante es empezar a programar. ¡Siempre puedes cambiar después!
¡Excelente! Ya tienes Python instalado y tu editor configurado. Ahora viene la parte emocionante: ¡vamos a escribir código Python de verdad!
En este capítulo vas a aprender las reglas básicas de Python y escribir varios programas. No te preocupes si al principio te parece extraño - es como aprender un nuevo idioma, y Python es uno de los más fáciles de aprender.
Antes de empezar, déjame mostrarte por qué Python es tan popular. Mira este ejemplo:
# En otros lenguajes podrías escribir algo así:
# if (edad >= 18) {
# printf("Eres mayor de edad\n");
# }
# En Python escribes simplemente:
if edad >= 18:
print("Eres mayor de edad")
¿Ves la diferencia? Python se lee casi como español normal. ¡Esa es su magia!
# Estas son variables DIFERENTES:
nombre = "Juan"
Nombre = "María"
NOMBRE = "Pedro"
print(nombre) # Imprime: Juan
print(Nombre) # Imprime: María
print(NOMBRE) # Imprime: Pedro
En otros lenguajes usas llaves {} para agrupar código. En Python usas espacios o tabs:
# ✅ CORRECTO
if edad >= 18:
print("Eres mayor de edad")
print("Puedes votar")
# ❌ INCORRECTO
if edad >= 18:
print("Eres mayor de edad") # Error: falta indentación
💡 Regla de oro: Usa siempre 4 espacios para indentar. La mayoría de los editores lo hacen automáticamente.
# Esto es un comentario - Python lo ignora
print("Hola") # También puedes comentar al final de una línea
# Los comentarios son para explicar tu código
# Son súper útiles para recordar qué hace cada parte
Vamos a crear un programa que demuestre las reglas básicas. Crea un archivo llamado sintaxis_basica.py:
# Mi segundo programa en Python
# Autor: [Tu nombre]
print("=== Programa de Sintaxis Básica ===")
# Variables (las veremos más a detalle en el siguiente capítulo)
mi_nombre = "Estudiante de Python"
mi_edad = 25
# Usando las variables
print("Hola, soy", mi_nombre)
print("Tengo", mi_edad, "años")
# Ejemplo de indentación con condicional
if mi_edad >= 18:
print("Soy mayor de edad")
print("Puedo programar sin supervisión 😄")
print("¡Fin del programa!")
Vamos a crear un programa más interesante. Crea un archivo llamado tarjeta_presentacion.py:
# Programa: Mi Tarjeta de Presentación Digital
# Descripción: Un programa que muestra información personal
print("=" * 40) # Imprime 40 signos de igual
print(" MI TARJETA DE PRESENTACIÓN")
print("=" * 40)
# Información personal (cambia estos datos por los tuyos)
nombre = "Tu Nombre Aquí"
edad = 25
ciudad = "Tu Ciudad"
hobby = "Tu Hobby Favorito"
# Mostrar la información
print() # Línea en blanco
print("Nombre:", nombre)
print("Edad:", edad, "años")
print("Ciudad:", ciudad)
print("Hobby favorito:", hobby)
print()
# Un mensaje personalizado
if edad >= 18:
print("¡Soy mayor de edad y estoy aprendiendo Python!")
else:
print("¡Soy joven y ya estoy aprendiendo Python!")
print()
print("¡Gracias por conocerme! 🐍")
print("=" * 40)
Personaliza este programa:
Cambia los valores de las variables por tu información real
Agrega más información si quieres (país, comida favorita, etc.)
# Información del usuario
nombre = "Juan"
edad = 25
# Procesar información
if edad >= 18:
print("Mayor de edad")
# Mensaje final
print("¡Hasta luego!")
Te doy este código con errores. Encuéntralos y corrígelos:
# Programa con errores - ¡encuéntralos!
print("Iniciando programa"
nombre = "Python"
if nombre == "Python":
print("¡Me gusta este lenguaje!")
# Falta algo aquí...
print("Fin del programa"
En el siguiente capítulo vamos a profundizar en variables y tipos de datos. Aprenderás:
Qué son las variables y cómo usarlas
Los diferentes tipos de datos en Python
Cómo convertir entre tipos
Reglas para nombrar variables
¡Y mucho más!
Ya tienes las bases de la sintaxis. Ahora vamos a aprender a manejar información de verdad.
💡 Consejo del capítulo: No te preocupes por memorizar toda la sintaxis. Lo importante es entender los conceptos. Con la práctica, escribir código Python se volverá tan natural como escribir en español. ¡La clave está en practicar un poco cada día!
La sintaxis son las “reglas de gramática” de Python. Así como el español tiene reglas sobre cómo formar oraciones, Python tiene reglas sobre cómo escribir código. ¡La buena noticia es que Python tiene menos reglas que el español!
La indentación son los espacios al inicio de una línea. En Python, estos espacios no son opcionales - son parte del código.
# Sin indentación (nivel 0)
print("Esta línea no tiene indentación")
# Con indentación (nivel 1)
if True:
print("Esta línea tiene 4 espacios de indentación")
# Con más indentación (nivel 2)
if True:
print("Esta línea tiene 8 espacios de indentación")
Python usa la indentación para saber qué código va junto:
# Ejemplo: código que va junto
if edad >= 18:
print("Eres mayor de edad") # Estas dos líneas
print("Puedes obtener tu licencia") # van juntas
print("Este mensaje siempre se muestra") # Esta línea es independiente
# ❌ Línea muy larga
mensaje_muy_largo = "Este es un mensaje extremadamente largo que es difícil de leer porque no cabe bien en la pantalla"
# ✅ Dividir con paréntesis
mensaje_muy_largo = ("Este es un mensaje extremadamente largo "
"que ahora es más fácil de leer "
"porque está dividido en varias líneas")
# ✅ Dividir con barra invertida
mensaje_muy_largo = "Este es un mensaje extremadamente largo " \
"que también es fácil de leer"
# ✅ Válidos
nombre = "Juan"
_edad = 25
mi_variable = 100
# ❌ Inválidos
2nombre = "Juan" # No puede empezar con número
mi-variable = 100 # No puede tener guiones
Solo pueden contener letras, números y guiones bajos
# ✅ Válidos
nombre1 = "Juan"
mi_edad = 25
variable_muy_larga = 100
# ❌ Inválidos
mi@variable = 100 # No puede tener @
mi variable = 100 # No puede tener espacios
mi-variable = 100 # No puede tener guiones
No pueden ser palabras reservadas
# ❌ Estas palabras están reservadas
if = 5 # Error: 'if' es palabra reservada
for = 10 # Error: 'for' es palabra reservada
def = 20 # Error: 'def' es palabra reservada
# Ejemplo 1 - Faltan los dos puntos
if True:
print("Hola")
# Ejemplo 2 - Falta indentación
if True:
print("Hola")
# Ejemplo 3 - print va en minúsculas
print("Hola mundo")
# Ejemplo 4 - No se pueden usar guiones en nombres
mi_nombre = "Juan"
# Ejemplo 5 - Indentación inconsistente
if True:
print("Línea 1")
print("Línea 2")
💡 Recuerda: La sintaxis de Python está diseñada para ser legible. Si tu código se ve limpio y organizado, probablemente está bien escrito. ¡La práctica hace al maestro!
Los comentarios son como notas que escribes para ti mismo (y otros programadores) para explicar qué hace tu código. Python los ignora completamente, pero son súper importantes para escribir código que sea fácil de entender.
Imagínate que escribes un programa hoy y lo revisas en 6 meses. Sin comentarios, probablemente te preguntes: “¿Qué diablos estaba pensando cuando escribí esto?” 😅
# Calcular el precio final de un producto con IVA
precio_producto = 1000 # Precio base del producto
iva = precio_producto * 0.16 # IVA del 16%
precio_final = precio_producto + iva # Precio total
print(precio_final) # Mostrar el resultado
Se escriben con # y Python ignora todo lo que viene después:
# Este es un comentario completo
print("Hola mundo") # Este es un comentario al final de la línea
# Puedes usar comentarios para "desactivar" código temporalmente
# print("Esta línea no se ejecutará")
print("Esta línea sí se ejecutará")
Para explicaciones largas, puedes usar comillas triples:
"""
Este es un docstring.
Se usa para documentar funciones, clases o módulos.
Puede ocupar varias líneas y es muy útil
para explicaciones detalladas.
"""
print("Mi programa")
# ✅ BUENO - explica por qué
precio_final = precio * 1.16 # Agregamos IVA del 16%
# ❌ MALO - solo repite lo que hace el código
precio_final = precio * 1.16 # Multiplicamos precio por 1.16
# ✅ BUENO
# Usamos el algoritmo de Luhn para validar números de tarjeta de crédito
# porque es el estándar de la industria bancaria
if validar_tarjeta(numero):
procesar_pago()
# ✅ BUENO
# CUIDADO: Esta función modifica la lista original
# Si necesitas conservar la original, haz una copia primero
def ordenar_lista(mi_lista):
mi_lista.sort()
# ✅ BUENO
# Usamos un diccionario en lugar de una lista
# porque necesitamos búsquedas rápidas por nombre
usuarios = {
"juan": {"edad": 25, "email": "juan@email.com"},
"maria": {"edad": 30, "email": "maria@email.com"}
}
# --- Datos del usuario ---
nombre = "Juan"
edad = 25
# --- Validaciones ---
if edad >= 18:
print("Válido")
# --- Fin del programa ---
print("Terminado")
Estos son comentarios especiales que muchos editores resaltan:
# TODO: Agregar validación de email
email = input("Tu email: ")
# FIXME: Este cálculo no funciona con números negativos
resultado = calcular_raiz(numero)
# HACK: Solución temporal hasta arreglar el bug #123
if sistema == "windows":
ruta = ruta.replace("/", "\\")
# NOTE: Esta función será removida en la versión 2.0
def funcion_antigua():
pass
"""
Programa: Verificador de Mayoría de Edad
Autor: [Tu nombre]
Fecha: [Fecha actual]
Descripción: Solicita nombre y edad al usuario y determina
si es mayor o menor de edad.
"""
# ================================
# ENTRADA DE DATOS
# ================================
# Solicitar información personal al usuario
nombre_usuario = input("Ingresa tu nombre: ")
edad_usuario = int(input("Ingresa tu edad: "))
# ================================
# PROCESAMIENTO
# ================================
# Verificar mayoría de edad (18 años en México)
if edad_usuario >= 18:
print("Hola", nombre_usuario, "eres mayor de edad")
else:
print("Hola", nombre_usuario, "eres menor de edad")
# ================================
# FIN DEL PROGRAMA
# ================================
# El programa termina aquí
Aunque tu código esté en español, los comentarios técnicos a veces se escriben en inglés:
# Configuración del sistema
API_KEY = "mi_clave_secreta"
# TODO: Add error handling for API failures
# FIXME: Memory leak in data processing function
def procesar_datos():
pass
Esto es normal y está bien. Lo importante es ser consistente.
# Este programa suma dos números
a = 5 # Variable a es igual a 5
b = 10 # Variable b es igual a 10
resultado = a * b # Sumamos a y b
print(resultado) # Imprimimos el resultado
Aquí tienes una plantilla que puedes usar para tus programas:
"""
Programa: [Nombre del programa]
Autor: [Tu nombre]
Fecha: [Fecha]
Versión: 1.0
Descripción:
[Explica qué hace tu programa]
Uso:
[Cómo se usa el programa]
"""
# ================================
# IMPORTACIONES Y CONFIGURACIÓN
# ================================
# [Aquí van los imports si los hay]
# ================================
# FUNCIONES
# ================================
# [Aquí van las funciones si las hay]
# ================================
# PROGRAMA PRINCIPAL
# ================================
if __name__ == "__main__":
# [Aquí va tu código principal]
pass
✅ Ayudar a otros programadores (¡y a ti mismo en el futuro!)
✅ Organizar secciones de código
✅ Marcar tareas pendientes (TODO)
Recuerda: el código se escribe una vez, pero se lee muchas veces. ¡Haz que sea fácil de entender!
💡 Consejo profesional: Un buen programador no es el que escribe código complejo, sino el que escribe código simple y bien documentado que cualquiera puede entender. ¡Los comentarios son tu mejor herramienta para esto!
Ya sabes escribir código Python, pero ¿sabías que hay varias formas de ejecutarlo? Cada método tiene sus ventajas y es útil en diferentes situaciones. ¡Vamos a explorarlas todas!
>>> print("¡Hola desde el intérprete!")
¡Hola desde el intérprete!
>>> 2 + 2
4
>>> nombre = "Python"
>>> print(f"Me gusta {nombre}")
Me gusta Python
>>> exit()
# Siempre incluye esta línea al final de tus scripts principales
if __name__ == "__main__":
# Tu código principal aquí
print("Ejecutando programa principal")
# Usa help() para obtener ayuda
>>> help(print)
# Usa dir() para ver qué métodos tiene un objeto
>>> dir("hola")
# Usa type() para ver el tipo de una variable
>>> type(42)
Cada método tiene su lugar. ¡Experimenta con todos y encuentra cuáles prefieres para diferentes situaciones!
💡 Consejo profesional: Los programadores expertos usan diferentes métodos según la situación. El intérprete para probar ideas, archivos .py para programas serios, y la línea de comandos para tareas rápidas. ¡Dominar todos te hace más eficiente!
¡Felicidades! Ya sabes escribir código Python básico. Ahora vamos a aprender sobre variables, que son una de las herramientas más importantes en programación.
💭 Nota del autor: Cuando enseño programación, me gusta usar analogías que conecten conceptos abstractos con cosas cotidianas. La analogía del “almacén con cajas” es mi favorita para explicar variables y tipos de datos.
Imagínate que eres el encargado de un almacén gigante. Tienes miles de cajas de diferentes tamaños y colores. Cada caja puede guardar un tipo específico de cosa:
Caja verde mediana → Solo números decimales (como 3.14, -2.5)
Caja amarilla grande → Solo texto (como “Hola”, “Python es genial”)
Caja roja pequeña → Solo valores de verdadero/falso
En Python, las variables son exactamente como estas cajas. Cada variable:
Tiene un nombre (como una etiqueta en la caja)
Puede guardar un valor (el contenido de la caja)
Tiene un tipo específico (el tipo de caja que es)
# Creando nuestras "cajas" (variables)
edad = 25 # Caja azul con el número 25
precio = 19.99 # Caja verde con el número 19.99
nombre = "Juan" # Caja amarilla con el texto "Juan"
es_estudiante = True # Caja roja con el valor True
Una variable es como una caja etiquetada donde puedes guardar información para usarla después. Es uno de los conceptos más fundamentales en programación.
# Sin variables (difícil de entender)
print("Hola, mi nombre es Juan")
print("Juan tiene 25 años")
print("Juan vive en México")
# Con variables (mucho más claro)
nombre = "Juan"
edad = 25
pais = "México"
# 🌟 Usando f-strings (recomendado)
print(f"Hola, mi nombre es {nombre}")
print(f"{nombre} tiene {edad} años")
print(f"{nombre} vive en {pais}")
¿Ves la diferencia? Con variables, si quieres cambiar el nombre de “Juan” a “María”, solo cambias una línea en lugar de tres. ¡Esto hace que tu código sea mucho más fácil de mantener!
💭 Nota del autor: Me gusta clasificar los tipos de datos en Python en dos grandes categorías: elementales y compuestos. Esta es mi perspectiva personal que me ha ayudado a enseñar estos conceptos de manera más clara.
Los tipos elementales son los componentes fundamentales que no se pueden descomponer más. Son como los ladrillos individuales con los que construiremos todo lo demás:
nombre = "María"
apellido = 'González' # Comillas simples también funcionan
mensaje = "¡Hola, cómo estás!"
direccion = "Calle Falsa 123"
print(f"El nombre es {nombre} y su tipo es: {type(nombre)}") # <class 'str'>
💭 Nota del autor: En capítulos posteriores veremos en detalle estos tipos compuestos, pero quiero mencionarlos ahora para que tengas una visión completa.
Los tipos compuestos son combinaciones organizadas de tipos elementales. Es como usar nuestros ladrillos básicos para construir estructuras más complejas:
# 📦 Lista - colección ordenada y modificable
nombres = ["Ana", "Carlos", "Luis"] # Una caja con compartimentos
# 📋 Tupla - colección ordenada e inmutable
coordenadas = (10.5, 20.8) # Una caja sellada
# 📚 Diccionario - colección de pares clave-valor
persona = {"nombre": "Ana", "edad": 25} # Una caja con etiquetas internas
# 🎯 Conjunto - colección desordenada de elementos únicos
colores = {"rojo", "verde", "azul"} # Una caja que elimina duplicados
Una de las cosas geniales de las variables es que puedes cambiar su contenido:
# Empezamos con una caja
mi_caja = "Hola"
print(f"Contenido inicial: {mi_caja}") # Hola
# Cambiamos el contenido de la misma caja
mi_caja = "Adiós"
print(f"Nuevo contenido: {mi_caja}") # Adiós
# Incluso podemos cambiar el tipo de contenido
mi_caja = 42
print(f"Contenido cambiado a número: {mi_caja}") # 42
mi_caja = True
print(f"Contenido cambiado a booleano: {mi_caja}") # True
Esto es como tomar la misma caja, vaciarla, y meter algo completamente diferente. En Python esto es posible porque es un lenguaje de tipado dinámico, lo que significa que una variable puede cambiar de tipo durante la ejecución del programa.
💡 Comprueba tu comprensión: ¿Qué pasaría si intentas sumar mi_caja + 10 después de cada cambio de valor? ¿Funcionaría en todos los casos?
Vamos a crear un programa que use diferentes tipos de cajas para guardar información de una persona:
# ================================
# INFORMACIÓN PERSONAL
# ================================
# Cajas de texto (str)
nombre = "Ana García"
apellido = "López"
email = "ana.garcia@email.com"
ciudad = "Ciudad de México"
# Cajas de números enteros (int)
edad = 28
año_nacimiento = 1995
numero_hijos = 2
# Cajas de números decimales (float)
altura = 1.65
peso = 58.5
salario = 25000.50
# Cajas de verdadero/falso (bool)
es_estudiante = False
tiene_trabajo = True
esta_casado = True
tiene_mascota = True
# ================================
# MOSTRAR INFORMACIÓN
# ================================
print("=== PERFIL DE USUARIO ===")
# 🌟 Usando f-strings para mostrar información
print(f"Nombre completo: {nombre} {apellido}")
print(f"Email: {email}")
print(f"Ciudad: {ciudad}")
print()
print("=== DATOS PERSONALES ===")
print(f"Edad: {edad} años")
print(f"Año de nacimiento: {año_nacimiento}")
print(f"Altura: {altura} metros")
print(f"Peso: {peso} kg")
print()
print("=== ESTADO ACTUAL ===")
# Usando expresiones condicionales dentro de f-strings
print(f"Estudiante: {'✓ Sí' if es_estudiante else '✗ No'}")
print(f"Trabajo: {'✓ Sí' if tiene_trabajo else '✗ No'}")
if tiene_trabajo:
print(f"Salario: ${salario:,.2f} pesos") # Con formato de miles y 2 decimales
print(f"Estado civil: {'✓ Casado/a' if esta_casado else '✗ Soltero/a'}")
print(f"Mascota: {'✓ Sí' if tiene_mascota else '✗ No'}")
print(f"Número de hijos: {numero_hijos}")
# ================================
# CÁLCULOS CON VARIABLES
# ================================
print("\n=== CÁLCULOS ADICIONALES ===")
imc = peso / (altura ** 2)
print(f"Índice de Masa Corporal: {imc:.2f}")
años_trabajando = edad - 22 # Suponiendo que empezó a trabajar a los 22
print(f"Años aproximados trabajando: {años_trabajando}")
salario_anual = salario * 12
print(f"Salario anual estimado: ${salario_anual:,.2f} pesos")
💡 Comprueba tu comprensión: ¿Cómo modificarías este programa para incluir información sobre estudios universitarios? ¿Qué variables añadirías y de qué tipo serían?
2nombre = "Juan" # No puede empezar con número
mi-variable = 100 # No puede tener guiones
mi variable = 100 # No puede tener espacios
if = 25 # No puede ser palabra reservada
# ✅ BUENO - se entiende qué contiene
nombre_usuario = "Ana"
edad_en_años = 25
precio_producto = 19.99
# ❌ MALO - no se entiende qué es
n = "Ana"
x = 25
p = 19.99
💭 Nota del autor: Después de años programando, he aprendido que el tiempo que inviertes en nombrar bien tus variables se recupera con creces cuando tienes que revisar o modificar tu código semanas o meses después. Un buen nombre de variable es como una buena señalización en una carretera: te dice exactamente dónde estás y hacia dónde vas.
Python te permite “espiar” dentro de las cajas para ver qué tipo de contenido tienen usando la función type():
# Crear diferentes tipos de cajas
numero = 42
texto = "Hola"
decimal = 3.14
verdadero = True
# Ver qué tipo de caja es cada una
print(f"numero es tipo: {type(numero)}") # <class 'int'>
print(f"texto es tipo: {type(texto)}") # <class 'str'>
print(f"decimal es tipo: {type(decimal)}") # <class 'float'>
print(f"verdadero es tipo: {type(verdadero)}") # <class 'bool'>
Es como tener una máquina de rayos X que te permite ver qué tipo de contenido hay dentro de cada caja sin tener que abrirla.
Vamos a crear un programa que use la analogía de las cajas para guardar tu información:
# ================================
# EJERCICIO: MIS CAJAS PERSONALES
# ================================
print("=== CREANDO MIS CAJAS DE INFORMACIÓN ===")
print()
# Caja amarilla para tu nombre
mi_nombre = "Tu nombre aquí"
print(f"📦 Caja amarilla (texto): {mi_nombre}")
# Caja azul para tu edad
mi_edad = 25 # Cambia por tu edad real
print(f"📦 Caja azul (número entero): {mi_edad}")
# Caja verde para tu altura
mi_altura = 1.70 # Cambia por tu altura real
print(f"📦 Caja verde (número decimal): {mi_altura}")
# Caja roja para si eres estudiante
soy_estudiante = True # Cambia por tu situación real
print(f"📦 Caja roja (verdadero/falso): {soy_estudiante}")
print()
print("=== INFORMACIÓN DE MIS CAJAS ===")
print(f"Nombre: {mi_nombre} - Tipo: {type(mi_nombre)}")
print(f"Edad: {mi_edad} - Tipo: {type(mi_edad)}")
print(f"Altura: {mi_altura} - Tipo: {type(mi_altura)}")
print(f"Estudiante: {soy_estudiante} - Tipo: {type(soy_estudiante)}")
# Añadamos algunos cálculos
print("\n=== CÁLCULOS CON MIS DATOS ===")
edad_en_meses = mi_edad * 12
print(f"Mi edad en meses: {edad_en_meses}")
altura_en_cm = mi_altura * 100
print(f"Mi altura en centímetros: {altura_en_cm}")
años_hasta_jubilacion = 65 - mi_edad
print(f"Años hasta la jubilación: {años_hasta_jubilacion}")
Tu tarea:
Copia este código
Cambia los valores por tu información real
Ejecuta el programa
Añade al menos 3 variables más con diferentes tipos de datos
Crea al menos 2 cálculos adicionales usando tus variables
💡 Desafío adicional: Intenta crear una variable que combine información de otras variables. Por ejemplo, una variable presentacion que contenga un texto que use tu nombre y edad.
Una cosa interesante es que puedes tomar el contenido de una caja y ponerlo en otra:
# Crear las cajas originales
caja_a = "Hola"
caja_b = "Mundo"
print("Antes del intercambio:")
print(f"Caja A: {caja_a}")
print(f"Caja B: {caja_b}")
# Intercambiar contenido (necesitamos una caja temporal)
caja_temporal = caja_a # Guardamos el contenido de A
caja_a = caja_b # Ponemos el contenido de B en A
caja_b = caja_temporal # Ponemos el contenido temporal en B
print("Después del intercambio:")
print(f"Caja A: {caja_a}")
print(f"Caja B: {caja_b}")
En Python hay una forma más fácil y elegante:
# Intercambio fácil en Python
caja_a, caja_b = caja_b, caja_a
¡Es como si Python fuera un mago que puede intercambiar el contenido de las cajas instantáneamente! Esta es una característica especial de Python que no todos los lenguajes de programación tienen.
💡 Comprueba tu comprensión: ¿Qué pasaría si intentas intercambiar tres variables a la vez? Por ejemplo: a, b, c = c, a, b. ¿Cuáles serían los valores finales?
En el siguiente capítulo vamos a aprender sobre conversión de tipos. Descubrirás:
Cómo cambiar el contenido de una caja azul (int) a una caja verde (float)
Cómo convertir números a texto y viceversa
Qué pasa cuando intentas mezclar diferentes tipos de cajas
Trucos para trabajar con diferentes tipos de datos
Más sobre f-strings y formateo de texto
¡Ya tienes tus cajas organizadas, ahora vamos a aprender a transformar su contenido! 📦➡️📦
💡 Consejo del capítulo: Piensa siempre en las variables como cajas etiquetadas y en los tipos de datos como materiales de construcción. Los tipos elementales son tus ladrillos básicos, y con ellos construirás estructuras más complejas (tipos compuestos) en los próximos capítulos. Esta forma de pensar te ayudará a entender conceptos más avanzados como listas (cajas con compartimentos) y diccionarios (cajas con etiquetas internas). ¡La programación es como organizar y construir en un almacén gigante!
Imagínate que eres el encargado de un almacén gigante. Tienes miles de cajas de diferentes tamaños, colores y formas. Cada caja puede guardar algo específico, y cada una tiene una etiqueta para saber qué contiene. Eso es exactamente lo que son las variables en programación.
💭 Nota del autor: Cuando enseño programación, siempre empiezo con esta analogía del almacén porque hace que un concepto abstracto como las “variables” se vuelva algo que puedes visualizar. Verás que esta analogía nos seguirá ayudando cuando lleguemos a conceptos más complejos.
Una variable es como una caja etiquetada en la memoria de tu computadora donde puedes guardar información para usarla después. Es uno de los conceptos más fundamentales en programación, y por suerte, es bastante intuitivo.
# Tu computadora es como un almacén gigante
# Las variables son las cajas etiquetadas
# Los valores son lo que guardas dentro de cada caja
nombre = "Ana" # Caja etiquetada "nombre" con "Ana" adentro
edad = 25 # Caja etiquetada "edad" con 25 adentro
altura = 1.65 # Caja etiquetada "altura" con 1.65 adentro
es_estudiante = True # Caja etiquetada "es_estudiante" con True adentro
¿Ves lo que estamos haciendo? Estamos creando cajas con etiquetas (nombres de variables) y guardando diferentes tipos de información dentro de ellas.
print("Hola, mi nombre es Ana")
print("Ana tiene 25 años")
print("Ana mide 1.65 metros")
print("Ana es estudiante")
print("El año que viene Ana tendrá 26 años")
¿Qué pasa si quieres cambiar “Ana” por “Carlos”? ¡Tienes que cambiar 5 líneas! Imagina si tu programa tuviera cientos de líneas… sería un desastre.
El signo igual (=) en programación no significa “igual que” como en matemáticas. Significa “guarda el valor de la derecha en la variable de la izquierda”.
El valor es lo que realmente estás guardando en la caja. Puede ser texto, números, valores verdadero/falso, y muchas otras cosas que veremos más adelante.
💭 Nota del autor: Como mencioné en la sección de tipos de datos, me gusta pensar en los tipos de datos como elementales (básicos) y compuestos. Aquí nos enfocaremos en los tipos elementales, que son como los ladrillos básicos con los que construiremos estructuras más complejas después.
En nuestro almacén de variables, tenemos diferentes tipos de cajas para diferentes tipos de cosas:
# Estas cajas solo guardan números sin decimales
edad = 25
año = 2024
puntos = 1500
temperatura = -5
# Son perfectas para:
# - Edades
# - Años
# - Cantidades exactas
# - Puntuaciones
💡 Comprueba tu comprensión: ¿Qué pasaría si necesitaras cambiar el precio del producto? ¿Cuántas líneas tendrías que modificar? ¿Y si quisieras añadir un 10% de descuento al precio?
Una de las cosas más útiles de las variables es que puedes cambiar su contenido a medida que tu programa se ejecuta:
# Empezamos con una caja
puntos_juego = 0
print(f"Puntos iniciales: {puntos_juego}") # 0
# El jugador gana puntos
puntos_juego = 100
print(f"Después del primer nivel: {puntos_juego}") # 100
# Gana más puntos
puntos_juego = puntos_juego + 150 # También podemos escribir: puntos_juego += 150
print(f"Después del segundo nivel: {puntos_juego}") # 250
# Pierde puntos
puntos_juego = puntos_juego - 50 # También podemos escribir: puntos_juego -= 50
print(f"Después de perder una vida: {puntos_juego}") # 200
💡 Comprueba tu comprensión: ¿Qué valor tendría puntos_juego si después añadimos puntos_juego *= 2? ¿Y si luego añadimos puntos_juego /= 4?
Puedes tomar el contenido de una caja y copiarlo a otra:
# Caja original
nombre_original = "Ana"
# Copiar el contenido a otra caja
nombre_copia = nombre_original
print(f"Original: {nombre_original}") # Ana
print(f"Copia: {nombre_copia}") # Ana
# Si cambias la copia, el original no se afecta
nombre_copia = "Carlos"
print(f"Original: {nombre_original}") # Ana (no cambió)
print(f"Copia: {nombre_copia}") # Carlos
Esto es importante porque demuestra que cada variable es independiente. Cuando copias el valor, estás creando una nueva caja con el mismo contenido, no vinculando las dos cajas.
Las variables son especialmente útiles cuando haces cálculos:
# Información de un rectángulo
largo = 10
ancho = 5
# Calcular área usando las variables
area = largo * ancho
print(f"El área es: {area} unidades cuadradas") # 50
# Calcular perímetro
perimetro = 2 * (largo + ancho)
print(f"El perímetro es: {perimetro} unidades") # 30
# Si cambias las dimensiones, todos los cálculos se actualizan
largo = 15
ancho = 8
# Recalculamos con los nuevos valores
area = largo * ancho
perimetro = 2 * (largo + ancho)
print(f"Nueva área: {area} unidades cuadradas") # 120
print(f"Nuevo perímetro: {perimetro} unidades") # 46
💡 Comprueba tu comprensión: ¿Qué pasaría si definiéramos una nueva variable altura = 3 y quisiéramos calcular el volumen del prisma rectangular? ¿Cómo lo harías?
Vamos a crear un programa que simule tu almacén personal de información. Copia este código y personalízalo con tu información:
# ================================
# MI ALMACÉN PERSONAL DE VARIABLES
# ================================
print("=== ORGANIZANDO MI ALMACÉN ===")
print()
# Caja 1: Información básica
print("📦 Creando caja amarilla para mi nombre...")
mi_nombre = "Tu nombre aquí" # Cambia esto por tu nombre
print(f" Contenido: {mi_nombre}")
print("📦 Creando caja azul para mi edad...")
mi_edad = 25 # Cambia por tu edad
print(f" Contenido: {mi_edad}")
print("📦 Creando caja verde para mi altura...")
mi_altura = 1.70 # Cambia por tu altura
print(f" Contenido: {mi_altura}")
print("📦 Creando caja roja para si tengo mascota...")
tengo_mascota = True # Cambia según tu situación
print(f" Contenido: {tengo_mascota}")
print()
print("=== INVENTARIO DE MI ALMACÉN ===")
print(f"Nombre: {mi_nombre} - Tipo de caja: {type(mi_nombre)}")
print(f"Edad: {mi_edad} - Tipo de caja: {type(mi_edad)}")
print(f"Altura: {mi_altura} - Tipo de caja: {type(mi_altura)}")
print(f"Tengo mascota: {tengo_mascota} - Tipo de caja: {type(tengo_mascota)}")
print()
print("=== USANDO EL CONTENIDO DE MIS CAJAS ===")
print(f"Hola, soy {mi_nombre}")
print(f"Tengo {mi_edad} años y mido {mi_altura} metros")
if tengo_mascota:
print("¡Y tengo una mascota!")
else:
print("No tengo mascota, pero me gustaría tener una.")
# Calculando con las cajas
edad_en_meses = mi_edad * 12
print(f"Mi edad en meses es aproximadamente: {edad_en_meses}")
# Calculando edad en días (aproximado)
edad_en_dias = mi_edad * 365
print(f"He vivido aproximadamente {edad_en_dias:,} días") # Con formato de miles
💡 Desafío: Añade más variables a tu almacén personal. Por ejemplo, tu película favorita, tu peso, si te gusta programar, etc. Luego, crea algunos cálculos interesantes con esas variables.
# ❌ ERROR - intentar usar el contenido como nombre
"Ana" = 25 # Error: can't assign to literal
# ✅ CORRECTO - el nombre va a la izquierda
nombre = "Ana"
edad = 25
Recuerda: la etiqueta (nombre de la variable) siempre va a la izquierda del signo igual.
# ✅ BUENO - primero las variables, luego los cálculos
base = 10
altura = 5
area = base * altura
# ❌ MALO - todo mezclado
base = 10
area = base * altura # ¿altura? Aún no existe
altura = 5
💡 Consejo personal: Yo siempre comento mis variables cuando trabajo en proyectos grandes. Por ejemplo: total_ventas = 0 # Acumulador para ventas del día. Esto me ayuda a recordar para qué sirve cada variable cuando reviso mi código semanas después.
Las variables son como cajas etiquetadas en el almacén de la memoria de tu computadora:
✅ Cada caja tiene un nombre (etiqueta) que debe ser descriptivo
✅ Cada caja guarda un valor (contenido) de un tipo específico
✅ Puedes cambiar el contenido cuando quieras, pero el tipo puede cambiar
✅ Hay diferentes tipos de cajas para diferentes tipos de contenido
✅ Usar variables hace tu código más fácil de leer y mantener
En la siguiente sección vamos a explorar en detalle los diferentes tipos de cajas (tipos de datos) que puedes usar en tu almacén de variables.
💡 Recuerda: Cada vez que creas una variable, estás organizando mejor tu almacén de información. Un almacén bien organizado (código con buenas variables) es fácil de encontrar lo que necesitas y hacer cambios cuando sea necesario. ¡La organización es la clave del éxito en programación!
En nuestro almacén de variables, no todas las cajas son iguales. Cada tipo de caja está diseñada para guardar un tipo específico de información. ¡Vamos a conocer todas las cajas disponibles en Python!
💭 Nota del autor: Esta es mi perspectiva personal para entender mejor los tipos de datos. No es una definición oficial de Python, pero me ha ayudado mucho a organizar conceptos y espero que te sea útil también.
Desde mi experiencia enseñando Python, me gusta pensar en los tipos de datos como si fueran materiales de construcción en nuestro almacén:
📚 Aprendizaje progresivo: Primero dominas los ladrillos básicos, luego aprendes a construir estructuras
🔧 Resolución de problemas: Sabes que cualquier estructura compleja está hecha de elementos simples
🐛 Depuración: Si algo falla en una estructura compuesta, revisas los elementos individuales
💡 Diseño: Planificas mejor qué tipos elementales necesitas para crear tus estructuras
# Ejemplo práctico: Sistema de estudiantes
# Primero definimos los elementos básicos (tipos elementales)
estudiante1_nombre = "Carlos Mendoza" # str
estudiante1_edad = 20 # int
estudiante1_promedio = 8.5 # float
estudiante1_activo = True # bool
estudiante2_nombre = "Ana García" # str
estudiante2_edad = 22 # int
estudiante2_promedio = 9.2 # float
estudiante2_activo = False # bool
# Luego construimos estructuras más complejas (tipos compuestos)
estudiante1 = { # Diccionario usando str, int, float, bool
"nombre": estudiante1_nombre,
"edad": estudiante1_edad,
"promedio": estudiante1_promedio,
"activo": estudiante1_activo
}
estudiante2 = { # Otro diccionario con la misma estructura
"nombre": estudiante2_nombre,
"edad": estudiante2_edad,
"promedio": estudiante2_promedio,
"activo": estudiante2_activo
}
# Y finalmente, estructuras aún más complejas
lista_estudiantes = [estudiante1, estudiante2] # Lista de diccionarios
print("🎓 SISTEMA DE ESTUDIANTES")
print("=" * 40)
for i, estudiante in enumerate(lista_estudiantes, 1):
print(f"Estudiante {i}:")
print(f" 📝 Nombre: {estudiante['nombre']}")
print(f" 🎂 Edad: {estudiante['edad']} años")
print(f" 📊 Promedio: {estudiante['promedio']}")
print(f" ✅ Activo: {estudiante['activo']}")
print()
💡 Reflexión: Observa cómo partimos de 4 tipos elementales simples (str, int, float, bool) y construimos un sistema completo de gestión de estudiantes. ¡Esa es la potencia de la composición!
Las cajas amarillas son perfectas para guardar texto. Son como cajas mágicas que pueden contener cualquier combinación de letras, números y símbolos, ¡pero siempre entre comillas!
# Creando cajas amarillas
nombre = "Ana García"
mensaje = "¡Hola mundo!"
email = "ana@email.com"
telefono = "555-1234" # Nota: entre comillas porque es texto
direccion = "Calle Falsa 123, Col. Centro"
# Verificar que son cajas amarillas
print(type(nombre)) # <class 'str'>
# Con comillas dobles
nombre = "Ana García"
mensaje = "Ella dijo: 'Hola'"
# Con comillas simples
apellido = 'López'
frase = 'El libro "Python para principiantes" es genial'
# Con comillas triples (para texto largo)
descripcion = """Este es un texto muy largo
que puede ocupar varias líneas
y es perfecto para descripciones detalladas."""
biografia = '''Ana García es una estudiante
de ingeniería en sistemas que le gusta
programar en Python.'''
Las cajas rojas son las más simples pero muy importantes. Solo pueden contener dos valores: True (verdadero) o False (falso). Son como interruptores que están encendidos o apagados.
# Información opcional de una persona
nombre = "Ana García"
segundo_nombre = None # No tiene segundo nombre
apellido_materno = "López"
apellido_paterno = None # No se especificó
print("=== INFORMACIÓN PERSONAL ===")
print("Nombre:", nombre)
print("Segundo nombre:", segundo_nombre)
print("Apellido materno:", apellido_materno)
print("Apellido paterno:", apellido_paterno)
# Estado inicial de variables
puntuacion_final = None # Aún no se calcula
fecha_graduacion = None # Aún no se define
trabajo_actual = None # Desempleado
print("\n=== INFORMACIÓN PENDIENTE ===")
print("Puntuación final:", puntuacion_final)
print("Fecha de graduación:", fecha_graduacion)
print("Trabajo actual:", trabajo_actual)
Python te permite “espiar” qué tipo de caja es cada variable:
# Crear diferentes tipos de cajas
mi_nombre = "Ana"
mi_edad = 25
mi_altura = 1.65
soy_estudiante = True
mi_mascota = None
# Espiar qué tipo de caja es cada una
print("=== IDENTIFICANDO TIPOS DE CAJAS ===")
print("mi_nombre es una caja tipo:", type(mi_nombre))
print("mi_edad es una caja tipo:", type(mi_edad))
print("mi_altura es una caja tipo:", type(mi_altura))
print("soy_estudiante es una caja tipo:", type(soy_estudiante))
print("mi_mascota es una caja tipo:", type(mi_mascota))
Domina los elementales primero: Son la base de todo lo demás
Elige el tipo correcto: Cada tipo tiene su propósito específico
Piensa en composición: Los tipos complejos se construyen con los simples
Practica la conversión: Saber cambiar entre tipos es fundamental
Elegir el tipo correcto de caja hace que tu código sea más eficiente y fácil de entender. En la siguiente sección aprenderemos cómo cambiar el contenido de una caja a otro tipo (conversión de tipos).
💡 Consejo del almacenista: Estos tipos elementales son como el alfabeto de la programación. Una vez que los domines, podrás “escribir” estructuras de datos tan complejas como necesites. ¡Recuerda: todo lo complejo está hecho de cosas simples!
¿Alguna vez has necesitado cambiar el contenido de una caja azul (números enteros) a una caja amarilla (texto)? ¿O transformar el contenido de una caja amarilla en una caja verde (decimales)? ¡En Python esto es posible y muy útil!
La conversión de tipos es como tener una máquina mágica que puede transformar el contenido de un tipo de caja a otro.
💭 Nota del autor: En mi experiencia enseñando Python, he notado que muchos principiantes se confunden con las diferentes formas de combinar texto y variables. Permíteme mostrarte las tres formas principales, en orden de preferencia.
Los f-strings son la forma más moderna, legible y eficiente de combinar texto y variables en Python:
nombre = "Ana"
edad = 25
salario = 1500.50
# F-string (prefijo f antes de las comillas)
mensaje = f"Hola {nombre}, tienes {edad} años y ganas ${salario:.2f}"
print(mensaje) # Hola Ana, tienes 25 años y ganas $1500.50
💭 Nota del autor: Después de años programando en Python, siempre recomiendo usar f-strings cuando sea posible. Son más legibles, más rápidos y hacen que tu código sea más mantenible. Solo usa concatenación para casos muy simples o cuando necesites compatibilidad con versiones antiguas de Python.
Imagínate que tienes una máquina transformadora en tu almacén. Puedes meter una caja azul con el número 25 y la máquina te devuelve una caja amarilla con el texto "25". ¡El contenido se ve igual, pero ahora es de un tipo diferente!
# Caja azul original
numero = 25
print("Contenido:", numero)
print("Tipo de caja:", type(numero)) # <class 'int'>
# Transformar a caja amarilla
numero_como_texto = str(numero)
print("Contenido transformado:", numero_como_texto)
print("Nuevo tipo de caja:", type(numero_como_texto)) # <class 'str'>
# ❌ ERROR
nombre = "Ana"
# numero = int(nombre) # ValueError: invalid literal for int()
# ✅ CORRECTO - verificar antes
texto = "123"
if texto.isdigit():
numero = int(texto)
print("Conversión exitosa:", numero)
else:
print("No se puede convertir a número")
# Verificar si un texto puede convertirse a número
entrada_usuario = "123"
nombre_usuario = "Carlos"
if entrada_usuario.isdigit():
numero = int(entrada_usuario)
# 🌟 F-string (recomendado)
mensaje_fstring = f"¡Perfecto {nombre_usuario}! '{entrada_usuario}' es un número válido: {numero}"
print("Con f-string:", mensaje_fstring)
# 🔧 Concatenación (fallback)
mensaje_concat = ("¡Perfecto " + nombre_usuario + "! '" + entrada_usuario +
"' es un número válido: " + str(numero))
print("Con concatenación:", mensaje_concat)
# 📚 Format (legacy)
mensaje_format = "¡Perfecto {}! '{}' es un número válido: {}".format(
nombre_usuario, entrada_usuario, numero
)
print("Con format:", mensaje_format)
else:
# 🌟 F-string para mensajes de error
error_fstring = f"Lo siento {nombre_usuario}, '{entrada_usuario}' no es un número válido"
print("Error con f-string:", error_fstring)
def convertir_a_numero(texto):
if texto.isdigit():
return int(texto)
else:
print(f"'{texto}' no es un número válido")
return None
# Uso seguro
resultado = convertir_a_numero("123") # 123
resultado = convertir_a_numero("abc") # None
# ✅ SIEMPRE PREFIERE ESTO (Python 3.6+):
mensaje = f"Usuario {nombre} tiene {edad} años y gana ${salario:,.2f}"
# 🔧 USA ESTO SI NECESITAS COMPATIBILIDAD:
mensaje = "Usuario " + nombre + " tiene " + str(edad) + " años"
# 📚 USA ESTO SOLO PARA CÓDIGO LEGACY:
mensaje = "Usuario {} tiene {} años".format(nombre, edad)
Usa f-strings por defecto - Son el estándar moderno
Concatenación para casos simples - Cuando solo unes 2-3 elementos
Format() solo para legacy - Mantener código existente
Siempre convierte tipos - Antes de combinar con texto
Practica los tres métodos - Para entender código de otros
En el siguiente capítulo aprenderemos sobre operadores y expresiones, donde usaremos estas conversiones y formateos para hacer cálculos y comparaciones más complejas.
💡 Consejo del transformador: Los f-strings son como tener una máquina de última generación en tu almacén. Son más rápidos, más fáciles de usar y hacen que tu código se vea profesional. ¡Úsalos siempre que puedas!
¡Es hora de poner en práctica lo que has aprendido! Los ejercicios son la mejor manera de consolidar tu conocimiento y desarrollar tu intuición para la programación.
💭 Nota del autor: Cuando enseño programación, siempre digo que programar es como aprender a tocar un instrumento musical: puedes leer todos los libros que quieras, pero solo mejorarás si practicas regularmente. Estos ejercicios están diseñados para reforzar los conceptos que acabamos de ver y ayudarte a desarrollar tu “intuición de programador”.
Objetivo: Practicar la creación de variables de diferentes tipos y mostrar su información.
Instrucciones:
Crea variables para almacenar tu información personal:
Nombre (string)
Edad (int)
Altura en metros (float)
¿Te gusta programar? (bool)
Al menos 3 variables más de tu elección
Muestra toda la información usando f-strings
Realiza al menos 3 cálculos con tus variables numéricas
Muestra el tipo de cada variable usando type()
Plantilla de código:
# ================================
# MI ALMACÉN PERSONAL
# ================================
# Información básica (completa con tus datos)
nombre = "Tu nombre"
edad = 0 # Tu edad
altura = 0.0 # Tu altura en metros
te_gusta_programar = True # Cambia según tu preferencia
# Añade al menos 3 variables más
# ...
# ...
# ...
# Muestra la información usando f-strings
print("=== MI INFORMACIÓN PERSONAL ===")
print(f"Nombre: {nombre}")
# Completa con el resto de variables...
# Realiza al menos 3 cálculos
print("\n=== CÁLCULOS ===")
# Ejemplo: edad en meses
edad_en_meses = edad * 12
print(f"Mi edad en meses es: {edad_en_meses}")
# Añade al menos 2 cálculos más...
# Muestra el tipo de cada variable
print("\n=== TIPOS DE VARIABLES ===")
print(f"Variable 'nombre' es de tipo: {type(nombre)}")
# Completa con el resto de variables...
Resultado esperado:
# Código para crear un almacén personal de información
nombre = "Ana García"
edad = 25
altura = 1.65
le_gusta_programar = True
# Realizar cálculos con las variables
edad_en_meses = edad * 12
altura_en_cm = altura * 100
# Mostrar información personal
print("# MI INFORMACIÓN PERSONAL")
print(f"Nombre: {nombre}")
print(f"Edad: {edad} años")
print(f"Altura: {altura} metros")
print(f"¿Me gusta programar?: {'Sí' if le_gusta_programar else 'No'}")
print("...")
# Mostrar cálculos
print("\n# CÁLCULOS")
print(f"Mi edad en meses es: {edad_en_meses}")
print(f"Mi altura en centímetros es: {altura_en_cm}")
print("...")
# Mostrar tipos de variables
print("\n# TIPOS DE VARIABLES")
print(f'Variable "nombre" es de tipo: {type(nombre)}')
print(f'Variable "edad" es de tipo: {type(edad)}')
print("...")
Objetivo: Crear una calculadora de Índice de Masa Corporal (IMC) usando variables y operaciones matemáticas.
Instrucciones:
Crea variables para almacenar:
Nombre de la persona
Peso en kilogramos (float)
Altura en metros (float)
Calcula el IMC usando la fórmula: IMC = peso / (altura * altura)
Determina la categoría de peso según el IMC:
Menos de 18.5: Bajo peso
Entre 18.5 y 24.9: Peso normal
Entre 25.0 y 29.9: Sobrepeso
30.0 o más: Obesidad
Muestra un informe completo con toda la información
Plantilla de código:
# ================================
# CALCULADORA DE IMC
# ================================
# Datos de la persona (completa con datos reales o inventados)
nombre = "Ana García"
peso = 65.5 # en kilogramos
altura = 1.65 # en metros
# Cálculo del IMC
# Nota: ** es el operador de potencia (se verá en detalle en el Capítulo 5)
# altura ** 2 significa "altura elevado al cuadrado"
imc = peso / (altura ** 2)
# Nota: La determinación de categorías se aprenderá en el Capítulo 6 (Estructuras de Control)
# Por ahora, solo calcularemos el IMC
categoria = "Consulta con un profesional de salud para interpretación"
# Mostrar informe
print("=== INFORME DE IMC ===")
print(f"Nombre: {nombre}")
print(f"Peso: {peso} kg")
print(f"Altura: {altura} m")
print(f"IMC calculado: {imc:.2f}")
print(f"Categoría: {categoria}")
# Añade un mensaje personalizado según la categoría
# ...
Resultado esperado:
## INFORME DE IMC
Nombre: Ana García
Peso: 65.5 kg
Altura: 1.65 m
IMC calculado: 24.06
Categoría: Peso normal
Tu peso está dentro del rango saludable. Sigue así!
Objetivo: Practicar la creación de variables, operaciones matemáticas y formateo de strings.
Instrucciones:
Crea un programa que convierta una distancia en kilómetros a:
Metros
Centímetros
Millas (1 km = 0.621371 millas)
Pies (1 km = 3280.84 pies)
Muestra los resultados formateados con 2 decimales
Incluye un encabezado y una presentación clara de los resultados
Plantilla de código:
# ================================
# CONVERSOR DE UNIDADES
# ================================
# Distancia en kilómetros
distancia_km = 10.0 # Cambia este valor
# Factores de conversión
km_a_metros = 1000
km_a_cm = 100000
km_a_millas = 0.621371
km_a_pies = 3280.84
# Realizar conversiones
# ...
# ...
# ...
# Mostrar resultados
print("## CONVERSOR DE DISTANCIAS")
print(f"Distancia original: {distancia_km} kilómetros")
print("\nEquivalencias:")
# Completa con los resultados formateados...
Resultado esperado:
## CONVERSOR DE DISTANCIAS
Distancia original: 10.0 kilómetros
Equivalencias:
- En metros: 10,000.00 m
- En centímetros: 1,000,000.00 cm
- En millas: 6.21 mi
- En pies: 32,808.40 ft
Objetivo: Integrar todos los conceptos aprendidos en un sistema más complejo.
Instrucciones:
Crea variables para almacenar:
Nombre del estudiante
Calificaciones de 5 materias diferentes (números del 0 al 100)
Si el estudiante tiene beca (booleano)
Calcula:
El promedio de calificaciones
La calificación más alta y más baja
Si el estudiante aprobó (promedio >= 70)
Cuántos puntos le faltaron para el siguiente nivel:
< 70: Puntos para aprobar
70-89: Puntos para excelencia
90-100: Ya tiene excelencia
Muestra un reporte completo y bien formateado
Plantilla de código:
# ================================
# SISTEMA DE CALIFICACIONES
# ================================
# Información del estudiante
nombre_estudiante = "Carlos Mendoza"
calificacion_matematicas = 85
calificacion_espanol = 92
calificacion_historia = 78
calificacion_ciencias = 90
calificacion_ingles = 88
tiene_beca = True
# Cálculos
# ...
# ...
# ...
# Mostrar reporte
print("=== REPORTE DE CALIFICACIONES ===")
print(f"Estudiante: {nombre_estudiante}")
print(f"Estado de beca: {'Activa' if tiene_beca else 'No tiene'}")
print("\n--- Calificaciones ---")
# Completa con las calificaciones y cálculos...
Resultado esperado:
## REPORTE DE CALIFICACIONES
Estudiante: Carlos Mendoza
Estado de beca: Activa
--- Calificaciones ---
Matemáticas: 85
Español: 92
Historia: 78
Ciencias: 90
Inglés: 88
--- Estadísticas ---
Promedio: 86.60
Calificación más alta: 92 (Español)
Calificación más baja: 78 (Historia)
Estado: APROBADO
Puntos para excelencia: 3.40
Felicidades Carlos! Tu desempeño es muy bueno.
Lee cuidadosamente las instrucciones antes de empezar
Planifica tu solución antes de escribir código
Divide el problema en partes más pequeñas
Prueba tu código con diferentes valores
Revisa los errores con calma y paciencia
No te rindas si algo no funciona a la primera
💭 Nota del autor: Cuando me enfrento a un problema de programación, siempre empiezo escribiendo en comentarios los pasos que voy a seguir. Esto me ayuda a organizar mis ideas y a no perderme en el proceso. Te recomiendo hacer lo mismo, especialmente cuando estás empezando.
Sabrás que has dominado los conceptos de este capítulo cuando:
✅ Puedas crear variables de diferentes tipos sin errores
✅ Sepas identificar qué tipo de variable es adecuado para cada situación
✅ Puedas realizar operaciones entre variables del mismo tipo
✅ Entiendas cuándo necesitas convertir entre tipos
✅ Puedas formatear la salida de tus programas de manera clara y profesional
¡Buena suerte con los ejercicios! Recuerda que la práctica constante es la clave para dominar la programación. Si te atascas, revisa los ejemplos del capítulo o busca ayuda, pero nunca dejes de intentarlo.
💡 Consejo final: Intenta resolver estos ejercicios sin mirar las soluciones primero. Es normal sentirse frustrado al principio, pero cada error que cometas y corrijas te hará mejor programador. ¡El aprendizaje está en el proceso, no solo en el resultado final!
¡Bienvenido al centro de operaciones de Python! En esta sección, exploraremos las diferentes herramientas que Python nos ofrece para manipular datos, realizar cálculos y tomar decisiones.
Los operadores son símbolos especiales que realizan operaciones sobre variables y valores. Son como las herramientas de un taller que nos permiten transformar y combinar nuestros datos.
¡Bienvenido al departamento de cálculos de nuestro almacén digital! Los operadores matemáticos son las herramientas fundamentales que nos permiten realizar cálculos con nuestros datos, como si fuéramos contadores con calculadoras súper inteligentes.
Los operadores matemáticos son símbolos especiales que realizan operaciones aritméticas sobre números. Son como las teclas de una calculadora empresarial que nos permiten procesar información numérica de manera automática.
# En el almacén: Productos que sobran al formar cajas
productos_totales = 127
productos_por_caja = 12
productos_sueltos = productos_totales % productos_por_caja
print(f"Productos sueltos: {productos_sueltos}") # 7 productos
# Calculando días de la semana
dia_actual = 15 # día del mes
dia_semana = dia_actual % 7
print(f"Día de la semana (0=lunes): {dia_semana}") # 1 (martes)
# Verificando números pares o impares
codigo_producto = 12345
if codigo_producto % 2 == 0:
print("El código es par")
else:
print("El código es impar") # El código es impar
En el almacén llegan 1,847 productos que deben empacarse en cajas de 24 unidades:
productos_totales = 1847
productos_por_caja = 24
# Calcula:
# 1. ¿Cuántas cajas completas se pueden formar?
# 2. ¿Cuántos productos quedan sueltos?
# 3. Si cada caja pesa 5.5 kg, ¿cuál es el peso total?
# Tu código aquí...
¡Es hora de poner a prueba tus conocimientos sobre operadores y expresiones en Python! Este quiz cubre todos los tipos de operadores que hemos visto en esta sección.
a) is compara valores, mientras que == compara identidad
b) is y == son siempre equivalentes
c) is compara identidad, mientras que == compara valores
d) is solo funciona con números, mientras que == funciona con cualquier tipo
¿Qué código verificaría correctamente si un número es divisible por 2 y por 3, pero no por 5?
a) numero % 2 == 0 and numero % 3 == 0 and numero % 5 != 0
b) numero % 2 == 0 or numero % 3 == 0 and numero % 5 != 0
c) numero % 2 == 0 and numero % 3 == 0 or numero % 5 != 0
d) not (numero % 2 or numero % 3) and numero % 5
¡Bienvenido al centro de decisiones y automatización de tu almacén! En esta sección, aprenderás a hacer que tu programa piense y repita tareas automáticamente.
Las estructuras de control son como el cerebro y los músculos de tu programa:
El cerebro (condicionales) toma decisiones basadas en condiciones
Los músculos (bucles) realizan tareas repetitivas sin cansarse
Sin estructuras de control, los programas serían lineales y limitados. Con ellas, pueden adaptarse a diferentes situaciones y automatizar tareas complejas.
¡Bienvenido al centro de decisiones de tu almacén! Hasta ahora has aprendido a organizar cajas (variables), usar herramientas (operadores), y hacer comparaciones. Ahora viene lo más emocionante: ¡hacer que tu programa piense y tome decisiones como tú!
Los condicionales son como tener un gerente súper inteligente en tu almacén que puede evaluar situaciones y decidir qué hacer en cada caso.
Imagínate que contratas a un gerente para tu almacén que es capaz de:
Evaluar situaciones usando las herramientas de comparación
Tomar decisiones basadas en esas evaluaciones
Ejecutar diferentes acciones según cada situación
Manejar múltiples escenarios complejos
# El gerente en acción
edad = 17
# El gerente evalúa y decide
if edad >= 18:
print(f"Eres mayor de edad, puedes votar")
else:
print(f"Eres menor de edad, aún no puedes votar")
Este gerente usa la palabra mágica if (si) para evaluar condiciones y tomar decisiones.
El control de flujo es la capacidad de tu programa para tomar diferentes caminos según las circunstancias. Es como tener un mapa con múltiples rutas y elegir cuál tomar según las condiciones del momento.
import datetime
hora_actual = datetime.datetime.now().hour
# El programa se adapta a la situación
if hora_actual < 12:
print("¡Buenos días!")
elif hora_actual < 18:
print("¡Buenas tardes!")
else:
print("¡Buenas noches!")
print("¿Cómo estás?")
print("Que tengas buen día")
🔍 Mi perspectiva personal: Siempre pienso en los condicionales como en las bifurcaciones de un camino. Cada if es un punto donde el programa debe decidir qué ruta tomar. Esta forma de pensar me ayuda a visualizar el flujo del programa y a entender cómo se comportará en diferentes situaciones.
# ================================
# DETECTOR DE TEMPERATURA
# ================================
temperatura = 35
print("🌡️ DETECTOR DE TEMPERATURA")
print(f"Temperatura actual: {temperatura} °C")
# El gerente evalúa la temperatura
if temperatura > 30:
print("🔥 ¡Hace mucho calor!")
print("💧 Recuerda hidratarte")
print("🏠 Considera usar aire acondicionado")
print("📊 Reporte de temperatura completado")
Nota importante: Fíjate en la indentación (espacios al inicio). Todo el código que está indentado después del if solo se ejecuta si la condición es verdadera.
if primera_condicion:
# Código para la primera condición
hacer_a()
elif segunda_condicion:
# Código para la segunda condición
hacer_b()
elif tercera_condicion:
# Código para la tercera condición
hacer_c()
else:
# Código si ninguna condición es verdadera
hacer_por_defecto()
A veces el gerente necesita tomar decisiones dentro de otras decisiones:
# ================================
# SISTEMA DE RECOMENDACIÓN DE ACTIVIDADES
# ================================
clima = "soleado" # "soleado", "lluvioso", "nublado"
temperatura = 25
tiene_dinero = True
tiene_tiempo = True
print("🌤️ SISTEMA DE RECOMENDACIÓN DE ACTIVIDADES")
print("=" * 50)
print(f"Clima: {clima}")
print(f"Temperatura: {temperatura} °C")
print(f"Tiene dinero: {tiene_dinero}")
print(f"Tiene tiempo: {tiene_tiempo}")
print()
print("🤔 Analizando opciones...")
print()
# Primera decisión: evaluar el clima
if clima == "soleado":
print("☀️ ¡Qué buen día!")
# Segunda decisión: evaluar la temperatura
if temperatura >= 25:
print("🏖️ Perfecto para actividades al aire libre")
# Tercera decisión: evaluar recursos
if tiene_dinero and tiene_tiempo:
print("🎯 RECOMENDACIONES:")
print(" • Ir a la playa")
print(" • Hacer un picnic en el parque")
print(" • Visitar un parque de diversiones")
elif tiene_tiempo:
print("🎯 RECOMENDACIONES (sin costo):")
print(" • Caminar en el parque")
print(" • Hacer ejercicio al aire libre")
print(" • Visitar lugares gratuitos")
elif tiene_dinero:
print("🎯 RECOMENDACIONES (rápidas):")
print(" • Tomar un café en terraza")
print(" • Comprar helado")
else:
print("🎯 RECOMENDACIÓN:")
print(" • Sentarse en el parque a disfrutar el sol")
else:
print("🧥 Hace un poco de frío, pero se puede salir")
if tiene_dinero and tiene_tiempo:
print("🎯 RECOMENDACIONES:")
print(" • Ir al cine")
print(" • Visitar un museo")
print(" • Ir a un café")
else:
print("🎯 RECOMENDACIÓN:")
print(" • Dar un paseo corto")
elif clima == "lluvioso":
print("🌧️ Está lloviendo")
if tiene_dinero:
print("🎯 RECOMENDACIONES (lugares cerrados):")
print(" • Ir al cine")
print(" • Visitar un centro comercial")
print(" • Ir a un café")
else:
print("🎯 RECOMENDACIONES (en casa):")
print(" • Leer un libro")
print(" • Ver películas")
print(" • Cocinar algo especial")
else: # clima nublado
print("☁️ Día nublado")
print("🎯 RECOMENDACIÓN:")
print(" • Actividades flexibles que se puedan mover adentro si es necesario")
print()
print("🌟 ¡Que disfrutes tu día!")
# ================================
# VERIFICADOR DE EDAD RÁPIDO
# ================================
edad = 20
# Versión normal con if-else
if edad >= 18:
estado = "Mayor de edad"
else:
estado = "Menor de edad"
print(f"Versión normal: {estado}")
# Versión con operador ternario
estado = "Mayor de edad" if edad >= 18 else "Menor de edad"
print(f"Versión ternaria: {estado}")
# Uso práctico en una función
def calcular_precio(precio_base, es_miembro):
descuento = 0.15 if es_miembro else 0
return precio_base * (1 - descuento)
precio_normal = calcular_precio(100, False)
precio_miembro = calcular_precio(100, True)
print(f"Precio normal: ${precio_normal}")
print(f"Precio miembro: ${precio_miembro}")
# ❌ DIFÍCIL DE LEER
if condicion1:
if condicion2:
if condicion3:
if condicion4:
hacer_algo()
# ✅ MÁS CLARO
if condicion1 and condicion2 and condicion3 and condicion4:
hacer_algo()
El código imprimirá "A" porque x > y es verdadero (15 > 10).
Diferencia entre if-elif-else y múltiples if:
En una estructura if-elif-else, solo se ejecuta el bloque de la primera condición que sea verdadera.
Con múltiples sentencias if independientes, se evalúan todas las condiciones y se ejecutan todos los bloques cuyas condiciones sean verdaderas.
Ejemplo:
# Con if-elif-else (solo se ejecuta uno)
if x > 10:
print("Mayor que 10")
elif x > 5:
print("Mayor que 5") # No se ejecuta aunque sea verdadero si x > 10
# Con múltiples if (se ejecutan todos los verdaderos)
if x > 10:
print("Mayor que 10")
if x > 5:
print("Mayor que 5") # Se ejecuta si x > 5, independientemente de x > 10
Código para determinar si un año es bisiesto:
año = 2024
if (año % 4 == 0 and año % 100 != 0) or (año % 400 == 0):
print(f"{año} es un año bisiesto")
else:
print(f"{año} no es un año bisiesto")
El valor de resultado será "Menor o igual" porque a > b es falso (5 no es mayor que 10).
Vamos a crear un sistema complejo que combine todo lo aprendido:
# ================================
# SISTEMA DE EVALUACIÓN MÉDICA BÁSICA
# ================================
print("🏥 SISTEMA DE EVALUACIÓN MÉDICA BÁSICA")
print("=" * 45)
# Datos del paciente
nombre_paciente = "Ana García"
edad = 45
temperatura = 38.2
presion_sistolica = 140
presion_diastolica = 85
frecuencia_cardiaca = 95
tiene_sintomas_respiratorios = True
tiene_dolor_pecho = False
toma_medicamentos = True
print("👤 INFORMACIÓN DEL PACIENTE:")
print(f"Nombre: {nombre_paciente}")
print(f"Edad: {edad} años")
print(f"Temperatura: {temperatura} °C")
print(f"Presión arterial: {presion_sistolica}/{presion_diastolica} mmHg")
print(f"Frecuencia cardíaca: {frecuencia_cardiaca} bpm")
print(f"Síntomas respiratorios: {tiene_sintomas_respiratorios}")
print(f"Dolor en el pecho: {tiene_dolor_pecho}")
print(f"Toma medicamentos: {toma_medicamentos}")
print()
print("🔍 EVALUACIÓN MÉDICA:")
print("=" * 25)
# Evaluación de signos vitales
signos_normales = True
alertas = []
# Evaluar temperatura
if temperatura >= 38.0:
if temperatura >= 39.5:
print("🚨 FIEBRE ALTA - Requiere atención inmediata")
signos_normales = False
alertas.append("Fiebre alta")
else:
print("⚠️ Fiebre moderada - Monitorear")
alertas.append("Fiebre moderada")
elif temperatura <= 35.0:
print("🚨 HIPOTERMIA - Requiere atención")
signos_normales = False
alertas.append("Hipotermia")
else:
print("✅ Temperatura normal")
# Evaluar presión arterial
if presion_sistolica >= 140 or presion_diastolica >= 90:
if presion_sistolica >= 160 or presion_diastolica >= 100:
print("🚨 HIPERTENSIÓN SEVERA - Atención urgente")
signos_normales = False
alertas.append("Hipertensión severa")
else:
print("⚠️ Hipertensión leve - Monitorear")
alertas.append("Hipertensión leve")
elif presion_sistolica <= 90 or presion_diastolica <= 60:
print("⚠️ Presión baja - Monitorear")
alertas.append("Hipotensión")
else:
print("✅ Presión arterial normal")
# Evaluar frecuencia cardíaca
if frecuencia_cardiaca >= 100:
if frecuencia_cardiaca >= 120:
print("🚨 TAQUICARDIA SEVERA - Atención urgente")
signos_normales = False
alertas.append("Taquicardia severa")
else:
print("⚠️ Taquicardia leve - Monitorear")
alertas.append("Taquicardia leve")
elif frecuencia_cardiaca <= 50:
print("⚠️ Bradicardia - Monitorear")
alertas.append("Bradicardia")
else:
print("✅ Frecuencia cardíaca normal")
# Evaluar síntomas adicionales
if tiene_dolor_pecho:
print("🚨 DOLOR EN EL PECHO - Evaluación cardiológica urgente")
signos_normales = False
alertas.append("Dolor torácico")
if tiene_sintomas_respiratorios:
if temperatura >= 38.0:
print("⚠️ Síntomas respiratorios con fiebre - Posible infección")
alertas.append("Síntomas respiratorios con fiebre")
else:
print("ℹ️ Síntomas respiratorios - Evaluación necesaria")
alertas.append("Síntomas respiratorios")
print()
print("🎯 EVALUACIÓN FINAL:")
print("=" * 20)
# Determinar nivel de prioridad
if not signos_normales or tiene_dolor_pecho:
if (temperatura >= 39.5 or presion_sistolica >= 160 or
presion_diastolica >= 100 or frecuencia_cardiaca >= 120 or tiene_dolor_pecho):
prioridad = "EMERGENCIA"
color = "🔴"
else:
prioridad = "URGENTE"
color = "🟡"
elif len(alertas) > 0:
prioridad = "PRIORITARIO"
color = "🟠"
else:
prioridad = "NORMAL"
color = "🟢"
print(f"{color} NIVEL DE PRIORIDAD: {prioridad}")
print()
# Recomendaciones específicas
if prioridad == "EMERGENCIA":
print("🚨 ACCIÓN INMEDIATA REQUERIDA:")
print(" • Atención médica urgente")
print(" • Considerar llamar ambulancia")
print(" • No esperar - ir a emergencias")
elif prioridad == "URGENTE":
print("⚠️ ATENCIÓN MÉDICA NECESARIA:")
print(" • Consultar médico hoy mismo")
print(" • Monitorear signos vitales")
print(" • Tener medicamentos a mano")
elif prioridad == "PRIORITARIO":
print("📋 SEGUIMIENTO RECOMENDADO:")
print(" • Agendar cita médica pronto")
print(" • Monitorear síntomas")
print(" • Mantener medicación actual")
else:
print("✅ ESTADO NORMAL:")
print(" • Continuar con cuidados habituales")
print(" • Chequeo médico de rutina")
# Consideraciones especiales por edad
if edad >= 65:
print()
print("👴 CONSIDERACIONES POR EDAD:")
print(" • Paciente de riesgo por edad avanzada")
print(" • Monitoreo más frecuente recomendado")
if toma_medicamentos:
print(" • Revisar interacciones medicamentosas")
# Resumen de alertas
if alertas:
print()
print("📋 RESUMEN DE ALERTAS:")
for i, alerta in enumerate(alertas, 1):
print(f" {i}. {alerta}")
print()
print("⚕️ Evaluación completada")
print("📞 En caso de emergencia, contactar servicios médicos")
¡Ahora tienes el poder de hacer que tu programa tome decisiones inteligentes! En el próximo capítulo, aprenderás a automatizar tareas repetitivas con bucles.
¡Bienvenido al departamento de automatización de tu almacén! Ahora que tu gerente inteligente sabe tomar decisiones, es momento de conocer a los robots que pueden repetir tareas automáticamente sin cansarse nunca.
El bucle for es como un robot especialista en tu almacén que puede procesar listas de elementos uno por uno, sin errores y sin quejarse.
Imagínate que contratas a un robot especialista para tu almacén que puede:
Procesar listas de elementos uno por uno
Trabajar con secuencias de cualquier tipo
Realizar la misma tarea para cada elemento
Llevar la cuenta de los elementos procesados
Transformar datos de forma sistemática
# El robot especialista en acción
productos = ["manzanas", "naranjas", "plátanos", "uvas"]
# El robot procesa cada producto
for producto in productos:
print(f"Procesando: {producto}")
print(f"✅ {producto} agregado al inventario")
print("---")
Este robot usa la palabra mágica for para saber exactamente qué elementos procesar y en qué orden.
🔍 Mi perspectiva personal: Siempre visualizo los bucles for como una cinta transportadora que va pasando elementos uno a uno frente a un trabajador. Cada elemento se detiene brevemente para ser procesado y luego continúa su camino. Esta imagen mental me ayuda a entender cómo Python maneja cada elemento de una secuencia.
Los bucles for son fundamentales para la automatización porque eliminan la necesidad de escribir código repetitivo. Compara estas dos formas de hacer lo mismo:
# ================================
# GENERADOR DE CÓDIGOS DE BARRAS
# ================================
print("🏷️ GENERADOR DE CÓDIGOS DE BARRAS")
print("=" * 35)
# Generar 10 códigos de barras (1-10)
for numero in range(1, 11):
codigo_barras = f"PROD-{numero:04d}" # Formato con ceros: PROD-0001
print(f"Código generado: {codigo_barras}")
print()
print("✅ Generación de códigos completada")
# ================================
# EJEMPLOS DE RANGE()
# ================================
print("🔢 EJEMPLOS DE RANGE()")
print("=" * 25)
# range con un solo argumento (fin)
print("📊 range(5):")
for i in range(5): # 0, 1, 2, 3, 4
print(f" • Valor: {i}")
print()
# range con inicio y fin
print("📊 range(5, 10):")
for i in range(5, 10): # 5, 6, 7, 8, 9
print(f" • Valor: {i}")
print()
# range con inicio, fin y paso
print("📊 range(0, 20, 5):")
for i in range(0, 20, 5): # 0, 5, 10, 15
print(f" • Valor: {i}")
print()
# range con paso negativo (cuenta regresiva)
print("📊 range(10, 0, -2):")
for i in range(10, 0, -2): # 10, 8, 6, 4, 2
print(f" • Valor: {i}")
print()
print("✅ Ejemplos completados")
# ================================
# TRANSFORMADOR DE DATOS
# ================================
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print("🔄 TRANSFORMADOR DE DATOS")
print("=" * 30)
# Forma tradicional con bucle for
cuadrados_tradicional = []
for numero in numeros:
cuadrados_tradicional.append(numero ** 2)
print("📊 Cuadrados (forma tradicional):")
print(f" {cuadrados_tradicional}")
# Forma moderna con comprensión de listas
cuadrados_comprension = [numero ** 2 for numero in numeros]
print("📊 Cuadrados (comprensión de listas):")
print(f" {cuadrados_comprension}")
# Comprensión con condición
pares = [numero for numero in numeros if numero % 2 == 0]
print("📊 Números pares:")
print(f" {pares}")
# Comprensión con transformación y condición
pares_cuadrados = [numero ** 2 for numero in numeros if numero % 2 == 0]
print("📊 Cuadrados de números pares:")
print(f" {pares_cuadrados}")
# Comprensión con strings
nombres = ["Ana", "Carlos", "Elena", "David"]
longitudes = [len(nombre) for nombre in nombres]
print("📊 Longitud de nombres:")
print(f" Nombres: {nombres}")
print(f" Longitudes: {longitudes}")
print("✅ Transformaciones completadas")
# ================================
# GENERADOR DE PATRONES
# ================================
print("🎨 GENERADOR DE PATRONES")
print("=" * 25)
# Patrón de triángulo
print("📐 Patrón de triángulo:")
for i in range(1, 6):
print("*" * i)
print()
# Patrón de cuadrado
print("🔲 Patrón de cuadrado:")
for i in range(5):
print("* " * 5)
print()
# Patrón de pirámide
print("🔺 Patrón de pirámide:")
for i in range(1, 6):
espacios = " " * (5 - i)
estrellas = "*" * (2 * i - 1)
print(f"{espacios}{estrellas}")
print()
# Patrón de tablero de ajedrez
print("🏁 Patrón de tablero de ajedrez:")
for i in range(4):
print("⬜⬛" * 4)
print("⬛⬜" * 4)
print()
print("✅ Generación de patrones completada")
# ❌ PELIGROSO
for elemento in lista:
if condicion(elemento):
lista.remove(elemento) # Puede causar comportamiento inesperado
# ✅ SEGURO
lista = [elemento for elemento in lista if not condicion(elemento)]
# ❌ MENOS PYTHÓNICO
for i in range(len(lista)):
elemento = lista[i]
print(f"{i}: {elemento}")
# ✅ MÁS PYTHÓNICO
for i, elemento in enumerate(lista):
print(f"{i}: {elemento}")
Explicación: range(5) genera los números del 0 al 4 (5 números en total). El parámetro end=" " en la función print() hace que los números se impriman en la misma línea separados por espacios en lugar de saltos de línea.
Diferencia entre range(5) y range(1, 6):
range(5) genera los números: 0, 1, 2, 3, 4 (comienza en 0 por defecto)
range(1, 6) genera los números: 1, 2, 3, 4, 5 (comienza en 1 y termina antes del 6)
Ambos generan 5 números, pero con diferentes valores iniciales y finales.
Comprensión de lista para los cuadrados de números impares del 1 al 10:
cuadrados_impares = [x**2 for x in range(1, 11) if x % 2 != 0]
# Resultado: [1, 9, 25, 49, 81]
La función enumerate() devuelve pares de (índice, valor) para cada elemento de una secuencia. Es útil cuando necesitas tanto el índice como el valor durante la iteración, evitando tener que mantener un contador separado.
Ejemplo:
frutas = ["manzana", "banana", "cereza"]
for i, fruta in enumerate(frutas):
print(f"{i}: {fruta}")
# ================================
# GENERADOR DE INFORMES DE ESTUDIANTES
# ================================
estudiantes = [
{"nombre": "Ana García", "calificaciones": [85, 90, 78, 92, 88]},
{"nombre": "Carlos López", "calificaciones": [75, 82, 79, 65, 68]},
{"nombre": "Elena Martínez", "calificaciones": [92, 97, 94, 90, 95]},
{"nombre": "David Rodríguez", "calificaciones": [60, 55, 68, 72, 65]},
{"nombre": "Sofía Pérez", "calificaciones": [88, 84, 90, 86, 82]}
]
print("📝 GENERADOR DE INFORMES DE ESTUDIANTES")
print("=" * 45)
# Procesar cada estudiante
for i, estudiante in enumerate(estudiantes, 1):
nombre = estudiante["nombre"]
calificaciones = estudiante["calificaciones"]
# Calcular estadísticas
promedio = sum(calificaciones) / len(calificaciones)
calificacion_maxima = max(calificaciones)
calificacion_minima = min(calificaciones)
# Determinar estado
if promedio >= 90:
estado = "Excelente"
emoji = "🏆"
elif promedio >= 80:
estado = "Bueno"
emoji = "👍"
elif promedio >= 70:
estado = "Satisfactorio"
emoji = "😊"
elif promedio >= 60:
estado = "Suficiente"
emoji = "😐"
else:
estado = "Insuficiente"
emoji = "⚠️"
# Generar informe
print(f"INFORME #{i}: {nombre}")
print("-" * 30)
print(f"Calificaciones: {calificaciones}")
print(f"Promedio: {promedio:.1f}")
print(f"Calificación más alta: {calificacion_maxima}")
print(f"Calificación más baja: {calificacion_minima}")
print(f"Estado: {emoji} {estado}")
# Mostrar calificaciones individuales
print("\nDesglose de calificaciones:")
materias = ["Matemáticas", "Ciencias", "Historia", "Literatura", "Inglés"]
for materia, calificacion in zip(materias, calificaciones):
if calificacion >= 90:
nivel = "Excelente"
elif calificacion >= 80:
nivel = "Bueno"
elif calificacion >= 70:
nivel = "Satisfactorio"
elif calificacion >= 60:
nivel = "Suficiente"
else:
nivel = "Insuficiente"
print(f" • {materia}: {calificacion} - {nivel}")
print("\n" + "=" * 45 + "\n")
print("✅ Generación de informes completada")
¡Ahora tienes el poder de automatizar tareas repetitivas con bucles for! En el próximo capítulo, aprenderás sobre los bucles while, que te permitirán repetir tareas mientras una condición sea verdadera.
¡Bienvenido al centro de monitoreo de tu almacén! Después de conocer al robot especialista (bucle for), es momento de presentarte al robot persistente: el bucle while.
El bucle while es como un robot vigilante en tu almacén que sigue trabajando mientras una condición sea verdadera, sin importar cuánto tiempo tome.
Imagínate que contratas a un robot persistente para tu almacén que puede:
Trabajar indefinidamente mientras una condición sea verdadera
Monitorear sistemas hasta que ocurra un evento específico
Procesar datos hasta que se cumplan ciertos criterios
Esperar hasta que algo importante suceda
Repetir tareas un número variable de veces
# El robot persistente en acción
inventario = 100
umbral_minimo = 20
# El robot monitorea el inventario
while inventario > umbral_minimo:
print(f"Inventario actual: {inventario} unidades")
print("Vendiendo 10 unidades...")
inventario -= 10
print("---")
print(f"¡Alerta! Inventario bajo: {inventario} unidades")
print("Solicitando reabastecimiento...")
Este robot usa la palabra mágica while para saber cuándo debe seguir trabajando y cuándo debe detenerse.
🔍 Mi perspectiva personal: Siempre pienso en los bucles while como un guardia de seguridad que vigila constantemente una puerta. No sabe cuánto tiempo tendrá que vigilar, pero sabe exactamente qué condición debe cumplirse para terminar su turno. Esta imagen me ayuda a recordar que siempre debe haber una condición clara de salida.
Los bucles while son perfectos para interactuar con el usuario hasta que proporcione una entrada válida:
# ================================
# SISTEMA DE AUTENTICACIÓN
# ================================
contraseña_correcta = "python123"
intentos_maximos = 3
intentos_actuales = 0
print("🔐 SISTEMA DE AUTENTICACIÓN")
print("=" * 30)
# En un programa real, usaríamos input() para obtener la contraseña del usuario
# Aquí simularemos diferentes entradas para mostrar el funcionamiento
contraseñas_simuladas = ["clave123", "python", "python123"]
# El robot sigue pidiendo contraseña hasta que sea correcta o se agoten los intentos
while intentos_actuales < intentos_maximos:
# Simular entrada del usuario
contraseña_ingresada = contraseñas_simuladas[intentos_actuales]
intentos_actuales += 1
print(f"Intento #{intentos_actuales}")
print(f"Contraseña ingresada: {contraseña_ingresada}")
if contraseña_ingresada == contraseña_correcta:
print("✅ Acceso concedido")
print("🏠 Bienvenido al sistema")
break # Salir del bucle
else:
intentos_restantes = intentos_maximos - intentos_actuales
if intentos_restantes > 0:
print(f"❌ Contraseña incorrecta")
print(f"Te quedan {intentos_restantes} intentos")
else:
print("🚫 Acceso denegado - Demasiados intentos fallidos")
print("🔒 Cuenta bloqueada temporalmente")
print("🔚 Proceso de autenticación terminado")
Puedes usar operadores lógicos para crear condiciones más complejas:
# ================================
# MONITOR DE RECURSOS DEL SISTEMA
# ================================
print("📊 MONITOR DE RECURSOS DEL SISTEMA")
print("=" * 35)
# Estado inicial del sistema
cpu_usage = 75
memory_usage = 60
disk_space = 50
is_monitoring = True
minutes = 0
print("Iniciando monitoreo del sistema...")
print("Presione Ctrl+C para detener (simulado con límite de 10 minutos)")
print()
# Monitorear mientras el sistema esté en niveles aceptables y el monitoreo esté activo
while (cpu_usage < 90 and memory_usage < 85 and disk_space < 95) and is_monitoring:
minutes += 1
print(f"Minuto {minutes} - Estado del sistema:")
print(f" CPU: {cpu_usage}%")
print(f" Memoria: {memory_usage}%")
print(f" Disco: {disk_space}%")
# Simular cambios en el sistema
cpu_usage += 2 if minutes % 2 == 0 else -1
memory_usage += 3 if minutes % 3 == 0 else 1
disk_space += 1
# Limitar valores
cpu_usage = min(100, max(0, cpu_usage))
memory_usage = min(100, max(0, memory_usage))
disk_space = min(100, max(0, disk_space))
# Simular detención después de 10 minutos
if minutes >= 10:
is_monitoring = False
print()
# Determinar por qué se detuvo el monitoreo
if not is_monitoring:
print("Monitoreo detenido manualmente")
elif cpu_usage >= 90:
print("⚠️ ¡Alerta! Uso de CPU crítico")
elif memory_usage >= 85:
print("⚠️ ¡Alerta! Uso de memoria crítico")
elif disk_space >= 95:
print("⚠️ ¡Alerta! Espacio en disco crítico")
print("Monitoreo finalizado")
Al igual que los bucles for, los bucles while pueden tener una cláusula else que se ejecuta cuando la condición se vuelve falsa (pero no si el bucle termina con break):
# ================================
# BUSCADOR DE PRODUCTOS
# ================================
print("🔍 BUSCADOR DE PRODUCTOS")
print("=" * 25)
productos = ["laptop", "mouse", "teclado", "monitor", "auriculares"]
producto_buscado = "impresora"
print(f"Buscando: {producto_buscado}")
print(f"En inventario: {productos}")
print()
# Inicializar variables
encontrado = False
indice = 0
# Buscar mientras haya elementos por revisar
while indice < len(productos):
print(f"Revisando posición {indice}: {productos[indice]}")
if productos[indice] == producto_buscado:
encontrado = True
print(f"✅ ¡Producto encontrado en posición {indice}!")
break
indice += 1
else:
# Este bloque se ejecuta si el while termina normalmente (sin break)
print("❌ Producto no encontrado en el inventario")
print("Búsqueda finalizada")
# ❌ DIFÍCIL DE LEER
while x > 0 and y < 100 and not z or w == 10:
# Código...
# ✅ MÁS CLARO
condicion1 = x > 0 and y < 100
condicion2 = not z or w == 10
while condicion1 and condicion2:
# Código...
La principal diferencia entre un bucle for y un bucle while es:
Un bucle for se utiliza para iterar sobre una secuencia conocida de elementos o un número conocido de veces.
Un bucle while se ejecuta mientras una condición sea verdadera, sin importar cuántas iteraciones sean necesarias.
En resumen, usamos for cuando sabemos cuántas veces queremos iterar, y while cuando no lo sabemos y dependemos de una condición.
El código imprimirá: 5 4 3 2 1
Explicación: El bucle comienza con x = 5 y se ejecuta mientras x > 0. En cada iteración, imprime el valor de x y luego lo decrementa en 1. El bucle se detiene cuando x llega a 0.
Si la condición de un bucle while nunca se vuelve falsa, se produce un “bucle infinito”. El programa seguirá ejecutando el código dentro del bucle indefinidamente, lo que puede hacer que el programa se bloquee o consuma recursos excesivamente. En entornos de desarrollo, generalmente necesitarás forzar la terminación del programa (por ejemplo, con Ctrl+C).
Bucle while para calcular la suma de los números del 1 al 10:
suma = 0
numero = 1
while numero <= 10:
suma += numero
numero += 1
print(f"La suma de los números del 1 al 10 es: {suma}") # 55
# ================================
# JUEGO DE ADIVINANZA
# ================================
import random
print("🎮 JUEGO DE ADIVINANZA")
print("=" * 25)
# Configuración del juego
numero_secreto = random.randint(1, 100)
intentos_maximos = 7
intentos_realizados = 0
adivinado = False
print("He pensado un número entre 1 y 100.")
print(f"Tienes {intentos_maximos} intentos para adivinarlo.")
print()
# Bucle principal del juego
while intentos_realizados < intentos_maximos and not adivinado:
# En un programa real, usaríamos input() para obtener la respuesta
# Aquí simularemos diferentes respuestas para mostrar el funcionamiento
if intentos_realizados == 0:
intento = 50 # Primera suposición: justo en medio
elif numero_secreto > intento:
intento += max(1, (100 - intento) // 2) # Simular que el jugador va más alto
else:
intento -= max(1, intento // 2) # Simular que el jugador va más bajo
intentos_realizados += 1
print(f"Intento #{intentos_realizados}: {intento}")
# Comprobar la respuesta
if intento == numero_secreto:
adivinado = True
print(f"🎉 ¡Correcto! El número era {numero_secreto}")
print(f"Lo has adivinado en {intentos_realizados} intentos")
elif intento < numero_secreto:
print("📈 Demasiado bajo. Intenta un número más alto.")
else:
print("📉 Demasiado alto. Intenta un número más bajo.")
print()
# Mensaje final
if not adivinado:
print(f"❌ Se acabaron los intentos. El número era {numero_secreto}")
print("Gracias por jugar")
¡Ahora tienes el poder de crear bucles que se ejecutan mientras una condición sea verdadera! En el próximo capítulo, aprenderás técnicas avanzadas para controlar el flujo de tus bucles.
¡Bienvenido al centro de control avanzado de tu almacén! Ahora que conoces a tus robots trabajadores (bucles for y while), es momento de aprender los comandos especiales que te permiten controlar su comportamiento con mayor precisión.
Los comandos de control de bucles son como el control remoto de tus robots, permitiéndote detenerlos, hacer que salten tareas o ejecuten acciones especiales cuando terminan su trabajo.
Imagínate que tienes un control remoto para tus robots con botones especiales:
Botón BREAK: Detiene completamente al robot y lo saca del bucle
Botón CONTINUE: Hace que el robot salte a la siguiente iteración
Botón ELSE: Programa una acción especial para cuando el robot termine su trabajo normalmente
# El control remoto en acción
productos = ["laptop", "mouse", "teclado", "monitor", "impresora"]
producto_agotado = "teclado"
print("🔍 VERIFICACIÓN DE INVENTARIO")
print("=" * 30)
# El robot verifica cada producto
for producto in productos:
print(f"Verificando: {producto}")
# Si encontramos el producto agotado, detenemos la verificación
if producto == producto_agotado:
print(f"❌ ¡{producto} está agotado!")
print("🛑 Deteniendo verificación para hacer un pedido urgente")
break
print(f"✅ {producto} disponible en inventario")
print()
print("Verificación finalizada")
🔍 Mi perspectiva personal: Siempre pienso en break y continue como “palancas de emergencia” que deben usarse estratégicamente. Son herramientas poderosas, pero si abusas de ellas, tu código puede volverse difícil de seguir. Uso break cuando encuentro exactamente lo que estaba buscando y no tiene sentido seguir iterando, y continue cuando quiero saltar un caso especial sin anidar más condiciones.
# ================================
# VALIDADOR DE ENTRADAS
# ================================
print("🔢 VALIDADOR DE ENTRADAS NUMÉRICAS")
print("=" * 35)
# Simular diferentes entradas del usuario
entradas_simuladas = ["abc", "123", "-5", "0", "42"]
indice = 0
# En un programa real, usaríamos input() en un bucle while True
while indice < len(entradas_simuladas):
# Simular entrada del usuario
entrada = entradas_simuladas[indice]
indice += 1
print(f"Procesando entrada: '{entrada}'")
# Validar que sea un número
if not entrada.lstrip('-').isdigit():
print(" ❌ Error: Debe ingresar un número")
print(" ⏭️ Saltando al siguiente intento")
continue
# Convertir a entero
numero = int(entrada)
# Validar que sea positivo
if numero <= 0:
print(" ❌ Error: El número debe ser positivo")
print(" ⏭️ Saltando al siguiente intento")
continue
# Si llegamos aquí, la entrada es válida
print(f" ✅ Entrada válida: {numero}")
print(f" 📊 El cuadrado de {numero} es {numero ** 2}")
print()
print("🔚 Proceso de validación terminado")
Python permite añadir una cláusula else a los bucles for y while. El código en el bloque else se ejecuta cuando el bucle termina normalmente (sin break):
# ================================
# VERIFICADOR DE CALIDAD
# ================================
productos = ["laptop", "mouse", "teclado", "monitor", "auriculares"]
umbral_calidad = 8
calidades = [9, 8, 7, 9, 9] # Calificaciones de 1 a 10
print("🔍 VERIFICADOR DE CALIDAD")
print("=" * 30)
# Verificar la calidad de cada producto
for i, producto in enumerate(productos):
calidad = calidades[i]
print(f"Verificando {producto}: calidad {calidad}/10")
if calidad < umbral_calidad:
print(f"❌ {producto} no cumple el estándar mínimo de calidad")
print("🛑 Deteniendo verificación - Se requiere revisión")
break
print(f"✅ {producto} aprobado")
print()
else:
# Este bloque se ejecuta si el bucle termina normalmente (sin break)
print("🎉 ¡Todos los productos cumplen con el estándar de calidad!")
print("📦 Lote aprobado para distribución")
print("🔚 Verificación finalizada")
# ================================
# MONITOR DE TEMPERATURA
# ================================
print("🌡️ MONITOR DE TEMPERATURA")
print("=" * 30)
# Simular lecturas de temperatura
temperaturas = [22, 23, 24, 25, 26]
umbral_alerta = 30
indice = 0
# Monitorear mientras haya lecturas disponibles
while indice < len(temperaturas):
temperatura = temperaturas[indice]
indice += 1
print(f"Lectura #{indice}: {temperatura}°C")
if temperatura >= umbral_alerta:
print(f"⚠️ ¡Alerta! Temperatura por encima del umbral: {temperatura}°C")
print("🛑 Activando sistema de enfriamiento")
break
print("✅ Temperatura normal")
print()
else:
# Este bloque se ejecuta si el bucle termina sin break
print("📊 Monitoreo completo: Todas las temperaturas están dentro del rango normal")
print("✅ No se requieren acciones adicionales")
print("🔚 Monitoreo finalizado")
# ================================
# BUSCADOR CON BANDERA
# ================================
datos = [10, 25, 3, 8, 42, 15, 7]
objetivo = 42
encontrado = False # Bandera
print("🔍 BUSCADOR CON BANDERA")
print("=" * 25)
print(f"Buscando: {objetivo}")
print(f"En datos: {datos}")
print()
# Buscar el objetivo
for i, valor in enumerate(datos):
print(f"Revisando posición {i}: {valor}")
if valor == objetivo:
encontrado = True # Activar la bandera
posicion = i
break
# Usar la bandera para determinar el resultado
if encontrado:
print(f"✅ Valor {objetivo} encontrado en posición {posicion}")
else:
print(f"❌ Valor {objetivo} no encontrado")
print("🔚 Búsqueda finalizada")
# ================================
# PATRÓN DE BUCLE Y MEDIO
# ================================
print("🔄 PATRÓN DE BUCLE Y MEDIO")
print("=" * 25)
# Simular entrada de usuario
entradas_simuladas = ["", "0", "negativo", "-5", "10"]
indice = 0
# Bucle externo que se repite hasta obtener una entrada válida
while True:
# Simular entrada del usuario
if indice >= len(entradas_simuladas):
break
entrada = entradas_simuladas[indice]
indice += 1
print(f"Procesando entrada: '{entrada}'")
# Validar que no esté vacía
if not entrada:
print(" ❌ Error: La entrada no puede estar vacía")
continue
# Validar que sea un número
try:
numero = int(entrada)
except ValueError:
print(" ❌ Error: Debe ingresar un número")
continue
# Validar que sea positivo
if numero <= 0:
print(" ❌ Error: El número debe ser positivo")
continue
# Si llegamos aquí, la entrada es válida
print(f" ✅ Entrada válida: {numero}")
break # Salir del bucle con una entrada válida
print("🔚 Proceso de validación terminado")
# ❌ ABUSO DE BREAK
for item in lista:
if condicion1:
break
if condicion2:
break
if condicion3:
break
# Código...
# ✅ MÁS CLARO
for item in lista:
if condicion1 or condicion2 or condicion3:
break
# Código...
# ❌ MUCHOS IFS ANIDADOS
for item in lista:
if condicion_valida:
if otra_condicion_valida:
if tercera_condicion_valida:
# Código...
# ✅ MÁS PLANO CON CONTINUE
for item in lista:
if not condicion_valida:
continue
if not otra_condicion_valida:
continue
if not tercera_condicion_valida:
continue
# Código...
# Buscar un elemento
for item in lista:
if item == objetivo:
print("Encontrado")
break
else:
print("No encontrado") # Se ejecuta si no se encontró el objetivo
# ❌ DIFÍCIL DE SEGUIR
for a in lista_a:
for b in lista_b:
for c in lista_c:
for d in lista_d:
# Código...
# ✅ EXTRAER A FUNCIONES
def procesar_listas_c_d(a, b):
for c in lista_c:
for d in lista_d:
# Código...
for a in lista_a:
for b in lista_b:
procesar_listas_c_d(a, b)
break: Termina completamente el bucle y continúa con el código después del bucle.
continue: Salta el resto del código en la iteración actual y pasa a la siguiente iteración del bucle.
El código imprimirá: 0 1 2 4
Explicación: Cuando i es igual a 3, la instrucción continue hace que se salte el print() y se pase a la siguiente iteración. Por lo tanto, se imprimen todos los números del 0 al 4 excepto el 3.
El bloque else de un bucle se ejecuta cuando el bucle termina normalmente, es decir, cuando se han completado todas las iteraciones sin encontrar una instrucción break. Si el bucle termina debido a un break, el bloque else no se ejecuta.
El código imprimirá: 0 1 2 Fin
Explicación: El bucle itera sobre los valores 0, 1 y 2, imprimiendo cada uno. La condición if i == 10 nunca se cumple, por lo que el break nunca se ejecuta. Como el bucle termina normalmente (sin break), se ejecuta el bloque else que imprime “Fin”.
# ================================
# SISTEMA DE PROCESAMIENTO DE PEDIDOS
# ================================
pedidos = [
{"id": 101, "cliente": "Ana García", "productos": ["laptop", "mouse"], "pagado": True},
{"id": 102, "cliente": "Carlos López", "productos": [], "pagado": True},
{"id": 103, "cliente": "Elena Martínez", "productos": ["monitor", "teclado"], "pagado": False},
{"id": 104, "cliente": "David Rodríguez", "productos": ["tablet"], "pagado": True},
{"id": 105, "cliente": "Sofía Pérez", "productos": ["impresora", "scanner", "papel"], "pagado": True}
]
print("📦 SISTEMA DE PROCESAMIENTO DE PEDIDOS")
print("=" * 40)
pedidos_procesados = 0
pedidos_con_error = 0
# Procesar cada pedido
for pedido in pedidos:
id_pedido = pedido["id"]
cliente = pedido["cliente"]
productos = pedido["productos"]
pagado = pedido["pagado"]
print(f"Procesando pedido #{id_pedido} de {cliente}")
# Verificar si el pedido está vacío
if not productos:
print(f" ⚠️ Error: Pedido vacío")
print(f" ⏭️ Saltando al siguiente pedido")
pedidos_con_error += 1
print()
continue
# Verificar si el pedido está pagado
if not pagado:
print(f" ⚠️ Error: Pedido no pagado")
print(f" ⏭️ Saltando al siguiente pedido")
pedidos_con_error += 1
print()
continue
# Procesar productos
print(f" ✅ Verificando {len(productos)} productos:")
for i, producto in enumerate(productos, 1):
print(f" {i}. {producto}")
# Simular procesamiento exitoso
print(f" ✅ Pedido #{id_pedido} procesado correctamente")
pedidos_procesados += 1
print()
else:
print("🎉 ¡Todos los pedidos han sido revisados!")
# Mostrar resumen
print("📊 RESUMEN DE PROCESAMIENTO:")
print(f"Pedidos procesados: {pedidos_procesados}")
print(f"Pedidos con error: {pedidos_con_error}")
print(f"Total de pedidos: {len(pedidos)}")
if pedidos_con_error == 0:
print("✅ Procesamiento completado sin errores")
else:
print(f"⚠️ {pedidos_con_error} pedidos requieren atención")
print("🔚 Proceso finalizado")
¡Ahora tienes el poder de controlar tus bucles con precisión! En el próximo capítulo, aprenderás patrones comunes que combinan todo lo que has aprendido para resolver problemas frecuentes en programación.
¡Bienvenido al departamento de soluciones eficientes de tu almacén! Ahora que conoces todas las herramientas de control de flujo, es momento de aprender algunos patrones comunes que los programadores experimentados utilizan para resolver problemas frecuentes.
Los patrones comunes son como recetas probadas que combinan condicionales y bucles para resolver tareas específicas de manera eficiente y elegante.
Imagínate que tienes un libro de recetas con soluciones optimizadas para las tareas más comunes en tu almacén:
Recetas de búsqueda: Encontrar elementos específicos
Recetas de filtrado: Seleccionar elementos que cumplan ciertos criterios
Recetas de transformación: Modificar elementos de forma sistemática
Recetas de acumulación: Combinar elementos para obtener resultados
Recetas de validación: Verificar que los datos cumplan ciertos requisitos
# Una receta en acción
numeros = [10, 25, 3, 8, 42, 15, 7]
# Receta para encontrar el máximo
maximo = numeros[0] # Empezamos asumiendo que el primero es el máximo
for numero in numeros[1:]: # Revisamos el resto
if numero > maximo:
maximo = numero # Actualizamos si encontramos uno mayor
print(f"El número máximo es: {maximo}") # 42
🔍 Mi perspectiva personal: Dominar estos patrones comunes fue un punto de inflexión en mi carrera como programador. Pasé de escribir código que “simplemente funcionaba” a escribir código elegante, eficiente y fácil de mantener. Estos patrones son como las piezas fundamentales de un juego de construcción: una vez que los dominas, puedes combinarlos para resolver problemas cada vez más complejos.
Este patrón te permite verificar que los datos cumplan ciertos requisitos:
# ================================
# VALIDADOR DE DATOS
# ================================
datos_usuario = {
"nombre": "Ana García",
"email": "ana@ejemplo.com",
"edad": 25,
"contraseña": "Abc123!"
}
print("✅ VALIDADOR DE DATOS")
print("=" * 25)
print("Validando datos de usuario:")
for campo, valor in datos_usuario.items():
print(f" • {campo}: {valor}")
print()
# Inicializar variables
errores = []
# Validar nombre
if not datos_usuario["nombre"]:
errores.append("El nombre no puede estar vacío")
elif len(datos_usuario["nombre"]) < 3:
errores.append("El nombre debe tener al menos 3 caracteres")
# Validar email
email = datos_usuario["email"]
if not email:
errores.append("El email no puede estar vacío")
elif "@" not in email or "." not in email:
errores.append("El email debe contener '@' y '.'")
# Validar edad
edad = datos_usuario["edad"]
if not isinstance(edad, int):
errores.append("La edad debe ser un número entero")
elif edad < 18:
errores.append("Debes ser mayor de edad (18+)")
elif edad > 120:
errores.append("La edad parece incorrecta")
# Validar contraseña
contraseña = datos_usuario["contraseña"]
if len(contraseña) < 6:
errores.append("La contraseña debe tener al menos 6 caracteres")
elif not any(c.isupper() for c in contraseña):
errores.append("La contraseña debe contener al menos una mayúscula")
elif not any(c.islower() for c in contraseña):
errores.append("La contraseña debe contener al menos una minúscula")
elif not any(c.isdigit() for c in contraseña):
errores.append("La contraseña debe contener al menos un número")
elif not any(c in "!@#$%^&*" for c in contraseña):
errores.append("La contraseña debe contener al menos un carácter especial")
# Mostrar resultado
if errores:
print("❌ Validación fallida:")
for error in errores:
print(f" • {error}")
else:
print("✅ Todos los datos son válidos")
print("🔚 Validación finalizada")
Este patrón te permite procesar grandes cantidades de datos en grupos más pequeños:
# ================================
# PROCESADOR POR LOTES
# ================================
datos = list(range(1, 101)) # Lista de números del 1 al 100
tamaño_lote = 10
print("📦 PROCESADOR POR LOTES")
print("=" * 25)
print(f"Total de elementos: {len(datos)}")
print(f"Tamaño de lote: {tamaño_lote}")
print()
# Procesar por lotes
total_lotes = (len(datos) + tamaño_lote - 1) // tamaño_lote # Redondeo hacia arriba
for i in range(total_lotes):
# Calcular índices del lote actual
inicio = i * tamaño_lote
fin = min(inicio + tamaño_lote, len(datos))
lote_actual = datos[inicio:fin]
print(f"Procesando lote {i+1}/{total_lotes} (elementos {inicio+1}-{fin}):")
# Procesar cada elemento del lote
suma_lote = 0
for elemento in lote_actual:
suma_lote += elemento
print(f" • Elementos: {lote_actual}")
print(f" • Suma del lote: {suma_lote}")
print()
print("🔚 Procesamiento por lotes finalizado")
¿Qué patrón utilizarías para encontrar todos los productos con precio mayor a $100?
¿Cuál es la diferencia entre el patrón de transformación y el patrón de acumulación?
Escribe un código que utilice el patrón de validación para verificar si una contraseña cumple con los siguientes requisitos: al menos 8 caracteres, al menos una mayúscula, al menos un número.
¿Qué patrón utilizarías para contar cuántas veces aparece cada letra en una cadena de texto?
Para encontrar todos los productos con precio mayor a $100, utilizaría el patrón de filtrado:
productos = [
{"nombre": "laptop", "precio": 1200},
{"nombre": "mouse", "precio": 25},
{"nombre": "teclado", "precio": 50},
{"nombre": "monitor", "precio": 300}
]
productos_caros = []
for producto in productos:
if producto["precio"] > 100:
productos_caros.append(producto)
# Alternativa con comprensión de listas:
# productos_caros = [p for p in productos if p["precio"] > 100]
Diferencia entre transformación y acumulación:
Transformación (Mapeo): Aplica una operación a cada elemento de una colección para crear una nueva colección con los resultados. La cantidad de elementos de entrada y salida es la misma.
Acumulación: Combina todos los elementos de una colección para producir un único resultado (como una suma, promedio, máximo, etc.).
Código para validar una contraseña:
def validar_contraseña(contraseña):
errores = []
if len(contraseña) < 8:
errores.append("La contraseña debe tener al menos 8 caracteres")
if not any(c.isupper() for c in contraseña):
errores.append("La contraseña debe contener al menos una mayúscula")
if not any(c.isdigit() for c in contraseña):
errores.append("La contraseña debe contener al menos un número")
return errores
# Ejemplo de uso
contraseña = "password123"
errores = validar_contraseña(contraseña)
if errores:
print("Contraseña inválida:")
for error in errores:
print(f"- {error}")
else:
print("Contraseña válida")
Para contar cuántas veces aparece cada letra en una cadena de texto, utilizaría el patrón de acumulación con diccionarios (contador de frecuencias):
texto = "programacion"
frecuencias = {}
for letra in texto:
if letra in frecuencias:
frecuencias[letra] += 1
else:
frecuencias[letra] = 1
# Alternativa con defaultdict:
# from collections import defaultdict
# frecuencias = defaultdict(int)
# for letra in texto:
# frecuencias[letra] += 1
# ================================
# SISTEMA DE ANÁLISIS DE VENTAS
# ================================
ventas = [
{"fecha": "2023-01-15", "producto": "laptop", "categoria": "electrónica", "cantidad": 1, "precio": 1200},
{"fecha": "2023-01-15", "producto": "mouse", "categoria": "electrónica", "cantidad": 3, "precio": 25},
{"fecha": "2023-01-16", "producto": "camisa", "categoria": "ropa", "cantidad": 2, "precio": 30},
{"fecha": "2023-01-17", "producto": "laptop", "categoria": "electrónica", "cantidad": 1, "precio": 1200},
{"fecha": "2023-01-18", "producto": "pantalón", "categoria": "ropa", "cantidad": 1, "precio": 50},
{"fecha": "2023-01-19", "producto": "monitor", "categoria": "electrónica", "cantidad": 2, "precio": 300},
{"fecha": "2023-01-20", "producto": "zapatos", "categoria": "ropa", "cantidad": 1, "precio": 80},
{"fecha": "2023-01-20", "producto": "teclado", "categoria": "electrónica", "cantidad": 4, "precio": 45}
]
print("📊 SISTEMA DE ANÁLISIS DE VENTAS")
print("=" * 35)
print(f"Total de transacciones: {len(ventas)}")
print()
# 1. Calcular ventas totales (patrón de acumulación)
total_ventas = 0
total_unidades = 0
for venta in ventas:
importe = venta["cantidad"] * venta["precio"]
total_ventas += importe
total_unidades += venta["cantidad"]
print(f"💰 VENTAS TOTALES: ${total_ventas:,.2f}")
print(f"📦 UNIDADES VENDIDAS: {total_unidades}")
print()
# 2. Agrupar ventas por categoría (patrón de agrupación)
ventas_por_categoria = {}
for venta in ventas:
categoria = venta["categoria"]
importe = venta["cantidad"] * venta["precio"]
if categoria in ventas_por_categoria:
ventas_por_categoria[categoria] += importe
else:
ventas_por_categoria[categoria] = importe
print("📊 VENTAS POR CATEGORÍA:")
for categoria, importe in ventas_por_categoria.items():
porcentaje = (importe / total_ventas) * 100
print(f" • {categoria}: ${importe:,.2f} ({porcentaje:.1f}%)")
print()
# 3. Encontrar el producto más vendido (patrones de acumulación y búsqueda)
ventas_por_producto = {}
for venta in ventas:
producto = venta["producto"]
cantidad = venta["cantidad"]
if producto in ventas_por_producto:
ventas_por_producto[producto] += cantidad
else:
ventas_por_producto[producto] = cantidad
producto_mas_vendido = ""
max_cantidad = 0
for producto, cantidad in ventas_por_producto.items():
if cantidad > max_cantidad:
max_cantidad = cantidad
producto_mas_vendido = producto
print(f"🏆 PRODUCTO MÁS VENDIDO: {producto_mas_vendido} ({max_cantidad} unidades)")
print()
# 4. Analizar ventas por día (patrón de agrupación)
ventas_por_dia = {}
for venta in ventas:
fecha = venta["fecha"]
importe = venta["cantidad"] * venta["precio"]
if fecha in ventas_por_dia:
ventas_por_dia[fecha]["importe"] += importe
ventas_por_dia[fecha]["transacciones"] += 1
else:
ventas_por_dia[fecha] = {"importe": importe, "transacciones": 1}
print("📅 VENTAS POR DÍA:")
for fecha, datos in ventas_por_dia.items():
print(f" • {fecha}: ${datos['importe']:,.2f} ({datos['transacciones']} transacciones)")
print("🔚 Análisis finalizado")
¡Ahora tienes un arsenal de patrones comunes para resolver problemas frecuentes en programación! En el próximo capítulo, aprenderás a visualizar el flujo de tus programas con diagramas.
Los diagramas de flujo son herramientas visuales que nos ayudan a entender cómo se ejecuta el código en diferentes situaciones. En esta sección, presentamos diagramas para las principales estructuras de control en Python.
for fruta in frutas:
if fruta == "banana":
continue # Salta a la siguiente iteración
if fruta == "cereza":
break # Sale del bucle
print(f"Me gusta la {fruta}")
while contador < 5:
contador += 1
if contador == 2:
continue # Salta a la siguiente iteración
if contador == 4:
break # Sale del bucle
print(f"Contador: {contador}")
InicioEjecutarbloque try¿Ocurreexcepción?¿Tipo deexcepción?Ejecutarexcept tipo 1Ejecutarexcept tipo 2Ejecutarexcept genéricoEjecutarbloque elseEjecutarbloque finallyFin
try:
numero = int(input("Ingrese un número: "))
resultado = 10 / numero
print(f"Resultado: {resultado}")
except ValueError:
print("Error: Debe ingresar un número válido")
except ZeroDivisionError:
print("Error: No se puede dividir por cero")
except:
print("Error inesperado")
else:
print("Operación completada con éxito")
finally:
print("Proceso finalizado")
Estos diagramas te ayudarán a visualizar el flujo de ejecución de las diferentes estructuras de control en Python, facilitando su comprensión y uso efectivo en tus programas.
¡Es hora de poner a prueba tus conocimientos sobre estructuras de control en Python! Este quiz cubre todos los conceptos que hemos visto en esta sección.
¿Cuál es el equivalente del siguiente código usando el operador ternario?
if edad >= 18:
estado = "Mayor de edad"
else:
estado = "Menor de edad"
a) estado = "Mayor de edad" if edad >= 18 else "Menor de edad"
b) estado = edad >= 18 ? "Mayor de edad" : "Menor de edad"
c) estado = "Mayor de edad" when edad >= 18 else "Menor de edad"
d) estado = edad >= 18 then "Mayor de edad" else "Menor de edad"
texto = "programacion"
frecuencias = {}
for letra in texto:
if letra in frecuencias:
frecuencias[letra] += 1
else:
frecuencias[letra] = 1
print(frecuencias)
a) Cuenta cuántas veces aparece cada letra en el texto
b) Elimina letras duplicadas del texto
c) Ordena las letras del texto alfabéticamente
d) Convierte el texto a mayúsculas
En los diagramas de flujo estándar, el rombo se utiliza para representar decisiones (puntos donde el flujo puede tomar diferentes caminos según una condición)
Antes de Python 3.7, los diccionarios no garantizaban un orden específico al iterar sobre ellos
A partir de Python 3.7, los diccionarios mantienen el orden de inserción
Sin embargo, en preguntas de examen, es mejor responder que el orden no está garantizado, ya que es la respuesta más segura y compatible con todas las versiones
Ahora que has completado el quiz, pon a prueba tus habilidades implementando un algoritmo que combine varias estructuras de control:
# ================================
# ANALIZADOR DE TEXTO AVANZADO
# ================================
def analizar_texto(texto):
"""
Analiza un texto y devuelve estadísticas sobre él.
"""
if not texto:
return "El texto está vacío"
# Inicializar contadores
caracteres_total = len(texto)
letras = 0
digitos = 0
espacios = 0
otros = 0
palabras = 0
# Contar frecuencia de cada carácter
frecuencias = {}
# Analizar cada carácter
for caracter in texto:
# Actualizar frecuencia
if caracter in frecuencias:
frecuencias[caracter] += 1
else:
frecuencias[caracter] = 1
# Clasificar carácter
if caracter.isalpha():
letras += 1
elif caracter.isdigit():
digitos += 1
elif caracter.isspace():
espacios += 1
else:
otros += 1
# Contar palabras
palabras = len(texto.split())
# Encontrar el carácter más frecuente
caracter_mas_frecuente = ""
max_frecuencia = 0
for caracter, frecuencia in frecuencias.items():
if frecuencia > max_frecuencia and not caracter.isspace():
max_frecuencia = frecuencia
caracter_mas_frecuente = caracter
# Generar informe
informe = {
"caracteres_total": caracteres_total,
"letras": letras,
"digitos": digitos,
"espacios": espacios,
"otros": otros,
"palabras": palabras,
"caracter_mas_frecuente": caracter_mas_frecuente,
"frecuencia_maxima": max_frecuencia
}
return informe
# Ejemplo de uso
texto_ejemplo = "Python es un lenguaje de programación versátil y poderoso. Fue creado en 1991 por Guido van Rossum."
resultado = analizar_texto(texto_ejemplo)
print("📊 ANÁLISIS DE TEXTO")
print("=" * 25)
print(f"Texto analizado: '{texto_ejemplo}'")
print()
print(f"Total de caracteres: {resultado['caracteres_total']}")
print(f"Letras: {resultado['letras']}")
print(f"Dígitos: {resultado['digitos']}")
print(f"Espacios: {resultado['espacios']}")
print(f"Otros caracteres: {resultado['otros']}")
print(f"Palabras: {resultado['palabras']}")
print(f"Carácter más frecuente: '{resultado['caracter_mas_frecuente']}' ({resultado['frecuencia_maxima']} veces)")
Este ejercicio combina múltiples estructuras de control y patrones que has aprendido:
Condicionales para manejar casos especiales
Bucles para recorrer cada carácter
Acumulación para contar diferentes tipos de caracteres
Contador de frecuencias con diccionarios
Búsqueda del máximo valor
¡Felicidades por completar la sección de Estructuras de Control! Ahora tienes las herramientas fundamentales para crear programas que puedan tomar decisiones y automatizar tareas repetitivas.
¡Bienvenido al sistema de almacenamiento avanzado de tu almacén! En esta sección, aprenderás a organizar y manipular datos de manera eficiente usando diferentes tipos de contenedores especializados.
¡Bienvenido a la sección de estanterías de nuestro almacén! En este capítulo, aprenderás sobre las listas, una de las estructuras de datos más versátiles y utilizadas en Python.
Imagina que en tu almacén acabas de instalar un sistema de estanterías modular súper avanzado. Estas estanterías tienen características especiales:
Se expanden automáticamente cuando necesitas más espacio
Mantienen el orden exacto de los productos que colocas
Numeran automáticamente cada posición (empezando desde 0)
Permiten reorganizar los productos fácilmente
Aceptan cualquier tipo de producto en cualquier posición
En Python, las listas funcionan exactamente así: son colecciones ordenadas y modificables que pueden contener cualquier tipo de datos.
# Tu estantería modular en acción
productos = ["laptop", "mouse", "teclado", "monitor"]
print("📦 Estantería de productos:")
for posicion, producto in enumerate(productos):
print(f"Posición {posicion}: {producto}")
# Resultado:
# Posición 0: laptop
# Posición 1: mouse
# Posición 2: teclado
# Posición 3: monitor
🔍 Mi perspectiva: Cuando empecé a programar, me costaba entender por qué la primera posición era 0 y no 1. Con el tiempo, me di cuenta de que es como medir distancias: el primer elemento está a 0 pasos del inicio, el segundo a 1 paso, y así sucesivamente. Esta forma de pensar me ayudó mucho.
El slicing (rebanado) es como seleccionar una sección completa de tu estantería:
productos = ["A", "B", "C", "D", "E", "F", "G", "H"]
# Sintaxis: lista[inicio:fin:paso]
# (fin no está incluido en el resultado)
# Primeros 3 productos
print(f"Primeros 3: {productos[:3]}") # ['A', 'B', 'C']
# Últimos 3 productos
print(f"Últimos 3: {productos[-3:]}") # ['F', 'G', 'H']
# Del tercero al sexto
print(f"Del 3 al 6: {productos[2:6]}") # ['C', 'D', 'E', 'F']
# Productos en posiciones pares
print(f"Posiciones pares: {productos[::2]}") # ['A', 'C', 'E', 'G']
# Invertir el orden
print(f"Orden inverso: {productos[::-1]}") # ['H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']
🔍 Mi perspectiva: El slicing fue uno de los conceptos que más me costó dominar al principio, pero una vez que lo entiendes, se convierte en una herramienta increíblemente poderosa. Yo lo visualizo como cortar rebanadas de un pan: defines dónde empiezas a cortar, dónde terminas, y qué tan gruesas son las rebanadas.
inventario = ["laptop", "mouse", "laptop", "teclado", "mouse", "laptop"]
# count() - Contar cuántas veces aparece un producto
laptops = inventario.count("laptop")
print(f"Laptops en inventario: {laptops}") # 3
# index() - Encontrar la posición de un producto
posicion_mouse = inventario.index("mouse")
print(f"Primer mouse en posición: {posicion_mouse}") # 1
# in - Verificar si un producto existe
if "webcam" in inventario:
print("Tenemos webcams en stock")
else:
print("No tenemos webcams en stock") # Este se ejecuta
# Datos de ventas mensuales
ventas = [1200, 1500, 1800, 1400, 1600, 2000, 1750, 1900, 1650, 1800, 2100, 2300]
# Operaciones básicas
total_ventas = sum(ventas)
promedio = sum(ventas) / len(ventas)
maximo = max(ventas)
minimo = min(ventas)
print(f"📊 Análisis de ventas anuales:")
print(f" Total: ${total_ventas:,}")
print(f" Promedio mensual: ${promedio:,.2f}")
print(f" Mejor mes: ${maximo:,}")
print(f" Peor mes: ${minimo:,}")
# Encontrar posiciones de máximo y mínimo
mes_mejor = ventas.index(maximo) + 1 # +1 porque los meses empiezan en 1
mes_peor = ventas.index(minimo) + 1
print(f" El mejor mes fue el mes {mes_mejor}")
print(f" El peor mes fue el mes {mes_peor}")
# ✅ Bueno: usar comprensiones de lista
numeros_pares = [x for x in range(1000) if x % 2 == 0]
# ❌ Menos eficiente: bucle tradicional con append
numeros_pares = []
for x in range(1000):
if x % 2 == 0:
numeros_pares.append(x)
# ✅ Bueno: usar extend() para añadir múltiples elementos
lista.extend([1, 2, 3, 4])
# ❌ Menos eficiente: múltiples append()
lista.append(1)
lista.append(2)
lista.append(3)
lista.append(4)
# ❌ Error: modificar lista mientras se itera
numeros = [1, 2, 3, 4, 5]
for numero in numeros:
if numero % 2 == 0:
numeros.remove(numero) # ¡Problemático!
# ✅ Correcto: crear nueva lista
numeros = [1, 2, 3, 4, 5]
numeros_impares = [n for n in numeros if n % 2 != 0]
# ❌ Error: no verificar índices
lista = [1, 2, 3]
# print(lista[5]) # IndexError!
# ✅ Correcto: verificar antes de acceder
if 5 < len(lista):
print(lista[5])
else:
print("Índice fuera de rango")
🔍 Mi perspectiva: Uno de los errores más comunes que veo en mis estudiantes es modificar una lista mientras la recorren. Esto puede causar comportamientos inesperados porque los índices cambian durante la iteración. Siempre recomiendo crear una nueva lista en lugar de modificar la original durante un bucle.
Analiza las ventas semanales de una tienda y calcula estadísticas importantes:
# Código para analizar ventas semanales
ventas_semana = {
"Lunes": 1200.50,
"Martes": 890.75,
"Miércoles": 1300.25,
"Jueves": 1500.80,
"Viernes": 2100.00,
"Sábado": 1850.50,
"Domingo": 900.00
}
# Calcular estadísticas
total_ventas = sum(ventas_semana.values())
dias = len(ventas_semana)
promedio = total_ventas / dias
# Encontrar días por encima del promedio
dias_buenos = [dia for dia, venta in ventas_semana.items() if venta > promedio]
# Encontrar mejor y peor día
mejor_dia = max(ventas_semana.items(), key=lambda x: x[1])
peor_dia = min(ventas_semana.items(), key=lambda x: x[1])
# Mostrar resultados
print("📊 ANÁLISIS DE VENTAS SEMANALES")
print(f"Promedio de ventas: ${promedio:.2f}")
print(f"Días por encima del promedio: {', '.join(dias_buenos)}")
print(f"Mejor día: {mejor_dia[0]} (${mejor_dia[1]:.2f})")
print(f"Peor día: {peor_dia[0]} (${peor_dia[1]:.2f})")
Ejemplo de salida esperada:
📊 ANÁLISIS DE VENTAS SEMANALES
Promedio de ventas: $1,428.57
Días por encima del promedio: Jueves, Viernes, Sábado
Mejor día: Viernes ($2,100.00)
Peor día: Domingo ($900.00)
Escribe una función que analice las ventas diarias de una semana y calcule: el día con más ventas, el día con menos ventas, y el promedio de ventas.
Ver solución
def analizar_ventas_semanales(ventas):
dias = ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"]
# Verificar que tenemos datos para 7 días
if len(ventas) != 7:
return "Error: Se necesitan datos de 7 días"
# Calcular estadísticas
total = sum(ventas)
promedio = total / 7
max_ventas = max(ventas)
min_ventas = min(ventas)
dia_max = dias[ventas.index(max_ventas)]
dia_min = dias[ventas.index(min_ventas)]
# Días por encima del promedio
dias_buenos = [dias[i] for i, venta in enumerate(ventas) if venta > promedio]
return {
"total": total,
"promedio": promedio,
"mejor_dia": dia_max,
"peor_dia": dia_min,
"ventas_max": max_ventas,
"ventas_min": min_ventas,
"dias_buenos": dias_buenos
}
# Ejemplo de uso
ventas_semana = [1200, 980, 1100, 1500, 2100, 1800, 900]
resultado = analizar_ventas_semanales(ventas_semana)
print("\n📊 ANÁLISIS DE VENTAS SEMANALES")
print(f"Total de ventas: ${resultado['total']:,.2f}")
print(f"Promedio diario: ${resultado['promedio']:,.2f}")
print(f"Mejor día: {resultado['mejor_dia']} (${resultado['ventas_max']:,.2f})")
print(f"Peor día: {resultado['peor_dia']} (${resultado['ventas_min']:,.2f})")
print(f"Días por encima del promedio: {', '.join(resultado['dias_buenos'])}")
Resultado:
📊 ANÁLISIS DE VENTAS SEMANALES
Total de ventas: $9,580.00
Promedio diario: $1,368.57
Mejor día: Viernes ($2,100.00)
Peor día: Domingo ($900.00)
Días por encima del promedio: Miércoles, Jueves, Viernes, Sábado
Las listas son una de las herramientas más versátiles en Python, y dominarlas te permitirá resolver una amplia variedad de problemas de programación de manera eficiente.
¡Bienvenido al sistema de inventario inteligente de nuestro almacén! En este capítulo, aprenderás sobre los diccionarios, una estructura de datos extremadamente poderosa y versátil en Python.
Imagina que acabas de implementar un sistema de inventario ultra moderno en tu almacén. Este sistema tiene características especiales:
Cada producto tiene un código único (como un código de barras o QR)
Puedes encontrar cualquier producto instantáneamente solo con su código
No importa cuántos productos tengas - la búsqueda siempre es igual de rápida
Cada código está asociado a toda la información del producto
Nunca hay códigos duplicados - cada uno es único
En Python, los diccionarios funcionan exactamente así: son colecciones de pares clave-valor donde cada clave es única y te permite acceder instantáneamente a su valor asociado.
# Tu sistema de inventario inteligente en acción
inventario = {
"LAP001": {"nombre": "Laptop Dell", "precio": 800, "stock": 5},
"MOU001": {"nombre": "Mouse Logitech", "precio": 25, "stock": 20},
"TEC001": {"nombre": "Teclado Mecánico", "precio": 60, "stock": 15}
}
print("🏷️ Sistema de inventario inteligente:")
codigo = "LAP001"
producto = inventario[codigo]
print(f"Código {codigo}: {producto['nombre']}")
print(f"Precio: ${producto['precio']}")
print(f"Stock: {producto['stock']} unidades")
# ¡Búsqueda instantánea sin importar el tamaño del inventario!
🔍 Mi perspectiva: Cuando empecé a programar, me costaba entender la diferencia entre listas y diccionarios. Todo cambió cuando me di cuenta de que las listas son como buscar productos recorriendo cada estante, mientras que los diccionarios son como usar un escáner de código de barras: ¡instantáneo! Esta analogía transformó mi forma de estructurar datos.
A partir de Python 3.7, los diccionarios mantienen el orden en que se agregaron los elementos:
proceso_pedido = {
"recibir": "Pedido recibido",
"verificar": "Stock verificado",
"procesar": "Pedido procesado",
"enviar": "Pedido enviado"
}
print("Pasos del proceso:")
for paso, estado in proceso_pedido.items():
print(f"- {paso}: {estado}")
# Se imprime en el mismo orden en que se definieron
La búsqueda por clave es O(1) - tiempo constante, sin importar el tamaño del diccionario:
# Igual de rápido con 10 elementos o 10,000,000
gran_inventario = {f"PROD{i}": f"Producto {i}" for i in range(1000000)}
producto = gran_inventario["PROD500000"] # ¡Instantáneo!
print(f"Producto encontrado: {producto}")
# Crear diccionario a partir de lista de tuplas (clave, valor)
datos = [("nombre", "Carlos"), ("edad", 35), ("ciudad", "Madrid")]
persona = dict(datos)
print(persona) # {'nombre': 'Carlos', 'edad': 35, 'ciudad': 'Madrid'}
producto = {"nombre": "Laptop", "precio": 800, "stock": 5}
# Acceso directo (puede causar error si la clave no existe)
nombre = producto["nombre"]
print(f"Nombre: {nombre}")
# get() - acceso seguro con valor por defecto
precio = producto.get("precio", 0) # 800
descuento = producto.get("descuento", 0) # 0 (valor por defecto)
print(f"Precio: ${precio}, Descuento: {descuento}%")
# setdefault() - obtener o establecer valor por defecto
garantia = producto.setdefault("garantia", "1 año") # Si no existe, la crea
print(f"Garantía: {garantia}")
print(producto) # Ahora incluye 'garantia': '1 año'
🔍 Mi perspectiva: Siempre recomiendo usar get() en lugar de acceso directo con corchetes cuando no estás 100% seguro de que la clave existe. Me ha salvado de muchos errores en producción, especialmente cuando trabajo con datos que vienen de APIs externas.
inventario = {"laptop": 10, "mouse": 25, "teclado": 15, "monitor": 8, "webcam": 20}
# pop() - eliminar y devolver valor
stock_mouse = inventario.pop("mouse")
print(f"Stock de mouse eliminado: {stock_mouse}")
print(f"Inventario: {inventario}")
# pop() con valor por defecto (no genera error si la clave no existe)
stock_tablet = inventario.pop("tablet", 0)
print(f"Stock de tablet: {stock_tablet}")
# popitem() - eliminar y devolver último par (Python ≥ 3.7)
ultimo_item = inventario.popitem()
print(f"Último item eliminado: {ultimo_item}")
# del - eliminar por clave
del inventario["teclado"]
print(f"Después de eliminar teclado: {inventario}")
# clear() - vaciar completamente
inventario_temp = {"a": 1, "b": 2}
inventario_temp.clear()
print(f"Inventario vacío: {inventario_temp}")
original = {"a": 1, "b": [2, 3, 4], "c": {"x": 10}}
# copy() - copia superficial
copia_superficial = original.copy()
copia_superficial["a"] = 999
copia_superficial["b"].append(5) # ¡Modifica el original también!
print(f"Original después de modificar copia superficial: {original}")
# La lista en 'b' se modificó en ambos diccionarios
# deepcopy() - copia profunda
import copy
copia_profunda = copy.deepcopy(original)
copia_profunda["b"].append(6) # No afecta el original
print(f"Original después de modificar copia profunda: {original}")
# La lista en 'b' no se modificó en el original
# ❌ Propenso a errores
def procesar_usuario(usuario):
nombre = usuario["nombre"] # KeyError si no existe
email = usuario["email"] # KeyError si no existe
# ...
# ✅ Más seguro
def procesar_usuario_seguro(usuario):
if "nombre" in usuario and "email" in usuario:
# Procesar usuario
print(f"Procesando usuario: {usuario['nombre']}")
else:
print("Datos de usuario incompletos")
from collections import defaultdict
# Contador de palabras
texto = "este es un ejemplo de texto este texto es un ejemplo"
palabras = texto.split()
# ❌ Forma tradicional
contador = {}
for palabra in palabras:
if palabra in contador:
contador[palabra] += 1
else:
contador[palabra] = 1
# ✅ Con defaultdict
contador_mejorado = defaultdict(int)
for palabra in palabras:
contador_mejorado[palabra] += 1
print(f"Contador: {dict(contador_mejorado)}")
# ❌ Error: modificar diccionario durante iteración
productos = {"laptop": 800, "mouse": 25, "teclado": 60}
for producto, precio in productos.items():
if precio < 100:
del productos[producto] # ¡Error! Modifica durante iteración
# ✅ Correcto: crear nuevo diccionario
productos = {"laptop": 800, "mouse": 25, "teclado": 60}
productos_caros = {p: precio for p, precio in productos.items() if precio >= 100}
print(f"Productos caros: {productos_caros}")
🔍 Mi perspectiva: Uno de los errores más comunes que veo en mis estudiantes es modificar un diccionario mientras lo recorren. Esto puede causar comportamientos impredecibles y errores difíciles de depurar. Siempre recomiendo crear un nuevo diccionario con los elementos filtrados en lugar de modificar el original durante la iteración.
Escribe una función que reciba una cadena de texto y devuelva un diccionario con la frecuencia de cada carácter.
Ver solución
def contar_caracteres(texto):
"""Cuenta la frecuencia de cada carácter en un texto."""
frecuencia = {}
for caracter in texto:
if caracter in frecuencia:
frecuencia[caracter] += 1
else:
frecuencia[caracter] = 1
return frecuencia
# Ejemplo de uso
texto = "programación en python"
resultado = contar_caracteres(texto)
print("Frecuencia de caracteres:")
for caracter, cantidad in sorted(resultado.items()):
print(f"'{caracter}': {cantidad}")
Crea una función que combine dos diccionarios de inventario, sumando las cantidades de productos que aparecen en ambos.
Ver solución
def fusionar_inventarios(inv1, inv2):
"""Fusiona dos inventarios sumando las cantidades de productos comunes."""
resultado = inv1.copy() # Comenzar con una copia del primer inventario
# Añadir o actualizar con elementos del segundo inventario
for producto, cantidad in inv2.items():
if producto in resultado:
resultado[producto] += cantidad # Sumar si ya existe
else:
resultado[producto] = cantidad # Añadir si es nuevo
return resultado
# Ejemplo de uso
inventario_almacen1 = {"laptop": 5, "mouse": 10, "teclado": 8}
inventario_almacen2 = {"monitor": 4, "laptop": 3, "impresora": 2}
inventario_total = fusionar_inventarios(inventario_almacen1, inventario_almacen2)
print("Inventario fusionado:")
for producto, cantidad in sorted(inventario_total.items()):
print(f"- {producto}: {cantidad} unidades")
Resultado:
## Ejercicio 2: Fusión de inventarios
Combina dos inventarios en uno solo, sumando las cantidades de productos duplicados:
```python
# Código para fusionar inventarios
inventario_tienda1 = {
"laptop": 5,
"mouse": 10,
"teclado": 8,
"monitor": 4
}
inventario_tienda2 = {
"laptop": 3,
"impresora": 2,
"mouse": 0,
"teclado": 0
}
# Fusionar inventarios
inventario_fusionado = {}
# Agregar todos los productos de la tienda 1
for producto, cantidad in inventario_tienda1.items():
inventario_fusionado[producto] = cantidad
# Agregar o actualizar con productos de la tienda 2
for producto, cantidad in inventario_tienda2.items():
if producto in inventario_fusionado:
inventario_fusionado[producto] += cantidad
else:
inventario_fusionado[producto] = cantidad
# Mostrar inventario fusionado
print("Inventario fusionado:")
for producto, cantidad in sorted(inventario_fusionado.items()):
print(f"- {producto}: {cantidad} unidades")
Modificar: dict[key] = value, update(), pop(), del
Iterar: keys(), values(), items()
Los diccionarios son una de las estructuras de datos más poderosas en Python, y dominarlos te permitirá organizar y manipular datos de manera eficiente en tus aplicaciones.
Imagina que en tu almacén tienes una sección especial para paquetes sellados que contienen productos que no deben ser alterados una vez empaquetados. Estos paquetes tienen una etiqueta de seguridad que indica “No modificar” y están diseñados para mantener su integridad durante todo el proceso logístico.
Las tuplas en Python funcionan exactamente así: son colecciones ordenadas de elementos que, una vez creadas, no pueden ser modificadas. Esta característica las hace perfectas para almacenar datos que deben permanecer constantes a lo largo de la ejecución de tu programa.
🔍 Perspectiva personal: Cuando trabajo con datos que no deben cambiar (como coordenadas geográficas, configuraciones fijas o valores de referencia), siempre uso tuplas. Me dan la seguridad de que estos valores permanecerán intactos, evitando errores accidentales en mi código.
Para crear una tupla con un solo elemento, necesitas incluir una coma después del elemento:
# Esto NO es una tupla, es un string
no_es_tupla = ("único elemento")
print(type(no_es_tupla)) # <class 'str'>
# Esto SÍ es una tupla
es_tupla = ("único elemento",) # ¡Nota la coma!
print(type(es_tupla)) # <class 'tuple'>
🔍 Perspectiva personal: La primera vez que intenté crear una tupla de un solo elemento, olvidé la coma y pasé horas depurando mi código. ¡No cometas mi error!
Al igual que las listas, puedes acceder a los elementos de una tupla mediante índices:
productos = ("laptop", "monitor", "teclado", "mouse", "cables")
# Acceder al primer elemento (índice 0)
print(productos[0]) # "laptop"
# Acceder al último elemento (índice -1)
print(productos[-1]) # "cables"
# Acceder a un rango de elementos (slicing)
print(productos[1:3]) # ("monitor", "teclado")
La característica más importante de las tuplas es que son inmutables, lo que significa que no puedes modificar, añadir o eliminar elementos después de crear la tupla:
coordenadas = (10, 20, 30)
# Intentar modificar un elemento
try:
coordenadas[0] = 15 # Esto generará un error
except TypeError as e:
print(f"Error: {e}") # Error: 'tuple' object does not support item assignment
# Intentar añadir un elemento
try:
coordenadas.append(40) # Esto generará un error
except AttributeError as e:
print(f"Error: {e}") # Error: 'tuple' object has no attribute 'append'
🔍 Perspectiva personal: La inmutabilidad puede parecer una limitación, pero en realidad es una ventaja. Cuando trabajo en equipos grandes, usar tuplas para datos críticos me asegura que nadie (¡ni siquiera yo mismo!) pueda modificarlos accidentalmente.
Si tienes una tupla con más elementos de los que quieres desempaquetar individualmente, puedes usar el operador * para recoger los elementos restantes en una lista:
inventario = ("Laptop Dell", 10, 899.99, "Disponible", "A-12", "Electrónicos")
# Desempaquetar los primeros 3 elementos y recoger el resto
producto, cantidad, precio, *detalles = inventario
print(f"Producto: {producto}") # Producto: Laptop Dell
print(f"Cantidad: {cantidad}") # Cantidad: 10
print(f"Precio: ${precio}") # Precio: $899.99
print(f"Detalles adicionales: {detalles}") # Detalles adicionales: ['Disponible', 'A-12', 'Electrónicos']
También puedes usar el operador * en cualquier posición:
# El asterisco puede estar en cualquier posición
numeros = (1, 2, 3, 4, 5, 6, 7)
primero, *medio, ultimo = numeros
print(f"Primero: {primero}") # Primero: 1
print(f"Medio: {medio}") # Medio: [2, 3, 4, 5, 6]
print(f"Último: {ultimo}") # Último: 7
🔍 Perspectiva personal: El desempaquetado con asterisco es una de mis características favoritas de Python. Me permite escribir código más limpio y expresivo, especialmente cuando trabajo con funciones que devuelven múltiples valores.
A diferencia de las listas, las tuplas pueden ser usadas como claves en diccionarios porque son inmutables:
# Usando tuplas como claves en un diccionario
# Cada clave es una coordenada (x, y)
ubicaciones = {
(0, 0): "Entrada principal",
(10, 5): "Área de electrónicos",
(20, 15): "Almacén de alimentos",
(30, 30): "Salida de emergencia"
}
# Acceder a un valor usando una tupla como clave
print(ubicaciones[(10, 5)]) # "Área de electrónicos"
# Intentar usar una lista como clave generará un error
try:
ubicaciones_error = {[0, 0]: "Esto no funcionará"}
except TypeError as e:
print(f"Error: {e}") # Error: unhashable type: 'list'
Las tuplas tienen menos métodos que las listas debido a su inmutabilidad, pero aún así ofrecen algunos métodos útiles:
productos = ("laptop", "monitor", "teclado", "mouse", "laptop", "cables")
# count(): Contar ocurrencias de un elemento
print(productos.count("laptop")) # 2
# index(): Encontrar la posición de un elemento
print(productos.index("teclado")) # 2
# Intentar encontrar un elemento que no existe generará un error
try:
print(productos.index("impresora"))
except ValueError as e:
print(f"Error: {e}") # Error: tuple.index(x): x not in tuple
Puedes convertir fácilmente entre tuplas y listas:
# Convertir una lista a tupla
lista_productos = ["laptop", "monitor", "teclado", "mouse"]
tupla_productos = tuple(lista_productos)
print(tupla_productos) # ("laptop", "monitor", "teclado", "mouse")
# Convertir una tupla a lista
tupla_numeros = (1, 2, 3, 4, 5)
lista_numeros = list(tupla_numeros)
print(lista_numeros) # [1, 2, 3, 4, 5]
🔍 Perspectiva personal: A veces necesito la flexibilidad de modificar una colección, pero también quiero asegurarme de que ciertos datos permanezcan inmutables. En estos casos, convierto entre listas y tuplas según sea necesario.
# Representar puntos en un espacio 3D
puntos = [
(0, 0, 0), # Origen
(10, 0, 0), # 10 unidades en el eje X
(0, 10, 0), # 10 unidades en el eje Y
(0, 0, 10) # 10 unidades en el eje Z
]
# Calcular la distancia desde el origen a cada punto
import math
def distancia_desde_origen(punto):
x, y, z = punto
return math.sqrt(x**2 + y**2 + z**2)
for i, punto in enumerate(puntos):
print(f"Distancia del punto {i} al origen: {distancia_desde_origen(punto):.2f} unidades")
El primer print accede al segundo elemento de la tupla (índice 1), que es 3.9.
El segundo print accede al último elemento de la tupla (índice -1), que es la lista [1, 2, 3], y luego al primer elemento de esa lista (índice 0), que es 1.
datos = ("Python", 3.9, 2023, [1, 2, 3])
datos[3].append(4)
print(datos)
Ver solución
# Resultado:
version_info = ("Python", 3.9, 2023, [1, 2, 3, 4])
version_info[3].append(5) # Modificamos la lista dentro de la tupla
print(version_info) # ("Python", 3.9, 2023, [1, 2, 3, 4, 5])
Aunque la tupla en sí es inmutable (no podemos cambiar sus elementos directamente), el cuarto elemento es una lista, que sí es mutable. Por lo tanto, podemos modificar la lista dentro de la tupla usando métodos como append().
El desempaquetado asigna el primer valor a a, los valores del medio a b (como una lista gracias al operador *), y los dos últimos valores a c y d respectivamente.
Escribe una función que reciba una lista de coordenadas (x, y) y devuelva la coordenada más cercana al origen (0, 0) y su distancia.
Ver solución
import math
def punto_mas_cercano(coordenadas):
"""
Encuentra el punto más cercano al origen (0, 0) y su distancia.
Args:
coordenadas: Lista de tuplas (x, y)
Returns:
Tupla con el punto más cercano y su distancia al origen
"""
if not coordenadas:
return None, None
# Función para calcular la distancia al origen
def distancia_origen(punto):
x, y = punto
return math.sqrt(x**2 + y**2)
# Encontrar el punto con la distancia mínima
punto_cercano = min(coordenadas, key=distancia_origen)
distancia = distancia_origen(punto_cercano)
return punto_cercano, distancia
# Ejemplo de uso
puntos = [(3, 4), (1, 2), (5, 6), (2, 1)]
punto, distancia = punto_mas_cercano(puntos)
print(f"El punto más cercano al origen es {punto} con una distancia de {distancia:.2f}")
# Salida: El punto más cercano al origen es (1, 2) con una distancia de 2.24
Esta función utiliza la función min() con un argumento key para encontrar el punto con la menor distancia al origen. La distancia se calcula utilizando el teorema de Pitágoras.
Las tuplas son estructuras de datos inmutables que ofrecen varias ventajas:
Inmutabilidad: Una vez creadas, no pueden ser modificadas
Eficiencia: Son más rápidas y usan menos memoria que las listas
Seguridad: Garantizan que los datos no cambiarán
Versatilidad: Pueden ser usadas como claves en diccionarios
Desempaquetado: Permiten asignar múltiples valores de forma concisa
Recuerda nuestra analogía del almacén: las tuplas son como paquetes sellados que garantizan que su contenido permanecerá intacto durante todo el proceso. Son ideales para datos que deben permanecer constantes, como configuraciones, coordenadas o registros históricos.
En el próximo capítulo, exploraremos los conjuntos (sets), que son como estaciones de clasificación que automáticamente eliminan duplicados y permiten operaciones matemáticas como uniones e intersecciones.
Imagina que en tu almacén tienes estaciones de clasificación especiales que automáticamente eliminan duplicados y organizan los elementos de manera eficiente. Estas estaciones están diseñadas para responder rápidamente a preguntas como: “¿Contiene este elemento?”, “¿Qué elementos tienen en común estas dos colecciones?” o “¿Qué elementos están en una colección pero no en la otra?”.
Los conjuntos (sets) en Python funcionan exactamente así: son colecciones desordenadas de elementos únicos que permiten operaciones matemáticas de conjuntos como uniones, intersecciones y diferencias.
🔍 Perspectiva personal: Cuando necesito eliminar duplicados de una lista o verificar pertenencia de manera eficiente, los conjuntos son mi primera opción. Son increíblemente rápidos para estas operaciones y simplifican enormemente el código.
Puedes crear un conjunto utilizando llaves {} o la función set():
# Crear un conjunto con llaves
frutas = {"manzana", "naranja", "plátano", "pera", "manzana"}
print(frutas) # {'naranja', 'plátano', 'manzana', 'pera'}
# Nota: Los duplicados se eliminan automáticamente
# Crear un conjunto a partir de una lista
colores_lista = ["rojo", "azul", "verde", "azul", "amarillo", "rojo"]
colores = set(colores_lista)
print(colores) # {'verde', 'amarillo', 'rojo', 'azul'}
# Crear un conjunto vacío
# Importante: {} crea un diccionario vacío, no un conjunto
conjunto_vacio = set()
print(type(conjunto_vacio)) # <class 'set'>
diccionario_vacio = {}
print(type(diccionario_vacio)) # <class 'dict'>
🔍 Perspectiva personal: Un error común que cometí al principio fue intentar crear un conjunto vacío con {}, ¡pero eso crea un diccionario vacío! Siempre usa set() para crear un conjunto vacío.
Los conjuntos en Python tienen varias características importantes:
Elementos únicos: No permiten duplicados
Desordenados: No mantienen un orden específico
Mutables: Puedes añadir o eliminar elementos
Elementos inmutables: Solo pueden contener elementos inmutables (números, strings, tuplas, pero no listas o diccionarios)
Optimizados para verificar pertenencia: La operación in es muy rápida
# Demostración de características
numeros = {1, 2, 3, 4, 5, 5, 4, 3}
print(numeros) # {1, 2, 3, 4, 5} - Sin duplicados
# Los conjuntos son desordenados
# El orden de impresión puede variar
print({3, 1, 4, 2, 5}) # Podría ser {1, 2, 3, 4, 5} u otro orden
# Verificación de pertenencia (muy eficiente)
print(3 in numeros) # True
print(6 in numeros) # False
# Intentar añadir elementos inmutables
try:
conjunto_invalido = {1, 2, [3, 4]} # Esto generará un error
except TypeError as e:
print(f"Error: {e}") # Error: unhashable type: 'list'
# Las tuplas son inmutables, por lo que pueden estar en un conjunto
conjunto_valido = {1, 2, (3, 4)}
print(conjunto_valido) # {1, 2, (3, 4)}
# Crear un conjunto
inventario = {"laptop", "monitor", "teclado"}
print(f"Inventario inicial: {inventario}")
# Añadir un elemento
inventario.add("mouse")
print(f"Después de añadir: {inventario}")
# Añadir varios elementos
inventario.update(["webcam", "auriculares", "altavoces"])
print(f"Después de update: {inventario}")
# Eliminar un elemento (genera error si no existe)
inventario.remove("webcam")
print(f"Después de remove: {inventario}")
# Eliminar un elemento (no genera error si no existe)
inventario.discard("impresora") # No existe, pero no genera error
print(f"Después de discard: {inventario}")
# Eliminar y devolver un elemento arbitrario
elemento = inventario.pop()
print(f"Elemento eliminado: {elemento}")
print(f"Después de pop: {inventario}")
# Eliminar todos los elementos
inventario.clear()
print(f"Después de clear: {inventario}") # set()
Los conjuntos en Python soportan todas las operaciones matemáticas estándar de teoría de conjuntos:
# Definir dos conjuntos
a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}
# Unión (elementos en A o B)
union = a | b # También: a.union(b)
print(f"Unión: {union}") # {1, 2, 3, 4, 5, 6, 7, 8}
# Intersección (elementos en A y B)
interseccion = a & b # También: a.intersection(b)
print(f"Intersección: {interseccion}") # {4, 5}
# Diferencia (elementos en A pero no en B)
diferencia = a - b # También: a.difference(b)
print(f"Diferencia (A-B): {diferencia}") # {1, 2, 3}
# Diferencia simétrica (elementos en A o B pero no en ambos)
dif_simetrica = a ^ b # También: a.symmetric_difference(b)
print(f"Diferencia simétrica: {dif_simetrica}") # {1, 2, 3, 6, 7, 8}
🔍 Perspectiva personal: Visualizo estas operaciones como si estuviera moviendo productos entre diferentes estaciones de clasificación en el almacén. La unión combina todos los productos, la intersección muestra solo los productos comunes, y la diferencia muestra los productos exclusivos de una estación.
# Definir conjuntos para verificación
frutas = {"manzana", "naranja", "plátano", "pera", "uva"}
citricos = {"naranja", "limón", "lima"}
frutas_tropicales = {"plátano", "piña", "mango"}
solo_naranjas = {"naranja"}
# Verificar si un conjunto es subconjunto de otro
print(solo_naranjas.issubset(frutas)) # True
print(solo_naranjas <= frutas) # True (operador de subconjunto)
# Verificar si un conjunto es superconjunto de otro
print(frutas.issuperset(solo_naranjas)) # True
print(frutas >= solo_naranjas) # True (operador de superconjunto)
# Verificar si dos conjuntos son disjuntos (no tienen elementos en común)
print(citricos.isdisjoint(frutas_tropicales)) # True
print(citricos.isdisjoint(frutas)) # False (tienen "naranja" en común)
Al igual que las listas y los diccionarios, los conjuntos también admiten comprensiones:
# Crear un conjunto de cuadrados de números del 1 al 10
cuadrados = {x**2 for x in range(1, 11)}
print(cuadrados) # {1, 4, 9, 16, 25, 36, 49, 64, 81, 100}
# Crear un conjunto de vocales en una cadena
texto = "Python es un lenguaje de programación versátil y poderoso"
vocales = {letra.lower() for letra in texto if letra.lower() in "aeiou"}
print(vocales) # {'e', 'a', 'i', 'o', 'u'}
# Crear un conjunto de números pares del 1 al 20
pares = {x for x in range(1, 21) if x % 2 == 0}
print(pares) # {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}
Python también ofrece una versión inmutable de los conjuntos llamada frozenset. Una vez creado, un frozenset no puede ser modificado:
# Crear un frozenset
colores_inmutables = frozenset(["rojo", "verde", "azul"])
print(colores_inmutables) # frozenset({'verde', 'azul', 'rojo'})
# Intentar modificar un frozenset generará un error
try:
colores_inmutables.add("amarillo")
except AttributeError as e:
print(f"Error: {e}") # Error: 'frozenset' object has no attribute 'add'
# Los frozensets pueden ser usados como claves en diccionarios
paletas = {
frozenset(["rojo", "verde", "azul"]): "RGB",
frozenset(["cian", "magenta", "amarillo", "negro"]): "CMYK"
}
print(paletas[frozenset(["rojo", "verde", "azul"])]) # "RGB"
🔍 Perspectiva personal: Los frozenset son útiles cuando necesitas la eficiencia de los conjuntos para verificar pertenencia, pero también necesitas inmutabilidad, como al usar conjuntos como claves en un diccionario.
# Ingredientes requeridos para una receta
ingredientes_requeridos = {"harina", "huevos", "azúcar", "leche", "mantequilla"}
# Ingredientes disponibles en la cocina
ingredientes_disponibles = {"harina", "huevos", "azúcar", "sal", "levadura", "mantequilla"}
# Verificar si tenemos todos los ingredientes requeridos
if ingredientes_requeridos.issubset(ingredientes_disponibles):
print("¡Tenemos todos los ingredientes para la receta!")
else:
# Encontrar qué ingredientes faltan
faltantes = ingredientes_requeridos - ingredientes_disponibles
print(f"Faltan los siguientes ingredientes: {faltantes}")
def analizar_texto(texto):
"""Analiza un texto y devuelve estadísticas sobre las palabras."""
# Convertir a minúsculas y dividir en palabras
palabras = texto.lower().split()
# Eliminar signos de puntuación
palabras = [palabra.strip(".,;:!?()[]{}\"'") for palabra in palabras]
# Contar palabras únicas
palabras_unicas = set(palabras)
return {
"total_palabras": len(palabras),
"palabras_unicas": len(palabras_unicas),
"palabras_comunes": sorted(palabras_unicas)[:5] if len(palabras_unicas) >= 5 else sorted(palabras_unicas)
}
# Ejemplo de uso
texto_ejemplo = """
Python es un lenguaje de programación versátil y poderoso.
Python es fácil de aprender y tiene una sintaxis clara.
Los programadores disfrutan usando Python para diversos proyectos.
"""
resultado = analizar_texto(texto_ejemplo)
print(f"Total de palabras: {resultado['total_palabras']}")
print(f"Palabras únicas: {resultado['palabras_unicas']}")
print(f"Algunas palabras comunes: {resultado['palabras_comunes']}")
Los conjuntos en Python están implementados como tablas hash, lo que los hace extremadamente eficientes para operaciones como verificar pertenencia, añadir elementos y eliminar elementos. Estas operaciones tienen una complejidad de tiempo promedio de O(1), lo que significa que son muy rápidas incluso con conjuntos grandes.
import time
# Comparar rendimiento entre lista y conjunto para verificar pertenencia
def comparar_rendimiento(n):
# Crear una lista y un conjunto con n elementos
lista = list(range(n))
conjunto = set(range(n))
# Elemento a buscar (peor caso)
elemento = n - 1
# Medir tiempo para lista
inicio = time.time()
elemento in lista
tiempo_lista = time.time() - inicio
# Medir tiempo para conjunto
inicio = time.time()
elemento in conjunto
tiempo_conjunto = time.time() - inicio
return tiempo_lista, tiempo_conjunto
# Probar con diferentes tamaños
tamaños = [1000, 10000, 100000, 1000000]
print("Comparación de rendimiento (verificación de pertenencia):")
print("Tamaño\t\tTiempo Lista\tTiempo Conjunto\tVeces más rápido")
print("-" * 70)
for n in tamaños:
tiempo_lista, tiempo_conjunto = comparar_rendimiento(n)
veces_mas_rapido = tiempo_lista / tiempo_conjunto if tiempo_conjunto > 0 else "∞"
print(f"{n:,}\t\t{tiempo_lista:.6f}s\t{tiempo_conjunto:.6f}s\t{veces_mas_rapido:.1f}x")
🔍 Perspectiva personal: La primera vez que vi la diferencia de rendimiento entre buscar en una lista vs. buscar en un conjunto con miles de elementos, quedé impresionado. Los conjuntos pueden ser cientos o miles de veces más rápidos para estas operaciones.
Escribe una función que elimine los duplicados de una lista manteniendo el orden original de los elementos.
Ver solución
def eliminar_duplicados_ordenados(lista):
"""
Elimina duplicados de una lista manteniendo el orden original.
Args:
lista: Lista con posibles duplicados
Returns:
Lista sin duplicados, manteniendo el orden original
"""
return list(dict.fromkeys(lista))
# Ejemplo de uso
numeros = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(eliminar_duplicados_ordenados(numeros)) # [3, 1, 4, 5, 9, 2, 6]
Esta solución utiliza un diccionario para mantener el orden (en Python 3.7+, los diccionarios mantienen el orden de inserción). Al convertir la lista a un diccionario con las claves como elementos de la lista, automáticamente se eliminan los duplicados mientras se preserva el orden.
Escribe una función que tome varias listas y devuelva los elementos que aparecen en una y solo una de las listas.
Ver solución
def elementos_unicos(*listas):
"""
Encuentra elementos que aparecen en exactamente una de las listas.
Args:
*listas: Múltiples listas
Returns:
Conjunto con elementos que aparecen en exactamente una lista
"""
if not listas:
return set()
# Convertir todas las listas a conjuntos
conjuntos = [set(lista) for lista in listas]
# Unión de todos los conjuntos
todos = set().union(*conjuntos)
# Elementos que aparecen en más de una lista
comunes = set()
for i, conjunto_a in enumerate(conjuntos):
for j, conjunto_b in enumerate(conjuntos):
if i < j: # Evitar comparar un conjunto consigo mismo o repetir comparaciones
comunes.update(conjunto_a & conjunto_b)
# Elementos únicos = todos - comunes
return todos - comunes
# Ejemplo de uso
lista1 = [1, 2, 3, 4, 5]
lista2 = [4, 5, 6, 7, 8]
lista3 = [7, 8, 9, 10]
unicos = elementos_unicos(lista1, lista2, lista3)
print(unicos) # {1, 2, 3, 6, 9, 10}
Esta función convierte cada lista a un conjunto, encuentra la unión de todos los conjuntos, y luego resta los elementos que aparecen en más de una lista.
Escribe una función que determine si dos palabras son anagramas (contienen exactamente las mismas letras).
Ver solución
def son_anagramas(palabra1, palabra2):
"""
Verifica si dos palabras son anagramas.
Args:
palabra1: Primera palabra
palabra2: Segunda palabra
Returns:
True si son anagramas, False en caso contrario
"""
# Eliminar espacios y convertir a minúsculas
palabra1 = palabra1.lower().replace(" ", "")
palabra2 = palabra2.lower().replace(" ", "")
# Verificar si tienen las mismas letras
return set(palabra1) == set(palabra2) and len(palabra1) == len(palabra2)
# Ejemplos de uso
print(son_anagramas("amor", "roma")) # True
print(son_anagramas("listen", "silent")) # True
print(son_anagramas("hello", "world")) # False
print(son_anagramas("astronomer", "moon starer")) # True
Esta función convierte ambas palabras a conjuntos y verifica si contienen las mismas letras. También verifica que tengan la misma longitud, ya que dos palabras con diferentes cantidades de las mismas letras no son anagramas (por ejemplo, “aab” y “aba” tienen las mismas letras pero no son anagramas).
Los conjuntos son estructuras de datos poderosas y eficientes que ofrecen varias ventajas:
Elementos únicos: Eliminan automáticamente los duplicados
Operaciones matemáticas: Soportan uniones, intersecciones y diferencias
Eficiencia: Verificación de pertenencia extremadamente rápida (O(1))
Flexibilidad: Pueden ser mutables (set) o inmutables (frozenset)
Recuerda nuestra analogía del almacén: los conjuntos son como estaciones de clasificación que automáticamente eliminan duplicados y permiten operaciones eficientes entre diferentes grupos de elementos. Son ideales para eliminar duplicados, verificar pertenencia eficientemente y realizar operaciones matemáticas entre colecciones.
Con esto, hemos completado nuestro recorrido por las principales estructuras de datos en Python:
Listas: Estanterías flexibles para secuencias ordenadas
Diccionarios: Sistema de inventario con etiquetas para acceso rápido
Tuplas: Paquetes sellados que no pueden modificarse
Conjuntos: Estaciones de clasificación que eliminan duplicados
Cada una tiene sus propias fortalezas y casos de uso, y dominarlas te permitirá elegir la herramienta adecuada para cada tarea en tus proyectos de Python.
En esta sección visualizaremos las diferentes estructuras de datos en Python para ayudar a comprender cómo se organizan y cómo se accede a los elementos.
¡Es hora de poner a prueba tus conocimientos sobre las estructuras de datos en Python! Este quiz cubre los conceptos clave de listas, diccionarios, tuplas y conjuntos que hemos explorado en este capítulo.
¿Qué estructura de datos corresponde a cada analogía del almacén?
a) Estanterías flexibles que pueden reorganizarse
b) Sistema de inventario con etiquetas
c) Paquetes sellados que no pueden modificarse
d) Estaciones de clasificación sin duplicados
Ver respuesta
Respuestas correctas:
a) Listas: Estanterías flexibles que pueden reorganizarse
b) Diccionarios: Sistema de inventario con etiquetas
c) Tuplas: Paquetes sellados que no pueden modificarse
d) Conjuntos: Estaciones de clasificación sin duplicados
Estas analogías nos ayudan a entender el propósito y comportamiento de cada estructura de datos:
Las listas son flexibles y ordenadas, como estanterías que podemos reorganizar
Los diccionarios permiten acceso rápido por clave, como un sistema de inventario
Las tuplas son inmutables, como paquetes sellados
Los conjuntos eliminan duplicados automáticamente, como estaciones de clasificación
¿Cuáles de las siguientes afirmaciones son correctas sobre la mutabilidad de las estructuras de datos?
a) Las listas son mutables y pueden contener cualquier tipo de dato
b) Los diccionarios son inmutables una vez creados
c) Las tuplas son inmutables pero pueden contener objetos mutables
d) Los conjuntos son mutables pero solo pueden contener objetos inmutables
Ver respuesta
Respuestas correctas: a), c) y d)
Explicación:
✅ Las listas son mutables y pueden contener cualquier tipo de dato
❌ Los diccionarios son mutables, no inmutables
✅ Las tuplas son inmutables pero pueden contener objetos mutables (como listas)
✅ Los conjuntos son mutables pero solo pueden contener objetos inmutables (números, strings, tuplas)
Ejemplo con tuplas conteniendo objetos mutables:
# La tupla es inmutable, pero la lista dentro de ella es mutable
t = (1, [2, 3], 4)
t[1].append(5) # Válido
print(t) # (1, [2, 3, 5], 4)
# Pero no podemos cambiar la tupla en sí
try:
t[1] = [6, 7] # Esto generará un error
except TypeError as e:
print(f"Error: {e}")
Ordena las siguientes operaciones de más eficiente a menos eficiente:
a) Buscar un elemento en una lista
b) Buscar una clave en un diccionario
c) Verificar si un elemento está en un conjunto
d) Buscar un elemento en una tupla
Ver respuesta
Orden correcto (de más eficiente a menos eficiente):
b) y c) - Buscar en diccionario y conjunto: O(1)
a) y d) - Buscar en lista y tupla: O(n)
Explicación:
Los diccionarios y conjuntos usan tablas hash, permitiendo búsquedas en tiempo constante O(1)
Las listas y tuplas requieren búsqueda lineal O(n), revisando cada elemento
Las tuplas no son más rápidas que las listas para búsquedas, aunque son más eficientes en memoria
# Ejemplo de rendimiento
import time
def medir_tiempo(operacion, n=1000000):
inicio = time.time()
operacion()
return time.time() - inicio
# Preparar estructuras
lista = list(range(n))
conjunto = set(lista)
diccionario = {x: x for x in lista}
elemento = n - 1 # Peor caso
# Medir tiempos
tiempo_lista = medir_tiempo(lambda: elemento in lista)
tiempo_conjunto = medir_tiempo(lambda: elemento in conjunto)
tiempo_dict = medir_tiempo(lambda: elemento in diccionario)
print(f"Tiempo lista: {tiempo_lista:.6f}s")
print(f"Tiempo conjunto: {tiempo_conjunto:.6f}s")
print(f"Tiempo diccionario: {tiempo_dict:.6f}s")
Escribe una función que calcule el valor total del inventario.
Ver respuesta
Solución:
def valor_total_inventario(inventario):
total = 0
for categoria in inventario.values():
for producto in categoria.values():
total += producto['stock'] * producto['precio']
return total
# Calcular el valor total
total = valor_total_inventario(almacen)
print(f"Valor total del inventario: ${total:,}") # $19,200
Explicación:
La función recorre cada categoría del almacén
Para cada categoría, recorre cada producto
Multiplica el stock por el precio de cada producto
Suma todos los valores para obtener el total
Alternativa usando comprensión de diccionarios:
def valor_total_inventario_comprension(inventario):
return sum(
producto['stock'] * producto['precio']
for categoria in inventario.values()
for producto in categoria.values()
)
Escribe expresiones para encontrar:
a) Empleados que conocen los tres lenguajes
b) Empleados que solo conocen Python
c) Empleados que conocen al menos dos lenguajes
Ver respuesta
Solución:
# a) Empleados que conocen los tres lenguajes
todos_lenguajes = empleados_python & empleados_java & empleados_javascript
print(f"Conocen los tres lenguajes: {todos_lenguajes}") # {'Carlos', 'Diana'}
# b) Empleados que solo conocen Python
solo_python = empleados_python - (empleados_java | empleados_javascript)
print(f"Solo conocen Python: {solo_python}") # {'Ana'}
# c) Empleados que conocen al menos dos lenguajes
python_java = empleados_python & empleados_java
python_js = empleados_python & empleados_javascript
java_js = empleados_java & empleados_javascript
al_menos_dos = python_java | python_js | java_js
print(f"Conocen al menos dos lenguajes: {al_menos_dos}") # {'Carlos', 'Diana'}
Explicación de operadores:
&: intersección (elementos comunes)
|: unión (todos los elementos)
-: diferencia (elementos en el primer conjunto pero no en el segundo)
Implementa un sistema simple de inventario que permita:
Añadir productos con cantidad y precio
Actualizar el stock
Calcular el valor total del inventario
Listar productos con bajo stock (menos de 5 unidades)
Ver respuesta
Solución:
class Inventario:
def __init__(self):
self.productos = {}
def agregar_producto(self, codigo, nombre, cantidad, precio):
"""Añade un nuevo producto o actualiza uno existente."""
self.productos[codigo] = {
'nombre': nombre,
'cantidad': cantidad,
'precio': precio
}
def actualizar_stock(self, codigo, cantidad):
"""Actualiza la cantidad de un producto."""
if codigo in self.productos:
self.productos[codigo]['cantidad'] += cantidad
return True
return False
def valor_total(self):
"""Calcula el valor total del inventario."""
return sum(
prod['cantidad'] * prod['precio']
for prod in self.productos.values()
)
def productos_bajo_stock(self, limite=5):
"""Lista productos con stock menor al límite."""
return {
codigo: datos
for codigo, datos in self.productos.items()
if datos['cantidad'] < limite
}
def mostrar_inventario(self):
"""Muestra el inventario completo."""
print("\nInventario Actual:")
print("-" * 50)
print(f"{'Código':<10} {'Nombre':<20} {'Cantidad':<10} {'Precio':>8}")
print("-" * 50)
for codigo, datos in self.productos.items():
print(f"{codigo:<10} {datos['nombre']:<20} {datos['cantidad']:<10} ${datos['precio']:>7.2f}")
print("-" * 50)
print(f"Valor total del inventario: ${self.valor_total():,.2f}")
# Ejemplo de uso
inventario = Inventario()
# Agregar productos
inventario.agregar_producto('LAP001', 'Laptop Dell', 10, 1200)
inventario.agregar_producto('MON001', 'Monitor 24"', 15, 200)
inventario.agregar_producto('TEC001', 'Teclado Mecánico', 3, 80)
inventario.agregar_producto('MOU001', 'Mouse Inalámbrico', 20, 30)
# Mostrar inventario inicial
inventario.mostrar_inventario()
# Actualizar stock
inventario.actualizar_stock('LAP001', -2) # Venta de 2 laptops
inventario.actualizar_stock('MON001', 5) # Recepción de 5 monitores
# Mostrar productos con bajo stock
print("\nProductos con bajo stock:")
for codigo, datos in inventario.productos_bajo_stock().items():
print(f"{datos['nombre']}: {datos['cantidad']} unidades")
# Mostrar inventario actualizado
inventario.mostrar_inventario()
Este ejemplo demuestra:
Uso de diccionarios anidados para almacenar datos
Métodos para manipular el inventario
Comprensiones de diccionarios para filtrar datos
Formateo de strings para presentación
Características adicionales que podrías implementar:
Validación de datos (cantidades no negativas, precios válidos)
from collections import Counter
from itertools import combinations
def productos_mas_vendidos(ventas):
"""Encuentra los productos más vendidos y su cantidad."""
contador = Counter()
for _, productos in ventas:
contador.update(productos)
return contador.most_common()
def ventas_por_dia(ventas):
"""Calcula el número de ventas por día."""
ventas_dia = {}
for fecha, productos in ventas:
ventas_dia[fecha] = ventas_dia.get(fecha, 0) + 1
return dict(sorted(ventas_dia.items()))
def productos_relacionados(ventas, min_frecuencia=2):
"""Identifica productos que se compran juntos frecuentemente."""
pares = Counter()
for _, productos in ventas:
# Generar todos los pares posibles de productos
for par in combinations(sorted(productos), 2):
pares[par] += 1
# Filtrar pares que aparecen al menos min_frecuencia veces
return {par: freq for par, freq in pares.items()
if freq >= min_frecuencia}
# Análisis de ventas
print("Productos más vendidos:")
for producto, cantidad in productos_mas_vendidos(ventas):
print(f"{producto}: {cantidad} ventas")
print("\nVentas por día:")
for fecha, cantidad in ventas_por_dia(ventas).items():
print(f"{fecha}: {cantidad} ventas")
print("\nProductos frecuentemente comprados juntos:")
for (prod1, prod2), freq in productos_relacionados(ventas).items():
print(f"{prod1} + {prod2}: {freq} veces")
Este ejemplo demuestra:
Uso de Counter para contar ocurrencias
Diccionarios para agrupar datos por fecha
Combinaciones para analizar patrones
Comprensiones de diccionarios para filtrar resultados
Optimiza el siguiente código que procesa datos de ventas:
def procesar_ventas(ventas):
productos_vendidos = []
ventas_por_producto = {}
clientes_por_producto = {}
for venta in ventas:
producto = venta['producto']
cantidad = venta['cantidad']
cliente = venta['cliente']
productos_vendidos.append(producto)
if producto in ventas_por_producto:
ventas_por_producto[producto] += cantidad
else:
ventas_por_producto[producto] = cantidad
if producto not in clientes_por_producto:
clientes_por_producto[producto] = []
if cliente not in clientes_por_producto[producto]:
clientes_por_producto[producto].append(cliente)
return (list(set(productos_vendidos)),
ventas_por_producto,
clientes_por_producto)
Ver respuesta
Solución optimizada:
from collections import defaultdict
def procesar_ventas_optimizado(ventas):
"""
Procesa datos de ventas de manera eficiente.
Args:
ventas: Lista de diccionarios con datos de ventas
Returns:
Tupla con (productos únicos, ventas por producto, clientes por producto)
"""
productos_vendidos = set() # Usar set desde el inicio
ventas_por_producto = defaultdict(int) # Valor default 0
clientes_por_producto = defaultdict(set) # Valor default set()
for venta in ventas:
producto = venta['producto']
productos_vendidos.add(producto)
ventas_por_producto[producto] += venta['cantidad']
clientes_por_producto[producto].add(venta['cliente'])
return (list(productos_vendidos),
dict(ventas_por_producto),
{k: list(v) for k, v in clientes_por_producto.items()})
# Comparación de rendimiento
import time
# Datos de prueba
datos_prueba = [
{'producto': 'laptop', 'cantidad': 1, 'cliente': 'Ana'},
{'producto': 'monitor', 'cantidad': 2, 'cliente': 'Bruno'},
{'producto': 'laptop', 'cantidad': 1, 'cliente': 'Carlos'},
{'producto': 'teclado', 'cantidad': 3, 'cliente': 'Ana'},
{'producto': 'monitor', 'cantidad': 1, 'cliente': 'Diana'}
]
# Medir tiempo de la versión original
inicio = time.time()
resultado_original = procesar_ventas(datos_prueba)
tiempo_original = time.time() - inicio
# Medir tiempo de la versión optimizada
inicio = time.time()
resultado_optimizado = procesar_ventas_optimizado(datos_prueba)
tiempo_optimizado = time.time() - inicio
print(f"Tiempo versión original: {tiempo_original:.6f}s")
print(f"Tiempo versión optimizada: {tiempo_optimizado:.6f}s")
print(f"Mejora: {(tiempo_original/tiempo_optimizado):.2f}x más rápido")
Mejoras realizadas:
Uso de set() desde el inicio para productos únicos
Uso de defaultdict para evitar verificaciones de existencia
Uso de set() para clientes por producto, eliminando duplicados automáticamente
Conversión final a los tipos de datos requeridos
Ventajas:
Código más conciso y legible
Mejor rendimiento
Menos propenso a errores
Más eficiente en memoria
La versión optimizada es especialmente más eficiente con conjuntos de datos grandes debido a:
¡Bienvenido al departamento de herramientas especializadas de nuestro almacén! Las funciones son como las máquinas y herramientas que nos permiten automatizar tareas repetitivas, mientras que los módulos son como los diferentes talleres especializados donde guardamos estas herramientas organizadas por propósito.
# Si cambia la tasa de impuesto, solo modificamos una función
def calcular_impuesto(precio, tasa_impuesto=0.16):
"""Máquina calculadora de impuestos actualizable"""
return precio * tasa_impuesto
# Todas las operaciones del almacén se actualizan automáticamente
def procesar_venta(precio_producto):
impuesto = calcular_impuesto(precio_producto) # Se actualiza automáticamente
return precio_producto + impuesto
# archivo: inventario.py (Departamento de Inventario)
def agregar_producto(nombre, cantidad, precio):
"""Añade un producto al inventario"""
pass
def buscar_producto(codigo):
"""Busca un producto por su código"""
pass
def actualizar_stock(codigo, nueva_cantidad):
"""Actualiza la cantidad en stock"""
pass
# archivo: ventas.py (Departamento de Ventas)
def procesar_venta(codigo_producto, cantidad):
"""Procesa una venta y actualiza inventario"""
pass
def calcular_total_venta(productos_vendidos):
"""Calcula el total de una venta múltiple"""
pass
# archivo: main.py (Centro de Comando)
# Importamos los departamentos especializados
import inventario
import ventas
# Coordinamos las operaciones
inventario.agregar_producto("Laptop", 10, 1500)
ventas.procesar_venta("LAP001", 2)
# ❌ Nombres poco descriptivos
def calc(p, d): # ¿Qué calcula? ¿Qué significan p y d?
return p * (1 - d)
# ✅ Nombres claros y descriptivos
def calcular_precio_con_descuento(precio_original, porcentaje_descuento):
"""Calcula el precio final después de aplicar un descuento"""
return precio_original * (1 - porcentaje_descuento / 100)
def procesar_devolucion(producto, motivo, condicion_producto):
"""
Procesa la devolución de un producto al almacén.
Args:
producto (dict): Información del producto a devolver
motivo (str): Razón de la devolución ('defectuoso', 'cambio_opinion', etc.)
condicion_producto (str): Estado del producto ('nuevo', 'usado', 'dañado')
Returns:
dict: Resultado del procesamiento con status y acciones tomadas
Raises:
ValueError: Si el producto no es válido para devolución
"""
# Implementación...
pass
InicioLlamada a funciónresultado = calcular_area(5, 3)Crear marco de ejecuciónAsignar argumentos a parámetrosbase = 5, altura = 3Ejecutar cuerpo de la funciónarea = base * alturaarea = 5 * 3 = 15Retornar valorreturn area (15)Destruir marco de ejecuciónLiberar variables localesAsignar valor retornadoresultado = 15Continuar ejecucióndel programa principalFinEstado de la MemoriaAntes de la llamada:Variables globalesDurante la ejecución:Variables globales+ Marco de calcular_area (base=5, altura=3, area=15)Después de la llamada:Variables globales+ resultado = 15
¡Bienvenido al departamento de archivos y documentación de nuestro almacén digital! El manejo de archivos es como tener un sistema de archivo empresarial donde podemos leer, escribir, organizar y procesar documentos de manera automática.
El manejo de archivos es la capacidad de interactuar con archivos del sistema: leer información de documentos existentes, crear nuevos archivos, y procesar grandes volúmenes de datos almacenados. Es como tener un asistente administrativo digital que puede:
# Modos de apertura de archivos
"r" # Lectura (archivo debe existir)
"w" # Escritura (sobrescribe si existe)
"a" # Añadir al final (append)
"r+" # Lectura y escritura
"x" # Creación exclusiva (falla si existe)
def generar_reporte_inventario_bajo():
"""Genera reporte de productos con stock bajo"""
productos_bajo_stock = []
# Leer inventario actual
with open("inventario.csv", "r") as f:
reader = csv.DictReader(f)
for producto in reader:
if int(producto["Stock"]) < 10:
productos_bajo_stock.append(producto)
# Generar reporte
with open("alerta_stock_bajo.txt", "w") as f:
f.write("\n=== ALERTA: PRODUCTOS CON STOCK BAJO ===\n")
for producto in productos_bajo_stock:
f.write(f"- {producto['Nombre']}: {producto['Stock']} unidades\n")
return len(productos_bajo_stock)
def analizar_ventas_mensuales():
"""Analiza las ventas del mes y genera estadísticas"""
ventas = []
# Leer datos de ventas
with open("ventas_enero.csv", "r") as f:
reader = csv.DictReader(f)
ventas = list(reader)
# Procesar estadísticas
total_ventas = sum(float(venta["Total"]) for venta in ventas)
promedio_diario = total_ventas / 31 # enero tiene 31 días
# Guardar análisis
analisis = {
"periodo": "Enero 2024",
"total_ventas": total_ventas,
"promedio_diario": promedio_diario,
"numero_transacciones": len(ventas)
}
with open("analisis_enero_2024.json", "w") as f:
json.dump(analisis, f, indent=2)
return analisis
# ✅ Correcto
with open("archivo.txt", "r") as f:
contenido = f.read()
# El archivo se cierra automáticamente
# ❌ Incorrecto
f = open("archivo.txt", "r")
contenido = f.read()
# ¿Qué pasa si ocurre un error antes de cerrar?
try:
with open("archivo_importante.txt", "r") as f:
datos = f.read()
except FileNotFoundError:
print("El archivo no existe, creando uno nuevo...")
with open("archivo_importante.txt", "w") as f:
f.write("Contenido inicial")
except PermissionError:
print("No tienes permisos para acceder al archivo")
def procesar_archivo_ventas(nombre_archivo):
"""Procesa archivo de ventas con validaciones"""
# Verificar que el archivo existe
if not os.path.exists(nombre_archivo):
raise FileNotFoundError(f"No se encuentra el archivo: {nombre_archivo}")
# Verificar que no está vacío
if os.path.getsize(nombre_archivo) == 0:
raise ValueError("El archivo está vacío")
# Procesar contenido
with open(nombre_archivo, "r", encoding="utf-8") as f:
# ... procesamiento seguro
pass
Los archivos de texto son como los documentos básicos de nuestra oficina del almacén: reportes, notas, logs de actividad, instrucciones y toda la documentación que necesitamos leer y generar automáticamente.
Los archivos de texto contienen información en formato legible por humanos, sin formato especial. Son como documentos escritos a máquina que pueden contener:
# Modos de apertura de archivos
"r" # Lectura: Leer un documento existente
"w" # Escritura: Crear nuevo documento (sobrescribe si existe)
"a" # Añadir: Agregar contenido al final del documento
"r+" # Lectura/Escritura: Leer y modificar documento existente
"x" # Creación exclusiva: Crear solo si NO existe
# Leer reporte completo de inventario
with open("reporte_inventario.txt", "r", encoding="utf-8") as archivo:
contenido_completo = archivo.read()
print(contenido_completo)
# Ejemplo práctico: Analizar reporte de ventas
def analizar_reporte_ventas():
"""Lee y analiza el reporte diario de ventas"""
try:
with open("ventas_hoy.txt", "r", encoding="utf-8") as f:
reporte = f.read()
# Buscar información específica
if "Stock bajo" in reporte:
print("⚠️ Alerta: Hay productos con stock bajo")
# Contar líneas del reporte
lineas = reporte.count("\n")
print(f"El reporte tiene {lineas} líneas")
return reporte
except FileNotFoundError:
print("No se encontró el reporte de ventas de hoy")
return None
# Procesar un log de actividades línea por línea
def procesar_log_almacen():
"""Procesa el log de actividades del almacén"""
actividades_importantes = []
with open("log_almacen.txt", "r", encoding="utf-8") as archivo:
for numero_linea, linea in enumerate(archivo, 1):
linea = linea.strip() # Eliminar espacios al inicio/final
# Filtrar solo actividades importantes
if "ERROR" in linea or "ALERTA" in linea:
actividades_importantes.append({
"linea": numero_linea,
"contenido": linea
})
return actividades_importantes
# Ejemplo de uso
alerts = procesar_log_almacen()
for alerta in alerts:
print(f"Línea {alerta['linea']}: {alerta['contenido']}")
# Leer lista de productos para inventario
def cargar_lista_productos():
"""Carga la lista de productos desde archivo"""
with open("productos_almacen.txt", "r", encoding="utf-8") as archivo:
todas_las_lineas = archivo.readlines()
# Limpiar espacios en blanco de cada línea
productos = [linea.strip() for linea in todas_las_lineas if linea.strip()]
return productos
# Ejemplo de uso
productos = cargar_lista_productos()
print(f"Tenemos {len(productos)} productos en el catálogo:")
for i, producto in enumerate(productos[:5], 1): # Mostrar primeros 5
print(f"{i}. {producto}")
# Sistema de log de actividades del almacén
def registrar_actividad(actividad, usuario="Sistema"):
"""Registra una actividad en el log del almacén"""
from datetime import datetime
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
entrada_log = f"[{timestamp}] {usuario}: {actividad}\n"
# Añadir al archivo de log existente
with open("log_almacen.txt", "a", encoding="utf-8") as archivo:
archivo.write(entrada_log)
print(f"✅ Actividad registrada: {actividad}")
# Ejemplos de uso del sistema de log
registrar_actividad("Inicio de jornada laboral", "Ana García")
registrar_actividad("Recibidos 50 productos nuevos", "Carlos López")
registrar_actividad("Venta procesada: $1,299.99", "Sistema")
registrar_actividad("Alerta: Stock bajo en Laptop Gaming Pro", "Sistema")
# ✅ Correcto: El archivo se cierra automáticamente
with open("archivo.txt", "r", encoding="utf-8") as f:
contenido = f.read()
# Aquí el archivo ya está cerrado
# ❌ Incorrecto: Debes cerrar manualmente
f = open("archivo.txt", "r")
contenido = f.read()
f.close() # ¿Qué pasa si hay un error antes de esto?
# ✅ Correcto: Especifica UTF-8 para caracteres especiales
with open("reporte.txt", "w", encoding="utf-8") as f:
f.write("Menú del día: café, niños, cumpleaños 🎉")
# ❌ Problemas potenciales sin encoding
with open("reporte.txt", "w") as f: # Puede fallar con acentos
f.write("Menú del día")
def leer_archivo_seguro(nombre_archivo):
"""Lee un archivo de manera segura con manejo de errores"""
try:
with open(nombre_archivo, "r", encoding="utf-8") as archivo:
return archivo.read()
except FileNotFoundError:
print(f"⚠️ Archivo no encontrado: {nombre_archivo}")
return None
except PermissionError:
print(f"⚠️ Sin permisos para leer: {nombre_archivo}")
return None
except UnicodeDecodeError:
print(f"⚠️ Error de codificación en: {nombre_archivo}")
# Intentar con codificación diferente
try:
with open(nombre_archivo, "r", encoding="latin-1") as archivo:
return archivo.read()
except:
return None
except Exception as e:
print(f"⚠️ Error inesperado: {e}")
return None
# Uso seguro
contenido = leer_archivo_seguro("reporte_importante.txt")
if contenido:
print("Archivo leído exitosamente")
else:
print("No se pudo leer el archivo")
def procesar_archivo_productos():
"""Procesa archivo de productos limpiando datos"""
productos_limpios = []
with open("productos_raw.txt", "r", encoding="utf-8") as f:
for numero_linea, linea in enumerate(f, 1):
# Limpiar la línea
linea_limpia = linea.strip()
# Saltar líneas vacías
if not linea_limpia:
continue
# Saltar comentarios
if linea_limpia.startswith("#"):
continue
# Validar que la línea tenga contenido válido
if len(linea_limpia) < 3:
print(f"⚠️ Línea {numero_linea} muy corta: '{linea_limpia}'")
continue
productos_limpios.append(linea_limpia)
return productos_limpios
Crea un sistema que genere reportes personalizados para el almacén:
def crear_reporte_personalizado(titulo, datos, observaciones):
"""
Crea un reporte personalizado con el formato del almacén.
Args:
titulo (str): Título del reporte
datos (list): Lista de datos para incluir
observaciones (list): Lista de observaciones
"""
# Tu implementación aquí...
pass
# Prueba tu función
datos_inventario = [
"Laptops en stock: 25 unidades",
"Valor total: $45,000",
"Productos vendidos hoy: 8"
]
observaciones_del_dia = [
"Alta demanda en laptops gaming",
"Considerar descuento en accesorios",
"Revisar proveedor de cables USB"
]
crear_reporte_personalizado(
"Reporte de Inventario Semanal",
datos_inventario,
observaciones_del_dia
)
Crea un analizador que procese logs del almacén y genere estadísticas:
def analizar_logs_almacen(archivo_log):
"""
Analiza el archivo de logs y genera estadísticas.
Debe contar:
- Total de líneas procesadas
- Número de errores encontrados
- Número de ventas exitosas
- Actividades por usuario
"""
# Tu implementación aquí...
pass
# Crear un log de ejemplo para probar
log_ejemplo = """
[2024-01-15 09:00:00] Ana García: Inicio de jornada
[2024-01-15 09:15:00] Sistema: Venta procesada - $299.99
[2024-01-15 09:30:00] Carlos López: ERROR - Producto no encontrado
[2024-01-15 10:00:00] Sistema: Venta procesada - $1,299.99
[2024-01-15 10:15:00] Ana García: Producto agregado al inventario
[2024-01-15 10:30:00] Sistema: ERROR - Conexión perdida
"""
with open("log_ejemplo.txt", "w", encoding="utf-8") as f:
f.write(log_ejemplo)
# Probar tu analizador
analizar_logs_almacen("log_ejemplo.txt")
¡Felicidades! Ahora dominas el manejo de archivos de texto en Python. Eres como un administrador de documentos experto que puede:
📄 Leer cualquier documento: Procesar reportes, logs y archivos de datos
✍️ Generar documentos automáticamente: Crear reportes personalizados y profesionales
📋 Organizar información: Estructurar datos de manera clara y legible
🛡️ Manejar errores: Trabajar de manera segura con archivos
🚀 Automatizar tareas: Procesar grandes volúmenes de información
Estas habilidades son fundamentales para cualquier proyecto de automatización y te permiten crear sistemas que pueden comunicarse con el mundo real a través de archivos.
Este ejemplo muestra cómo crear una jerarquía de excepciones para validar datos de entrada:
class ValidacionError(Exception):
"""Clase base para errores de validación de datos."""
pass
class TipoInvalidoError(ValidacionError):
"""Se lanza cuando el tipo de dato no es el esperado."""
def __init__(self, valor, tipo_esperado):
self.valor = valor
self.tipo_esperado = tipo_esperado
self.tipo_recibido = type(valor).__name__
mensaje = f"Tipo inválido: se esperaba {tipo_esperado.__name__}, se recibió {self.tipo_recibido}"
super().__init__(mensaje)
class ValorFueraDeRangoError(ValidacionError):
"""Se lanza cuando un valor numérico está fuera del rango permitido."""
def __init__(self, valor, minimo=None, maximo=None):
self.valor = valor
self.minimo = minimo
self.maximo = maximo
if minimo is not None and maximo is not None:
mensaje = f"Valor {valor} fuera de rango: debe estar entre {minimo} y {maximo}"
elif minimo is not None:
mensaje = f"Valor {valor} demasiado pequeño: debe ser >= {minimo}"
elif maximo is not None:
mensaje = f"Valor {valor} demasiado grande: debe ser <= {maximo}"
else:
mensaje = f"Valor {valor} fuera de rango"
super().__init__(mensaje)
class FormatoInvalidoError(ValidacionError):
"""Se lanza cuando un valor no cumple con el formato esperado."""
def __init__(self, valor, patron=None, descripcion=None):
self.valor = valor
self.patron = patron
if descripcion:
mensaje = f"Formato inválido: {descripcion}"
elif patron:
mensaje = f"Formato inválido: '{valor}' no coincide con el patrón '{patron}'"
else:
mensaje = f"Formato inválido: '{valor}'"
super().__init__(mensaje)
class ValorRequeridoError(ValidacionError):
"""Se lanza cuando falta un valor requerido."""
def __init__(self, campo):
self.campo = campo
mensaje = f"Valor requerido: el campo '{campo}' es obligatorio"
super().__init__(mensaje)
# Funciones de validación
def validar_tipo(valor, tipo_esperado):
"""Valida que un valor sea del tipo esperado."""
if not isinstance(valor, tipo_esperado):
raise TipoInvalidoError(valor, tipo_esperado)
return valor
def validar_rango(valor, minimo=None, maximo=None):
"""Valida que un valor numérico esté dentro del rango especificado."""
if (minimo is not None and valor < minimo) or (maximo is not None and valor > maximo):
raise ValorFueraDeRangoError(valor, minimo, maximo)
return valor
def validar_formato(valor, patron, descripcion=None):
"""Valida que un valor cumpla con un patrón de formato."""
import re
if not re.match(patron, valor):
raise FormatoInvalidoError(valor, patron, descripcion)
return valor
def validar_requerido(valor, campo):
"""Valida que un valor requerido no sea None o vacío."""
if valor is None or (isinstance(valor, (str, list, dict)) and len(valor) == 0):
raise ValorRequeridoError(campo)
return valor
# Ejemplo de uso
def validar_usuario(datos):
"""Valida los datos de un usuario."""
try:
# Validar campos requeridos
validar_requerido(datos.get('nombre'), 'nombre')
validar_requerido(datos.get('email'), 'email')
# Validar tipos
validar_tipo(datos['nombre'], str)
validar_tipo(datos['email'], str)
if 'edad' in datos:
validar_tipo(datos['edad'], int)
# Validar rangos
if 'edad' in datos:
validar_rango(datos['edad'], 18, 120)
# Validar formatos
validar_formato(
datos['email'],
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
"El email debe tener un formato válido"
)
return True
except ValidacionError as e:
# Podemos manejar la excepción o dejarla propagar
print(f"Error de validación: {e}")
raise
# Prueba
try:
usuario = {
'nombre': 'Ana García',
'email': 'ana.garcia@ejemplo.com',
'edad': 25
}
validar_usuario(usuario)
print("Usuario válido")
except TipoInvalidoError as e:
print(f"Error de tipo: {e}")
print(f"Valor: {e.valor}, Tipo esperado: {e.tipo_esperado.__name__}, Tipo recibido: {e.tipo_recibido}")
except ValorFueraDeRangoError as e:
print(f"Error de rango: {e}")
print(f"Valor: {e.valor}, Rango: {e.minimo} - {e.maximo}")
except FormatoInvalidoError as e:
print(f"Error de formato: {e}")
print(f"Valor: {e.valor}, Patrón: {e.patron}")
except ValorRequeridoError as e:
print(f"Error de campo requerido: {e}")
print(f"Campo: {e.campo}")
except ValidacionError as e:
print(f"Error de validación general: {e}")
Este ejemplo muestra cómo crear excepciones personalizadas para una capa de acceso a datos:
class BaseDatosError(Exception):
"""Clase base para errores relacionados con la base de datos."""
def __init__(self, mensaje, codigo=None, query=None):
self.codigo = codigo
self.query = query
mensaje_completo = mensaje
if codigo:
mensaje_completo += f" (Código: {codigo})"
super().__init__(mensaje_completo)
class ConexionError(BaseDatosError):
"""Error al conectar con la base de datos."""
def __init__(self, mensaje, host=None, puerto=None, codigo=None):
self.host = host
self.puerto = puerto
mensaje_completo = mensaje
if host:
mensaje_completo += f" (Host: {host}"
if puerto:
mensaje_completo += f", Puerto: {puerto}"
mensaje_completo += ")"
super().__init__(mensaje_completo, codigo)
class ConsultaError(BaseDatosError):
"""Error al ejecutar una consulta SQL."""
pass
class DatoNoEncontradoError(BaseDatosError):
"""No se encontró el dato solicitado."""
def __init__(self, tabla, condiciones, codigo=None, query=None):
self.tabla = tabla
self.condiciones = condiciones
mensaje = f"No se encontró el registro en '{tabla}' con condiciones: {condiciones}"
super().__init__(mensaje, codigo, query)
class DuplicadoError(BaseDatosError):
"""Se intentó insertar un registro duplicado."""
def __init__(self, tabla, campo, valor, codigo=None, query=None):
self.tabla = tabla
self.campo = campo
self.valor = valor
mensaje = f"Registro duplicado en '{tabla}': {campo}='{valor}'"
super().__init__(mensaje, codigo, query)
class IntegridadError(BaseDatosError):
"""Error de integridad referencial."""
def __init__(self, mensaje, tabla=None, campo=None, codigo=None, query=None):
self.tabla = tabla
self.campo = campo
mensaje_completo = mensaje
if tabla and campo:
mensaje_completo += f" (Tabla: {tabla}, Campo: {campo})"
super().__init__(mensaje_completo, codigo, query)
# Clase de acceso a datos que usa las excepciones
class BaseDatos:
def __init__(self, host, usuario, password, base_datos):
self.host = host
self.usuario = usuario
self.password = password
self.base_datos = base_datos
self.conexion = None
def conectar(self):
"""Establece conexión con la base de datos."""
try:
# Aquí iría el código real de conexión
# Por ejemplo, con psycopg2 para PostgreSQL:
# self.conexion = psycopg2.connect(
# host=self.host,
# user=self.usuario,
# password=self.password,
# dbname=self.base_datos
# )
# Simulamos una conexión exitosa
self.conexion = "Conexión simulada"
print(f"Conectado a {self.base_datos} en {self.host}")
return True
except Exception as e:
# Convertimos la excepción estándar en nuestra excepción personalizada
raise ConexionError(
f"Error al conectar a la base de datos: {str(e)}",
host=self.host,
codigo="DB_CONN_001"
) from e
def ejecutar_consulta(self, query, parametros=None):
"""Ejecuta una consulta SQL."""
if not self.conexion:
raise ConexionError("No hay conexión activa a la base de datos")
try:
# Aquí iría el código real de ejecución de consulta
# Por ejemplo:
# cursor = self.conexion.cursor()
# cursor.execute(query, parametros)
# resultados = cursor.fetchall()
# return resultados
# Simulamos diferentes escenarios según la consulta
if "SELECT" in query and "WHERE id = 999" in query:
# Simular registro no encontrado
raise DatoNoEncontradoError(
"usuarios",
"id = 999",
codigo="DB_NOT_FOUND_001",
query=query
)
elif "INSERT" in query and "usuarios" in query:
# Simular error de duplicado
if parametros and "usuario@ejemplo.com" in str(parametros):
raise DuplicadoError(
"usuarios",
"email",
"usuario@ejemplo.com",
codigo="DB_DUP_001",
query=query
)
elif "DELETE" in query and "productos" in query:
# Simular error de integridad referencial
raise IntegridadError(
"No se puede eliminar el producto porque tiene pedidos asociados",
tabla="productos",
campo="id",
codigo="DB_FK_001",
query=query
)
# Simulamos una ejecución exitosa para otras consultas
print(f"Consulta ejecutada: {query}")
if "SELECT" in query:
return [{"id": 1, "nombre": "Ejemplo"}]
else:
return True
except (DatoNoEncontradoError, DuplicadoError, IntegridadError):
# Re-lanzamos nuestras propias excepciones
raise
except Exception as e:
# Convertimos otras excepciones
raise ConsultaError(
f"Error al ejecutar consulta: {str(e)}",
codigo="DB_QUERY_001",
query=query
) from e
def obtener_usuario(self, usuario_id):
"""Obtiene un usuario por su ID."""
try:
query = f"SELECT * FROM usuarios WHERE id = {usuario_id}"
resultado = self.ejecutar_consulta(query)
if not resultado:
raise DatoNoEncontradoError(
"usuarios",
f"id = {usuario_id}",
query=query
)
return resultado[0]
except BaseDatosError:
# Re-lanzamos nuestras propias excepciones
raise
except Exception as e:
# Convertimos otras excepciones
raise BaseDatosError(f"Error inesperado: {str(e)}") from e
# Ejemplo de uso
try:
db = BaseDatos("localhost", "usuario", "contraseña", "mi_base_datos")
db.conectar()
# Intentar obtener un usuario que no existe
usuario = db.obtener_usuario(999)
print(f"Usuario encontrado: {usuario}")
except ConexionError as e:
print(f"Error de conexión: {e}")
if hasattr(e, 'host'):
print(f"Host: {e.host}")
except DatoNoEncontradoError as e:
print(f"Dato no encontrado: {e}")
print(f"Tabla: {e.tabla}, Condiciones: {e.condiciones}")
if e.query:
print(f"Query: {e.query}")
except DuplicadoError as e:
print(f"Dato duplicado: {e}")
print(f"Tabla: {e.tabla}, Campo: {e.campo}, Valor: {e.valor}")
except IntegridadError as e:
print(f"Error de integridad: {e}")
if hasattr(e, 'tabla') and e.tabla:
print(f"Tabla: {e.tabla}, Campo: {e.campo}")
except BaseDatosError as e:
print(f"Error de base de datos: {e}")
if hasattr(e, 'codigo') and e.codigo:
print(f"Código de error: {e.codigo}")
Este ejemplo muestra cómo crear excepciones personalizadas para operaciones con archivos:
class ArchivoError(Exception):
"""Clase base para errores relacionados con archivos."""
def __init__(self, mensaje, ruta=None):
self.ruta = ruta
mensaje_completo = mensaje
if ruta:
mensaje_completo += f" (Ruta: {ruta})"
super().__init__(mensaje_completo)
class ArchivoNoEncontradoError(ArchivoError):
"""El archivo no existe."""
def __init__(self, ruta):
super().__init__("Archivo no encontrado", ruta)
class ArchivoNoAccesibleError(ArchivoError):
"""No se tiene permiso para acceder al archivo."""
def __init__(self, ruta, operacion=None):
self.operacion = operacion
mensaje = "Archivo no accesible"
if operacion:
mensaje += f" para {operacion}"
super().__init__(mensaje, ruta)
class FormatoArchivoError(ArchivoError):
"""El formato del archivo no es válido."""
def __init__(self, ruta, formato_esperado=None, detalle=None):
self.formato_esperado = formato_esperado
self.detalle = detalle
mensaje = "Formato de archivo inválido"
if formato_esperado:
mensaje += f", se esperaba {formato_esperado}"
if detalle:
mensaje += f": {detalle}"
super().__init__(mensaje, ruta)
class ArchivoCorruptoError(ArchivoError):
"""El archivo está corrupto o dañado."""
def __init__(self, ruta, detalle=None):
self.detalle = detalle
mensaje = "Archivo corrupto o dañado"
if detalle:
mensaje += f": {detalle}"
super().__init__(mensaje, ruta)
# Funciones que utilizan las excepciones
def leer_archivo(ruta, modo='r'):
"""Lee el contenido de un archivo."""
import os
# Verificar si el archivo existe
if not os.path.exists(ruta):
raise ArchivoNoEncontradoError(ruta)
# Verificar si se puede leer
if not os.access(ruta, os.R_OK):
raise ArchivoNoAccesibleError(ruta, "lectura")
try:
with open(ruta, modo) as archivo:
return archivo.read()
except PermissionError:
raise ArchivoNoAccesibleError(ruta, "lectura")
except UnicodeDecodeError:
raise FormatoArchivoError(ruta, "texto", "Error de codificación")
except Exception as e:
raise ArchivoError(f"Error al leer archivo: {str(e)}", ruta) from e
def leer_json(ruta):
"""Lee un archivo JSON."""
import json
try:
contenido = leer_archivo(ruta)
return json.loads(contenido)
except json.JSONDecodeError as e:
raise FormatoArchivoError(ruta, "JSON", str(e)) from e
except ArchivoError:
# Re-lanzar excepciones de archivo
raise
except Exception as e:
raise ArchivoError(f"Error inesperado: {str(e)}", ruta) from e
def leer_csv(ruta, delimitador=','):
"""Lee un archivo CSV."""
import csv
try:
contenido = leer_archivo(ruta)
filas = []
for linea in contenido.splitlines():
if not linea.strip():
continue
filas.append(linea.split(delimitador))
return filas
except ArchivoError:
# Re-lanzar excepciones de archivo
raise
except Exception as e:
raise FormatoArchivoError(ruta, "CSV", str(e)) from e
# Ejemplo de uso
def procesar_archivo_configuracion():
"""Procesa un archivo de configuración."""
try:
# Intentar leer un archivo JSON
config = leer_json("config.json")
print(f"Configuración cargada: {config}")
return config
except ArchivoNoEncontradoError as e:
print(f"Error: {e}")
print("Creando archivo de configuración por defecto...")
# Crear archivo de configuración por defecto
return {"configuracion": "por defecto"}
except ArchivoNoAccesibleError as e:
print(f"Error: {e}")
print(f"Operación: {e.operacion}")
print("Verifique los permisos del archivo")
return None
except FormatoArchivoError as e:
print(f"Error: {e}")
if e.formato_esperado:
print(f"Formato esperado: {e.formato_esperado}")
if e.detalle:
print(f"Detalle: {e.detalle}")
print("El archivo de configuración tiene un formato incorrecto")
return None
except ArchivoError as e:
print(f"Error general de archivo: {e}")
if e.ruta:
print(f"Ruta: {e.ruta}")
return None
Este patrón permite añadir información contextual a las excepciones:
class ContextoError(Exception):
"""Excepción base que mantiene información de contexto."""
def __init__(self, mensaje, **contexto):
self.contexto = contexto
mensaje_completo = mensaje
if contexto:
detalles = ", ".join(f"{k}={v}" for k, v in contexto.items())
mensaje_completo += f" [{detalles}]"
super().__init__(mensaje_completo)
# Ejemplo de uso
def procesar_datos(datos, contexto=None):
"""Procesa datos con información de contexto."""
contexto = contexto or {}
try:
# Código que puede fallar
resultado = datos["valor"] / datos.get("divisor", 0)
return resultado
except KeyError as e:
# Añadir contexto a la excepción
raise ContextoError(
f"Falta la clave requerida: {e}",
operacion="procesar_datos",
datos_recibidos=str(datos),
**contexto
) from e
except ZeroDivisionError as e:
# Añadir contexto a la excepción
raise ContextoError(
"División por cero",
operacion="procesar_datos",
datos_recibidos=str(datos),
**contexto
) from e
Este patrón permite definir estrategias de recuperación dentro de las excepciones:
class RecuperableError(Exception):
"""Excepción base que incluye estrategias de recuperación."""
def __init__(self, mensaje):
self.recuperado = False
super().__init__(mensaje)
def intentar_recuperacion(self):
"""Método que las subclases deben implementar para recuperarse."""
raise NotImplementedError("Las subclases deben implementar este método")
class ConexionPerdidaError(RecuperableError):
"""Error de conexión perdida con estrategia de reconexión."""
def __init__(self, servicio, intentos_maximos=3):
self.servicio = servicio
self.intentos_maximos = intentos_maximos
self.intentos = 0
super().__init__(f"Conexión perdida con {servicio}")
def intentar_recuperacion(self):
"""Intenta reconectar al servicio."""
if self.intentos >= self.intentos_maximos:
return False
self.intentos += 1
print(f"Intento {self.intentos}/{self.intentos_maximos} de reconexión a {self.servicio}...")
# Aquí iría el código real de reconexión
# Simulamos éxito en el tercer intento
if self.intentos == 3:
print(f"Reconexión exitosa a {self.servicio}")
self.recuperado = True
return True
print("Reconexión fallida")
return False
# Ejemplo de uso
def ejecutar_con_recuperacion(funcion, *args, **kwargs):
"""Ejecuta una función con soporte para recuperación de errores."""
try:
return funcion(*args, **kwargs)
except RecuperableError as e:
print(f"Error recuperable: {e}")
while not e.recuperado:
if e.intentar_recuperacion():
# Reintentar la función original
print("Reintentando operación original...")
return funcion(*args, **kwargs)
if e.intentos >= e.intentos_maximos:
print("Se agotaron los intentos de recuperación")
raise
# Si llegamos aquí, la recuperación fue exitosa
print("Recuperación exitosa, reintentando operación...")
return funcion(*args, **kwargs)
InicioEjecutar códigoen bloque try¿Ocurreexcepción?¿Hay manejadorpara esta excepción?Ejecutar bloqueexcept correspondienteEjecutar bloqueelse (si existe)Ejecutar bloquefinally (si existe)Propagar excepciónal nivel superiorFin
try:
# Código que puede generar excepciones
numero = int(input("Ingrese un número: "))
resultado = 10 / numero
print(f"El resultado es: {resultado}")
except ValueError:
# Se ejecuta si ocurre ValueError
print("Error: Debe ingresar un número válido")
except ZeroDivisionError:
# Se ejecuta si ocurre ZeroDivisionError
print("Error: No se puede dividir por cero")
except Exception as e:
# Se ejecuta para otras excepciones
print(f"Error inesperado: {e}")
else:
# Se ejecuta si no ocurre ninguna excepción
print("Operación completada con éxito")
finally:
# Se ejecuta siempre, haya o no excepciones
print("Proceso finalizado")
class ErrorDatoInvalido(Exception):
"""Excepción para datos que no cumplen con los requisitos."""
def __init__(self, valor, mensaje="Valor no válido"):
self.valor = valor
self.mensaje = mensaje
super().__init__(f"{mensaje}: {valor}")
def __str__(self):
return f"{self.mensaje}: {self.valor}"
def validar_edad(edad):
if not isinstance(edad, int):
raise ErrorDatoInvalido(edad, "La edad debe ser un número entero")
if edad < 0 or edad > 120:
raise ErrorDatoInvalido(edad, "La edad debe estar entre 0 y 120")
return True
try:
validar_edad("veinticinco")
except ErrorDatoInvalido as e:
print(f"Error: {e}")
Herencia de ExcepcionesExceptionErrorAplicacionErrorValidacionErrorDatoInvalidoErrorConexionOrganizaciónjerárquica
try:
valor = diccionario["clave"]
resultado = 10 / valor
except KeyError:
print("La clave no existe")
except ZeroDivisionError:
print("No se puede dividir por cero")
if "clave" in diccionario:
valor = diccionario["clave"]
if valor != 0:
resultado = 10 / valor
else:
print("No se puede dividir por cero")
else:
print("La clave no existe")
Estos diagramas te ayudarán a visualizar cómo funciona el manejo de errores en Python, facilitando su comprensión y uso efectivo en tus programas.
¡Bienvenido al capítulo de Proyectos de Automatización! Ahora que has dominado los fundamentos de Python, es momento de aplicar tus conocimientos en proyectos prácticos que te ayudarán a automatizar tareas cotidianas.
La automatización es una de las aplicaciones más poderosas y prácticas de la programación. Consiste en usar código para realizar tareas repetitivas o complejas sin intervención humana, ahorrando tiempo y reduciendo errores.
¡Es hora de poner en práctica todo lo aprendido! Estos proyectos no solo te ayudarán a consolidar tus conocimientos de Python, sino que también te proporcionarán herramientas útiles que podrás usar y personalizar según tus necesidades.
Todos hemos experimentado alguna vez la pérdida de archivos importantes: un documento en el que trabajamos durante horas, fotos familiares irremplazables, o información crítica para nuestro trabajo o estudios. Estas pérdidas pueden ocurrir por diversos motivos:
Fallas de hardware
Errores humanos (borrado accidental)
Malware o virus
Robo o daño físico del dispositivo
Corrupción de archivos
La solución es implementar un sistema de copias de seguridad (backups) que automáticamente resguarde tus archivos importantes de forma regular.
import os # Para operaciones con el sistema de archivos
import shutil # Para copiar archivos y directorios
import zipfile # Para comprimir archivos
import datetime # Para obtener la fecha actual
import logging # Para registrar eventos
import schedule # Para programar la ejecución automática
import time # Para pausas en la ejecución
Primero, definiremos las rutas de origen (archivos a respaldar) y destino (donde se guardarán los backups):
# backup_config.py
# Rutas de origen (archivos/carpetas a respaldar)
SOURCE_PATHS = [
'/ruta/a/documentos_importantes',
'/ruta/a/fotos_familiares',
'/ruta/a/proyectos/python'
]
# Ruta de destino (donde se guardarán los backups)
BACKUP_DESTINATION = '/ruta/a/backups'
# Configuración de registro (logs)
LOG_FILE = '/ruta/a/backups/backup_log.txt'
# Programación (cuándo ejecutar el backup)
BACKUP_SCHEDULE = {
'daily': '20:00', # Todos los días a las 8:00 PM
'weekly': 'monday', # Cada lunes
}
Esta función se encargará de realizar la copia de seguridad:
# backup_functions.py
import os
import shutil
import zipfile
from datetime import datetime
import logging
def create_backup(source_paths, backup_destination):
"""
Crea una copia de seguridad de los archivos especificados.
Args:
source_paths: Lista de rutas a respaldar
backup_destination: Directorio donde se guardarán los backups
Returns:
str: Ruta del archivo de backup creado
"""
# Crear carpeta de destino si no existe
if not os.path.exists(backup_destination):
os.makedirs(backup_destination)
logging.info(f"Creado directorio de destino: {backup_destination}")
# Crear subcarpeta con la fecha actual
today = datetime.now().strftime('%Y-%m-%d')
backup_dir = os.path.join(backup_destination, today)
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
logging.info(f"Creado directorio para backup de hoy: {backup_dir}")
# Nombre del archivo zip (con hora para evitar sobreescrituras)
timestamp = datetime.now().strftime('%H-%M-%S')
zip_filename = f"backup_{today}_{timestamp}.zip"
zip_path = os.path.join(backup_dir, zip_filename)
# Crear archivo zip
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
# Procesar cada ruta de origen
for source_path in source_paths:
if os.path.exists(source_path):
logging.info(f"Respaldando: {source_path}")
# Si es un directorio, añadir todos sus contenidos
if os.path.isdir(source_path):
for root, dirs, files in os.walk(source_path):
for file in files:
file_path = os.path.join(root, file)
# Guardar con ruta relativa dentro del zip
arcname = os.path.relpath(file_path, os.path.dirname(source_path))
zipf.write(file_path, arcname)
logging.debug(f"Añadido al zip: {file_path}")
# Si es un archivo, añadirlo directamente
elif os.path.isfile(source_path):
arcname = os.path.basename(source_path)
zipf.write(source_path, arcname)
logging.debug(f"Añadido al zip: {source_path}")
else:
logging.warning(f"Ruta no encontrada, omitiendo: {source_path}")
logging.info(f"Backup completado: {zip_path}")
logging.info(f"Tamaño del archivo: {os.path.getsize(zip_path) / (1024*1024):.2f} MB")
return zip_path
Para evitar que el espacio de almacenamiento se llene con backups antiguos:
def cleanup_old_backups(backup_destination, days_to_keep=30):
"""
Elimina backups más antiguos que el número de días especificado.
Args:
backup_destination: Directorio donde se guardan los backups
days_to_keep: Número de días a mantener los backups
"""
if not os.path.exists(backup_destination):
logging.warning(f"Directorio de backups no encontrado: {backup_destination}")
return
logging.info(f"Iniciando limpieza de backups antiguos (manteniendo {days_to_keep} días)")
# Calcular la fecha límite
cutoff_date = datetime.now() - datetime.timedelta(days=days_to_keep)
# Revisar cada subdirectorio (que debería ser una fecha)
for item in os.listdir(backup_destination):
item_path = os.path.join(backup_destination, item)
# Solo procesar directorios
if os.path.isdir(item_path):
try:
# Intentar parsear el nombre del directorio como fecha
dir_date = datetime.strptime(item, '%Y-%m-%d')
# Si es más antiguo que la fecha límite, eliminar
if dir_date < cutoff_date:
shutil.rmtree(item_path)
logging.info(f"Eliminado backup antiguo: {item_path}")
except ValueError:
# Si el nombre del directorio no es una fecha, ignorarlo
logging.warning(f"Directorio con formato inesperado, ignorando: {item}")
logging.info("Limpieza de backups antiguos completada")
# backup_system.py
import schedule
import time
from backup_config import SOURCE_PATHS, BACKUP_DESTINATION, LOG_FILE, BACKUP_SCHEDULE
from backup_logger import setup_logger
from backup_functions import create_backup, cleanup_old_backups
def run_backup():
"""Ejecuta el proceso de backup completo."""
logger = setup_logger(LOG_FILE)
logger.info("Iniciando proceso de backup programado")
try:
# Crear backup
backup_file = create_backup(SOURCE_PATHS, BACKUP_DESTINATION)
# Limpiar backups antiguos (mantener últimos 30 días)
cleanup_old_backups(BACKUP_DESTINATION, days_to_keep=30)
logger.info("Proceso de backup completado exitosamente")
return backup_file
except Exception as e:
logger.error(f"Error durante el proceso de backup: {str(e)}")
return None
# Programar backups
if BACKUP_SCHEDULE.get('daily'):
schedule.every().day.at(BACKUP_SCHEDULE['daily']).do(run_backup)
print(f"Backup diario programado para las {BACKUP_SCHEDULE['daily']}")
if BACKUP_SCHEDULE.get('weekly'):
if BACKUP_SCHEDULE['weekly'].lower() == 'monday':
schedule.every().monday.at("00:00").do(run_backup)
# Añadir más días según sea necesario
print(f"Backup semanal programado para {BACKUP_SCHEDULE['weekly']}")
# Ejecutar un backup inmediato al iniciar
print("Ejecutando backup inicial...")
run_backup()
# Mantener el script en ejecución para que los backups programados funcionen
while True:
schedule.run_pending()
time.sleep(60) # Verificar cada minuto
En este proyecto, has creado un sistema completo de copias de seguridad automáticas que:
Respalda archivos y carpetas importantes
Organiza los backups por fecha
Comprime los archivos para ahorrar espacio
Mantiene un registro detallado de las operaciones
Se ejecuta automáticamente según una programación
Limpia backups antiguos para gestionar el espacio
Este sistema no solo te protege contra la pérdida de datos, sino que también te ha permitido aplicar conceptos importantes de Python como el manejo de archivos, la programación de tareas, el registro de eventos y la organización de código en módulos.
¿Te resulta familiar tener una carpeta de descargas llena de archivos de todo tipo? ¿O un escritorio tan abarrotado de documentos que es imposible encontrar lo que buscas? El desorden digital es un problema común que afecta nuestra productividad y eficiencia.
Mantener los archivos organizados manualmente es:
Tedioso y repetitivo
Propenso a errores
Consumidor de tiempo
Fácil de postergar
La solución es crear un sistema automatizado que organice los archivos según su tipo, moviendo cada uno a la carpeta correspondiente.
import os # Para operaciones con el sistema de archivos
import shutil # Para mover archivos
import datetime # Para registrar fechas en informes
import logging # Para registrar eventos
import json # Para manejar configuraciones
import argparse # Para procesar argumentos de línea de comandos
# file_organizer.py
import os
import shutil
import logging
import datetime
from file_categories import get_file_category
class FileOrganizer:
"""Clase para organizar archivos por tipo."""
def __init__(self, source_dir, organize_directories=False):
"""
Inicializa el organizador de archivos.
Args:
source_dir: Directorio a organizar
organize_directories: Si True, también organiza subdirectorios
"""
self.source_dir = os.path.abspath(source_dir)
self.organize_directories = organize_directories
self.stats = {
"total_files": 0,
"organized_files": 0,
"skipped_files": 0,
"categories": {}
}
# Configurar logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(self.source_dir, "organizer_log.txt")),
logging.StreamHandler()
]
)
self.logger = logging.getLogger()
def organize(self):
"""Organiza los archivos en el directorio fuente."""
self.logger.info(f"Iniciando organización de archivos en: {self.source_dir}")
# Verificar que el directorio existe
if not os.path.exists(self.source_dir):
self.logger.error(f"El directorio {self.source_dir} no existe.")
return False
# Obtener lista de archivos (no directorios)
items = os.listdir(self.source_dir)
self.stats["total_files"] = len(items)
# Procesar cada elemento
for item_name in items:
item_path = os.path.join(self.source_dir, item_name)
# Saltar el archivo de log
if item_name == "organizer_log.txt":
continue
# Verificar si es un directorio
if os.path.isdir(item_path):
if self.organize_directories:
# Si queremos organizar directorios, tratarlos como archivos
self._process_item(item_path)
else:
self.logger.info(f"Saltando directorio: {item_name}")
self.stats["skipped_files"] += 1
else:
# Procesar archivo
self._process_item(item_path)
# Generar informe
self._generate_report()
return True
def _process_item(self, item_path):
"""
Procesa un archivo o directorio y lo mueve a la categoría correspondiente.
Args:
item_path: Ruta completa al elemento a procesar
"""
item_name = os.path.basename(item_path)
# Determinar categoría
category = get_file_category(item_name)
# Crear directorio de destino si no existe
dest_dir = os.path.join(self.source_dir, category)
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
self.logger.info(f"Creado directorio: {category}")
# Ruta de destino
dest_path = os.path.join(dest_dir, item_name)
# Verificar si ya existe un archivo con el mismo nombre
if os.path.exists(dest_path):
# Añadir timestamp al nombre para evitar sobreescritura
name, ext = os.path.splitext(item_name)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
new_name = f"{name}_{timestamp}{ext}"
dest_path = os.path.join(dest_dir, new_name)
self.logger.warning(f"Archivo ya existe, renombrando a: {new_name}")
try:
# Mover el archivo
shutil.move(item_path, dest_path)
self.logger.info(f"Movido: {item_name} -> {category}/{os.path.basename(dest_path)}")
# Actualizar estadísticas
self.stats["organized_files"] += 1
if category not in self.stats["categories"]:
self.stats["categories"][category] = 0
self.stats["categories"][category] += 1
except Exception as e:
self.logger.error(f"Error al mover {item_name}: {str(e)}")
self.stats["skipped_files"] += 1
def _generate_report(self):
"""Genera un informe de la organización realizada."""
report = [
"=" * 50,
"INFORME DE ORGANIZACIÓN DE ARCHIVOS",
"=" * 50,
f"Fecha: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
f"Directorio: {self.source_dir}",
"-" * 50,
f"Total de elementos: {self.stats['total_files']}",
f"Archivos organizados: {self.stats['organized_files']}",
f"Elementos omitidos: {self.stats['skipped_files']}",
"-" * 50,
"Distribución por categorías:"
]
# Añadir estadísticas por categoría
for category, count in self.stats["categories"].items():
report.append(f"- {category}: {count} archivos")
report.extend([
"=" * 50,
"Organización completada con éxito.",
"=" * 50
])
# Guardar informe en archivo
report_path = os.path.join(self.source_dir, "organizacion_informe.txt")
with open(report_path, "w", encoding="utf-8") as f:
f.write("\n".join(report))
self.logger.info(f"Informe generado: {report_path}")
# También mostrar en consola
print("\n".join(report))
# organize_files.py
import argparse
import os
from file_organizer import FileOrganizer
def main():
"""Función principal del programa."""
# Configurar el parser de argumentos
parser = argparse.ArgumentParser(
description="Organizador automático de archivos por tipo"
)
parser.add_argument(
"directory",
nargs="?",
default=os.getcwd(),
help="Directorio a organizar (por defecto: directorio actual)"
)
parser.add_argument(
"-d", "--dirs",
action="store_true",
help="Organizar también subdirectorios"
)
# Parsear argumentos
args = parser.parse_args()
# Verificar que el directorio existe
if not os.path.exists(args.directory):
print(f"Error: El directorio '{args.directory}' no existe.")
return 1
# Crear y ejecutar el organizador
organizer = FileOrganizer(args.directory, args.dirs)
success = organizer.organize()
return 0 if success else 1
if __name__ == "__main__":
exit(main())
# Editar crontab
crontab -e
# Añadir una línea para ejecutar el script todos los viernes a las 8 PM
0 20 * * 5 python /ruta/a/organize_files.py /ruta/a/descargas
En este proyecto, has creado un sistema de organización de archivos que:
Clasifica automáticamente archivos según su tipo
Crea una estructura de carpetas ordenada
Maneja conflictos de nombres de archivo
Genera informes detallados de las acciones realizadas
Puede ejecutarse desde la línea de comandos con opciones configurables
Este organizador no solo te ayudará a mantener tu espacio digital ordenado, sino que también te ha permitido aplicar conceptos importantes de Python como el manejo de archivos y directorios, la creación de clases, el procesamiento de argumentos de línea de comandos y la generación de informes.
Internet es una fuente inagotable de información, pero muchas veces los datos que necesitamos no están disponibles en un formato fácil de procesar. Quizás quieras:
Monitorear precios de productos en tiendas online
Recopilar noticias sobre un tema específico
Extraer información de contacto de directorios
Obtener datos meteorológicos o financieros
Recopilar estadísticas deportivas
Copiar esta información manualmente sería extremadamente tedioso y propenso a errores. Aquí es donde entra el web scraping: la técnica de extraer automáticamente datos de sitios web.
import requests # Para realizar peticiones HTTP
from bs4 import BeautifulSoup # Para analizar HTML
import pandas as pd # Para manipular datos estructurados
import json # Para manejar formato JSON
import csv # Para manejar archivos CSV
import time # Para pausas entre peticiones
import logging # Para registrar eventos
import schedule # Para programar ejecuciones
# web_scraper.py
import requests
from bs4 import BeautifulSoup
import pandas as pd
import json
import csv
import time
import logging
import os
from datetime import datetime
from urllib.parse import urlparse
class WebScraper:
"""Clase base para web scraping."""
def __init__(self, base_url, output_dir="./data", delay=2):
"""
Inicializa el scraper.
Args:
base_url: URL base del sitio a scrapear
output_dir: Directorio donde se guardarán los datos
delay: Tiempo de espera entre peticiones (segundos)
"""
self.base_url = base_url
self.output_dir = output_dir
self.delay = delay
# Extraer el dominio para nombrar archivos
parsed_url = urlparse(base_url)
self.domain = parsed_url.netloc
# Crear directorio de salida si no existe
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Configurar logging
log_file = os.path.join(output_dir, f"{self.domain}_scraper.log")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
self.logger = logging.getLogger()
# Headers para simular un navegador
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3',
}
self.logger.info(f"Scraper inicializado para: {base_url}")
def fetch_page(self, url):
"""
Obtiene el contenido HTML de una página.
Args:
url: URL de la página a obtener
Returns:
BeautifulSoup: Objeto con el contenido parseado o None si hay error
"""
try:
# Verificar si la URL es relativa
if not url.startswith('http'):
url = f"{self.base_url.rstrip('/')}/{url.lstrip('/')}"
self.logger.info(f"Obteniendo página: {url}")
# Realizar la petición HTTP
response = requests.get(url, headers=self.headers)
# Verificar si la petición fue exitosa
if response.status_code == 200:
# Parsear el HTML
soup = BeautifulSoup(response.text, 'html.parser')
# Esperar para no sobrecargar el servidor
time.sleep(self.delay)
return soup
else:
self.logger.error(f"Error al obtener {url}: Código {response.status_code}")
return None
except Exception as e:
self.logger.error(f"Error al obtener {url}: {str(e)}")
return None
def save_to_csv(self, data, filename):
"""
Guarda datos en formato CSV.
Args:
data: Lista de diccionarios con los datos
filename: Nombre del archivo (sin extensión)
"""
if not data:
self.logger.warning("No hay datos para guardar en CSV")
return
filepath = os.path.join(self.output_dir, f"{filename}.csv")
try:
# Convertir a DataFrame y guardar
df = pd.DataFrame(data)
df.to_csv(filepath, index=False, encoding='utf-8')
self.logger.info(f"Datos guardados en CSV: {filepath}")
except Exception as e:
self.logger.error(f"Error al guardar CSV: {str(e)}")
def save_to_json(self, data, filename):
"""
Guarda datos en formato JSON.
Args:
data: Datos a guardar (lista o diccionario)
filename: Nombre del archivo (sin extensión)
"""
if not data:
self.logger.warning("No hay datos para guardar en JSON")
return
filepath = os.path.join(self.output_dir, f"{filename}.json")
try:
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
self.logger.info(f"Datos guardados en JSON: {filepath}")
except Exception as e:
self.logger.error(f"Error al guardar JSON: {str(e)}")
Vamos a crear un scraper para extraer información de libros de un sitio de ejemplo:
# book_scraper.py
from web_scraper import WebScraper
import re
class BookScraper(WebScraper):
"""Scraper especializado para extraer información de libros."""
def __init__(self, base_url="http://books.toscrape.com", output_dir="./data"):
"""Inicializa el scraper de libros."""
super().__init__(base_url, output_dir)
def scrape_books(self, pages=1):
"""
Extrae información de libros de varias páginas.
Args:
pages: Número de páginas a scrapear
Returns:
list: Lista de diccionarios con información de libros
"""
all_books = []
for page in range(1, pages + 1):
# URL de la página actual
if page == 1:
url = self.base_url
else:
url = f"{self.base_url}/catalogue/page-{page}.html"
# Obtener la página
soup = self.fetch_page(url)
if not soup:
continue
# Encontrar todos los libros en la página
book_containers = soup.select("article.product_pod")
self.logger.info(f"Encontrados {len(book_containers)} libros en página {page}")
# Procesar cada libro
for book in book_containers:
book_data = self._extract_book_info(book)
if book_data:
all_books.append(book_data)
# Guardar resultados
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.save_to_csv(all_books, f"books_{timestamp}")
self.save_to_json(all_books, f"books_{timestamp}")
return all_books
def _extract_book_info(self, book_container):
"""
Extrae información de un libro individual.
Args:
book_container: Elemento HTML que contiene la información del libro
Returns:
dict: Diccionario con la información del libro
"""
try:
# Extraer título
title_element = book_container.select_one("h3 a")
title = title_element["title"] if title_element else "Sin título"
# Extraer URL
book_url = title_element["href"] if title_element else ""
if book_url and not book_url.startswith("http"):
book_url = f"{self.base_url}/catalogue/{book_url.lstrip('../')}"
# Extraer precio
price_element = book_container.select_one("p.price_color")
price_text = price_element.text if price_element else "0"
# Limpiar el precio (quitar símbolo de moneda y convertir a float)
price = float(re.sub(r'[^\d.]', '', price_text))
# Extraer disponibilidad
stock_element = book_container.select_one("p.availability")
in_stock = "in stock" in stock_element.text.lower() if stock_element else False
# Extraer valoración (estrellas)
rating_element = book_container.select_one("p.star-rating")
rating = rating_element["class"][1] if rating_element and len(rating_element["class"]) > 1 else "No rating"
# Extraer imagen
img_element = book_container.select_one("img")
img_url = img_element["src"] if img_element else ""
if img_url and not img_url.startswith("http"):
img_url = f"{self.base_url}/{img_url}"
# Crear diccionario con la información
return {
"title": title,
"url": book_url,
"price": price,
"in_stock": in_stock,
"rating": rating,
"image_url": img_url,
"scraped_at": datetime.now().isoformat()
}
except Exception as e:
self.logger.error(f"Error al extraer información del libro: {str(e)}")
return None
def get_book_details(self, book_url):
"""
Obtiene detalles adicionales de un libro específico.
Args:
book_url: URL de la página del libro
Returns:
dict: Diccionario con detalles del libro
"""
soup = self.fetch_page(book_url)
if not soup:
return None
try:
# Extraer descripción
desc_element = soup.select_one("article.product_page p:not(.price_color):not(.availability)")
description = desc_element.text.strip() if desc_element else "Sin descripción"
# Extraer información de la tabla de producto
product_info = {}
info_table = soup.select_one("table.table-striped")
if info_table:
rows = info_table.select("tr")
for row in rows:
header = row.select_one("th")
value = row.select_one("td")
if header and value:
key = header.text.strip()
product_info[key] = value.text.strip()
# Extraer categoría
breadcrumb = soup.select("ul.breadcrumb li")
category = breadcrumb[2].text.strip() if len(breadcrumb) > 2 else "Sin categoría"
return {
"description": description,
"category": category,
"product_info": product_info
}
except Exception as e:
self.logger.error(f"Error al obtener detalles del libro: {str(e)}")
return None
En este proyecto, has creado un sistema de web scraping que:
Extrae información estructurada de páginas web
Navega a través de múltiples páginas
Procesa y limpia los datos obtenidos
Guarda la información en formatos útiles (CSV, JSON)
Puede programarse para ejecutarse periódicamente
Este scraper no solo te permite obtener datos de forma automática, sino que también te ha permitido aplicar conceptos importantes de Python como el manejo de peticiones HTTP, el análisis de HTML, la manipulación de datos estructurados y la programación de tareas.
Respetar los términos de servicio de los sitios web
Consultar el archivo robots.txt antes de scrapear
Implementar límites de velocidad razonables
No extraer información personal o protegida
Usar los datos de forma ética y legal
El web scraping es una herramienta poderosa que debe usarse con responsabilidad.
¡Felicidades! Has completado los tres proyectos de automatización. Ahora tienes herramientas prácticas para:
Crear copias de seguridad automáticas
Organizar archivos por tipo
Extraer información de sitios web
Estos proyectos no solo te han ayudado a aplicar tus conocimientos de Python, sino que también te han proporcionado herramientas útiles que puedes personalizar según tus necesidades específicas.
En el próximo capítulo, aprenderemos sobre Despliegue Básico para que puedas ejecutar tus scripts en la nube y programar tareas automáticas.
InicioConfiguración- Directorio origen- Directorio destino- Frecuencia¿Directoriosexisten?Crear directoriosfaltantesGenerar nombre de backupcon fecha y horaComprimir archivosdel directorio origenGuardar archivo ZIPen directorio destinoRegistrar operaciónen archivo de log¿Hay backupsantiguos?Eliminar backupsmás antiguosProgramar siguienteejecuciónFinComponentes del Sistema
InicioConfiguración- URL objetivo- Elementos a extraer- Formato de salida¿Bibliotecasinstaladas?Instalar bibliotecasrequests, BeautifulSoupRealizar petición HTTPa la URL objetivo¿Respuestaexitosa?Parsear HTML conBeautifulSoupExtraer datos conselectores CSSProcesar y limpiarlos datos extraídosExportar datos aCSV, JSON o DB¿Ejecuciónperiódica?Configurar ejecuciónprogramadaFin
Bibliotecas:
requests: Realizar peticiones HTTP
BeautifulSoup4: Parsear HTML
pandas: Procesar datos
csv, json: Exportar datos
time: Controlar velocidad de scraping
logging: Registrar eventos
Buenas Prácticas:
Respetar robots.txt
Añadir delays entre peticiones
Usar User-Agent apropiado
Implementar rate limiting
Manejar errores y excepciones
Implementar caché para reducir peticiones
Sistema Integrado de AutomatizaciónWeb ScrapingExtractor de DatosProcesador de DatosOrganizador de ArchivosClasificadorGestor de ArchivosSistema de BackupCompresorProgramadorAlmacenamientoLogsFlujo de Trabajo1. Extracción de datos web2. Procesamiento y exportación3. Organización por tipo4. Backup automáticoImplementa
Estos diagramas te ayudarán a visualizar el flujo de trabajo y la estructura de los proyectos de automatización presentados en este capítulo, facilitando su comprensión e implementación.
¡Llegó el momento más emocionante! 🎉 Es hora de construir tu obra maestra de Python combinando todas las herramientas que has dominado. Imagina que eres el encargado de un almacén moderno donde cada concepto aprendido se convierte en una pieza fundamental de un sistema completo y funcional.
Ahora creemos la interfaz principal que permite al usuario interactuar con todas las funciones:
def mostrar_menu():
"""Muestra el menú principal del sistema"""
print("\n" + "="*50)
print("🏪 SISTEMA DE GESTIÓN DE ALMACÉN")
print("="*50)
print("1. 📦 Ver inventario")
print("2. 🔍 Buscar producto")
print("3. 💰 Registrar venta")
print("4. 📊 Reporte de ventas")
print("5. 📁 Exportar ventas a CSV")
print("6. 💾 Respaldar inventario")
print("7. 📂 Cargar respaldo")
print("8. ➕ Agregar producto")
print("9. 🚪 Salir")
print("-" * 50)
def agregar_producto():
"""Permite agregar un nuevo producto al inventario"""
try:
print("\n➕ AGREGAR NUEVO PRODUCTO")
print("-" * 30)
codigo = input("Código del producto: ").upper()
# Validar que el código no exista
if codigo in inventario:
print("❌ Error: El código ya existe")
return
nombre = input("Nombre del producto: ")
precio = float(input("Precio: $"))
stock = int(input("Stock inicial: "))
categoria = input("Categoría: ")
proveedor = input("Proveedor: ")
# Validaciones básicas
if precio <= 0:
print("❌ Error: El precio debe ser mayor a 0")
return
if stock < 0:
print("❌ Error: El stock no puede ser negativo")
return
# Agregar al inventario
inventario[codigo] = {
"nombre": nombre,
"precio": precio,
"stock": stock,
"categoria": categoria,
"proveedor": proveedor
}
print(f"✅ Producto {nombre} agregado exitosamente")
except ValueError:
print("❌ Error: Ingresa valores numéricos válidos")
except Exception as e:
print(f"❌ Error inesperado: {e}")
def ejecutar_sistema():
"""Función principal que ejecuta el sistema completo"""
print("🎉 ¡Bienvenido al Sistema de Gestión de Almacén!")
print("Este sistema integra todos los conceptos de Python que has aprendido.")
while True:
mostrar_menu()
try:
opcion = input("\nSelecciona una opción (1-9): ")
if opcion == "1":
mostrar_inventario()
elif opcion == "2":
codigo = input("Ingresa el código del producto: ").upper()
producto = buscar_producto(codigo)
if producto:
print(f"\n🎯 Producto encontrado:")
print(f"Nombre: {producto['nombre']}")
print(f"Precio: ${producto['precio']:,.2f}")
print(f"Stock: {producto['stock']}")
print(f"Categoría: {producto['categoria']}")
else:
print("❌ Producto no encontrado")
elif opcion == "3":
codigo = input("Código del producto: ").upper()
cantidad = int(input("Cantidad: "))
cliente = input("Nombre del cliente (opcional): ") or "Cliente general"
registrar_venta(codigo, cantidad, cliente)
elif opcion == "4":
generar_reporte_ventas()
elif opcion == "5":
exportar_ventas_csv()
elif opcion == "6":
respaldar_inventario()
elif opcion == "7":
cargar_respaldo()
elif opcion == "8":
agregar_producto()
elif opcion == "9":
print("\n👋 ¡Gracias por usar el Sistema de Gestión de Almacén!")
print("¡Has completado exitosamente tu proyecto integrador!")
break
else:
print("❌ Opción inválida. Selecciona del 1 al 9.")
except ValueError:
print("❌ Error: Ingresa un número válido")
except KeyboardInterrupt:
print("\n\n👋 Sistema cerrado por el usuario")
break
except Exception as e:
print(f"❌ Error inesperado: {e}")
print("El sistema continuará funcionando...")
# Pausa para que el usuario pueda leer los resultados
input("\nPresiona Enter para continuar...")
# ¡Ejecutar el sistema!
if __name__ == "__main__":
ejecutar_sistema()
La planificación es el ADN del éxito en cualquier proyecto de software. Como un arquitecto que diseña un rascacielos, nosotros diseñaremos un sistema empresarial robusto, escalable y mantenible. Esta fase es donde transformamos ideas en planos ejecutables.
# Operaciones CRUD empresariales
OPERACIONES_PRODUCTO = {
"crear": "Agregar nuevos productos al catálogo",
"leer": "Consultar información de productos existentes",
"actualizar": "Modificar datos de productos (precio, stock, etc.)",
"eliminar": "Remover productos del catálogo"
}
# Validaciones de negocio
REGLAS_PRODUCTO = {
"codigo_unico": "Cada producto debe tener código único",
"precio_positivo": "Precio debe ser mayor a cero",
"stock_no_negativo": "Stock no puede ser negativo",
"categoria_valida": "Debe pertenecer a categoría existente"
}
# Funcionalidades de inventario
CONTROL_INVENTARIO = {
"seguimiento_stock": "Monitoreo en tiempo real de existencias",
"alertas_automaticas": "Notificaciones de stock bajo",
"reposicion_sugerida": "Cálculo automático de cantidades a reponer",
"historial_movimientos": "Registro de entradas y salidas"
}
# Parámetros configurables
PARAMETROS_INVENTARIO = {
"stock_minimo_default": 5,
"stock_critico": 2,
"margen_reposicion": 1.5, # Factor multiplicador para sugerencias
"dias_proyeccion": 30 # Días para cálculo de demanda
}
# Tipos de reportes
REPORTES_DISPONIBLES = {
"inventario_actual": "Estado completo del inventario",
"productos_stock_bajo": "Alertas de reposición",
"ventas_periodo": "Análisis de ventas por período",
"productos_mas_vendidos": "Top de productos por demanda",
"analisis_rentabilidad": "Productos más rentables",
"proyeccion_demanda": "Predicción de necesidades futuras"
}
# Formatos de exportación
FORMATOS_REPORTE = ["consola", "json", "csv", "html"]
CRITERIOS_USABILIDAD = {
"interfaz_intuitiva": "Menús claros y navegación simple",
"mensajes_informativos": "Feedback claro en cada operación",
"manejo_errores": "Mensajes de error comprensibles",
"ayuda_contextual": "Instrucciones disponibles en cada pantalla"
}
# Organización por responsabilidades
ESTRUCTURA_PROYECTO = {
"nucleo/": {
"main.py": "Punto de entrada del sistema",
"config.py": "Configuración global",
"app.py": "Aplicación principal"
},
"modelos/": {
"producto.py": "Entidad Producto y lógica asociada",
"venta.py": "Entidad Venta y procesamiento",
"usuario.py": "Gestión de usuarios y permisos",
"base.py": "Clase base para todas las entidades"
},
"gestores/": {
"inventario.py": "Gestor principal de inventario",
"ventas.py": "Procesador de transacciones",
"reportes.py": "Generador de análisis",
"archivos.py": "Persistencia de datos"
},
"interfaces/": {
"menu.py": "Sistema de menús interactivos",
"formularios.py": "Captura de datos del usuario",
"reportes_vista.py": "Presentación de reportes"
},
"utilidades/": {
"validadores.py": "Validaciones de negocio",
"formateadores.py": "Formateo de datos",
"helpers.py": "Funciones auxiliares"
},
"datos/": {
"productos.json": "Base de datos de productos",
"ventas.json": "Historial de transacciones",
"configuracion.json": "Parámetros del sistema"
},
"respaldos/": {
"automaticos/": "Respaldos programados",
"manuales/": "Respaldos bajo demanda"
}
}
class FabricaReportes:
"""Factory para crear diferentes tipos de reportes"""
@staticmethod
def crear_reporte(tipo: str, datos: Dict) -> 'ReporteBase':
"""Crea reporte según el tipo especificado"""
if tipo == "inventario":
return ReporteInventario(datos)
elif tipo == "ventas":
return ReporteVentas(datos)
elif tipo == "estadisticas":
return ReporteEstadisticas(datos)
else:
raise ValueError(f"Tipo de reporte no soportado: {tipo}")
class SistemaAlertas:
"""Observer para alertas del sistema"""
def __init__(self):
self.observadores = []
def agregar_observador(self, observador):
"""Registra un observador de alertas"""
self.observadores.append(observador)
def notificar_stock_bajo(self, producto: Producto):
"""Notifica a todos los observadores sobre stock bajo"""
for observador in self.observadores:
observador.alerta_stock_bajo(producto)
# Extensible sin modificar código existente
class ReporteBase:
"""Clase base para reportes"""
def generar(self):
raise NotImplementedError
class ReporteInventario(ReporteBase):
"""Reporte específico de inventario"""
def generar(self):
# Implementación específica
pass
# Agregar nuevos reportes sin modificar código existente
class ReporteVentas(ReporteBase):
def generar(self):
# Nueva funcionalidad
pass
# Jerarquía de excepciones del negocio
class ErrorSistemaInventario(Exception):
"""Excepción base del sistema"""
pass
class ErrorProductoNoEncontrado(ErrorSistemaInventario):
"""Producto no existe en el inventario"""
pass
class ErrorStockInsuficiente(ErrorSistemaInventario):
"""No hay suficiente stock para la operación"""
pass
class ErrorValidacionNegocio(ErrorSistemaInventario):
"""Violación de reglas de negocio"""
pass
Con esta planificación sólida, estamos listos para la implementación paso a paso. En la siguiente sección comenzaremos a construir nuestro sistema empresarial, transformando estos planos en código funcional.
¡Es hora de construir nuestra obra maestra! 🏗️ En esta sección transformaremos todos los planos y diseños en código funcional. Será como dirigir la construcción de un rascacielos: cada línea de código es un ladrillo que colocamos con precisión para crear algo extraordinario.
Comenzamos construyendo los cimientos de nuestro sistema empresarial. Como en cualquier construcción, los cimientos determinan la solidez de toda la estructura.
# archivo: utilidades.py
"""
Caja de herramientas empresarial - Utilidades fundamentales
Todas las herramientas auxiliares que necesita nuestro sistema
"""
import json
import os
import csv
import hashlib
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple, Union
from pathlib import Path
import re
from config import config
from logger import logger
class ValidadorEmpresarial:
"""Validador de reglas de negocio empresarial"""
@staticmethod
def codigo_producto(codigo: str) -> Tuple[bool, str]:
"""Valida código de producto según estándares empresariales"""
if not codigo:
return False, "Código no puede estar vacío"
codigo = codigo.strip().upper()
if len(codigo) < 3:
return False, "Código debe tener mínimo 3 caracteres"
if len(codigo) > 10:
return False, "Código debe tener máximo 10 caracteres"
if not re.match(r'^[A-Z0-9]+$', codigo):
return False, "Código solo puede contener letras y números (sin espacios ni símbolos)"
# Verificar que no sea solo números
if codigo.isdigit():
return False, "Código debe contener al menos una letra"
return True, "Código válido"
@staticmethod
def precio_producto(precio: Union[int, float]) -> Tuple[bool, str]:
"""Valida precio según políticas empresariales"""
try:
precio = float(precio)
except (ValueError, TypeError):
return False, "Precio debe ser numérico"
if precio <= 0:
return False, "Precio debe ser positivo"
if precio < config.PRECIO_MINIMO:
return False, f"Precio mínimo permitido: ${config.PRECIO_MINIMO}"
if precio > config.PRECIO_MAXIMO:
return False, f"Precio máximo permitido: ${config.PRECIO_MAXIMO:,.2f}"
# Verificar precisión decimal
if round(precio, config.PRECISION_DECIMAL) != precio:
return False, f"Precio debe tener máximo {config.PRECISION_DECIMAL} decimales"
return True, "Precio válido"
@staticmethod
def stock_producto(stock: Union[int, str]) -> Tuple[bool, str]:
"""Valida stock según reglas empresariales"""
try:
stock = int(stock)
except (ValueError, TypeError):
return False, "Stock debe ser un número entero"
if stock < 0:
return False, "Stock no puede ser negativo"
if stock > 999999:
return False, "Stock máximo permitido: 999,999 unidades"
return True, "Stock válido"
@staticmethod
def nombre_producto(nombre: str) -> Tuple[bool, str]:
"""Valida nombre de producto"""
if not nombre or not nombre.strip():
return False, "Nombre no puede estar vacío"
nombre = nombre.strip()
if len(nombre) < 2:
return False, "Nombre debe tener mínimo 2 caracteres"
if len(nombre) > 100:
return False, "Nombre debe tener máximo 100 caracteres"
# Verificar caracteres válidos
if not re.match(r'^[a-zA-Z0-9\s\-_.,()]+$', nombre):
return False, "Nombre contiene caracteres no permitidos"
return True, "Nombre válido"
@staticmethod
def categoria_producto(categoria: str) -> Tuple[bool, str]:
"""Valida categoría de producto"""
if not categoria or not categoria.strip():
return True, "Categoría válida (se usará 'General')"
categoria = categoria.strip()
if len(categoria) > 50:
return False, "Categoría debe tener máximo 50 caracteres"
if not re.match(r'^[a-zA-Z0-9\s\-_]+$', categoria):
return False, "Categoría contiene caracteres no permitidos"
return True, "Categoría válida"
@staticmethod
def cantidad_venta(cantidad: Union[int, str]) -> Tuple[bool, str]:
"""Valida cantidad para venta"""
try:
cantidad = int(cantidad)
except (ValueError, TypeError):
return False, "Cantidad debe ser un número entero"
if cantidad <= 0:
return False, "Cantidad debe ser mayor a cero"
if cantidad > 1000:
return False, "Cantidad máxima por venta: 1,000 unidades"
return True, "Cantidad válida"
@staticmethod
def descuento(descuento: Union[int, float]) -> Tuple[bool, str]:
"""Valida descuento aplicado"""
try:
descuento = float(descuento)
except (ValueError, TypeError):
return False, "Descuento debe ser numérico"
if descuento < 0:
return False, "Descuento no puede ser negativo"
if descuento > config.DESCUENTO_MAXIMO:
return False, f"Descuento máximo permitido: {config.DESCUENTO_MAXIMO * 100}%"
return True, "Descuento válido"
class FormateadorEmpresarial:
"""Formateador de datos para presentación empresarial"""
@staticmethod
def moneda(cantidad: float, incluir_simbolo: bool = True) -> str:
"""Formatea cantidad como moneda"""
if incluir_simbolo:
return f"{config.MONEDA_SIMBOLO}{cantidad:,.{config.PRECISION_DECIMAL}f}"
else:
return f"{cantidad:,.{config.PRECISION_DECIMAL}f}"
@staticmethod
def fecha_empresarial(fecha: datetime) -> str:
"""Formatea fecha para reportes empresariales"""
return fecha.strftime(config.FORMATO_FECHA_REPORTE)
@staticmethod
def fecha_corta(fecha: datetime) -> str:
"""Formatea fecha en formato corto"""
return fecha.strftime("%d/%m/%Y")
@staticmethod
def porcentaje(decimal: float, decimales: int = 1) -> str:
"""Convierte decimal a porcentaje"""
return f"{decimal * 100:.{decimales}f}%"
@staticmethod
def numero_entero(numero: int) -> str:
"""Formatea número entero con separadores de miles"""
return f"{numero:,}"
@staticmethod
def codigo_producto(codigo: str) -> str:
"""Formatea código de producto"""
return codigo.upper().strip()
@staticmethod
def nombre_propio(texto: str) -> str:
"""Convierte texto a formato de nombre propio"""
return texto.strip().title()
@staticmethod
def texto_tabla(texto: str, ancho: int, alineacion: str = "izquierda") -> str:
"""Formatea texto para tablas con ancho fijo"""
texto = str(texto)[:ancho] # Truncar si es muy largo
if alineacion == "derecha":
return texto.rjust(ancho)
elif alineacion == "centro":
return texto.center(ancho)
else: # izquierda
return texto.ljust(ancho)
class GestorArchivos:
"""Gestor de persistencia empresarial con respaldos automáticos"""
@staticmethod
def cargar_json(archivo: Path, crear_si_no_existe: bool = True) -> Dict[str, Any]:
"""Carga datos desde archivo JSON con manejo robusto de errores"""
try:
if archivo.exists():
with open(archivo, 'r', encoding='utf-8') as f:
datos = json.load(f)
logger.debug(f"Datos cargados desde {archivo}")
return datos
else:
if crear_si_no_existe:
logger.info(f"Archivo {archivo} no existe, creando archivo vacío")
GestorArchivos.guardar_json({}, archivo)
return {}
else:
logger.warning(f"Archivo {archivo} no existe")
return {}
except json.JSONDecodeError as e:
logger.error(f"Error JSON en {archivo}: {e}")
# Intentar recuperar desde respaldo
return GestorArchivos._recuperar_desde_respaldo(archivo)
except Exception as e:
logger.error(f"Error cargando {archivo}: {e}")
return {}
@staticmethod
def guardar_json(datos: Dict[str, Any], archivo: Path, crear_respaldo: bool = True) -> bool:
"""Guarda datos en archivo JSON con respaldo automático"""
try:
# Crear respaldo si el archivo existe y está configurado
if crear_respaldo and archivo.exists() and config.RESPALDO_AUTOMATICO:
GestorArchivos._crear_respaldo(archivo)
# Crear directorio si no existe
archivo.parent.mkdir(parents=True, exist_ok=True)
# Guardar datos con formato bonito
with open(archivo, 'w', encoding='utf-8') as f:
json.dump(
datos,
f,
indent=2,
ensure_ascii=False,
default=str,
sort_keys=True
)
logger.debug(f"Datos guardados en {archivo}")
return True
except Exception as e:
logger.error(f"Error guardando {archivo}: {e}")
return False
@staticmethod
def _crear_respaldo(archivo: Path):
"""Crea respaldo de un archivo"""
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
nombre_respaldo = f"{archivo.stem}_backup_{timestamp}{archivo.suffix}"
archivo_respaldo = config.RESPALDOS_DIR / nombre_respaldo
# Copiar archivo
import shutil
shutil.copy2(archivo, archivo_respaldo)
logger.info(f"Respaldo creado: {archivo_respaldo}")
# Limpiar respaldos antiguos
GestorArchivos._limpiar_respaldos_antiguos(archivo.stem)
except Exception as e:
logger.error(f"Error creando respaldo de {archivo}: {e}")
@staticmethod
def _limpiar_respaldos_antiguos(prefijo: str):
"""Limpia respaldos antiguos manteniendo solo los más recientes"""
try:
patron = f"{prefijo}_backup_*"
respaldos = list(config.RESPALDOS_DIR.glob(patron))
if len(respaldos) > config.RESPALDOS_MAXIMOS:
# Ordenar por fecha de modificación (más reciente primero)
respaldos.sort(key=lambda x: x.stat().st_mtime, reverse=True)
# Eliminar los más antiguos
for respaldo in respaldos[config.RESPALDOS_MAXIMOS:]:
respaldo.unlink()
logger.debug(f"Respaldo antiguo eliminado: {respaldo}")
except Exception as e:
logger.error(f"Error limpiando respaldos antiguos: {e}")
@staticmethod
def _recuperar_desde_respaldo(archivo: Path) -> Dict[str, Any]:
"""Intenta recuperar datos desde el respaldo más reciente"""
try:
patron = f"{archivo.stem}_backup_*{archivo.suffix}"
respaldos = list(config.RESPALDOS_DIR.glob(patron))
if respaldos:
# Obtener el respaldo más reciente
respaldo_reciente = max(respaldos, key=lambda x: x.stat().st_mtime)
with open(respaldo_reciente, 'r', encoding='utf-8') as f:
datos = json.load(f)
logger.warning(f"Datos recuperados desde respaldo: {respaldo_reciente}")
return datos
else:
logger.error(f"No hay respaldos disponibles para {archivo}")
return {}
except Exception as e:
logger.error(f"Error recuperando desde respaldo: {e}")
return {}
@staticmethod
def exportar_csv(datos: List[Dict], archivo: Path, encabezados: List[str] = None) -> bool:
"""Exporta datos a archivo CSV"""
try:
if not datos:
logger.warning("No hay datos para exportar")
return False
# Crear directorio si no existe
archivo.parent.mkdir(parents=True, exist_ok=True)
# Determinar encabezados si no se proporcionan
if not encabezados:
encabezados = list(datos[0].keys())
with open(archivo, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=encabezados)
writer.writeheader()
writer.writerows(datos)
logger.info(f"Datos exportados a CSV: {archivo}")
return True
except Exception as e:
logger.error(f"Error exportando CSV {archivo}: {e}")
return False
class InterfazUsuario:
"""Utilidades para interfaz de usuario empresarial"""
@staticmethod
def limpiar_pantalla():
"""Limpia la pantalla de la consola"""
if config.LIMPIAR_PANTALLA:
os.system('cls' if os.name == 'nt' else 'clear')
@staticmethod
def pausar(mensaje: str = "Presiona Enter para continuar..."):
"""Pausa la ejecución hasta que el usuario presione Enter"""
try:
input(f"\n{mensaje}")
except KeyboardInterrupt:
print("\n👋 Operación cancelada por el usuario")
@staticmethod
def mostrar_titulo(titulo: str, caracter: str = "=", ancho: int = 60):
"""Muestra un título formateado empresarial"""
print(f"\n{caracter * ancho}")
print(f"{titulo.center(ancho)}")
print(f"{caracter * ancho}")
@staticmethod
def mostrar_subtitulo(subtitulo: str, caracter: str = "-", ancho: int = 50):
"""Muestra un subtítulo formateado"""
print(f"\n{caracter * ancho}")
print(f"{subtitulo.center(ancho)}")
print(f"{caracter * ancho}")
@staticmethod
def mostrar_mensaje_exito(mensaje: str):
"""Muestra mensaje de éxito"""
print(f"✅ {mensaje}")
@staticmethod
def mostrar_mensaje_error(mensaje: str):
"""Muestra mensaje de error"""
print(f"❌ {mensaje}")
@staticmethod
def mostrar_mensaje_advertencia(mensaje: str):
"""Muestra mensaje de advertencia"""
print(f"⚠️ {mensaje}")
@staticmethod
def mostrar_mensaje_info(mensaje: str):
"""Muestra mensaje informativo"""
print(f"ℹ️ {mensaje}")
@staticmethod
def confirmar_accion(mensaje: str, default: bool = False) -> bool:
"""Pide confirmación al usuario"""
opciones = "(s/N)" if not default else "(S/n)"
respuesta = input(f"{mensaje} {opciones}: ").strip().lower()
if not respuesta:
return default
return respuesta in ['s', 'si', 'sí', 'y', 'yes', '1']
@staticmethod
def obtener_entrada_usuario(
mensaje: str,
tipo: type = str,
validador=None,
obligatorio: bool = True,
valor_default=None
):
"""Obtiene entrada del usuario con validación robusta"""
while True:
try:
# Mostrar mensaje con valor default si existe
prompt = mensaje
if valor_default is not None:
prompt += f" [{valor_default}]"
prompt += ": "
entrada = input(prompt).strip()
# Usar valor default si no se ingresa nada
if not entrada and valor_default is not None:
entrada = str(valor_default)
# Verificar si es obligatorio
if not entrada and obligatorio:
InterfazUsuario.mostrar_mensaje_error("Este campo es obligatorio")
continue
# Si no es obligatorio y está vacío, retornar None
if not entrada and not obligatorio:
return None
# Convertir al tipo deseado
if tipo == int:
valor = int(entrada)
elif tipo == float:
valor = float(entrada)
elif tipo == bool:
valor = entrada.lower() in ['true', '1', 's', 'si', 'sí', 'yes']
else:
valor = entrada
# Aplicar validador si existe
if validador:
if callable(validador):
# Validador es una función
if hasattr(validador, '__call__'):
resultado = validador(valor)
if isinstance(resultado, tuple):
es_valido, mensaje_error = resultado
if not es_valido:
InterfazUsuario.mostrar_mensaje_error(mensaje_error)
continue
elif not resultado:
InterfazUsuario.mostrar_mensaje_error("Valor inválido")
continue
else:
# Validador es un valor/lista de valores válidos
if valor not in validador:
InterfazUsuario.mostrar_mensaje_error(f"Valor debe ser uno de: {validador}")
continue
return valor
except ValueError as e:
InterfazUsuario.mostrar_mensaje_error(f"Debe ingresar un {tipo.__name__} válido")
except KeyboardInterrupt:
print("\n👋 Operación cancelada por el usuario")
return None
except Exception as e:
InterfazUsuario.mostrar_mensaje_error(f"Error inesperado: {e}")
@staticmethod
def mostrar_menu(opciones: List[str], titulo: str = "MENÚ", mostrar_salir: bool = True) -> int:
"""Muestra un menú empresarial y devuelve la opción seleccionada"""
InterfazUsuario.mostrar_titulo(titulo)
# Mostrar opciones
for i, opcion in enumerate(opciones, 1):
print(f" {i}. {opcion}")
if mostrar_salir:
print(f" 0. Salir")
print() # Línea en blanco
# Obtener selección
while True:
try:
max_opcion = len(opciones)
min_opcion = 0 if mostrar_salir else 1
seleccion = int(input(f"Selecciona una opción ({min_opcion}-{max_opcion}): "))
if min_opcion <= seleccion <= max_opcion:
return seleccion
else:
InterfazUsuario.mostrar_mensaje_error(
f"Opción debe estar entre {min_opcion} y {max_opcion}"
)
except ValueError:
InterfazUsuario.mostrar_mensaje_error("Debe ingresar un número válido")
except KeyboardInterrupt:
print("\n👋 Saliendo del menú...")
return 0
@staticmethod
def mostrar_tabla(
datos: List[Dict],
encabezados: List[str] = None,
titulo: str = None,
max_filas: int = None
):
"""Muestra datos en formato de tabla empresarial"""
if not datos:
InterfazUsuario.mostrar_mensaje_info("No hay datos para mostrar")
return
# Determinar encabezados si no se proporcionan
if not encabezados:
encabezados = list(datos[0].keys())
# Mostrar título si se proporciona
if titulo:
InterfazUsuario.mostrar_subtitulo(titulo)
# Calcular anchos de columna
anchos = {}
for encabezado in encabezados:
ancho_encabezado = len(str(encabezado))
ancho_datos = max(len(str(fila.get(encabezado, ""))) for fila in datos)
anchos[encabezado] = max(ancho_encabezado, ancho_datos, 10) # Mínimo 10
# Mostrar encabezados
linea_separadora = "+" + "+".join("-" * (anchos[enc] + 2) for enc in encabezados) + "+"
print(linea_separadora)
encabezados_formateados = "|".join(
f" {enc.center(anchos[enc])} " for enc in encabezados
)
print(f"|{encabezados_formateados}|")
print(linea_separadora)
# Mostrar datos (limitados si se especifica max_filas)
datos_mostrar = datos[:max_filas] if max_filas else datos
for fila in datos_mostrar:
fila_formateada = "|".join(
f" {str(fila.get(enc, '')).ljust(anchos[enc])} " for enc in encabezados
)
print(f"|{fila_formateada}|")
print(linea_separadora)
# Mostrar información adicional si hay más filas
if max_filas and len(datos) > max_filas:
print(f"... y {len(datos) - max_filas} filas más")
print(f"Total de registros: {len(datos)}")
# Instancias globales para fácil acceso
validador = ValidadorEmpresarial()
formateador = FormateadorEmpresarial()
gestor_archivos = GestorArchivos()
interfaz = InterfazUsuario()
Con los cimientos sólidos, ahora construimos las entidades principales de nuestro sistema empresarial.
# archivo: reportes.py
"""
Módulo de generación de reportes para el sistema de inventario.
"""
from datetime import datetime
from typing import List, Dict
from inventario import GestorInventario, Producto
from utilidades import *
class GeneradorReportes:
"""Clase para generar diferentes tipos de reportes"""
def __init__(self, gestor: GestorInventario):
self.gestor = gestor
def reporte_inventario_completo(self):
"""Genera reporte completo del inventario"""
mostrar_titulo("REPORTE COMPLETO DE INVENTARIO", "=")
productos = self.gestor.listar_productos()
if not productos:
print("📦 No hay productos en el inventario")
return
# Estadísticas generales
stats = self.gestor.obtener_estadisticas()
print(f"📊 ESTADÍSTICAS GENERALES:")
print(f" Total de productos: {stats['total_productos']}")
print(f" Total de unidades: {stats['total_stock']}")
print(f" Valor total: {formatear_precio(stats['valor_total'])}")
print(f" Productos con stock bajo: {stats['productos_stock_bajo']}")
print(f" Categorías: {stats['categorias']}")
# Lista de productos
print(f"\n📦 PRODUCTOS:")
for producto in productos:
print(f" {producto}")
def reporte_stock_bajo(self):
"""Genera reporte de productos con stock bajo"""
mostrar_titulo("REPORTE DE STOCK BAJO", "⚠")
productos_bajo = self.gestor.obtener_productos_stock_bajo()
if not productos_bajo:
print("✅ Todos los productos tienen stock suficiente")
return
print(f"⚠️ {len(productos_bajo)} productos con stock bajo:")
for producto in productos_bajo:
print(f" {producto.codigo}: {producto.nombre}")
print(f" Stock actual: {producto.stock}")
print(f" Stock mínimo: {producto.stock_minimo}")
print(f" Sugerido reponer: {producto.stock_minimo * 2}")
print()
def reporte_por_categoria(self):
"""Genera reporte agrupado por categorías"""
mostrar_titulo("REPORTE POR CATEGORÍAS", "📂")
categorias = self.gestor.obtener_categorias()
for categoria in categorias:
productos = self.gestor.listar_productos(categoria)
valor_categoria = sum(p.precio * p.stock for p in productos)
print(f"\n📂 {categoria.upper()}:")
print(f" Productos: {len(productos)}")
print(f" Valor total: {formatear_precio(valor_categoria)}")
for producto in productos:
print(f" {producto}")
¡Felicidades! 🎉 Has construido un sistema empresarial completo y funcional. Pero como todo gran arquitecto sabe, la construcción nunca termina realmente. Siempre hay nuevos pisos que agregar, tecnologías que integrar y funcionalidades que perfeccionar.
Esta sección es tu hoja de ruta hacia la excelencia, donde transformarás tu buen sistema en un sistema extraordinario. Cada mejora que implementes te acercará más al nivel de un desarrollador profesional.
Evoluciona de archivos JSON a una base de datos real:
# archivo: database.py
"""
Sistema de base de datos profesional con SQLite
Reemplaza el almacenamiento JSON con una base de datos real
"""
import sqlite3
import json
from datetime import datetime
from typing import List, Dict, Optional, Tuple
from pathlib import Path
from config import config
from logger import logger
class GestorBaseDatos:
"""Gestor de base de datos SQLite para el sistema empresarial"""
def __init__(self, db_path: Path = None):
self.db_path = db_path or config.DATOS_DIR / "inventario.db"
self.init_database()
def init_database(self):
"""Inicializa la base de datos con todas las tablas necesarias"""
try:
conn = self.get_connection()
cursor = conn.cursor()
# Tabla de productos
cursor.execute('''
CREATE TABLE IF NOT EXISTS productos (
codigo TEXT PRIMARY KEY,
nombre TEXT NOT NULL,
precio REAL NOT NULL CHECK(precio > 0),
stock INTEGER NOT NULL CHECK(stock >= 0),
categoria TEXT DEFAULT 'General',
stock_minimo INTEGER DEFAULT 5 CHECK(stock_minimo >= 0),
proveedor TEXT,
ubicacion TEXT,
descripcion TEXT,
activo BOOLEAN DEFAULT 1,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
fecha_actualizacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Tabla de ventas
cursor.execute('''
CREATE TABLE IF NOT EXISTS ventas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
codigo_producto TEXT NOT NULL,
cantidad INTEGER NOT NULL CHECK(cantidad > 0),
precio_unitario REAL NOT NULL CHECK(precio_unitario > 0),
descuento REAL DEFAULT 0 CHECK(descuento >= 0 AND descuento <= 1),
impuesto REAL DEFAULT 0.16,
total REAL NOT NULL,
usuario TEXT DEFAULT 'sistema',
fecha_venta TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (codigo_producto) REFERENCES productos (codigo)
)
''')
# Tabla de categorías
cursor.execute('''
CREATE TABLE IF NOT EXISTS categorias (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT UNIQUE NOT NULL,
descripcion TEXT,
activa BOOLEAN DEFAULT 1,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Tabla de usuarios
cursor.execute('''
CREATE TABLE IF NOT EXISTS usuarios (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
nombre_completo TEXT NOT NULL,
email TEXT UNIQUE,
rol TEXT DEFAULT 'operador',
activo BOOLEAN DEFAULT 1,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ultimo_acceso TIMESTAMP
)
''')
# Tabla de configuración
cursor.execute('''
CREATE TABLE IF NOT EXISTS configuracion (
clave TEXT PRIMARY KEY,
valor TEXT NOT NULL,
descripcion TEXT,
fecha_actualizacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Índices para mejorar rendimiento
cursor.execute('CREATE INDEX IF NOT EXISTS idx_productos_categoria ON productos(categoria)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_productos_activo ON productos(activo)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ventas_fecha ON ventas(fecha_venta)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ventas_producto ON ventas(codigo_producto)')
conn.commit()
logger.info("Base de datos inicializada correctamente")
except Exception as e:
logger.error(f"Error inicializando base de datos: {e}")
raise
finally:
conn.close()
def get_connection(self) -> sqlite3.Connection:
"""Obtiene conexión a la base de datos"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row # Para acceso por nombre de columna
return conn
def migrar_desde_json(self, archivo_productos: Path):
"""Migra datos existentes desde JSON a SQLite"""
try:
if not archivo_productos.exists():
logger.info("No hay archivo JSON para migrar")
return
with open(archivo_productos, 'r', encoding='utf-8') as f:
productos_json = json.load(f)
conn = self.get_connection()
cursor = conn.cursor()
productos_migrados = 0
for codigo, datos in productos_json.items():
try:
cursor.execute('''
INSERT OR REPLACE INTO productos
(codigo, nombre, precio, stock, categoria, stock_minimo, fecha_creacion)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (
codigo,
datos['nombre'],
datos['precio'],
datos['stock'],
datos.get('categoria', 'General'),
datos.get('stock_minimo', 5),
datos.get('fecha_creacion', datetime.now().isoformat())
))
productos_migrados += 1
except Exception as e:
logger.error(f"Error migrando producto {codigo}: {e}")
conn.commit()
logger.info(f"Migración completada: {productos_migrados} productos")
except Exception as e:
logger.error(f"Error en migración: {e}")
finally:
conn.close()
def ejecutar_consulta(self, query: str, params: tuple = ()) -> List[sqlite3.Row]:
"""Ejecuta una consulta SELECT y retorna los resultados"""
try:
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(query, params)
resultados = cursor.fetchall()
return resultados
except Exception as e:
logger.error(f"Error ejecutando consulta: {e}")
return []
finally:
conn.close()
def ejecutar_comando(self, query: str, params: tuple = ()) -> bool:
"""Ejecuta un comando INSERT/UPDATE/DELETE"""
try:
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(query, params)
conn.commit()
return True
except Exception as e:
logger.error(f"Error ejecutando comando: {e}")
return False
finally:
conn.close()
# Ejemplo de uso del nuevo sistema de base de datos
class ProductoConDB:
"""Versión mejorada de la clase Producto que usa SQLite"""
def __init__(self, db_manager: GestorBaseDatos):
self.db = db_manager
def crear_producto(self, codigo: str, nombre: str, precio: float,
stock: int, categoria: str = "General") -> Tuple[bool, str]:
"""Crea un nuevo producto en la base de datos"""
query = '''
INSERT INTO productos (codigo, nombre, precio, stock, categoria)
VALUES (?, ?, ?, ?, ?)
'''
if self.db.ejecutar_comando(query, (codigo, nombre, precio, stock, categoria)):
logger.operacion("CREAR_PRODUCTO", detalles=f"Código: {codigo}")
return True, f"Producto {codigo} creado exitosamente"
else:
return False, "Error al crear el producto"
def obtener_producto(self, codigo: str) -> Optional[Dict]:
"""Obtiene un producto por su código"""
query = "SELECT * FROM productos WHERE codigo = ? AND activo = 1"
resultados = self.db.ejecutar_consulta(query, (codigo,))
if resultados:
return dict(resultados[0])
return None
def listar_productos(self, categoria: str = None, activos_solo: bool = True) -> List[Dict]:
"""Lista productos con filtros opcionales"""
query = "SELECT * FROM productos"
params = []
condiciones = []
if activos_solo:
condiciones.append("activo = 1")
if categoria:
condiciones.append("categoria = ?")
params.append(categoria)
if condiciones:
query += " WHERE " + " AND ".join(condiciones)
query += " ORDER BY nombre"
resultados = self.db.ejecutar_consulta(query, tuple(params))
return [dict(row) for row in resultados]
def actualizar_stock(self, codigo: str, nuevo_stock: int) -> Tuple[bool, str]:
"""Actualiza el stock de un producto"""
query = '''
UPDATE productos
SET stock = ?, fecha_actualizacion = CURRENT_TIMESTAMP
WHERE codigo = ? AND activo = 1
'''
if self.db.ejecutar_comando(query, (nuevo_stock, codigo)):
logger.operacion("ACTUALIZAR_STOCK", detalles=f"Código: {codigo}, Nuevo stock: {nuevo_stock}")
return True, f"Stock actualizado para {codigo}"
else:
return False, "Error al actualizar stock"
def productos_stock_bajo(self) -> List[Dict]:
"""Obtiene productos con stock bajo"""
query = '''
SELECT * FROM productos
WHERE stock <= stock_minimo AND activo = 1
ORDER BY (stock - stock_minimo), nombre
'''
resultados = self.db.ejecutar_consulta(query)
return [dict(row) for row in resultados]
¡Felicidades, constructor maestro! 🎉 Has completado un viaje extraordinario desde la concepción hasta la implementación de un sistema empresarial completo. Este no es un logro menor: has aplicado prácticamente todos los conceptos de Python que hemos explorado a lo largo del libro para crear algo funcional, útil y profesional.
+---------------------------------------------------------------+
| |
| CERTIFICADO DE FINALIZACIÓN |
| |
| Este documento certifica que has completado exitosamente |
| el proyecto integrador "Sistema de Gestión Empresarial" |
| demostrando dominio de los conceptos fundamentales de |
| Python y habilidades de desarrollo de software. |
| |
| Habilidades demostradas: |
| - Diseño e implementación de sistemas modulares |
| - Programación orientada a objetos |
| - Gestión de datos y persistencia |
| - Desarrollo de interfaces de usuario |
| - Implementación de lógica de negocio |
| |
+---------------------------------------------------------------+
¿Qué patrones de diseño implementaste en este proyecto y por qué son útiles?
¿Cómo manejarías la migración de datos si quisieras pasar de archivos JSON a una base de datos SQL?
¿Qué consideraciones de seguridad deberías tener en cuenta si este sistema se usara en un entorno de producción?
¿Cómo podrías adaptar este sistema para manejar múltiples tiendas o sucursales?
Si tuvieras que elegir una característica para implementar a continuación, ¿cuál sería y por qué?
En el próximo capítulo, aprenderemos sobre Despliegue Básico, donde descubrirás cómo llevar tus aplicaciones Python al mundo real, ejecutándolas en servidores y programando tareas automáticas.
¡Bienvenido al capítulo de Despliegue Básico! Hasta ahora, has aprendido a crear scripts de Python que funcionan en tu computadora local. Pero, ¿qué sucede cuando quieres que tus programas se ejecuten automáticamente, incluso cuando tu computadora está apagada? ¿O cuando quieres compartir tus aplicaciones con otros usuarios?
En este capítulo, aprenderás a dar el siguiente paso: llevar tus scripts de Python al mundo real mediante técnicas de despliegue básico.
El despliegue (deployment) es el proceso de poner un programa o aplicación en un entorno donde pueda ejecutarse de forma independiente y ser accesible para sus usuarios finales. En términos simples, es mover tu código desde tu entorno de desarrollo (tu computadora) a un entorno de producción (donde realmente funcionará).
Como en capítulos anteriores, aprenderemos haciendo. Tomaremos uno de los proyectos de automatización del capítulo anterior y lo desplegaremos en la nube para que se ejecute automáticamente.
¡Comencemos nuestro viaje hacia el despliegue de aplicaciones Python!
Imagina que tu computadora es como tu almacén principal, donde desarrollas y pruebas tus productos (scripts). Pero ahora necesitas un “almacén satélite” que funcione de forma autónoma, incluso cuando cierras tu almacén principal por la noche.
La nube es ese almacén satélite: un espacio virtual donde tus scripts pueden vivir y ejecutarse independientemente de tu computadora personal.
Para este tutorial, usaremos un VPS como ejemplo, ya que ofrece el mejor balance entre flexibilidad, costo y facilidad de uso para scripts de automatización.
Antes de desplegar tu script, necesitas asegurarte de que esté listo para funcionar en un entorno de servidor:
Usa rutas absolutas o relativas al directorio del script:
# En lugar de esto:
with open("datos.txt", "r") as f:
# código...
# Usa esto:
import os
# Obtener el directorio donde está el script
script_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(script_dir, "datos.txt")
with open(file_path, "r") as f:
# código...
Maneja las dependencias adecuadamente:
Crea un archivo requirements.txt que liste todas las bibliotecas que tu script necesita:
Toma uno de los scripts de automatización que creamos en el capítulo anterior (por ejemplo, el organizador de archivos) y prepáralo para despliegue siguiendo estos pasos:
Modifica el script para usar rutas absolutas
Añade logging adecuado
Crea un archivo requirements.txt
Documenta los pasos necesarios para desplegarlo
Si tienes acceso a un VPS o una cuenta gratuita en algún proveedor de nube, intenta desplegar el script siguiendo los pasos de este tutorial.
En la siguiente sección, aprenderemos a programar tareas automáticas para que nuestros scripts se ejecuten periódicamente sin intervención manual.
Este script verifica si un sitio web está funcionando correctamente y envía una notificación por correo electrónico si detecta algún problema.
#!/usr/bin/env python3
# monitor_sitio.py - Script para monitorear la disponibilidad de un sitio web
import requests
import smtplib
import time
import logging
import os
from email.mime.text import MIMEText
from datetime import datetime
# Configuración de logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(os.path.dirname(os.path.abspath(__file__)), "monitor_log.txt")),
logging.StreamHandler()
]
)
# Configuración (mejor usar variables de entorno en producción)
SITIOS_A_MONITOREAR = [
{"url": "https://ejemplo.com", "nombre": "Sitio Principal"},
{"url": "https://api.ejemplo.com", "nombre": "API"},
{"url": "https://blog.ejemplo.com", "nombre": "Blog"}
]
INTERVALO_VERIFICACION = 300 # segundos (5 minutos)
TIEMPO_ESPERA = 10 # segundos para timeout
DESTINATARIOS = ["admin@ejemplo.com", "soporte@ejemplo.com"]
# Configuración de correo (usar variables de entorno en producción)
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_USER = os.environ.get("EMAIL_USER", "")
EMAIL_PASSWORD = os.environ.get("EMAIL_PASSWORD", "")
def verificar_sitio(url, nombre):
"""Verifica si un sitio web está funcionando correctamente."""
try:
inicio = time.time()
respuesta = requests.get(url, timeout=TIEMPO_ESPERA)
tiempo_respuesta = time.time() - inicio
if respuesta.status_code == 200:
logging.info(f"✅ {nombre} ({url}) está funcionando. Tiempo de respuesta: {tiempo_respuesta:.2f}s")
return True, tiempo_respuesta
else:
logging.error(f"❌ {nombre} ({url}) devolvió código de estado: {respuesta.status_code}")
return False, tiempo_respuesta
except requests.RequestException as e:
logging.error(f"❌ Error al verificar {nombre} ({url}): {str(e)}")
return False, 0
def enviar_alerta(sitio, error):
"""Envía una alerta por correo electrónico."""
try:
asunto = f"🚨 ALERTA: {sitio['nombre']} no está disponible"
cuerpo = f"""
Se ha detectado un problema con {sitio['nombre']}.
URL: {sitio['url']}
Fecha y hora: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
Error: {error}
Por favor, verifique el estado del servicio.
Este es un mensaje automático del sistema de monitoreo.
"""
msg = MIMEText(cuerpo)
msg['Subject'] = asunto
msg['From'] = EMAIL_USER
msg['To'] = ", ".join(DESTINATARIOS)
servidor = smtplib.SMTP(EMAIL_HOST, EMAIL_PORT)
servidor.starttls()
servidor.login(EMAIL_USER, EMAIL_PASSWORD)
servidor.send_message(msg)
servidor.quit()
logging.info(f"✉️ Alerta enviada para {sitio['nombre']}")
except Exception as e:
logging.error(f"❌ Error al enviar alerta: {str(e)}")
def main():
"""Función principal del script."""
logging.info("🚀 Iniciando sistema de monitoreo de sitios web")
while True:
for sitio in SITIOS_A_MONITOREAR:
funcionando, tiempo = verificar_sitio(sitio['url'], sitio['nombre'])
if not funcionando:
enviar_alerta(sitio, f"Sitio no disponible o con respuesta incorrecta")
# Esperar hasta la próxima verificación
logging.info(f"💤 Esperando {INTERVALO_VERIFICACION} segundos hasta la próxima verificación")
time.sleep(INTERVALO_VERIFICACION)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
logging.info("👋 Monitoreo detenido manualmente")
except Exception as e:
logging.critical(f"❌ Error crítico: {str(e)}")
# En producción, aquí podrías enviar una alerta sobre el error del script
#!/bin/bash
# stop_monitor.sh
if [ -f monitor.pid ]; then
PID=$(cat monitor.pid)
if ps -p $PID > /dev/null; then
echo "Deteniendo monitor con PID $PID"
kill $PID
else
echo "El proceso no está en ejecución"
fi
rm monitor.pid
else
echo "Archivo PID no encontrado"
fi
Este ejemplo muestra cómo desplegar un servicio web simple usando Flask, que podría servir como API para tus scripts de automatización.
#!/usr/bin/env python3
# api_servicio.py - API simple para gestionar tareas
from flask import Flask, request, jsonify
import os
import json
import logging
from datetime import datetime
# Configuración de logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("api_log.txt"),
logging.StreamHandler()
]
)
app = Flask(__name__)
# Ruta al archivo de datos
DATOS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tareas.json")
def cargar_tareas():
"""Carga las tareas desde el archivo JSON."""
try:
if os.path.exists(DATOS_PATH):
with open(DATOS_PATH, 'r') as f:
return json.load(f)
else:
return {"tareas": []}
except Exception as e:
logging.error(f"Error al cargar tareas: {e}")
return {"tareas": []}
def guardar_tareas(datos):
"""Guarda las tareas en el archivo JSON."""
try:
with open(DATOS_PATH, 'w') as f:
json.dump(datos, f, indent=2)
return True
except Exception as e:
logging.error(f"Error al guardar tareas: {e}")
return False
@app.route('/api/tareas', methods=['GET'])
def obtener_tareas():
"""Endpoint para obtener todas las tareas."""
datos = cargar_tareas()
return jsonify(datos)
@app.route('/api/tareas', methods=['POST'])
def crear_tarea():
"""Endpoint para crear una nueva tarea."""
try:
nueva_tarea = request.json
if not nueva_tarea or 'titulo' not in nueva_tarea:
return jsonify({"error": "Se requiere un título para la tarea"}), 400
datos = cargar_tareas()
# Generar ID para la nueva tarea
tarea_id = 1
if datos["tareas"]:
tarea_id = max(tarea["id"] for tarea in datos["tareas"]) + 1
# Crear la tarea con datos adicionales
tarea_completa = {
"id": tarea_id,
"titulo": nueva_tarea["titulo"],
"descripcion": nueva_tarea.get("descripcion", ""),
"completada": False,
"fecha_creacion": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
datos["tareas"].append(tarea_completa)
if guardar_tareas(datos):
return jsonify(tarea_completa), 201
else:
return jsonify({"error": "Error al guardar la tarea"}), 500
except Exception as e:
logging.error(f"Error al crear tarea: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/tareas/<int:tarea_id>', methods=['GET'])
def obtener_tarea(tarea_id):
"""Endpoint para obtener una tarea específica."""
datos = cargar_tareas()
for tarea in datos["tareas"]:
if tarea["id"] == tarea_id:
return jsonify(tarea)
return jsonify({"error": "Tarea no encontrada"}), 404
@app.route('/api/tareas/<int:tarea_id>', methods=['PUT'])
def actualizar_tarea(tarea_id):
"""Endpoint para actualizar una tarea existente."""
try:
actualizacion = request.json
datos = cargar_tareas()
for i, tarea in enumerate(datos["tareas"]):
if tarea["id"] == tarea_id:
# Actualizar campos permitidos
if "titulo" in actualizacion:
datos["tareas"][i]["titulo"] = actualizacion["titulo"]
if "descripcion" in actualizacion:
datos["tareas"][i]["descripcion"] = actualizacion["descripcion"]
if "completada" in actualizacion:
datos["tareas"][i]["completada"] = actualizacion["completada"]
datos["tareas"][i]["fecha_actualizacion"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if guardar_tareas(datos):
return jsonify(datos["tareas"][i])
else:
return jsonify({"error": "Error al guardar la tarea"}), 500
return jsonify({"error": "Tarea no encontrada"}), 404
except Exception as e:
logging.error(f"Error al actualizar tarea: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/tareas/<int:tarea_id>', methods=['DELETE'])
def eliminar_tarea(tarea_id):
"""Endpoint para eliminar una tarea."""
try:
datos = cargar_tareas()
for i, tarea in enumerate(datos["tareas"]):
if tarea["id"] == tarea_id:
tarea_eliminada = datos["tareas"].pop(i)
if guardar_tareas(datos):
return jsonify({"mensaje": f"Tarea '{tarea_eliminada['titulo']}' eliminada"})
else:
return jsonify({"error": "Error al guardar cambios"}), 500
return jsonify({"error": "Tarea no encontrada"}), 404
except Exception as e:
logging.error(f"Error al eliminar tarea: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/estado', methods=['GET'])
def estado_servicio():
"""Endpoint para verificar el estado del servicio."""
return jsonify({
"estado": "activo",
"version": "1.0.0",
"fecha_hora": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
if __name__ == '__main__':
# En desarrollo
# app.run(debug=True)
# En producción
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
Implementa reintentos para operaciones que pueden fallar:
def operacion_con_reintentos(max_intentos=3, espera=5):
for intento in range(max_intentos):
try:
# Operación que puede fallar
resultado = operacion_riesgosa()
return resultado
except Exception as e:
logging.warning(f"Intento {intento+1}/{max_intentos} falló: {e}")
if intento < max_intentos - 1:
logging.info(f"Esperando {espera} segundos antes de reintentar...")
time.sleep(espera)
else:
logging.error("Se agotaron los reintentos")
raise
Guarda el estado para poder recuperarte de interrupciones:
def procesar_items(items):
# Cargar estado anterior si existe
estado_archivo = "estado_procesamiento.json"
if os.path.exists(estado_archivo):
with open(estado_archivo, 'r') as f:
estado = json.load(f)
items_procesados = estado.get("procesados", [])
logging.info(f"Recuperando estado: {len(items_procesados)} items ya procesados")
else:
items_procesados = []
for item in items:
if item["id"] in items_procesados:
logging.info(f"Item {item['id']} ya procesado, omitiendo")
continue
try:
procesar_item(item)
items_procesados.append(item["id"])
# Guardar estado después de cada item
with open(estado_archivo, 'w') as f:
json.dump({"procesados": items_procesados}, f)
except Exception as e:
logging.error(f"Error al procesar item {item['id']}: {e}")
Imagina que tu almacén (sistema) necesita realizar ciertas tareas de forma regular: inventarios semanales, limpieza diaria, o pedidos mensuales a proveedores. En lugar de hacerlo manualmente, puedes programar un “reloj inteligente” que active estas tareas automáticamente en los momentos adecuados.
La programación de tareas es ese reloj inteligente que permite que tus scripts se ejecuten automáticamente según un calendario predefinido, sin necesidad de intervención manual.
# Ejecutar cada minuto
* * * * * /ruta/al/script.py
# Ejecutar a las 3:30 AM todos los días
30 3 * * * /ruta/al/script.py
# Ejecutar cada hora en punto
0 * * * * /ruta/al/script.py
# Ejecutar a las 8 AM de lunes a viernes
0 8 * * 1-5 /ruta/al/script.py
# Ejecutar el primer día de cada mes a medianoche
0 0 1 * * /ruta/al/script.py
# Ejecutar cada domingo a las 7 PM
0 19 * * 0 /ruta/al/script.py
# Ejecutar cada 15 minutos
*/15 * * * * /ruta/al/script.py
La biblioteca schedule ofrece una forma sencilla de programar tareas:
import schedule
import time
def job():
print("Ejecutando tarea...")
# Tu código aquí
# Programar la tarea para ejecutarse cada día a las 10:30
schedule.every().day.at("10:30").do(job)
# Programar para ejecutarse cada hora
schedule.every().hour.do(job)
# Programar para ejecutarse cada 5 minutos
schedule.every(5).minutes.do(job)
# Programar para ejecutarse cada lunes
schedule.every().monday.do(job)
# Bucle principal para mantener el programa ejecutándose
while True:
schedule.run_pending()
time.sleep(1)
Para usar schedule, instálalo con pip:
# Instalar la biblioteca schedule
# pip install schedule
Nota importante: Para que schedule funcione, tu script debe estar ejecutándose continuamente. Esto lo hace más adecuado para servicios que ya están en ejecución constante, no como reemplazo de cron o Task Scheduler.
Para necesidades más avanzadas, APScheduler ofrece más flexibilidad:
from apscheduler.schedulers.blocking import BlockingScheduler
def job():
print("Ejecutando tarea...")
# Tu código aquí
scheduler = BlockingScheduler()
# Programar para ejecutarse cada día a las 10:30
scheduler.add_job(job, 'cron', hour=10, minute=30)
# Programar para ejecutarse cada 5 minutos
scheduler.add_job(job, 'interval', minutes=5)
# Iniciar el scheduler
scheduler.start()
Para usar APScheduler, instálalo con pip:
# Instalar la biblioteca APScheduler
# pip install apscheduler
La combinación perfecta para la automatización completa es:
Desplegar tu script en un servidor en la nube (como vimos en la sección anterior)
Programar su ejecución con cron (en el servidor)
Ejemplo completo para un script de backup automático:
# En el servidor (después de subir tu script)
cd /opt/myscripts
# Crear entorno virtual e instalar dependencias
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Editar crontab
crontab -e
# Añadir esta línea para ejecutar el script todos los días a las 3 AM
0 3 * * * cd /opt/myscripts && source venv/bin/activate && python backup_script.py >> /opt/myscripts/logs/backup.log 2>&1
Cómo programar tareas automáticas en diferentes sistemas operativos
La sintaxis y uso de cron en sistemas Linux/Unix
Cómo configurar el Programador de tareas en Windows
Opciones para programar tareas desde dentro de tus scripts Python
Cómo combinar el despliegue en la nube con la programación de tareas
Con estos conocimientos, puedes crear sistemas de automatización completos que funcionen de forma autónoma, ejecutándose en los momentos precisos sin intervención manual.
En la siguiente sección, aprenderemos sobre monitoreo y mantenimiento para asegurarnos de que nuestros scripts automatizados sigan funcionando correctamente a largo plazo.
Imagina que has automatizado completamente tu almacén: los productos se mueven solos, los inventarios se actualizan automáticamente y los pedidos se procesan sin intervención humana. Pero, ¿qué sucede si una máquina falla o un proceso se detiene inesperadamente? Necesitas un supervisor que vigile constantemente que todo funcione correctamente.
El monitoreo y mantenimiento son ese supervisor para tus scripts automatizados, asegurando que sigan funcionando correctamente y alertándote cuando algo va mal.
Puedes usar servicios como Telegram, Slack o Discord para recibir notificaciones:
# Ejemplo con Telegram (requiere python-telegram-bot)
from telegram import Bot
def send_telegram_alert(message):
"""Envía una alerta a través de Telegram."""
bot_token = 'tu_token_de_bot'
chat_id = 'tu_chat_id'
bot = Bot(token=bot_token)
bot.send_message(chat_id=chat_id, text=message)
# Usar en caso de error
try:
# Código que podría fallar
process_data()
except Exception as e:
error_msg = f"⚠️ ALERTA: Script falló con error: {str(e)}"
send_telegram_alert(error_msg)
Una técnica simple pero efectiva es crear “heartbeats” (latidos) que indiquen que tu script sigue funcionando:
def update_heartbeat():
"""Actualiza el archivo de heartbeat con la hora actual."""
with open("heartbeat.txt", "w") as f:
f.write(datetime.now().isoformat())
# Llamar periódicamente durante la ejecución
update_heartbeat()
Luego puedes tener otro script que verifique si el heartbeat está actualizado:
def check_heartbeat(max_age_minutes=60):
"""Verifica si el heartbeat está actualizado."""
try:
with open("heartbeat.txt", "r") as f:
last_beat = datetime.fromisoformat(f.read().strip())
age = datetime.now() - last_beat
if age.total_seconds() > max_age_minutes * 60:
send_alert(f"Script inactivo por {age.total_seconds() / 60:.1f} minutos")
return False
return True
except Exception:
send_alert("No se pudo leer el heartbeat")
return False
import requests
def ping_healthcheck(success=True):
"""Notifica a Healthchecks.io sobre el estado del script."""
hc_url = "https://hc-ping.com/tu-uuid-de-healthcheck"
if success:
requests.get(hc_url)
else:
requests.get(f"{hc_url}/fail")
# Al inicio del script
try:
# Código principal
main_process()
# Al finalizar con éxito
ping_healthcheck(success=True)
except Exception as e:
logging.error(f"Error: {str(e)}")
ping_healthcheck(success=False)
raise
Implementa pruebas automáticas que verifiquen que todo sigue funcionando:
def run_self_tests():
"""Ejecuta pruebas básicas para verificar la funcionalidad."""
tests_passed = True
# Prueba 1: Verificar conexión a la base de datos
try:
db_connection = connect_to_database()
logging.info("Test de conexión a BD: OK")
except Exception as e:
logging.error(f"Test de conexión a BD: FALLÓ - {str(e)}")
tests_passed = False
# Prueba 2: Verificar acceso a API externa
try:
api_response = call_external_api()
if api_response.status_code == 200:
logging.info("Test de API externa: OK")
else:
logging.error(f"Test de API externa: FALLÓ - Código {api_response.status_code}")
tests_passed = False
except Exception as e:
logging.error(f"Test de API externa: FALLÓ - {str(e)}")
tests_passed = False
return tests_passed
import time
def measure_execution_time(func):
"""Decorador para medir el tiempo de ejecución de una función."""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
logging.info(f"Función {func.__name__} ejecutada en {execution_time:.2f} segundos")
return result
return wrapper
@measure_execution_time
def process_large_dataset():
# Procesamiento que podría ser lento
pass
import psutil
def log_resource_usage():
"""Registra el uso actual de recursos."""
process = psutil.Process()
# Uso de memoria
memory_info = process.memory_info()
memory_mb = memory_info.rss / (1024 * 1024)
# Uso de CPU
cpu_percent = process.cpu_percent(interval=1)
# Uso de disco
disk = psutil.disk_usage('/')
disk_percent = disk.percent
logging.info(f"Recursos: Memoria={memory_mb:.1f}MB, CPU={cpu_percent}%, Disco={disk_percent}%")
Mantén un archivo CHANGELOG.md para documentar cambios importantes:
# Changelog
## [1.2.0] - 2023-07-15
### Añadido
- Notificaciones por Telegram cuando ocurren errores
- Rotación automática de logs
### Corregido
- Error al procesar archivos con nombres especiales
## [1.1.0] - 2023-06-20
### Añadido
- Soporte para nuevos formatos de archivo
- Mejor manejo de errores
### Cambiado
- Optimizado el procesamiento de archivos grandes
La importancia del monitoreo para scripts automatizados
Diferentes estrategias de monitoreo: logging, notificaciones, heartbeats y servicios profesionales
Técnicas de mantenimiento preventivo para evitar problemas
Cómo monitorear el rendimiento de tus scripts
Buenas prácticas para actualizar y mejorar tus scripts a lo largo del tiempo
Con estos conocimientos, puedes asegurarte de que tus scripts automatizados no solo funcionen correctamente al principio, sino que sigan siendo confiables y eficientes a largo plazo.
En la siguiente sección, haremos un resumen del capítulo y veremos cómo todos estos conceptos se integran para crear sistemas de automatización robustos y profesionales.
¡Felicidades! Has completado el capítulo de Despliegue Básico y ahora tienes las herramientas para llevar tus scripts de Python al siguiente nivel: la automatización completa y autónoma.
En este capítulo, hemos recorrido el ciclo completo de despliegue y automatización:
Ejecutar scripts en la nube: Aprendiste a configurar servidores virtuales donde tus scripts pueden funcionar 24/7
Programar tareas automáticas: Descubriste cómo programar la ejecución periódica de tus scripts sin intervención manual
Monitoreo y mantenimiento: Exploraste técnicas para asegurar que tus scripts sigan funcionando correctamente a largo plazo
El despliegue y la automatización son habilidades que transforman a un programador principiante en un desarrollador capaz de crear soluciones reales y prácticas. Con lo que has aprendido hasta ahora, ya puedes crear sistemas que funcionen de forma autónoma y resuelvan problemas reales.
Recuerda que la automatización es un proceso iterativo: comienza con soluciones simples, monitorea su funcionamiento, y mejóralas gradualmente. Con el tiempo, construirás sistemas cada vez más robustos y sofisticados.
¡Felicidades por completar este capítulo! Ahora tienes las herramientas para llevar tus habilidades de Python al mundo real, creando soluciones que funcionan de forma autónoma y continua.
🎯 Próximo capítulo: Ejercicios por Capítulo - Pon a prueba todo lo que has aprendido con ejercicios prácticos que integran los conceptos de todos los capítulos anteriores.
¡Bienvenido a la sección de ejercicios! Esta es tu oportunidad para poner en práctica todo lo que has aprendido a lo largo del libro. Aquí encontrarás ejercicios adicionales organizados por capítulo, desde los conceptos más básicos hasta proyectos más complejos.
Para poner a prueba todo lo que has aprendido, te invitamos a completar un Proyecto Final Integrador que combina conceptos de todos los capítulos en una aplicación completa y funcional.
Este proyecto te permitirá:
Aplicar todos los conocimientos adquiridos
Desarrollar un sistema completo desde cero
Practicar el ciclo completo de desarrollo
Crear algo útil que puedas incluir en tu portafolio
Este glosario contiene definiciones de los términos técnicos utilizados a lo largo del libro. Está organizado alfabéticamente para facilitar su consulta.
Conjunto de instrucciones o reglas definidas y ordenadas que permite realizar una actividad mediante pasos sucesivos que no generen dudas a quien deba realizar dicha actividad.
Ejemplo: Un algoritmo para calcular el promedio de una lista de números incluiría los pasos para sumar todos los números y dividir por la cantidad de elementos.
Conjunto de reglas y especificaciones que las aplicaciones pueden seguir para comunicarse entre ellas, sirviendo de interfaz entre programas diferentes.
Ejemplo: La API de Twitter permite a los desarrolladores acceder a datos de Twitter desde sus propias aplicaciones.
Conjunto de implementaciones funcionales, codificadas en un lenguaje de programación, que ofrece una interfaz bien definida para la funcionalidad que se invoca.
Ejemplo: La biblioteca estándar de Python incluye módulos como math, os y datetime.
¡Felicidades por llegar hasta aquí! 🎉 Has dado los primeros pasos sólidos en tu viaje como programador Python. Ahora es momento de expandir tus horizontes y descubrir el vasto ecosistema que te espera.
🎉 ¡Felicidades por completar este viaje! Has adquirido las bases sólidas para convertirte en un programador Python competente.
🛤️ Recuerda: La programación es una habilidad que se desarrolla con la práctica constante. No te desanimes si algunos conceptos toman tiempo en asentarse – es completamente normal.
🚀 Tu próximo paso: Elige UN recurso de esta lista y comienza mañana. La consistencia es más importante que la intensidad.
👍 ¡Éxito en tu carrera como programador!
💫 Consejo del autor: No intentes aprender todo a la vez. Elige una especialización, masónala, y luego expande. La profundidad es más valiosa que la amplitud cuando estás comenzando.
Este apéndice contiene las soluciones completas y comentadas a los ejercicios presentados a lo largo del libro. Cada solución incluye no solo el código, sino también explicaciones detalladas del razonamiento detrás de cada decisión.
💡 Consejo importante: Intenta resolver los ejercicios por tu cuenta antes de consultar estas soluciones. El aprendizaje real ocurre cuando luchas con los problemas y encuentras tus propias soluciones.
Intenta variaciones: Modifica los ejercicios con diferentes datos
Combina conceptos: Usa técnicas de varios ejercicios juntas
Crea tus propios ejercicios: Diseña problemas similares
Busca optimizaciones: ¿Puedes hacer el código más eficiente?
Añade funcionalidades: Extiende las soluciones con nuevas características
💫 Recuerda: No hay una única forma “correcta” de resolver un problema en programación. Estas soluciones representan un enfoque, pero tu solución puede ser igualmente válida si resuelve el problema eficientemente.
Este índice temático te ayudará a encontrar rápidamente conceptos específicos, funciones, métodos y temas tratados a lo largo del libro. Está organizado alfabéticamente por temas principales, con referencias a los capítulos y secciones correspondientes.