{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Editado por Equipo Docente IIC2233 2018-1 al 2025-2.</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Diseño de software: *Front-end* y *Back-end*](#Diseño-de-software:-Front-end-y-Back-end)\n",
    "1. [Ejemplo: Escribiendo un programa separando *front-end* y *back-end*](#Ejemplo:-Escribiendo-un-programa-separando-front-end-y-back-end)\n",
    "    1. [Versión 1: Programa poco cohesivo](#Versión-1:-Programa-poco-cohesivo)\n",
    "    2. [Versión 2: Programa cohesivo pero acoplado](#Versión-2:-Programa-cohesivo-pero-acoplado)\n",
    "    3. [Versión 3: Programa cohesivo y poco acoplado](#Versión-3:-Programa-cohesivo-y-poco-acoplado)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Diseño de software: *Front-end* y *Back-end*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En la ingeniería de *software* existen los conceptos de ***front-end*** y ***back-end*** para referirse a la separación que existe entre la capa de presentación y la capa de acceso a los datos, respectivamente. \n",
    "\n",
    "En el caso de interfaces gráficas:\n",
    "- El ***front-end*** está relacionado a la **interfaz gráfica** con la cual el usuario interactúa, y \n",
    "- El ***back-end*** se refiere a la **lógica** detrás de ella. \n",
    "\n",
    "Esta separación se alinea con un principio importante en el diseño de *software* de calidad que indica que siempre debemos buscar **alta cohesión y bajo acoplamiento** en nuestros diseños.\n",
    "\n",
    "- **Cohesión**: Cada una de las componentes del *software* debe realizar **solo las tareas para las cuales fue creada**, delegando otras tareas a otras componentes según corresponda. Por ejemplo, si tengo una clase `SimulaciónDeParque`, un diseño altamente cohesionado incluiría métodos como `iniciar_simulación()` o `detener_simulación()`, pero no métodos como `limpiar_atracción()` o `ingresar_clientes_a_restaurant()`, ya que la clase `SimulaciónDeParque` fue diseñada para administrar la simulación y no para hacerse cargo de métodos que deberían ser ejecutados por (*delegados a*) otras clases de la simulación.\n",
    "\n",
    "- **Acoplamiento**: Cuando la modificación de una componente implica que es necesario modificar otra componente para que la implementación del cambio sea correcta y completa. Por ejemplo, si al modificar los atributos de una clase `A`, también se deben modificar los atributos de otra clase `B`, se dice que hay *alto acoplamiento* entre las clases `A` y `B`. Un buen diseño intenta reducir el acoplamiento entre clases.\n",
    "\n",
    "De ahí que siempre debemos buscar **ALTA COHESIÓN y BAJO ACOPLAMIENTO** en nuestros diseños."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Escribir código con separación del ***front-end*** y ***back-end***, significa implementar las funciones de un programa con una clara separación entre entidades dedicadas a lo visual (***front-end***) y entidades dedicadas a detalles algorítmicos, de datos, o de lógica (***back-end***). Algunas ventajas de hacerlo son las siguientes:\n",
    "\n",
    "1. **Modularidad**: Permite cambiar cualquiera de las dos partes sin afectar la otra (bajo acoplamiento). En particular, podemos editar el *front-end* suponiendo que las funciones utilizadas por el *back-end* mantienen su comportamiento. Al mismo tiempo, es posible modularizar el *back-end* de manera independiente del *front-end*. Podemos reescribir el código para hacerlo cada vez más eficiente y específico (alta cohesión). Podemos incluso modularizar el *back-end* en muchos archivos distintos y luego consultar todas las funcionalidades desde un solo archivo de conexión con el *front-end* (alta cohesión).\n",
    "1. **Uso de recursos**: Algunas veces el *front-end* está corriendo en un computador distinto al *back-end*. Si el *back-end* ejecuta cálculos muy costosos, no nos gustaría cargarle este tiempo computacional a la interfaz gráfica. Un ejemplo claro de esto son los navegadores de internet (*browsers*), donde la mayoría de los cálculos o datos que queremos obtener se generan en un servidor de *back-end* y el resultado obtenido solo se muestra gráficamente en el computador del usuario (*front-end*). De este modo, nuestro computador no tiene que sobrecargarse procesando cosas.\n",
    "1. **Escalamiento**: Por un lado, permite hacer crecer un *software* sin mucha interferencia a las funcionalidades antiguas. Por otro lado, permite distribuir el procesamiento en el *back-end* utilizando múltiples réplicas de este, lo que es muy usado en la web.\n",
    "1. **Experticia**: Usualmente los desarrolladores de un *front-end* tienen un tipo de experiencia muy distinta a la que tienen quienes desarrollan el *back-end*. Mantenerlos separados permite obtener lo mejor de ambas partes.\n",
    "1. **Mantención**: Es posible hacer *testing* parte por parte de una pieza de *software*, e introducir correcciones o mejoras evitando que un alto acoplamiento de funcionalidades haga que las modificaciones deban ser propagadas a múltiples partes del código.\n",
    "1. **Evolución del *software*/versionamiento**: Si quieres cambiar completamente una de las partes puedes hacerlo sin problema, mientras las funciones utilizadas en el *front-end* sigan teniendo los mismos nombres que antes (dicho de otra forma, mientras se mantenga la misma interfaz de métodos). De esta manera, por ejemplo, si programas un *back-end* para PyQt5 (una versión anterior de PyQt) y luego quieres usarlo para PyQt6, puedes hacerlo sin problemas (alta cohesión). O mejor aún, exportar tus funcionalidades para un *back-end* web.\n",
    "\n",
    "Estas ventajas pueden transformarse en costos si es que hay que tener dos equipos distintos de desarrollo o hay que mantener dos *software* distintos. Sin embargo, las ventajas siguen siendo mayores."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejemplo: Escribiendo un programa separando *front-end* y *back-end*\n",
    "\n",
    "Se mostrará mediante ejemplos la evolución de un programa que inicialmente no sigue la arquitectura *front-end*/*back-end*, y termina siguiéndola. El programa que se construye tiene por objetivo recibir una lista de números (separados por coma), y muestra como resultado la lista ordenada de números."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Versión 1: Programa poco cohesivo\n",
    "\n",
    "El siguiente programa resuelve lo pedido, y sigue el siguiente flujo:\n",
    "\n",
    "- Define los componentes de interfaz gráfica: dos *labels*, un *input* de texto y un botón.\n",
    "- Conecta el clic sobre el botón con la funcionalidad de ordenamiento.\n",
    "- Al hacer clic y gatillar el evento de ordenar:\n",
    "    - Por simplicidad, se remueven todos los espacios de `texto_input` y comas sobrantes.\n",
    "    - Se verifica que el texto recibido (la lista de números ingresada por el usuario) sea válido\n",
    "    - Si no es válido, se notifica al usuario mediante una etiqueta de texto.\n",
    "    - Si lo es, se ordenan los números mediante un algoritmo que saca el mínimo sucesivamente y luego muestra el resultado.\n",
    "    \n",
    "Este código se encuentra en el archivo `4-diseño-front-back/1_baja-cohesion/main.py`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "import sys\n",
    "from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QPushButton,\n",
    "                             QLineEdit)\n",
    "\n",
    "\n",
    "class Ventana(QWidget):\n",
    "    def __init__(self) -> None:\n",
    "        super().__init__()\n",
    "        self.inicializa_gui()\n",
    "\n",
    "    def inicializa_gui(self) -> None:\n",
    "        self.etiqueta = QLabel('Ingresa una lista de números '\n",
    "                               'separados por comas:', self)\n",
    "        self.etiqueta.move(20, 10)\n",
    "        self.etiqueta.resize(self.etiqueta.sizeHint())\n",
    "\n",
    "        self.input = QLineEdit('', self)\n",
    "        self.input.setGeometry(20, 40, 360, 20)\n",
    "\n",
    "        self.boton = QPushButton('Ordenar', self)\n",
    "        self.boton.setGeometry(20, 70, 360, 30)\n",
    "        self.boton.clicked.connect(self.revisar_y_ordenar)\n",
    "\n",
    "        self.resultado = QLabel('', self)\n",
    "        self.resultado.move(20, 100)\n",
    "        self.resultado.resize(self.resultado.sizeHint())\n",
    "\n",
    "        self.setGeometry(700, 300, 400, 200)\n",
    "        self.setWindowTitle('Ordenador de números')\n",
    "        self.show()\n",
    "\n",
    "    def revisar_y_ordenar(self) -> None:\n",
    "        texto_input = self.input.text()\n",
    "        if not texto_input:\n",
    "            return\n",
    "\n",
    "        texto_input = texto_input.replace(' ', '').strip(',')\n",
    "        if not texto_input.replace(',', '').isnumeric():\n",
    "            self.resultado.setText('Input no válido')\n",
    "            self.resultado.resize(self.resultado.sizeHint())\n",
    "            return\n",
    "\n",
    "        lista_de_numeros = [int(porcion) for porcion in texto_input.split(',')]\n",
    "        lista_de_numeros.sort()\n",
    "        texto_resultado = \", \".join([str(numero) for numero in lista_de_numeros])\n",
    "\n",
    "        self.resultado.setText(texto_resultado)\n",
    "        self.resultado.resize(self.resultado.sizeHint())\n",
    "        self.resultado.repaint()\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    def hook(type, value, traceback) -> None:\n",
    "        print(type)\n",
    "        print(traceback)\n",
    "\n",
    "    sys.__excepthook__ = hook\n",
    "\n",
    "    app = QApplication([])\n",
    "    ventana = Ventana()\n",
    "    sys.exit(app.exec())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si bien el código logra lo esperado, notamos primero que es muy poco **cohesivo**. La clase `Ventana` realiza **todas** las operaciones necesarias para lograr el resultado del programa. La clase `Ventana` al ser un componente visual, solo debiese encargarse de tareas visuales que involucren a esta ventana. El método `revisar_y_ordenar` específicamente realiza tareas de procesamiento de datos **y** tareas visuales en la ventana. Las responsabilidades de tareas no se reparten correctamente entre más componentes, por lo que resulta en código poco cohesivo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Versión 2: Programa cohesivo pero acoplado\n",
    "\n",
    "En la siguiente versión del programa se logra separar las responsabilidades de código en distintos componentes lógicos. Se crea un módulo llamado `4-diseño-front-back/2_alta-cohesion-alto-acoplamiento/backend.py` que se encarga de la validación del *input* y ordenamiento del la lista de números, mientras que el módulo `4-diseño-front-back/2_alta-cohesion-alto-acoplamiento/frontend.py` se encarga del aspecto visual."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Módulo: `4-diseño-front-back/2_alta-cohesion-alto-acoplamiento/frontend.py`**\n",
    "\n",
    "```python\n",
    "import sys\n",
    "from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QPushButton,\n",
    "                             QLineEdit)\n",
    "from backend import procesar_input\n",
    "\n",
    "\n",
    "class Ventana(QWidget):\n",
    "    def __init__(self) -> None:\n",
    "        super().__init__()\n",
    "        self.inicializa_gui()\n",
    "\n",
    "    def inicializa_gui(self) -> None:\n",
    "\n",
    "        self.etiqueta = QLabel('Ingresa una lista de números '\n",
    "                               'separados por comas:', self)\n",
    "        self.etiqueta.move(20, 10)\n",
    "        self.etiqueta.resize(self.etiqueta.sizeHint())\n",
    "\n",
    "        self.input = QLineEdit('', self)\n",
    "        self.input.setGeometry(20, 40, 360, 20)\n",
    "\n",
    "        self.boton = QPushButton('Ordenar', self)\n",
    "        self.boton.setGeometry(20, 70, 360, 30)\n",
    "        self.boton.clicked.connect(self.boton_clickeado)\n",
    "\n",
    "        self.resultado = QLabel('', self)\n",
    "        self.resultado.move(20, 100)\n",
    "        self.resultado.resize(self.resultado.sizeHint())\n",
    "\n",
    "        self.setGeometry(700, 300, 400, 150)\n",
    "        self.setWindowTitle('Ordenador de números')\n",
    "        self.show()\n",
    "\n",
    "    def boton_clickeado(self) -> None:\n",
    "        texto_input = self.input.text()\n",
    "        texto_resultado = procesar_input(texto_input)\n",
    "        self.resultado.setText(texto_resultado)\n",
    "        self.resultado.resize(self.resultado.sizeHint())\n",
    "        self.resultado.repaint()\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    def hook(type, value, traceback) -> None:\n",
    "        print(type)\n",
    "        print(traceback)\n",
    "\n",
    "    sys.__excepthook__ = hook\n",
    "\n",
    "    app = QApplication([])\n",
    "    ventana = Ventana()\n",
    "    sys.exit(app.exec())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Módulo: `4-diseño-front-back/2_alta-cohesion-alto-acoplamiento/backend.py`**\n",
    "```python\n",
    "def es_valido(texto: str) -> bool:\n",
    "    for valor in texto.split(','):\n",
    "        if not valor.isnumeric():\n",
    "            return False\n",
    "    return True\n",
    "\n",
    "\n",
    "def ordenar(lista_de_numeros: list) -> list:\n",
    "    lista_de_numeros.sort()\n",
    "    return lista_de_numeros\n",
    "\n",
    "\n",
    "def procesar_input(texto_input: str) -> str:\n",
    "    texto_input = texto_input.r place(' ', '').strip(',')\n",
    "    if not es_valido(texto_input):\n",
    "        return 'Input no válido'\n",
    "    lista_de_numeros = [int(porcion) for porcion in texto_input.split(',')]\n",
    "    numeros_ordenados = ordenar(lista_de_numeros)\n",
    "    texto_resultado = \", \".join([str(numero) for numero in numeros_ordenados])\n",
    "    return texto_resultado\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notamos dos beneficios:\n",
    "\n",
    "- Todo el código encargado de procesamiento de datos queda en un archivo separado y ordenado.\n",
    "- El código de la ventana principal se simplifica y solo se encarga de tareas visuales.\n",
    "\n",
    "Es decir, hemos logrado mayor cohesión en nuestro programa.\n",
    "\n",
    "También, notamos que esta separación ya califica para considerarse arquitectura *front-end*/*back-end*: La clase `Ventana` es parte del *front-end* de nuestro programa al encargarse de solo tareas visuales y de interfaz, mientras que `2_alta-cohesion-alto-acoplamiento/backend.py` es parte del *back-end* de nuestro programa al encargarse de la parte lógica del programa.\n",
    "\n",
    "Ahora, ¿es el código resultante poco acoplado? Lamentablemente, no tanto. Esto debido a que la componente visual llama directamente a la componente de lógica (específicamente, la función `procesar_input`). Luego, si eventualmente se modifica el nombre de esta función en el módulo `2_alta-cohesion-alto-acoplamiento/backend.py`, implica necesariamente cambiar su nombre también en la clase `Ventana` y en cualquier otro lugar que sea referenciado. Puede que este ejemplo parezca simple y poco grave ya que es solo una función la que se importa y utiliza, pero si esto se extrapola a un programa de mayor tamaño con múltiples funciones importadas, se vuelve un mayor problema. Es decir, nuestro programa está relativamente **acoplado**.\n",
    "\n",
    "Pero, ¿cómo se soluciona esta situación? **Hay** que llamar de una forma u otra a esta función para lograr el objetivo resultante, ¿cómo se logra sin llamar a la función directamente? ¡Señales! "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Versión 3: Programa cohesivo y poco acoplado\n",
    "\n",
    "En esta última versión, se logra una separación de componentes *front-end*/*back-end* que logra un programa altamente cohesivo, y mediante comunicación via señales se logra un programa levemente acoplado.\n",
    "\n",
    "La diferencia con la última versión es que se elimina el llamado directo de objetos de distintos componentes y se delega todo a señales para comunicarse. Se crean dos señales: `senal_actualizar` cuyo objetivo es comunicar desde *back-end* al *front-end* una actualización de ventana, y `senal_procesar` cuyo objetivo es comunicar desde *front-end* a *back-end* cuando es necesario procesar *input* nuevo.\n",
    "\n",
    "Ahora las funciones de procesamiento se encapsulan en una clase `Procesador` para contener la señal de PyQt, y en el código principal es necesario conectar las señales de una clase con el método correspondiente de la otra clase. En este caso:\n",
    "* La `senal_procesar` del *front-end* se conecta con el método `procesar_input` del *back-end*.\n",
    "* La `senal_actualizar` del *back-end* se conecta con el método `actualizar_resultado` del *front-end*.\n",
    "\n",
    "Este último detalle es necesario para que efectivamente se comuniquen los componentes, y es inevitablemente un aspecto de acoplamiento del programa. Es imposible lograr acoplamiento cero en un programa, pero si es posible **minimizarlo**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Módulo: `4-diseño-front-back/3_alta-cohesion-bajo-acoplamiento/frontend.py`**\n",
    "```python\n",
    "import sys\n",
    "from PyQt6.QtCore import pyqtSignal\n",
    "from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QPushButton,\n",
    "                             QLineEdit)\n",
    "from backend import Procesador\n",
    "\n",
    "\n",
    "class Ventana(QWidget):\n",
    "    senal_procesar = pyqtSignal(str)\n",
    "\n",
    "    def __init__(self) -> None:\n",
    "        super().__init__()\n",
    "        self.inicializa_gui()\n",
    "\n",
    "    def inicializa_gui(self) -> None:\n",
    "        self.etiqueta = QLabel('Ingresa una lista de números '\n",
    "                               'separados por comas:', self)\n",
    "        self.etiqueta.move(20, 10)\n",
    "        self.etiqueta.resize(self.etiqueta.sizeHint())\n",
    "\n",
    "        self.input = QLineEdit('', self)\n",
    "        self.input.setGeometry(20, 40, 360, 20)\n",
    "\n",
    "        self.boton = QPushButton('Ordenar', self)\n",
    "        self.boton.setGeometry(20, 70, 360, 30)\n",
    "        self.boton.clicked.connect(self.boton_clickeado)\n",
    "\n",
    "        self.resultado = QLabel('', self)\n",
    "        self.resultado.move(20, 100)\n",
    "        self.resultado.resize(self.resultado.sizeHint())\n",
    "\n",
    "        self.setGeometry(700, 300, 400, 150)\n",
    "        self.setWindowTitle('Ordenador de números')\n",
    "        self.show()\n",
    "\n",
    "    def boton_clickeado(self) -> None:\n",
    "        texto_input = self.input.text()\n",
    "        self.senal_procesar.emit(texto_input)\n",
    "\n",
    "    def actualizar_resultado(self, texto: str) -> None:\n",
    "        self.resultado.setText(texto)\n",
    "        self.resultado.resize(self.resultado.sizeHint())\n",
    "        self.resultado.repaint()\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    def hook(type, value, traceback) -> None:\n",
    "        print(type)\n",
    "        print(traceback)\n",
    "\n",
    "    sys.__excepthook__ = hook\n",
    "\n",
    "    app = QApplication([])\n",
    "    procesador = Procesador()\n",
    "    ventana = Ventana()\n",
    "    procesador.senal_actualizar.connect(ventana.actualizar_resultado)\n",
    "    ventana.senal_procesar.connect(procesador.procesar_input)\n",
    "    sys.exit(app.exec())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Módulo: `4-diseño-front-back/3_alta-cohesion-bajo-acoplamiento/backend.py`**\n",
    "```python\n",
    "from PyQt6.QtCore import QObject, pyqtSignal\n",
    "\n",
    "\n",
    "class Procesador(QObject):\n",
    "    senal_actualizar = pyqtSignal(str)\n",
    "\n",
    "    def es_valido(self, texto: str) -> bool:\n",
    "        for valor in texto.split(','):\n",
    "            if not valor.isnumeric():\n",
    "                return False\n",
    "        return True\n",
    "\n",
    "    def ordenar(self, lista_de_numeros: list) -> list:\n",
    "        lista_de_numeros.sort()\n",
    "        return lista_de_numeros\n",
    "\n",
    "    def procesar_input(self, texto_input: str) -> None:\n",
    "        texto_input = texto_input.replace(' ', '').strip(',')\n",
    "        if not self.es_valido(texto_input):\n",
    "            self.actualizar_interfaz('Input no válido')\n",
    "            return\n",
    "        lista_de_numeros = [int(porcion) for porcion in texto_input.split(',')]\n",
    "        numeros_ordenados = self.ordenar(lista_de_numeros)\n",
    "        texto_resultado = \", \".join([str(numero)\n",
    "                                    for numero in numeros_ordenados])\n",
    "        self.actualizar_interfaz(texto_resultado)\n",
    "\n",
    "    def actualizar_interfaz(self, texto: str) -> None:\n",
    "        self.senal_actualizar.emit(texto)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es importante notar que el código dentro de las dos clases principales creadas (`Ventana` y `Procesador`) nunca llama directamente al código de la otra clase, solo llama y ejecuta código interno. Cada una funciona por sí sola y delega la responsabilidad de envío de información a señales.\n",
    "\n",
    "En general, para escribir un programa de:\n",
    "- _Software_ altamente **cohesivo**, la estrategia es separar de forma adecuada las **responsabilidades** de los distintos componentes del programa; y escribir distintos módulos y clases destinados a objetivos claros.\n",
    "\n",
    "- _Software_ poco **acoplado**, la estrategia es independizar lo más posible los distintos componentes; el uso de **señales** es muy compatible con esta idea ya que reduce el acoplamiento al uso de señales comunes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Algunas dudas frecuentes que pueden aparecer al ver este ejemplo:\n",
    "\n",
    "- 🤔 ¿Por qué `Procesador` hereda de `QObject`? 🤔 \n",
    "\n",
    "   > Porque por diseño se genera la señal de procesamiento en esta clase, y por reglas de PyQt, toda clase que crea como atributo una `pyqtSignal`, es necesario que herede de `QObject` y llame a su constructor (`super().__init__()`).\n",
    "\n",
    "- 🤔 ¿Por qué una señal se crea en *back-end* y otra señal en *front-end*? 🤔 \n",
    "\n",
    "   > Esa fue una decisión de diseño específica de este ejemplo. Se eligió crear la señal según quién emite dicha señal: actualizar el resultado en *back-end* y procesar input en *front-end*. Es posible definir de otra forma perfectamente válida, pero las conexiones se han de hacer en otro orden.\n",
    "\n",
    "- 🤔 ¿No es sobre complicado para el ejemplo buscado? 🤔 \n",
    "\n",
    "   > Tal vez un poco, pero como se menciona anteriormente, esto es más aparente por que el programa es relativamente pequeño y simple. En ejemplos más extensos y complicados, hacer este tipo de separación es mucho más evidente y provechosa."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.10"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
