{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Errores y depuración\n",
    "\n",
    "Los errores que de verdad salen con datos, cómo atraparlos sin taparlos, y cómo encontrar el problema sin volverte loca.\n",
    "\n",
    "Cuaderno de práctica del capítulo 7 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/errores-y-depuracion/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"VmFsdWVFcnJvcjogaW52YWxpZCBsaXRlcmFsIGZvciBpbnQoKSB3aXRoIGJhc2UgMTA6ICdzaW4gZGF0bycKVHlwZUVycm9yOiBjYW4gb25seSBjb25jYXRlbmF0ZSBzdHIgKG5vdCAiaW50IikgdG8gc3Ry\",\n",
    "    2: \"WzEwMC4wLCAyNTAuNSwgMTIwMC4wXSAzIGRlIDUgbW9udG9zIGxlZ2libGVz\",\n",
    "    3: \"VG90YWwgZGUgbGEgY2FqYSBkZWwgZMOtYTogMA==\",\n",
    "    4: \"eydXZWInOiAyLCAnU2luIGNhbmFsJzogMX0=\",\n",
    "    5: \"VmFsdWVFcnJvcjogTGFzIHVuaWRhZGVzIG5vIHB1ZWRlbiBzZXIgbmVnYXRpdmFzIHkgbGxlZ2Fyb24gLTMuIFNpIGVzIHVuYSBkZXZvbHVjacOzbiwgdmEgZW4gZWwgY2FtcG8gZGUgbm90YXMgZGUgY3LDqWRpdG8u\",\n",
    "    6: \"MCAwCjE1LjAgMTUuMA==\",\n",
    "    7: \"RmFsc2UKJ0MwMDQ1JyAnQzAwNDUgJwo1IDY=\",\n",
    "    8: \"eydubyBudW3DqXJpY28nOiAyLCAndmFjw61vJzogMX0=\",\n",
    "    9: \"ZmFsdG8gbGEgY2xhdmU6ICdjaXVkYWQnCiAgbGluZWEgIDEyICBkZW50cm8gZGUgIDxtb2R1bGU+CiAgbGluZWEgICA2ICBkZW50cm8gZGUgIGNpdWRhZGVzX2RlCiAgbGluZWEgICA2ICBkZW50cm8gZGUgIDxsaXN0Y29tcD4KICBsaW5lYSAgIDIgIGRlbnRybyBkZSAgY2l1ZGFkX2Rl\",\n",
    "    10: \"Y29uX2Zyb20KICAgY2F1c2EgZGVjbGFyYWRhOiBWYWx1ZUVycm9yKCJjb3VsZCBub3QgY29udmVydCBzdHJpbmcgdG8gZmxvYXQ6ICduL2EnIikKICAgY2F1c2EgZGUgZm9uZG8gOiBWYWx1ZUVycm9yKCJjb3VsZCBub3QgY29udmVydCBzdHJpbmcgdG8gZmxvYXQ6ICduL2EnIikKc2luX2Zyb20KICAgY2F1c2EgZGVjbGFyYWRhOiBOb25lCiAgIGNhdXNhIGRlIGZvbmRvIDogVmFsdWVFcnJvcigiY291bGQgbm90IGNvbnZlcnQgc3RyaW5nIHRvIGZsb2F0OiAnbi9hJyIp\",\n",
    "    11: \"bGVpZG9zOiBbNDgwLjM3LCAxMjAwLjBdCiAgIGZpbGEgMjogbm8gcHVlZG8gbGVlciAnbi9hJyBjb21vIG1vbnRvIHwgZmlsYSBndWFyZGFkYSBhcGFydGU6IDIKICAgZmlsYSA0OiBubyBwdWVkbyBsZWVyICcnIGNvbW8gbW9udG8gfCBmaWxhIGd1YXJkYWRhIGFwYXJ0ZTogNAp5IHNpZ3VlIHNpZW5kbyB1biBWYWx1ZUVycm9yOiBUcnVl\",\n",
    "    12: \"REVCVUcgICAgZmlsYSAxIGxlaWRhOiA0ODAuMzcKV0FSTklORyAgZmlsYSAyIGRlc2NhcnRhZGE6ICduL2EnCkRFQlVHICAgIGZpbGEgMyBsZWlkYTogMTIwMC4wCldBUk5JTkcgIGZpbGEgNCBkZXNjYXJ0YWRhOiAnJwpJTkZPICAgICAyIGRlIDQgZmlsYXMgbGVpZGFz\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Vamos a hacernos amigas de los errores"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Te lo dije en el capítulo 2 y lo repito porque importa: los errores no son un\n",
    "castigo, son información 💜\n",
    "\n",
    "Un error que revienta es **el mejor de los casos**. El peligroso\n",
    "es el que no revienta y te deja un número mal, que es del que va buena parte de\n",
    "este capítulo.\n",
    "\n",
    "Y para en seco un segundo antes de seguir: **¿alguna vez entregaste un número sin comprobar de dónde salía?** Todas lo hicimos alguna vez. Este capítulo va de que no vuelva a pasar sin que te enteres 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los seis errores que de verdad vas a ver"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Error | Qué significa | Cuándo aparece con datos |\n",
    "|---|---|---|\n",
    "| `TypeError` | Le pediste algo imposible a ese tipo | Sumar un texto con un número |\n",
    "| `ValueError` | El tipo está bien, el valor no | Convertir `'n/a'` a número |\n",
    "| `KeyError` | Esa clave no existe | Una columna que no vino en el archivo |\n",
    "| `IndexError` | Esa posición no existe | Una fila con menos campos de los esperados |\n",
    "| `FileNotFoundError` | No está el archivo | La ruta, siempre la ruta |\n",
    "| `ZeroDivisionError` | División entre cero | Un promedio de una lista vacía |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a verlos de verdad, que es como se aprenden:"
   ]
  },
  {
   "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",
    "    int('n/a')\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",
    "ValueError: invalid literal for int() with base 10: 'n/a'\n",
    "```"
   ]
  },
  {
   "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",
    "    venta = {'ciudad': 'Lima'}\n",
    "    venta['descuento']\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",
    "KeyError: 'descuento'\n",
    "```"
   ]
  },
  {
   "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",
    "    sum([]) / len([])\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",
    "ZeroDivisionError: division by zero\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en la diferencia entre los dos primeros. `int('12')`\n",
    "funciona, o sea que `int()` sí acepta textos: el tipo estaba bien y lo\n",
    "que estaba mal era el valor. Eso es un `ValueError`.\n",
    "\n",
    "En cambio `'12' + 5` es un `TypeError`, porque la\n",
    "operación misma no tiene sentido entre esos dos tipos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## try y except"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "valores = ['1250.50', '480,37', 'n/a', '95.5', '']\n",
    "\n",
    "limpios = []\n",
    "descartados = []\n",
    "\n",
    "for v in valores:\n",
    "    try:\n",
    "        limpios.append(float(v))\n",
    "    except ValueError:\n",
    "        descartados.append(v)\n",
    "\n",
    "print('limpios     :', limpios)\n",
    "print('descartados :', descartados)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso ya es código útil de verdad: procesa lo que puede y **guarda lo que\n",
    "no pudo** para mirarlo después.\n",
    "\n",
    "Y mira qué se ve ahí: `'480,37'` no era basura, era un número con\n",
    "coma decimal. Si lo hubiera botado sin mirar, habría perdido una venta buena. Por\n",
    "eso los descartes se guardan, no se ignoran 🔍"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for v in descartados:\n",
    "    print(repr(v), '->', float(v.replace(',', '.')) if v.replace(',', '').replace('.', '').isdigit() else 'sin arreglo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El anti-patrón que más daño hace"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto lo vas a ver en internet y en código de gente con años de experiencia. No\n",
    "lo copies."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "valores = ['1250.50', 'n/a', '95.5']\n",
    "total = 0\n",
    "\n",
    "for v in valores:\n",
    "    try:\n",
    "        total = total + float(v)\n",
    "    except:\n",
    "        pass\n",
    "\n",
    "print(total)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Funciona. Ese es justo el problema 😖\n",
    "\n",
    "Un `except` pelado se traga **todo**: el valor\n",
    "inválido que esperabas, pero también un error de tipeo tuyo, un problema de\n",
    "memoria, una interrupción con Ctrl+C. Y `pass` significa \"no hagas\n",
    "nada al respecto\".\n",
    "\n",
    "Entonces un día tu proceso devuelve un total que es la mitad del real y no hay\n",
    "ni una línea en ningún lado que diga qué pasó. Lo he visto costar semanas de\n",
    "confianza en un equipo de datos.\n",
    "\n",
    "Las dos reglas, y son duras:\n",
    "\n",
    "- 🎯 **Atrapa el error concreto** que esperas:\n",
    "`except ValueError`, no `except` a secas.\n",
    "\n",
    "- 📢 **Haz algo con él.** Guárdalo, cuéntalo, imprímelo. Nunca\n",
    "`pass`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "valores = ['1250.50', 'n/a', '95.5']\n",
    "total = 0\n",
    "problemas = []\n",
    "\n",
    "for v in valores:\n",
    "    try:\n",
    "        total = total + float(v)\n",
    "    except ValueError as e:\n",
    "        problemas.append((v, str(e)))\n",
    "\n",
    "print(f'Total: {total}')\n",
    "print(f'Problemas: {len(problemas)}')\n",
    "for valor, mensaje in problemas:\n",
    "    print(f'  {repr(valor)}: {mensaje}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El mismo total, pero ahora sabes que hubo un problema y cuál fue. Esa\n",
    "diferencia es la que separa un script de un proceso en el que se puede\n",
    "confiar 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## else y finally"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def leer_monto(texto):\n",
    "    try:\n",
    "        valor = float(texto)\n",
    "    except ValueError:\n",
    "        print(f'  {repr(texto)} no es un numero')\n",
    "        return None\n",
    "    else:\n",
    "        print(f'  {repr(texto)} convertido bien')\n",
    "        return valor\n",
    "    finally:\n",
    "        print('  (esto sale siempre)')\n",
    "\n",
    "print('Caso bueno:')\n",
    "leer_monto('480.37')\n",
    "print('Caso malo:')\n",
    "leer_monto('n/a')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- ✅ `else` corre solo si **no** hubo error.\n",
    "\n",
    "- 🔒 `finally` corre siempre, con error o sin él, incluso si hay un\n",
    "`return` en medio. Es donde se cierran archivos y conexiones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Levantar tus propios errores"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A veces el error no lo produce Python: lo produces tú, porque los datos no\n",
    "tienen sentido y seguir sería peor."
   ]
  },
  {
   "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 calcula_descuento(monto, porcentaje):\n",
    "        if not 0 <= porcentaje <= 1:\n",
    "            raise ValueError(\n",
    "                f'El porcentaje debe ir entre 0 y 1, y llegó {porcentaje}. '\n",
    "                f'Si viene como 15 en vez de 0.15, divídelo entre 100.')\n",
    "        return round(monto * porcentaje, 2)\n",
    "\n",
    "    print(calcula_descuento(1000, 0.15))\n",
    "    calcula_descuento(1000, 15)\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",
    "ValueError: El porcentaje debe ir entre 0 y 1, y llegó 15. Si viene como 15 en vez de 0.15, divídelo entre 100.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sin ese `raise`, el descuento de 15 habría dado S/15.000 sobre una\n",
    "venta de S/1.000. Habría corrido perfecto y el reporte habría salido absurdo.\n",
    "\n",
    "Un buen mensaje de error dice tres cosas: qué se esperaba, qué llegó, y qué\n",
    "hacer al respecto. Ese de arriba dice las tres 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cómo depurar sin volverte loca"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuando algo no funciona y no entiendes por qué, este es el orden que a mí me\n",
    "funciona.\n",
    "\n",
    "**1. Imprime el tipo antes que el valor.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dato = '1250.50'\n",
    "\n",
    "print(type(dato), repr(dato))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `repr` es clave: te muestra las comillas y los espacios\n",
    "invisibles. La mitad de los misterios se resuelven aquí.\n",
    "\n",
    "**2. Parte el problema en pedazos.** Si una línea larga falla,\n",
    "pártela y mira cada trozo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fila = '  C0045 ; Arequipa ; 480,37  '\n",
    "\n",
    "paso1 = fila.strip()\n",
    "paso2 = paso1.split(';')\n",
    "paso3 = [p.strip() for p in paso2]\n",
    "\n",
    "print(repr(paso1))\n",
    "print(paso2)\n",
    "print(paso3)\n",
    "print(float(paso3[2].replace(',', '.')))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí se ve clarito que después de partir por punto y coma quedaban espacios, y\n",
    "que por eso hacía falta el segundo `strip`.\n",
    "\n",
    "**3. Reduce el caso.** Si falla con tres mil filas, prueba con la\n",
    "primera. Casi siempre falla también, y depurar una fila es mil veces más rápido\n",
    "que depurar tres mil.\n",
    "\n",
    "**4. Lee el traceback de abajo hacia arriba** y busca la última\n",
    "línea que menciona tu código. Eso ya lo tienes del capítulo 2.\n",
    "\n",
    "**5. Cuando nada funcione, explícaselo en voz alta a alguien.**\n",
    "Te vas a dar cuenta sola a mitad de la explicación. A mí me pasa siempre, y no\n",
    "tengo idea de por qué funciona tan bien 😄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La traza es un mapa, no un castigo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Arriba te dije \"lee el traceback de abajo hacia arriba\". Ahora te lo enseño\n",
    "con uno de verdad, porque cuando hay funciones llamando a funciones el mapa tiene\n",
    "varias paradas 🗺️"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import traceback\n",
    "\n",
    "\n",
    "def limpia_monto(texto):\n",
    "    return float(texto.replace(',', '.'))\n",
    "\n",
    "\n",
    "def total_de(filas):\n",
    "    return sum(limpia_monto(f['monto']) for f in filas)\n",
    "\n",
    "\n",
    "filas = [{'monto': '480.37'}, {'monto': '524,90'}, {'monto': 'n/a'}]\n",
    "\n",
    "try:\n",
    "    total_de(filas)\n",
    "except ValueError as e:\n",
    "    print('el error  :', type(e).__name__)\n",
    "    print('el mensaje:', e)\n",
    "    print()\n",
    "    print('por donde paso, de fuera hacia dentro:')\n",
    "    for marco in traceback.extract_tb(e.__traceback__):\n",
    "        print(f'  linea {marco.lineno:>3}  dentro de  {marco.name}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es exactamente lo que te sale en rojo cuando algo revienta, solo que aquí\n",
    "lo pedí ordenadito con `traceback` 🔍\n",
    "\n",
    "Léelo así:\n",
    "\n",
    "- 🔻 **De arriba abajo es de fuera adentro.** Arriba está la línea\n",
    "que tú escribiste, abajo la línea que finalmente falló.\n",
    "\n",
    "- 🎯 **La última parada es dónde reventó.** Ahí está el bug si el\n",
    "código es tuyo.\n",
    "\n",
    "- 👀 **La primera parada tuya, contando desde abajo, es dónde\n",
    "mirar.** Cuando la última parada está dentro de pandas o de numpy, el\n",
    "problema casi nunca es de pandas: es lo que tú le pasaste.\n",
    "\n",
    "Y ese `<genexpr>` que aparece en medio no es un error tuyo: es\n",
    "la expresión generadora de dentro del `sum()`. Verás también\n",
    "`<listcomp>` por las comprensiones de lista y\n",
    "`<module>` por el nivel de arriba del todo, el del cuaderno."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que no dice qué fila"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira el mensaje de arriba otra vez: *could not convert string to float:\n",
    "'n/a'*. Te dice el valor. No te dice **cuál de las tres mil filas\n",
    "es**, y ese es el dato que necesitas para arreglarlo 😖\n",
    "\n",
    "La solución es atraparlo, añadirle lo que tú sí sabes, y volver a lanzarlo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def monto_de_la_fila(numero, texto):\n",
    "    try:\n",
    "        return float(texto.replace(',', '.'))\n",
    "    except ValueError as e:\n",
    "        raise ValueError(f'fila {numero}: el monto dice {texto!r}') from e\n",
    "\n",
    "\n",
    "try:\n",
    "    monto_de_la_fila(1847, 'n/a')\n",
    "except ValueError as e:\n",
    "    print('lo que ves tu   :', e)\n",
    "    print('lo que paso antes:', e.__cause__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí tienes las dos mitades 💡\n",
    "\n",
    "El mensaje de arriba es el útil, el que dice fila 1847. Y el de abajo, el\n",
    "original, no se pierde: queda enganchado en `__cause__` gracias al\n",
    "`from e`.\n",
    "\n",
    "Eso es lo que en la pantalla te sale como *The above exception was the\n",
    "direct cause of the following exception*, con las dos trazas una encima de\n",
    "otra. Que parece que fallaron dos cosas y no: es la misma, contada dos veces, la\n",
    "de fuera con más información.\n",
    "\n",
    "Si te olvidas del `from e`, Python engancha el original igual pero\n",
    "con otra etiqueta: *During handling of the above exception, another exception\n",
    "occurred*, que suena a \"y encima falló otra cosa\". Es la diferencia entre\n",
    "\"esto lo causó aquello\" y \"esto pasó mientras manejaba aquello\", y la primera es\n",
    "la verdad. Un carácter, dos lecturas distintas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Un procesador de archivo que aguanta datos feos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "filas = [\n",
    "    'C0045;Arequipa;480.37',\n",
    "    'C0325;Lima;524,90',\n",
    "    'C0260;Cusco;n/a',\n",
    "    'C0111;Piura',\n",
    "    'C0777;Trujillo;1200.00',\n",
    "]\n",
    "\n",
    "buenas = []\n",
    "rechazadas = []\n",
    "\n",
    "for numero, fila in enumerate(filas, start=1):\n",
    "    partes = fila.split(';')\n",
    "\n",
    "    if len(partes) != 3:\n",
    "        rechazadas.append((numero, fila, 'no tiene 3 campos'))\n",
    "        continue\n",
    "\n",
    "    codigo, ciudad, monto_texto = partes\n",
    "    try:\n",
    "        monto = float(monto_texto.replace(',', '.'))\n",
    "    except ValueError:\n",
    "        rechazadas.append((numero, fila, f'monto ilegible: {monto_texto}'))\n",
    "        continue\n",
    "\n",
    "    buenas.append({'codigo': codigo, 'ciudad': ciudad, 'monto': monto})\n",
    "\n",
    "print(f'{len(buenas)} filas buenas, {len(rechazadas)} rechazadas')\n",
    "print(f'Total: S/{sum(b[\"monto\"] for b in buenas):,.2f}')\n",
    "print()\n",
    "for numero, fila, motivo in rechazadas:\n",
    "    print(f'  fila {numero}: {motivo}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es lo que quiero que te lleves de este capítulo. No es que no falle: es\n",
    "que **cuando falla, te dice qué fila y por qué**, y sigue con las\n",
    "demás.\n",
    "\n",
    "Ese informe de rechazos es lo que le mandas a quien te pasó el archivo. Y es\n",
    "la diferencia entre \"tu archivo tiene errores\" y \"las filas 3 y 4 tienen esto,\n",
    "¿me las puedes corregir?\" 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Reconoce el error\n",
    "\n",
    "Provoca un `ValueError` y un\n",
    "`TypeError`, y explica en qué se diferencian."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Convertir sin morir\n",
    "\n",
    "Convierte una lista de textos a número, saltándote los que\n",
    "no se puedan, y cuenta cuántos se salvaron."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El except pelado, y por qué no\n",
    "\n",
    "Escribe un `except` vacío que tape un error de\n",
    "tipeo tuyo, para ver lo fácil que es."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Clave que puede no venir\n",
    "\n",
    "Recorre una lista de ventas donde algunas no traen\n",
    "`canal` y arma el conteo por canal sin que reviente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Tu propio raise\n",
    "\n",
    "Escribe una función que rechace unidades negativas con un\n",
    "mensaje que explique qué hacer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. La división que a veces es cero\n",
    "\n",
    "Calcula el promedio de una lista que puede venir vacía, de\n",
    "dos formas: con `try` y con `if`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Depurar con repr\n",
    "\n",
    "Tienes dos códigos de cliente que parecen iguales y no lo\n",
    "son. Encuentra la diferencia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Contar los rechazos por motivo\n",
    "\n",
    "Procesa una lista de filas y arma un conteo de rechazos\n",
    "agrupado por motivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. La traza de una columna que no vino\n",
    "\n",
    "Provoca un `KeyError` a dos niveles de\n",
    "profundidad y lee el mapa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 9\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Con `from` y sin `from`\n",
    "\n",
    "Lanza el mismo error de las dos maneras y mira qué guarda\n",
    "Python en cada caso."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 10\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Un error con tu nombre y tus datos\n",
    "\n",
    "Crea tu propio tipo de error, que además guarde el número de\n",
    "fila para poder usarlo después."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 11\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. Dejar rastro con `logging`\n",
    "\n",
    "Sustituye los `print` de depuración por un\n",
    "registro con niveles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 12\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuando atrapar el error es peor que no atraparlo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo este capítulo va de no dejar que las cosas revienten en silencio. Así que toca mirar el otro lado: el `try` escrito con las mejores intenciones que termina restando dinero de un total 🕳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Atrapas el error concreto, como manda el capítulo, y devuelves None para que el programa no se caiga. Es lo prudente.\n",
    "\n",
    "```\n",
    "def a_numero(texto):\n",
    "    try:\n",
    "        return float(texto)\n",
    "    except ValueError:\n",
    "        return None\n",
    "\n",
    "montos = [a_numero(x) for x in columna]\n",
    "print(sum(m for m in montos if m))\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Envuelves cien líneas en un `try` con un `except` pelado y ya no falla nada. ¿Está bien?\n",
    "\n",
    "a) No: ahora los errores pasan igual y nadie se entera\n",
    "\n",
    "b) Sí, mientras el programa no se caiga\n",
    "\n",
    "c) Sí, si además imprimes un mensaje\n",
    "\n",
    "d) No, porque try es lento"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎯 Atrapa el error concreto, nunca `except` a secas.\n",
    "\n",
    "- 📢 Nunca `pass` sin dejar rastro: cuenta, guarda o imprime.\n",
    "\n",
    "- 🛑 Un `raise` tuyo, a tiempo, evita un reporte absurdo.\n",
    "\n",
    "- 🔍 `repr()` y `type()` resuelven la mitad de los\n",
    "misterios.\n",
    "\n",
    "- 📋 Un buen proceso no es el que no falla, es el que dice qué fila falló y por\n",
    "qué.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si no sabes qué hacer con un error, no lo atrapes. Que reviente y te\n",
    "obligue a decidir."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y este capítulo importa más de lo que parece por lo que viene después: un\n",
    "total al que le faltan filas en silencio es exactamente lo que termina\n",
    "entrenando un modelo torcido, y de eso va el\n",
    "[libro de machine learning](https://missyera.com/guias/machine-learning-desde-cero/) 🤖\n",
    "\n",
    "En el capítulo 8 salimos del cuaderno: módulos, `import`, y los\n",
    "entornos virtuales, que son la respuesta a \"en mi máquina funcionaba\".\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Cómo funciona try except en Python?Lo que va en try se intenta; si revienta, se ejecuta el except en vez de cortar el programa. Conviene atrapar el error concreto y no todos, porque un except pelado se traga también los errores tuyos.\n",
    "\n",
    "¿Qué es un traceback?La lista de por dónde pasó el programa antes de reventar. Se lee de abajo hacia arriba: la última línea dice qué pasó y la penúltima dónde.\n",
    "\n",
    "¿Qué significa TypeError en Python?Que le pediste a un tipo de dato algo que no sabe hacer, como sumar un texto con un número. Suele venir de un CSV donde una columna vino como texto.\n",
    "\n",
    "¿Qué significa KeyError?Que pediste una clave que no está en el diccionario. Casi siempre es un nombre de columna con un espacio de más o en otra caja."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 7 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/errores-y-depuracion/\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
}
