{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Funciones\n",
    "\n",
    "Empaquetar trabajo con nombre, devolver resultados, y la trampa del argumento por defecto que muerde en producción.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 6 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/funciones/\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! El momento de dejar de copiar y pegar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si llegaste hasta aquí ya escribiste el mismo bloque de código dos o tres\n",
    "veces cambiando un número. A todas nos pasa 😅\n",
    "\n",
    "Y hazte esta pregunta cada vez que copies algo: **¿este bloque lo estoy pegando por segunda vez?** Si la respuesta es sí, ahí va una función. No conozco regla mejor que esa 💛\n",
    "\n",
    "Una función es **trabajo con nombre**. Lo escribes una vez, le\n",
    "pones un nombre, y lo usas todas las veces que quieras."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def con_igv(monto):\n",
    "    return round(monto * 1.18, 2)\n",
    "\n",
    "print(con_igv(100))\n",
    "print(con_igv(480.37))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres piezas y ya está: `def` para definirla, el nombre y los\n",
    "paréntesis con lo que recibe, y `return` para devolver el resultado.\n",
    "\n",
    "Y sí, la sangría manda igual que en el capítulo 5: lo que está dentro de la\n",
    "función lleva sus cuatro espacios.\n",
    "\n",
    "### return no es print\n",
    "\n",
    "Esta confusión la tiene todo el mundo al principio, así que veámosla de\n",
    "frente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def suma_mala(a, b):\n",
    "    print(a + b)\n",
    "\n",
    "def suma_buena(a, b):\n",
    "    return a + b\n",
    "\n",
    "resultado_malo = suma_mala(2, 3)\n",
    "resultado_bueno = suma_buena(2, 3)\n",
    "\n",
    "print('malo :', resultado_malo)\n",
    "print('bueno:', resultado_bueno)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las dos \"muestran\" un 5, pero solo una **devuelve** algo con lo\n",
    "que puedas seguir trabajando. La primera imprimió y se olvidó, así que\n",
    "`resultado_malo` quedó en `None`.\n",
    "\n",
    "La regla: **una función calcula y devuelve; quien la llama decide si\n",
    "imprime**. Si la función imprime por su cuenta, no la puedes reutilizar\n",
    "dentro de otro cálculo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Argumentos: por posición y por nombre"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def total_venta(unidades, precio, descuento):\n",
    "    bruto = unidades * precio\n",
    "    return round(bruto * (1 - descuento), 2)\n",
    "\n",
    "print(total_venta(10, 45.0, 0.15))\n",
    "print(total_venta(unidades=10, precio=45.0, descuento=0.15))\n",
    "print(total_venta(10, descuento=0.15, precio=45.0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las tres llamadas hacen lo mismo. Pero mira la primera y dime de memoria qué\n",
    "significa ese 0,15 sin subir a leer la definición 🤔\n",
    "\n",
    "Por eso, cuando una función recibe más de dos cosas, **llámala por\n",
    "nombre**. Tu yo del futuro te lo va a agradecer.\n",
    "\n",
    "Y el orden importa cuando mezclas: los posicionales van primero, siempre."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto ni siquiera compila, y de eso va.** Va como texto porque una celda que no compila no se puede ejecutar; pégalo en una celda si quieres verlo.\n",
    "\n",
    "```\n",
    "total_venta(unidades=10, 45.0, 0.15)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "SyntaxError: positional argument follows keyword argument\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Valores por defecto"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def total_venta(unidades, precio, descuento=0, igv=0.18):\n",
    "    bruto = unidades * precio * (1 - descuento)\n",
    "    return round(bruto * (1 + igv), 2)\n",
    "\n",
    "print(total_venta(10, 45.0))\n",
    "print(total_venta(10, 45.0, descuento=0.15))\n",
    "print(total_venta(10, 45.0, igv=0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que casi siempre vale igual se pone por defecto, y quien necesite otra cosa\n",
    "lo dice. Así la llamada normal queda cortita y la excepcional sigue siendo\n",
    "posible."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La trampa del argumento por defecto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta te la enseño ahora porque es de las que muerden en producción y casi\n",
    "nadie la explica en español. Presta atención un minuto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def agrega_venta(monto, historial=[]):\n",
    "    historial.append(monto)\n",
    "    return historial\n",
    "\n",
    "print(agrega_venta(100))\n",
    "print(agrega_venta(200))\n",
    "print(agrega_venta(300))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Ves lo que pasó? Cada llamada debería empezar con una lista vacía, y en vez\n",
    "de eso se van acumulando 😳\n",
    "\n",
    "La razón: **el valor por defecto se crea una sola vez, cuando se define\n",
    "la función**, no en cada llamada. Así que las tres llamadas están usando\n",
    "la misma lista, exactamente como el aliasing del capítulo 4.\n",
    "\n",
    "La solución es siempre la misma y se escribe así:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def agrega_venta(monto, historial=None):\n",
    "    if historial is None:\n",
    "        historial = []\n",
    "    historial.append(monto)\n",
    "    return historial\n",
    "\n",
    "print(agrega_venta(100))\n",
    "print(agrega_venta(200))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Regla dura: nunca pongas una lista, un diccionario ni un conjunto como\n",
    "valor por defecto.** Usa `None` y créalo dentro. Con números,\n",
    "textos y `True` o `False` no hay problema, porque esos no\n",
    "se pueden modificar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Devolver varias cosas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def resumen(montos):\n",
    "    return len(montos), sum(montos), sum(montos) / len(montos)\n",
    "\n",
    "cuantas, total, promedio = resumen([480.37, 154.83, 2480.84])\n",
    "\n",
    "print(cuantas)\n",
    "print(f'{total:,.2f}')\n",
    "print(f'{promedio:,.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por dentro devuelve una tupla, como vimos en el capítulo 4, y al recibirla la\n",
    "desempaquetas en tres variables. Es limpísimo.\n",
    "\n",
    "Cuando son más de tres cosas, mejor devuelve un diccionario: los nombres\n",
    "sobreviven y no dependes del orden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def resumen(montos):\n",
    "    return {\n",
    "        'cuantas': len(montos),\n",
    "        'total': sum(montos),\n",
    "        'promedio': sum(montos) / len(montos),\n",
    "        'maximo': max(montos),\n",
    "    }\n",
    "\n",
    "r = resumen([480.37, 154.83, 2480.84])\n",
    "print(r['promedio'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dónde vive cada variable"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que creas dentro de una función se muere cuando la función termina. Eso se\n",
    "llama alcance y es una protección, no un capricho."
   ]
  },
  {
   "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",
    "    def calcular():\n",
    "        interno = 42\n",
    "        return interno * 2\n",
    "\n",
    "    print(calcular())\n",
    "    print(interno)\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",
    "NameError: name 'interno' is not defined\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Al revés sí funciona: desde dentro se ve lo de fuera."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "IGV = 0.18          # constante, en mayusculas por costumbre\n",
    "\n",
    "def con_impuesto(monto):\n",
    "    return monto * (1 + IGV)\n",
    "\n",
    "print(con_impuesto(100))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, que se pueda no significa que se deba. **Una función que depende\n",
    "de variables de fuera es una función que solo funciona en tu cuaderno**.\n",
    "Para constantes de verdad como el IGV está bien; para datos, pásalos como\n",
    "argumento."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Docstrings: explicar para qué sirve"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un texto entre triples comillas justo debajo del `def` es la\n",
    "documentación de la función. No es adorno: `help()` la lee, tu editor\n",
    "la muestra, y en un equipo es la diferencia entre que usen tu función o la\n",
    "reescriban."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def clasifica_venta(monto):\n",
    "    \"\"\"\n",
    "    Devuelve la categoria comercial de una venta segun su monto.\n",
    "\n",
    "    Los cortes los fijo comercial en enero de 2026 y se revisan cada\n",
    "    semestre, asi que si cambian, se cambian aqui y en un solo sitio.\n",
    "    \"\"\"\n",
    "    if monto > 2000:\n",
    "        return 'Mayorista'\n",
    "    if monto > 500:\n",
    "        return 'Mediana'\n",
    "    return 'Pequeña'\n",
    "\n",
    "print(clasifica_venta(780))\n",
    "print(clasifica_venta.__doc__.strip().splitlines()[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en algo de esa función: como cada rama hace `return`, no\n",
    "hacen falta los `elif`. Al hacer `return` se sale de la\n",
    "función ahí mismo. Queda más plano y se lee mejor 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## lambda: funciones de una línea"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una `lambda` es una función chiquita y sin nombre. Se usa para\n",
    "pasarla como argumento a otra cosa, y ahí es donde brilla."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ventas = [\n",
    "    {'ciudad': 'Lima', 'monto': 480.37},\n",
    "    {'ciudad': 'Cusco', 'monto': 2480.84},\n",
    "    {'ciudad': 'Piura', 'monto': 154.83},\n",
    "]\n",
    "\n",
    "por_monto = sorted(ventas, key=lambda v: v['monto'], reverse=True)\n",
    "print([v['ciudad'] for v in por_monto])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `key=` le dice a `sorted` qué mirar de cada\n",
    "elemento para comparar. Sin la lambda tendrías que definir una función entera\n",
    "para algo de una línea.\n",
    "\n",
    "Mi consejo: **usa lambda solo para eso, para pasarla como\n",
    "argumento**. Si le vas a poner nombre, entonces era una función normal."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Una función de verdad, con todo junto"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def limpia_ciudad(texto, por_defecto='Sin ciudad'):\n",
    "    \"\"\"Normaliza el nombre de una ciudad que viene de un sistema externo.\"\"\"\n",
    "    if texto is None:\n",
    "        return por_defecto\n",
    "\n",
    "    limpio = texto.strip().title()\n",
    "    if limpio == '':\n",
    "        return por_defecto\n",
    "\n",
    "    equivalencias = {'Lma': 'Lima', 'Aqp': 'Arequipa', 'Cuzco': 'Cusco'}\n",
    "    return equivalencias.get(limpio, limpio)\n",
    "\n",
    "for entrada in ['  lima ', 'CUZCO', 'Aqp', '', None, 'Piura']:\n",
    "    print(f'{repr(entrada):10} -> {limpia_ciudad(entrada)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es una función lista para producción: tiene su docstring, maneja el nulo,\n",
    "maneja el vacío, tiene un valor por defecto que quien llama puede cambiar, y las\n",
    "equivalencias están en un solo sitio donde se pueden corregir.\n",
    "\n",
    "Y lo mejor: la puedes probar con seis entradas en dos segundos, que es\n",
    "exactamente lo que acabamos de hacer 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuando no sabes cuántos argumentos van a venir"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hay funciones a las que quieres pasarle tres cosas hoy y siete mañana. Para\n",
    "eso están el asterisco y el doble asterisco."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def resume(*montos, moneda='S/', decimales=2):\n",
    "    total = sum(montos)\n",
    "    return f'{len(montos)} ventas, total {moneda}{total:.{decimales}f}'\n",
    "\n",
    "print(resume(120.5, 340, 89.9))\n",
    "print(resume(120.5, 340, moneda='US$', decimales=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `*montos` recoge en una tupla todo lo que llegue por posición.\n",
    "Y lo que va después del asterisco, `moneda` y `decimales`,\n",
    "solo se puede pasar por nombre 🏷️\n",
    "\n",
    "Eso último no es un capricho de Python: es lo que evita que alguien escriba\n",
    "`resume(120.5, 340, 'US$')` y que ese `'US$'` se cuele en\n",
    "la suma.\n",
    "\n",
    "El doble asterisco hace lo mismo con los argumentos con nombre, y los recoge\n",
    "en un diccionario:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def registra(**datos):\n",
    "    return ', '.join(f'{k}={v}' for k, v in sorted(datos.items()))\n",
    "\n",
    "print(registra(cliente='Rosita', monto=340, canal='WhatsApp'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y lo mejor es que funciona al revés. Si ya tienes una lista o un diccionario,\n",
    "el asterisco los **desempaqueta** en la llamada:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ventas = [120.5, 340, 89.9]\n",
    "print('desempaquetando una lista:', resume(*ventas))\n",
    "\n",
    "config = {'moneda': 'US$', 'decimales': 0}\n",
    "print('y un diccionario:', resume(*ventas, **config))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto es lo que hace que puedas guardar la configuración en un diccionario, o\n",
    "leerla de un archivo, y pasársela a la función sin escribir cada parámetro a\n",
    "mano 📦\n",
    "\n",
    "Lo vas a ver muchísimo en las librerías. Cuando la documentación de pandas\n",
    "dice `**kwargs`, te está diciendo \"pásame lo que quieras, que yo se lo\n",
    "paso a quien corresponda\" 🐼"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Anotaciones: decir qué entra y qué sale"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El docstring explica para qué sirve la función. Las anotaciones dicen\n",
    "**de qué tipo** es cada cosa, y se escriben en la misma línea de la\n",
    "definición 🏷️"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def total_con_igv(monto: float, tasa: float = 0.18) -> float:\n",
    "    'Devuelve el monto con el IGV incluido, redondeado a dos decimales.'\n",
    "    return round(monto * (1 + tasa), 2)\n",
    "\n",
    "\n",
    "print(total_con_igv(480.37))\n",
    "print(total_con_igv.__annotations__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los dos puntos después de cada parámetro dicen qué tipo espera, y la flecha\n",
    "del final dice qué devuelve. Se quedan guardadas en\n",
    "`__annotations__`, así que no son un comentario: son datos.\n",
    "\n",
    "Para qué sirven de verdad 🎯\n",
    "\n",
    "- 👀 **Para quien lee.** Con la anotación no tienes que bajar a\n",
    "leer el cuerpo para saber si esa función devuelve un número o un texto.\n",
    "\n",
    "- 💡 **Para tu editor.** VS Code y PyCharm las leen y te avisan\n",
    "antes de ejecutar nada.\n",
    "\n",
    "- 📄 **Para las dataclass** del capítulo 9, que las\n",
    "usan de verdad para armar la clase.\n",
    "\n",
    "Y ahora lo importante, que es lo que casi nunca se dice:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def etiqueta(codigo: str) -> int:\n",
    "    return 'cliente ' + codigo\n",
    "\n",
    "\n",
    "print('promete un int y devuelve texto:', repr(etiqueta('C0045')))\n",
    "print('y Python no dijo absolutamente nada')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Python no comprueba las anotaciones.** Son documentación que se\n",
    "puede leer con código, y nada más 🤷‍♀️\n",
    "\n",
    "Esa función promete devolver un entero, devuelve un texto, y corre tan\n",
    "contenta. Si quieres que alguien las verifique de verdad hace falta una\n",
    "herramienta aparte, tipo `mypy`, que las lee y te avisa sin ejecutar el\n",
    "programa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    total_con_igv('480.37')\n",
    "except TypeError as e:\n",
    "    print('TypeError:', e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en dónde falla: en la multiplicación, no en la puerta 🚪\n",
    "\n",
    "La anotación decía `float` y le pasé un texto, y a Python le dio\n",
    "igual hasta que llegó el momento de multiplicar. Por eso el mensaje habla de\n",
    "secuencias y no de tu parámetro.\n",
    "\n",
    "Mi costumbre: anotar las funciones que van a usar otras personas, y no\n",
    "molestarme en los scripts de exploración. Es como el docstring, la pongo cuando\n",
    "la función deja de ser mía y pasa a ser de todos 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Tu primera función\n",
    "\n",
    "Escribe una función que reciba un monto y devuelva el\n",
    "descuento del 12%, redondeado a dos decimales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def descuento(monto):\n",
    "    return round(monto * 0.12, 2)\n",
    "\n",
    "print(descuento(480.37))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "57.64\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. return contra print\n",
    "\n",
    "Escribe la misma función con `print` en vez de\n",
    "`return` e intenta usar su resultado en otra cuenta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def descuento_print(monto):\n",
    "    print(round(monto * 0.12, 2))\n",
    "\n",
    "r = descuento_print(480.37)\n",
    "print(r)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "57.64\n",
    "None\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El 57,64 lo viste, pero no lo tienes. Y `None + algo` revienta con\n",
    "un TypeError, que es como se descubre este error en la práctica."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Con valor por defecto\n",
    "\n",
    "Haz que el porcentaje de descuento sea un argumento con 12%\n",
    "por defecto, y pruébalo con y sin él."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def descuento(monto, porcentaje=0.12):\n",
    "    return round(monto * porcentaje, 2)\n",
    "\n",
    "print(descuento(1000))\n",
    "print(descuento(1000, porcentaje=0.25))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "120.0\n",
    "250.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Tres valores de vuelta\n",
    "\n",
    "Una función que reciba una lista de montos y devuelva el\n",
    "mínimo, el máximo y el rango."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def extremos(montos):\n",
    "    return min(montos), max(montos), max(montos) - min(montos)\n",
    "\n",
    "bajo, alto, rango = extremos([480.37, 154.83, 2480.84])\n",
    "print(bajo, alto, round(rango, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "154.83 2480.84 2326.01\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La trampa, provocada\n",
    "\n",
    "Escribe la función con la lista por defecto, llámala tres\n",
    "veces y explica la salida. Después arréglala."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mal(venta, carrito=[]):\n",
    "    carrito.append(venta)\n",
    "    return carrito\n",
    "\n",
    "print(mal('Abarrotes'), mal('Bebidas'), mal('Snacks'))\n",
    "\n",
    "def bien(venta, carrito=None):\n",
    "    if carrito is None:\n",
    "        carrito = []\n",
    "    carrito.append(venta)\n",
    "    return carrito\n",
    "\n",
    "print(bien('Abarrotes'), bien('Bebidas'), bien('Snacks'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "['Abarrotes', 'Bebidas', 'Snacks'] ['Abarrotes', 'Bebidas', 'Snacks'] ['Abarrotes', 'Bebidas', 'Snacks']\n",
    "['Abarrotes'] ['Bebidas'] ['Snacks']\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En la primera línea salen tres veces la misma lista porque los tres\n",
    "`return` devuelven el mismo objeto, ya con todo dentro."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Validar antes de calcular\n",
    "\n",
    "Una función que calcule el ticket promedio y devuelva 0 si\n",
    "la lista viene vacía, en vez de reventar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ticket_promedio(montos):\n",
    "    if not montos:\n",
    "        return 0\n",
    "    return round(sum(montos) / len(montos), 2)\n",
    "\n",
    "print(ticket_promedio([100, 200, 300]))\n",
    "print(ticket_promedio([]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "200.0\n",
    "0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sin ese `if` te sale un `ZeroDivisionError`. Y las listas\n",
    "vacías llegan más seguido de lo que uno cree: un filtro que no encontró nada ya\n",
    "te dejó una."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El alcance\n",
    "\n",
    "Comprueba que una variable creada dentro de una función no\n",
    "existe fuera."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def total_con_igv(monto):\n",
    "    igv = monto * 0.18          # solo existe aqui dentro\n",
    "    return monto + igv\n",
    "\n",
    "print(total_con_igv(100))\n",
    "print(igv)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "NameError: name 'igv' is not defined\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Ordenar con lambda\n",
    "\n",
    "Ordena una lista de clientes por su nombre sin que las\n",
    "mayúsculas alteren el orden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "clientes = ['bodega rosa', 'Abarrotes Luz', 'MINIMARKET SOL']\n",
    "\n",
    "print(sorted(clientes, key=lambda c: c.lower()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "['Abarrotes Luz', 'bodega rosa', 'MINIMARKET SOL']\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Una función que usa otra\n",
    "\n",
    "Escribe `con_igv` y `con_descuento`, y\n",
    "después una tercera que las combine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def con_igv(monto, igv=0.18):\n",
    "    return monto * (1 + igv)\n",
    "\n",
    "def con_descuento(monto, dcto=0.10):\n",
    "    return monto * (1 - dcto)\n",
    "\n",
    "def precio_final(base):\n",
    "    return round(con_igv(con_descuento(base)), 2)\n",
    "\n",
    "print(precio_final(1000))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "1062.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Funciones chiquitas que se combinan es exactamente la idea. Y de paso: el\n",
    "orden importa, porque el IGV se calcula sobre el monto ya descontado y no al\n",
    "revés."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Con su docstring\n",
    "\n",
    "Escribe una función que clasifique un puntaje de\n",
    "satisfacción del 1 al 5, con docstring, y muéstrala con `help`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def clasifica(puntaje):\n",
    "    \"\"\"Traduce un puntaje de 1 a 5 a una etiqueta de riesgo.\"\"\"\n",
    "    if puntaje >= 4:\n",
    "        return 'Satisfecho'\n",
    "    if puntaje == 3:\n",
    "        return 'Neutral'\n",
    "    return 'En riesgo'\n",
    "\n",
    "print(clasifica(5), clasifica(3), clasifica(1))\n",
    "print(clasifica.__doc__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Satisfecho Neutral En riesgo\n",
    "Traduce un puntaje de 1 a 5 a una etiqueta de riesgo.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Una función que recibe otra función\n",
    "\n",
    "En Python las funciones son valores, así que se pueden\n",
    "guardar en variables y pasar como argumento."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def aplica_a_todas(ventas, calculo):\n",
    "    return [calculo(v) for v in ventas]\n",
    "\n",
    "def con_igv(monto):\n",
    "    return round(monto * 1.18, 2)\n",
    "\n",
    "def redondea_a_soles(monto):\n",
    "    return round(monto)\n",
    "\n",
    "ventas = [120.5, 340.0, 89.9]\n",
    "print('con IGV   :', aplica_a_todas(ventas, con_igv))\n",
    "print('redondeado:', aplica_a_todas(ventas, redondea_a_soles))\n",
    "print('y con lambda:', aplica_a_todas(ventas, lambda m: m / 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "con IGV   : [142.19, 401.2, 106.08]\n",
    "redondeado: [120, 340, 90]\n",
    "y con lambda: [60.25, 170.0, 44.95]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en que `con_igv` va **sin paréntesis** al\n",
    "pasarla. Con paréntesis la llamarías; sin ellos la entregas 🎁\n",
    "\n",
    "Ese es exactamente el error del ejercicio 3 de este capítulo visto desde el\n",
    "otro lado, y es el mismo patrón que usas cada vez que escribes\n",
    "`sorted(lista, key=...)`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. La función que se llama a sí misma\n",
    "\n",
    "Recorrer un diccionario con diccionarios dentro, sin saber\n",
    "cuántos niveles tiene."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pedido = {\n",
    "    'cliente': 'Rosita',\n",
    "    'entrega': {'ciudad': 'Lima', 'distrito': 'Villa El Salvador'},\n",
    "    'items': {'abarrotes': {'arroz': 12, 'aceite': 4}, 'bebidas': {'gaseosa': 20}},\n",
    "}\n",
    "\n",
    "def aplana(d, prefijo=''):\n",
    "    salida = {}\n",
    "    for clave, valor in d.items():\n",
    "        nombre = f'{prefijo}{clave}'\n",
    "        if isinstance(valor, dict):\n",
    "            salida.update(aplana(valor, prefijo=f'{nombre}.'))\n",
    "        else:\n",
    "            salida[nombre] = valor\n",
    "    return salida\n",
    "\n",
    "for clave, valor in aplana(pedido).items():\n",
    "    print(f'{clave:28s} {valor}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "cliente                      Rosita\n",
    "entrega.ciudad               Lima\n",
    "entrega.distrito             Villa El Salvador\n",
    "items.abarrotes.arroz        12\n",
    "items.abarrotes.aceite       4\n",
    "items.bebidas.gaseosa        20\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una función que se llama a sí misma se llama recursiva, y da más miedo del\n",
    "que merece 👻\n",
    "\n",
    "Lo único que hay que asegurar es que en algún momento pare. Aquí para sola:\n",
    "cuando el valor deja de ser un diccionario, no vuelve a llamarse.\n",
    "\n",
    "Y este ejercicio concreto lo vas a necesitar de verdad. Los JSON que\n",
    "devuelven las APIs vienen anidados así, y para meterlos en una tabla hay que\n",
    "aplanarlos primero 🗜️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La función que limpia y ensucia a la vez"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Falta una sola cosa antes de cerrar funciones, y es la que más veces he visto acabar en una tarde entera de buscar dónde cambió el dato 🔎"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Escribes una función que limpia una lista de ciudades y devuelve el resultado. La usas, guardas lo que devuelve y sigues.\n",
    "\n",
    "```\n",
    "def limpiar(ciudades):\n",
    "    for i, c in enumerate(ciudades):\n",
    "        ciudades[i] = c.strip().lower()\n",
    "    return ciudades\n",
    "\n",
    "limpias = limpiar(originales)\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "La función modificó `originales` también 🪞 En Python los argumentos se pasan por referencia, así que dentro de la función `ciudades` y fuera `originales` son la misma lista. `limpias` no es una copia limpia: es otro nombre para lo mismo.\n",
    "\n",
    "Y como además devuelve la lista, todo aparenta que devolvió algo nuevo. Dos líneas más abajo comparas el antes con el después y salen idénticos, y te vuelves loca buscando por qué.\n",
    "\n",
    "Una función que devuelve algo **no debería modificar lo que recibe**. O modifica en el sitio y no devuelve nada, o construye una lista nueva y la devuelve. Las dos cosas a la vez es la receta de las tardes perdidas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Escribes `def guarda(dato, lista=[])` y al llamarla dos veces la lista trae lo de la llamada anterior. ¿Por qué?\n",
    "\n",
    "a) Porque el valor por defecto se crea una sola vez, al definir\n",
    "\n",
    "b) Porque falta un return\n",
    "\n",
    "c) Porque las listas son globales\n",
    "\n",
    "d) Porque hay que llamarla con lista=[] cada vez\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Con return o sin él, la lista sigue siendo la misma de antes.\n",
    "\n",
    "*c)* No lo son. Lo que pasa es cuándo se creó esta.\n",
    "\n",
    "*d)* Eso lo esquiva, pero la pregunta es por qué hace falta.\n",
    "\n",
    "Los valores por defecto se evalúan al definir la función, no al llamarla."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎁 Una función calcula y `return`; quien la llama decide si\n",
    "imprime.\n",
    "\n",
    "- 🏷️ Con más de dos argumentos, llámalos por nombre.\n",
    "\n",
    "- 💣 Nunca una lista o un diccionario como valor por defecto: usa\n",
    "`None`.\n",
    "\n",
    "- 📦 Varias cosas de vuelta se desempaquetan; si son muchas, devuelve un\n",
    "diccionario.\n",
    "\n",
    "- 📝 El docstring dice para qué sirve y de dónde salieron los números.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una función o devuelve algo o cambia algo. Las dos a la vez es la receta\n",
    "de las tardes perdidas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y esto de empaquetar lo que repites para no volver a escribirlo no es\n",
    "solo de Python: es lo mismo que hago con la IA para no repetir la misma\n",
    "instrucción cada semana, que lo conté en\n",
    "[skills de Claude](https://missyera.com/blog/skills-claude/) 🤖\n",
    "\n",
    "En el capítulo 7 vamos a lo que pasa cuando algo falla: los errores más\n",
    "comunes con datos, cómo atraparlos sin taparlos, y cómo depurar sin volverte\n",
    "loca.\n",
    "\n",
    "Que tengas un hermoso día! 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Cómo se hace una función en Python?Con def, el nombre, los paréntesis con lo que recibe y dos puntos. Lo que va dentro se escribe indentado, y return devuelve el resultado.\n",
    "\n",
    "¿Qué hace return en Python?Devuelve un valor y termina la función ahí mismo. Sin return la función devuelve None, que es el motivo número uno de que algo salga vacío sin dar error.\n",
    "\n",
    "¿Qué es una función lambda?Una función de una sola línea y sin nombre. Sirve para pasarla como argumento a otra cosa, y si la necesitas dos veces, mejor escríbela con def.\n",
    "\n",
    "¿Cuál es la diferencia entre parámetro y argumento?El parámetro es el nombre que la función usa por dentro; el argumento es el valor que le pasas al llamarla."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 6 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/funciones/\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
}
