diff --git a/01_intro/ejercicio_guiado/calculadora_v1.py b/01_intro/ejercicio_guiado/calculadora_v1.py new file mode 100644 index 0000000..d597d8e --- /dev/null +++ b/01_intro/ejercicio_guiado/calculadora_v1.py @@ -0,0 +1,48 @@ +""" +Calculadora v1 - Suma básica +============================= + +En esta primera versión crearás una calculadora simple que solo suma dos números. + +Conceptos aplicados: +- Variables +- input() para leer datos del usuario +- print() para mostrar resultados +- Conversión de tipos (float()) + +Instrucciones: +1. Pide al usuario el primer número +2. Pide al usuario el segundo número +3. Convierte ambos a números decimales (float) +4. Suma los dos números +5. Muestra el resultado +""" + +# TODO 1: Pide el primer número al usuario +# Pista: usa input() y guarda el valor en una variable +primer_numero = input("Ingresa el primer número: ") + + +# TODO 2: Pide el segundo número al usuario +segundo_numero = input("Ingresa el segundo número: ") + + +# TODO 3: Convierte los strings a números decimales +# Pista: usa float() para permitir decimales (ej: 3.5) +num1 = float(primer_numero) +num2 = float(segundo_numero) + + +# TODO 4: Realiza la suma +resultado = num1 + num2 + + +# TODO 5: Muestra el resultado +print("El resultado es: ", resultado) + + +# ¡Ya está! Ejecuta el programa y prueba con diferentes números +# Ejemplos para probar: +# - 5 y 3 → debe dar 8 +# - -2 y 7 → debe dar 5 +# - 3.5 y 2.5 → debe dar 6.0 \ No newline at end of file diff --git a/02_estructuras/ejercicio_guiado/calculadora_v2.py b/02_estructuras/ejercicio_guiado/calculadora_v2.py new file mode 100644 index 0000000..68fc8da --- /dev/null +++ b/02_estructuras/ejercicio_guiado/calculadora_v2.py @@ -0,0 +1,65 @@ +""" +Calculadora v2 - Cuatro operaciones básicas +============================================ + +En esta segunda versión expandirás la calculadora para que pueda realizar +las cuatro operaciones básicas: suma, resta, multiplicación y división. + +Conceptos aplicados: +- Operadores aritméticos (+, -, *, /) +- Condicionales if/elif/else +- F-strings para formateo profesional +- Manejo básico de tipos numéricos + +Instrucciones: +1. Pide dos números al usuario +2. Pregunta qué operación desea realizar +3. Usa if/elif/else para realizar la operación correspondiente +4. Muestra el resultado con f-strings formateados +""" + +# TODO 1: Pide el primer número al usuario y conviértelo a float +num1 = float(input("Ingresa el primer número: ")) + + +# TODO 2: Pide el segundo número al usuario y conviértelo a float +num2 = float(input("Ingresa el segundo número: ")) + + +# TODO 3: Pregunta qué operación desea realizar +# Pista: input("¿Qué operación deseas realizar? (+, -, *, /): ") +operacion = input("¿Qué operación deseas realizar? (+, -, *, /): ") + + +# TODO 4: Realiza la operación correspondiente usando if/elif/else +# Pista: Compara la variable 'operacion' con "+", "-", "*", "/" + +if operacion == "+": + resultado = num1 + num2 +elif operacion == "-": + resultado = num1 - num2 +elif operacion == "*": + resultado = num1 * num2 +elif operacion == "/": + resultado = num1 / num2 +else: + print("❌ Operación no válida") + + +# TODO 5: Muestra el resultado usando f-strings +print(f"El resultado de {num1} {operacion} {num2} = {resultado:.2f}") + + + + +# ¡Perfecto! Ahora tu calculadora puede hacer las 4 operaciones básicas +# +# Ejemplos para probar: +# - 10 + 5 → debe dar 15.00 +# - 10 - 3 → debe dar 7.00 +# - 4 * 5 → debe dar 20.00 +# - 10 / 3 → debe dar 3.33 +# - 10 % 3 → (si pruebas una operación no válida, debe mostrar mensaje de error) +# +# 💡 Nota: Si intentas dividir por cero (10 / 0), Python mostrará un error. +# Esto lo arreglaremos en la v3 con validación de entrada. \ No newline at end of file diff --git a/03_control_flujo/ejercicio_guiado/GUIA.md b/03_control_flujo/ejercicio_guiado/GUIA.md new file mode 100644 index 0000000..5795ef7 --- /dev/null +++ b/03_control_flujo/ejercicio_guiado/GUIA.md @@ -0,0 +1,109 @@ +# 🎯 La Calculadora que Crece - Versión 3 + +¡Tu calculadora sigue mejorando! En esta versión añadirás un menú interactivo con bucles, validación de entrada y control de errores. + +## 🎓 Qué aprenderás + +En este módulo estamos aprendiendo: +- ✅ Condicionales if/elif/else complejos +- ✅ Bucle while para repetir acciones +- ✅ break y continue para controlar el flujo +- ✅ Validación de entrada del usuario + +## 🎯 Objetivo de la v3 + +Mejorar tu calculadora para que: +1. Muestre un menú numerado con las opciones +2. Permita realizar múltiples operaciones sin reiniciar el programa +3. Valide que el usuario ingrese opciones válidas +4. Controle la división por cero +5. Tenga una opción para salir + +**Ejemplo de ejecución:** +``` +=== CALCULADORA === +1. Sumar +2. Restar +3. Multiplicar +4. Dividir +5. Salir + +Elige una opción: 1 +Primer número: 5 +Segundo número: 3 +✅ 5.0 + 3.0 = 8.0 + +=== CALCULADORA === +1. Sumar +2. Restar +3. Multiplicar +4. Dividir +5. Salir + +Elige una opción: 4 +Primer número: 10 +Segundo número: 0 +❌ Error: No se puede dividir por cero + +=== CALCULADORA === +... +Elige una opción: 5 +¡Hasta pronto! 👋 +``` + +## 📝 Instrucciones + +1. Abre el archivo `calculadora_v3.py` +2. Encontrarás comentarios con instrucciones paso a paso +3. Completa el código siguiendo las pistas +4. Ejecuta y prueba tu programa con varias operaciones seguidas + +## ✅ Cómo probar tu código + +Ejecuta el programa: +```powershell +python calculadora_v3.py +``` + +Casos de prueba: +- ✅ Realiza varias operaciones sin salir del programa +- ✅ Intenta una opción no válida (ej: 7) → debe mostrar error y volver al menú +- ✅ Intenta dividir por cero → debe mostrar error y volver al menú +- ✅ Elige opción 5 → debe salir del programa + +## 🆚 Cambios respecto a la v2 + +| Aspecto | v2 | v3 | +|---------|----|----| +| Ejecución | Una sola operación | Múltiples operaciones | +| Menú | No tiene | Menú numerado | +| Validación | No valida | Valida opciones y división por cero | +| Bucle | No | `while True` con `break` | +| UX | Básica | Mensajes con emojis y formato | + +## 🚀 ¿Terminaste? + +¡Genial! Tu calculadora ahora es mucho más usable: +```bash +git add 03_control_flujo/ejercicio_guiado/calculadora_v3.py +git commit -m "feat: completar calculadora v3 - menú interactivo con validación" +``` + +En el módulo 04 refactorizarás todo el código en funciones para que sea más limpio y mantenible. + +## 💡 Tips + +- `while True:` crea un bucle infinito que solo termina con `break` +- `continue` salta al inicio del bucle sin ejecutar el resto del código +- Usa `if opcion not in ["1", "2", "3", "4", "5"]:` para validar opciones +- Para división por cero: `if num2 == 0 and opcion == "4":` + +## 📚 Recursos + +- Consulta [`cheatsheets/03_control_flujo.md`](../../cheatsheets/03_control_flujo.md) para bucles y control +- Revisa ejemplos de while con break/continue + +--- + +**Versión anterior**: [`02_estructuras/ejercicio_guiado/GUIA.md`](../../02_estructuras/ejercicio_guiado/GUIA.md) +**Siguiente versión**: [`04_funciones/ejercicio_guiado/GUIA.md`](../../04_funciones/ejercicio_guiado/GUIA.md) - Organizarás el código en funciones \ No newline at end of file diff --git a/03_control_flujo/ejercicio_guiado/calculadora_v3.py b/03_control_flujo/ejercicio_guiado/calculadora_v3.py new file mode 100644 index 0000000..721cdd4 --- /dev/null +++ b/03_control_flujo/ejercicio_guiado/calculadora_v3.py @@ -0,0 +1,85 @@ +""" +Calculadora v3 - Menú interactivo con validación +================================================= + +En esta tercera versión añadirás un menú que se repite hasta que el usuario +decida salir, validación de entrada y control de errores (división por cero). + +Conceptos aplicados: +- Bucle while para repetir el menú +- break para salir del bucle +- continue para volver al inicio del bucle +- Validación de entrada del usuario +- Control de división por cero + +Instrucciones: +1. Crea un bucle while infinito +2. Muestra un menú numerado +3. Valida que la opción sea válida +4. Pide los números y realiza la operación +5. Controla la división por cero +6. Permite salir con la opción 5 +""" + +while True: + + + print("\n=== CALCULADORA ===") + print("1. Sumar") + print("2. Restar") + print("3. Multiplicar") + print("4. Dividir") + print("5. Salir") + + + opcion = input("\nElige una opción: ") + + if opcion == "5": + print("¡Hasta pronto! 👋") + break + + + if opcion not in ["1", "2", "3", "4"]: + print("❌ Opción no válida. Intenta de nuevo.") + continue + + + num1 = float(input("Primer número: ")) + num2 = float(input("Segundo número: ")) + + + if opcion == "4" and num2 == 0: + print("❌ Error: No se puede dividir por cero") + continue + + + if opcion == "1": + resultado = num1 + num2 + simbolo = "+" + elif opcion == "2": + resultado = num1 - num2 + simbolo = "-" + elif opcion == "3": + resultado = num1 * num2 + simbolo = "*" + elif opcion == "4": + resultado = num1 / num2 + simbolo = "/" + + + print(f"✅ {num1} {simbolo} {num2} = {resultado:.2f}") + + +# ¡Excelente trabajo! Ahora tienes una calculadora interactiva que: +# - Se repite hasta que el usuario quiera salir +# - Valida las opciones ingresadas +# - Controla la división por cero +# - Muestra mensajes claros con emojis +# +# Prueba estos casos: +# 1. Realiza varias operaciones seguidas sin salir +# 2. Intenta una opción inválida (ej: 9) → debe mostrar error y volver al menú +# 3. Intenta dividir por cero → debe mostrar error y volver al menú +# 4. Sal con la opción 5 → el programa debe terminar correctamente +# +# 💡 En la v4 organizarás todo este código en funciones para que sea más limpio \ No newline at end of file diff --git a/04_funciones/ejercicio_guiado/GUIA.md b/04_funciones/ejercicio_guiado/GUIA.md new file mode 100644 index 0000000..3e925ca --- /dev/null +++ b/04_funciones/ejercicio_guiado/GUIA.md @@ -0,0 +1,98 @@ +# 🎯 La Calculadora que Crece - Versión 4 + +¡Hora de la gran refactorización! En esta versión organizarás todo tu código en funciones reutilizables, siguiendo las mejores prácticas de programación. + +## 🎓 Qué aprenderás + +En este módulo estamos aprendiendo: +- ✅ Definir funciones con `def` +- ✅ Parámetros y valores de retorno +- ✅ Docstrings para documentar código +- ✅ Organización modular del código +- ✅ Patrón `if __name__ == "__main__"` + +## 🎯 Objetivo de la v4 + +Refactorizar la calculadora para que: +1. Cada operación sea una función separada +2. El menú esté en su propia función +3. La lógica principal esté en una función `main()` +4. Cada función tenga su docstring +5. El código sea más fácil de leer, probar y mantener + +**Estructura del código:** +```python +def sumar(a, b): + """Suma dos números.""" + return a + b + +def mostrar_menu(): + """Muestra el menú de opciones.""" + # ... + +def obtener_numeros(): + """Pide dos números al usuario.""" + # ... + +def main(): + """Función principal.""" + while True: + # Lógica del programa + +if __name__ == "__main__": + main() +``` + +## 📝 Instrucciones + +1. Abre el archivo `calculadora_v4.py` +2. Encontrarás la estructura base con TODOs +3. Completa cada función siguiendo las instrucciones +4. Nota cómo el código es más limpio y organizado + +## ✅ Cómo probar tu código + +Ejecuta el programa: +```powershell +python calculadora_v4.py +``` + +La funcionalidad debe ser idéntica a la v3, pero el código está mucho mejor organizado. + +## 🆚 Cambios respecto a la v3 + +| Aspecto | v3 | v4 | +|---------|----|----| +| Organización | Todo en secuencia | Funciones modulares | +| Operaciones | Código repetido | Funciones `sumar()`, `restar()`, etc. | +| Menú | Mezclado con lógica | Función `mostrar_menu()` | +| Lectura números | Repetido | Función `obtener_numeros()` | +| Documentación | Comentarios sueltos | Docstrings en cada función | +| Reutilización | Difícil | Fácil importar y usar | + +## 🚀 ¿Terminaste? + +¡Perfecto! Has dado un gran paso hacia código profesional: +```bash +git add 04_funciones/ejercicio_guiado/calculadora_v4.py +git commit -m "refactor: calculadora v4 - código modular con funciones" +``` + +En el módulo 05 añadirás un historial de operaciones usando listas y diccionarios. + +## 💡 Tips + +- Las funciones deben hacer **una sola cosa** (principio de responsabilidad única) +- Usa `return` para devolver valores desde las funciones +- Los docstrings van justo después de la definición de la función, entre `"""` +- `if __name__ == "__main__":` permite que el archivo sea importable sin ejecutarse automáticamente + +## 📚 Recursos + +- Consulta [`cheatsheets/04_funciones.md`](../../cheatsheets/04_funciones.md) para funciones +- Revisa ejemplos de funciones con parámetros y return + +--- + +**Versión anterior**: [`03_control_flujo/ejercicio_guiado/GUIA.md`](../../03_control_flujo/ejercicio_guiado/GUIA.md) +**Siguiente versión**: [`05_colecciones/ejercicio_guiado/GUIA.md`](../../05_colecciones/ejercicio_guiado/GUIA.md) - Añadirás historial de operaciones \ No newline at end of file diff --git a/04_funciones/ejercicio_guiado/calculadora_v4.py b/04_funciones/ejercicio_guiado/calculadora_v4.py new file mode 100644 index 0000000..e8f67e2 --- /dev/null +++ b/04_funciones/ejercicio_guiado/calculadora_v4.py @@ -0,0 +1,131 @@ +""" +Calculadora v4 - Código modular con funciones +============================================== + +En esta cuarta versión refactorizarás todo el código en funciones reutilizables. +Verás cómo el código se vuelve más limpio, mantenible y profesional. + +Conceptos aplicados: +- Definición de funciones con def +- Parámetros y return +- Docstrings para documentación +- Separación de responsabilidades +- Patrón if __name__ == "__main__" + +Instrucciones: +1. Crea funciones para cada operación matemática +2. Crea función para mostrar el menú +3. Crea función para obtener números del usuario +4. Organiza todo en una función main() +""" + +# TODO 1: Define las funciones para cada operación matemática +# Cada función debe recibir dos parámetros (a, b) y devolver el resultado + +def sumar(a: float, b:float) -> float: + if not (isinstance(a, (int, float)) and isinstance(b, (int, float))): + raise TypeError("a y b deben ser números") + + return a + b + + +def restar(a: float, b: float) -> float: + if not (isinstance(a, (int, float)) and isinstance(b, (int, float))): + raise TypeError("a y b deben ser números") + + return a - b + + +def multiplicar(a: float, b: float) -> float: + if not (isinstance(a, (int, float)) and isinstance(b, (int, float))): + raise TypeError("a y b deben ser números") + + return a * b + + +def dividir(a: float, b: float) -> float: + if not (isinstance(a, (int, float)) and isinstance(b, (int, float))): + raise TypeError("a y b deben ser números") + + return a / b + + +def mostrar_menu(): + + print("\n=== CALCULADORA ===") + print("1. Sumar") + print("2. Restar") + print("3. Multiplicar") + print("4. Dividir") + print("5. Salir") + + +def obtener_numeros(): + + num1 = float(input("Primer número: ")) + num2 = float(input("Segundo número: ")) + return num1, num2 + + + +def main(): + + + while True: + + mostrar_menu() + + opcion = input("\nElige una opción: ") + + if opcion == "5": + print("¡Hasta pronto! 👋") + break + + if opcion not in ["1", "2", "3", "4"]: + print("❌ Opción no válida") + continue + + + num1, num2 = obtener_numeros() + + + if opcion == "4" and num2 == 0: + print("❌ No se puede dividir por cero") + continue + + + if opcion == "1": + resultado = sumar(num1, num2) + simbolo = "+" + elif opcion == "2": + resultado = restar(num1, num2) + simbolo = "-" + elif opcion == "3": + resultado = multiplicar(num1, num2) + simbolo = "*" + elif opcion == "4": + resultado = dividir(num1, num2) + simbolo = "/" + + + print(f"✅ {num1} {simbolo} {num2} = {resultado:.2f}") + + + +# Este patrón permite que el archivo sea importable sin ejecutarse automáticamente +if __name__ == "__main__": + main() + + +# ¡Excelente! Has refactorizado tu calculadora con funciones. +# +# Ventajas de esta versión: +# ✅ Cada función tiene una responsabilidad clara +# ✅ El código es reutilizable (puedes importar estas funciones en otros archivos) +# ✅ Es más fácil de leer y entender +# ✅ Es más fácil de probar (puedes testear cada función individualmente) +# ✅ Es más fácil de mantener y extender +# +# Prueba que funcione igual que la v3, pero nota cómo el código es más claro. +# +# 💡 En la v5 añadirás un historial de operaciones usando listas y diccionarios \ No newline at end of file diff --git a/05_colecciones/ejercicio_guiado/GUIA.md b/05_colecciones/ejercicio_guiado/GUIA.md new file mode 100644 index 0000000..6c8235a --- /dev/null +++ b/05_colecciones/ejercicio_guiado/GUIA.md @@ -0,0 +1,104 @@ +# 🎯 La Calculadora que Crece - Versión 5 + +¡Tu calculadora ahora tiene memoria! En esta versión añadirás un historial de operaciones usando listas y diccionarios. + +## 🎓 Qué aprenderás + +En este módulo estamos aprendiendo: +- ✅ Listas para almacenar datos +- ✅ Diccionarios para estructurar información +- ✅ Métodos append(), enumerate() +- ✅ Iteración sobre colecciones +- ✅ Estructuras de datos complejas + +## 🎯 Objetivo de la v5 + +Añadir a tu calculadora: +1. Una lista que guarde todas las operaciones realizadas +2. Cada operación se guarda como un diccionario con: números, operación, resultado +3. Nueva opción en el menú: "Ver historial" +4. Mostrar el historial con formato numerado + +**Ejemplo de ejecución:** +``` +=== CALCULADORA === +1. Sumar +2. Restar +3. Multiplicar +4. Dividir +5. Ver historial +6. Salir + +Elige una opción: 1 +Primer número: 5 +Segundo número: 3 +✅ 5.0 + 3.0 = 8.0 + +(realiza más operaciones...) + +Elige una opción: 5 + +📜 HISTORIAL DE OPERACIONES: +1. 5.0 + 3.0 = 8.0 +2. 10.0 - 4.0 = 6.0 +3. 7.0 * 2.0 = 14.0 +4. 15.0 / 3.0 = 5.0 +``` + +## 📝 Instrucciones + +1. Abre el archivo `calculadora_v5.py` +2. Encontrarás la estructura base con funciones nuevas +3. Completa las funciones de historial +4. Añade la opción 5 al menú + +## ✅ Cómo probar tu código + +Ejecuta el programa: +```powershell +python calculadora_v5.py +``` + +Casos de prueba: +- ✅ Realiza varias operaciones (al menos 4-5) +- ✅ Elige "Ver historial" → debe mostrar todas las operaciones realizadas +- ✅ Realiza más operaciones y vuelve a ver el historial → deben aparecer las nuevas +- ✅ El historial se pierde al salir (normal, lo arreglaremos en v6) + +## 🆚 Cambios respecto a la v4 + +| Aspecto | v4 | v5 | +|---------|----|----| +| Memoria | No guarda nada | Guarda todas las operaciones | +| Historial | No tiene | Opción "Ver historial" | +| Estructura datos | Variables simples | Lista + diccionarios | +| Menú | 5 opciones | 6 opciones (añade historial) | +| Persistencia | - | En memoria (se pierde al salir) | + +## 🚀 ¿Terminaste? + +¡Increíble! Tu calculadora ahora recuerda todo: +```bash +git add 05_colecciones/ejercicio_guiado/calculadora_v5.py +git commit -m "feat: calculadora v5 - historial de operaciones con listas y diccionarios" +``` + +En el módulo 06 guardarás el historial en un archivo JSON para que persista entre sesiones. + +## 💡 Tips + +- Crea una lista vacía al inicio: `historial = []` +- Cada operación es un diccionario: `{"num1": 5.0, "num2": 3.0, "operacion": "+", "resultado": 8.0}` +- Usa `historial.append(operacion_dict)` para añadir operaciones +- `enumerate(historial, 1)` te da índice y valor: `for i, op in enumerate(historial, 1):` +- Verifica si la lista está vacía: `if not historial:` + +## 📚 Recursos + +- Consulta [`cheatsheets/05_colecciones.md`](../../cheatsheets/05_colecciones.md) para listas y diccionarios +- Revisa ejemplos de append() y enumerate() + +--- + +**Versión anterior**: [`04_funciones/ejercicio_guiado/GUIA.md`](../../04_funciones/ejercicio_guiado/GUIA.md) +**Siguiente versión**: [`06_archivos_y_modulos/ejercicio_guiado/GUIA.md`](../../06_archivos_y_modulos/ejercicio_guiado/GUIA.md) - Añadirás persistencia con JSON \ No newline at end of file diff --git a/05_colecciones/ejercicio_guiado/calculadora_v5.py b/05_colecciones/ejercicio_guiado/calculadora_v5.py new file mode 100644 index 0000000..6d7238e --- /dev/null +++ b/05_colecciones/ejercicio_guiado/calculadora_v5.py @@ -0,0 +1,183 @@ +""" +Calculadora v5 - Historial de operaciones +========================================== + +En esta quinta versión añadirás un historial que guarda todas las operaciones +realizadas durante la sesión usando listas y diccionarios. + +Conceptos aplicados: +- Listas para almacenar múltiples elementos +- Diccionarios para estructurar datos +- append() para añadir a listas +- enumerate() para iterar con índice +- Verificación de listas vacías + +Instrucciones: +1. Crea una lista para almacenar operaciones +2. Crea función para guardar operaciones en el historial +3. Crea función para mostrar el historial +4. Añade opción "Ver historial" al menú +""" + +# ===== FUNCIONES DE OPERACIONES (copiadas de v4) ===== + +def sumar(a, b): + """Suma dos números.""" + return a + b + + +def restar(a, b): + """Resta dos números.""" + return a - b + + +def multiplicar(a, b): + """Multiplica dos números.""" + return a * b + + +def dividir(a, b): + """Divide dos números.""" + return a / b + + +# ===== FUNCIONES DE INTERFAZ ===== + +def mostrar_menu(): + """Muestra el menú de opciones de la calculadora.""" + print("\n=== CALCULADORA ===") + print("1. Sumar") + print("2. Restar") + print("3. Multiplicar") + print("4. Dividir") + print("5. Ver historial") + print("6. Salir") + + +def obtener_numeros(): + """Pide dos números al usuario y los devuelve.""" + num1 = float(input("Primer número: ")) + num2 = float(input("Segundo número: ")) + return num1, num2 + + +# ===== NUEVAS FUNCIONES PARA HISTORIAL ===== + +# TODO 1: Crea una lista global para almacenar el historial +# (Nota: en programación avanzada evitamos globales, pero aquí es didáctico) +historial = [] + + +def guardar_operacion(num1, num2, operacion, resultado): + """Guarda una operación en el historial. + + Args: + num1: Primer número + num2: Segundo número + operacion: Símbolo de la operación (+, -, *, /) + resultado: Resultado de la operación + """ + # TODO 2: Crea un diccionario con los datos de la operación + operacion_dict = { + "num1": num1, + "num2": num2, + "operacion": operacion, + "resultado": resultado + } + + # TODO 3: Añade el diccionario a la lista historial + historial.append(operacion_dict) + + + +def mostrar_historial(): + """Muestra todas las operaciones del historial.""" + # TODO 4: Verifica si el historial está vacío + if not historial: + print("📭 No hay operaciones en el historial") + return + + # TODO 5: Muestra el título + print("\n📜 HISTORIAL DE OPERACIONES:") + + # TODO 6: Itera sobre el historial con enumerate() + # enumerate() nos da el índice (i) y el elemento (op) + # El segundo parámetro (1) indica que empiece a contar desde 1 + for i, op in enumerate(historial, 1): + print(f"{i}. {op['num1']} {op['operacion']} {op['num2']} = {op['resultado']:.2f}") + + pass + + +# ===== FUNCIÓN PRINCIPAL ===== + +def main(): + """Función principal de la calculadora.""" + + while True: + mostrar_menu() + opcion = input("\nElige una opción: ") + + # TODO 7: Actualiza la condición de salir (ahora es la opción 6) + if opcion == "6": + print("¡Hasta pronto! 👋") + break + + # TODO 8: Añade la nueva opción 5 para ver el historial + if opcion == "5": + mostrar_historial() + continue # Vuelve al menú sin pedir números + + # TODO 9: Actualiza la validación (ahora hay 5 opciones válidas) + if opcion not in ["1", "2", "3", "4", "5"]: + print("❌ Opción no válida") + continue + + num1, num2 = obtener_numeros() + + if opcion == "4" and num2 == 0: + print("❌ No se puede dividir por cero") + continue + + # TODO 10: Realiza la operación y guarda en el historial + if opcion == "1": + resultado = sumar(num1, num2) + simbolo = "+" + elif opcion == "2": + resultado = restar(num1, num2) + simbolo = "-" + elif opcion == "3": + resultado = multiplicar(num1, num2) + simbolo = "*" + elif opcion == "4": + resultado = dividir(num1, num2) + simbolo = "/" + + print(f"✅ {num1} {simbolo} {num2} = {resultado:.2f}") + + # TODO 11: Guarda la operación en el historial + guardar_operacion(num1, num2, simbolo, resultado) + + pass + + +if __name__ == "__main__": + main() + + +# ¡Fantástico! Ahora tu calculadora tiene memoria. +# +# Prueba estos casos: +# 1. Realiza varias operaciones (5 + 3, 10 - 4, 7 * 2, 15 / 3) +# 2. Elige "Ver historial" → debe mostrar todas las operaciones +# 3. Realiza más operaciones y vuelve a ver el historial → deben aparecer +# 4. Sal del programa y vuelve a ejecutarlo → el historial se habrá borrado +# (es normal, en v6 lo guardaremos en un archivo para que persista) +# +# Ventajas de esta versión: +# ✅ Guarda todas las operaciones de la sesión +# ✅ Puedes revisar qué has calculado +# ✅ Usa estructuras de datos complejas (lista de diccionarios) +# ✅ Es fácil añadir más funcionalidades (ej: buscar, borrar operaciones) +# +# 💡 En la v6 guardarás el historial en un archivo JSON para que persista \ No newline at end of file diff --git a/06_archivos_y_modulos/ejercicio_guiado/GUIA.md b/06_archivos_y_modulos/ejercicio_guiado/GUIA.md new file mode 100644 index 0000000..db742f9 --- /dev/null +++ b/06_archivos_y_modulos/ejercicio_guiado/GUIA.md @@ -0,0 +1,137 @@ +# 🎯 La Calculadora que Crece - Versión 6 (Final) + +¡La versión final! En esta última versión añadirás persistencia: el historial se guardará en un archivo JSON para que no se pierda al cerrar el programa. + +## 🎓 Qué aprenderás + +En este módulo estamos aprendiendo: +- ✅ Lectura y escritura de archivos +- ✅ Formato JSON +- ✅ Manejo de excepciones (FileNotFoundError, JSONDecodeError) +- ✅ Persistencia de datos +- ✅ Módulo `json` de Python + +## 🎯 Objetivo de la v6 + +Añadir persistencia a tu calculadora para que: +1. Al iniciar, cargue el historial desde un archivo JSON (si existe) +2. Al salir, guarde el historial en el archivo JSON +3. Opción adicional: Limpiar historial +4. El historial persiste entre sesiones + +**Ejemplo de ejecución:** +``` +🔄 Cargando historial... +✅ Historial cargado: 3 operaciones + +=== CALCULADORA === +1. Sumar +2. Restar +3. Multiplicar +4. Dividir +5. Ver historial +6. Limpiar historial +7. Salir + +(realizas operaciones...) + +Elige una opción: 7 +💾 Guardando historial... +✅ Historial guardado correctamente +¡Hasta pronto! 👋 +``` + +Al volver a ejecutar: +``` +🔄 Cargando historial... +✅ Historial cargado: 5 operaciones +``` + +¡El historial se mantuvo! + +## 📝 Instrucciones + +1. Abre el archivo `calculadora_v6.py` +2. Encontrarás nuevas funciones para cargar y guardar +3. Completa las funciones de persistencia +4. Actualiza main() para cargar al inicio y guardar al salir + +## ✅ Cómo probar tu código + +Ejecuta el programa: +```powershell +python calculadora_v6.py +``` + +Casos de prueba: +- ✅ Primera ejecución: No hay historial → se crea vacío +- ✅ Realiza 3-4 operaciones +- ✅ Sal del programa (opción 7) +- ✅ Vuelve a ejecutar → el historial debe estar ahí +- ✅ Usa "Limpiar historial" → debe borrar el archivo y vaciar la lista +- ✅ Verifica que se creó `historial_calculadora.json` en la misma carpeta + +## 🆚 Cambios respecto a la v5 + +| Aspecto | v5 | v6 | +|---------|----|----| +| Persistencia | Se pierde al salir | Se guarda en archivo JSON | +| Al iniciar | Historial vacío | Carga historial previo | +| Al salir | Historial se pierde | Guarda automáticamente | +| Limpieza | No disponible | Opción "Limpiar historial" | +| Archivo | No usa archivos | `historial_calculadora.json` | +| Menú | 6 opciones | 7 opciones | + +## 🚀 ¿Terminaste? + +¡**FELICIDADES**! Has completado toda la serie de la calculadora: +```bash +git add 06_archivos_y_modulos/ejercicio_guiado/calculadora_v6.py +git commit -m "feat: calculadora v6 - persistencia con JSON (versión final)" +``` + +### 🎉 Lo que has logrado + +Has construido una calculadora completa que: +- ✅ Realiza las 4 operaciones básicas +- ✅ Tiene un menú interactivo con validación +- ✅ Está organizada en funciones modulares +- ✅ Guarda historial de operaciones +- ✅ Persiste datos entre sesiones con JSON +- ✅ Maneja errores correctamente + +**Has aplicado**: +- Variables, tipos, input/output (Módulo 01) +- Operadores, f-strings (Módulo 02) +- Condicionales, bucles, validación (Módulo 03) +- Funciones, modularidad (Módulo 04) +- Listas, diccionarios (Módulo 05) +- Archivos, JSON, excepciones (Módulo 06) + +## 💡 Tips + +- Usa `json.load()` para leer JSON y `json.dump()` para escribir +- Parámetro `indent=2` hace el JSON más legible +- `ensure_ascii=False` permite caracteres especiales (ej: emojis) +- Usa `try/except` para manejar archivo no encontrado o corrupto +- Encoding UTF-8: `open(archivo, "r", encoding="utf-8")` + +## 📚 Recursos + +- Consulta [`cheatsheets/06_archivos_y_modulos.md`](../../cheatsheets/06_archivos_y_modulos.md) para archivos y JSON +- Revisa ejemplos de lectura/escritura con manejo de errores + +## 🎁 Bonus: Versión modular (opcional) + +Si quieres ir más allá, puedes separar el código en múltiples archivos: +- `operaciones.py` - Funciones matemáticas +- `historial.py` - Gestión del historial +- `interfaz.py` - Menú y entrada de usuario +- `main.py` - Punto de entrada + +Consulta la carpeta `calculadora_modular/` (si existe) para ver un ejemplo. + +--- + +**Versión anterior**: [`05_colecciones/ejercicio_guiado/GUIA.md`](../../05_colecciones/ejercicio_guiado/GUIA.md) +**¡Proyecto completado!** 🎉 Has terminado toda la serie de ejercicios guiados. \ No newline at end of file diff --git a/06_archivos_y_modulos/ejercicio_guiado/calculadora_v6.py b/06_archivos_y_modulos/ejercicio_guiado/calculadora_v6.py new file mode 100644 index 0000000..6c98daa --- /dev/null +++ b/06_archivos_y_modulos/ejercicio_guiado/calculadora_v6.py @@ -0,0 +1,255 @@ +""" +Calculadora v6 - Persistencia con JSON (VERSIÓN FINAL) +======================================================= + +En esta sexta y última versión añadirás persistencia: el historial se guardará +en un archivo JSON para que no se pierda al cerrar el programa. + +Conceptos aplicados: +- Lectura y escritura de archivos +- Módulo json (load, dump) +- Manejo de excepciones (FileNotFoundError, JSONDecodeError) +- Context managers (with open...) +- Persistencia de datos + +Instrucciones: +1. Importa el módulo json +2. Crea funciones para cargar y guardar el historial en JSON +3. Carga el historial al iniciar el programa +4. Guarda el historial al salir +5. Añade opción para limpiar el historial +""" + +# TODO 1: Importa el módulo json +import json +import os + + +# Nombre del archivo donde se guardará el historial +ARCHIVO_HISTORIAL = "historial_calculadora.json" + +# Lista global para el historial +historial = [] + + +# ===== FUNCIONES DE OPERACIONES ===== + +def sumar(a, b): + """Suma dos números.""" + return a + b + + +def restar(a, b): + """Resta dos números.""" + return a - b + + +def multiplicar(a, b): + """Multiplica dos números.""" + return a * b + + +def dividir(a, b): + """Divide dos números.""" + return a / b + + +# ===== FUNCIONES DE INTERFAZ ===== + +def mostrar_menu(): + """Muestra el menú de opciones de la calculadora.""" + print("\n=== CALCULADORA ===") + print("1. Sumar") + print("2. Restar") + print("3. Multiplicar") + print("4. Dividir") + print("5. Ver historial") + print("6. Limpiar historial") + print("7. Salir") + + +def obtener_numeros(): + """Pide dos números al usuario y los devuelve.""" + num1 = float(input("Primer número: ")) + num2 = float(input("Segundo número: ")) + return num1, num2 + + +# ===== FUNCIONES DE HISTORIAL (memoria) ===== + +def guardar_operacion(num1, num2, operacion, resultado): + """Guarda una operación en el historial (en memoria).""" + operacion_dict = { + "num1": num1, + "num2": num2, + "operacion": operacion, + "resultado": resultado + } + historial.append(operacion_dict) + + +def mostrar_historial(): + """Muestra todas las operaciones del historial.""" + if not historial: + print("📭 No hay operaciones en el historial") + return + + print("\n📜 HISTORIAL DE OPERACIONES:") + for i, op in enumerate(historial, 1): + print(f"{i}. {op['num1']} {op['operacion']} {op['num2']} = {op['resultado']:.2f}") + + +# ===== NUEVAS FUNCIONES DE PERSISTENCIA (archivos JSON) ===== + +def cargar_historial(): + """Carga el historial desde el archivo JSON. + + Returns: + Lista con el historial cargado, o lista vacía si no existe el archivo. + """ + # TODO 2: Intenta cargar el archivo JSON + try: + with open(ARCHIVO_HISTORIAL, "r", encoding="utf-8") as archivo: + datos = json.load(archivo) + print(f"✅ Historial cargado: {len(datos)} operaciones") + return datos + except FileNotFoundError: + # El archivo no existe (primera vez que se ejecuta) + print("📝 No hay historial previo, iniciando uno nuevo") + return [] + except json.JSONDecodeError: + # El archivo existe pero está corrupto + print("⚠️ Archivo de historial corrupto, iniciando uno nuevo") + return [] + + + +def guardar_historial_archivo(): + """Guarda el historial actual en el archivo JSON.""" + # TODO 3: Guarda el historial en el archivo JSON + try: + with open(ARCHIVO_HISTORIAL, "w", encoding="utf-8") as archivo: + # indent=2 hace que el JSON sea más legible + # ensure_ascii=False permite emojis y caracteres especiales + json.dump(historial, archivo, indent=2, ensure_ascii=False) + print("✅ Historial guardado correctamente") + except Exception as e: + print(f"❌ Error al guardar el historial: {e}") + + + +def limpiar_historial(): + """Limpia el historial en memoria y elimina el archivo.""" + global historial + + # TODO 4: Limpia el historial + # Pide confirmación al usuario + confirmacion = input("⚠️ ¿Estás seguro de que quieres limpiar el historial? (s/n): ") + if confirmacion.lower() != "s": + print("❌ Operación cancelada") + return + + # Vacía la lista en memoria + historial = [] + + # Elimina el archivo si existe + try: + if os.path.exists(ARCHIVO_HISTORIAL): + os.remove(ARCHIVO_HISTORIAL) + print("🗑️ Historial limpiado correctamente") + except Exception as e: + print(f"❌ Error al eliminar el archivo: {e}") + + + +# ===== FUNCIÓN PRINCIPAL ===== + +def main(): + """Función principal de la calculadora.""" + + # TODO 5: Carga el historial al iniciar + global historial + print("🔄 Cargando historial...") + historial = cargar_historial() + + while True: + mostrar_menu() + opcion = input("\nElige una opción: ") + + # TODO 6: Actualiza la condición de salir (ahora es opción 7) + if opcion == "7": + print("💾 Guardando historial...") + guardar_historial_archivo() + print("¡Hasta pronto! 👋") + break + + # TODO 7: Añade la opción 5 (ver historial) + if opcion == "5": + mostrar_historial() + continue + + # TODO 8: Añade la opción 6 (limpiar historial) + if opcion == "6": + limpiar_historial() + continue + + # TODO 9: Actualiza la validación de opciones (ahora son 1-6) + if opcion not in ["1", "2", "3", "4", "5", "6"]: + print("❌ Opción no válida") + continue + + num1, num2 = obtener_numeros() + + if opcion == "4" and num2 == 0: + print("❌ No se puede dividir por cero") + continue + + if opcion == "1": + resultado = sumar(num1, num2) + simbolo = "+" + elif opcion == "2": + resultado = restar(num1, num2) + simbolo = "-" + elif opcion == "3": + resultado = multiplicar(num1, num2) + simbolo = "*" + elif opcion == "4": + resultado = dividir(num1, num2) + simbolo = "/" + + print(f"✅ {num1} {simbolo} {num2} = {resultado:.2f}") + guardar_operacion(num1, num2, simbolo, resultado) + + + +if __name__ == "__main__": + main() + + +# ¡FELICIDADES! Has completado toda la serie "La Calculadora que Crece" +# +# 🎉 Lo que has logrado: +# ✅ v1: Suma básica (input, print, variables) +# ✅ v2: 4 operaciones (operadores, if/elif, f-strings) +# ✅ v3: Menú interactivo (while, break/continue, validación) +# ✅ v4: Código modular (funciones, docstrings, return) +# ✅ v5: Historial en memoria (listas, diccionarios, append) +# ✅ v6: Persistencia con JSON (archivos, json.load/dump, excepciones) +# +# Pruebas finales: +# 1. Ejecuta el programa y realiza varias operaciones +# 2. Sal del programa (opción 7) +# 3. Verifica que se creó el archivo "historial_calculadora.json" +# 4. Ábrelo con un editor de texto y verás el JSON con tus operaciones +# 5. Vuelve a ejecutar el programa → el historial debe cargarse automáticamente +# 6. Prueba "Limpiar historial" → el archivo debe desaparecer +# +# 💡 Extensiones opcionales si quieres seguir aprendiendo: +# - Añadir más operaciones (potencia, raíz cuadrada, módulo) +# - Exportar historial a CSV para Excel +# - Interfaz gráfica con Tkinter +# - Tests unitarios con pytest +# - Separar en múltiples módulos (ver carpeta calculadora_modular/) +# +# ¡Has terminado un proyecto completo desde cero hasta una aplicación funcional! +# Este es el tipo de evolución que verás en proyectos reales. \ No newline at end of file diff --git a/06_archivos_y_modulos/ejercicio_guiado/historial_calculadora.json b/06_archivos_y_modulos/ejercicio_guiado/historial_calculadora.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/06_archivos_y_modulos/ejercicio_guiado/historial_calculadora.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/07_mini_proyectos/retos.md b/07_mini_proyectos/retos.md index a59bdbe..c748972 100644 --- a/07_mini_proyectos/retos.md +++ b/07_mini_proyectos/retos.md @@ -12,4 +12,4 @@ ## 3) Parser de logs simple - Lee un archivo de logs, separa por espacio, cuenta por tipo o fecha. -- Guarda resultados en `resumen.txt`. +- Guarda resultados en `resumen.txt`. \ No newline at end of file diff --git a/soluciones/01_intro/ejemplos/hola.py b/soluciones/01_intro/ejemplos/hola.py deleted file mode 100644 index 1a1e3f5..0000000 --- a/soluciones/01_intro/ejemplos/hola.py +++ /dev/null @@ -1 +0,0 @@ -print("Hola, Python") diff --git a/soluciones/01_intro/sol_1_primer_script.py b/soluciones/01_intro/sol_1_primer_script.py index 0dc300b..0a855ce 100644 --- a/soluciones/01_intro/sol_1_primer_script.py +++ b/soluciones/01_intro/sol_1_primer_script.py @@ -1,3 +1,4 @@ nombre = "Ana" + print("Hola, Python") -print(f"Hola, {nombre}") +print(f"Hola, {nombre}") \ No newline at end of file diff --git a/soluciones/01_intro/sol_2_saludo_input.py b/soluciones/01_intro/sol_2_saludo_input.py index 73af2a3..afffdbb 100644 --- a/soluciones/01_intro/sol_2_saludo_input.py +++ b/soluciones/01_intro/sol_2_saludo_input.py @@ -1,5 +1,6 @@ nombre = input("¿Cómo te llamas? ") -if not nombre.strip(): + +if nombre.strip() == "": print("Nombre no válido") else: - print(f"Hola, {nombre}") + print (f"Hola, {nombre}") diff --git a/soluciones/01_intro/sol_3_variables_basicas.py b/soluciones/01_intro/sol_3_variables_basicas.py new file mode 100644 index 0000000..7ac0559 --- /dev/null +++ b/soluciones/01_intro/sol_3_variables_basicas.py @@ -0,0 +1,4 @@ +nombre = "Ana" +ciudad = "Lima" + +print(f"{nombre} vive en {ciudad}") \ No newline at end of file diff --git a/soluciones/01_intro/sol_4_conversor.py b/soluciones/01_intro/sol_4_conversor.py new file mode 100644 index 0000000..d139272 --- /dev/null +++ b/soluciones/01_intro/sol_4_conversor.py @@ -0,0 +1,16 @@ +def main(): + # Pedimos al usuario un número + entrada = input("Introduce un número: ") + + try: + # Intentamos convertir la entrada a float + numero = float(entrada) + print(f"Has introducido el número {numero}") + + except ValueError: + # Si la conversión falla, mostramos un mensaje amable + print("Parece que eso no es un número válido 😊. Inténtalo de nuevo.") + +# Ejecutamos la función principal solo si el script se ejecuta directamente +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/soluciones/01_intro/sol_5_tres_prints.py b/soluciones/01_intro/sol_5_tres_prints.py new file mode 100644 index 0000000..e69de29 diff --git a/soluciones/01_intro/sol_6_eco.py b/soluciones/01_intro/sol_6_eco.py new file mode 100644 index 0000000..7af98f9 --- /dev/null +++ b/soluciones/01_intro/sol_6_eco.py @@ -0,0 +1,3 @@ +entrada = input("Introduce un texto: ") + +print(entrada) \ No newline at end of file diff --git a/soluciones/01_intro/sol_7_bigotes_felices.py b/soluciones/01_intro/sol_7_bigotes_felices.py new file mode 100644 index 0000000..c4a22ef --- /dev/null +++ b/soluciones/01_intro/sol_7_bigotes_felices.py @@ -0,0 +1,16 @@ +def main(): + + nombre = input("Introduce el nombre del gato: ") + + edad_input = input("Introduce la edad del gato: ") + + try: + + edad = float(edad_input) + print(f"Gato: {nombre} (edad: {edad})") + except ValueError: + + print("Edad inválida") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/soluciones/02_estructuras/sol_1_operaciones.py b/soluciones/02_estructuras/sol_1_operaciones.py index e630553..d319121 100644 --- a/soluciones/02_estructuras/sol_1_operaciones.py +++ b/soluciones/02_estructuras/sol_1_operaciones.py @@ -1,6 +1,11 @@ -a = 10 -b = 3 -print(f"suma={a+b}") -print(f"resta={a-b}") -print(f"mul={a*b}") -print(f"div={a/b:.2f}") +## Guiado 1: Operaciones numéricas + +a=10 +b=3 + +suma = a + b +resta = a - b +multiplicación = a * b +división = a / b + +print(f"El resultado es: suma = {suma}, resta = {resta}, multiplicación = {multiplicación}, división = {división}") \ No newline at end of file diff --git a/soluciones/02_estructuras/sol_2_casting.py b/soluciones/02_estructuras/sol_2_casting.py index 0cece65..581a7e9 100644 --- a/soluciones/02_estructuras/sol_2_casting.py +++ b/soluciones/02_estructuras/sol_2_casting.py @@ -1,6 +1,8 @@ -txt = input("Número: ") +numero_input = input("Imprime un número aquí: ") + try: - n = int(txt) - print(n) + numero = int(numero_input) + print(f"Has imprimido el número: {numero}") + except ValueError: print("Entrada inválida") diff --git a/soluciones/02_estructuras/sol_3_booleans.py b/soluciones/02_estructuras/sol_3_booleans.py new file mode 100644 index 0000000..ab46aa4 --- /dev/null +++ b/soluciones/02_estructuras/sol_3_booleans.py @@ -0,0 +1,5 @@ +x = 5 +y = 15 + +print(y >= x) +print(x == y) \ No newline at end of file diff --git a/soluciones/02_estructuras/sol_4_strings.py b/soluciones/02_estructuras/sol_4_strings.py new file mode 100644 index 0000000..f161f87 --- /dev/null +++ b/soluciones/02_estructuras/sol_4_strings.py @@ -0,0 +1,6 @@ +nombre = "Yulia" +apellido = "Bonart" + +nombre_completo = f"{nombre} {apellido}".title() + +print(nombre_completo) \ No newline at end of file diff --git a/soluciones/02_estructuras/sol_5_redondeo.py b/soluciones/02_estructuras/sol_5_redondeo.py new file mode 100644 index 0000000..37f2b1e --- /dev/null +++ b/soluciones/02_estructuras/sol_5_redondeo.py @@ -0,0 +1,5 @@ +numero = 3.14159 + +numero_decimal = int(numero) + +print(numero_decimal) \ No newline at end of file diff --git a/soluciones/02_estructuras/sol_6_porcentajes.py b/soluciones/02_estructuras/sol_6_porcentajes.py new file mode 100644 index 0000000..e2e2058 --- /dev/null +++ b/soluciones/02_estructuras/sol_6_porcentajes.py @@ -0,0 +1,6 @@ +cantidad = 200 +porcentaje = 15 + +resultado = cantidad * porcentaje / 100 + +print(resultado) \ No newline at end of file diff --git a/soluciones/02_estructuras/sol_7_bigotes_felices_pesaje.py b/soluciones/02_estructuras/sol_7_bigotes_felices_pesaje.py new file mode 100644 index 0000000..ef15179 --- /dev/null +++ b/soluciones/02_estructuras/sol_7_bigotes_felices_pesaje.py @@ -0,0 +1,9 @@ +try: + gato1_peso = float(input("Imprime el peso del primer gato aquí: ")) + gato2_peso = float(input("Imprime el peso del segundo gato aquí: ")) + gato3_peso = float(input("Imprime el peso del tercer gato aquí: ")) +except ValueError: + print("Entrada inválida") +else: + promedio_de_pesos = (gato1_peso + gato2_peso + gato3_peso) / 3 + print(f"Aquí tienes el promedio: {promedio_de_pesos}") \ No newline at end of file diff --git a/soluciones/03_control_flujo/sol_1_clasificador.py b/soluciones/03_control_flujo/sol_1_clasificador.py index fd9aa5e..5f82e1c 100644 --- a/soluciones/03_control_flujo/sol_1_clasificador.py +++ b/soluciones/03_control_flujo/sol_1_clasificador.py @@ -1,9 +1,9 @@ txt = input("Número: ") try: - n = int(txt) - if n > 0: + x = int(txt) + if x > 0: print("positivo") - elif n == 0: + elif x == 0: print("cero") else: print("negativo") diff --git a/soluciones/03_control_flujo/sol_2_suma_acumulada.py b/soluciones/03_control_flujo/sol_2_suma_acumulada.py index b153900..364a387 100644 --- a/soluciones/03_control_flujo/sol_2_suma_acumulada.py +++ b/soluciones/03_control_flujo/sol_2_suma_acumulada.py @@ -1,10 +1,11 @@ -total = 0.0 +total = 0 while True: - s = input("n o fin: ") - if s == "fin": + entrada = input("Introduce un número (o 'fin' para terminar): ") + if entrada.lower() == "fin": break try: - total += float(s) + numero = float(entrada) + total += numero except ValueError: - print("Entrada inválida") -print(total) + print("Por favor, introduce un número válido o 'fin' para salir.") +print(f"La suma total es: {total}") diff --git a/soluciones/03_control_flujo/sol_3_recorrer_lista.py b/soluciones/03_control_flujo/sol_3_recorrer_lista.py new file mode 100644 index 0000000..3e8a796 --- /dev/null +++ b/soluciones/03_control_flujo/sol_3_recorrer_lista.py @@ -0,0 +1,4 @@ +lista = {"Garfield", "Luna", "Chichi", "Marusya"} + +for nombre in lista: + print(nombre) \ No newline at end of file diff --git a/soluciones/03_control_flujo/sol_4_contar_letras.py b/soluciones/03_control_flujo/sol_4_contar_letras.py new file mode 100644 index 0000000..594571b --- /dev/null +++ b/soluciones/03_control_flujo/sol_4_contar_letras.py @@ -0,0 +1,8 @@ +string = "Yulia" + +contador = 0 + +for letra in string: + contador += 1 + +print(f"El texto {string} tiene {contador} letras") \ No newline at end of file diff --git a/soluciones/03_control_flujo/sol_5_tabla_multiplicacion.py b/soluciones/03_control_flujo/sol_5_tabla_multiplicacion.py new file mode 100644 index 0000000..2219ca9 --- /dev/null +++ b/soluciones/03_control_flujo/sol_5_tabla_multiplicacion.py @@ -0,0 +1,5 @@ +for x in range(1, 6): + print(f"\nTabla del {x}:") + for y in range(1, 11): + resultado = x * y + print(f"{x} x {y} = {resultado}") \ No newline at end of file diff --git a/soluciones/03_control_flujo/sol_6_contrasena_correcta.py b/soluciones/03_control_flujo/sol_6_contrasena_correcta.py new file mode 100644 index 0000000..a743271 --- /dev/null +++ b/soluciones/03_control_flujo/sol_6_contrasena_correcta.py @@ -0,0 +1,9 @@ +while True: + contraseña = input("Introduce la contraseña: ") + if contraseña == "gato123": + print("¡Contraseña correcta!") + break + else: + print("Contraseña incorrecta. Intenta de nuevo.") + + diff --git a/soluciones/03_control_flujo/sol_7_b_f_turnos.py b/soluciones/03_control_flujo/sol_7_b_f_turnos.py new file mode 100644 index 0000000..a7655d7 --- /dev/null +++ b/soluciones/03_control_flujo/sol_7_b_f_turnos.py @@ -0,0 +1,13 @@ +num_gatos = int(input("¿Cuántos gatos hay? ")) + +turno_A = [] +turno_B = [] + +for i in range(1, num_gatos + 1): + if i % 2 == 0: + turno_B.append(f"Gato {i}") + else: + turno_A.append(f"Gato {i}") + +print("\nTurno A:", turno_A) +print("Turno B:", turno_B) \ No newline at end of file diff --git a/soluciones/04_funciones/sol_2_sumar.py b/soluciones/04_funciones/sol_2_sumar.py index 3774be7..9974a9a 100644 --- a/soluciones/04_funciones/sol_2_sumar.py +++ b/soluciones/04_funciones/sol_2_sumar.py @@ -7,4 +7,4 @@ def sumar(nums): return total if __name__ == "__main__": - print(sumar([1, 2, 3])) + print(sumar([1, 2, 3, 4])) diff --git a/soluciones/04_funciones/sol_3_es_par.py b/soluciones/04_funciones/sol_3_es_par.py new file mode 100644 index 0000000..5f35da3 --- /dev/null +++ b/soluciones/04_funciones/sol_3_es_par.py @@ -0,0 +1,8 @@ +def es_par(n: int) -> bool: + if not isinstance(n, int): + raise TypeError("n debe ser un número entero") + return n % 2 == 0 + +if __name__ == "__main__": + print(es_par(7)) + print(es_par(9.8)) \ No newline at end of file diff --git a/soluciones/04_funciones/sol_4_area_rectangulo.py b/soluciones/04_funciones/sol_4_area_rectangulo.py new file mode 100644 index 0000000..4cf6a89 --- /dev/null +++ b/soluciones/04_funciones/sol_4_area_rectangulo.py @@ -0,0 +1,11 @@ +def area_rectangulo(a: float, b: float) -> float: + if not (isinstance(a, (int, float)) and isinstance(b, (int, float))): + raise TypeError("a y b deben ser números") + if a <= 0 or b <= 0: + raise ValueError("a y b deben ser mayores que 0") + return a * b + +if __name__ == "__main__": + print(area_rectangulo(7, 6)) + print(area_rectangulo(-2, 5)) + print(area_rectangulo(8, "4")) \ No newline at end of file diff --git a/soluciones/04_funciones/sol_5_formatear_gato.py b/soluciones/04_funciones/sol_5_formatear_gato.py new file mode 100644 index 0000000..9a9146e --- /dev/null +++ b/soluciones/04_funciones/sol_5_formatear_gato.py @@ -0,0 +1,11 @@ +def formatear_gato(nombre: str, edad: int) -> str: + if not isinstance(nombre, str): + raise TypeError("nombre debe ser una cadena") + if not isinstance(edad, int): + raise TypeError("edad debe ser un número entero") + return f"El gato se llama {nombre} y tiene {edad} años." + +if __name__ == "__main__": + print(formatear_gato("Garfield", 6)) + print(formatear_gato(6, 6)) + print(formatear_gato("Garfield", 6.7)) \ No newline at end of file diff --git a/soluciones/04_funciones/sol_6_promedio_nums.py b/soluciones/04_funciones/sol_6_promedio_nums.py new file mode 100644 index 0000000..70a5ce6 --- /dev/null +++ b/soluciones/04_funciones/sol_6_promedio_nums.py @@ -0,0 +1,10 @@ +def promedio(nums: list[float]) -> float: + if not isinstance(nums, list): + raise TypeError("nums debe ser una lista") + if len(nums) == 0: + raise ValueError("La lista no puede estar vacía") + return sum(nums) / len(nums) + +print(promedio([5, 6, 8, 9])) +print(promedio([9])) +print(promedio([])) \ No newline at end of file diff --git a/soluciones/04_funciones/sol_7_BF_mayores.py b/soluciones/04_funciones/sol_7_BF_mayores.py new file mode 100644 index 0000000..b066847 --- /dev/null +++ b/soluciones/04_funciones/sol_7_BF_mayores.py @@ -0,0 +1,13 @@ +gatos = [ + {"nombre": "Garfield", "edad": 3}, + {"nombre": "Luna", "edad": 7}, + {"nombre": "Bagira", "edad": 5}, + {"nombre": "Nina", "edad": 8} +] + +def gatos_mayores(gatos: list[dict]) -> list[dict]: + if not isinstance(gatos, list): + raise TypeError("Se esperaba una lista de gatos") + return [g for g in gatos if isinstance(g, dict) and g.get("edad", 0) > 5] + +print(gatos_mayores(gatos)) \ No newline at end of file diff --git a/soluciones/05_colecciones/sol_2_diccionario.py b/soluciones/05_colecciones/sol_2_diccionario.py index d76c0dc..2ef3c55 100644 --- a/soluciones/05_colecciones/sol_2_diccionario.py +++ b/soluciones/05_colecciones/sol_2_diccionario.py @@ -1,2 +1,2 @@ -f = {"nombre": "Mishi", "edad": 3} -print(f.get("nombre"), f.get("edad")) +dict = {"nombre": "Mishi", "edad": 3} +print(dict.get("nombre"), dict.get("edad")) diff --git a/soluciones/05_colecciones/sol_3_vacunas.py b/soluciones/05_colecciones/sol_3_vacunas.py new file mode 100644 index 0000000..fd6a91c --- /dev/null +++ b/soluciones/05_colecciones/sol_3_vacunas.py @@ -0,0 +1,11 @@ +# En Python, un set es una colección sin elementos duplicados y sin orden específico. +vacunas = {"Pfizer", "Moderna", "AstraZeneca", "Sputnik", "Sinovac"} + +print("Vacunas disponibles:", vacunas) + +nombre = input("Escribe el nombre de una vacuna: ") + +if nombre in vacunas: + print(f"✅ La vacuna '{nombre}' está en la lista.") +else: + print(f"❌ La vacuna '{nombre}' no está registrada.") \ No newline at end of file diff --git a/soluciones/05_colecciones/sol_4_combinar_listas.py b/soluciones/05_colecciones/sol_4_combinar_listas.py new file mode 100644 index 0000000..a9f7e61 --- /dev/null +++ b/soluciones/05_colecciones/sol_4_combinar_listas.py @@ -0,0 +1,11 @@ +nombres = ["Ana", "Luis", "María", "Carlos"] +edades = [25, 30, 22, 28] + +personas = dict(zip(nombres, edades)) + +if len(nombres) != len(edades): + print("⚠️ Las listas no tienen la misma cantidad de elementos") + +print("Diccionario combinado:") +for nombre, edad in personas.items(): + print(f"{nombre} tiene {edad} años.") \ No newline at end of file diff --git a/soluciones/05_colecciones/sol_5_contar_ocurrencias.py b/soluciones/05_colecciones/sol_5_contar_ocurrencias.py new file mode 100644 index 0000000..4cea1fc --- /dev/null +++ b/soluciones/05_colecciones/sol_5_contar_ocurrencias.py @@ -0,0 +1,7 @@ +texto = "banana" +conteo = {} + +for letra in texto: + conteo[letra] = conteo.get(letra, 0) + 1 + +print(conteo) \ No newline at end of file diff --git a/soluciones/05_colecciones/sol_6_slicing_y_comprehensions.py b/soluciones/05_colecciones/sol_6_slicing_y_comprehensions.py new file mode 100644 index 0000000..2960428 --- /dev/null +++ b/soluciones/05_colecciones/sol_6_slicing_y_comprehensions.py @@ -0,0 +1,34 @@ +nombres = ["Ana", "Luis", "María", "Carlos", "Elena"] + +print(f"Del índice 1 al 3: {nombres[1:4]}") +print(f"Desde el inicio hasta el 3 (sin incluirlo): {nombres[:3]}") +print(f"Desde el 2 hasta el final: {nombres[2:]}") +print(f"Últimos 2 elementos: {nombres[-2:]}") +print(f"Cada 2 elementos: {nombres[::2]}") +print(f"Lista invertida: {nombres[::-1]}") + + +#Crear una lista de cuadrados: +numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9] +cuadrados = [n ** 2 for n in numeros] +print(cuadrados) + + +#Filtrar solo los números pares: +pares = [n for n in numeros if n % 2 == 0] +print(pares) + + +#Convertir una lista de nombres a mayúsculas: +mayus = [nombre.upper() for nombre in nombres] +print(mayus) + + +#Extraer vocales de una cadena: +texto = "python" +vocales = [letra for letra in texto if letra in "aeiou"] +print(vocales) + + +pares_invertidos = [n for n in numeros[::-1] if n % 2 == 0] +print(pares_invertidos) \ No newline at end of file diff --git a/soluciones/05_colecciones/sol_7_bf_tres_pesados.py b/soluciones/05_colecciones/sol_7_bf_tres_pesados.py new file mode 100644 index 0000000..78e7015 --- /dev/null +++ b/soluciones/05_colecciones/sol_7_bf_tres_pesados.py @@ -0,0 +1,8 @@ +pesos = [3.5, 4.2, 2.8, 5.0, 4.7, 3.9] + + +ordenados = sorted(enumerate(pesos), key=lambda x: x[1], reverse=True) + +print("Top 3 gatos más pesados:") +for i, peso in ordenados[:3]: + print(f"Gato #{i+1}: {peso} kg") \ No newline at end of file diff --git a/soluciones/06_archivos_y_modulos/animales.txt b/soluciones/06_archivos_y_modulos/animales.txt new file mode 100644 index 0000000..e206350 --- /dev/null +++ b/soluciones/06_archivos_y_modulos/animales.txt @@ -0,0 +1,3 @@ +Gato +Perro +Conejo diff --git a/soluciones/06_archivos_y_modulos/gatos.csv b/soluciones/06_archivos_y_modulos/gatos.csv new file mode 100644 index 0000000..ba5657c --- /dev/null +++ b/soluciones/06_archivos_y_modulos/gatos.csv @@ -0,0 +1,5 @@ +Nombre,Edad,Raza,Color +Michi,2,Siames,Crema +Luna,3,Persa,Blanco +Simba,1,Bengalí,Naranja +Nala,4,Mestizo,Gris diff --git a/soluciones/06_archivos_y_modulos/resumen_visitas.csv b/soluciones/06_archivos_y_modulos/resumen_visitas.csv new file mode 100644 index 0000000..c9d1988 --- /dev/null +++ b/soluciones/06_archivos_y_modulos/resumen_visitas.csv @@ -0,0 +1,3 @@ +Fecha,Visitas +2025-11-05,2 +2025-11-06,3 diff --git a/soluciones/06_archivos_y_modulos/sol_3_gatos.py b/soluciones/06_archivos_y_modulos/sol_3_gatos.py new file mode 100644 index 0000000..3349f25 --- /dev/null +++ b/soluciones/06_archivos_y_modulos/sol_3_gatos.py @@ -0,0 +1,23 @@ +import csv + +# Datos de ejemplo +gatos = [ + ["Nombre", "Edad", "Raza", "Color"], + ["Michi", 2, "Siames", "Crema"], + ["Luna", 3, "Persa", "Blanco"], + ["Simba", 1, "Bengalí", "Naranja"], + ["Nala", 4, "Mestizo", "Gris"] +] + +# 1️⃣ Escribir el archivo CSV +with open("gatos.csv", "w", encoding="utf-8", newline="") as f: + escritor = csv.writer(f) + escritor.writerows(gatos) + +# 2️⃣ Leer el archivo CSV y mostrar su contenido +with open("gatos.csv", "r", encoding="utf-8") as f: + lector = csv.reader(f) + for fila in lector: + print(fila) + +#print("\n".join(str(x) for x in gatos)) \ No newline at end of file diff --git a/soluciones/06_archivos_y_modulos/sol_4_utiles.py b/soluciones/06_archivos_y_modulos/sol_4_utiles.py new file mode 100644 index 0000000..47fe11c --- /dev/null +++ b/soluciones/06_archivos_y_modulos/sol_4_utiles.py @@ -0,0 +1,28 @@ +def leer_lineas(ruta): + """ + Lee todas las líneas de un archivo de texto y las devuelve como una lista. + Cada línea se devuelve sin el salto de línea final. + """ + try: + with open(ruta, "r", encoding="utf-8") as f: + return [linea.strip() for linea in f.readlines()] + except FileNotFoundError: + print(f"⚠️ El archivo '{ruta}' no existe.") + return [] + except Exception as e: + print(f"❌ Error al leer el archivo: {e}") + return [] + + +def guardar_lineas(ruta, lineas): + """ + Guarda una lista de cadenas en un archivo de texto, + escribiendo una línea por cada elemento de la lista. + """ + try: + with open(ruta, "w", encoding="utf-8") as f: + for linea in lineas: + f.write(str(linea) + "\n") + print(f"✅ Archivo '{ruta}' guardado correctamente.") + except Exception as e: + print(f"❌ Error al guardar el archivo: {e}") \ No newline at end of file diff --git a/soluciones/06_archivos_y_modulos/sol_5_app.py b/soluciones/06_archivos_y_modulos/sol_5_app.py new file mode 100644 index 0000000..b7bcae0 --- /dev/null +++ b/soluciones/06_archivos_y_modulos/sol_5_app.py @@ -0,0 +1,9 @@ +import sol_4_utiles + +# Guardar líneas en un archivo +lineas = ["Gato", "Perro", "Conejo"] +sol_4_utiles.guardar_lineas("animales.txt", lineas) + +# Leer las líneas del archivo +resultado = sol_4_utiles.leer_lineas("animales.txt") +print(resultado) \ No newline at end of file diff --git a/soluciones/06_archivos_y_modulos/sol_6_error.py b/soluciones/06_archivos_y_modulos/sol_6_error.py new file mode 100644 index 0000000..2e077b8 --- /dev/null +++ b/soluciones/06_archivos_y_modulos/sol_6_error.py @@ -0,0 +1,7 @@ +try: + with open("archivo_inexistente.txt", "r", encoding="utf-8") as f: + contenido = f.read() + print(contenido) +except FileNotFoundError: + print("⚠️ No se encontró el archivo 'archivo_inexistente.txt'.") + print("💡 Asegúrate de que el archivo exista o revisa la ruta indicada.") \ No newline at end of file diff --git a/soluciones/06_archivos_y_modulos/sol_7_visitas.py b/soluciones/06_archivos_y_modulos/sol_7_visitas.py new file mode 100644 index 0000000..690759d --- /dev/null +++ b/soluciones/06_archivos_y_modulos/sol_7_visitas.py @@ -0,0 +1,25 @@ +from collections import Counter +import csv + +# Archivo de entrada y salida +entrada = "visitas.log" +salida = "resumen_visitas.csv" + +# Contador de visitas por día +conteo = Counter() + +# Leer el archivo línea por línea +with open(entrada, "r", encoding="utf-8") as f: + for linea in f: + # Tomamos la fecha (primer campo antes del espacio) + fecha = linea.split()[0] + conteo[fecha] += 1 + +# Guardar resumen en CSV +with open(salida, "w", encoding="utf-8", newline="") as f: + escritor = csv.writer(f) + escritor.writerow(["Fecha", "Visitas"]) + for fecha, visitas in sorted(conteo.items()): + escritor.writerow([fecha, visitas]) + +print("✅ Resumen guardado en 'resumen_visitas.csv'") diff --git a/soluciones/06_archivos_y_modulos/visitas.log b/soluciones/06_archivos_y_modulos/visitas.log new file mode 100644 index 0000000..56fdfbd --- /dev/null +++ b/soluciones/06_archivos_y_modulos/visitas.log @@ -0,0 +1,5 @@ +2025-11-05 10:12:03 Usuario1 +2025-11-05 11:45:19 Usuario2 +2025-11-06 09:05:42 Usuario3 +2025-11-06 12:30:01 Usuario4 +2025-11-06 13:10:27 Usuario5 \ No newline at end of file diff --git a/soluciones/07_mini_proyectos/gatos.csv b/soluciones/07_mini_proyectos/gatos.csv new file mode 100644 index 0000000..68abf04 --- /dev/null +++ b/soluciones/07_mini_proyectos/gatos.csv @@ -0,0 +1,6 @@ +peso,edad +3.4,2 +4.1,3 +2.8,1 +5.0,4 +4.8,5 \ No newline at end of file diff --git a/soluciones/07_mini_proyectos/logs.txt b/soluciones/07_mini_proyectos/logs.txt new file mode 100644 index 0000000..1bad5b7 --- /dev/null +++ b/soluciones/07_mini_proyectos/logs.txt @@ -0,0 +1,5 @@ +2025-11-09 10:00:01 INFO Iniciando sistema +2025-11-09 10:01:15 ERROR Fallo de conexión +2025-11-09 10:02:10 INFO Usuario conectado +2025-11-10 09:15:33 WARN Memoria alta +2025-11-10 09:16:02 ERROR Archivo no encontrado diff --git a/soluciones/07_mini_proyectos/resumen.txt b/soluciones/07_mini_proyectos/resumen.txt new file mode 100644 index 0000000..5de4748 --- /dev/null +++ b/soluciones/07_mini_proyectos/resumen.txt @@ -0,0 +1,4 @@ +📄 Resumen de logs (fecha) +------------------------------ +2025-11-09: 3 +2025-11-10: 2 \ No newline at end of file diff --git a/soluciones/07_mini_proyectos/sol_1_refugio.py b/soluciones/07_mini_proyectos/sol_1_refugio.py new file mode 100644 index 0000000..a7efcd2 --- /dev/null +++ b/soluciones/07_mini_proyectos/sol_1_refugio.py @@ -0,0 +1,117 @@ +import csv +import statistics + +def estadisticas_gatos(archivo="gatos.csv", lista=None, k=1, guardar=None): + """ + + Parámetros: + archivo (str): ruta a un archivo CSV con columnas 'peso' y 'edad'. + lista (list of tuples): lista [(peso, edad), ...]. + k (int): número de gatos más pesados a mostrar. + guardar (str): ruta opcional para guardar el resumen. + """ + + # --- Cargar datos --- + datos = [] + +#Si se pasó lista (no es None ni vacía), se asigna directamente a datos. Esto prioriza los datos en memoria sobre leer un archivo. + if lista: + datos = lista + +# Si lista no existe/pertenece y archivo tiene algún valor (habitualmente sí), entra en este bloque para intentar leer el CSV. + elif archivo: + try: + +# abre el archivo en modo lectura con codificación UTF-8 y garantiza su cierre automático al terminar. newline='' es la recomendación para trabajar con csv y evitar problemas con nuevas líneas. + with open(archivo, newline='', encoding="utf-8") as f: + +# crea un lector que interpreta cada fila del CSV como un diccionario donde las claves son los nombres de las columnas (por ejemplo 'peso' y 'edad'). + reader = csv.DictReader(f) + + # itera sobre cada fila del CSV. + for row in reader: + + # toma el valor de la columna 'peso' y lo convierte a float. Si el campo no es convertible a número, lanzará ValueError. Hace lo mismo para la columna 'edad'. + peso = float(row['peso']) + edad = float(row['edad']) + # añade la tupla (peso, edad) a la lista datos. + datos.append((peso, edad)) + + # Si al intentar abrir el archivo se lanza FileNotFoundError (archivo no existe en la ruta indicada), captura esa excepción, imprime un mensaje de error con f-string e inmediatamente return para terminar la función sin más procesamiento. + except FileNotFoundError: + print(f"❌ No se encontró el archivo '{archivo}'.") + return + + # si lista no se dio y archivo también es falsy (por ejemplo None o ""), entonces no hay fuente de datos válida. Se lanza un ValueError para indicar al llamador que debe proporcionar datos. + else: + raise ValueError("Debe proporcionar un archivo o una lista.") + +# Si tras intentar obtener datos la lista datos está vacía (por ejemplo, CSV vacío o lista=[]), se imprime una advertencia y se retorna, evitando dividir por cero o calcular estadísticas sin datos. + if not datos: + print("⚠️ No hay datos disponibles.") + return + + # --- Separar pesos y edades --- + + # Crea una lista pesos que contiene el primer elemento (peso) de cada tupla en datos. Es una list comprehension que desempaqueta cada tupla en p, _ (la _ es convencional para ignorar la edad aquí). + pesos = [p for p, _ in datos] + # Similar a la línea anterior, crea una lista edades con el segundo elemento (edad) de cada tupla. + edades = [e for _, e in datos] + + # --- Calcular estadísticas --- + + # conteo es el número total de gatos (filas/datos), usando la longitud de la lista. + conteo = len(datos) + # Calcula la media aritmética de los pesos con statistics.mean. + media_peso = statistics.mean(pesos) + # Calcula la media aritmética de las edades. + media_edad = statistics.mean(edades) + # Calcula la mediana de los pesos con statistics.median. + mediana_peso = statistics.median(pesos) + # Calcula la mediana de las edades. + mediana_edad = statistics.median(edades) + + ## Nota: si pesos o edades tiene datos no numéricos o está vacío, estas llamadas lanzarán excepciones; pero ya se comprobó que datos no está vacío, así que OK. + + # --- Top K más pesados --- + + # sorted(datos, key=lambda x: x[0], reverse=True) ordena datos de mayor a menor según el elemento x[0] (el peso). [:k] toma los primeros k elementos de esa lista ordenada, es decir, los k gatos más pesados. Asigna el resultado a top_k. + top_k = sorted(datos, key=lambda x: x[0], reverse=True)[:k] + + # --- Crear resumen --- + + # Construye una lista de cadenas resumen con líneas de texto que formarán el informe final. + # Usa f-strings para insertar variables. Ejemplo: {media_peso:.2f} formatea el número con 2 decimales. + # Incluye una cabecera, una línea separadora y las estadísticas calculadas. + resumen = [ + "📊 Estadísticas del refugio de gatos", + "----------------------------------", + f"Total de gatos: {conteo}", + f"Peso promedio: {media_peso:.2f}", + f"Edad promedio: {media_edad:.2f}", + f"Peso mediano: {mediana_peso:.2f}", + f"Edad mediana: {mediana_edad:.2f}", + "", + f"🐾 Top {k} gatos más pesados:" + ] + +# Itera sobre top_k usando enumerate para tener un índice i (empezando en 1). +# En cada iteración añade una línea al resumen con el ranking, el peso (dos decimales) y la edad (un decimal). Ejemplo: " 1. Peso: 4.50 kg | Edad: 3.0 años". + for i, (peso, edad) in enumerate(top_k, start=1): + resumen.append(f" {i}. Peso: {peso:.2f} kg | Edad: {edad:.1f} años") + +# Concatena las líneas de resumen en un único string resumen_texto, separadas por saltos de línea \n. + resumen_texto = "\n".join(resumen) + print(resumen_texto) + + # --- Guardar si se solicita --- + + # Si el parámetro guardar no es None ni vacío, abre (o crea) un archivo en modo escritura con codificación UTF-8 en la ruta indicada por guardar. + # Escribe resumen_texto en ese archivo. + # Imprime un mensaje de confirmación indicando la ruta donde se guardó. + if guardar: + with open(guardar, "w", encoding="utf-8") as f: + f.write(resumen_texto) + print(f"\n✅ Resumen guardado en '{guardar}'") + +estadisticas_gatos(archivo="gatos.csv", k=3) \ No newline at end of file diff --git a/soluciones/07_mini_proyectos/sol_2_sistema_tickets.py b/soluciones/07_mini_proyectos/sol_2_sistema_tickets.py new file mode 100644 index 0000000..92b4f10 --- /dev/null +++ b/soluciones/07_mini_proyectos/sol_2_sistema_tickets.py @@ -0,0 +1,126 @@ +import csv +import os + +# ------------------------------ +# Mini Sistema de Tickets +# ------------------------------ + +TICKETS = [] # Lista principal: cada ticket será un diccionario + +# define la función que carga tickets desde el CSV por defecto tickets.csv. archivo es el nombre/ruta del fichero. +def cargar_tickets(archivo="tickets.csv"): + + if not os.path.exists(archivo): # comprueba si el archivo no existe. + return # si no existe el archivo, sale de la función (no hace nada). + + # abre el archivo en modo lectura, asegura cierre automático, newline='' es la práctica recomendada con csv y se usa utf-8. + with open(archivo, newline='', encoding="utf-8") as f: + + #crea un lector que devuelve cada fila como diccionario usando la primera fila del CSV como nombres de columna (id, titulo, estado). + reader = csv.DictReader(f) + for row in reader: # itera por cada fila/diccionario leído. + + # añade a la lista global TICKETS un diccionario con: + # "id": int(row["id"]) — convierte el campo id del CSV a entero. + # "titulo": row["titulo"] — título tal cual. + # "estado": row["estado"] — estado tal cual (por ejemplo "abierto" o "cerrado"). + TICKETS.append({ + "id": int(row["id"]), + "titulo": row["titulo"], + "estado": row["estado"] + }) + +# define la función para escribir los tickets en archivo. +def guardar_tickets(archivo="tickets.csv"): + + with open(archivo, "w", newline='', encoding="utf-8") as f: # abre/crea el archivo en modo escritura (sobrescribe si ya existe). + fieldnames = ["id", "titulo", "estado"] # lista de columnas para el CSV. + writer = csv.DictWriter(f, fieldnames=fieldnames) # crea un escritor que acepta diccionarios con esas claves. + writer.writeheader() # escribe la fila de cabecera (id,titulo,estado). + for t in TICKETS: # itera cada ticket en la lista global. + writer.writerow(t) # escribe la fila correspondiente al diccionario t. + +# función que recibe un titulo para el ticket. +def crear_ticket(titulo): + + # Obtener el ID más alto existente o 0 si no hay tickets + # (t["id"] for t in TICKETS) → es una expresión generadora que recorre todos los tickets y extrae sus IDs. + # max(..., default=0) → obtiene el máximo ID actual. Si TICKETS está vacío, usa default=0 para evitar error y empezar desde 1. + # Luego le sumamos +1 para crear el nuevo ID. + nuevo_id = max((t["id"] for t in TICKETS), default=0) + 1 + ticket = {"id": nuevo_id, "titulo": titulo, "estado": "abierto"} # crea el diccionario del ticket con estado inicial "abierto". + TICKETS.append(ticket) # añade el ticket a la lista global. + print(f"✅ Ticket creado: #{nuevo_id} - '{titulo}'") # confirma por consola que se creó el ticket mostrando su id y título. + + +def listar_tickets(): # función que imprime la lista de tickets por consola. + + if not TICKETS: # si la lista está vacía: + print("⚠️ No hay tickets registrados.") + return + + print("\n🎟️ Lista de tickets:") + print("-" * 35) # línea separadora (35 guiones). + for t in TICKETS: # itera todos los tickets. + print(f"#{t['id']:03d} | {t['titulo']:<20} | Estado: {t['estado']}") # imprime cada ticket con formato: + # #{t['id']:03d} → id con 3 dígitos, relleno con ceros (p. ej. #001). + # {t['titulo']:<20} → título alineado a la izquierda en campo de 20 caracteres. + # Estado: {t['estado']} → imprime el estado. + print("-" * 35) + + +def cerrar_ticket(ticket_id): # cierra (marca como "cerrado") un ticket por su id. + """Cierra un ticket según su ID.""" + for t in TICKETS: + if t["id"] == ticket_id: # si encuentra el ticket con el id buscado: + if t["estado"] == "cerrado": # si ya estaba cerrado: imprime advertencia y return (sale). + print(f"⚠️ El ticket #{ticket_id} ya estaba cerrado.") + return + t["estado"] = "cerrado" # cambia el estado a "cerrado". + print(f"✅ Ticket #{ticket_id} cerrado correctamente.") + return + print(f"❌ No se encontró el ticket con ID {ticket_id}.") + + +# ------------------------------ +# Ejemplo de uso interactivo +# ------------------------------ + +def menu(): + cargar_tickets() # al iniciar el menú, intenta cargar tickets desde tickets.csv (si existe). + + while True: + print("\n--- 🎫 Sistema de Tickets ---") + print("1. Crear ticket") + print("2. Listar tickets") + print("3. Cerrar ticket") + print("4. Guardar y salir") + + opcion = input("Elige una opción: ") + + if opcion == "1": + titulo = input("Título del ticket: ") + crear_ticket(titulo) + + elif opcion == "2": + listar_tickets() + + elif opcion == "3": + try: + ticket_id = int(input("ID del ticket a cerrar: ")) + cerrar_ticket(ticket_id) + except ValueError: + print("⚠️ ID inválido.") + + elif opcion == "4": + guardar_tickets() + print("💾 Datos guardados. ¡Hasta luego!") + break + + else: + print("❌ Opción no válida, intenta de nuevo.") + + +# Ejecutar el menú solo si el archivo se ejecuta directamente +if __name__ == "__main__": + menu() diff --git a/soluciones/07_mini_proyectos/sol_3_parser_logs.py b/soluciones/07_mini_proyectos/sol_3_parser_logs.py new file mode 100644 index 0000000..60243c6 --- /dev/null +++ b/soluciones/07_mini_proyectos/sol_3_parser_logs.py @@ -0,0 +1,65 @@ +import os +from collections import Counter + +def parser_logs(archivo="logs.txt", modo="tipo", guardar="resumen.txt"): + """ + Analiza un archivo de logs simple y genera un resumen. + + Parámetros: + archivo (str): ruta al archivo de logs. + modo (str): "tipo" para contar por tipo (INFO, ERROR, etc.) + "fecha" para contar por fecha (primera columna). + guardar (str): ruta del archivo donde guardar el resumen. + """ + if not os.path.exists(archivo): + print(f"❌ No se encontró el archivo '{archivo}'.") + return + + conteo = Counter() + + with open(archivo, encoding="utf-8") as f: + for linea in f: + partes = linea.strip().split() + if not partes: + continue # saltar líneas vacías + + if modo == "tipo" and len(partes) >= 3: + tipo = partes[2] # ejemplo: "ERROR" en "2025-11-09 10:33:22 ERROR Falló conexión" + conteo[tipo] += 1 + + elif modo == "fecha" and len(partes) >= 1: + fecha = partes[0] # ejemplo: "2025-11-09" + conteo[fecha] += 1 + + if not conteo: + print("⚠️ No se encontraron datos válidos.") + return + + # --- Crear resumen --- + resumen_lineas = [ + f"📄 Resumen de logs ({modo})", + "-" * 30 + ] + for clave, cantidad in conteo.most_common(): + resumen_lineas.append(f"{clave}: {cantidad}") + + resumen_texto = "\n".join(resumen_lineas) + + print(resumen_texto) + + # --- Guardar --- + with open(guardar, "w", encoding="utf-8") as f: + f.write(resumen_texto) + + print(f"\n✅ Resumen guardado en '{guardar}'") + + +# ------------------------------ +# Ejemplo de uso +# ------------------------------ +if __name__ == "__main__": + # Cambia el modo según quieras: + # modo="tipo" → agrupa por tipo de log (INFO, ERROR, etc.) + # modo="fecha" → agrupa por fecha (primer campo) + parser_logs(archivo="logs.txt", modo="tipo") + # parser_logs("logs.txt", modo="fecha") diff --git a/soluciones/07_mini_proyectos/tickets.csv b/soluciones/07_mini_proyectos/tickets.csv new file mode 100644 index 0000000..c8351e7 --- /dev/null +++ b/soluciones/07_mini_proyectos/tickets.csv @@ -0,0 +1,6 @@ +id,titulo,estado +1,Error en login,cerrado +2,Mejora de interfaz,cerrado +3,Fresa,cerrado +4,Comprender Python,abierto +5,Errores manejados,cerrado