Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

¡Bienvenido a Python para Principiantes!

¡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.

¿Por qué este libro?

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.

¿Qué vas a aprender?

Al terminar este libro, vas a poder:

  • 🏗️ Construir tu propio “almacén digital” con Python
  • 📦 Organizar datos usando diferentes tipos de “contenedores”
  • 🔧 Crear herramientas personalizadas (funciones) para automatizar tareas
  • 🤖 Programar robots digitales que trabajen por ti (bucles y condicionales)
  • 📊 Generar reportes y análisis automáticos
  • 🚀 Desarrollar un sistema completo de gestión de inventario

¿Cómo está organizado este libro?

El libro sigue una progresión natural, como construir y equipar tu almacén paso a paso:

🚀 Parte I: Preparación (Capítulos 1-3)

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.

🧠 Parte II: Conceptos Fundamentales (Capítulos 4-7)

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.

🏗️ Parte III: Estructuras de Datos y Organización (Capítulos 8-10)

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).

🎯 Parte IV: Proyecto Integrador (Capítulos 11-12)

¡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.

Mi filosofía de enseñanza

Creo firmemente en aprender haciendo y entender el porqué. Por eso, cada concepto viene acompañado de:

  • 🔍 Analogías claras: Cada concepto de programación se explica usando la metáfora del almacén
  • 💡 Ejemplos prácticos: Código que puedes probar inmediatamente y ver funcionando
  • 🏋️ Ejercicios progresivos: Desde básicos hasta desafiantes, con soluciones incluidas
  • 🚀 Proyectos aplicados: Pequeños proyectos que muestran aplicaciones reales
  • 💭 Consejos de experiencia: Tips basados en años de programación y enseñanza

¿Necesitas conocimientos previos?

¡Absolutamente ninguno! Este libro asume que nunca has programado antes. Lo único que necesitas es:

  • 💻 Una computadora (Windows, Mac o Linux - te enseño a configurar cualquiera)
  • 🧠 Curiosidad y ganas de aprender
  • ⏰ Tiempo para practicar (recomiendo 30-60 minutos por sesión)
  • 🎯 Paciencia contigo mismo (todos cometemos errores al aprender)

¿Por qué Python?

Python es el lenguaje perfecto para principiantes porque:

  • 📖 Es fácil de leer: Su sintaxis es casi como inglés
  • 🚀 Es poderoso: Usado por Google, Netflix, Instagram y miles de empresas
  • 🌍 Es versátil: Desde páginas web hasta inteligencia artificial
  • 👥 Tiene una comunidad increíble: Millones de programadores dispuestos a ayudar
  • 💼 Tiene demanda laboral: Una de las habilidades más buscadas en el mercado

Cómo usar este libro

📚 Para el aprendizaje óptimo:

  1. Lee cada capítulo completamente antes de pasar al siguiente
  2. Prueba todos los ejemplos en tu computadora
  3. Haz los ejercicios - son fundamentales para el aprendizaje
  4. No te saltes capítulos - cada uno construye sobre el anterior
  5. Toma descansos cuando te sientas abrumado

🎯 Ritmo recomendado:

  • Principiante total: 1-2 capítulos por semana
  • Con algo de experiencia: 2-3 capítulos por semana
  • Intensivo: 1 capítulo por día (con práctica)

🆘 Si te atascas:

  • Vuelve a leer la sección anterior
  • Prueba los ejemplos paso a paso
  • Busca en la comunidad Python (te enseño cómo en el último capítulo)
  • Recuerda: ¡todos los programadores se atascan a veces!

Tu viaje comienza ahora

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.

Una nota personal

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.

¡Empecemos!

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)
  • Ganas de aprender y experimentar
  • Una computadora con internet

Cómo usar este libro

Te recomiendo que:

  1. Leas cada capítulo en orden - cada uno se basa en el anterior
  2. Practiques todos los ejemplos - no solo los leas, ¡escríbelos!
  3. Hagas los ejercicios - son la clave para realmente aprender
  4. No te apures - es mejor entender bien cada concepto

Una nota personal

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.

¿Listo para empezar?

¡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.

¡Vamos a programar! 🐍

Capítulo 1: Introducción a la Programación y Python

¡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?

¿Qué es la programación?

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:

  1. Ve a la cocina
  2. Llena la cafetera con agua
  3. Pon el filtro
  4. Agrega dos cucharadas de café
  5. Enciende la cafetera
  6. 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.

Un ejemplo cotidiano

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.

¿Por qué Python?

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.

Mira esta comparación

En otros lenguajes podrías escribir algo así:

// Código en C++
#include <iostream>
using namespace std;
int main() {
    cout << "¡Hola, mundo!" << endl;
    return 0;
}

En Python escribes simplemente:

print("¡Hola, mundo!")

¿Ves la diferencia? Python es mucho más directo y fácil de leer.

¿Dónde se usa Python en el mundo real?

Python no es solo para principiantes. Se usa en lugares que probablemente conoces:

🎬 Entretenimiento

  • Netflix usa Python para recomendarte películas
  • Instagram procesa millones de fotos con Python
  • Spotify analiza tu música favorita con Python

🏢 Empresas

  • Google usa Python en muchos de sus servicios
  • Dropbox está construido principalmente en Python
  • Uber calcula rutas y precios con Python

🔬 Ciencia y Tecnología

  • NASA usa Python para analizar datos espaciales
  • Los científicos usan Python para descubrir nuevos medicamentos
  • Los bancos usan Python para detectar fraudes

Las ventajas de Python

1. Fácil de leer

El código de Python se parece mucho al inglés normal. Si ves:

if edad >= 18:
    print("Eres mayor de edad")

Probablemente puedes adivinar qué hace, ¡aunque nunca hayas programado!

2. Comunidad gigante

Python tiene millones de programadores en todo el mundo. Esto significa:

  • Muchísimos tutoriales y recursos gratuitos
  • Respuestas rápidas a tus preguntas
  • Miles de herramientas ya hechas que puedes usar

3. Versatilidad increíble

Con Python puedes hacer:

  • Páginas web
  • Aplicaciones móviles
  • Análisis de datos
  • Inteligencia artificial
  • Automatización de tareas
  • Videojuegos
  • ¡Y mucho más!

4. Oportunidades laborales

Python está entre los lenguajes más demandados. Las empresas buscan programadores de Python para:

  • Desarrollo web
  • Ciencia de datos
  • Automatización
  • Inteligencia artificial
  • DevOps

La filosofía de Python

Python tiene una filosofía muy clara llamada “El Zen de Python”. Aquí tienes algunas de sus ideas principales:

  • Hermoso es mejor que feo
  • Explícito es mejor que implícito
  • Simple es mejor que complejo
  • La legibilidad cuenta

Esto significa que Python prefiere código que sea fácil de leer y entender, en lugar de código “inteligente” pero confuso.

Tu viaje de aprendizaje

Aprender a programar es como aprender a tocar un instrumento:

🎵 Al principio (donde estás ahora)

  • Todo parece confuso
  • Cada concepto nuevo es un reto
  • Te sientes abrumado

🎶 Después de unas semanas

  • Empiezas a reconocer patrones
  • Los conceptos básicos se vuelven naturales
  • Puedes escribir programas simples

🎼 Después de unos meses

  • Puedes resolver problemas reales
  • Entiendes cómo funcionan las cosas
  • ¡Empiezas a disfrutar programar!

¿Qué vamos a construir juntos?

A lo largo de este libro, vamos a crear varios proyectos pequeños que te ayudarán a practicar:

  1. Una calculadora personal - para practicar operaciones básicas
  2. Un organizador de tareas - para manejar listas y datos
  3. Un juego de adivinanzas - para practicar lógica y decisiones
  4. Un analizador de texto - para trabajar con archivos

Al final del libro, vas a crear un proyecto integrador que combine todo lo que aprendiste.

Preparándote mentalmente

Antes de continuar, quiero que sepas algunas cosas importantes:

✅ Es normal sentirse confundido

Todos los programadores se sienten así al principio. La confusión es parte del proceso de aprendizaje.

✅ Los errores son tus amigos

En programación, los errores (llamados “bugs”) son completamente normales. De hecho, ¡vas a aprender más de tus errores que de tus éxitos!

✅ La práctica es clave

Leer sobre programación está bien, pero programar de verdad es lo que te va a hacer mejorar.

✅ Cada persona aprende diferente

Algunos entienden rápido los conceptos teóricos, otros prefieren ir directo a los ejemplos. Encuentra tu ritmo.

Ejercicio de reflexión

Antes de continuar al siguiente capítulo, piensa en estas preguntas:

  1. ¿Qué tareas repetitivas haces en tu computadora? (organizar archivos, enviar emails, etc.)
  2. ¿Qué te gustaría automatizar en tu vida diaria?
  3. ¿Qué tipo de programas te gustaría crear?

Escribe tus respuestas. Al final del libro, vas a ver que muchas de estas cosas las puedes hacer con Python.

Resumen del capítulo

En este capítulo aprendiste:

  • ✅ Qué es la programación (dar instrucciones específicas a una computadora)
  • ✅ Por qué Python es perfecto para principiantes (fácil de leer y muy poderoso)
  • ✅ Dónde se usa Python en el mundo real (Netflix, Google, NASA, etc.)
  • ✅ Las ventajas de Python (legible, versátil, gran comunidad)
  • ✅ Qué esperar en tu viaje de aprendizaje

¿Qué sigue?

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!

Capítulo 2: Configuración del Entorno de Python

¡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.

¿Qué vamos a instalar?

Antes de empezar, déjame explicarte qué necesitamos:

1. Python (el lenguaje)

Es como instalar un nuevo idioma en tu computadora. Una vez instalado, tu computadora va a entender las instrucciones que escribas en Python.

2. Un editor de código (tu herramienta de trabajo)

Es como un procesador de texto, pero diseñado especialmente para escribir código. Te ayuda con colores, sugerencias y detecta errores.

3. La terminal/línea de comandos (ya viene con tu computadora)

Es una forma de darle instrucciones directas a tu computadora usando texto. No te asustes, ¡es más fácil de lo que parece!

Antes de empezar

¿Qué sistema operativo tienes?

Dependiendo de tu sistema operativo, los pasos son un poco diferentes:

  • Windows - Si ves el logo de Windows al encender tu computadora
  • macOS - Si tienes una Mac (computadora de Apple)
  • Linux - Si usas Ubuntu, Fedora, o alguna distribución de Linux

¿Tienes derechos de administrador?

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.

Guías de instalación por sistema operativo

Elige tu sistema operativo y sigue la guía correspondiente:

🪟 Instalación en Windows

  • Descargar Python desde el sitio oficial
  • Configurar las variables de entorno
  • Verificar la instalación

🍎 Instalación en macOS

  • Usar Homebrew (recomendado) o descarga directa
  • Configurar el PATH
  • Verificar la instalación

🐧 Instalación en Linux

  • Usar el gestor de paquetes de tu distribución
  • Compilar desde código fuente (si es necesario)
  • Verificar la instalación

Después de instalar Python

Una vez que tengas Python instalado, vamos a verificar que todo funcione correctamente.

Verificar la instalación

Abre tu terminal o línea de comandos y escribe:

python --version

Deberías ver algo como:

Python 3.11.5

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.

Tu primer comando 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! 🎉

Para salir del intérprete, escribe:

exit()

Eligiendo un editor de código

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.

📝 Editores e IDEs Recomendados

En la siguiente sección te explico las mejores opciones, desde las más simples hasta las más avanzadas.

Conceptos importantes

Python Shell vs. Archivos de Script

Hay dos formas principales de ejecutar código Python:

1. Python Shell (Intérprete Interactivo)

  • Lo que acabas de probar
  • Perfecto para probar cosas rápidas
  • Escribes una línea, presionas Enter, ves el resultado
  • Se pierde todo cuando sales

2. Archivos de Script (.py)

  • Escribes todo tu código en un archivo
  • Guardas el archivo con extensión .py
  • Ejecutas todo el archivo de una vez
  • Puedes guardar y reutilizar tu código

¿Cuándo usar cada uno?

Usa el Shell cuando:

  • Quieras probar algo rápido
  • Necesites hacer un cálculo simple
  • Estés experimentando con una función nueva

Usa archivos .py cuando:

  • Escribas un programa completo
  • Quieras guardar tu trabajo
  • Tengas más de unas pocas líneas de código

Organizando tu espacio de trabajo

Te recomiendo crear una carpeta especial para tus proyectos de Python:

En Windows:

C:\Users\TuNombre\Documentos\Python\

En macOS/Linux:

/Users/TuNombre/Documents/Python/

Dentro de esta carpeta, puedes crear subcarpetas para diferentes proyectos:

Python/
|-- ejercicios-libro/
|-- mi-primer-proyecto/
|-- experimentos/

Problemas comunes y soluciones

“python no se reconoce como comando”

Problema: Windows no encuentra Python Solución: Reinstala Python y asegúrate de marcar “Add Python to PATH”

“Permission denied”

Problema: No tienes permisos para instalar Solución: Ejecuta como administrador o pide ayuda a IT

“Python 2.7 en lugar de Python 3”

Problema: Tu sistema tiene Python 2 por defecto Solución: Usa python3 en lugar de python

El editor no reconoce Python

Problema: Tu editor no sabe dónde está Python Solución: Configura la ruta de Python en las preferencias del editor

Ejercicio práctico

Antes de continuar al siguiente capítulo, vamos a hacer un ejercicio para asegurarnos de que todo funciona:

Paso 1: Crear tu primer archivo Python

  1. Abre tu editor de código
  2. Crea un nuevo archivo
  3. Escribe este código:
print("¡Mi primer programa en Python!")
print("Mi nombre es [tu nombre aquí]")
print("Estoy aprendiendo a programar")
  1. Guarda el archivo como mi_primer_programa.py en tu carpeta de Python

Paso 2: Ejecutar tu programa

  1. Abre la terminal
  2. Navega a tu carpeta de Python
  3. Ejecuta: python mi_primer_programa.py

Si ves tus mensajes en la pantalla, ¡todo está funcionando perfectamente!

Resumen del capítulo

En este capítulo lograste:

  • ✅ Entender qué necesitas para programar en Python
  • ✅ Instalar Python en tu sistema operativo
  • ✅ Verificar que la instalación funciona correctamente
  • ✅ Conocer la diferencia entre el Shell y los archivos .py
  • ✅ Elegir un editor de código apropiado
  • ✅ Crear y ejecutar tu primer programa

¿Qué sigue?

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!

Instalación de Python en Windows

¡Perfecto! Tienes Windows. Esta es una de las instalaciones más sencillas. Te voy a guiar paso a paso.

Paso 1: Descargar Python

Ir al sitio oficial

  1. Abre tu navegador web
  2. Ve a python.org
  3. Haz clic en el botón amarillo grande que dice “Download Python 3.x.x”

💡 Tip: Siempre descarga desde python.org. Es el sitio oficial y más seguro.

¿Qué versión descargar?

  • Python 3.11 o superior es perfecto para este libro
  • Evita Python 2.x (ya está obsoleto)
  • La página automáticamente te sugiere la mejor versión para Windows

Paso 2: Ejecutar el instalador

Abrir el archivo descargado

  1. Ve a tu carpeta de Descargas
  2. Busca un archivo que se llame algo como python-3.11.5-amd64.exe
  3. Haz doble clic para ejecutarlo

⚠️ IMPORTANTE: Configuración del instalador

Cuando se abra el instalador, vas a ver una ventana como esta:

+-----------------------------------+
| Install Python 3.11.5             |
|                                   |
| [ ] Use admin privileges when...  |
| [X] Add python.exe to PATH        | <- ¡MUY IMPORTANTE!
|                                   |
| [Install Now]  [Customize...]     |
+-----------------------------------+

¡ASEGÚRATE DE MARCAR LA CASILLA “Add python.exe to PATH”!

Esta casilla es súper importante. Si no la marcas, Windows no va a saber dónde está Python y tendrás problemas después.

Instalar

  1. Marca la casilla “Add python.exe to PATH”
  2. Haz clic en “Install Now”
  3. Si Windows te pide permisos de administrador, haz clic en “Sí”
  4. Espera a que termine la instalación (puede tomar unos minutos)

Paso 3: Verificar la instalación

Abrir la línea de comandos

  1. Presiona Windows + R
  2. Escribe cmd y presiona Enter
  3. Se abrirá una ventana negra (la línea de comandos)

Probar Python

En la ventana negra, escribe:

python --version

Deberías ver algo como:

Python 3.11.5

Si ves esto, ¡felicidades! Python está instalado correctamente.

Si no funciona…

Problema: “python no se reconoce como comando”

Esto significa que la casilla “Add to PATH” no se marcó correctamente.

Solución rápida:

  1. Desinstala Python desde “Configuración > Aplicaciones”
  2. Vuelve a descargar el instalador
  3. Esta vez SÍ marca la casilla “Add python.exe to PATH”
  4. Reinstala

Solución avanzada (si sabes lo que haces): Puedes agregar Python al PATH manualmente, pero es más fácil reinstalar.

Paso 4: Probar el intérprete interactivo

En la línea de comandos, escribe:

python

Deberías ver:

# 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.
>>>

¡Perfecto! Ahora prueba tu primer comando:

print("¡Hola desde Windows!")

Para salir, escribe:

exit()

Paso 5: Instalar pip (gestor de paquetes)

La buena noticia es que pip viene incluido con Python 3.4+. Vamos a verificar que funciona:

pip --version

Deberías ver algo como:

# Ejemplo de salida:
pip 23.2.1 from C:/Users/TuNombre/AppData/Local/Programs/Python/Python311/Lib/site-packages/pip (python 3.11)

Configuración adicional para Windows

Windows Terminal (recomendado)

Si tienes Windows 10 o 11, te recomiendo instalar Windows Terminal desde la Microsoft Store. Es mucho mejor que el cmd tradicional:

  1. Abre Microsoft Store
  2. Busca “Windows Terminal”
  3. Instala la aplicación gratuita de Microsoft

PowerShell vs CMD

  • CMD: La línea de comandos tradicional (funciona perfectamente)
  • PowerShell: Más moderna y poderosa (también funciona bien)
  • Windows Terminal: La más moderna y bonita

Cualquiera de las tres funciona para Python. Usa la que te sea más cómoda.

Problemas comunes en Windows

Error: “Microsoft Visual C++ 14.0 is required”

Cuándo aparece: Al instalar algunos paquetes de Python Solución: Instala “Microsoft C++ Build Tools” desde el sitio de Microsoft

Python se abre desde Microsoft Store

Problema: Windows 10/11 a veces redirige python a la Microsoft Store Solución:

  1. Ve a Configuración > Aplicaciones > Alias de ejecución de aplicaciones
  2. Desactiva los alias de Python

Antivirus bloquea la instalación

Problema: Algunos antivirus son muy estrictos Solución: Temporalmente desactiva el antivirus durante la instalación

Verificación final

Para asegurarte de que todo está perfecto, crea un archivo de prueba:

Paso 1: Crear el archivo

  1. Abre el Bloc de notas
  2. Escribe:
print("¡Python funciona perfectamente en Windows!")
import sys
print(f"Versión de Python: {sys.version}")
print(f"Ubicación de Python: {sys.executable}")
  1. Guarda como prueba.py en tu escritorio

Paso 2: Ejecutar el archivo

  1. Abre la línea de comandos
  2. Navega al escritorio: cd Desktop
  3. Ejecuta: python prueba.py

Si ves la información de tu instalación, ¡todo está listo!

Siguiente paso

Ahora que tienes Python funcionando en Windows, es hora de elegir un buen editor de código.

👉 Continúa con: Editores e IDEs Recomendados


🎯 Resumen para Windows: Descarga desde python.org, marca “Add to PATH”, verifica con python --version. ¡Así de simple!

Instalación de Python en macOS

¡Excelente! Tienes una Mac. macOS ya viene con Python, pero probablemente es una versión antigua. Vamos a instalar la versión más reciente.

¿Ya tienes Python?

Primero, vamos a ver qué tienes instalado:

  1. Abre Terminal (Aplicaciones > Utilidades > Terminal)
  2. Escribe: python3 --version

Probablemente veas algo como:

Python 3.9.6

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.

Método 1: Homebrew (Recomendado) 🍺

Homebrew es el “gestor de paquetes” más popular para Mac. Es como una App Store para herramientas de desarrollo.

Paso 1: Instalar Homebrew

Si no tienes Homebrew, instálalo primero:

  1. Abre Terminal
  2. Copia y pega este comando:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  1. Presiona Enter y sigue las instrucciones
  2. Te va a pedir tu contraseña (es normal)

Paso 2: Instalar Python con Homebrew

Una vez que tengas Homebrew:

brew install python

Esto instala la versión más reciente de Python 3.

Paso 3: Verificar la instalación

python3 --version

Deberías ver algo como:

Python 3.11.5

Método 2: Descarga directa desde python.org

Si prefieres no usar Homebrew:

Paso 1: Descargar

  1. Ve a python.org
  2. Haz clic en “Download Python 3.x.x”
  3. Descarga el archivo .pkg para macOS

Paso 2: Instalar

  1. Abre el archivo .pkg descargado
  2. Sigue el asistente de instalación
  3. Usa todas las opciones por defecto

Paso 3: Verificar

python3 --version

Configurar el PATH (importante)

En Mac, es posible que necesites usar python3 en lugar de python. Para hacer tu vida más fácil, puedes crear un alias:

Opción 1: Alias temporal

alias python=python3
alias pip=pip3

Opción 2: Alias permanente

  1. Abre tu archivo de configuración:
nano ~/.zshrc
  1. Agrega estas líneas al final:
alias python=python3
alias pip=pip3
  1. Guarda (Ctrl+X, luego Y, luego Enter)
  2. Recarga la configuración:
source ~/.zshrc

Ahora puedes usar python en lugar de python3.

Verificar pip

Python viene con pip (el gestor de paquetes). Verifica que funciona:

pip3 --version

O si configuraste el alias:

pip --version

Probar el intérprete interactivo

python3

Deberías ver:

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.
>>>

Prueba:

print("¡Hola desde macOS!")

Para salir:

exit()

Configuración adicional para Mac

Xcode Command Line Tools

Algunos paquetes de Python necesitan herramientas de compilación. Instálalas con:

xcode-select --install

Esto abre una ventana donde puedes instalar las herramientas básicas de desarrollo.

iTerm2 (opcional pero recomendado)

iTerm2 es una terminal mucho mejor que la que viene por defecto:

  1. Ve a iterm2.com
  2. Descarga e instala iTerm2
  3. Úsala en lugar de Terminal

Problemas comunes en macOS

“python: command not found”

Problema: macOS no encuentra Python Solución: Usa python3 o configura el alias como se explicó arriba

Problemas de permisos

Problema: “Permission denied” al instalar paquetes Solución: Usa pip3 install --user nombre_paquete en lugar de sudo pip3 install

Conflicto entre versiones

Problema: Tienes múltiples versiones de Python Solución: Usa python3.11 para ser específico sobre la versión

Homebrew no funciona

Problema: Homebrew no se instala correctamente Solución: Verifica que tienes Xcode Command Line Tools instalado

Gestión de versiones con pyenv (avanzado)

Si planeas trabajar con múltiples proyectos que requieren diferentes versiones de Python, considera usar pyenv:

# Instalar pyenv
brew install pyenv

# Instalar Python 3.11
pyenv install 3.11.5

# Usar Python 3.11 globalmente
pyenv global 3.11.5

Pero para empezar, esto no es necesario.

Verificación final

Crea un archivo de prueba para asegurarte de que todo funciona:

Paso 1: Crear el archivo

nano prueba.py

Paso 2: Escribir el código

print("¡Python funciona perfectamente en macOS!")
import sys
print(f"Versión de Python: {sys.version}")
print(f"Ubicación de Python: {sys.executable}")

Paso 3: Guardar y ejecutar

  1. Guarda (Ctrl+X, luego Y, luego Enter)
  2. Ejecuta: python3 prueba.py

Si ves la información de tu instalación, ¡todo está listo!

Comandos útiles para recordar

# 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

Siguiente paso

Ahora que tienes Python funcionando en macOS, es hora de elegir un buen editor de código.

👉 Continúa con: Editores e IDEs Recomendados


🍎 Resumen para macOS: Usa Homebrew (brew install python) o descarga desde python.org. Recuerda usar python3 y considera crear aliases para mayor comodidad.

Instalación de Python en Linux

¡Genial! Usas Linux. Eres oficialmente un usuario avanzado 🐧. La buena noticia es que Linux y Python se llevan muy bien.

¿Ya tienes Python?

La mayoría de las distribuciones de Linux vienen con Python preinstalado. Vamos a verificar:

python3 --version

Probablemente veas algo como:

Python 3.9.2

Si tienes Python 3.8 o superior, técnicamente puedes usarlo. Pero te recomiendo instalar la versión más reciente.

Instalación por distribución

Ubuntu/Debian y derivados 📦

Actualizar el sistema

sudo apt update
sudo apt upgrade

Instalar Python 3.11 (o la más reciente)

# Agregar el repositorio deadsnakes (para versiones más nuevas)
sudo apt install software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update

# Instalar Python 3.11
sudo apt install python3.11 python3.11-venv python3.11-pip

# Instalar herramientas de desarrollo
sudo apt install python3.11-dev build-essential

Verificar la instalación

python3.11 --version

Fedora/CentOS/RHEL 🎩

Fedora (DNF)

# Actualizar el sistema
sudo dnf update

# Instalar Python 3.11
sudo dnf install python3.11 python3.11-pip python3.11-devel

# Herramientas de desarrollo
sudo dnf groupinstall "Development Tools"

CentOS/RHEL (YUM)

# Habilitar EPEL
sudo yum install epel-release

# Instalar Python 3.11
sudo yum install python311 python311-pip python311-devel

# Herramientas de desarrollo
sudo yum groupinstall "Development Tools"

Arch Linux/Manjaro 🏹

# Actualizar el sistema
sudo pacman -Syu

# Instalar Python
sudo pacman -S python python-pip

# Herramientas de desarrollo
sudo pacman -S base-devel

openSUSE 🦎

# Actualizar el sistema
sudo zypper refresh
sudo zypper update

# Instalar Python
sudo zypper install python311 python311-pip python311-devel

# Herramientas de desarrollo
sudo zypper install -t pattern devel_basis

Compilar desde código fuente (método universal)

Si tu distribución no tiene Python 3.11+ en los repositorios:

Paso 1: Instalar dependencias

Ubuntu/Debian:

sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev

Fedora:

sudo dnf groupinstall "Development Tools"
sudo dnf install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel expat-devel

Paso 2: Descargar y compilar

# 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.

Paso 3: Verificar

python3.11 --version

Configurar aliases y PATH

Crear aliases útiles

Edita tu archivo de configuración de shell:

Para Bash:

nano ~/.bashrc

Para Zsh:

nano ~/.zshrc

Agrega estas líneas:

alias python=python3.11
alias pip=pip3.11

Recarga la configuración:

source ~/.bashrc  # o ~/.zshrc

Verificar pip

python3.11 -m pip --version

Si pip no está instalado:

# Ubuntu/Debian
sudo apt install python3.11-pip

# Fedora
sudo dnf install python3.11-pip

# Método universal
python3.11 -m ensurepip --upgrade

Entornos virtuales

En Linux es especialmente importante usar entornos virtuales para no interferir con el Python del sistema:

Instalar venv

# Ubuntu/Debian
sudo apt install python3.11-venv

# Fedora
sudo dnf install python3.11-venv

Crear un entorno virtual

python3.11 -m venv mi_entorno
source mi_entorno/bin/activate

Para desactivar:

deactivate

Problemas comunes en Linux

“python3.11: command not found”

Problema: Python no está en el PATH Solución: Usa la ruta completa /usr/local/bin/python3.11 o crea un symlink

Problemas de permisos con pip

Problema: “Permission denied” al instalar paquetes Solución: Usa --user o entornos virtuales

pip3.11 install --user nombre_paquete

Falta librerías de desarrollo

Problema: Error al compilar paquetes Solución: Instala las herramientas de desarrollo mencionadas arriba

Conflicto con Python del sistema

Problema: Múltiples versiones de Python Solución: Usa versiones específicas (python3.11) y entornos virtuales

Gestión avanzada con pyenv

Para manejar múltiples versiones de Python fácilmente:

Instalar pyenv

# Clonar pyenv
git clone https://github.com/pyenv/pyenv.git ~/.pyenv

# Configurar PATH
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc

# Recargar
source ~/.bashrc

Usar pyenv

# Ver versiones disponibles
pyenv install --list

# Instalar Python 3.11.5
pyenv install 3.11.5

# Usar globalmente
pyenv global 3.11.5

# Verificar
python --version

Verificación final

Crea un archivo de prueba:

# Crear el archivo
cat > prueba.py << EOF
print("¡Python funciona perfectamente en Linux!")
import sys
print(f"Versión de Python: {sys.version}")
print(f"Ubicación de Python: {sys.executable}")
print(f"Distribución: {sys.platform}")
EOF

# Ejecutar
python3.11 prueba.py

Comandos útiles para Linux

# Verificar versión
python3.11 --version

# Verificar ubicación
which python3.11

# Ver información del sistema
python3.11 -c "import sys; print(sys.version_info)"

# Instalar paquete para el usuario
pip3.11 install --user paquete

# Crear entorno virtual
python3.11 -m venv nombre_entorno

# Activar entorno virtual
source nombre_entorno/bin/activate

# Ver paquetes instalados
pip3.11 list

Distribuciones específicas

WSL (Windows Subsystem for Linux)

Si usas WSL, sigue las instrucciones de Ubuntu/Debian. Todo funciona igual.

Raspberry Pi OS

sudo apt update
sudo apt install python3.11 python3.11-pip python3.11-venv

Alpine Linux

apk add python3 py3-pip

Siguiente paso

Ahora que tienes Python funcionando en Linux, es hora de elegir un buen editor de código.

👉 Continúa con: Editores e IDEs Recomendados


🐧 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.

Editores e IDEs Recomendados

¡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.

¿Editor o IDE? ¿Cuál es la diferencia?

Editor de código

  • Más simple y ligero
  • Se enfoca en escribir código
  • Rápido de abrir y usar
  • Perfecto para empezar

IDE (Entorno de Desarrollo Integrado)

  • Más completo y robusto
  • Incluye muchas herramientas integradas
  • Puede ser abrumador para principiantes
  • Perfecto cuando ya tienes experiencia

Recomendaciones por nivel

🌱 Para principiantes absolutos

1. Visual Studio Code (VS Code)MÁS RECOMENDADO

¿Por qué es perfecto para empezar?

  • Gratuito y de código abierto
  • Muy fácil de usar
  • Excelente soporte para Python
  • Funciona en Windows, Mac y Linux
  • Comunidad gigante

Instalación:

  1. Busca “Visual Studio Code” en tu navegador o ve directamente a Microsoft Store/App Store
  2. Descarga para tu sistema operativo (Windows, Mac, Linux)
  3. Instala normalmente siguiendo el asistente

Configuración para Python:

  1. Abre VS Code
  2. Ve a Extensions (Ctrl+Shift+X)
  3. Busca “Python”
  4. Instala la extensión oficial de Microsoft
  5. ¡Listo!

Características que te van a encantar:

  • Resalta tu código con colores
  • Te sugiere mientras escribes
  • Detecta errores antes de ejecutar
  • Terminal integrada
  • Explorador de archivos

2. Thonny 🐍 PERFECTO PARA APRENDER

¿Por qué es genial para principiantes?

  • Diseñado específicamente para aprender Python
  • Interfaz súper simple
  • Debugger visual (puedes ver cómo se ejecuta tu código paso a paso)
  • Viene con Python incluido

Instalación:

  1. Ve a thonny.org
  2. Descarga para tu sistema
  3. Instala y ¡ya está listo para usar!

Perfecto si:

  • Nunca has programado antes
  • Quieres entender cómo funciona tu código
  • Prefieres algo súper simple

🌿 Para usuarios con algo de experiencia

3. Sublime TextSÚPER RÁPIDO

¿Por qué es genial?

  • Extremadamente rápido
  • Interfaz elegante y minimalista
  • Muy personalizable
  • Excelente para archivos grandes

Instalación:

  1. Ve a sublimetext.com
  2. Descarga la versión 4
  3. Instala normalmente

Nota: Es de pago, pero puedes usarlo gratis indefinidamente (solo aparece un recordatorio ocasional).

4. Atom (Descontinuado pero aún funcional)

GitHub descontinuó Atom, pero si ya lo tienes instalado, sigue funcionando bien.

🌳 Para usuarios avanzados

5. PyCharm 🚀 EL MÁS COMPLETO

¿Por qué es increíble?

  • IDE profesional específico para Python
  • Herramientas de debugging avanzadas
  • Refactoring inteligente
  • Integración con Git
  • Soporte para frameworks web

Versiones:

  • PyCharm Community (gratuito) - Perfecto para empezar
  • PyCharm Professional (de pago) - Para desarrollo web y científico

Instalación:

  1. Ve a jetbrains.com/pycharm
  2. Descarga Community Edition
  3. Instala siguiendo el asistente

Advertencia: Puede ser abrumador para principiantes.

6. Vim/Neovim 🤓 PARA NINJAS

Si ya usas Vim, puedes configurarlo para Python. Pero si no sabes qué es Vim, ¡ignora esta opción por ahora!

Comparación rápida

EditorDificultadVelocidadCaracterísticasPrecio
VS Code🟢 Fácil🟡 Buena🟢 MuchasGratis
Thonny🟢 Muy fácil🟡 Buena🟡 BásicasGratis
Sublime🟡 Media🟢 Excelente🟡 Buenas$99
PyCharm🔴 Difícil🟡 Buena🟢 ExcelentesGratis/Pago

Mi recomendación personal

Si nunca has programado: Thonny

  • Súper fácil de usar
  • Te ayuda a entender cómo funciona Python
  • No te abruma con opciones

Si tienes algo de experiencia con computadoras: VS Code

  • Balance perfecto entre simplicidad y potencia
  • Vas a poder usarlo para otros lenguajes también
  • Comunidad enorme = muchos tutoriales

Si ya eres programador: PyCharm Community

  • Herramientas profesionales
  • Te hace más productivo
  • Específico para Python

Configuración básica de VS Code para Python

Como VS Code es el más popular, aquí tienes una configuración básica:

Extensiones esenciales:

  1. Python (Microsoft) - Soporte básico para Python
  2. Pylance (Microsoft) - Análisis de código avanzado
  3. Python Docstring Generator - Para documentar tu código
  4. autoDocstring - Genera documentación automáticamente

Configuración recomendada:

  1. Abre VS Code
  2. Ve a File > Preferences > Settings (o Ctrl+,)
  3. Busca estas configuraciones:
{
    "python.defaultInterpreterPath": "python3",
    "python.linting.enabled": true,
    "python.linting.pylintEnabled": true,
    "python.formatting.provider": "black",
    "editor.formatOnSave": true,
    "editor.tabSize": 4,
    "editor.insertSpaces": true
}

Características importantes que buscar

1. Resaltado de sintaxis

Tu código debe verse con colores diferentes para palabras clave, strings, comentarios, etc.

2. Autocompletado

El editor debe sugerirte mientras escribes.

3. Detección de errores

Debe subrayar errores antes de que ejecutes el código.

4. Terminal integrada

Para ejecutar tu código sin salir del editor.

5. Explorador de archivos

Para navegar entre tus archivos fácilmente.

Editores que NO recomiendo para empezar

Notepad/Bloc de notas

  • No tiene resaltado de sintaxis
  • No detecta errores
  • Muy básico

Word/LibreOffice Writer

  • Son procesadores de texto, no editores de código
  • Agregan formato que rompe el código

IDEs muy complejos

  • Eclipse con PyDev
  • NetBeans
  • Son muy complicados para empezar

Configuración del primer proyecto

Una vez que elijas tu editor, vamos a configurar tu primer proyecto:

Paso 1: Crear una carpeta

Mi_Primer_Proyecto_Python/
|-- main.py
|-- ejercicios/
|-- notas.txt

Paso 2: Abrir en tu editor

  • VS Code: File > Open Folder
  • Thonny: File > Open
  • PyCharm: File > Open

Paso 3: Crear tu primer archivo

  1. Crea un archivo llamado hola.py
  2. Escribe:
print("¡Hola, mundo!")
print("Mi primer programa en Python")
  1. Guarda el archivo
  2. Ejecuta desde la terminal: python hola.py

Atajos de teclado útiles

VS Code:

  • Ctrl+Shift+P - Paleta de comandos
  • Ctrl+ ` - Abrir terminal
  • F5 - Ejecutar código
  • Ctrl+/ - Comentar/descomentar línea

Thonny:

  • F5 - Ejecutar código
  • Ctrl+D - Debugger paso a paso
  • Ctrl+/ - Comentar línea

Próximos pasos

Una vez que tengas tu editor configurado:

  1. Practica escribiendo código simple
  2. Aprende los atajos básicos
  3. Explora las características poco a poco
  4. No te abrumes con plugins al principio

Resumen de recomendaciones

🥇 Primera opción: VS Code

  • Perfecto balance para principiantes
  • Gratis y muy popular
  • Crecerás con él

🥈 Segunda opción: Thonny

  • Si quieres algo súper simple
  • Perfecto para aprender conceptos
  • Cambiarás después a algo más avanzado

🥉 Tercera opción: PyCharm Community

  • Si ya tienes experiencia programando
  • Muy completo pero puede abrumar

¿Qué sigue?

¡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.

👉 Continúa con: Capítulo 3 - Sintaxis de Python y Tu Primer Programa


💡 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!

Capítulo 3: Sintaxis de Python y Tu Primer Programa

¡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.

¿Qué hace especial a Python?

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!

Tu primer programa (de verdad)

Vamos a empezar con algo simple pero significativo. Abre tu editor y crea un archivo llamado mi_primer_programa.py:

print("¡Hola, mundo!")
print("Mi nombre es [tu nombre aquí]")
print("Estoy aprendiendo Python")
print("¡Y me está gustando mucho!")

Guarda el archivo y ejecútalo desde la terminal:

python mi_primer_programa.py

¡Felicidades! Acabas de escribir y ejecutar tu primer programa en Python. 🎉

Las reglas básicas de Python

1. Python es sensible a mayúsculas y minúsculas

# Estas son variables DIFERENTES:
nombre = "Juan"
Nombre = "María"
NOMBRE = "Pedro"

print(nombre)  # Imprime: Juan
print(Nombre)  # Imprime: María
print(NOMBRE)  # Imprime: Pedro

2. La indentación es SÚPER importante

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.

3. Una instrucción por línea

# ✅ CORRECTO
print("Primera línea")
print("Segunda línea")

# ❌ Evita esto (aunque funciona)
print("Primera línea"); print("Segunda línea")

4. Los comentarios empiezan con #

# 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

Probando la sintaxis básica

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!")

Ejecuta este programa y observa cómo funciona:

python sintaxis_basica.py

Entendiendo los errores de sintaxis

Los errores son normales y útiles. Python te dice exactamente qué está mal:

Error de indentación:

# ❌ Código con error
if True:
print("Hola")  # Falta indentación

Error que verás:

IndentationError: expected an indented block

Solución:

# ✅ Código corregido
if True:
    print("Hola")  # Ahora sí está indentado

Error de sintaxis:

# ❌ Código con error
print("Hola mundo"  # Falta cerrar el paréntesis

Error que verás:

SyntaxError: unexpected EOF while parsing

Solución:

# ✅ Código corregido
print("Hola mundo")  # Paréntesis cerrado

Diferentes formas de ejecutar código Python

1. Archivos .py (lo que hemos estado haciendo)

python mi_programa.py

2. Intérprete interactivo

python
>>> print("Hola desde el intérprete")
Hola desde el intérprete
>>> exit()

3. Desde tu editor (si tiene esta función)

  • En VS Code: presiona F5
  • En Thonny: presiona F5
  • En PyCharm: presiona Shift+F10

Ejercicio práctico: Tu tarjeta de presentación

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:

  1. Cambia los valores de las variables por tu información real
  2. Agrega más información si quieres (país, comida favorita, etc.)
  3. Experimenta con diferentes mensajes

Buenas prácticas de escritura

1. Usa nombres descriptivos

# ✅ BUENO
nombre_usuario = "Juan"
edad_en_años = 25

# ❌ MALO
n = "Juan"
x = 25

2. Agrega comentarios útiles

# ✅ BUENO
# Calcular el precio con descuento del 10%
precio_final = precio_original * 0.9

# ❌ MALO
# Multiplicar por 0.9
precio_final = precio_original * 0.9

3. Usa espacios para mayor legibilidad

# ✅ BUENO
resultado = (a + b) * c

# ❌ MALO (funciona, pero es difícil de leer)
resultado=(a+b)*c

4. Organiza tu código con líneas en blanco

# Información del usuario
nombre = "Juan"
edad = 25

# Procesar información
if edad >= 18:
    print("Mayor de edad")

# Mensaje final
print("¡Hasta luego!")

Herramientas útiles en tu editor

VS Code:

  • Ctrl + / - Comentar/descomentar líneas
  • Shift + Alt + F - Formatear código automáticamente
  • F5 - Ejecutar programa

Thonny:

  • Ctrl + / - Comentar líneas
  • F5 - Ejecutar programa
  • Ctrl + D - Debugger (muy útil para aprender)

Ejercicios para practicar

Ejercicio 1: Calculadora de presentación

Crea un programa que:

  1. Muestre un título bonito
  2. Defina dos números
  3. Muestre la suma, resta, multiplicación y división
  4. Use comentarios para explicar cada parte

Ejercicio 2: Historia personal

Crea un programa que cuente una historia corta usando:

  1. Al menos 5 líneas de print()
  2. Variables para nombres y lugares
  3. Comentarios explicando la historia
  4. Buena indentación y formato

Ejercicio 3: Detector de errores

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"

Resumen del capítulo

En este capítulo aprendiste:

  • ✅ Las reglas básicas de sintaxis de Python
  • ✅ La importancia de la indentación
  • ✅ Cómo escribir y ejecutar programas .py
  • ✅ Diferentes formas de ejecutar código Python
  • ✅ Cómo leer y entender errores de sintaxis
  • ✅ Buenas prácticas de escritura de código
  • ✅ Herramientas útiles en tu editor

¿Qué sigue?

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!

Reglas de Sintaxis de Python

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 regla más importante: Indentación

¿Qué es la indentación?

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")

¿Por qué es importante?

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

Reglas de indentación:

  1. Usa siempre la misma cantidad de espacios
# ✅ CORRECTO - siempre 4 espacios
if True:
    print("Línea 1")
    print("Línea 2")

# ❌ INCORRECTO - mezcla 4 y 2 espacios
if True:
    print("Línea 1")    # 4 espacios
  print("Línea 2")      # 2 espacios - ¡Error!
  1. La recomendación oficial es 4 espacios
# ✅ Recomendado
if True:
    print("Usando 4 espacios")

# ✅ Funciona, pero no es recomendado
if True:
  print("Usando 2 espacios")
  1. No mezcles espacios y tabs
# ❌ NUNCA hagas esto
if True:
    print("Esta línea usa espacios")
	print("Esta línea usa tab")  # ¡Error!

Sensibilidad a mayúsculas y minúsculas

Python distingue entre mayúsculas y minúsculas en todo:

Variables:

nombre = "Juan"
Nombre = "María"
NOMBRE = "Pedro"

print(nombre)  # Juan
print(Nombre)  # María
print(NOMBRE)  # Pedro

Funciones:

print("Hola")    # ✅ Correcto
Print("Hola")    # ❌ Error: Print no existe
PRINT("Hola")    # ❌ Error: PRINT no existe

Palabras clave:

if True:         # ✅ Correcto
    print("Sí")

If True:         # ❌ Error: If no es una palabra clave
    print("No")

Estructura de líneas

Una instrucción por línea (recomendado):

# ✅ Fácil de leer
print("Primera línea")
print("Segunda línea")
print("Tercera línea")

Múltiples instrucciones en una línea (evítalo):

# ❌ Funciona, pero es difícil de leer
print("Línea 1"); print("Línea 2"); print("Línea 3")

Líneas muy largas (cómo dividirlas):

# ❌ 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"

Espacios en blanco

Espacios alrededor de operadores:

# ✅ Fácil de leer
resultado = a + b * c
nombre = "Juan"

# ❌ Funciona, pero es difícil de leer
resultado=a+b*c
nombre="Juan"

Espacios después de comas:

# ✅ Correcto
print("Hola", "mundo", "desde", "Python")

# ❌ Funciona, pero es menos legible
print("Hola","mundo","desde","Python")

NO uses espacios innecesarios:

# ✅ Correcto
print("Hola")
lista[0]

# ❌ Espacios innecesarios
print ( "Hola" )
lista [ 0 ]

Nombres válidos en Python

Reglas para nombres de variables y funciones:

  1. Deben empezar con letra o guión bajo
# ✅ 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
  1. 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
  1. 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

Convenciones de nombres (no son reglas, pero es buena práctica):

# ✅ Variables y funciones: snake_case (minúsculas con guiones bajos)
mi_nombre = "Juan"
edad_usuario = 25

# ✅ Constantes: MAYÚSCULAS
PI = 3.14159
VELOCIDAD_LUZ = 299792458

# ✅ Clases: PascalCase (lo veremos más adelante)
MiClase = "ejemplo"

Caracteres especiales

Comillas para texto:

# Todas estas formas son válidas
nombre1 = "Juan"
nombre2 = 'María'
mensaje = """Este es un mensaje
que ocupa varias líneas"""

Paréntesis para funciones:

print("Hola")      # Los paréntesis son obligatorios
len("Python")      # Incluso si no hay argumentos: len()

Dos puntos para bloques de código:

# Los dos puntos son obligatorios antes de un bloque indentado
if edad >= 18:     # ← Dos puntos aquí
    print("Mayor de edad")

Ejercicio práctico: Detector de errores de sintaxis

Aquí tienes varios ejemplos con errores. ¿Puedes encontrarlos?

# Ejemplo 1
if True
    print("Hola")

# Ejemplo 2
if True:
print("Hola")

# Ejemplo 3
Print("Hola mundo")

# Ejemplo 4
mi-nombre = "Juan"

# Ejemplo 5
if True:
    print("Línea 1")
  print("Línea 2")

Soluciones:

# 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")

Consejos para evitar errores de sintaxis

1. Configura tu editor correctamente

  • Haz que muestre espacios y tabs
  • Configura indentación automática de 4 espacios
  • Activa el resaltado de sintaxis

2. Lee los mensajes de error

Python te dice exactamente dónde está el problema:

  File "mi_programa.py", line 3
    print("Hola")
    ^
IndentationError: expected an indented block

3. Usa un estilo consistente

  • Siempre 4 espacios para indentar
  • Espacios alrededor de operadores
  • Nombres descriptivos en snake_case

4. Practica regularmente

La sintaxis se vuelve natural con la práctica. ¡No te desanimes si al principio cometes errores!

Resumen de reglas importantes

Regla✅ Correcto❌ Incorrecto
Indentaciónif True:
    print("Hola")
if True:
print("Hola")
Mayúsculasprint("Hola")Print("Hola")
Dos puntosif True:if True
Nombresmi_variable = 5mi-variable = 5
Espaciosa + ba+b

💡 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!

Comentarios y Documentación

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.

¿Por qué son importantes los comentarios?

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?” 😅

Ejemplo sin comentarios:

x = 1000
y = x * 0.16
z = x + y
print(z)

El mismo ejemplo con comentarios:

# 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

¿Cuál es más fácil de entender? ¡Exacto!

Tipos de comentarios en Python

1. Comentarios de una línea

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á")

2. Comentarios de múltiples líneas

Python no tiene comentarios de múltiples líneas como otros lenguajes, pero puedes usar varias líneas con #:

# Este es un comentario
# que ocupa varias líneas
# para explicar algo complejo
print("Hola")

3. Docstrings (cadenas de documentación)

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")

Cuándo y cómo usar comentarios

✅ Buenos comentarios:

1. Explican el “por qué”, no el “qué”

# ✅ 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

2. Explican lógica compleja

# ✅ 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()

3. Advierten sobre cosas importantes

# ✅ 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()

4. Explican decisiones de diseño

# ✅ 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"}
}

❌ Malos comentarios:

1. Comentarios obvios

# ❌ MALO - es obvio lo que hace
edad = 25  # Asignar 25 a la variable edad
print(edad)  # Imprimir la variable edad

2. Comentarios desactualizados

# ❌ MALO - el comentario no coincide con el código
# Calcular descuento del 10%
descuento = precio * 0.20  # ¡En realidad es 20%!

3. Comentarios que insultan

# ❌ MALO - no seas negativo
# Este código es horrible pero funciona
# TODO: Reescribir esta porquería

Comentarios para organizar tu código

Secciones del programa:

# ================================
# CONFIGURACIÓN INICIAL
# ================================
nombre_usuario = "Juan"
edad_usuario = 25

# ================================
# PROCESAMIENTO DE DATOS
# ================================
if edad_usuario >= 18:
    print("Mayor de edad")

# ================================
# RESULTADOS FINALES
# ================================
print("Programa terminado")

Separadores visuales:

# --- Datos del usuario ---
nombre = "Juan"
edad = 25

# --- Validaciones ---
if edad >= 18:
    print("Válido")

# --- Fin del programa ---
print("Terminado")

Comentarios TODO y FIXME

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

Documentando tu primer programa

Vamos a tomar un programa simple y documentarlo correctamente:

Antes (sin documentación):

n = input("Nombre: ")
e = int(input("Edad: "))
if e >= 18:
    print("Hola", n, "eres mayor")
else:
    print("Hola", n, "eres menor")

Después (bien documentado):

"""
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í

Comentarios en diferentes idiomas

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.

Herramientas para comentarios

En VS Code:

  • Ctrl + / - Comentar/descomentar líneas seleccionadas
  • Shift + Alt + A - Comentario de bloque
  • Las extensiones pueden resaltar TODO, FIXME, etc.

En Thonny:

  • Ctrl + / - Comentar/descomentar
  • Ctrl + Shift + / - Comentario de bloque

Ejercicios prácticos

Ejercicio 1: Documenta este código

p = float(input("Precio: "))
d = float(input("Descuento %: "))
df = p * (d / 100)
pf = p - df
print("Precio final:", pf)

Ejercicio 2: Crea un programa documentado

Escribe un programa que:

  1. Tenga un docstring al inicio
  2. Pida dos números al usuario
  3. Calcule y muestre la suma, resta, multiplicación y división
  4. Use comentarios para explicar cada sección
  5. Use nombres de variables descriptivos

Ejercicio 3: Encuentra los problemas

¿Qué está mal con estos comentarios?

# 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

Buenas prácticas para comentarios

1. Escribe comentarios mientras programas

No los dejes para después - es fácil olvidar qué estabas pensando.

2. Mantén los comentarios actualizados

Si cambias el código, actualiza los comentarios.

3. Usa un estilo consistente

Decide un formato y úsalo en todo tu programa.

4. No comentes código obvio

Si el código es claro, no necesita comentarios.

5. Explica el contexto

¿Por qué tomaste esa decisión? ¿Qué problema resuelve?

Plantilla para tus programas

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

Resumen

Los comentarios son esenciales para:

  • ✅ Explicar código complejo
  • ✅ Documentar decisiones de diseño
  • ✅ 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!

Ejecutar Código Python

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!

Método 1: Archivos .py (El más común)

Esta es la forma que hemos estado usando. Escribes tu código en un archivo con extensión .py y lo ejecutas desde la terminal.

Paso a paso:

  1. Crear el archivo
# archivo: mi_programa.py
print("¡Hola desde un archivo Python!")
nombre = "Estudiante"
print(f"Bienvenido, {nombre}")
  1. Ejecutar desde la terminal
python mi_programa.py
  1. Resultado
¡Hola desde un archivo Python!
Bienvenido, Estudiante

Ventajas:

  • ✅ Puedes guardar y reutilizar tu código
  • ✅ Perfecto para programas largos
  • ✅ Fácil de compartir con otros
  • ✅ Puedes usar control de versiones (Git)

Cuándo usarlo:

  • Programas que vas a usar varias veces
  • Código que quieres guardar
  • Proyectos serios

Método 2: Intérprete Interactivo (REPL)

REPL significa “Read-Eval-Print Loop” (Leer-Evaluar-Imprimir-Repetir). Es perfecto para probar cosas rápidamente.

Cómo acceder:

python

Verás algo así:

Python 3.11.5 (main, Aug 24 2023, 15:18:16)
Type "help", "copyright", "credits" or "license" for more information.
>>>

Ejemplo de uso:

>>> 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()

Ventajas:

  • ✅ Resultados inmediatos
  • ✅ Perfecto para experimentar
  • ✅ Ideal para cálculos rápidos
  • ✅ Puedes probar funciones antes de usarlas en tu programa

Cuándo usarlo:

  • Probar una función nueva
  • Hacer cálculos rápidos
  • Experimentar con código
  • Aprender cómo funciona algo

Método 3: Desde tu Editor/IDE

La mayoría de los editores modernos pueden ejecutar Python directamente.

VS Code:

  1. Método 1: Botón de Play

    • Abre tu archivo .py
    • Haz clic en el triángulo ▶️ en la esquina superior derecha
  2. Método 2: Atajo de teclado

    • Presiona F5
    • O Ctrl + F5 para ejecutar sin debugger
  3. Método 3: Terminal integrada

    • Presiona `Ctrl + `` (backtick) para abrir terminal
    • Ejecuta: python nombre_archivo.py

Thonny:

  1. Presiona F5 o haz clic en el botón “Run”
  2. El resultado aparece en la parte inferior
  3. Puedes usar el debugger para ver paso a paso

PyCharm:

  1. Clic derecho en el archivo → “Run”
  2. Shift + F10 para ejecutar
  3. Shift + F9 para ejecutar con debugger

Ventajas:

  • ✅ No necesitas cambiar de ventana
  • ✅ Integración con debugger
  • ✅ Fácil acceso a herramientas
  • ✅ Resaltado de errores

Método 4: Línea de comandos con -c

Puedes ejecutar código Python directamente desde la terminal sin crear un archivo:

python -c "print('Hola desde la línea de comandos')"
python -c "
import math
print(f'El valor de pi es: {math.pi}')
print(f'La raíz cuadrada de 16 es: {math.sqrt(16)}')
"

Ventajas:

  • ✅ Súper rápido para comandos simples
  • ✅ Útil en scripts de automatización
  • ✅ No crea archivos temporales

Cuándo usarlo:

  • Comandos de una línea
  • Scripts de automatización
  • Cálculos rápidos en la terminal

Método 5: Jupyter Notebooks (Avanzado)

Los notebooks son documentos interactivos que mezclan código, texto y resultados. Son muy populares en ciencia de datos.

Instalación:

pip install jupyter

Uso:

jupyter notebook

Ejemplo de celda:

# Celda 1
print("Esta es la primera celda")
x = 10

# Celda 2
print(f"El valor de x es: {x}")
y = x * 2
print(f"El doble de x es: {y}")

Ventajas:

  • ✅ Perfecto para análisis de datos
  • ✅ Combina código, texto y gráficos
  • ✅ Fácil de compartir resultados
  • ✅ Ideal para experimentación

Método 6: Scripts ejecutables (Linux/Mac)

Puedes hacer que tus archivos Python se ejecuten directamente como programas:

Paso 1: Agregar shebang

#!/usr/bin/env python3
print("¡Este script se ejecuta directamente!")

Paso 2: Hacer ejecutable

chmod +x mi_script.py

Paso 3: Ejecutar

./mi_script.py

Comparación de métodos

MétodoVelocidadPermanenciaUso típico
Archivos .pyMedia✅ PermanenteProgramas completos
Intérprete✅ Rápida❌ TemporalPruebas rápidas
Editor/IDEMedia✅ PermanenteDesarrollo
Línea -c✅ Rápida❌ TemporalComandos simples
JupyterMedia✅ PermanenteAnálisis de datos
EjecutableMedia✅ PermanenteScripts del sistema

Consejos para cada método

Para archivos .py:

# Siempre incluye esta línea al final de tus scripts principales
if __name__ == "__main__":
    # Tu código principal aquí
    print("Ejecutando programa principal")

Para el intérprete:

# 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)

Para editores:

  • Configura atajos de teclado personalizados
  • Usa el debugger para entender tu código
  • Aprovecha las extensiones de Python

Ejercicios prácticos

Ejercicio 1: Prueba todos los métodos

Crea este programa simple y ejecútalo de 3 formas diferentes:

# programa_prueba.py
import math

print("=== Calculadora Básica ===")
numero = 16
print(f"Número: {numero}")
print(f"Raíz cuadrada: {math.sqrt(numero)}")
print(f"Cuadrado: {numero ** 2}")
print("=== Fin ===")
  1. Guárdalo como archivo y ejecútalo con python programa_prueba.py
  2. Copia el código línea por línea en el intérprete interactivo
  3. Ejecútalo desde tu editor favorito

Ejercicio 2: Intérprete interactivo

Usa el intérprete para:

  1. Calcular cuántos segundos hay en un año
  2. Convertir 100 grados Fahrenheit a Celsius
  3. Crear una lista con los nombres de 3 amigos
  4. Mostrar el tercer nombre de la lista

Ejercicio 3: Línea de comandos

Ejecuta estos comandos desde la terminal:

python -c "print('Hola mundo')"
python -c "import datetime; print(f'Hoy es: {datetime.date.today()}')"
python -c "print(' '.join(['Python', 'es', 'genial']))"

Problemas comunes y soluciones

“python no se reconoce como comando”

Problema: Windows no encuentra Python Solución:

  • Reinstala Python marcando “Add to PATH”
  • O usa py en lugar de python

“No such file or directory”

Problema: El archivo no existe o estás en el directorio incorrecto Solución:

# Verificar dónde estás
pwd

# Listar archivos
ls  # En Linux/Mac
dir  # En Windows

# Cambiar directorio
cd ruta/a/tu/archivo

El programa no hace nada

Problema: Puede que tengas errores de sintaxis Solución:

  • Revisa la indentación
  • Verifica que no falten dos puntos :
  • Lee el mensaje de error completo

“ModuleNotFoundError”

Problema: Intentas importar algo que no está instalado Solución:

pip install nombre_del_modulo

Flujo de trabajo recomendado

Para principiantes, te recomiendo este flujo:

  1. Experimenta en el intérprete - Prueba ideas rápidas
  2. Escribe en archivos .py - Para código que quieres guardar
  3. Ejecuta desde tu editor - Para desarrollo cómodo
  4. Usa la terminal - Para ejecutar programas finales

Resumen

Ahora conoces 6 formas de ejecutar código Python:

  • Archivos .py - Para programas completos
  • Intérprete interactivo - Para pruebas rápidas
  • Editor/IDE - Para desarrollo cómodo
  • Línea de comandos (-c) - Para comandos simples
  • Jupyter Notebooks - Para análisis de datos
  • Scripts ejecutables - Para automatización

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!

Capítulo 4: Variables y Tipos de Datos

¡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.

La analogía del almacén con cajas 📦

💭 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 azul pequeña → Solo números enteros (como 5, 10, -3)
  • 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

¿Qué son las variables?

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.

Ejemplo práctico:

# 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!

Creando tu primera variable

En Python, crear una variable es súper fácil:

# Sintaxis: nombre_variable = valor
mi_nombre = "Tu nombre aquí"

Es como tomar una caja, ponerle una etiqueta que dice “mi_nombre”, y meter dentro el texto “Tu nombre aquí”.

Ejemplos de diferentes tipos de cajas:

# Caja para números enteros (int)
edad = 20
puntos = 1500
temperatura = -5

# Caja para números decimales (float)
altura = 1.75
precio = 29.99
pi = 3.14159

# Caja para texto (str)
nombre = "Ana"
mensaje = "¡Hola mundo!"
email = "ana@email.com"

# Caja para verdadero/falso (bool)
es_mayor_edad = True
tiene_mascota = False
esta_lloviendo = True

🧱 Tipos de datos elementales y compuestos

💭 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.

Tipos Elementales (Los ladrillos básicos)

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:

1. int (Enteros) - La caja azul 🔵

Guarda números enteros (sin decimales):

edad = 25
año_nacimiento = 1998
temperatura = -10
puntos_juego = 0

print(f"La edad es {edad} y su tipo es: {type(edad)}")  # <class 'int'>

Ejemplos de uso:

  • Edad de una persona
  • Cantidad de productos
  • Año
  • Puntuación en un juego

2. float (Decimales) - La caja verde 🟢

Guarda números con decimales:

altura = 1.75
precio = 19.99
temperatura = 36.5
pi = 3.14159

print(f"La altura es {altura} y su tipo es: {type(altura)}")  # <class 'float'>

Ejemplos de uso:

  • Precios
  • Medidas (altura, peso, distancia)
  • Porcentajes
  • Constantes matemáticas

3. str (Cadenas de texto) - La caja amarilla 🟡

Guarda texto (siempre entre comillas):

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'>

Ejemplos de uso:

  • Nombres y apellidos
  • Direcciones
  • Mensajes
  • Cualquier texto

4. bool (Booleanos) - La caja roja 🔴

Guarda solo dos valores: True o False:

es_estudiante = True
tiene_trabajo = False
esta_casado = True
es_fin_semana = False

print(f"¿Es estudiante? {es_estudiante} - Tipo: {type(es_estudiante)}")  # <class 'bool'>

Ejemplos de uso:

  • Estados (encendido/apagado)
  • Condiciones (sí/no)
  • Permisos (permitido/no permitido)
  • Cualquier cosa que sea verdadero o falso

5. NoneType - La caja vacía ⚪

Representa “nada” o “vacío”:

resultado = None
valor_inicial = None

print(f"El resultado es {resultado} y su tipo es: {type(resultado)}")  # <class 'NoneType'>

Tipos Compuestos (Las estructuras construidas)

💭 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

Intercambiando contenido entre cajas

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?

Ejemplo práctico: Perfil de usuario

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?

Reglas para nombrar cajas (variables)

Así como no puedes poner cualquier etiqueta en una caja, Python tiene reglas para nombrar variables:

✅ Nombres válidos:

nombre = "Juan"
edad_usuario = 25
mi_variable = 100
_variable_privada = "secreto"
variable2 = "segunda"
CONSTANTE = 3.14159

❌ Nombres inválidos:

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

🎯 Buenas prácticas para nombres:

1. Usa nombres descriptivos

# ✅ 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

2. Usa snake_case (guiones bajos)

# ✅ BUENO - estilo Python
nombre_completo = "Ana García"
fecha_nacimiento = "1995-05-15"
es_mayor_edad = True

# ❌ MALO - no es el estilo de Python
nombreCompleto = "Ana García"      # camelCase
NombreCompleto = "Ana García"      # PascalCase

3. Sé consistente

# ✅ BUENO - mismo patrón
nombre_usuario = "Ana"
apellido_usuario = "García"
email_usuario = "ana@email.com"

# ❌ MALO - patrones mezclados
nombre_usuario = "Ana"
apellidoUsuario = "García"
EMAIL = "ana@email.com"

💭 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.

Viendo qué hay dentro de las cajas

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.

Ejercicio práctico: Tu información personal

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:

  1. Copia este código
  2. Cambia los valores por tu información real
  3. Ejecuta el programa
  4. Añade al menos 3 variables más con diferentes tipos de datos
  5. 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.

Intercambiando cajas

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?

Resumen del capítulo

En este capítulo aprendiste:

  • Variables son como cajas que guardan información
  • Los tipos elementales son los ladrillos básicos (int, float, str, bool, None)
  • Los tipos compuestos son estructuras construidas con tipos elementales
  • Puedes cambiar el contenido de las cajas en cualquier momento
  • Reglas para nombrar las cajas (variables) en Python
  • Buenas prácticas para nombres descriptivos y consistentes
  • Cómo ver el tipo de contenido con type()
  • Cómo usar f-strings para mostrar variables en texto

¿Qué sigue?

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!

Concepto de Variables

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.

¿Qué es una variable?

💭 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.

La analogía completa del almacén:

# 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.

¿Por qué necesitamos variables?

Déjame mostrarte por qué las variables son tan importantes con un ejemplo sencillo:

Sin variables (muy difícil):

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.

Con variables (mucho mejor):

# Creamos nuestras "cajas"
nombre = "Ana"
edad = 25
altura = 1.65
es_estudiante = True

# Usamos las cajas
# 🌟 Usando f-strings (recomendado)
print(f"Hola, mi nombre es {nombre}")
print(f"{nombre} tiene {edad} años")
print(f"{nombre} mide {altura} metros")
print(f"{nombre} es estudiante: {es_estudiante}")
print(f"El año que viene {nombre} tendrá {edad + 1} años")

Ahora, si quieres cambiar “Ana” por “Carlos”, ¡solo cambias una línea! Esto hace que tu código sea mucho más fácil de mantener y modificar.

Anatomía de una variable

Cada variable tiene tres partes principales:

1. Nombre (la etiqueta de la caja)

nombre = "Ana"
#  ↑
# Esta es la etiqueta

El nombre es como la etiqueta que le pones a tu caja. Debe ser descriptivo para que sepas qué hay dentro sin tener que abrirla.

2. Operador de asignación (el signo =)

nombre = "Ana"
#      ↑
# Esto significa "guardar en"

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”.

3. Valor (el contenido de la caja)

nombre = "Ana"
#        ↑
# Esto es lo que guardamos

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.

Tipos de cajas en nuestro almacén

💭 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:

🔵 Cajas azules (números enteros - int)

# 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

🟢 Cajas verdes (números decimales - float)

# Estas cajas guardan números con decimales
altura = 1.75
precio = 29.99
temperatura = 36.5
porcentaje = 85.7

# Son perfectas para:
# - Medidas
# - Precios
# - Temperaturas
# - Porcentajes

🟡 Cajas amarillas (texto - str)

# Estas cajas guardan texto (siempre entre comillas)
nombre = "Ana García"
mensaje = "¡Hola mundo!"
email = "ana@email.com"
direccion = "Calle Falsa 123"

# Son perfectas para:
# - Nombres
# - Mensajes
# - Direcciones
# - Cualquier texto

🔴 Cajas rojas (verdadero/falso - bool)

# Estas cajas solo guardan True o False
es_estudiante = True
tiene_trabajo = False
esta_casado = True
es_fin_semana = False

# Son perfectas para:
# - Estados (sí/no)
# - Condiciones
# - Permisos
# - Cualquier cosa que sea verdadero o falso

Creando y usando variables

Vamos a ver cómo trabajar con estas cajas paso a paso:

Paso 1: Crear la caja

# Sintaxis: nombre_de_la_caja = contenido
mi_nombre = "Tu nombre aquí"

Es como tomar una caja del almacén, ponerle una etiqueta que dice “mi_nombre”, y meter dentro el texto “Tu nombre aquí”.

Paso 2: Usar la caja

mi_nombre = "Ana"
# 🌟 Con f-string (recomendado)
print(f"Hola, {mi_nombre}")  # Hola, Ana
print(f"¿Cómo estás, {mi_nombre}?")  # ¿Cómo estás, Ana?

# También puedes usar concatenación
print("Hola, " + mi_nombre)  # Hola, Ana

Paso 3: Cambiar el contenido de la caja

mi_nombre = "Ana"
print(f"Nombre inicial: {mi_nombre}")  # Ana

# Cambiar el contenido
mi_nombre = "Carlos"
print(f"Nombre actualizado: {mi_nombre}")  # Carlos

Es como vaciar la caja y meter algo nuevo. ¡Así de simple!

Ejemplo práctico: Organizando información de una tienda

Imagínate que tienes una tienda y necesitas organizar información de productos. Las variables te ayudan a mantener todo ordenado:

# ================================
# INFORMACIÓN DEL PRODUCTO
# ================================

# Cajas amarillas (texto)
nombre_producto = "Laptop Gaming"
marca = "TechPro"
modelo = "X1-2024"
descripcion = "Laptop para gaming de alta gama"

# Cajas azules (números enteros)
cantidad_stock = 15
año_lanzamiento = 2024
garantia_meses = 24

# Cajas verdes (números decimales)
precio = 25999.99
peso_kg = 2.5
pantalla_pulgadas = 15.6

# Cajas rojas (verdadero/falso)
esta_disponible = True
es_nuevo = True
incluye_mouse = False
tiene_descuento = True

# ================================
# MOSTRAR INFORMACIÓN
# ================================

print("=== INFORMACIÓN DEL PRODUCTO ===")
# 🌟 Usando f-strings para mostrar información
print(f"Producto: {nombre_producto}")
print(f"Marca: {marca}")
print(f"Modelo: {modelo}")
print(f"Descripción: {descripcion}")
print()

print("=== ESPECIFICACIONES ===")
print(f"Precio: ${precio:,.2f}")  # Formato con separador de miles y 2 decimales
print(f"Peso: {peso_kg} kg")
print(f"Pantalla: {pantalla_pulgadas} pulgadas")
print(f"Año de lanzamiento: {año_lanzamiento}")
print(f"Garantía: {garantia_meses} meses")
print()

print("=== DISPONIBILIDAD ===")
print(f"En stock: {cantidad_stock} unidades")
print(f"Disponible: {'Sí' if esta_disponible else 'No'}")  # Expresión condicional
print(f"Producto nuevo: {'Sí' if es_nuevo else 'No'}")
print(f"Incluye mouse: {'Sí' if incluye_mouse else 'No'}")
print(f"Tiene descuento: {'Sí' if tiene_descuento else 'No'}")

💡 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?

Variables que cambian de contenido

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?

Copiando contenido entre cajas

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.

Usando variables en operaciones

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?

Ejercicio práctico: Tu almacén personal

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.

Errores comunes con variables

Cuando trabajas con variables, hay algunos errores comunes que debes evitar:

1. Usar una variable antes de crearla

# ❌ ERROR
print(nombre)  # Error: name 'nombre' is not defined
nombre = "Ana"

# ✅ CORRECTO
nombre = "Ana"
print(nombre)

Python lee tu código de arriba hacia abajo, así que debes crear la caja antes de intentar ver qué hay dentro.

2. Confundir el nombre con el contenido

# ❌ 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.

3. Olvidar las comillas en texto

# ❌ ERROR
nombre = Ana  # Error: name 'Ana' is not defined

# ✅ CORRECTO
nombre = "Ana"  # o 'Ana'

Sin comillas, Python piensa que Ana es otra variable, no un texto.

Consejos para organizar tu almacén

Después de años programando, he aprendido algunos trucos para mantener mi “almacén” de variables bien organizado:

1. Agrupa variables relacionadas

# Información de una persona
nombre = "Ana"
apellido = "García"
edad = 25
email = "ana@email.com"

# Información de un producto (separado claramente)
producto_nombre = "Laptop"
producto_precio = 15000
producto_stock = 10

2. Usa nombres que expliquen el contenido

# ✅ BUENO - nombres descriptivos
precio_producto = 100
edad_usuario = 25
nombre_completo = "Ana García"

# ❌ MALO - nombres confusos
p = 100  # ¿p de precio? ¿de peso? ¿de puntos?
x = 25   # ¿qué representa x?
n = "Ana García"  # ¿n de nombre? ¿de...?

3. Mantén un orden lógico

# ✅ 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.

Resumen

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!

Tipos de Datos Básicos

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!

🧱 Una perspectiva personal: Tipos elementales vs compuestos

💭 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:

🧱 Tipos Elementales (Los ladrillos básicos)

Son los componentes fundamentales que no se pueden descomponer más. Son como los ladrillos individuales con los que construimos todo lo demás:

  • 🔵 int (números enteros): 25, -10, 1000
  • 🟢 float (números decimales): 3.14, 19.99, -2.5
  • 🟡 str (texto): "Ana", "Hola mundo", "123"
  • 🔴 bool (verdadero/falso): True, False
  • ⚪ None (ausencia de valor): None

🏗️ Tipos Compuestos (Las estructuras construidas)

Son combinaciones organizadas de tipos elementales. Es como usar nuestros ladrillos básicos para construir estructuras más complejas:

  • 📦 Listas: [25, 30, 35] (varios int juntos)
  • 📋 Tuplas: ("Ana", 25, True) (str, int, bool combinados)
  • 📚 Diccionarios: {"nombre": "Ana", "edad": 25} (str como claves, varios tipos como valores)
  • 🎯 Conjuntos: {1, 2, 3, 4} (int únicos agrupados)

🔧 La magia de la composición

Lo interesante es que puedes “crear” tipos compuestos usando combinaciones de tipos elementales:

# Tipos elementales (ladrillos básicos)
nombre = "Ana García"        # 🟡 str
edad = 28                   # 🔵 int
altura = 1.65              # 🟢 float
es_estudiante = True       # 🔴 bool

# Tipos compuestos (estructuras construidas con los ladrillos)
persona_lista = [nombre, edad, altura, es_estudiante]  # 📦 Lista
persona_tupla = (nombre, edad, altura, es_estudiante)  # 📋 Tupla
persona_dict = {                                       # 📚 Diccionario
    "nombre": nombre,      # str como clave, str como valor
    "edad": edad,          # str como clave, int como valor
    "altura": altura,      # str como clave, float como valor
    "estudiante": es_estudiante  # str como clave, bool como valor
}

print("🧱 Tipos elementales usados:")
print(f"   Nombre: {nombre} ({type(nombre).__name__})")
print(f"   Edad: {edad} ({type(edad).__name__})")
print(f"   Altura: {altura} ({type(altura).__name__})")
print(f"   Estudiante: {es_estudiante} ({type(es_estudiante).__name__})")

print("\n🏗️ Tipos compuestos creados:")
print(f"   Lista: {persona_lista} ({type(persona_lista).__name__})")
print(f"   Tupla: {persona_tupla} ({type(persona_tupla).__name__})")
print(f"   Diccionario: {persona_dict} ({type(persona_dict).__name__})")

🎯 ¿Por qué es útil esta perspectiva?

  1. 📚 Aprendizaje progresivo: Primero dominas los ladrillos básicos, luego aprendes a construir estructuras
  2. 🔧 Resolución de problemas: Sabes que cualquier estructura compleja está hecha de elementos simples
  3. 🐛 Depuración: Si algo falla en una estructura compuesta, revisas los elementos individuales
  4. 💡 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!


Los 5 tipos de cajas principales (Tipos Elementales)

🔵 Caja Azul: int (Números Enteros)

Las cajas azules son perfectas para guardar números enteros (sin decimales). Son como cajas resistentes que solo aceptan números completos.

# Creando cajas azules
edad = 25
año_actual = 2024
temperatura = -5
puntos = 0
habitantes = 1000000

# Verificar que son cajas azules
print(type(edad))  # <class 'int'>

¿Cuándo usar cajas azules?

  • Edades: edad = 25
  • Años: año_nacimiento = 1995
  • Cantidades exactas: numero_estudiantes = 30
  • Puntuaciones: puntos_juego = 1500
  • Posiciones: fila = 5, columna = 3

Ejemplos prácticos:

# Información de un estudiante
edad_estudiante = 20
semestre_actual = 6
materias_cursando = 5
creditos_completados = 120

print("=== INFORMACIÓN ACADÉMICA ===")
print("Edad:", edad_estudiante, "años")
print("Semestre:", semestre_actual)
print("Materias este semestre:", materias_cursando)
print("Créditos completados:", creditos_completados)

# Cálculos con cajas azules
creditos_faltantes = 200 - creditos_completados
print("Créditos faltantes:", creditos_faltantes)

Números negativos también son bienvenidos:

temperatura_invierno = -10
deuda = -5000
diferencia_puntos = -25

print("Temperatura:", temperatura_invierno, "°C")
print("Balance:", deuda, "pesos")

🟢 Caja Verde: float (Números Decimales)

Las cajas verdes son especiales para números con decimales. Son como cajas de precisión que pueden guardar fracciones.

# Creando cajas verdes
altura = 1.75
precio = 29.99
temperatura = 36.5
porcentaje = 85.7
pi = 3.14159

# Verificar que son cajas verdes
print(type(altura))  # <class 'float'>

¿Cuándo usar cajas verdes?

  • Medidas: altura = 1.75, peso = 68.5
  • Precios: precio = 199.99
  • Temperaturas: temperatura = 36.5
  • Porcentajes: descuento = 15.5
  • Constantes matemáticas: pi = 3.14159

Ejemplos prácticos:

# Información física de una persona
altura_metros = 1.75
peso_kg = 68.5
temperatura_corporal = 36.8

print("=== DATOS FÍSICOS ===")
print("Altura:", altura_metros, "metros")
print("Peso:", peso_kg, "kg")
print("Temperatura:", temperatura_corporal, "°C")

# Cálculos con cajas verdes
imc = peso_kg / (altura_metros ** 2)
print("IMC:", round(imc, 2))  # Redondeamos a 2 decimales

# Información de compras
precio_producto = 199.99
descuento_porcentaje = 15.5
iva_porcentaje = 16.0

descuento_pesos = precio_producto * (descuento_porcentaje / 100)
precio_con_descuento = precio_producto - descuento_pesos
iva_pesos = precio_con_descuento * (iva_porcentaje / 100)
precio_final = precio_con_descuento + iva_pesos

print("\n=== CÁLCULO DE PRECIO ===")
print("Precio original: $", precio_producto)
print("Descuento (", descuento_porcentaje, "%): $", round(descuento_pesos, 2))
print("Precio con descuento: $", round(precio_con_descuento, 2))
print("IVA (", iva_porcentaje, "%): $", round(iva_pesos, 2))
print("Precio final: $", round(precio_final, 2))

🟡 Caja Amarilla: str (Cadenas de Texto)

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'>

¿Cuándo usar cajas amarillas?

  • Nombres: nombre = "Ana"
  • Mensajes: saludo = "¡Hola!"
  • Direcciones: email = "ana@email.com"
  • Identificadores: codigo = "ABC123"
  • Cualquier texto: descripcion = "Producto de alta calidad"

Formas de crear cajas amarillas:

# 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.'''

Ejemplos prácticos:

# Información personal
nombre_completo = "Ana García López"
email_personal = "ana.garcia@email.com"
telefono_celular = "555-123-4567"
ciudad_residencia = "Ciudad de México"
ocupacion = "Estudiante de Ingeniería"

print("=== INFORMACIÓN DE CONTACTO ===")
print("Nombre:", nombre_completo)
print("Email:", email_personal)
print("Teléfono:", telefono_celular)
print("Ciudad:", ciudad_residencia)
print("Ocupación:", ocupacion)

# Mensajes personalizados
saludo_matutino = "¡Buenos días!"
saludo_vespertino = "¡Buenas tardes!"
despedida = "¡Hasta luego!"

print("\n=== MENSAJES ===")
print(saludo_matutino, nombre_completo)
print("Espero que tengas un excelente día.")
print(despedida)

# Información de productos
producto_nombre = "Laptop Gaming Pro"
producto_marca = "TechMaster"
producto_modelo = "GM-2024-X1"
producto_descripcion = "Laptop de alta gama para gaming y trabajo profesional"

print("\n=== INFORMACIÓN DEL PRODUCTO ===")
print("Producto:", producto_nombre)
print("Marca:", producto_marca)
print("Modelo:", producto_modelo)
print("Descripción:", producto_descripcion)

🔴 Caja Roja: bool (Booleanos)

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.

# Creando cajas rojas
es_estudiante = True
tiene_trabajo = False
esta_casado = True
es_fin_semana = False

# Verificar que son cajas rojas
print(type(es_estudiante))  # <class 'bool'>

¿Cuándo usar cajas rojas?

  • Estados: esta_encendido = True
  • Permisos: puede_votar = True
  • Condiciones: es_mayor_edad = False
  • Configuraciones: modo_oscuro = True
  • Resultados de comparaciones: es_igual = (5 == 5)

Ejemplos prácticos:

# Estado de un usuario
usuario_activo = True
email_verificado = True
perfil_completo = False
notificaciones_activadas = True
modo_privado = False

print("=== ESTADO DEL USUARIO ===")
print("Usuario activo:", usuario_activo)
print("Email verificado:", email_verificado)
print("Perfil completo:", perfil_completo)
print("Notificaciones:", notificaciones_activadas)
print("Modo privado:", modo_privado)

# Configuración de una aplicación
tema_oscuro = True
sonido_activado = False
actualizaciones_automaticas = True
compartir_ubicacion = False

print("\n=== CONFIGURACIÓN DE LA APP ===")
print("Tema oscuro:", tema_oscuro)
print("Sonido:", sonido_activado)
print("Actualizaciones automáticas:", actualizaciones_automaticas)
print("Compartir ubicación:", compartir_ubicacion)

# Resultados de verificaciones
contraseña_valida = True
edad_suficiente = False
terminos_aceptados = True

print("\n=== VERIFICACIONES ===")
print("Contraseña válida:", contraseña_valida)
print("Edad suficiente:", edad_suficiente)
print("Términos aceptados:", terminos_aceptados)

⚪ Caja Vacía: None (Nada)

La caja vacía es especial. Representa “nada” o “vacío”. Es como tener una caja etiquetada pero sin contenido adentro.

# Creando cajas vacías
resultado = None
valor_inicial = None
respuesta_usuario = None

# Verificar que son cajas vacías
print(type(resultado))  # <class 'NoneType'>

¿Cuándo usar cajas vacías?

  • Valores iniciales: resultado = None
  • Valores opcionales: segundo_nombre = None
  • Indicar ausencia: fecha_fin = None
  • Valores por defecto: configuracion = None

Ejemplos prácticos:

# 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)

Comparando los tipos de cajas

Vamos a crear un ejemplo que use todos los tipos de cajas:

# ================================
# PERFIL COMPLETO DE USUARIO
# ================================

print("=== CREANDO PERFIL DE USUARIO ===")
print()

# 🟡 Cajas amarillas (texto)
nombre_usuario = "Carlos Mendoza"
email = "carlos.mendoza@email.com"
ciudad = "Guadalajara"
profesion = "Ingeniero de Software"

# 🔵 Cajas azules (números enteros)
edad = 28
años_experiencia = 5
proyectos_completados = 23

# 🟢 Cajas verdes (números decimales)
salario_mensual = 35000.50
altura = 1.78
calificacion_promedio = 4.7

# 🔴 Cajas rojas (verdadero/falso)
esta_empleado = True
busca_trabajo = False
disponible_freelance = True
tiene_auto = True

# ⚪ Cajas vacías (información pendiente)
fecha_ultimo_proyecto = None
siguiente_vacacion = None

# ================================
# MOSTRAR INFORMACIÓN ORGANIZADA
# ================================

print("📋 INFORMACIÓN PERSONAL")
print("Nombre:", nombre_usuario)
print("Email:", email)
print("Ciudad:", ciudad)
print("Profesión:", profesion)
print("Edad:", edad, "años")
print("Altura:", altura, "metros")
print()

print("💼 INFORMACIÓN PROFESIONAL")
print("Años de experiencia:", años_experiencia)
print("Proyectos completados:", proyectos_completados)
print("Salario mensual: $", salario_mensual)
print("Calificación promedio:", calificacion_promedio, "/5.0")
print()

print("✅ ESTADO ACTUAL")
print("Está empleado:", esta_empleado)
print("Busca trabajo:", busca_trabajo)
print("Disponible para freelance:", disponible_freelance)
print("Tiene auto:", tiene_auto)
print()

print("⏳ INFORMACIÓN PENDIENTE")
print("Fecha último proyecto:", fecha_ultimo_proyecto)
print("Siguiente vacación:", fecha_ultimo_proyecto)
print()

print("🔍 TIPOS DE CAJAS UTILIZADAS")
print("nombre_usuario:", type(nombre_usuario))
print("edad:", type(edad))
print("salario_mensual:", type(salario_mensual))
print("esta_empleado:", type(esta_empleado))
print("fecha_ultimo_proyecto:", type(fecha_ultimo_proyecto))

Identificando tipos de cajas

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))

Ejercicio práctico: Inventario de tu almacén

Vamos a crear un programa que use todos los tipos de cajas:

# ================================
# INVENTARIO DE MI ALMACÉN PERSONAL
# ================================

print("📦 ORGANIZANDO MI ALMACÉN DE VARIABLES")
print("=" * 50)

# Crear una caja de cada tipo
print("\n🟡 Creando caja amarilla (texto)...")
mi_comida_favorita = "Pizza"
print(f"   Contenido: {mi_comida_favorita}")
print(f"   Tipo de caja: {type(mi_comida_favorita)}")

print("\n🔵 Creando caja azul (número entero)...")
mi_edad = 25  # Cambia por tu edad
print(f"   Contenido: {mi_edad}")
print(f"   Tipo de caja: {type(mi_edad)}")

print("\n🟢 Creando caja verde (número decimal)...")
mi_estatura = 1.70  # Cambia por tu estatura
print(f"   Contenido: {mi_estatura}")
print(f"   Tipo de caja: {type(mi_estatura)}")

print("\n🔴 Creando caja roja (verdadero/falso)...")
me_gusta_programar = True  # Cambia según tu preferencia
print(f"   Contenido: {me_gusta_programar}")
print(f"   Tipo de caja: {type(me_gusta_programar)}")

print("\n⚪ Creando caja vacía (None)...")
mi_segundo_apellido = None  # Si no tienes segundo apellido
print(f"   Contenido: {mi_segundo_apellido}")
print(f"   Tipo de caja: {type(mi_segundo_apellido)}")

print("\n" + "=" * 50)
print("📋 RESUMEN DE MI ALMACÉN")
print("=" * 50)
print(f"🟡 Cajas amarillas (str): 1 - Contenido: {mi_comida_favorita}")
print(f"🔵 Cajas azules (int): 1 - Contenido: {mi_edad}")
print(f"🟢 Cajas verdes (float): 1 - Contenido: {mi_estatura}")
print(f"🔴 Cajas rojas (bool): 1 - Contenido: {me_gusta_programar}")
print(f"⚪ Cajas vacías (None): 1 - Contenido: {mi_segundo_apellido}")
print(f"\n📊 Total de cajas en mi almacén: 5")

Consejos para elegir el tipo de caja correcto

1. Para números sin decimales → Caja azul (int)

edad = 25          # ✅ Correcto
edad = 25.0        # ❌ Innecesario (aunque funciona)

2. Para números con decimales → Caja verde (float)

precio = 19.99     # ✅ Correcto
precio = 19        # ❌ Podría perder precisión

3. Para cualquier texto → Caja amarilla (str)

nombre = "Ana"     # ✅ Correcto
telefono = "555-1234"  # ✅ Correcto (aunque sean números)

4. Para sí/no, verdadero/falso → Caja roja (bool)

es_estudiante = True   # ✅ Correcto
es_estudiante = "Sí"   # ❌ Menos eficiente

5. Para valores ausentes → Caja vacía (None)

segundo_nombre = None      # ✅ Correcto
segundo_nombre = ""        # ❌ Cadena vacía, no ausencia

Resumen

🧱 Tipos Elementales (Los fundamentos)

En tu almacén de variables tienes 5 tipos principales de cajas elementales:

  • 🔵 Cajas azules (int): Números enteros como 25, -10, 1000
  • 🟢 Cajas verdes (float): Números decimales como 3.14, 19.99, -2.5
  • 🟡 Cajas amarillas (str): Texto como “Ana”, “Hola mundo”, “123”
  • 🔴 Cajas rojas (bool): Solo True o False
  • Cajas vacías (None): Representan ausencia de valor

🏗️ El camino hacia los tipos compuestos

Estos tipos elementales son tus ladrillos básicos. En capítulos posteriores aprenderás a combinarlos para crear tipos compuestos más sofisticados:

# Hoy aprendiste los ladrillos básicos (tipos elementales)
nombre = "Ana"          # 🟡 str
edad = 25              # 🔵 int
activa = True          # 🔴 bool

# Próximamente aprenderás a construir estructuras (tipos compuestos)
persona = [nombre, edad, activa]                    # 📦 Lista (próximo capítulo)
datos = {"nombre": nombre, "edad": edad}            # 📚 Diccionario (más adelante)
coordenadas = (10.5, 20.3)                        # 📋 Tupla (también veremos)

💡 Principios clave

  1. Domina los elementales primero: Son la base de todo lo demás
  2. Elige el tipo correcto: Cada tipo tiene su propósito específico
  3. Piensa en composición: Los tipos complejos se construyen con los simples
  4. 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!

Conversión de Tipos y Formateo de Strings

¿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.

🌟 Tres formas de combinar texto y variables

💭 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.

1. F-strings (Recomendado - Python 3.6+)

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

2. Concatenación (Fallback - Universal)

La concatenación es la forma más básica y funciona en todas las versiones de Python:

nombre = "Ana"
edad = 25
salario = 1500.50

# Concatenación (usando el operador +)
mensaje = "Hola " + nombre + ", tienes " + str(edad) + " años y ganas $" + str(salario)
print(mensaje)  # Hola Ana, tienes 25 años y ganas $1500.5

3. Format() (Legacy - Código Antiguo)

El método format() fue popular antes de los f-strings:

nombre = "Ana"
edad = 25
salario = 1500.50

# Método format()
mensaje = "Hola {}, tienes {} años y ganas ${:.2f}".format(nombre, edad, salario)
print(mensaje)  # Hola Ana, tienes 25 años y ganas $1500.50

🎯 Mi recomendación personal

💭 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.

¿Qué es la conversión de tipos?

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'>

Las máquinas transformadoras de Python

Python tiene varias máquinas transformadoras:

🔄 str() - Transformadora a Caja Amarilla

Convierte cualquier cosa a texto:

# De caja azul a caja amarilla
edad = 25
edad_texto = str(edad)
print("Edad como texto:", edad_texto)  # "25"

# De caja verde a caja amarilla
precio = 19.99
precio_texto = str(precio)
print("Precio como texto:", precio_texto)  # "19.99"

# De caja roja a caja amarilla
es_estudiante = True
estudiante_texto = str(es_estudiante)
print("Estado como texto:", estudiante_texto)  # "True"

🔄 int() - Transformadora a Caja Azul

Convierte a números enteros:

# De caja amarilla a caja azul
edad_texto = "25"
edad_numero = int(edad_texto)
print("Edad como número:", edad_numero)  # 25

# De caja verde a caja azul (¡cuidado, pierde decimales!)
precio = 19.99
precio_entero = int(precio)
print("Precio sin decimales:", precio_entero)  # 19 (se trunca)

# De caja roja a caja azul
es_verdadero = True
verdadero_numero = int(es_verdadero)
print("True como número:", verdadero_numero)  # 1

es_falso = False
falso_numero = int(es_falso)
print("False como número:", falso_numero)  # 0

🔄 float() - Transformadora a Caja Verde

Convierte a números decimales:

# De caja amarilla a caja verde
precio_texto = "19.99"
precio_decimal = float(precio_texto)
print("Precio como decimal:", precio_decimal)  # 19.99

# De caja azul a caja verde
edad = 25
edad_decimal = float(edad)
print("Edad como decimal:", edad_decimal)  # 25.0

# De caja roja a caja verde
es_verdadero = True
verdadero_decimal = float(es_verdadero)
print("True como decimal:", verdadero_decimal)  # 1.0

🔄 bool() - Transformadora a Caja Roja

Convierte a verdadero/falso:

# De caja azul a caja roja
numero_positivo = 5
es_verdadero = bool(numero_positivo)
print("5 como booleano:", es_verdadero)  # True

numero_cero = 0
es_falso = bool(numero_cero)
print("0 como booleano:", es_falso)  # False

# De caja amarilla a caja roja
texto_lleno = "Hola"
texto_verdadero = bool(texto_lleno)
print("'Hola' como booleano:", texto_verdadero)  # True

texto_vacio = ""
texto_falso = bool(texto_vacio)
print("'' como booleano:", texto_falso)  # False

Ejemplo práctico: Calculadora de edad

Vamos a crear un programa que demuestre la conversión de tipos y las diferentes formas de combinar texto con variables:

# ================================
# CALCULADORA DE EDAD
# ================================

print("=== CALCULADORA DE EDAD ===")
print()

# El usuario ingresa su año de nacimiento como texto
print("Por favor, ingresa tu año de nacimiento:")
año_nacimiento_texto = "1995"  # Simular entrada del usuario
print("Entrada del usuario:", año_nacimiento_texto)
print("Tipo de caja:", type(año_nacimiento_texto))  # str
print()

# Transformar de caja amarilla a caja azul para hacer cálculos
print("🔄 Transformando texto a número...")
año_nacimiento = int(año_nacimiento_texto)
print("Año como número:", año_nacimiento)
print("Tipo de caja:", type(año_nacimiento))  # int
print()

# Calcular edad (usando caja azul para el año actual)
año_actual = 2024
edad = año_actual - año_nacimiento
print("Cálculo: ", año_actual, "-", año_nacimiento, "=", edad)
print()

# ================================
# FORMAS DE COMBINAR TEXTO Y VARIABLES
# ================================

print("🔄 Creando mensajes con diferentes métodos...")
print()

# 🌟 MÉTODO 1: F-STRINGS (RECOMENDADO - MODERNO Y FÁCIL)
print("🌟 Método 1: F-strings (Recomendado)")
mensaje_fstring = f"Tienes {edad} años"
print("Resultado:", mensaje_fstring)
print("Código:", 'f"Tienes {edad} años"')
print()

# 🔧 MÉTODO 2: CONCATENACIÓN (FALLBACK - SIMPLE Y UNIVERSAL)
print("🔧 Método 2: Concatenación (Fallback)")
edad_texto = str(edad)
mensaje_concatenacion = "Tienes " + edad_texto + " años"
print("Resultado:", mensaje_concatenacion)
print("Código:", '"Tienes " + str(edad) + " años"')
print()

# 📚 MÉTODO 3: FORMAT() (LEGACY - MÉTODO ANTERIOR)
print("📚 Método 3: format() (Legacy)")
mensaje_format = "Tienes {} años".format(edad)
print("Resultado:", mensaje_format)
print("Código:", '"Tienes {} años".format(edad)')
print()

# Verificar si es mayor de edad (transformar a booleano)
print("🔄 Verificando mayoría de edad...")
es_mayor_edad = edad >= 18
print("¿Es mayor de edad?", es_mayor_edad)
print("Tipo de caja:", type(es_mayor_edad))  # bool

Conversiones automáticas vs manuales

Conversiones automáticas (Python las hace solo):

# Python convierte automáticamente en operaciones mixtas
numero_entero = 5
numero_decimal = 2.5
resultado = numero_entero + numero_decimal  # 5 se convierte a 5.0
print("Resultado:", resultado)  # 7.5
print("Tipo:", type(resultado))  # float

Conversiones manuales (tú las haces):

# Tú decides cuándo y cómo convertir
edad_texto = "25"
edad_numero = int(edad_texto)  # Conversión manual
print("Edad:", edad_numero)

Errores comunes en conversiones

1. Intentar convertir texto no numérico a número

# ❌ 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")

2. Perder información en conversiones

# ❌ CUIDADO - se pierden decimales
precio = 19.99
precio_entero = int(precio)
print("Precio original:", precio)      # 19.99
print("Precio convertido:", precio_entero)  # 19 (se perdió .99)

# ✅ MEJOR - usar round() si quieres redondear
precio_redondeado = round(precio)
print("Precio redondeado:", precio_redondeado)  # 20

3. Confundir tipos en operaciones

# ❌ ERROR - no puedes sumar texto con números
edad = 25
# mensaje = "Tengo " + edad + " años"  # TypeError

# ✅ CORRECTO - convertir a texto primero
mensaje = "Tengo " + str(edad) + " años"
print(mensaje)  # "Tengo 25 años"

Ejemplo práctico: Sistema de calificaciones

# ================================
# SISTEMA DE CALIFICACIONES
# ================================

print("=== SISTEMA DE CALIFICACIONES ===")
print()

# Calificaciones ingresadas como texto (simulando entrada de usuario)
calificacion1_texto = "85"
calificacion2_texto = "92"
calificacion3_texto = "78"
nombre_estudiante = "María González"

print("📝 Calificaciones ingresadas:")
# 🌟 Usando f-strings para mostrar información
print(f"Estudiante: {nombre_estudiante}")
print(f"Materia 1: {calificacion1_texto}")
print(f"Materia 2: {calificacion2_texto}")
print(f"Materia 3: {calificacion3_texto}")
print(f"Tipo de datos: {type(calificacion1_texto)}")
print()

# Convertir de texto a números para hacer cálculos
print("🔄 Convirtiendo calificaciones a números...")
cal1 = int(calificacion1_texto)
cal2 = int(calificacion2_texto)
cal3 = int(calificacion3_texto)

# 🌟 F-strings para mostrar conversiones
print(f"Calificación 1: {cal1} - Tipo: {type(cal1)}")
print(f"Calificación 2: {cal2} - Tipo: {type(cal2)}")
print(f"Calificación 3: {cal3} - Tipo: {type(cal3)}")
print()

# Calcular promedio (resultado será decimal)
print("📊 Calculando promedio...")
suma = cal1 + cal2 + cal3
promedio = suma / 3

# 🌟 F-strings con formateo de números
print(f"Suma: {suma}")
print(f"Promedio: {promedio:.2f}")
print(f"Tipo del promedio: {type(promedio)}")
print()

# Redondear promedio
promedio_redondeado = round(promedio, 2)
print(f"Promedio redondeado: {promedio_redondeado}")
print()

# Determinar si aprobó (convertir a booleano)
print("✅ Verificando aprobación...")
aprobo = promedio >= 70
print(f"¿Aprobó? (promedio >= 70): {aprobo}")
print(f"Tipo: {type(aprobo)}")
print()

# Crear mensaje final con los tres métodos
print("📄 Generando reporte final con diferentes métodos...")
print()

estado_texto = "APROBADO" if aprobo else "REPROBADO"

# 🌟 F-string (recomendado)
reporte_fstring = f"Estudiante: {nombre_estudiante} | Promedio: {promedio_redondeado} | Estado: {estado_texto}"
print("🌟 Con f-string:")
print(f"   {reporte_fstring}")
print()

# 🔧 Concatenación (fallback)
promedio_texto = str(promedio_redondeado)
reporte_concat = ("Estudiante: " + nombre_estudiante + " | Promedio: " + 
                 promedio_texto + " | Estado: " + estado_texto)
print("🔧 Con concatenación:")
print(f"   {reporte_concat}")
print()

# 📚 Format (legacy)
reporte_format = "Estudiante: {} | Promedio: {} | Estado: {}".format(
    nombre_estudiante, promedio_redondeado, estado_texto
)
print("📚 Con format:")
print(f"   {reporte_format}")
print()

# Reporte detallado con f-strings
print("📋 REPORTE DETALLADO (con f-strings):")
print("=" * 50)
print(f"👤 Estudiante: {nombre_estudiante}")
print(f"📊 Calificaciones individuales:")
print(f"   • Materia 1: {cal1}/100")
print(f"   • Materia 2: {cal2}/100") 
print(f"   • Materia 3: {cal3}/100")
print(f"📈 Estadísticas:")
print(f"   • Suma total: {suma} puntos")
print(f"   • Promedio: {promedio:.2f}/100")
print(f"   • Estado: {estado_texto}")
print(f"   • Puntos para aprobar: {70 - promedio:.1f}" if not aprobo else f"   • Puntos sobre el mínimo: {promedio - 70:.1f}")

Conversiones útiles en la vida real

1. Entrada de usuario (siempre texto)

# Simular entrada de usuario
nombre = "Ana"  # input() siempre devuelve texto
edad_texto = "25"  # input() siempre devuelve texto

# Convertir edad para cálculos
edad = int(edad_texto)
año_nacimiento = 2024 - edad

# 🌟 F-string (recomendado)
mensaje_fstring = f"Hola {nombre}, naciste aproximadamente en {año_nacimiento}"
print("Con f-string:", mensaje_fstring)

# 🔧 Concatenación (fallback)
mensaje_concat = "Hola " + nombre + ", naciste aproximadamente en " + str(año_nacimiento)
print("Con concatenación:", mensaje_concat)

# 📚 Format (legacy)
mensaje_format = "Hola {}, naciste aproximadamente en {}".format(nombre, año_nacimiento)
print("Con format:", mensaje_format)

2. Formateo de números para mostrar

# Cálculos con decimales
precio_base = 100
descuento = 0.15
precio_final = precio_base * (1 - descuento)

# 🌟 F-string (recomendado) - con formateo automático
mensaje_fstring = f"El precio final es: ${precio_final:.2f}"
print("Con f-string:", mensaje_fstring)

# 🔧 Concatenación (fallback) - necesita conversión manual
precio_texto = str(round(precio_final, 2))
mensaje_concat = "El precio final es: $" + precio_texto
print("Con concatenación:", mensaje_concat)

# 📚 Format (legacy)
mensaje_format = "El precio final es: ${:.2f}".format(precio_final)
print("Con format:", mensaje_format)

3. Validaciones con mensajes

# 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)

Tabla de conversiones comunes

DesdeHaciaFunciónEjemploResultado
intstrstr()str(25)"25"
intfloatfloat()float(25)25.0
intboolbool()bool(25)True
floatstrstr()str(19.99)"19.99"
floatintint()int(19.99)19
floatboolbool()bool(19.99)True
strintint()int("25")25
strfloatfloat()float("19.99")19.99
strboolbool()bool("Hola")True
boolstrstr()str(True)"True"
boolintint()int(True)1
boolfloatfloat()float(True)1.0

Ejercicio práctico: Conversor universal

# ================================
# CONVERSOR UNIVERSAL DE TIPOS
# ================================

print("🔄 CONVERSOR UNIVERSAL DE TIPOS")
print("=" * 40)

# Valor original
valor_original = 42
print(f"\n📦 Valor original: {valor_original}")
print(f"   Tipo: {type(valor_original)}")

# Convertir a todos los tipos posibles
print("\n🔄 CONVERSIONES:")

# A texto
valor_str = str(valor_original)
print(f"🟡 Como texto (str): '{valor_str}' - Tipo: {type(valor_str)}")

# A decimal
valor_float = float(valor_original)
print(f"🟢 Como decimal (float): {valor_float} - Tipo: {type(valor_float)}")

# A booleano
valor_bool = bool(valor_original)
print(f"🔴 Como booleano (bool): {valor_bool} - Tipo: {type(valor_bool)}")

print("\n" + "=" * 40)
print("🧪 PROBANDO CON DIFERENTES VALORES:")

# Probar con diferentes valores
valores_prueba = [0, -5, 3.14, "123", "Hola", True, False, ""]

for i, valor in enumerate(valores_prueba, 1):
    print(f"\n📦 Prueba {i}: {repr(valor)} ({type(valor).__name__})")
    
    # Intentar convertir a booleano (siempre funciona)
    print(f"   🔴 bool(): {bool(valor)}")
    
    # Intentar convertir a texto (siempre funciona)
    print(f"   🟡 str(): '{str(valor)}'")
    
    # Intentar convertir a número (puede fallar)
    try:
        if isinstance(valor, str) and valor.replace('.', '').replace('-', '').isdigit():
            if '.' in valor:
                resultado_float = float(valor)
                print(f"   🟢 float(): {resultado_float}")
            else:
                resultado_int = int(valor)
                print(f"   🔵 int(): {resultado_int}")
        elif isinstance(valor, (int, float, bool)):
            resultado_int = int(valor)
            resultado_float = float(valor)
            print(f"   🔵 int(): {resultado_int}")
            print(f"   🟢 float(): {resultado_float}")
    except ValueError:
        print("   ❌ No se puede convertir a número")

print("\n" + "=" * 40)
print("📊 COMPARACIÓN DE MÉTODOS DE FORMATEO:")

# Ejemplo con datos complejos
nombre = "Ana García"
edad = 28
salario = 45000.75
es_activa = True

print(f"\n📋 DATOS DE EJEMPLO:")
print(f"   Nombre: {nombre}")
print(f"   Edad: {edad}")
print(f"   Salario: {salario}")
print(f"   Activa: {es_activa}")

print(f"\n📝 MENSAJE COMPLEJO CON TRES MÉTODOS:")

# 🌟 F-string (recomendado)
mensaje_fstring = f"Empleada: {nombre}, {edad} años, salario: ${salario:,.2f}, estado: {'Activa' if es_activa else 'Inactiva'}"
print(f"\n🌟 F-string (recomendado):")
print(f"   Resultado: {mensaje_fstring}")
print(f"   Legibilidad: ⭐⭐⭐⭐⭐")
print(f"   Rendimiento: ⭐⭐⭐⭐⭐")

# 🔧 Concatenación (fallback)
estado_texto = "Activa" if es_activa else "Inactiva"
mensaje_concat = ("Empleada: " + nombre + ", " + str(edad) + " años, salario: $" + 
                 f"{salario:,.2f}" + ", estado: " + estado_texto)
print(f"\n🔧 Concatenación (fallback):")
print(f"   Resultado: {mensaje_concat}")
print(f"   Legibilidad: ⭐⭐")
print(f"   Rendimiento: ⭐⭐⭐")

# 📚 Format (legacy)
mensaje_format = "Empleada: {}, {} años, salario: ${:,.2f}, estado: {}".format(
    nombre, edad, salario, "Activa" if es_activa else "Inactiva"
)
print(f"\n📚 Format (legacy):")
print(f"   Resultado: {mensaje_format}")
print(f"   Legibilidad: ⭐⭐⭐")
print(f"   Rendimiento: ⭐⭐⭐⭐")

print(f"\n🎯 RECOMENDACIÓN:")
print(f"   Usa f-strings siempre que sea posible (Python 3.6+)")
print(f"   Usa concatenación para casos simples o compatibilidad")
print(f"   Usa format() solo para mantener código legacy")

Consejos para conversiones seguras

1. Siempre verifica antes de convertir

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

2. Usa try/except para conversiones riesgosas

def convertir_seguro(valor, tipo_destino):
    try:
        if tipo_destino == int:
            return int(valor)
        elif tipo_destino == float:
            return float(valor)
        elif tipo_destino == str:
            return str(valor)
        elif tipo_destino == bool:
            return bool(valor)
    except ValueError:
        print(f"No se puede convertir '{valor}' a {tipo_destino.__name__}")
        return None

# Uso
numero = convertir_seguro("123", int)    # 123
numero = convertir_seguro("abc", int)    # None

3. Recuerda las reglas de conversión a booleano

# Valores que se convierten a False
print(bool(0))        # False
print(bool(0.0))      # False
print(bool(""))       # False
print(bool([]))       # False (lista vacía)
print(bool(None))     # False

# Todo lo demás se convierte a True
print(bool(1))        # True
print(bool(-1))       # True
print(bool("Hola"))   # True
print(bool(" "))      # True (espacio no es vacío)

Resumen

La conversión de tipos es como tener máquinas transformadoras en tu almacén, y combinar texto con variables es una habilidad esencial en Python:

🔄 Conversiones de Tipos:

  • str() convierte cualquier cosa a texto (caja amarilla)
  • int() convierte a números enteros (caja azul)
  • float() convierte a números decimales (caja verde)
  • bool() convierte a verdadero/falso (caja roja)
  • ✅ Siempre verifica que la conversión sea posible
  • ✅ Ten cuidado con la pérdida de información (float a int)
  • ✅ Usa try/except para conversiones riesgosas

📝 Formateo de Strings (Orden de Prioridad):

🥇 1. F-strings (Recomendado - Python 3.6+)

nombre = "Ana"
edad = 25
mensaje = f"Hola {nombre}, tienes {edad} años"

Ventajas:

  • ✅ Más legible y moderno
  • ✅ Mejor rendimiento
  • ✅ Formateo automático de números
  • ✅ Expresiones dentro de las llaves

🥈 2. Concatenación (Fallback - Universal)

nombre = "Ana"
edad = 25
mensaje = "Hola " + nombre + ", tienes " + str(edad) + " años"

Ventajas:

  • ✅ Simple y universal
  • ✅ Funciona en todas las versiones
  • ✅ Fácil de entender

Desventajas:

  • ❌ Requiere conversión manual
  • ❌ Menos legible con muchas variables

🥉 3. Format() (Legacy - Código Antiguo)

nombre = "Ana"
edad = 25
mensaje = "Hola {}, tienes {} años".format(nombre, edad)

Cuándo usar:

  • 📚 Mantener código legacy
  • 📚 Compatibilidad con Python < 3.6
  • 📚 Proyectos que ya usan este estilo

🎯 Guía de Decisión Rápida:

# ✅ 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)

💡 Consejos Finales:

  1. Usa f-strings por defecto - Son el estándar moderno
  2. Concatenación para casos simples - Cuando solo unes 2-3 elementos
  3. Format() solo para legacy - Mantener código existente
  4. Siempre convierte tipos - Antes de combinar con texto
  5. 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!

Ejercicios: Variables y Tipos de Datos

¡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”.

🏋️ Ejercicio 1: Creando tu almacén personal

Objetivo: Practicar la creación de variables de diferentes tipos y mostrar su información.

Instrucciones:

  1. 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
  2. Muestra toda la información usando f-strings

  3. Realiza al menos 3 cálculos con tus variables numéricas

  4. 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("...")

🏋️ Ejercicio 2: Calculadora de IMC

Objetivo: Crear una calculadora de Índice de Masa Corporal (IMC) usando variables y operaciones matemáticas.

Instrucciones:

  1. Crea variables para almacenar:

    • Nombre de la persona
    • Peso en kilogramos (float)
    • Altura en metros (float)
  2. Calcula el IMC usando la fórmula: IMC = peso / (altura * altura)

  3. 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
  4. 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í!

🏋️ Ejercicio 3: Conversor de unidades

Objetivo: Practicar la creación de variables, operaciones matemáticas y formateo de strings.

Instrucciones:

  1. 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)
  2. Muestra los resultados formateados con 2 decimales

  3. 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

🏋️ Ejercicio 4: Intercambio de variables

Objetivo: Practicar el intercambio de valores entre variables.

Instrucciones:

  1. Crea tres variables: a, b y c con valores diferentes
  2. Muestra los valores iniciales
  3. Intercambia los valores de manera que:
    • a reciba el valor de b
    • b reciba el valor de c
    • c reciba el valor de a (el valor original)
  4. Muestra los valores finales

Plantilla de código:

# ================================
# INTERCAMBIO DE VARIABLES
# ================================

# Valores iniciales
a = 5
b = 10
c = 15

# Mostrar valores iniciales
print("=== VALORES INICIALES ===")
print(f"a = {a}")
print(f"b = {b}")
print(f"c = {c}")

# Realizar el intercambio
# ...
# ...
# ...

# Mostrar valores finales
print("\n=== VALORES FINALES ===")
print(f"a = {a}")
print(f"b = {b}")
print(f"c = {c}")

Resultado esperado:

## VALORES INICIALES
a = 5
b = 10
c = 15

## VALORES FINALES
a = 10
b = 15
c = 5

🏋️ Ejercicio 5: Calculadora de descuentos

Objetivo: Practicar operaciones con variables de diferentes tipos y formateo de salida.

Instrucciones:

  1. Crea variables para:

    • Nombre del producto
    • Precio original
    • Porcentaje de descuento
    • Si el producto está en oferta (booleano)
  2. Calcula:

    • El monto del descuento
    • El precio final después del descuento
    • El IVA (16% sobre el precio con descuento)
    • El precio final con IVA
  3. Muestra un ticket de compra formateado

Plantilla de código:

# ================================
# CALCULADORA DE DESCUENTOS
# ================================

# Información del producto
nombre_producto = "Laptop Gaming Pro"
precio_original = 25000.0
porcentaje_descuento = 15.0  # 15%
en_oferta = True

# Cálculos
# ...
# ...
# ...

# Mostrar ticket
print("## TICKET DE COMPRA")
print(f"Producto: {nombre_producto}")
print(f"Precio original: ${precio_original:,.2f}")
# Completa el ticket con el resto de la información...

Resultado esperado:

## TICKET DE COMPRA
Producto: Laptop Gaming Pro
Precio original: $25,000.00
Descuento (15.0%): $3,750.00
Precio con descuento: $21,250.00
IVA (16.0%): $3,400.00
PRECIO FINAL: $24,650.00

Estado: EN OFERTA!

🏆 Desafío avanzado: Sistema de calificaciones

Objetivo: Integrar todos los conceptos aprendidos en un sistema más complejo.

Instrucciones:

  1. Crea variables para almacenar:

    • Nombre del estudiante
    • Calificaciones de 5 materias diferentes (números del 0 al 100)
    • Si el estudiante tiene beca (booleano)
  2. 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
  3. 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.

💡 Consejos para resolver los ejercicios

  1. Lee cuidadosamente las instrucciones antes de empezar
  2. Planifica tu solución antes de escribir código
  3. Divide el problema en partes más pequeñas
  4. Prueba tu código con diferentes valores
  5. Revisa los errores con calma y paciencia
  6. 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.

🎯 Criterios de éxito

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!

Operadores y Expresiones en Python

🧭 Navegación:

¡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.

¿Qué son los operadores?

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.

Contenido de esta sección

En esta sección aprenderás sobre:

  1. Operadores Matemáticos - Suma, resta, multiplicación, división y más
  2. Operadores de Comparación - Igual, diferente, mayor que, menor que
  3. Operadores Lógicos - AND, OR, NOT para combinar condiciones
  4. Operadores de Asignación - Asignar y modificar valores de variables
  5. Operadores Bit a Bit - Manipulación a nivel de bits
  6. Operadores de Identidad y Pertenencia - Verificar identidad y pertenencia
  7. Resumen de Operadores y Expresiones - Combinando operadores en expresiones complejas

¿Por qué son importantes los operadores?

Los operadores son fundamentales en programación porque:

  • Nos permiten realizar cálculos matemáticos
  • Facilitan la comparación de valores
  • Posibilitan la toma de decisiones lógicas
  • Ayudan a manipular y transformar datos
  • Son la base para construir expresiones complejas

Analogía del almacén

A lo largo de esta sección, usaremos la analogía del almacén que ya conoces:

  • Las variables son como cajas que contienen valores
  • Los operadores son herramientas que manipulan estas cajas
  • Las expresiones son recetas que combinan operadores y variables

Mapa conceptual

OPERADORES EN PYTHON
|
|-- Matemáticos (+, -, *, /, //, %, **)
|   |-- Realizan cálculos numéricos
|
|-- Comparación (==, !=, >, <, >=, <=)
|   |-- Comparan valores y devuelven booleanos
|
|-- Lógicos (and, or, not)
|   |-- Combinan condiciones booleanas
|
|-- Asignación (=, +=, -=, *=, /=, etc.)
|   |-- Asignan y modifican valores de variables
|
|-- Bit a bit (&, |, ^, ~, <<, >>)
|   |-- Manipulan bits individuales
|
|-- Identidad/Pertenencia (is, is not, in, not in)
    |-- Verifican identidad y pertenencia

¡Comencemos nuestro viaje por el mundo de los operadores en Python!


🧭 Navegación:

Capítulos de esta sección:

Operadores Matemáticos

🧭 Navegación:

¡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.

🧮 ¿Qué son los operadores matemáticos?

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.

🏪 Analogía del almacén: La oficina de contabilidad

Imagina que nuestro almacén tiene una oficina de contabilidad donde:

  • Los números son como facturas y documentos con cantidades
  • Los operadores son las calculadoras y herramientas de cálculo
  • Las operaciones son los procedimientos contables que aplicamos
  • Los resultados son los balances y reportes finales

📊 Los operadores matemáticos básicos

➕ Suma (+): Acumulando inventario

# En el almacén: Sumamos productos que llegan
inventario_inicial = 50
productos_nuevos = 25
inventario_total = inventario_inicial + productos_nuevos
print(f"Inventario total: {inventario_total} unidades")  # 75 unidades

# Ejemplos prácticos
precio_producto = 299.99
impuestos = 48.00
precio_final = precio_producto + impuestos
print(f"Precio final: ${precio_final}")  # $347.99

# Suma de múltiples valores
ventas_lunes = 1500
ventas_martes = 2300
ventas_miercoles = 1800
total_ventas = ventas_lunes + ventas_martes + ventas_miercoles
print(f"Ventas de la semana: ${total_ventas}")  # $5600

➖ Resta (-): Descontando del inventario

# En el almacén: Restamos productos vendidos
inventario_actual = 100
productos_vendidos = 15
inventario_restante = inventario_actual - productos_vendidos
print(f"Quedan: {inventario_restante} unidades")  # 85 unidades

# Calculando descuentos
precio_original = 599.99
descuento = 120.00
precio_con_descuento = precio_original - descuento
print(f"Precio con descuento: ${precio_con_descuento}")  # $479.99

# Calculando diferencias
ventas_objetivo = 10000
ventas_reales = 8500
diferencia = ventas_objetivo - ventas_reales
print(f"Faltan ${diferencia} para cumplir objetivo")  # Faltan $1500

✖️ Multiplicación (*): Cálculos de volumen

# En el almacén: Calculamos totales por cantidad
precio_unitario = 25.50
cantidad = 12
total_compra = precio_unitario * cantidad
print(f"Total de la compra: ${total_compra}")  # $306.00

# Calculando áreas del almacén
largo_almacen = 50  # metros
ancho_almacen = 30  # metros
area_total = largo_almacen * ancho_almacen
print(f"Área del almacén: {area_total} m²")  # 1500 m²

# Proyecciones de ventas
ventas_diarias = 450
dias_mes = 30
proyeccion_mensual = ventas_diarias * dias_mes
print(f"Proyección mensual: ${proyeccion_mensual}")  # $13500

➗ División (/): Distribución y promedios

# En el almacén: Dividimos costos entre productos
costo_total_envio = 240.00
num_productos = 8
costo_por_producto = costo_total_envio / num_productos
print(f"Costo de envío por producto: ${costo_por_producto}")  # $30.0

# Calculando promedios de ventas
ventas_totales = 15600
num_vendedores = 4
promedio_por_vendedor = ventas_totales / num_vendedores
print(f"Promedio por vendedor: ${promedio_por_vendedor}")  # $3900.0

# Calculando velocidad de ventas
productos_vendidos = 450
dias_transcurridos = 15
velocidad_ventas = productos_vendidos / dias_transcurridos
print(f"Vendemos {velocidad_ventas} productos por día")  # 30.0

🎯 Operadores matemáticos avanzados

🔢 División entera (//): Cálculos exactos sin decimales

# En el almacén: Calculamos cajas completas
productos_totales = 127
productos_por_caja = 12
cajas_completas = productos_totales // productos_por_caja
print(f"Cajas completas: {cajas_completas}")  # 10 cajas

# Calculando turnos de trabajo
horas_totales = 65
horas_por_turno = 8
turnos_completos = horas_totales // horas_por_turno
print(f"Turnos completos: {turnos_completos}")  # 8 turnos

# Distribución equitativa
bonificacion_total = 5000
num_empleados = 7
bonificacion_por_empleado = bonificacion_total // num_empleados
print(f"Bonificación por empleado: ${bonificacion_por_empleado}")  # $714

🔄 Módulo (%): Calculando sobrantes

# 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

⚡ Potenciación (**): Cálculos exponenciales

# En el almacén: Calculamos espacios cúbicos
lado_contenedor = 3  # metros
volumen = lado_contenedor ** 3
print(f"Volumen del contenedor: {volumen} m³")  # 27 m³

# Calculando interés compuesto
capital_inicial = 10000
tasa_interes = 1.05  # 5% anual
anos = 3
capital_final = capital_inicial * (tasa_interes ** anos)
print(f"Capital después de {anos} años: ${capital_final:.2f}")  # $11576.25

# Crecimiento exponencial de ventas
ventas_base = 1000
factor_crecimiento = 1.15  # 15% mensual
meses = 6
ventas_proyectadas = ventas_base * (factor_crecimiento ** meses)
print(f"Ventas proyectadas: ${ventas_proyectadas:.2f}")  # $2313.06

🔧 Orden de las operaciones (Jerarquía)

Python sigue las reglas matemáticas estándar para el orden de las operaciones:

📋 Prioridad de operadores (de mayor a menor)

  1. Paréntesis ()
  2. Potenciación **
  3. Multiplicación, División, División entera, Módulo *, /, //, %
  4. Suma y Resta +, -
# Ejemplo sin paréntesis
resultado = 2 + 3 * 4 ** 2
print(f"2 + 3 * 4 ** 2 = {resultado}")  # 50
# Se calcula: 2 + 3 * 16 = 2 + 48 = 50

# Ejemplo con paréntesis
resultado = (2 + 3) * 4 ** 2
print(f"(2 + 3) * 4 ** 2 = {resultado}")  # 80
# Se calcula: 5 * 16 = 80

# Cálculo empresarial complejo
precio_base = 100
descuento = 0.15
impuesto = 0.16
cantidad = 5

# Sin paréntesis (incorrecto)
total_incorrecto = precio_base - descuento * precio_base + impuesto * cantidad
print(f"Cálculo incorrecto: ${total_incorrecto}")  # $85.8

# Con paréntesis (correcto)
total_correcto = (precio_base * (1 - descuento) * (1 + impuesto)) * cantidad
print(f"Cálculo correcto: ${total_correcto}")  # $493.0

💡 Casos prácticos del almacén

🏷️ Calculadora de precios con descuentos

# Sistema de precios del almacén
def calcular_precio_final(precio_base, descuento_porcentaje, impuesto_porcentaje, cantidad):
    """Calcula el precio final de una compra en el almacén"""
    
    # Convertir porcentajes a decimales
    descuento = descuento_porcentaje / 100
    impuesto = impuesto_porcentaje / 100
    
    # Aplicar descuento
    precio_con_descuento = precio_base * (1 - descuento)
    
    # Aplicar impuesto
    precio_con_impuesto = precio_con_descuento * (1 + impuesto)
    
    # Calcular total por cantidad
    total_final = precio_con_impuesto * cantidad
    
    return total_final

# Ejemplo de uso
precio_laptop = 1299.99
descuento_oferta = 20  # 20%
iva = 16  # 16%
cantidad_comprada = 3

total = calcular_precio_final(precio_laptop, descuento_oferta, iva, cantidad_comprada)
print(f"\n🏷️ FACTURA DEL ALMACÉN")
print(f"Producto: Laptop Gaming")
print(f"Precio unitario: ${precio_laptop}")
print(f"Descuento: {descuento_oferta}%")
print(f"IVA: {iva}%")
print(f"Cantidad: {cantidad_comprada}")
print(f"TOTAL A PAGAR: ${total:.2f}")

📦 Calculadora de espacios del almacén

# Sistema de gestión de espacios
def calcular_capacidad_almacen(largo, ancho, alto, espacio_por_producto):
    """Calcula cuántos productos caben en el almacén"""
    
    volumen_total = largo * ancho * alto
    capacidad_productos = volumen_total // espacio_por_producto
    espacio_sobrante = volumen_total % espacio_por_producto
    
    return capacidad_productos, espacio_sobrante

# Dimensiones del almacén
largo_almacen = 25  # metros
ancho_almacen = 15  # metros
alto_almacen = 4    # metros
espacio_por_caja = 2  # metros cúbicos por caja

cajas_que_caben, espacio_libre = calcular_capacidad_almacen(
    largo_almacen, ancho_almacen, alto_almacen, espacio_por_caja
)

print(f"\n📦 ANÁLISIS DE CAPACIDAD")
print(f"Dimensiones del almacén: {largo_almacen}m x {ancho_almacen}m x {alto_almacen}m")
print(f"Volumen total: {largo_almacen * ancho_almacen * alto_almacen} m³")
print(f"Cajas que caben: {cajas_que_caben}")
print(f"Espacio libre: {espacio_libre} m³")

💰 Calculadora de ROI (Retorno de Inversión)

# Sistema de análisis financiero
def calcular_roi(inversion_inicial, ganancia_anual, anos):
    """Calcula el retorno de inversión"""
    
    ganancia_total = ganancia_anual * anos
    roi_total = ((ganancia_total - inversion_inicial) / inversion_inicial) * 100
    roi_anual = roi_total / anos
    
    return roi_total, roi_anual

# Análisis de inversión en nuevo almacén
inversion = 500000  # $500,000
ganancia_por_ano = 120000  # $120,000 anuales
periodo_analisis = 5  # 5 años

roi_total, roi_anual = calcular_roi(inversion, ganancia_por_ano, periodo_analisis)

print(f"\n💰 ANÁLISIS DE INVERSIÓN")
print(f"Inversión inicial: ${inversion:,}")
print(f"Ganancia anual: ${ganancia_por_ano:,}")
print(f"Período: {periodo_analisis} años")
print(f"ROI total: {roi_total:.1f}%")
print(f"ROI anual promedio: {roi_anual:.1f}%")

# Determinar si es buena inversión
if roi_anual >= 15:
    print("✅ Excelente inversión")
elif roi_anual >= 10:
    print("✅ Buena inversión")
elif roi_anual >= 5:
    print("⚠️ Inversión moderada")
else:
    print("❌ Inversión no recomendada")

🚨 Comprueba tu comprensión

🎯 Ejercicio 1: Calculadora de nómina

Crea un programa que calcule el salario neto de un empleado del almacén:

# Datos del empleado
salario_base = 15000  # pesos mensuales
horas_extra = 10     # horas trabajadas extra
pago_por_hora_extra = 150  # pesos por hora extra
bono_productividad = 2000   # pesos

# Deducciones
impuesto_renta = 0.10    # 10%
seguro_social = 0.0625   # 6.25%
seguro_medico = 500      # pesos fijos

# ¿Puedes calcular el salario neto?
# Tu código aquí...

🎯 Ejercicio 2: Distribución de productos

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í...

🎯 Ejercicio 3: Proyección de crecimiento

Las ventas del almacén crecen 8% cada mes. Si este mes vendiste $50,000:

ventas_actuales = 50000
tasa_crecimiento = 0.08  # 8%

# Calcula las ventas proyectadas para los próximos 6 meses
# Tu código aquí...

📝 Puntos clave para recordar

  • Suma (+): Acumula valores, como inventario que llega
  • Resta (-): Descuenta valores, como productos vendidos
  • Multiplicación (*): Calcula totales por cantidad
  • División (/): Obtiene promedios y distribuciones
  • División entera (//): Calcula unidades completas
  • Módulo (%): Encuentra sobrantes y restos
  • Potenciación (**): Eleva a potencias, útil para cálculos complejos
  • Orden de operaciones: Los paréntesis cambian las prioridades
  • Aplicaciones prácticas: Finanzas, inventarios, espacios, ROI

🎯 Lo que has logrado

¡Felicidades! Ahora dominas las herramientas matemáticas fundamentales de Python. Eres como un contador experto del almacén que puede:

  • 🧮 Realizar cualquier cálculo empresarial
  • 📊 Procesar datos numéricos automáticamente
  • 💰 Crear sistemas de precios y facturación
  • 📦 Optimizar espacios y recursos
  • 📈 Proyectar crecimiento y analizar inversiones

Estos operadores matemáticos son la base de prácticamente todos los sistemas empresariales y aplicaciones que usarás en tu carrera como programador.


🧭 Navegación:

En esta sección:

Operadores de Comparación

Operadores Lógicos

Operadores de Asignación

Operadores Bit a Bit

Operadores de Identidad y Pertenencia

Resumen y Expresiones Complejas

Quiz: Operadores y Expresiones

🧭 Navegación:

¡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.

Instrucciones

  • Lee cada pregunta cuidadosamente
  • Intenta responder sin mirar las soluciones
  • Al final, compara tus respuestas con las soluciones proporcionadas
  • Cada respuesta correcta vale 1 punto

Preguntas

1. Operadores Matemáticos

¿Cuál es el resultado de la siguiente expresión?

resultado = 20 - 5 * 2 + 10 / 2

a) 5.0 b) 10.0 c) 15.0 d) 20.0

2. Operadores de Comparación

¿Cuál de las siguientes expresiones evalúa a True?

a) 5 > 7 b) "abc" > "abd" c) 10 <= 10 d) 5 != 5

3. Operadores Lógicos

¿Cuál es el resultado de la siguiente expresión?

not (True and False) or (True or False)

a) True b) False c) Error de sintaxis d) Depende del contexto

4. Operadores de Asignación

¿Qué valor tendrá x después de ejecutar este código?

x = 10
x += 5
x *= 2
x //= 3

a) 8 b) 9 c) 10 d) 11

5. Operadores Bit a Bit

¿Cuál es el resultado de 5 & 3?

a) 0 b) 1 c) 7 d) 8

6. Operadores de Identidad

¿Cuál de las siguientes afirmaciones es correcta?

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

7. Operadores de Pertenencia

¿Cuál es el resultado de la siguiente expresión?

"a" in "Python"

a) True b) False c) Error de sintaxis d) None

8. Precedencia de Operadores

¿Cuál es el resultado de la siguiente expresión?

2 ** 3 * 2 + 10 // 5

a) 18 b) 16 c) 20 d) 64

9. Expresiones Complejas

¿Cuál es el resultado de la siguiente expresión?

x = 5
y = 10
z = 0
resultado = x < y and y > z or x / z

a) True b) False c) Error (división por cero) d) None

10. Aplicación Práctica

¿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

Soluciones

Haz clic para ver las respuestas

1. Operadores Matemáticos

Respuesta: c) 15.0

Explicación:

resultado = 20 - 5 * 2 + 10 / 2
         = 20 - 10 + 5.0
         = 10 + 5.0
         = 15.0

2. Operadores de Comparación

Respuesta: c) 10 <= 10

Explicación:

  • 5 > 7 es False porque 5 no es mayor que 7
  • "abc" > "abd" es False porque “abc” viene antes que “abd” en orden lexicográfico
  • 10 <= 10 es True porque 10 es igual a 10
  • 5 != 5 es False porque 5 es igual a 5

3. Operadores Lógicos

Respuesta: a) True

Explicación:

not (True and False) or (True or False)
= not (False) or (True)
= True or True
= True

4. Operadores de Asignación

Respuesta: c) 10

Explicación:

x = 10
x += 5  # x = 10 + 5 = 15
x *= 2  # x = 15 * 2 = 30
x //= 3  # x = 30 // 3 = 10

5. Operadores Bit a Bit

Respuesta: b) 1

Explicación:

5 en binario: 101
3 en binario: 011
5 & 3:        001 (1 en decimal)

6. Operadores de Identidad

Respuesta: c) is compara identidad, mientras que == compara valores

Explicación:

  • is verifica si dos variables se refieren al mismo objeto en memoria
  • == verifica si dos variables tienen el mismo valor

7. Operadores de Pertenencia

Respuesta: b) False

Explicación: La letra “a” no está presente en la cadena “Python”.

8. Precedencia de Operadores

Respuesta: a) 18

Explicación:

2 ** 3 * 2 + 10 // 5
= 8 * 2 + 10 // 5
= 8 * 2 + 2
= 16 + 2
= 18

9. Expresiones Complejas

Respuesta: a) True

Explicación: Debido a la evaluación en cortocircuito, Python evalúa:

x < y and y > z or x / z
= 5 < 10 and 10 > 0 or x / z
= True and True or x / z
= True or x / z

Como el primer operando de or es True, Python no evalúa el segundo operando (x / z), evitando así el error de división por cero.

10. Aplicación Práctica

Respuesta: a) numero % 2 == 0 and numero % 3 == 0 and numero % 5 != 0

Explicación:

  • numero % 2 == 0: Verifica si el número es divisible por 2
  • numero % 3 == 0: Verifica si el número es divisible por 3
  • numero % 5 != 0: Verifica si el número NO es divisible por 5

Puntuación

  • 9-10 puntos: ¡Excelente! Dominas los operadores en Python.
  • 7-8 puntos: Muy bien. Tienes un buen entendimiento, pero repasa algunos conceptos.
  • 5-6 puntos: Aceptable. Necesitas reforzar tu comprensión de algunos operadores.
  • Menos de 5 puntos: Recomendamos revisar nuevamente los capítulos sobre operadores.

🧭 Navegación:

Estructuras de Control en Python

🧭 Navegación:

¡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.

¿Qué son las estructuras de control?

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.

Contenido de esta sección

En esta sección aprenderás sobre:

  1. Condicionales - El gerente inteligente que toma decisiones

    • Sentencias if/elif/else
    • Condiciones simples y complejas
    • Condicionales anidados
  2. Bucles For - El robot especialista que procesa listas

    • Iteración sobre secuencias
    • Función range()
    • Enumerate y zip
  3. Bucles While - El robot persistente que trabaja mientras una condición sea verdadera

    • Bucles basados en condiciones
    • Contadores y acumuladores
    • Evitar bucles infinitos
  4. Control de Bucles - Comandos especiales para tus robots

    • Break para detener bucles
    • Continue para saltar iteraciones
    • Else en bucles
  5. Patrones Comunes - Recetas probadas para problemas frecuentes

    • Acumuladores y contadores
    • Búsqueda y filtrado
    • Transformación de datos
  6. Diagramas de Flujo - Visualización del control de flujo

    • Representación gráfica de decisiones
    • Mapas de ejecución de bucles
    • Combinación de estructuras
  7. Quiz de Estructuras de Control - Pon a prueba tus conocimientos

    • Preguntas de comprensión
    • Ejercicios prácticos
    • Desafíos de integración

¿Por qué son importantes las estructuras de control?

Las estructuras de control son fundamentales en programación porque:

  • Permiten que los programas tomen decisiones basadas en condiciones
  • Automatizan tareas repetitivas sin escribir código redundante
  • Hacen que los programas sean adaptables a diferentes situaciones
  • Son la base para implementar algoritmos y lógica de negocio
  • Permiten procesar grandes cantidades de datos eficientemente

Analogía del almacén

A lo largo de esta sección, continuaremos con la analogía del almacén:

  • Los condicionales son como el gerente inteligente que toma decisiones
  • Los bucles for son como robots especialistas que procesan listas de elementos
  • Los bucles while son como robots persistentes que trabajan hasta cumplir un objetivo
  • Los comandos break/continue son como instrucciones especiales para los robots

Mapa conceptual

ESTRUCTURAS DE CONTROL
|
|-- Condicionales (Toma de decisiones)
|   |-- if (si)
|   |-- elif (sino si)
|   |-- else (sino)
|
|-- Bucles (Repetición)
    |-- for (para cada elemento)
    |   |-- Iteración sobre secuencias
    |   |-- range(), enumerate(), zip()
    |
    |-- while (mientras)
        |-- Condición de continuación
        |-- Control de bucles
            |-- break (romper)
            |-- continue (continuar)

¡Comencemos nuestro viaje por el mundo de las estructuras de control en Python!


🧭 Navegación:

Capítulos de esta sección:

Condicionales: El Gerente Inteligente

🧭 Navegación:

¡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.

El gerente inteligente de tu almacén 🧠

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.

¿Qué es el control de flujo?

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.

Sin control de flujo (aburrido):

# El programa siempre hace lo mismo
print("Buenos días")
print("¿Cómo estás?")
print("Que tengas buen día")

Con control de flujo (¡inteligente!):

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.

La estructura básica: if (si)

La estructura más básica es if (si). Es como preguntarle al gerente: “Si esta condición es verdadera, ¿qué hago?”

Sintaxis básica:

if condicion:
    # Código que se ejecuta si la condición es True
    accion_a_realizar()

Ejemplo práctico:

# ================================
# 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.

La estructura completa: if-else (si-sino)

A veces el gerente necesita decidir entre dos opciones: “Si pasa esto, haz A; si no, haz B”.

Sintaxis:

if condicion:
    # Código si la condición es True
    hacer_esto()
else:
    # Código si la condición es False
    hacer_esto_otro()

Ejemplo práctico:

# ================================
# SISTEMA DE ACCESO
# ================================

contraseña_correcta = "python123"
contraseña_ingresada = "python123"  # Simular entrada del usuario

print("🔐 SISTEMA DE ACCESO")
print("Verificando credenciales...")

# El gerente evalúa las credenciales
if contraseña_ingresada == contraseña_correcta:
    print("✅ Acceso concedido")
    print("🏠 Bienvenido al sistema")
    print("📊 Cargando panel de control...")
else:
    print("❌ Acceso denegado")
    print("🚫 Contraseña incorrecta")
    print("🔄 Intenta nuevamente")

print("🔚 Proceso de autenticación completado")

La estructura múltiple: if-elif-else (si-sino si-sino)

Para situaciones más complejas, el gerente puede evaluar múltiples condiciones en orden:

Sintaxis:

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()

Ejemplo práctico:

# ================================
# CALCULADORA DE CALIFICACIONES
# ================================

calificacion = 85

print("📊 CALCULADORA DE CALIFICACIONES")
print(f"Calificación obtenida: {calificacion}")
print()

# El gerente evalúa múltiples rangos
if calificacion >= 90:
    print("🏆 ¡EXCELENTE!")
    print("⭐ Calificación: A")
    print("🎉 ¡Felicidades por tu excelente desempeño!")
elif calificacion >= 80:
    print("😊 ¡MUY BIEN!")
    print("⭐ Calificación: B")
    print("👍 Buen trabajo, sigue así")
elif calificacion >= 70:
    print("🙂 BIEN")
    print("⭐ Calificación: C")
    print("📚 Puedes mejorar con más estudio")
elif calificacion >= 60:
    print("😐 REGULAR")
    print("⭐ Calificación: D")
    print("⚠️ Necesitas esforzarte más")
else:
    print("😞 INSUFICIENTE")
    print("⭐ Calificación: F")
    print("📖 Es importante que estudies más")

print()
print("📋 Evaluación completada")

Condiciones complejas

El gerente puede evaluar condiciones muy complejas usando las herramientas que aprendimos en el capítulo anterior:

Usando operadores lógicos:

# ================================
# SISTEMA DE DESCUENTOS INTELIGENTE
# ================================

edad_cliente = 25
es_estudiante = True
compra_total = 500
es_primera_compra = False
es_cliente_premium = False

print("🛒 SISTEMA DE DESCUENTOS INTELIGENTE")
print("=" * 40)
print("👤 Información del cliente:")
print(f"Edad: {edad_cliente}")
print(f"Es estudiante: {es_estudiante}")
print(f"Compra total: ${compra_total}")
print(f"Primera compra: {es_primera_compra}")
print(f"Cliente premium: {es_cliente_premium}")
print()

# El gerente evalúa múltiples condiciones complejas
print("🔍 Evaluando descuentos disponibles...")
print()

# Descuento por edad (tercera edad o joven)
if edad_cliente >= 65 or edad_cliente <= 25:
    if edad_cliente >= 65:
        print("👴 Descuento tercera edad: 15%")
        descuento_edad = 15
    else:
        print("👶 Descuento joven: 10%")
        descuento_edad = 10
else:
    descuento_edad = 0

# Descuento por estudiante
if es_estudiante and edad_cliente <= 30:
    print("🎓 Descuento estudiante: 12%")
    descuento_estudiante = 12
else:
    descuento_estudiante = 0

# Descuento por monto de compra
if compra_total >= 1000:
    print("💰 Descuento compra alta: 20%")
    descuento_compra = 20
elif compra_total >= 500:
    print("💵 Descuento compra media: 10%")
    descuento_compra = 10
else:
    descuento_compra = 0

# Descuento especial
if es_primera_compra and compra_total >= 200:
    print("🎁 Descuento primera compra: 15%")
    descuento_especial = 15
elif es_cliente_premium:
    print("⭐ Descuento cliente premium: 25%")
    descuento_especial = 25
else:
    descuento_especial = 0

# Calcular el mejor descuento
descuento_maximo = max(descuento_edad, descuento_estudiante, 
                      descuento_compra, descuento_especial)

print()
print("🎯 RESULTADO:")
if descuento_maximo > 0:
    ahorro = compra_total * (descuento_maximo / 100)
    precio_final = compra_total - ahorro
    
    print("✅ ¡Tienes descuento!")
    print(f"Descuento aplicado: {descuento_maximo}%")
    print(f"Ahorro: ${round(ahorro, 2)}")
    print(f"Precio final: ${round(precio_final, 2)}")
else:
    print("❌ No hay descuentos disponibles")
    print(f"Precio final: ${compra_total}")

Condicionales anidados

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!")

Operador ternario: Decisiones en una línea

Python ofrece una forma compacta de tomar decisiones simples en una sola línea:

Sintaxis:

valor_si_verdadero if condicion else valor_si_falso

Ejemplo práctico:

# ================================
# 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}")

Buenas prácticas para condicionales

1. Usa nombres descriptivos para condiciones complejas:

# ❌ CONFUSO
if edad >= 18 and (ingresos > 30000 or tiene_aval) and historial_crediticio > 650:
    aprobar_prestamo()

# ✅ CLARO
es_mayor_edad = edad >= 18
tiene_ingresos_suficientes = ingresos > 30000 or tiene_aval
buen_historial_crediticio = historial_crediticio > 650

if es_mayor_edad and tiene_ingresos_suficientes and buen_historial_crediticio:
    aprobar_prestamo()

2. Ordena las condiciones de más específica a más general:

# ✅ CORRECTO - de específico a general
if calificacion >= 95:
    print("Excelencia académica")
elif calificacion >= 90:
    print("Muy bueno")
elif calificacion >= 80:
    print("Bueno")
else:
    print("Necesita mejorar")

3. Evita condicionales demasiado anidados:

# ❌ 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()

Comprueba tu comprensión 🧠

  1. ¿Qué imprimirá el siguiente código?

    x = 15
    y = 10
    
    if x > y:
        print("A")
    elif x == y:
        print("B")
    else:
        print("C")
    
  2. ¿Cuál es la diferencia entre if-elif-else y múltiples sentencias if independientes?

  3. Escribe un código que determine si un año es bisiesto usando condicionales.

  4. ¿Qué valor tendrá resultado en este código?

    a = 5
    b = 10
    resultado = "Mayor" if a > b else "Menor o igual"
    

Soluciones

  1. El código imprimirá "A" porque x > y es verdadero (15 > 10).

  2. 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
    
  3. 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")
    
  4. El valor de resultado será "Menor o igual" porque a > b es falso (5 no es mayor que 10).

Ejercicio práctico: Sistema de evaluación médica

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.


🧭 Navegación:

Capítulos de esta sección:

Bucles For: El Robot Especialista

🧭 Navegación:

¡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.

El robot especialista de tu almacén 🤖

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.

¿Por qué necesitamos bucles for?

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:

Sin bucles (tedioso y propenso a errores):

# Procesar 5 productos manualmente
print(f"Procesando producto 1")
print(f"Procesando producto 2")
print(f"Procesando producto 3")
print(f"Procesando producto 4")
print(f"Procesando producto 5")

Con bucles (elegante y escalable):

# Procesar cualquier cantidad de productos automáticamente
for i in range(1, 6):
    print(f"Procesando producto {i}")

La diferencia se vuelve aún más dramática cuando necesitas procesar decenas, cientos o miles de elementos.

Sintaxis básica del bucle for

La estructura básica de un bucle for en Python es:

for elemento in secuencia:
    # Código que se ejecuta para cada elemento
    procesar(elemento)

Donde:

  • elemento es una variable que tomará el valor de cada elemento de la secuencia
  • secuencia es cualquier objeto iterable (lista, tupla, string, etc.)
  • El código indentado se ejecutará una vez para cada elemento

Iterando sobre diferentes tipos de secuencias

Ejemplo con lista de productos:

# ================================
# PROCESADOR DE INVENTARIO
# ================================

productos = ["laptop", "mouse", "teclado", "monitor", "audífonos"]
precios = [15000, 500, 800, 8000, 1200]

print("🏪 PROCESADOR DE INVENTARIO")
print("=" * 30)

# El robot procesa cada producto
for producto in productos:
    print(f"📦 Procesando: {producto}")
    print(f"   ✅ Verificado en almacén")
    print(f"   📊 Actualizado en sistema")
    print()

print("🎯 Procesamiento completado")
print(f"Total de productos procesados: {len(productos)}")

Ejemplo con cadenas de texto:

# ================================
# ANALIZADOR DE TEXTO
# ================================

mensaje = "Python"

print("🔤 ANALIZADOR DE TEXTO")
print("=" * 25)
print(f"Analizando texto: '{mensaje}'")
print()

# El robot procesa cada carácter
for caracter in mensaje:
    print(f"Carácter: '{caracter}'")
    
    # Análisis del carácter
    if caracter.isupper():
        print("   📏 Tipo: Mayúscula")
    elif caracter.islower():
        print("   📏 Tipo: Minúscula")
    elif caracter.isdigit():
        print("   📏 Tipo: Dígito")
    else:
        print("   📏 Tipo: Especial")
    
    # Código ASCII
    print(f"   🔢 Código ASCII: {ord(caracter)}")
    print()

print(f"✅ Análisis completado: {len(mensaje)} caracteres procesados")

Ejemplo con diccionarios:

# ================================
# GESTOR DE CONFIGURACIÓN
# ================================

configuracion = {
    "tema": "oscuro",
    "fuente": "Roboto",
    "tamaño": 14,
    "notificaciones": True,
    "idioma": "español"
}

print("⚙️ GESTOR DE CONFIGURACIÓN")
print("=" * 30)

# Iterar sobre claves
print("📋 CLAVES DE CONFIGURACIÓN:")
for clave in configuracion.keys():
    print(f"   • {clave}")
print()

# Iterar sobre valores
print("📊 VALORES DE CONFIGURACIÓN:")
for valor in configuracion.values():
    print(f"   • {valor}")
print()

# Iterar sobre pares clave-valor
print("🔧 CONFIGURACIÓN COMPLETA:")
for clave, valor in configuracion.items():
    print(f"   • {clave}: {valor}")

La función range(): Generando secuencias numéricas

La función range() es una herramienta poderosa que genera secuencias numéricas automáticamente:

Sintaxis de range():

range(inicio, fin, paso)
  • inicio: Valor inicial (incluido) - por defecto es 0
  • fin: Valor final (excluido)
  • paso: Incremento entre valores - por defecto es 1

Ejemplos con range():

# ================================
# 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")

Diferentes formas de usar range():

# ================================
# 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")

Funciones útiles para bucles for

enumerate(): Obtener índice y valor

La función enumerate() te permite obtener tanto el índice como el valor de cada elemento:

# ================================
# REPORTE DE VENTAS DIARIAS
# ================================

ventas_semana = [1200, 1500, 980, 1800, 2100, 2500, 1900]
dias_semana = ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"]

print("📊 REPORTE DE VENTAS SEMANALES")
print("=" * 35)

# El robot procesa cada día con su posición
for indice, venta in enumerate(ventas_semana):
    dia = dias_semana[indice]
    print(f"📅 {dia}: ${venta:,}")
    
    # Análisis automático
    if venta > 2000:
        print("   🎉 ¡Excelente día de ventas!")
    elif venta > 1500:
        print("   👍 Buen día de ventas")
    elif venta > 1000:
        print("   📈 Día promedio")
    else:
        print("   ⚠️ Día por debajo del promedio")
    print()

# Estadísticas automáticas
total_ventas = sum(ventas_semana)
promedio_diario = total_ventas / len(ventas_semana)
mejor_dia = max(ventas_semana)
peor_dia = min(ventas_semana)

print("📈 ESTADÍSTICAS SEMANALES:")
print(f"Total de ventas: ${total_ventas:,}")
print(f"Promedio diario: ${promedio_diario:,.2f}")
print(f"Mejor día: ${mejor_dia:,}")
print(f"Peor día: ${peor_dia:,}")

zip(): Combinar múltiples secuencias

La función zip() te permite iterar sobre múltiples secuencias al mismo tiempo:

# ================================
# COMBINADOR DE DATOS
# ================================

nombres = ["Ana", "Carlos", "Elena", "David"]
edades = [28, 35, 42, 31]
ciudades = ["Madrid", "Barcelona", "Sevilla", "Valencia"]

print("👥 COMBINADOR DE DATOS")
print("=" * 25)

# El robot procesa elementos de múltiples listas simultáneamente
for nombre, edad, ciudad in zip(nombres, edades, ciudades):
    print(f"👤 {nombre}")
    print(f"   🎂 Edad: {edad} años")
    print(f"   🏙️ Ciudad: {ciudad}")
    print()

print("✅ Procesamiento completado")

Comprensión de listas: Bucles for en una línea

Las comprensiones de listas son una forma elegante y concisa de crear listas usando bucles for en una sola línea:

Sintaxis básica:

nueva_lista = [expresion for elemento in secuencia]

Ejemplos de comprensión de listas:

# ================================
# 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")

Bucles for anidados: Robots trabajando en equipo

Puedes tener bucles dentro de otros bucles, como robots trabajando en diferentes niveles:

# ================================
# ORGANIZADOR DE ALMACÉN POR SECCIONES
# ================================

almacen = {
    "Electrónicos": ["laptop", "mouse", "teclado"],
    "Ropa": ["camisa", "pantalón", "zapatos"],
    "Hogar": ["mesa", "silla", "lámpara"]
}

print("🏪 ORGANIZADOR DE ALMACÉN")
print("=" * 30)

# Robot principal recorre secciones
for seccion, productos in almacen.items():
    print(f"📂 SECCIÓN: {seccion}")
    print("-" * 20)
    
    # Robot secundario organiza productos en cada sección
    for indice, producto in enumerate(productos, 1):
        print(f"   {indice}. 📦 {producto}")
        print(f"      ✅ Ubicado en {seccion}")
    
    print(f"   📊 Total en {seccion}: {len(productos)} productos")
    print()

print("🎯 Organización completada")

Generando patrones con bucles anidados:

# ================================
# 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")

Ejemplo práctico completo: Analizador de datos de ventas

# ================================
# ANALIZADOR DE DATOS DE VENTAS
# ================================

# Datos de ventas por producto y región
ventas = [
    {"producto": "Laptop", "region": "Norte", "unidades": 50, "precio": 1200},
    {"producto": "Laptop", "region": "Sur", "unidades": 35, "precio": 1200},
    {"producto": "Tablet", "region": "Norte", "unidades": 80, "precio": 300},
    {"producto": "Tablet", "region": "Sur", "unidades": 68, "precio": 300},
    {"producto": "Móvil", "region": "Norte", "unidades": 100, "precio": 800},
    {"producto": "Móvil", "region": "Sur", "unidades": 120, "precio": 800},
    {"producto": "Auriculares", "region": "Norte", "unidades": 200, "precio": 100},
    {"producto": "Auriculares", "region": "Sur", "unidades": 220, "precio": 100},
]

print("📊 ANALIZADOR DE DATOS DE VENTAS")
print("=" * 40)

# 1. Calcular ventas totales
total_unidades = 0
total_ingresos = 0

for venta in ventas:
    unidades = venta["unidades"]
    ingreso = unidades * venta["precio"]
    total_unidades += unidades
    total_ingresos += ingreso

print("📈 RESUMEN GENERAL:")
print(f"Total de unidades vendidas: {total_unidades:,}")
print(f"Total de ingresos: ${total_ingresos:,}")
print()

# 2. Ventas por producto
print("📦 VENTAS POR PRODUCTO:")
productos = {}

for venta in ventas:
    producto = venta["producto"]
    unidades = venta["unidades"]
    ingreso = unidades * venta["precio"]
    
    if producto in productos:
        productos[producto]["unidades"] += unidades
        productos[producto]["ingresos"] += ingreso
    else:
        productos[producto] = {
            "unidades": unidades,
            "ingresos": ingreso
        }

# Ordenar productos por ingresos (de mayor a menor)
productos_ordenados = sorted(
    productos.items(), 
    key=lambda x: x[1]["ingresos"], 
    reverse=True
)

for producto, datos in productos_ordenados:
    print(f"🏷️ {producto}:")
    print(f"   Unidades: {datos['unidades']:,}")
    print(f"   Ingresos: ${datos['ingresos']:,}")
    print(f"   % del total: {(datos['ingresos'] / total_ingresos) * 100:.1f}%")
    print()

# 3. Ventas por región
print("🗺️ VENTAS POR REGIÓN:")
regiones = {}

for venta in ventas:
    region = venta["region"]
    unidades = venta["unidades"]
    ingreso = unidades * venta["precio"]
    
    if region in regiones:
        regiones[region]["unidades"] += unidades
        regiones[region]["ingresos"] += ingreso
    else:
        regiones[region] = {
            "unidades": unidades,
            "ingresos": ingreso
        }

for region, datos in regiones.items():
    print(f"📍 {region}:")
    print(f"   Unidades: {datos['unidades']:,}")
    print(f"   Ingresos: ${datos['ingresos']:,}")
    print(f"   % del total: {(datos['ingresos'] / total_ingresos) * 100:.1f}%")
    print()

# 4. Producto más vendido por región
print("🏆 PRODUCTO MÁS VENDIDO POR REGIÓN:")
productos_por_region = {}

for venta in ventas:
    region = venta["region"]
    producto = venta["producto"]
    unidades = venta["unidades"]
    
    if region not in productos_por_region:
        productos_por_region[region] = {}
    
    if producto in productos_por_region[region]:
        productos_por_region[region][producto] += unidades
    else:
        productos_por_region[region][producto] = unidades

for region, productos in productos_por_region.items():
    # Encontrar el producto más vendido
    producto_mas_vendido = max(productos.items(), key=lambda x: x[1])
    
    print(f"📍 {region}:")
    print(f"   Producto estrella: {producto_mas_vendido[0]}")
    print(f"   Unidades vendidas: {producto_mas_vendido[1]:,}")
    print()

print("✅ Análisis completado")

Buenas prácticas para bucles for

1. Usa nombres descriptivos para las variables de iteración:

# ❌ CONFUSO
for x in data:
    process(x)

# ✅ CLARO
for producto in inventario:
    procesar_producto(producto)

2. Evita modificar la colección durante la iteración:

# ❌ 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)]

3. Usa enumerate() cuando necesites el índice:

# ❌ 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}")

4. Prefiere comprensiones de listas para transformaciones simples:

# ❌ MÁS VERBOSO
resultado = []
for x in numeros:
    resultado.append(x * 2)

# ✅ MÁS CONCISO
resultado = [x * 2 for x in numeros]

Comprueba tu comprensión 🧠

  1. ¿Qué imprimirá el siguiente código?

    for i in range(5):
        print(i, end=" ")
    
  2. ¿Cuál es la diferencia entre range(5) y range(1, 6)?

  3. Escribe una comprensión de lista que genere los cuadrados de los números impares del 1 al 10.

  4. ¿Qué hace la función enumerate() y por qué es útil?

Soluciones

  1. El código imprimirá: 0 1 2 3 4

    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.

  2. 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.
  3. 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]
    
  4. 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}")
    

    Salida:

    0: manzana
    1: banana
    2: cereza
    

Ejercicio práctico: Generador de informes

# ================================
# 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.


🧭 Navegación:

Capítulos de esta sección:

Bucles While: El Robot Persistente

🧭 Navegación:

¡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.

El robot persistente de tu almacén 🤖

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.

¿Cuándo usar while en lugar de for?

Los bucles for y while tienen propósitos diferentes:

  • Bucle for: Cuando sabes exactamente cuántas iteraciones necesitas o quieres procesar todos los elementos de una secuencia.
  • Bucle while: Cuando no sabes cuántas iteraciones necesitas y dependes de una condición que puede cambiar durante la ejecución.

Ejemplos de situaciones ideales para while:

  • Esperar a que el usuario ingrese una respuesta válida
  • Procesar datos hasta encontrar un valor específico
  • Ejecutar un juego hasta que el jugador pierda
  • Monitorear un sistema hasta que ocurra un evento
  • Implementar algoritmos que requieren un número variable de pasos

Sintaxis básica del bucle while

La estructura básica de un bucle while en Python es:

while condicion:
    # Código que se ejecuta mientras la condición sea True
    hacer_algo()
    # IMPORTANTE: Asegúrate de que la condición cambie eventualmente

Donde:

  • condicion es una expresión que se evalúa como True o False
  • El código indentado se ejecuta repetidamente mientras la condición sea True
  • Es crucial que algo dentro del bucle eventualmente cambie la condición a False, o tendrás un bucle infinito

Ejemplos básicos de bucles while

Contador simple:

# ================================
# CONTADOR DE INVENTARIO
# ================================

print("📦 CONTADOR DE INVENTARIO")
print("=" * 25)

contador = 1
total_productos = 5

# El robot cuenta mientras haya productos
while contador <= total_productos:
    print(f"Contando producto #{contador}")
    print(f"   📋 Registrado en sistema")
    contador += 1  # CRUCIAL: incrementar el contador

print()
print(f"✅ Conteo completado: {total_productos} productos")

Procesamiento hasta una condición:

# ================================
# PROCESADOR DE PEDIDOS
# ================================

print("🛒 PROCESADOR DE PEDIDOS")
print("=" * 25)

pedidos_pendientes = 8
capacidad_diaria = 3
dia = 1

print(f"Pedidos pendientes iniciales: {pedidos_pendientes}")
print(f"Capacidad de procesamiento diaria: {capacidad_diaria}")
print()

# Procesar pedidos hasta que no queden pendientes
while pedidos_pendientes > 0:
    # Determinar cuántos pedidos procesar hoy
    pedidos_hoy = min(capacidad_diaria, pedidos_pendientes)
    pedidos_pendientes -= pedidos_hoy
    
    print(f"Día {dia}:")
    print(f"   📦 Pedidos procesados: {pedidos_hoy}")
    print(f"   📋 Pedidos pendientes: {pedidos_pendientes}")
    print()
    
    dia += 1

print(f"✅ Todos los pedidos han sido procesados en {dia-1} días")

Bucles while con entrada del usuario

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")

Bucles while con condiciones complejas

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")

El peligro de los bucles infinitos

Un bucle infinito ocurre cuando la condición del while nunca se vuelve falsa. Esto puede bloquear tu programa:

# ❌ PELIGRO: Bucle infinito
# while True:
#     print("¡Esto nunca terminará!")

Para evitar bucles infinitos:

  1. Asegúrate de que la condición eventualmente se vuelva falsa
  2. Incluye una cláusula de escape (como break)
  3. Verifica que las variables en la condición se actualicen dentro del bucle

Ejemplo de bucle infinito controlado:

# ================================
# SIMULADOR DE SERVICIO CONTINUO
# ================================

print("🔄 SIMULADOR DE SERVICIO CONTINUO")
print("=" * 35)

contador = 0
max_iteraciones = 5  # En un servicio real, esto sería infinito

print("Iniciando servicio...")
print("(Limitado a 5 iteraciones para la demostración)")
print()

# Bucle "infinito" controlado
while True:
    contador += 1
    print(f"Iteración #{contador}")
    print("   ✅ Servicio ejecutándose")
    
    # Cláusula de escape para la demostración
    if contador >= max_iteraciones:
        print("   ⚠️ Límite de demostración alcanzado")
        break
    
    print()

print("Servicio detenido")

Bucles while con else

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")

Ejemplo práctico completo: Simulador de cajero automático

# ================================
# SIMULADOR DE CAJERO AUTOMÁTICO
# ================================

print("🏧 SIMULADOR DE CAJERO AUTOMÁTICO")
print("=" * 35)

# Datos de la cuenta
saldo = 1000
pin_correcto = "1234"
intentos_pin = 3
sesion_activa = False

# Función para mostrar el menú
def mostrar_menu():
    print("\n=== MENÚ PRINCIPAL ===")
    print("1. Consultar saldo")
    print("2. Retirar dinero")
    print("3. Depositar dinero")
    print("4. Salir")
    return input("Seleccione una opción (1-4): ")

# Autenticación
print("Bienvenido a su cajero automático")
while intentos_pin > 0 and not sesion_activa:
    # En un programa real, usaríamos input() para obtener el PIN
    pin_ingresado = "1234"  # Simular entrada correcta
    
    if pin_ingresado == pin_correcto:
        print("✅ PIN correcto")
        sesion_activa = True
    else:
        intentos_pin -= 1
        if intentos_pin > 0:
            print(f"❌ PIN incorrecto. Le quedan {intentos_pin} intentos")
        else:
            print("🚫 Demasiados intentos fallidos. Tarjeta bloqueada")

# Menú principal
if sesion_activa:
    print("\n¡Bienvenido a su cuenta!")
    
    opcion = ""
    while opcion != "4" and sesion_activa:
        opcion = mostrar_menu()
        
        if opcion == "1":
            # Consultar saldo
            print(f"\n💰 Su saldo actual es: ${saldo}")
            
        elif opcion == "2":
            # Retirar dinero
            print("\n=== RETIRO DE DINERO ===")
            # En un programa real, usaríamos input() para obtener la cantidad
            cantidad = 300  # Simular entrada
            
            if cantidad > saldo:
                print("❌ Fondos insuficientes")
            elif cantidad <= 0:
                print("❌ Cantidad inválida")
            else:
                saldo -= cantidad
                print(f"✅ Ha retirado ${cantidad}")
                print(f"💰 Nuevo saldo: ${saldo}")
                
        elif opcion == "3":
            # Depositar dinero
            print("\n=== DEPÓSITO DE DINERO ===")
            # En un programa real, usaríamos input() para obtener la cantidad
            cantidad = 500  # Simular entrada
            
            if cantidad <= 0:
                print("❌ Cantidad inválida")
            else:
                saldo += cantidad
                print(f"✅ Ha depositado ${cantidad}")
                print(f"💰 Nuevo saldo: ${saldo}")
                
        elif opcion == "4":
            # Salir
            print("\n👋 Gracias por usar nuestro cajero automático")
            print("Sesión finalizada")
            sesion_activa = False
            
        else:
            print("\n❌ Opción inválida. Por favor, seleccione una opción válida (1-4)")

print("\n🔚 Programa terminado")

Buenas prácticas para bucles while

1. Asegúrate de que la condición eventualmente se vuelva falsa:

# ❌ PELIGROSO
# contador = 10
# while contador > 0:
#     print(contador)
#     # Olvidamos decrementar el contador

# ✅ SEGURO
contador = 10
while contador > 0:
    print(contador)
    contador -= 1  # Aseguramos que la condición eventualmente sea falsa

2. Usa break para salir del bucle en casos especiales:

while True:
    respuesta = input("¿Continuar? (s/n): ")
    if respuesta.lower() == 'n':
        break  # Salir del bucle cuando el usuario responda 'n'

3. Evita condiciones demasiado complejas:

# ❌ 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...

4. Considera usar for cuando conozcas el número de iteraciones:

# ❌ MENOS PYTHÓNICO
i = 0
while i < 10:
    print(i)
    i += 1

# ✅ MÁS PYTHÓNICO
for i in range(10):
    print(i)

Comprueba tu comprensión 🧠

  1. ¿Cuál es la principal diferencia entre un bucle for y un bucle while?

  2. ¿Qué imprimirá el siguiente código?

    x = 5
    while x > 0:
        print(x, end=" ")
        x -= 1
    
  3. ¿Qué sucede si la condición de un bucle while nunca se vuelve falsa?

  4. Escribe un bucle while que calcule la suma de los números del 1 al 10.

Soluciones

  1. 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.
  2. 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.

  3. 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).

  4. 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
    

Ejercicio práctico: Juego de adivinanza

# ================================
# 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.


🧭 Navegación:

Capítulos de esta sección:

Control de Bucles: Comandos Especiales para tus Robots

🧭 Navegación:

¡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.

El control remoto de tus robots 🎮

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.

El comando break: Detener el bucle inmediatamente

El comando break detiene la ejecución del bucle y continúa con el código que sigue después del bucle:

Ejemplo con for:

# ================================
# BÚSQUEDA DE PRODUCTO ESPECÍFICO
# ================================

productos = ["laptop", "mouse", "teclado", "tablet", "monitor"]
producto_buscado = "tablet"

print("🔍 BÚSQUEDA DE PRODUCTO")
print("=" * 25)

for i, producto in enumerate(productos):
    print(f"Revisando posición {i}: {producto}")
    
    if producto == producto_buscado:
        print(f"✅ ¡Producto encontrado: {producto}!")
        print(f"   Ubicación: Estante {i+1}")
        print("🛑 Deteniendo búsqueda")
        break  # Salir del bucle inmediatamente
    
    print("   ⏭️ Continuando búsqueda...")

print("🔚 Búsqueda terminada")

Ejemplo con while:

# ================================
# SISTEMA DE AUTENTICACIÓN
# ================================

contraseña_correcta = "python123"
intentos_maximos = 3
intentos = 0

print("🔐 SISTEMA DE AUTENTICACIÓN")
print("=" * 30)

# Simular diferentes intentos
contraseñas_simuladas = ["clave123", "python", "python123"]

while intentos < intentos_maximos:
    # Simular entrada del usuario
    contraseña = contraseñas_simuladas[intentos]
    intentos += 1
    
    print(f"Intento #{intentos}: {contraseña}")
    
    if contraseña == contraseña_correcta:
        print("✅ Acceso concedido")
        print("🏠 Bienvenido al sistema")
        break  # Salir del bucle si la contraseña es correcta
    
    print("❌ Contraseña incorrecta")
    print(f"Intentos restantes: {intentos_maximos - intentos}")
    print()

if intentos == intentos_maximos and contraseña != contraseña_correcta:
    print("🚫 Demasiados intentos fallidos")
    print("🔒 Cuenta bloqueada temporalmente")

print("🔚 Proceso de autenticación terminado")

El comando continue: Saltar a la siguiente iteración

El comando continue salta el resto del código en la iteración actual y pasa a la siguiente iteración:

Ejemplo con for:

# ================================
# PROCESADOR DE PEDIDOS
# ================================

pedidos = [
    {"id": 1, "estado": "pendiente", "total": 500},
    {"id": 2, "estado": "cancelado", "total": 300},
    {"id": 3, "estado": "pendiente", "total": 800},
    {"id": 4, "estado": "cancelado", "total": 150},
    {"id": 5, "estado": "pendiente", "total": 1200}
]

print("📋 PROCESADOR DE PEDIDOS PENDIENTES")
print("=" * 40)

total_procesado = 0

for pedido in pedidos:
    print(f"Revisando pedido #{pedido['id']}")
    
    # Saltar pedidos cancelados
    if pedido["estado"] == "cancelado":
        print("   ❌ Pedido cancelado - Saltando")
        continue  # Ir al siguiente pedido
    
    # Este código solo se ejecuta para pedidos no cancelados
    print(f"   ✅ Procesando pedido por ${pedido['total']}")
    total_procesado += pedido["total"]
    print(f"   📦 Pedido #{pedido['id']} completado")
    print()

print("📊 RESUMEN:")
print(f"Total procesado: ${total_procesado}")

Ejemplo con while:

# ================================
# 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")

La cláusula else en bucles: Acción al completar

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):

Ejemplo con for:

# ================================
# 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")

Ejemplo con while:

# ================================
# 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")

Combinando break, continue y else

Puedes combinar estos comandos para crear lógicas de control sofisticadas:

# ================================
# SISTEMA DE PROCESAMIENTO DE ARCHIVOS
# ================================

archivos = [
    {"nombre": "datos.csv", "tamaño": 1024, "corrupto": False},
    {"nombre": "imagen.jpg", "tamaño": 2048, "corrupto": False},
    {"nombre": "documento.pdf", "tamaño": 3072, "corrupto": True},
    {"nombre": "video.mp4", "tamaño": 4096, "corrupto": False},
    {"nombre": "audio.mp3", "tamaño": 5120, "corrupto": False}
]

tamaño_maximo = 4000
total_procesado = 0

print("📁 SISTEMA DE PROCESAMIENTO DE ARCHIVOS")
print("=" * 40)

for archivo in archivos:
    nombre = archivo["nombre"]
    tamaño = archivo["tamaño"]
    corrupto = archivo["corrupto"]
    
    print(f"Procesando: {nombre}")
    
    # Verificar si el archivo está corrupto
    if corrupto:
        print(f"   ❌ Archivo corrupto: {nombre}")
        print("   ⚠️ Se requiere intervención manual")
        print("   🛑 Deteniendo procesamiento")
        break
    
    # Verificar tamaño del archivo
    if tamaño > tamaño_maximo:
        print(f"   ⚠️ Archivo demasiado grande: {tamaño} KB")
        print("   ⏭️ Saltando al siguiente archivo")
        continue
    
    # Procesar archivo
    print(f"   ✅ Procesando archivo: {nombre} ({tamaño} KB)")
    total_procesado += tamaño
    print(f"   📊 Total procesado: {total_procesado} KB")
    print()
else:
    print("🎉 ¡Todos los archivos han sido procesados correctamente!")
    print(f"📊 Total de datos procesados: {total_procesado} KB")

print("🔚 Procesamiento finalizado")

Bucles anidados y control de flujo

El control de flujo se vuelve especialmente útil en bucles anidados:

# ================================
# BUSCADOR DE PRODUCTOS EN ALMACENES
# ================================

almacenes = [
    {"nombre": "Central", "productos": ["laptop", "tablet", "smartphone"]},
    {"nombre": "Norte", "productos": ["monitor", "teclado", "mouse"]},
    {"nombre": "Sur", "productos": ["impresora", "scanner", "tablet"]}
]

producto_buscado = "tablet"
encontrado = False

print("🔍 BUSCADOR DE PRODUCTOS EN ALMACENES")
print("=" * 40)
print(f"Buscando: {producto_buscado}")
print()

# Buscar en cada almacén
for almacen in almacenes:
    nombre_almacen = almacen["nombre"]
    productos = almacen["productos"]
    
    print(f"Buscando en Almacén {nombre_almacen}:")
    
    # Buscar en los productos de este almacén
    for i, producto in enumerate(productos):
        print(f"   Revisando producto #{i+1}: {producto}")
        
        if producto == producto_buscado:
            print(f"   ✅ ¡Producto encontrado en Almacén {nombre_almacen}!")
            encontrado = True
            break  # Salir del bucle interno
    
    print(f"   Búsqueda en Almacén {nombre_almacen} completada")
    print()
    
    if encontrado:
        break  # Salir del bucle externo si ya encontramos el producto

if encontrado:
    print(f"🎯 {producto_buscado} encontrado. Búsqueda exitosa.")
else:
    print(f"❌ {producto_buscado} no encontrado en ningún almacén.")

print("🔚 Búsqueda finalizada")

Patrones avanzados de control de bucles

Patrón: Búsqueda con bandera

# ================================
# 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: Bucle y medio

# ================================
# 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")

Buenas prácticas para el control de bucles

1. Usa break con moderación:

# ❌ 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...

2. Prefiere continue sobre if anidados:

# ❌ 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...

3. Usa else para casos de éxito:

# 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

4. Evita bucles anidados profundos:

# ❌ 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)

Comprueba tu comprensión 🧠

  1. ¿Cuál es la diferencia entre break y continue?

  2. ¿Qué imprimirá el siguiente código?

    for i in range(5):
        if i == 3:
            continue
        print(i, end=" ")
    
  3. ¿Cuándo se ejecuta el bloque else de un bucle?

  4. ¿Qué imprimirá este código?

    for i in range(3):
        if i == 10:
            break
        print(i, end=" ")
    else:
        print("Fin", end=" ")
    

Soluciones

  1. Diferencia entre break y continue:

    • 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.
  2. 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.

  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.

  4. 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”.

Ejercicio práctico: Sistema de procesamiento de pedidos

# ================================
# 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.


🧭 Navegación:

Capítulos de esta sección:

Patrones Comunes: Recetas Probadas para Problemas Frecuentes

🧭 Navegació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.

El libro de recetas de tu almacén 📚

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.

Patrón 1: Búsqueda lineal

Este patrón te permite encontrar un elemento específico en una colección:

# ================================
# BUSCADOR DE ELEMENTOS
# ================================

productos = ["laptop", "mouse", "teclado", "monitor", "auriculares"]
producto_buscado = "teclado"

print("🔍 BUSCADOR DE ELEMENTOS")
print("=" * 25)
print(f"Buscando: {producto_buscado}")
print(f"En lista: {productos}")
print()

# Inicializar variables
encontrado = False
posicion = -1

# Buscar el elemento
for i, producto in enumerate(productos):
    print(f"Revisando posición {i}: {producto}")
    
    if producto == producto_buscado:
        encontrado = True
        posicion = i
        print(f"   ✅ ¡Elemento encontrado!")
        break
    
    print("   ⏭️ Continuando búsqueda...")

# Mostrar resultado
if encontrado:
    print(f"\n🎯 {producto_buscado} encontrado en posición {posicion}")
else:
    print(f"\n❌ {producto_buscado} no encontrado en la lista")

print("🔚 Búsqueda finalizada")

Variante: Búsqueda de todos los elementos que cumplen un criterio

# ================================
# BUSCADOR DE MÚLTIPLES ELEMENTOS
# ================================

productos = ["laptop", "mouse", "teclado", "monitor", "teclado", "auriculares"]
producto_buscado = "teclado"

print("🔍 BUSCADOR DE MÚLTIPLES ELEMENTOS")
print("=" * 35)
print(f"Buscando todas las ocurrencias de: {producto_buscado}")
print(f"En lista: {productos}")
print()

# Inicializar variables
posiciones = []

# Buscar todas las ocurrencias
for i, producto in enumerate(productos):
    print(f"Revisando posición {i}: {producto}")
    
    if producto == producto_buscado:
        posiciones.append(i)
        print(f"   ✅ ¡Elemento encontrado!")
    else:
        print("   ⏭️ Continuando búsqueda...")

# Mostrar resultado
if posiciones:
    print(f"\n🎯 {producto_buscado} encontrado en {len(posiciones)} posiciones: {posiciones}")
else:
    print(f"\n❌ {producto_buscado} no encontrado en la lista")

print("🔚 Búsqueda finalizada")

Patrón 2: Filtrado

Este patrón te permite seleccionar elementos que cumplen ciertos criterios:

# ================================
# FILTRADOR DE ELEMENTOS
# ================================

productos = [
    {"nombre": "laptop", "precio": 1200, "disponible": True},
    {"nombre": "mouse", "precio": 25, "disponible": True},
    {"nombre": "teclado", "precio": 50, "disponible": False},
    {"nombre": "monitor", "precio": 300, "disponible": True},
    {"nombre": "auriculares", "precio": 80, "disponible": False}
]

print("🔍 FILTRADOR DE ELEMENTOS")
print("=" * 25)

# Filtrar productos disponibles
productos_disponibles = []

for producto in productos:
    if producto["disponible"]:
        productos_disponibles.append(producto)

print(f"Productos disponibles: {len(productos_disponibles)}")
for producto in productos_disponibles:
    print(f"   • {producto['nombre']} - ${producto['precio']}")
print()

# Filtrar productos económicos (menos de $100)
productos_economicos = []

for producto in productos:
    if producto["precio"] < 100:
        productos_economicos.append(producto)

print(f"Productos económicos: {len(productos_economicos)}")
for producto in productos_economicos:
    print(f"   • {producto['nombre']} - ${producto['precio']}")
print()

# Filtrar productos disponibles Y económicos
productos_disponibles_economicos = []

for producto in productos:
    if producto["disponible"] and producto["precio"] < 100:
        productos_disponibles_economicos.append(producto)

print(f"Productos disponibles y económicos: {len(productos_disponibles_economicos)}")
for producto in productos_disponibles_economicos:
    print(f"   • {producto['nombre']} - ${producto['precio']}")
print()

# Versión con comprensión de listas
productos_filtrados = [p for p in productos if p["disponible"] and p["precio"] < 100]
print(f"Usando comprensión de listas: {len(productos_filtrados)} productos")
for producto in productos_filtrados:
    print(f"   • {producto['nombre']} - ${producto['precio']}")

print("🔚 Filtrado finalizado")

Patrón 3: Transformación (Mapeo)

Este patrón te permite aplicar una transformación a cada elemento de una colección:

# ================================
# TRANSFORMADOR DE ELEMENTOS
# ================================

precios = [100, 25, 50, 300, 80]

print("🔄 TRANSFORMADOR DE ELEMENTOS")
print("=" * 30)
print(f"Precios originales: {precios}")
print()

# Aplicar descuento del 10%
precios_con_descuento = []

for precio in precios:
    precio_descuento = precio * 0.9  # 10% de descuento
    precios_con_descuento.append(precio_descuento)

print(f"Precios con 10% de descuento: {precios_con_descuento}")
print()

# Aplicar impuesto del 21%
precios_con_impuesto = []

for precio in precios:
    precio_impuesto = precio * 1.21  # 21% de impuesto
    precios_con_impuesto.append(precio_impuesto)

print(f"Precios con 21% de impuesto: {precios_con_impuesto}")
print()

# Versión con comprensión de listas
precios_formateados = [f"${precio:.2f}" for precio in precios]
print(f"Precios formateados: {precios_formateados}")
print()

# Transformación de objetos
productos = [
    {"nombre": "laptop", "precio": 1200},
    {"nombre": "mouse", "precio": 25},
    {"nombre": "teclado", "precio": 50}
]

# Añadir campo de precio con impuesto
for producto in productos:
    producto["precio_con_impuesto"] = producto["precio"] * 1.21

print("Productos con precio e impuesto:")
for producto in productos:
    print(f"   • {producto['nombre']}: ${producto['precio']} (con impuesto: ${producto['precio_con_impuesto']:.2f})")

print("🔚 Transformación finalizada")

Patrón 4: Acumulación

Este patrón te permite combinar elementos para obtener un resultado acumulado:

# ================================
# ACUMULADOR DE VALORES
# ================================

ventas = [1200, 300, 800, 550, 1000, 450]

print("📊 ACUMULADOR DE VALORES")
print("=" * 25)
print(f"Ventas diarias: {ventas}")
print()

# Calcular suma total
total_ventas = 0

for venta in ventas:
    total_ventas += venta

print(f"Total de ventas: ${total_ventas}")
print()

# Calcular promedio
promedio_ventas = total_ventas / len(ventas)
print(f"Promedio de ventas: ${promedio_ventas:.2f}")
print()

# Encontrar máximo y mínimo
venta_maxima = ventas[0]
venta_minima = ventas[0]
dia_maximo = 1
dia_minimo = 1

for i, venta in enumerate(ventas, 1):
    if venta > venta_maxima:
        venta_maxima = venta
        dia_maximo = i
    
    if venta < venta_minima:
        venta_minima = venta
        dia_minimo = i

print(f"Venta máxima: ${venta_maxima} (Día {dia_maximo})")
print(f"Venta mínima: ${venta_minima} (Día {dia_minimo})")
print()

# Contar ventas por encima del promedio
ventas_sobre_promedio = 0

for venta in ventas:
    if venta > promedio_ventas:
        ventas_sobre_promedio += 1

print(f"Días con ventas sobre el promedio: {ventas_sobre_promedio}")

print("🔚 Análisis finalizado")

Variante: Acumulación con diccionarios

# ================================
# CONTADOR DE FRECUENCIAS
# ================================

votos = ["A", "B", "A", "C", "B", "B", "A", "C", "A", "D", "B", "A"]

print("📊 CONTADOR DE FRECUENCIAS")
print("=" * 25)
print(f"Votos: {votos}")
print()

# Contar frecuencia de cada opción
frecuencias = {}

for voto in votos:
    if voto in frecuencias:
        frecuencias[voto] += 1
    else:
        frecuencias[voto] = 1

print("Resultados de la votación:")
for opcion, cantidad in frecuencias.items():
    print(f"   • Opción {opcion}: {cantidad} votos")
print()

# Encontrar la opción ganadora
opcion_ganadora = ""
max_votos = 0

for opcion, cantidad in frecuencias.items():
    if cantidad > max_votos:
        max_votos = cantidad
        opcion_ganadora = opcion

print(f"🏆 La opción ganadora es: {opcion_ganadora} con {max_votos} votos")

print("🔚 Conteo finalizado")

Patrón 5: Validación

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")

Patrón 6: Agrupación

Este patrón te permite organizar elementos en grupos según ciertos criterios:

# ================================
# AGRUPADOR DE ELEMENTOS
# ================================

productos = [
    {"nombre": "laptop", "categoria": "electrónica", "precio": 1200},
    {"nombre": "camisa", "categoria": "ropa", "precio": 25},
    {"nombre": "pantalón", "categoria": "ropa", "precio": 35},
    {"nombre": "tablet", "categoria": "electrónica", "precio": 300},
    {"nombre": "zapatos", "categoria": "ropa", "precio": 80},
    {"nombre": "monitor", "categoria": "electrónica", "precio": 250}
]

print("📊 AGRUPADOR DE ELEMENTOS")
print("=" * 25)
print(f"Total de productos: {len(productos)}")
print()

# Agrupar por categoría
productos_por_categoria = {}

for producto in productos:
    categoria = producto["categoria"]
    
    if categoria not in productos_por_categoria:
        productos_por_categoria[categoria] = []
    
    productos_por_categoria[categoria].append(producto)

# Mostrar grupos
print("Productos agrupados por categoría:")
for categoria, lista_productos in productos_por_categoria.items():
    print(f"📁 {categoria.upper()} ({len(lista_productos)} productos):")
    
    for producto in lista_productos:
        print(f"   • {producto['nombre']} - ${producto['precio']}")
    
    # Calcular precio promedio de la categoría
    precio_total = sum(p["precio"] for p in lista_productos)
    precio_promedio = precio_total / len(lista_productos)
    print(f"   📊 Precio promedio: ${precio_promedio:.2f}")
    print()

print("🔚 Agrupación finalizada")

Patrón 7: Combinación de colecciones

Este patrón te permite trabajar con múltiples colecciones relacionadas:

# ================================
# COMBINADOR DE COLECCIONES
# ================================

clientes = [
    {"id": 1, "nombre": "Ana García"},
    {"id": 2, "nombre": "Carlos López"},
    {"id": 3, "nombre": "Elena Martínez"}
]

pedidos = [
    {"cliente_id": 1, "producto": "laptop", "cantidad": 1},
    {"cliente_id": 2, "producto": "monitor", "cantidad": 2},
    {"cliente_id": 1, "producto": "mouse", "cantidad": 1},
    {"cliente_id": 3, "producto": "teclado", "cantidad": 1},
    {"cliente_id": 2, "producto": "auriculares", "cantidad": 1},
    {"cliente_id": 1, "producto": "tablet", "cantidad": 1}
]

print("🔄 COMBINADOR DE COLECCIONES")
print("=" * 30)
print(f"Clientes: {len(clientes)}")
print(f"Pedidos: {len(pedidos)}")
print()

# Crear informe de pedidos por cliente
print("INFORME DE PEDIDOS POR CLIENTE:")
print("=" * 30)

for cliente in clientes:
    cliente_id = cliente["id"]
    nombre = cliente["nombre"]
    
    print(f"👤 Cliente: {nombre}")
    
    # Encontrar todos los pedidos de este cliente
    pedidos_cliente = []
    for pedido in pedidos:
        if pedido["cliente_id"] == cliente_id:
            pedidos_cliente.append(pedido)
    
    # Mostrar pedidos
    if pedidos_cliente:
        print(f"   📦 Pedidos ({len(pedidos_cliente)}):")
        for i, pedido in enumerate(pedidos_cliente, 1):
            print(f"      {i}. {pedido['producto']} (x{pedido['cantidad']})")
    else:
        print("   ❌ No tiene pedidos")
    
    print()

print("🔚 Informe finalizado")

Patrón 8: Procesamiento por lotes

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")

Comprueba tu comprensión 🧠

  1. ¿Qué patrón utilizarías para encontrar todos los productos con precio mayor a $100?

  2. ¿Cuál es la diferencia entre el patrón de transformación y el patrón de acumulación?

  3. 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.

  4. ¿Qué patrón utilizarías para contar cuántas veces aparece cada letra en una cadena de texto?

Soluciones

  1. 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]
    
  2. 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.).
  3. 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")
    
  4. 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
    

Ejercicio práctico: Sistema de análisis de ventas

# ================================
# 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.


🧭 Navegación:

Capítulos de esta sección:

Diagramas de Flujo para Estructuras de Control

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.

Estructura Condicional (if-elif-else)

Inicio¿Condición 1es verdadera?¿Condición 2es verdadera?Ejecutarbloque ifEjecutarbloque elifEjecutarbloque elseFin

Ejemplo de código

edad = 25

if edad < 18: print("Menor de edad") elif edad < 65: print("Adulto") else: print("Adulto mayor")

NoNo

Bucle For

InicioObtener iterabley preparar iteración¿Quedanelementos?Obtener siguienteelementoEjecutar bloquede código¿break?¿continue?Fin

Ejemplo de bucle for

frutas = ["manzana", "banana", "cereza"]

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}")

NoNoNo

Bucle While

Inicio¿Condiciónes verdadera?Ejecutar bloquede código¿break?¿continue?Actualizar variablesde controlFin

Ejemplo de bucle while

contador = 0

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}")

NoNoNo

Estructura Try-Except-Finally

InicioEjecutarbloque try¿Ocurreexcepción?¿Tipo deexcepción?Ejecutarexcept tipo 1Ejecutarexcept tipo 2Ejecutarexcept genéricoEjecutarbloque elseEjecutarbloque finallyFin

Ejemplo de try-except-finally

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")

NoTipo 1Tipo 2Otro

Comprensiones de Lista

InicioCrear listavacía (resultado)Obtener iterablede origen¿Quedanelementos?Obtener siguienteelemento¿Cumplecondición?Aplicar expresiónal elementoAgregar resultadoa la listaFin

Ejemplo de comprensión de lista

numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Obtener cuadrados de números pares

cuadrados_pares = [x**2 for x in numeros if x % 2 == 0] print(cuadrados_pares) # [4, 16, 36, 64, 100]

Equivalente con bucle for tradicional

NoNo

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.

Quiz: Estructuras de Control

🧭 Navegación:

¡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.

Instrucciones

  • Lee cada pregunta cuidadosamente
  • Intenta responder sin mirar las soluciones
  • Al final, compara tus respuestas con las soluciones proporcionadas
  • Cada respuesta correcta vale 1 punto

Preguntas

1. Condicionales

¿Qué imprimirá el siguiente código?

x = 5
y = 10
if x > y:
    print("A")
elif x + 5 >= y:
    print("B")
elif y - 5 >= x:
    print("C")
else:
    print("D")

a) A b) B c) C d) D

2. Operador Ternario

¿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"

3. Bucles For

¿Qué imprimirá el siguiente código?

for i in range(1, 10, 2):
    print(i, end=" ")

a) 1 2 3 4 5 6 7 8 9 b) 1 3 5 7 9 c) 2 4 6 8 d) 1 3 5 7

4. Bucles While

¿Cuántas veces se ejecutará el bucle en el siguiente código?

contador = 10
while contador > 0:
    print(contador)
    contador -= 2

a) 5 veces b) 10 veces c) 6 veces d) Infinitas veces

5. Break y Continue

¿Qué imprimirá el siguiente código?

for i in range(10):
    if i == 3:
        continue
    if i == 8:
        break
    print(i, end=" ")

a) 0 1 2 4 5 6 7 b) 0 1 2 4 5 6 7 8 c) 0 1 2 3 4 5 6 7 d) 0 1 2 4 5 6 7 9

6. Bucles Anidados

¿Qué imprimirá el siguiente código?

for i in range(3):
    for j in range(2):
        print(f"({i},{j})", end=" ")
    print()

a)

(0,0) (0,1)
(1,0) (1,1)
(2,0) (2,1)

b)

(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)

c)

(0,0) (1,0) (2,0)
(0,1) (1,1) (2,1)

d)

(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)

7. Else en Bucles

¿Qué imprimirá el siguiente código?

for i in range(5):
    if i == 10:
        break
    print(i, end=" ")
else:
    print("Fin", end=" ")

a) 0 1 2 3 4 b) 0 1 2 3 4 Fin c) Fin d) No imprimirá nada

8. Patrones Comunes

¿Qué patrón se está utilizando en el siguiente código?

numeros = [10, 25, 3, 8, 42, 15, 7]
maximo = numeros[0]
for numero in numeros[1:]:
    if numero > maximo:
        maximo = numero
print(maximo)

a) Filtrado b) Transformación c) Acumulación d) Búsqueda

9. Comprensión de Listas

¿Cuál es el resultado de la siguiente comprensión de lista?

[x**2 for x in range(1, 6) if x % 2 == 0]

a) [1, 4, 9, 16, 25] b) [4, 16] c) [1, 9, 25] d) [2, 4]

10. Diagramas de Flujo

¿Qué símbolo se utiliza para representar una decisión en un diagrama de flujo?

a) Rectángulo b) Óvalo c) Rombo d) Paralelogramo

11. Bucles y Listas

¿Qué imprimirá el siguiente código?

lista = [1, 2, 3, 4, 5]
for i in range(len(lista)):
    lista[i] *= 2
print(lista)

a) [1, 2, 3, 4, 5] b) [2, 4, 6, 8, 10] c) [1, 4, 9, 16, 25] d) [2, 4, 6, 8, 10, 1, 2, 3, 4, 5]

12. Bucles y Diccionarios

¿Qué imprimirá el siguiente código?

diccionario = {"a": 1, "b": 2, "c": 3}
for clave in diccionario:
    print(clave, end=" ")

a) a b c b) 1 2 3 c) a 1 b 2 c 3 d) El orden no está garantizado

13. Evaluación en Cortocircuito

¿Qué imprimirá el siguiente código?

x = 5
y = 0
if x > 10 and y / x > 2:
    print("A")
else:
    print("B")

a) A b) B c) Error de división por cero d) No imprimirá nada

14. Bucles y Funciones

¿Qué imprimirá el siguiente código?

def sumar_lista(lista):
    total = 0
    for numero in lista:
        if numero < 0:
            return 0
        total += numero
    return total

print(sumar_lista([1, 2, 3, -4, 5]))

a) 6 b) 7 c) 0 d) 11

15. Patrones Avanzados

¿Qué hace el siguiente código?

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

Soluciones

Haz clic para ver las respuestas

1. Condicionales

Respuesta: b) B

Explicación:

  • x > y es False (5 no es mayor que 10), así que no entra en el primer if
  • x + 5 >= y es True (5 + 5 = 10, que es igual a 10), así que entra en el primer elif
  • Imprime “B” y no evalúa las demás condiciones

2. Operador Ternario

Respuesta: a) estado = "Mayor de edad" if edad >= 18 else "Menor de edad"

Explicación:

  • En Python, el operador ternario tiene la forma valor_si_verdadero if condicion else valor_si_falso
  • Las otras opciones son sintaxis de otros lenguajes o no existen en Python

3. Bucles For

Respuesta: b) 1 3 5 7 9

Explicación:

  • range(1, 10, 2) genera los números del 1 al 9 (10 no incluido) con paso 2
  • Esto produce la secuencia 1, 3, 5, 7, 9

4. Bucles While

Respuesta: a) 5 veces

Explicación:

  • El bucle comienza con contador = 10
  • En cada iteración, contador se reduce en 2
  • Las iteraciones son: 10, 8, 6, 4, 2
  • Después de la quinta iteración, contador = 0, y la condición contador > 0 es falsa, por lo que el bucle termina

5. Break y Continue

Respuesta: a) 0 1 2 4 5 6 7

Explicación:

  • El bucle itera de 0 a 9
  • Cuando i == 3, la instrucción continue hace que se salte el resto del código en esa iteración y pase a la siguiente
  • Cuando i == 8, la instrucción break hace que el bucle termine completamente
  • Por lo tanto, se imprimen los números 0, 1, 2, 4, 5, 6, 7

6. Bucles Anidados

Respuesta: a)

(0,0) (0,1)
(1,0) (1,1)
(2,0) (2,1)

Explicación:

  • El bucle externo itera de 0 a 2
  • Para cada valor de i, el bucle interno itera de 0 a 1
  • Después de cada bucle interno completo, se imprime una nueva línea

7. Else en Bucles

Respuesta: b) 0 1 2 3 4 Fin

Explicación:

  • El bucle itera de 0 a 4, imprimiendo cada número
  • La condición 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

8. Patrones Comunes

Respuesta: c) Acumulación

Explicación:

  • El código está encontrando el valor máximo en una lista
  • Comienza con un valor inicial (el primer elemento) y lo actualiza si encuentra uno mayor
  • Este es un patrón de acumulación, específicamente para encontrar un máximo

9. Comprensión de Listas

Respuesta: b) [4, 16]

Explicación:

  • La comprensión genera el cuadrado de cada número del 1 al 5, pero solo para aquellos que son pares
  • Los números pares en ese rango son 2 y 4
  • Sus cuadrados son 4 y 16

10. Diagramas de Flujo

Respuesta: c) Rombo

Explicación:

  • 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)

11. Bucles y Listas

Respuesta: b) [2, 4, 6, 8, 10]

Explicación:

  • El bucle recorre cada índice de la lista
  • En cada iteración, multiplica el elemento en ese índice por 2
  • Esto modifica la lista original, duplicando cada valor

12. Bucles y Diccionarios

Respuesta: d) El orden no está garantizado

Explicació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

13. Evaluación en Cortocircuito

Respuesta: b) B

Explicación:

  • La expresión x > 10 es False (5 no es mayor que 10)
  • Debido a la evaluación en cortocircuito, Python no evalúa la segunda parte de la expresión and cuando la primera parte es False
  • Por lo tanto, y / x nunca se evalúa y no hay error de división por cero
  • Como la condición es False, se ejecuta el bloque else y se imprime “B”

14. Bucles y Funciones

Respuesta: c) 0

Explicación:

  • La función suma los números de la lista hasta que encuentra un número negativo
  • Cuando encuentra -4, retorna 0 inmediatamente
  • Por lo tanto, el resultado es 0, no la suma de los números positivos

15. Patrones Avanzados

Respuesta: a) Cuenta cuántas veces aparece cada letra en el texto

Explicación:

  • El código crea un diccionario donde las claves son las letras del texto
  • Para cada letra, incrementa su contador si ya existe en el diccionario, o lo inicializa en 1 si es la primera vez que aparece
  • Este es un patrón común para contar frecuencias

Puntuación

  • 13-15 puntos: ¡Excelente! Dominas las estructuras de control en Python.
  • 10-12 puntos: Muy bien. Tienes un buen entendimiento, pero repasa algunos conceptos.
  • 7-9 puntos: Aceptable. Necesitas reforzar tu comprensión de algunas estructuras de control.
  • Menos de 7 puntos: Recomendamos revisar nuevamente los capítulos sobre estructuras de control.

Ejercicio Final: Implementación de un Algoritmo Completo

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.


🧭 Navegación:

Capítulos de esta sección:

Estructuras de Datos en Python

🧭 Navegación:

¡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.

¿Qué son las estructuras de datos?

Las estructuras de datos son como sistemas de almacenamiento especializados en tu almacén:

  • Listas: Estanterías flexibles donde puedes añadir, quitar y reorganizar elementos
  • Diccionarios: Sistema de inventario con etiquetas para acceso rápido
  • Tuplas: Paquetes sellados que no pueden modificarse una vez creados
  • Conjuntos (Sets): Estaciones de clasificación que eliminan duplicados automáticamente

Estas estructuras te permiten almacenar, organizar y manipular datos de manera eficiente según tus necesidades específicas.

Contenido de esta sección

En esta sección aprenderás sobre:

  1. Listas - Estanterías flexibles para almacenar secuencias de elementos

    • Creación y acceso a elementos
    • Métodos para manipular listas
    • Listas por comprensión
  2. Diccionarios - Sistema de inventario con pares clave-valor

    • Creación y acceso a valores
    • Operaciones comunes
    • Diccionarios anidados
  3. Tuplas - Contenedores inmutables para datos que no deben cambiar

    • Creación y características
    • Cuándo usar tuplas vs listas
    • Desempaquetado de tuplas
  4. Conjuntos (Sets) - Colecciones sin duplicados para operaciones matemáticas

    • Creación y operaciones
    • Operaciones de conjuntos
    • Aplicaciones prácticas

¿Por qué son importantes las estructuras de datos?

Las estructuras de datos son fundamentales en programación porque:

  • Permiten organizar información de manera lógica y eficiente
  • Ofrecen operaciones especializadas para diferentes tipos de datos
  • Mejoran el rendimiento de tus programas
  • Facilitan la resolución de problemas complejos
  • Son la base para algoritmos y estructuras más avanzadas

Analogía del almacén

A lo largo de esta sección, continuaremos con la analogía del almacén:

  • Las listas son como estanterías o cintas transportadoras donde puedes colocar elementos en secuencia
  • Los diccionarios son como un sistema de inventario con códigos de barras para acceso instantáneo
  • Las tuplas son como paquetes sellados que garantizan que su contenido no cambiará
  • Los conjuntos son como estaciones de clasificación que automáticamente eliminan duplicados

Mapa conceptual

ESTRUCTURAS DE DATOS
|
|-- Listas (Secuencias mutables)
|   |-- Creación y acceso
|   |-- Métodos (append, insert, remove...)
|   |-- Listas por comprensión
|
|-- Diccionarios (Mapeo clave-valor)
|   |-- Creación y acceso
|   |-- Métodos (keys, values, items...)
|   |-- Diccionarios anidados
|
|-- Tuplas (Secuencias inmutables)
|   |-- Creación y características
|   |-- Desempaquetado
|   |-- Casos de uso
|
|-- Conjuntos (Colecciones sin duplicados)
    |-- Creación y operaciones
    |-- Operaciones matemáticas
    |-- Aplicaciones prácticas

¡Comencemos nuestro viaje por el mundo de las estructuras de datos en Python!


🧭 Navegación:

Capítulos de esta sección:

Listas: Las Estanterías Flexibles

🧭 Navegación:

¡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.

📚 Las estanterías inteligentes de tu almacén

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.

Características principales de las listas

1. ✅ Ordenadas: Mantienen el orden de inserción

Las listas son como una cinta transportadora que mantiene el orden exacto de los productos:

pasos_proceso = ["recibir", "verificar", "almacenar", "etiquetar", "enviar"]
print(f"Primer paso: {pasos_proceso[0]}")  # recibir
print(f"Último paso: {pasos_proceso[-1]}")  # enviar

2. ✅ Modificables (Mutables): Puedes cambiar su contenido

Puedes añadir, quitar o cambiar productos en tu estantería en cualquier momento:

inventario = ["laptop", "mouse", "teclado"]

# Añadir un producto nuevo
inventario.append("monitor")

# Cambiar un producto existente
inventario[1] = "mouse_gaming"

print(inventario)  # ['laptop', 'mouse_gaming', 'teclado', 'monitor']

3. ✅ Permiten duplicados: El mismo elemento puede aparecer varias veces

A diferencia de otras estructuras, las listas permiten tener múltiples copias del mismo elemento:

pedidos_dia = ["laptop", "mouse", "laptop", "teclado", "laptop"]
print(f"Laptops pedidas hoy: {pedidos_dia.count('laptop')}")  # 3

4. ✅ Indexadas: Acceso directo a cualquier posición

Puedes acceder instantáneamente a cualquier producto conociendo su posición:

productos = ["A", "B", "C", "D", "E"]
print(f"Primero: {productos[0]}")   # A
print(f"Tercero: {productos[2]}")   # C
print(f"Último: {productos[-1]}")   # E

Creando tus propias estanterías (listas)

Hay varias formas de crear listas en Python, igual que hay diferentes formas de montar estanterías en un almacén:

Método 1: Estantería vacía lista para llenar

# Estantería completamente vacía
productos = []
inventario = list()  # Otra forma de crear una lista vacía

Método 2: Estantería con productos iniciales

# Estantería ya con productos
frutas = ["manzana", "naranja", "plátano", "uva"]
numeros = [1, 2, 3, 4, 5]

Método 3: Estantería con productos variados

Las listas pueden contener cualquier tipo de datos, incluso otras listas:

# Información completa de un producto
producto_info = ["Laptop Dell XPS", 15.6, True, 999.99, ["negro", "plata"]]
print(f"Nombre: {producto_info[0]}")
print(f"Tamaño: {producto_info[1]} pulgadas")
print(f"Disponible: {producto_info[2]}")
print(f"Precio: ${producto_info[3]}")
print(f"Colores disponibles: {producto_info[4][0]} y {producto_info[4][1]}")

Método 4: Estantería con secuencias numéricas

# Crear lista de números con range()
numeros_pares = list(range(0, 11, 2))  # [0, 2, 4, 6, 8, 10]
print(f"Números pares hasta 10: {numeros_pares}")

Método 5: Estantería con generación automática (comprensión de listas)

# Generar lista con una fórmula
cuadrados = [x**2 for x in range(1, 6)]  # [1, 4, 9, 16, 25]
print(f"Cuadrados del 1 al 5: {cuadrados}")

Accediendo a los productos en tus estanterías

Acceso por índice: Conocer la posición exacta

productos = ["laptop", "mouse", "teclado", "monitor", "webcam"]

# Índices positivos (desde el inicio)
print(f"Primer producto: {productos[0]}")    # laptop
print(f"Tercer producto: {productos[2]}")    # teclado

# Índices negativos (desde el final)
print(f"Último producto: {productos[-1]}")   # webcam
print(f"Penúltimo producto: {productos[-2]}")  # monitor

Slicing: Obtener secciones de estantería

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.

Operaciones básicas con estanterías

1. Añadir productos a tu estantería

productos = ["laptop", "mouse"]

# append() - Añadir al final de la estantería
productos.append("teclado")
print(productos)  # ['laptop', 'mouse', 'teclado']

# insert() - Colocar en una posición específica
productos.insert(1, "monitor")  # Insertar en posición 1
print(productos)  # ['laptop', 'monitor', 'mouse', 'teclado']

# extend() - Añadir múltiples productos de una vez
nuevos_productos = ["webcam", "altavoces"]
productos.extend(nuevos_productos)
print(productos)  # ['laptop', 'monitor', 'mouse', 'teclado', 'webcam', 'altavoces']

2. Quitar productos de tu estantería

productos = ["laptop", "mouse", "teclado", "monitor", "mouse", "webcam"]

# remove() - Quitar la primera ocurrencia de un producto
productos.remove("mouse")  # Elimina el primer "mouse"
print(productos)  # ['laptop', 'teclado', 'monitor', 'mouse', 'webcam']

# pop() - Quitar y devolver un producto
ultimo = productos.pop()  # Quita el último
print(f"Producto retirado: {ultimo}")  # webcam
print(productos)  # ['laptop', 'teclado', 'monitor', 'mouse']

segundo = productos.pop(1)  # Quita el de posición 1
print(f"Producto retirado: {segundo}")  # teclado
print(productos)  # ['laptop', 'monitor', 'mouse']

# del - Eliminar por índice o sección
del productos[0]  # Elimina el primero
print(productos)  # ['monitor', 'mouse']

# clear() - Vaciar completamente la estantería
productos_temp = ["a", "b", "c"]
productos_temp.clear()
print(productos_temp)  # []

3. Buscar y contar productos

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

4. Organizar tu estantería

# Datos de ejemplo
numeros = [64, 34, 25, 12, 22, 11, 90]
nombres = ["Carlos", "Ana", "Pedro", "María", "Luis"]

# sort() - Ordenar la estantería original
numeros.sort()  # Orden ascendente
print(f"Números ordenados: {numeros}")  # [11, 12, 22, 25, 34, 64, 90]

nombres.sort(reverse=True)  # Orden descendente
print(f"Nombres en orden Z-A: {nombres}")  # ['Pedro', 'María', 'Luis', 'Carlos', 'Ana']

# sorted() - Crear una copia ordenada sin modificar la original
precios = [299.99, 49.99, 89.99, 159.99]
precios_ordenados = sorted(precios)
print(f"Precios ordenados: {precios_ordenados}")
print(f"Precios originales: {precios}")  # Sin cambios

# reverse() - Invertir el orden
productos = ["A", "B", "C", "D"]
productos.reverse()
print(f"Productos invertidos: {productos}")  # ['D', 'C', 'B', 'A']

Técnicas avanzadas para gestionar tus estanterías

1. Comprensiones de listas: La forma elegante de llenar estanterías

Las comprensiones de listas son como tener robots que llenan tu estantería siguiendo reglas específicas:

# Sintaxis: [expresión for elemento in iterable if condición]

# Crear lista de cuadrados
numeros = [1, 2, 3, 4, 5]
cuadrados = [x**2 for x in numeros]
print(f"Cuadrados: {cuadrados}")  # [1, 4, 9, 16, 25]

# Filtrar números pares
pares = [x for x in range(1, 11) if x % 2 == 0]
print(f"Números pares: {pares}")  # [2, 4, 6, 8, 10]

# Procesar strings
nombres = ["ana", "carlos", "maría", "pedro"]
nombres_titulo = [nombre.title() for nombre in nombres]
print(f"Nombres formateados: {nombres_titulo}")  # ['Ana', 'Carlos', 'María', 'Pedro']

2. Listas anidadas: Estanterías con compartimentos

Las listas anidadas son como estanterías con compartimentos o cajones, cada uno conteniendo su propia colección de elementos:

# Crear matriz 3x3 (como una estantería con 3 niveles y 3 compartimentos por nivel)
matriz = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# Acceder a elementos específicos
print(f"Elemento en fila 1, columna 2: {matriz[1][2]}")  # 6

# Ejemplo práctico: inventario por categorías
inventario_tienda = [
    ["Laptop Dell", "Laptop HP", "Laptop Lenovo"],  # Laptops
    ["Mouse Logitech", "Mouse Razer"],              # Mouses
    ["Teclado mecánico", "Teclado inalámbrico"]     # Teclados
]

print("📦 Inventario por categorías:")
categorias = ["Laptops", "Mouses", "Teclados"]
for i, categoria in enumerate(categorias):
    print(f"\n{categoria}:")
    for producto in inventario_tienda[i]:
        print(f"  - {producto}")

3. Operaciones matemáticas con listas

# 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}")

Aplicaciones prácticas de las listas

1. Sistema de gestión de inventario

def gestionar_inventario():
    """Sistema simple de gestión de inventario usando listas"""
    
    # Inventario inicial
    inventario = [
        {"id": 1, "nombre": "Laptop", "stock": 5, "precio": 800},
        {"id": 2, "nombre": "Mouse", "stock": 10, "precio": 25},
        {"id": 3, "nombre": "Teclado", "stock": 8, "precio": 60}
    ]
    
    # Mostrar inventario actual
    print("\n📦 INVENTARIO ACTUAL:")
    print("-" * 50)
    print(f"{'ID':<5}{'PRODUCTO':<15}{'STOCK':<10}{'PRECIO':<10}{'VALOR':<10}")
    print("-" * 50)
    
    valor_total = 0
    for item in inventario:
        valor_item = item["stock"] * item["precio"]
        valor_total += valor_item
        print(f"{item['id']:<5}{item['nombre']:<15}{item['stock']:<10}${item['precio']:<9}${valor_item:<9}")
    
    print("-" * 50)
    print(f"Valor total del inventario: ${valor_total:,.2f}")
    
    # Productos con poco stock (menos de 10 unidades)
    poco_stock = [item["nombre"] for item in inventario if item["stock"] < 10]
    print(f"\n⚠️ Productos con poco stock: {', '.join(poco_stock)}")
    
    # Producto más caro
    producto_mas_caro = max(inventario, key=lambda x: x["precio"])
    print(f"💰 Producto más caro: {producto_mas_caro['nombre']} (${producto_mas_caro['precio']})")

# Ejecutar el sistema
gestionar_inventario()

2. Análisis de datos de ventas

def analizar_ventas(datos_ventas):
    """Analiza datos de ventas y genera reporte usando listas"""
    
    # Extraer información
    productos = [venta["producto"] for venta in datos_ventas]
    cantidades = [venta["cantidad"] for venta in datos_ventas]
    precios = [venta["precio"] for venta in datos_ventas]
    totales = [venta["cantidad"] * venta["precio"] for venta in datos_ventas]
    
    # Análisis básico
    total_ingresos = sum(totales)
    venta_promedio = sum(totales) / len(totales)
    producto_mas_vendido = max(set(productos), key=productos.count)
    
    # Top 3 ventas más altas
    ventas_con_total = [(datos_ventas[i], totales[i]) for i in range(len(datos_ventas))]
    top_ventas = sorted(ventas_con_total, key=lambda x: x[1], reverse=True)[:3]
    
    # Generar reporte
    print("\n📊 REPORTE DE VENTAS")
    print("=" * 50)
    print(f"Total de transacciones: {len(datos_ventas)}")
    print(f"Ingresos totales: ${total_ingresos:,.2f}")
    print(f"Venta promedio: ${venta_promedio:.2f}")
    print(f"Producto más vendido: {producto_mas_vendido}")
    
    print(f"\n🏆 Top 3 ventas más altas:")
    for i, (venta, total) in enumerate(top_ventas, 1):
        print(f"   {i}. {venta['producto']} - ${total:.2f}")

# Datos de ejemplo
ventas_mes = [
    {"producto": "Laptop", "cantidad": 2, "precio": 800},
    {"producto": "Mouse", "cantidad": 5, "precio": 25},
    {"producto": "Teclado", "cantidad": 3, "precio": 60},
    {"producto": "Laptop", "cantidad": 1, "precio": 800},
    {"producto": "Monitor", "cantidad": 2, "precio": 300},
    {"producto": "Mouse", "cantidad": 10, "precio": 25}
]

analizar_ventas(ventas_mes)

Consejos y mejores prácticas

1. Rendimiento y eficiencia

# ✅ 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)

2. Evitar errores comunes

# ❌ 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.

Comprueba tu comprensión

Ejercicio 1: Filtrado de productos

Escribe una función que, dada una lista de productos con sus precios, devuelva una nueva lista con los productos que cuestan menos de 50 dólares.

Ver solución
def filtrar_productos_economicos(productos):
    return [producto for producto in productos if producto["precio"] < 50]

# Ejemplo de uso
inventario = [
    {"nombre": "Teclado", "precio": 45.99},
    {"nombre": "Monitor", "precio": 199.99},
    {"nombre": "Mouse", "precio": 25.50},
    {"nombre": "Disco duro", "precio": 89.99},
    {"nombre": "Webcam", "precio": 39.99}
]

productos_economicos = filtrar_productos_economicos(inventario)
print("Productos económicos:")
for producto in productos_economicos:
    print(f"- {producto['nombre']}: ${producto['precio']}")

Resultado:

Productos económicos:
- Teclado: $45.99
- Mouse: $25.5
- Webcam: $39.99

Ejercicio 2: Organización de inventario

Crea una función que organice una lista de productos en categorías, utilizando listas anidadas.

Ver solución
def organizar_por_categorias(productos):
    categorias = {}
    
    # Agrupar productos por categoría
    for producto in productos:
        categoria = producto["categoria"]
        if categoria not in categorias:
            categorias[categoria] = []
        categorias[categoria].append(producto["nombre"])
    
    # Convertir a lista de listas
    resultado = []
    for categoria, productos in categorias.items():
        resultado.append([categoria, productos])
    
    return resultado

# Ejemplo de uso
productos = [
    {"nombre": "Laptop Dell", "categoria": "Computadoras"},
    {"nombre": "Mouse Logitech", "categoria": "Accesorios"},
    {"nombre": "Teclado Mecánico", "categoria": "Accesorios"},
    {"nombre": "Monitor LG", "categoria": "Monitores"},
    {"nombre": "Laptop HP", "categoria": "Computadoras"}
]

inventario_organizado = organizar_por_categorias(productos)

print("Inventario por categorías:")
for categoria in inventario_organizado:
    print(f"\n{categoria[0]}:")
    for producto in categoria[1]:
        print(f"  - {producto}")

Resultado:

Inventario por categorías:
Computadoras:
  - Laptop Dell
  - Laptop HP

Accesorios:
  - Mouse Logitech
  - Teclado Mecánico

Monitores:
  - Monitor LG

Ejercicio 3: Análisis de ventas semanales

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)

Ejercicio 3: Análisis de ventas semanales

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

Resumen

Las listas en Python son como estanterías flexibles en tu almacén de datos:

  • Ordenadas: Mantienen el orden de los elementos
  • Modificables: Puedes añadir, quitar o cambiar elementos
  • Indexadas: Acceso directo a cualquier posición
  • Permiten duplicados: El mismo valor puede aparecer varias veces

Operaciones clave que debes recordar:

  • Crear: [], list(), comprensiones de lista
  • Acceder: lista[índice], lista[inicio:fin:paso]
  • Añadir: append(), insert(), extend()
  • Eliminar: remove(), pop(), del, clear()
  • Buscar: index(), count(), in
  • Ordenar: sort(), sorted(), reverse()

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.


🧭 Navegación:

Capítulos de esta sección:

Diccionarios: El Sistema de Inventario Inteligente

🧭 Navegación:

¡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.

🏷️ El sistema de inventario con etiquetas inteligentes

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.

Características principales de los diccionarios

1. ✅ Pares clave-valor: Cada elemento tiene una etiqueta única

Los diccionarios almacenan información en pares clave-valor, donde la clave es como el código de barras y el valor es toda la información asociada:

empleado = {
    "id": "E12345",             # clave: "id", valor: "E12345"
    "nombre": "Ana García",     # clave: "nombre", valor: "Ana García"
    "edad": 28,                 # clave: "edad", valor: 28
    "puesto": "Gerente",        # clave: "puesto", valor: "Gerente"
    "salario": 75000            # clave: "salario", valor: 75000
}

print(f"Empleado: {empleado['nombre']}")
print(f"Puesto: {empleado['puesto']}")

2. ✅ Ordenados por inserción (Python ≥ 3.7): Mantienen el orden

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

3. ✅ Modificables (Mutables): Puedes actualizar la información

Puedes añadir, cambiar o eliminar información en cualquier momento:

producto = {"nombre": "Laptop", "precio": 800}

# Actualizar el precio
producto["precio"] = 750

# Añadir información nueva
producto["descuento"] = 0.1
producto["stock"] = 5

# Eliminar información
del producto["descuento"]

print(f"Producto actualizado: {producto}")

4. ✅ Claves únicas: No puede haber códigos duplicados

Si intentas usar la misma clave dos veces, la segunda sobrescribe a la primera:

config = {
    "host": "localhost",
    "puerto": 8080,
    "host": "servidor.com"  # Esta sobrescribe la anterior
}
print(config)  # {'host': 'servidor.com', 'puerto': 8080}

5. ✅ Acceso súper rápido: Búsqueda instantánea

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}")

Creando tu propio sistema de inventario (diccionarios)

Hay varias formas de crear diccionarios en Python:

Método 1: Diccionario vacío listo para llenar

# Diccionario completamente vacío
inventario = {}
tambien_vacio = dict()  # Otra forma de crear un diccionario vacío

Método 2: Diccionario con elementos iniciales

# Diccionario ya con productos
stock = {
    "laptop": 15,
    "mouse": 50,
    "teclado": 30,
    "monitor": 8
}

Método 3: Diccionario usando dict() con argumentos nombrados

# Crear diccionario con argumentos nombrados
empleado = dict(nombre="Ana", edad=28, departamento="Ventas")
print(empleado)  # {'nombre': 'Ana', 'edad': 28, 'departamento': 'Ventas'}

Método 4: Diccionario a partir de pares clave-valor

# 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'}

Método 5: Diccionario por comprensión

# Generar diccionario con una fórmula
cuadrados = {x: x**2 for x in range(1, 6)}
print(cuadrados)  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Método 6: Diccionario con zip()

# Combinar dos listas en un diccionario
claves = ["nombre", "edad", "puesto"]
valores = ["María", 30, "Analista"]
empleado = dict(zip(claves, valores))
print(empleado)  # {'nombre': 'María', 'edad': 30, 'puesto': 'Analista'}

Operaciones básicas con diccionarios

1. Accediendo a la información

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.

2. Explorando el inventario

producto = {"nombre": "Laptop", "precio": 800, "stock": 5, "categoria": "Electrónicos"}

# keys() - obtener todas las claves
claves = producto.keys()
print(f"Información disponible: {list(claves)}")

# values() - obtener todos los valores
valores = producto.values()
print(f"Valores: {list(valores)}")

# items() - obtener pares clave-valor
pares = producto.items()
print(f"Pares clave-valor: {list(pares)}")

# Iteración sobre el diccionario
print("\nDetalles del producto:")
for clave, valor in producto.items():
    print(f"- {clave}: {valor}")

3. Actualizando el inventario

inventario = {"laptop": 10, "mouse": 25}

# Agregar/modificar elementos
inventario["teclado"] = 15      # Agregar nuevo
inventario["laptop"] = 12       # Modificar existente

# update() - actualizar con otro diccionario
nuevos_productos = {"monitor": 8, "webcam": 20}
inventario.update(nuevos_productos)

# update() con argumentos nombrados
inventario.update(altavoces=12, microfono=8)

print(f"Inventario actualizado: {inventario}")

4. Eliminando productos

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}")

Técnicas avanzadas para gestionar tu inventario

1. Diccionarios anidados: Información detallada y estructurada

Los diccionarios anidados te permiten organizar información compleja de manera jerárquica:

# Sistema de inventario con categorías y productos
inventario_completo = {
    "electrónicos": {
        "laptops": {
            "LAP001": {"nombre": "Laptop Dell XPS", "precio": 1200, "stock": 5},
            "LAP002": {"nombre": "MacBook Pro", "precio": 1800, "stock": 3}
        },
        "smartphones": {
            "CEL001": {"nombre": "iPhone 13", "precio": 999, "stock": 10},
            "CEL002": {"nombre": "Samsung S21", "precio": 850, "stock": 8}
        }
    },
    "muebles": {
        "sillas": {
            "SIL001": {"nombre": "Silla Ergonómica", "precio": 250, "stock": 15}
        },
        "mesas": {
            "MES001": {"nombre": "Mesa de Oficina", "precio": 350, "stock": 7}
        }
    }
}

# Acceder a información anidada
laptop_dell = inventario_completo["electrónicos"]["laptops"]["LAP001"]
print(f"Producto: {laptop_dell['nombre']}, Precio: ${laptop_dell['precio']}")

# Añadir nuevo producto a una categoría existente
inventario_completo["electrónicos"]["smartphones"]["CEL003"] = {
    "nombre": "Google Pixel", 
    "precio": 799, 
    "stock": 12
}

# Añadir nueva categoría
inventario_completo["accesorios"] = {
    "auriculares": {
        "AUR001": {"nombre": "Auriculares Bluetooth", "precio": 89, "stock": 20}
    }
}

2. Diccionarios por comprensión: Generación automática

Las comprensiones de diccionarios te permiten crear y transformar diccionarios de manera concisa:

# Crear diccionario de precios con IVA incluido
productos = {"laptop": 800, "mouse": 25, "teclado": 60, "monitor": 300}
productos_con_iva = {nombre: precio * 1.21 for nombre, precio in productos.items()}
print(f"Precios con IVA: {productos_con_iva}")

# Filtrar productos caros
productos_caros = {nombre: precio for nombre, precio in productos.items() if precio > 100}
print(f"Productos caros: {productos_caros}")

# Crear diccionario con formato específico
nombres = ["Ana", "Carlos", "María", "Pedro"]
ids_empleados = {nombre: f"EMP{i+1:03d}" for i, nombre in enumerate(nombres)}
print(f"IDs de empleados: {ids_empleados}")

3. Fusión y comparación de diccionarios

# Fusionar diccionarios (Python ≥ 3.9)
inventario_sede_a = {"laptop": 10, "mouse": 20, "teclado": 15}
inventario_sede_b = {"monitor": 8, "laptop": 5, "webcam": 12}

# Usando el operador | (Python 3.9+)
inventario_total = inventario_sede_a | inventario_sede_b
print(f"Inventario total: {inventario_total}")

# En versiones anteriores
from collections import ChainMap
inventario_combinado = dict(ChainMap(inventario_sede_b, inventario_sede_a))
print(f"Inventario combinado: {inventario_combinado}")

# Comparar diccionarios
config_predeterminada = {"tema": "claro", "notificaciones": True, "idioma": "es"}
config_usuario = {"tema": "oscuro", "idioma": "es"}

# Encontrar diferencias
diferencias = {k: config_usuario[k] for k in config_usuario 
               if k in config_predeterminada and config_usuario[k] != config_predeterminada[k]}
print(f"Configuraciones personalizadas: {diferencias}")

4. Copia de diccionarios: Superficial vs. Profunda

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

Aplicaciones prácticas de los diccionarios

1. Sistema de gestión de inventario

def gestionar_inventario():
    """Sistema simple de gestión de inventario usando diccionarios"""
    
    # Inventario inicial
    inventario = {
        "LAP001": {"nombre": "Laptop", "precio": 800, "stock": 5, "categoria": "Electrónicos"},
        "MOU001": {"nombre": "Mouse", "precio": 25, "stock": 10, "categoria": "Accesorios"},
        "TEC001": {"nombre": "Teclado", "precio": 60, "stock": 8, "categoria": "Accesorios"}
    }
    
    # Función para añadir producto
    def agregar_producto(codigo, nombre, precio, stock, categoria):
        if codigo in inventario:
            print(f"⚠️ El código {codigo} ya existe. Use otro código.")
            return False
        
        inventario[codigo] = {
            "nombre": nombre,
            "precio": precio,
            "stock": stock,
            "categoria": categoria
        }
        print(f"✅ Producto {codigo} ({nombre}) agregado correctamente.")
        return True
    
    # Función para actualizar stock
    def actualizar_stock(codigo, cantidad):
        if codigo not in inventario:
            print(f"❌ El código {codigo} no existe en el inventario.")
            return False
        
        inventario[codigo]["stock"] += cantidad
        print(f"✅ Stock de {inventario[codigo]['nombre']} actualizado a {inventario[codigo]['stock']} unidades.")
        return True
    
    # Función para mostrar inventario
    def mostrar_inventario():
        print("\n📦 INVENTARIO ACTUAL:")
        print("-" * 60)
        print(f"{'CÓDIGO':<10}{'PRODUCTO':<20}{'PRECIO':<10}{'STOCK':<10}{'CATEGORÍA':<15}")
        print("-" * 60)
        
        for codigo, datos in inventario.items():
            print(f"{codigo:<10}{datos['nombre']:<20}${datos['precio']:<9}{datos['stock']:<10}{datos['categoria']:<15}")
    
    # Función para buscar por categoría
    def buscar_por_categoria(categoria):
        productos = {codigo: datos for codigo, datos in inventario.items() 
                    if datos["categoria"].lower() == categoria.lower()}
        
        if not productos:
            print(f"❌ No hay productos en la categoría '{categoria}'.")
            return
        
        print(f"\n🔍 Productos en categoría '{categoria}':")
        for codigo, datos in productos.items():
            print(f"  • {datos['nombre']} (${datos['precio']}) - Stock: {datos['stock']}")
    
    # Demostración del sistema
    mostrar_inventario()
    
    # Agregar nuevo producto
    agregar_producto("MON001", "Monitor 24\"", 200, 3, "Electrónicos")
    
    # Actualizar stock
    actualizar_stock("MOU001", 5)  # Añadir 5 unidades
    actualizar_stock("TEC001", -2)  # Restar 2 unidades
    
    # Mostrar inventario actualizado
    mostrar_inventario()
    
    # Buscar por categoría
    buscar_por_categoria("Accesorios")

# Ejecutar el sistema
gestionar_inventario()

2. Análisis de datos de ventas

def analizar_ventas(datos_ventas):
    """Analiza datos de ventas agrupados por diferentes criterios"""
    
    # Inicializar diccionarios para análisis
    ventas_por_mes = {}
    ventas_por_producto = {}
    ventas_por_vendedor = {}
    
    # Procesar cada venta
    for venta in datos_ventas:
        fecha = venta["fecha"]
        mes = fecha[:7]  # "2024-01"
        producto = venta["producto"]
        vendedor = venta["vendedor"]
        total = venta["total"]
        
        # Agrupar por mes
        if mes not in ventas_por_mes:
            ventas_por_mes[mes] = {"total": 0, "cantidad": 0}
        ventas_por_mes[mes]["total"] += total
        ventas_por_mes[mes]["cantidad"] += 1
        
        # Agrupar por producto
        if producto not in ventas_por_producto:
            ventas_por_producto[producto] = {"total": 0, "cantidad": 0}
        ventas_por_producto[producto]["total"] += total
        ventas_por_producto[producto]["cantidad"] += 1
        
        # Agrupar por vendedor
        if vendedor not in ventas_por_vendedor:
            ventas_por_vendedor[vendedor] = {"total": 0, "cantidad": 0}
        ventas_por_vendedor[vendedor]["total"] += total
        ventas_por_vendedor[vendedor]["cantidad"] += 1
    
    # Generar reporte
    print("\n📊 ANÁLISIS DE VENTAS")
    print("=" * 50)
    
    # Reporte por mes
    print("\n📅 VENTAS POR MES:")
    for mes, datos in sorted(ventas_por_mes.items()):
        promedio = datos["total"] / datos["cantidad"]
        print(f"  {mes}: ${datos['total']:,.2f} ({datos['cantidad']} ventas, promedio: ${promedio:.2f})")
    
    # Top 3 productos
    top_productos = sorted(
        ventas_por_producto.items(),
        key=lambda x: x[1]["total"],
        reverse=True
    )[:3]
    
    print(f"\n🏆 TOP 3 PRODUCTOS:")
    for i, (producto, datos) in enumerate(top_productos, 1):
        print(f"  {i}. {producto}: ${datos['total']:,.2f} ({datos['cantidad']} ventas)")
    
    # Top vendedores
    top_vendedores = sorted(
        ventas_por_vendedor.items(),
        key=lambda x: x[1]["total"],
        reverse=True
    )
    
    print(f"\n👑 VENDEDORES POR DESEMPEÑO:")
    for i, (vendedor, datos) in enumerate(top_vendedores, 1):
        print(f"  {i}. {vendedor}: ${datos['total']:,.2f} ({datos['cantidad']} ventas)")

# Datos de ejemplo
ventas_ejemplo = [
    {"fecha": "2024-01-15", "producto": "Laptop", "vendedor": "Ana", "total": 1200},
    {"fecha": "2024-01-20", "producto": "Mouse", "vendedor": "Carlos", "total": 45},
    {"fecha": "2024-01-25", "producto": "Laptop", "vendedor": "Ana", "total": 1200},
    {"fecha": "2024-02-10", "producto": "Teclado", "vendedor": "María", "total": 120},
    {"fecha": "2024-02-15", "producto": "Monitor", "vendedor": "Carlos", "total": 400},
    {"fecha": "2024-02-20", "producto": "Laptop", "vendedor": "Ana", "total": 1200},
]

# Realizar análisis
analizar_ventas(ventas_ejemplo)

Consejos y mejores prácticas

1. Verificación de claves

# ❌ 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")

2. Uso de defaultdict para valores por defecto

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)}")

3. Evitar modificar durante la iteración

# ❌ 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.

Comprueba tu comprensión

Ejercicio 1: Contador de frecuencia

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}")

Resultado:

Frecuencia de caracteres:
' ': 2
'a': 2
'c': 1
'e': 1
'g': 1
'h': 1
'i': 1
'm': 1
'n': 2
'o': 2
'p': 2
'r': 2
't': 1
'y': 1
'ó': 1

Ejercicio 2: Fusión de inventarios

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")

Ejemplo de salida esperada:

Inventario fusionado:
- impresora: 2 unidades
- laptop: 8 unidades
- monitor: 4 unidades
- mouse: 10 unidades
- teclado: 8 unidades

Ejercicio 3: Análisis de ventas por categoría

Escribe una función que analice un conjunto de ventas y calcule el total vendido por categoría de producto.

Ver solución
# Código para analizar ventas por categoría
def analizar_ventas_por_categoria(ventas, productos):
    """
    Analiza ventas y calcula totales por categoría.
    
    Args:
        ventas: Lista de diccionarios con información de ventas
        productos: Diccionario con información de productos (incluye categoría)
    """
    # Inicializar diccionario para totales por categoría
    totales_por_categoria = {}
    
    # Procesar cada venta
    for venta in ventas:
        producto_id = venta["producto_id"]
        cantidad = venta["cantidad"]
        precio = venta["precio"]
        
        # Obtener categoría del producto
        if producto_id in productos:
            categoria = productos[producto_id]["categoria"]
            
            # Calcular total de esta venta
            total_venta = cantidad * precio
            
            # Actualizar total de la categoría
            if categoria in totales_por_categoria:
                totales_por_categoria[categoria] += total_venta
            else:
                totales_por_categoria[categoria] = total_venta
    
    # Mostrar resultados
    print("Ventas por categoría:")
    for categoria, total in sorted(totales_por_categoria.items()):
        print(f"- {categoria}: ${total:,.2f}")
    
    return totales_por_categoria

# Datos de ejemplo
productos = {
    "p1": {"nombre": "Laptop", "categoria": "Electrónicos"},
    "p2": {"nombre": "Mouse", "categoria": "Accesorios"},
    "p3": {"nombre": "Monitor", "categoria": "Periféricos"},
    "p4": {"nombre": "Teclado", "categoria": "Accesorios"},
    "p5": {"nombre": "Smartphone", "categoria": "Electrónicos"}
}

ventas = [
    {"producto_id": "p1", "cantidad": 3, "precio": 1200.00},
    {"producto_id": "p2", "cantidad": 5, "precio": 25.00},
    {"producto_id": "p3", "cantidad": 2, "precio": 200.00},
    {"producto_id": "p4", "cantidad": 7, "precio": 45.00},
    {"producto_id": "p5", "cantidad": 1, "precio": 300.00}
]

# Ejecutar análisis
analizar_ventas_por_categoria(ventas, productos)

Ejemplo de salida esperada:

Ventas por categoría:
- Accesorios: $365.00
- Electrónicos: $3,900.00
- Periféricos: $400.00
def analizar_ventas_por_categoria(ventas, productos):
    """
    Analiza ventas y calcula totales por categoría.
    
    Args:
        ventas: Lista de diccionarios con información de ventas
        productos: Diccionario con información de productos (incluye categoría)
    
    Returns:
        Diccionario con totales por categoría
    """
    ventas_por_categoria = {}
    
    for venta in ventas:
        codigo_producto = venta["producto"]
        cantidad = venta["cantidad"]
        
        # Obtener información del producto
        if codigo_producto in productos:
            producto_info = productos[codigo_producto]
            categoria = producto_info["categoria"]
            precio = producto_info["precio"]
            
            # Calcular importe de esta venta
            importe = precio * cantidad
            
            # Actualizar total de la categoría
            if categoria in ventas_por_categoria:
                ventas_por_categoria[categoria] += importe
            else:
                ventas_por_categoria[categoria] = importe
    
    return ventas_por_categoria

# Datos de ejemplo
productos = {
    "P001": {"nombre": "Laptop", "precio": 1200, "categoria": "Electrónicos"},
    "P002": {"nombre": "Mouse", "precio": 25, "categoria": "Accesorios"},
    "P003": {"nombre": "Monitor", "precio": 300, "categoria": "Electrónicos"},
    "P004": {"nombre": "Teclado", "precio": 80, "categoria": "Accesorios"},
    "P005": {"nombre": "Impresora", "precio": 200, "categoria": "Periféricos"}
}

ventas = [
    {"producto": "P001", "cantidad": 2, "fecha": "2024-01-15"},
    {"producto": "P002", "cantidad": 5, "fecha": "2024-01-16"},
    {"producto": "P003", "cantidad": 1, "fecha": "2024-01-20"},
    {"producto": "P001", "cantidad": 1, "fecha": "2024-02-05"},
    {"producto": "P004", "cantidad": 3, "fecha": "2024-02-10"},
    {"producto": "P005", "cantidad": 2, "fecha": "2024-02-15"}
]

resultado = analizar_ventas_por_categoria(ventas, productos)

print("\nVentas por categoría:")
for categoria, total in sorted(resultado.items(), key=lambda x: x[1], reverse=True):
    print(f"- {categoria}: ${total:,.2f}")

Resultado:

Ventas por categoría:
- Electrónicos: $3,900.00
- Periféricos: $400.00

Ejercicio 3: Análisis de ventas por categoría

# Ejemplo de salida esperada
print("Ventas por categoría:")
print("- Electrónicos: $3,900.00")
print("- Accesorios: $365.00")
print("- Periféricos: $400.00")

Resumen

Los diccionarios en Python son como un sistema de inventario inteligente en tu almacén de datos:

  • Pares clave-valor: Cada elemento tiene una etiqueta única (clave) y un valor asociado
  • Acceso instantáneo: Búsqueda por clave extremadamente rápida (O(1))
  • Modificables: Puedes añadir, cambiar o eliminar elementos fácilmente
  • Claves únicas: No puede haber claves duplicadas
  • Ordenados: Mantienen el orden de inserción (desde Python 3.7)

Operaciones clave que debes recordar:

  • Crear: {}, dict(), comprensiones de diccionario
  • Acceder: dict[key], get(), setdefault()
  • 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.


🧭 Navegación:

Capítulos de esta sección:

Tuplas en Python

🧭 Navegación:

Introducción a las Tuplas

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.

Creando Tuplas

Crear una tupla es similar a crear una lista, pero utilizamos paréntesis () en lugar de corchetes []:

# Tupla de productos no modificables
productos_basicos = ("arroz", "frijoles", "aceite", "sal")

# Tupla de coordenadas (x, y, z)
ubicacion_almacen = (40.7128, -74.0060, 10)

# Tupla de información de contacto
contacto_emergencia = ("Juan Pérez", "Supervisor", "555-123-4567")

También puedes crear una tupla sin paréntesis, simplemente separando los elementos con comas:

# Esto también es una tupla
dimensiones = 20, 30, 15  # (ancho, alto, profundidad)
print(type(dimensiones))  # <class 'tuple'>

Tupla de un solo elemento

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!

Tupla vacía

También puedes crear una tupla vacía:

tupla_vacia = ()
print(type(tupla_vacia))  # <class 'tuple'>

Accediendo a los Elementos de una Tupla

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")

Inmutabilidad: La Característica Clave

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.

Operaciones con Tuplas

Aunque no puedes modificar una tupla, puedes realizar varias operaciones con ellas:

1. Concatenación

Puedes unir dos o más tuplas para crear una nueva:

productos_oficina = ("laptop", "monitor", "teclado")
accesorios = ("mouse", "webcam", "auriculares")

# Concatenar tuplas
todos_productos = productos_oficina + accesorios
print(todos_productos)  # ("laptop", "monitor", "teclado", "mouse", "webcam", "auriculares")

2. Multiplicación

Puedes repetir una tupla multiplicándola por un número:

patron = (1, 2, 3)
patron_repetido = patron * 3
print(patron_repetido)  # (1, 2, 3, 1, 2, 3, 1, 2, 3)

3. Verificar si un elemento existe

productos = ("laptop", "monitor", "teclado", "mouse")
print("teclado" in productos)  # True
print("impresora" in productos)  # False

4. Longitud, mínimo, máximo y suma

numeros = (10, 5, 8, 3, 12)
print(len(numeros))  # 5
print(min(numeros))  # 3
print(max(numeros))  # 12
print(sum(numeros))  # 38

Desempaquetado de Tuplas

Una de las características más útiles de las tuplas es el desempaquetado, que te permite asignar los elementos de una tupla a variables individuales:

# Desempaquetado básico
dimensiones = (100, 50, 25)
ancho, alto, profundidad = dimensiones

print(f"Ancho: {ancho} cm")  # Ancho: 100 cm
print(f"Alto: {alto} cm")  # Alto: 50 cm
print(f"Profundidad: {profundidad} cm")  # Profundidad: 25 cm

El desempaquetado es especialmente útil para intercambiar valores sin necesidad de una variable temporal:

# Intercambio de valores
a = 5
b = 10
print(f"Antes - a: {a}, b: {b}")  # Antes - a: 5, b: 10

# Intercambio usando tuplas
a, b = b, a
print(f"Después - a: {a}, b: {b}")  # Después - a: 10, b: 5

Desempaquetado con asterisco

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.

Tuplas vs Listas: ¿Cuándo usar cada una?

CaracterísticaTuplasListas
MutabilidadInmutables (no se pueden modificar)Mutables (se pueden modificar)
SintaxisParéntesis ()Corchetes []
RendimientoLigeramente más rápidasLigeramente más lentas
Uso de memoriaMenorMayor
Casos de usoDatos que no deben cambiarDatos que necesitan ser modificados

Usa tuplas cuando:

  1. Necesites datos inmutables: Valores que no deben cambiar, como coordenadas, configuraciones o constantes.
  2. Quieras mejorar el rendimiento: Las tuplas son ligeramente más eficientes que las listas.
  3. Necesites claves para diccionarios: A diferencia de las listas, las tuplas pueden ser usadas como claves en diccionarios.
  4. Quieras comunicar intención: Usar una tupla indica a otros programadores que esos datos no deben cambiar.

Usa listas cuando:

  1. Necesites modificar la colección: Añadir, eliminar o cambiar elementos.
  2. Trabajes con datos homogéneos: Colecciones de elementos del mismo tipo que pueden crecer o reducirse.
  3. Necesites operaciones específicas de listas: Como sort(), append(), extend(), etc.

Tuplas como Claves de Diccionarios

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'

Métodos de Tuplas

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

Tuplas Anidadas

Al igual que las listas, las tuplas pueden contener otras tuplas:

# Tupla de productos con sus detalles (nombre, precio, stock)
inventario = (
    ("Laptop", 999.99, 10),
    ("Monitor", 299.50, 15),
    ("Teclado", 89.99, 30),
    ("Mouse", 24.99, 50)
)

# Acceder a elementos de tuplas anidadas
print(f"Producto: {inventario[0][0]}")  # Producto: Laptop
print(f"Precio: ${inventario[0][1]}")  # Precio: $999.99
print(f"Stock: {inventario[0][2]} unidades")  # Stock: 10 unidades

# Iterar sobre tuplas anidadas
print("\nInventario completo:")
for producto, precio, stock in inventario:
    print(f"{producto}: ${precio:.2f} - {stock} unidades disponibles")

Conversión entre Tuplas y Listas

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.

Aplicaciones Prácticas de las Tuplas

1. Registro de datos inmutables

# Registro de transacciones (id, fecha, monto, tipo)
transacciones = [
    (1001, "2023-07-15", 1299.99, "venta"),
    (1002, "2023-07-15", 499.50, "venta"),
    (1003, "2023-07-16", 1299.99, "devolución"),
    (1004, "2023-07-17", 899.99, "venta")
]

# Calcular el total de ventas
total_ventas = 0
for transaccion in transacciones:
    if transaccion[3] == "venta":
        total_ventas += transaccion[2]
    elif transaccion[3] == "devolución":
        total_ventas -= transaccion[2]

print(f"Total de ventas: ${total_ventas:.2f}")  # Total de ventas: $1799.98

2. Retorno múltiple de funciones

def obtener_estadisticas(numeros):
    """Calcula varias estadísticas de una lista de números."""
    total = sum(numeros)
    promedio = total / len(numeros)
    minimo = min(numeros)
    maximo = max(numeros)
    return (total, promedio, minimo, maximo)

# Usar la función y desempaquetar los resultados
ventas_semana = [1200, 1500, 900, 1100, 1800, 2000, 1300]
total, promedio, minimo, maximo = obtener_estadisticas(ventas_semana)

print(f"Ventas totales: ${total}")
print(f"Promedio diario: ${promedio:.2f}")
print(f"Venta mínima: ${minimo}")
print(f"Venta máxima: ${maximo}")

3. Coordenadas y puntos geométricos

# 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")

Comprueba tu comprensión

Vamos a poner a prueba tu comprensión de las tuplas con algunos ejercicios prácticos:

Ejercicio 1: Creación y acceso a tuplas

¿Qué imprimirá el siguiente código?

datos = ("Python", 3.9, 2023, [1, 2, 3])
print(datos[1])
print(datos[-1][0])
Ver solución
# Resultado:
print(version_info[1])  # 3.9
print(version_info[-1][0])  # 1

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.

Ejercicio 2: Inmutabilidad y modificación

¿Qué sucederá al ejecutar este código?

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().

Ejercicio 3: Desempaquetado de tuplas

¿Qué valores tendrán las variables después de ejecutar este código?

datos = (1, 2, 3, 4, 5, 6, 7)
a, *b, c, d = datos
Ver solución
# Código:
numeros = (1, 2, 3, 4, 5, 6, 7)
a, *b, c, d = numeros

# Resultado:
print(a)  # 1
print(b)  # [2, 3, 4, 5]
print(c)  # 6
print(d)  # 7

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.

Ejercicio 4: Aplicación práctica

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.

Resumen

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.


🧭 Navegación:

Conjuntos (Sets) en Python

🧭 Navegación:

Introducción a los Conjuntos

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.

Creando Conjuntos

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.

Características de los Conjuntos

Los conjuntos en Python tienen varias características importantes:

  1. Elementos únicos: No permiten duplicados
  2. Desordenados: No mantienen un orden específico
  3. Mutables: Puedes añadir o eliminar elementos
  4. Elementos inmutables: Solo pueden contener elementos inmutables (números, strings, tuplas, pero no listas o diccionarios)
  5. 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)}

Operaciones con Conjuntos

Añadir y Eliminar Elementos

# 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()

Operaciones Matemáticas de Conjuntos

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.

Verificación de Subconjuntos y Superconjuntos

# 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)

Comprensiones de Conjuntos

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}

Conjuntos Inmutables (frozenset)

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.

Aplicaciones Prácticas de los Conjuntos

1. Eliminar duplicados de una lista

# Lista con elementos duplicados
numeros_con_duplicados = [1, 2, 3, 2, 4, 5, 3, 6, 1, 7]

# Eliminar duplicados manteniendo el orden
numeros_sin_duplicados = list(dict.fromkeys(numeros_con_duplicados))
print(f"Sin duplicados (manteniendo orden): {numeros_sin_duplicados}")

# Eliminar duplicados (sin mantener el orden)
numeros_sin_duplicados_set = list(set(numeros_con_duplicados))
print(f"Sin duplicados (sin mantener orden): {numeros_sin_duplicados_set}")

2. Encontrar elementos únicos y comunes entre listas

# Listas de productos en diferentes almacenes
almacen_a = ["laptop", "monitor", "teclado", "mouse", "impresora", "scanner"]
almacen_b = ["laptop", "tablet", "smartphone", "monitor", "cables", "accesorios"]

# Convertir a conjuntos
set_a = set(almacen_a)
set_b = set(almacen_b)

# Productos en ambos almacenes
productos_comunes = set_a & set_b
print(f"Productos en ambos almacenes: {productos_comunes}")

# Productos únicos del almacén A
productos_unicos_a = set_a - set_b
print(f"Productos únicos del almacén A: {productos_unicos_a}")

# Productos únicos del almacén B
productos_unicos_b = set_b - set_a
print(f"Productos únicos del almacén B: {productos_unicos_b}")

# Todos los productos disponibles
todos_productos = set_a | set_b
print(f"Todos los productos disponibles: {todos_productos}")

3. Verificar si una lista contiene todos los elementos requeridos

# 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}")

4. Análisis de texto

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']}")

Rendimiento de los Conjuntos

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.

Conjuntos vs Listas vs Diccionarios: ¿Cuándo usar cada uno?

EstructuraCaracterísticasCasos de uso
Conjuntos- Elementos únicos
- Desordenados
- Operaciones matemáticas de conjuntos
- Verificación de pertenencia O(1)
- Eliminar duplicados
- Verificar pertenencia eficientemente
- Operaciones matemáticas entre colecciones
Listas- Permite duplicados
- Ordenados por índice
- Acceso por posición
- Verificación de pertenencia O(n)
- Cuando el orden importa
- Cuando necesitas duplicados
- Cuando necesitas acceder por índice
Diccionarios- Pares clave-valor
- Claves únicas
- Acceso por clave O(1)
- Mapeo de valores
- Búsqueda rápida por clave
- Almacenar propiedades de objetos

Usa conjuntos cuando:

  1. Necesites elementos únicos: Eliminar duplicados automáticamente
  2. La verificación de pertenencia sea crítica: Operaciones in muy rápidas
  3. Necesites operaciones matemáticas de conjuntos: Uniones, intersecciones, diferencias
  4. El orden no sea importante: Los conjuntos no mantienen un orden específico

Comprueba tu comprensión

Vamos a poner a prueba tu comprensión de los conjuntos con algunos ejercicios prácticos:

Ejercicio 1: Operaciones básicas con conjuntos

¿Qué imprimirá el siguiente código?

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(b - a)
Ver solución
# Código:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# Resultados:
print(a | b)  # {1, 2, 3, 4, 5, 6}
print(a & b)  # {3, 4}
print(a - b)  # {1, 2}
print(b - a)  # {5, 6}
  • a | b es la unión: todos los elementos que están en a o en b
  • a & b es la intersección: elementos comunes a a y b
  • a - b es la diferencia: elementos en a pero no en b
  • b - a es la diferencia: elementos en b pero no en a

Ejercicio 2: Eliminar duplicados manteniendo el orden

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.

Ejercicio 3: Encontrar elementos únicos en múltiples listas

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.

Ejercicio 4: Verificar anagramas

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).

Resumen

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.


🧭 Navegación:

Diagramas de Estructuras de Datos

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.

Diagrama de Listas

Las listas son colecciones ordenadas y mutables de elementos:

Lista: mi_lista = [10, 20, 30, 40, 50]Índice 0Valor: 10Índice 1Valor: 20Índice 2Valor: 30Índice 3Valor: 40Índice 4Valor: 50Acceso a elementosmi_lista[0] = 10mi_lista[2] = 30mi_lista[1:3] = [20, 30]mi_lista[-1] = 50  













Diagrama de Diccionarios

Los diccionarios son colecciones no ordenadas de pares clave-valor:

Diccionario: mi_dict = {'nombre': 'Ana', 'edad': 25, 'ciudad': 'Madrid'}Par clave-valor 1Clave: 'nombre'Valor: 'Ana'Par clave-valor 2Clave: 'edad'Valor: 25Par clave-valor 3Clave: 'ciudad'Valor: 'Madrid'Acceso a elementosmi_dict['nombre'] = 'Ana'mi_dict['edad'] = 25mi_dict.get('ciudad') = 'Madrid'  
















Diagrama de Tuplas

Las tuplas son colecciones ordenadas e inmutables de elementos:

Tupla: mi_tupla = (10, 'Hola', True, 3.14)Índice 0Valor: 10Índice 1Valor: 'Hola'Índice 2Valor: TrueÍndice 3Valor: 3.14InmutableAcceso a elementosmi_tupla[0] = 10mi_tupla[1] = 'Hola'mi_tupla[0] = 20 # Error!  












Diagrama de Conjuntos (Sets)

Los conjuntos son colecciones no ordenadas de elementos únicos:

Conjunto: mi_set = {10, 20, 30, 40}10203040Elementos únicosOperaciones comunesmi_set.add(50)mi_set.remove(10)20 in mi_set # Truemi_set.add(20) # No cambia  













Comparación de Estructuras de Datos

Este diagrama muestra una comparación de las principales características de las estructuras de datos en Python:

Estructuras de Datos en PythonLista

Características:

Ordenada: Sí Mutable: Sí Indexable: Sí Permite duplicados: Sí Sintaxis: [ ] Ejemplo: [1, 2, 3, 'hola']

Diccionario

Características:

Ordenada: Sí (desde 3.7) Mutable: Sí Indexable: Por clave Permite duplicados: No (claves) Sintaxis: { : } Ejemplo: {'a': 1, 'b': 2}

Tupla

Características:

Ordenada: Sí Mutable: No Indexable: Sí Permite duplicados: Sí Sintaxis: ( ) Ejemplo: (1, 2, 3, 'hola')

Conjunto

Características:

Ordenada: No Mutable: Sí Indexable: No Permite duplicados: No Sintaxis: { } Ejemplo: {1, 2, 3, 'hola'}

Estos diagramas te ayudarán a visualizar cómo se organizan las diferentes estructuras de datos en Python y cómo interactuar con ellas.

Quiz: Estructuras de Datos

🧭 Navegación:

¡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.

Instrucciones

  • Lee cada pregunta cuidadosamente
  • Intenta responder antes de ver la solución
  • Las soluciones están ocultas bajo cada pregunta
  • Al final encontrarás una evaluación basada en tus respuestas correctas

¡Buena suerte!


Preguntas Teóricas

1. Analogías del Almacén

¿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

2. Mutabilidad vs Inmutabilidad

¿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}")

3. Eficiencia de Operaciones

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):

  1. b) y c) - Buscar en diccionario y conjunto: O(1)
  2. 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")

Preguntas Prácticas

4. Manipulación de Listas

¿Qué imprimirá el siguiente código?

lista = [1, 2, 3, 4, 5]
lista[1:4] = [10]
print(lista)
Ver respuesta

Respuesta: [1, 10, 5]

Explicación:

  1. La lista original es [1, 2, 3, 4, 5]
  2. El slice [1:4] selecciona los elementos en los índices 1, 2 y 3
  3. Estos tres elementos son reemplazados por un solo elemento [10]
  4. El resultado es [1, 10, 5]

Este ejemplo demuestra que:

  • Los slices pueden ser reemplazados por un número diferente de elementos
  • La lista se ajusta automáticamente al nuevo tamaño
  • Los elementos fuera del slice permanecen sin cambios

5. Diccionarios Anidados

Dado el siguiente diccionario:

almacen = {
    'electrónicos': {
        'computadoras': {'stock': 10, 'precio': 1200},
        'tablets': {'stock': 15, 'precio': 300}
    },
    'muebles': {
        'sillas': {'stock': 20, 'precio': 50},
        'mesas': {'stock': 8, 'precio': 150}
    }
}

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:

  1. La función recorre cada categoría del almacén
  2. Para cada categoría, recorre cada producto
  3. Multiplica el stock por el precio de cada producto
  4. 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()
    )

6. Tuplas y Desempaquetado

¿Qué valores tendrán las variables después de ejecutar este código?

datos = [(1, 'a'), (2, 'b'), (3, 'c')]
numeros, letras = zip(*datos)
print(numeros)
print(letras)
Ver respuesta

Respuesta:

numeros = (1, 2, 3)
letras = ('a', 'b', 'c')

Explicación:

  1. zip(*datos) desempaqueta la lista de tuplas
  2. El operador * desempaqueta datos en argumentos individuales
  3. zip() crea un nuevo iterador que agrupa los elementos por posición
  4. El resultado se asigna a numeros y letras mediante desempaquetado

Este patrón es útil para:

  • Transponer matrices
  • Separar datos en columnas
  • Procesar datos estructurados

Ejemplo adicional:

# Crear una lista de tuplas desde columnas separadas
nums = [1, 2, 3]
lets = ['a', 'b', 'c']
combinados = list(zip(nums, lets))
print(combinados)  # [(1, 'a'), (2, 'b'), (3, 'c')]

# Volver a separar
n, l = zip(*combinados)
print(n)  # (1, 2, 3)
print(l)  # ('a', 'b', 'c')

7. Operaciones con Conjuntos

Dado el siguiente código:

empleados_python = {'Ana', 'Carlos', 'Diana', 'Eduardo'}
empleados_java = {'Bruno', 'Carlos', 'Diana', 'Fernando'}
empleados_javascript = {'Carlos', 'Gabriel', 'Diana', 'Helena'}

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)

Alternativa usando métodos:

# Los mismos resultados usando métodos
todos_lenguajes = empleados_python.intersection(empleados_java, empleados_javascript)
solo_python = empleados_python.difference(empleados_java.union(empleados_javascript))
al_menos_dos = (empleados_python.intersection(empleados_java)
                .union(empleados_python.intersection(empleados_javascript))
                .union(empleados_java.intersection(empleados_javascript)))

8. Proyecto Práctico: Sistema de Inventario

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:

  1. Uso de diccionarios anidados para almacenar datos
  2. Métodos para manipular el inventario
  3. Comprensiones de diccionarios para filtrar datos
  4. Formateo de strings para presentación

Características adicionales que podrías implementar:

  • Validación de datos (cantidades no negativas, precios válidos)
  • Historial de transacciones usando tuplas
  • Categorización de productos usando conjuntos
  • Búsqueda de productos por nombre o código

9. Desafío: Análisis de Datos

Dado un conjunto de datos de ventas, escribe funciones para:

  • Encontrar los productos más vendidos
  • Calcular las ventas por día
  • Identificar patrones de compra (productos que se compran juntos)
ventas = [
    ('2023-07-01', ['laptop', 'mouse', 'teclado']),
    ('2023-07-01', ['monitor', 'teclado']),
    ('2023-07-02', ['laptop', 'monitor']),
    ('2023-07-02', ['mouse', 'teclado', 'auriculares']),
    ('2023-07-03', ['laptop', 'mouse'])
]
Ver respuesta

Solución:

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:

  1. Uso de Counter para contar ocurrencias
  2. Diccionarios para agrupar datos por fecha
  3. Combinaciones para analizar patrones
  4. Comprensiones de diccionarios para filtrar resultados

Mejoras posibles:

  • Añadir análisis de tendencias temporales
  • Calcular correlaciones entre productos
  • Visualizar datos con gráficos
  • Generar recomendaciones basadas en patrones

10. Desafío Final: Optimización de Código

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:

  1. Uso de set() desde el inicio para productos únicos
  2. Uso de defaultdict para evitar verificaciones de existencia
  3. Uso de set() para clientes por producto, eliminando duplicados automáticamente
  4. 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:

  • Menos operaciones de verificación
  • Uso de estructuras de datos optimizadas
  • Mejor manejo de memoria

Evaluación

Cuenta tus respuestas correctas:

  • 9-10 correctas: ¡Excelente! Dominas las estructuras de datos en Python.
  • 7-8 correctas: ¡Muy bien! Tienes una comprensión sólida.
  • 5-6 correctas: Bien. Entiendes los conceptos básicos pero necesitas más práctica.
  • 3-4 correctas: Regular. Repasa los conceptos clave.
  • 0-2 correctas: Necesitas revisar el capítulo nuevamente.

Próximos Pasos

  1. Revisa los conceptos en los que tuviste dificultades
  2. Practica con los ejercicios adicionales de cada sección
  3. Intenta resolver problemas reales usando estas estructuras
  4. Avanza al siguiente capítulo: Funciones y Módulos

🧭 Navegación:

Capítulo 8: Funciones y Módulos

🧭 Navegación:

¡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.

🏗️ ¿Qué son las funciones?

Las funciones son bloques de código reutilizable que realizan una tarea específica. Son como máquinas especializadas en nuestro almacén que:

  • Reciben materias primas (parámetros de entrada)
  • Procesan la información (ejecutan el código interno)
  • Entregan un producto terminado (devuelven un resultado)
  • Se pueden usar múltiples veces sin tener que reconstruir la máquina

🏭 Analogía del almacén: La fábrica de herramientas

Imagina que nuestro almacén tiene una fábrica de herramientas donde:

  • Las funciones son máquinas especializadas (calculadora de precios, empaquetadora, etiquetadora)
  • Los parámetros son los materiales que introducimos en la máquina
  • El código interno es el proceso de manufactura de la máquina
  • El valor de retorno es el producto final que sale de la máquina
  • Los módulos son diferentes secciones del taller (sección eléctrica, sección mecánica, etc.)

🛠️ Ventajas de usar funciones

Reutilización de código

# Sin funciones: Código repetitivo
precio_laptop = 1500
impuesto_laptop = precio_laptop * 0.16
total_laptop = precio_laptop + impuesto_laptop
print(f"Total laptop: ${total_laptop}")

precio_mouse = 25
impuesto_mouse = precio_mouse * 0.16
total_mouse = precio_mouse + impuesto_mouse
print(f"Total mouse: ${total_mouse}")

# Con funciones: Código reutilizable
def calcular_precio_final(precio):
    """Máquina calculadora de precios con impuestos"""
    impuesto = precio * 0.16
    total = precio + impuesto
    return total

# Usamos la máquina múltiples veces
total_laptop = calcular_precio_final(1500)
total_mouse = calcular_precio_final(25)
print(f"Total laptop: ${total_laptop}")
print(f"Total mouse: ${total_mouse}")

Organización y legibilidad

# Funciones como "máquinas especializadas" del almacén
def validar_codigo_producto(codigo):
    """Máquina validadora de códigos de producto"""
    return len(codigo) >= 3 and codigo.isalnum()

def calcular_descuento(precio, porcentaje_descuento):
    """Máquina calculadora de descuentos"""
    return precio * (porcentaje_descuento / 100)

def generar_etiqueta_precio(nombre, precio, descuento=0):
    """Máquina generadora de etiquetas de precio"""
    precio_final = precio - calcular_descuento(precio, descuento)
    return f"🏷️ {nombre}: ${precio_final:.2f}"

# Proceso de etiquetado en el almacén
if validar_codigo_producto("LAP001"):
    etiqueta = generar_etiqueta_precio("Laptop Gaming", 1500, 10)
    print(etiqueta)  # 🏷️ Laptop Gaming: $1350.00

Facilidad de mantenimiento

# 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

📚 ¿Qué son los módulos?

Los módulos son archivos de Python que contienen funciones, clases y variables relacionadas. Son como departamentos especializados de nuestro almacén:

  • inventario.py - Departamento de gestión de inventario
  • ventas.py - Departamento de procesamiento de ventas
  • reportes.py - Departamento de análisis y reportes
  • utilidades.py - Departamento de herramientas generales

🏢 Estructura modular del almacén

# 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)

🎯 Contenido de este capítulo

En este capítulo construiremos nuestro departamento de herramientas especializadas aprendiendo:

🔧 Crear funciones con def

  • Anatomía de una función: máquinas personalizadas
  • Sintaxis y mejores prácticas
  • Funciones simples y complejas
  • Documentación con docstrings

⚙️ Parámetros y argumentos

  • Parámetros obligatorios y opcionales
  • Argumentos posicionales y por nombre
  • *args y **kwargs: máquinas flexibles
  • Valores por defecto inteligentes

📤 Retorno de valores

  • La instrucción return: productos de salida
  • Retorno de múltiples valores
  • Funciones sin retorno vs con retorno
  • Mejores prácticas de retorno

📦 Módulos estándar

  • Biblioteca estándar de Python: herramientas pre-fabricadas
  • Módulos esenciales: math, datetime, random, os
  • Importación y uso de módulos
  • Creación de módulos personalizados

📊 Diagramas de Funciones

  • Visualización del flujo de funciones
  • Mapas conceptuales de módulos
  • Diagramas de arquitectura modular

🏭 Proyecto práctico: Sistema de gestión modular

A lo largo del capítulo desarrollaremos un sistema de gestión de almacén modular que incluirá:

# Vista previa del sistema que construiremos

# Módulo: productos.py
def crear_producto(codigo, nombre, precio, categoria):
    """Crea un nuevo producto en el sistema"""
    return {
        'codigo': codigo,
        'nombre': nombre,
        'precio': precio,
        'categoria': categoria,
        'stock': 0
    }

def calcular_precio_con_descuento(producto, descuento_porcentaje):
    """Calcula precio final con descuento aplicado"""
    descuento = producto['precio'] * (descuento_porcentaje / 100)
    return producto['precio'] - descuento

# Módulo: inventario.py
def agregar_stock(producto, cantidad):
    """Añade stock a un producto existente"""
    producto['stock'] += cantidad
    return producto

def verificar_disponibilidad(producto, cantidad_solicitada):
    """Verifica si hay suficiente stock"""
    return producto['stock'] >= cantidad_solicitada

# Módulo: reportes.py
def generar_reporte_inventario(lista_productos):
    """Genera reporte completo de inventario"""
    total_productos = len(lista_productos)
    valor_total = sum(p['precio'] * p['stock'] for p in lista_productos)
    
    return {
        'total_productos': total_productos,
        'valor_total_inventario': valor_total,
        'productos_sin_stock': [p for p in lista_productos if p['stock'] == 0]
    }

🎨 Principios de diseño de funciones

1. Principio de responsabilidad única

# ❌ Función que hace demasiadas cosas
def procesar_producto_completo(codigo, nombre, precio, cantidad):
    # Crear producto
    # Validar datos
    # Actualizar inventario
    # Generar reporte
    # Enviar notificación
    pass  # Demasiada responsabilidad

# ✅ Funciones especializadas
def crear_producto(codigo, nombre, precio):
    """Se enfoca solo en crear el producto"""
    pass

def validar_datos_producto(producto):
    """Se enfoca solo en validar"""
    pass

def actualizar_inventario(producto, cantidad):
    """Se enfoca solo en el inventario"""
    pass

2. Nombres descriptivos

# ❌ 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)

3. Documentación clara

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

🚀 Lo que lograrás en este capítulo

Al finalizar este capítulo, tendrás:

  • Dominio completo de funciones: Crear, usar y optimizar funciones
  • Arquitectura modular: Organizar código en módulos especializados
  • Biblioteca personal: Colección de funciones reutilizables
  • Código profesional: Funciones bien documentadas y estructuradas
  • Sistema escalable: Base para proyectos más complejos

🎯 Preparando tu taller de herramientas

Antes de comenzar, piensa en las funciones como inversiones en eficiencia:

  • Tiempo inicial: Crear una función toma tiempo al principio
  • Beneficio a largo plazo: Cada uso posterior ahorra tiempo
  • Mantenimiento: Cambios centralizados en un solo lugar
  • Escalabilidad: Facilita el crecimiento del proyecto

¡Comencemos a construir tu departamento de herramientas especializadas que transformará tu forma de programar!


🧭 Navegación:

En este capítulo:

Crear funciones con def

Parámetros y argumentos

Retorno de valores

Módulos estándar

Diagramas de Funciones y Módulos

En esta sección visualizaremos cómo funcionan las funciones y módulos en Python para ayudar a comprender su estructura y comportamiento.

Anatomía de una Función

Función en Pythondef calcular_area(base, altura):

"""Calcula el área de un rectángulo.

Args: base: Longitud de la base altura: Altura del rectángulo

Returns: float: El área calculada """

Calcular el área

area = base * altura

Retornar el resultado

return area

Elementos de una FunciónNombre: calcular_areaParámetros: base, alturaDocumentación: DocstringLógica: Cálculos y operacionesValor de retorno: area

Flujo de Ejecución de una Función

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  















Estructura de Módulos y Paquetes

Proyecto Pythonmi_paquete/__init__.pymodulo1.pymodulo2.pysubpaquete/__init__.pymodulo3.pymodulo4.pymain.pyImportaciones

En main.py

import mi_paquete.modulo1 from mi_paquete import modulo2 from mi_paquete.subpaquete import modulo3

En modulo1.py

from . import modulo2 from .subpaquete import modulo3

En subpaquete/init.py

from . import modulo3, modulo4

Ciclo de Vida de un Módulo

Inicioimport mi_modulo¿Módulo ensys.modules?Buscar archivomi_modulo.pyCrear namespacepara el móduloEjecutar códigodel móduloAgregar móduloa sys.modulesUsar módulodesde cachéFin

Primera importación - Ejecuta todo el código

import mi_modulo print("Primera importación completada")

Segunda importación - Usa la versión en caché

import mi_modulo print("Segunda importación completada")

Recarga forzada del módulo

import importlib importlib.reload(mi_modulo) print("Módulo recargado")

No

Estos diagramas te ayudarán a visualizar cómo funcionan las funciones y módulos en Python, facilitando su comprensión y uso efectivo en tus programas.

Capítulo 9: Manejo de Archivos

🧭 Navegación:

¡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.

📁 ¿Qué es el manejo de archivos?

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:

  • Leer informes y extraer información importante
  • Generar documentos automáticamente
  • Procesar datos masivos de hojas de cálculo
  • Crear respaldos de información crítica
  • Organizar archivos según criterios específicos

🏢 Analogía del almacén: El departamento de documentación

Imagina que nuestro almacén tiene un departamento de documentación donde:

  • Los archivos de texto son como documentos y reportes escritos
  • Los archivos CSV son como hojas de cálculo con datos tabulares
  • Los archivos JSON son como formularios estructurados con información organizada
  • Python es nuestro asistente administrativo que puede leer, escribir y procesar todos estos documentos
  • Los métodos de archivo son las diferentes herramientas de oficina (lectores, impresoras, organizadores)

📄 Tipos de archivos en el almacén

📝 Archivos de texto (.txt)

  • Uso: Reportes, logs, notas, documentación
  • Características: Fáciles de leer y escribir, universales
  • Ejemplo práctico: Reportes diarios de inventario
# Ejemplo: Generar reporte diario de ventas
reporte_ventas = """
=== REPORTE DIARIO DE VENTAS ===
Fecha: 2024-01-15
Total vendido: $15,750.00
Productos más vendidos:
1. Laptop Gaming - 12 unidades
2. Mouse Inalámbrico - 25 unidades
3. Teclado Mecánico - 8 unidades

Observaciones:
- Incremento del 15% respecto a ayer
- Stock bajo en laptops gaming
"""

with open("reporte_ventas_2024_01_15.txt", "w", encoding="utf-8") as archivo:
    archivo.write(reporte_ventas)

📊 Archivos CSV (.csv)

  • Uso: Inventarios, listas de precios, datos de ventas
  • Características: Estructura tabular, compatible con Excel
  • Ejemplo práctico: Inventario de productos
import csv

# Ejemplo: Generar inventario en formato CSV
inventario_productos = [
    ["Codigo", "Nombre", "Categoria", "Precio", "Stock"],
    ["LAP001", "Laptop Gaming Pro", "Computadoras", 1299.99, 15],
    ["MOU001", "Mouse Inalámbrico", "Accesorios", 29.99, 45],
    ["TEC001", "Teclado Mecánico", "Accesorios", 89.99, 23],
    ["MON001", "Monitor 4K", "Pantallas", 399.99, 8]
]

with open("inventario_productos.csv", "w", newline="", encoding="utf-8") as archivo:
    escritor = csv.writer(archivo)
    escritor.writerows(inventario_productos)

📋 Archivos JSON (.json)

  • Uso: Configuraciones, APIs, datos estructurados
  • Características: Formato web, estructura anidada
  • Ejemplo práctico: Configuración del sistema
import json

# Ejemplo: Configuración del sistema de almacén
configuracion_sistema = {
    "almacen": {
        "nombre": "TechStore Central",
        "ubicacion": "Ciudad de México",
        "capacidad_maxima": 10000
    },
    "configuracion_ventas": {
        "impuesto_default": 0.16,
        "descuento_maximo": 0.30,
        "moneda": "MXN"
    },
    "alertas": {
        "stock_minimo": 5,
        "notificar_email": True,
        "email_admin": "admin@techstore.com"
    }
}

with open("configuracion_almacen.json", "w", encoding="utf-8") as archivo:
    json.dump(configuracion_sistema, archivo, indent=2, ensure_ascii=False)

🖄️ Flujo de trabajo con archivos

1. Planificación 📋

  • ¿Qué tipo de archivo necesito?
  • ¿Cómo estructuraré la información?
  • ¿El archivo ya existe o lo crearé nuevo?

2. Apertura 🔓

# 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)

3. Procesamiento ⚙️

  • Leer contenido existente
  • Procesar y transformar datos
  • Generar nuevo contenido

4. Cierre seguro 🔒

# Siempre usar context managers
with open("archivo.txt", "r") as f:
    contenido = f.read()
# El archivo se cierra automáticamente

🚀 Casos de uso en el almacén

📋 Generación de reportes automáticos

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)

📊 Procesamiento de datos de ventas

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

📋 Sistema de respaldos automáticos

import shutil
from datetime import datetime

def crear_respaldo_diario():
    """Crea respaldo diario de archivos importantes"""
    fecha_hoy = datetime.now().strftime("%Y%m%d")
    
    archivos_importantes = [
        "inventario.csv",
        "configuracion_almacen.json",
        "ventas_del_dia.csv"
    ]
    
    # Crear directorio de respaldo
    directorio_respaldo = f"respaldos/respaldo_{fecha_hoy}/"
    os.makedirs(directorio_respaldo, exist_ok=True)
    
    # Copiar archivos importantes
    for archivo in archivos_importantes:
        if os.path.exists(archivo):
            shutil.copy2(archivo, directorio_respaldo)
    
    print(f"Respaldo creado en: {directorio_respaldo}")

🎯 Contenido de este capítulo

En este capítulo construiremos nuestro departamento de documentación digital aprendiendo:

📄 Leer y escribir archivos de texto

  • Operaciones básicas con archivos
  • Context managers y buenas prácticas
  • Manejo de codificación (UTF-8, ASCII)
  • Procesamiento de logs y reportes

📊 Trabajar con CSV

  • Módulo csv de Python
  • Lectura y escritura de datos tabulares
  • Procesamiento de inventarios y ventas
  • Integración con Excel y hojas de cálculo

📋 Trabajar con JSON

  • Formato JSON para datos estructurados
  • Serialización y deserialización
  • Configuraciones y APIs
  • Intercambio de datos entre sistemas

🛡️ Mejores prácticas para manejo de archivos

Siempre usar context managers

# ✅ 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?

Especificar codificación

# ✅ Siempre especifica encoding
with open("archivo.txt", "r", encoding="utf-8") as f:
    contenido = f.read()

Manejar errores apropiadamente

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")

Validar datos antes de procesar

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

🚀 Lo que lograrás en este capítulo

Al finalizar este capítulo, tendrás:

  • Dominio de archivos de texto: Leer, escribir y procesar documentos
  • Manejo profesional de CSV: Procesar datos tabulares como Excel
  • Competencia en JSON: Trabajar con APIs y configuraciones
  • Automatización de reportes: Generar documentos automáticamente
  • Sistema de respaldos: Proteger información importante
  • Procesamiento masivo: Manejar grandes volúmenes de datos

🎯 Preparando tu oficina digital

Antes de comenzar, ten en cuenta que el manejo de archivos es fundamental para la automatización:

  • Entrada de datos: Los archivos son la principal fuente de información
  • Salida de resultados: Generar reportes y documentos finales
  • Persistencia: Guardar información entre ejecuciones del programa
  • Integración: Comunicarse con otros sistemas y herramientas

¡Comencemos a construir tu departamento de documentación digital que automatizará todo el papeleo de tu almacén!


🧭 Navegación:

En este capítulo:

Leer y escribir archivos de texto

🧭 Navegación:

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.

📝 ¿Qué son los archivos de texto?

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:

  • Reportes de inventario con listas de productos
  • Logs de actividad del sistema del almacén
  • Instrucciones y manuales para empleados
  • Notas y observaciones de operaciones diarias
  • Configuraciones simples en formato texto

🏢 Analogía del almacén: La secretaria digital

Imagina que tienes una secretaria digital que puede:

  • Leer cualquier documento escrito y entender su contenido
  • Escribir reportes y documentos automáticamente
  • Organizar información en archivos bien estructurados
  • Procesar grandes volúmenes de documentos en segundos

🔓 Operaciones básicas con archivos

Apertura de archivos: Accediendo a los documentos

# 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

📄 Lectura de archivos: Consultando documentos

Método 1: Leer todo el archivo de una vez

# 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

Método 2: Leer línea por línea

# 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']}")

Método 3: Leer todas las líneas como lista

# 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}")

✍️ Escritura de archivos: Creando documentos

Crear nuevos documentos

# Generar reporte diario de inventario
def generar_reporte_inventario():
    """Genera el reporte diario de inventario"""
    from datetime import datetime
    
    fecha_hoy = datetime.now().strftime("%Y-%m-%d")
    hora_actual = datetime.now().strftime("%H:%M:%S")
    
    reporte = f"""
=== REPORTE DIARIO DE INVENTARIO ===
Fecha: {fecha_hoy}
Hora de generación: {hora_actual}

📦 RESUMEN DEL INVENTARIO:
- Total de productos: 1,250 unidades
- Valor total del inventario: $45,750.00
- Productos con stock bajo: 8 productos

📈 PRODUCTOS MÁS VENDIDOS HOY:
1. Laptop Gaming Pro - 15 unidades vendidas
2. Mouse Inalámbrico - 32 unidades vendidas
3. Teclado Mecánico RGB - 18 unidades vendidas

⚠️ ALERTAS:
- Laptop Gaming Pro: Stock bajo (5 unidades restantes)
- Auriculares Bluetooth: Stock crítico (2 unidades restantes)

📋 NOTAS:
- Pedido de reposición programado para mañana
- Revisar calidad de mouse inalámbrico (3 devoluciones)

--- Fin del reporte ---
"""
    
    nombre_archivo = f"reporte_inventario_{fecha_hoy}.txt"
    
    with open(nombre_archivo, "w", encoding="utf-8") as archivo:
        archivo.write(reporte)
    
    print(f"✅ Reporte generado: {nombre_archivo}")
    return nombre_archivo

# Generar el reporte
archivo_creado = generar_reporte_inventario()

Añadir contenido a documentos existentes

# 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")

📊 Casos prácticos del almacén

📁 Sistema de reportes automáticos

def generar_reporte_ventas_semanal():
    """Genera un reporte semanal de ventas automáticamente"""
    from datetime import datetime, timedelta
    
    # Datos simulados de ventas de la semana
    ventas_semana = {
        "Lunes": 2450.00,
        "Martes": 3200.00,
        "Miércoles": 2890.00,
        "Jueves": 3450.00,
        "Viernes": 4200.00,
        "Sábado": 5100.00,
        "Domingo": 1800.00
    }
    
    total_semanal = sum(ventas_semana.values())
    promedio_diario = total_semanal / 7
    mejor_dia = max(ventas_semana, key=ventas_semana.get)
    
    fecha_reporte = datetime.now().strftime("%Y-%m-%d")
    
    reporte = f"""
📈 ========================================
   REPORTE SEMANAL DE VENTAS
   Generado: {fecha_reporte}
========================================

💰 RESUMEN FINANCIERO:
   Total semanal: ${total_semanal:,.2f}
   Promedio diario: ${promedio_diario:,.2f}
   Mejor día: {mejor_dia} (${ventas_semana[mejor_dia]:,.2f})

📅 VENTAS POR DÍA:
"""
    
    for dia, venta in ventas_semana.items():
        porcentaje = (venta / total_semanal) * 100
        barra = "█" * int(porcentaje / 5)  # Barra visual
        reporte += f"   {dia:10} ${venta:8.2f} {barra} ({porcentaje:.1f}%)\n"
    
    reporte += f"""

📊 ANÁLISIS:
   - El mejor día fue {mejor_dia} con ${ventas_semana[mejor_dia]:,.2f}
   - Los fines de semana representan el {'alto' if ventas_semana['Sábado'] + ventas_semana['Domingo'] > total_semanal * 0.3 else 'bajo'} volumen de ventas
   - Tendencia semanal: {'Positiva' if ventas_semana['Viernes'] > ventas_semana['Lunes'] else 'Variable'}

🎯 RECOMENDACIONES:
   - Mantener inventario alto para {mejor_dia}s
   - Revisar estrategia para días de menor venta
   - Considerar promociones en días de baja actividad

--- Fin del reporte ---
"""
    
    nombre_archivo = f"reporte_semanal_{fecha_reporte}.txt"
    
    with open(nombre_archivo, "w", encoding="utf-8") as archivo:
        archivo.write(reporte)
    
    print(f"✅ Reporte semanal generado: {nombre_archivo}")
    return nombre_archivo

# Generar reporte
generar_reporte_ventas_semanal()

📋 Procesador de listas de tareas

def procesar_lista_tareas_diarias():
    """Procesa y organiza las tareas diarias del almacén"""
    
    # Crear lista de tareas si no existe
    tareas_ejemplo = """
# TAREAS DEL ALMACÉN - PENDIENTES

## Urgentes:
- Revisar stock de laptops gaming (Stock: 3 unidades)
- Procesar devolución de mouse defectuoso
- Llamar a proveedor de teclados mecánicos

## Importantes:
- Actualizar precios de temporada
- Organizar sección de accesorios
- Revisar sistema de seguridad

## Rutinarias:
- Limpieza de área de almacenamiento
- Backup de datos del sistema
- Revisión de inventario semanal

## Completadas:
- [x] Recibir pedido de monitors 4K
- [x] Actualizar sistema de punto de venta
"""
    
    # Crear archivo si no existe
    try:
        with open("tareas_diarias.txt", "r", encoding="utf-8") as f:
            contenido = f.read()
    except FileNotFoundError:
        with open("tareas_diarias.txt", "w", encoding="utf-8") as f:
            f.write(tareas_ejemplo)
        contenido = tareas_ejemplo
    
    # Procesar el contenido
    lineas = contenido.split("\n")
    
    tareas_urgentes = []
    tareas_importantes = []
    tareas_rutinarias = []
    tareas_completadas = []
    
    categoria_actual = None
    
    for linea in lineas:
        linea = linea.strip()
        
        # Identificar categorías
        if "Urgentes:" in linea:
            categoria_actual = "urgentes"
        elif "Importantes:" in linea:
            categoria_actual = "importantes"
        elif "Rutinarias:" in linea:
            categoria_actual = "rutinarias"
        elif "Completadas:" in linea:
            categoria_actual = "completadas"
        # Procesar tareas
        elif linea.startswith("- ") or linea.startswith("- [x]"):
            tarea = linea[2:]  # Quitar "- "
            
            if categoria_actual == "urgentes":
                tareas_urgentes.append(tarea)
            elif categoria_actual == "importantes":
                tareas_importantes.append(tarea)
            elif categoria_actual == "rutinarias":
                tareas_rutinarias.append(tarea)
            elif categoria_actual == "completadas":
                tareas_completadas.append(tarea)
    
    # Generar reporte de tareas
    reporte_tareas = f"""
📋 ========================================
   REPORTE DE TAREAS DEL ALMACÉN
   Fecha: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
========================================

🔴 TAREAS URGENTES ({len(tareas_urgentes)}):
"""
    
    for i, tarea in enumerate(tareas_urgentes, 1):
        reporte_tareas += f"   {i}. {tarea}\n"
    
    reporte_tareas += f"""

🟠 TAREAS IMPORTANTES ({len(tareas_importantes)}):
"""
    
    for i, tarea in enumerate(tareas_importantes, 1):
        reporte_tareas += f"   {i}. {tarea}\n"
    
    reporte_tareas += f"""

🗾 TAREAS RUTINARIAS ({len(tareas_rutinarias)}):
"""
    
    for i, tarea in enumerate(tareas_rutinarias, 1):
        reporte_tareas += f"   {i}. {tarea}\n"
    
    reporte_tareas += f"""

✅ TAREAS COMPLETADAS ({len(tareas_completadas)}):
"""
    
    for i, tarea in enumerate(tareas_completadas, 1):
        reporte_tareas += f"   {i}. {tarea}\n"
    
    total_pendientes = len(tareas_urgentes) + len(tareas_importantes) + len(tareas_rutinarias)
    reporte_tareas += f"""

📊 RESUMEN:
   - Total tareas pendientes: {total_pendientes}
   - Tareas completadas: {len(tareas_completadas)}
   - Prioridad: {'ALTA - Revisar urgentes inmediatamente' if tareas_urgentes else 'Normal - Continuar con importantes'}
"""
    
    # Guardar reporte
    with open("reporte_tareas.txt", "w", encoding="utf-8") as archivo:
        archivo.write(reporte_tareas)
    
    print("✅ Reporte de tareas generado: reporte_tareas.txt")
    return total_pendientes, len(tareas_completadas)

# Procesar tareas
pendientes, completadas = procesar_lista_tareas_diarias()
print(f"Tienes {pendientes} tareas pendientes y {completadas} completadas")

🛡️ Mejores prácticas para archivos de texto

Siempre usar context managers

# ✅ 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?

Especificar codificación (encoding)

# ✅ 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")

Manejar errores apropiadamente

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")

Limpiar y validar datos

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

🚨 Comprueba tu comprensión

🎯 Ejercicio 1: Generador de reportes personalizado

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
)

🎯 Ejercicio 2: Analizador de logs

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")

🎯 Ejercicio 3: Sistema de configuración

Crea un sistema que maneje la configuración del almacén mediante archivos de texto:

def cargar_configuracion(archivo_config):
    """
    Carga configuración desde archivo de texto con formato:
    clave = valor
    
    Ejemplo:
    nombre_almacen = TechStore Central
    ubicacion = Ciudad de Mexico
    impuesto = 0.16
    stock_minimo = 5
    """
    # Tu implementación aquí...
    pass

def guardar_configuracion(config_dict, archivo_config):
    """Guarda configuración en archivo de texto"""
    # Tu implementación aquí...
    pass

# Probar el sistema
config_ejemplo = {
    "nombre_almacen": "TechStore Central",
    "ubicacion": "Ciudad de Mexico",
    "impuesto": "0.16",
    "stock_minimo": "5",
    "email_admin": "admin@techstore.com"
}

guardar_configuracion(config_ejemplo, "config_almacen.txt")
config_cargada = cargar_configuracion("config_almacen.txt")
print(config_cargada)

📈 Lo que has logrado

¡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.


🧭 Navegación:

En este capítulo:

Trabajar con CSV

Trabajar con JSON

Capítulo 10: Manejo de Errores

Excepciones comunes

Bloques try/except/finally

Errores personalizados

Ejemplos Adicionales: Excepciones Personalizadas

🧭 Navegación:

Ejemplos Prácticos de Excepciones Personalizadas

En esta sección, encontrarás ejemplos adicionales de excepciones personalizadas en contextos reales.

Ejemplo 1: Sistema de Validación de Datos

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}")

Ejemplo 2: Excepciones para una API de Base de Datos

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}")

Ejemplo 3: Excepciones para un Sistema de Archivos

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

Patrones Avanzados con Excepciones Personalizadas

Patrón: Excepciones con Contexto

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

Patrón: Excepciones con Recuperación

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)

Comprueba tu comprensión

  1. ¿Cuál es la ventaja de incluir atributos adicionales en las excepciones personalizadas?
  2. ¿Por qué es útil crear una jerarquía de excepciones en lugar de una sola clase de excepción?
  3. ¿Cómo puedes preservar la excepción original al convertir excepciones estándar en personalizadas?
  4. ¿Qué patrón utilizarías para añadir información contextual a tus excepciones?
  5. ¿Cómo implementarías un mecanismo de reintentos automáticos usando excepciones personalizadas?

🧭 Navegación:

Diagramas de Manejo de Errores

En esta sección visualizaremos cómo funciona el manejo de errores en Python para ayudar a comprender su estructura y comportamiento.

Jerarquía de Excepciones

BaseExceptionSystemExitKeyboardInterruptExceptionStopIterationArithmeticErrorLookupErrorRuntimeErrorNameErrorTypeErrorValueErrorZeroDivisionErrorOverflowErrorIndexErrorKeyErrorRecursionErrorMiErrorPersonalizadoLeyendaExcepciones BaseExcepciones ComunesExcepciones EspecíficasExcepciones Personalizadas  
























Flujo de Manejo de Excepciones

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")

NoNo

Creación de Excepciones Personalizadas

Excepción Personalizada

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 ExcepcionesExceptionErrorAplicacionErrorValidacionErrorDatoInvalidoErrorConexion Organizaciónjerárquica

Patrones de Manejo de Errores

Patrones de Manejo de ErroresEAFP(Es más fácil pedir perdón que permiso)

Estilo EAFP (Pythónico)

try: valor = diccionario["clave"] resultado = 10 / valor except KeyError: print("La clave no existe") except ZeroDivisionError: print("No se puede dividir por cero")

LBYL(Mira antes de saltar)

Estilo LBYL (menos Pythónico)

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.

Capítulo 10: Proyectos de Automatización

🧭 Navegación:

¡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: El poder de Python en acción

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.

¿Por qué automatizar con Python?

Python es ideal para la automatización por varias razones:

  • Sintaxis clara y legible: Facilita escribir y mantener scripts de automatización
  • Amplia biblioteca estándar: Incluye módulos para casi cualquier tarea
  • Gran ecosistema de paquetes: Bibliotecas especializadas para diferentes necesidades
  • Multiplataforma: Funciona en Windows, macOS y Linux
  • Bajo nivel de entrada: No necesitas ser un experto para crear automatizaciones útiles

Beneficios de la automatización

  • Ahorro de tiempo: Tareas que tomarían horas se completan en segundos
  • Reducción de errores: Las computadoras no se cansan ni se distraen
  • Consistencia: Los resultados son siempre los mismos bajo las mismas condiciones
  • Escalabilidad: Puedes procesar grandes volúmenes de datos o tareas
  • Documentación implícita: El código sirve como registro de cómo se realiza una tarea

🗂️ Proyectos que desarrollaremos

En este capítulo, crearemos tres proyectos prácticos de automatización:

  1. Sistema de copias de seguridad automáticas

    • Respaldar archivos importantes periódicamente
    • Organizar backups por fecha
    • Comprimir archivos para ahorrar espacio
  2. Organizador de archivos por tipo

    • Clasificar archivos según su extensión
    • Mover archivos a carpetas específicas
    • Generar informes de organización
  3. Web scraping básico

    • Extraer información de sitios web
    • Procesar y analizar datos obtenidos
    • Guardar resultados en formatos útiles

Cada proyecto incluirá:

  • Explicación detallada del problema a resolver
  • Diseño de la solución paso a paso
  • Código completo comentado
  • Pruebas y ejemplos de uso
  • Ideas para mejoras y personalizaciones

🛠️ Herramientas y bibliotecas que utilizaremos

Para estos proyectos, aprovecharemos varias bibliotecas de Python:

  • os y shutil: Para operaciones con archivos y directorios
  • datetime: Para manejar fechas y horas
  • zipfile: Para comprimir y descomprimir archivos
  • schedule: Para programar tareas recurrentes
  • requests: Para realizar peticiones HTTP
  • BeautifulSoup: Para analizar HTML y extraer información
  • pandas: Para procesar y analizar datos estructurados
  • logging: Para registrar eventos y errores

🧠 Conceptos que aplicaremos

Estos proyectos te permitirán aplicar y reforzar muchos de los conceptos que has aprendido:

  • Funciones y módulos
  • Manejo de archivos y directorios
  • Estructuras de datos (listas, diccionarios)
  • Control de flujo (condicionales, bucles)
  • Manejo de errores y excepciones
  • Trabajo con bibliotecas externas
  • Procesamiento de datos

🚀 ¡Manos a la obra!

¡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.

Avancemos al primer proyecto: Copias de Seguridad Automáticas

Proyecto 1: Copias de Seguridad Automáticas

🧭 Navegación:

🔄 El problema: Proteger tus datos importantes

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.

🎯 Objetivo del proyecto

Crearemos un script de Python que:

  1. Identifique archivos y carpetas importantes que necesitan respaldo
  2. Copie estos archivos a una ubicación segura (disco externo, carpeta en la nube, etc.)
  3. Organice los backups por fecha para facilitar la recuperación
  4. Comprima los archivos para ahorrar espacio
  5. Mantenga un registro (log) de las operaciones realizadas
  6. Se ejecute automáticamente según una programación definida

📋 Planificación del sistema

Componentes principales:

  1. Configuración: Definir qué archivos/carpetas respaldar y dónde
  2. Respaldo: Copiar los archivos a la ubicación de destino
  3. Organización: Estructurar los backups por fecha
  4. Compresión: Reducir el tamaño de los archivos respaldados
  5. Registro: Documentar las operaciones realizadas
  6. Programación: Ejecutar el script automáticamente

Bibliotecas que utilizaremos:

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

💻 Implementación paso a paso

Paso 1: Configuración inicial

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
}

Paso 2: Configuración del sistema de registro (logging)

Es importante mantener un registro de las operaciones realizadas:

# backup_logger.py

import logging
from datetime import datetime

def setup_logger(log_file):
    """Configura el sistema de registro."""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(log_file),
            logging.StreamHandler()  # También muestra mensajes en consola
        ]
    )
    
    logging.info(f"=== Iniciando sesión de backup: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===")
    
    return logging.getLogger()

Paso 3: Función principal de respaldo

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

Paso 4: Limpieza de backups antiguos

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")

Paso 5: Script principal

Ahora, uniremos todo en un script principal:

# 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

🚀 Uso del sistema

Configuración inicial

  1. Instala las dependencias necesarias:

    pip install schedule
    
  2. Modifica el archivo backup_config.py para especificar:

    • Las rutas que deseas respaldar
    • La ubicación donde se guardarán los backups
    • La programación de los backups
  3. Ejecuta el script principal:

    python backup_system.py
    

Ejecución como servicio

Para que el sistema funcione continuamente en segundo plano:

En Windows:

Puedes crear una tarea programada:

  1. Abre el Programador de tareas
  2. Crea una nueva tarea básica
  3. Configúrala para que se ejecute al inicio del sistema
  4. Apunta al script backup_system.py

En Linux:

Puedes crear un servicio systemd:

  1. Crea un archivo .service en /etc/systemd/system/
  2. Configúralo para ejecutar el script
  3. Habilita e inicia el servicio

🔍 Comprueba tu comprensión

  1. ¿Qué sucedería si una de las rutas de origen no existe?
  2. ¿Cómo modificarías el código para respaldar solo archivos modificados después de la última copia de seguridad?
  3. ¿Qué otras estrategias de respaldo podrías implementar además de la compresión en ZIP?
  4. ¿Cómo adaptarías el sistema para enviar notificaciones por correo electrónico cuando se complete un backup?

🛠️ Ideas para mejoras

  • Respaldo incremental: Respaldar solo los archivos que han cambiado desde el último backup
  • Cifrado: Añadir protección con contraseña a los archivos ZIP
  • Notificaciones: Enviar correos electrónicos o mensajes cuando se complete un backup
  • Interfaz gráfica: Crear una GUI simple para configurar y monitorear los backups
  • Respaldo en la nube: Integrar con servicios como Google Drive, Dropbox o AWS S3
  • Verificación: Comprobar la integridad de los archivos respaldados

📝 Resumen

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.

En el próximo proyecto, aprenderemos a organizar archivos automáticamente por tipo.

Proyecto 2: Organizador de Archivos por Tipo

🧭 Navegación:

📁 El problema: El caos digital

¿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.

🎯 Objetivo del proyecto

Desarrollaremos un script de Python que:

  1. Analice una carpeta desordenada (como Descargas o Escritorio)
  2. Identifique el tipo de cada archivo según su extensión
  3. Cree carpetas organizadas por categorías (Documentos, Imágenes, Videos, etc.)
  4. Mueva cada archivo a la carpeta correspondiente
  5. Genere un informe de la organización realizada
  6. Pueda ejecutarse manualmente o programarse para correr periódicamente

📋 Planificación del sistema

Componentes principales:

  1. Análisis: Escanear la carpeta y clasificar los archivos
  2. Categorización: Definir categorías y asignar extensiones a cada una
  3. Organización: Crear carpetas y mover archivos
  4. Informes: Generar resumen de las acciones realizadas
  5. Configuración: Permitir personalizar el comportamiento

Bibliotecas que utilizaremos:

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

💻 Implementación paso a paso

Paso 1: Definir categorías y extensiones

Primero, crearemos un mapeo entre extensiones de archivo y categorías:

# file_categories.py

# Mapeo de extensiones a categorías
FILE_CATEGORIES = {
    # Documentos
    "Documentos": [
        # Documentos de texto
        "pdf", "doc", "docx", "txt", "rtf", "odt", 
        # Hojas de cálculo
        "xls", "xlsx", "csv", "ods",
        # Presentaciones
        "ppt", "pptx", "odp",
        # Otros documentos
        "md", "epub", "mobi"
    ],
    
    # Imágenes
    "Imagenes": [
        "jpg", "jpeg", "png", "gif", "bmp", "svg", 
        "tiff", "webp", "ico", "raw", "psd", "ai"
    ],
    
    # Audio
    "Audio": [
        "mp3", "wav", "ogg", "flac", "aac", "wma", 
        "m4a", "mid", "midi"
    ],
    
    # Video
    "Videos": [
        "mp4", "avi", "mkv", "mov", "wmv", "flv", 
        "webm", "m4v", "mpg", "mpeg", "3gp"
    ],
    
    # Archivos comprimidos
    "Comprimidos": [
        "zip", "rar", "7z", "tar", "gz", "bz2", 
        "xz", "iso"
    ],
    
    # Código y programación
    "Codigo": [
        "py", "js", "html", "css", "java", "c", "cpp", 
        "h", "cs", "php", "rb", "go", "rs", "swift",
        "json", "xml", "yaml", "yml", "sql", "sh", "bat"
    ],
    
    # Ejecutables e instaladores
    "Ejecutables": [
        "exe", "msi", "apk", "dmg", "deb", "rpm"
    ]
}

# Función para obtener la categoría de un archivo según su extensión
def get_file_category(filename):
    """
    Determina la categoría de un archivo basado en su extensión.
    
    Args:
        filename: Nombre del archivo a categorizar
        
    Returns:
        str: Nombre de la categoría o 'Otros' si no coincide con ninguna
    """
    # Obtener la extensión (sin el punto)
    extension = filename.split('.')[-1].lower() if '.' in filename else ""
    
    # Buscar la categoría correspondiente
    for category, extensions in FILE_CATEGORIES.items():
        if extension in extensions:
            return category
    
    # Si no coincide con ninguna categoría conocida
    return "Otros"

Paso 2: Crear la clase principal del organizador

# 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))

Paso 3: Script principal con interfaz de línea de comandos

# 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())

🚀 Uso del sistema

Uso básico

Para organizar el directorio actual:

python organize_files.py

Para organizar un directorio específico:

python organize_files.py /ruta/a/mi/carpeta/desordenada

Para incluir subdirectorios en la organización:

python organize_files.py -d /ruta/a/mi/carpeta

Automatización periódica

En Windows:

Puedes crear una tarea programada para ejecutar el script periódicamente:

  1. Abre el Programador de tareas
  2. Crea una nueva tarea básica
  3. Configúrala para que se ejecute diariamente o semanalmente
  4. Apunta al script con los parámetros deseados

En Linux:

Puedes usar cron para programar la ejecución:

# 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

🔍 Comprueba tu comprensión

  1. ¿Qué sucedería si dos archivos con el mismo nombre pertenecen a la misma categoría?
  2. ¿Cómo modificarías el código para manejar archivos sin extensión?
  3. ¿Qué estrategia usarías para organizar archivos basándote en su contenido en lugar de su extensión?
  4. ¿Cómo adaptarías el sistema para permitir configuraciones personalizadas de categorías?

🛠️ Ideas para mejoras

  • Configuración personalizada: Permitir al usuario definir sus propias categorías y extensiones
  • Modo simulación: Mostrar qué cambios se harían sin realizar movimientos reales
  • Organización por fecha: Agrupar archivos por fecha de creación o modificación
  • Detección inteligente: Usar el contenido del archivo para determinar su tipo, no solo la extensión
  • Interfaz gráfica: Crear una GUI para facilitar el uso
  • Integración con el sistema: Añadir opciones al menú contextual del explorador de archivos
  • Filtros avanzados: Permitir incluir o excluir archivos según patrones

📝 Resumen

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.

En el próximo proyecto, aprenderemos a extraer información de sitios web con web scraping.

Proyecto 3: Web Scraping Básico

🧭 Navegación:

🌐 El problema: Extraer datos de la web

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.

🎯 Objetivo del proyecto

Desarrollaremos un script de Python que:

  1. Acceda a páginas web de forma automática
  2. Extraiga información específica usando selectores HTML
  3. Procese y limpie los datos obtenidos
  4. Guarde la información en formatos útiles (CSV, JSON)
  5. Respete las políticas de los sitios web (robots.txt, límites de velocidad)
  6. Pueda programarse para ejecutarse periódicamente

⚠️ Consideraciones éticas y legales

Antes de comenzar, es importante entender algunas consideraciones:

  • Términos de servicio: Muchos sitios prohíben el scraping en sus términos de uso
  • robots.txt: Archivo que indica qué partes de un sitio pueden ser rastreadas
  • Límites de velocidad: Hacer demasiadas peticiones puede sobrecargar servidores
  • Datos personales: Extraer información personal puede violar leyes de privacidad
  • Derechos de autor: El contenido puede estar protegido por copyright

Para este proyecto, usaremos sitios que permiten explícitamente el scraping o sitios de ejemplo creados con este propósito.

📋 Planificación del sistema

Componentes principales:

  1. Solicitud HTTP: Obtener el contenido HTML de la página
  2. Parsing HTML: Analizar la estructura y extraer datos específicos
  3. Procesamiento: Limpiar y estructurar la información obtenida
  4. Almacenamiento: Guardar los datos en formatos útiles
  5. Programación: Automatizar la ejecución periódica

Bibliotecas que utilizaremos:

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

💻 Implementación paso a paso

Paso 1: Configuración inicial y funciones de utilidad

# 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)}")

Paso 2: Implementar un scraper específico para un sitio de libros

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

Paso 3: Script principal para ejecutar el scraper

# run_book_scraper.py

import argparse
import schedule
import time
from book_scraper import BookScraper

def scrape_books(pages=1, output_dir="./data"):
    """Ejecuta el scraper de libros."""
    scraper = BookScraper(output_dir=output_dir)
    books = scraper.scrape_books(pages)
    print(f"Se han extraído {len(books)} libros.")
    return books

def main():
    """Función principal del programa."""
    parser = argparse.ArgumentParser(description="Web Scraper de libros")
    
    parser.add_argument(
        "-p", "--pages",
        type=int,
        default=1,
        help="Número de páginas a scrapear (por defecto: 1)"
    )
    
    parser.add_argument(
        "-o", "--output",
        default="./data",
        help="Directorio de salida para los datos (por defecto: ./data)"
    )
    
    parser.add_argument(
        "-s", "--schedule",
        action="store_true",
        help="Programar ejecución diaria"
    )
    
    args = parser.parse_args()
    
    if args.schedule:
        print(f"Programando scraper para ejecutarse diariamente a las 10:00 AM...")
        schedule.every().day.at("10:00").do(scrape_books, args.pages, args.output)
        
        while True:
            schedule.run_pending()
            time.sleep(60)
    else:
        scrape_books(args.pages, args.output)

if __name__ == "__main__":
    main()

🚀 Uso del sistema

Instalación de dependencias

Antes de usar el scraper, necesitas instalar las bibliotecas requeridas:

pip install requests beautifulsoup4 pandas schedule

Uso básico

Para extraer información de la primera página:

python run_book_scraper.py

Para extraer información de varias páginas:

python run_book_scraper.py --pages 5

Para especificar un directorio de salida:

python run_book_scraper.py --output ./mis_datos

Para programar una ejecución diaria:

python run_book_scraper.py --schedule

🔍 Comprueba tu comprensión

  1. ¿Por qué es importante incluir un tiempo de espera entre peticiones?
  2. ¿Cómo modificarías el código para extraer información de un sitio web diferente?
  3. ¿Qué estrategias usarías para manejar cambios en la estructura HTML del sitio web?
  4. ¿Cómo podrías implementar un sistema de notificaciones cuando se encuentren nuevos libros?

🛠️ Ideas para mejoras

  • Proxy rotativo: Usar diferentes IPs para evitar bloqueos
  • Autenticación: Añadir soporte para sitios que requieren inicio de sesión
  • Manejo de JavaScript: Usar Selenium para sitios que cargan contenido dinámicamente
  • Filtros avanzados: Permitir buscar libros por categoría, precio o valoración
  • Análisis de datos: Generar gráficos y estadísticas de los datos extraídos
  • Detección de cambios: Notificar cuando hay nuevos elementos o cambios de precio
  • API: Crear una API REST para acceder a los datos extraídos

📝 Resumen

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.

⚠️ Nota importante sobre el uso ético

Recuerda siempre:

  1. Respetar los términos de servicio de los sitios web
  2. Consultar el archivo robots.txt antes de scrapear
  3. Implementar límites de velocidad razonables
  4. No extraer información personal o protegida
  5. 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:

  1. Crear copias de seguridad automáticas
  2. Organizar archivos por tipo
  3. 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.

Diagramas de Proyectos de Automatización

En esta sección visualizaremos los flujos de trabajo y la estructura de los proyectos de automatización presentados en este capítulo.

Sistema de Copias de Seguridad 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

Módulos:

os, sys datetime zipfile shutil logging schedule

Funciones Principales:

crear_backup() comprimir_directorio() limpiar_backups() configurar_log() programar_tareas()

NoNo

Organizador de Archivos por Tipo

InicioConfiguración- Directorio a organizar- Tipos de archivos- Modo (automático/manual)¿Directorioexiste?Crear directoriospor tipo de archivoEscanear archivosen directorio origenClasificar archivospor extensión¿Hay archivospara mover?Mover archivos adirectorios correspondientesGenerar reportede organización¿Modoautomático?Programar siguienteejecuciónFin

Tipos de Archivos:

Imágenes: .jpg, .png, .gif, .bmp Documentos: .pdf, .doc, .docx, .txt Videos: .mp4, .avi, .mov, .mkv Audio: .mp3, .wav, .ogg, .flac Código: .py, .js, .html, .css Comprimidos: .zip, .rar, .7z Otros: archivos no categorizados

NoNoNo

Web Scraping Básico

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

NoNoNo

Integración de Proyectos

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ático  Implementa




















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.

Capítulo 12: Mini Proyecto Integrador

¡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.

🏗️ El Proyecto: Sistema de Gestión de Almacén

Vamos a crear un Sistema de Gestión de Almacén que use TODOS los conceptos que has aprendido hasta ahora:

📋 ¿Qué hará nuestro sistema?

  1. Gestionar inventario con diccionarios y listas
  2. Procesar ventas con funciones y control de flujo
  3. Generar reportes guardándolos en archivos CSV
  4. Validar datos con manejo de errores
  5. Automatizar tareas como respaldos y alertas

🧰 Conceptos que integraremos

  • Variables y tipos de datos → Información de productos
  • Operadores → Cálculos de precios y totales
  • Estructuras de control → Lógica de negocio
  • Listas y diccionarios → Base de datos en memoria
  • Funciones → Operaciones reutilizables
  • Manejo de archivos → Persistencia de datos
  • Manejo de errores → Sistema robusto

🚀 Desarrollo del Proyecto

Paso 1: Estructura de Datos del Almacén

Primero, definamos cómo representaremos nuestro almacén usando las estructuras de datos que conocemos:

# Base de datos del almacén (usando diccionarios y listas)
inventario = {
    "PROD001": {
        "nombre": "Laptop HP",
        "precio": 15000.00,
        "stock": 5,
        "categoria": "Electrónicos",
        "proveedor": "TechCorp"
    },
    "PROD002": {
        "nombre": "Mouse Inalámbrico", 
        "precio": 350.00,
        "stock": 20,
        "categoria": "Accesorios",
        "proveedor": "GadgetCo"
    },
    "PROD003": {
        "nombre": "Monitor 24 pulgadas",
        "precio": 4500.00,
        "stock": 8,
        "categoria": "Electrónicos", 
        "proveedor": "ScreenTech"
    }
}

# Registro de ventas (lista de diccionarios)
ventas = []

# Configuración del sistema
configuracion = {
    "iva": 0.16,
    "descuento_mayoreo": 0.10,
    "stock_minimo": 5,
    "archivo_ventas": "ventas_almacen.csv",
    "archivo_inventario": "inventario_respaldo.json"
}

Paso 2: Funciones de Operación

Creemos las funciones principales que harán funcionar nuestro almacén:

import json
import csv
from datetime import datetime

def mostrar_inventario():
    """Muestra todo el inventario actual del almacén"""
    print("\n" + "="*50)
    print("📦 INVENTARIO DEL ALMACÉN")
    print("="*50)
    
    for codigo, producto in inventario.items():
        print(f"\nCódigo: {codigo}")
        print(f"  Nombre: {producto['nombre']}")
        print(f"  Precio: ${producto['precio']:,.2f}")
        print(f"  Stock: {producto['stock']} unidades")
        print(f"  Categoría: {producto['categoria']}")
        
        # Alerta de stock bajo usando condicionales
        if producto['stock'] <= configuracion['stock_minimo']:
            print(f"  🚨 ALERTA: Stock bajo!")

def buscar_producto(codigo):
    """Busca un producto por código"""
    if codigo in inventario:
        return inventario[codigo]
    else:
        return None

def calcular_precio_venta(codigo, cantidad):
    """Calcula el precio final de una venta con IVA y descuentos"""
    producto = buscar_producto(codigo)
    
    if not producto:
        return None, "Producto no encontrado"
    
    if cantidad > producto['stock']:
        return None, f"Stock insuficiente. Disponible: {producto['stock']}"
    
    # Cálculo del precio base
    precio_base = producto['precio'] * cantidad
    
    # Aplicar descuento por mayoreo (si compra 10 o más)
    if cantidad >= 10:
        descuento = precio_base * configuracion['descuento_mayoreo']
        precio_con_descuento = precio_base - descuento
        print(f"💰 Descuento por mayoreo aplicado: ${descuento:,.2f}")
    else:
        precio_con_descuento = precio_base
        descuento = 0
    
    # Calcular IVA
    iva = precio_con_descuento * configuracion['iva']
    precio_final = precio_con_descuento + iva
    
    return {
        'precio_base': precio_base,
        'descuento': descuento,
        'iva': iva,
        'precio_final': precio_final
    }, "OK"

def registrar_venta(codigo, cantidad, cliente="Cliente general"):
    """Registra una venta en el sistema"""
    try:
        # Calcular precios
        calculo, mensaje = calcular_precio_venta(codigo, cantidad)
        
        if calculo is None:
            print(f"❌ Error: {mensaje}")
            return False
        
        # Crear registro de venta
        venta = {
            'fecha': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'codigo_producto': codigo,
            'nombre_producto': inventario[codigo]['nombre'],
            'cantidad': cantidad,
            'precio_unitario': inventario[codigo]['precio'],
            'precio_base': calculo['precio_base'],
            'descuento': calculo['descuento'],
            'iva': calculo['iva'],
            'precio_final': calculo['precio_final'],
            'cliente': cliente
        }
        
        # Agregar a lista de ventas
        ventas.append(venta)
        
        # Actualizar stock
        inventario[codigo]['stock'] -= cantidad
        
        print(f"\n✅ Venta registrada exitosamente")
        print(f"Cliente: {cliente}")
        print(f"Producto: {venta['nombre_producto']}")
        print(f"Cantidad: {cantidad}")
        print(f"Total: ${calculo['precio_final']:,.2f}")
        
        return True
        
    except Exception as e:
        print(f"❌ Error al registrar venta: {e}")
        return False

def generar_reporte_ventas():
    """Genera un reporte completo de ventas"""
    if not ventas:
        print("📊 No hay ventas registradas")
        return
    
    print("\n" + "="*60)
    print("📊 REPORTE DE VENTAS")
    print("="*60)
    
    total_ventas = 0
    total_iva = 0
    productos_vendidos = {}
    
    # Procesar cada venta usando bucles
    for venta in ventas:
        total_ventas += venta['precio_final']
        total_iva += venta['iva']
        
        # Contabilizar productos vendidos
        codigo = venta['codigo_producto']
        if codigo in productos_vendidos:
            productos_vendidos[codigo] += venta['cantidad']
        else:
            productos_vendidos[codigo] = venta['cantidad']
    
    print(f"Total de ventas: {len(ventas)}")
    print(f"Ingresos totales: ${total_ventas:,.2f}")
    print(f"IVA recaudado: ${total_iva:,.2f}")
    
    print("\n📈 Productos más vendidos:")
    # Ordenar productos por cantidad vendida
    productos_ordenados = sorted(productos_vendidos.items(), 
                                key=lambda x: x[1], reverse=True)
    
    for codigo, cantidad in productos_ordenados:
        nombre = inventario[codigo]['nombre']
        print(f"  • {nombre}: {cantidad} unidades")

def exportar_ventas_csv():
    """Exporta las ventas a un archivo CSV"""
    try:
        with open(configuracion['archivo_ventas'], 'w', newline='', encoding='utf-8') as archivo:
            campos = ['fecha', 'codigo_producto', 'nombre_producto', 'cantidad', 
                     'precio_unitario', 'precio_base', 'descuento', 'iva', 
                     'precio_final', 'cliente']
            
            escritor = csv.DictWriter(archivo, fieldnames=campos)
            escritor.writeheader()
            
            for venta in ventas:
                escritor.writerow(venta)
        
        print(f"✅ Ventas exportadas a {configuracion['archivo_ventas']}")
        
    except Exception as e:
        print(f"❌ Error al exportar ventas: {e}")

def respaldar_inventario():
    """Crea un respaldo del inventario en JSON"""
    try:
        respaldo = {
            'fecha_respaldo': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'inventario': inventario,
            'configuracion': configuracion
        }
        
        with open(configuracion['archivo_inventario'], 'w', encoding='utf-8') as archivo:
            json.dump(respaldo, archivo, indent=2, ensure_ascii=False)
        
        print(f"✅ Inventario respaldado en {configuracion['archivo_inventario']}")
        
    except Exception as e:
        print(f"❌ Error al respaldar inventario: {e}")

def cargar_respaldo():
    """Carga un respaldo del inventario"""
    try:
        with open(configuracion['archivo_inventario'], 'r', encoding='utf-8') as archivo:
            respaldo = json.load(archivo)
            
        global inventario
        inventario = respaldo['inventario']
        
        print("✅ Respaldo cargado exitosamente")
        print(f"Fecha del respaldo: {respaldo['fecha_respaldo']}")
        
    except FileNotFoundError:
        print("❌ No se encontró archivo de respaldo")
    except Exception as e:
        print(f"❌ Error al cargar respaldo: {e}")

Paso 3: Sistema de Menús Interactivo

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()

🎯 Análisis del Proyecto: ¿Qué has logrado?

Conceptos Integrados Exitosamente:

1. Variables y Tipos de Datos

  • Diccionarios para productos, configuración
  • Listas para ventas
  • Strings, floats, integers, booleans

2. Operadores

  • Aritméticos: cálculos de precios, IVA, descuentos
  • Comparación: validaciones de stock, precios
  • Lógicos: condiciones complejas

3. Estructuras de Control

  • if/elif/else: validaciones, alertas, descuentos
  • for: procesar ventas, mostrar inventario
  • while: menú principal interactivo

4. Estructuras de Datos

  • Diccionarios: inventario, productos, configuración
  • Listas: registro de ventas
  • Operaciones: búsqueda, ordenamiento, filtrado

5. Funciones

  • Modularización del código
  • Parámetros y valores de retorno
  • Manejo de errores localizado

6. Manejo de Archivos

  • CSV: exportación de ventas
  • JSON: respaldos del inventario
  • Encoding UTF-8: soporte para caracteres especiales

7. Manejo de Errores

  • try/except: captura de errores
  • Validaciones: datos de entrada
  • Mensajes informativos: experiencia de usuario

8. Automatización

  • Respaldos automáticos
  • Cálculos automáticos de precios
  • Generación automática de reportes

🏆 ¡Felicitaciones!

Has creado un sistema completo y funcional que demuestra el dominio de todos los conceptos fundamentales de Python. Este proyecto:

  • Es realista: Simula un caso de uso del mundo real
  • Es completo: Integra todos los conceptos aprendidos
  • Es escalable: Puedes agregar más funcionalidades
  • Es educativo: Refuerza el aprendizaje práctico

💡 Ideas para Expandir el Proyecto:

  1. Agregar más validaciones de datos de entrada
  2. Crear categorías dinámicas de productos
  3. Implementar alertas automáticas por email
  4. Agregar gráficos con matplotlib
  5. Crear una interfaz web con Flask

🎓 Reflexión Final

Con este proyecto has demostrado que puedes:

  • 🧠 Pensar como programador: Dividir problemas complejos en funciones simples
  • 🔧 Usar las herramientas correctas: Elegir la estructura de datos apropiada
  • 🛡️ Crear código robusto: Manejar errores y validar datos
  • 🎨 Diseñar experiencias: Crear interfaces amigables
  • 📈 Resolver problemas reales: Automatizar tareas del mundo real

¡Ya no eres un principiante, eres un programador Python! 🐍✨

¿Listo para los siguientes desafíos? El mundo de la programación está lleno de oportunidades esperándote…

Planificación del Proyecto: Arquitectura Empresarial

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.

🎯 Análisis de Requisitos: Definiendo el Éxito

📋 Requisitos Funcionales (¿Qué debe hacer?)

RF-001: Gestión Completa de Productos

# 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"
}

RF-002: Control Inteligente de Inventario

# 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
}

RF-003: Procesamiento de Ventas

# Flujo de ventas
PROCESO_VENTA = {
    "validacion_producto": "Verificar existencia y disponibilidad",
    "validacion_stock": "Confirmar cantidad suficiente",
    "calculo_total": "Precio × cantidad + impuestos - descuentos",
    "actualizacion_inventario": "Descontar stock automáticamente",
    "registro_transaccion": "Guardar historial de venta"
}

# Reglas de negocio para ventas
REGLAS_VENTA = {
    "cantidad_minima": 1,
    "cantidad_maxima": "No puede exceder stock disponible",
    "descuento_maximo": 0.50,  # 50% máximo de descuento
    "impuesto_default": 0.16   # 16% IVA
}

RF-004: Sistema de Reportes Inteligentes

# 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"]

🔧 Requisitos No Funcionales (¿Cómo debe funcionar?)

RNF-001: Rendimiento

METRICAS_RENDIMIENTO = {
    "tiempo_respuesta_crud": "< 0.5 segundos",
    "tiempo_carga_inventario": "< 2 segundos para 10,000 productos",
    "tiempo_generacion_reporte": "< 3 segundos",
    "memoria_maxima": "< 100MB para operación normal"
}

RNF-002: Confiabilidad

REQUISITOS_CONFIABILIDAD = {
    "disponibilidad": "99.9% uptime",
    "respaldo_automatico": "Cada operación crítica",
    "recuperacion_errores": "Rollback automático en fallos",
    "validacion_datos": "100% de entradas validadas"
}

RNF-003: Usabilidad

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"
}

🏗️ Arquitectura del Sistema: Planos Maestros

📐 Patrón de Arquitectura: MVC Empresarial

# Modelo (Model) - Lógica de negocio y datos
CAPA_MODELO = {
    "entidades": ["Producto", "Venta", "Usuario", "Categoria"],
    "gestores": ["GestorInventario", "GestorVentas", "GestorReportes"],
    "validadores": ["ValidadorProducto", "ValidadorVenta"],
    "persistencia": ["GestorArchivos", "GestorRespaldos"]
}

# Vista (View) - Interfaz de usuario
CAPA_VISTA = {
    "menus": ["MenuPrincipal", "MenuProductos", "MenuVentas", "MenuReportes"],
    "formularios": ["FormularioProducto", "FormularioVenta"],
    "reportes": ["VistaReporte", "VistaEstadisticas"],
    "utilidades": ["FormateadorPantalla", "GestorMensajes"]
}

# Controlador (Controller) - Coordinación
CAPA_CONTROLADOR = {
    "coordinadores": ["ControladorPrincipal", "ControladorProductos"],
    "procesadores": ["ProcesadorComandos", "ProcesadorEventos"],
    "validadores": ["ValidadorEntradas", "ValidadorPermisos"]
}

🏢 Estructura Modular Empresarial

# 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"
    }
}

🎨 Diseño de Clases: Ingeniería de Software

🏭 Jerarquía de Clases Empresariales

# Clase base para todas las entidades
class EntidadBase:
    """Clase base con funcionalidades comunes"""
    def __init__(self):
        self.id = self._generar_id()
        self.fecha_creacion = datetime.now()
        self.fecha_actualizacion = datetime.now()
        self.activo = True
    
    def _generar_id(self) -> str:
        """Genera ID único para la entidad"""
        pass
    
    def to_dict(self) -> Dict:
        """Serialización a diccionario"""
        pass
    
    def from_dict(self, datos: Dict):
        """Deserialización desde diccionario"""
        pass
    
    def validar(self) -> Tuple[bool, List[str]]:
        """Validación de reglas de negocio"""
        pass

# Especialización para productos
class Producto(EntidadBase):
    """Entidad de negocio: Producto del inventario"""
    
    def __init__(self, codigo: str, nombre: str, precio: float, stock: int):
        super().__init__()
        self.codigo = codigo.upper()
        self.nombre = nombre
        self.precio = precio
        self.stock = stock
        self.categoria = "General"
        self.stock_minimo = 5
        self.proveedor = None
        self.ubicacion = None
    
    def calcular_valor_inventario(self) -> float:
        """Calcula valor total del producto en inventario"""
        return self.precio * self.stock
    
    def necesita_reposicion(self) -> bool:
        """Determina si el producto necesita reposición"""
        return self.stock <= self.stock_minimo
    
    def aplicar_descuento(self, porcentaje: float) -> float:
        """Aplica descuento y retorna nuevo precio"""
        if 0 <= porcentaje <= 0.50:  # Máximo 50% descuento
            return self.precio * (1 - porcentaje)
        raise ValueError("Descuento debe estar entre 0% y 50%")

🎯 Patrones de Diseño Aplicados

Patrón Singleton: Configuración Global

class ConfiguracionSistema:
    """Configuración única del sistema (Singleton)"""
    _instancia = None
    
    def __new__(cls):
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
            cls._instancia._inicializado = False
        return cls._instancia
    
    def __init__(self):
        if not self._inicializado:
            self.cargar_configuracion()
            self._inicializado = True
    
    def cargar_configuracion(self):
        """Carga configuración desde archivo"""
        self.stock_minimo_default = 5
        self.impuesto_default = 0.16
        self.descuento_maximo = 0.50

Patrón Factory: Creación de Reportes

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}")

Patrón Observer: Sistema de Alertas

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)

📊 Flujo de Datos: Arquitectura de Información

🔄 Ciclo de Vida de los Datos

# Flujo completo de una operación
FLUJO_OPERACION = {
    "1_entrada": {
        "fuente": "Usuario/Sistema",
        "validacion": "Sintáctica y semántica",
        "sanitizacion": "Limpieza y normalización"
    },
    
    "2_procesamiento": {
        "validacion_negocio": "Reglas empresariales",
        "transformacion": "Conversión a formato interno",
        "calculo": "Operaciones matemáticas/lógicas"
    },
    
    "3_persistencia": {
        "respaldo_previo": "Backup antes de cambios",
        "transaccion": "Operación atómica",
        "verificacion": "Confirmación de escritura"
    },
    
    "4_respuesta": {
        "formateo": "Preparación para presentación",
        "logging": "Registro de la operación",
        "notificacion": "Feedback al usuario"
    }
}

🗄️ Modelo de Datos

# Estructura de datos empresarial
MODELO_DATOS = {
    "productos": {
        "estructura": {
            "codigo": "str (PK, unique, 3-10 chars)",
            "nombre": "str (required, 1-100 chars)",
            "precio": "float (required, > 0)",
            "stock": "int (required, >= 0)",
            "categoria": "str (FK to categorias)",
            "stock_minimo": "int (default: 5)",
            "proveedor": "str (optional)",
            "fecha_creacion": "datetime (auto)",
            "fecha_actualizacion": "datetime (auto)"
        },
        "indices": ["codigo", "categoria", "nombre"],
        "validaciones": ["codigo_unico", "precio_positivo", "stock_no_negativo"]
    },
    
    "ventas": {
        "estructura": {
            "id": "str (PK, UUID)",
            "codigo_producto": "str (FK to productos)",
            "cantidad": "int (required, > 0)",
            "precio_unitario": "float (snapshot del precio)",
            "descuento": "float (0-0.5)",
            "impuesto": "float (default: 0.16)",
            "total": "float (calculated)",
            "fecha_venta": "datetime (auto)",
            "usuario": "str (optional)"
        },
        "indices": ["fecha_venta", "codigo_producto"],
        "validaciones": ["cantidad_positiva", "descuento_valido"]
    }
}

🚀 Plan de Implementación: Roadmap Ejecutivo

📅 Cronograma de Desarrollo

Sprint 1: Fundamentos (Días 1-2)

SPRINT_1 = {
    "objetivos": [
        "Configurar estructura del proyecto",
        "Implementar utilidades básicas",
        "Crear sistema de configuración",
        "Establecer manejo de archivos"
    ],
    "entregables": [
        "Estructura de directorios completa",
        "Módulo de utilidades funcional",
        "Sistema de logging operativo",
        "Configuración centralizada"
    ],
    "criterios_aceptacion": [
        "Proyecto se ejecuta sin errores",
        "Archivos JSON se crean/leen correctamente",
        "Logs se generan apropiadamente"
    ]
}

Sprint 2: Entidades Core (Días 3-4)

SPRINT_2 = {
    "objetivos": [
        "Implementar clase Producto completa",
        "Crear GestorInventario básico",
        "Desarrollar validaciones de negocio",
        "Establecer persistencia de datos"
    ],
    "entregables": [
        "Clase Producto con todos sus métodos",
        "CRUD básico de productos",
        "Validaciones robustas",
        "Persistencia JSON funcional"
    ]
}

Sprint 3: Lógica de Negocio (Días 5-6)

SPRINT_3 = {
    "objetivos": [
        "Implementar procesamiento de ventas",
        "Crear sistema de alertas",
        "Desarrollar búsquedas y filtros",
        "Establecer cálculos empresariales"
    ]
}

Sprint 4: Reportes y Análisis (Días 7-8)

SPRINT_4 = {
    "objetivos": [
        "Crear generador de reportes",
        "Implementar estadísticas",
        "Desarrollar exportación de datos",
        "Crear dashboards básicos"
    ]
}

Sprint 5: Interfaz de Usuario (Días 9-10)

SPRINT_5 = {
    "objetivos": [
        "Crear sistema de menús",
        "Implementar formularios interactivos",
        "Desarrollar navegación fluida",
        "Establecer manejo de errores UX"
    ]
}

🎯 Métricas de Éxito

METRICAS_PROYECTO = {
    "cobertura_funcional": "100% de requisitos implementados",
    "calidad_codigo": "0 errores críticos, < 5 warnings",
    "rendimiento": "Todas las operaciones < 2 segundos",
    "usabilidad": "Usuario puede completar tareas sin ayuda",
    "mantenibilidad": "Código documentado y modular"
}

🔍 Consideraciones de Diseño: Principios de Ingeniería

🏗️ Principios SOLID Aplicados

S - Single Responsibility Principle

# ❌ MAL: Clase con múltiples responsabilidades
class ProductoTodoEnUno:
    def __init__(self):
        pass
    
    def validar_producto(self):
        pass
    
    def guardar_en_archivo(self):
        pass
    
    def generar_reporte(self):
        pass
    
    def enviar_email(self):
        pass

# ✅ BIEN: Responsabilidades separadas
class Producto:
    """Solo maneja datos del producto"""
    pass

class ValidadorProducto:
    """Solo valida productos"""
    pass

class GestorArchivos:
    """Solo maneja persistencia"""
    pass

O - Open/Closed Principle

# 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

🛡️ Manejo de Errores Empresarial

# 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

🎯 Próximos Pasos

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.

✅ Lo que hemos definido:

  • Requisitos claros y medibles
  • Arquitectura robusta y escalable
  • Diseño modular y mantenible
  • Plan de implementación estructurado
  • Métricas de éxito específicas

Próximo paso: Desarrollo paso a paso del sistema completo, comenzando por los cimientos y construyendo hacia arriba.

Desarrollo Paso a Paso: Construyendo el Sistema Empresarial

¡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.

🚀 Metodología de Construcción: De Cimientos a Cúspide

🏗️ Fase 1: Cimientos Sólidos (Configuración y Utilidades)

Comenzamos construyendo los cimientos de nuestro sistema empresarial. Como en cualquier construcción, los cimientos determinan la solidez de toda la estructura.

Paso 1.1: Configuración Central del Sistema

# archivo: config.py
"""
Configuración centralizada del Sistema de Gestión Empresarial
Todos los parámetros del sistema en un solo lugar
"""

import os
from pathlib import Path
from datetime import datetime
import json

class ConfiguracionSistema:
    """Configuración única y centralizada del sistema (Patrón Singleton)"""
    
    _instancia = None
    _inicializado = False
    
    def __new__(cls):
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
        return cls._instancia
    
    def __init__(self):
        if not ConfiguracionSistema._inicializado:
            self._inicializar_configuracion()
            ConfiguracionSistema._inicializado = True
    
    def _inicializar_configuracion(self):
        """Inicializa toda la configuración del sistema"""
        
        # 📁 RUTAS DEL SISTEMA
        self.BASE_DIR = Path(__file__).parent
        self.DATOS_DIR = self.BASE_DIR / "datos"
        self.RESPALDOS_DIR = self.BASE_DIR / "respaldos"
        self.LOGS_DIR = self.BASE_DIR / "logs"
        self.REPORTES_DIR = self.BASE_DIR / "reportes"
        self.TEMPLATES_DIR = self.BASE_DIR / "templates"
        
        # 💾 ARCHIVOS DE BASE DE DATOS
        self.DB_PRODUCTOS = self.DATOS_DIR / "productos.json"
        self.DB_VENTAS = self.DATOS_DIR / "ventas.json"
        self.DB_USUARIOS = self.DATOS_DIR / "usuarios.json"
        self.DB_CONFIGURACION = self.DATOS_DIR / "configuracion.json"
        self.DB_CATEGORIAS = self.DATOS_DIR / "categorias.json"
        
        # 🏢 PARÁMETROS DE NEGOCIO
        self.STOCK_MINIMO_DEFAULT = 5
        self.STOCK_CRITICO = 2
        self.DESCUENTO_MAXIMO = 0.50  # 50% máximo
        self.IMPUESTO_DEFAULT = 0.16  # 16% IVA
        self.MARGEN_REPOSICION = 2.0  # Factor para sugerencias de reposición
        
        # 💰 CONFIGURACIÓN FINANCIERA
        self.MONEDA_SIMBOLO = "$"
        self.MONEDA_CODIGO = "MXN"
        self.PRECISION_DECIMAL = 2
        self.PRECIO_MINIMO = 0.01
        self.PRECIO_MAXIMO = 999999.99
        
        # 🔔 CONFIGURACIÓN DE ALERTAS
        self.ALERTAS_HABILITADAS = True
        self.ALERTA_STOCK_BAJO = True
        self.ALERTA_PRECIO_ALTO = True
        self.ALERTA_VENTA_GRANDE = True
        self.UMBRAL_VENTA_GRANDE = 10000.0
        
        # 📊 CONFIGURACIÓN DE REPORTES
        self.REPORTES_AUTOMATICOS = True
        self.FORMATO_FECHA_REPORTE = "%d/%m/%Y %H:%M:%S"
        self.PRODUCTOS_POR_PAGINA = 20
        self.DIAS_HISTORIAL_DEFAULT = 30
        
        # 🔐 CONFIGURACIÓN DE SEGURIDAD
        self.RESPALDO_AUTOMATICO = True
        self.RESPALDOS_MAXIMOS = 10
        self.VALIDACION_ESTRICTA = True
        self.LOG_OPERACIONES = True
        
        # 🎨 CONFIGURACIÓN DE INTERFAZ
        self.LIMPIAR_PANTALLA = True
        self.MOSTRAR_LOGO = True
        self.COLOR_EXITO = "verde"
        self.COLOR_ERROR = "rojo"
        self.COLOR_ADVERTENCIA = "amarillo"
        
        # Crear directorios necesarios
        self._crear_directorios()
        
        # Cargar configuración personalizada si existe
        self._cargar_configuracion_personalizada()
    
    def _crear_directorios(self):
        """Crea todos los directorios necesarios"""
        directorios = [
            self.DATOS_DIR,
            self.RESPALDOS_DIR,
            self.LOGS_DIR,
            self.REPORTES_DIR,
            self.TEMPLATES_DIR
        ]
        
        for directorio in directorios:
            directorio.mkdir(parents=True, exist_ok=True)
    
    def _cargar_configuracion_personalizada(self):
        """Carga configuración personalizada desde archivo"""
        if self.DB_CONFIGURACION.exists():
            try:
                with open(self.DB_CONFIGURACION, 'r', encoding='utf-8') as f:
                    config_personalizada = json.load(f)
                
                # Aplicar configuración personalizada
                for clave, valor in config_personalizada.items():
                    if hasattr(self, clave):
                        setattr(self, clave, valor)
                        
            except Exception as e:
                print(f"⚠️ Error cargando configuración personalizada: {e}")
    
    def guardar_configuracion(self):
        """Guarda la configuración actual"""
        config_data = {
            attr: getattr(self, attr) 
            for attr in dir(self) 
            if not attr.startswith('_') and not callable(getattr(self, attr))
        }
        
        try:
            with open(self.DB_CONFIGURACION, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=2, ensure_ascii=False, default=str)
            return True
        except Exception as e:
            print(f"❌ Error guardando configuración: {e}")
            return False
    
    def obtener_info_sistema(self):
        """Retorna información del sistema"""
        return {
            "version": "1.0.0",
            "nombre": "Sistema de Gestión Empresarial",
            "autor": "Tu Nombre",
            "fecha_creacion": datetime.now().isoformat(),
            "python_version": f"{os.sys.version_info.major}.{os.sys.version_info.minor}",
            "directorio_base": str(self.BASE_DIR)
        }

# Instancia global de configuración
config = ConfiguracionSistema()

Paso 1.2: Sistema de Logging Empresarial

# archivo: logger.py
"""
Sistema de logging empresarial para auditoría y debugging
"""

import logging
import logging.handlers
from datetime import datetime
from pathlib import Path
from config import config

class LoggerEmpresarial:
    """Sistema de logging centralizado para el sistema empresarial"""
    
    def __init__(self):
        self.logger = None
        self._configurar_logger()
    
    def _configurar_logger(self):
        """Configura el sistema de logging"""
        
        # Crear logger principal
        self.logger = logging.getLogger('SistemaEmpresarial')
        self.logger.setLevel(logging.DEBUG)
        
        # Evitar duplicación de logs
        if self.logger.handlers:
            return
        
        # Formato de logs
        formato = logging.Formatter(
            '%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        
        # Handler para archivo principal
        archivo_log = config.LOGS_DIR / "sistema.log"
        handler_archivo = logging.handlers.RotatingFileHandler(
            archivo_log,
            maxBytes=5*1024*1024,  # 5MB
            backupCount=5,
            encoding='utf-8'
        )
        handler_archivo.setLevel(logging.INFO)
        handler_archivo.setFormatter(formato)
        
        # Handler para errores críticos
        archivo_errores = config.LOGS_DIR / "errores.log"
        handler_errores = logging.handlers.RotatingFileHandler(
            archivo_errores,
            maxBytes=2*1024*1024,  # 2MB
            backupCount=3,
            encoding='utf-8'
        )
        handler_errores.setLevel(logging.ERROR)
        handler_errores.setFormatter(formato)
        
        # Handler para consola (solo en desarrollo)
        handler_consola = logging.StreamHandler()
        handler_consola.setLevel(logging.WARNING)
        handler_consola.setFormatter(logging.Formatter(
            '%(levelname)s: %(message)s'
        ))
        
        # Agregar handlers
        self.logger.addHandler(handler_archivo)
        self.logger.addHandler(handler_errores)
        self.logger.addHandler(handler_consola)
    
    def info(self, mensaje, **kwargs):
        """Log de información"""
        self.logger.info(mensaje, extra=kwargs)
    
    def warning(self, mensaje, **kwargs):
        """Log de advertencia"""
        self.logger.warning(mensaje, extra=kwargs)
    
    def error(self, mensaje, **kwargs):
        """Log de error"""
        self.logger.error(mensaje, extra=kwargs)
    
    def critical(self, mensaje, **kwargs):
        """Log crítico"""
        self.logger.critical(mensaje, extra=kwargs)
    
    def debug(self, mensaje, **kwargs):
        """Log de debug"""
        self.logger.debug(mensaje, extra=kwargs)
    
    def operacion(self, operacion, usuario="sistema", detalles=None):
        """Log específico para operaciones de negocio"""
        mensaje = f"OPERACION: {operacion} | Usuario: {usuario}"
        if detalles:
            mensaje += f" | Detalles: {detalles}"
        self.info(mensaje)
    
    def venta(self, codigo_producto, cantidad, total, usuario="sistema"):
        """Log específico para ventas"""
        self.operacion(
            "VENTA",
            usuario,
            f"Producto: {codigo_producto}, Cantidad: {cantidad}, Total: ${total:.2f}"
        )
    
    def inventario(self, operacion, codigo_producto, detalles=""):
        """Log específico para operaciones de inventario"""
        self.operacion(
            f"INVENTARIO_{operacion}",
            "sistema",
            f"Producto: {codigo_producto} | {detalles}"
        )

# Instancia global del logger
logger = LoggerEmpresarial()

Paso 1.3: Caja de Herramientas Empresarial (Utilidades)

# 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()

🏗️ Fase 2: Estructura Principal (Entidades de Negocio)

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}")

Programa principal

# archivo: main.py
"""
Sistema de Gestión de Inventario Inteligente
Programa principal con interfaz de usuario
"""

from inventario import GestorInventario
from reportes import GeneradorReportes
from utilidades import *

class SistemaInventario:
    """Clase principal del sistema"""
    
    def __init__(self):
        self.gestor = GestorInventario()
        self.reportes = GeneradorReportes(self.gestor)
        self.ejecutando = True
    
    def mostrar_menu_principal(self):
        """Muestra el menú principal"""
        opciones = [
            "Gestión de Productos",
            "Procesar Venta",
            "Reportes",
            "Buscar Productos",
            "Alertas de Stock"
        ]
        return mostrar_menu(opciones, "SISTEMA DE INVENTARIO")
    
    def menu_gestion_productos(self):
        """Menú de gestión de productos"""
        while True:
            opciones = [
                "Agregar Producto",
                "Listar Productos",
                "Actualizar Producto",
                "Eliminar Producto"
            ]
            
            opcion = mostrar_menu(opciones, "GESTIÓN DE PRODUCTOS")
            
            if opcion == 0:
                break
            elif opcion == 1:
                self.agregar_producto()
            elif opcion == 2:
                self.listar_productos()
            elif opcion == 3:
                self.actualizar_producto()
            elif opcion == 4:
                self.eliminar_producto()
    
    def agregar_producto(self):
        """Interfaz para agregar producto"""
        mostrar_titulo("AGREGAR PRODUCTO")
        
        try:
            codigo = obtener_entrada_usuario("Código del producto", str, validar_codigo_producto)
            if not codigo:
                return
            
            nombre = obtener_entrada_usuario("Nombre del producto")
            if not nombre:
                return
            
            precio = obtener_entrada_usuario("Precio", float, validar_precio)
            if precio is None:
                return
            
            stock = obtener_entrada_usuario("Stock inicial", int, validar_stock)
            if stock is None:
                return
            
            categoria = obtener_entrada_usuario("Categoría (opcional)", str) or "General"
            stock_minimo = obtener_entrada_usuario("Stock mínimo (opcional)", int, validar_stock) or 5
            
            exito, mensaje = self.gestor.agregar_producto(codigo, nombre, precio, stock, categoria, stock_minimo)
            
            if exito:
                print(f"✅ {mensaje}")
            else:
                print(f"❌ {mensaje}")
                
        except KeyboardInterrupt:
            print("\n👋 Operación cancelada")
        
        pausar()
    
    def procesar_venta(self):
        """Interfaz para procesar ventas"""
        mostrar_titulo("PROCESAR VENTA")
        
        try:
            codigo = obtener_entrada_usuario("Código del producto")
            if not codigo:
                return
            
            producto = self.gestor.obtener_producto(codigo)
            if not producto:
                print(f"❌ Producto {codigo} no encontrado")
                pausar()
                return
            
            print(f"📦 Producto: {producto.nombre}")
            print(f"💰 Precio: {formatear_precio(producto.precio)}")
            print(f"📊 Stock disponible: {producto.stock}")
            
            cantidad = obtener_entrada_usuario("Cantidad a vender", int, lambda x: x > 0)
            if cantidad is None:
                return
            
            total = producto.precio * cantidad
            print(f"\n💰 Total de la venta: {formatear_precio(total)}")
            
            if confirmar_accion("¿Confirmar venta?"):
                exito, mensaje, total_real = self.gestor.procesar_venta(codigo, cantidad)
                
                if exito:
                    print(f"✅ {mensaje}")
                    print(f"💰 Total: {formatear_precio(total_real)}")
                else:
                    print(f"❌ {mensaje}")
            else:
                print("👋 Venta cancelada")
                
        except KeyboardInterrupt:
            print("\n👋 Operación cancelada")
        
        pausar()
    
    def menu_reportes(self):
        """Menú de reportes"""
        while True:
            opciones = [
                "Reporte Completo",
                "Productos con Stock Bajo",
                "Reporte por Categorías",
                "Estadísticas Generales"
            ]
            
            opcion = mostrar_menu(opciones, "REPORTES")
            
            if opcion == 0:
                break
            elif opcion == 1:
                self.reportes.reporte_inventario_completo()
                pausar()
            elif opcion == 2:
                self.reportes.reporte_stock_bajo()
                pausar()
            elif opcion == 3:
                self.reportes.reporte_por_categoria()
                pausar()
            elif opcion == 4:
                self.mostrar_estadisticas()
                pausar()
    
    def ejecutar(self):
        """Ejecuta el sistema principal"""
        print("🏪 Bienvenido al Sistema de Gestión de Inventario")
        
        while self.ejecutando:
            try:
                opcion = self.mostrar_menu_principal()
                
                if opcion == 0:
                    self.ejecutando = False
                elif opcion == 1:
                    self.menu_gestion_productos()
                elif opcion == 2:
                    self.procesar_venta()
                elif opcion == 3:
                    self.menu_reportes()
                elif opcion == 4:
                    self.buscar_productos()
                elif opcion == 5:
                    self.mostrar_alertas()
                
            except KeyboardInterrupt:
                print("\n👋 Saliendo del sistema...")
                self.ejecutando = False
        
        print("¡Gracias por usar el Sistema de Inventario! 👋")

def main():
    """Función principal"""
    sistema = SistemaInventario()
    sistema.ejecutar()

if __name__ == "__main__":
    main()

Pruebas del sistema

# archivo: test_sistema.py
"""
Pruebas básicas del sistema de inventario
"""

from inventario import GestorInventario, Producto

def test_crear_producto():
    """Prueba la creación de productos"""
    producto = Producto("TEST01", "Producto de Prueba", 100.0, 10)
    assert producto.codigo == "TEST01"
    assert producto.nombre == "Producto de Prueba"
    assert producto.precio == 100.0
    assert producto.stock == 10
    print("✅ Test crear producto: PASÓ")

def test_gestor_inventario():
    """Prueba el gestor de inventario"""
    gestor = GestorInventario("test_productos.json")
    
    # Agregar producto
    exito, mensaje = gestor.agregar_producto("TEST02", "Producto Test", 50.0, 20)
    assert exito, f"Error al agregar producto: {mensaje}"
    
    # Obtener producto
    producto = gestor.obtener_producto("TEST02")
    assert producto is not None, "Producto no encontrado"
    assert producto.nombre == "Producto Test"
    
    # Procesar venta
    exito, mensaje, total = gestor.procesar_venta("TEST02", 5)
    assert exito, f"Error al procesar venta: {mensaje}"
    assert total == 250.0, f"Total incorrecto: {total}"
    
    # Verificar stock actualizado
    producto = gestor.obtener_producto("TEST02")
    assert producto.stock == 15, f"Stock incorrecto: {producto.stock}"
    
    print("✅ Test gestor inventario: PASÓ")

if __name__ == "__main__":
    test_crear_producto()
    test_gestor_inventario()
    print("🎉 Todas las pruebas pasaron!")

Mejoras y Desafíos: Evolución Empresarial

¡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.

🚀 Niveles de Evolución: Tu Camino al Dominio

🥉 Nivel Bronce: Mejoras Fundamentales

“Perfeccionando los cimientos”

Mejora 1: Base de Datos Profesional (SQLite)

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]

Mejora 2: Sistema de Autenticación y Usuarios

# archivo: autenticacion.py
"""
Sistema de autenticación y gestión de usuarios
"""

import hashlib
import secrets
from datetime import datetime, timedelta
from typing import Optional, Dict, List, Tuple

class GestorUsuarios:
    """Gestor de usuarios y autenticación"""
    
    def __init__(self, db_manager):
        self.db = db_manager
        self.usuario_actual = None
        self.sesion_activa = False
    
    def hash_password(self, password: str, salt: str = None) -> Tuple[str, str]:
        """Genera hash seguro de contraseña"""
        if not salt:
            salt = secrets.token_hex(16)
        
        # Usar PBKDF2 para mayor seguridad
        password_hash = hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt.encode('utf-8'),
            100000  # 100,000 iteraciones
        )
        
        return password_hash.hex(), salt
    
    def crear_usuario(self, username: str, password: str, nombre_completo: str,
                     email: str = None, rol: str = "operador") -> Tuple[bool, str]:
        """Crea un nuevo usuario"""
        
        # Verificar si el usuario ya existe
        if self.obtener_usuario(username):
            return False, "El usuario ya existe"
        
        # Generar hash de contraseña
        password_hash, salt = self.hash_password(password)
        
        # Insertar usuario
        query = '''
            INSERT INTO usuarios (username, password_hash, salt, nombre_completo, email, rol)
            VALUES (?, ?, ?, ?, ?, ?)
        '''
        
        if self.db.ejecutar_comando(query, (username, password_hash, salt, nombre_completo, email, rol)):
            logger.operacion("CREAR_USUARIO", detalles=f"Usuario: {username}, Rol: {rol}")
            return True, f"Usuario {username} creado exitosamente"
        else:
            return False, "Error al crear usuario"
    
    def autenticar(self, username: str, password: str) -> Tuple[bool, str]:
        """Autentica un usuario"""
        usuario = self.obtener_usuario(username)
        
        if not usuario:
            return False, "Usuario no encontrado"
        
        if not usuario['activo']:
            return False, "Usuario inactivo"
        
        # Verificar contraseña
        password_hash, _ = self.hash_password(password, usuario['salt'])
        
        if password_hash == usuario['password_hash']:
            self.usuario_actual = usuario
            self.sesion_activa = True
            
            # Actualizar último acceso
            self.db.ejecutar_comando(
                "UPDATE usuarios SET ultimo_acceso = CURRENT_TIMESTAMP WHERE username = ?",
                (username,)
            )
            
            logger.operacion("LOGIN", usuario=username)
            return True, f"Bienvenido, {usuario['nombre_completo']}"
        else:
            logger.operacion("LOGIN_FALLIDO", usuario=username)
            return False, "Contraseña incorrecta"
    
    def cerrar_sesion(self):
        """Cierra la sesión actual"""
        if self.usuario_actual:
            logger.operacion("LOGOUT", usuario=self.usuario_actual['username'])
        
        self.usuario_actual = None
        self.sesion_activa = False
    
    def tiene_permiso(self, operacion: str) -> bool:
        """Verifica si el usuario actual tiene permiso para una operación"""
        if not self.sesion_activa or not self.usuario_actual:
            return False
        
        rol = self.usuario_actual['rol']
        
        # Definir permisos por rol
        permisos = {
            'admin': ['*'],  # Todos los permisos
            'gerente': ['ver_productos', 'crear_producto', 'actualizar_producto', 
                       'procesar_venta', 'ver_reportes', 'gestionar_usuarios'],
            'vendedor': ['ver_productos', 'procesar_venta', 'ver_reportes_basicos'],
            'operador': ['ver_productos', 'actualizar_stock']
        }
        
        permisos_rol = permisos.get(rol, [])
        return '*' in permisos_rol or operacion in permisos_rol

Mejora 3: Sistema de Reportes Avanzados

# archivo: reportes_avanzados.py
"""
Sistema de reportes empresariales avanzados con gráficos y análisis
"""

import matplotlib.pyplot as plt
import pandas as pd
from datetime import datetime, timedelta
import seaborn as sns
from typing import Dict, List, Optional

class GeneradorReportesAvanzados:
    """Generador de reportes empresariales con visualizaciones"""
    
    def __init__(self, db_manager):
        self.db = db_manager
        # Configurar estilo de gráficos
        plt.style.use('seaborn-v0_8')
        sns.set_palette("husl")
    
    def reporte_ventas_temporales(self, dias: int = 30) -> Dict:
        """Genera reporte de ventas por período con gráfico"""
        
        fecha_inicio = datetime.now() - timedelta(days=dias)
        
        query = '''
            SELECT DATE(fecha_venta) as fecha, 
                   COUNT(*) as num_ventas,
                   SUM(total) as total_ventas,
                   AVG(total) as promedio_venta
            FROM ventas 
            WHERE fecha_venta >= ?
            GROUP BY DATE(fecha_venta)
            ORDER BY fecha
        '''
        
        resultados = self.db.ejecutar_consulta(query, (fecha_inicio.isoformat(),))
        
        if not resultados:
            return {"mensaje": "No hay datos de ventas para el período"}
        
        # Convertir a DataFrame para análisis
        df = pd.DataFrame([dict(row) for row in resultados])
        df['fecha'] = pd.to_datetime(df['fecha'])
        
        # Crear gráfico
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
        
        # Gráfico de ventas diarias
        ax1.plot(df['fecha'], df['total_ventas'], marker='o', linewidth=2, markersize=6)
        ax1.set_title(f'Ventas Diarias - Últimos {dias} días', fontsize=14, fontweight='bold')
        ax1.set_ylabel('Total Ventas ($)')
        ax1.grid(True, alpha=0.3)
        ax1.tick_params(axis='x', rotation=45)
        
        # Gráfico de número de transacciones
        ax2.bar(df['fecha'], df['num_ventas'], alpha=0.7, color='skyblue')
        ax2.set_title('Número de Transacciones Diarias', fontsize=14, fontweight='bold')
        ax2.set_ylabel('Número de Ventas')
        ax2.set_xlabel('Fecha')
        ax2.grid(True, alpha=0.3)
        ax2.tick_params(axis='x', rotation=45)
        
        plt.tight_layout()
        
        # Guardar gráfico
        archivo_grafico = config.REPORTES_DIR / f"ventas_temporales_{datetime.now().strftime('%Y%m%d')}.png"
        plt.savefig(archivo_grafico, dpi=300, bbox_inches='tight')
        plt.show()
        
        # Estadísticas resumidas
        estadisticas = {
            "periodo": f"{fecha_inicio.strftime('%d/%m/%Y')} - {datetime.now().strftime('%d/%m/%Y')}",
            "total_ventas": float(df['total_ventas'].sum()),
            "promedio_diario": float(df['total_ventas'].mean()),
            "mejor_dia": {
                "fecha": df.loc[df['total_ventas'].idxmax(), 'fecha'].strftime('%d/%m/%Y'),
                "ventas": float(df['total_ventas'].max())
            },
            "total_transacciones": int(df['num_ventas'].sum()),
            "promedio_transacciones": float(df['num_ventas'].mean()),
            "archivo_grafico": str(archivo_grafico)
        }
        
        return estadisticas
    
    def analisis_productos_abc(self) -> Dict:
        """Análisis ABC de productos (80/20 rule)"""
        
        query = '''
            SELECT p.codigo, p.nombre, p.categoria,
                   COALESCE(SUM(v.cantidad * v.precio_unitario), 0) as ingresos_totales,
                   COALESCE(SUM(v.cantidad), 0) as unidades_vendidas,
                   p.stock, p.precio
            FROM productos p
            LEFT JOIN ventas v ON p.codigo = v.codigo_producto
            WHERE p.activo = 1
            GROUP BY p.codigo, p.nombre, p.categoria, p.stock, p.precio
            ORDER BY ingresos_totales DESC
        '''
        
        resultados = self.db.ejecutar_consulta(query)
        
        if not resultados:
            return {"mensaje": "No hay datos suficientes para análisis ABC"}
        
        df = pd.DataFrame([dict(row) for row in resultados])
        
        # Calcular porcentajes acumulados
        df['porcentaje_ingresos'] = (df['ingresos_totales'] / df['ingresos_totales'].sum()) * 100
        df['porcentaje_acumulado'] = df['porcentaje_ingresos'].cumsum()
        
        # Clasificar productos ABC
        def clasificar_abc(porcentaje_acum):
            if porcentaje_acum <= 80:
                return 'A'
            elif porcentaje_acum <= 95:
                return 'B'
            else:
                return 'C'
        
        df['clasificacion'] = df['porcentaje_acumulado'].apply(clasificar_abc)
        
        # Crear gráfico de Pareto
        fig, ax1 = plt.subplots(figsize=(14, 8))
        
        # Barras de ingresos
        bars = ax1.bar(range(len(df)), df['ingresos_totales'], alpha=0.7, color='steelblue')
        ax1.set_xlabel('Productos (ordenados por ingresos)')
        ax1.set_ylabel('Ingresos Totales ($)', color='steelblue')
        ax1.tick_params(axis='y', labelcolor='steelblue')
        
        # Línea de porcentaje acumulado
        ax2 = ax1.twinx()
        ax2.plot(range(len(df)), df['porcentaje_acumulado'], color='red', marker='o', linewidth=2)
        ax2.set_ylabel('Porcentaje Acumulado (%)', color='red')
        ax2.tick_params(axis='y', labelcolor='red')
        ax2.set_ylim(0, 100)
        
        # Líneas de referencia ABC
        ax2.axhline(y=80, color='orange', linestyle='--', alpha=0.7, label='80% (A-B)')
        ax2.axhline(y=95, color='green', linestyle='--', alpha=0.7, label='95% (B-C)')
        
        plt.title('Análisis ABC de Productos (Principio de Pareto)', fontsize=16, fontweight='bold')
        plt.legend()
        plt.tight_layout()
        
        # Guardar gráfico
        archivo_grafico = config.REPORTES_DIR / f"analisis_abc_{datetime.now().strftime('%Y%m%d')}.png"
        plt.savefig(archivo_grafico, dpi=300, bbox_inches='tight')
        plt.show()
        
        # Estadísticas por clasificación
        resumen_abc = df.groupby('clasificacion').agg({
            'codigo': 'count',
            'ingresos_totales': 'sum',
            'unidades_vendidas': 'sum'
        }).round(2)
        
        return {
            "productos_a": int(resumen_abc.loc['A', 'codigo']) if 'A' in resumen_abc.index else 0,
            "productos_b": int(resumen_abc.loc['B', 'codigo']) if 'B' in resumen_abc.index else 0,
            "productos_c": int(resumen_abc.loc['C', 'codigo']) if 'C' in resumen_abc.index else 0,
            "ingresos_a": float(resumen_abc.loc['A', 'ingresos_totales']) if 'A' in resumen_abc.index else 0,
            "archivo_grafico": str(archivo_grafico),
            "productos_detalle": df.to_dict('records')
        }

        self.crear_interfaz()
   
def crear_interfaz(self):
    """Crea la interfaz gráfica"""
    # Frame principal
    main_frame = ttk.Frame(self.root, padding="10")
    main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
    
    # Botones principales
    ttk.Button(main_frame, text="Agregar Producto", 
              command=self.ventana_agregar_producto).grid(row=0, column=0, padx=5)
    ttk.Button(main_frame, text="Listar Productos", 
              command=self.mostrar_productos).grid(row=0, column=1, padx=5)
                  command=self.listar_productos).grid(row=0, column=1, padx=5)
        
        # Tabla de productos
        self.tree = ttk.Treeview(main_frame, columns=("Código", "Nombre", "Precio", "Stock"))
        self.tree.grid(row=1, column=0, columnspan=3, pady=10)

3. API REST

Crear una API con Flask:

from flask import Flask, jsonify, request
from inventario import GestorInventario

app = Flask(__name__)
gestor = GestorInventario()

@app.route("/api/productos", methods=["GET"])
def obtener_productos():
    productos = gestor.listar_productos()
    return jsonify([p.to_dict() for p in productos])

@app.route('/api/productos', methods=['POST'])
def agregar_producto():
    datos = request.json
    exito, mensaje = gestor.agregar_producto(
        datos['codigo'], datos['nombre'], 
        datos['precio'], datos['stock']
    )
    return jsonify({'exito': exito, 'mensaje': mensaje})

if __name__ == '__main__':
    app.run(debug=True)

    app.run(debug=True)

Funcionalidades adicionales

1. Sistema de usuarios

class Usuario:
    def __init__(self, username, password, rol="empleado"):
        self.username = username
        self.password = password  # En producción, usar hash
        self.rol = rol
        self.fecha_creacion = datetime.now()

class GestorUsuarios:
    def __init__(self):
        self.usuarios = {}
        self.usuario_actual = None
    
    def autenticar(self, username, password):
        """Autentica un usuario"""
        usuario = self.usuarios.get(username)
        if usuario and usuario.password == password:
            self.usuario_actual = usuario
            return True
        return False

2. Historial de movimientos

class MovimientoInventario:
    def __init__(self, tipo, producto_codigo, cantidad, usuario, motivo=""):
        self.tipo = tipo  # "entrada", "salida", "ajuste"
        self.producto_codigo = producto_codigo
        self.cantidad = cantidad
        self.usuario = usuario
        self.motivo = motivo
        self.fecha = datetime.now()

class GestorMovimientos:
    def __init__(self):
        self.movimientos = []
    
    def registrar_movimiento(self, tipo, producto_codigo, cantidad, usuario, motivo=""):
        """Registra un movimiento de inventario"""
        movimiento = MovimientoInventario(tipo, producto_codigo, cantidad, usuario, motivo)
        self.movimientos.append(movimiento)

3. Códigos de barras

import barcode
from barcode.writer import ImageWriter

def generar_codigo_barras(codigo_producto):
    """Genera código de barras para un producto"""
    code128 = barcode.get_barcode_class('code128')
    codigo = code128(codigo_producto, writer=ImageWriter())
    filename = f"codigos_barras/{codigo_producto}.png"
    codigo.save(filename)
    return filename

Desafíos de programación

Desafío 1: Predicción de demanda

Implementa un algoritmo simple para predecir cuándo se agotará un producto:

def predecir_agotamiento(producto, historial_ventas):
    """Predice cuándo se agotará un producto"""
    if not historial_ventas:
        return None
    
    # Calcular promedio de ventas diarias
    ventas_diarias = sum(historial_ventas) / len(historial_ventas)
    
    if ventas_diarias <= 0:
        return None
    
    # Días hasta agotamiento
    dias_restantes = producto.stock / ventas_diarias
    fecha_agotamiento = datetime.now() + timedelta(days=dias_restantes)
    
    return fecha_agotamiento

Desafío 2: Optimización de inventario

Calcula el punto de reorden óptimo:

def calcular_punto_reorden(demanda_promedio, tiempo_reposicion, stock_seguridad):
    """Calcula el punto de reorden óptimo"""
    return (demanda_promedio * tiempo_reposicion) + stock_seguridad

def sugerir_cantidad_pedido(producto, costo_pedido, costo_almacenamiento, demanda_anual):
    """Sugiere cantidad óptima de pedido usando EOQ"""
    import math
    
    # Fórmula EOQ (Economic Order Quantity)
    eoq = math.sqrt((2 * demanda_anual * costo_pedido) / costo_almacenamiento)
    return round(eoq)

Desafío 3: Dashboard en tiempo real

Crea un dashboard que se actualice automáticamente:

import threading
import time

class DashboardTiempoReal:
    def __init__(self, gestor):
        self.gestor = gestor
        self.ejecutando = False
    
    def iniciar_monitoreo(self):
        """Inicia el monitoreo en tiempo real"""
        self.ejecutando = True
        thread = threading.Thread(target=self._monitorear)
        thread.daemon = True
        thread.start()
    
    def _monitorear(self):
        """Monitorea el inventario continuamente"""
        while self.ejecutando:
            self._actualizar_dashboard()
            time.sleep(30)  # Actualizar cada 30 segundos
    
    def _actualizar_dashboard(self):
        """Actualiza la información del dashboard"""
        stats = self.gestor.obtener_estadisticas()
        productos_bajo = self.gestor.obtener_productos_stock_bajo()
        
        # Aquí actualizarías la interfaz gráfica
        print(f"Dashboard actualizado: {len(productos_bajo)} productos con stock bajo")

Mejores prácticas implementadas

1. Logging

import logging

# Configurar logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('inventario.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

class GestorInventario:
    def agregar_producto(self, codigo, nombre, precio, stock):
        logger.info(f"Agregando producto: {codigo} - {nombre}")
        # ... resto del código

2. Configuración externa

# archivo: config.py
import json

class Configuracion:
    def __init__(self, archivo_config="config.json"):
        self.config = self.cargar_configuracion(archivo_config)
    
    def cargar_configuracion(self, archivo):
        """Carga configuración desde archivo JSON"""
        try:
            with open(archivo, 'r') as f:
                return json.load(f)
        except FileNotFoundError:
            return self.configuracion_por_defecto()
    
    def configuracion_por_defecto(self):
        """Configuración por defecto"""
        return {
            "stock_minimo_global": 5,
            "moneda": "USD",
            "idioma": "es",
            "backup_automatico": True,
            "intervalo_backup": 3600  # segundos
        }

3. Tests automatizados

import unittest
from inventario import GestorInventario, Producto

class TestInventario(unittest.TestCase):
    def setUp(self):
        """Configuración antes de cada test"""
        self.gestor = GestorInventario("test_db.json")
    
    def test_agregar_producto(self):
        """Test agregar producto"""
        exito, mensaje = self.gestor.agregar_producto("TEST01", "Test", 100, 10)
        self.assertTrue(exito)
        self.assertIn("TEST01", self.gestor.productos)
    
    def test_procesar_venta(self):
        """Test procesar venta"""
        self.gestor.agregar_producto("TEST02", "Test", 50, 20)
        exito, mensaje, total = self.gestor.procesar_venta("TEST02", 5)
        self.assertTrue(exito)
        self.assertEqual(total, 250.0)
    
    def tearDown(self):
        """Limpieza después de cada test"""
        import os
        if os.path.exists("test_db.json"):
            os.remove("test_db.json")

if __name__ == '__main__':
    unittest.main()

Próximos pasos

  1. Implementa una mejora: Elige una de las sugerencias y desarróllala
  2. Optimiza el rendimiento: Identifica cuellos de botella
  3. Añade más validaciones: Mejora la robustez del sistema
  4. Crea documentación: Escribe manuales de usuario
  5. Despliega el sistema: Ponlo en producción

¡El proyecto está listo para crecer y evolucionar según tus necesidades!

Conclusión: Tu Viaje de Construcción Empresarial

🧭 Navegación:

🏆 ¡Lo has logrado!

¡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.

📊 Evaluación del Proyecto: Lo que has construido

Tomemos un momento para apreciar la magnitud de lo que has logrado:

🏗️ Arquitectura Completa

  • Diseñaste una estructura modular con separación clara de responsabilidades
  • Implementaste patrones de diseño como Singleton, Factory y Repository
  • Creaste un sistema con capas bien definidas (datos, lógica de negocio, interfaz)

💾 Gestión de Datos Robusta

  • Desarrollaste un sistema de persistencia con archivos JSON
  • Implementaste validación de datos para mantener la integridad
  • Creaste mecanismos de respaldo para prevenir pérdida de información

🧠 Lógica de Negocio Sofisticada

  • Programaste reglas de negocio complejas para inventario y ventas
  • Implementaste cálculos automáticos para precios, impuestos y ganancias
  • Creaste alertas inteligentes basadas en umbrales configurables

👤 Interfaz de Usuario Funcional

  • Diseñaste un menú interactivo intuitivo y fácil de navegar
  • Implementaste formularios de entrada con validación en tiempo real
  • Creaste reportes formativos que presentan información de manera clara

🔄 Integración de Conceptos: Tu Caja de Herramientas Completa

Este proyecto ha sido el escenario perfecto para aplicar todo lo que has aprendido:

📚 Fundamentos de Python

  • Variables y tipos de datos: Para almacenar y manipular información empresarial
  • Operadores y expresiones: Para cálculos de precios, impuestos y descuentos
  • Estructuras de control: Para implementar la lógica de decisión del negocio

🧰 Estructuras de Datos

  • Listas: Para colecciones de productos, transacciones y usuarios
  • Diccionarios: Para representar entidades con atributos (productos, clientes)
  • Tuplas: Para datos inmutables como códigos y registros históricos
  • Conjuntos: Para operaciones de filtrado y eliminación de duplicados

🛠️ Organización de Código

  • Funciones: Para encapsular operaciones específicas y promover la reutilización
  • Módulos: Para organizar el código en unidades lógicas y manejables
  • Clases y objetos: Para modelar entidades del mundo real con comportamiento
  • Manejo de errores: Para crear un sistema robusto que anticipa problemas

📂 Operaciones con Archivos

  • Lectura/escritura de archivos: Para persistencia de datos
  • Formato JSON: Para almacenamiento estructurado
  • Logging: Para registro de actividades y depuración

🚀 De Proyecto a Producto: Próximos Pasos

Tu sistema tiene el potencial de convertirse en un producto real. Aquí hay algunas direcciones que podrías tomar:

💼 Uso Profesional

  • Personalízalo para un negocio específico (tienda, restaurante, almacén)
  • Expande las funcionalidades según las necesidades específicas
  • Documenta exhaustivamente para facilitar su uso y mantenimiento

🌐 Evolución Tecnológica

  • Migra a una base de datos como SQLite o PostgreSQL
  • Desarrolla una interfaz web con Flask o Django
  • Implementa una API REST para integración con otros sistemas
  • Añade autenticación y autorización para múltiples usuarios con diferentes roles

📱 Expansión de Plataformas

  • Crea una versión móvil para gestión en movimiento
  • Implementa sincronización en la nube para acceso desde múltiples dispositivos
  • Desarrolla un panel de administración para configuración avanzada

🧠 Reflexión: Más Allá del Código

Este proyecto no solo te ha enseñado a programar; te ha introducido a:

🏢 Pensamiento Empresarial

  • Comprender cómo el software puede modelar procesos de negocio
  • Identificar necesidades y traducirlas en soluciones técnicas
  • Balancear funcionalidad, usabilidad y mantenibilidad

🧩 Arquitectura de Software

  • Diseñar sistemas modulares y escalables
  • Separar responsabilidades en componentes especializados
  • Planificar para el cambio y la evolución

👥 Perspectiva de Usuario

  • Considerar la experiencia del usuario final
  • Diseñar interfaces intuitivas y eficientes
  • Anticipar necesidades y puntos de fricción

🌟 Tu Legado de Código

Este proyecto representa tu transición de estudiante a creador. Es la prueba tangible de que puedes:

  1. Analizar un problema complejo y dividirlo en partes manejables
  2. Diseñar una solución estructurada y coherente
  3. Implementar esa solución con código limpio y funcional
  4. Probar y refinar hasta lograr un resultado profesional

🔮 El Futuro: Tu Camino Como Desarrollador

Con las habilidades que has demostrado en este proyecto, estás preparado para:

  • Contribuir a proyectos de código abierto
  • Desarrollar aplicaciones personales para resolver problemas específicos
  • Explorar roles profesionales en desarrollo de software
  • Especializarte en áreas como desarrollo web, ciencia de datos o automatización

🎓 Certificado de Logro

+---------------------------------------------------------------+
|                                                               |
|                  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                        |
|                                                               |
+---------------------------------------------------------------+

🔍 Comprueba tu comprensión final

  1. ¿Qué patrones de diseño implementaste en este proyecto y por qué son útiles?
  2. ¿Cómo manejarías la migración de datos si quisieras pasar de archivos JSON a una base de datos SQL?
  3. ¿Qué consideraciones de seguridad deberías tener en cuenta si este sistema se usara en un entorno de producción?
  4. ¿Cómo podrías adaptar este sistema para manejar múltiples tiendas o sucursales?
  5. 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.

Capítulo 11: Despliegue Básico

🧭 Navegación:

¡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.

🚀 ¿Qué es el despliegue?

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á).

¿Por qué es importante el despliegue?

  • Disponibilidad: Tus aplicaciones pueden funcionar 24/7, incluso cuando tu computadora está apagada
  • Accesibilidad: Otros usuarios pueden acceder a tus aplicaciones desde cualquier lugar
  • Escalabilidad: Puedes manejar más usuarios o procesar más datos según sea necesario
  • Automatización: Tus scripts pueden ejecutarse automáticamente según horarios predefinidos
  • Profesionalismo: Completa el ciclo de vida del desarrollo de software

📋 Lo que aprenderás en este capítulo

En este capítulo, nos centraremos en dos aspectos fundamentales del despliegue básico:

  1. Ejecutar scripts en la nube: Aprenderás a configurar servidores virtuales para ejecutar tus scripts de Python
  2. Programar tareas automáticas: Descubrirás cómo programar la ejecución periódica de tus scripts

Estos conocimientos te permitirán:

  • Automatizar tareas que deben ejecutarse regularmente
  • Mantener tus scripts funcionando incluso cuando no estás presente
  • Dar los primeros pasos hacia aplicaciones más complejas basadas en la nube

🛠️ Herramientas y conceptos que utilizaremos

Para este capítulo, nos familiarizaremos con:

  • Servidores virtuales: Máquinas en la nube donde ejecutar nuestro código
  • SSH: Protocolo para conectarnos y administrar servidores remotos
  • Cron: Herramienta para programar tareas en sistemas Unix/Linux
  • Servicios en la nube: Plataformas que ofrecen recursos computacionales
  • Entornos virtuales: Para gestionar dependencias en el servidor
  • Logs y monitoreo: Para verificar que todo funciona correctamente

🌩️ Opciones de despliegue

Existen muchas formas de desplegar aplicaciones Python, desde las más simples hasta las más complejas:

Nivel básico (cubriremos en este capítulo)

  • Servidores virtuales (VPS): Como DigitalOcean, Linode, AWS EC2
  • Programación de tareas: Con cron (Linux) o Task Scheduler (Windows)

Nivel intermedio (mencionaremos brevemente)

  • Plataformas como servicio (PaaS): Heroku, PythonAnywhere, Google App Engine
  • Contenedores: Docker, Docker Compose

Nivel avanzado (solo para referencia)

  • Orquestación de contenedores: Kubernetes
  • Serverless: AWS Lambda, Google Cloud Functions
  • Infraestructura como código: Terraform, CloudFormation

🧠 Enfoque práctico

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!

Avancemos a la primera sección: Ejecutar Scripts en la Nube

Ejecutar Scripts en la Nube

🧭 Navegación:

🌩️ La nube: Tu almacén virtual

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.

🤔 ¿Por qué ejecutar scripts en la nube?

Hay muchas razones para mover tus scripts a la nube:

  • Disponibilidad 24/7: Tus scripts funcionan incluso cuando tu computadora está apagada
  • Recursos dedicados: No consumen recursos de tu máquina personal
  • Conectividad constante: Mejor acceso a internet y menos interrupciones
  • Escalabilidad: Puedes aumentar recursos según tus necesidades
  • Acceso remoto: Puedes monitorear y actualizar desde cualquier lugar

🛠️ Opciones para desplegar en la nube

Existen varias opciones para ejecutar scripts en la nube, con diferentes niveles de complejidad y costo:

1. Servidores Virtuales Privados (VPS)

Un VPS es como alquilar un pequeño servidor en la nube. Es la opción más flexible y te da control total.

Proveedores populares:

  • DigitalOcean: Droplets desde $5/mes
  • Linode: Servidores desde $5/mes
  • AWS EC2: Instancias con capa gratuita disponible
  • Google Cloud Compute Engine: Instancias con crédito gratuito inicial
  • Microsoft Azure: Máquinas virtuales con crédito gratuito inicial

Ventajas:

  • Control total sobre el servidor
  • Puedes instalar cualquier software
  • Buena relación costo-beneficio para scripts que se ejecutan constantemente

Desventajas:

  • Requiere conocimientos básicos de administración de servidores
  • Tú eres responsable de la seguridad y mantenimiento

2. Plataformas como Servicio (PaaS)

Las PaaS son plataformas que manejan la infraestructura por ti, permitiéndote enfocarte solo en tu código.

Opciones populares:

  • PythonAnywhere: Específico para Python, con plan gratuito disponible
  • Heroku: Plataforma general, con plan gratuito limitado
  • Google App Engine: Servicio de Google Cloud con capa gratuita

Ventajas:

  • Más fácil de usar, menos configuración
  • No requiere conocimientos de administración de servidores
  • Muchas ofrecen planes gratuitos para proyectos pequeños

Desventajas:

  • Menos flexibilidad que un VPS
  • Pueden ser más costosas para uso intensivo
  • Algunas tienen limitaciones en ejecuciones en segundo plano

3. Servicios Serverless

Los servicios serverless ejecutan tu código solo cuando es necesario, sin mantener un servidor activo constantemente.

Opciones populares:

  • AWS Lambda: Funciones con capa gratuita generosa
  • Google Cloud Functions: Funciones con capa gratuita
  • Azure Functions: Funciones con capa gratuita

Ventajas:

  • Pagas solo por el tiempo de ejecución real
  • Escalado automático
  • Sin mantenimiento de servidor

Desventajas:

  • Limitaciones de tiempo de ejecución (generalmente máximo 15 minutos)
  • No ideal para scripts que necesitan ejecutarse constantemente
  • Curva de aprendizaje para configuración inicial

🚀 Tutorial: Desplegando un script en un VPS

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.

Paso 1: Preparar tu script para el despliegue

Antes de desplegar tu script, necesitas asegurarte de que esté listo para funcionar en un entorno de servidor:

  1. 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...
  1. Maneja las dependencias adecuadamente:

Crea un archivo requirements.txt que liste todas las bibliotecas que tu script necesita:

requests 2.28.1
beautifulsoup4 4.11.1
pandas 1.5.0
  1. Añade logging para depuración remota:
import logging

# Configurar logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("script_log.txt"),
        logging.StreamHandler()
    ]
)

# Usar logging en lugar de print
logging.info("Script iniciado")
try:
    # Tu código aquí
    logging.info("Operación completada")
except Exception as e:
    logging.error(f"Error: {str(e)}")
  1. Asegúrate de que tu script sea independiente:

Evita dependencias de interfaces gráficas o recursos que solo existen en tu computadora local.

Paso 2: Obtener un servidor virtual (VPS)

Para este ejemplo, usaremos DigitalOcean, pero los pasos son similares en otros proveedores:

  1. Crea una cuenta en DigitalOcean (u otro proveedor de VPS)
  2. Crea un Droplet (servidor virtual):
    • Selecciona Ubuntu como sistema operativo
    • Elige el plan más básico ($5/mes es suficiente para empezar)
    • Selecciona una región cercana a ti
    • Añade tu clave SSH o configura una contraseña
    • Crea el Droplet

Paso 3: Conectarte a tu servidor

Una vez que tu servidor esté listo, necesitas conectarte a él:

En Linux/macOS:

Abre una terminal y usa SSH:

ssh root@tu_ip_del_servidor

En Windows:

Usa PuTTY o el cliente SSH integrado en Windows 10/11:

ssh root@tu_ip_del_servidor

Paso 4: Configurar el entorno en el servidor

Una vez conectado, configura el entorno para ejecutar tu script:

  1. Actualiza el sistema:
apt update && apt upgrade -y
  1. Instala Python y herramientas necesarias:
apt install python3 python3-pip python3-venv -y
  1. Crea un directorio para tu proyecto:
mkdir -p /opt/myscripts
cd /opt/myscripts

Paso 5: Transferir tu script al servidor

Hay varias formas de transferir archivos a tu servidor:

Usando SCP (desde tu computadora local):

# En Linux/macOS/Windows con OpenSSH
scp tu_script.py requirements.txt root@tu_ip_del_servidor:/opt/myscripts/

Usando SFTP:

sftp root@tu_ip_del_servidor
cd /opt/myscripts
put tu_script.py
put requirements.txt
exit

Usando un editor en el servidor:

# En el servidor
nano /opt/myscripts/tu_script.py
# Pega tu código, guarda con Ctrl+O, sal con Ctrl+X

Paso 6: Configurar un entorno virtual

Es una buena práctica usar entornos virtuales para aislar las dependencias:

cd /opt/myscripts
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Paso 7: Ejecutar tu script manualmente

Primero, verifica que tu script funcione correctamente:

python tu_script.py

Si todo funciona bien, verás la salida esperada o los logs indicando que el script se ejecutó correctamente.

Paso 8: Configurar la ejecución automática

Para que tu script se ejecute automáticamente, puedes usar cron (lo veremos en detalle en la siguiente sección):

crontab -e

Añade una línea para ejecutar tu script periódicamente:

# Ejecutar el script todos los días a las 3 AM
0 3 * * * cd /opt/myscripts && source venv/bin/activate && python tu_script.py

🔒 Consideraciones de seguridad

Al desplegar scripts en la nube, la seguridad es crucial:

  1. Actualiza regularmente tu servidor con parches de seguridad
  2. Usa un usuario no-root para ejecutar tus scripts
  3. Configura un firewall para limitar el acceso
  4. No almacenes credenciales directamente en tu código
  5. Usa variables de entorno o archivos de configuración seguros
  6. Habilita autenticación de dos factores para acceder a tu servidor

💰 Optimización de costos

Para mantener los costos bajo control:

  1. Comienza pequeño: Usa el plan más básico y escala según sea necesario
  2. Monitorea el uso: Verifica si estás pagando por recursos que no utilizas
  3. Considera opciones serverless para scripts que se ejecutan con poca frecuencia
  4. Aprovecha las capas gratuitas que ofrecen muchos proveedores
  5. Comparte recursos: Ejecuta múltiples scripts en el mismo servidor

🔍 Comprueba tu comprensión

  1. ¿Cuál es la principal ventaja de ejecutar scripts en la nube en lugar de en tu computadora local?
  2. ¿Por qué es importante usar rutas absolutas en scripts que se ejecutarán en servidores?
  3. ¿Cuál es la diferencia principal entre un VPS y un servicio serverless?
  4. ¿Por qué es recomendable usar entornos virtuales en el servidor?
  5. ¿Qué información NO deberías incluir directamente en el código de tu script?

🛠️ Ejercicio práctico

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:

  1. Modifica el script para usar rutas absolutas
  2. Añade logging adecuado
  3. Crea un archivo requirements.txt
  4. 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.

Ejemplos Adicionales: Ejecutar Scripts en la Nube

🧭 Navegación:

Ejemplos Prácticos para Despliegue en la Nube

En esta sección, encontrarás ejemplos adicionales y más detallados para desplegar tus scripts de Python en diferentes entornos de nube.

Ejemplo 1: Script de Monitoreo de Sitio Web

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

Preparación para Despliegue

  1. Crear archivo de requisitos:

    # requirements.txt
    requests==2.28.1
    
  2. Configurar variables de entorno en el servidor:

    # En el servidor
    export EMAIL_USER="tu_correo@gmail.com"
    export EMAIL_PASSWORD="tu_contraseña_o_token"
    
    # Para hacerlas permanentes, añadir a ~/.bashrc
    echo 'export EMAIL_USER="tu_correo@gmail.com"' >> ~/.bashrc
    echo 'export EMAIL_PASSWORD="tu_contraseña_o_token"' >> ~/.bashrc
    
  3. Script de inicio:

    #!/bin/bash
    # start_monitor.sh
    cd /opt/monitoring
    source venv/bin/activate
    nohup python monitor_sitio.py > monitor.log 2>&1 &
    echo $! > monitor.pid
    echo "Monitor iniciado con PID $(cat monitor.pid)"
    
  4. Script de detención:

    #!/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
    

Ejemplo 2: Servicio Web Simple con Flask

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)))

Preparación para Despliegue

  1. Crear archivo de requisitos:

    # requirements.txt
    flask==2.2.3
    gunicorn==20.1.0
    
  2. Archivo de configuración para Gunicorn:

    # gunicorn_config.py
    bind = "0.0.0.0:5000"
    workers = 4
    accesslog = "access.log"
    errorlog = "error.log"
    capture_output = True
    
  3. Script de inicio:

    #!/bin/bash
    # start_api.sh
    cd /opt/api_service
    source venv/bin/activate
    nohup gunicorn -c gunicorn_config.py api_servicio:app > api.log 2>&1 &
    echo $! > api.pid
    echo "API iniciada con PID $(cat api.pid)"
    
  4. Configuración de Nginx como proxy inverso:

    # /etc/nginx/sites-available/api_service
    server {
        listen 80;
        server_name api.tudominio.com;
    
        location / {
            proxy_pass http://localhost:5000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
    

Ejemplo 3: Script de Respaldo en AWS S3

Este script realiza copias de seguridad de archivos y los sube a Amazon S3.

#!/usr/bin/env python3
# s3_backup.py - Script para hacer respaldos en AWS S3

import os
import sys
import boto3
import logging
import tarfile
import tempfile
from datetime import datetime
from botocore.exceptions import ClientError

# 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__)), "backup_log.txt")),
        logging.StreamHandler()
    ]
)

# Configuración (mejor usar variables de entorno en producción)
S3_BUCKET = os.environ.get("S3_BUCKET", "mi-bucket-de-respaldos")
S3_PREFIX = os.environ.get("S3_PREFIX", "backups/")
BACKUP_DIRS = [
    "/var/www/html",
    "/etc/nginx",
    "/home/usuario/datos"
]
MAX_BACKUPS = 7  # Mantener solo los últimos 7 respaldos

def crear_archivo_tar(directorios):
    """Crea un archivo tar.gz con los directorios especificados."""
    fecha_actual = datetime.now().strftime("%Y%m%d_%H%M%S")
    nombre_archivo = f"backup_{fecha_actual}.tar.gz"
    ruta_temporal = os.path.join(tempfile.gettempdir(), nombre_archivo)
    
    try:
        with tarfile.open(ruta_temporal, "w:gz") as tar:
            for directorio in directorios:
                if os.path.exists(directorio):
                    logging.info(f"Añadiendo {directorio} al archivo...")
                    
                    # Obtener ruta base para mantener estructura relativa
                    base_dir = os.path.dirname(directorio)
                    
                    # Añadir al tar manteniendo estructura relativa
                    tar.add(directorio, arcname=os.path.relpath(directorio, base_dir))
                else:
                    logging.warning(f"El directorio {directorio} no existe, se omitirá")
        
        logging.info(f"Archivo tar creado: {ruta_temporal}")
        return ruta_temporal, nombre_archivo
    except Exception as e:
        logging.error(f"Error al crear archivo tar: {e}")
        return None, None

def subir_a_s3(archivo_local, nombre_archivo):
    """Sube un archivo a Amazon S3."""
    try:
        s3_client = boto3.client('s3')
        s3_key = f"{S3_PREFIX}{nombre_archivo}"
        
        logging.info(f"Subiendo {archivo_local} a s3://{S3_BUCKET}/{s3_key}...")
        
        with open(archivo_local, "rb") as file:
            s3_client.upload_fileobj(file, S3_BUCKET, s3_key)
        
        logging.info("Archivo subido exitosamente")
        return True
    except ClientError as e:
        logging.error(f"Error de AWS al subir archivo: {e}")
        return False
    except Exception as e:
        logging.error(f"Error al subir archivo: {e}")
        return False

def listar_backups():
    """Lista los backups existentes en S3."""
    try:
        s3_client = boto3.client('s3')
        response = s3_client.list_objects_v2(
            Bucket=S3_BUCKET,
            Prefix=S3_PREFIX
        )
        
        if 'Contents' in response:
            return sorted([
                {
                    'key': obj['Key'],
                    'size': obj['Size'],
                    'last_modified': obj['LastModified']
                }
                for obj in response['Contents']
            ], key=lambda x: x['last_modified'])
        else:
            return []
    except Exception as e:
        logging.error(f"Error al listar backups: {e}")
        return []

def eliminar_backups_antiguos():
    """Elimina los backups más antiguos, manteniendo solo los últimos MAX_BACKUPS."""
    try:
        backups = listar_backups()
        
        if len(backups) <= MAX_BACKUPS:
            logging.info(f"Solo hay {len(backups)} backups, no es necesario eliminar ninguno")
            return
        
        # Calcular cuántos backups eliminar
        a_eliminar = len(backups) - MAX_BACKUPS
        backups_a_eliminar = backups[:a_eliminar]
        
        s3_client = boto3.client('s3')
        
        for backup in backups_a_eliminar:
            logging.info(f"Eliminando backup antiguo: {backup['key']}")
            s3_client.delete_object(
                Bucket=S3_BUCKET,
                Key=backup['key']
            )
        
        logging.info(f"Se eliminaron {a_eliminar} backups antiguos")
    except Exception as e:
        logging.error(f"Error al eliminar backups antiguos: {e}")

def main():
    """Función principal del script."""
    logging.info("🚀 Iniciando proceso de respaldo")
    
    # Crear archivo tar
    archivo_local, nombre_archivo = crear_archivo_tar(BACKUP_DIRS)
    if not archivo_local:
        logging.error("❌ No se pudo crear el archivo de respaldo")
        sys.exit(1)
    
    # Subir a S3
    if subir_a_s3(archivo_local, nombre_archivo):
        logging.info("✅ Respaldo completado exitosamente")
        
        # Eliminar archivo temporal
        os.remove(archivo_local)
        logging.info(f"🧹 Archivo temporal eliminado: {archivo_local}")
        
        # Eliminar backups antiguos
        eliminar_backups_antiguos()
    else:
        logging.error("❌ No se pudo completar el respaldo")
        sys.exit(1)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        logging.info("👋 Proceso interrumpido manualmente")
        sys.exit(0)
    except Exception as e:
        logging.critical(f"❌ Error crítico: {str(e)}")
        sys.exit(1)

Preparación para Despliegue

  1. Crear archivo de requisitos:

    # requirements.txt
    boto3==1.26.90
    
  2. Configurar credenciales de AWS:

    # En el servidor
    mkdir -p ~/.aws
    
    # Crear archivo de credenciales
    cat > ~/.aws/credentials << EOF
    [default]
    aws_access_key_id = TU_ACCESS_KEY
    aws_secret_access_key = TU_SECRET_KEY
    EOF
    
    # Crear archivo de configuración
    cat > ~/.aws/config << EOF
    [default]
    region = us-east-1
    output = json
    EOF
    
    # Establecer permisos adecuados
    chmod 600 ~/.aws/credentials
    
  3. Configurar variables de entorno:

    export S3_BUCKET="mi-bucket-de-respaldos"
    export S3_PREFIX="servidor1/backups/"
    
    # Para hacerlas permanentes
    echo 'export S3_BUCKET="mi-bucket-de-respaldos"' >> ~/.bashrc
    echo 'export S3_PREFIX="servidor1/backups/"' >> ~/.bashrc
    

Consejos Adicionales para Despliegue en la Nube

Monitoreo y Alertas

Para asegurar que tus scripts funcionen correctamente en la nube:

  1. Implementa logging detallado:

    import logging
    
    # Configuración básica
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler("app.log"),
            logging.StreamHandler()
        ]
    )
    
    # Uso en diferentes partes del código
    logger = logging.getLogger("mi_script")
    logger.info("Iniciando proceso")
    logger.warning("Advertencia: recurso no encontrado")
    logger.error("Error al procesar datos")
    
  2. Configura alertas por correo o SMS para errores críticos.

  3. Utiliza servicios de monitoreo como:

    • AWS CloudWatch
    • Google Cloud Monitoring
    • Datadog
    • Prometheus + Grafana

Seguridad

  1. Nunca almacenes credenciales en el código:

    # MAL
    password = "mi_contraseña_secreta"  # No hagas esto
    
    # BIEN
    import os
    password = os.environ.get("MI_PASSWORD")
    
  2. Usa un usuario con privilegios limitados para ejecutar tus scripts.

  3. Implementa autenticación para APIs y servicios web:

    # Ejemplo simple con Flask
    from functools import wraps
    from flask import request, jsonify
    
    def require_api_key(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            api_key = request.headers.get('X-API-Key')
            if api_key and api_key == os.environ.get('API_KEY'):
                return f(*args, **kwargs)
            return jsonify({"error": "Acceso no autorizado"}), 401
        return decorated
    
    @app.route('/api/datos')
    @require_api_key
    def obtener_datos():
        return jsonify({"datos": "información confidencial"})
    

Recuperación ante Fallos

  1. 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
    
  2. 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}")
    

Recursos Adicionales


🧭 Navegación:

Programar Tareas Automáticas

🧭 Navegación:

⏰ Automatización temporal: El reloj de tu almacén

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.

🤔 ¿Por qué programar tareas?

La programación de tareas ofrece numerosas ventajas:

  • Consistencia: Las tareas se ejecutan exactamente cuando deben, sin olvidos
  • Eficiencia: No necesitas iniciar manualmente cada tarea
  • Conveniencia: Las tareas pueden ejecutarse en horarios de baja actividad
  • Regularidad: Ideal para mantenimiento, backups y actualizaciones periódicas
  • Automatización completa: Combinado con scripts en la nube, logras un sistema totalmente autónomo

🛠️ Herramientas para programar tareas

Existen diferentes herramientas según el sistema operativo:

En sistemas Linux/Unix (incluido macOS)

Cron es la herramienta estándar para programar tareas en sistemas Unix:

  • Potente y flexible
  • Integrado en prácticamente todos los sistemas Unix
  • Permite programación muy precisa
  • Usa una sintaxis específica para definir cuándo se ejecutan las tareas

En Windows

Programador de tareas (Task Scheduler) es la herramienta nativa de Windows:

  • Interfaz gráfica intuitiva
  • Integrado en todas las versiones de Windows
  • Opciones avanzadas para condiciones y disparadores
  • Puede ejecutar scripts Python directamente

Bibliotecas de Python

También existen bibliotecas de Python para programar tareas desde dentro de tus scripts:

  • schedule: Biblioteca simple para programar tareas
  • APScheduler: Programador avanzado con múltiples opciones
  • Celery: Sistema completo de colas y tareas (más avanzado)

📅 Programación con Cron (Linux/Unix)

Cron es la herramienta más común para programar tareas en servidores Linux, así que la veremos en detalle.

Sintaxis básica de Cron

Un trabajo (job) de cron se define con una línea en el archivo crontab, con este formato:

m h dom mon dow comando_a_ejecutar

Donde:

  • m: Minuto (0-59)
  • h: Hora (0-23)
  • dom: Día del mes (1-31)
  • mon: Mes (1-12)
  • dow: Día de la semana (0-7, donde 0 y 7 son domingo)

Cada posición puede contener:

  • Un número específico (ej: 5)
  • Un rango (ej: 1-5)
  • Una lista (ej: 1,3,5)
  • Un intervalo (ej: */2 significa “cada 2”)
  • Un asterisco * que significa “cualquier valor”

Ejemplos comunes de cron

# 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

Configurando tareas con crontab

Para editar tu crontab personal:

# Comando para editar crontab
crontab -e

Esto abrirá un editor donde puedes añadir tus tareas programadas. Para ver las tareas actuales:

# Comando para listar tareas crontab
crontab -l

Ejecutando scripts Python con cron

Para ejecutar un script Python con cron, es importante especificar la ruta completa tanto al intérprete de Python como al script:

# Forma básica (no recomendada para entornos virtuales)
0 3 * * * /usr/bin/python3 /ruta/completa/al/script.py

# Con entorno virtual (recomendado)
0 3 * * * cd /ruta/al/proyecto && /ruta/al/proyecto/venv/bin/python /ruta/al/proyecto/script.py

Capturando la salida y errores

Es buena práctica redirigir la salida y los errores a archivos para poder revisarlos después:

# Ejemplo de redirección de salida y errores
0 3 * * * /usr/bin/python3 /ruta/al/script.py > /ruta/al/logs/script.log 2>&1

Donde:

  • > redirige la salida estándar al archivo, reemplazando el contenido anterior
  • 2>&1 redirige los errores al mismo lugar que la salida estándar

🪟 Programación con Task Scheduler (Windows)

En Windows, el Programador de tareas ofrece una interfaz gráfica para configurar tareas programadas.

Configurando una tarea básica

  1. Abre el Programador de tareas (busca “Task Scheduler” en el menú Inicio)
  2. Haz clic en “Crear tarea básica” en el panel derecho
  3. Sigue el asistente:
    • Nombre y descripción de la tarea
    • Disparador (cuándo se ejecutará)
    • Acción (qué hará)
    • Finalizar

Ejecutando scripts Python

Para ejecutar un script Python:

  1. En la sección “Acción”, selecciona “Iniciar un programa”

  2. En “Programa/script”, ingresa la ruta al ejecutable de Python:

    # Ruta al ejecutable de Python
    # C:/Path/To/Python/python.exe
    
  3. En “Agregar argumentos”, ingresa la ruta a tu script:

    # Ruta al script Python
    # C:/Path/To/Your/script.py
    
  4. En “Iniciar en”, ingresa el directorio donde está tu script:

    # Directorio de trabajo
    # C:/Path/To/Your
    

Opciones avanzadas

El Programador de tareas de Windows ofrece opciones avanzadas:

  • Ejecutar solo cuando el usuario ha iniciado sesión o no
  • Ejecutar con privilegios elevados
  • Configurar condiciones (ej: solo si la computadora está inactiva)
  • Configurar opciones de recuperación si la tarea falla

🐍 Programación desde Python

Si prefieres manejar la programación desde dentro de tu script, puedes usar bibliotecas de Python.

Usando la biblioteca schedule

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.

Usando APScheduler

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

🔄 Combinando despliegue en la nube y programación de tareas

La combinación perfecta para la automatización completa es:

  1. Desplegar tu script en un servidor en la nube (como vimos en la sección anterior)
  2. 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

🔍 Comprueba tu comprensión

  1. ¿Qué significa la siguiente expresión cron: 0 */2 * * *?
  2. ¿Por qué es importante especificar rutas absolutas en las tareas de cron?
  3. ¿Cuál es la diferencia entre usar cron y la biblioteca schedule de Python?
  4. ¿Cómo capturarías los errores de un script programado con cron?
  5. ¿Qué consideraciones debes tener al programar tareas que se ejecutan durante la noche?

🛠️ Ejercicio práctico

Toma el script de copias de seguridad automáticas que creamos en el capítulo anterior y configúralo para que se ejecute automáticamente:

  1. En Linux/macOS:

    • Configura una tarea cron para ejecutar el script todos los días a las 2 AM
    • Asegúrate de que los logs se guarden en un archivo
  2. En Windows:

    • Usa el Programador de tareas para ejecutar el script todos los días a las 2 AM
    • Configura opciones para que se ejecute incluso si el usuario no ha iniciado sesión
  3. Usando Python:

    • Modifica el script para usar schedule o APScheduler
    • Configúralo para ejecutarse cada 24 horas

📝 Resumen

En esta sección, has aprendido:

  • 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.

Monitoreo y Mantenimiento

🧭 Navegación:

👁️ Vigilancia continua: El supervisor del almacén

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.

🤔 ¿Por qué es crucial el monitoreo?

Incluso los mejores scripts pueden fallar por razones inesperadas:

  • Cambios externos: APIs que cambian, sitios web que se actualizan
  • Problemas de recursos: Memoria insuficiente, disco lleno
  • Errores inesperados: Casos extremos no contemplados
  • Problemas de red: Interrupciones de conexión
  • Actualizaciones del sistema: Cambios en dependencias o el entorno

Sin un buen sistema de monitoreo, podrías no darte cuenta de que tus scripts han dejado de funcionar hasta que sea demasiado tarde.

📊 Estrategias de monitoreo

Existen varias estrategias para monitorear tus scripts automatizados:

1. Logging detallado

El logging es la base de cualquier sistema de monitoreo:

import logging
from datetime import datetime

# Configurar logging con timestamp
log_file = f"script_{datetime.now().strftime('%Y%m%d')}.log"
logging.basicConfig(
    filename=log_file,
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

# Usar logging en puntos clave
logging.info("Script iniciado")
try:
    # Operación importante
    result = perform_critical_operation()
    logging.info(f"Operación completada con resultado: {result}")
except Exception as e:
    logging.error(f"Error en operación crítica: {str(e)}")
    # Posiblemente enviar notificación
finally:
    logging.info("Script finalizado")

2. Notificaciones de errores

Configura tu script para enviar alertas cuando ocurran problemas:

Notificaciones por correo electrónico:

import smtplib
from email.message import EmailMessage

def send_error_email(error_message):
    """Envía un correo electrónico de alerta."""
    msg = EmailMessage()
    msg.set_content(f"Error en script automatizado:\n\n{error_message}")
    
    msg['Subject'] = 'ALERTA: Error en script automatizado'
    msg['From'] = 'tu_correo@gmail.com'
    msg['To'] = 'destinatario@example.com'
    
    # Configurar servidor SMTP (ejemplo para Gmail)
    server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
    server.login("tu_correo@gmail.com", "tu_contraseña_o_token")
    server.send_message(msg)
    server.quit()

# Usar en el manejo de excepciones
try:
    # Código que podría fallar
    result = risky_operation()
except Exception as e:
    error_msg = f"Error: {str(e)}"
    logging.error(error_msg)
    send_error_email(error_msg)

Nota de seguridad: Para servicios como Gmail, usa contraseñas de aplicación en lugar de tu contraseña principal.

Notificaciones por servicios de mensajería:

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)

3. Archivos de estado y heartbeats

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

4. Servicios de monitoreo

Para scripts críticos, considera usar servicios de monitoreo profesionales:

  • Uptime Robot: Monitoreo gratuito de sitios web y endpoints
  • Healthchecks.io: Servicio especializado en monitorear tareas cron
  • Sentry: Monitoreo de errores en aplicaciones
  • Prometheus + Grafana: Solución completa de monitoreo y visualización

Ejemplo con Healthchecks.io:

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

🔧 Mantenimiento preventivo

Además del monitoreo, es importante realizar mantenimiento preventivo:

1. Rotación de logs

Evita que los archivos de log crezcan indefinidamente:

import logging
from logging.handlers import RotatingFileHandler

# Configurar rotación de logs (máximo 5 archivos de 1MB cada uno)
handler = RotatingFileHandler(
    "app.log",
    maxBytes=1_000_000,  # 1MB
    backupCount=5
)

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[handler, logging.StreamHandler()]
)

2. Limpieza de archivos temporales

Implementa rutinas para limpiar archivos temporales:

def cleanup_temp_files(directory, max_age_days=7):
    """Elimina archivos temporales más antiguos que max_age_days."""
    now = datetime.now()
    count = 0
    
    for filename in os.listdir(directory):
        if not filename.endswith('.tmp'):
            continue
            
        filepath = os.path.join(directory, filename)
        file_time = datetime.fromtimestamp(os.path.getmtime(filepath))
        age_days = (now - file_time).days
        
        if age_days > max_age_days:
            os.remove(filepath)
            count += 1
    
    logging.info(f"Limpieza completada: {count} archivos eliminados")

3. Verificación de dependencias

Verifica periódicamente que las dependencias estén actualizadas:

import pkg_resources
import requests

def check_dependencies():
    """Verifica si hay actualizaciones disponibles para las dependencias."""
    outdated = []
    
    for dist in pkg_resources.working_set:
        package_name = dist.project_name
        current_version = dist.version
        
        try:
            # Consultar la última versión en PyPI
            response = requests.get(f"https://pypi.org/pypi/{package_name}/json")
            if response.status_code == 200:
                latest_version = response.json()["info"]["version"]
                
                if latest_version != current_version:
                    outdated.append(f"{package_name}: {current_version} -> {latest_version}")
        except Exception:
            pass
    
    if outdated:
        message = "Dependencias desactualizadas:\n" + "\n".join(outdated)
        logging.warning(message)
        # Posiblemente enviar notificación

4. Pruebas periódicas

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

📈 Monitoreo de rendimiento

Para scripts que procesan grandes cantidades de datos o se ejecutan frecuentemente, es útil monitorear el rendimiento:

1. Tiempo de ejecución

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

2. Uso de recursos

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}%")

🔄 Actualizaciones y mejoras

Mantén tus scripts actualizados y mejorándolos continuamente:

1. Control de versiones

Usa un sistema de control de versiones como Git para rastrear cambios:

# Inicializar repositorio
git init

# Añadir archivos
git add .

# Crear commit inicial
git commit -m "Versión inicial del script de automatización"

# Para cada actualización
git add script.py
git commit -m "Mejora: añadida verificación de errores en la función X"

2. Documentación de cambios

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

3. Actualizaciones graduales

Cuando implementes cambios importantes:

  1. Haz una copia de seguridad del script actual
  2. Prueba exhaustivamente los cambios en un entorno de desarrollo
  3. Implementa gradualmente en producción
  4. Monitorea de cerca después de la actualización
  5. Ten un plan de rollback en caso de problemas

🔍 Comprueba tu comprensión

  1. ¿Por qué es importante implementar logging en scripts automatizados?
  2. ¿Qué ventajas ofrece un servicio como Healthchecks.io sobre un sistema de heartbeat casero?
  3. ¿Cómo podrías detectar si un script está consumiendo demasiada memoria?
  4. ¿Por qué es importante la rotación de logs?
  5. ¿Qué estrategias puedes usar para ser notificado inmediatamente si un script falla?

🛠️ Ejercicio práctico

Mejora uno de los scripts de automatización que creamos en el capítulo anterior añadiendo:

  1. Logging detallado con rotación de archivos
  2. Un sistema de notificación por correo electrónico o Telegram
  3. Medición y registro del tiempo de ejecución
  4. Verificación de recursos disponibles antes de ejecutar operaciones intensivas
  5. Un archivo de heartbeat que se actualice periódicamente

📝 Resumen

En esta sección, has aprendido:

  • 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.

Resumen del Capítulo

🧭 Navegación:

🏁 El ciclo completo de automatización

¡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:

  1. Ejecutar scripts en la nube: Aprendiste a configurar servidores virtuales donde tus scripts pueden funcionar 24/7
  2. Programar tareas automáticas: Descubriste cómo programar la ejecución periódica de tus scripts sin intervención manual
  3. Monitoreo y mantenimiento: Exploraste técnicas para asegurar que tus scripts sigan funcionando correctamente a largo plazo

🧠 Conceptos clave aprendidos

Sobre despliegue en la nube:

  • Diferentes opciones de despliegue: VPS, PaaS y servicios serverless
  • Configuración de un servidor virtual para ejecutar scripts Python
  • Transferencia de archivos y gestión de dependencias en el servidor
  • Consideraciones de seguridad para scripts en producción

Sobre programación de tareas:

  • Uso de cron en sistemas Linux/Unix
  • Configuración del Programador de tareas en Windows
  • Bibliotecas de Python para programación de tareas (schedule, APScheduler)
  • Estrategias para capturar la salida y errores de tareas programadas

Sobre monitoreo y mantenimiento:

  • Implementación de logging detallado y rotación de logs
  • Sistemas de notificación para alertar sobre errores
  • Técnicas de heartbeat para verificar que los scripts siguen funcionando
  • Mantenimiento preventivo y monitoreo de rendimiento

🔄 El flujo de trabajo completo

Con los conocimientos de este capítulo, ahora puedes implementar un flujo de trabajo profesional para tus scripts de automatización:

  1. Desarrollo local: Crear y probar tu script en tu computadora
  2. Preparación para despliegue: Adaptar el script para funcionar en un entorno de servidor
  3. Despliegue en la nube: Transferir el script a un servidor virtual
  4. Configuración del entorno: Instalar dependencias y configurar el entorno de ejecución
  5. Programación de tareas: Configurar la ejecución automática según un calendario
  6. Implementación de monitoreo: Añadir logging, notificaciones y verificaciones de salud
  7. Mantenimiento continuo: Realizar actualizaciones y mejoras periódicas

🚀 Aplicaciones prácticas

Los conocimientos de este capítulo te permiten automatizar una amplia variedad de tareas:

  • Backups automáticos que se ejecutan durante la noche y te notifican si hay problemas
  • Recopilación de datos periódica de sitios web o APIs
  • Generación de informes programados que se envían automáticamente por correo
  • Procesamiento de archivos que llegan a un servidor
  • Monitoreo de sistemas con alertas cuando se detectan problemas
  • Sincronización de datos entre diferentes sistemas

🔍 Comprueba tu comprensión general

Para verificar que has comprendido los conceptos clave de este capítulo, responde estas preguntas:

  1. ¿Cuáles son las principales ventajas de ejecutar scripts en la nube en lugar de en tu computadora local?
  2. ¿Qué consideraciones de seguridad debes tener al desplegar scripts en un servidor?
  3. ¿Cuál es la diferencia entre un VPS y un servicio serverless? ¿Cuándo usarías cada uno?
  4. ¿Qué significa la expresión cron 30 2 * * 1-5 y qué tarea programaría?
  5. ¿Por qué es importante implementar un sistema de monitoreo para scripts automatizados?
  6. ¿Qué estrategias puedes usar para ser notificado si un script falla durante la noche?
  7. ¿Cómo podrías implementar un sistema de “heartbeat” básico para verificar que un script sigue funcionando?

🛠️ Proyecto integrador

Para aplicar todos los conocimientos de este capítulo, te propongo un proyecto integrador:

Sistema de monitoreo de precios automatizado

Crea un sistema que:

  1. Extraiga datos de precios de productos de un sitio web (usando web scraping)
  2. Almacene los datos en un archivo CSV o una base de datos simple
  3. Analice cambios de precios y detecte ofertas
  4. Genere informes con estadísticas y tendencias
  5. Envíe notificaciones cuando encuentre ofertas interesantes

Luego, despliega este sistema en la nube:

  1. Configura un servidor virtual (VPS) económico
  2. Transfiere tu script y configura el entorno
  3. Programa la ejecución diaria con cron
  4. Implementa monitoreo con logging y notificaciones
  5. Configura mantenimiento para rotación de logs y limpieza de datos antiguos

Este proyecto integra perfectamente el web scraping que aprendiste en el capítulo anterior con las técnicas de despliegue de este capítulo.

🔮 Próximos pasos

Ahora que dominas los fundamentos del despliegue básico, podrías explorar:

  • Contenedorización con Docker: Para entornos de ejecución más consistentes y portables
  • Bases de datos: Para almacenar datos de forma más robusta y escalable
  • APIs REST: Para crear interfaces programáticas para tus aplicaciones
  • Frameworks web: Como Flask o Django para crear aplicaciones web completas
  • CI/CD: Integración y despliegue continuos para automatizar el proceso de actualización

📝 Reflexión final

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.

Ejercicios por Capítulo

🧭 Navegación:

¡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.

🎯 Propósito de los ejercicios

Los ejercicios están diseñados para:

  • Reforzar los conceptos aprendidos en cada capítulo
  • Desafiar tu comprensión con problemas de dificultad creciente
  • Integrar conocimientos de múltiples capítulos
  • Aplicar lo aprendido en situaciones prácticas del mundo real
  • Prepararte para crear tus propios proyectos

📝 Cómo usar esta sección

Para aprovechar al máximo estos ejercicios:

  1. Intenta resolverlos por tu cuenta antes de mirar las soluciones
  2. Usa papel y lápiz para planificar tu solución antes de programar
  3. Experimenta con variaciones de los ejercicios para profundizar tu comprensión
  4. Revisa las soluciones en el Apéndice C solo después de intentarlo
  5. No te desanimes si no puedes resolver algún ejercicio; el aprendizaje es un proceso

🧩 Estructura de los ejercicios

Cada conjunto de ejercicios incluye:

  • Ejercicios básicos: Para practicar los conceptos fundamentales
  • Ejercicios intermedios: Para aplicar los conceptos en situaciones más complejas
  • Desafíos: Para poner a prueba tu creatividad y habilidades de resolución de problemas
  • Proyectos mini: Para integrar múltiples conceptos en una aplicación práctica

📚 Ejercicios por capítulo

Capítulo 3: Sintaxis y Primer Programa

Ejercicios para practicar la sintaxis básica de Python, comentarios y ejecución de programas.

Capítulo 4: Variables y Tipos de Datos

Ejercicios sobre creación de variables, tipos de datos básicos y conversión entre tipos.

Capítulo 5: Operadores y Expresiones

Ejercicios para practicar operadores matemáticos, de comparación, lógicos y expresiones complejas.

Capítulo 6: Estructuras de Control

Ejercicios sobre condicionales, bucles for, bucles while y control de flujo.

Capítulo 7: Estructuras de Datos

Ejercicios para trabajar con listas, diccionarios, tuplas y conjuntos.

Capítulo 8: Funciones y Módulos

Ejercicios sobre creación y uso de funciones, parámetros, valores de retorno y módulos.

Capítulo 9: Manejo de Archivos

Ejercicios para practicar la lectura y escritura de archivos de texto, CSV y JSON.

Capítulo 10: Manejo de Errores

Ejercicios sobre manejo de excepciones, bloques try/except y errores personalizados.

Capítulo 11: Proyectos de Automatización

Ejercicios para aplicar conceptos de automatización en proyectos prácticos.

Capítulo 12: Despliegue Básico

Ejercicios sobre despliegue de scripts, programación de tareas y monitoreo.

🚀 Proyecto Final Integrador

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

💡 Consejos para resolver ejercicios

  • Divide y vencerás: Descompón problemas complejos en partes más pequeñas
  • Planifica antes de programar: Esboza tu solución en pseudocódigo
  • Prueba incrementalmente: No esperes a terminar todo para probar tu código
  • Depura sistemáticamente: Si algo no funciona, identifica el problema paso a paso
  • Busca alternativas: Hay múltiples formas de resolver un mismo problema
  • Aprende de los errores: Los errores son oportunidades de aprendizaje valiosas

🏆 Reconocimiento de logros

A medida que completes los ejercicios, reconoce y celebra tu progreso. Cada ejercicio resuelto representa un paso adelante en tu dominio de Python.

¡Disfruta del proceso de aprendizaje y buena suerte con los ejercicios!

Apéndice A: Glosario de Términos

🧭 Navegación:

Este glosario contiene definiciones de los términos técnicos utilizados a lo largo del libro. Está organizado alfabéticamente para facilitar su consulta.

A

Algoritmo

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.

API (Application Programming Interface)

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.

Argumento

Valor que se pasa a una función cuando es llamada.

Ejemplo: En print("Hola"), "Hola" es un argumento pasado a la función print().

B

Biblioteca (Library)

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.

Booleano

Tipo de dato que puede tener dos valores: True (verdadero) o False (falso).

Ejemplo: es_mayor_de_edad = edad >= 18 asigna un valor booleano a la variable.

Bucle (Loop)

Estructura de control que permite ejecutar un bloque de código repetidamente mientras se cumpla una condición.

Ejemplo: Un bucle for que recorre una lista de nombres para saludar a cada persona.

C

Clase

Plantilla para la creación de objetos que define sus propiedades y comportamientos.

Ejemplo: Una clase Coche podría definir propiedades como color y modelo, y métodos como arrancar() y frenar().

Comentario

Texto en el código fuente que es ignorado por el intérprete y sirve para documentar el código.

Ejemplo: # Este es un comentario en Python o """Este es un comentario de múltiples líneas""".

Compilador

Programa que traduce código escrito en un lenguaje de programación a otro lenguaje, generalmente código máquina.

Ejemplo: El compilador de C++ traduce el código fuente a código máquina ejecutable.

Condicional

Estructura de control que permite ejecutar diferentes bloques de código según se cumpla o no una condición.

Ejemplo: Una estructura if-else que muestra diferentes mensajes según la edad del usuario.

D

Depuración (Debugging)

Proceso de identificar y corregir errores en el código.

Ejemplo: Usar print() para mostrar valores de variables en diferentes puntos del código para encontrar dónde ocurre un error.

Diccionario

Estructura de datos que almacena pares clave-valor, donde cada clave es única.

Ejemplo: persona = {"nombre": "Ana", "edad": 25, "ciudad": "Madrid"}.

E

Encapsulamiento

Principio de la programación orientada a objetos que consiste en ocultar los detalles de implementación de un objeto y exponer solo lo necesario.

Ejemplo: Usar métodos getter y setter para acceder a atributos privados de una clase.

Excepción

Evento que ocurre durante la ejecución de un programa que interrumpe el flujo normal de instrucciones.

Ejemplo: Una ZeroDivisionError ocurre cuando se intenta dividir por cero.

Expresión

Combinación de valores, variables y operadores que se evalúa para producir un resultado.

Ejemplo: 2 * (3 + 4) es una expresión que se evalúa a 14.

F

Función

Bloque de código reutilizable que realiza una tarea específica.

Ejemplo: Una función calcular_area_circulo(radio) que devuelve el área de un círculo dado su radio.

H

Herencia

Mecanismo de la programación orientada a objetos que permite que una clase adquiera propiedades y métodos de otra clase.

Ejemplo: Una clase Coche que hereda de una clase Vehiculo.

I

IDE (Integrated Development Environment)

Aplicación que proporciona facilidades integrales para el desarrollo de software, como editor de código, depurador y compilador.

Ejemplo: Visual Studio Code, PyCharm y Jupyter Notebook son IDEs populares para Python.

Indentación

Espacios o tabulaciones al principio de una línea de código que indican la estructura y jerarquía del código.

Ejemplo: En Python, la indentación se usa para definir bloques de código dentro de funciones, bucles y condicionales.

Intérprete

Programa que ejecuta instrucciones escritas en un lenguaje de programación sin necesidad de compilarlas previamente.

Ejemplo: Python utiliza un intérprete para ejecutar código línea por línea.

Iteración

Proceso de repetir un bloque de código múltiples veces.

Ejemplo: Recorrer una lista con un bucle for es un proceso de iteración.

L

Lista

Estructura de datos que almacena una colección ordenada de elementos.

Ejemplo: numeros = [1, 2, 3, 4, 5].

M

Método

Función que está asociada a un objeto o clase.

Ejemplo: El método append() de una lista añade un elemento al final de la lista.

Módulo

Archivo que contiene definiciones y declaraciones de Python que pueden ser importadas para su uso en otros programas.

Ejemplo: El módulo math contiene funciones matemáticas como sqrt() y sin().

O

Objeto

Instancia de una clase que encapsula datos y comportamientos.

Ejemplo: Si Coche es una clase, mi_coche = Coche("rojo", "Toyota") crea un objeto de esa clase.

Operador

Símbolo que realiza operaciones sobre variables y valores.

Ejemplo: +, -, *, /, ==, and, or son operadores en Python.

P

Parámetro

Variable utilizada en la definición de una función para recibir valores cuando la función es llamada.

Ejemplo: En def saludar(nombre):, nombre es un parámetro.

Paquete

Directorio que contiene módulos de Python y un archivo especial __init__.py.

Ejemplo: NumPy y Pandas son paquetes populares de Python para análisis de datos.

PEP 8

Guía de estilo para el código Python que proporciona convenciones para escribir código legible.

Ejemplo: Según PEP 8, las líneas de código no deberían exceder los 79 caracteres.

Polimorfismo

Capacidad de diferentes clases de responder al mismo método de diferentes maneras.

Ejemplo: Diferentes clases que implementan un método hablar() pero producen sonidos diferentes.

R

Recursión

Técnica de programación donde una función se llama a sí misma.

Ejemplo: Una función factorial que se llama a sí misma con valores decrecientes.

Refactorización

Proceso de reestructurar el código existente sin cambiar su comportamiento externo.

Ejemplo: Extraer código repetido a una función para mejorar la mantenibilidad.

S

Script

Programa escrito en un lenguaje interpretado como Python, generalmente contenido en un solo archivo.

Ejemplo: Un archivo automatizar_backup.py que realiza copias de seguridad automáticas.

String (Cadena de texto)

Secuencia de caracteres.

Ejemplo: "Hola, mundo!" es una cadena de texto.

T

Tupla

Estructura de datos similar a una lista pero inmutable (no se puede modificar después de su creación).

Ejemplo: coordenadas = (10, 20).

Tipo de dato

Clasificación que determina qué valores puede tener una variable y qué operaciones se pueden realizar con ella.

Ejemplo: Enteros, flotantes, cadenas, booleanos, listas, diccionarios son tipos de datos en Python.

V

Variable

Nombre que se refiere a un valor almacenado en la memoria del ordenador.

Ejemplo: edad = 25 asigna el valor 25 a la variable edad.

Virtual Environment (Entorno Virtual)

Herramienta que ayuda a mantener las dependencias requeridas por diferentes proyectos en entornos aislados.

Ejemplo: Crear un entorno virtual con venv para instalar paquetes específicos para un proyecto sin afectar a otros proyectos.

Apéndice B: Recursos Adicionales

¡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.

📚 Documentación Oficial y Fundamental

🐍 Python.org - La fuente oficial

  • URL: https://www.python.org/
  • ¿Qué encontrarás?: Documentación oficial, tutoriales, y noticias sobre Python
  • Por qué es importante: Es la referencia autoritativa para todo lo relacionado con Python
  • Consejo: Marca la sección “Python Tutorial” como favorita

📜 Python Documentation

  • URL: https://docs.python.org/3/
  • ¿Qué encontrarás?: Documentación completa de la biblioteca estándar
  • Por qué es importante: Cuando necesites detalles específicos sobre funciones y módulos
  • Consejo: Usa la función de búsqueda para encontrar rápidamente lo que necesitas

🔍 PEP 8 - Style Guide for Python Code

  • URL: https://pep8.org/
  • ¿Qué encontrarás?: Las convenciones oficiales de estilo para escribir código Python
  • Por qué es importante: Te ayudará a escribir código que otros programadores puedan leer fácilmente
  • Consejo: No necesitas memorizarlo todo, pero consulta cuando tengas dudas sobre formato

🌐 Plataformas de Aprendizaje Interactivo

💻 Codecademy Python Course

  • URL: https://www.codecademy.com/learn/learn-python-3
  • Tipo: Curso interactivo gratuito/premium
  • Nivel: Principiante a intermedio
  • Fortaleza: Ejercicios prácticos paso a paso
  • Ideal para: Reforzar conceptos con práctica dirigida

🎮 Python Challenge

  • URL: http://www.pythonchallenge.com/
  • Tipo: Acertijos de programación
  • Nivel: Intermedio a avanzado
  • Fortaleza: Problemas creativos que te hacen pensar fuera de la caja
  • Ideal para: Cuando quieras desafiarte con puzzles divertidos

🚀 LeetCode

  • URL: https://leetcode.com/
  • Tipo: Plataforma de práctica de algoritmos
  • Nivel: Todos los niveles
  • Fortaleza: Preparación para entrevistas técnicas
  • Ideal para: Mejorar habilidades de resolución de problemas

🏆 HackerRank

  • URL: https://www.hackerrank.com/domains/python
  • Tipo: Desafíos de programación
  • Nivel: Principiante a experto
  • Fortaleza: Categorías específicas de Python con certificaciones
  • Ideal para: Practicar temas específicos y obtener reconocimientos

📺 Canales de YouTube en Español

🎥 Canales Recomendados para Python

MoureDev by Brais Moure

  • URL: https://www.youtube.com/@mouredev
  • Especialidad: Programación general y Python
  • Estilo: Tutoriales claros y proyectos prácticos
  • Ideal para: Seguir aprendiendo con proyectos reales

Dot CSV

  • URL: https://www.youtube.com/@DotCSV
  • Especialidad: Machine Learning y Data Science con Python
  • Estilo: Explicaciones accesibles de temas complejos
  • Ideal para: Cuando quieras explorar inteligencia artificial

Fazt

  • URL: https://www.youtube.com/@FaztTech
  • Especialidad: Desarrollo web y programación en general
  • Estilo: Tutoriales step-by-step
  • Ideal para: Aprender desarrollo web con Python

📚 Libros Recomendados

📖 Para Consolidar Fundamentos

“Python Crash Course” - Eric Matthes

  • Nivel: Principiante
  • Fortaleza: Excelente para solidificar conceptos básicos
  • Incluye: Proyectos prácticos como juegos y aplicaciones web
  • Disponible en: Inglés (con traducciones parciales)

“Automate the Boring Stuff with Python” - Al Sweigart

  • Nivel: Principiante
  • Fortaleza: Enfocado en automatización práctica
  • Incluye: Scripts útiles para tareas del día a día
  • Disponible: Gratis en línea en https://automatetheboringstuff.com/

📗 Para Nivel Intermedio

“Effective Python” - Brett Slatkin

  • Nivel: Intermedio
  • Fortaleza: 90 mejores prácticas para escribir mejor código Python
  • Ideal para: Cuando ya domines lo básico y quieras profesionalizarte

“Python Tricks” - Dan Bader

  • Nivel: Intermedio
  • Fortaleza: Consejos y trucos para escribir código más elegante
  • Ideal para: Aprender patrones avanzados de Python

🚀 Frameworks y Librerías Esenciales

🌐 Desarrollo Web

Django

  • URL: https://www.djangoproject.com/
  • Qué es: Framework web completo para aplicaciones robustas
  • Ideal para: Proyectos web grandes y complejos
  • Aprende si: Quieres crear sitios web profesionales

Flask

  • URL: https://flask.palletsprojects.com/
  • Qué es: Microframework web ligero y flexible
  • Ideal para: Proyectos web pequeños y APIs
  • Aprende si: Prefieres simplicidad y control total

FastAPI

  • URL: https://fastapi.tiangolo.com/
  • Qué es: Framework moderno para crear APIs rápidas
  • Ideal para: APIs modernas con documentación automática
  • Aprende si: Quieres crear servicios web de última generación

📊 Data Science y Análisis

Pandas

  • URL: https://pandas.pydata.org/
  • Qué es: Biblioteca para manipulación y análisis de datos
  • Ideal para: Trabajar con tablas y datasets
  • Aprende si: Te interesa el análisis de datos

NumPy

  • URL: https://numpy.org/
  • Qué es: Biblioteca para cómputo numérico
  • Ideal para: Operaciones matemáticas complejas
  • Aprende si: Quieres trabajar con matrices y arrays

Matplotlib / Seaborn

  • URLs: https://matplotlib.org/ | https://seaborn.pydata.org/
  • Qué son: Bibliotecas para crear gráficos y visualizaciones
  • Ideal para: Crear gráficos y dashboards
  • Aprende si: Quieres visualizar datos de manera profesional

🤖 Machine Learning e IA

Scikit-learn

  • URL: https://scikit-learn.org/
  • Qué es: Biblioteca de machine learning
  • Ideal para: Algoritmos de ML tradicionales
  • Aprende si: Quieres iniciarte en inteligencia artificial

TensorFlow / PyTorch

  • URLs: https://www.tensorflow.org/ | https://pytorch.org/
  • Qué son: Frameworks para deep learning
  • Ideal para: Redes neuronales y AI avanzada
  • Aprende si: Quieres especializarte en deep learning

🛠️ Herramientas de Desarrollo

📝 Editores y IDEs Recomendados

Visual Studio Code

  • URL: https://code.visualstudio.com/
  • Tipo: Editor gratuito
  • Fortalezas: Extensiones, debugging, Git integrado
  • Extensiones clave: Python, Pylance, Python Docstring Generator

PyCharm

  • URL: https://www.jetbrains.com/pycharm/
  • Tipo: IDE especializado (versión gratuita disponible)
  • Fortalezas: Refactoring avanzado, debugging potente
  • Ideal para: Proyectos grandes y desarrollo profesional

Jupyter Notebook

  • URL: https://jupyter.org/
  • Tipo: Entorno interactivo
  • Fortalezas: Ideal para experimentación y data science
  • Ideal para: Análisis de datos y prototipado rápido

📆 Control de Versiones

Git y GitHub

  • URLs: https://git-scm.com/ | https://github.com/
  • Qué son: Sistema de control de versiones y plataforma de código
  • Por qué son esenciales: Todo programador profesional debe conocerlos
  • Aprende: Comandos básicos de Git y cómo usar GitHub

🌍 Comunidades y Foros

💬 Comunidades en Español

Python España

  • URL: https://www.python-spain.es/
  • Qué es: Asociación oficial de Python en España
  • Incluye: Eventos, meetups, conferencias
  • Ideal para: Conectar con la comunidad local

Discord de programación en español

  • Busca servidores como “Programadores”, “Python en Español”
  • Ideal para: Chat en tiempo real y ayuda rápida

🌐 Comunidades Internacionales

Stack Overflow

  • URL: https://stackoverflow.com/questions/tagged/python
  • Qué es: Plataforma de preguntas y respuestas
  • Cómo usar: Busca antes de preguntar, se específico en tus dudas
  • Consejos: Lee las guías para hacer buenas preguntas

Reddit - r/Python

  • URL: https://www.reddit.com/r/Python/
  • Qué es: Comunidad activa de desarrolladores Python
  • Incluye: Noticias, proyectos, discusiones
  • Ideal para: Mantenerte al día con tendencias

Real Python

  • URL: https://realpython.com/
  • Qué es: Plataforma de tutoriales y artículos
  • Fortaleza: Contenido de alta calidad para todos los niveles
  • Ideal para: Aprendizaje estructurado y proyectos prácticos

💰 Recursos Gratuitos Especiales

🎓 Cursos Universitarios Gratuitos

MIT OpenCourseWare

  • URL: https://ocw.mit.edu/
  • Buscar: “Introduction to Computer Science and Programming in Python”
  • Nivel: Universitario
  • Incluye: Videos, ejercicios, exámenes

Harvard CS50

  • URL: https://cs50.harvard.edu/python/
  • Qué es: Curso introductorio de Harvard
  • Fortaleza: Producción profesional y ejercicios desafiantes
  • Disponible: Completamente gratis en línea

📚 Bibliotecas de Código

GitHub Awesome Python

  • URL: https://github.com/vinta/awesome-python
  • Qué es: Lista curada de librerías y recursos Python
  • Ideal para: Descubrir nuevas herramientas y librerías

Python Package Index (PyPI)

  • URL: https://pypi.org/
  • Qué es: Repositorio oficial de paquetes Python
  • Cómo usar: pip install nombre_paquete
  • Consejos: Lee la documentación antes de instalar

🚀 Plan de Continuación Sugerido

📅 Primeras 4 Semanas

  1. Semana 1-2: Refuerza conceptos con ejercicios de HackerRank
  2. Semana 3: Comienza un proyecto personal pequeño
  3. Semana 4: Aprende Git y sube tu proyecto a GitHub

📅 Siguientes 2 Meses

  1. Mes 1: Elige una especialización (web, data science, automatización)
  2. Mes 2: Aprende un framework relacionado (Django/Flask, Pandas, etc.)

📅 A Largo Plazo (6 meses+)

  1. Contribuye a proyectos open source
  2. Participa en comunidades locales
  3. Considera certificaciones profesionales
  4. Busca oportunidades de prácticas o trabajo

💼 Oportunidades Profesionales

👼 Roles que puedes aspirar con Python

  • Desarrollador Backend: Crear APIs y servicios web
  • Data Analyst/Scientist: Análisis de datos e insights de negocio
  • DevOps Engineer: Automatización de infraestructura
  • QA Automation Engineer: Automatización de pruebas
  • Machine Learning Engineer: Implementar modelos de IA
  • Full-Stack Developer: Desarrollo web completo

📊 Sectores con alta demanda

  • Fintech: Tecnología financiera
  • HealthTech: Tecnología médica
  • E-commerce: Comercio electrónico
  • EdTech: Tecnología educativa
  • Consulting: Consultoría tecnológica

✨ Mensaje Final

🎉 ¡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.

Apéndice C: Soluciones a Ejercicios

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.

📚 Capítulo 4: Variables y Tipos de Datos

🎯 Ejercicio 1: Creando tu almacén personal

Solución completa:

# ================================
# MI ALMACÉN PERSONAL - SOLUCIÓN COMPLETA
# ================================

# Información básica
nombre = "María González"
edad = 28
altura = 1.68  # metros
te_gusta_programar = True

# Variables adicionales (ejemplos creativos)
ciudad_origen = "Guadalajara"
color_favorito = "azul"
num_hermanos = 2
tiene_mascota = True
nombre_mascota = "Luna"
salario_deseado = 45000.0

# Mostrar información usando f-strings
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 te_gusta_programar else 'No'}")
print(f"Ciudad de origen: {ciudad_origen}")
print(f"Color favorito: {color_favorito}")
print(f"Número de hermanos: {num_hermanos}")
print(f"¿Tengo mascota?: {'Sí' if tiene_mascota else 'No'}")
if tiene_mascota:
    print(f"Nombre de mi mascota: {nombre_mascota}")
print(f"Salario deseado: ${salario_deseado:,.2f}")

# Realizar cálculos
print("\n=== CÁLCULOS ===")
# Cálculo 1: Edad en meses
edad_en_meses = edad * 12
print(f"Mi edad en meses: {edad_en_meses} meses")

# Cálculo 2: Altura en centímetros
altura_en_cm = altura * 100
print(f"Mi altura en centímetros: {altura_en_cm:.1f} cm")

# Cálculo 3: Años hasta jubilación (asumiendo jubilación a los 65)
edad_jubilacion = 65
anos_hasta_jubilacion = edad_jubilacion - edad
print(f"Años hasta jubilación: {anos_hasta_jubilacion} años")

# Cálculo 4: Salario anual vs mensual
salario_mensual = salario_deseado / 12
print(f"Salario mensual deseado: ${salario_mensual:.2f}")

# Mostrar tipos de variables
print("\n=== TIPOS DE VARIABLES ===")
print(f'Variable "nombre" es de tipo: {type(nombre).__name__}')
print(f'Variable "edad" es de tipo: {type(edad).__name__}')
print(f'Variable "altura" es de tipo: {type(altura).__name__}')
print(f'Variable "te_gusta_programar" es de tipo: {type(te_gusta_programar).__name__}')
print(f'Variable "ciudad_origen" es de tipo: {type(ciudad_origen).__name__}')
print(f'Variable "num_hermanos" es de tipo: {type(num_hermanos).__name__}')
print(f'Variable "salario_deseado" es de tipo: {type(salario_deseado).__name__}')

Explicación de la solución:

  1. Variables diversas: Incluí variables de diferentes tipos para demostrar la versatilidad
  2. F-strings avanzados: Uso condicionales dentro de f-strings para mostrar texto dinámico
  3. Cálculos variados: Incluyo operaciones que son útiles en la vida real
  4. Formateo de números: Uso :,.2f para formatear el salario con comas y 2 decimales
  5. Uso de __name__: Para mostrar nombres de tipo más limpios

🎯 Ejercicio 2: Calculadora de IMC

Solución completa:

# ================================
# CALCULADORA DE IMC - SOLUCIÓN COMPLETA
# ================================

# Datos de la persona
nombre = "Carlos Mendoza"
peso = 75.5  # en kilogramos
altura = 1.78  # en metros

# Cálculo del IMC
imc = peso / (altura ** 2)

# Determinación de la categoría con lógica detallada
if imc < 18.5:
    categoria = "Bajo peso"
    recomendacion = "Considera consultar a un nutricionista para un plan de alimentación saludable."
    emoji = "🟡"
elif imc < 25:
    categoria = "Peso normal"
    recomendacion = "¡Excelente! Mantén tus hábitos saludables de alimentación y ejercicio."
    emoji = "🟢"
elif imc < 30:
    categoria = "Sobrepeso"
    recomendacion = "Considera incorporar más ejercicio y revisar tu alimentación."
    emoji = "🟠"
else:
    categoria = "Obesidad"
    recomendacion = "Te recomendamos consultar con un profesional de la salud."
    emoji = "🔴"

# Cálculos adicionales
peso_ideal_min = 18.5 * (altura ** 2)
peso_ideal_max = 24.9 * (altura ** 2)

# Mostrar informe completo
print("=== INFORME COMPLETO 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: {emoji} {categoria}")
print(f"\nRango de peso ideal para tu altura:")
print(f"  Mínimo: {peso_ideal_min:.1f} kg")
print(f"  Máximo: {peso_ideal_max:.1f} kg")

# Análisis personalizado
if peso < peso_ideal_min:
    diferencia = peso_ideal_min - peso
    print(f"\nPara alcanzar el peso mínimo ideal, necesitarías ganar {diferencia:.1f} kg")
elif peso > peso_ideal_max:
    diferencia = peso - peso_ideal_max
    print(f"\nPara alcanzar el peso máximo ideal, necesitarías perder {diferencia:.1f} kg")
else:
    print(f"\n¡Tu peso está dentro del rango ideal!")

print(f"\nRecomendación: {recomendacion}")

# Información educativa adicional
print("\n=== INFORMACIÓN EDUCATIVA ===")
print("Rangos de IMC:")
print("  < 18.5: Bajo peso")
print("  18.5 - 24.9: Peso normal")
print("  25.0 - 29.9: Sobrepeso")
print("  ≥ 30.0: Obesidad")
print("\nNota: El IMC es una herramienta de orientación. Consulta siempre")
print("con profesionales de la salud para evaluaciones completas.")

Explicación de la solución:

  1. Lógica extendida: Además de calcular el IMC, incluyo recomendaciones personalizadas
  2. Cálculos adicionales: Determino el rango de peso ideal para la altura
  3. Feedback personalizado: El programa da consejos específicos según el resultado
  4. Emojis para claridad: Uso colores emoji para visualizar rápidamente el estado
  5. Información educativa: Incluyo los rangos para que el usuario aprenda

🎯 Ejercicio 3: Conversor de unidades

Solución completa:

# ================================
# CONVERSOR DE UNIDADES - SOLUCIÓN COMPLETA
# ================================

# Distancia en kilómetros
distancia_km = 42.195  # Distancia de un maratón

# Factores de conversión (constantes)
KM_A_METROS = 1000
KM_A_CM = 100000
KM_A_MILLAS = 0.621371
KM_A_PIES = 3280.84
KM_A_YARDAS = 1093.61
KM_A_PULGADAS = 39370.1

# Realizar todas las conversiones
metros = distancia_km * KM_A_METROS
centimetros = distancia_km * KM_A_CM
millas = distancia_km * KM_A_MILLAS
pies = distancia_km * KM_A_PIES
yardas = distancia_km * KM_A_YARDAS
pulgadas = distancia_km * KM_A_PULGADAS

# Mostrar resultados con formato elegante
print("🏃 ========================================")
print("   CONVERSOR DE DISTANCIAS")
print("   (Distancia de un Maratón)")
print("========================================")
print(f"Distancia original: {distancia_km} kilómetros")
print("\n📏 EQUIVALENCIAS:")
print(f"   En metros:      {metros:>12,.2f} m")
print(f"   En centímetros: {centimetros:>12,.0f} cm")
print(f"   En millas:      {millas:>12,.2f} mi")
print(f"   En pies:        {pies:>12,.2f} ft")
print(f"   En yardas:      {yardas:>12,.2f} yd")
print(f"   En pulgadas:    {pulgadas:>12,.0f} in")

# Comparaciones interesantes
print("\n🎯 COMPARACIONES INTERESANTES:")
print(f"   Un maratón equivale a correr {pies/5280:.1f} millas")
print(f"   O caminar aproximadamente {int(metros/0.75)} pasos (75cm por paso)")
print(f"   Es como {centimetros/30.48:.0f} reglas de 30cm puestas en fila")

# Función reutilizable
def convertir_distancia(km, unidad="todas"):
    """Convierte kilómetros a diferentes unidades"""
    conversiones = {
        "metros": km * 1000,
        "centimetros": km * 100000,
        "millas": km * 0.621371,
        "pies": km * 3280.84,
        "yardas": km * 1093.61,
        "pulgadas": km * 39370.1
    }
    
    if unidad == "todas":
        return conversiones
    else:
        return conversiones.get(unidad, "Unidad no reconocida")

# Ejemplo de uso de la función
print("\n🔧 EJEMPLO DE FUNCIÓN REUTILIZABLE:")
distancia_test = 5.0
resultado = convertir_distancia(distancia_test, "millas")
print(f"{distancia_test} km = {resultado:.2f} millas")

Explicación de la solución:

  1. Constantes con nombres claros: Uso variables en mayúsculas para las constantes
  2. Formateo avanzado: Uso >12,.2f para alinear números a la derecha con comas
  3. Contexto real: Uso la distancia de un maratón para hacer el ejemplo más interesante
  4. Comparaciones útiles: Añado equivalencias que ayudan a visualizar la distancia
  5. Función reutilizable: Creo una función que se puede usar en otros proyectos

🗗️ Capítulo 5: Operadores Matemáticos

🎯 Ejercicio 1: Calculadora de nómina

Solución completa:

# ================================
# CALCULADORA DE NÓMINA - SOLUCIÓN COMPLETA
# ================================

# Datos del empleado
nombre_empleado = "Ana Martínez"
salario_base = 15000  # pesos mensuales
horas_extra = 10     # horas trabajadas extra
pago_por_hora_extra = 150  # pesos por hora extra
bono_productividad = 2000   # pesos
bono_puntualidad = 500      # pesos adicional

# Deducciones (porcentajes)
impuesto_renta = 0.10    # 10%
seguro_social = 0.0625   # 6.25%
seguro_medico = 500      # pesos fijos
fondo_pension = 0.04     # 4%

# CÁLCULOS DE INGRESOS
# Calcular pago por horas extra
pago_extra = horas_extra * pago_por_hora_extra

# Calcular ingresos brutos
ingresos_brutos = salario_base + pago_extra + bono_productividad + bono_puntualidad

# CÁLCULOS DE DEDUCCIONES
# Deducciones porcentuales (se calculan sobre salario base + extras)
base_para_porcentajes = salario_base + pago_extra

deduccion_impuesto = base_para_porcentajes * impuesto_renta
deduccion_seguro_social = base_para_porcentajes * seguro_social
deduccion_pension = base_para_porcentajes * fondo_pension

# Total de deducciones
total_deducciones = (deduccion_impuesto + deduccion_seguro_social + 
                    seguro_medico + deduccion_pension)

# Salario neto
salario_neto = ingresos_brutos - total_deducciones

# GENERAR RECIBO DE NÓMINA
print("💰 " + "=" * 50)
print("           RECIBO DE NÓMINA")
print("=" * 52)
print(f"Empleado: {nombre_empleado}")
print(f"Fecha: {__import__('datetime').datetime.now().strftime('%d/%m/%Y')}")
print("\n💵 INGRESOS:")
print(f"   Salario base:        ${salario_base:>10,.2f}")
print(f"   Horas extra ({horas_extra}h):    ${pago_extra:>10,.2f}")
print(f"   Bono productividad:  ${bono_productividad:>10,.2f}")
print(f"   Bono puntualidad:    ${bono_puntualidad:>10,.2f}")
print(f"   {'-' * 35}")
print(f"   TOTAL BRUTO:         ${ingresos_brutos:>10,.2f}")

print("\n📉 DEDUCCIONES:")
print(f"   Impuesto renta (10%): ${deduccion_impuesto:>9,.2f}")
print(f"   Seguro social (6.25%): ${deduccion_seguro_social:>8,.2f}")
print(f"   Seguro médico:       ${seguro_medico:>10,.2f}")
print(f"   Fondo pensión (4%):  ${deduccion_pension:>10,.2f}")
print(f"   {'-' * 35}")
print(f"   TOTAL DEDUCCIONES:   ${total_deducciones:>10,.2f}")

print("\n💰 RESUMEN:")
print(f"   SALARIO NETO:        ${salario_neto:>10,.2f}")

# Análisis adicional
porcentaje_deducciones = (total_deducciones / ingresos_brutos) * 100
print(f"\n📊 ANÁLISIS:")
print(f"   Porcentaje deducido:  {porcentaje_deducciones:>10.1f}%")
print(f"   Efectivo a recibir:   {100 - porcentaje_deducciones:>10.1f}%")

# Proyección anual
salario_anual_neto = salario_neto * 12
print(f"\n📅 PROYECCIÓN ANUAL:")
print(f"   Salario neto anual:  ${salario_anual_neto:>11,.2f}")

print("=" * 52)

Explicación de la solución:

  1. Estructura profesional: El código está organizado en secciones claras
  2. Cálculos precisos: Distingo entre deducciones porcentuales y fijas
  3. Formateo profesional: El recibo se ve como uno real
  4. Análisis adicional: Incluyo porcentajes y proyecciones anuales
  5. Documentación clara: Cada sección está bien comentada

🎯 Ejercicio 2: Distribución de productos

Solución completa:

# ================================
# DISTRIBUCIÓN DE PRODUCTOS - SOLUCIÓN COMPLETA
# ================================

productos_totales = 1847
productos_por_caja = 24
peso_por_caja = 5.5  # kg

# Cálculos principales
cajas_completas = productos_totales // productos_por_caja
productos_sueltos = productos_totales % productos_por_caja
peso_total_cajas = cajas_completas * peso_por_caja

# Cálculos adicionales
productos_empacados = cajas_completas * productos_por_caja
eficiencia_empacado = (productos_empacados / productos_totales) * 100

# Si empacamos los productos sueltos en una caja adicional
cajas_totales_necesarias = cajas_completas + (1 if productos_sueltos > 0 else 0)
peso_total_con_caja_parcial = cajas_totales_necesarias * peso_por_caja

print("📦 " + "=" * 50)
print("      ANÁLISIS DE DISTRIBUCIÓN DE PRODUCTOS")
print("=" * 52)
print(f"Productos totales recibidos: {productos_totales:,} unidades")
print(f"Capacidad por caja: {productos_por_caja} unidades")
print(f"Peso por caja: {peso_por_caja} kg")

print("\n📊 RESULTADOS DE EMPACADO:")
print(f"   Cajas completas formadas: {cajas_completas:,} cajas")
print(f"   Productos en cajas:       {productos_empacados:,} unidades")
print(f"   Productos sueltos:        {productos_sueltos} unidades")
print(f"   Eficiencia de empacado:   {eficiencia_empacado:.1f}%")

print("\n⚖️ PESO Y LOGÍSTICA:")
print(f"   Peso de cajas completas:  {peso_total_cajas:.1f} kg")
print(f"   Cajas necesarias total:   {cajas_totales_necesarias} cajas")
print(f"   Peso total estimado:      {peso_total_con_caja_parcial:.1f} kg")

# Análisis de optimización
print("\n🎯 ANÁLISIS DE OPTIMIZACIÓN:")
if productos_sueltos > 0:
    porcentaje_sueltos = (productos_sueltos / productos_por_caja) * 100
    print(f"   La última caja estará {porcentaje_sueltos:.1f}% llena")
    
    if productos_sueltos >= productos_por_caja / 2:
        print(f"   ✅ Recomendación: Empacar en caja adicional")
    else:
        print(f"   ⚠️  Considerar: Los {productos_sueltos} productos sueltos ocupan poco espacio")
else:
    print(f"   ✅ Perfecto: No hay productos sueltos")

# Cálculos de transporte
print("\n🚛 ESTIMACIÓN DE TRANSPORTE:")
camion_capacidad = 1000  # kg
camiones_necesarios = peso_total_con_caja_parcial / camion_capacidad
print(f"   Para camión de {camion_capacidad}kg: {camiones_necesarios:.2f} camiones")
print(f"   Camiones enteros necesarios: {int(camiones_necesarios) + (1 if camiones_necesarios % 1 > 0 else 0)}")

print("=" * 52)

Explicación de la solución:

  1. Uso correcto de operadores: // para cajas completas, % para sobrantes
  2. Análisis completo: No solo respondo las preguntas, sino que agrego insights útiles
  3. Eficiencia de empacado: Calculo qué porcentaje de productos se empaca eficientemente
  4. Recomendaciones prácticas: Doy consejos sobre qué hacer con productos sueltos
  5. Extensión logística: Incluyo cálculos de transporte que serían útiles en la realidad

📚 Capítulo 6: Estructuras de Control

🎯 Ejercicio: Sistema de clasificación de productos

Solución completa:

# ================================
# SISTEMA DE CLASIFICACIÓN DE PRODUCTOS
# ================================

# Simulación de productos del almacén
productos = [
    {"codigo": "LAP001", "nombre": "Laptop Gaming", "precio": 1299.99, "stock": 5},
    {"codigo": "MOU001", "nombre": "Mouse Inalámbrico", "precio": 29.99, "stock": 50},
    {"codigo": "TEC001", "nombre": "Teclado Mecánico", "precio": 89.99, "stock": 25},
    {"codigo": "MON001", "nombre": "Monitor 4K", "precio": 399.99, "stock": 8},
    {"codigo": "AUD001", "nombre": "Auriculares Gaming", "precio": 79.99, "stock": 15},
    {"codigo": "CAM001", "nombre": "Cámara Web HD", "precio": 49.99, "stock": 3},
    {"codigo": "IMP001", "nombre": "Impresora Láser", "precio": 199.99, "stock": 0},
    {"codigo": "TAB001", "nombre": "Tablet 10 pulgadas", "precio": 299.99, "stock": 12}
]

# Contadores para estadísticas
total_productos = len(productos)
productos_premium = 0
productos_economicos = 0
productos_stock_bajo = 0
productos_sin_stock = 0
valor_total_inventario = 0

# Listas para categorización
lista_premium = []
lista_economicos = []
lista_stock_critico = []
lista_sin_stock = []

print("🏪 " + "=" * 60)
print("        SISTEMA DE CLASIFICACIÓN DE PRODUCTOS")
print("=" * 62)

# Procesar cada producto
for producto in productos:
    codigo = producto["codigo"]
    nombre = producto["nombre"]
    precio = producto["precio"]
    stock = producto["stock"]
    
    # Calcular valor del inventario
    valor_producto = precio * stock
    valor_total_inventario += valor_producto
    
    # Clasificación por precio
    if precio >= 200:
        categoria_precio = "Premium"
        productos_premium += 1
        lista_premium.append(producto)
        emoji_precio = "💰"
    elif precio >= 50:
        categoria_precio = "Medio"
        emoji_precio = "🟡"
    else:
        categoria_precio = "Económico"
        productos_economicos += 1
        lista_economicos.append(producto)
        emoji_precio = "🟢"
    
    # Clasificación por stock
    if stock == 0:
        categoria_stock = "Sin stock"
        productos_sin_stock += 1
        lista_sin_stock.append(producto)
        emoji_stock = "🔴"
    elif stock <= 5:
        categoria_stock = "Stock crítico"
        productos_stock_bajo += 1
        lista_stock_critico.append(producto)
        emoji_stock = "🟠"
    elif stock <= 15:
        categoria_stock = "Stock bajo"
        emoji_stock = "🟡"
    else:
        categoria_stock = "Stock normal"
        emoji_stock = "🟢"
    
    # Mostrar información del producto
    print(f"{codigo} | {nombre:<20} | ${precio:>7.2f} | {stock:>3} und")
    print(f"        {emoji_precio} {categoria_precio:<10} | {emoji_stock} {categoria_stock}")
    print(f"        Valor inventario: ${valor_producto:,.2f}")
    print("-" * 62)

# Mostrar estadísticas generales
print("\n📊 ESTADÍSTICAS GENERALES:")
print(f"   Total de productos:     {total_productos}")
print(f"   Productos premium:      {productos_premium} ({productos_premium/total_productos*100:.1f}%)")
print(f"   Productos económicos:   {productos_economicos} ({productos_economicos/total_productos*100:.1f}%)")
print(f"   Con stock crítico:      {productos_stock_bajo} ({productos_stock_bajo/total_productos*100:.1f}%)")
print(f"   Sin stock:              {productos_sin_stock} ({productos_sin_stock/total_productos*100:.1f}%)")
print(f"   Valor total inventario: ${valor_total_inventario:,.2f}")

# Alertas importantes
print("\n⚠️  ALERTAS IMPORTANTES:")
if productos_sin_stock > 0:
    print(f"   🔴 {productos_sin_stock} productos SIN STOCK:")
    for prod in lista_sin_stock:
        print(f"      - {prod['nombre']} ({prod['codigo']})")

if productos_stock_bajo > 0:
    print(f"   🟠 {productos_stock_bajo} productos con STOCK CRÍTICO:")
    for prod in lista_stock_critico:
        print(f"      - {prod['nombre']} ({prod['codigo']}): {prod['stock']} unidades")

# Recomendaciones
print("\n💡 RECOMENDACIONES:")
if productos_sin_stock > 0:
    print("   1. Reabastecer inmediatamente productos sin stock")
if productos_stock_bajo > 0:
    print("   2. Programar reabastecimiento para productos con stock crítico")
if productos_premium > productos_economicos:
    print("   3. Considerar promoción de productos premium")
else:
    print("   3. Considerar expandir línea de productos premium")

print("=" * 62)

Explicación de la solución:

  1. Estructuras anidadas: Uso if/elif/else para múltiples categorizaciones
  2. Bucles con lógica compleja: Cada iteración realiza múltiples cálculos y clasificaciones
  3. Contadores y acumuladores: Mantengo estadísticas durante el procesamiento
  4. Listas de categorización: Guardo productos en listas según sus características
  5. Sistema de alertas: Genero recomendaciones basadas en los datos

✨ Notas Importantes

🎯 Cómo usar este apéndice efectivamente

  1. Antes de ver la solución: Intenta resolver el ejercicio completamente
  2. Compara tu solución: Ve las diferencias entre tu enfoque y el propuesto
  3. Entiende el razonamiento: Lee las explicaciones, no solo el código
  4. Experimenta: Modifica las soluciones para entender mejor cómo funcionan
  5. Aplica conceptos: Usa las técnicas aprendidas en tus propios proyectos

📝 Patrones comunes en las soluciones

  • Validación de datos: Siempre verifico que los datos sean válidos
  • Formateo profesional: Uso formateo avanzado para salidas legibles
  • Cálculos adicionales: Añado análisis que serían útiles en la vida real
  • Manejo de errores: Considero casos especiales y situaciones límite
  • Documentación: Comento el código para explicar decisiones no obvias

🚀 Siguientes pasos

Despues de revisar estas soluciones:

  1. Intenta variaciones: Modifica los ejercicios con diferentes datos
  2. Combina conceptos: Usa técnicas de varios ejercicios juntas
  3. Crea tus propios ejercicios: Diseña problemas similares
  4. Busca optimizaciones: ¿Puedes hacer el código más eficiente?
  5. 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.

Índice Temático

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.

A

Analogías del Almacén

APIs y Servicios Web

Archivos

Automatización

B

Booleanos

Bucles

C

Cadenas de Texto (Strings)

Clases y Objetos

Comentarios

Comparación

Comprensión de Listas

Conjuntos (Sets)

CSV

D

Diccionarios

Datos

Depuración

E

Entorno de Desarrollo

Errores

Estructuras de Control

Estructuras de Datos

F

f-strings

Funciones

Flujo de Control

G

Git

GUI

H

Herencia

Herramientas de Desarrollo

I

import

Índices

input()

Instalación

Iteración

J

JSON

L

Listas

Loops

M

Métodos

Módulos

Mutabilidad

N

Números

None

O

Objetos

Operadores

P

Parámetros

print()

Programación Orientada a Objetos

Proyectos

R

range()

return

S

Strings

Slicing

Sets

T

try/except

Tuplas

Tipos de Datos

V

Variables

Virtual Environment

W

while

with

📚 Índice por Funciones y Métodos

Funciones Built-in

Métodos de Listas

Métodos de Diccionarios

Métodos de Strings

🔧 Índice por Herramientas y Librerías

Módulos Estándar

Herramientas de Desarrollo

🎯 Índice por Casos de Uso

Gestión de Inventario

Análisis de Datos

Automatización de Tareas

Interfaces de Usuario

📖 Cómo Usar Este Índice

Para Encontrar Conceptos Específicos

  1. Busca el término en orden alfabético
  2. Sigue el enlace al capítulo correspondiente
  3. Usa Ctrl+F en tu navegador para buscar en la página

Para Repasar Temas

  1. Identifica el área de interés (funciones, estructuras de datos, etc.)
  2. Revisa todos los temas relacionados
  3. Practica con los ejercicios de cada capítulo

Para Resolver Problemas

  1. Identifica qué tipo de problema estás resolviendo
  2. Busca en “Índice por Casos de Uso”
  3. Revisa ejemplos similares en el libro

Para Aprender Progresivamente

  1. Sigue el orden de los capítulos
  2. Usa este índice para conectar conceptos
  3. Refuerza con ejercicios prácticos

💡 Tip: Guarda esta página como favorito y úsala como referencia rápida durante tu aprendizaje y desarrollo de proyectos.