diff --git a/content/sesion10/Readme.md b/content/sesion10/Readme.md
index bf24b28..c91514a 100644
--- a/content/sesion10/Readme.md
+++ b/content/sesion10/Readme.md
@@ -996,13 +996,17 @@ El encargado utiliza la **máquina adecuada** para preparar cada sabor:
- La *máquina de vainilla* solo prepara helados de vainilla.
- La *máquina de chocolate* solo prepara helados de chocolate.
+*(1/5)*
+
+---
+
Todos los helados comparten características en común:
- Se pueden **comer**.
- Tienen un **envase** (cono o vaso).
-El sistema debe permitir al usuario realizar pedidos de helado mediante un **Factory**, y escribir `"salir"` para terminar el proceso.
+Debe permitir al usuario realizar pedidos de helado mediante un **Factory**, y escribir `"salir"` para terminar el proceso.
-*(1/4)*
+*(2/5)*
---
@@ -1021,11 +1025,11 @@ El sistema debe permitir al usuario realizar pedidos de helado mediante un **Fac
Escribe "salir" para terminar.
```
-*(2/4)*
+*(3/5)*
---
-El sistema debe permitir:
+Debe permitir:
Crear helados mediante una **fábrica**
@@ -1033,7 +1037,7 @@ Representar cada helado en el formato especificado.
Registrar varios pedidos hasta que el usuario escriba "salir".
-*(3/4)*
+*(4/5)*
---
@@ -1043,7 +1047,7 @@ Instrucciones:
- Escribe el código en Python implementando el patrón Factory en `ejercicio_01.py`
- Prueba varios pedidos de helado para verificar el correcto funcionamiento
-*(4/4)*
+*(5/5)*
---
@@ -1103,7 +1107,7 @@ Escribe "salir" para terminar.
---
-El sistema debe permitir:
+Debe permitir:
- Crear monstruos mediante una **fábrica**
- Representar cada monstruo en el formato especificado.
diff --git a/content/sesion11/Readme.md b/content/sesion11/Readme.md
new file mode 100644
index 0000000..d91f9c5
--- /dev/null
+++ b/content/sesion11/Readme.md
@@ -0,0 +1,1779 @@
+
+
+
+
+
+
+
+### Sesión 11
+#### Buenas prácticas
+#### Refactorización y organización
+
+---
+
+### Antes de empezar
+
+---
+
+#### Proyecto en VSCode
+
+Abre el proyecto en VSCode
+
+```bash
+code psg-oop-2025
+```
+
+Crea una carpeta con el nombre `sesion11`
+
+```bash
+mkdir sesion11
+cd sesion11
+```
+
+- Los archivos de esta sesión deben estar dentro de esta carpeta
+
+- Al finalizar la sesión, sube los cambios al repositorio en un commit
+
+---
+
+#### Introducción a la refactorización
+
+La refactorización es el proceso de mejorar el código existente sin cambiar su **funcionalidad** externa
+
+---
+
+#### ¿Por qué refactorizar?
+
+- Mejora la **legibilidad** del código
+- Facilita el **mantenimiento** y la evolución del software
+- Reduce la **complejidad** y el riesgo de errores
+
+---
+
+#### ¿Qué implica la refactorización?
+
+- *Reorganizar* el código en módulos y clases
+- *Eliminar* código duplicado
+- *Renombrar* variables y funciones para que sean más descriptivas
+- *Dividir* funciones grandes en funciones más pequeñas y manejables
+
+---
+
+#### Reorganización del código
+
+En python se puede organizar el código en **archivos y carpetas** para mejorar la estructura del proyecto
+
+
+
+---
+
+En lugar de tener un *archivo gigante* con todo el código, podemos dividirlo en **varios archivos** y carpetas
+
+---
+
+#### Buenas prácticas de organización
+
+- Utilizar **nombres descriptivos** para archivos y carpetas
+- **Agrupar** archivos relacionados en carpetas
+- **Mantener una estructura** coherente en todo el proyecto
+
+---
+
+En Python se recomienda seguir la convención [PEP 8](https://peps.python.org/pep-0008/)
+
+| Elemento | Convención | Ejemplo |
+| --------------------- | ---------- | ------------------------------ |
+| Clases | CamelCase | `MiClase` |
+| Funciones y variables | snake_case | `mi_funcion`, `mi_variable` |
+| Constantes | UPPER_CASE | `MI_CONSTANTE` |
+| Archivos y carpetas | snake_case | `mi_archivo.py`, `mi_carpeta/` |
+| Módulos y paquetes | snake_case | `mi_modulo` |
+
+---
+
+#### Ejemplo 01
+
+```markdown
+Juega "Piedra, Papel o Tijera" con un jugador y una computadora
+Ingresa el nombre del jugador y elige piedra, papel o tijera
+La computadora hará su elección al azar
+y se determinará el ganador según las reglas del juego
+Puede jugar varias rondas hasta que el jugador decida salir
+El juego esta compuesto por ambos jugadores
+Cuenta con un menú
+
+1. jugar
+2. salir
+```
+
+Crearemos un archivo llamado `juego.md` con el **análisis y diagrama**
+y el archivo `juego.py` con la **lógica** del juego
+
+---
+
+Análisis
+
+```markdown
+Requisitos:
+- El jugador debe ingresar su nombre
+- El jugador debe elegir entre piedra, papel o tijera
+- La computadora debe elegir al azar entre piedra, papel o tijera
+- El juego debe determinar el ganador según las reglas del juego
+- El juego debe mostrar el resultado de cada partida
+- El juego debe permitir al jugador jugar varias partidas
+- El juego debe tener un menú para jugar o salir
+- El juego esta compuesto por ambos jugadores (Jugador y Computadora)
+
+Objetos:
+- Jugador
+- Computadora
+- Juego
+
+Características:
+- Jugador:
+ - nombre
+- Computadora:
+ - nombre
+- Juego:
+ - jugador
+ - computadora
+
+Acciones:
+- Jugador:
+ - elegir
+- Computadora:
+ - elegir
+- Juego:
+ - jugar
+ - determinar ganador
+```
+
+---
+
+Diagrama de clases en Mermaid
+
+````
+```mermaid
+classDiagram
+ class Jugador {
+ +nombre: string
+ +elegir()
+ }
+ class Computadora {
+ +nombre: string
+ +elegir()
+ }
+ class Juego {
+ +jugador: Jugador
+ +computadora: Computadora
+ +determinar_ganador(eleccion_jugador, eleccion_cpu)
+ +jugar()
+ }
+ Juego *-- Jugador
+ Juego *-- Computadora
+```
+````
+
+---
+
+Diagrama de clases en Mermaid
+
+```mermaid
+%%{init: {"theme": "dark", "look": "handDrawn" }}%%
+classDiagram
+ class Jugador {
+ +nombre: string
+ +elegir()
+ }
+ class Computadora {
+ +nombre: string
+ +elegir()
+ }
+ class Juego {
+ +jugador: Jugador
+ +computadora: Computadora
+ +determinar_ganador(eleccion_jugador, eleccion_cpu)
+ +jugar()
+ }
+ Juego *-- Jugador
+ Juego *-- Computadora
+```
+
+
+---
+
+Código del juego de Piedra, Papel o Tijera
+
+```python [1|2-11|13-18|20-45|47-65]
+import random
+
+class Jugador:
+ def __init__(self, nombre):
+ self.nombre = nombre
+
+ def elegir(self):
+ eleccion = input(f"{self.nombre}, elige piedra, papel o tijera: ").lower()
+ while eleccion not in ["piedra", "papel", "tijera"]:
+ eleccion = input("Opción inválida. Intenta de nuevo: ").lower()
+ return eleccion
+
+class Computadora:
+ def __init__(self):
+ self.nombre = "Computadora"
+
+ def elegir(self):
+ return random.choice(["piedra", "papel", "tijera"])
+
+class Juego:
+ def __init__(self, jugador, computadora):
+ self.jugador = jugador
+ self.computadora = computadora
+
+ def determinar_ganador(self, eleccion_jugador, eleccion_computadora):
+ if eleccion_jugador == eleccion_computadora:
+ return "Empate"
+ elif (
+ (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or
+ (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or
+ (eleccion_jugador == "tijera" and eleccion_computadora == "papel")
+ ):
+ return f"{self.jugador.nombre} gana!"
+ else:
+ return f"{self.computadora.nombre} gana!"
+
+ def jugar(self):
+ eleccion_jugador = self.jugador.elegir()
+ eleccion_computadora = self.computadora.elegir()
+
+ print(f"{self.jugador.nombre} eligió: {eleccion_jugador}")
+ print(f"{self.computadora.nombre} eligió: {eleccion_computadora}")
+
+ resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora)
+ print(resultado)
+
+print("🎮 Bienvenido al juego de Piedra, Papel o Tijera")
+nombre = input("Introduce tu nombre: ")
+jugador = Jugador(nombre)
+computadora = Computadora()
+juego = Juego(jugador, computadora)
+
+while True:
+ print("\n--- Menú ---")
+ print("1. Jugar")
+ print("2. Salir")
+ opcion = input("Elige una opción: ")
+
+ if opcion == "1":
+ juego.jugar()
+ elif opcion == "2":
+ print("¡Gracias por jugar! 👋")
+ break
+ else:
+ print("Opción inválida, intenta de nuevo.")
+```
+
+---
+
+Probamos el juego para asegurarnos que funciona **correctamente**
+
+```bash
+python juego.py
+```
+
+```text
+🎮 Bienvenido al juego de Piedra, Papel o Tijera
+Introduce tu nombre: Jhon
+--- Menú ---
+1. Jugar
+2. Salir
+Elige una opción: 1
+Jhon, elige piedra, papel o tijera: piedra
+Jhon eligió: piedra
+Computadora eligió: tijera
+Jhon gana!
+--- Menú ---
+1. Jugar
+2. Salir
+Elige una opción: 2
+¡Gracias por jugar! 👋
+```
+
+---
+
+**Reorganizaremos** el juego en archivos dentro de una carpeta llamada `juego`
+
+Donde primero separaremos las partes del juego en diferentes archivos
+
+```mermaid
+%%{init: {"theme": "dark", "look": "handDrawn" }}%%
+graph TD;
+ A["📄 juego.py"]
+ B["📕 juego.md"]
+```
+
+
+⬇
+
+```mermaid
+%%{init: {"theme": "dark", "look": "handDrawn" }}%%
+graph TD;
+ A["📁 juego"]
+ B["📄 jugador.py"]
+ C["📄 computadora.py"]
+ D["📄 juego.py"]
+ E["📄 main.py"]
+ F["📕 juego.md"]
+ A --> B
+ A --> C
+ A --> D
+ A --> E
+ A --> F
+```
+
+
+---
+
+#### 1. Crear la carpeta y los archivos
+
+**Crear** la carpeta `juego` y los archivos vacíos dentro
+
+```markdown
+Con `mkdir` creamos la carpeta
+Con `cd` entramos a la carpeta
+Con `touch` creamos los archivos vacíos
+Con `tree` vemos la estructura de carpetas y archivos
+```
+
+```bash
+mkdir juego
+cd juego
+touch jugador.py computadora.py juego.py main.py
+tree
+```
+
+```bash
+.
+├── computadora.py
+├── juego.py
+├── jugador.py
+└── main.py
+
+1 directory, 4 files
+```
+
+---
+
+**Mover** el archivo `juego.md` a la carpeta `juego` para mantener todo organizado
+
+```markdown
+Con `cd ..` volvemos a la carpeta anterior
+Con `mv` movemos el archivo `juego.md` a la carpeta `juego`
+Con `cd juego` entramos a la carpeta `juego`
+Con `tree` vemos la estructura de carpetas y archivos
+```
+
+```bash
+cd ..
+mv juego.md juego
+cd juego
+tree
+```
+
+```bash
+.
+├── computadora.py
+├── juego.md
+├── juego.py
+├── jugador.py
+└── main.py
+
+1 directory, 5 files
+```
+
+
+---
+
+#### 2. Llenar los archivos con el código
+
+Ya con la estructura creada, **llenar** cada archivo con el código correspondiente
+
+El archivo `jugador.py` contendrá la clase `Jugador`
+
+```python
+# jugador.py
+class Jugador:
+ def __init__(self, nombre):
+ self.nombre = nombre
+ def elegir(self):
+ eleccion = input(f"{self.nombre}, elige piedra, papel o tijera: ").lower()
+ while eleccion not in ["piedra", "papel", "tijera"]:
+ eleccion = input("Opción inválida. Intenta de nuevo: ").lower()
+ return eleccion
+```
+
+---
+
+El archivo `computadora.py` contendrá la clase `Computadora`
+
+
+**Importamos** el módulo `random` porque el método `elegir` de la clase `Computadora` utiliza la función `choice` de este módulo para seleccionar una opción al azar
+
+```python
+# computadora.py
+import random
+class Computadora:
+ def __init__(self):
+ self.nombre = "Computadora"
+
+ def elegir(self):
+ return random.choice(["piedra", "papel", "tijera"])
+```
+
+
+---
+
+En el archivo `juego.py` **copiar** la clase `Juego`
+
+```python
+# juego.py
+class Juego:
+ def __init__(self, jugador, computadora):
+ self.jugador = jugador
+ self.computadora = computadora
+
+ def determinar_ganador(self, eleccion_jugador, eleccion_computadora):
+ if eleccion_jugador == eleccion_computadora:
+ return "Empate"
+ elif (
+ (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or
+ (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or
+ (eleccion_jugador == "tijera" and eleccion_computadora == "papel")
+ ):
+ return f"{self.jugador.nombre} gana!"
+ else:
+ return f"{self.computadora.nombre} gana!"
+
+ def jugar(self):
+ eleccion_jugador = self.jugador.elegir()
+ eleccion_computadora = self.computadora.elegir()
+
+ print(f"{self.jugador.nombre} eligió: {eleccion_jugador}")
+ print(f"{self.computadora.nombre} eligió: {eleccion_computadora}")
+
+ resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora)
+ print(resultado)
+```
+
+---
+
+Por último, el archivo `main.py` contendrá la lógica para iniciar e interactuar con el juego
+
+Pero primero el juego necesita tener acceso a las clases `Jugador`, `Computadora` y `Juego` que están definidas en **otros archivos**
+
+Por lo que debemos **importar** los archivos correspondientes
+
+---
+#### Importación de módulos
+
+Python permite **importar** módulos para reutilizar código
+
+Podemos importar un **módulo completo** o solo **partes** específicas de él
+
+Para importar un módulo completo utilizamos la palabra clave `import` seguida del nombre del módulo
+
+```python
+import random
+```
+
+---
+
+Para importar **una parte** específica de un módulo como una función o clase
+
+Utilizamos la palabra clave `from` seguida del **módulo**
+
+Luego la palabra `import` seguido del nombre de la **función o clase**
+
+```python
+from random import choice
+```
+
+---
+
+Es **recomendable** importar solo lo que se necesita para evitar *conflictos de nombres* y mejorar la **legibilidad** del código
+
+> No es necesario importar todo el módulo,
+>
+> si solo se utiliza una parte de él
+
+---
+
+Cada archivo que hemos creado **puede ser importado** en Python
+
+Para importar las clases desde los archivos que hemos creado, utilizamos la sintaxis
+
+`from nombre_archivo import NombreClase`.
+
+---
+
+En el archivo `main.py`, importamos las clases de la siguiente manera
+
+```python
+# main.py
+from jugador import Jugador
+from computadora import Computadora
+from juego import Juego
+```
+
+Esto nos permite utilizar las clases `Jugador`, `Computadora` y `Juego` en el archivo `main.py`
+
+---
+
+El archivo `main.py` quedaría así
+
+```python
+# main.py
+from jugador import Jugador
+from computadora import Computadora
+from juego import Juego
+
+
+print("🎮 Bienvenido al juego de Piedra, Papel o Tijera")
+nombre = input("Introduce tu nombre: ")
+jugador = Jugador(nombre)
+computadora = Computadora()
+juego = Juego(jugador, computadora)
+
+while True:
+ print("\n--- Menú ---")
+ print("1. Jugar")
+ print("2. Salir")
+ opcion = input("Elige una opción: ")
+
+ if opcion == "1":
+ juego.jugar()
+ elif opcion == "2":
+ print("¡Gracias por jugar! 👋")
+ break
+ else:
+ print("Opción inválida, intenta de nuevo.")
+```
+
+---
+
+#### 3. Ejecutar el juego
+
+Después de organizar el código en **archivos separados**, probamos que todo funcione correctamente
+
+donde el archivo `main.py` es el **punto de entrada** del juego
+
+Para ejecutar, debemos estar en el directorio `juego` y ejecutar el archivo `main.py`
+
+
+---
+
+Ejecutamos el juego
+
+```bash
+cd juego # Si no estamos ya en la carpeta juego
+python main.py
+```
+
+```text
+🎮 Bienvenido al juego de Piedra, Papel o Tijera
+Introduce tu nombre: Jhon
+
+--- Menú ---
+1. Jugar
+2. Salir
+Elige una opción: 1
+Jhon, elige piedra, papel o tijera: tijera
+Jhon eligió: tijera
+Computadora eligió: piedra
+Computadora gana!
+
+--- Menú ---
+1. Jugar
+2. Salir
+Elige una opción: 1
+Jhon eligió: papel
+Computadora eligió: papel
+Empate
+
+--- Menú ---
+1. Jugar
+2. Salir
+Elige una opción: 2
+¡Gracias por jugar! 👋
+```
+
+---
+
+Refactorizamos el código del juego de Piedra, Papel o Tijera
+y lo **organizamos** en una estructura de archivos pero de *un sólo nivel*
+
+Podemos seguir mejorando la organización del código creando una **estructura de carpetas**
+para ordenar los archivos según su **funcionalidad**
+
+---
+#### Estructura de carpetas
+
+Organizaremos en carpetas para **agrupar** clases y lógica del juego
+
+```mermaid
+%%{init: {"theme": "dark", "look": "handDrawn" }}%%
+graph LR;
+ A["📁 juego"]
+ B["📁 clases"]
+ C["📁 logica"]
+ D["📄 jugador.py"]
+ E["📄 computadora.py"]
+ F["📄 juego.py"]
+ G["📄 main.py"]
+ H["📕 juego.md"]
+ A --> B
+ A --> C
+ B --> D
+ B --> E
+ C --> F
+ A --> G
+ A --> H
+```
+
+
+---
+
+#### 1. Crear las carpetas y los archivos
+
+**Crear** las carpetas `clases` y `logica` dentro de la carpeta `juego`
+
+```markdown
+Con `cd` entramos a la carpeta `juego`
+Con `mkdir` creamos las carpetas `clases` y `logica`
+```
+
+```bash
+cd juego
+mkdir clases logica
+```
+
+---
+
+**Mover** los archivos correspondientes a cada carpeta
+
+```markdown
+Con `mv` movemos los archivos `jugador.py` y `computadora.py`
+a la carpeta `clases`
+Con `mv` movemos el archivo `juego.py` a la carpeta `logica`
+```
+
+```bash
+mv jugador.py clases
+mv computadora.py clases
+mv juego.py logica
+```
+
+---
+
+**Quedará** la siguiente estructura de carpetas y archivos:
+
+```markdown
+Con `tree` vemos la estructura de carpetas y archivos
+```
+
+```bash
+tree
+```
+
+```bash
+.
+├── clases
+│ ├── computadora.py
+│ └── jugador.py
+├── logica
+│ └── juego.py
+├── juego.md
+└── main.py
+
+
+3 directories, 5 files
+```
+
+---
+
+#### 2. Actualizar las importaciones
+
+Hay que **actualizar** las importaciones en los archivos para reflejar la nueva estructura de carpetas
+
+En `main.py` **importar** las clases desde las nuevas ubicaciones
+
+Como se movió los archivos para importar ahora se utiliza la sintaxis
+
+`from carpeta.archivo import Clase`
+
+```python
+# main.py
+from clases.jugador import Jugador
+from clases.computadora import Computadora
+from logica.juego import Juego
+```
+
+---
+
+#### 3. Ejecutar el juego
+
+Actualizado las importaciones para reflejar la nueva estructura de carpetas
+
+**Ejecutamos** el juego de la misma manera que antes:
+
+```bash
+python main.py
+```
+
+---
+
+Podemos mejorar más la organización del código
+
+creando un archivo `__init__.py` en cada carpeta para convertir las *carpetas* en **paquetes** de Python
+
+---
+
+#### `__init__.py`
+
+El archivo `__init__.py` es un **archivo especial** en Python que indica que una carpeta debe ser tratada como un **paquete**
+
+Esto permite importar módulos desde esa carpeta de manera más sencilla
+
+Son **opcionales** pero es recomendable incluirlos para indicar que la carpeta es un paquete de Python
+
+---
+
+#### 1. Crear los archivos `__init__.py`
+
+**Crear** un archivo `__init__.py` vacío en cada carpeta para indicar que son paquetes
+
+```markdown
+Con `cd` entramos a la carpeta `juego`
+Con `touch` creamos los archivos `__init__.py`
+en las carpetas `clases` y `logica`
+```
+
+```bash
+cd juego
+touch clases/__init__.py logica/__init__.py
+```
+
+---
+
+La **estructura** de carpetas y archivos quedará así:
+
+```bash
+tree
+```
+
+```bash
+.
+├── clases
+│ ├── __init__.py
+│ ├── computadora.py
+│ └── jugador.py
+├── logica
+│ ├── __init__.py
+│ └── juego.py
+├── juego.md
+└── main.py
+
+3 directories, 7 files
+```
+
+---
+
+#### 2. Importar desde los paquetes
+
+En el archivo `__init__.py` añadimos los archivos que podremos importar directamente desde el paquete
+
+En `clases/__init__.py` añadimos:
+
+```python
+# clases/__init__.py
+from .jugador import Jugador
+from .computadora import Computadora
+```
+
+Al utilizar el punto `.` antes del **nombre del archivo**, indicando que importamos el archivo desde la **misma carpeta**
+
+---
+
+En `logica/__init__.py` añadimos:
+
+```python
+# logica/__init__.py
+from .juego import Juego
+```
+
+Asi cada paquete podrá **importar** sus clases correspondientes sin necesidad de especificar el archivo
+
+---
+
+#### 2. Importar desde los paquetes
+
+Para importar desde los paquetes en `main.py`, uilizamos el **nombre del paquete** seguido de la clase
+
+```python
+# main.py
+from clases import Jugador, Computadora
+from logica import Juego
+```
+
+Esto permite **importar directamente** desde los paquetes sin especificar el archivo donde se encuentra
+
+---
+
+#### 3. Ejecutar el juego
+
+**Organizamos** el código en **paquetes** y podemos ejecutar el juego de la misma manera que antes:
+
+```bash
+python main.py
+```
+
+---
+
+Después de **organizar el código**, podemos continuar **refactorizando**
+
+Ahora *eliminaremos* el código duplicado y dividir en funciones más pequeñas
+
+---
+
+#### Eliminación de código duplicado
+
+El código duplicado es un *problema* común en la programación que hace que el código sea más difícil de *mantener y entender*
+
+La eliminación de código duplicado implica **identificar y eliminar** partes del **código que se repiten** innecesariamente
+
+y reemplazarlas por funciones o métodos reutilizables
+
+---
+
+#### ¿Por qué eliminar el código duplicado?
+
+- **Mejora** la legibilidad del código
+- **Facilita** el mantenimiento y la evolución del software
+- **Reduce** el riesgo de errores al modificar el código
+
+---
+
+#### Refactorización del código duplicado
+
+En el Jugador podemos notar que al elegir piedra, papel o tijera,
+**se repite el código** para validar la elección del jugador
+
+```python [4-8]
+# original
+class Jugador:
+ def __init__(self, nombre):
+ self.nombre = nombre
+ def elegir(self):
+ eleccion = input(f"{self.nombre}, elige piedra, papel o tijera: ").lower()
+ while eleccion not in ["piedra", "papel", "tijera"]:
+ eleccion = input("Opción inválida. Intenta de nuevo: ").lower()
+ return eleccion
+```
+
+---
+
+**Refactorizamos** separando las opciones válidas en una constante y
+la validación de la elección en un método separado
+
+```python [1|6-9|11-14]
+OPCIONES = ["piedra", "papel", "tijera"]
+class Jugador:
+ def __init__(self, nombre):
+ self.nombre = nombre
+
+ def validar_eleccion(self, eleccion):
+ while eleccion not in OPCIONES:
+ eleccion = input("Opción inválida. Intenta de nuevo: ").lower()
+ return eleccion
+
+ def elegir(self):
+ opciones = ", ".join(OPCIONES)
+ eleccion = input(f"{self.nombre}, elige {opciones}: ").lower()
+ return self.validar_eleccion(eleccion)
+```
+
+---
+
+**Refactorizar** el `input` del jugador para que sea más claro y reutilizable.
+
+**Definir** un `__str__` para la clase Jugador
+
+```python [6-7|9-10|14,19]
+OPCIONES = ["piedra", "papel", "tijera"]
+class Jugador:
+ def __init__(self, nombre):
+ self.nombre = nombre
+
+ def __str__(self):
+ return self.nombre
+
+ def ingresar(self, mensaje):
+ return input(f"{self}, {mensaje}: ").lower()
+
+ def validar_eleccion(self, eleccion):
+ while eleccion not in OPCIONES:
+ eleccion = self.ingresar("opción inválida. Intenta de nuevo")
+ return eleccion
+
+ def elegir(self):
+ opciones = ", ".join(OPCIONES)
+ eleccion = self.ingresar(f"elige {opciones}")
+ return self.validar_eleccion(eleccion)
+```
+
+---
+
+En la clase Computadora, se puede hacer algo similar
+
+**Importar** sólo lo necesario del módulo `random` y no todo el módulo
+
+**Definir** un `__str__` para la clase Computadora
+
+```python [2,8]
+# original
+import random
+class Computadora:
+ def __init__(self):
+ self.nombre = "Computadora"
+
+ def elegir(self):
+ return random.choice(["piedra", "papel", "tijera"])
+```
+
+---
+
+**Importando** solo lo necesario del módulo `random`
+
+**Refactorizar** la clase Computadora, separando las opciones válidas en una constante y definiendo el método `__str__`
+
+
+```python [1|2,11| 7-8]
+from random import choice
+OPCIONES = ["piedra", "papel", "tijera"]
+class Computadora:
+ def __init__(self):
+ self.nombre = "Computadora"
+
+ def __str__(self):
+ return self.nombre
+
+ def elegir(self):
+ return choice(OPCIONES)
+```
+
+---
+
+**Identificar** el código duplicado en la clase `Juego` como la impresión de las elecciones de los jugadores y sus nombres
+
+
+```python [14-16,22-23]
+class Juego:
+ def __init__(self, jugador, computadora):
+ self.jugador = jugador
+ self.computadora = computadora
+
+ def determinar_ganador(self, eleccion_jugador, eleccion_computadora):
+ if eleccion_jugador == eleccion_computadora:
+ return "Empate"
+ elif (
+ (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or
+ (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or
+ (eleccion_jugador == "tijera" and eleccion_computadora == "papel")
+ ):
+ return f"{self.jugador.nombre} gana!"
+ else:
+ return f"{self.computadora.nombre} gana!"
+
+ def jugar(self):
+ eleccion_jugador = self.jugador.elegir()
+ eleccion_computadora = self.computadora.elegir()
+
+ print(f"{self.jugador.nombre} eligió: {eleccion_jugador}")
+ print(f"{self.computadora.nombre} eligió: {eleccion_computadora}")
+
+ resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora)
+ print(resultado)
+```
+
+---
+
+**Refactorizar** el archivo `juego.py` usando el método `__str__` de las clases `Jugador` y `Computadora`
+
+**Adicionar** un método para mostrar las elecciones
+
+```python [14,15,17,18,23,24]
+class Juego:
+ def __init__(self, jugador, computadora):
+ self.jugador = jugador
+ self.computadora = computadora
+
+ def determinar_ganador(self, eleccion_jugador, eleccion_computadora):
+ if eleccion_jugador == eleccion_computadora:
+ return "Empate"
+ if (
+ (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or
+ (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or
+ (eleccion_jugador == "tijera" and eleccion_computadora == "papel")
+ ):
+ return f"{self.jugador} gana!"
+ return f"{self.computadora} gana!"
+
+ def mostrar_elecciones(self, jugador, eleccion):
+ print(f"{jugador} eligió: {eleccion}")
+
+ def jugar(self):
+ eleccion_jugador = self.jugador.elegir()
+ eleccion_computadora = self.computadora.elegir()
+ self.mostrar_elecciones(self.jugador, eleccion_jugador)
+ self.mostrar_elecciones(self.computadora, eleccion_computadora)
+ resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora)
+ print(resultado)
+```
+
+---
+
+Se **eliminó** algunos duplicados y mejorado la legibilidad del código
+
+**Podemos** seguir mejorando y refactorizando el código y dividiendo
+en funciones más pequeñas y manejables
+
+---
+
+El objetivo es tener un **código limpio y fácil de mantener** y también es importante
+saber cuando detenerse, ya que la *refactorización excesiva* puede llevar a un código innecesariamente complejo
+
+---
+
+Al final **actualizar** el análisis y el diagrama de clases para reflejar los cambios realizados
+
+---
+
+#### Análisis actualizado
+
+```markdown
+Requisitos:
+- El jugador debe ingresar su nombre
+- El jugador debe elegir entre piedra, papel o tijera
+- La computadora debe elegir al azar entre piedra, papel o tijera
+- El juego debe determinar el ganador según las reglas del juego
+- El juego debe mostrar el resultado de cada partida
+- El juego debe permitir al jugador jugar varias partidas
+- El juego debe tener un menú para jugar o salir
+- El juego esta compuesto por ambos jugadores (Jugador y Computadora)
+
+Objetos:
+- Jugador
+- Computadora
+- Juego
+
+Características:
+- Jugador:
+ - nombre
+- Computadora:
+ - nombre
+- Juego:
+ - jugador
+ - computadora
+
+Acciones:
+- Jugador:
+ - elegir
+ - validar eleccion
+ - ingresar
+- Computadora:
+ - elegir
+- Juego:
+ - jugar
+ - determinar ganador
+ - mostrar elecciones
+```
+
+---
+
+#### Diagrama de clases actualizado
+
+````
+```mermaid
+classDiagram
+ class Jugador {
+ +nombre: string
+ +ingresar(mensaje)
+ +validar_eleccion(eleccion)
+ +elegir()
+ }
+ class Computadora {
+ +nombre: string
+ +elegir()
+ }
+ class Juego {
+ +jugador: Jugador
+ +computadora: Computadora
+ +determinar_ganador(eleccion_jugador, eleccion_cpu)
+ +mostrar_elecciones(jugador, eleccion)
+ +jugar()
+ }
+ Juego *-- Jugador
+ Juego *-- Computadora
+```
+````
+
+---
+
+#### Diagrama de clases actualizado
+
+```mermaid
+%%{init: {"theme": "dark", "look": "handDrawn" }}%%
+classDiagram
+ class Jugador {
+ +nombre: string
+ +ingresar(mensaje)
+ +validar_eleccion(eleccion)
+ +elegir()
+ }
+ class Computadora {
+ +nombre: string
+ +elegir()
+ }
+ class Juego {
+ +jugador: Jugador
+ +computadora: Computadora
+ +determinar_ganador(eleccion_jugador, eleccion_cpu)
+ +mostrar_elecciones(jugador, eleccion)
+ +jugar()
+ }
+ Juego *-- Jugador
+ Juego *-- Computadora
+```
+
+
+---
+
+Cambiar el nombre de `juego.md` a `README.md`
+será reconocido como la documentación principal
+
+```markdown
+Con `mv` cambiamos el nombre del archivo `juego.md` a `README.md`
+Con `tree` vemos la estructura de carpetas y archivos
+```
+
+```bash
+mv juego.md README.md
+tree
+```
+```bash
+.
+├── clases
+│ ├── __init__.py
+│ ├── computadora.py
+│ └── jugador.py
+├── logica
+│ ├── __init__.py
+│ └── juego.py
+├── README.md
+└── main.py
+3 directories, 7 files
+```
+
+---
+
+#### Dependencia circular
+
+
+
+Ocurre cuando dos o más módulos *dependen entre sí* directa o indirectamente
+
+---
+
+La dependencia circular es *mala*
+
+Esto puede llevar a problemas como:
+
+- *Dificultad* para entender y mantener el código
+- *Problemas de rendimiento* debido a la carga repetida de módulos
+- *Errores en tiempo de ejecución* si los módulos no se cargan en el orden correcto
+
+---
+
+Para evitar dependencias circulares, es recomendable:
+
+- **Reorganizar el código** para reducir las dependencias
+- **Dividir módulos grandes** en módulos más pequeños y manejables
+
+---
+
+Para definir el **punto de entrada** de un programa, utilizamos la variable especial `__name__` en Python
+
+---
+
+#### La variable `__name__`
+
+En Python, la variable especial `__name__` se utiliza para determinar si un módulo
+se está ejecutando como **programa principal** o si se está **importando** desde otro módulo
+
+---
+
+Cuando un módulo se **ejecuta directamente**, su variable `__name__` se establece en `"__main__"`
+
+Cuando *se importa* desde otro módulo, su variable `__name__` se establece en el nombre del módulo
+
+---
+
+Esto permite que el código dentro de un bloque
+
+`if __name__ == "__main__":`
+
+se ejecute cuando el módulo se **ejecuta directamente**, y NO cuando se *importa* desde otro módulo
+
+Es una buena práctica utilizar este bloque para definir el **punto de entrada** del programa
+
+---
+
+**Encapsular** la lógica principal del juego dentro de una función `main`
+
+**Agregar** al final del archivo `main.py` el bloque `if` para llamar a la función `main`
+
+```python [3|24-25]
+from clases import Jugador, Computadora
+from logica import Juego
+def main():
+ print("🎮 Bienvenido al juego de Piedra, Papel o Tijera")
+ nombre = input("Introduce tu nombre: ")
+ jugador = Jugador(nombre)
+ computadora = Computadora()
+ juego = Juego(jugador, computadora)
+
+ while True:
+ print("\n--- Menú ---")
+ print("1. Jugar")
+ print("2. Salir")
+ opcion = input("Elige una opción: ")
+
+ if opcion == "1":
+ juego.jugar()
+ elif opcion == "2":
+ print("¡Gracias por jugar! 👋")
+ break
+ else:
+ print("Opción inválida, intenta de nuevo.")
+
+if __name__ == "__main__":
+ main()
+```
+
+---
+
+Con esto se completó la **refactorización** y se puede los cambios al repositorio de GitHub
+
+```bash
+git add .
+git commit -m "Refactorización y organización del código"
+git push
+```
+
+---
+
+#### Ejemplo 02
+
+```text
+Al ejemplo anterior aún faltan algunas mejoras que hacer
+
+Elimina la duplicidad del código en OPCIONES almacenando
+en paquete que se llame `constantes` y crea un
+archivo `opciones.py` donde almacenes las opciones
+válidas del juego y reglas
+
+El juego debe funcionar con emojis: 🧱, 📄 y ✂️
+cambiando en las constantes y la lógica del juego
+
+Crea las reglas del juego en un diccionario
+para determinar el ganador
+```
+
+Realizar los cambios 4 minutos
+
+
+
+---
+
+#### 1. Estructura de carpetas y archivos
+
+```bash
+cd juego
+mkdir constantes
+touch constantes/__init__.py constantes/opciones.py
+tree
+```
+
+```bash
+.
+├── clases
+│ ├── __init__.py
+│ ├── computadora.py
+│ └── jugador.py
+├── constantes
+│ ├── __init__.py
+│ └── opciones.py
+├── logica
+│ ├── __init__.py
+│ └── juego.py
+├── README.md
+└── main.py
+4 directories, 8 files
+```
+
+---
+
+#### 2. Opciones válidas
+
+En `constantes/opciones.py` definir las opciones válidas del juego y las reglas
+
+```python
+# constantes/opciones.py
+PIEDRA = "piedra"
+PAPEL = "papel"
+TIJERA = "tijera"
+OPCIONES = [PIEDRA, PAPEL, TIJERA]
+REGLAS = {
+ PIEDRA: TIJERA,
+ PAPEL: PIEDRA,
+ TIJERA: PAPEL
+}
+```
+
+---
+
+En `constantes/__init__.py` importamos las opciones para que estén disponibles al importar el paquete `constantes`.
+
+```python
+# constantes/__init__.py
+from .opciones import OPCIONES, REGLAS
+```
+
+---
+
+#### 3. Actualizar el código
+
+Actualizar el código en los archivos `jugador.py`, `computadora.py` utilizando las constantes definidas
+
+```python
+# clases/jugador.py
+from constantes import OPCIONES
+...
+```
+
+```python
+# clases/computadora.py
+from constantes import OPCIONES
+from random import choice
+...
+```
+
+---
+
+En `juego.py` actualizar las importaciones y el método `determinar_ganador` para utilizar las constantes y reglas definidas
+
+```python [2|4-9]
+# logica/juego.py
+from constantes import REGLAS
+...
+ def determinar_ganador(self, eleccion_jugador, eleccion_computadora):
+ if eleccion_jugador == eleccion_computadora:
+ return "Empate"
+ if REGLAS[eleccion_jugador] == eleccion_computadora:
+ return f"{self.jugador} gana!"
+ return f"{self.computadora} gana!"
+```
+
+---
+
+Actualizar `main.py` para que muestre las opciones válidas al jugador
+
+```python [2,6]
+# main.py
+from constantes import OPCIONES
+from clases import Jugador, Computadora
+from logica import Juego
+def main():
+ print(f"🎮 Bienvenido al juego de {', '.join(OPCIONES)}")
+ nombre = input("Introduce tu nombre: ")
+ jugador = Jugador(nombre)
+ computadora = Computadora()
+ juego = Juego(jugador, computadora)
+...
+```
+
+---
+Reafactorizado el código para utilizar constantes y mejorar la flexibilidad del juego
+
+Ejecutar el juego para verificar que todo funciona correctamente:
+
+```bash
+python main.py
+```
+
+---
+
+Al final *modificar* las constantes para utilizar emojis en lugar de texto
+
+```python
+# constantes/opciones.py
+PIEDRA = "🧱"
+PAPEL = "📄"
+TIJERA = "✂️"
+```
+
+---
+
+Ejecutamos el juego para verificar que todo funciona correctamente:
+
+```bash
+python main.py
+```
+
+```text
+🎮 Bienvenido al juego de 🧱, 📄, ✂️
+Introduce tu nombre: jose
+
+--- Menú ---
+1. Jugar
+2. Salir
+Elige una opción: 1
+jose, elige 🧱, 📄, ✂️: 🧱
+jose eligió: 🧱
+Computadora eligió: 🧱
+Empate
+```
+
+---
+
+Subimos los avances de la sesión al repositorio en **GitHub**
+
+```bash
+git add .
+git commit -m "Sesión 11"
+git push origin main
+```
+
+---
+
+#### Resumen
+
+- La refactorización es el proceso de mejorar el código existente sin cambiar su funcionalidad externa.
+- Refactorizar mejora la legibilidad, facilita el mantenimiento y reduce la complejidad y el riesgo de errores.
+- La refactorización implica reorganizar el código en módulos y clases, eliminar código duplicado, renombrar variables y funciones, y dividir funciones grandes en funciones más pequeñas.
+
+---
+
+- En Python se recomienda organizar el código en archivos y carpetas para mejorar la estructura del proyecto.
+- Es buena práctica utilizar nombres descriptivos para archivos y carpetas, agrupar archivos relacionados y mantener una estructura coherente.
+- En Python se recomienda seguir la convención de nombres PEP 8 para clases, funciones, variables, constantes, archivos y módulos.
+
+---
+
+- La importación de módulos permite reutilizar código y que es recomendable importar solo lo necesario para evitar conflictos y mejorar la legibilidad.
+- Crear archivos `__init__.py` en las carpetas permite que sean tratados como paquetes de Python y facilita la importación de módulos.
+- Eliminar código duplicado mejora la legibilidad y facilita el mantenimiento del software.
+
+---
+
+- La dependencia circular entre módulos es mala porque dificulta el mantenimiento y puede causar errores de ejecución.
+- La variable especial `__name__` en Python permite determinar si un módulo se ejecuta directamente o se importa desde otro módulo.
+- Es buena práctica encapsular la lógica principal en una función y usar el bloque `if __name__ == "__main__":` para definir el punto de entrada del programa.
+
+---
+
+- El uso de constantes y la organización en paquetes mejora la flexibilidad y mantenibilidad del código.
+- La documentación y la estructura clara de carpetas y archivos son esenciales para proyectos bien organizados.
+
+---
+
+#### Retos
+
+Crear una carpeta con el nombre "retos_sesion_11" dentro del proyecto en la raíz, en la cual por cada ejercicio debes crear las siguientes carpetas y archivos:
+
+```bash
+# Estructura de carpetas
+psg-oop-2025/
+ retos_sesion_11/
+ ejercicio_01/
+ README.md
+
+ .py
+ main.py
+ ejercicio_02/
+ README.md
+
+ .py
+ main.py
+```
+
+---
+
+1. **Refactoriza** el siguiente ejemplo en una estructura de carpetas y archivos
+
+Un programa para gestionar una lista de tareas pendientes:
+
+- Permite agregar nuevas tareas
+- Eliminar tareas
+- Marcar tareas como completadas
+- Eliminar solo tareas completadas
+- Eliminar todas las tareas
+
+*(1/6)*
+
+---
+
+El programa debe ser **refactorizado**:
+
+- **Utilizar** convenciones de estilo PEP 8
+- **Mejorar** nombres en clases, variables y métodos
+- **Crear** una estructura ordenada de carpetas y módulos de Python
+- **Incluir** la documentación del análisis y diagrama en un archivo README.md
+
+*(2/6)*
+
+---
+
+
+Separar las clases en **archivos individuales** dentro de la carpeta `ejercicio_01/models`
+
+Implementar una organización de carpetas y archivos como la siguiente:
+
+```bash
+ejercicio_01/
+ ├── models/
+ │ ├── __init__.py
+ │ ├── tarea.py
+ │ └── lista_tareas.py
+ ├── main.py
+ └── README.md
+```
+
+Mantener la misma funcionalidad del programa original
+
+*(3/6)*
+
+---
+
+Diagrama de clases original
+
+```mermaid
+%%{init: {"theme": "dark", "look": "handDrawn" }}%%
+classDiagram
+direction LR
+ class Tarea {
+ +des
+ +comp
+ completada()
+ }
+ class ListaTareas {
+ +t
+ agregar(tarea)
+ ver()
+ completar(tarea)
+ eliminar(tarea)
+ eliminar_comp()
+ del_all()
+ }
+ Tarea --* ListaTareas
+```
+
+
+Debe incluirse el análisis y diagramas en `README.md`
+
+*(4/6)*
+
+---
+
+Código original
+
+```python
+class Tarea:
+ def __init__(self, d):
+ self.des = d
+ self.comp = 0
+ def completada(self):
+ self.comp = 1
+
+class ListaTareas:
+ def __init__(self):
+ self.t = []
+ def agregar(self, t):
+ self.t.append(Tarea(t))
+ def ver(self):
+ if not self.t:
+ print("No hay tareas pendientes.")
+ else:
+ for i, tarea in enumerate(self.t, 1):
+ estado = "✔️" if tarea.comp else " "
+ print(f"{i}. [{estado}] {tarea.des} ")
+ def completar(self, i):
+ if 0 < i <= len(self.t):
+ self.t[i-1].comp = 1
+ print(f"Tarea '{self.t[i-1].des}' marcada como completada.")
+ else:
+ print("Índice de tarea inválido.")
+ def eliminar(self, i):
+ if 0 < i <= len(self.t):
+ tarea_eliminada = self.t.pop(i-1)
+ print(f"Tarea '{tarea_eliminada.des}' eliminada.")
+ else:
+ print("Índice de tarea inválido.")
+ def eliminar_comp(self):
+ self.t = [tarea for tarea in self.t if not tarea.comp]
+ print("Tareas completadas eliminadas.")
+ def del_all(self):
+ self.t.clear()
+ print("Todas las tareas eliminadas.")
+lista = ListaTareas()
+while True:
+ print("\n--- Menú ---")
+ print("1. Agregar tarea")
+ print("2. Ver tareas")
+ print("3. Completar tarea")
+ print("4. Eliminar tarea")
+ print("5. Eliminar tareas completadas")
+ print("6. Eliminar todas las tareas")
+ print("7. Salir")
+ opcion = input("Elige una opción: ")
+
+ if opcion == "1":
+ tarea = input("Ingrese la descripción de la tarea: ")
+ lista.agregar(tarea)
+ elif opcion == "2":
+ lista.ver()
+ elif opcion == "3":
+ lista.ver()
+ i = int(input("Ingrese el índice de la tarea a completar: "))
+ lista.completar(i)
+ elif opcion == "4":
+ lista.ver()
+ i = int(input("Ingrese el índice de la tarea a eliminar: "))
+ lista.eliminar(i)
+ elif opcion == "5":
+ lista.eliminar_comp()
+ elif opcion == "6":
+ lista.del_all()
+ elif opcion == "7":
+ print("¡Gracias por usar la aplicación! 👋")
+ break
+ else:
+ print("Opción inválida, intenta de nuevo.")
+```
+
+Debe refactorizarse y organizarse en archivos y carpetas en la carpeta `ejercicio_01`
+
+*(5/6)*
+
+---
+
+- Realiza el **análisis** y **diagrama de clases** para el programa en `README.md` dentro de la carpeta `ejercicio_01`
+- Refactoriza el código manteniendo la misma funcionalidad
+- Escribe el código en archivos separados y organiza en carpetas en `ejercicio_01`
+- Utiliza **nombres descriptivos** y sigue las buenas prácticas de **PEP 8**
+- Prueba que el programa funcione correctamente
+
+*(6/6)*
+
+---
+
+2. La Biblioteca Municipal iniciará la digitalización de su registro de préstamos
+Actualmente, todos los datos se anotan manualmente en un cuaderno:
+
+Información de **libros**: título, autor e ISBN
+Información de **usuarios**: nombre
+
+El objetivo es digitalizar el flujo de préstamos de libros
+
+*(1/5)*
+
+---
+
+El flujo de préstamos es el **siguiente**:
+
+1. Un usuario ingresa su nombre
+2. Se lista los libros
+3. El usuario elige un libro para prestar
+4. Se registra la lista de prestados con el nombre del usuario y el libro prestado
+5. Puede prestarse más de un libro al mismo usuario
+6. Deben devolver todos los libros a la vez
+7. Se puede ver la lista de libros prestados y los usuarios que los tienen
+8. Para salir se debe ingresar "salir"
+
+*(2/5)*
+
+---
+
+- **Crear** clases para representar `Libro`, `Usuario`
+- **Organizar** el código en una estructura ordenada de carpetas y módulos de Python
+- **Incluir** la documentación del análisis y diagrama en un archivo `README.md`
+- **Mantener** la funcionalidad descrita en el flujo de préstamos
+- **Utilizar** nombres descriptivos para clases, variables y métodos
+
+*(3/5)*
+
+---
+
+La estructura de carpetas y archivos debe ser similar a la siguiente:
+
+```bash
+ejercicio_02/
+ ├── modelos/
+ │ ├── __init__.py
+ │ ├── libro.py
+ │ └── usuario.py
+ ├── logica/
+ │ │── __init__.py
+ │ └── biblioteca.py
+ ├── main.py
+ └── README.md
+```
+
+*(4/5)*
+
+---
+
+- Realizar el análisis y diagrama de clases para el programa en `README.md` dentro de la carpeta `ejercicio_02`
+- Refactorizar el código manteniendo la misma funcionalidad
+- Escribir el código en archivos separados y organizar en carpetas en `ejercicio_02`
+- Utilizar nombres descriptivos y seguir las buenas prácticas de PEP 8
+- Probar que el programa funcione correctamente
+
+*(5/5)*
+
+---
+
+
+
+
+
+
+
+
+
+[ ](https://github.com/python-la-paz/python-study-group-oop/tree/main/content/sesion11)
+
+Repositorio de la Sesión
+
+---
+
+## Bibliografía y Referencias
+
+- [Best Practices for Refactoring Code](https://www.freecodecamp.org/news/best-practices-for-refactoring-code/)
+- [Python Refactoring](https://realpython.com/python-refactoring/)
+- [Guía PEP 8](https://peps.python.org/pep-0008/#class-names)
+- [Mermaid Charts](https://www.mermaidchart.com/play)
+- [Draw.io](https://app.diagrams.net/)
+- [Python 3 Object-oriented Programming, Second Edition, Dusty PhillipsDusty Phillips](https://github.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition)
+- [Objetos en programación](https://ebac.mx/blog/objeto-en-programacion)
+- [Enfoque orientado a objetos](https://1library.co/article/enfoque-orientado-a-objetos-base-te%C3%B3rica.qvld461y)
+- [OOAD](https://www.tutorialspoint.com/object_oriented_analysis_design/ooad_object_oriented_analysis.htm)
diff --git a/content/sesion11/img/dependencia_circular.jpeg b/content/sesion11/img/dependencia_circular.jpeg
new file mode 100644
index 0000000..4f3d683
Binary files /dev/null and b/content/sesion11/img/dependencia_circular.jpeg differ
diff --git a/content/sesion11/img/folders.png b/content/sesion11/img/folders.png
new file mode 100644
index 0000000..d4a16a2
Binary files /dev/null and b/content/sesion11/img/folders.png differ
diff --git a/content/sesion11/index.html b/content/sesion11/index.html
new file mode 100644
index 0000000..92fbc15
--- /dev/null
+++ b/content/sesion11/index.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+ Sesión 11 | Python Study Group | OOP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/content/sesion12/Readme.md b/content/sesion12/Readme.md
new file mode 100644
index 0000000..af674b9
--- /dev/null
+++ b/content/sesion12/Readme.md
@@ -0,0 +1,1565 @@
+
+
+
+
+
+
+
+### Sesión 12
+#### Buenas prácticas
+#### Anotaciones, Documentación y Principios
+
+---
+
+### Antes de empezar
+
+---
+
+#### Proyecto en VSCode
+
+Abre el proyecto en VSCode
+
+```bash
+code psg-oop-2025
+```
+
+Crea una carpeta con el nombre `sesion12`
+
+```bash
+mkdir sesion12
+cd sesion12
+```
+
+- Los archivos de esta sesión deben estar dentro de esta carpeta
+
+- Al finalizar la sesión, sube los cambios al repositorio en un commit
+
+---
+
+**Python** es uno de los lenguajes más populares y entre sus características
+se encuentra la **facilidad** para escribir código
+
+El **tipado dinámico** de Python permite a los desarrolladores escribir código
+rápido y flexible
+
+---
+
+Pero esto también puede llevar a *errores* difíciles de detectar si no se tiene *cuidado*
+
+---
+
+Una **variable** puede **cambiar de tipo** en cualquier momento, lo que puede causar *errores* en tiempo de ejecución
+
+Desde la terminal interactiva de Python
+
+```python
+def sumar(a, b):
+ return a + b
+x = 5 # x es un entero
+y = 10 # y es un entero
+print(sumar(x, y)) # Funciona correctamente
+x = "5" # x ahora es una cadena
+print(sumar(x, y)) # Error en tiempo de ejecución: TypeError
+```
+
+```text
+15
+ERROR!
+Traceback (most recent call last):
+ File "", line 4, in
+ File "", line 2, in sumar
+TypeError: unsupported operand type(s) for +: 'int' and 'str'
+```
+
+---
+
+Este tipo de *errores* puede ser difícil de *detectar y corregir*, especialmente en proyectos grandes
+
+Para lo cual, en **Python** se ha introducido el concepto de **anotaciones de tipo**
+
+---
+
+#### Anotaciones
+
+Python desde su versión **3.6** introdujo el concepto de **anotaciones de tipo**
+que permiten a los desarrolladores especificar el tipo de datos esperado
+para
+
+- Variables
+- Parámetros de funciones
+- Valores de retorno de funciones
+- Atributos de clases
+
+---
+
+Esto **ayuda** a mejorar la **legibilidad** del código y a **detectar errores**
+antes de que se ejecuten
+
+Además, los **editores de código** y herramientas de análisis estático
+pueden utilizar estas anotaciones para proporcionar **sugerencias** y *advertencias*
+
+
+
+---
+
+Crearemos un archivo llamado `anotaciones.py` en la carpeta `sesion12`
+
+```bash
+touch anotaciones.py
+```
+
+---
+
+#### Anotaciones en variables
+
+Las anotaciones de tipo en **variables** se utilizan para indicar el tipo de dato
+que se espera que contenga una variable
+
+```python
+edad: int = 30
+nombre: str = "Jhon"
+altura: float = 1.75
+activo: bool = True
+print(type(edad)) #
+print(type(nombre)) #
+print(type(altura)) #
+print(type(activo)) #
+```
+
+Estas **anotaciones** no afectan el comportamiento del código,
+pero proporcionan **información adicional** para los desarrolladores
+
+---
+
+De esta manera, podemos ver que
+
+- `edad` es un **entero**
+- `nombre` es una **cadena**
+- `altura` es un **flotante**
+- `activo` es un **booleano**
+
+Esto ayuda a los desarrolladores a entender mejor el **propósito** de cada variable
+
+---
+
+También se pueden utilizar anotaciones de tipo para **estructuras** de datos donde
+se incluyen los **tipos de los elementos** que contienen
+
+En el caso de listas se utiliza la sintaxis
+
+`list[tipo]`
+
+- `list` es la estructura de datos
+- `tipo` es el tipo de los elementos que contiene la lista
+
+---
+
+En el caso de diccionarios se utiliza la sintaxis
+
+`dict[clave_tipo, valor_tipo]`
+
+- `dict` es la estructura de datos
+- `clave_tipo` es el tipo de las claves
+- `valor_tipo` es el tipo de los valores
+
+---
+
+En el caso de tuplas se utiliza la sintaxis
+
+`tuple[tipo1, tipo2, ...]`
+
+- `tuple` es la estructura de datos
+- `tipo1`, `tipo2`, `...` Son los tipos de los elementos que contiene la tupla
+
+
+---
+
+Ejemplos de anotaciones en estructuras de datos
+
+```python
+numeros: list[int] = []
+nombres: dict[str, str] = {}
+tuplas: tuple[int, str] = ()
+print(type(numeros)) #
+print(type(nombres)) #
+print(type(tuplas)) #
+```
+
+- `numeros`: lista de enteros
+- `nombres`: diccionario con claves y valores de tipo cadena
+- `tuplas` : tuplas de enteros y cadenas
+
+---
+
+*Sin* anotaciones
+
+
+
+**Con** anotaciones
+
+
+
+---
+
+#### Anotaciones en funciones
+
+Se utilizan para indicar los tipos de datos que se espera que contengan los **parámetros**
+y el tipo de dato que se espera que **retorne**
+
+Los parámetros de una función se anotan utilizando la sintaxis
+
+`parametro: tipo`
+
+y el tipo de retorno se anota utilizando la sintaxis
+
+`-> tipo`
+
+El tipo de retorno se coloca antes de los dos puntos `:`
+
+---
+
+Por ejemplo, si tenemos una función que suma **dos números enteros** y retorna un **entero**
+
+```python
+def sumar(a: int, b: int) -> int:
+ return a + b
+```
+
+- La función `sumar` espera dos parámetros
+- `a` y `b` de tipo entero
+- Retorna un valor de tipo entero
+
+---
+
+*Sin* anotaciones
+
+
+
+**Con** anotaciones
+
+
+
+---
+
+Se pueden utilizar anotaciones de tipo para funciones que aceptan
+parámetros de **estructuras** de datos
+
+Por ejemplo, si tenemos una función que recibe una **lista de enteros** y
+retorna una **lista de enteros** que son los cuadrados de los números
+
+```python
+def cuadrados(numeros: list[int]) -> list[int]:
+ return [n ** 2 for n in numeros]
+numeros = cuadrados([2,4,6])
+print (numeros)
+```
+
+- La función `cuadrados` espera un parámetro
+- `numeros` que es una lista de enteros
+- Retorna una lista de enteros
+
+---
+
+Las anotaciones nos ayudan a saber que la variable `numeros` que almacena el resultado de la función
+es una **lista de enteros**
+
+
+
+---
+
+#### Anotaciones en clases
+
+Las anotaciones de tipo también se pueden utilizar en clases para indicar
+los tipos de datos de los **atributos** de una clase
+
+Por ejemplo, si tenemos una clase `Persona` con atributos de clase `especie`
+y atributos de instancia `nombre` y `edad`
+
+---
+
+Ejemplo de anotaciones en clases
+
+```python
+class Persona:
+ especie: str = "Homo sapiens"
+ def __init__(self, nombre: str, edad: int):
+ self.nombre: str = nombre
+ self.edad: int = edad
+```
+
+- Clase `Persona` tiene un atributos
+- Atributo `especie` es de tipo cadena con valor por defecto `"Homo sapiens"`
+- Atributo `nombre` de tipo cadena
+- Atributo `edad` de tipo entero
+
+---
+
+**Con** anotaciones en atributos y en el constructor al **instanciar** la clase
+muestra el editor los tipos de datos que necesita la clase
+
+
+
+---
+
+Existe otro estilo de anotaciones usado mayormente en versiones anteriores a *Python 3.8*
+
+importando el módulo `typing` y las clases `List`, `Dict`, `Tuple`, etc.
+
+---
+
+Si tenemos una función que recibe una **lista de enteros** y retorna una **lista de enteros**
+
+```python
+from typing import List
+def cuadrados(numeros: List[int]) -> List[int]:
+ return [n ** 2 for n in numeros]
+numeros = cuadrados([2, 4, 6])
+print(numeros)
+```
+
+Actualmente, este estilo es menos común y se recomienda utilizar las anotaciones
+directamente sin importar el módulo `typing`
+
+---
+
+Es importante recordar que las anotaciones de tipo en Python **son opcionales**
+y **NO afectan el comportamiento** del código en tiempo de ejecución
+
+---
+
+Pero no es suficiente para escribir código limpio y mantenible
+
+También es importante **documentar el código**
+para que se pueda entender fácilmente su propósito y funcionamiento
+
+---
+
+#### Documentación
+
+La documentación es una **parte esencial** del desarrollo de software
+ya que ayuda a los desarrolladores a entender el **propósito y funcionamiento** del código
+
+En las **clases** podemos utilizar cadenas de documentación *docstrings*
+para documentar el propósito de la clase, sus atributos y métodos.
+
+---
+
+Se coloca entre comillas triples `"""` justo después de la definición de la clase
+o del método
+
+Los **editores de código** y herramientas de análisis estático utilizan
+estas cadenas de documentación para mostrar **sugerencias** y *advertencias* sobre el uso del código
+
+---
+Crearemos un archivo llamado `documentacion.py` en la carpeta `sesion12`
+
+```bash
+touch documentacion.py
+```
+
+---
+
+#### Documentación en clases
+
+Por ejemplo, Una clase `Persona` con un atributo `nombre` y un método `saludar`
+
+*Sin* documentación
+
+```python
+class Persona:
+ def __init__(self, nombre: str):
+ self.nombre: str = nombre
+ def saludar(self):
+ print(f"Hola, mi nombre es {self.nombre}")
+jhon = Persona("Jhon")
+jhon.saludar()
+```
+
+
+
+
+
+---
+
+**Con** documentación
+
+```python
+class Persona:
+ """Clase que representa a una persona."""
+ def __init__(self, nombre: str):
+ """Inicializa una nueva instancia de la clase Persona.
+
+ Args:
+ nombre (str): El nombre de la persona.
+ """
+ self.nombre: str = nombre
+
+ def saludar(self):
+ """Imprime un saludo con el nombre de la persona."""
+ print(f"Hola, mi nombre es {self.nombre}")
+jhon = Persona("Jhon")
+jhon.saludar()
+```
+
+---
+
+
+
+
+
+---
+
+Existen diferentes convenciones para escribir **documentación en Python**
+la [PEP 257](https://peps.python.org/pep-0257/) establece la base para las cadenas de documentación
+
+Existen diferentes estilos de documentación
+
+- Google
+- NumPy
+- reStructuredText (reST)
+
+Cada uno tiene su propia **sintaxis y convenciones**
+
+---
+
+Usos **típicos** de cada estilo
+
+| Estilo | Uso típico |
+| ----------------------- | -------------------------------------------- |
+| Google | Documentación de proyectos y bibliotecas |
+| NumPy | Documentación científica y técnica |
+| reStructuredText (reST) | Documentación profesional, grandes proyectos |
+
+
+---
+
+**Comparación** entre estilos
+
+| Estilo | Ventajas | Desventajas |
+| ------ | --------------------------- | ------------------------------------------------ |
+| Google | Fácil de leer y escribir | Menos detallado para proyectos complejos |
+| NumPy | Estructurado y detallado | Puede ser más difícil de leer para principiantes |
+| reST | Muy detallado y profesional | Más complejo de escribir menos amigable |
+
+---
+
+**Legibilidad** de cada estilo
+
+| Estilo | Legibilidad |
+| ------ | --------------------------------------------------------------------------------- |
+| Google | Claro y conciso, fácil para humanos |
+| NumPy | Muy estructurado, detallado, generado por herramientas |
+| reST | Muy detallado, profesional, orientado a herramientas de documentación como Sphinx |
+
+
+---
+
+#### Estilo Google
+
+Se utiliza la siguiente sintaxis para documentar
+
+```python [2-17]
+def funcion(parametro1: tipo, parametro2: tipo) -> tipo:
+ """Descripción breve de la función
+
+ Args:
+ parametro1 (tipo): Descripción del primer parámetro
+ parametro2 (tipo): Descripción del segundo parámetro
+
+ Returns:
+ tipo: Descripción del valor de retorno
+
+ Raises:
+ TipoDeError: Condiciones que causan este error
+
+ Examples:
+ >>> funcion(valor1, valor2)
+ resultado_esperado
+ """
+ pass
+```
+
+
+---
+
+Crea el archivo `estilo_google.py` en la carpeta `sesion12`
+
+Documenta la siguiente función que devuelve la secuencia de Fibonacci hasta el n-ésimo número
+usando el estilo Google
+
+```python
+def fibonacci(n: int) -> str:
+ if n < 0:
+ raise ValueError("El índice no puede ser negativo")
+ fib = [0, 1]
+ for i in range(2, n):
+ fib.append(fib[i - 1] + fib[i - 2])
+ return ', '.join(str(x) for x in fib[:n])
+print (fibonacci(5))
+```
+
+---
+
+Documentación estilo Google
+
+```python [2|4-5|7-8|10-11|13-16]
+def fibonacci(n: int) -> str:
+ """Devuelve la secuencia de Fibonacci hasta el n-ésimo número.
+
+ Args:
+ n (int): El índice del número de Fibonacci a calcular.
+
+ Returns:
+ str: La secuencia de Fibonacci hasta el n-ésimo número.
+
+ Raises:
+ ValueError: Si n es negativo.
+
+ Examples:
+ >>> fibonacci(5)
+ '0, 1, 1, 2, 3'
+ """
+ if n < 0:
+ raise ValueError("El índice no puede ser negativo")
+ fib = [0, 1]
+ for i in range(2, n):
+ fib.append(fib[i - 1] + fib[i - 2])
+ return ', '.join(str(x) for x in fib[:n])
+print (fibonacci(5))
+```
+
+---
+El editor de código **interpreta la documentación** y proporcionar sugerencias de autocompletado
+
+
+
+
+---
+
+#### Estilo NumPy
+
+Se utiliza la siguiente sintaxis para documentar
+
+```python
+def funcion(parametro1: tipo, parametro2: tipo) -> tipo:
+ """
+ Descripción breve de la función.
+
+ Parameters
+ ----------
+ parametro1 : tipo
+ Descripción del primer parámetro.
+ parametro2 : tipo
+ Descripción del segundo parámetro.
+
+ Returns
+ -------
+ tipo
+ Descripción del valor de retorno.
+
+ Raises
+ ------
+ TipoDeError
+ Condiciones que causan este error.
+
+ Examples
+ --------
+ >>> funcion(valor1, valor2)
+ resultado_esperado
+ """
+ pass
+```
+
+---
+
+Crea el archivo `estilo_numpy.py` en la carpeta `sesion12`
+
+Documenta la siguiente función que devuelve la secuencia de Fibonacci hasta el n-ésimo número
+
+```python
+def fibonacci(n: int) -> str:
+ if n < 0:
+ raise ValueError("El índice no puede ser negativo")
+ fib = [0, 1]
+ for i in range(2, n):
+ fib.append(fib[i - 1] + fib[i - 2])
+ return ', '.join(str(x) for x in fib[:n])
+print (fibonacci(5))
+```
+
+---
+
+Documentación estilo NumPy
+
+```python [3|5-8|10-13|15-18|20-23]
+def fibonacci(n: int) -> str:
+ """
+ Devuelve la secuencia de Fibonacci hasta el n-ésimo número.
+
+ Parameters
+ ----------
+ n : int
+ El índice del número de Fibonacci a calcular.
+
+ Returns
+ -------
+ str
+ La secuencia de Fibonacci hasta el n-ésimo número.
+
+ Raises
+ ------
+ ValueError
+ Si n es negativo.
+
+ Examples
+ --------
+ >>> fibonacci(5)
+ '0, 1, 1, 2, 3'
+ """
+ if n < 0:
+ raise ValueError("El índice no puede ser negativo")
+ fib = [0, 1]
+ for i in range(2, n):
+ fib.append(fib[i - 1] + fib[i - 2])
+ return ', '.join(str(x) for x in fib[:n])
+
+print(fibonacci(5))
+```
+
+---
+El editor de código **interpretar la documentación** y proporcionar sugerencias de autocompletado
+
+
+
+---
+
+#### Estilo reStructuredText (reST)
+
+Se utiliza la siguiente sintaxis para documentar
+
+```python
+def funcion(parametro1: tipo, parametro2: tipo) -> tipo:
+ """
+ Descripción breve de la función.
+
+ :param parametro1: Descripción del primer parámetro.
+ :type parametro1: tipo
+ :param parametro2: Descripción del segundo parámetro.
+ :type parametro2: tipo
+ :return: Descripción del valor de retorno.
+ :rtype: tipo
+ :raises TipoDeError: Condiciones que causan este error.
+ :example:
+ >>> funcion(valor1, valor2)
+ resultado_esperado
+ """
+ pass
+```
+
+---
+
+Crea el archivo `estilo_rest.py` en la carpeta `sesion12`
+
+Documenta la siguiente función que devuelve la secuencia de Fibonacci hasta el n-ésimo número
+
+```python
+def fibonacci(n: int) -> str:
+ if n < 0:
+ raise ValueError("El índice no puede ser negativo")
+ fib = [0, 1]
+ for i in range(2, n):
+ fib.append(fib[i - 1] + fib[i - 2])
+ return ', '.join(str(x) for x in fib[:n])
+print (fibonacci(5))
+```
+
+---
+
+Documentación estilo reST
+
+```python [3|5-6|7-8|9|10-12]
+def fibonacci(n: int) -> str:
+ """
+ Devuelve la secuencia de Fibonacci hasta el n-ésimo número.
+
+ :param n: El índice del número de Fibonacci a calcular.
+ :type n: int
+ :return: La secuencia de Fibonacci hasta el n-ésimo número.
+ :rtype: str
+ :raises ValueError: Si n es negativo.
+ :example:
+ >>> fibonacci(5)
+ '0, 1, 1, 2, 3'
+ """
+ if n < 0:
+ raise ValueError("El índice no puede ser negativo")
+ fib = [0, 1]
+ for i in range(2, n):
+ fib.append(fib[i - 1] + fib[i - 2])
+ return ', '.join(str(x) for x in fib[:n])
+
+print(fibonacci(5))
+```
+
+---
+
+El editor de código **interpretar la documentación** y proporcionar sugerencias de autocompletado
+
+
+
+---
+
+Todos los estilos son **válidos** y tienen sus propias ventajas y desventajas
+
+
+
+
+
+---
+
+**Elige** un estilo de documentación y sé **consistente** en su uso a lo largo de tu código
+
+Esto **facilitará** la lectura y comprensión del código para ti y para otros desarrolladores
+
+---
+
+Añadir anotaciones de tipo y documentación a tu código es una **buena práctica**
+
+Existen otras buenas prácticas como los **principios de desarrollo**
+
+que ayudan a escribir código elegante y mantenible
+
+---
+
+#### Principios de desarrollo
+
+Los principios de desarrollo son un conjunto de **buenas prácticas**
+que ayudan a los desarrolladores a escribir código limpio, mantenible y escalable
+
+Al igual que los *patrones de diseño*, los principios de desarrollo
+proporcionan soluciones a problemas comunes en el desarrollo de software
+
+---
+
+Algunos de los principios más importantes son:
+
+- **KISS** (Keep It Simple, Stpid)
+- **DRY** (Don't Repeat Yourself)
+- **YAGNI** (You Aren't Gonna Need It)
+
+---
+
+#### KISS (Keep It Simple, St*pid)
+
+El principio KISS enfatiza la importancia de mantener **el código simple** y fácil de entender
+
+Un código simple es **más fácil** de leer, mantener y depurar
+
+---
+
+Se resume en
+
+- **Simplicidad**: Las soluciones deben ser lo más simples posible evitando características innecesarias
+- **Claridad**: El código debe ser claro y fácil de entender
+
+---
+
+- **Minimalismo**: Evitar la complejidad innecesaria y centrarse en lo esencial
+- **Eficiencia**: Las soluciones simples son más eficientes
+- *Evite la sobreingeniería*: No agregue características o funcionalidades innecesarias
+
+
+---
+
+Una forma de aplicarlo es
+
+- **Dividir** problemas complejos en partes más pequeñas y manejables
+- *Evitar* la sobreingeniería y **centrarse** en lo esencial
+- **Utilizar** nombres claros y descriptivos para variables, funciones y clases
+
+---
+
+#### Ejemplo KISS
+
+Crear el archivo `kiss.py` en la carpeta `sesion12`
+
+```text
+La primera calculadora de un estudiante sólo sabe
+sumar dos números enteros, tiene una clase SumaDeNumeros
+con el método operar que realiza la suma.
+Sin embargo, no es muy claro, ni simple.
+Simplifica el código aplicando el principio KISS
+```
+
+```python [1-11|13-18]
+class SumaDeNumeros:
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+
+ def operar(self):
+ return self.a + self.b
+
+suma = SumaDeNumeros(5, 7)
+print(suma.operar())
+```
+
+---
+
+Solución simple
+
+```python
+class Calculadora:
+ def sumar(self, a, b):
+ return a + b
+calc = Calculadora()
+print(calc.sumar(5, 7))
+```
+
+---
+
+Solución con anotaciones y documentación
+
+```python
+class Calculadora:
+ """Clase que representa una calculadora simple."""
+
+ def sumar(self, a: int, b: int) -> int:
+ """Suma dos números enteros.
+
+ Args:
+ a (int): El primer número.
+ b (int): El segundo número.
+
+ Returns:
+ int: La suma de los dos números.
+ """
+ return a + b
+calc = Calculadora()
+print(calc.sumar(5, 7))
+```
+
+---
+
+#### Ejercicio para ti (01)
+
+```text
+Una calculadora de areas que pueda calcular
+el área de rectángulos y cuadrados.
+Donde el código tiene mucha complejidad
+Simplifica el código aplicando el principio KISS
+Después de simplificar, agrega anotaciones y documentación
+```
+
+Crea el archivo `areas.py` en la carpeta `sesion12`
+
+---
+
+Realizar los cambios 4 minutos
+
+```python
+class Area:
+ def __init__(self, tipo, lado1, lado2=None):
+ self.tipo = tipo
+ self.lado1 = lado1
+ self.lado2 = lado2
+ def calcular(self):
+ if self.tipo == "rectangulo":
+ return self.lado1 * self.lado2
+ elif self.tipo == "cuadrado":
+ return self.lado1 ** 2
+area_rectangulo = Area("rectangulo", 5, 10)
+print(area_rectangulo.calcular())
+area_cuadrado = Area("cuadrado", 5)
+print(area_cuadrado.calcular())
+```
+
+
+
+---
+
+Solución simple
+
+```python
+class Calculadora:
+ def area(self, lado1, lado2):
+ return lado1 * lado2
+calculadora = Calculadora()
+print(calculadora.area(5, 10)) # Área del rectángulo
+print(calculadora.area(5, 5)) # Área del cuadrado
+```
+
+```bash
+python areas.py
+```
+
+```text
+50
+25
+```
+
+---
+
+Solución con anotaciones y documentación
+
+```python
+class Calculadora:
+ """Clase que representa una calculadora simple."""
+
+ def area(self, lado1: int, lado2: int) -> int:
+ """Calcula el área de un rectángulo o cuadrado.
+
+ Args:
+ lado1 (int): El primer lado.
+ lado2 (int): El segundo lado.
+
+ Returns:
+ int: El área calculada.
+ """
+ return lado1 * lado2
+calculadora = Calculadora()
+print(calculadora.area(5, 10)) # Área del rectángulo
+print(calculadora.area(5, 5)) # Área del cuadrado
+```
+
+
+---
+
+#### DRY (Don't Repeat Yourself)
+
+El principio DRY enfatiza la importancia de **evitar la duplicación** de código
+
+El código *duplicado* puede llevar a errores y dificultades en el mantenimiento
+
+---
+
+Se resume en
+
+- **Unicidad**: Cada pieza de conocimiento o lógica debe tener una única representación en el código
+- **Mantenimiento**: El código duplicado es más difícil de mantener y actualizar
+- **Reutilización**: Fomenta la reutilización de código a través de funciones, clases y módulos
+
+---
+
+- **Reducción de errores**: Menos duplicación significa menos oportunidades para errores
+- **Eficiencia**: Un código sin duplicación es más eficiente
+- **Claridad**: Un código sin duplicación es más claro y fácil de entender
+
+
+---
+
+Una forma de aplicarlo es
+
+- **Identificar** y eliminar código duplicado
+- **Utilizar** funciones, clases y módulos para reutilizar código
+- **Mantener** el código DRY durante el desarrollo y la refactorización
+
+---
+
+#### Ejemplo DRY
+
+Crear el archivo `dry.py` en la carpeta `sesion12`
+
+```text
+Calcular el área de un rectángulo y un cuadrado,
+pero el código tiene duplicación
+Utiliza el principio DRY para eliminar la duplicación
+Unifica el código para que el cálculo del área
+sea realizado por una sola función
+```
+
+
+```python [1-7|9-13]
+# Código duplicado
+def area_rectangulo(lado1, lado2):
+ return lado1 * lado2
+def area_cuadrado(lado):
+ return lado * lado
+print(area_rectangulo(5, 10))
+print(area_cuadrado(5))
+```
+
+---
+
+Código sin duplicación
+
+```python
+
+# Código sin duplicación
+def area(lado1, lado2):
+ return lado1 * lado2
+print(area(5, 10)) # Área del rectángulo
+print(area(5, 5)) # Área del cuadrado
+```
+
+---
+
+Código sin duplicación con anotaciones y documentación
+
+```python
+def area(lado1: int, lado2: int) -> int:
+ """Calcula el área de un rectángulo o cuadrado.
+
+ Args:
+ lado1 (int): El primer lado.
+ lado2 (int): El segundo lado.
+
+ Returns:
+ int: El área calculada.
+ """
+ return lado1 * lado2
+print(area(5, 10)) # Área del rectángulo
+print(area(5, 5)) # Área del cuadrado
+```
+
+---
+
+#### Ejercicio para ti (02)
+
+```text
+Una tienda en línea permite pagar con diferentes métodos de pago:
+- Tarjeta
+- Transferencia
+- QR
+Y por cada canal aplica un impuesto diferente
+- Tarjeta 5%
+- Transferencia 2%
+- QR 1%
+Simplifica el código aplicando el principio DRY
+```
+
+Crea archivo `tienda.py` en la carpeta `sesion12`
+
+---
+
+Realizar los cambios 4 minutos
+
+```python
+class Tienda:
+ def impuesto_tarjeta(self, monto):
+ return monto * 0.05
+ def impuesto_transferencia(self, monto):
+ return monto * 0.02
+ def impuesto_qr(self, monto):
+ return monto * 0.01
+ def pagar_tarjeta(self, monto):
+ impuesto = self.impuesto_tarjeta(monto)
+ total = monto + impuesto
+ print(f"Pago con tarjeta: Monto={monto}, Impuesto={impuesto}, Total={total}")
+ def pagar_transferencia(self, monto):
+ impuesto = self.impuesto_transferencia(monto)
+ total = monto + impuesto
+ print(f"Pago con transferencia: Monto={monto}, Impuesto={impuesto}, Total={total}")
+ def pagar_qr(self, monto):
+ impuesto = self.impuesto_qr(monto)
+ total = monto + impuesto
+ print(f"Pago con QR: Monto={monto}, Impuesto={impuesto}, Total={total}")
+tienda = Tienda()
+tienda.pagar_tarjeta(100)
+tienda.pagar_transferencia(100)
+tienda.pagar_qr(100)
+```
+
+
+
+---
+
+Solución simple
+
+```python
+class Tienda:
+ def calcular_impuesto(self, monto, tasa):
+ return monto * tasa
+ def pagar(self, monto, metodo):
+ tasas = {
+ "tarjeta": 0.05,
+ "transferencia": 0.02,
+ "qr": 0.01
+ }
+ tasa = tasas.get(metodo, 0)
+ impuesto = self.calcular_impuesto(monto, tasa)
+ total = monto + impuesto
+ print(f"Pago con {metodo}: Monto={monto}, Impuesto={impuesto}, Total={total}")
+tienda = Tienda()
+tienda.pagar(100, "tarjeta")
+tienda.pagar(100, "transferencia")
+tienda.pagar(100, "qr")
+```
+
+```bash
+python tienda.py
+```
+
+```text
+Pago con tarjeta: Monto=100, Impuesto=5.0, Total=105.0
+Pago con transferencia: Monto=100, Impuesto=2.0, Total=102.0
+Pago con qr: Monto=100, Impuesto=1.0, Total=101.0
+```
+
+---
+
+Solución con anotaciones y documentación
+
+```python
+class Tienda:
+ """Clase que representa una tienda en línea."""
+
+ def calcular_impuesto(self, monto: float, tasa: float) -> float:
+ """Calcula el impuesto basado en el monto y la tasa.
+
+ Args:
+ monto (float): El monto sobre el cual se calcula el impuesto.
+ tasa (float): La tasa de impuesto.
+
+ Returns:
+ float: El monto del impuesto calculado.
+ """
+ return monto * tasa
+ def pagar(self, monto: float, metodo: str) -> None:
+ """Procesa el pago utilizando el método especificado.
+ Args:
+ monto (float): El monto a pagar.
+ metodo (str): El método de pago (tarjeta, transferencia, qr).
+ """
+ tasas = {
+ "tarjeta": 0.05,
+ "transferencia": 0.02,
+ "qr": 0.01
+ }
+tienda = Tienda()
+tienda.pagar(100, "tarjeta")
+tienda.pagar(100, "transferencia")
+tienda.pagar(100, "qr")
+```
+
+---
+
+#### YAGNI (You Aren't Gonna Need It)
+
+El principio YAGNI enfatiza la importancia de *NO agregar* funcionalidades
+o características que *no son necesarias* en el momento
+
+Ayuda a evitar la *sobreingeniería* y a mantener el código simple y enfocado
+
+---
+
+Se resume en
+
+- **Necesidad**: No agregue funcionalidades o características que no son necesarias
+- **Simplicidad**: Mantenga el código simple y enfocado en el propósito actual
+- **Eficiencia**: Evite la complejidad innecesaria que puede afectar el rendimiento
+
+---
+
+- **Mantenimiento**: Un código sin características innecesarias es más fácil de mantener
+- **Claridad**: Un código sin características innecesarias es más fácil de entender
+- **Flexibilidad**: Un código sin características innecesarias es más flexible y adaptable a cambios futuros
+
+---
+
+Una forma de aplicarlo es
+
+- **Enfocarse** en los **requisitos actuales** y NO en posibles requisitos futuros
+- *Evitar* la sobreingeniería y centrarse en lo esencial
+- Revisar y *eliminar características innecesarias* durante la refactorización
+
+---
+
+- **Priorizar** la simplicidad y claridad del código
+- **Mantener** el código enfocado en el propósito actual
+- **Evitar** la tentación de agregar características
+
+*"por si acaso"*
+
+---
+#### Ejemplo YAGNI
+
+Crear el archivo `yagni.py` en la carpeta `sesion12`
+
+```text
+Un estudiante esta aprendiendo a sumar dos números, y le dieron
+una calculadora para para que pueda practicar la suma, pero
+tiene muchas funciones que no va a usar aún
+Simplifica el código aplicando el principio YAGNI
+Deja solo la funcionalidad necesaria para sumar dos números
+agrega anotaciones y documentación
+```
+
+```python [1-14]
+class Calculadora:
+ def sumar(self, a, b):
+ return a + b
+ def restar(self, a, b):
+ return a - b
+ def multiplicar(self, a, b):
+ return a * b
+ def dividir(self, a, b):
+ return a / b
+calc = Calculadora()
+print(calc.sumar(5, 7))
+```
+
+---
+
+Solución simple
+
+```python
+class Calculadora:
+ def sumar(self, a, b):
+ return a + b
+calc = Calculadora()
+print(calc.sumar(5, 7))
+```
+
+---
+Solución con anotaciones y documentación
+
+```python
+class Calculadora:
+ """Clase que representa una calculadora simple."""
+ def sumar(self, a: int, b: int) -> int:
+ """Suma dos números enteros.
+
+ Args:
+ a (int): El primer número.
+ b (int): El segundo número.
+
+ Returns:
+ int: La suma de los dos números.
+ """
+ return a + b
+calc = Calculadora()
+print(calc.sumar(5, 7))
+```
+
+---
+#### Ejercicio para ti (03)
+
+```text
+Un profesor necesita una forma sencilla de llevar
+el registro de sus estudiantes, con sus nombres y
+las notas de sus exámenes. Quiere poder calcular
+fácilmente el promedio de cada estudiante y saber
+de si ha aprobado o no el curso,
+con nota mayor a 51, además de tener
+un resumen claro de sus resultados para mostrárselos
+Simplifica el código aplicando el principio YAGNI
+Agrega anotaciones y documentación
+```
+
+Crea archivo `estudiantes.py` en la carpeta `sesion12`
+
+---
+Realizar los cambios 4 minutos
+
+```python
+class Estudiante:
+ def __init__(self, nombre, notas):
+ self.nombre = nombre
+ self.notas = notas
+
+ self.promedio_cache = None
+
+ def calcular_promedio(self):
+ total = 0
+ for n in self.notas:
+ total += n
+ return total / len(self.notas)
+
+ def mostrar_promedio(self):
+ total = 0
+ for n in self.notas:
+ total += n
+ return f"Promedio de {self.nombre}: {total / len(self.notas)}"
+
+ def aprobo(self):
+ promedio = self.calcular_promedio()
+ return promedio > 50 or promedio == 51
+
+ def mejor_nota(self):
+ return max(self.notas)
+
+ def peor_nota(self):
+ return min(self.notas)
+ def resumen(self):
+ return f"Estudiante: {self.nombre}, Notas: {self.notas}, Promedio: {self.calcular_promedio()}, Aprobado: {self.aprobo()}"
+
+estudiante1 = Estudiante("Ana", [85, 90, 78])
+print(estudiante1.mostrar_promedio())
+print(f"Aprobó: {estudiante1.aprobo()}")
+print(estudiante1.resumen())
+```
+
+
+
+---
+Solución simple
+
+```python
+class Estudiante:
+ def __init__(self, nombre, notas):
+ self.nombre = nombre
+ self.notas = notas
+ def calcular_promedio(self):
+ return sum(self.notas) / len(self.notas)
+ def aprobo(self):
+ return self.calcular_promedio() >= 51
+ def resumen(self):
+ mensaje = f"Estudiante: {self.nombre}"
+ mensaje += f", Notas: {self.notas}"
+ mensaje += f", Promedio: {self.calcular_promedio()}"
+ mensaje += f", Aprobado: {self.aprobo()}"
+ return mensaje
+estudiante1 = Estudiante("Ana", [85, 90, 78])
+print(estudiante1.resumen())
+```
+
+```bash
+python estudiantes.py
+```
+```text
+Estudiante: Ana, Notas: [85, 90, 78], Promedio: 84.33333333333333, Aprobado: True
+```
+
+---
+
+Solución con anotaciones y documentación
+
+```python
+class Estudiante:
+ """Clase que representa a un estudiante."""
+ def __init__(self, nombre: str, notas: list[int]) -> None:
+ """Inicializa una nueva instancia de la clase Estudiante.
+
+ Args:
+ nombre (str): El nombre del estudiante.
+ notas (list[int]): Las notas del estudiante.
+ """
+ self.nombre = nombre
+ self.notas = notas
+ def calcular_promedio(self) -> float:
+ """Calcula el promedio de las notas del estudiante.
+ Returns:
+ float: El promedio de las notas.
+ """
+ return sum(self.notas) / len(self.notas)
+ def aprobo(self) -> bool:
+ """Determina si el estudiante aprobó el curso.
+ Returns:
+ bool: True si aprobó, False en caso contrario.
+ """
+ return self.calcular_promedio() >= 51
+ def resumen(self) -> str:
+ """Genera un resumen del estudiante.
+ Returns:
+ str: Un resumen con el nombre, notas, promedio y estado de aprobación.
+ """
+ mensaje = f"Estudiante: {self.nombre}"
+ mensaje += f", Notas: {self.notas}"
+ mensaje += f", Promedio: {self.calcular_promedio()}"
+ mensaje += f", Aprobado: {self.aprobo()}"
+ return mensaje
+estudiante1 = Estudiante("Ana", [85, 90, 78])
+print(estudiante1.resumen())
+```
+
+---
+
+#### Resumen
+
+- Python es un lenguaje popular por su facilidad para escribir código y su tipado dinámico, lo que permite flexibilidad pero puede causar errores difíciles de detectar.
+- Las anotaciones de tipo permiten especificar el tipo de datos esperado para variables, parámetros de funciones, valores de retorno y atributos de clases, mejorando la legibilidad y ayudando a detectar errores antes de la ejecución.
+
+---
+
+- Las anotaciones de tipo en Python son opcionales y no afectan el comportamiento del código en tiempo de ejecución, pero son útiles para desarrolladores y herramientas de análisis estático.
+- Existen diferentes estilos para escribir anotaciones, como el uso directo de tipos o el módulo typing, aunque se recomienda el uso directo en versiones recientes.
+
+---
+
+- La documentación es esencial para entender el propósito y funcionamiento del código, y que se puede realizar mediante cadenas de documentación (docstrings) en clases, funciones y métodos.
+- Existen diferentes estilos de documentación en Python, como Google, NumPy y reStructuredText (reST), cada uno con sus ventajas y desventajas, y que es importante elegir uno y ser consistente.
+
+---
+
+- Los principios de desarrollo ayudan a escribir código limpio, mantenible y escalable, proporcionando soluciones a problemas comunes en el desarrollo de software.
+- El principio KISS enfatiza la simplicidad, claridad y eficiencia, evitando la sobreingeniería y manteniendo el código enfocado en lo esencial.
+
+---
+
+- El principio DRY promueve evitar la duplicación de código, fomentando la reutilización y facilitando el mantenimiento.
+- El principio YAGNI recomienda no agregar funcionalidades innecesarias, manteniendo el código simple y enfocado en los requisitos actuales.
+
+---
+
+#### Retos
+
+Crear una carpeta con el nombre "retos_sesion_12" dentro del proyecto en la raíz, en la cual por cada ejercicio debes crear los siguientes archivos:
+
+```bash
+# Estructura de carpetas
+psg-oop-2025/
+ retos_sesion_12/
+ ejercicio_01.md
+ ejercicio_01.py
+ ejercicio_02.md
+ ejercicio_02.py
+```
+
+---
+
+1. Una empresa de juegos moviles crea un nuevo juego **Dados de la Suerte**, las reglas son sencillas:
+
+- El jugador lanza **dos** dados
+- Si la suma es **7 o 11 gana**
+- Si es **2, 3 o 12 pierde**
+- Con cualquier otro valor puede volver a lanzar los dados.
+
+En el futuro tiene planificado registrar el nombre del jugador y su puntuación más alta
+pero para el lanzamiento inicial no es necesario
+
+*(1/5)*
+
+---
+
+
+Cuenta con un flujo básico:
+
+- Jugador inicia el juego
+- Juego lanza los dados automáticamente
+- Juego muestra el resultado y si ganó, perdió o si puede volver a lanzar
+- Jugador responde SI o NO para volver a lanzar los dados
+- El juego termina cuando el jugador decide NO lanzar más o gana o pierde
+- El juego muestra un mensaje final con el resultado
+
+*(2/5)*
+
+
+---
+
+- Crea la clase `DadosDeLaSuerte` donde se implemente el flujo básico del juego
+- Identifica atributos y métodos relevantes para la clase
+- Implementa la **lógica del juego** según las reglas descritas
+- Documenta el código y usa **anotaciones de tipo**
+- Utiliza los principios KISS, DRY y YAGNI
+
+*(3/5)*
+
+---
+
+Realiza el **análisis** y **diagrama de clases** en el archivo `ejercicio_01.md`
+
+Escribe el código en Python para la clase de tu diagrama de clases en el archivo `ejercicio_01.py`
+
+*(4/5)*
+
+---
+
+- Asegúrate de que la clase incluyan *atributos* y *métodos* coherentes con su propósito
+- Utiliza buenas prácticas de *nomenclatura*, *legibilidad* y *PEP 8*
+- Juega varias veces para verificar que la lógica del juego funciona correctamente
+
+*(5/5)*
+
+---
+
+2. Una empresa de citas necesita un programa para gestionar tareas.
+
+El programa puede:
+
+- Agregar nuevas tareas
+- Eliminar tareas existentes
+- Marcar tareas como completadas
+
+En el futuro se planea agregar fechas de vencimiento y prioridades a las tareas
+
+*(1/5)*
+
+---
+
+Cada tarea tiene un *título*, una *descripción* y un *estado* (completada o no completada).
+
+Cuenta con un menú básico:
+
+1. **Agregar** una nueva tarea proporcionando un título y una descripción
+2. **Eliminar** una tarea por su título
+3. **Marcar** una tarea como completada por su título
+4. **Listar** todas las tareas con su estado actual
+
+*(2/5)*
+
+---
+
+- Crea la clase `Tarea` para representar una tarea individual con atributos y métodos relevantes
+- Crea la clase `GestorDeTareas` para gestionar **una colección** de tareas, incluyendo métodos para agregar, eliminar y marcar tareas como completadas
+- Documenta el código y usa **anotaciones de tipo**
+- Utiliza los principios KISS, DRY y YAGNI
+
+*(3/5)*
+
+---
+
+- Realiza el **análisis** y **diagrama de clases** en el archivo `ejercicio_02.md`
+- Escribe el código en Python para las clases de tu diagrama de clases en el archivo `ejercicio_02.py`
+- Implementa un menú interactivo para que el usuario pueda gestionar las tareas
+- Documenta el código y usa **anotaciones de tipo**
+- Utiliza los principios KISS, DRY y YAGNI
+
+*(4/5)*
+
+---
+
+- Asegúrate de que las clases incluyan *atributos* y *métodos* coherentes con su propósito
+- Utiliza buenas prácticas de *nomenclatura*, *legibilidad* y *PEP 8*
+- Prueba las clases creando instancias y realizando operaciones de agregar, eliminar y marcar tareas como completadas
+
+*(5/5)*
+
+---
+
+Gracias por Participar del
+
+🐍 Python Study Group - OOP 2025 🐍
+
+> Un objeto puede ser pequeño, pero bien diseñado puede sostener un sistema entero
+
+---
+
+
+
+
+
+
+
+
+[ ](https://github.com/python-la-paz/python-study-group-oop/tree/main/content/sesion12)
+
+Repositorio de la Sesión
+
+---
+
+## Bibliografía y Referencias
+
+- [Function Annotations](https://ellibrodepython.com/function-annotations)
+- [Annotations HowTo](https://docs.python.org/es/3/howto/annotations.html)
+- [Python Annotations](https://pywombat.com/articles/python-annotation)
+- [PEP 484](https://peps.python.org/pep-0484/)
+- [PEP 585](https://peps.python.org/pep-0585/#parameters-to-generics-are-available-at-runtime)
+- [KISS Principle](https://fourweekmba.com/kiss-principle/)
+- [Principles of Clean Code: DRY, KISS, and YAGNI](https://medium.com/@curiousraj/the-principles-of-clean-code-dry-kiss-and-yagni-f973aa95fc4d)
+- [DRY Principle](https://www.techtarget.com/whatis/definition/DRY-principle)
+- [The DRY Principle](https://dev.to/ahadalireach/the-dry-principle-50hh)
+- [KISS, DRY, SOLID, YAGNI: A Simple Guide to Some Principles of Software Engineering and Clean Code](https://medium.com/@hlfdev/kiss-dry-solid-yagni-a-simple-guide-to-some-principles-of-software-engineering-and-clean-code-05e60233c79f)
+- [Object Oriented Analysis](https://www.gyata.ai/es/object-oriented-programming/object-oriented-analysis)
+- [DDOO Unidad 1](https://dmd.unadmexico.mx/contenidos/DCEIT/BLOQUE1/DS/02/DDOO/U1/descargables/DDOO_Unidad_1.pdf)
+- [Programación procedural VS orientada a objetos](https://programacionpro.com/programacion-procedural-vs-orientada-a-objetos-diferencias-y-similitudes/)
+- [Python OOP](https://www.learnpython.org/en/Classes_and_Objects)
+- [Atributos de clase](https://oregoom.com/python/atributos-clase/)
+- [Diagrama de clases](https://diagramasuml.com/diagrama-de-clases/)
+- [Guía PEP 8](https://peps.python.org/pep-0008/#class-names)
+- [Mermaid Charts](https://www.mermaidchart.com/play)
+- [Draw.io](https://app.diagrams.net/)
+- [Python 3 Object-oriented Programming, Second Edition, Dusty PhillipsDusty Phillips](https://github.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition)
+- [Objetos en programación](https://ebac.mx/blog/objeto-en-programacion)
+- [Enfoque orientado a objetos](https://1library.co/article/enfoque-orientado-a-objetos-base-te%C3%B3rica.qvld461y)
+- [OOAD](https://www.tutorialspoint.com/object_oriented_analysis_design/ooad_object_oriented_analysis.htm)
diff --git a/content/sesion12/img/annotation_01.png b/content/sesion12/img/annotation_01.png
new file mode 100644
index 0000000..e74d4b9
Binary files /dev/null and b/content/sesion12/img/annotation_01.png differ
diff --git a/content/sesion12/img/annotation_02.png b/content/sesion12/img/annotation_02.png
new file mode 100644
index 0000000..a0ded45
Binary files /dev/null and b/content/sesion12/img/annotation_02.png differ
diff --git a/content/sesion12/img/annotation_03.png b/content/sesion12/img/annotation_03.png
new file mode 100644
index 0000000..fe086b0
Binary files /dev/null and b/content/sesion12/img/annotation_03.png differ
diff --git a/content/sesion12/img/annotation_04.png b/content/sesion12/img/annotation_04.png
new file mode 100644
index 0000000..3dccd26
Binary files /dev/null and b/content/sesion12/img/annotation_04.png differ
diff --git a/content/sesion12/img/annotation_05.png b/content/sesion12/img/annotation_05.png
new file mode 100644
index 0000000..750588a
Binary files /dev/null and b/content/sesion12/img/annotation_05.png differ
diff --git a/content/sesion12/img/annotation_06.png b/content/sesion12/img/annotation_06.png
new file mode 100644
index 0000000..cc84150
Binary files /dev/null and b/content/sesion12/img/annotation_06.png differ
diff --git a/content/sesion12/img/annotation_07.png b/content/sesion12/img/annotation_07.png
new file mode 100644
index 0000000..da916b5
Binary files /dev/null and b/content/sesion12/img/annotation_07.png differ
diff --git a/content/sesion12/img/documentation_01.png b/content/sesion12/img/documentation_01.png
new file mode 100644
index 0000000..6952192
Binary files /dev/null and b/content/sesion12/img/documentation_01.png differ
diff --git a/content/sesion12/img/documentation_02.png b/content/sesion12/img/documentation_02.png
new file mode 100644
index 0000000..7899223
Binary files /dev/null and b/content/sesion12/img/documentation_02.png differ
diff --git a/content/sesion12/img/documentation_03.png b/content/sesion12/img/documentation_03.png
new file mode 100644
index 0000000..dd21ab7
Binary files /dev/null and b/content/sesion12/img/documentation_03.png differ
diff --git a/content/sesion12/img/documentation_04.png b/content/sesion12/img/documentation_04.png
new file mode 100644
index 0000000..2722380
Binary files /dev/null and b/content/sesion12/img/documentation_04.png differ
diff --git a/content/sesion12/img/documentation_05.png b/content/sesion12/img/documentation_05.png
new file mode 100644
index 0000000..52b348a
Binary files /dev/null and b/content/sesion12/img/documentation_05.png differ
diff --git a/content/sesion12/img/documentation_06.png b/content/sesion12/img/documentation_06.png
new file mode 100644
index 0000000..4b0d424
Binary files /dev/null and b/content/sesion12/img/documentation_06.png differ
diff --git a/content/sesion12/img/documentation_07.png b/content/sesion12/img/documentation_07.png
new file mode 100644
index 0000000..eb79eba
Binary files /dev/null and b/content/sesion12/img/documentation_07.png differ
diff --git a/content/sesion12/index.html b/content/sesion12/index.html
new file mode 100644
index 0000000..56e5d31
--- /dev/null
+++ b/content/sesion12/index.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+ Sesión 12 | Python Study Group | OOP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
index 2c0ebff..fcb4aa8 100644
--- a/index.html
+++ b/index.html
@@ -168,6 +168,36 @@