From 07f0049c93484ec7327a6d45e51352b695ae386a Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 27 Jan 2025 08:19:24 +0100
Subject: [PATCH 01/66] Ejercicios

---
 Ejercicios/00_helloworld_exercises.py | 59 +++++++++++++++++++++++++
 Ejercicios/01_variables_exercises.py  | 62 +++++++++++++++++++++++++++
 Ejercicios/02_operators_exercises.py  | 53 +++++++++++++++++++++++
 Ejercicios/03_strings_exercices.py    | 19 ++++++++
 Hello-Python.code-workspace           |  8 ++++
 5 files changed, 201 insertions(+)
 create mode 100644 Ejercicios/00_helloworld_exercises.py
 create mode 100644 Ejercicios/01_variables_exercises.py
 create mode 100644 Ejercicios/02_operators_exercises.py
 create mode 100644 Ejercicios/03_strings_exercices.py
 create mode 100644 Hello-Python.code-workspace

diff --git a/Ejercicios/00_helloworld_exercises.py b/Ejercicios/00_helloworld_exercises.py
new file mode 100644
index 0000000..926d6e0
--- /dev/null
+++ b/Ejercicios/00_helloworld_exercises.py
@@ -0,0 +1,59 @@
+# 1. Imprime "Â¡Hola Mundo!" por consola.
+
+print("Â¡Hola Mundo!")
+
+# 2. Escribe un comentario de una sola lÃ­nea explicando quÃ© hace el cÃ³digo del Ejercicio 1.
+
+# Este cÃ³digo imprime "Â¡Hola Mundo!" por consola.
+
+# 3. Imprime tu nombre y edad en la misma lÃ­nea utilizando la funciÃ³n print.
+
+print("Mi nombre es Brais y tengo", 37, "aÃ±os.")
+
+# 4. Usa la funciÃ³n type() para imprimir el tipo de dato de una cadena de texto, un nÃºmero entero y un nÃºmero decimal.
+
+print(type("Brais"))  # str
+print(type(37))       # int
+print(type(3.14))     # float
+
+# 5. Escribe un comentario en varias lÃ­neas explicando quÃ© son los tipos de datos en Python.
+
+"""
+En Python, los tipos de datos mÃ¡s comunes son:
+- str: para cadenas de texto
+- int: para nÃºmeros enteros
+- float: para nÃºmeros con decimales
+- bool: para valores booleanos (True/False)
+"""
+
+# 6. Imprime el resultado de concatenar dos cadenas de texto, por ejemplo: "Hola" y "Mundo".
+
+print("Hola" + " " + "Mundo")
+
+# 7. Crea una variable para almacenar tu nombre, otra para tu edad, e imprime ambas en la misma lÃ­nea.
+
+my_name = "Brais"
+my_age = 37
+print("Mi nombre es", my_name, "y tengo", my_age, "aÃ±os.")
+
+# 8. Escribe un programa que solicite al usuario su nombre y lo imprima junto con un saludo.
+
+user_name = input("Â¿CuÃ¡l es tu nombre? ")
+print("Â¡Hola", user_name + "!")
+
+# 9. Usa print() para mostrar el resultado de la suma de dos nÃºmeros enteros y el tipo de dato resultante.
+
+result = 5 + 10
+print("El resultado es:", result)
+print("El tipo de dato del resultado es:", type(result))
+
+# 10. Comenta el cÃ³digo del Ejercicio 9, y explica quÃ© hace cada lÃ­nea usando comentarios de una sola lÃ­nea.
+
+# Suma dos nÃºmeros enteros.
+result = 5 + 10
+
+# Imprime el resultado de la suma.
+print("El resultado es:", result)
+
+# Imprime el tipo de dato del resultado, que es 'int'.
+print("El tipo de dato del resultado es:", type(result))
\ No newline at end of file
diff --git a/Ejercicios/01_variables_exercises.py b/Ejercicios/01_variables_exercises.py
new file mode 100644
index 0000000..d1df4a9
--- /dev/null
+++ b/Ejercicios/01_variables_exercises.py
@@ -0,0 +1,62 @@
+# 1. Declara y asigna valores a las siguientes variables:
+# •	name: una cadena que contenga tu nombre.
+# •	age: un número entero que represente tu edad.
+# •	height: un número flotante que represente tu altura.
+# •	Imprime cada variable en una línea separada.
+
+name = "Brais"
+age = 37
+height = 1.77
+
+print(name)
+print(age)
+print(height)
+
+# 2. Convierte la variable edad de entero a cadena y concatenala con un texto que diga cuántos años tienes.
+
+print("Tengo " + str(age) + " años")
+
+# 3. Declara una variable booleana is_student que indique si eres estudiante o no. Usa True o False según corresponda e imprímela.
+
+is_student = True
+print(is_student)
+
+# 4. Usa la función len() para calcular cuántos caracteres tiene tu nombre completo, almacenado en una variable.
+
+full_name = "Brais Moure"
+print(len(full_name))
+
+# 5. Declara tres variables en una sola línea que representen tu nombre, apellido y ciudad de origen. Luego, imprime estos valores.
+
+name, surname, city = "Brais", "Moure", "Galicia"
+print(name, surname, city)
+
+# 6. Usa la función input() para solicitar al usuario su color favorito y almacénalo en una variable color. Luego, imprime el valor ingresado.
+
+color = input("¿Cuál es tu color favorito? ")
+print(color)
+
+# 7. Declara una variable fruit e inicialízala con un valor. Luego, cambia el valor de la fruta a otro diferente y vuelve a imprimirla.
+
+fruit = "Manzana"
+print(fruit)
+fruit = "Naranja"
+print(fruit)
+
+# 8. Convierte un número decimal, almacenado en la variable price, a un número entero y luego imprímelo.
+
+price = 9.99
+print(int(price))
+
+# 9. Declara una variable llamada address_len y almacena en ella la cantidad de caracteres de una dirección usando la función len(). Imprime el resultado.
+
+address = "Calle 123, Ciudad"
+address_len = len(address)
+print(address_len)
+
+# 10. Usa un tipo de dato forzado para declarar una variable phone, asegurándote de que siempre será un número. Luego, cambia su valor a un número diferente y verifica el tipo de la variable con type().
+
+phone: int = 123456789
+print(type(phone))
+phone = 987654321
+print(type(phone))
diff --git a/Ejercicios/02_operators_exercises.py b/Ejercicios/02_operators_exercises.py
new file mode 100644
index 0000000..fc87be4
--- /dev/null
+++ b/Ejercicios/02_operators_exercises.py
@@ -0,0 +1,53 @@
+# 1. Realiza las siguientes operaciones aritméticas:
+# •	Suma: 15 + 25
+# •	Resta: 50 - 22
+# •	Multiplicación: 8 * 7
+# •	División: 100 / 20
+
+print(15 + 25)
+print(50 - 22)
+print(8 * 7)
+print(100 / 20)
+
+# 2. Calcula el resto de la división de 37 entre 5 y almacénalo en una variable remainder. Luego imprímelo.
+
+remainder = 37 % 5
+print(remainder)
+
+# 3. Convierte el número 7 en una cadena de texto y concaténalo con la frase “ es mi número favorito”. Imprime el resultado.
+
+number = 7
+result = str(number) + " es mi número favorito"
+print(result)
+
+# 4. Repite la palabra “Python” 10 veces usando el operador de multiplicación para cadenas y luego imprímela.
+
+print("Python" * 10)
+
+# 5. Crea dos variables: a y b con los valores 12 y 8 respectivamente. Compara si a es mayor que b y almacena el resultado en una variable booleana resultado. Imprime el valor de resultado.
+
+a = 12
+b = 8
+result = a > b
+print(result)
+
+# 6. Compara dos cadenas de texto (“apple” y “banana”) usando los operadores > y < y explica cuál tiene mayor orden alfabético.
+
+print("apple" > "banana")
+print("apple" < "banana")
+
+# 7. Realiza una comparación lógica usando and para verificar si el número 10 es mayor que 5 y menor que 20. Imprime el resultado.
+
+print(10 > 5 and 10 < 20)
+
+# 8. Usa el operador or para verificar si el número 7 es menor que 3 o mayor que 5. Imprime el resultado.
+
+print(7 < 3 or 7 > 5)
+
+# 9. Aplica el operador not para invertir el resultado de la comparación 15 > 20. ¿Cuál es el resultado?
+
+print(not (15 > 20))
+
+# 10. Combina operadores aritméticos y lógicos: Verifica si el número resultante de la expresión (5 * 3) + 2 es mayor que 10 y menor que 20. Imprime el resultado.
+
+print((5 * 3) + 2 > 10 and (5 * 3) + 2 < 20)
diff --git a/Ejercicios/03_strings_exercices.py b/Ejercicios/03_strings_exercices.py
new file mode 100644
index 0000000..6e2ff6f
--- /dev/null
+++ b/Ejercicios/03_strings_exercices.py
@@ -0,0 +1,19 @@
+# 1. Declara una variable text con la frase “Aprendiendo Python” y luego imprime la longitud de la cadena usando len().
+
+# 2. Concatena dos cadenas: “Hola” y “Python”, y muestra el resultado en una sola línea.
+
+# 3. Crea una cadena que incluya un salto de línea, y luego imprímela para ver el resultado.
+
+# 4. Usa el formateo de cadenas con f-strings para imprimir tu nombre, apellido y edad en una cadena de texto.
+
+# 5. Desempaqueta los caracteres de la palabra “Python” en variables separadas y luego imprímelos uno por uno.
+
+# 6. Extrae un “slice” de la palabra “Programación” para obtener los caracteres desde la posición 3 hasta la 7.
+
+# 7. Invierte la cadena “Python” usando slicing y muestra el resultado.
+
+# 8. Convierte la cadena “aprendiendo python” en mayúsculas usando el método adecuado e imprímela.
+
+# 9. Cuenta cuántas veces aparece la letra “n” en la cadena “Programación en Python”.
+
+# 10. Verifica si la cadena “12345” es numérica usando el método adecuado e imprime el resultado.
diff --git a/Hello-Python.code-workspace b/Hello-Python.code-workspace
new file mode 100644
index 0000000..876a149
--- /dev/null
+++ b/Hello-Python.code-workspace
@@ -0,0 +1,8 @@
+{
+	"folders": [
+		{
+			"path": "."
+		}
+	],
+	"settings": {}
+}
\ No newline at end of file

From ff9039d62254f528b212535d96e8d4dabf2d496a Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 27 Jan 2025 08:41:06 +0100
Subject: [PATCH 02/66] =?UTF-8?q?M=C3=A1s=20Ejercicios?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Ejercicios/03_strings_exercices.py      | 27 +++++++++++++++++++++++++
 Ejercicios/04_lists_exercises.py        | 19 +++++++++++++++++
 Ejercicios/05_tuples_exercises.py       | 19 +++++++++++++++++
 Ejercicios/06_sets_exercises.py         | 19 +++++++++++++++++
 Ejercicios/07_dicts_exercises.py        | 19 +++++++++++++++++
 Ejercicios/08_conditionals_exercises.py | 19 +++++++++++++++++
 Ejercicios/09_loops_exercises.py        | 19 +++++++++++++++++
 Ejercicios/10_functions_exercises.py    | 19 +++++++++++++++++
 Ejercicios/11_classes_exercises.py      | 19 +++++++++++++++++
 Ejercicios/12_exceptions_exercises.py   | 19 +++++++++++++++++
 Ejercicios/13_modules_exercises.py      | 19 +++++++++++++++++
 11 files changed, 217 insertions(+)
 create mode 100644 Ejercicios/04_lists_exercises.py
 create mode 100644 Ejercicios/05_tuples_exercises.py
 create mode 100644 Ejercicios/06_sets_exercises.py
 create mode 100644 Ejercicios/07_dicts_exercises.py
 create mode 100644 Ejercicios/08_conditionals_exercises.py
 create mode 100644 Ejercicios/09_loops_exercises.py
 create mode 100644 Ejercicios/10_functions_exercises.py
 create mode 100644 Ejercicios/11_classes_exercises.py
 create mode 100644 Ejercicios/12_exceptions_exercises.py
 create mode 100644 Ejercicios/13_modules_exercises.py

diff --git a/Ejercicios/03_strings_exercices.py b/Ejercicios/03_strings_exercices.py
index 6e2ff6f..ef5ce76 100644
--- a/Ejercicios/03_strings_exercices.py
+++ b/Ejercicios/03_strings_exercices.py
@@ -1,19 +1,46 @@
 # 1. Declara una variable text con la frase “Aprendiendo Python” y luego imprime la longitud de la cadena usando len().
+text = "Aprendiendo Python"
+print(len(text))
 
 # 2. Concatena dos cadenas: “Hola” y “Python”, y muestra el resultado en una sola línea.
+text2 = "Hola " + "Python"
+print(text2)
 
 # 3. Crea una cadena que incluya un salto de línea, y luego imprímela para ver el resultado.
+text_salto = "Hola\nPython"
+print(text_salto)
 
 # 4. Usa el formateo de cadenas con f-strings para imprimir tu nombre, apellido y edad en una cadena de texto.
+nombre, apellido, edad = "Brais", "Moure", 37
+print(f"Mi nombre es {nombre} {apellido} y tengo {edad} años")
 
 # 5. Desempaqueta los caracteres de la palabra “Python” en variables separadas y luego imprímelos uno por uno.
+language_slice = "Python"
+a, b, c, d, e, f = language_slice
+print(a)
+print(b)
+print(c)
+print(d)
+print(e)
+print(f)
 
 # 6. Extrae un “slice” de la palabra “Programación” para obtener los caracteres desde la posición 3 hasta la 7.
+lenguaje = "Programación"
+language_slice = lenguaje[3:7]
+print(language_slice)
 
 # 7. Invierte la cadena “Python” usando slicing y muestra el resultado.
+reverse_language = lenguaje[::-1]
+print(reverse_language)
 
 # 8. Convierte la cadena “aprendiendo python” en mayúsculas usando el método adecuado e imprímela.
+cadena = "aprendiendo python"
+print(cadena.upper())
 
 # 9. Cuenta cuántas veces aparece la letra “n” en la cadena “Programación en Python”.
+cadena2 = "Programación en Python"
+print(cadena2.count("n"))
 
 # 10. Verifica si la cadena “12345” es numérica usando el método adecuado e imprime el resultado.
+cadena3 = "12345"
+print(cadena3.isnumeric())
diff --git a/Ejercicios/04_lists_exercises.py b/Ejercicios/04_lists_exercises.py
new file mode 100644
index 0000000..92f85dc
--- /dev/null
+++ b/Ejercicios/04_lists_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea una lista con los números del 1 al 5 e imprímela.
+
+# 2. Accede e imprime el tercer elemento de la lista [10, 20, 30, 40, 50].
+
+# 3. Agrega el número 6 al final de la lista [1, 2, 3, 4, 5] e imprímela.
+
+# 4. Inserta el número 15 en la posición 2 de la lista [10, 20, 30, 40, 50].
+
+# 5. Elimina el primer valor 30 de la lista [10, 20, 30, 30, 40, 50].
+
+# 6. Usa la función pop() para eliminar el último elemento de la lista [1, 2, 3, 4, 5] y almacénalo en una variable. Imprime la variable y la lista.
+
+# 7. Invierte la lista [100, 200, 300, 400, 500] e imprímela.
+
+# 8. Ordena la lista [3, 1, 4, 2, 5] en orden ascendente e imprímela.
+
+# 9. Concatena las listas [1, 2, 3] y [4, 5, 6] y almacena el resultado en una nueva lista. Imprime la lista resultante.
+
+# 10. Crea una sublista con los elementos de la lista [10, 20, 30, 40, 50] que van desde la posición 1 hasta la 3 (sin incluir la posición 3).
diff --git a/Ejercicios/05_tuples_exercises.py b/Ejercicios/05_tuples_exercises.py
new file mode 100644
index 0000000..12a1031
--- /dev/null
+++ b/Ejercicios/05_tuples_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea una tupla con los valores (10, 20, 30, 40, 50) e imprímela.
+
+# 2. Accede al segundo elemento de la tupla (100, 200, 300, 400, 500) y muéstralo.
+
+# 3. Intenta modificar el primer elemento de la tupla (1, 2, 3) a 10 y observa el resultado.
+
+# 4. Cuenta cuántas veces aparece el número 3 en la tupla (1, 2, 3, 3, 4, 5, 3).
+
+# 5. Encuentra el índice de la primera aparición de la cadena "Python" en la tupla ("Java", "Python", "JavaScript", "Python").
+
+# 6. Concatena dos tuplas: (1, 2, 3) y (4, 5, 6) e imprime la tupla resultante.
+
+# 7. Crea una subtupla con los elementos desde la posición 2 hasta la 4 (sin incluir la 4) de la tupla (10, 20, 30, 40, 50).
+
+# 8. Convierte la tupla ("rojo", "verde", "azul") en una lista, cambia el segundo elemento a "amarillo" y vuelve a convertirla en una tupla. Imprime la tupla resultante.
+
+# 9. Elimina una tupla llamada my_tuple usando del y luego intenta imprimirla para ver el resultado.
+
+# 10. Crea una tupla con un solo elemento (el número 100) e imprímela. Asegúrate de usar la sintaxis correcta para crear una tupla con un solo elemento.
diff --git a/Ejercicios/06_sets_exercises.py b/Ejercicios/06_sets_exercises.py
new file mode 100644
index 0000000..9d66cc6
--- /dev/null
+++ b/Ejercicios/06_sets_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea un set con los números del 1 al 5 e imprímelo.
+
+# 2. Añade el número 6 al set {1, 2, 3, 4, 5} e imprímelo.
+
+# 3. Intenta añadir el número 5 al set {1, 2, 3, 4, 5} nuevamente. ¿Qué sucede?
+
+# 4. Verifica si el número 3 está en el set {1, 2, 3, 4, 5} e imprime el resultado.
+
+# 5. Elimina el número 4 del set {1, 2, 3, 4, 5} e imprime el set resultante.
+
+# 6. Usa el método clear() para vaciar un set y luego imprime su longitud.
+
+# 7. Convierte el set {"manzana", "naranja", "plátano"} en una lista e imprime el primer elemento de la lista.
+
+# 8. Realiza la unión de dos sets: {1, 2, 3} y {4, 5, 6}, e imprime el set resultante.
+
+# 9. Calcula la diferencia entre los sets {1, 2, 3, 4} y {3, 4, 5, 6} e imprime el resultado.
+
+# 10. Elimina un set llamado my_set usando del y luego intenta imprimirlo para ver el resultado.
diff --git a/Ejercicios/07_dicts_exercises.py b/Ejercicios/07_dicts_exercises.py
new file mode 100644
index 0000000..42eb8db
--- /dev/null
+++ b/Ejercicios/07_dicts_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea un diccionario con las claves name, age, y country, asignando valores a cada una. Imprime el diccionario.
+
+# 2. Accede al valor de la clave name en el diccionario.
+
+# 3. Añade una nueva clave job con el valor "Programador" al diccionario del punto anterior. Imprime el diccionario actualizado.
+
+# 4. Modifica el valor de la clave age en el diccionario para que sea 38. Imprime el diccionario actualizado.
+
+# 5. Elimina la clave country del diccionario e imprime el diccionario resultante.
+
+# 6. Crea un diccionario donde las claves sean números del 1 al 5 y los valores sean sus cuadrados (ejemplo: 1: 1, 2: 4, ...).
+
+# 7. Verifica si la clave age está presente en el diccionario {"name": "Brais", "age": 37, "country": "Galicia"}.
+
+# 8. Imprime solo las claves del diccionario.
+
+# 9. Convierte las claves del diccionario en una lista e imprime la lista resultante.
+
+# 10. Crea un nuevo diccionario a partir de una lista de claves ["name", "age", "job"] usando fromkeys(), asignando a todas las claves el valor "Desconocido".
diff --git a/Ejercicios/08_conditionals_exercises.py b/Ejercicios/08_conditionals_exercises.py
new file mode 100644
index 0000000..42d2b5b
--- /dev/null
+++ b/Ejercicios/08_conditionals_exercises.py
@@ -0,0 +1,19 @@
+# 1. Escribe un programa que verifique si un número es positivo, negativo o cero.
+
+# 2. Solicita al usuario que ingrese su edad y muestra un mensaje indicando si es mayor de edad(18 años o más) o menor de edad.
+
+# 3. Escribe un programa que verifique si una cadena de texto está vacía y muestre un mensaje en consecuencia.
+
+# 4. Crea un programa que solicite dos números al usuario y compare cuál es mayor. Si son iguales, muestra un mensaje indicando la igualdad.
+
+# 5. Escribe un programa que verifique si un número es divisible por 3 y por 5 al mismo tiempo.
+
+# 6. Solicita al usuario que ingrese un número y verifica si es par o impar.
+
+# 7. Escribe un programa que determine si una persona puede votar en función de su edad(mayor o igual a 18). Si tiene 16 o 17 años, indica que puede votar con permiso especial.
+
+# 8. Crea un programa que solicite una contraseña al usuario y verifique si coincide con una contraseña predefinida. Si no coincide, muestra un mensaje de error.
+
+# 9. Escribe un programa que determine si un número está entre 10 y 20 (ambos incluidos).
+
+# 10. Escribe un programa que simule un semáforo: solicita al usuario que ingrese un color(rojo, amarillo, verde) y muestra un mensaje indicando si debe detenerse, estar alerta o avanzar.
diff --git a/Ejercicios/09_loops_exercises.py b/Ejercicios/09_loops_exercises.py
new file mode 100644
index 0000000..48c7842
--- /dev/null
+++ b/Ejercicios/09_loops_exercises.py
@@ -0,0 +1,19 @@
+# 1. Usa un bucle while para imprimir los números del 1 al 10.
+
+# 2. Usa un bucle for para recorrer la lista[10, 20, 30, 40, 50] e imprime cada número.
+
+# 3. Escribe un programa que use un bucle while para sumar los números del 1 al 100 e imprime el resultado.
+
+# 4. Escribe un bucle for que imprima cada carácter de la cadena "Python".
+
+# 5. Usa un bucle while para encontrar el primer número divisible por 7 entre 1 y 50.
+
+# 6. Usa un bucle for para recorrer el diccionario {"name": "Brais", "age": 37, "country": "Galicia"} e imprime las claves.
+
+# 7. Escribe un programa que use un bucle while para imprimir los números pares entre 1 y 20.
+
+# 8. Usa un bucle for con la función range() para imprimir los números del 1 al 10 en orden inverso.
+
+# 9. Escribe un programa que use un bucle for para contar cuántas veces aparece el número 30 en la lista[30, 10, 30, 20, 30, 40].
+
+# 10. Usa un bucle for para recorrer una lista de nombres y detener el bucle cuando se encuentre el nombre "Brais".
diff --git a/Ejercicios/10_functions_exercises.py b/Ejercicios/10_functions_exercises.py
new file mode 100644
index 0000000..5aad0cd
--- /dev/null
+++ b/Ejercicios/10_functions_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea una función llamada "personalized_greeting" que reciba un nombre como argumento e imprima "Hola, <nombre>". Si no se proporciona ningún nombre, debe saludar diciendo "Hola, desconocido".
+
+# 2. Escribe una función llamada "multiply" que reciba dos números como argumentos y retorne el resultado de multiplicarlos.
+
+# 3. Crea una función llamada "is_even" que reciba un número entero como argumento y retorne True si es par y False si es impar.
+
+# 4. Escribe una función llamada "convert_to_uppercase" que reciba una cadena de texto y la retorne en mayúsculas.
+
+# 5. Crea una función llamada "arbitrary_sum" que reciba un número arbitrario de números como argumentos y retorne la suma de todos ellos.
+
+# 6. Escribe una función llamada "generate_full_greeting" que reciba dos argumentos: nombre y apellido, y retorne el saludo completo "Hola, <nombre> <apellido>". Los argumentos deben ser pasados por clave.
+
+# 7. Crea una función llamada "power" que reciba dos números: base y exponente, y retorne el resultado de elevar la base al exponente.
+
+# 8. Escribe una función llamada "calculate_average" que reciba tres números y retorne su promedio.
+
+# 9. Crea una función llamada "count_characters" que reciba una cadena de texto y retorne el número de caracteres que contiene.
+
+# 10. Escribe una función llamada "display_messages" que reciba un número indefinido de cadenas y las imprima en mayúsculas, una por una, tal como se hizo en el archivo proporcionado.
diff --git a/Ejercicios/11_classes_exercises.py b/Ejercicios/11_classes_exercises.py
new file mode 100644
index 0000000..637a51b
--- /dev/null
+++ b/Ejercicios/11_classes_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea una clase llamada "Animal" que tenga una propiedad "species" y un método "make_sound" que imprima un sonido genérico.
+
+# 2. Modifica la clase "Animal" para que reciba la especie al crear un objeto y almacénala en una propiedad pública. Añade el método "make_sound" que imprima un sonido dependiendo de la especie.
+
+# 3. Crea una clase llamada "Car" con las propiedades públicas "brand" y "model". Además, debe tener una propiedad privada "_speed" que inicialmente será 0.
+
+# 4. Añade a la clase "Car" un método llamado "accelerate" que aumente la velocidad en 10 unidades. Añade también un método "brake" que reduzca la velocidad en 10 unidades. Asegúrate de que la velocidad no sea negativa.
+
+# 5. Crea una clase "Book" que tenga propiedades como "title" (público) y "author" (privado). Añade un método para obtener el autor y otro para cambiar el título del libro.
+
+# 6. Crea una clase "Estudiante" que tenga como propiedades su nombre, apellido y una lista de notas. Añade un método para calcular y devolver la nota media del estudiante.
+
+# 7. Crea una clase "BankAccount" con propiedades como "owner" y "balance". Añade métodos para depositar y retirar dinero, asegurándote de que no se pueda retirar más de lo que hay en la cuenta.
+
+# 8. Crea una clase "Point" que represente un punto en el espacio 2D con coordenadas "x" e "y". Añade un método que calcule la distancia entre dos puntos.
+
+# 9. Crea una clase "Employee" que tenga propiedades como "name", "hourly_wage" (pago por hora) y "hours_worked". Añade un método que calcule el pago total basado en las horas trabajadas y el salario por hora.
+
+# 10. Crea una clase "Store" que tenga una propiedad "inventory" (una lista de productos). Añade un método para agregar un producto al inventario y otro para mostrar todos los productos disponibles.
diff --git a/Ejercicios/12_exceptions_exercises.py b/Ejercicios/12_exceptions_exercises.py
new file mode 100644
index 0000000..ba03a68
--- /dev/null
+++ b/Ejercicios/12_exceptions_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea una función que intente dividir dos números proporcionados por el usuario. Usa try-except para capturar cualquier error de división (por ejemplo, división por cero).
+
+# 2. Crea una función que tome una cadena e intente convertirla en un número entero. Usa try-except para capturar cualquier error en la conversión.
+
+# 3. Crea una función que abra un archivo, lea su contenido y maneje posibles errores (por ejemplo, archivo no encontrado). Usa try-except para gestionar las operaciones de archivos de forma segura.
+
+# 4. Crea una función que realice múltiples operaciones (suma, resta, división, multiplicación) con dos números. Usa try-except-else-finally para manejar errores y asegurar que se imprima un mensaje final, independientemente de los errores.
+
+# 5. Crea una función que le pida al usuario su edad y lance un ValueError si la entrada no es un número entero positivo. Usa el manejo de excepciones para gestionar la entrada y lanzar excepciones personalizadas cuando sea necesario.
+
+# 6. Crea una función que intente acceder a un elemento de una lista por índice. Usa try-except para manejar el caso donde el índice esté fuera de rango.
+
+# 7. Crea una función que use try-except para manejar múltiples excepciones: ZeroDivisionError, ValueError y TypeError.
+
+# 8. Crea una función que simule una transacción. Lanza una excepción personalizada llamada InsufficientFundsError si el saldo es menor que la cantidad a retirar.
+
+# 9. Crea una función que intente convertir una lista de cadenas en enteros. Maneja cualquier error que surja cuando una cadena no pueda convertirse.
+
+# 10. Crea una función que calcule la raíz cuadrada de un número. Lanza un ValueError si el número es negativo.
diff --git a/Ejercicios/13_modules_exercises.py b/Ejercicios/13_modules_exercises.py
new file mode 100644
index 0000000..039be42
--- /dev/null
+++ b/Ejercicios/13_modules_exercises.py
@@ -0,0 +1,19 @@
+# 1. Crea un módulo llamado "calculator" que contenga funciones para sumar, restar, multiplicar y dividir dos números. Importa este módulo en otro archivo y usa sus funciones.
+
+# 2. Crea un módulo llamado "converter" que tenga funciones para convertir temperaturas entre Celsius y Fahrenheit. Escribe un programa que importe este módulo y realice conversiones.
+
+# 3. Crea un módulo que contenga una lista de nombres de estudiantes y una función que imprima todos los nombres. Importa este módulo en otro archivo y usa la función para mostrar la lista.
+
+# 4. Crea un módulo llamado "geometry" que tenga una función para calcular el área de un círculo y un cuadrado. Usa este módulo en otro archivo para calcular áreas.
+
+# 5. Escribe un módulo que contenga una función que acepte cualquier número de argumentos y devuelva su suma. Importa y usa la función en otro archivo.
+
+# 6. Crea un módulo que defina una clase llamada "Car" con propiedades como marca, modelo y año. Importa este módulo en otro archivo y crea una instancia de la clase "Car".
+
+# 7. Escribe un módulo que contenga funciones para leer y escribir en archivos de texto. Crea un programa que use estas funciones para escribir y leer datos.
+
+# 8. Crea un módulo llamado "statistics" que tenga funciones para calcular la media y la mediana de una lista de números. Usa este módulo para calcular estos valores en una lista dada.
+
+# 9. Crea un módulo que contenga una función para contar cuántas veces aparece una palabra en un texto. Escribe un programa que importe el módulo y lo use para contar palabras en una cadena.
+
+# 10 Crea un módulo llamado "dates" que contenga funciones para obtener la fecha actual y calcular la diferencia entre dos fechas. Usa este módulo en un programa para mostrar la fecha actual y la diferencia entre dos fechas específicas.

From ba34c0b9032676cf0579a511307c87db773bbea6 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 30 Jan 2025 08:32:14 +0100
Subject: [PATCH 03/66] Tuplas, Sets, Diccionarios

---
 Ejercicios/04_lists_exercises.py  | 24 +++++++++++++++++++++++
 Ejercicios/05_tuples_exercises.py | 32 +++++++++++++++++++++++++++++++
 Ejercicios/06_sets_exercises.py   | 28 +++++++++++++++++++++++++++
 Ejercicios/07_dicts_exercises.py  | 29 ++++++++++++++++++++++++++++
 4 files changed, 113 insertions(+)

diff --git a/Ejercicios/04_lists_exercises.py b/Ejercicios/04_lists_exercises.py
index 92f85dc..6b83f39 100644
--- a/Ejercicios/04_lists_exercises.py
+++ b/Ejercicios/04_lists_exercises.py
@@ -1,19 +1,43 @@
 # 1. Crea una lista con los números del 1 al 5 e imprímela.
+mi_lista = [1, 2, 3, 4, 5]
+print(mi_lista)
 
 # 2. Accede e imprime el tercer elemento de la lista [10, 20, 30, 40, 50].
+mi_lista2 = [10, 20, 30, 40, 50]
+print(mi_lista2[2])
 
 # 3. Agrega el número 6 al final de la lista [1, 2, 3, 4, 5] e imprímela.
+mi_lista.append(6)
+print(mi_lista)
 
 # 4. Inserta el número 15 en la posición 2 de la lista [10, 20, 30, 40, 50].
+mi_lista2.insert(1, 15)
+print(mi_lista2)
 
 # 5. Elimina el primer valor 30 de la lista [10, 20, 30, 30, 40, 50].
+mi_lista2.remove(30)
+print(mi_lista2)
 
 # 6. Usa la función pop() para eliminar el último elemento de la lista [1, 2, 3, 4, 5] y almacénalo en una variable. Imprime la variable y la lista.
+mi_lista.pop()
+print(mi_lista)
 
 # 7. Invierte la lista [100, 200, 300, 400, 500] e imprímela.
+mi_lista3 = [100, 200, 300, 400, 500]
+mi_lista3.reverse()
+print(mi_lista3)
 
 # 8. Ordena la lista [3, 1, 4, 2, 5] en orden ascendente e imprímela.
+mi_lista4 = [3, 1, 4, 2, 5]
+mi_lista4.sort()
+print(mi_lista4)
 
 # 9. Concatena las listas [1, 2, 3] y [4, 5, 6] y almacena el resultado en una nueva lista. Imprime la lista resultante.
+mi_lista5 = [1, 2, 3] + [4, 5, 6]
+print(mi_lista5)
 
 # 10. Crea una sublista con los elementos de la lista [10, 20, 30, 40, 50] que van desde la posición 1 hasta la 3 (sin incluir la posición 3).
+mi_lista6 = [10, 20, 30, 40, 50]
+mi_lista6 = mi_lista6[1:3]
+print(mi_lista6)
+
diff --git a/Ejercicios/05_tuples_exercises.py b/Ejercicios/05_tuples_exercises.py
index 12a1031..ddac024 100644
--- a/Ejercicios/05_tuples_exercises.py
+++ b/Ejercicios/05_tuples_exercises.py
@@ -1,19 +1,51 @@
 # 1. Crea una tupla con los valores (10, 20, 30, 40, 50) e imprímela.
+mi_tupla = (10, 20, 30, 40, 50)
+print(mi_tupla)
+print(type(mi_tupla))
 
 # 2. Accede al segundo elemento de la tupla (100, 200, 300, 400, 500) y muéstralo.
+mi_tupla = (100, 200, 300, 400, 500)
+print(mi_tupla[1])
 
 # 3. Intenta modificar el primer elemento de la tupla (1, 2, 3) a 10 y observa el resultado.
+mi_tupla = (1, 2, 3)
+mi_tupla = list(mi_tupla)
+mi_tupla[0] = 10
+mi_tupla = tuple(mi_tupla)
+print(mi_tupla)
 
 # 4. Cuenta cuántas veces aparece el número 3 en la tupla (1, 2, 3, 3, 4, 5, 3).
+mi_tupla = (1, 2, 3, 3, 4, 5, 3)
+print(mi_tupla.count(3))
 
 # 5. Encuentra el índice de la primera aparición de la cadena "Python" en la tupla ("Java", "Python", "JavaScript", "Python").
+mi_tupla = ("Java", "Python", "JavaScript", "Python")
+print(mi_tupla.index("Python"))
 
 # 6. Concatena dos tuplas: (1, 2, 3) y (4, 5, 6) e imprime la tupla resultante.
+my_tupla1 = (1, 2, 3)
+my_tupla2 = (4, 5, 6)
+my_tupla3 = my_tupla1 + my_tupla2
+print(my_tupla3)
 
 # 7. Crea una subtupla con los elementos desde la posición 2 hasta la 4 (sin incluir la 4) de la tupla (10, 20, 30, 40, 50).
+my_tupla1 = (10, 20, 30, 40, 50)
+my_tupla2 = my_tupla1[1:4]
+print(my_tupla2)
 
 # 8. Convierte la tupla ("rojo", "verde", "azul") en una lista, cambia el segundo elemento a "amarillo" y vuelve a convertirla en una tupla. Imprime la tupla resultante.
+my_tupla1 = ("rojo", "verde", "azul")
+my_tupla1 = list(my_tupla1)
+my_tupla1[1] = "amarillo"
+my_tupla1 = tuple(my_tupla1)
+print(my_tupla1)
 
 # 9. Elimina una tupla llamada my_tuple usando del y luego intenta imprimirla para ver el resultado.
+my_tupla1 = (1, 2, 3)
+del my_tupla1
+# print(my_tupla1) # NameError: name 'my_tupla1' is not defined
 
 # 10. Crea una tupla con un solo elemento (el número 100) e imprímela. Asegúrate de usar la sintaxis correcta para crear una tupla con un solo elemento.
+my_tupla1 = (100,)
+print(my_tupla1)
+print(type(my_tupla1))
diff --git a/Ejercicios/06_sets_exercises.py b/Ejercicios/06_sets_exercises.py
index 9d66cc6..9f808d8 100644
--- a/Ejercicios/06_sets_exercises.py
+++ b/Ejercicios/06_sets_exercises.py
@@ -1,19 +1,47 @@
 # 1. Crea un set con los números del 1 al 5 e imprímelo.
+my_set = {1, 2, 3, 4, 5}
+print(my_set)
+print(type(my_set))
 
 # 2. Añade el número 6 al set {1, 2, 3, 4, 5} e imprímelo.
+my_set.add(6)
+print(my_set)
 
 # 3. Intenta añadir el número 5 al set {1, 2, 3, 4, 5} nuevamente. ¿Qué sucede?
+my_set.add(5)
+print(my_set)
 
 # 4. Verifica si el número 3 está en el set {1, 2, 3, 4, 5} e imprime el resultado.
+my_set = {1, 2, 3, 4, 5}
+print(3 in my_set)
 
 # 5. Elimina el número 4 del set {1, 2, 3, 4, 5} e imprime el set resultante.
+my_set.remove(4)
+print(my_set)
 
 # 6. Usa el método clear() para vaciar un set y luego imprime su longitud.
+my_set.clear()
+print(len(my_set))
 
 # 7. Convierte el set {"manzana", "naranja", "plátano"} en una lista e imprime el primer elemento de la lista.
+my_set = {"manzana", "naranja", "plátano"}
+my_list = list(my_set)
+print(my_list[0])
+print(type(my_list))
 
 # 8. Realiza la unión de dos sets: {1, 2, 3} y {4, 5, 6}, e imprime el set resultante.
+my_set1 = {1, 2, 3}
+my_set2 = {4, 5, 6}
+my_set3 = my_set1.union(my_set2)
+print(my_set3)
 
 # 9. Calcula la diferencia entre los sets {1, 2, 3, 4} y {3, 4, 5, 6} e imprime el resultado.
+my_set1 = {1, 2, 3, 4}
+my_set2 = {3, 4, 5, 6}
+my_set3 = my_set1.difference(my_set2)
+print(my_set3)
 
 # 10. Elimina un set llamado my_set usando del y luego intenta imprimirlo para ver el resultado.
+my_set = {1, 2, 3}
+del my_set
+# print(my_set) # NameError: name 'my_set' is not defined
diff --git a/Ejercicios/07_dicts_exercises.py b/Ejercicios/07_dicts_exercises.py
index 42eb8db..a38e02f 100644
--- a/Ejercicios/07_dicts_exercises.py
+++ b/Ejercicios/07_dicts_exercises.py
@@ -1,19 +1,48 @@
 # 1. Crea un diccionario con las claves name, age, y country, asignando valores a cada una. Imprime el diccionario.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+print(my_dict)
 
 # 2. Accede al valor de la clave name en el diccionario.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+print(my_dict["name"])
 
 # 3. Añade una nueva clave job con el valor "Programador" al diccionario del punto anterior. Imprime el diccionario actualizado.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+my_dict["job"] = "Programador"
+print(my_dict)
 
 # 4. Modifica el valor de la clave age en el diccionario para que sea 38. Imprime el diccionario actualizado.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+my_dict["age"] = 38
+print(my_dict)
 
 # 5. Elimina la clave country del diccionario e imprime el diccionario resultante.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+my_dict.pop("country")
+print(my_dict)
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+del my_dict["country"]
+print(my_dict)
+
 
 # 6. Crea un diccionario donde las claves sean números del 1 al 5 y los valores sean sus cuadrados (ejemplo: 1: 1, 2: 4, ...).
+my_dict = {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
+print(my_dict)
 
 # 7. Verifica si la clave age está presente en el diccionario {"name": "Brais", "age": 37, "country": "Galicia"}.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+print("age" in my_dict)
 
 # 8. Imprime solo las claves del diccionario.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}    
+print(my_dict.keys())
 
 # 9. Convierte las claves del diccionario en una lista e imprime la lista resultante.
+my_dict = {"name": "Brais", "age": 37, "country": "Galicia"}
+my_list = list(my_dict.keys())
+print(my_list)
 
 # 10. Crea un nuevo diccionario a partir de una lista de claves ["name", "age", "job"] usando fromkeys(), asignando a todas las claves el valor "Desconocido".
+my_dict = dict.fromkeys(["name", "age", "job"], "Desconocido")
+print(my_dict)
+print(type(my_dict))

From 1ca52ac5f46138a9e3335fcdc61e53213f775584 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 30 Jan 2025 08:36:07 +0100
Subject: [PATCH 04/66] Nokey

---
 Ejercicios/07_dicts_exercises.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Ejercicios/07_dicts_exercises.py b/Ejercicios/07_dicts_exercises.py
index a38e02f..1918cb8 100644
--- a/Ejercicios/07_dicts_exercises.py
+++ b/Ejercicios/07_dicts_exercises.py
@@ -46,3 +46,5 @@
 my_dict = dict.fromkeys(["name", "age", "job"], "Desconocido")
 print(my_dict)
 print(type(my_dict))
+
+print(my_dict["Nokey"])

From ba55079d6c4178801b385846248aa7ab9eb0e202 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 31 Jan 2025 10:17:34 +0100
Subject: [PATCH 05/66] Conditionals

---
 Ejercicios/08_conditionals_exercises.py | 64 +++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/Ejercicios/08_conditionals_exercises.py b/Ejercicios/08_conditionals_exercises.py
index 42d2b5b..e096b10 100644
--- a/Ejercicios/08_conditionals_exercises.py
+++ b/Ejercicios/08_conditionals_exercises.py
@@ -1,19 +1,83 @@
 # 1. Escribe un programa que verifique si un número es positivo, negativo o cero.
+my_number = int(input("Introduce un número: "))
+if my_number > 0:
+    print("El número es positivo.")
+elif my_number < 0:
+    print("El número es negativo.")
+else:
+    print("El número es cero.")
 
 # 2. Solicita al usuario que ingrese su edad y muestra un mensaje indicando si es mayor de edad(18 años o más) o menor de edad.
+my_edad = int(input("Introduce tu edad: "))
+if my_edad >= 18:
+    print("Eres mayor de edad.")
+else:
+    print("Eres menor de edad.")
 
 # 3. Escribe un programa que verifique si una cadena de texto está vacía y muestre un mensaje en consecuencia.
+my_cadena = input("Introduce una cadena de texto: ")
+if len(my_cadena) == 0:
+    print("La cadena de texto está vacía.")
+else:
+    print("La cadena de texto no está vacía.")
 
 # 4. Crea un programa que solicite dos números al usuario y compare cuál es mayor. Si son iguales, muestra un mensaje indicando la igualdad.
+my_number1 = int(input("Introduce un número: "))
+my_number2 = int(input("Introduce otro número: "))
+if my_number1 > my_number2:
+    print("El primer número es mayor que el segundo.")
+elif my_number1 < my_number2:
+    print("El segundo número es mayor que el primero.")
+else:
+    print("Los números son iguales.")
 
 # 5. Escribe un programa que verifique si un número es divisible por 3 y por 5 al mismo tiempo.
+my_number = int(input("Introduce un número: "))
+if my_number % 3 == 0 and my_number % 5 == 0:
+    print("El número es divisible por 3 y por 5.")
+else:
+    print("El número no es divisible por 3 y por 5.")
 
 # 6. Solicita al usuario que ingrese un número y verifica si es par o impar.
+my_number = int(input("Introduce un número: "))
+if my_number % 2 == 0:
+    print("El número es par.")
+else:
+    print("El número es impar.")
 
 # 7. Escribe un programa que determine si una persona puede votar en función de su edad(mayor o igual a 18). Si tiene 16 o 17 años, indica que puede votar con permiso especial.
+my_edad = int(input("Introduce tu edad: "))
+if my_edad >= 18:
+    print("Puedes votar.")
+elif my_edad == 16 or my_edad == 17:
+    print("Puedes votar con permiso especial.")
+else:
+    print("No puedes votar.")
 
 # 8. Crea un programa que solicite una contraseña al usuario y verifique si coincide con una contraseña predefinida. Si no coincide, muestra un mensaje de error.
+my_user = input("Introduce un usuario: ")
+my_password = input("Introduce una contraseña: ")
+if my_user == "admin" and my_password == "1234":
+    print("Acceso concedido.")
+else:
+    print("Acceso denegado.")
+
 
 # 9. Escribe un programa que determine si un número está entre 10 y 20 (ambos incluidos).
+my_number = int(input("Introduce un número: "))
+if my_number >= 10 and my_number <= 20:
+    print("El número está entre 10 y 20.")
+else:
+    print("El número no está entre 10 y 20.")
+
 
 # 10. Escribe un programa que simule un semáforo: solicita al usuario que ingrese un color(rojo, amarillo, verde) y muestra un mensaje indicando si debe detenerse, estar alerta o avanzar.
+my_color = input("Introduce un color (rojo, amarillo, verde): ")
+if my_color == "rojo":
+    print("Detente.")
+elif my_color == "amarillo":
+    print("Estate alerta.")
+elif my_color == "verde":
+    print("Avanza.")
+else:
+    print("Color no válido.")

From 2a44b9e5c640b4b2eaff3ebb8130609d0f40cc6d Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 4 Feb 2025 08:50:04 +0100
Subject: [PATCH 06/66] Clases y Excepciones

---
 Ejercicios/09_loops_exercises.py      |  43 ++++++-
 Ejercicios/10_functions_exercises.py  |  31 +++++
 Ejercicios/11_classes_exercises.py    | 157 ++++++++++++++++++++++++++
 Ejercicios/12_exceptions_exercises.py |  89 +++++++++++++++
 4 files changed, 319 insertions(+), 1 deletion(-)

diff --git a/Ejercicios/09_loops_exercises.py b/Ejercicios/09_loops_exercises.py
index 48c7842..c45114d 100644
--- a/Ejercicios/09_loops_exercises.py
+++ b/Ejercicios/09_loops_exercises.py
@@ -1,19 +1,60 @@
 # 1. Usa un bucle while para imprimir los números del 1 al 10.
+i = 1
+while i <= 10:
+    print(i)
+    i += 1
 
 # 2. Usa un bucle for para recorrer la lista[10, 20, 30, 40, 50] e imprime cada número.
+my_lista = [10, 20, 30, 40, 50]
+for number in my_lista:
+    print(number)
 
 # 3. Escribe un programa que use un bucle while para sumar los números del 1 al 100 e imprime el resultado.
+i_number = 1
+sum_number = 0
+while i_number <= 100:
+    sum_number += i_number
+    i_number += 1
+print(sum_number)
 
 # 4. Escribe un bucle for que imprima cada carácter de la cadena "Python".
+for letter in "Python":
+    print(letter)   
 
 # 5. Usa un bucle while para encontrar el primer número divisible por 7 entre 1 y 50.
+i_number = 1
+while i_number <= 50:
+    if i_number % 7 == 0:
+        print(i_number)
+        break
+    i_number += 1
 
 # 6. Usa un bucle for para recorrer el diccionario {"name": "Brais", "age": 37, "country": "Galicia"} e imprime las claves.
+for key in {"name": "Brais", "age": 37, "country": "Galicia"}:
+    print(key)  
 
 # 7. Escribe un programa que use un bucle while para imprimir los números pares entre 1 y 20.
+i_number = 1
+while i_number <= 20:
+    if i_number % 2 == 0:
+        print(i_number)
+    i_number += 1
 
 # 8. Usa un bucle for con la función range() para imprimir los números del 1 al 10 en orden inverso.
+for i in range(10, 0, -1):
+    print(i)
 
 # 9. Escribe un programa que use un bucle for para contar cuántas veces aparece el número 30 en la lista[30, 10, 30, 20, 30, 40].
-
+count_number = 0
+for number in [30, 10, 30, 20, 30, 40]:
+    if number == 30:
+        count_number += 1
+print(count_number)   
+        
 # 10. Usa un bucle for para recorrer una lista de nombres y detener el bucle cuando se encuentre el nombre "Brais".
+my_lista = ["Ana", "Carlos", "Brais", "David", "Elena"]
+for name in my_lista:
+    print(name)
+    if name == "Brais":
+        break
+    
\ No newline at end of file
diff --git a/Ejercicios/10_functions_exercises.py b/Ejercicios/10_functions_exercises.py
index 5aad0cd..b8e1526 100644
--- a/Ejercicios/10_functions_exercises.py
+++ b/Ejercicios/10_functions_exercises.py
@@ -1,19 +1,50 @@
 # 1. Crea una función llamada "personalized_greeting" que reciba un nombre como argumento e imprima "Hola, <nombre>". Si no se proporciona ningún nombre, debe saludar diciendo "Hola, desconocido".
+def personalized_greeting(name="desconocido"):
+    print(f"Hola, {name}")
+personalized_greeting("Brais")
 
 # 2. Escribe una función llamada "multiply" que reciba dos números como argumentos y retorne el resultado de multiplicarlos.
+def multiply(number1, number2):
+    return number1 * number2
+print(multiply(2, 3))
 
 # 3. Crea una función llamada "is_even" que reciba un número entero como argumento y retorne True si es par y False si es impar.
+def is_even(number):
+    return number % 2 == 0
+print(is_even(4))
 
 # 4. Escribe una función llamada "convert_to_uppercase" que reciba una cadena de texto y la retorne en mayúsculas.
+def convert_to_uppercase(text):
+    return text.upper() 
+print(convert_to_uppercase("Hola"))
 
 # 5. Crea una función llamada "arbitrary_sum" que reciba un número arbitrario de números como argumentos y retorne la suma de todos ellos.
+def arbitrary_sum(*numbers):
+    return sum(numbers)
+print(arbitrary_sum(1, 2, 3, 4, 5))
 
 # 6. Escribe una función llamada "generate_full_greeting" que reciba dos argumentos: nombre y apellido, y retorne el saludo completo "Hola, <nombre> <apellido>". Los argumentos deben ser pasados por clave.
+def generate_full_greeting(name, surname):
+    return f"Hola, {name} {surname}"
+print(generate_full_greeting(name="Brais", surname="Moure"))
 
 # 7. Crea una función llamada "power" que reciba dos números: base y exponente, y retorne el resultado de elevar la base al exponente.
+def power(base, exponent):
+    return base ** exponent
+print(power(2, 3))
 
 # 8. Escribe una función llamada "calculate_average" que reciba tres números y retorne su promedio.
+def calculate_average(number1, number2, number3):
+    return (number1 + number2 + number3) / 3
+print(calculate_average(2, 4, 6))
 
 # 9. Crea una función llamada "count_characters" que reciba una cadena de texto y retorne el número de caracteres que contiene.
+def count_characters(text):
+    return len(text)
+print(count_characters("Hola"))
 
 # 10. Escribe una función llamada "display_messages" que reciba un número indefinido de cadenas y las imprima en mayúsculas, una por una, tal como se hizo en el archivo proporcionado.
+def display_messages(*messages):
+    for message in messages:
+        print(message.upper())
+display_messages("Hola", "Adiós", "Buenos días")
diff --git a/Ejercicios/11_classes_exercises.py b/Ejercicios/11_classes_exercises.py
index 637a51b..e7bcc53 100644
--- a/Ejercicios/11_classes_exercises.py
+++ b/Ejercicios/11_classes_exercises.py
@@ -1,19 +1,176 @@
 # 1. Crea una clase llamada "Animal" que tenga una propiedad "species" y un método "make_sound" que imprima un sonido genérico.
+class Animal:
+    def __init__(self, species):
+        self.species = species
+
+    def make_sound(self):
+        print("Sonido genérico")
+print("Ejercicio 1:")
+animal = Animal("Perro")
+print(animal.species)
+animal.make_sound()
 
 # 2. Modifica la clase "Animal" para que reciba la especie al crear un objeto y almacénala en una propiedad pública. Añade el método "make_sound" que imprima un sonido dependiendo de la especie.
+class Animal:
+    def __init__(self, species):
+        self.species = species
+
+    def make_sound(self):
+        if self.species == "Perro":
+            print("Guau guau")
+        elif self.species == "Gato":
+            print("Miau miau")
+        else:
+            print("Sonido genérico")
+print("Ejercicio 2:")
+animal = Animal("Perro")
+print(animal.species)
+animal.make_sound()
+animal = Animal("Gato")
+print(animal.species)
+animal.make_sound()
+animal = Animal("Pájaro")
+print(animal.species)
+animal.make_sound()
 
 # 3. Crea una clase llamada "Car" con las propiedades públicas "brand" y "model". Además, debe tener una propiedad privada "_speed" que inicialmente será 0.
+class Car:
+    def __init__(self, brand, model):
+        self.brand = brand
+        self.model = model
+        self._speed = 0
+print("Ejercicio 3:")
+car = Car("Ford", "Focus")
+print(car.brand)
+print(car.model)
+print(car._speed)
 
 # 4. Añade a la clase "Car" un método llamado "accelerate" que aumente la velocidad en 10 unidades. Añade también un método "brake" que reduzca la velocidad en 10 unidades. Asegúrate de que la velocidad no sea negativa.
+class Car:
+    def __init__(self, brand, model):
+        self.brand = brand
+        self.model = model
+        self._speed = 0
+
+    def accelerate(self):
+        self._speed += 10
+
+    def brake(self):
+        self._speed -= 10
+        if self._speed < 0:
+            self._speed = 0
+print("Ejercicio 4:")
+car = Car("Ford", "Focus")
+print(car._speed)
+car.accelerate()
+print(car._speed)
+car.brake()
+print(car._speed)
+car.brake()
+print(car._speed)
 
 # 5. Crea una clase "Book" que tenga propiedades como "title" (público) y "author" (privado). Añade un método para obtener el autor y otro para cambiar el título del libro.
+class Book:
+    def __init__(self, title, author):
+        self.title = title
+        self._author = author
+
+    def get_author(self):
+        return self._author
+
+    def set_title(self, title):
+        self.title = title      
+print("Ejercicio 5:")
+book = Book("El Quijote", "Miguel de Cervantes")
+print(book.title)
+print(book.get_author())
+book.set_title("Don Quijote")
+print(book.title)
 
 # 6. Crea una clase "Estudiante" que tenga como propiedades su nombre, apellido y una lista de notas. Añade un método para calcular y devolver la nota media del estudiante.
+class Student:
+    def __init__(self, name, surname, grades):
+        self.name = name
+        self.surname = surname
+        self.grades = grades
+
+    def calculate_average(self):
+        return sum(self.grades) / len(self.grades)
+print("Ejercicio 6:")
+student = Student("Brais", "Moure", [7, 8, 9])
+print(student.name)
+print(student.surname)
+print(student.grades)
+print(student.calculate_average())
 
 # 7. Crea una clase "BankAccount" con propiedades como "owner" y "balance". Añade métodos para depositar y retirar dinero, asegurándote de que no se pueda retirar más de lo que hay en la cuenta.
+class BankAccount:
+    def __init__(self, owner, balance):
+        self.owner = owner
+        self.balance = balance
+
+    def deposit(self, amount):
+        self.balance += amount
+
+    def withdraw(self, amount):
+        if amount <= self.balance:
+            self.balance -= amount
+        else:
+            print("No hay suficiente saldo.")       
+print("Ejercicio 7:")
+account = BankAccount("Brais", 1000)
+print(account.owner)
+print(account.balance)
+account.deposit(500)
+print(account.balance)
+account.withdraw(200)
+print(account.balance)
+account.withdraw(2000)
+print(account.balance)
 
 # 8. Crea una clase "Point" que represente un punto en el espacio 2D con coordenadas "x" e "y". Añade un método que calcule la distancia entre dos puntos.
+class Point:
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+
+    def calculate_distance(self, point):
+        return ((self.x - point.x) ** 2 + (self.y - point.y) ** 2) ** 0.5       
+print("Ejercicio 8:")
+point1 = Point(0, 0)
+point2 = Point(3, 4)
+print(point1.calculate_distance(point2))
 
 # 9. Crea una clase "Employee" que tenga propiedades como "name", "hourly_wage" (pago por hora) y "hours_worked". Añade un método que calcule el pago total basado en las horas trabajadas y el salario por hora.
+class Employee:
+    def __init__(self, name, hourly_wage, hours_worked):
+        self.name = name
+        self.hourly_wage = hourly_wage
+        self.hours_worked = hours_worked
+
+    def calculate_payment(self):
+        return self.hourly_wage * self.hours_worked     
+print("Ejercicio 9:")
+employee = Employee("Brais", 10, 40)
+print(employee.name)
+print(employee.hourly_wage)
+print(employee.hours_worked)
+print(employee.calculate_payment())
 
 # 10. Crea una clase "Store" que tenga una propiedad "inventory" (una lista de productos). Añade un método para agregar un producto al inventario y otro para mostrar todos los productos disponibles.
+class Store:
+    def __init__(self):
+        self.inventory = []
+
+    def add_product(self, product):
+        self.inventory.append(product)
+
+    def show_inventory(self):
+        for product in self.inventory:
+            print(product)      
+print("Ejercicio 10:")
+store = Store()
+store.add_product("Leche")
+store.add_product("Pan")
+store.add_product("Huevos")
+store.show_inventory()
diff --git a/Ejercicios/12_exceptions_exercises.py b/Ejercicios/12_exceptions_exercises.py
index ba03a68..d4cf2d9 100644
--- a/Ejercicios/12_exceptions_exercises.py
+++ b/Ejercicios/12_exceptions_exercises.py
@@ -1,19 +1,108 @@
 # 1. Crea una función que intente dividir dos números proporcionados por el usuario. Usa try-except para capturar cualquier error de división (por ejemplo, división por cero).
+def divide_numbers():
+    try:
+        number1 = int(input("Introduce un número: "))
+        number2 = int(input("Introduce otro número: "))
+        result = number1 / number2
+        print(f"El resultado de la división es {result}")
+    except ZeroDivisionError:
+        print("No se puede dividir por cero.")
+divide_numbers()
 
 # 2. Crea una función que tome una cadena e intente convertirla en un número entero. Usa try-except para capturar cualquier error en la conversión.
+def convert_to_integer(text):
+    try:
+        number = int(text)
+        print(f"El número es {number}")
+    except ValueError:
+        print("No se pudo convertir la cadena en un número entero.")
+convert_to_integer("Hola")
 
 # 3. Crea una función que abra un archivo, lea su contenido y maneje posibles errores (por ejemplo, archivo no encontrado). Usa try-except para gestionar las operaciones de archivos de forma segura.
+def read_file(file_name):
+    try:
+        with open(file_name, "r") as file:
+            print(file.read())
+    except FileNotFoundError:
+        print("El archivo no se encontró.")
+read_file("file.txt")
 
 # 4. Crea una función que realice múltiples operaciones (suma, resta, división, multiplicación) con dos números. Usa try-except-else-finally para manejar errores y asegurar que se imprima un mensaje final, independientemente de los errores.
+def operations(number1, number2):
+    try:
+        print(f"Suma: {number1 + number2}")
+        print(f"Resta: {number1 - number2}")
+        print(f"División: {number1 / number2}")
+        print(f"Multiplicación: {number1 * number2}")
+    except ZeroDivisionError:
+        print("No se puede dividir por cero.")
+    else:
+        print("Operaciones realizadas con éxito.")
+    finally:
+        print("Operaciones finalizadas.")
+operations(10, 0)
 
 # 5. Crea una función que le pida al usuario su edad y lance un ValueError si la entrada no es un número entero positivo. Usa el manejo de excepciones para gestionar la entrada y lanzar excepciones personalizadas cuando sea necesario.
+def get_age():
+    try:
+        age = int(input("Introduce tu edad: "))
+        if age < 0:
+            raise ValueError("La edad debe ser un número entero positivo.")
+        print(f"Tu edad es {age}")
+    except ValueError as e:
+        print(e)
+get_age()
 
 # 6. Crea una función que intente acceder a un elemento de una lista por índice. Usa try-except para manejar el caso donde el índice esté fuera de rango.
+def get_element(my_list, index):
+    try:
+        print(my_list[index])
+    except IndexError:
+        print("El índice está fuera de rango.")
+get_element([1, 2, 3], 3)
 
 # 7. Crea una función que use try-except para manejar múltiples excepciones: ZeroDivisionError, ValueError y TypeError.
+def multiple_exceptions():
+    try:
+        number = int(input("Introduce un número: "))
+        result = 10 / number
+        print(result)
+    except ZeroDivisionError:
+        print("No se puede dividir por cero.")
+    except ValueError:
+        print("La entrada no es un número válido.")
+    except TypeError:
+        print("Error de tipo.")
+multiple_exceptions()
 
 # 8. Crea una función que simule una transacción. Lanza una excepción personalizada llamada InsufficientFundsError si el saldo es menor que la cantidad a retirar.
+def transaction(balance, amount):
+    class InsufficientFundsError(Exception):
+        pass
+    try:
+        if amount > balance:
+            raise InsufficientFundsError("Saldo insuficiente.")
+        balance -= amount
+        print(f"Transacción exitosa. Saldo restante: {balance}")
+    except InsufficientFundsError as e:
+        print(e)
+transaction(100, 200)
 
 # 9. Crea una función que intente convertir una lista de cadenas en enteros. Maneja cualquier error que surja cuando una cadena no pueda convertirse.
+def convert_to_integers(my_list):
+    try:
+        integers = [int(value) for value in my_list]
+        print(integers)
+    except ValueError:
+        print("Error al convertir las cadenas en enteros.")
+convert_to_integers(["1", "2", "3", "a"])
 
 # 10. Crea una función que calcule la raíz cuadrada de un número. Lanza un ValueError si el número es negativo.
+def calculate_square_root(number):
+    if number < 0:
+        raise ValueError("No se puede calcular la raíz cuadrada de un número negativo.")
+    return number ** 0.5
+try:
+    print(calculate_square_root(-1))
+except ValueError as e:
+    print(e)    

From c2ec6dbf56e77fed935d5249f864a17865b15b87 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 5 Feb 2025 09:34:32 +0100
Subject: [PATCH 07/66] Modules

---
 Basic/13_modules.py                |  2 +-
 Ejercicios/13_modules_exercises.py | 52 ++++++++++++++++++++++++++++++
 Ejercicios/archivos.py             | 10 ++++++
 Ejercicios/calculator.py           | 13 ++++++++
 Ejercicios/cars.py                 | 11 +++++++
 Ejercicios/converter.py            |  3 ++
 Ejercicios/dates.py                | 10 ++++++
 Ejercicios/geometry.py             | 10 ++++++
 Ejercicios/lista_nombres.py        |  9 ++++++
 Ejercicios/operaciones.py          |  9 ++++++
 Ejercicios/statistics.py           | 12 +++++++
 11 files changed, 140 insertions(+), 1 deletion(-)
 create mode 100644 Ejercicios/archivos.py
 create mode 100644 Ejercicios/calculator.py
 create mode 100644 Ejercicios/cars.py
 create mode 100644 Ejercicios/converter.py
 create mode 100644 Ejercicios/dates.py
 create mode 100644 Ejercicios/geometry.py
 create mode 100644 Ejercicios/lista_nombres.py
 create mode 100644 Ejercicios/operaciones.py
 create mode 100644 Ejercicios/statistics.py

diff --git a/Basic/13_modules.py b/Basic/13_modules.py
index c6c2657..5910039 100644
--- a/Basic/13_modules.py
+++ b/Basic/13_modules.py
@@ -2,7 +2,7 @@
 
 ### Modules ###
 
-from math import pi as PI_VALUE
+from math import pi as PI_VALUE #Crea un alias
 import math
 from my_module import sumValue, printValue
 import my_module
diff --git a/Ejercicios/13_modules_exercises.py b/Ejercicios/13_modules_exercises.py
index 039be42..2a7bde3 100644
--- a/Ejercicios/13_modules_exercises.py
+++ b/Ejercicios/13_modules_exercises.py
@@ -1,19 +1,71 @@
 # 1. Crea un módulo llamado "calculator" que contenga funciones para sumar, restar, multiplicar y dividir dos números. Importa este módulo en otro archivo y usa sus funciones.
+import calculator
+
+print(calculator.sum(2, 3))
+print(calculator.subtract(5, 3))
+print(calculator.multiply(2, 3))
+print(calculator.divide(6, 3))
+
 
 # 2. Crea un módulo llamado "converter" que tenga funciones para convertir temperaturas entre Celsius y Fahrenheit. Escribe un programa que importe este módulo y realice conversiones.
+import converter
+
+print(converter.celsius_to_fahrenheit(0))
+print(converter.celsius_to_fahrenheit(100))
+
 
 # 3. Crea un módulo que contenga una lista de nombres de estudiantes y una función que imprima todos los nombres. Importa este módulo en otro archivo y usa la función para mostrar la lista.
+import lista_nombres
+
+lista_nombres.print_students()
 
 # 4. Crea un módulo llamado "geometry" que tenga una función para calcular el área de un círculo y un cuadrado. Usa este módulo en otro archivo para calcular áreas.
 
+import geometry
+
+print(geometry.circle_area(5))
+print(geometry.square_area(5))
+
+
 # 5. Escribe un módulo que contenga una función que acepte cualquier número de argumentos y devuelva su suma. Importa y usa la función en otro archivo.
+import operaciones
+
+print(operaciones.sum(1, 2, 3, 4, 5))
+
 
 # 6. Crea un módulo que defina una clase llamada "Car" con propiedades como marca, modelo y año. Importa este módulo en otro archivo y crea una instancia de la clase "Car".
 
+import cars
+
+car = cars.Car("Toyota", "Corolla", 2020)
+print(car.brand)
+print(car.model)
+print(car.year)
+
 # 7. Escribe un módulo que contenga funciones para leer y escribir en archivos de texto. Crea un programa que use estas funciones para escribir y leer datos.
 
+import archivos as ficheros
+
+ficheros.write_file("data.txt", "Hello, world!")
+print(ficheros.read_file("data.txt"))
+
+
 # 8. Crea un módulo llamado "statistics" que tenga funciones para calcular la media y la mediana de una lista de números. Usa este módulo para calcular estos valores en una lista dada.
+import statistics
+
+numbers = [1, 2, 3, 4, 5]
+print(statistics.mean(numbers))
+print(statistics.median(numbers))
 
 # 9. Crea un módulo que contenga una función para contar cuántas veces aparece una palabra en un texto. Escribe un programa que importe el módulo y lo use para contar palabras en una cadena.
 
+import operaciones
+
+text = "Hello, world! Hello, Python!"
+print(operaciones.count_words(text, "Hello"))
+
 # 10 Crea un módulo llamado "dates" que contenga funciones para obtener la fecha actual y calcular la diferencia entre dos fechas. Usa este módulo en un programa para mostrar la fecha actual y la diferencia entre dos fechas específicas.
+import dates    
+
+print(dates.get_current_date())
+print(dates.get_date_diff("2021-01-01", "2021-12-31"))
diff --git a/Ejercicios/archivos.py b/Ejercicios/archivos.py
new file mode 100644
index 0000000..00f0ae5
--- /dev/null
+++ b/Ejercicios/archivos.py
@@ -0,0 +1,10 @@
+# 7. Escribe un módulo que contenga funciones para leer y escribir en archivos de texto. Crea un programa que use estas funciones para escribir y leer datos.
+
+def write_file(file_name, data):
+    with open(file_name, "w") as file:
+        file.write(data)
+
+def read_file(file_name):
+    with open(file_name, "r") as file:
+        return file.read()
+    
diff --git a/Ejercicios/calculator.py b/Ejercicios/calculator.py
new file mode 100644
index 0000000..56ef731
--- /dev/null
+++ b/Ejercicios/calculator.py
@@ -0,0 +1,13 @@
+# 1. Crea un módulo llamado "calculator" que contenga funciones para sumar, restar, multiplicar y dividir dos números.
+
+def sum(number1, number2):
+    return number1 + number2    
+
+def subtract(number1, number2):
+    return number1 - number2
+
+def multiply(number1, number2):
+    return number1 * number2
+
+def divide(number1, number2):
+    return number1 / number2
diff --git a/Ejercicios/cars.py b/Ejercicios/cars.py
new file mode 100644
index 0000000..ff2c9f1
--- /dev/null
+++ b/Ejercicios/cars.py
@@ -0,0 +1,11 @@
+# 6. Crea un módulo que defina una clase llamada "Car" con propiedades como marca, modelo y año.
+
+class Car:
+    def __init__(self, brand, model, year):
+        self.brand = brand
+        self.model = model
+        self.year = year
+
+    def __str__(self):
+        return f"{self.brand} {self.model} ({self.year})"
+
diff --git a/Ejercicios/converter.py b/Ejercicios/converter.py
new file mode 100644
index 0000000..8377bc7
--- /dev/null
+++ b/Ejercicios/converter.py
@@ -0,0 +1,3 @@
+# Crea un módulo llamado "converter" que tenga funciones para convertir temperaturas entre Celsius y Fahrenheit.
+def celsius_to_fahrenheit(celsius):
+    return celsius * 9/5 + 32
diff --git a/Ejercicios/dates.py b/Ejercicios/dates.py
new file mode 100644
index 0000000..c95481a
--- /dev/null
+++ b/Ejercicios/dates.py
@@ -0,0 +1,10 @@
+# 10 Crea un módulo llamado "dates" que contenga funciones para obtener la fecha actual y calcular la diferencia entre dos fechas.
+
+import datetime
+
+def current_date():
+    return datetime.date.today()
+
+def days_between_dates(date1, date2):
+    return abs((date2 - date1).days)
+
diff --git a/Ejercicios/geometry.py b/Ejercicios/geometry.py
new file mode 100644
index 0000000..90e4188
--- /dev/null
+++ b/Ejercicios/geometry.py
@@ -0,0 +1,10 @@
+# 4. Crea un módulo llamado "geometry" que tenga una función para calcular el área de un círculo y un cuadrado.
+
+import math
+
+def circle_area(radius):
+    return math.pi * radius ** 2
+
+def square_area(side):
+    return side ** 2
+
diff --git a/Ejercicios/lista_nombres.py b/Ejercicios/lista_nombres.py
new file mode 100644
index 0000000..fb491e8
--- /dev/null
+++ b/Ejercicios/lista_nombres.py
@@ -0,0 +1,9 @@
+# 3. Crea un módulo que contenga una lista de nombres de estudiantes y una función que imprima todos los nombres.
+
+students = ["Alice", "Bob", "Charlie", "David", "Eve"]
+
+def print_students():
+    for student in students:
+        print(student)
+
+    
\ No newline at end of file
diff --git a/Ejercicios/operaciones.py b/Ejercicios/operaciones.py
new file mode 100644
index 0000000..b85cb75
--- /dev/null
+++ b/Ejercicios/operaciones.py
@@ -0,0 +1,9 @@
+# 5. Escribe un módulo que contenga una función que acepte cualquier número de argumentos y devuelva su suma. 
+
+def sum(*numbers):
+    return sum(numbers) 
+
+# 9. Crea un módulo que contenga una función para contar cuántas veces aparece una palabra en un texto.
+
+def count_words(text, word):
+    return text.count(word)
diff --git a/Ejercicios/statistics.py b/Ejercicios/statistics.py
new file mode 100644
index 0000000..6a98c3f
--- /dev/null
+++ b/Ejercicios/statistics.py
@@ -0,0 +1,12 @@
+# 8. Crea un módulo llamado "statistics" que tenga funciones para calcular la media y la mediana de una lista de números.
+
+def mean(numbers):
+    return sum(numbers) / len(numbers)
+
+def median(numbers):
+    numbers.sort()
+    n = len(numbers)
+    mid = n // 2
+    if n % 2 == 0:
+        return (numbers[mid - 1] + numbers[mid]) / 2
+    return numbers[mid]

From 11b889f5a43a55528591d5d981f6f50fa133cde2 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 12 Mar 2025 08:23:31 +0100
Subject: [PATCH 08/66] Estrucuturas de Datos

---
 Logic/01.Sintaxis.py                       | 36 +++++++++++++
 Logic/02.OperadoresYEstrucuturasControl.py | 12 +++++
 Logic/03.Funciones.py                      | 26 +++++++++
 Logic/04.EstructurasDatos.py               | 63 ++++++++++++++++++++++
 4 files changed, 137 insertions(+)
 create mode 100644 Logic/01.Sintaxis.py
 create mode 100644 Logic/02.OperadoresYEstrucuturasControl.py
 create mode 100644 Logic/03.Funciones.py
 create mode 100644 Logic/04.EstructurasDatos.py

diff --git a/Logic/01.Sintaxis.py b/Logic/01.Sintaxis.py
new file mode 100644
index 0000000..05f7b1c
--- /dev/null
+++ b/Logic/01.Sintaxis.py
@@ -0,0 +1,36 @@
+# https://python.org
+
+# Comentario en una línea
+
+"""
+Comentario en
+varias líneas
+"""
+
+'''
+Otro forma
+de comentario en 
+varias líneas
+'''
+
+my_variable = "Hola Mundo"
+my_entero = 5
+my_decimal = 1.5
+my_complejo = 3 + 1j
+my_booleano = True
+my_texto = "Mi cadena de texto"
+my_array = [1, 2, 3, 4, 5]
+my_object = {
+    "key": "value",
+    "key2": "value2"
+}
+my_function = print
+
+#Por convención las constantes se escriben en mayúsculas
+#Pero realmente no existen como tal, son mutables.
+MY_CONSTANT = "Mi constante" 
+print(MY_CONSTANT)
+
+print(type(my_variable))
+print("Hola Python")
+print('Hola Python')
\ No newline at end of file
diff --git a/Logic/02.OperadoresYEstrucuturasControl.py b/Logic/02.OperadoresYEstrucuturasControl.py
new file mode 100644
index 0000000..9b78d7c
--- /dev/null
+++ b/Logic/02.OperadoresYEstrucuturasControl.py
@@ -0,0 +1,12 @@
+# Ejercicio Operadores + Estrucuturas de Control
+
+my_number = 10
+
+while my_number < 56:
+    if (my_number % 2 == 0) and (my_number != 16) and (my_number % 3 != 0):
+        print(my_number)
+    my_number += 1
+
+for number in range(10, 56):
+    if number % 2 == 0 and number != 16 and number % 3 != 0:
+        print(number)
\ No newline at end of file
diff --git a/Logic/03.Funciones.py b/Logic/03.Funciones.py
new file mode 100644
index 0000000..1170019
--- /dev/null
+++ b/Logic/03.Funciones.py
@@ -0,0 +1,26 @@
+def imprimir_numeros(texto1, texto2) -> int:
+    num_veces = 0;
+    texto_devolver = texto1 + texto2;
+    for number in range(1, 101):
+        texto_devolver = validar_numero(number, texto1, texto2);
+        if texto_devolver != "":
+            print(texto_devolver);
+            num_veces+=1;
+    return num_veces;
+
+def validar_numero(num, texto1, texto2):
+    texto_devolver = "";
+    es_multiplo_3 = num % 3 == 0;
+    es_multiplo_5 = num % 5 == 0;
+
+    if es_multiplo_3:
+        texto_devolver = texto1;
+    if es_multiplo_5:
+        texto_devolver = texto2;
+    if es_multiplo_3 and es_multiplo_5:
+        texto_devolver = texto1 + " " + texto2;
+    return texto_devolver;
+
+
+print(f"Se han impreso {imprimir_numeros("Fizz", "Buzz")} veces los textos");
+
diff --git a/Logic/04.EstructurasDatos.py b/Logic/04.EstructurasDatos.py
new file mode 100644
index 0000000..e05817f
--- /dev/null
+++ b/Logic/04.EstructurasDatos.py
@@ -0,0 +1,63 @@
+# Este es un ejemplo de una agenda simple, donde se pueden añadir, modificar, buscar y eliminar contactos.
+
+def mi_agenda():
+
+    agenda = {}     # Esta es la agenda como tal
+    def up_numero():        # Esta es la funcion para añadir/modificar un contacto
+        numero = input("Introduce numero de contacto:")
+        if numero.isdigit() and len(numero) > 0 and len(numero) <= 10:
+            agenda[name] = numero
+            print("Contacto añadido.")
+        else:
+            print("Solo se aceptan numeros de hasta 10 digitos.")
+    
+    while True:     # De esta manera se mantiene la funcion en bucle
+
+        # Se inicia imprimiendo el menu
+
+        print("")
+        print("1 - Buscar contacto")
+        print("2 - Agregar Contacto")
+        print("3 - Actualizar contacto")
+        print("4 - Eliminar contactos")
+        print("5 - Salir")
+        print("6 - Ver agenda")
+
+        option = input("Elige la opcion deseada:")      # Input se utiliza para poder interactuar con terminal
+
+        match option:       # Se puede usar if, elif y else, pero en python existe match
+            case "1":
+                name = input("Introduce el nombre de contacto:")
+                if name in agenda:
+                    print(
+                        f"El numero de {name} es {agenda[name]}")
+                else:
+                    print(f"El nombre {name} no existe en la agenda.")
+            case "2":
+                name = input("Introduce nombre de contacto:")
+                up_numero()
+            case "3":
+                name = input("Introduce el nombre de contacto que desea actualizar:")
+                if name in agenda:
+                    up_numero()
+                else:
+                    print(f"El nombre {name} no existe en la agenda.")
+
+            case "4":
+                name = input("Introduce el nombre de contacto que desea eliminar:")
+                if name in agenda:
+                    del agenda[name]
+                    print("El contacto se ha borrado.")
+                else:
+                    print(f"El nombre {name} no existe en la agenda.")
+            case "5":
+                print("Saliendo")
+                break
+            case "6":
+                    print(agenda)
+            case _:
+                print("Opcion invalida. Elige del 1 al 6")
+
+
+mi_agenda()
+

From bec29633d01d87d33e78bd44548bc6af9680aaf3 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 14 Mar 2025 08:38:57 +0100
Subject: [PATCH 09/66] Ejercicio Cadenas

---
 Logic/05.Cadenas.py | 48 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 Logic/05.Cadenas.py

diff --git a/Logic/05.Cadenas.py b/Logic/05.Cadenas.py
new file mode 100644
index 0000000..bacc4c5
--- /dev/null
+++ b/Logic/05.Cadenas.py
@@ -0,0 +1,48 @@
+#función palindromo
+def palindromo (cadena1, cadena2) -> str:
+    if cadena1.upper() == cadena2.upper()[::-1]:
+        return "Es un palindromo"
+    return "No es un palindromo"
+
+print(palindromo("oso", "perro"))
+print(palindromo("oso", "oso"))
+
+#función anagrama
+def anagrama (cadena1, cadena2) -> str:
+    if sorted(cadena1.upper()) == sorted(cadena2.upper()):
+        return "Es un anagrama"
+    return "No es un anagrama"
+print(anagrama("pozo", "oso"))
+print(anagrama("pozo", "zopo"))
+
+#función isograma
+def isograma (cadena) -> str:
+    cadena_sin_espacios = cadena.replace(" ", "")
+    cadena_sin_repetidos = "".join(set(cadena_sin_espacios))
+    if cadena_sin_espacios == cadena_sin_repetidos:
+        return "Es un isograma"
+    return "No es un isograma"
+
+print(isograma("oso"))
+print(isograma("murcielago"))
+
+# Unificada
+
+def check (word: str, word2: str) -> str:
+    #palindromo
+    print(f"¿{word} es un palindromo? {word.upper() == word2.upper()[::-1]}")
+
+    #anagrama
+    print(f"¿{word} es un anagrama de {word2}? {sorted(word.upper()) == sorted(word2.upper())}")
+
+    #isograma
+    word_sin_espacios = word.replace(" ", "")
+    word_sin_repetidos = "".join(set(word_sin_espacios))
+    print(f"¿{word} es un isograma? {word_sin_espacios == word_sin_repetidos}") 
+
+check("oso", "oso")
+check("oso", "perro")
+check("pozo", "oso")
+check("pozo", "zopo")
+check("oso", "oso")
+check("murcielago", "murcielago")

From ec511f33ab5a7e88f783914753455b4d7e41c799 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 19 Mar 2025 07:40:43 +0100
Subject: [PATCH 10/66] ValoryReferencia y Recursividad

---
 ...py => 02.OperadoresYEstructurasControl.py} |  0
 Logic/06.ValoryReferencia.py                  | 30 +++++++++++++++++++
 Logic/07.Recursividad.py                      | 21 +++++++++++++
 3 files changed, 51 insertions(+)
 rename Logic/{02.OperadoresYEstrucuturasControl.py => 02.OperadoresYEstructurasControl.py} (100%)
 create mode 100644 Logic/06.ValoryReferencia.py
 create mode 100644 Logic/07.Recursividad.py

diff --git a/Logic/02.OperadoresYEstrucuturasControl.py b/Logic/02.OperadoresYEstructurasControl.py
similarity index 100%
rename from Logic/02.OperadoresYEstrucuturasControl.py
rename to Logic/02.OperadoresYEstructurasControl.py
diff --git a/Logic/06.ValoryReferencia.py b/Logic/06.ValoryReferencia.py
new file mode 100644
index 0000000..2111695
--- /dev/null
+++ b/Logic/06.ValoryReferencia.py
@@ -0,0 +1,30 @@
+valor1 = 3
+valor2 = 5
+
+def programValor(paramValor1: int, paramValor2:int):
+    tempValor = paramValor2
+    paramValor2 = paramValor1
+    paramValor1 = tempValor
+
+print("Programa por Valor:")
+print(f"valor1 {valor1}, valor2 {valor2}")
+programValor(valor1,valor2)
+print(f"valor1 {valor1}, valor2 {valor2}")
+
+valorRef1 = [3]
+valorRef2 = [5]
+
+
+def programReferencia(paramValor1: list, paramValor2:list):
+    tempValor = paramValor2
+    paramValor2 = paramValor1
+    paramValor1 = tempValor
+    paramValor1.append(10)
+    paramValor2.append(20)
+
+
+print("Programa por Referencia:")
+print(f"valor1 {valorRef1}, valor2 {valorRef2}")
+programReferencia(valorRef1, valorRef2)
+print(f"valor1 {valorRef1}, valor2 {valorRef2}")
+
diff --git a/Logic/07.Recursividad.py b/Logic/07.Recursividad.py
new file mode 100644
index 0000000..4fdcdf1
--- /dev/null
+++ b/Logic/07.Recursividad.py
@@ -0,0 +1,21 @@
+# Función que calcula el factorial de un número
+def factorial(number:int) -> int:
+    if number == 0:
+        return 1
+    else:
+        return number * factorial(number-1) 
+
+print(factorial(5))
+
+# Funcion que calcula el valor de un elemento concreto (según su posición) en la sucesión de Fibonacci
+def fibonacci(number:int) -> int:
+    if number == 0:
+        return 0
+    elif number == 1:
+        return 1
+    else:
+        return fibonacci(number-1) + fibonacci(number-2)
+
+print(fibonacci(5))
+  
+    
\ No newline at end of file

From 136e4e6592607da842adf9dc6ac9b348bdcea3ba Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 19 Mar 2025 07:57:11 +0100
Subject: [PATCH 11/66] PilasYColas

---
 Logic/08.PilasYColas.py | 131 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 131 insertions(+)
 create mode 100644 Logic/08.PilasYColas.py

diff --git a/Logic/08.PilasYColas.py b/Logic/08.PilasYColas.py
new file mode 100644
index 0000000..ee46f63
--- /dev/null
+++ b/Logic/08.PilasYColas.py
@@ -0,0 +1,131 @@
+# Utilizando una pila y cadenas de texto, simula el mecanismo adelante/atrás de un navegador web. Crea un programa en el que puedes navegar a una página o indicarle que te quieresdesplazar aledante o atrás, mostrando en cada caso el nombre de la web
+# Las palabras "adelante", "atras" desencadena esta acción, el resto se interpreta como el nombre de una nueva web.
+
+# Importamos la librería de pilas
+from collections import deque
+
+# Creamos una pila vacía
+pila = deque()
+
+# Creamos una variable para almacenar la página actual
+
+pagina_actual = ""
+# Creamos un bucle infinito
+while True:
+    # Pedimos al usuario que introduzca un comando
+    comando = input("Introduce un comando: ")
+    # Si el comando es "atras"
+    if comando == "atras":
+        # Si la pila no está vacía
+        if len(pila) > 0:
+            # Sacamos el último elemento de la pila
+            pagina_actual = pila.pop()
+            # Mostramos la página actual
+            print("Página actual:", pagina_actual)
+        # Si la pila está vacía
+        else:
+            # Mostramos un mensaje de error
+            print("No hay páginas para retroceder")
+    # Si el comando es "adelante"
+    elif comando == "adelante":
+        # Si la página actual no está vacía
+        if pagina_actual != "":
+            # Añadimos la página actual a la pila
+            pila.append(pagina_actual)
+            # Mostramos un mensaje de error
+            print("No hay páginas para avanzar")
+    # Si el comando no es "atras" ni "adelante"
+    else:
+        # Si la página actual no está vacía
+        if pagina_actual != "":
+            # Añadimos la página actual a la pila
+            pila.append(pagina_actual)
+        # Actualizamos la página actual con el comando introducido
+        pagina_actual = comando
+        # Mostramos la página actual
+        print("Página actual:", pagina_actual)
+
+# Ejecuta el programa y prueba a navegar entre diferentes páginas web utilizando los comandos "adelante" y "atras".
+# Introduce el nombre de una página web para navegar a ella.
+# Por ejemplo, puedes probar a introducir los siguientes comandos:
+# google.com
+# youtube.com
+# facebook.com
+
+# ¿Qué ocurre si introduces el comando "adelante" cuando no hay ninguna página actual?
+# ¿Qué ocurre si introduces el comando "atras" cuando no hay ninguna página en la pila?
+# ¿Qué ocurre si introduces el comando "atras" después de haber introducido una página web?
+
+# Respuestas:
+# Si introduces el comando "adelante" cuando no hay ninguna página actual, el programa mostrará el mensaje "No hay páginas para avanzar".
+# Si introduces el comando "atras" cuando no hay ninguna página en la pila, el programa mostrará el mensaje "No hay páginas para retroceder".
+# Si introduces el comando "atras" después de haber introducido una página web, la página actual se moverá a la pila y se mostrará la página anterior.
+
+
+# Utilizando colas y cadenas de texto, simula el mecanismo de una cola de impresión. Crea un programa en el que puedes añadir trabajos a la cola, mostrar el trabajo que se está imprimiendo y eliminar el trabajo que se ha impreso.
+# Las palabras "imprimir", "siguiente" y "eliminar" desencadenan estas acciones, el resto se interpreta como un trabajo a añadir a la cola.
+
+# Importamos la librería de colas
+from collections import deque
+
+# Creamos una cola vacía
+cola = deque()
+
+# Creamos un bucle infinito
+while True:
+    # Pedimos al usuario que introduzca un comando
+    comando = input("Introduce un comando: ")
+    # Si el comando es "imprimir"
+    if comando == "imprimir":
+        # Si la cola no está vacía
+        if len(cola) > 0:
+            # Sacamos el primer
+            trabajo = cola.popleft()
+            # Mostramos el trabajo que se está imprimiendo
+            print("Imprimiendo:", trabajo)
+        # Si la cola está vacía
+        else:
+            # Mostramos un mensaje de error
+            print("No hay trabajos para imprimir")
+    # Si el comando es "siguiente"
+    elif comando == "siguiente":
+        # Si la cola no está vacía
+        if len(cola) > 0:
+            # Mostramos el siguiente trabajo a imprimir
+            print("Siguiente trabajo:", cola[0])
+        # Si la cola está vacía
+        else:
+            # Mostramos un mensaje de error
+            print("No hay trabajos para imprimir")
+    # Si el comando es "eliminar"
+    elif comando == "eliminar":
+        # Si la cola no está vacía
+        if len(cola) > 0:
+            # Sacamos el primer trabajo de la cola
+            trabajo = cola.popleft()
+            # Mostramos un mensaje de éxito 
+            print("Trabajo eliminado:", trabajo)
+        # Si la cola está vacía
+        else:
+            # Mostramos un mensaje de error
+            print("No hay trabajos para eliminar")
+    # Si el comando no es "imprimir", "siguiente" ni "eliminar"
+    else:
+        # Añadimos el trabajo a la cola
+        cola.append(comando)
+        # Mostramos un mensaje de éxito
+        print("Trabajo añadido a la cola:", comando)
+
+# Ejecuta el programa y prueba a añadir trabajos a la cola de impresión utilizando el comando "imprimir".
+# Utiliza el comando "siguiente" para ver cuál es el siguiente trabajo a imprimir.
+# Utiliza el comando "eliminar" para eliminar el trabajo que se ha impreso.
+# Por ejemplo, puedes probar a introducir los siguientes comandos:
+
+# imprimir
+# siguiente
+# eliminar
+# imprimir
+# siguiente
+
+
+

From 528d6a954502c8e6783d1450fdf1fe0120344679 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 20 Mar 2025 08:47:18 +0100
Subject: [PATCH 12/66] Clases y Herencia

---
 Logic/09.Clases.py      | 76 +++++++++++++++++++++++++++++++++++++++++
 Logic/10.Herencia.py    | 62 +++++++++++++++++++++++++++++++++
 Logic/11.Excepciones.py |  0
 3 files changed, 138 insertions(+)
 create mode 100644 Logic/09.Clases.py
 create mode 100644 Logic/10.Herencia.py
 create mode 100644 Logic/11.Excepciones.py

diff --git a/Logic/09.Clases.py b/Logic/09.Clases.py
new file mode 100644
index 0000000..3283326
--- /dev/null
+++ b/Logic/09.Clases.py
@@ -0,0 +1,76 @@
+# Implementa dos clases que representen las estructuras de Pila y Cola. Deben poder inicializarse y disponer de operaciones para añadir, eliminar, retornar el número de elementos e imprimir todo su contenido
+
+class Pila:
+    def __init__(self):
+        self.items = []
+
+    def esta_vacia(self):
+        return len(self.items) == 0
+
+    def apilar(self, item):
+        self.items.append(item)
+
+    def desapilar(self):
+        if not self.esta_vacia():
+            return self.items.pop()
+        else:
+            return None
+
+    def tamano(self):
+        return len(self.items)
+
+    def imprimir(self):
+        print(self.items)
+
+
+class Cola:
+    def __init__(self):
+        self.items = []
+
+    def esta_vacia(self):
+        return len(self.items) == 0
+
+    def encolar(self, item):
+        self.items.insert(0, item)
+
+    def desencolar(self):
+        if not self.esta_vacia():
+            return self.items.pop()
+        else:
+            return None
+
+    def tamano(self):
+        return len(self.items)
+
+    def imprimir(self):
+        print(self.items)
+
+# Ejemplo de uso de la clase Pila
+pila = Pila()
+pila.apilar(1)
+pila.apilar(2)
+pila.apilar(3)
+print("Contenido de la pila después de apilar 1, 2, 3:")
+pila.imprimir()
+
+print("Elemento desapilado:", pila.desapilar())
+print("Contenido de la pila después de desapilar:")
+pila.imprimir()
+
+print("Tamaño de la pila:", pila.tamano())
+print("¿La pila está vacía?", pila.esta_vacia())
+
+# Ejemplo de uso de la clase Cola
+cola = Cola()
+cola.encolar(1)
+cola.encolar(2)
+cola.encolar(3)
+print("Contenido de la cola después de encolar 1, 2, 3:")
+cola.imprimir()
+
+print("Elemento desencolado:", cola.desencolar())
+print("Contenido de la cola después de desencolar:")
+cola.imprimir()
+
+print("Tamaño de la cola:", cola.tamano())
+print("¿La cola está vacía?", cola.esta_vacia())
diff --git a/Logic/10.Herencia.py b/Logic/10.Herencia.py
new file mode 100644
index 0000000..3190423
--- /dev/null
+++ b/Logic/10.Herencia.py
@@ -0,0 +1,62 @@
+# Implementa la jerarquia de una empresa de desarrollo formada por Empleados que pueden ser Gerentes, Gerentes de Proyectos o Programadores.
+# Cada empleado tiene un identificador y un nombre
+# Dependiendo de su labor, tienen propiedades y funciones exclusivas de su actividad, y almacenan los empleados a su cargo
+
+class Empleado:
+    def __init__(self, id, nombre):
+        self.id = id
+        self.nombre = nombre
+
+    def mostrar_informacion(self):
+        print(f"ID: {self.id}, Nombre: {self.nombre}")
+
+
+class Gerente(Empleado):
+    def __init__(self, id, nombre):
+        super().__init__(id, nombre)
+        self.empleados_a_cargo = []
+
+    def agregar_empleado(self, empleado):
+        self.empleados_a_cargo.append(empleado)
+
+    def mostrar_empleados_a_cargo(self):
+        print(f"Empleados a cargo de {self.nombre}:")
+        for empleado in self.empleados_a_cargo:
+            empleado.mostrar_informacion()
+
+
+class GerenteDeProyectos(Gerente):
+    def __init__(self, id, nombre, proyecto):
+        super().__init__(id, nombre)
+        self.proyecto = proyecto
+
+    def mostrar_informacion(self):
+        super().mostrar_informacion()
+        print(f"Proyecto: {self.proyecto}")
+
+
+class Programador(Empleado):
+    def __init__(self, id, nombre, lenguaje):
+        super().__init__(id, nombre)
+        self.lenguaje = lenguaje
+
+    def mostrar_informacion(self):
+        super().mostrar_informacion()
+        print(f"Lenguaje de programación: {self.lenguaje}")
+
+
+# Ejemplo de uso
+gerente = Gerente(1, "Carlos")
+programador1 = Programador(2, "Ana", "Python")
+programador2 = Programador(3, "Luis", "JavaScript")
+
+gerente.agregar_empleado(programador1)
+gerente.agregar_empleado(programador2)
+
+gerente.mostrar_informacion()
+gerente.mostrar_empleados_a_cargo()
+
+gerente_proyectos = GerenteDeProyectos(4, "Marta", "Proyecto X")
+gerente_proyectos.agregar_empleado(gerente)
+gerente_proyectos.mostrar_informacion()
+gerente_proyectos.mostrar_empleados_a_cargo()
diff --git a/Logic/11.Excepciones.py b/Logic/11.Excepciones.py
new file mode 100644
index 0000000..e69de29

From ceea9066ac898910caee24c96a923f80fc979cec Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 20 Mar 2025 09:34:42 +0100
Subject: [PATCH 13/66] Excepciones

---
 Logic/11.Excepciones.py | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/Logic/11.Excepciones.py b/Logic/11.Excepciones.py
index e69de29..d747c70 100644
--- a/Logic/11.Excepciones.py
+++ b/Logic/11.Excepciones.py
@@ -0,0 +1,40 @@
+
+# Crea una función que sea capaz de procesar parámetros, pero que también pueda lanzar 3 tipos diferentes de excepcions 
+# (una de ellas tiene que corresponderse con un tipo de excepción creada por nosotros de manera personalizada, 
+# y debe ser lanzada de manera manual) en caso de error.
+#  Captura todas las excepciones desde el lugar donde llamas a la función.
+# Imprime el tipo de error.
+# Imprime si no se ha producido ningún error.
+# Imprime que la ejecución ha finalizado.
+
+# 1. Definición de excepción personalizada
+class CustomError(Exception):
+    def __init__(self, message):
+        super().__init__(message)
+
+# 2. Función que procesa parámetros
+def procesar_parametro(param):
+    if not isinstance(param, int):
+        raise TypeError("El parámetro debe ser un entero")
+    elif param < 0:
+        raise ValueError("El parámetro no puede ser negativo")
+    elif param == 0:
+        raise CustomError("El parámetro no puede ser cero")  # Excepción personalizada
+    return param * 2
+
+# 3. Bloque de ejecución y manejo de excepciones
+try:
+    resultado = procesar_parametro(0)  # Prueba con diferentes valores
+    print(f"Resultado: {resultado}")
+except CustomError as ce:
+    print(f"Error personalizado: {type(ce).__name__} - {ce}")
+except TypeError as te:
+    print(f"Error de tipo: {type(te).__name__} - {te}")
+except ValueError as ve:
+    print(f"Error de valor: {type(ve).__name__} - {ve}")
+except Exception as e:
+    print(f"Error inesperado: {type(e).__name__} - {e}")
+else:
+    print("No se produjeron errores")
+finally:
+    print("Ejecución finalizada")

From 45c556b45567d9e31efd25db85aa6ee85f960cd1 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 24 Mar 2025 08:09:34 +0100
Subject: [PATCH 14/66] Ficherow

---
 Logic/12.Ficheros.py | 113 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)
 create mode 100644 Logic/12.Ficheros.py

diff --git a/Logic/12.Ficheros.py b/Logic/12.Ficheros.py
new file mode 100644
index 0000000..bf80d57
--- /dev/null
+++ b/Logic/12.Ficheros.py
@@ -0,0 +1,113 @@
+import os
+
+ARCHIVO = 'ventas.txt'
+
+def cargar_ventas():
+    try:
+        with open(ARCHIVO, 'r') as f:
+            ventas = []
+            for linea in f:
+                nombre, cantidad, precio = linea.strip().split(',')
+                ventas.append({
+                    'nombre': nombre.strip(),
+                    'cantidad': int(cantidad),
+                    'precio': float(precio)
+                })
+            return ventas
+    except FileNotFoundError:
+        return []
+
+def guardar_ventas(ventas):
+    with open(ARCHIVO, 'w') as f:
+        for p in ventas:
+            f.write(f"{p['nombre']}, {p['cantidad']}, {p['precio']}\n")
+
+def mostrar_menu():
+    print('\n===== GESTIÓN DE VENTAS =====')
+    print('1. Añadir producto')
+    print('2. Consultar productos')
+    print('3. Actualizar producto')
+    print('4. Eliminar producto')
+    print('5. Total ventas')
+    print('6. Ventas por producto')
+    print('7. Salir')
+
+def añadir_producto(ventas):
+    nombre = input('Nombre del producto: ')
+    cantidad = int(input('Cantidad vendida: '))
+    precio = float(input('Precio unitario: '))
+    ventas.append({
+        'nombre': nombre,
+        'cantidad': cantidad,
+        'precio': precio
+    })
+    guardar_ventas(ventas)
+    print('Producto añadido!')
+
+def listar_productos(ventas):
+    print('\n=== LISTADO DE PRODUCTOS ===')
+    for i, p in enumerate(ventas):
+        print(f"{i+1}. {p['nombre']} - {p['cantidad']} u. - ${p['precio']} c/u")
+
+def actualizar_producto(ventas):
+    listar_productos(ventas)
+    indice = int(input('Índice del producto a actualizar: ')) - 1
+    if 0 <= indice < len(ventas):
+        p = ventas[indice]
+        p['cantidad'] = int(input(f"Nueva cantidad ({p['cantidad']}): ") or p['cantidad'])
+        p['precio'] = float(input(f"Nuevo precio ({p['precio']}): ") or p['precio'])
+        guardar_ventas(ventas)
+        print('Producto actualizado!')
+    else:
+        print('Índice inválido.')
+
+def eliminar_producto(ventas):
+    listar_productos(ventas)
+    indice = int(input('Índice del producto a eliminar: ')) - 1
+    if 0 <= indice < len(ventas):
+        del ventas[indice]
+        guardar_ventas(ventas)
+        print('Producto eliminado!')
+    else:
+        print('Índice inválido.')
+
+def calcular_total(ventas):
+    total = sum(p['cantidad'] * p['precio'] for p in ventas)
+    print(f"\nTOTAL VENDIDO: ${total:.2f}")
+
+def ventas_por_producto(ventas):
+    nombre = input('Nombre del producto: ')
+    total = sum(p['cantidad'] * p['precio'] for p in ventas if p['nombre'].lower() == nombre.lower())
+    print(f"TOTAL PARA {nombre}: ${total:.2f}")
+
+def salir(ventas):
+    if os.path.exists(ARCHIVO):
+        os.remove(ARCHIVO)
+    print('\nArchivo borrado. ¡Hasta luego!')
+    exit()
+
+def main():
+    ventas = cargar_ventas()
+    while True:
+        mostrar_menu()
+        opcion = input('Seleccione opción: ')
+
+        if opcion == '1':
+            añadir_producto(ventas)
+        elif opcion == '2':
+            listar_productos(ventas)
+        elif opcion == '3':
+            actualizar_producto(ventas)
+        elif opcion == '4':
+            eliminar_producto(ventas)
+        elif opcion == '5':
+            calcular_total(ventas)
+        elif opcion == '6':
+            ventas_por_producto(ventas)
+        elif opcion == '7':
+            salir(ventas)
+        else:
+            print('Opción inválida!')
+
+if __name__ == "__main__":
+    main()

From 2b269387f06ca5ecb7469e61e953d4da46086135 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 25 Mar 2025 08:02:01 +0100
Subject: [PATCH 15/66] XML y JSON

---
 Logic/13.XML.py  | 116 +++++++++++++++++++++++++++++++++++++++++++++++
 Logic/14.JSON.py |  91 +++++++++++++++++++++++++++++++++++++
 2 files changed, 207 insertions(+)
 create mode 100644 Logic/13.XML.py
 create mode 100644 Logic/14.JSON.py

diff --git a/Logic/13.XML.py b/Logic/13.XML.py
new file mode 100644
index 0000000..5733f9b
--- /dev/null
+++ b/Logic/13.XML.py
@@ -0,0 +1,116 @@
+# Desarrolla un programa capaz de crear un archivo XML que guarde los siguientes datos (sintaxis correcta en cada caso):
+# Nombre, Edad, Fecha de nacimiento, Listado de lenguajes de programación
+# Muestra el contenido del archivo
+# Borra el archivo
+
+# Adicionalmente, utilizando la lógica del archivo anterior, crea un programa capaz de leer y transformar en una misma clase custom los datos alamecenados en el XML.
+
+import xml.etree.ElementTree as ET
+
+def crear_xml(data):
+    # Crear el elemento raíz
+    root = ET.Element('Persona')
+
+    # Añadir subelementos
+    ET.SubElement(root, 'Nombre').text = data['Nombre']
+    ET.SubElement(root, 'Edad').text = data['Edad']
+    ET.SubElement(root, 'Fecha_de_nacimiento').text = data['Fecha_de_nacimiento']
+
+    lenguajes = ET.SubElement(root, 'Lenguajes_de_programacion')
+    for lenguaje in data['Lenguajes_de_programacion']:
+        ET.SubElement(lenguajes, 'Lenguaje').text = lenguaje
+
+    # Crear el árbol y guardar en un archivo
+    tree = ET.ElementTree(root)
+    with open('persona.xml', 'wb') as file:
+        tree.write(file)
+
+def mostrar_contenido():
+    with open('persona.xml', 'r') as file:
+        contenido = file.read()
+    return contenido
+
+def borrar_archivo():
+    import os
+    if os.path.exists('persona.xml'):
+        os.remove('persona.xml')
+        return 'Archivo eliminado correctamente.'
+    else:
+        return 'El archivo no existe.'
+
+# Datos a guardar
+data = {
+    'Nombre': 'Juan Pérez',
+    'Edad': '30',
+    'Fecha_de_nacimiento': '1995-05-15',
+    'Lenguajes_de_programacion': ['Python', 'Java', 'C++']
+}
+
+# Crear archivo XML
+crear_xml(data)
+
+# Mostrar contenido
+print("Contenido del archivo:")
+print(mostrar_contenido())
+
+# Borrar archivo
+print("\nResultado al borrar el archivo:")
+print(borrar_archivo())
+
+import xml.etree.ElementTree as ET
+
+class Persona:
+    def __init__(self, nombre, edad, fecha_de_nacimiento, lenguajes):
+        self.nombre = nombre
+        self.edad = edad
+        self.fecha_de_nacimiento = fecha_de_nacimiento
+        self.lenguajes = lenguajes
+
+    def __repr__(self):
+        return f"Persona(Nombre: {self.nombre}, Edad: {self.edad}, Fecha de nacimiento: {self.fecha_de_nacimiento}, Lenguajes: {self.lenguajes})"
+
+def leer_xml_y_transformar():
+    # Crear nuevamente el archivo XML para leerlo
+    data = {
+        'Nombre': 'Juan Pérez',
+        'Edad': '30',
+        'Fecha_de_nacimiento': '1995-05-15',
+        'Lenguajes_de_programacion': ['Python', 'Java', 'C++']
+    }
+
+    root = ET.Element('Persona')
+    ET.SubElement(root, 'Nombre').text = data['Nombre']
+    ET.SubElement(root, 'Edad').text = data['Edad']
+    ET.SubElement(root, 'Fecha_de_nacimiento').text = data['Fecha_de_nacimiento']
+
+    lenguajes = ET.SubElement(root, 'Lenguajes_de_programacion')
+    for lenguaje in data['Lenguajes_de_programacion']:
+        ET.SubElement(lenguajes, 'Lenguaje').text = lenguaje
+
+    tree = ET.ElementTree(root)
+    with open('persona.xml', 'wb') as file:
+        tree.write(file)
+
+    # Leer el archivo XML
+    tree = ET.parse('persona.xml')
+    root = tree.getroot()
+
+    # Extraer datos
+    nombre = root.find('Nombre').text
+    edad = root.find('Edad').text
+    fecha_de_nacimiento = root.find('Fecha_de_nacimiento').text
+    lenguajes = [lenguaje.text for lenguaje in root.find('Lenguajes_de_programacion')]
+
+    # Transformar en una clase custom
+    persona = Persona(nombre, edad, fecha_de_nacimiento, lenguajes)
+
+    # Borrar el archivo después de leerlo
+    import os
+    if os.path.exists('persona.xml'):
+        os.remove('persona.xml')
+
+    return persona
+
+# Leer y transformar datos
+print("\nDatos transformados en clase custom:")
+print(leer_xml_y_transformar())
diff --git a/Logic/14.JSON.py b/Logic/14.JSON.py
new file mode 100644
index 0000000..4498d99
--- /dev/null
+++ b/Logic/14.JSON.py
@@ -0,0 +1,91 @@
+# Desarrolla un programa capaz de crear un archivo JSON que guarde los siguientes datos (sintaxis correcta en cada caso):
+# Nombre, Edad, Fecha de nacimiento, Listado de lenguajes de programación
+# Muestra el contenido del archivo
+# Borra el archivo
+
+# Adicionalmente, utilizando la lógica del archivo anterior, crea un programa capaz de leer y transformar en una misma clase custom los datos alamecenados en el XML.
+import json
+
+def crear_json(data):
+    # Crear archivo JSON
+    with open('persona.json', 'w') as file:
+        json.dump(data, file, indent=4)
+
+def mostrar_contenido():
+    with open('persona.json', 'r') as file:
+        contenido = file.read()
+    return contenido
+
+def borrar_archivo():
+    import os
+    if os.path.exists('persona.json'):
+        os.remove('persona.json')
+        return 'Archivo eliminado correctamente.'
+    else:
+        return 'El archivo no existe.'
+
+# Datos a guardar
+data = {
+    'Nombre': 'Juan Pérez',
+    'Edad': 30,
+    'Fecha_de_nacimiento': '1995-05-15',
+    'Lenguajes_de_programacion': ['Python', 'Java', 'C++']
+}
+
+# Crear archivo JSON
+crear_json(data)
+
+# Mostrar contenido
+print("Contenido del archivo:")
+print(mostrar_contenido())
+
+# Borrar archivo
+print("\nResultado al borrar el archivo:")
+print(borrar_archivo())
+
+import json
+
+class Persona:
+    def __init__(self, nombre, edad, fecha_de_nacimiento, lenguajes):
+        self.nombre = nombre
+        self.edad = edad
+        self.fecha_de_nacimiento = fecha_de_nacimiento
+        self.lenguajes = lenguajes
+
+    def __repr__(self):
+        return f"Persona(Nombre: {self.nombre}, Edad: {self.edad}, Fecha de nacimiento: {self.fecha_de_nacimiento}, Lenguajes: {self.lenguajes})"
+
+def leer_json_y_transformar():
+    # Crear nuevamente el archivo JSON para leerlo
+    data = {
+        'Nombre': 'Juan Pérez',
+        'Edad': 30,
+        'Fecha_de_nacimiento': '1995-05-15',
+        'Lenguajes_de_programacion': ['Python', 'Java', 'C++']
+    }
+
+    with open('persona.json', 'w') as file:
+        json.dump(data, file, indent=4)
+
+    # Leer el archivo JSON
+    with open('persona.json', 'r') as file:
+        datos_json = json.load(file)
+
+    # Transformar en una clase custom
+    persona = Persona(
+        datos_json['Nombre'],
+        datos_json['Edad'],
+        datos_json['Fecha_de_nacimiento'],
+        datos_json['Lenguajes_de_programacion']
+    )
+
+    # Borrar el archivo después de leerlo
+    import os
+    if os.path.exists('persona.json'):
+        os.remove('persona.json')
+
+    return persona
+
+# Leer y transformar datos
+print("\nDatos transformados en clase custom:")
+print(leer_json_y_transformar())

From b2bf3cd1295f8fde31b3f243cddfe84ca97e8714 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 27 Mar 2025 07:30:59 +0100
Subject: [PATCH 16/66] Tests

---
 Hello-Python.code-workspace | 23 +++++++++++++++-
 Logic/15.Test.py            | 52 +++++++++++++++++++++++++++++++++++++
 2 files changed, 74 insertions(+), 1 deletion(-)
 create mode 100644 Logic/15.Test.py

diff --git a/Hello-Python.code-workspace b/Hello-Python.code-workspace
index 876a149..1cfc275 100644
--- a/Hello-Python.code-workspace
+++ b/Hello-Python.code-workspace
@@ -4,5 +4,26 @@
 			"path": "."
 		}
 	],
-	"settings": {}
+	"settings": {
+		"workbench.colorCustomizations": {
+			"activityBar.activeBackground": "#d69640",
+			"activityBar.background": "#d69640",
+			"activityBar.foreground": "#15202b",
+			"activityBar.inactiveForeground": "#15202b99",
+			"activityBarBadge.background": "#cbf4e2",
+			"activityBarBadge.foreground": "#15202b",
+			"commandCenter.border": "#15202b99",
+			"sash.hoverBorder": "#d69640",
+			"statusBar.background": "#bb7c28",
+			"statusBar.foreground": "#15202b",
+			"statusBarItem.hoverBackground": "#91601f",
+			"statusBarItem.remoteBackground": "#bb7c28",
+			"statusBarItem.remoteForeground": "#15202b",
+			"titleBar.activeBackground": "#bb7c28",
+			"titleBar.activeForeground": "#15202b",
+			"titleBar.inactiveBackground": "#bb7c2899",
+			"titleBar.inactiveForeground": "#15202b99"
+		},
+		"peacock.color": "#bb7c28"
+	}
 }
\ No newline at end of file
diff --git a/Logic/15.Test.py b/Logic/15.Test.py
new file mode 100644
index 0000000..62bc29c
--- /dev/null
+++ b/Logic/15.Test.py
@@ -0,0 +1,52 @@
+# Crea una función que se encargue de sumar dos números y retornar su resultado
+# Crea una función que se encargue de sumar dos números y retornar su resultado
+def sumar_numeros(a, b):
+    return a + b
+
+# Ejemplo de uso
+resultado = sumar_numeros(5, 3)
+print(f"El resultado de la suma es: {resultado}")
+
+# Crea un test, utilizando las herramientas propias del lenguaje, que sea capaz de determinar si esa función se ejecuta correctamente.
+def test_sumar_numeros():
+    resultado = sumar_numeros(5, 3)
+    assert resultado == 8, f"El resultado esperado es 8 y se obtuvo {resultado}"
+    print("La función 'sumar_numeros' es correcta.")
+
+"""
+Crea un diccionario con las siguientes calves y valores
+clave: nombre, valor: Juan
+clave: edad, valor: 30
+clave: fecha_nacuimiento , valor: 12/05/1980
+clave: lenguajes, valor: JavaScript, Python, Java
+
+Crear dos test:
+- Uno que determine que existen todos los campos
+- Otro que determine que los datos introducidos son correctos
+"""
+
+# Diccionario con las claves y valores especificados
+persona = {
+    "nombre": "Juan",
+    "edad": 30,
+    "fecha_nacimiento": "12/05/1980",
+    "lenguajes": ["JavaScript", "Python", "Java"]
+}
+
+# Test para determinar que existen todos los campos
+def test_existen_todos_los_campos():
+    campos_requeridos = {"nombre", "edad", "fecha_nacimiento", "lenguajes"}
+    assert campos_requeridos.issubset(persona.keys()), "Faltan campos en el diccionario"
+    print("Todos los campos están presentes en el diccionario.")
+
+# Test para determinar que los datos introducidos son correctos
+def test_datos_correctos():
+    assert persona["nombre"] == "Juan", "El nombre no es correcto"
+    assert persona["edad"] == 30, "La edad no es correcta"
+    assert persona["fecha_nacimiento"] == "12/05/1980", "La fecha de nacimiento no es correcta"
+    assert persona["lenguajes"] == ["JavaScript", "Python", "Java"], "Los lenguajes no son correctos"
+    print("Todos los datos son correctos.")
+
+# Ejecución de los tests
+test_existen_todos_los_campos()
+test_datos_correctos()

From 62ae324a5764b9c3b1156d4b63728f55e7d4dca8 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 31 Mar 2025 07:48:29 +0200
Subject: [PATCH 17/66] Fechas

---
 Logic/16.Fechas.py | 45 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 Logic/16.Fechas.py

diff --git a/Logic/16.Fechas.py b/Logic/16.Fechas.py
new file mode 100644
index 0000000..c5c1543
--- /dev/null
+++ b/Logic/16.Fechas.py
@@ -0,0 +1,45 @@
+from datetime import datetime
+
+"""
+Ejercicio
+"""
+
+now = datetime.now()
+birth_date = datetime(1987, 4, 29, 12, 0, 0)
+
+print(now)
+print(birth_date)
+
+difference = now - birth_date
+print(type(difference))
+
+print(f"Tengo {difference.days // 365} aÃ±os.")
+
+"""
+Extra
+"""
+
+# DÃ­a, mes y aÃ±o
+print(birth_date.strftime("%d/%m/%y"))
+print(birth_date.strftime("%d/%m/%Y"))
+
+# Horas, minutos y segundos
+print(birth_date.strftime("%H:%M:%S"))
+
+# DÃ­a del aÃ±o
+print(birth_date.strftime("%j"))
+
+# DÃ­a de la semana
+print(birth_date.strftime("%A"))
+
+# Nombre del mes
+print(birth_date.strftime("%h"))
+print(birth_date.strftime("%B"))
+
+# RepresentaciÃ³n por defecto del locale
+print(birth_date.strftime("%c"))
+print(birth_date.strftime("%x"))
+print(birth_date.strftime("%X"))
+
+# AM/PM
+print(birth_date.strftime("%p"))

From c0d13d9f4690bbafab8909eeb6fe2d335df297ca Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 2 Apr 2025 08:00:51 +0200
Subject: [PATCH 18/66] =?UTF-8?q?As=C3=ADncronia?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Logic/17.Asincronia.py | 56 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)
 create mode 100644 Logic/17.Asincronia.py

diff --git a/Logic/17.Asincronia.py b/Logic/17.Asincronia.py
new file mode 100644
index 0000000..92c10c7
--- /dev/null
+++ b/Logic/17.Asincronia.py
@@ -0,0 +1,56 @@
+"""
+Crea un programa en javascript capaz de ejecutar de manera asíncrona una función que tardará en finalizar un número concreto de segundos parametrizables.
+También debes poder asignarle un nombre.
+La función imprime su nombre, cuándo empieza, el tiempo que durará su ejecución y cuando finalia.
+"""
+
+import asyncio
+
+async def tarea(nombre: str, duracion: int):
+    print(f"[{nombre}] Iniciando tarea. Duración: {duracion} segundos.")
+    await asyncio.sleep(duracion)
+    print(f"[{nombre}] Tarea finalizada tras {duracion} segundos.")
+
+async def main():
+    # Ejecutar varias tareas asíncronas
+    tareas = [
+        tarea("Tarea 1", 3),
+        tarea("Tarea 2", 5),
+        tarea("Tarea 3", 2)
+    ]
+    await asyncio.gather(*tareas)
+
+if __name__ == "__main__":
+    asyncio.run(main())
+
+"""
+Utilizando el concepto de asincronía y la función anterior, crea el siguiente programa que ejecuta en este orden
+* Una función C que dura 3 segundos
+* Una función B que dura 2 segundos
+* Una función A que dura 1 segundo
+* Una función D que dura 1 segundo
+* Las funciones C, B y A se ejecutan en paralelo.
+* La función D comienza su ejecución cuando las 3 anteriores han finalizado.
+"""
+import asyncio
+
+async def tarea(nombre: str, duracion: int):
+    print(f"[{nombre}] Iniciando tarea. Duración: {duracion} segundos.")
+    await asyncio.sleep(duracion)
+    print(f"[{nombre}] Tarea finalizada tras {duracion} segundos.")
+
+async def main():
+    # Ejecutar C, B y A en paralelo
+    tareas_paralelas = [
+        tarea("C", 3),
+        tarea("B", 2),
+        tarea("A", 1)
+    ]
+    await asyncio.gather(*tareas_paralelas)
+    
+    # Ejecutar D solo después de que C, B y A hayan finalizado
+    await tarea("D", 1)
+
+if __name__ == "__main__":
+    asyncio.run(main())
+

From f2678ca37128bc71500077d88faba28e03983eec Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 3 Apr 2025 07:48:32 +0200
Subject: [PATCH 19/66] Expresiones Regulares

---
 Logic/18.ExpresionesRegulares.py | 33 ++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 Logic/18.ExpresionesRegulares.py

diff --git a/Logic/18.ExpresionesRegulares.py b/Logic/18.ExpresionesRegulares.py
new file mode 100644
index 0000000..c198a3f
--- /dev/null
+++ b/Logic/18.ExpresionesRegulares.py
@@ -0,0 +1,33 @@
+"""
+Crea 3 expresiones regulares (a tu criterio) capaces de:
+* Validar un email
+* Validar un número de teléfono
+* Validar una url
+"""
+import re
+
+# Expresiones regulares
+EMAIL_REGEX = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
+PHONE_REGEX = r'^\+?\d{1,4}[\s-]?\d{1,14}([\s-]?\d{1,13})?$'
+URL_REGEX = r'^(https?:\/\/)?(www\.)?[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}([\/\w.-]*)*\/?$'
+
+def validar_dato(tipo: str, dato: str):
+    regex = {
+        "email": EMAIL_REGEX,
+        "telefono": PHONE_REGEX,
+        "url": URL_REGEX
+    }.get(tipo)
+    
+    if regex and re.match(regex, dato):
+        print(f"{tipo.capitalize()} válido: {dato}")
+    else:
+        print(f"{tipo.capitalize()} inválido: {dato}")
+
+def main():
+    # Validaciones
+    validar_dato("email", "usuario@example.com")
+    validar_dato("telefono", "+34 600-123-456")
+    validar_dato("url", "https://www.ejemplo.com")
+
+main()
+

From 1baf958b20e40bbddccd291aee755fe815204491 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 3 Apr 2025 08:02:18 +0200
Subject: [PATCH 20/66] Iteraciones

---
 Logic/19.Iteraciones.py | 72 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)
 create mode 100644 Logic/19.Iteraciones.py

diff --git a/Logic/19.Iteraciones.py b/Logic/19.Iteraciones.py
new file mode 100644
index 0000000..3fa40ac
--- /dev/null
+++ b/Logic/19.Iteraciones.py
@@ -0,0 +1,72 @@
+"""
+Escribe el mayor número de mecanismos que posee python para iterar valores.
+"""
+import itertools
+from functools import reduce
+
+print("\n1. Bucle for con lista:")
+for i in [1, 2, 3]:
+    print(i)
+
+print("\n2. Bucle while:")
+i = 0
+while i < 3:
+    print(i)
+    i += 1
+
+print("\n3. Iteración con range:")
+for i in range(1, 5, 2):
+    print(i)
+
+print("\n4. Iteración sobre diccionario:")
+diccionario = {"a": 1, "b": 2}
+for clave, valor in diccionario.items():
+    print(clave, valor)
+
+print("\n5. Enumerate:")
+lista = ['a', 'b', 'c']
+for i, valor in enumerate(lista):
+    print(i, valor)
+
+print("\n6. Zip:")
+nombres = ['Ana', 'Luis']
+edades = [25, 30]
+for nombre, edad in zip(nombres, edades):
+    print(nombre, edad)
+
+print("\n7. List Comprehension:")
+cuadrados = [x**2 for x in range(5)]
+print(cuadrados)
+
+print("\n8. Set y Dict Comprehension:")
+conjunto = {x for x in range(5)}
+print(conjunto)
+dicc = {x: x**2 for x in range(3)}
+print(dicc)
+
+print("\n9. Iteradores y Generadores:")
+lista = iter([1, 2, 3])
+print(next(lista))
+def generador():
+    for i in range(3):
+        yield i
+for valor in generador():
+    print(valor)
+
+print("\n10. Funciones Funcionales:")
+print(list(map(lambda x: x*2, [1, 2, 3])))
+print(list(filter(lambda x: x % 2 == 0, range(5))))
+print(reduce(lambda x, y: x + y, [1, 2, 3]))
+
+print("\n11. Itertools:")
+print(list(itertools.islice(itertools.count(10, 2), 5)))
+print(list(itertools.islice(itertools.cycle([1, 2, 3]), 7)))
+print(list(itertools.permutations([1, 2, 3])))
+
+print("\n12. Leer archivo línea por línea:")
+try:
+    with open('archivo.txt') as f:
+        for linea in f:
+            print(linea.strip())
+except FileNotFoundError:
+    print("archivo.txt no encontrado, omitiendo este paso.")

From d61875b87d0249d8acdc839eeb70fe0114b30df9 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 3 Apr 2025 08:08:04 +0200
Subject: [PATCH 21/66] Conjuntos

---
 Logic/20.Conjuntos.py | 56 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)
 create mode 100644 Logic/20.Conjuntos.py

diff --git a/Logic/20.Conjuntos.py b/Logic/20.Conjuntos.py
new file mode 100644
index 0000000..e68b76e
--- /dev/null
+++ b/Logic/20.Conjuntos.py
@@ -0,0 +1,56 @@
+"""
+Realiza un programa python que muestre ejemplos de las siguientes operaciones con conjuntos:
+* Unión
+* Intersección
+* Diferencia
+* Diferencia simétrica
+* Subconjunto
+* Producto cartesiano
+* Potencia de un conjunto
+* Complemento de un conjunto
+* Producto de conjuntos
+"""
+from itertools import chain, combinations, product
+
+# Funciones para operaciones con conjuntos
+def union(setA, setB):
+    return setA | setB
+
+def interseccion(setA, setB):
+    return setA & setB
+
+def diferencia(setA, setB):
+    return setA - setB
+
+def diferencia_simetrica(setA, setB):
+    return setA ^ setB
+
+def es_subconjunto(subset, setA):
+    return subset.issubset(setA)
+
+def producto_cartesiano(setA, setB):
+    return set(product(setA, setB))
+
+def potencia_conjunto(setA):
+    return set(frozenset(s) for s in chain.from_iterable(combinations(setA, r) for r in range(len(setA) + 1)))
+
+def complemento(setA, universo):
+    return universo - setA
+
+def producto_de_conjuntos(setA, setB):
+    return {a * b for a in setA for b in setB}
+
+# Ejemplo de uso
+A = {1, 2, 3}
+B = {3, 4, 5}
+universo = {1, 2, 3, 4, 5, 6, 7, 8, 9}
+
+print("Unión:", union(A, B))
+print("Intersección:", interseccion(A, B))
+print("Diferencia A - B:", diferencia(A, B))
+print("Diferencia simétrica:", diferencia_simetrica(A, B))
+print("¿A es subconjunto de B?", es_subconjunto(A, B))
+print("Producto cartesiano:", producto_cartesiano(A, B))
+print("Potencia de A:", potencia_conjunto(A))
+print("Complemento de A:", complemento(A, universo))
+print("Producto de conjuntos:", producto_de_conjuntos(A, B))

From 9f5a6b6c84658fad9cd155874c74ec368c2f392f Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 4 Apr 2025 07:52:25 +0200
Subject: [PATCH 22/66] Enumeraciones

---
 Logic/21.Enumeraciones.py | 83 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 Logic/21.Enumeraciones.py

diff --git a/Logic/21.Enumeraciones.py b/Logic/21.Enumeraciones.py
new file mode 100644
index 0000000..98a2072
--- /dev/null
+++ b/Logic/21.Enumeraciones.py
@@ -0,0 +1,83 @@
+"""
+Crea un pequeño sistema de gestión del estado de pedidos.
+Implementa una clase que defina un pedido con las siguientes características:
+* El pedido tiene un ID único y un estado.
+* El estado es un enumerado que puede ser PENDIENTE, ENVIADO, ENTREGADO y CANCELADO.
+* Implementa las funciones que sirvan para modificar el estado:
+    * Cambiar el estado a ENVIADO.
+    * Cambiar el estado a ENTREGADO.
+    * Cambiar el estado a CANCELADO.
+    * Cambiar el estado a PENDIENTE.
+    * Cambiar el estado a un valor inválido (debería lanzar un error).
+    (establece una lógica, por ejemplo, no se puede entregar si no se ha enviado, etc...)
+* Implementa un método para mostrar el estado actual del pedido.
+* Crea diferentes pedidos y muestra cómo se interactua con ellos.
+"""
+from enum import Enum, auto
+import uuid
+
+class EstadoPedido(Enum):
+    PENDIENTE = auto()
+    ENVIADO = auto()
+    ENTREGADO = auto()
+    CANCELADO = auto()
+
+class Pedido:
+    def __init__(self):
+        self.id = str(uuid.uuid4())
+        self.estado = EstadoPedido.PENDIENTE
+
+    def mostrar_estado(self):
+        print(f"Pedido {self.id[:8]} - Estado actual: {self.estado.name}")
+
+    def cambiar_estado(self, nuevo_estado):
+        if not isinstance(nuevo_estado, EstadoPedido):
+            raise ValueError("Estado inválido. Debe ser una instancia de EstadoPedido.")
+
+        if nuevo_estado == EstadoPedido.ENVIADO:
+            if self.estado != EstadoPedido.PENDIENTE:
+                raise ValueError("Solo se puede enviar un pedido que esté PENDIENTE.")
+
+        elif nuevo_estado == EstadoPedido.ENTREGADO:
+            if self.estado != EstadoPedido.ENVIADO:
+                raise ValueError("Solo se puede entregar un pedido que haya sido ENVIADO.")
+
+        elif nuevo_estado == EstadoPedido.CANCELADO:
+            if self.estado == EstadoPedido.ENTREGADO:
+                raise ValueError("No se puede cancelar un pedido ya ENTREGADO.")
+
+        elif nuevo_estado == EstadoPedido.PENDIENTE:
+            if self.estado != EstadoPedido.CANCELADO:
+                raise ValueError("Solo se puede volver a estado PENDIENTE desde CANCELADO.")
+
+        self.estado = nuevo_estado
+        print(f"El estado del pedido {self.id[:8]} ha cambiado a: {self.estado.name}")
+
+# Demostración del sistema de gestión de pedidos
+if __name__ == "__main__":
+    pedido1 = Pedido()
+    pedido2 = Pedido()
+
+    pedido1.mostrar_estado()
+    pedido1.cambiar_estado(EstadoPedido.ENVIADO)
+    pedido1.cambiar_estado(EstadoPedido.ENTREGADO)
+    pedido1.mostrar_estado()
+
+    pedido2.mostrar_estado()
+    pedido2.cambiar_estado(EstadoPedido.CANCELADO)
+    pedido2.mostrar_estado()
+    try:
+        pedido2.cambiar_estado(EstadoPedido.ENTREGADO)
+    except ValueError as e:
+        print("Error:", e)
+
+    try:
+        pedido2.cambiar_estado("REEMBOLSADO")  # Estado inválido
+    except ValueError as e:
+        print("Error:", e)
+
+    try:
+        pedido2.cambiar_estado(EstadoPedido.PENDIENTE)  # válido desde CANCELADO
+        pedido2.mostrar_estado()
+    except ValueError as e:
+        print("Error:", e)

From da2e1891caf008da622d0d7e5c3ee5bc8cff4621 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 8 Apr 2025 08:30:08 +0200
Subject: [PATCH 23/66] PeticionesHTTP

---
 Logic/22.PeticionesHTTP.py | 85 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 85 insertions(+)
 create mode 100644 Logic/22.PeticionesHTTP.py

diff --git a/Logic/22.PeticionesHTTP.py b/Logic/22.PeticionesHTTP.py
new file mode 100644
index 0000000..9c2be0d
--- /dev/null
+++ b/Logic/22.PeticionesHTTP.py
@@ -0,0 +1,85 @@
+"""
+Utilizando un mecanismo de peticiones HTTP de python, 
+realiza una petición a la web que tu quieras, 
+verifica que dicha petición fue exitosa y muestra por consola el contenido de la web.
+"""
+import requests
+
+# URL de ejemplo
+url = "https://www.example.com"
+
+try:
+    response = requests.get(url)
+
+    # Verificamos si la respuesta fue exitosa (código 200)
+    if response.status_code == 200:
+        print("✅ Petición exitosa")
+        print("Contenido de la página:")
+        print(response.text)
+    else:
+        print(f"❌ La petición falló con código de estado: {response.status_code}")
+
+except requests.RequestException as e:
+    print(f"⚠️ Error durante la petición HTTP: {e}")
+
+"""
+Utilizando la PokéAPI (https://pokeapi.co), crea un programa por terminal al que le puedas solicitar
+información de un Pokémon concreto utilizando su nombre o número.
+- Muestra el nombre, id, peso, altura y tipo(s) del Pokémon.
+- Si el Pokémon no existe, muestra un mensaje de error.
+- Muestra el nombre de su cadena de evoluciones.
+- Muestra los juegos en los que aparece.
+- Controla posibles errores.
+"""
+#import requests
+
+def obtener_pokemon(nombre_o_id):
+    url = f"https://pokeapi.co/api/v2/pokemon/{nombre_o_id.lower()}"
+    try:
+        respuesta = requests.get(url)
+        if respuesta.status_code != 200:
+            print("\n❌ Pokémon no encontrado.")
+            return
+
+        datos = respuesta.json()
+
+        nombre = datos['name']
+        id_pokemon = datos['id']
+        peso = datos['weight']
+        altura = datos['height']
+        tipos = [tipo['type']['name'] for tipo in datos['types']]
+
+        print(f"\n🧾 Información de {nombre.capitalize()}")
+        print(f"ID: {id_pokemon}")
+        print(f"Peso: {peso}")
+        print(f"Altura: {altura}")
+        print(f"Tipo(s): {', '.join(tipos)}")
+
+        # Mostrar juegos donde aparece
+        juegos = {entry['version']['name'] for entry in datos['game_indices']}
+        print(f"\n🎮 Aparece en los juegos:")
+        for juego in sorted(juegos):
+            print(f"- {juego}")
+
+        # Obtener cadena de evolución
+        url_especie = datos['species']['url']
+        especie = requests.get(url_especie).json()
+        url_evoluciones = especie['evolution_chain']['url']
+        cadena = requests.get(url_evoluciones).json()
+
+        print("\n🔗 Cadena de evoluciones:")
+        mostrar_cadena(cadena['chain'])
+
+    except requests.RequestException as e:
+        print(f"\n⚠️ Error de conexión: {e}")
+
+def mostrar_cadena(cadena):
+    actual = cadena['species']['name']
+    print(f"- {actual}")
+    for evolucion in cadena['evolves_to']:
+        mostrar_cadena(evolucion)
+
+if __name__ == "__main__":
+    nombre = input("Introduce el nombre o número del Pokémon: ")
+    obtener_pokemon(nombre)
+

From 49af8a84cee16f4450c31550f956e2d58b1b046f Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 8 Apr 2025 08:46:20 +0200
Subject: [PATCH 24/66] Callbacks

---
 Logic/23.Callbacks.py | 76 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 Logic/23.Callbacks.py

diff --git a/Logic/23.Callbacks.py b/Logic/23.Callbacks.py
new file mode 100644
index 0000000..b66d0f8
--- /dev/null
+++ b/Logic/23.Callbacks.py
@@ -0,0 +1,76 @@
+"""
+ * EJERCICIO:
+ * Explora el concepto de callback en tu lenguaje creando un ejemplo
+ * simple (a tu elección) que muestre su funcionamiento.
+"""
+
+def procesar_lista(lista, callback):
+    """Aplica una función (callback) a cada elemento de la lista."""
+    resultado = []
+    for elemento in lista:
+        resultado.append(callback(elemento))
+    return resultado
+
+# Funciones de ejemplo para usar como callbacks
+def al_cuadrado(x):
+    return x * x
+
+def al_doble(x):
+    return x * 2
+
+# Uso
+numeros = [1, 2, 3, 4, 5]
+
+print("Aplicando al_cuadrado:")
+print(procesar_lista(numeros, al_cuadrado))  # [1, 4, 9, 16, 25]
+
+print("Aplicando al_doble:")
+print(procesar_lista(numeros, al_doble))     # [2, 4, 6, 8, 10]
+
+"""
+ * DIFICULTAD EXTRA (opcional):
+ * Crea un simulador de pedidos de un restaurante utilizando callbacks.
+ * Estará formado por una función que procesa pedidos.
+ * Debe aceptar el nombre del plato, una callback de confirmación, una
+ * de listo y otra de entrega.
+ * - Debe imprimir un confirmación cuando empiece el procesamiento.
+ * - Debe simular un tiempo aleatorio entre 1 a 10 segundos entre
+ *   procesos.
+ * - Debe invocar a cada callback siguiendo un orden de procesado.
+ * - Debe notificar que el plato está listo o ha sido entregado.
+"""
+import time
+import random
+
+def procesar_pedido(plato, on_confirmacion, on_listo, on_entrega):
+    print(f"\n🔔 Recibido pedido de: {plato}")
+
+    # Confirmación del pedido
+    on_confirmacion(plato)
+    tiempo_preparacion = random.randint(1, 10)
+    time.sleep(tiempo_preparacion)
+
+    # Plato listo
+    on_listo(plato)
+    tiempo_entrega = random.randint(1, 5)
+    time.sleep(tiempo_entrega)
+
+    # Plato entregado
+    on_entrega(plato)
+
+# Callbacks
+
+def confirmar_pedido(plato):
+    print(f"✅ Pedido confirmado: {plato}")
+
+def plato_listo(plato):
+    print(f"🍽️ El plato '{plato}' está listo para servir.")
+
+def entregar_pedido(plato):
+    print(f"📦 Pedido '{plato}' ha sido entregado al cliente.\n")
+
+# Simulación de varios pedidos
+if __name__ == "__main__":
+    pedidos = ["Pizza Margarita", "Ensalada César", "Sushi", "Hamburguesa"]
+    for plato in pedidos:
+        procesar_pedido(plato, confirmar_pedido, plato_listo, entregar_pedido)

From f6e76ae0ff1f74e51136be942f0e9c4b746d7217 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 9 Apr 2025 08:27:32 +0200
Subject: [PATCH 25/66] OrdenSuperior

---
 Logic/24.OrdenSuperior.py | 126 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 126 insertions(+)
 create mode 100644 Logic/24.OrdenSuperior.py

diff --git a/Logic/24.OrdenSuperior.py b/Logic/24.OrdenSuperior.py
new file mode 100644
index 0000000..88a4541
--- /dev/null
+++ b/Logic/24.OrdenSuperior.py
@@ -0,0 +1,126 @@
+"""
+ * EJERCICIO:
+ Explora el concepto de funciones de orden superior en python 
+ creando ejemplos simples (a tu elección) que muestren su funcionamiento.
+"""
+# 1.Recibir funciones como argumentos
+
+def aplicar_operacion(x, y, operacion):
+    return operacion(x, y)
+
+def sumar(a, b):
+    return a + b
+
+def multiplicar(a, b):
+    return a * b
+
+# Uso:
+print(aplicar_operacion(5, 3, sumar))        # 8
+print(aplicar_operacion(5, 3, multiplicar))  # 15
+
+# 2.Devolver una función
+
+def crear_saludo(tipo):
+    def saludo_formal(nombre):
+        return f"Buenos días, {nombre}."
+
+    def saludo_informal(nombre):
+        return f"¡Hey, {nombre}!"
+
+    return saludo_formal if tipo == "formal" else saludo_informal
+
+# Uso:
+saludo = crear_saludo("informal")
+print(saludo("Ana"))  # ¡Hey, Ana!
+
+saludo = crear_saludo("formal")
+print(saludo("Carlos"))  # Buenos días, Carlos.
+
+# 3.Combinar funciones como datos
+
+def procesar_lista(lista, funcion):
+    return [funcion(x) for x in lista]
+
+doble = lambda x: x * 2
+cuadrado = lambda x: x ** 2
+
+nums = [1, 2, 3, 4]
+
+print(procesar_lista(nums, doble))     # [2, 4, 6, 8]
+print(procesar_lista(nums, cuadrado))  # [1, 4, 9, 16]
+
+
+"""
+ * DIFICULTAD EXTRA (opcional):
+ * Dada una lista de estudiantes (con sus nombres, fecha de nacimiento y 
+ * lista de calificaciones), utiliza funciones de orden superior para 
+ * realizar las siguientes operaciones de procesamiento y análisis:
+ * - Promedio calificaciones: Obtiene una lista de estudiantes por nombre
+ *   y promedio de sus calificaciones.
+ * - Mejores estudiantes: Obtiene una lista con el nombre de los estudiantes
+ *   que tienen calificaciones con un 9 o más de promedio.
+ * - Nacimiento: Obtiene una lista de estudiantes ordenada desde el más joven.
+ * - Mayor calificación: Obtiene la calificación más alta de entre todas las
+ *   de los alumnos.
+ * - Una calificación debe estar comprendida entre 0 y 10 (admite decimales).
+ """
+
+from datetime import datetime
+from statistics import mean
+from itertools import chain
+
+
+# Lista de estudiantes
+estudiantes = [
+    {
+        "nombre": "Ana",
+        "nacimiento": "2005-06-10",
+        "calificaciones": [8.5, 9.0, 7.5]
+    },
+    {
+        "nombre": "Luis",
+        "nacimiento": "2004-02-20",
+        "calificaciones": [9.5, 9.0, 10.0]
+    },
+    {
+        "nombre": "María",
+        "nacimiento": "2006-11-05",
+        "calificaciones": [6.0, 7.0, 5.5]
+    },
+    {
+        "nombre": "Pedro",
+        "nacimiento": "2003-09-15",
+        "calificaciones": [10.0, 10.0, 9.5]
+    }
+]
+
+# Validación de calificaciones
+for est in estudiantes:
+    est['calificaciones'] = list(filter(lambda x: 0 <= x <= 10, est['calificaciones']))
+
+# 1. Promedio de calificaciones
+promedios = list(map(lambda e: {
+    "nombre": e['nombre'],
+    "promedio": round(mean(e['calificaciones']), 2)
+}, estudiantes))
+
+print("\n🎓 Promedios de calificaciones:")
+for p in promedios:
+    print(f"{p['nombre']}: {p['promedio']}")
+
+# 2. Mejores estudiantes (promedio >= 9)
+mejores = list(filter(lambda e: mean(e['calificaciones']) >= 9, estudiantes))
+print("\n🏅 Mejores estudiantes (promedio >= 9):")
+for e in mejores:
+    print(f"{e['nombre']}")
+
+# 3. Estudiantes ordenados desde el más joven
+ordenados_por_edad = sorted(estudiantes, key=lambda e: datetime.strptime(e['nacimiento'], "%Y-%m-%d"), reverse=True)
+print("\n📅 Estudiantes ordenados por edad (de más joven a mayor):")
+for e in ordenados_por_edad:
+    print(f"{e['nombre']} - {e['nacimiento']}")
+
+# 4. Mayor calificación entre todos
+todas_las_calificaciones = list(chain.from_iterable(map(lambda e: e['calificaciones'], estudiantes)))
+mayor_calificacion = max(todas_las_calificaciones)
+print(f"\n🏆 Mayor calificación entre todos: {mayor_calificacion}")

From cee10eaa645bdf72375fa888b787963f3aa580b6 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 11 Apr 2025 18:19:33 +0200
Subject: [PATCH 26/66] Patterns

---
 Logic/25.Singleton.py | 13 +++++++++++++
 Logic/26.Decorator.py | 11 +++++++++++
 Logic/27.Logs.py      | 17 +++++++++++++++++
 3 files changed, 41 insertions(+)
 create mode 100644 Logic/25.Singleton.py
 create mode 100644 Logic/26.Decorator.py
 create mode 100644 Logic/27.Logs.py

diff --git a/Logic/25.Singleton.py b/Logic/25.Singleton.py
new file mode 100644
index 0000000..22e3255
--- /dev/null
+++ b/Logic/25.Singleton.py
@@ -0,0 +1,13 @@
+ """
+ * EJERCICIO:
+ * Explora el patrón de diseño "singleton" y muestra cómo crearlo
+ * con un ejemplo genérico.
+ """
+
+ """
+ * DIFICULTAD EXTRA (opcional):
+ * Utiliza el patrón de diseño "singleton" para representar una clase que
+ * haga referencia a la sesión de usuario de una aplicación ficticia.
+ * La sesión debe permitir asignar un usuario (id, username, nombre y email),
+ * recuperar los datos del usuario y borrar los datos de la sesión.
+ """
\ No newline at end of file
diff --git a/Logic/26.Decorator.py b/Logic/26.Decorator.py
new file mode 100644
index 0000000..a5b28ae
--- /dev/null
+++ b/Logic/26.Decorator.py
@@ -0,0 +1,11 @@
+"""
+ * EJERCICIO:
+ * Explora el concepto de "decorador" y muestra cómo crearlo
+ * con un ejemplo genérico.
+"""
+
+"""
+ * DIFICULTAD EXTRA (opcional):
+ * Crea un decorador que sea capaz de contabilizar cuántas veces
+ * se ha llamado a una función y aplícalo a una función de tu elección.
+"""
diff --git a/Logic/27.Logs.py b/Logic/27.Logs.py
new file mode 100644
index 0000000..5282f6c
--- /dev/null
+++ b/Logic/27.Logs.py
@@ -0,0 +1,17 @@
+"""
+ * EJERCICIO:
+ * Explora el concepto de "logging" en tu lenguaje. Configúralo y muestra
+ * un ejemplo con cada nivel de "severidad" disponible.
+"""
+
+"""
+ * DIFICULTAD EXTRA (opcional):
+ * Crea un programa ficticio de gestión de tareas que permita añadir, eliminar
+ * y listar dichas tareas.
+ * - Añadir: recibe nombre y descripción.
+ * - Eliminar: por nombre de la tarea.
+ * Implementa diferentes mensajes de log que muestren información según la 
+ * tarea ejecutada (a tu elección).
+ * Utiliza el log para visualizar el tiempo de ejecución de cada tarea. 
+"""
+

From 0e1969c21b211c4fad8a158b18881e0c26654cd5 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 11 Apr 2025 20:21:46 +0200
Subject: [PATCH 27/66] Singleton

---
 Logic/25.Singleton.py | 60 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 50 insertions(+), 10 deletions(-)

diff --git a/Logic/25.Singleton.py b/Logic/25.Singleton.py
index 22e3255..7058b93 100644
--- a/Logic/25.Singleton.py
+++ b/Logic/25.Singleton.py
@@ -1,13 +1,53 @@
- """
+"""
  * EJERCICIO:
  * Explora el patrón de diseño "singleton" y muestra cómo crearlo
  * con un ejemplo genérico.
- """
-
- """
- * DIFICULTAD EXTRA (opcional):
- * Utiliza el patrón de diseño "singleton" para representar una clase que
- * haga referencia a la sesión de usuario de una aplicación ficticia.
- * La sesión debe permitir asignar un usuario (id, username, nombre y email),
- * recuperar los datos del usuario y borrar los datos de la sesión.
- """
\ No newline at end of file
+"""
+class SesionUsuario:
+    _instancia = None
+
+    def __new__(cls):
+        if cls._instancia is None:
+            cls._instancia = super(SesionUsuario, cls).__new__(cls)
+            cls._instancia.usuario = None
+        return cls._instancia
+
+    def asignar_usuario(self, id, username, nombre, email):
+        self.usuario = {
+            "id": id,
+            "username": username,
+            "nombre": nombre,
+            "email": email
+        }
+        print(f"✅ Usuario asignado: {self.usuario['username']}")
+
+    def obtener_usuario(self):
+        if self.usuario:
+            return self.usuario
+        else:
+            print("⚠️ No hay usuario en sesión.")
+            return None
+
+    def cerrar_sesion(self):
+        if self.usuario:
+            print(f"🔒 Sesión cerrada para: {self.usuario['username']}")
+            self.usuario = None
+        else:
+            print("⚠️ No hay sesión activa para cerrar.")
+
+
+# Ejemplo de uso
+if __name__ == "__main__":
+    sesion1 = SesionUsuario()
+    sesion1.asignar_usuario(1, "jdoe", "John Doe", "jdoe@example.com")
+
+    sesion2 = SesionUsuario()
+    print("\n📋 Datos de usuario desde otra instancia:")
+    print(sesion2.obtener_usuario())
+
+    print("\n🧪 Verificando que ambas instancias son la misma:")
+    print(sesion1 is sesion2)  # Debe ser True
+
+    sesion2.cerrar_sesion()
+    print(sesion1.obtener_usuario())
+

From 8169c0a2e07bdd56adfcd4dc339e858100966d32 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 11 Apr 2025 20:25:53 +0200
Subject: [PATCH 28/66] Decorator

---
 Logic/26.Decorator.py | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/Logic/26.Decorator.py b/Logic/26.Decorator.py
index a5b28ae..876e902 100644
--- a/Logic/26.Decorator.py
+++ b/Logic/26.Decorator.py
@@ -3,9 +3,44 @@
  * Explora el concepto de "decorador" y muestra cómo crearlo
  * con un ejemplo genérico.
 """
+class Decorador:
+    def __init__(self, funcion):
+        self.funcion = funcion
+
+    def __call__(self, *args, **kwargs):
+        print("🔄 Antes de ejecutar la función.")
+        resultado = self.funcion(*args, **kwargs)
+        print("✅ Después de ejecutar la función.")
+        return resultado
+@Decorador
+def saludar(nombre):
+    print(f"Hola, {nombre}!")
+
+# Ejemplo de uso
+if __name__ == "__main__":
+    saludar("Juan")
+    print("\n🔄 Decorador aplicado a la función 'saludar'.")
 
 """
  * DIFICULTAD EXTRA (opcional):
  * Crea un decorador que sea capaz de contabilizar cuántas veces
  * se ha llamado a una función y aplícalo a una función de tu elección.
 """
+class ContadorLlamadas:
+    def __init__(self, funcion):
+        self.funcion = funcion
+        self.contador = 0
+
+    def __call__(self, *args, **kwargs):
+        self.contador += 1
+        print(f"🔄 Llamada número: {self.contador}")
+        return self.funcion(*args, **kwargs)
+@ContadorLlamadas
+def sumar(a, b):
+    return a + b
+# Ejemplo de uso
+if __name__ == "__main__":
+    print(sumar(3, 4))
+    print(sumar(5, 6))
+    print(sumar(7, 8))
+    print("\n🔄 Decorador 'ContadorLlamadas' aplicado a la función 'sumar'.")
\ No newline at end of file

From b263d7359734dcbf77affad5492d7ee6d09ba002 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 11 Apr 2025 20:35:18 +0200
Subject: [PATCH 29/66] Logs

---
 Logic/27.Logs.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/Logic/27.Logs.py b/Logic/27.Logs.py
index 5282f6c..bd8829e 100644
--- a/Logic/27.Logs.py
+++ b/Logic/27.Logs.py
@@ -3,6 +3,21 @@
  * Explora el concepto de "logging" en tu lenguaje. Configúralo y muestra
  * un ejemplo con cada nivel de "severidad" disponible.
 """
+import logging
+
+# Configuración del logging
+logging.basicConfig(
+    level=logging.DEBUG,  # Nivel mínimo a registrar
+    format='[%(levelname)s] %(asctime)s - %(message)s',
+    datefmt='%H:%M:%S'
+)
+
+# Ejemplos por nivel
+logging.debug("Este es un mensaje DEBUG: útil para desarrollo.")
+logging.info("Este es un mensaje INFO: todo funciona correctamente.")
+logging.warning("Este es un WARNING: algo inesperado ha ocurrido.")
+logging.error("Este es un ERROR: algo ha fallado.")
+logging.critical("Este es un CRITICAL: fallo crítico del sistema.")
 
 """
  * DIFICULTAD EXTRA (opcional):
@@ -14,4 +29,58 @@
  * tarea ejecutada (a tu elección).
  * Utiliza el log para visualizar el tiempo de ejecución de cada tarea. 
 """
+import logging
+import time
+
+# Configuración del logging
+logging.basicConfig(
+    level=logging.DEBUG,
+    format='[%(levelname)s] %(asctime)s - %(message)s',
+    datefmt='%H:%M:%S'
+)
+
+# Lista de tareas
+tareas = []
+
+# Función para añadir tarea
+def añadir_tarea(nombre, descripcion):
+    start_time = time.time()  # Empezamos a medir el tiempo de ejecución
+    tareas.append({'nombre': nombre, 'descripcion': descripcion})
+    end_time = time.time()  # Fin de la medición del tiempo
+    execution_time = end_time - start_time
+    logging.info(f'✔️ Tarea añadida: {nombre}')
+    logging.debug(f'⏱ Tiempo de ejecución de añadir_tarea: {execution_time:.4f} segundos')
+
+# Función para eliminar tarea
+def eliminar_tarea(nombre):
+    start_time = time.time()
+    tarea_encontrada = False
+    for tarea in tareas:
+        if tarea['nombre'] == nombre:
+            tareas.remove(tarea)
+            tarea_encontrada = True
+            break
+    end_time = time.time()
+    execution_time = end_time - start_time
+    if tarea_encontrada:
+        logging.info(f'✔️ Tarea eliminada: {nombre}')
+    else:
+        logging.warning(f'⚠️ Tarea no encontrada: {nombre}')
+    logging.debug(f'⏱ Tiempo de ejecución de eliminar_tarea: {execution_time:.4f} segundos')
+
+# Función para listar tareas
+def listar_tareas():
+    logging.info('📋 Listando tareas actuales:')
+    if tareas:
+        for tarea in tareas:
+            logging.info(f'📝 {tarea["nombre"]}: {tarea["descripcion"]}')
+    else:
+        logging.warning('⚠️ No hay tareas en la lista.')
 
+# Ejemplo de uso
+if __name__ == '__main__':
+    añadir_tarea('Comprar leche', 'Ir al supermercado y comprar leche.')
+    añadir_tarea('Estudiar Python', 'Practicar programación en Python para mejorar habilidades.')
+    listar_tareas()
+    eliminar_tarea('Comprar leche')
+    listar_tareas()

From 08e350f1bc55a992152cae2b6946a9d34b52c62c Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 14 Apr 2025 21:53:16 +0200
Subject: [PATCH 30/66] SRP

---
 SOLID/SRP.py | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 166 insertions(+)
 create mode 100644 SOLID/SRP.py

diff --git a/SOLID/SRP.py b/SOLID/SRP.py
new file mode 100644
index 0000000..4d6322e
--- /dev/null
+++ b/SOLID/SRP.py
@@ -0,0 +1,166 @@
+"""
+ * EJERCICIO:
+ * Explora el "Principio SOLID de Responsabilidad Única (Single Responsibility
+ * Principle, SRP)" y crea un ejemplo simple donde se muestre su funcionamiento
+ * de forma correcta e incorrecta.
+"""
+#/Ejemplo erroneo
+class Usuario:
+    def __init__(self, nombre, email):
+        self.nombre = nombre
+        self.email = email
+
+    def guardar_usuario(self):
+        # Código para guardar el usuario en la base de datos
+        pass
+
+    def enviar_email(self):
+        # Código para enviar un email al usuario
+        pass
+# Ejemplo OK
+class Usuario:
+    def __init__(self, nombre, email):
+        self.nombre = nombre
+        self.email = email
+class UsuarioDB:
+    def guardar_usuario(self, usuario):
+        # Código para guardar el usuario en la base de datos
+        pass
+class EmailService:
+    def enviar_email(self, usuario):
+        # Código para enviar un email al usuario
+        pass
+# Ejemplo de uso
+usuario = Usuario("Alice", "alice@example.com")
+gestor = UsuarioDB()
+email_service = EmailService()
+
+gestor.guardar_usuario(usuario)
+email_service.enviar_email(usuario)
+
+"""
+* DIFICULTAD EXTRA (opcional):
+ * Desarrolla un sistema de gestión para una biblioteca. El sistema necesita
+ * manejar diferentes aspectos como el registro de libros, la gestión de usuarios
+ * y el procesamiento de préstamos de libros.
+ * Requisitos:
+ * 1. Registrar libros: El sistema debe permitir agregar nuevos libros con 
+ * información básica como título, autor y número de copias disponibles.
+ * 2. Registrar usuarios: El sistema debe permitir agregar nuevos usuarios con
+ * información básica como nombre, número de identificación y correo electrónico.
+ * 3. Procesar préstamos de libros: El sistema debe permitir a los usuarios
+ * tomar prestados y devolver libros.
+ * Instrucciones:
+ * 1. Diseña una clase que no cumple el SRP: Crea una clase Library que maneje
+ * los tres aspectos mencionados anteriormente (registro de libros, registro de
+ * usuarios y procesamiento de préstamos).
+ * 2. Refactoriza el código: Separa las responsabilidades en diferentes clases
+ * siguiendo el Principio de Responsabilidad Única.
+"""
+
+# ❌ Ejemplo que VIOLA el SRP
+class Library:
+    def __init__(self):
+        self.libros = []
+        self.usuarios = []
+        self.prestamos = []
+
+    def registrar_libro(self, titulo, autor, copias):
+        self.libros.append({"titulo": titulo, "autor": autor, "copias": copias})
+
+    def registrar_usuario(self, nombre, id_usuario, email):
+        self.usuarios.append({"nombre": nombre, "id": id_usuario, "email": email})
+
+    def prestar_libro(self, id_usuario, titulo_libro):
+        libro = next((l for l in self.libros if l["titulo"] == titulo_libro and l["copias"] > 0), None)
+        if libro:
+            self.prestamos.append({"usuario": id_usuario, "libro": titulo_libro})
+            libro["copias"] -= 1
+            print(f"Libro '{titulo_libro}' prestado a usuario {id_usuario}")
+        else:
+            print("Libro no disponible")
+
+    def devolver_libro(self, id_usuario, titulo_libro):
+        prestamo = next((p for p in self.prestamos if p["usuario"] == id_usuario and p["libro"] == titulo_libro), None)
+        if prestamo:
+            self.prestamos.remove(prestamo)
+            for libro in self.libros:
+                if libro["titulo"] == titulo_libro:
+                    libro["copias"] += 1
+                    break
+            print(f"Libro '{titulo_libro}' devuelto por usuario {id_usuario}")
+        else:
+            print("No se encontró el préstamo")
+
+
+# ✅ Refactorizado aplicando SRP
+class Libro:
+    def __init__(self, titulo, autor, copias):
+        self.titulo = titulo
+        self.autor = autor
+        self.copias = copias
+
+class Usuario:
+    def __init__(self, nombre, id_usuario, email):
+        self.nombre = nombre
+        self.id_usuario = id_usuario
+        self.email = email
+
+class GestorLibros:
+    def __init__(self):
+        self.libros = []
+
+    def registrar_libro(self, titulo, autor, copias):
+        self.libros.append(Libro(titulo, autor, copias))
+
+    def buscar_libro(self, titulo):
+        return next((libro for libro in self.libros if libro.titulo == titulo), None)
+
+class GestorUsuarios:
+    def __init__(self):
+        self.usuarios = []
+
+    def registrar_usuario(self, nombre, id_usuario, email):
+        self.usuarios.append(Usuario(nombre, id_usuario, email))
+
+    def obtener_usuario(self, id_usuario):
+        return next((u for u in self.usuarios if u.id_usuario == id_usuario), None)
+
+class GestorPrestamos:
+    def __init__(self, gestor_libros, gestor_usuarios):
+        self.prestamos = []
+        self.gestor_libros = gestor_libros
+        self.gestor_usuarios = gestor_usuarios
+
+    def prestar_libro(self, id_usuario, titulo_libro):
+        libro = self.gestor_libros.buscar_libro(titulo_libro)
+        usuario = self.gestor_usuarios.obtener_usuario(id_usuario)
+        if libro and usuario and libro.copias > 0:
+            self.prestamos.append({"usuario": id_usuario, "libro": titulo_libro})
+            libro.copias -= 1
+            print(f"✅ '{titulo_libro}' prestado a {usuario.nombre}")
+        else:
+            print("❌ Libro no disponible o usuario no encontrado")
+
+    def devolver_libro(self, id_usuario, titulo_libro):
+        prestamo = next((p for p in self.prestamos if p["usuario"] == id_usuario and p["libro"] == titulo_libro), None)
+        if prestamo:
+            self.prestamos.remove(prestamo)
+            libro = self.gestor_libros.buscar_libro(titulo_libro)
+            if libro:
+                libro.copias += 1
+            print(f"🔄 '{titulo_libro}' devuelto por usuario {id_usuario}")
+        else:
+            print("⚠️ Préstamo no encontrado")
+
+# 🧪 Ejemplo de uso
+if __name__ == '__main__':
+    libros = GestorLibros()
+    usuarios = GestorUsuarios()
+    prestamos = GestorPrestamos(libros, usuarios)
+
+    libros.registrar_libro("1984", "George Orwell", 2)
+    usuarios.registrar_usuario("Alice", 1, "alice@mail.com")
+
+    prestamos.prestar_libro(1, "1984")
+    prestamos.devolver_libro(1, "1984")

From b01bb7a8cf27b39f0bbb3b90f316263698484f8f Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 15 Apr 2025 07:42:15 +0200
Subject: [PATCH 31/66] OCP

---
 SOLID/OCP.py | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 SOLID/OCP.py

diff --git a/SOLID/OCP.py b/SOLID/OCP.py
new file mode 100644
index 0000000..7a8add9
--- /dev/null
+++ b/SOLID/OCP.py
@@ -0,0 +1,123 @@
+"""
+ * EJERCICIO:
+ * Explora el "Principio SOLID Abierto-Cerrado (Open-Close Principle, OCP)" 
+ * y crea un ejemplo simple donde se muestre su funcionamiento
+ * de forma correcta e incorrecta.
+"""
+#❌ Ejemplo INCORRECTO (Violando OCP)
+class CalculadoraDescuentos:
+    def calcular(self, tipo_cliente, total):
+        if tipo_cliente == "regular":
+            return total * 0.95
+        elif tipo_cliente == "vip":
+            return total * 0.90
+        elif tipo_cliente == "super_vip":
+            return total * 0.85
+        else:
+            return total
+
+#✅ Ejemplo CORRECTO (Aplicando OCP con polimorfismo)
+from abc import ABC, abstractmethod
+
+class EstrategiaDescuento(ABC):
+    @abstractmethod
+    def calcular_descuento(self, total):
+        pass
+
+class DescuentoRegular(EstrategiaDescuento):
+    def calcular_descuento(self, total):
+        return total * 0.95
+
+class DescuentoVIP(EstrategiaDescuento):
+    def calcular_descuento(self, total):
+        return total * 0.90
+
+class DescuentoSuperVIP(EstrategiaDescuento):
+    def calcular_descuento(self, total):
+        return total * 0.85
+
+class CalculadoraDescuentos:
+    def __init__(self, estrategia: EstrategiaDescuento):
+        self.estrategia = estrategia
+
+    def calcular(self, total):
+        return self.estrategia.calcular_descuento(total)
+
+cliente_vip = CalculadoraDescuentos(DescuentoVIP())
+total_con_descuento = cliente_vip.calcular(100)
+print(f"Total con descuento VIP: {total_con_descuento}")
+
+"""
+ * DIFICULTAD EXTRA (opcional):
+ * Desarrolla una calculadora que necesita realizar diversas operaciones matemáticas. 
+ * Requisitos:
+ * - Debes diseñar un sistema que permita agregar nuevas operaciones utilizando el OCP.
+ * Instrucciones:
+ * 1. Implementa las operaciones de suma, resta, multiplicación y división.
+ * 2. Comprueba que el sistema funciona.
+ * 3. Agrega una quinta operación para calcular potencias.
+ * 4. Comprueba que se cumple el OCP.
+"""
+from abc import ABC, abstractmethod
+
+# Interfaz para todas las operaciones
+class Operacion(ABC):
+    @abstractmethod
+    def ejecutar(self, a, b):
+        pass
+
+# Operaciones básicas
+class Suma(Operacion):
+    def ejecutar(self, a, b):
+        return a + b
+
+class Resta(Operacion):
+    def ejecutar(self, a, b):
+        return a - b
+
+class Multiplicacion(Operacion):
+    def ejecutar(self, a, b):
+        return a * b
+
+class Division(Operacion):
+    def ejecutar(self, a, b):
+        if b == 0:
+            raise ValueError("No se puede dividir por cero")
+        return a / b
+
+# ✅ Nueva operación: potencia (extensión sin modificar código anterior)
+class Potencia(Operacion):
+    def ejecutar(self, a, b):
+        return a ** b
+
+# Calculadora que usa operaciones
+class Calculadora:
+    def __init__(self):
+        self.operaciones = {}
+
+    def registrar_operacion(self, nombre, operacion):
+        self.operaciones[nombre] = operacion
+
+    def calcular(self, nombre, a, b):
+        if nombre in self.operaciones:
+            return self.operaciones[nombre].ejecutar(a, b)
+        else:
+            raise ValueError(f"Operación '{nombre}' no registrada")
+
+# 🧪 Ejemplo de uso
+def main():
+    calc = Calculadora()
+    calc.registrar_operacion("suma", Suma())
+    calc.registrar_operacion("resta", Resta())
+    calc.registrar_operacion("multiplicacion", Multiplicacion())
+    calc.registrar_operacion("division", Division())
+    calc.registrar_operacion("potencia", Potencia())
+
+    print("Suma:", calc.calcular("suma", 10, 5))
+    print("Resta:", calc.calcular("resta", 10, 5))
+    print("Multiplicacion:", calc.calcular("multiplicacion", 10, 5))
+    print("Division:", calc.calcular("division", 10, 2))
+    print("Potencia:", calc.calcular("potencia", 2, 3))
+
+if __name__ == '__main__':
+    main()

From 1d90e92db197c7cb8b26ca1ffcbee9b457c81d39 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 15 Apr 2025 07:52:56 +0200
Subject: [PATCH 32/66] LSP

---
 SOLID/LSP.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 112 insertions(+)
 create mode 100644 SOLID/LSP.py

diff --git a/SOLID/LSP.py b/SOLID/LSP.py
new file mode 100644
index 0000000..08a376e
--- /dev/null
+++ b/SOLID/LSP.py
@@ -0,0 +1,112 @@
+"""
+ * EJERCICIO:
+ * Explora el "Principio SOLID de Sustitución de Liskov (Liskov Substitution Principle, LSP)" 
+ * y crea un ejemplo simple donde se muestre su funcionamiento
+ * de forma correcta e incorrecta.
+"""
+#❌ Ejemplo INCORRECTO (Violando LSP)
+class Ave:
+    def volar(self):
+        print("Estoy volando")
+
+class Pinguino(Ave):
+    def volar(self):
+        raise Exception("¡Los pingüinos no pueden volar!")
+
+def hacer_volar(ave: Ave):
+    ave.volar()
+
+# Esto funciona:
+hacer_volar(Ave())
+
+# Pero esto rompe el principio LSP:
+#hacer_volar(Pinguino())  # ❌ Error en tiempo de ejecución
+
+#✅ Ejemplo CORRECTO (Aplicando LSP)
+from abc import ABC, abstractmethod
+
+class Ave(ABC):
+    @abstractmethod
+    def hacer_sonido(self):
+        pass
+
+class AveVoladora(Ave):
+    @abstractmethod
+    def volar(self):
+        pass
+
+class Aguila(AveVoladora):
+    def hacer_sonido(self):
+        print("¡Kree!")
+
+    def volar(self):
+        print("El águila está volando")
+
+class Pinguino(Ave):
+    def hacer_sonido(self):
+        print("¡Noot noot!")
+
+# ✅ Ambas clases se comportan correctamente dentro de su jerarquía
+
+def presentar_ave(ave: Ave):
+    ave.hacer_sonido()
+
+presentar_ave(Aguila())     # ✅
+presentar_ave(Pinguino())   # ✅
+
+
+"""
+ * DIFICULTAD EXTRA (opcional):
+ * Crea una jerarquía de vehículos. Todos ellos deben poder acelerar y frenar, así como
+ * cumplir el LSP.
+ * Instrucciones:
+ * 1. Crea la clase Vehículo.
+ * 2. Añade tres subclases de Vehículo.
+ * 3. Implementa las operaciones "acelerar" y "frenar" como corresponda.
+ * 4. Desarrolla un código que compruebe que se cumple el LSP.
+"""
+from abc import ABC, abstractmethod
+
+# 1. Clase base
+class Vehiculo(ABC):
+    @abstractmethod
+    def acelerar(self):
+        pass
+
+    @abstractmethod
+    def frenar(self):
+        pass
+
+# 2. Subclases de Vehículo
+class Coche(Vehiculo):
+    def acelerar(self):
+        print("El coche acelera suavemente.")
+
+    def frenar(self):
+        print("El coche frena con ABS.")
+
+class Moto(Vehiculo):
+    def acelerar(self):
+        print("La moto acelera rápidamente.")
+
+    def frenar(self):
+        print("La moto frena con los frenos de disco.")
+
+class Camion(Vehiculo):
+    def acelerar(self):
+        print("El camión acelera lentamente debido a su peso.")
+
+    def frenar(self):
+        print("El camión frena con aire comprimido.")
+
+# 4. Función que comprueba LSP
+def probar_vehiculo(vehiculo: Vehiculo):
+    print(f"\nProbando un {vehiculo.__class__.__name__}:")
+    vehiculo.acelerar()
+    vehiculo.frenar()
+
+# 🧪 Prueba del cumplimiento de LSP
+if __name__ == '__main__':
+    vehiculos = [Coche(), Moto(), Camion()]
+    for v in vehiculos:
+        probar_vehiculo(v)

From ca3adbb40f726f87e4c1e14b9a1aeec72ed1eb61 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 16 Apr 2025 08:15:22 +0200
Subject: [PATCH 33/66] ISP

---
 SOLID/ISP.py | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 150 insertions(+)
 create mode 100644 SOLID/ISP.py

diff --git a/SOLID/ISP.py b/SOLID/ISP.py
new file mode 100644
index 0000000..a7b17a7
--- /dev/null
+++ b/SOLID/ISP.py
@@ -0,0 +1,150 @@
+"""
+EJERCICIO:
+ * Explora el "Principio SOLID de Segregación de Interfaces (Interface Segregation Principle, ISP)"
+ * y crea un ejemplo simple donde se muestre su funcionamiento de forma correcta e incorrecta.
+"""
+# ❌ Ejemplo INCORRECTO (Violando ISP)
+from abc import ABC, abstractmethod
+
+class Animal(ABC):
+    @abstractmethod
+    def volar(self):
+        pass
+
+    @abstractmethod
+    def nadar(self):
+        pass
+
+class Pato(Animal):
+    def volar(self):
+        print("El pato está volando")
+
+    def nadar(self):
+        print("El pato está nadando")
+
+class Perro(Animal):
+    def volar(self):
+        raise NotImplementedError("¡El perro no puede volar!")
+
+    def nadar(self):
+        print("El perro está nadando")
+
+
+# ✅ Ejemplo CORRECTO (Aplicando ISP)
+from abc import ABC, abstractmethod
+
+class Nadador(ABC):
+    @abstractmethod
+    def nadar(self):
+        pass
+
+class Volador(ABC):
+    @abstractmethod
+    def volar(self):
+        pass
+
+class Pato(Nadador, Volador):
+    def nadar(self):
+        print("El pato nada en el lago")
+
+    def volar(self):
+        print("El pato vuela sobre el campo")
+
+class Perro(Nadador):
+    def nadar(self):
+        print("El perro nada en la piscina")
+
+def hacer_nadar(nadador: Nadador):
+    nadador.nadar()
+
+def hacer_volar(volador: Volador):
+    volador.volar()
+
+pato = Pato()
+perro = Perro()
+
+hacer_nadar(pato)
+hacer_volar(pato)
+hacer_nadar(perro)
+# hacer_volar(perro)  # ❌ Esto daría error porque Perro no es un Volador
+
+
+"""
+* DIFICULTAD EXTRA (opcional):
+ * Crea un gestor de impresoras.
+ * Requisitos:
+ * 1. Algunas impresoras sólo imprimen en blanco y negro.
+ * 2. Otras sólo a color.
+ * 3. Otras son multifunción, pueden imprimir, escanear y enviar fax.
+ * Instrucciones:
+ * 1. Implementa el sistema, con los diferentes tipos de impresoras y funciones.
+ * 2. Aplica el ISP a la implementación.
+ * 3. Desarrolla un código que compruebe que se cumple el principio.
+"""
+from abc import ABC, abstractmethod
+
+# Interfaces segregadas según funcionalidad
+class ImpresoraBlancoNegro(ABC):
+    @abstractmethod
+    def imprimir_bn(self, documento):
+        pass
+
+class ImpresoraColor(ABC):
+    @abstractmethod
+    def imprimir_color(self, documento):
+        pass
+
+class Escaner(ABC):
+    @abstractmethod
+    def escanear(self, documento):
+        pass
+
+class Fax(ABC):
+    @abstractmethod
+    def enviar_fax(self, documento):
+        pass
+
+class ImpresoraBN(ImpresoraBlancoNegro):
+    def imprimir_bn(self, documento):
+        print(f"Imprimiendo en blanco y negro: {documento}")
+
+class ImpresoraColorSimple(ImpresoraColor):
+    def imprimir_color(self, documento):
+        print(f"Imprimiendo en color: {documento}")
+
+class ImpresoraMultifuncion(ImpresoraBlancoNegro, ImpresoraColor, Escaner, Fax):
+    def imprimir_bn(self, documento):
+        print(f"[MF] Imprimiendo en blanco y negro: {documento}")
+
+    def imprimir_color(self, documento):
+        print(f"[MF] Imprimiendo en color: {documento}")
+
+    def escanear(self, documento):
+        print(f"[MF] Escaneando documento: {documento}")
+
+    def enviar_fax(self, documento):
+        print(f"[MF] Enviando fax del documento: {documento}")
+
+def probar_impresora_bn(impresora: ImpresoraBlancoNegro):
+    impresora.imprimir_bn("Factura 001")
+
+def probar_impresora_color(impresora: ImpresoraColor):
+    impresora.imprimir_color("Informe de ventas")
+
+def probar_multifuncion(impresora: Fax):
+    impresora.enviar_fax("Contrato firmado")
+
+# Crear instancias
+bn = ImpresoraBN()
+color = ImpresoraColorSimple()
+multifuncion = ImpresoraMultifuncion()
+
+# Probar funcionalidades
+probar_impresora_bn(bn)
+probar_impresora_color(color)
+probar_impresora_bn(multifuncion)
+probar_impresora_color(multifuncion)
+probar_multifuncion(multifuncion)
+
+# ❌ Estas no deberían tener métodos innecesarios, y así es
+# probar_impresora_color(bn)  # Esto causaría error si se descomenta

From 55205cf1f2022d3dcc527509680e2c625a0f172a Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 16 Apr 2025 08:23:08 +0200
Subject: [PATCH 34/66] DIP

---
 SOLID/DIP.py | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 SOLID/DIP.py

diff --git a/SOLID/DIP.py b/SOLID/DIP.py
new file mode 100644
index 0000000..c2edd6f
--- /dev/null
+++ b/SOLID/DIP.py
@@ -0,0 +1,109 @@
+"""
+ * EJERCICIO:
+ * Explora el "Principio SOLID de Inversión de Dependencias (Dependency Inversion
+ * Principle, DIP)" y crea un ejemplo simple donde se muestre su funcionamiento 
+ * de forma correcta e incorrecta.
+"""
+# ❌ Ejemplo INCORRECTO (violando DIP)
+
+# Módulo de alto nivel depende directamente de un módulo de bajo nivel
+
+class MotorGasolina:
+    def encender(self):
+        print("Motor de gasolina encendido")
+
+class Coche:
+    def __init__(self):
+        self.motor = MotorGasolina()  # 😬 dependencia directa
+
+    def arrancar(self):
+        self.motor.encender()
+
+
+
+# ✅ Ejemplo CORRECTO (cumpliendo DIP)
+
+from abc import ABC, abstractmethod
+
+class Motor(ABC):
+    @abstractmethod
+    def encender(self):
+        pass
+
+class MotorGasolina(Motor):
+    def encender(self):
+        print("Motor de gasolina encendido")
+
+class MotorElectrico(Motor):
+    def encender(self):
+        print("Motor eléctrico encendido")
+
+class Coche:
+    def __init__(self, motor: Motor):
+        self.motor = motor  # ✅ se inyecta la dependencia
+
+    def arrancar(self):
+        self.motor.encender()
+
+# Crear coches con diferentes motores sin modificar la clase Coche
+motor1 = MotorGasolina()
+motor2 = MotorElectrico()
+
+coche1 = Coche(motor1)
+coche2 = Coche(motor2)
+
+coche1.arrancar()  # Motor de gasolina encendido
+coche2.arrancar()  # Motor eléctrico encendido
+
+"""
+ * DIFICULTAD EXTRA (opcional):
+ * Crea un sistema de notificaciones.
+ * Requisitos:
+ * 1. El sistema puede enviar Email, PUSH y SMS (implementaciones específicas).
+ * 2. El sistema de notificaciones no puede depender de las implementaciones específicas.
+ * Instrucciones:
+ * 1. Crea la interfaz o clase abstracta.
+ * 2. Desarrolla las implementaciones específicas.
+ * 3. Crea el sistema de notificaciones usando el DIP.
+ * 4. Desarrolla un código que compruebe que se cumple el principio.
+"""
+
+from abc import ABC, abstractmethod
+
+class Notificador(ABC):
+    @abstractmethod
+    def enviar(self, mensaje: str):
+        pass
+
+class EmailNotificador(Notificador):
+    def enviar(self, mensaje: str):
+        print(f"[Email] Enviando: {mensaje}")
+
+class SMSNotificador(Notificador):
+    def enviar(self, mensaje: str):
+        print(f"[SMS] Enviando: {mensaje}")
+
+class PushNotificador(Notificador):
+    def enviar(self, mensaje: str):
+        print(f"[Push] Enviando: {mensaje}")
+
+class SistemaNotificaciones:
+    def __init__(self, notificador: Notificador):
+        self.notificador = notificador  # ✅ depende de la abstracción
+
+    def notificar(self, mensaje: str):
+        self.notificador.enviar(mensaje)
+
+# Podemos inyectar cualquier tipo de notificador sin modificar el sistema
+
+email = EmailNotificador()
+sms = SMSNotificador()
+push = PushNotificador()
+
+sistema_email = SistemaNotificaciones(email)
+sistema_sms = SistemaNotificaciones(sms)
+sistema_push = SistemaNotificaciones(push)
+
+sistema_email.notificar("Tu pedido ha sido enviado.")
+sistema_sms.notificar("Código de verificación: 123456")
+sistema_push.notificar("¡Nueva actualización disponible!")

From 1a6b357c1c3cbca97a43df35192ff4be1b7e8c9c Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 21 Apr 2025 08:33:02 +0200
Subject: [PATCH 35/66] SimuladorJJOO

---
 Advanced/01.SimuladorJJOO.py | 112 +++++++++++++++++++++++++++++++++++
 1 file changed, 112 insertions(+)
 create mode 100644 Advanced/01.SimuladorJJOO.py

diff --git a/Advanced/01.SimuladorJJOO.py b/Advanced/01.SimuladorJJOO.py
new file mode 100644
index 0000000..3f335da
--- /dev/null
+++ b/Advanced/01.SimuladorJJOO.py
@@ -0,0 +1,112 @@
+"""
+* EJERCICIO:
+ * ¡Los JJOO de París 2024 han comenzado!
+ * Crea un programa que simule la celebración de los juegos.
+ * El programa debe permitir al usuario registrar eventos y participantes,
+ * realizar la simulación de los eventos asignando posiciones de manera aleatoria
+ * y generar un informe final. Todo ello por terminal.
+ * Requisitos:
+ * 1. Registrar eventos deportivos.
+ * 2. Registrar participantes por nombre y país.
+ * 3. Simular eventos de manera aleatoria en base a los participantes (mínimo 3).
+ * 4. Asignar medallas (oro, plata y bronce) basándose en el resultado del evento.
+ * 5. Mostrar los ganadores por cada evento.
+ * 6. Mostrar el ranking de países según el número de medallas.
+ * Acciones:
+ * 1. Registro de eventos.
+ * 2. Registro de participantes.
+ * 3. Simulación de eventos.
+ * 4. Creación de informes.
+ * 5. Salir del programa.
+"""
+import random
+from collections import defaultdict
+
+class Participante:
+    def __init__(self, nombre, pais):
+        self.nombre = nombre
+        self.pais = pais
+
+class Evento:
+    def __init__(self, nombre):
+        self.nombre = nombre
+        self.participantes = []
+        self.resultados = []
+
+    def agregar_participante(self, participante):
+        self.participantes.append(participante)
+
+    def simular(self):
+        if len(self.participantes) < 3:
+            print(f"No hay suficientes participantes en {self.nombre} para simular.")
+            return False
+        self.resultados = random.sample(self.participantes, len(self.participantes))
+        return True
+
+    def obtener_podio(self):
+        return self.resultados[:3]
+
+class JuegosOlimpicos:
+    def __init__(self):
+        self.eventos = []
+        self.paises_medallas = defaultdict(lambda: {'oro': 0, 'plata': 0, 'bronce': 0})
+
+    def registrar_evento(self, nombre):
+        self.eventos.append(Evento(nombre))
+
+    def listar_eventos(self):
+        for i, evento in enumerate(self.eventos):
+            print(f"{i + 1}. {evento.nombre} ({len(evento.participantes)} participantes)")
+
+    def registrar_participante(self, evento_index, nombre, pais):
+        if 0 <= evento_index < len(self.eventos):
+            self.eventos[evento_index].agregar_participante(Participante(nombre, pais))
+
+    def simular_eventos(self):
+        for evento in self.eventos:
+            if evento.simular():
+                print(f"\nEvento: {evento.nombre}")
+                podio = evento.obtener_podio()
+                medallas = ['oro', 'plata', 'bronce']
+                for idx, participante in enumerate(podio):
+                    self.paises_medallas[participante.pais][medallas[idx]] += 1
+                    print(f"{medallas[idx].capitalize()}: {participante.nombre} ({participante.pais})")
+
+    def mostrar_ranking(self):
+        print("\nRanking de países por medallas:")
+        ranking = sorted(self.paises_medallas.items(), key=lambda p: (p[1]['oro'], p[1]['plata'], p[1]['bronce']), reverse=True)
+        for pais, medallas in ranking:
+            print(f"{pais} - Oro: {medallas['oro']}, Plata: {medallas['plata']}, Bronce: {medallas['bronce']}")
+
+def menu():
+    juegos = JuegosOlimpicos()
+    while True:
+        print("\n--- JJOO París 2024 ---")
+        print("1. Registrar evento")
+        print("2. Registrar participante")
+        print("3. Simular eventos")
+        print("4. Mostrar ranking de países")
+        print("5. Salir")
+        opcion = input("Seleccione una opción: ")
+
+        if opcion == '1':
+            nombre = input("Nombre del evento: ")
+            juegos.registrar_evento(nombre)
+        elif opcion == '2':
+            juegos.listar_eventos()
+            idx = int(input("Seleccione evento por número: ")) - 1
+            nombre = input("Nombre del participante: ")
+            pais = input("País: ")
+            juegos.registrar_participante(idx, nombre, pais)
+        elif opcion == '3':
+            juegos.simular_eventos()
+        elif opcion == '4':
+            juegos.mostrar_ranking()
+        elif opcion == '5':
+            print("¡Gracias por usar el simulador JJOO!")
+            break
+        else:
+            print("Opción no válida.")
+
+if __name__ == "__main__":
+    menu()

From 12aa3f8e402f9268ae16a298da0ad9085ba3743c Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 21 Apr 2025 10:13:35 +0200
Subject: [PATCH 36/66] BatallaDeadpoolWolverine

---
 Advanced/02.BatallaDeadpoolWolverine.py | 68 +++++++++++++++++++++++++
 1 file changed, 68 insertions(+)
 create mode 100644 Advanced/02.BatallaDeadpoolWolverine.py

diff --git a/Advanced/02.BatallaDeadpoolWolverine.py b/Advanced/02.BatallaDeadpoolWolverine.py
new file mode 100644
index 0000000..fabba9f
--- /dev/null
+++ b/Advanced/02.BatallaDeadpoolWolverine.py
@@ -0,0 +1,68 @@
+import random
+import time
+
+class Personaje:
+    def __init__(self, nombre, vida, min_dano, max_dano, evasion):
+        self.nombre = nombre
+        self.vida = vida
+        self.min_dano = min_dano
+        self.max_dano = max_dano
+        self.evasion = evasion  # probabilidad entre 0 y 1
+        self.omitir_turno = False
+
+    def atacar(self):
+        return random.randint(self.min_dano, self.max_dano)
+
+    def esquiva(self):
+        return random.random() < self.evasion
+
+    def recibir_dano(self, dano):
+        self.vida -= dano
+
+
+def simular_batalla(deadpool, wolverine):
+    turno = 1
+    atacante = deadpool
+    defensor = wolverine
+
+    while deadpool.vida > 0 and wolverine.vida > 0:
+        print(f"\n--- Turno {turno} ---")
+        time.sleep(1)
+
+        if atacante.omitir_turno:
+            print(f"{atacante.nombre} se está recuperando del daño máximo anterior y pierde este turno.")
+            atacante.omitir_turno = False
+        else:
+            if defensor.esquiva():
+                print(f"{defensor.nombre} esquivó el ataque de {atacante.nombre}!")
+            else:
+                dano = atacante.atacar()
+                defensor.recibir_dano(dano)
+                print(f"{atacante.nombre} ataca a {defensor.nombre} y causa {dano} de daño.")
+                if dano == atacante.max_dano:
+                    print(f"¡Daño máximo! {defensor.nombre} pierde su próximo turno para recuperarse.")
+                    defensor.omitir_turno = True
+
+        print(f"Vida de {deadpool.nombre}: {deadpool.vida} | Vida de {wolverine.nombre}: {wolverine.vida}")
+
+        atacante, defensor = defensor, atacante
+        turno += 1
+
+    print("\n=== ¡Fin del combate! ===")
+    if deadpool.vida <= 0 and wolverine.vida <= 0:
+        print("Ambos héroes han caído. ¡Es un empate épico!")
+    elif deadpool.vida <= 0:
+        print("Wolverine gana la batalla.")
+    else:
+        print("Deadpool gana la batalla.")
+
+
+if __name__ == "__main__":
+    print("=== ¡Deadpool vs Wolverine! ===")
+    vida_deadpool = int(input("Introduce la vida inicial de Deadpool: "))
+    vida_wolverine = int(input("Introduce la vida inicial de Wolverine: "))
+
+    deadpool = Personaje("Deadpool", vida_deadpool, 10, 100, 0.25)
+    wolverine = Personaje("Wolverine", vida_wolverine, 10, 120, 0.20)
+
+    simular_batalla(deadpool, wolverine)

From cc7c20f1a31412f17c896e4afae8e5f9bc19b758 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 22 Apr 2025 08:23:50 +0200
Subject: [PATCH 37/66] RescatandoMickey

---
 Advanced/03.RescatandoMickey.py | 104 ++++++++++++++++++++++++++++++++
 1 file changed, 104 insertions(+)
 create mode 100644 Advanced/03.RescatandoMickey.py

diff --git a/Advanced/03.RescatandoMickey.py b/Advanced/03.RescatandoMickey.py
new file mode 100644
index 0000000..73415e2
--- /dev/null
+++ b/Advanced/03.RescatandoMickey.py
@@ -0,0 +1,104 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡Disney ha presentado un montón de novedades en su D23! 
+ * Pero... ¿Dónde está Mickey?
+ * Mickey Mouse ha quedado atrapado en un laberinto mágico 
+ * creado por Maléfica.
+ * Desarrolla un programa para ayudarlo a escapar.
+ * Requisitos:
+ * 1. El laberinto está formado por un cuadrado de 6x6 celdas.
+ * 2. Los valores de las celdas serán:
+ *    - ⬜️ Vacío
+ *    - ⬛️ Obstáculo
+ *    - 🐭 Mickey
+ *    - 🚪 Salida
+ * Acciones:
+ * 1. Crea una matriz que represente el laberinto (no hace falta
+ * que se genere de manera automática).
+ * 2. Interactúa con el usuario por consola para preguntarle hacia
+ * donde se tiene que desplazar (arriba, abajo, izquierda o derecha).
+ * 3. Muestra la actualización del laberinto tras cada desplazamiento.
+ * 4. Valida todos los movimientos, teniendo en cuenta los límites
+ * del laberinto y los obstáculos. Notifica al usuario.
+ * 5. Finaliza el programa cuando Mickey llegue a la salida.
+ */
+"""
+import os
+
+# Símbolos del laberinto
+VACIO = "⬜️"
+OBSTACULO = "⬛️"
+MICKEY = "🐭"
+SALIDA = "🚪"
+
+# Laberinto 6x6 definido manualmente
+laberinto = [
+    [VACIO, OBSTACULO, VACIO, VACIO, VACIO, VACIO],
+    [VACIO, OBSTACULO, VACIO, OBSTACULO, OBSTACULO, VACIO],
+    [VACIO, VACIO, VACIO, VACIO, VACIO, VACIO],
+    [OBSTACULO, OBSTACULO, OBSTACULO, OBSTACULO, VACIO, OBSTACULO],
+    [VACIO, VACIO, VACIO, VACIO, VACIO, VACIO],
+    [VACIO, OBSTACULO, OBSTACULO, OBSTACULO, VACIO, SALIDA],
+]
+
+# Posición inicial de Mickey
+mickey_pos = [0, 0]
+laberinto[mickey_pos[0]][mickey_pos[1]] = MICKEY
+
+def imprimir_laberinto():
+    os.system('cls' if os.name == 'nt' else 'clear')
+    for fila in laberinto:
+        print(" ".join(fila))
+    print()
+
+def mover_mickey(direccion):
+    dx, dy = 0, 0
+    if direccion == "arriba":
+        dx = -1
+    elif direccion == "abajo":
+        dx = 1
+    elif direccion == "izquierda":
+        dy = -1
+    elif direccion == "derecha":
+        dy = 1
+    else:
+        print("Dirección no válida. Usa arriba, abajo, izquierda o derecha.")
+        return False
+
+    nueva_x = mickey_pos[0] + dx
+    nueva_y = mickey_pos[1] + dy
+
+    if not (0 <= nueva_x < 6 and 0 <= nueva_y < 6):
+        print("¡No puedes salirte del laberinto!")
+        return False
+    if laberinto[nueva_x][nueva_y] == OBSTACULO:
+        print("¡Hay un obstáculo en esa dirección!")
+        return False
+
+    # Verifica si llegó a la salida
+    if laberinto[nueva_x][nueva_y] == SALIDA:
+        laberinto[mickey_pos[0]][mickey_pos[1]] = VACIO
+        mickey_pos[0], mickey_pos[1] = nueva_x, nueva_y
+        laberinto[nueva_x][nueva_y] = MICKEY
+        imprimir_laberinto()
+        print("¡Mickey ha escapado del laberinto! 🎉🚪")
+        return True
+
+    # Mueve a Mickey
+    laberinto[mickey_pos[0]][mickey_pos[1]] = VACIO
+    mickey_pos[0], mickey_pos[1] = nueva_x, nueva_y
+    laberinto[nueva_x][nueva_y] = MICKEY
+    return False
+
+def juego():
+    imprimir_laberinto()
+    while True:
+        direccion = input("¿Hacia dónde debe ir Mickey? (arriba, abajo, izquierda, derecha): ").strip().lower()
+        terminado = mover_mickey(direccion)
+        imprimir_laberinto()
+        if terminado:
+            break
+
+if __name__ == "__main__":
+    juego()

From 452f8da1916f09a8701089f1884115ce4bc985f9 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 22 Apr 2025 08:26:56 +0200
Subject: [PATCH 38/66] ArbolGenealogico

---
 Advanced/04.ArbolGenealogico.py | 145 ++++++++++++++++++++++++++++++++
 1 file changed, 145 insertions(+)
 create mode 100644 Advanced/04.ArbolGenealogico.py

diff --git a/Advanced/04.ArbolGenealogico.py b/Advanced/04.ArbolGenealogico.py
new file mode 100644
index 0000000..2a5d8a8
--- /dev/null
+++ b/Advanced/04.ArbolGenealogico.py
@@ -0,0 +1,145 @@
+"""
+* EJERCICIO:
+ * ¡La Casa del Dragón ha finalizado y no volverá hasta 2026! 
+ * ¿Alguien se entera de todas las relaciones de parentesco
+ * entre personajes que aparecen en la saga?
+ * Desarrolla un árbol genealógico para relacionarlos (o invéntalo).
+ * Requisitos:
+ * 1. Estará formado por personas con las siguientes propiedades:
+ *    - Identificador único (obligatorio)
+ *    - Nombre (obligatorio)
+ *    - Pareja (opcional)
+ *    - Hijos (opcional)
+ * 2. Una persona sólo puede tener una pareja (para simplificarlo).
+ * 3. Las relaciones deben validarse dentro de lo posible.
+ *    Ejemplo: Un hijo no puede tener tres padres.
+ * Acciones:
+ * 1. Crea un programa que permita crear y modificar el árbol.
+ *    - Añadir y eliminar personas
+ *    - Modificar pareja e hijos
+ * 2. Podrás imprimir el árbol (de la manera que consideres).
+ * 
+ * NOTA: Ten en cuenta que la complejidad puede ser alta si
+ * se implementan todas las posibles relaciones. Intenta marcar
+ * tus propias reglas y límites para que te resulte asumible.
+"""
+class Persona:
+    def __init__(self, id, nombre):
+        self.id = id
+        self.nombre = nombre
+        self.pareja_id = None
+        self.hijos_ids = []
+
+    def __str__(self):
+        return f"{self.nombre} (ID: {self.id})"
+
+
+class ArbolGenealogico:
+    def __init__(self):
+        self.personas = {}
+
+    def agregar_persona(self, id, nombre):
+        if id in self.personas:
+            print("⚠️ Ya existe una persona con ese ID.")
+            return
+        self.personas[id] = Persona(id, nombre)
+        print(f"✅ Persona '{nombre}' añadida.")
+
+    def eliminar_persona(self, id):
+        if id not in self.personas:
+            print("⚠️ Persona no encontrada.")
+            return
+
+        # Eliminar de pareja y de hijos
+        for persona in self.personas.values():
+            if persona.pareja_id == id:
+                persona.pareja_id = None
+            if id in persona.hijos_ids:
+                persona.hijos_ids.remove(id)
+
+        del self.personas[id]
+        print(f"🗑️ Persona con ID {id} eliminada.")
+
+    def asignar_pareja(self, id1, id2):
+        if id1 not in self.personas or id2 not in self.personas:
+            print("⚠️ Uno de los ID no existe.")
+            return
+        p1, p2 = self.personas[id1], self.personas[id2]
+        if p1.pareja_id or p2.pareja_id:
+            print("⚠️ Alguno ya tiene pareja.")
+            return
+        p1.pareja_id = id2
+        p2.pareja_id = id1
+        print(f"💍 {p1.nombre} y {p2.nombre} ahora son pareja.")
+
+    def agregar_hijo(self, padre_id, hijo_id):
+        if padre_id not in self.personas or hijo_id not in self.personas:
+            print("⚠️ ID no encontrado.")
+            return
+        padre = self.personas[padre_id]
+        hijo = self.personas[hijo_id]
+
+        # Contar cuántos padres tiene ya
+        padres = [p for p in self.personas.values() if hijo_id in p.hijos_ids]
+        if len(padres) >= 2:
+            print("⚠️ El hijo ya tiene dos padres.")
+            return
+        if hijo_id not in padre.hijos_ids:
+            padre.hijos_ids.append(hijo_id)
+            print(f"👶 {hijo.nombre} ha sido asignado como hijo de {padre.nombre}.")
+
+    def imprimir_arbol(self):
+        for persona in self.personas.values():
+            print(f"\n🧍 {persona}")
+            if persona.pareja_id:
+                pareja = self.personas.get(persona.pareja_id)
+                print(f"   💞 Pareja: {pareja.nombre}")
+            if persona.hijos_ids:
+                hijos = [self.personas[hid].nombre for hid in persona.hijos_ids]
+                print(f"   👶 Hijos: {', '.join(hijos)}")
+
+    def menu(self):
+        while True:
+            print("\n--- Árbol Genealógico ---")
+            print("1. Añadir persona")
+            print("2. Eliminar persona")
+            print("3. Asignar pareja")
+            print("4. Asignar hijo")
+            print("5. Mostrar árbol")
+            print("6. Salir")
+            opcion = input("Elige una opción: ")
+
+            if opcion == "1":
+                id = input("ID: ")
+                nombre = input("Nombre: ")
+                self.agregar_persona(id, nombre)
+
+            elif opcion == "2":
+                id = input("ID de la persona a eliminar: ")
+                self.eliminar_persona(id)
+
+            elif opcion == "3":
+                id1 = input("ID de la primera persona: ")
+                id2 = input("ID de la segunda persona: ")
+                self.asignar_pareja(id1, id2)
+
+            elif opcion == "4":
+                padre_id = input("ID del padre o madre: ")
+                hijo_id = input("ID del hijo: ")
+                self.agregar_hijo(padre_id, hijo_id)
+
+            elif opcion == "5":
+                self.imprimir_arbol()
+
+            elif opcion == "6":
+                print("¡Hasta la próxima! 🐉")
+                break
+
+            else:
+                print("❌ Opción no válida.")
+
+
+# Ejecución
+if __name__ == "__main__":
+    arbol = ArbolGenealogico()
+    arbol.menu()

From 38d6e091b8ba020c7b90135d7b17a6d834c3085e Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 22 Apr 2025 08:29:21 +0200
Subject: [PATCH 39/66] AnillosPoder

---
 Advanced/05.AnillosPoder.py | 60 +++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 Advanced/05.AnillosPoder.py

diff --git a/Advanced/05.AnillosPoder.py b/Advanced/05.AnillosPoder.py
new file mode 100644
index 0000000..9759291
--- /dev/null
+++ b/Advanced/05.AnillosPoder.py
@@ -0,0 +1,60 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡La temporada 2 de "Los Anillos de Poder" está a punto de estrenarse! 
+ * ¿Qué pasaría si tuvieras que encargarte de repartir los anillos
+ * entre las razas de la Tierra Media?
+ * Desarrolla un programa que se encargue de distribuirlos.
+ * Requisitos:
+ * 1. Los Elfos recibirán un número impar.
+ * 2. Los Enanos un número primo.
+ * 3. Los Hombres un número par.
+ * 4. Sauron siempre uno.
+ * Acciones:
+ * 1. Crea un programa que reciba el número total de anillos
+ *    y busque una posible combinación para repartirlos.
+ * 2. Muestra el reparto final o el error al realizarlo.
+ */
+"""
+import math
+
+def es_primo(n):
+    if n < 2:
+        return False
+    for i in range(2, int(math.sqrt(n))+1):
+        if n % i == 0:
+            return False
+    return True
+
+def buscar_reparto(total_anillos):
+    total_sin_sauron = total_anillos - 1
+    encontrado = False
+
+    for elfos in range(1, total_sin_sauron, 2):  # números impares
+        for enanos in range(2, total_sin_sauron - elfos):
+            if not es_primo(enanos):
+                continue
+            hombres = total_sin_sauron - elfos - enanos
+            if hombres >= 0 and hombres % 2 == 0:
+                print("\n🎉 Reparto posible:")
+                print(f"🧝 Elfos: {elfos}")
+                print(f"⛏️ Enanos: {enanos}")
+                print(f"🧔 Hombres: {hombres}")
+                print(f"👁️ Sauron: 1")
+                encontrado = True
+                return
+
+    if not encontrado:
+        print("❌ No hay una combinación válida para repartir los anillos.")
+
+# --- Interacción principal ---
+
+if __name__ == "__main__":
+    try:
+        total = int(input("🔢 Introduce el número total de anillos: "))
+        if total < 4:
+            print("⚠️ Debe haber al menos 4 anillos para hacer un reparto.")
+        else:
+            buscar_reparto(total)
+    except ValueError:
+        print("⚠️ Por favor, introduce un número válido.")

From 55287a1885bf00c9b8c2ba8d4b02f22e15d432ca Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 22 Apr 2025 08:35:24 +0200
Subject: [PATCH 40/66] SombreroSeleccionador

---
 Advanced/06.SombreoSeleccionador.py | 161 ++++++++++++++++++++++++++++
 1 file changed, 161 insertions(+)
 create mode 100644 Advanced/06.SombreoSeleccionador.py

diff --git a/Advanced/06.SombreoSeleccionador.py b/Advanced/06.SombreoSeleccionador.py
new file mode 100644
index 0000000..1e23e79
--- /dev/null
+++ b/Advanced/06.SombreoSeleccionador.py
@@ -0,0 +1,161 @@
+"""
+/*
+ * EJERCICIO:
+ * Cada 1 de septiembre, el Hogwarts Express parte hacia la escuela
+ * de programación de Hogwarts para magos y brujas del código.
+ * En ella, su famoso sombrero seleccionador ayuda a los programadores
+ * a encontrar su camino...
+ * Desarrolla un programa que simule el comportamiento del sombrero.
+ * Requisitos:
+ * 1. El sombrero realizará 10 preguntas para determinar la casa del alumno.
+ * 2. Deben existir 4 casas. Por ejemplo: Frontend, Backend, Mobile y Data.
+ *    (Puedes elegir las que quieras)
+ * Acciones:
+ * 1. Crea un programa que solicite el nombre del alumno y realice 10
+ *    preguntas, con cuatro posibles respuestas cada una.
+ * 2. Cada respuesta asigna puntos a cada una de las casas (a tu elección).
+ * 3. Una vez finalizado, el sombrero indica el nombre del alumno 
+ *    y a qué casa pertenecerá (resuelve el posible empate de manera aleatoria,
+ *    pero indicándole al alumno que la decisión ha sido complicada).
+ */
+"""
+import random
+
+# Definición de casas
+casas = {
+    "Frontend": 0,
+    "Backend": 0,
+    "Mobile": 0,
+    "Data": 0
+}
+
+# Preguntas y respuestas con puntuación
+preguntas = [
+    {
+        "texto": "¿Qué parte de un proyecto disfrutas más?",
+        "respuestas": {
+            "a": ("Diseñar interfaces llamativas", "Frontend"),
+            "b": ("Optimizar bases de datos", "Backend"),
+            "c": ("Construir apps nativas", "Mobile"),
+            "d": ("Analizar grandes volúmenes de datos", "Data")
+        }
+    },
+    {
+        "texto": "¿Cuál es tu herramienta favorita?",
+        "respuestas": {
+            "a": ("Figma", "Frontend"),
+            "b": ("PostgreSQL", "Backend"),
+            "c": ("Flutter", "Mobile"),
+            "d": ("Jupyter Notebook", "Data")
+        }
+    },
+    {
+        "texto": "¿Qué te motiva a programar?",
+        "respuestas": {
+            "a": ("Crear experiencias bonitas", "Frontend"),
+            "b": ("Resolver problemas complejos", "Backend"),
+            "c": ("Innovar en dispositivos móviles", "Mobile"),
+            "d": ("Descubrir patrones en los datos", "Data")
+        }
+    },
+    {
+        "texto": "¿Qué lenguaje prefieres?",
+        "respuestas": {
+            "a": ("JavaScript", "Frontend"),
+            "b": ("Python", "Backend"),
+            "c": ("Kotlin", "Mobile"),
+            "d": ("R", "Data")
+        }
+    },
+    {
+        "texto": "¿Cuál sería tu hechizo favorito?",
+        "respuestas": {
+            "a": ("Lumos: iluminar interfaces", "Frontend"),
+            "b": ("Alohomora: desbloquear lógica", "Backend"),
+            "c": ("Accio: invocar apps", "Mobile"),
+            "d": ("Legilimens: leer datos", "Data")
+        }
+    },
+    {
+        "texto": "¿Qué tipo de bugs odias más?",
+        "respuestas": {
+            "a": ("Desalineación de elementos", "Frontend"),
+            "b": ("Problemas de concurrencia", "Backend"),
+            "c": ("Errores de compatibilidad", "Mobile"),
+            "d": ("Errores estadísticos", "Data")
+        }
+    },
+    {
+        "texto": "¿Dónde te gustaría trabajar?",
+        "respuestas": {
+            "a": ("Agencia de diseño web", "Frontend"),
+            "b": ("Startup de IA", "Backend"),
+            "c": ("Empresa de wearables", "Mobile"),
+            "d": ("Laboratorio de datos", "Data")
+        }
+    },
+    {
+        "texto": "¿Qué superpoder querrías tener?",
+        "respuestas": {
+            "a": ("Crear belleza visual al instante", "Frontend"),
+            "b": ("Resolver algoritmos imposibles", "Backend"),
+            "c": ("Adaptarte a cualquier dispositivo", "Mobile"),
+            "d": ("Predecir el futuro con datos", "Data")
+        }
+    },
+    {
+        "texto": "¿Qué prefieres en tu tiempo libre?",
+        "respuestas": {
+            "a": ("Explorar webs creativas", "Frontend"),
+            "b": ("Ver documentales de tecnología", "Backend"),
+            "c": ("Probar nuevas apps", "Mobile"),
+            "d": ("Leer sobre ciencia y datos", "Data")
+        }
+    },
+    {
+        "texto": "¿Con qué personaje te identificas más?",
+        "respuestas": {
+            "a": ("Hermione: brillante y detallista", "Frontend"),
+            "b": ("Snape: estratega y profundo", "Backend"),
+            "c": ("Harry: valiente e intuitivo", "Mobile"),
+            "d": ("Dumbledore: sabio y analítico", "Data")
+        }
+    }
+]
+
+def preguntar(nombre):
+    print(f"\n🎩 Bienvenido/a, {nombre}. El Sombrero Seleccionador hablará...")
+    print("Responde las siguientes preguntas eligiendo a, b, c o d:\n")
+
+    for i, pregunta in enumerate(preguntas):
+        print(f"{i + 1}. {pregunta['texto']}")
+        for clave, (texto, _) in pregunta["respuestas"].items():
+            print(f"   {clave}) {texto}")
+        while True:
+            respuesta = input("Tu elección: ").lower()
+            if respuesta in pregunta["respuestas"]:
+                casa = pregunta["respuestas"][respuesta][1]
+                casas[casa] += 1
+                break
+            else:
+                print("Por favor, elige a, b, c o d.")
+        print()
+
+def determinar_casa(nombre):
+    max_puntos = max(casas.values())
+    ganadoras = [casa for casa, puntos in casas.items() if puntos == max_puntos]
+
+    print(f"🧙 El sombrero ha analizado tus respuestas, {nombre}...")
+    if len(ganadoras) == 1:
+        print(f"✅ ¡Has sido asignado a la casa: {ganadoras[0]}!")
+    else:
+        seleccionada = random.choice(ganadoras)
+        print("🤔 Hmm... ¡Qué decisión tan difícil!")
+        print(f"✅ Finalmente, el sombrero ha decidido: ¡{seleccionada}!")
+
+# ---- Ejecución principal ----
+
+if __name__ == "__main__":
+    nombre = input("🎓 ¿Cuál es tu nombre, joven aprendiz? ")
+    preguntar(nombre)
+    determinar_casa(nombre)

From a4b7178c814e4d6feedba4bc2f1e8a6081be542f Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 24 Apr 2025 07:53:55 +0200
Subject: [PATCH 41/66] OasisLinkinPark

---
 Advanced/07.OasisLinkinPark.py | 98 ++++++++++++++++++++++++++++++++++
 1 file changed, 98 insertions(+)
 create mode 100644 Advanced/07.OasisLinkinPark.py

diff --git a/Advanced/07.OasisLinkinPark.py b/Advanced/07.OasisLinkinPark.py
new file mode 100644
index 0000000..aa60bbc
--- /dev/null
+++ b/Advanced/07.OasisLinkinPark.py
@@ -0,0 +1,98 @@
+"""
+ * EJERCICIO:
+ * ¡Dos de las bandas más grandes de la historia están de vuelta!
+ * Oasis y Linkin Park han anunciado nueva gira, pero, ¿quién es más popular?
+ * Desarrolla un programa que se conecte al API de Spotify y los compare.
+ * Requisitos:
+ * 1. Crea una cuenta de desarrollo en https://developer.spotify.com.
+ * 2. Conéctate al API utilizando tu lenguaje de programación.
+ * 3. Recupera datos de los endpoint que tú quieras.
+ * Acciones:
+ * 1. Accede a las estadísticas de las dos bandas.
+ *    Por ejemplo: número total de seguidores, escuchas mensuales,
+ *    canción con más reproducciones...
+ * 2. Compara los resultados de, por lo menos, 3 endpoint.
+ * 3. Muestra todos los resultados por consola para notificar al usuario.
+ * 4. Desarrolla un criterio para seleccionar qué banda es más popular.
+"""
+import requests
+import base64
+
+# Rellena con tus credenciales de Spotify
+CLIENT_ID = "TU_CLIENT_ID"
+CLIENT_SECRET = "TU_CLIENT_SECRET"
+
+# Obtener token de autenticación
+def obtener_token():
+    auth = f"{CLIENT_ID}:{CLIENT_SECRET}"
+    auth_bytes = auth.encode('utf-8')
+    auth_base64 = base64.b64encode(auth_bytes).decode('utf-8')
+
+    headers = {
+        "Authorization": f"Basic {auth_base64}"
+    }
+
+    data = {
+        "grant_type": "client_credentials"
+    }
+
+    response = requests.post("https://accounts.spotify.com/api/token", headers=headers, data=data)
+    return response.json()["access_token"]
+
+# Buscar artista por nombre
+def buscar_artista(nombre, token):
+    url = f"https://api.spotify.com/v1/search?q={nombre}&type=artist"
+    headers = {"Authorization": f"Bearer {token}"}
+    response = requests.get(url, headers=headers)
+    items = response.json()["artists"]["items"]
+    return items[0] if items else None
+
+# Obtener top tracks de un artista
+def obtener_top_tracks(artist_id, token, market="US"):
+    url = f"https://api.spotify.com/v1/artists/{artist_id}/top-tracks?market={market}"
+    headers = {"Authorization": f"Bearer {token}"}
+    response = requests.get(url, headers=headers)
+    return response.json()["tracks"]
+
+# Comparar artistas
+def comparar_artistas(artista1, artista2):
+    token = obtener_token()
+
+    datos = {}
+    for nombre in [artista1, artista2]:
+        artista = buscar_artista(nombre, token)
+        top_tracks = obtener_top_tracks(artista["id"], token)
+        datos[nombre] = {
+            "followers": artista["followers"]["total"],
+            "popularity": artista["popularity"],
+            "top_track": top_tracks[0]["name"],
+            "top_track_popularity": top_tracks[0]["popularity"]
+        }
+
+    print("\n🎵 RESULTADOS COMPARATIVOS:")
+    for nombre in datos:
+        info = datos[nombre]
+        print(f"\n🎤 {nombre}")
+        print(f"- Seguidores: {info['followers']:,}")
+        print(f"- Popularidad (0-100): {info['popularity']}")
+        print(f"- Canción más popular: {info['top_track']} (popularidad: {info['top_track_popularity']})")
+
+    # Evaluar popularidad
+    print("\n🔥 VEREDICTO FINAL:")
+    puntos = {artista1: 0, artista2: 0}
+
+    # Comparación por puntos
+    for criterio in ["followers", "popularity", "top_track_popularity"]:
+        ganador = artista1 if datos[artista1][criterio] > datos[artista2][criterio] else artista2
+        puntos[ganador] += 1
+
+    if puntos[artista1] > puntos[artista2]:
+        print(f"🏆 ¡{artista1} es más popular!")
+    elif puntos[artista1] < puntos[artista2]:
+        print(f"🏆 ¡{artista2} es más popular!")
+    else:
+        print("🤝 ¡Empate técnico!")
+
+# Ejecutar comparación
+if __name__ == "__main__":
+    comparar_artistas("Oasis", "Linkin Park")

From 17341cc60fb6c9be6ca1ed106d446927c2260dfd Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 24 Apr 2025 08:20:37 +0200
Subject: [PATCH 42/66] MoureDevPro

---
 Advanced/08.MoureDevPro.py | 73 ++++++++++++++++++++++++++++++++++++++
 suscriptores.csv           |  7 ++++
 2 files changed, 80 insertions(+)
 create mode 100644 Advanced/08.MoureDevPro.py
 create mode 100644 suscriptores.csv

diff --git a/Advanced/08.MoureDevPro.py b/Advanced/08.MoureDevPro.py
new file mode 100644
index 0000000..76dbae0
--- /dev/null
+++ b/Advanced/08.MoureDevPro.py
@@ -0,0 +1,73 @@
+"""
+/*
+ * EJERCICIO:
+ * He presentado mi proyecto más importante del año: mouredev pro.
+ * Un campus para la comunidad, que lanzaré en octubre, donde estudiar
+ * programación de una manera diferente.
+ * Cualquier persona suscrita a la newsletter de https://mouredev.pro
+ * accederá a sorteos mensuales de suscripciones, regalos y descuentos.
+ *
+ * Desarrolla un programa que lea los registros de un fichero .csv y
+ * seleccione de manera aleatoria diferentes ganadores.
+ * Requisitos:
+ * 1. Crea un .csv con 3 columnas: id, email y status con valor "activo"
+ *    o "inactivo" (y datos ficticios).
+ *    Ejemplo: 1 | test@test.com | activo
+ *             2 | test2@test.com | inactivo
+ *    (El .csv no debe subirse como parte de la corrección)
+ * 2. Recupera los datos desde el programa y selecciona email aleatorios.
+ * Acciones:
+ * 1. Accede al fichero .csv y selecciona de manera aleatoria un email
+ *    ganador de una suscripción, otro ganador de un descuento y un último
+ *    ganador de un libro (sólo si tiene status "activo" y no está repetido).
+ * 2. Muestra los emails ganadores y su id.
+ * 3. Ten en cuenta que la primera fila (con el nombre de las columnas)
+ *    no debe tenerse en cuenta.
+ */
+"""
+import csv
+import random
+
+def cargar_usuarios_activos(ruta_csv):
+    usuarios = []
+
+    with open(ruta_csv, newline='', encoding='utf-8') as archivo:
+        lector = csv.DictReader(archivo)
+
+        for fila in lector:
+            if fila["status"].strip().lower() == "activo":
+                usuarios.append({
+                    "id": fila["id"],
+                    "email": fila["email"]
+                })
+
+    return usuarios
+
+def seleccionar_ganadores(usuarios):
+    if len(usuarios) < 3:
+        raise ValueError("No hay suficientes usuarios activos para seleccionar 3 ganadores distintos.")
+
+    ganadores = random.sample(usuarios, 3)
+
+    premios = ["Suscripción", "Descuento", "Libro"]
+
+    return list(zip(premios, ganadores))
+
+def mostrar_ganadores(ganadores):
+    print("🎉 GANADORES DEL SORTEO MOUREDEV PRO 🎉\n")
+
+    for premio, ganador in ganadores:
+        print(f"🏆 {premio}: {ganador['email']} (ID: {ganador['id']})")
+
+if __name__ == "__main__":
+    ruta_csv = "suscriptores.csv"
+
+    try:
+        usuarios_activos = cargar_usuarios_activos(ruta_csv)
+        ganadores = seleccionar_ganadores(usuarios_activos)
+        mostrar_ganadores(ganadores)
+
+    except FileNotFoundError:
+        print(f"❌ El archivo '{ruta_csv}' no se encuentra.")
+    except ValueError as e:
+        print(f"⚠️ {str(e)}")
diff --git a/suscriptores.csv b/suscriptores.csv
new file mode 100644
index 0000000..25fec64
--- /dev/null
+++ b/suscriptores.csv
@@ -0,0 +1,7 @@
+id,email,status
+1,test1@example.com,activo
+2,test2@example.com,inactivo
+3,test3@example.com,activo
+4,test4@example.com,activo
+5,test5@example.com,inactivo
+6,test6@example.com,activo

From 920c6fbeaf2ac07b187b61b4ab5a2d74aa61d7db Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 24 Apr 2025 08:27:08 +0200
Subject: [PATCH 43/66] BatmanDay

---
 Advanced/09.BatmanDay.py | 103 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 103 insertions(+)
 create mode 100644 Advanced/09.BatmanDay.py

diff --git a/Advanced/09.BatmanDay.py b/Advanced/09.BatmanDay.py
new file mode 100644
index 0000000..be2c6a9
--- /dev/null
+++ b/Advanced/09.BatmanDay.py
@@ -0,0 +1,103 @@
+"""
+/*
+ * EJERCICIO:
+ * Cada año se celebra el Batman Day durante la tercera semana de septiembre... 
+ * ¡Y este año cumple 85 años! Te propongo un reto doble:
+ *
+ * RETO 1:
+ * Crea un programa que calcule cuándo se va a celebrar el Batman Day hasta 
+ * su 100 aniversario.
+ *
+ * RETO 2:
+ * Crea un programa que implemente el sistema de seguridad de la Batcueva. 
+ * Este sistema está diseñado para monitorear múltiples sensores distribuidos
+ * por Gotham, detectar intrusos y activar respuestas automatizadas. 
+ * Cada sensor reporta su estado en tiempo real, y Batman necesita un programa 
+ * que procese estos datos para tomar decisiones estratégicas.
+ * Requisitos:
+ * - El mapa de Gotham y los sensores se representa con una cuadrícula 20x20.
+ * - Cada sensor se identifica con una coordenada (x, y) y un nivel
+ *   de amenaza entre 0 a 10 (número entero).
+ * - Batman debe concentrar recursos en el área más crítica de Gotham.
+ * - El programa recibe un listado de tuplas representando coordenadas de los 
+ *   sensores y su nivel de amenaza. El umbral de activación del protocolo de
+ *   seguridad es 20 (sumatorio de amenazas en una cuadrícula 3x3).
+ * Acciones: 
+ * - Identifica el área con mayor concentración de amenazas
+ *   (sumatorio de amenazas en una cuadrícula 3x3).
+ * - Si el sumatorio de amenazas es mayor al umbral, activa el 
+ *   protocolo de seguridad.
+ * - Calcula la distancia desde la Batcueva, situada en (0, 0). La distancia es
+ *   la suma absoluta de las coordenadas al centro de la cuadrícula amenazada.
+ * - Muestra la coordenada al centro de la cuadrícula más amenazada, la suma de
+ *   sus amenazas, la distancia a la Batcueva y si se debe activar el
+ *   protocolo de seguridad.
+ */
+"""
+import datetime
+import calendar
+
+def obtener_batman_day(anio):
+    # Buscar el tercer sábado de septiembre
+    mes = 9  # septiembre
+    sabados = [day for day in range(1, 31) 
+               if calendar.weekday(anio, mes, day) == 5]  # 5: sábado
+    return datetime.date(anio, mes, sabados[2])  # tercer sábado
+
+print("🦇 Próximos Batman Day hasta el centenario:")
+for anio in range(2024, 2040):  # De 85 a 100 aniversario
+    dia = obtener_batman_day(anio)
+    print(f"{anio} ({anio - 1939}º aniversario): {dia.strftime('%A, %d %B %Y')}")
+
+import random
+
+TAMANO = 20
+UMBRAL_AMENAZA = 20
+
+# Simular sensores: lista de (x, y, amenaza)
+def generar_sensores(num_sensores):
+    sensores = []
+    for _ in range(num_sensores):
+        x = random.randint(0, TAMANO - 1)
+        y = random.randint(0, TAMANO - 1)
+        amenaza = random.randint(0, 10)
+        sensores.append((x, y, amenaza))
+    return sensores
+
+# Crear mapa de Gotham
+def construir_mapa(sensores):
+    mapa = [[0 for _ in range(TAMANO)] for _ in range(TAMANO)]
+    for x, y, amenaza in sensores:
+        mapa[x][y] = amenaza
+    return mapa
+
+# Buscar la mejor zona 3x3
+def buscar_zona_critica(mapa):
+    max_amenaza = -1
+    centro_critico = None
+    for i in range(TAMANO - 2):
+        for j in range(TAMANO - 2):
+            suma = sum(mapa[i + dx][j + dy] for dx in range(3) for dy in range(3))
+            if suma > max_amenaza:
+                max_amenaza = suma
+                centro_critico = (i + 1, j + 1)
+    return centro_critico, max_amenaza
+
+# Calcular distancia Manhattan
+def distancia_batcueva(centro):
+    x, y = centro
+    return abs(x - 0) + abs(y - 0)
+
+# Programa principal
+if __name__ == "__main__":
+    sensores = generar_sensores(100)
+    mapa = construir_mapa(sensores)
+    centro, amenaza_total = buscar_zona_critica(mapa)
+    distancia = distancia_batcueva(centro)
+    activar_protocolo = amenaza_total > UMBRAL_AMENAZA
+
+    print("\n🛡️ Sistema de Seguridad de la Batcueva:")
+    print(f"📍 Centro zona crítica: {centro}")
+    print(f"🔥 Nivel total de amenaza: {amenaza_total}")
+    print(f"📏 Distancia a la Batcueva (0,0): {distancia}")
+    print(f"{'🚨 PROTOCOLO ACTIVADO' if activar_protocolo else '✅ Sin amenazas graves'}")

From 4ea650a4a7e88339489f39e2fa5f46048d13ef89 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 24 Apr 2025 08:33:41 +0200
Subject: [PATCH 44/66] ForniteRubius

---
 Advanced/10.ForniteRubius.py | 100 +++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)
 create mode 100644 Advanced/10.ForniteRubius.py

diff --git a/Advanced/10.ForniteRubius.py b/Advanced/10.ForniteRubius.py
new file mode 100644
index 0000000..2070aa0
--- /dev/null
+++ b/Advanced/10.ForniteRubius.py
@@ -0,0 +1,100 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡Rubius tiene su propia skin en Fortnite!
+ * Y va a organizar una competición para celebrarlo.
+ * Esta es la lista de participantes:
+ * https://x.com/Rubiu5/status/1840161450154692876
+ *
+ * Desarrolla un programa que obtenga el número de seguidores en
+ * Twitch de cada participante, la fecha de creación de la cuenta 
+ * y ordene los resultados en dos listados.
+ * - Usa el API de Twitch: https://dev.twitch.tv/docs/api/reference
+ *   (NO subas las credenciales de autenticación)
+ * - Crea un ranking por número de seguidores y por antigüedad.
+ * - Si algún participante no tiene usuario en Twitch, debe reflejarlo.
+ */
+ Obtener token de autenticación:
+ curl -X POST "https://id.twitch.tv/oauth2/token" \
+-d "client_id=TU_CLIENT_ID" \
+-d "client_secret=TU_CLIENT_SECRET" \
+-d "grant_type=client_credentials"
+ """
+import requests
+import time
+
+# Sustituye con tus credenciales de Twitch
+CLIENT_ID = "TU_CLIENT_ID"
+AUTH_TOKEN = "TU_AUTH_TOKEN"
+
+HEADERS = {
+    "Client-ID": CLIENT_ID,
+    "Authorization": f"Bearer {AUTH_TOKEN}"
+}
+
+# Lista de participantes desde el tweet (puedes adaptarla a mano o scrapear el tweet)
+PARTICIPANTES = [
+    "Rubiu5", "Auronplay", "Ibai", "TheGrefg", "xQc", "ElMariana", "Rivers_gg", "JuanSGuarnizo",
+    "BarbeQ", "Biyin_", "Cristinini", "Folagor03", "Gemita", "Illojuan", "Komanche",
+    "Luzu", "Quackity", "Reborn_Live", "Spursito", "Zeling", "Sekiam", "Mayichi", "Axel", "Tortillaland", "Karchez"
+]
+
+def obtener_usuario(usuario):
+    url = f"https://api.twitch.tv/helix/users?login={usuario}"
+    resp = requests.get(url, headers=HEADERS)
+    data = resp.json()
+    if data["data"]:
+        return data["data"][0]
+    return None
+
+def obtener_seguidores(user_id):
+    url = f"https://api.twitch.tv/helix/users/follows?to_id={user_id}"
+    resp = requests.get(url, headers=HEADERS)
+    data = resp.json()
+    return data.get("total", 0)
+
+datos_usuarios = []
+
+for nombre in PARTICIPANTES:
+    print(f"Buscando datos de: {nombre}")
+    user = obtener_usuario(nombre)
+    if user:
+        seguidores = obtener_seguidores(user["id"])
+        datos_usuarios.append({
+            "nombre": nombre,
+            "seguidores": seguidores,
+            "creacion": user["created_at"]
+        })
+    else:
+        datos_usuarios.append({
+            "nombre": nombre,
+            "seguidores": None,
+            "creacion": None
+        })
+    time.sleep(0.5)  # Para evitar rate limiting
+
+# Ranking por seguidores
+ranking_seguidores = sorted(
+    [u for u in datos_usuarios if u["seguidores"] is not None],
+    key=lambda x: x["seguidores"],
+    reverse=True
+)
+
+# Ranking por antigüedad
+ranking_antiguedad = sorted(
+    [u for u in datos_usuarios if u["creacion"] is not None],
+    key=lambda x: x["creacion"]
+)
+
+print("\n📊 Ranking por número de seguidores:")
+for i, u in enumerate(ranking_seguidores, 1):
+    print(f"{i}. {u['nombre']} - {u['seguidores']} seguidores")
+
+print("\n📜 Ranking por antigüedad:")
+for i, u in enumerate(ranking_antiguedad, 1):
+    print(f"{i}. {u['nombre']} - Creado el {u['creacion']}")
+
+print("\n❌ Usuarios no encontrados en Twitch:")
+no_encontrados = [u for u in datos_usuarios if u["seguidores"] is None]
+for u in no_encontrados:
+    print(f"- {u['nombre']}")

From a63521f287301b3115ccc081cb7647bcc2394ebc Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 07:18:52 +0200
Subject: [PATCH 45/66] CamisetaRAR

---
 Advanced/11.CamisetaRAR.py |  33 +++++++++++++++++++++++++++++++++
 camiseta.txt               |   1 +
 camiseta.zip               | Bin 0 -> 161 bytes
 3 files changed, 34 insertions(+)
 create mode 100644 Advanced/11.CamisetaRAR.py
 create mode 100644 camiseta.txt
 create mode 100644 camiseta.zip

diff --git a/Advanced/11.CamisetaRAR.py b/Advanced/11.CamisetaRAR.py
new file mode 100644
index 0000000..1d7aaf5
--- /dev/null
+++ b/Advanced/11.CamisetaRAR.py
@@ -0,0 +1,33 @@
+"""
+/*
+ * EJERCICIO:
+ * ¿Has visto la camiseta.rar?
+ * https://x.com/MoureDev/status/1841531938961592740
+ *
+ * Crea un programa capaz de comprimir un archivo 
+ * en formato .zip (o el que tú quieras).
+ * - No subas el archivo o el zip.
+ */
+"""
+import zipfile
+import os
+
+def comprimir_archivo(ruta_origen, ruta_destino_zip):
+    with zipfile.ZipFile(ruta_destino_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
+        if os.path.isfile(ruta_origen):
+            zipf.write(ruta_origen, arcname=os.path.basename(ruta_origen))
+            print(f"Archivo comprimido: {ruta_destino_zip}")
+        elif os.path.isdir(ruta_origen):
+            for root, dirs, files in os.walk(ruta_origen):
+                for file in files:
+                    ruta_completa = os.path.join(root, file)
+                    arcname = os.path.relpath(ruta_completa, start=ruta_origen)
+                    zipf.write(ruta_completa, arcname=arcname)
+            print(f"Carpeta comprimida: {ruta_destino_zip}")
+        else:
+            print("Ruta no válida.")
+
+# Ejemplo de uso
+origen = "camiseta.txt"  # O una carpeta, por ejemplo: "mi_carpeta/"
+destino = "camiseta.zip"
+comprimir_archivo(origen, destino)
diff --git a/camiseta.txt b/camiseta.txt
new file mode 100644
index 0000000..3368c29
--- /dev/null
+++ b/camiseta.txt
@@ -0,0 +1 @@
+Ejemplo fichero para el ejercicio RAR
\ No newline at end of file
diff --git a/camiseta.zip b/camiseta.zip
new file mode 100644
index 0000000000000000000000000000000000000000..7abb28516d907bccc85b67857d147713cd896cf3
GIT binary patch

From 6d6a9f7ddefa2254caffd732eecbe6d72688b807 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 07:26:00 +0200
Subject: [PATCH 46/66] DragonBall

---
 Advanced/12.DragonBall.py | 117 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)
 create mode 100644 Advanced/12.DragonBall.py

diff --git a/Advanced/12.DragonBall.py b/Advanced/12.DragonBall.py
new file mode 100644
index 0000000..a2d3de7
--- /dev/null
+++ b/Advanced/12.DragonBall.py
@@ -0,0 +1,117 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡El último videojuego de Dragon Ball ya está aquí!
+ * Se llama Dragon Ball: Sparking! ZERO.
+ *
+ * Simula un Torneo de Artes Marciales, al más puro estilo
+ * de la saga, donde participarán diferentes luchadores, y el
+ * sistema decidirá quién es el ganador.
+ *
+ * Luchadores:
+ * - Nombre.
+ * - Tres atributos: velocidad, ataque y defensa
+ *   (con valores entre 0 a 100 que tú decidirás).
+ * - Comienza cada batalla con 100 de salud.
+ * Batalla:
+ * - En cada batalla se enfrentan 2 luchadores.
+ * - El luchador con más velocidad comienza atacando.
+ * - El daño se calcula restando el daño de ataque del
+ *   atacante menos la defensa del oponente.
+ * - El oponente siempre tiene un 20% de posibilidad de
+ *   esquivar el ataque.
+ * - Si la defensa es mayor que el ataque, recibe un 10%
+ *   del daño de ataque.
+ * - Después de cada turno y ataque, el oponente pierde salud.
+ * - La batalla finaliza cuando un luchador pierde toda su salud.
+ * Torneo:
+ * - Un torneo sólo es válido con un número de luchadores
+ *   potencia de 2.
+ * - El torneo debe crear parejas al azar en cada ronda.
+ * - Los luchadores se enfrentan en rondas eliminatorias.
+ * - El ganador avanza a la siguiente ronda hasta que sólo
+ *   quede uno.
+ * - Debes mostrar por consola todo lo que sucede en el torneo,
+ *   así como el ganador.
+ */
+"""
+import random
+import time
+from typing import List
+
+class Luchador:
+    def __init__(self, nombre, velocidad, ataque, defensa):
+        self.nombre = nombre
+        self.velocidad = velocidad
+        self.ataque = ataque
+        self.defensa = defensa
+        self.salud = 100
+
+    def restablecer_salud(self):
+        self.salud = 100
+
+    def __str__(self):
+        return f"{self.nombre} [VEL: {self.velocidad} | ATK: {self.ataque} | DEF: {self.defensa}]"
+
+def batalla(l1: Luchador, l2: Luchador) -> Luchador:
+    print(f"\n🔔 ¡Comienza la batalla entre {l1.nombre} y {l2.nombre}!")
+    l1.restablecer_salud()
+    l2.restablecer_salud()
+
+    atacante, defensor = (l1, l2) if l1.velocidad >= l2.velocidad else (l2, l1)
+    print(f"⚡ {atacante.nombre} ataca primero por su mayor velocidad.")
+
+    while l1.salud > 0 and l2.salud > 0:
+        if random.random() <= 0.2:
+            print(f"🛡️ {defensor.nombre} esquiva el ataque de {atacante.nombre}!")
+        else:
+            if atacante.ataque > defensor.defensa:
+                dano = atacante.ataque - defensor.defensa
+            else:
+                dano = int(atacante.ataque * 0.1)
+            defensor.salud -= dano
+            print(f"💥 {atacante.nombre} ataca a {defensor.nombre} causando {dano} de daño. ({defensor.salud} de salud restante)")
+
+        atacante, defensor = defensor, atacante
+        time.sleep(0.5)
+
+    ganador = l1 if l1.salud > 0 else l2
+    print(f"🏆 ¡{ganador.nombre} gana la batalla!\n")
+    return ganador
+
+def es_potencia_de_2(n):
+    return (n != 0) and (n & (n - 1) == 0)
+
+def torneo(luchadores: List[Luchador]):
+    if not es_potencia_de_2(len(luchadores)):
+        print("❌ El número de luchadores debe ser una potencia de 2 (2, 4, 8, 16, ...)")
+        return
+
+    ronda = 1
+    while len(luchadores) > 1:
+        print(f"\n🔶 RONDA {ronda} 🔶")
+        random.shuffle(luchadores)
+        ganadores = []
+        for i in range(0, len(luchadores), 2):
+            ganador = batalla(luchadores[i], luchadores[i+1])
+            ganadores.append(ganador)
+        luchadores = ganadores
+        ronda += 1
+
+    print(f"\n🎉 ¡El campeón del torneo es {luchadores[0].nombre}!")
+    print("🥇 ¡Dragon Ball Sparking! ZERO Torneo Finalizado 🐉")
+
+# 🧙‍♂️ Luchadores de ejemplo
+luchadores = [
+    Luchador("Goku", 90, 85, 70),
+    Luchador("Vegeta", 85, 90, 65),
+    Luchador("Gohan", 75, 80, 60),
+    Luchador("Piccolo", 70, 75, 80),
+    Luchador("Trunks", 80, 78, 68),
+    Luchador("Freezer", 60, 88, 72),
+    Luchador("Cell", 65, 82, 75),
+    Luchador("Majin Buu", 55, 95, 85),
+]
+
+# 🏁 Iniciar torneo
+torneo(luchadores)

From a72f40814c3f52dfa988ef45b51d500d2efd71ee Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 07:29:08 +0200
Subject: [PATCH 47/66] GithubCli

---
 Advanced/13.GithubCli.py | 125 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 Advanced/13.GithubCli.py

diff --git a/Advanced/13.GithubCli.py b/Advanced/13.GithubCli.py
new file mode 100644
index 0000000..a6dd55d
--- /dev/null
+++ b/Advanced/13.GithubCli.py
@@ -0,0 +1,125 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡Me voy de viaje al GitHub Universe 2024 de San Francisco!
+ *
+ * Desarrolla un CLI (Command Line Interface) que permita 
+ * interactuar con Git y GitHub de manera real desde terminal.
+ * 
+ * El programa debe permitir las siguientes opciones:
+ * 1. Establecer el directorio de trabajo
+ * 2. Crear un nuevo repositorio
+ * 3. Crear una nueva rama
+ * 4. Cambiar de rama
+ * 5. Mostrar ficheros pendientes de hacer commit
+ * 6. Hacer commit (junto con un add de todos los ficheros)
+ * 7. Mostrar el historial de commits
+ * 8. Eliminar rama
+ * 9. Establecer repositorio remoto
+ * 10. Hacer pull
+ * 11. Hacer push
+ * 12. Salir
+ *
+ * Puedes intentar controlar los diferentes errores.
+ */
+ """
+import subprocess
+import os
+
+def ejecutar_comando(comando):
+    try:
+        resultado = subprocess.run(comando, shell=True, text=True, capture_output=True)
+        if resultado.stdout:
+            print(resultado.stdout)
+        if resultado.stderr:
+            print("⚠️", resultado.stderr)
+    except Exception as e:
+        print("❌ Error ejecutando el comando:", e)
+
+def establecer_directorio():
+    ruta = input("📁 Introduce el path del directorio de trabajo: ").strip()
+    if os.path.isdir(ruta):
+        os.chdir(ruta)
+        print(f"✅ Directorio cambiado a: {os.getcwd()}")
+    else:
+        print("❌ El directorio no existe.")
+
+def crear_repositorio():
+    ejecutar_comando("git init")
+
+def crear_rama():
+    nombre = input("🌿 Nombre de la nueva rama: ").strip()
+    ejecutar_comando(f"git branch {nombre}")
+
+def cambiar_rama():
+    nombre = input("🔀 Nombre de la rama a cambiar: ").strip()
+    ejecutar_comando(f"git checkout {nombre}")
+
+def ficheros_pendientes():
+    ejecutar_comando("git status")
+
+def hacer_commit():
+    mensaje = input("✏️ Escribe el mensaje del commit: ").strip()
+    ejecutar_comando("git add .")
+    ejecutar_comando(f'git commit -m "{mensaje}"')
+
+def mostrar_historial():
+    ejecutar_comando("git log --oneline")
+
+def eliminar_rama():
+    nombre = input("🧨 Nombre de la rama a eliminar: ").strip()
+    ejecutar_comando(f"git branch -d {nombre}")
+
+def establecer_remoto():
+    url = input("🔗 URL del repositorio remoto: ").strip()
+    ejecutar_comando(f"git remote add origin {url}")
+
+def hacer_pull():
+    rama = input("⬇️ Rama desde la que hacer pull (ej. main): ").strip()
+    ejecutar_comando(f"git pull origin {rama}")
+
+def hacer_push():
+    rama = input("⬆️ Rama a la que hacer push (ej. main): ").strip()
+    ejecutar_comando(f"git push origin {rama}")
+
+def mostrar_menu():
+    print("""
+========= 🌌 GitHub Universe CLI 🌌 =========
+1.  Establecer directorio de trabajo
+2.  Crear un nuevo repositorio
+3.  Crear una nueva rama
+4.  Cambiar de rama
+5.  Mostrar ficheros pendientes de hacer commit
+6.  Hacer commit
+7.  Mostrar el historial de commits
+8.  Eliminar rama
+9.  Establecer repositorio remoto
+10. Hacer pull
+11. Hacer push
+12. Salir
+============================================
+""")
+
+def main():
+    while True:
+        mostrar_menu()
+        opcion = input("Selecciona una opción (1-12): ").strip()
+        match opcion:
+            case "1": establecer_directorio()
+            case "2": crear_repositorio()
+            case "3": crear_rama()
+            case "4": cambiar_rama()
+            case "5": ficheros_pendientes()
+            case "6": hacer_commit()
+            case "7": mostrar_historial()
+            case "8": eliminar_rama()
+            case "9": establecer_remoto()
+            case "10": hacer_pull()
+            case "11": hacer_push()
+            case "12":
+                print("👋 ¡Hasta luego, GitHubnauta!")
+                break
+            case _: print("❌ Opción no válida.")
+
+if __name__ == "__main__":
+    main()

From 63861b75de93e0c974bea798523e0816339ebd2c Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 07:52:59 +0200
Subject: [PATCH 48/66] CuentaAtras

---
 Advanced/14.CuentaAtras.py | 74 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 Advanced/14.CuentaAtras.py

diff --git a/Advanced/14.CuentaAtras.py b/Advanced/14.CuentaAtras.py
new file mode 100644
index 0000000..e599a89
--- /dev/null
+++ b/Advanced/14.CuentaAtras.py
@@ -0,0 +1,74 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡El 12 de noviembre lanzo mouredev pro!
+ * El campus de la comunidad para estudiar programación de
+ * una manera diferente: https://mouredev.pro
+ *
+ * Crea un programa que funcione como una cuenta atrás.
+ *
+ * - Al iniciarlo tendrás que indicarle el día, mes, año,
+ *   hora, minuto y segundo en el que quieres que finalice.
+ * - Deberás transformar esa fecha local a UTC.
+ * - La cuenta atrás comenzará y mostrará los días, horas,
+ *   minutos y segundos que faltan.
+ * - Se actualizará cada segundo y borrará la terminal en
+ *   cada nueva representación del tiempo restante.
+ * - Una vez finalice, mostrará un mensaje.
+ * - Realiza la ejecución, si el lenguaje lo soporta, en
+ *   un hilo independiente.
+ */
+"""
+import datetime
+import time
+import threading
+import os
+
+def limpiar_terminal():
+    os.system('cls' if os.name == 'nt' else 'clear')
+
+def cuenta_atras(fecha_objetivo_local):
+    # Convertimos la fecha local a UTC
+    fecha_utc = fecha_objetivo_local.astimezone(datetime.timezone.utc)
+    print(f"📅 Fecha objetivo (UTC): {fecha_utc.strftime('%Y-%m-%d %H:%M:%S')}")
+
+    while True:
+        ahora_utc = datetime.datetime.now(datetime.timezone.utc)
+        diferencia = fecha_utc - ahora_utc
+
+        if diferencia.total_seconds() <= 0:
+            limpiar_terminal()
+            print("🚀 ¡Ha llegado el momento! ¡MoureDev Pro ya está disponible! 🎉")
+            break
+
+        dias = diferencia.days
+        horas, resto = divmod(diferencia.seconds, 3600)
+        minutos, segundos = divmod(resto, 60)
+
+        limpiar_terminal()
+        print("⌛ Cuenta atrás para el lanzamiento de MoureDev Pro:")
+        print(f"{dias} días, {horas:02} horas, {minutos:02} minutos, {segundos:02} segundos restantes.")
+        time.sleep(1)
+
+def iniciar_cuenta_atras():
+    try:
+        print("Introduce la fecha y hora de finalización (hora local):")
+        anio = int(input("Año: "))
+        mes = int(input("Mes: "))
+        dia = int(input("Día: "))
+        hora = int(input("Hora (0-23): "))
+        minuto = int(input("Minuto: "))
+        segundo = int(input("Segundo: "))
+
+        fecha_local = datetime.datetime(anio, mes, dia, hora, minuto, segundo)
+        # Asumimos zona horaria local (sin especificar tzinfo explícito)
+        fecha_local = fecha_local.astimezone()  # Lo convierte a la zona local con tzinfo
+    except Exception as e:
+        print("❌ Error al introducir la fecha:", e)
+        return
+
+    hilo = threading.Thread(target=cuenta_atras, args=(fecha_local,))
+    hilo.start()
+
+if __name__ == "__main__":
+    iniciar_cuenta_atras()

From 887c7ab993d0f65bc763b77cb46b522ae1cfcfbb Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 08:00:38 +0200
Subject: [PATCH 49/66] Octoverse

---
 Advanced/15.Octoverse.py | 90 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)
 create mode 100644 Advanced/15.Octoverse.py

diff --git a/Advanced/15.Octoverse.py b/Advanced/15.Octoverse.py
new file mode 100644
index 0000000..f704b32
--- /dev/null
+++ b/Advanced/15.Octoverse.py
@@ -0,0 +1,90 @@
+"""
+/*
+ * EJERCICIO:
+ * GitHub ha publicado el Octoverse 2024, el informe
+ * anual del estado de la plataforma:
+ * https://octoverse.github.com
+ *
+ * Utilizando el API de GitHub, crea un informe asociado
+ * a un usuario concreto.
+ * 
+ * - Se debe poder definir el nombre del usuario
+ *   sobre el que se va a generar el informe.
+ *   
+ * - Crea un informe de usuario basándote en las 5 métricas
+ *   que tú quieras, utilizando la información que te
+ *   proporciona GitHub. Por ejemplo:
+ *   - Lenguaje más utilizado
+ *   - Cantidad de repositorios
+ *   - Seguidores/Seguidos
+ *   - Stars/forks
+ *   - Contribuciones
+ *   (lo que se te ocurra)
+ */
+"""
+import requests
+from collections import Counter
+
+# Token personal opcional para más peticiones sin límites
+GITHUB_TOKEN = None  # O pon tu token aquí como string
+
+def get_headers():
+    headers = {"Accept": "application/vnd.github.v3+json"}
+    if GITHUB_TOKEN:
+        headers["Authorization"] = f"token {GITHUB_TOKEN}"
+    return headers
+
+def get_user_info(username):
+    url = f"https://api.github.com/users/{username}"
+    response = requests.get(url, headers=get_headers())
+    response.raise_for_status()
+    return response.json()
+
+def get_user_repos(username):
+    repos = []
+    page = 1
+    while True:
+        url = f"https://api.github.com/users/{username}/repos?per_page=100&page={page}"
+        response = requests.get(url, headers=get_headers())
+        response.raise_for_status()
+        data = response.json()
+        if not data:
+            break
+        repos.extend(data)
+        page += 1
+    return repos
+
+def generar_informe(username):
+    try:
+        user = get_user_info(username)
+        repos = get_user_repos(username)
+
+        lenguajes = Counter()
+        estrellas = 0
+        forks = 0
+
+        for repo in repos:
+            lenguaje = repo.get("language")
+            if lenguaje:
+                lenguajes[lenguaje] += 1
+            estrellas += repo.get("stargazers_count", 0)
+            forks += repo.get("forks_count", 0)
+
+        lenguaje_principal = lenguajes.most_common(1)[0][0] if lenguajes else "Desconocido"
+        
+        print(f"\n🧾 Informe de GitHub para @{username}\n" + "-"*40)
+        print(f"👤 Nombre: {user.get('name') or 'No disponible'}")
+        print(f"📁 Repos públicos: {user.get('public_repos')}")
+        print(f"🌍 Lenguaje más utilizado: {lenguaje_principal}")
+        print(f"⭐ Total de estrellas recibidas: {estrellas}")
+        print(f"🍴 Total de forks: {forks}")
+        print(f"👥 Seguidores: {user.get('followers')} | Siguiendo: {user.get('following')}")
+        print(f"🔗 Perfil: {user.get('html_url')}")
+        print("-" * 40)
+
+    except requests.HTTPError as e:
+        print(f"❌ Error al recuperar datos de GitHub: {e}")
+
+if __name__ == "__main__":
+    username = input("Introduce el nombre de usuario de GitHub: ")
+    generar_informe(username.strip())

From 8cd9200b416bc340ea65eced2e2a93dd7b28433c Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 08:05:28 +0200
Subject: [PATCH 50/66] XBluesky

---
 Advanced/16.XBluesky.py | 141 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 141 insertions(+)
 create mode 100644 Advanced/16.XBluesky.py

diff --git a/Advanced/16.XBluesky.py b/Advanced/16.XBluesky.py
new file mode 100644
index 0000000..4683e31
--- /dev/null
+++ b/Advanced/16.XBluesky.py
@@ -0,0 +1,141 @@
+"""
+/*
+ * EJERCICIO:
+ * La alternativa descentralizada a X, Bluesky, comienza a atraer
+ * a nuevos usuarios. ¿Cómo funciona una red de este estilo?
+ * 
+ * Implementa un sistema que simule el comportamiento de estas
+ * redes sociales.
+ * 
+ * Debes crear las siguientes operaciones:
+ * - Registrar un usuario por nombre e identificador único.
+ * - Un usuario puede seguir/dejar de seguir a otro.
+ * - Creación de post asociado a un usuario. Debe poseer
+ *   texto (200 caracteres máximo), fecha de creación 
+ *   e identificador único.   
+ * - Eliminación de un post.
+ * - Posibilidad de hacer like (y eliminarlo) en un post.
+ * - Visualización del feed de un usuario con sus 10 publicaciones
+ *   más actuales ordenadas desde la más reciente.
+ * - Visualización del feed de un usuario con las 10 publicaciones
+ *   más actuales de los usuarios que sigue ordenadas 
+ *   desde la más reciente.
+ *   
+ * Cuando se visualiza un post, debe mostrarse:
+ * ID de usuario, nombre de usuario, texto del post, 
+ * fecha de creación y número total de likes.
+ * 
+ * Controla errores en duplicados o acciones no permitidas.
+ */
+"""
+import uuid
+from datetime import datetime
+
+class Usuario:
+    def __init__(self, nombre):
+        self.id = str(uuid.uuid4())
+        self.nombre = nombre
+        self.seguidores = set()
+        self.seguidos = set()
+        self.posts = []
+    
+    def seguir(self, otro_usuario):
+        if otro_usuario != self:
+            self.seguidos.add(otro_usuario)
+            otro_usuario.seguidores.add(self)
+        else:
+            print("No puedes seguirte a ti mismo.")
+    
+    def dejar_de_seguir(self, otro_usuario):
+        if otro_usuario != self:
+            self.seguidos.discard(otro_usuario)
+            otro_usuario.seguidores.discard(self)
+        else:
+            print("No puedes dejar de seguirte a ti mismo.")
+    
+    def crear_post(self, texto):
+        if len(texto) > 200:
+            print("El texto excede el límite de 200 caracteres.")
+            return
+        post = Post(self, texto)
+        self.posts.append(post)
+        print(f"Post creado con ID: {post.id}")
+    
+    def eliminar_post(self, post_id):
+        post = next((p for p in self.posts if p.id == post_id), None)
+        if post:
+            self.posts.remove(post)
+            print(f"Post {post_id} eliminado.")
+        else:
+            print("Post no encontrado.")
+    
+    def mostrar_feed(self, num_posts=10):
+        feed = sorted(self.posts, key=lambda p: p.fecha_creacion, reverse=True)[:num_posts]
+        for post in feed:
+            print(post.mostrar())
+    
+    def mostrar_feed_seguidos(self, num_posts=10):
+        feed = []
+        for seguido in self.seguidos:
+            feed.extend(seguido.posts)
+        feed = sorted(feed, key=lambda p: p.fecha_creacion, reverse=True)[:num_posts]
+        for post in feed:
+            print(post.mostrar())
+    
+
+class Post:
+    def __init__(self, usuario, texto):
+        self.id = str(uuid.uuid4())
+        self.usuario = usuario
+        self.texto = texto
+        self.fecha_creacion = datetime.now()
+        self.likes = set()
+    
+    def dar_like(self, usuario):
+        if usuario != self.usuario:
+            self.likes.add(usuario)
+            print(f"Like dado por {usuario.nombre}.")
+        else:
+            print("No puedes dar like a tu propio post.")
+    
+    def quitar_like(self, usuario):
+        if usuario in self.likes:
+            self.likes.remove(usuario)
+            print(f"Like quitado por {usuario.nombre}.")
+        else:
+            print(f"{usuario.nombre} no había dado like a este post.")
+    
+    def mostrar(self):
+        return f"Post ID: {self.id}\nUsuario: {self.usuario.nombre} | Texto: {self.texto}\nFecha: {self.fecha_creacion}\nLikes: {len(self.likes)}"
+
+
+# Interacción con el sistema
+if __name__ == "__main__":
+    # Crear usuarios
+    usuario1 = Usuario("Alice")
+    usuario2 = Usuario("Bob")
+    
+    # Crear posts
+    usuario1.crear_post("¡Este es mi primer post!")
+    usuario2.crear_post("¡Este es el post de Bob!")
+    
+    # Seguir usuarios
+    usuario1.seguir(usuario2)
+    
+    # Dar like a un post
+    post_bob = usuario2.posts[0]
+    usuario1.dar_like(post_bob)
+    
+    # Mostrar feeds
+    print("\nFeed de Alice (mis posts):")
+    usuario1.mostrar_feed()
+    
+    print("\nFeed de Alice (posts de los que sigo):")
+    usuario1.mostrar_feed_seguidos()
+    
+    # Eliminar post
+    usuario1.eliminar_post(post_bob.id)
+    
+    # Mostrar feed después de eliminar
+    print("\nFeed de Alice (posts de los que sigo) después de eliminar:")
+    usuario1.mostrar_feed_seguidos()

From e967602a2d18cc81f69106f9c47e687ea642fd3f Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 08:20:19 +0200
Subject: [PATCH 51/66] Adviento

---
 Advanced/17.Adviento.py | 83 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 Advanced/17.Adviento.py

diff --git a/Advanced/17.Adviento.py b/Advanced/17.Adviento.py
new file mode 100644
index 0000000..d9be721
--- /dev/null
+++ b/Advanced/17.Adviento.py
@@ -0,0 +1,83 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡Cada año celebramos el aDEViento! 24 días, 24 regalos para
+ * developers. Del 1 al 24 de diciembre: https://adviento.dev
+ * 
+ * Dibuja un calendario por terminal e implementa una
+ * funcionalidad para seleccionar días y mostrar regalos.
+ * - El calendario mostrará los días del 1 al 24 repartidos
+ *   en 6 columnas a modo de cuadrícula.
+ * - Cada cuadrícula correspondiente a un día tendrá un tamaño 
+ *   de 4x3 caracteres, y sus bordes serán asteríscos.
+ * - Las cuadrículas dejarán un espacio entre ellas.
+ * - En el medio de cada cuadrícula aparecerá el día entre el
+ *   01 y el 24.
+ *
+ * Ejemplo de cuadrículas:
+ * **** **** ****
+ * *01* *02* *03* ...
+ * **** **** ****
+ *
+ * - El usuario selecciona qué día quiere descubrir.
+ * - Si está sin descubrir, se le dirá que ha abierto ese día
+ *   y se mostrará de nuevo el calendario con esa cuadrícula
+ *   cubierta de asteríscos (sin mostrar el día).
+ *
+ * Ejemplo de selección del día 1
+ * **** **** ****
+ * **** *02* *03* ...
+ * **** **** ****
+ *   
+ * - Si se selecciona un número ya descubierto, se le notifica
+ *   al usuario.
+ */
+"""
+class CalendarioADEViento:
+    def __init__(self):
+        self.dias_descubiertos = set()  # Conjunto de días descubiertos
+
+    def dibujar_calendario(self):
+        dia = 1
+        calendario = ''
+        
+        # Recorremos las filas del calendario (6 filas de 4 días)
+        for i in range(6):
+            fila = ''
+            for j in range(4):
+                # Si el día ha sido descubierto, mostramos asteriscos
+                # Si no, mostramos el número del día
+                if dia in self.dias_descubiertos:
+                    fila += '**** '
+                else:
+                    fila += f'*{self.formatear_dia(dia)}* '
+                dia += 1
+                if dia > 24:
+                    break
+            calendario += fila.strip() + '\n'
+        
+        print(calendario)
+
+    def formatear_dia(self, dia):
+        """ Formatea el día para que siempre tenga dos dígitos """
+        return f'{dia:02d}'
+
+    def seleccionar_dia(self, dia):
+        """ Permite al usuario seleccionar un día para descubrirlo """
+        if dia in self.dias_descubiertos:
+            print(f'El día {self.formatear_dia(dia)} ya ha sido descubierto.')
+        else:
+            self.dias_descubiertos.add(dia)
+            print(f'¡Felicidades! Has descubierto el día {self.formatear_dia(dia)}.')
+        self.dibujar_calendario()  # Redibuja el calendario actualizado
+
+# Crear el calendario
+calendario = CalendarioADEViento()
+
+# Dibujar el calendario inicial
+calendario.dibujar_calendario()
+
+# Simulación de selección de días
+calendario.seleccionar_dia(1)  # Seleccionar el día 1
+calendario.seleccionar_dia(1)  # Intentar seleccionar el día 1 de nuevo
+calendario.seleccionar_dia(5)  # Seleccionar el día 5

From f7f0be5ce0c0072e1ffb5ce69a4b3d34444fa16b Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 08:25:40 +0200
Subject: [PATCH 52/66] ArbolNavidad

---
 Advanced/18.ArbolNavidad.py | 196 ++++++++++++++++++++++++++++++++++++
 1 file changed, 196 insertions(+)
 create mode 100644 Advanced/18.ArbolNavidad.py

diff --git a/Advanced/18.ArbolNavidad.py b/Advanced/18.ArbolNavidad.py
new file mode 100644
index 0000000..0221d94
--- /dev/null
+++ b/Advanced/18.ArbolNavidad.py
@@ -0,0 +1,196 @@
+"""
+/*
+ * EJERCICIO:
+ * ¡Ha comenzado diciembre! Es hora de montar nuestro
+ * árbol de Navidad...
+ * 
+ * Desarrolla un programa que cree un árbol de Navidad
+ * con una altura dinámica definida por el usuario por terminal.
+ * 
+ * Ejemplo de árbol de altura 5 (el tronco siempre será igual):
+ * 
+ *     *
+ *    ***
+ *   *****
+ *  *******
+ * *********
+ *    |||
+ *    |||
+ *
+ * El usuario podrá seleccionar las siguientes acciones:
+ * 
+ * - Añadir o eliminar la estrella en la copa del árbol (@)
+ * - Añadir o eliminar bolas de dos en dos (o) aleatoriamente
+ * - Añadir o eliminar luces de tres en tres (+) aleatoriamente
+ * - Apagar (*) o encender (+) las luces (conservando su posición)
+ * - Una luz y una bola no pueden estar en el mismo sitio
+ *
+ * Sólo puedes añadir una estrella, y tantas luces o bolas
+ * como tengan cabida en el árbol. El programa debe notificar
+ * cada una de las acciones (o por el contrario, cuando no
+ * se pueda realizar alguna).
+ */
+"""
+import random
+
+class ArbolNavidad:
+    def __init__(self, altura):
+        self.altura = altura
+        self.estrella = False
+        self.bolas = set()  # Conjunto de posiciones de bolas
+        self.luces = set()  # Conjunto de posiciones de luces
+        self.tree = self.crear_arbol()
+
+    def crear_arbol(self):
+        """ Crea el árbol representado por una lista de cadenas. """
+        tree = []
+        for i in range(self.altura):
+            espacios = ' ' * (self.altura - i - 1)
+            estrellas = '*' * (2 * i + 1)
+            tree.append(espacios + estrellas)
+        # Tronco del árbol
+        tree.append(' ' * (self.altura - 1) + '|||')
+        return tree
+
+    def dibujar_arbol(self):
+        """ Dibuja el árbol con las interacciones actuales. """
+        arbol_con_elementos = self.tree[:]
+        
+        # Agregar estrella en la copa
+        if self.estrella:
+            arbol_con_elementos[0] = ' ' * (self.altura - 1) + '@' + ' ' * (self.altura - 1)
+
+        # Agregar bolas y luces en las posiciones correspondientes
+        for i in range(self.altura):
+            arbol_con_elementos[i] = list(arbol_con_elementos[i])
+            for pos in self.bolas:
+                if pos == i:
+                    arbol_con_elementos[i][self.altura - 1 - (i * 2 + 1) // 2] = 'o'
+            for pos in self.luces:
+                if pos == i:
+                    arbol_con_elementos[i][self.altura - 1 - (i * 2 + 1) // 2] = '+'
+            arbol_con_elementos[i] = ''.join(arbol_con_elementos[i])
+
+        # Mostrar árbol
+        for linea in arbol_con_elementos:
+            print(linea)
+
+    def añadir_estrella(self):
+        """ Añade o elimina la estrella en la copa del árbol. """
+        if self.estrella:
+            print("La estrella ya está en la copa del árbol.")
+        else:
+            self.estrella = True
+            print("¡Estrella añadida en la copa del árbol!")
+
+    def eliminar_estrella(self):
+        """ Elimina la estrella en la copa del árbol. """
+        if not self.estrella:
+            print("La estrella ya está eliminada.")
+        else:
+            self.estrella = False
+            print("¡Estrella eliminada!")
+
+    def añadir_bolas(self):
+        """ Añade dos bolas aleatoriamente en el árbol. """
+        if len(self.bolas) + 2 > self.altura * self.altura - self.altura:
+            print("No hay suficiente espacio para añadir más bolas.")
+        else:
+            while len(self.bolas) < len(self.bolas) + 2:
+                pos = random.randint(0, self.altura - 1)
+                if pos not in self.bolas:
+                    self.bolas.add(pos)
+            print("¡Bolas añadidas aleatoriamente!")
+
+    def eliminar_bolas(self):
+        """ Elimina dos bolas aleatoriamente. """
+        if len(self.bolas) < 2:
+            print("No hay suficientes bolas para eliminar.")
+        else:
+            for _ in range(2):
+                bola = random.choice(list(self.bolas))
+                self.bolas.remove(bola)
+            print("¡Bolas eliminadas aleatoriamente!")
+
+    def añadir_luces(self):
+        """ Añade tres luces aleatoriamente en el árbol. """
+        if len(self.luces) + 3 > self.altura * self.altura - self.altura:
+            print("No hay suficiente espacio para añadir más luces.")
+        else:
+            while len(self.luces) < len(self.luces) + 3:
+                pos = random.randint(0, self.altura - 1)
+                if pos not in self.luces and pos not in self.bolas:
+                    self.luces.add(pos)
+            print("¡Luces añadidas aleatoriamente!")
+
+    def eliminar_luces(self):
+        """ Elimina tres luces aleatoriamente. """
+        if len(self.luces) < 3:
+            print("No hay suficientes luces para eliminar.")
+        else:
+            for _ in range(3):
+                luz = random.choice(list(self.luces))
+                self.luces.remove(luz)
+            print("¡Luces eliminadas aleatoriamente!")
+
+    def encender_luces(self):
+        """ Enciende las luces (cambia su estado). """
+        if len(self.luces) == 0:
+            print("No hay luces para encender.")
+        else:
+            print("¡Luces encendidas!")
+
+    def apagar_luces(self):
+        """ Apaga las luces (cambia su estado). """
+        if len(self.luces) == 0:
+            print("No hay luces para apagar.")
+        else:
+            print("¡Luces apagadas!")
+
+
+# Interacción con el programa
+def main():
+    altura = int(input("Introduce la altura del árbol de Navidad: "))
+    arbol = ArbolNavidad(altura)
+    
+    while True:
+        arbol.dibujar_arbol()
+        
+        print("\nOpciones:")
+        print("1. Añadir estrella en la copa")
+        print("2. Eliminar estrella en la copa")
+        print("3. Añadir bolas")
+        print("4. Eliminar bolas")
+        print("5. Añadir luces")
+        print("6. Eliminar luces")
+        print("7. Encender luces")
+        print("8. Apagar luces")
+        print("9. Salir")
+        
+        opcion = int(input("Selecciona una opción: "))
+        
+        if opcion == 1:
+            arbol.añadir_estrella()
+        elif opcion == 2:
+            arbol.eliminar_estrella()
+        elif opcion == 3:
+            arbol.añadir_bolas()
+        elif opcion == 4:
+            arbol.eliminar_bolas()
+        elif opcion == 5:
+            arbol.añadir_luces()
+        elif opcion == 6:
+            arbol.eliminar_luces()
+        elif opcion == 7:
+            arbol.encender_luces()
+        elif opcion == 8:
+            arbol.apagar_luces()
+        elif opcion == 9:
+            print("¡Feliz Navidad!")
+            break
+        else:
+            print("Opción no válida.")
+
+# Ejecutar el programa
+if __name__ == "__main__":
+    main()

From eb2c9e77532c6e4292a0436d2043769b1121b135 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 08:30:29 +0200
Subject: [PATCH 53/66] AlmacenPapaNoel

---
 Advanced/19.AlmacenPapaNoel.py | 63 ++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)
 create mode 100644 Advanced/19.AlmacenPapaNoel.py

diff --git a/Advanced/19.AlmacenPapaNoel.py b/Advanced/19.AlmacenPapaNoel.py
new file mode 100644
index 0000000..8c927b2
--- /dev/null
+++ b/Advanced/19.AlmacenPapaNoel.py
@@ -0,0 +1,63 @@
+import random
+
+def generar_codigo():
+    """Genera un código aleatorio de 4 caracteres con letras (A-C) y números (1-3)."""
+    letras = ['A', 'B', 'C']
+    numeros = ['1', '2', '3']
+    elementos = letras + numeros
+    codigo = random.sample(elementos, 4)
+    return ''.join(codigo)
+
+def verificar_codigo(codigo_secreto, intento):
+    """Compara el código de intento con el código secreto y devuelve las pistas."""
+    pistas = []
+    
+    for i in range(4):
+        if intento[i] == codigo_secreto[i]:
+            pistas.append('Correcto')
+        elif intento[i] in codigo_secreto:
+            pistas.append('Presente')
+        else:
+            pistas.append('Incorrecto')
+    
+    return pistas
+
+def juego():
+    """Simula el juego de adivinar el código secreto."""
+    codigo_secreto = generar_codigo()
+    intentos = 0
+    max_intentos = 10
+    
+    print("Bienvenido a la adivinanza del código secreto de Papá Noel!")
+    print("El código tiene 4 caracteres y está compuesto por letras (A, B, C) y números (1, 2, 3).")
+    print("No hay repetidos y puedes realizar hasta 10 intentos.")
+    
+    while intentos < max_intentos:
+        intento = input(f"Intento {intentos + 1}/{max_intentos}: Introduce el código de 4 caracteres: ").upper()
+        
+        # Comprobar si el intento tiene una longitud de 4 caracteres y está compuesto por caracteres válidos
+        if len(intento) != 4:
+            print("Error: El código debe tener exactamente 4 caracteres.")
+            continue
+        
+        if not all(c in "ABC123" for c in intento):
+            print("Error: El código solo puede contener letras A, B, C y números 1, 2, 3.")
+            continue
+        
+        # Verificar el código y dar pistas
+        pistas = verificar_codigo(codigo_secreto, intento)
+        
+        print("Pistas: " + " | ".join(pistas))
+        
+        if all(pista == 'Correcto' for pista in pistas):
+            print(f"¡Felicidades! Has adivinado el código secreto: {codigo_secreto}")
+            break
+        
+        intentos += 1
+    
+    if intentos == max_intentos:
+        print(f"Lo siento, no has adivinado el código en {max_intentos} intentos. El código secreto era: {codigo_secreto}")
+
+# Ejecutar el juego
+if __name__ == "__main__":
+    juego()

From 52571c5bf50d2b4ce169aca5f5363b20265de9ab Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 25 Apr 2025 08:34:35 +0200
Subject: [PATCH 54/66] PlanificadorObjetivos

---
 Advanced/20.PlanificadorObjetivos.py | 140 +++++++++++++++++++++++++++
 1 file changed, 140 insertions(+)
 create mode 100644 Advanced/20.PlanificadorObjetivos.py

diff --git a/Advanced/20.PlanificadorObjetivos.py b/Advanced/20.PlanificadorObjetivos.py
new file mode 100644
index 0000000..51c3120
--- /dev/null
+++ b/Advanced/20.PlanificadorObjetivos.py
@@ -0,0 +1,140 @@
+"""
+/*
+ * EJERCICIO:
+ * El nuevo año está a punto de comenzar...
+ * ¡Voy a ayudarte a planificar tus propósitos de nuevo año!
+ *
+ * Programa un gestor de objetivos con las siguientes características:
+ * - Permite añadir objetivos (máximo 10)
+ * - Calcular el plan detallado
+ * - Guardar la planificación
+ * 
+ * Cada entrada de un objetivo está formado por (con un ejemplo):
+ * - Meta: Leer libros
+ * - Cantidad: 12
+ * - Unidades: libros
+ * - Plazo (en meses): 12 (máximo 12)
+ *
+ * El cálculo del plan detallado generará la siguiente salida:
+ * - Un apartado para cada mes
+ * - Un listado de objetivos calculados a cumplir en cada mes
+ *   (ejemplo: si quiero leer 12 libros, dará como resultado 
+ *   uno al mes)
+ * - Cada objetivo debe poseer su nombre, la cantidad de
+ *   unidades a completar en cada mes y su total. Por ejemplo:
+ *
+ *   Enero:
+ *   [ ] 1. Leer libros (1 libro/mes). Total: 12.
+ *   [ ] 2. Estudiar Git (1 curso/mes). Total: 1.
+ *   Febrero:
+ *   [ ] 1. Leer libros (1 libro/mes). Total: 12.
+ *   ...
+ *   Diciembre:
+ *   [ ] 1. Leer libros (1 libro/mes). Total: 12.
+ *
+ * - Si la duración es menor a un año, finalizará en el mes
+ *   correspondiente.
+ *   
+ * Por último, el cálculo detallado debe poder exportarse a .txt
+ * (No subir el fichero)
+ */
+"""
+import os
+
+class Objetivo:
+    def __init__(self, meta, cantidad, unidades, plazo):
+        self.meta = meta
+        self.cantidad = cantidad
+        self.unidades = unidades
+        self.plazo = plazo
+
+    def calcular_plan(self):
+        plan_mensual = self.cantidad // self.plazo  # División entera
+        if self.cantidad % self.plazo != 0:
+            plan_mensual += 1  # Si no es divisible, ajusta para el último mes
+        return plan_mensual
+
+    def mostrar_plan(self):
+        plan_mensual = self.calcular_plan()
+        plan_total = self.cantidad
+        return f"[ ] 1. {self.meta} ({plan_mensual} {self.unidades}/mes). Total: {plan_total}."
+
+
+class GestorObjetivos:
+    def __init__(self):
+        self.objetivos = []
+
+    def añadir_objetivo(self):
+        if len(self.objetivos) >= 10:
+            print("¡Ya has alcanzado el número máximo de objetivos (10)! No puedes añadir más.")
+            return
+
+        meta = input("Meta: ")
+        cantidad = int(input("Cantidad: "))
+        unidades = input("Unidades: ")
+        plazo = int(input("Plazo (en meses, máximo 12): "))
+
+        if plazo > 12:
+            print("El plazo no puede ser superior a 12 meses.")
+            return
+
+        objetivo = Objetivo(meta, cantidad, unidades, plazo)
+        self.objetivos.append(objetivo)
+        print(f"Objetivo '{meta}' añadido correctamente.")
+
+    def calcular_plan_detallado(self):
+        meses = [
+            "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
+            "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
+        ]
+        plan_detallado = ""
+        for mes in meses[:min(12, max([obj.plazo for obj in self.objetivos]))]:
+            plan_detallado += f"\n{mes}:\n"
+            for objetivo in self.objetivos:
+                if objetivo.plazo >= meses.index(mes) + 1:  # Si el objetivo se cumple en ese mes
+                    plan_detallado += f"  {objetivo.mostrar_plan()}\n"
+        return plan_detallado
+
+    def guardar_plan(self, plan_detallado):
+        with open("plan_detallado.txt", "w") as file:
+            file.write(plan_detallado)
+        print("Plan guardado en 'plan_detallado.txt'.")
+
+    def mostrar_objetivos(self):
+        if not self.objetivos:
+            print("No tienes objetivos añadidos.")
+            return
+
+        for idx, obj in enumerate(self.objetivos, 1):
+            print(f"{idx}. {obj.meta} - {obj.cantidad} {obj.unidades} en {obj.plazo} meses")
+
+def ejecutar_programa():
+    gestor = GestorObjetivos()
+
+    while True:
+        print("\nGestor de Objetivos del Nuevo Año")
+        print("1. Añadir Objetivo")
+        print("2. Calcular Plan Detallado")
+        print("3. Guardar Plan Detallado")
+        print("4. Ver Objetivos")
+        print("5. Salir")
+        opcion = input("Elige una opción (1-5): ")
+
+        if opcion == "1":
+            gestor.añadir_objetivo()
+        elif opcion == "2":
+            plan_detallado = gestor.calcular_plan_detallado()
+            print(plan_detallado)
+        elif opcion == "3":
+            plan_detallado = gestor.calcular_plan_detallado()
+            gestor.guardar_plan(plan_detallado)
+        elif opcion == "4":
+            gestor.mostrar_objetivos()
+        elif opcion == "5":
+            print("¡Hasta el próximo año! ¡Que cumplas tus objetivos!")
+            break
+        else:
+            print("Opción no válida. Intenta nuevamente.")
+
+if __name__ == "__main__":
+    ejecutar_programa()

From 81d5cd792baa6cdfc311e99051dfe89c6de8fe13 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 28 Apr 2025 11:01:41 +0200
Subject: [PATCH 55/66] Dates

---
 Intermediate/00_dates.py | 92 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)

diff --git a/Intermediate/00_dates.py b/Intermediate/00_dates.py
index 26b1411..aa42e26 100644
--- a/Intermediate/00_dates.py
+++ b/Intermediate/00_dates.py
@@ -73,3 +73,95 @@ def print_date(date):
 
 print(end_timedelta - start_timedelta)
 print(end_timedelta + start_timedelta)
+
+"""
+1. Crea una variable con la fecha y hora actual.
+2. Imprime solo el año, mes y día de la fecha actual.
+3. Crea una fecha específica: 25 de diciembre de 2025 y
+muéstrala.
+4. Muestra solo la hora, los minutos y los segundos de un
+objeto time.
+5. Calcula cuántos días faltan para el 1 de enero del año
+siguiente.
+6. Crea una función que reciba una fecha y devuelva su
+timestamp.
+7. Suma 30 días a la fecha actual usando timedelta.
+8. Crea una fecha y añade 1 mes (consejo: hazlo sumando 30
+días como simplificación).
+9. Compara dos fechas y muestra cuál es anterior.
+10. Crea una lista con varias fechas y ordénalas
+cronológicamente
+"""
+# Ejercicio 1: Crear una variable con la fecha y hora actual
+current_datetime = datetime.now()
+print("Fecha y hora actual:", current_datetime)
+
+# Ejercicio 2: Imprimir solo el año, mes y día de la fecha actual
+date_today = current_datetime.date()
+print("Año:", date_today.year)
+print("Mes:", date_today.month)
+print("Día:", date_today.day)
+
+# Ejercicio 3: Crear una fecha específica: 25 de diciembre de 2025
+specific_date = date(2025, 12, 25)
+print("Fecha específica:", specific_date)
+
+# Ejercicio 4: Mostrar solo la hora, los minutos y los segundos de un objeto time
+define_time = time(15, 30, 45)  # 3:30:45 PM
+print("Hora:", define_time.hour)    
+print("Minutos:", define_time.minute)
+print("Segundos:", define_time.second)
+
+# Ejercicio 5: Calcular cuántos días faltan para el 1 de enero del año siguiente
+next_year = date_today.year + 1
+next_year_date = date(next_year, 1, 1)
+days_until_next_year = (next_year_date - date_today).days
+print("Días hasta el 1 de enero del año siguiente:", days_until_next_year)
+
+# Ejercicio 6: Crear una función que reciba una fecha y devuelva su timestamp
+def get_timestamp(date_obj):
+    # Convertimos el objeto date a datetime para obtener el timestamp
+    datetime_obj = datetime.combine(date_obj, datetime.min.time())
+    return datetime_obj.timestamp()
+date_obj = date(2023, 10, 6)
+timestamp = get_timestamp(date_obj) 
+print("Timestamp de la fecha", date_obj, ":", timestamp)
+
+# Ejercicio 7: Sumar 30 días a la fecha actual usando timedelta
+date_plus_30_days = current_datetime + timedelta(days=30)
+print("Fecha actual + 30 días:", date_plus_30_days) 
+
+# Ejercicio 8: Crear una fecha y añadir 1 mes (simplificación sumando 30 días)
+date_plus_1_month = current_datetime + timedelta(days=30)
+print("Fecha actual + 1 mes (30 días):", date_plus_1_month)
+
+# Ejercicio 9: Comparar dos fechas y mostrar cuál es anterior
+date1 = date(2023, 10, 6)
+date2 = date(2024, 1, 1)
+if date1 < date2:
+    print(f"La fecha {date1} es anterior a {date2}.")
+elif date1 > date2:
+    print(f"La fecha {date1} es posterior a {date2}.")
+else:
+    print(f"Las fechas {date1} y {date2} son iguales.")
+
+# Ejercicio 10: Crear una lista con varias fechas y ordenarlas cronológicamente
+list_of_dates = [
+    date(2023, 10, 6),
+    date(2022, 5, 15),
+    date(2024, 1, 1),
+    date(2023, 7, 20)
+]
+sorted_dates = sorted(list_of_dates)
+print("Fechas ordenadas cronológicamente:")
+for d in sorted_dates:
+    print(d)
+
+# Fin del ejercicio
+
+
+
+
+
+
+

From 014629c665d3eae55e9418066eef6a1130355ddb Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 29 Apr 2025 08:26:45 +0200
Subject: [PATCH 56/66] ListComprehension

---
 Intermediate/01_list_comprehension.py | 45 +++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/Intermediate/01_list_comprehension.py b/Intermediate/01_list_comprehension.py
index eb84749..f594819 100644
--- a/Intermediate/01_list_comprehension.py
+++ b/Intermediate/01_list_comprehension.py
@@ -26,3 +26,48 @@ def sum_five(number):
 
 my_list = [sum_five(i) for i in range(8)]
 print(my_list)
+
+#EJERCICIOS
+
+#1. Genera una lista utilizando comprensión con los números del 0 al 10.
+my_list = [i for i in range(11)]
+print(my_list)
+
+#2. Crea una lista utilizando comprensión con los cuadrados de los números del 1 al 10.
+my_list = [i * i for i in range(1, 11)]
+print(my_list)  
+
+#3. Genera una lista utilizando comprensión con los números pares del 0 al 20.
+my_list = [i for i in range(21) if i % 2 == 0]
+print(my_list)
+
+#4. Convierte una lista de temperaturas en Celsius a Fahrenheit utilizando comprensión.
+my_list = [temp * 9 / 5 + 32 for temp in [0, 10, 20, 30, 40]]
+print(my_list)
+
+#5. Crea una lista utilizando comprensión con los caracteres de una cadena.
+my_string = "Hola"
+my_list = [char for char in my_string]
+print(my_list)
+
+#6. Filtra una lista de palabras y deja solo las que tienen más de 4 letras utilizando comprensión.
+my_list = [word for word in ["Hola", "mundo", "Python", "es", "genial"] if len(word) > 4]
+print(my_list)
+
+#7. Aumenta en 5 cada número de una lista con comprensión usando una función externa.
+def sum_five(number):
+    return number + 5
+my_list = [sum_five(i) for i in [0, 1, 2, 3, 4, 5, 6, 7]]
+print(my_list)
+
+#8. Crea una lista de booleanos que indique si cada número es mayor que 10 utilizando comprensión.
+my_list = [num > 10 for num in [5, 10, 15, 20, 25]]
+print(my_list)
+
+#9. Multiplica solo los números impares por 3 en una lista utilizando comprensión.
+my_list = [num * 3 for num in [1, 2, 3, 4, 5] if num % 2 != 0]
+print(my_list)
+
+#10. Usa comprensión de listas anidada para generar una matriz 3x3 con números del 1 al 9.
+my_list = [[i + j * 3 for i in range(1, 4)] for j in range(3)]
+print(my_list)

From 6f123b047cf7ebcbd304ccc13cf573c77a152045 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 29 Apr 2025 08:43:44 +0200
Subject: [PATCH 57/66] Challenge

---
 Intermediate/03_lambdas.py                | 13 +++++++++++++
 Intermediate/04_higher_order_functions.py | 14 ++++++++++++++
 Intermediate/05_error_types.py            | 12 ++++++++++++
 Intermediate/06_file_handling.py          | 12 ++++++++++++
 Intermediate/07_regular_expressions.py    | 12 ++++++++++++
 Intermediate/08_python_package_manager.py | 12 ++++++++++++
 6 files changed, 75 insertions(+)

diff --git a/Intermediate/03_lambdas.py b/Intermediate/03_lambdas.py
index d23507a..37bc3cc 100644
--- a/Intermediate/03_lambdas.py
+++ b/Intermediate/03_lambdas.py
@@ -21,3 +21,16 @@ def sum_three_values(value):
 
 
 print(sum_three_values(5)(2, 4))
+
+# Ejercicios Lambdas
+
+#1. Crea una lambda que sume dos números.
+#2. Crea una lambda que calcule el cuadrado de un número.
+#3. Crea una lambda que devuelva el mayor de dos números.
+#4. Crea una lambda que sume 10 a un número dado.
+#5. Crea una lambda que devuelva el último carácter de una cadena.
+#6. Crea una lambda que indique si una palabra tiene más de 6 letras.
+#7. Crea una lambda que convierta una cadena a minúsculas.
+#8. Crea una lambda que devuelva True si un número es positivo.
+#9. Crea una lambda que devuelva "Cadena vacía" si el string está vacío.
+#10. Crea una lambda que calcule el precio final con un impuesto añadido del 21%.
\ No newline at end of file
diff --git a/Intermediate/04_higher_order_functions.py b/Intermediate/04_higher_order_functions.py
index 4c0c6e0..2993a53 100644
--- a/Intermediate/04_higher_order_functions.py
+++ b/Intermediate/04_higher_order_functions.py
@@ -67,3 +67,17 @@ def sum_two_values(first_value, second_value):
 
 
 print(reduce(sum_two_values, numbers))
+
+#Ejercicios Funciones de orden superior
+
+#1. Crea una función que reciba una función y un número, y devuelva el resultado de aplicar la función al número.
+
+#2. Crea una función que reciba dos números y una función, y devuelva el resultado de sumar los dos números y aplicar la función.
+#3. Crea una función que devuelva otra función que sume un número fijo.
+#4. Usa map() con lambda para multiplicar cada número de una lista por 10.
+#5. Usa filter() con lambda para quedarte solo con los números pares.
+#6. Usa reduce() con lambda para obtener la suma total de una lista.
+#7. Escribe una función que devuelva una función que reciba un nombre y devuelva "Hola, ".
+#8. Crea una función que reciba una lista y una función, y cuente cuántos elementos cumplen con la función.
+#9. Crea una función que reciba dos funciones y un número, y las aplique en orden.
+#10. Crea una función que reciba una lista y una función, y aplique esa función a cada elemento usando un bucle (sin map).
\ No newline at end of file
diff --git a/Intermediate/05_error_types.py b/Intermediate/05_error_types.py
index 853f329..13b5f21 100644
--- a/Intermediate/05_error_types.py
+++ b/Intermediate/05_error_types.py
@@ -49,3 +49,15 @@
 # ZeroDivisionError
 # print(4/0) # Descomentar para Error
 print(4/2)
+
+#Ejercicios Tipos de error
+#1. Genera un SyntaxError al imprimir una cadena sin paréntesis.
+#2. Genera un NameError intentando usar una variable no definida.
+#3. Genera un IndexError accediendo a un índice inexistente de una lista.
+#4. Genera un ModuleNotFoundError al importar un módulo inexistente.
+#5. Genera un AttributeError accediendo a un atributo que no existe.
+#6. Genera un KeyError al acceder a una clave inexistente de un diccionario.
+#7. Genera un TypeError usando tipos incorrectos (índice string en lista).
+#8. Genera un ImportError al importar una función que no existe desde un módulo.
+#9. Genera un ValueError intentando convertir un string no numérico a entero.
+#10. Intenta detectar si un error ocurre usando tryexcept con un KeyError.
\ No newline at end of file
diff --git a/Intermediate/06_file_handling.py b/Intermediate/06_file_handling.py
index 05ab8df..42950f5 100644
--- a/Intermediate/06_file_handling.py
+++ b/Intermediate/06_file_handling.py
@@ -81,3 +81,15 @@
 # .xml file
 
 # ¿Te atreves a practicar cómo trabajar con este tipo de ficheros?
+
+#Ejercicios File Handling
+#1. Crea un archivo de texto y escribe en él la frase "Hola desde Python".
+#2. Abre un archivo y lee todo su contenido.
+#3. Añade una nueva línea al final del archivo con el texto "Línea añadida".
+#4. Lee solo los primeros 10 caracteres del archivo.
+#5. Usa seek para volver al inicio del archivo y leer desde ahí.
+#6. Lee e imprime el contenido línea por línea usando readline.
+#7. Lee todas las líneas del archivo en una lista y recórrelas con un bucle.
+#8. Crea un archivo nuevo que sobrescriba si ya existe, y escribe varias líneas.
+#9. Usa una función para abrir un archivo, escribir texto y cerrarlo automáticamente con with.
+#10. Lee un archivo línea por línea y muestra solo las que contienen la palabra "Python".
\ No newline at end of file
diff --git a/Intermediate/07_regular_expressions.py b/Intermediate/07_regular_expressions.py
index d2b1f9a..ddf1339 100644
--- a/Intermediate/07_regular_expressions.py
+++ b/Intermediate/07_regular_expressions.py
@@ -78,3 +78,15 @@
 
 email = "mouredev@mouredev.com.mx"
 print(re.findall(pattern, email))
+
+# Ejercicios Regular Expressions
+#1. Busca si una cadena empieza por "Hola".
+# 2. Busca la palabra "Python" en una cadena aunque esté en minúsculas.
+# 3. Encuentra todas las apariciones de la palabra "curso" en una cadena.
+# 4. Reemplaza todas las apariciones de "lección" por "LECCIÓN".
+# 5. Divide un texto en partes separadas por comas.
+# 6. Busca la primera palabra que comience con "A" o "a".
+# 7. Encuentra todas las palabras en una cadena que terminen en "ción".
+# 8. Verifica si una cadena contiene solo números.
+# 9. Reemplaza todos los números de una cadena por el texto "[número]".
+# 10. Encuentra todas las palabras de 4 letras exactas en una cadena.
diff --git a/Intermediate/08_python_package_manager.py b/Intermediate/08_python_package_manager.py
index e0e7410..1a64d51 100644
--- a/Intermediate/08_python_package_manager.py
+++ b/Intermediate/08_python_package_manager.py
@@ -37,3 +37,15 @@
 
 
 print(arithmetics.sum_two_values(1, 4))
+
+#EJERCICIOS Manejo de paquetes
+#1. Importa el módulo math y muestra el valor de pi.
+# 2. Crea un array de números usando numpy y multiplícalo por 3.
+# 3. Muestra la versión instalada de numpy.
+# 4. Realiza una petición HTTP con requests a una API pública y muestra el código de estado.
+# 5. Importa una función llamada sum_two_values desde un paquete personalizado mypackage.arithmetics y utilízala.
+# 6. Usa pandas para crear un DataFrame con nombres en español.
+# 7. Ejecuta el comando para instalar el paquete requests desde la terminal.
+# 8. Usa requests para obtener datos de una API y extrae solo los nombres de los primeros Pokémon.
+# 9. Muestra todos los paquetes instalados con pip desde la terminal.
+# 10. Escribe una línea de código que muestre la ayuda sobre el paquete numpy desde Python.

From df758979f8aadd7425235c8c71a2bab77e8bdc68 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 29 Apr 2025 08:45:57 +0200
Subject: [PATCH 58/66] Lambdas

---
 Intermediate/03_lambdas.py | 32 +++++++++++++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/Intermediate/03_lambdas.py b/Intermediate/03_lambdas.py
index 37bc3cc..18581ed 100644
--- a/Intermediate/03_lambdas.py
+++ b/Intermediate/03_lambdas.py
@@ -25,12 +25,42 @@ def sum_three_values(value):
 # Ejercicios Lambdas
 
 #1. Crea una lambda que sume dos números.
+def sum_two_numbers(a, b): return a + b
+print(sum_two_numbers(3, 5))
+
 #2. Crea una lambda que calcule el cuadrado de un número.
+def square_number(x): return x * x
+print(square_number(4))
+
 #3. Crea una lambda que devuelva el mayor de dos números.
+def max_of_two(a, b): return a if a > b else b
+print(max_of_two(10, 5))
+
 #4. Crea una lambda que sume 10 a un número dado.
+def add_ten(x): return x + 10
+print(add_ten(5))
+
 #5. Crea una lambda que devuelva el último carácter de una cadena.
+def last_character(string): return string[-1] if string else None
+print(last_character("Python"))
+
 #6. Crea una lambda que indique si una palabra tiene más de 6 letras.
+def has_more_than_six_letters(word): return len(word) > 6
+print(has_more_than_six_letters("Pythonista"))
+
 #7. Crea una lambda que convierta una cadena a minúsculas.
+def to_lowercase(string): return string.lower()
+print(to_lowercase("PYTHON"))
+
 #8. Crea una lambda que devuelva True si un número es positivo.
+def is_positive(number): return number > 0
+print(is_positive(5))
+
 #9. Crea una lambda que devuelva "Cadena vacía" si el string está vacío.
-#10. Crea una lambda que calcule el precio final con un impuesto añadido del 21%.
\ No newline at end of file
+def empty_string(string): return "Cadena vacía" if not string else string
+print(empty_string(""))
+print(empty_string("Python"))   
+
+#10. Crea una lambda que calcule el precio final con un impuesto añadido del 21%.
+def final_price(price): return price * 1.21
+print(final_price(100))

From 8e81619d5d063fb57d5472318d51a75ca81d7951 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Tue, 29 Apr 2025 08:51:42 +0200
Subject: [PATCH 59/66] HigherOrderFunctions

---
 Intermediate/04_higher_order_functions.py | 75 ++++++++++++++++++++++-
 1 file changed, 74 insertions(+), 1 deletion(-)

diff --git a/Intermediate/04_higher_order_functions.py b/Intermediate/04_higher_order_functions.py
index 2993a53..cf75950 100644
--- a/Intermediate/04_higher_order_functions.py
+++ b/Intermediate/04_higher_order_functions.py
@@ -71,13 +71,86 @@ def sum_two_values(first_value, second_value):
 #Ejercicios Funciones de orden superior
 
 #1. Crea una función que reciba una función y un número, y devuelva el resultado de aplicar la función al número.
+def apply_function(func, number):
+    return func(number) 
+def square(x):
+    return x * x
+print(apply_function(square, 5)) #25
+
 
 #2. Crea una función que reciba dos números y una función, y devuelva el resultado de sumar los dos números y aplicar la función.
+def apply_function_to_sum(func, a, b):
+    return func(a + b)
+def double(x):
+    return x * 2
+print(apply_function_to_sum(double, 2, 3)) #10
+
 #3. Crea una función que devuelva otra función que sume un número fijo.
+def add_fixed_value(fixed_value):
+    def add(value):
+        return value + fixed_value
+    return add
+add_five = add_fixed_value(5)
+print(add_five(10)) #15
+
 #4. Usa map() con lambda para multiplicar cada número de una lista por 10.
+numbers = [1, 2, 3, 4, 5]
+print(list(map(lambda x: x * 10, numbers))) # [10, 20, 30, 40, 50]
+
 #5. Usa filter() con lambda para quedarte solo con los números pares.
+numbers = [1, 2, 3, 4, 5, 6]
+print(list(filter(lambda x: x % 2 == 0, numbers))) # [2, 4, 6]
+
 #6. Usa reduce() con lambda para obtener la suma total de una lista.
+from functools import reduce
+numbers = [1, 2, 3, 4, 5]
+print(reduce(lambda x, y: x + y, numbers)) #15
+
 #7. Escribe una función que devuelva una función que reciba un nombre y devuelva "Hola, ".
+# seguido del nombre.
+def greet(greeting):
+    def say_hello(name):
+        return f"{greeting}, {name}"
+    return say_hello    
+def hello(name):
+    return greet("Hola")(name)
+print(hello("Juan")) #Hola, Juan
+
 #8. Crea una función que reciba una lista y una función, y cuente cuántos elementos cumplen con la función.
+def count_elements(lst, func):
+    return len(list(filter(func, lst)))
+def is_even(x):
+    return x % 2 == 0
+numbers = [1, 2, 3, 4, 5, 6]
+print(count_elements(numbers, is_even)) #3
+
 #9. Crea una función que reciba dos funciones y un número, y las aplique en orden.
-#10. Crea una función que reciba una lista y una función, y aplique esa función a cada elemento usando un bucle (sin map).
\ No newline at end of file
+def apply_functions_in_order(func1, func2, number):
+    return func2(func1(number))
+def add_one(x):
+    return x + 1
+def multiply_by_two(x):
+    return x * 2
+print(apply_functions_in_order(add_one, multiply_by_two, 5)) #12
+
+#10. Crea una función que reciba una lista y una función, y aplique esa función a cada elemento usando un bucle (sin map).
+def apply_function_with_loop(lst, func):
+    result = []
+    for item in lst:
+        result.append(func(item))
+    return result
+def square(x):
+    return x * x
+numbers = [1, 2, 3, 4, 5]
+print(apply_function_with_loop(numbers, square)) # [1, 4, 9, 16, 25]
+#
+#11. Crea una función que reciba una lista y una función, y aplique esa función a cada elemento usando un bucle (sin map).
+def apply_function_with_loop(lst, func):
+    result = []
+    for item in lst:
+        result.append(func(item))
+    return result
+def square(x):
+    return x * x
+numbers = [1, 2, 3, 4, 5]
+print(apply_function_with_loop(numbers, square)) # [1, 4, 9, 16, 25]

From 869a1bf661b5bc27d3b45dbd180b7109cbdfc223 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Wed, 30 Apr 2025 08:51:48 +0200
Subject: [PATCH 60/66] TiposError

---
 Intermediate/05_error_types.py | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/Intermediate/05_error_types.py b/Intermediate/05_error_types.py
index 13b5f21..23adf02 100644
--- a/Intermediate/05_error_types.py
+++ b/Intermediate/05_error_types.py
@@ -52,12 +52,40 @@
 
 #Ejercicios Tipos de error
 #1. Genera un SyntaxError al imprimir una cadena sin paréntesis.
+#from math import pi
+#print "¡Hola comunidad!" # Descomentar para Error
+
 #2. Genera un NameError intentando usar una variable no definida.
+#language = "Spanish"  # Comentar para Error
+print(language) # Descomentar para Error
+
 #3. Genera un IndexError accediendo a un índice inexistente de una lista.
+my_list = ["Python", "Swift", "Kotlin", "Dart", "JavaScript"]
+#print(my_list[5]) # Descomentar para Error
+
 #4. Genera un ModuleNotFoundError al importar un módulo inexistente.
+#import maths # Descomentar para Error
+
 #5. Genera un AttributeError accediendo a un atributo que no existe.
+#print(math.PI) # Descomentar para Error
+
 #6. Genera un KeyError al acceder a una clave inexistente de un diccionario.
+my_dict = {"Nombre": "Brais", "Apellido": "Moure", "Edad": 35, 1: "Python"}
+#print(my_dict["Apelido"]) # Descomentar para Error
+
 #7. Genera un TypeError usando tipos incorrectos (índice string en lista).
+my_list = ["Python", "Swift", "Kotlin", "Dart", "JavaScript"]
+#print(my_list["0"]) # Descomentar para Error
+
 #8. Genera un ImportError al importar una función que no existe desde un módulo.
+# from math import PI # Descomentar para Error
+
 #9. Genera un ValueError intentando convertir un string no numérico a entero.
-#10. Intenta detectar si un error ocurre usando tryexcept con un KeyError.
\ No newline at end of file
+#my_int = int("10 Años") # Descomentar para Error
+
+#10. Intenta detectar si un error ocurre usando tryexcept con un KeyError.
+my_dict = {"Nombre": "Brais", "Apellido": "Moure", "Edad": 35, 1: "Python"}
+try:
+    print(my_dict["Apelido"])
+except KeyError:
+    print("Error: La clave 'Apelido' no existe en el diccionario.")

From 7fa5d42cd6907a33c1978fdf334372634e261856 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 1 May 2025 12:27:10 +0200
Subject: [PATCH 61/66] .vscode

---
 .vscode/settings.json | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 .vscode/settings.json

diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..e1888d1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,22 @@
+{
+  "workbench.colorCustomizations": {
+    "activityBar.activeBackground": "#c5f75c",
+    "activityBar.background": "#c5f75c",
+    "activityBar.foreground": "#15202b",
+    "activityBar.inactiveForeground": "#15202b99",
+    "activityBarBadge.background": "#0ca9f3",
+    "activityBarBadge.foreground": "#15202b",
+    "commandCenter.border": "#15202b99",
+    "sash.hoverBorder": "#c5f75c",
+    "statusBar.background": "#b4f52b",
+    "statusBar.foreground": "#15202b",
+    "statusBarItem.hoverBackground": "#9de20b",
+    "statusBarItem.remoteBackground": "#b4f52b",
+    "statusBarItem.remoteForeground": "#15202b",
+    "titleBar.activeBackground": "#b4f52b",
+    "titleBar.activeForeground": "#15202b",
+    "titleBar.inactiveBackground": "#b4f52b99",
+    "titleBar.inactiveForeground": "#15202b99"
+  },
+  "peacock.color": "#b4f52b"
+}

From c99466b1b8c2b30f2e6ee75955ea2613b5dc2b8a Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 1 May 2025 12:42:17 +0200
Subject: [PATCH 62/66] FileHandling

---
 Intermediate/06_file_handling.py | 91 +++++++++++++++++++++++++++++++-
 Intermediate/fer.csv             |  3 ++
 Intermediate/fer.json            | 11 ++++
 Intermediate/fer.txt             |  1 +
 Intermediate/fer.xml             |  6 +++
 5 files changed, 111 insertions(+), 1 deletion(-)
 create mode 100644 Intermediate/fer.csv
 create mode 100644 Intermediate/fer.json
 create mode 100644 Intermediate/fer.txt
 create mode 100644 Intermediate/fer.xml

diff --git a/Intermediate/06_file_handling.py b/Intermediate/06_file_handling.py
index 42950f5..3f90601 100644
--- a/Intermediate/06_file_handling.py
+++ b/Intermediate/06_file_handling.py
@@ -84,12 +84,101 @@
 
 #Ejercicios File Handling
 #1. Crea un archivo de texto y escribe en él la frase "Hola desde Python".
+file_fer_txt = open("Intermediate/fer.txt", "w+")
+file_fer_txt.write("Hola desde Python")
+file_fer_txt.close()
+
 #2. Abre un archivo y lee todo su contenido.
+file_fer_txt = open("Intermediate/fer.txt", "r")
+print(file_fer_txt.read())
+file_fer_txt.close()
+
 #3. Añade una nueva línea al final del archivo con el texto "Línea añadida".
+file_fer_txt = open("Intermediate/fer.txt", "a")
+file_fer_txt.write("\nLínea añadida")
+file_fer_txt.close()
+
 #4. Lee solo los primeros 10 caracteres del archivo.
+file_fer_txt = open("Intermediate/fer.txt", "r")
+print(file_fer_txt.read(10))
+file_fer_txt.close()
+
 #5. Usa seek para volver al inicio del archivo y leer desde ahí.
+file_fer_txt = open("Intermediate/fer.txt", "r")
+file_fer_txt.seek(0)
+print(file_fer_txt.read())
+file_fer_txt.close()
+
 #6. Lee e imprime el contenido línea por línea usando readline.
+file_fer_txt = open("Intermediate/fer.txt", "r")
+print(file_fer_txt.readline())
+print(file_fer_txt.readline())
+print(file_fer_txt.readline())
+file_fer_txt.close()
+
 #7. Lee todas las líneas del archivo en una lista y recórrelas con un bucle.
+file_fer_txt = open("Intermediate/fer.txt", "r")
+lines = file_fer_txt.readlines()
+for line in lines:
+    print(line)
+file_fer_txt.close()
+
 #8. Crea un archivo nuevo que sobrescriba si ya existe, y escribe varias líneas.
+file_fer_txt = open("Intermediate/fer.txt", "w+")
+file_fer_txt.write("Línea 1\nLínea 2\nLínea 3")
+file_fer_txt.close()
+
 #9. Usa una función para abrir un archivo, escribir texto y cerrarlo automáticamente con with.
-#10. Lee un archivo línea por línea y muestra solo las que contienen la palabra "Python".
\ No newline at end of file
+def write_to_file(filename, text):
+    with open(filename, "w") as file:
+        file.write(text)
+write_to_file("Intermediate/fer.txt", "Texto escrito con función")
+
+#10. Lee un archivo línea por línea y muestra solo las que contienen la palabra "Python".
+file_fer_txt = open("Intermediate/fer.txt", "r")
+for line in file_fer_txt:
+    if "Python" in line:
+        print(line)
+file_fer_txt.close()
+#11. Crea un archivo CSV y escribe varias filas de datos en él.
+csv_file = open("Intermediate/fer.csv", "w+")
+csv_writer = csv.writer(csv_file)
+csv_writer.writerow(["name", "surname", "age", "language", "website"])
+csv_writer.writerow(["Brais", "Moure", 35, "Python", "https://moure.dev"])
+csv_writer.writerow(["Roswell", "", 2, "COBOL", ""])
+csv_file.close()
+
+#12. Lee un archivo CSV y muestra su contenido.
+csv_file = open("Intermediate/fer.csv", "r")
+csv_reader = csv.reader(csv_file)
+for row in csv_reader:
+    print(row)
+csv_file.close()
+
+#13. Crea un archivo JSON y escribe un diccionario en él.
+json_file = open("Intermediate/fer.json", "w+")
+json_test = {
+    "name": "Brais",
+    "surname": "Moure",
+    "age": 35,
+    "languages": ["Python", "Swift", "Kotlin"],
+    "website": "https://moure.dev"}
+json.dump(json_test, json_file, indent=2)
+json_file.close()
+#14. Lee un archivo JSON y muestra su contenido.
+json_file = open("Intermediate/fer.json", "r")
+json_dict = json.load(json_file)
+print(json_dict)
+print(type(json_dict))
+print(json_dict["name"])
+json_file.close()
+#15. Crea un archivo XML y escribe un documento XML básico en él.
+xml_file = open("Intermediate/fer.xml", "w+")
+xml_file.write(
+    '<?xml version="1.0" encoding="UTF-8"?>\n<root>\n\t<name>Brais</name>\n\t<surname>Moure</surname>\n\t<age>35</age>\n</root>')
+xml_file.close()
+#16. Lee un archivo XML y muestra su contenido.
+xml_file = open("Intermediate/fer.xml", "r")
+xml_content = xml_file.read()
+print(xml_content)
+xml_file.close()
diff --git a/Intermediate/fer.csv b/Intermediate/fer.csv
new file mode 100644
index 0000000..eeb0e38
--- /dev/null
+++ b/Intermediate/fer.csv
@@ -0,0 +1,3 @@
+name,surname,age,language,website
+Brais,Moure,35,Python,https://moure.dev
+Roswell,,2,COBOL,
diff --git a/Intermediate/fer.json b/Intermediate/fer.json
new file mode 100644
index 0000000..dec5a3c
--- /dev/null
+++ b/Intermediate/fer.json
@@ -0,0 +1,11 @@
+{
+  "name": "Brais",
+  "surname": "Moure",
+  "age": 35,
+  "languages": [
+    "Python",
+    "Swift",
+    "Kotlin"
+  ],
+  "website": "https://moure.dev"
+}
\ No newline at end of file
diff --git a/Intermediate/fer.txt b/Intermediate/fer.txt
new file mode 100644
index 0000000..a960a51
--- /dev/null
+++ b/Intermediate/fer.txt
@@ -0,0 +1 @@
+Texto escrito con función
\ No newline at end of file
diff --git a/Intermediate/fer.xml b/Intermediate/fer.xml
new file mode 100644
index 0000000..f8251cf
--- /dev/null
+++ b/Intermediate/fer.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+	<name>Brais</name>
+	<surname>Moure</surname>
+	<age>35</age>
+</root>
\ No newline at end of file

From a68094837157f492aa075d98ffb130ce09e63210 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Thu, 1 May 2025 12:45:36 +0200
Subject: [PATCH 63/66] RegularExpression

---
 Intermediate/07_regular_expressions.py | 46 ++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/Intermediate/07_regular_expressions.py b/Intermediate/07_regular_expressions.py
index ddf1339..a37d634 100644
--- a/Intermediate/07_regular_expressions.py
+++ b/Intermediate/07_regular_expressions.py
@@ -81,12 +81,58 @@
 
 # Ejercicios Regular Expressions
 #1. Busca si una cadena empieza por "Hola".
+cadena = "Hola, ¿cómo estás?"
+pattern = r"^Hola"
+print(re.match(pattern, cadena))
+
 # 2. Busca la palabra "Python" en una cadena aunque esté en minúsculas.
+cadena = "Me encanta programar en python"
+pattern = r"python"
+print(re.search(pattern, cadena, re.I))
+
 # 3. Encuentra todas las apariciones de la palabra "curso" en una cadena.
+cadena = "Este curso es genial. El curso de Python es el mejor curso."
+pattern = r"curso"
+print(re.findall(pattern, cadena))
+
 # 4. Reemplaza todas las apariciones de "lección" por "LECCIÓN".
+cadena = "Esta es la lección número 7: Lección llamada Expresiones Regulares"
+pattern = r"lección"
+print(re.sub(pattern, "LECCIÓN", cadena, flags=re.I))
+
 # 5. Divide un texto en partes separadas por comas.
+cadena = "manzana,pera,plátano,naranja"
+pattern = r","
+print(re.split(pattern, cadena))
+
 # 6. Busca la primera palabra que comience con "A" o "a".
+cadena = "Aprender Python es asombroso"
+pattern = r"\b[Aa]\w*"
+print(re.search(pattern, cadena))
+
 # 7. Encuentra todas las palabras en una cadena que terminen en "ción".
+cadena = "La educación es la clave para la evolución y la revolución."
+pattern = r"\b\w*ción\b"
+print(re.findall(pattern, cadena))
+
 # 8. Verifica si una cadena contiene solo números.
+cadena = "1234567890"
+pattern = r"^\d+$"
+print(re.match(pattern, cadena))
+
 # 9. Reemplaza todos los números de una cadena por el texto "[número]".
+cadena = "El año es 2023 y tengo 30 años."
+pattern = r"\d+"
+print(re.sub(pattern, "[número]", cadena))
+cadena = "El año es 2023 y tengo 30 años."
+pattern = r"\d+"
+print(re.sub(pattern, "[número]", cadena))
+
 # 10. Encuentra todas las palabras de 4 letras exactas en una cadena.
+cadena = "Este es un texto con varias palabras de cuatro letras."
+pattern = r"\b\w{4}\b"
+print(re.findall(pattern, cadena))
+# 11. Busca si una cadena contiene un número de teléfono en formato (XXX) XXX-XXXX.
+cadena = "(123) 456-7890"
+pattern = r"\(\d{3}\) \d{3}-\d{4}"
+print(re.match(pattern, cadena))

From 8e5198dd7c696f3699ed49bbbf1bb5c51472fc7c Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Fri, 2 May 2025 11:04:34 +0200
Subject: [PATCH 64/66] PackageManager

---
 Intermediate/08_python_package_manager.py | 69 ++++++++++++++++++++++-
 1 file changed, 68 insertions(+), 1 deletion(-)

diff --git a/Intermediate/08_python_package_manager.py b/Intermediate/08_python_package_manager.py
index 1a64d51..5ecf5bf 100644
--- a/Intermediate/08_python_package_manager.py
+++ b/Intermediate/08_python_package_manager.py
@@ -39,13 +39,80 @@
 print(arithmetics.sum_two_values(1, 4))
 
 #EJERCICIOS Manejo de paquetes
-#1. Importa el módulo math y muestra el valor de pi.
+# 1. Importa el módulo math y muestra el valor de pi.
+import math
+print(math.pi)
+
 # 2. Crea un array de números usando numpy y multiplícalo por 3.
+import numpy as np
+array = np.array([1, 2, 3, 4, 5])
+print(array * 3)
+
 # 3. Muestra la versión instalada de numpy.
+print(numpy.__version__)
+
 # 4. Realiza una petición HTTP con requests a una API pública y muestra el código de estado.
+response = requests.get("https://api.github.com")
+print(response.status_code)
+
 # 5. Importa una función llamada sum_two_values desde un paquete personalizado mypackage.arithmetics y utilízala.
+from mypackage.arithmetics import sum_two_values
+result = sum_two_values(5, 10)
+print(result)
+
 # 6. Usa pandas para crear un DataFrame con nombres en español.
+import pandas as pd
+data = {'Nombre': ['Juan', 'Ana', 'Pedro'], 'Edad': [28, 22, 35]}
+df = pd.DataFrame(data)
+print(df)
+
 # 7. Ejecuta el comando para instalar el paquete requests desde la terminal.
+# pip install requests
+
 # 8. Usa requests para obtener datos de una API y extrae solo los nombres de los primeros Pokémon.
+response = requests.get("https://pokeapi.co/api/v2/pokemon?limit=10")
+data = response.json()
+pokemon_names = [pokemon['name'] for pokemon in data['results']]
+print(pokemon_names)
+
 # 9. Muestra todos los paquetes instalados con pip desde la terminal.
+# pip list
+# pip freeze
+# pip show requests
+# pip show pandas
 # 10. Escribe una línea de código que muestre la ayuda sobre el paquete numpy desde Python.
+help(numpy)
+# 11. Crea un entorno virtual y activa el entorno.
+# python -m venv myenv
+# source myenv/bin/activate (Linux/Mac)
+# myenv\Scripts\activate (Windows)
+# 12. Instala un paquete en el entorno virtual.
+# pip install requests
+# 13. Desactiva el entorno virtual.
+# deactivate
+# 14. Elimina el entorno virtual.
+# rm -rf myenv (Linux/Mac)
+# 15. Crea un archivo requirements.txt con los paquetes instalados.
+# pip freeze > requirements.txt
+# 16. Instala los paquetes desde requirements.txt.
+# pip install -r requirements.txt
+# 17. Actualiza un paquete específico.
+# pip install --upgrade requests
+# 18. Desinstala un paquete.
+# pip uninstall requests
+# 19. Busca un paquete en PyPI.
+# pip search requests
+# 20. Muestra la documentación de un paquete.
+# pip show -f requests
+# 21. Usa pipenv para crear un entorno virtual y gestionar dependencias.
+# pip install pipenv
+# pipenv install requests
+# pipenv shell
+# 22. Usa poetry para gestionar dependencias y crear un entorno virtual.
+# pip install poetry
+# poetry new myproject
+# cd myproject
+# poetry add requests
+# poetry install
+# 23. Usa conda para crear un entorno virtual y gestionar dependencias.q
+

From a378443ce4184dcf3c1059a7787f71a36190c7b2 Mon Sep 17 00:00:00 2001
From: ilnanu <ilnanu@gmail.com>
Date: Mon, 5 May 2025 13:08:42 +0200
Subject: [PATCH 65/66] Globoticket

---
 IAapps/Globotickets/.env                 |   1 +
 IAapps/Globotickets/Prompts.txt          |  62 ++++++++++++
 IAapps/Globotickets/app.py               |  88 ++++++++++++++++
 IAapps/Globotickets/copilot.js           |   0
 IAapps/Globotickets/requirements.txt     |   3 +
 IAapps/Globotickets/static/logo.png      | Bin 0 -> 1437217 bytes
 IAapps/Globotickets/static/styles.css    |  67 ++++++++++++
 IAapps/Globotickets/templates/index.html |  84 +++++++++++++++
 IAapps/Globotickets/tests/test_app.py    | 124 +++++++++++++++++++++++
 9 files changed, 429 insertions(+)
 create mode 100644 IAapps/Globotickets/.env
 create mode 100644 IAapps/Globotickets/Prompts.txt
 create mode 100644 IAapps/Globotickets/app.py
 create mode 100644 IAapps/Globotickets/copilot.js
 create mode 100644 IAapps/Globotickets/requirements.txt
 create mode 100644 IAapps/Globotickets/static/logo.png
 create mode 100644 IAapps/Globotickets/static/styles.css
 create mode 100644 IAapps/Globotickets/templates/index.html
 create mode 100644 IAapps/Globotickets/tests/test_app.py

diff --git a/IAapps/Globotickets/.env b/IAapps/Globotickets/.env
new file mode 100644
index 0000000..ed1ce32
--- /dev/null
+++ b/IAapps/Globotickets/.env
@@ -0,0 +1 @@
+TICKETMASTER_API_KEY="XXX5CRYN8ZjvPzKCUKbMZBweY8iDaXXX"
diff --git a/IAapps/Globotickets/Prompts.txt b/IAapps/Globotickets/Prompts.txt
new file mode 100644
index 0000000..9a8ddcc
--- /dev/null
+++ b/IAapps/Globotickets/Prompts.txt
@@ -0,0 +1,62 @@
+Quiero crear una aplicación sencilla para eventos llamada Globotickets, utilizando Python y Javascript. 
+El usuario podrá entrar el nombre de una ciudad y entonces la aplicación obtendrá y visualizara una lista de eventos en la ciudad
+llamando al API de Ticketmaster. ¿Puedes describir a alto nivel como deberia encarar esta aplicación?
+
+trabajemos en el backend.Escribe el código Python/Flask necesario
+
+Te paso la documentacion de la API para Tocketmaster's Event Images API. Escribe el código necesario en Python/Flask para el backend de la aplicacion Globotickets.
+###
+Get Event Images
+Method: GET
+
+Summary: Get Event Images
+
+Description: Get images for a specific event using the unique identifier for the event.
+
+/discovery/v2/events/{id}/images
+
+URL parameters:
+Parameter	Description	Type	Default Value	Required
+id 	ID of the event	String		Yes
+Query parameters:
+Parameter	Description	Type	Default Value	Required
+locale	The locale in ISO code format. Multiple comma-separated values can be provided. When omitting the country part of the code (e.g. only 'en' or 'fr') then the first matching locale is used. When using a '*' it matches all locales. '*' can only be used at the end (e.g. 'en-us,en,*')	String	*	No
+domain	Filter entities based on domains they are available on	Array		No
+Response structure:
+200successful operation
+
+_links(object) - links to data sets
+type(string) - Type of the entity
+id(string) - Unique id of the entity in the discovery API
+images(array) - Images of the entity
+JavaScript
+$.ajax({
+  type:"GET",
+  url:"https://app.ticketmaster.com/discovery/v2/events/k7vGFKzleBdwS/images.json?apikey=d925CRYN8ZjvPzKCUKbMZBweY8iDaZjB",
+  async:true,
+  dataType: "json",
+  success: function(json) {
+              console.log(json);
+              // Parse the response.
+              // Do other things.
+           },
+  error: function(xhr, status, err) {
+              // This time, we do not end up here!
+           }
+});
+Examples:
+RequestResponse
+
+GET /discovery/v2/events/0B004F0401BD55E5/images.json?apikey=d925CRYN8ZjvPzKCUKbMZBweY8iDaZjB HTTP/1.1
+Host: app.ticketmaster.com
+X-Target-URI: https://app.ticketmaster.com
+Connection: Keep-Alive
+
+# Ahora trabajemos en el front-end de la aplicacion. Escribe el código Javascript necesario para obtener la entrada del usuario y la envie al backend.
+
+Vamos a trabajar en el estilo, y luego tengo un par de requisitos sencillos. 
+La parte superior de la página debe tener un banner negro y un logotipo llamado logo.png que añadiré a mi proyecto. 
+Queremos que esté alineado a la izquierda. El color de énfasis debe ser rosa. Tengo el código hexadecimal aqui #FF1675 
+.La fuente debe ser Segoe UI. El código deberia estar en un archivo CSS aparte.
+
+Por favor, escribe test unitarios para la aplicación utilizando el framework pytest
\ No newline at end of file
diff --git a/IAapps/Globotickets/app.py b/IAapps/Globotickets/app.py
new file mode 100644
index 0000000..ed000e0
--- /dev/null
+++ b/IAapps/Globotickets/app.py
@@ -0,0 +1,88 @@
+import os
+import requests
+from flask import Flask, request, jsonify
+from flask import render_template
+from dotenv import load_dotenv
+from flask_cors import CORS
+
+# Cargar variables de entorno (como la API Key)
+load_dotenv()
+
+app = Flask(__name__)
+CORS(app)  # Habilita CORS para permitir peticiones desde frontend
+@app.route('/')
+def index():
+    return render_template('index.html')    
+
+TICKETMASTER_API_KEY = os.getenv("TICKETMASTER_API_KEY")
+TICKETMASTER_BASE_URL = "https://app.ticketmaster.com/discovery/v2/events.json"
+
+@app.route('/api/events', methods=['GET'])
+def get_events():
+    city = request.args.get('city')
+    if not city:
+        return jsonify({"error": "Debe proporcionar el nombre de una ciudad."}), 400
+
+    params = {
+        'apikey': TICKETMASTER_API_KEY,
+        'city': city,
+        'size': 10,  # Puedes ajustar cuántos eventos devolver
+        'sort': 'date,asc'
+    }
+
+    try:
+        response = requests.get(TICKETMASTER_BASE_URL, params=params)
+        response.raise_for_status()
+        data = response.json()
+
+        # Parsear resultados relevantes
+        events = []
+        for event in data.get('_embedded', {}).get('events', []):
+            events.append({
+                'id': event['id'],
+                'name': event['name'],
+                'date': event['dates']['start'].get('localDate'),
+                'time': event['dates']['start'].get('localTime'),
+                'venue': event['_embedded']['venues'][0]['name'],
+                'url': event['url']
+            })
+
+        return jsonify(events)
+
+    except requests.exceptions.RequestException as e:
+        return jsonify({"error": "Error al contactar con Ticketmaster", "details": str(e)}), 500
+    except KeyError:
+        return jsonify([])  # Devuelve lista vacía si no hay eventos
+
+@app.route('/api/events/<event_id>/images', methods=['GET'])
+def get_event_images(event_id):
+    locale = request.args.get('locale', '*')  # valor por defecto
+    params = {
+        'apikey': TICKETMASTER_API_KEY,
+        'locale': locale
+    }
+
+    url = f"https://app.ticketmaster.com/discovery/v2/events/{event_id}/images.json"
+
+    try:
+        response = requests.get(url, params=params)
+        response.raise_for_status()
+        data = response.json()
+
+        images = data.get('images', [])
+
+        # Devuelve lista de URLs e información básica
+        result = [{
+            'url': img.get('url'),
+            'width': img.get('width'),
+            'height': img.get('height'),
+            'ratio': img.get('ratio')
+        } for img in images]
+
+        return jsonify(result)
+
+    except requests.exceptions.RequestException as e:
+        return jsonify({"error": "Error al obtener imágenes del evento", "details": str(e)}), 500
+
+if __name__ == '__main__':
+    app.run(debug=True)
diff --git a/IAapps/Globotickets/copilot.js b/IAapps/Globotickets/copilot.js
new file mode 100644
index 0000000..e69de29
diff --git a/IAapps/Globotickets/requirements.txt b/IAapps/Globotickets/requirements.txt
new file mode 100644
index 0000000..5e021ef
--- /dev/null
+++ b/IAapps/Globotickets/requirements.txt
@@ -0,0 +1,3 @@
+Flask==3.0.3
+requests==2.31.0
+pytest==8.1.1
diff --git a/IAapps/Globotickets/static/logo.png b/IAapps/Globotickets/static/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..2ad39e1a74ad1aa870a5337945cdd68e03f56181
GIT binary patch
diff --git a/IAapps/Globotickets/static/styles.css b/IAapps/Globotickets/static/styles.css
new file mode 100644
index 0000000..8b461a1
--- /dev/null
+++ b/IAapps/Globotickets/static/styles.css
@@ -0,0 +1,67 @@
+body {
+    font-family: 'Segoe UI', sans-serif;
+    margin: 0;
+    background-color: #f9f9f9;
+}
+
+header {
+    background-color: #000;
+    color: #FF1675;
+    display: flex;
+    align-items: center;
+    padding: 10px 20px;
+}
+
+header img {
+    height: 40px;
+    margin-right: 15px;
+}
+
+h1 {
+    font-size: 1.5em;
+    margin: 0;
+}
+
+.container {
+    padding: 20px;
+}
+
+input[type="text"] {
+    padding: 8px;
+    font-size: 16px;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+}
+
+button {
+    background-color: #FF1675;
+    color: white;
+    border: none;
+    padding: 8px 12px;
+    margin-left: 5px;
+    cursor: pointer;
+    border-radius: 4px;
+}
+
+button:hover {
+    background-color: #e31369;
+}
+
+.event {
+    border: 1px solid #ccc;
+    background-color: white;
+    padding: 15px;
+    margin-bottom: 15px;
+    border-radius: 6px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.images {
+    margin-top: 10px;
+}
+
+.images img {
+    max-width: 200px;
+    border-radius: 4px;
+    margin: 5px;
+}
\ No newline at end of file
diff --git a/IAapps/Globotickets/templates/index.html b/IAapps/Globotickets/templates/index.html
new file mode 100644
index 0000000..142f1fd
--- /dev/null
+++ b/IAapps/Globotickets/templates/index.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="es">
+
+<head>
+    <meta charset="UTF-8">
+    <title>Globotickets</title>
+    <link rel="stylesheet" href="static/styles.css">
+</head>
+
+<body>
+
+    <header>
+        <img src="static/logo.png" alt="Globotickets Logo">
+        <h1>Globotickets</h1>
+    </header>
+
+    <div class="container">
+        <input type="text" id="cityInput" placeholder="Introduce una ciudad" />
+        <button onclick="searchEvents()">Buscar eventos</button>
+
+        <div id="eventsContainer"></div>
+    </div>
+
+    <script>
+        async function searchEvents() {
+            const city = document.getElementById("cityInput").value;
+            const container = document.getElementById("eventsContainer");
+            container.innerHTML = "Buscando eventos...";
+
+            const res = await fetch(`http://localhost:5000/api/events?city=${encodeURIComponent(city)}`);
+            const events = await res.json();
+
+            if (events.length === 0) {
+                container.innerHTML = "No se encontraron eventos.";
+                return;
+            }
+
+            container.innerHTML = "";
+            for (const event of events) {
+                const eventDiv = document.createElement("div");
+                eventDiv.className = "event";
+
+                eventDiv.innerHTML = `
+          <strong>${event.name}</strong><br/>
+          Fecha: ${event.date || 'Desconocida'} ${event.time || ''}<br/>
+          Lugar: ${event.venue}<br/>
+          <a href="${event.url}" target="_blank">Comprar entradas</a><br/>
+          <button onclick="loadImages('${event.id}', this)">Ver imágenes</button>
+          <div class="images"></div>
+        `;
+
+                container.appendChild(eventDiv);
+            }
+        }
+
+        async function loadImages(eventId, button) {
+            const imagesDiv = button.nextElementSibling;
+            imagesDiv.innerHTML = "Cargando imágenes...";
+
+            try {
+                const res = await fetch(`http://localhost:5000/api/events/${eventId}/images`);
+                const images = await res.json();
+
+                if (images.length === 0) {
+                    imagesDiv.innerHTML = "No hay imágenes disponibles.";
+                    return;
+                }
+
+                imagesDiv.innerHTML = "";
+                for (const img of images) {
+                    const imgElem = document.createElement("img");
+                    imgElem.src = img.url;
+                    imgElem.alt = "Imagen del evento";
+                    imagesDiv.appendChild(imgElem);
+                }
+            } catch (e) {
+                imagesDiv.innerHTML = "Error al cargar imágenes.";
+            }
+        }
+    </script>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/IAapps/Globotickets/tests/test_app.py b/IAapps/Globotickets/tests/test_app.py
new file mode 100644
index 0000000..64baf22
--- /dev/null
+++ b/IAapps/Globotickets/tests/test_app.py
@@ -0,0 +1,124 @@
+import pytest
+import sys
+import os
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+from app import app
+
+@pytest.fixture
+def client():
+    app.testing = True
+    with app.test_client() as client:
+        yield client
+
+def test_get_events_success(client, monkeypatch):
+    mock_data = {
+        "_embedded": {
+            "events": [
+                {
+                    "name": "Concierto de Prueba",
+                    "id": "123",
+                    "url": "https://ticketmaster.com/event/123",
+                    "dates": {
+                        "start": {
+                            "localDate": "2025-10-01",
+                            "localTime": "20:00"
+                        }
+                    },
+                    "_embedded": {
+                        "venues": [
+                            {
+                                "name": "Auditorio Nacional"
+                            }
+                        ]
+                    }
+                }
+            ]
+        }
+    }
+
+    class MockResponse:
+        status_code = 200
+        
+        def json(self):
+            return mock_data
+        
+        def raise_for_status(self):
+            pass  # Do nothing for a successful response
+
+    def mock_get(*args, **kwargs):
+        return MockResponse()
+
+    # Patch the requests.get method to use the mock_get
+    monkeypatch.setattr("requests.get", mock_get)
+
+    # Call the endpoint
+    response = client.get("/api/events?city=Madrid")
+    
+    # Assert the response data if needed
+    assert response.status_code == 200
+    assert response.json() == mock_data  # You can add more assertions as needed
+
+
+    def mock_get(*args, **kwargs):
+        class MockResponse:
+            status_code = 200
+            def json(self):
+                return mock_data
+        return MockResponse()
+
+    monkeypatch.setattr("requests.get", mock_get)
+
+    response = client.get("/api/events?city=Madrid")
+    assert response.status_code == 200
+    data = response.get_json()
+    assert isinstance(data, list)
+    assert data[0]["name"] == "Concierto de Prueba"
+
+def test_get_events_empty(client, monkeypatch):
+    def mock_get(*args, **kwargs):
+        class MockResponse:
+            status_code = 200
+            def json(self):
+                return {}
+        return MockResponse()
+
+    monkeypatch.setattr("requests.get", mock_get)
+    response = client.get("/api/events?city=Desconocido")
+    assert response.status_code == 200
+    assert response.get_json() == []
+
+def test_get_event_images_success(client, monkeypatch):
+    mock_data = {
+        "images": [
+            {"url": "http://example.com/image1.jpg"},
+            {"url": "http://example.com/image2.jpg"}
+        ]
+    }
+
+    def mock_get(*args, **kwargs):
+        class MockResponse:
+            status_code = 200
+            def json(self):
+                return mock_data
+        return MockResponse()
+
+    monkeypatch.setattr("requests.get", mock_get)
+    response = client.get("/api/events/123/images")
+    assert response.status_code == 200
+    data = response.get_json()
+    assert isinstance(data, list)
+    assert data[0]["url"] == "http://example.com/image1.jpg"
+
+def test_get_event_images_empty(client, monkeypatch):
+    def mock_get(*args, **kwargs):
+        class MockResponse:
+            status_code = 200
+            def json(self):
+                return {"images": []}
+        return MockResponse()
+
+    monkeypatch.setattr("requests.get", mock_get)
+    response = client.get("/api/events/000/images")
+    assert response.status_code == 200
+    assert response.get_json() == []

From 3bc0698ffb48cd0dbcfaa857502041173b5bedd4 Mon Sep 17 00:00:00 2001
From: ilnanu <35577028+ilnanu@users.noreply.github.com>
Date: Thu, 8 May 2025 22:16:06 +0200
Subject: [PATCH 66/66] Update .env hide key

---
 IAapps/Globotickets/.env | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/IAapps/Globotickets/.env b/IAapps/Globotickets/.env
index ed1ce32..9705b73 100644
--- a/IAapps/Globotickets/.env
+++ b/IAapps/Globotickets/.env
@@ -1 +1 @@
-TICKETMASTER_API_KEY="XXX5CRYN8ZjvPzKCUKbMZBweY8iDaXXX"
+TICKETMASTER_API_KEY="MY_TOCKETMASTER_KEY"
