{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Módulos, paquetes y entornos virtuales\n",
    "\n",
    "Salir del cuaderno, importar sin romper nada, y la respuesta a \"en mi máquina funcionaba\".\n",
    "\n",
    "Cuaderno de soluciones del capítulo 8 de **Python desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/python-desde-cero/modulos-y-entornos/\n",
    "\n",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Salgamos del cuaderno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo de los capítulos anteriores vivía en una celda. Perfecto para\n",
    "aprender, y en algún momento se te queda chico 🌱\n",
    "\n",
    "Cuando una función te sirve en tres cuadernos distintos, copiarla tres veces\n",
    "es pedir problemas: arreglas una y las otras dos siguen mal. Para eso están los\n",
    "módulos.\n",
    "\n",
    "Y una pregunta que vale para todo lo que viene: **¿te ha pasado que algo te funcionaba y al día siguiente no, sin haber tocado nada?** Casi siempre es una versión que cambió por debajo, y la respuesta a eso está en la segunda mitad del capítulo 🧰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Un módulo es un archivo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nada más. Si guardas esto como `utiles.py`:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "# utiles.py\n",
    "IGV = 0.18\n",
    "\n",
    "def con_igv(monto):\n",
    "    return round(monto * (1 + IGV), 2)\n",
    "\n",
    "def limpia_ciudad(texto):\n",
    "    return texto.strip().title()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Desde otro archivo o cuaderno en la misma carpeta lo traes así:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "import utiles\n",
    "\n",
    "print(utiles.con_igv(100))\n",
    "print(utiles.IGV)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese punto se lee como \"de dentro de\". `utiles.con_igv` es \"la\n",
    "función `con_igv` de dentro de `utiles`\".\n",
    "\n",
    "### Las cuatro formas de importar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "from math import sqrt\n",
    "from math import sqrt as raiz\n",
    "import statistics as stats\n",
    "\n",
    "print(math.sqrt(16))\n",
    "print(sqrt(16))\n",
    "print(raiz(16))\n",
    "print(stats.mean([10, 20, 30]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuál usar:\n",
    "\n",
    "- 📦 `import math` cuando vas a usar varias cosas del módulo. Se lee\n",
    "de dónde viene cada una.\n",
    "\n",
    "- 🎯 `from math import sqrt` cuando usas una sola y mucho.\n",
    "\n",
    "- 🏷️ `as` para acortar. Aquí hay convenciones que todo el mundo\n",
    "respeta: `import pandas as pd`, `import numpy as np`.\n",
    "Úsalas, que así tu código se lee igual que el de cualquiera.\n",
    "\n",
    "Y una que no debes usar aunque la veas por ahí:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "from math import *      # NO\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso trae todo y llena tu espacio de nombres de cosas que no sabes que están.\n",
    "Si dos módulos tienen una función con el mismo nombre, el segundo pisa al primero\n",
    "sin avisarte 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La librería estándar, que ya tienes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python viene con muchísimo incluido. Estos son los que de verdad vas a usar en\n",
    "datos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import date, datetime, timedelta\n",
    "\n",
    "hoy = date(2026, 8, 15)\n",
    "compra = date(2026, 5, 30)\n",
    "\n",
    "print((hoy - compra).days)\n",
    "print(hoy + timedelta(days=30))\n",
    "print(hoy.strftime('%d/%m/%Y'))\n",
    "print(datetime.strptime('29/06/2025', '%d/%m/%Y').date())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esas cuatro líneas son el 80% de lo que se hace con fechas: cuántos días\n",
    "pasaron, sumar días, mostrarla bonita y leerla desde un texto.\n",
    "\n",
    "`strftime` convierte fecha a texto y `strptime`\n",
    "convierte texto a fecha. La `p` es de \"parse\". Yo las confundí durante\n",
    "años, así que no te sientas mal 😄"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import Counter\n",
    "\n",
    "canales = ['Web', 'Tienda', 'Web', 'WhatsApp', 'Web', 'Tienda']\n",
    "\n",
    "conteo = Counter(canales)\n",
    "print(conteo)\n",
    "print(conteo.most_common(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Te acuerdas del bucle con `.get(c, 0) + 1` del capítulo 4? Esto\n",
    "hace lo mismo en una línea. Yo te hice escribir el bucle a propósito, para que\n",
    "entiendas qué hay debajo 💜"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "texto = '{\"ciudad\": \"Lima\", \"monto\": 480.37, \"pagado\": true}'\n",
    "venta = json.loads(texto)\n",
    "\n",
    "print(venta['ciudad'], venta['monto'])\n",
    "print(type(venta['pagado']), venta['pagado'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en que el `true` en minúscula del JSON se convirtió en el\n",
    "`True` de Python. Esa traducción la hace `json` sola, y es\n",
    "la razón por la que nunca hay que parsear JSON a mano."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Módulo | Para qué |\n",
    "|---|---|\n",
    "| `datetime` | Fechas y diferencias entre fechas |\n",
    "| `collections` | `Counter`, `defaultdict` |\n",
    "| `json` | Leer y escribir JSON |\n",
    "| `csv` | CSV sin pandas, cuando el archivo es enorme |\n",
    "| `pathlib` | Rutas de archivos que funcionan en Windows y en Mac |\n",
    "| `statistics` | Media, mediana, desviación, sin instalar nada |\n",
    "| `re` | Expresiones regulares, para buscar patrones en texto |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de instalar una librería para algo, pregúntate si la estándar ya lo\n",
    "hace. Muchas veces sí 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## pathlib: rutas que no se rompen"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las rutas escritas a mano son una fuente clásica de dolor, porque Windows usa\n",
    "barras al revés que Mac y Linux."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "carpeta = Path('datos')\n",
    "archivo = carpeta / 'ventas' / 'enero.csv'\n",
    "\n",
    "print(archivo)\n",
    "print(archivo.name)\n",
    "print(archivo.stem)\n",
    "print(archivo.suffix)\n",
    "print(archivo.exists())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `/` entre carpetas es magia de `pathlib`: arma la\n",
    "ruta con el separador correcto de cada sistema.\n",
    "\n",
    "Y ese `.exists()` en `False` es tu mejor amigo. Porque\n",
    "si no preguntas, pasa esto:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    open('datos/ventas/enero.csv')\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "FileNotFoundError: [Errno 2] No such file or directory: 'datos/ventas/enero.csv'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ojo con cómo se lee ese mensaje: dice la ruta *relativa*, así que no\n",
    "te dice desde dónde estaba buscando. Cuando te pase, esta línea te lo aclara en\n",
    "un segundo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "aqui = Path.cwd()\n",
    "print('Python esta mirando desde una carpeta que existe?', aqui.is_dir())\n",
    "print('y el nombre de esa carpeta tiene', len(aqui.name), 'letras')\n",
    "print()\n",
    "print('en tu maquina, imprime aqui a secas y sale la ruta completa')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En tu cuaderno eso imprime la ruta entera. Aquí no la enseño porque sería la\n",
    "ruta de *mi* máquina, y este libro publica salidas que se comprueban: una\n",
    "ruta absoluta cambiaría con cada persona que lo ejecute 📁\n",
    "\n",
    "Ahí sale la carpeta desde la que Python está mirando. El 90% de los\n",
    "`FileNotFoundError` son eso: el archivo existe, pero está en otro\n",
    "sitio del que tú creías 📁\n",
    "\n",
    "Así que la costumbre buena es preguntar antes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ruta = Path('datos/ventas/enero.csv')\n",
    "\n",
    "if ruta.exists():\n",
    "    print('leyendo', ruta)\n",
    "else:\n",
    "    print(f'no encuentro {ruta} desde {Path.cwd().name}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Entornos virtuales, o \"en mi máquina funcionaba\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta parte te la explico porque tarde o temprano te va a pasar y quiero que\n",
    "sepas qué es.\n",
    "\n",
    "Imagina que tienes dos proyectos. Uno viejo que necesita pandas 1.5 y uno\n",
    "nuevo que necesita pandas 2.2. Si instalas todo en el mismo Python, la segunda\n",
    "instalación pisa a la primera y el proyecto viejo deja de funcionar 😖\n",
    "\n",
    "**Un entorno virtual es una carpeta con su propio Python y sus propias\n",
    "librerías.** Cada proyecto tiene el suyo y no se pisan."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "# Crear el entorno, una sola vez por proyecto\n",
    "python3 -m venv .venv\n",
    "\n",
    "# Activarlo (Mac y Linux)\n",
    "source .venv/bin/activate\n",
    "\n",
    "# Activarlo (Windows)\n",
    ".venv\\Scripts\\activate\n",
    "\n",
    "# Ya dentro: instalar lo del proyecto\n",
    "pip install pandas matplotlib\n",
    "\n",
    "# Guardar la lista exacta\n",
    "pip freeze > requirements.txt\n",
    "\n",
    "# Salir\n",
    "deactivate\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuando está activado, tu terminal muestra `(.venv)` al principio de\n",
    "la línea. Esa es la señal de que estás dentro.\n",
    "\n",
    "Ese `requirements.txt` es el archivo que hace que otra persona\n",
    "pueda reproducir tu entorno exacto:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "pip install -r requirements.txt\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y dos cosas que se olvidan siempre:\n",
    "\n",
    "- 🚫 La carpeta `.venv` **no se sube** a git. Se pone\n",
    "en el `.gitignore`. Lo que se sube es el\n",
    "`requirements.txt`.\n",
    "\n",
    "- 💻 Si usas un editor como VS Code, hay que decirle que use ese Python. Si no,\n",
    "te va a decir que pandas no está instalado aunque lo esté.\n",
    "\n",
    "En Colab nada de esto aplica: cada cuaderno ya es su propio entorno aislado, y\n",
    "por eso es tan cómodo para aprender."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Organizar tu propio código"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuando un proyecto crece, esta estructura funciona y es la que yo uso:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "mi_proyecto/\n",
    "    .venv/                  no se sube a git\n",
    "    datos/\n",
    "        ventas.csv\n",
    "    src/\n",
    "        __init__.py\n",
    "        limpieza.py\n",
    "        metricas.py\n",
    "    cuadernos/\n",
    "        exploracion.ipynb\n",
    "    requirements.txt\n",
    "    README.md\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `__init__.py` vacío es lo que convierte una carpeta en paquete,\n",
    "y permite importar así:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "from src.limpieza import limpia_ciudad\n",
    "from src.metricas import ticket_promedio\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La regla que te va a servir: **el cuaderno explora, el módulo\n",
    "guarda**. Cuando una función del cuaderno ya funciona bien y la vas a\n",
    "volver a usar, se muda a un `.py`. Así el cuaderno queda para probar\n",
    "cosas y el código bueno vive en un solo sitio 🌟\n",
    "\n",
    "### El bloque que aparece al final de los scripts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def principal():\n",
    "    print('esto es el trabajo de verdad')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    principal()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `if` significa \"corre esto solo si me estás ejecutando a mí\n",
    "directamente, no si me estás importando\".\n",
    "\n",
    "Sin él, importar tu módulo desde otro sitio ejecutaría todo el script entero.\n",
    "Y sí, se ve raro la primera vez. Con verlo tres veces se te queda 😄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El import que no encuentra nada"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este error te lo vas a comer, y no una vez."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    import analisis_de_ventas\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "ModuleNotFoundError: No module named 'analisis_de_ventas'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python no busca en todo tu disco: busca en una lista de sitios concretos, y\n",
    "el primero de esa lista es **la carpeta desde la que lanzaste el\n",
    "programa**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "primero = Path(sys.path[0] or '.').resolve()\n",
    "print('el primer sitio de la lista es una carpeta de verdad?', primero.is_dir())\n",
    "print('y esa carpeta es desde donde lanzaste el programa')\n",
    "print()\n",
    "print('y despues vienen las carpetas de Python y las de las librerias')\n",
    "print('instaladas, que son unas cuantas y dependen de tu instalacion')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De ahí salen los dos casos que te van a pasar 🔍\n",
    "\n",
    "- 📁 **El archivo existe pero estás en otra carpeta.** Lo típico\n",
    "al abrir el cuaderno en un sitio y tener el `.py` en otro.\n",
    "\n",
    "- 📦 **La librería no está instalada en ESE entorno.** La\n",
    "instalaste, sí, pero en otro. Es la sección de abajo.\n",
    "\n",
    "Y un tercero que da más rabia porque el mensaje no ayuda: le pusiste a tu\n",
    "archivo el nombre de una librería. Si guardas un `random.py` en tu\n",
    "carpeta, el `import random` de cualquier otro archivo va a encontrar\n",
    "el tuyo primero y a reventar por sitios rarísimos 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Escribir las versiones, que es lo que hace que funcione mañana"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un entorno virtual guarda las librerías. Lo que hay que guardar aparte es\n",
    "**qué versión** de cada una, y eso vive en un archivo de texto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import importlib.metadata as metadatos\n",
    "\n",
    "for paquete in ('pandas', 'numpy'):\n",
    "    print(f'{paquete}=={metadatos.version(paquete)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso mismo es lo que escribe `pip freeze > requirements.txt`, y\n",
    "lo que lee `pip install -r requirements.txt` para dejar la máquina de\n",
    "tu compañera igual que la tuya 📋\n",
    "\n",
    "Fíjate en el doble igual. No es adorno: `pandas` a secas instala\n",
    "la última que haya hoy, y la última de dentro de seis meses no es la misma. El\n",
    "día que tu código deje de funcionar sin que tú tocaras nada, va a ser por\n",
    "esto.\n",
    "\n",
    "Los tres comandos que se usan de verdad, y no hay más:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Qué quieres | Qué escribes |\n",
    "|---|---|\n",
    "| Crear el entorno | `python3 -m venv .venv` |\n",
    "| Entrar | `source .venv/bin/activate` |\n",
    "| Dejar constancia | `pip freeze > requirements.txt` |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En Windows el segundo es `.venv\\\\Scripts\\\\activate`, y esa barra al\n",
    "revés es la única diferencia que importa 🪟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Días entre fechas\n",
    "\n",
    "Un cliente compró el 15 de enero y volvió el 30 de junio de\n",
    "2026. Calcula cuántos días estuvo sin comprar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import date\n",
    "\n",
    "compra_anterior = date(2026, 1, 15)\n",
    "compra_nueva = date(2026, 6, 30)\n",
    "\n",
    "print((compra_nueva - compra_anterior).days, 'días sin comprar')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "166 días sin comprar\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. La fecha que vino como texto\n",
    "\n",
    "Del archivo te llega la fecha `'29/06/2025'` en\n",
    "formato peruano. Conviértela y muéstrala en formato internacional, que es el que\n",
    "no se presta a confusión."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "f = datetime.strptime('29/06/2025', '%d/%m/%Y').date()\n",
    "print(f, f.strftime('%Y-%m-%d'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "2025-06-29 2025-06-29\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El canal más usado\n",
    "\n",
    "Con `Counter`, encuentra el canal más frecuente y\n",
    "cuántas veces aparece."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import Counter\n",
    "\n",
    "canales = ['Web', 'Tienda', 'Web', 'WhatsApp', 'Web', 'Marketplace', 'Tienda']\n",
    "top = Counter(canales).most_common(1)[0]\n",
    "\n",
    "print(f'{top[0]} con {top[1]} ventas')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Web con 3 ventas\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Fecha de vencimiento\n",
    "\n",
    "Una factura del 15 de agosto de 2026 vence a los 45 días.\n",
    "Calcula la fecha y en qué día de la semana cae."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import date, timedelta\n",
    "\n",
    "emision = date(2026, 8, 15)\n",
    "vence = emision + timedelta(days=45)\n",
    "\n",
    "dias = ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo']\n",
    "print(vence, dias[vence.weekday()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "2026-09-29 martes\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`weekday()` devuelve 0 para lunes. Un clásico error por uno es\n",
    "asumir que empieza en domingo, como en Excel."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Un JSON de una API\n",
    "\n",
    "Parsea una respuesta con varias ventas y saca el total."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "respuesta = ('{\"ventas\": ['\n",
    "             '{\"ciudad\": \"Lima\", \"monto\": 480.37},'\n",
    "             '{\"ciudad\": \"Cusco\", \"monto\": 154.83}]}')\n",
    "\n",
    "datos = json.loads(respuesta)\n",
    "print(round(sum(v['monto'] for v in datos['ventas']), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "635.2\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Rutas sin romperse\n",
    "\n",
    "Arma la ruta al archivo de ventas de agosto y muestra su\n",
    "nombre sin extensión."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "ruta = Path('ventas') / '2026' / 'agosto.csv'\n",
    "print(ruta, '|', ruta.stem, '|', ruta.suffix)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ventas/2026/agosto.csv | agosto | .csv\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Estadística sin instalar nada\n",
    "\n",
    "Con el módulo `statistics`, saca media y mediana\n",
    "de unos montos y explica por qué difieren."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import statistics as stats\n",
    "\n",
    "montos = [120, 150, 180, 200, 5000]\n",
    "\n",
    "print(stats.mean(montos))\n",
    "print(stats.median(montos))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "1130\n",
    "180\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La media se fue a 1.130 por culpa de un solo valor de 5.000. La mediana ni se\n",
    "inmutó. Por eso con dinero casi siempre se reporta la mediana, y eso lo\n",
    "desarrollo en el [libro de\n",
    "estadística](https://missyera.com/guias/estadistica-desde-cero/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Buscar un patrón en texto\n",
    "\n",
    "Con el módulo `re`, saca todos los códigos de\n",
    "cliente de un texto libre. Los códigos son una C y cuatro dígitos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "nota = 'Revisar C0045 y C0325, tambien el pedido de C0260 de ayer'\n",
    "print(re.findall(r'C\\d{4}', nota))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "['C0045', 'C0325', 'C0260']\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `\\d{4}` significa \"cuatro dígitos\". Las expresiones regulares\n",
    "dan para un libro entero, pero con `findall` y dos patrones simples ya\n",
    "resuelves muchísimo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Qué hay dentro de un módulo\n",
    "\n",
    "Antes de buscar en internet cómo se llama una función,\n",
    "pregúntale al propio módulo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import statistics\n",
    "\n",
    "publicas = [n for n in dir(statistics) if not n.startswith('_')]\n",
    "print('cuantas cosas trae:', len(publicas))\n",
    "print('las primeras diez  :', publicas[:10])\n",
    "print()\n",
    "print('y la ayuda de una  :')\n",
    "print(statistics.median.__doc__.strip().splitlines()[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "cuantas cosas trae: 44\n",
    "las primeras diez  : ['Counter', 'Decimal', 'Fraction', 'LinearRegression', 'NormalDist', 'StatisticsError', 'bisect_left', 'bisect_right', 'correlation', 'covariance']\n",
    "\n",
    "y la ayuda de una  :\n",
    "Return the median (middle value) of numeric data.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`dir()` lista lo que hay dentro y `__doc__` trae la\n",
    "explicación que escribió quien lo programó 📚\n",
    "\n",
    "Esto funciona con cualquier cosa, incluida una librería que acabas de\n",
    "instalar y de la que no tienes ni idea. Y funciona sin internet, que el día que\n",
    "estés en un avión lo vas a agradecer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Un módulo que sabe si lo están ejecutando\n",
    "\n",
    "El `if __name__ == '__main__'` que ves en todos\n",
    "lados y que casi nadie explica."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import subprocess\n",
    "import sys\n",
    "import tempfile\n",
    "\n",
    "lineas = [\n",
    "    \"def igv(monto):\",\n",
    "    \"    return round(monto * 1.18, 2)\",\n",
    "    \"\",\n",
    "    \"print('esta linea corre siempre que alguien me importe')\",\n",
    "    \"\",\n",
    "    \"if __name__ == '__main__':\",\n",
    "    \"    print('y esta solo si me ejecutan directamente')\",\n",
    "    \"    print('120 con IGV:', igv(120))\",\n",
    "]\n",
    "\n",
    "carpeta = tempfile.mkdtemp()\n",
    "ruta = os.path.join(carpeta, 'impuestos.py')\n",
    "open(ruta, 'w', encoding='utf-8').write('\\n'.join(lineas))\n",
    "\n",
    "print('--- ejecutandolo ---')\n",
    "print(subprocess.run([sys.executable, ruta], capture_output=True,\n",
    "                     text=True).stdout.rstrip())\n",
    "\n",
    "usa = os.path.join(carpeta, 'usa.py')\n",
    "open(usa, 'w', encoding='utf-8').write(\n",
    "    'import impuestos\\n'\n",
    "    'print(\"340 con IGV:\", impuestos.igv(340))\\n')\n",
    "\n",
    "print('--- importandolo ---')\n",
    "print(subprocess.run([sys.executable, usa], capture_output=True, text=True,\n",
    "                     cwd=carpeta).stdout.rstrip())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "--- ejecutandolo ---\n",
    "esta linea corre siempre que alguien me importe\n",
    "y esta solo si me ejecutan directamente\n",
    "120 con IGV: 141.6\n",
    "--- importandolo ---\n",
    "esta linea corre siempre que alguien me importe\n",
    "340 con IGV: 401.2\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está la diferencia entera 🎯\n",
    "\n",
    "Al ejecutar el archivo, Python le pone a `__name__` el valor\n",
    "`'__main__'`. Al importarlo, le pone el nombre del módulo. Por eso el\n",
    "bloque de abajo corre en un caso y en el otro no.\n",
    "\n",
    "Para qué sirve de verdad: para que un archivo pueda ser **las dos\n",
    "cosas**. Una caja de funciones que otros importan, y un programa que se\n",
    "ejecuta solo cuando quieres probarlo 🧰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El archivo con el nombre bonito que rompe el proyecto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya sabes cómo Python encuentra lo que importas. Con eso en la cabeza, mira este, que es de los que te dejan mirando la pantalla sin entender nada 🎭"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Guardas tus utilidades en un archivo con un nombre claro dentro de la carpeta del proyecto. Al día siguiente el proyecto entero deja de funcionar y no has tocado nada.\n",
    "\n",
    "```\n",
    "# tienes un archivo tuyo: random.py\n",
    "\n",
    "import random\n",
    "print(random.choice([1, 2, 3]))\n",
    "\n",
    "# AttributeError: module 'random'\n",
    "# has no attribute 'choice'\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Python busca los módulos empezando por **tu carpeta**, así que `import random` encuentra el tuyo antes que el de la librería estándar 🎭 Y no solo en tu archivo: en cualquier librería que también lo importe.\n",
    "\n",
    "Los nombres que más lo provocan son justo los que más apetece usar: `random.py`, `csv.py`, `json.py`, `email.py`, `test.py`. Y el error que sale no habla de nombres: habla de un atributo que falta, así que se busca donde no es.\n",
    "\n",
    "Antes de nombrar un archivo, comprueba que ese nombre no exista ya: `python -c \"import csv; print(csv.__file__)\"`. Si te contesta con una ruta del sistema, el nombre está cogido."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tu código corre en tu máquina y revienta en la de tu compañera. Lo primero que preguntas es:\n",
    "\n",
    "a) Qué versión de cada librería tiene instalada\n",
    "\n",
    "b) Si copió bien el archivo\n",
    "\n",
    "c) Si tiene Python instalado\n",
    "\n",
    "d) Si su computadora es más lenta\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Puede ser, pero si el error es de import, la pista es otra.\n",
    "\n",
    "*c)* Si no lo tuviera, no habría llegado a ejecutar nada.\n",
    "\n",
    "*d)* La velocidad no hace que un programa reviente.\n",
    "\n",
    "Un entorno virtual con sus versiones escritas evita esta conversación."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📄 Un módulo es un archivo .py, y el punto se lee como \"de dentro de\".\n",
    "\n",
    "- 🏷️ `import pandas as pd` y `import numpy as np` son\n",
    "convenciones: úsalas.\n",
    "\n",
    "- 🧰 Antes de instalar algo, mira si la librería estándar ya lo hace.\n",
    "\n",
    "- 📁 `pathlib` para rutas, siempre, y `.exists()` antes\n",
    "de leer.\n",
    "\n",
    "- 🧪 Un entorno virtual por proyecto, `requirements.txt` al repo y\n",
    "`.venv` al `.gitignore`.\n",
    "\n",
    "- ✍️ El cuaderno explora, el módulo guarda.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En mi máquina funcionaba no es una excusa. Es un entorno que nadie\n",
    "escribió."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 9 vemos objetos, y te adelanto que va a ser más corto de lo que\n",
    "temes: para datos hace falta entenderlos, no diseñarlos.\n",
    "\n",
    "Que tengas un hermoso día! 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 8 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/modulos-y-entornos/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
