{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Leer y escribir archivos\n",
    "\n",
    "Abrir lo que te mandan sin romperlo, y por qué a veces ves \"Ã¡\" donde debería haber una á.\n",
    "\n",
    "Cuaderno de práctica del capítulo 10 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/archivos/\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"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# Este capitulo abre el archivo por su nombre, sin URL, que es lo que se hace\n",
    "# cuando lo tienes al lado. Aqui se baja primero para que exista.\n",
    "for nombre in ['ventas-miss-yera.csv']:\n",
    "    urllib.request.urlretrieve(f\"https://missyera.com/static/datasets/{nombre}\", nombre)\n",
    "    print(nombre, \"lista\")"
   ]
  },
  {
   "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: \"WydMaW1hJywgJ0N1c2NvJywgJ1BpdXJhJ10gMw==\",\n",
    "    2: \"J2NhamEgZGVsIG1hcnRlc1xuJwonY2FqYSBkZWwgbWFydGVzXG5jYWphIGRlbCBtacOpcmNvbGVzXG4n\",\n",
    "    3: \"Q2HDg8KxZXRlIHkgSm9zw4PCqQ==\",\n",
    "    4: \"KCd1dGYtOCcsICdDYcOxZXRlJykKKCdsYXRpbi0xJywgJ0Nhw7FldGUnKQ==\",\n",
    "    5: \"MyBbJ2lkJywgJ2RpcmVjY2lvbicsICdtb250byddCjMgWycxJywgJ0F2LiBMYXJjbyAxMjMsIE1pcmFmbG9yZXMnLCAnNDgwLjM3J10=\",\n",
    "    6: \"NDgwLjM3\",\n",
    "    7: \"eyJjaXVkYWQiOiAiXHUwMGQxYVx1MDBmMWEifQp7ImNpdWRhZCI6ICLDkWHDsWEifQ==\",\n",
    "    8: \"MTAwMA==\",\n",
    "    9: \"ZXNjcml0byBlbiByZXN1bWVuLTIwMjYtMDktMTUuY3N2CnBlc2EgNzMgYnl0ZXMKCnNlZ21lbnRvLHZlbnRhcyx0b3RhbApCb2RlZ2EsNzA3LDEyNjM0MC41Ck1heW9yaXN0YSw3NTAsMTM5MjI1Mi43NQ==\",\n",
    "    10: \"c3VtYWRhcyAwIGZpbGFzLCB0b3RhbCAw\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Vamos por lo que te mandan de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hasta ahora los datos los escribíamos nosotras dentro del código. En el\n",
    "trabajo real te llegan en un archivo, casi siempre por correo, casi siempre con\n",
    "algún problema 😅\n",
    "\n",
    "Este capítulo va de abrirlos sin romperlos. Y de la codificación, que es el\n",
    "dolor número uno de trabajar con datos en español y que casi nadie explica\n",
    "bien.\n",
    "\n",
    "Y dime si te suena esto: **¿abriste alguna vez un archivo y viste Ã¡ donde tenía que ir una á?** Eso tiene nombre, tiene causa y tiene arreglo, y los tres están en este capítulo 🔤"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## with open, y por qué siempre con with"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tempfile\n",
    "from pathlib import Path\n",
    "\n",
    "# Uso una carpeta temporal para no ensuciar la tuya\n",
    "carpeta = Path(tempfile.mkdtemp())\n",
    "archivo = carpeta / 'notas.txt'\n",
    "\n",
    "with open(archivo, 'w', encoding='utf-8') as f:\n",
    "    f.write('Primera línea\\n')\n",
    "    f.write('Segunda línea con tildes: áéíóú ñ\\n')\n",
    "\n",
    "with open(archivo, encoding='utf-8') as f:\n",
    "    contenido = f.read()\n",
    "\n",
    "print(contenido)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres cosas de ahí que valen para siempre:\n",
    "\n",
    "- 🔒 **`with`** cierra el archivo solo, aunque el\n",
    "código de dentro reviente. Sin él tienes que acordarte de cerrarlo, y no te vas\n",
    "a acordar.\n",
    "\n",
    "- ✍️ El modo: `'r'` leer (es el que viene por defecto),\n",
    "`'w'` escribir **borrando lo que había**,\n",
    "`'a'` agregar al final.\n",
    "\n",
    "- 🌍 **`encoding='utf-8'` siempre**, al leer y al\n",
    "escribir. En un minuto te explico por qué.\n",
    "\n",
    "Ese `'w'` borra sin preguntar, así que ten cuidadito. Si querías\n",
    "agregar y pusiste `'w'`, te quedaste sin lo anterior 😬\n",
    "\n",
    "Para leer línea por línea, que es lo que haces con archivos grandes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(archivo, encoding='utf-8') as f:\n",
    "    for numero, linea in enumerate(f, start=1):\n",
    "        print(numero, repr(linea))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en el `\\n` al final de cada línea: viene incluido. Por eso\n",
    "casi siempre se hace `linea.strip()` antes de usarla.\n",
    "\n",
    "Y esta forma de leer tiene una ventaja enorme: **no carga el archivo\n",
    "entero en memoria**. Con un archivo de dos gigas, `f.read()` te\n",
    "tumba la máquina y el bucle no."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La codificación, o por qué ves \"Ã¡\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta es la parte más importante del capítulo. Presta atención dos minutos y te\n",
    "ahorras años de misterio 💜\n",
    "\n",
    "Un archivo de texto no guarda letras, guarda números. La\n",
    "**codificación** es la tabla que dice qué número es cada letra.\n",
    "\n",
    "Hay dos que te vas a encontrar:\n",
    "\n",
    "- 🌍 **UTF-8.** El estándar de hoy. Le entran todos los idiomas.\n",
    "Es lo que usa Python por defecto y lo que debes usar siempre que puedas.\n",
    "\n",
    "- 🗄️ **latin-1** (también llamado ISO-8859-1 o cp1252). El de\n",
    "Windows en español, de toda la vida. Sigue saliendo de sistemas antiguos y de\n",
    "algunos Excel.\n",
    "\n",
    "Y ahora mira lo que pasa cuando se confunden, que es exactamente lo que has\n",
    "visto mil veces sin saber por qué:"
   ]
  },
  {
   "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",
    "    texto = 'Cañete, Trujillo, Ancón'\n",
    "\n",
    "    # Alguien lo guardo con latin-1\n",
    "    bytes_latin = texto.encode('latin-1')\n",
    "\n",
    "    # Y tu lo lees como si fuera utf-8\n",
    "    print(bytes_latin.decode('utf-8'))\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",
    "UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 2: invalid continuation byte\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese es el error que revienta. Pero hay un caso peor, el que **no**\n",
    "revienta:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Guardado en utf-8 y leido como latin-1: no falla, sale mal\n",
    "bytes_utf = texto.encode('utf-8')\n",
    "print(bytes_utf.decode('latin-1'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está 😱 Eso tiene nombre y se llama **mojibake**. El archivo\n",
    "se lee sin un solo error y las tildes salen convertidas en pares de símbolos\n",
    "raros.\n",
    "\n",
    "La regla para reconocerlo: **si ves \"Ã\" seguida de otro símbolo, es\n",
    "UTF-8 leído como latin-1**. Y se arregla leyendo con la codificación\n",
    "correcta, no reemplazando caracteres a mano.\n",
    "\n",
    "Cuando no sepas cuál es, este orden funciona casi siempre:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "datos = texto.encode('latin-1')\n",
    "\n",
    "for codificacion in ['utf-8', 'latin-1']:\n",
    "    try:\n",
    "        print(codificacion, '->', datos.decode(codificacion))\n",
    "        break\n",
    "    except UnicodeDecodeError:\n",
    "        print(codificacion, '-> no')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Primero UTF-8, y si revienta, latin-1. En ese orden, porque latin-1 casi nunca\n",
    "falla (acepta cualquier byte) y por eso probarlo primero te esconde el\n",
    "problema."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CSV sin pandas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sí, pandas lo hace más fácil, y aun así vale la pena conocer el módulo\n",
    "`csv`: es de la librería estándar, no carga el archivo entero en\n",
    "memoria y no se confunde con las comas dentro de un campo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import csv\n",
    "\n",
    "ruta = carpeta / 'ventas.csv'\n",
    "filas = [\n",
    "    ['cliente', 'ciudad', 'monto'],\n",
    "    ['C0045', 'Trujillo', '480.37'],\n",
    "    ['C0325', 'Lima, Miraflores', '524.90'],\n",
    "]\n",
    "\n",
    "with open(ruta, 'w', newline='', encoding='utf-8') as f:\n",
    "    csv.writer(f).writerows(filas)\n",
    "\n",
    "print(ruta.read_text(encoding='utf-8'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la última línea: como la ciudad tenía una coma dentro, el módulo la puso\n",
    "entre comillas sola. Si hubieras armado el CSV pegando textos con\n",
    "`','.join()`, esa fila habría quedado con cuatro campos en vez de\n",
    "tres y nadie te habría avisado 🙃\n",
    "\n",
    "Ese `newline=''` parece un detalle raro y hace falta: sin él, en\n",
    "Windows te salen líneas en blanco entre fila y fila.\n",
    "\n",
    "Para leer, lo mejor es `DictReader`, que te da cada fila como\n",
    "diccionario usando la cabecera:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(ruta, encoding='utf-8') as f:\n",
    "    for fila in csv.DictReader(f):\n",
    "        print(fila['ciudad'], '->', float(fila['monto']) * 1.18)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pides por nombre de columna y no por posición, así que si mañana cambian el\n",
    "orden de las columnas tu código sigue funcionando 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Excel"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para leer Excel hace falta instalar `openpyxl`\n",
    "(`pip install openpyxl`). Después es directo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openpyxl import Workbook, load_workbook\n",
    "\n",
    "libro = Workbook()\n",
    "hoja = libro.active\n",
    "hoja.title = 'Ventas'\n",
    "\n",
    "hoja.append(['cliente', 'ciudad', 'monto'])\n",
    "hoja.append(['C0045', 'Trujillo', 480.37])\n",
    "hoja.append(['C0325', 'Lima', 524.90])\n",
    "\n",
    "ruta_xlsx = carpeta / 'ventas.xlsx'\n",
    "libro.save(ruta_xlsx)\n",
    "\n",
    "leido = load_workbook(ruta_xlsx)\n",
    "print(leido.sheetnames)\n",
    "\n",
    "for fila in leido['Ventas'].iter_rows(min_row=2, values_only=True):\n",
    "    print(fila)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `min_row=2` se salta la cabecera y `values_only=True`\n",
    "te da los valores en vez de los objetos de celda, que es lo que quieres el 99%\n",
    "de las veces.\n",
    "\n",
    "Dos avisos sobre Excel, aprendidos a la mala:\n",
    "\n",
    "- 🧮 **Las fórmulas no vienen calculadas.** Por defecto lees la\n",
    "fórmula (`=A2*1.18`) y no su resultado. Con\n",
    "`load_workbook(ruta, data_only=True)` lees el último valor calculado,\n",
    "que existe solo si el archivo se abrió en Excel alguna vez.\n",
    "\n",
    "- 📅 **Las fechas vienen como fechas** si la celda tenía formato\n",
    "de fecha, y como texto si no. Siempre revisa el tipo antes de confiar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## JSON"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "ventas = [\n",
    "    {'cliente': 'C0045', 'ciudad': 'Trujillo', 'monto': 480.37},\n",
    "    {'cliente': 'C0325', 'ciudad': 'Ñaña', 'monto': 524.90},\n",
    "]\n",
    "\n",
    "ruta_json = carpeta / 'ventas.json'\n",
    "with open(ruta_json, 'w', encoding='utf-8') as f:\n",
    "    json.dump(ventas, f, ensure_ascii=False, indent=2)\n",
    "\n",
    "print(ruta_json.read_text(encoding='utf-8'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `ensure_ascii=False` es el que hace que la ñ se escriba como ñ.\n",
    "Sin él sale `\"\\u00d1a\\u00f1a\"`, que es válido y es ilegible.\n",
    "\n",
    "Y las cuatro funciones de `json`, que se confunden siempre:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Función | Qué hace |\n",
    "|---|---|\n",
    "| `json.load(f)` | De archivo a Python |\n",
    "| `json.loads(texto)` | De texto a Python |\n",
    "| `json.dump(obj, f)` | De Python a archivo |\n",
    "| `json.dumps(obj)` | De Python a texto |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La `s` del final es de \"string\". Con eso ya no las confundes 😄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Leer el archivo del libro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a leer el CSV de verdad con el que vamos a trabajar el resto del libro,\n",
    "todavía sin pandas:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "from urllib.request import urlopen\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "\n",
    "with urlopen(URL) as respuesta:\n",
    "    texto = respuesta.read().decode('utf-8')\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si ya lo descargaste, que es lo que vas a hacer casi siempre, se lee igual\n",
    "pero con `open` y la ruta de tu computadora:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import csv\n",
    "\n",
    "ARCHIVO = 'ventas-miss-yera.csv'      # descargado al lado del cuaderno\n",
    "\n",
    "with open(ARCHIVO, encoding='utf-8') as f:\n",
    "    texto = f.read()\n",
    "\n",
    "lineas = texto.splitlines()\n",
    "print('filas (con cabecera):', len(lineas))\n",
    "print(lineas[0])\n",
    "\n",
    "lector = csv.DictReader(lineas)\n",
    "primera = next(lector)\n",
    "print(primera['ciudad'], primera['monto'], primera['compro'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres mil treinta y ocho líneas contando la cabecera, o sea 3.037 ventas. Y\n",
    "fíjate en algo: `primera['monto']` devolvió `'480.37'`\n",
    "**como texto**, porque un CSV no guarda tipos. Todo lo que sale de\n",
    "ahí es texto hasta que tú lo conviertas.\n",
    "\n",
    "Eso es exactamente lo que pandas te va a hacer solo en el capítulo 12, y ahora\n",
    "ya sabes que no es magia 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Escribir, que es la otra mitad del trabajo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo el capítulo ha ido de leer. Pero un análisis que no sale a ningún sitio\n",
    "no le sirve a nadie, y casi siempre sale a un CSV que alguien va a abrir en\n",
    "Excel 📤"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import csv\n",
    "import os\n",
    "import tempfile\n",
    "\n",
    "carpeta = tempfile.mkdtemp()\n",
    "ruta = os.path.join(carpeta, 'resumen.csv')\n",
    "\n",
    "filas = [{'segmento': 'Bodega', 'ventas': 707, 'total': 126340.5},\n",
    "         {'segmento': 'Mayorista', 'ventas': 750, 'total': 1392252.75}]\n",
    "\n",
    "with open(ruta, 'w', newline='', encoding='utf-8') as f:\n",
    "    escritor = csv.DictWriter(f, fieldnames=['segmento', 'ventas', 'total'])\n",
    "    escritor.writeheader()\n",
    "    escritor.writerows(filas)\n",
    "\n",
    "print(open(ruta, encoding='utf-8').read().rstrip())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres cosas de ese bloque que no son opcionales 🔧\n",
    "\n",
    "**El `newline=''`** es el que más gente pilla. El\n",
    "módulo `csv` ya escribe su propio salto de línea, así que si no le\n",
    "dices esto, en Windows te salen filas vacías entre medio. En Linux y Mac no se\n",
    "nota, y por eso el archivo llega roto justo cuando lo abre otra persona.\n",
    "\n",
    "**El `'w'`** borra el archivo entero antes de\n",
    "escribir. Si querías añadir al final, es `'a'`.\n",
    "\n",
    "**Y el `encoding='utf-8'`**, que es el mismo cuidado\n",
    "de la sección de arriba pero en la otra dirección. Un archivo escrito sin\n",
    "declarar codificación es el mojibake de otra persona 🔤\n",
    "\n",
    "Para Excel hay un detalle más: Excel espera `utf-8-sig`, que es\n",
    "UTF-8 con una marca invisible al principio. Con `utf-8` a secas, los\n",
    "acentos se ven mal al abrirlo con doble clic 🪟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Archivos que no caben en memoria"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un CSV de dos giga no se abre con `pd.read_csv` en un portátil\n",
    "normal. Pero casi nunca hace falta tenerlo entero: hace falta recorrerlo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grande = os.path.join(carpeta, 'grande.csv')\n",
    "with open(grande, 'w', encoding='utf-8') as f:\n",
    "    f.write('monto\\\\n')\n",
    "    for i in range(100000):\n",
    "        f.write(f'{i % 500}\\\\n')\n",
    "\n",
    "total = 0\n",
    "lineas = 0\n",
    "with open(grande, encoding='utf-8') as f:\n",
    "    next(f)                       # saltarse la cabecera\n",
    "    for linea in f:\n",
    "        total += int(linea)\n",
    "        lineas += 1\n",
    "\n",
    "print(f'{lineas} lineas sumadas, total {total}')\n",
    "print('el archivo pesa', os.path.getsize(grande), 'bytes')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `for linea in f` es la clave y es una de las cosas más bonitas\n",
    "de Python: **no carga el archivo**. Va leyendo una línea, te la da,\n",
    "la suelta y pasa a la siguiente 🚿\n",
    "\n",
    "Por eso funciona igual con cien mil líneas que con cien millones. Lo que\n",
    "gasta memoria es `f.read()` o `f.readlines()`, que sí se\n",
    "lo traen todo.\n",
    "\n",
    "La versión de pandas de esta misma idea es\n",
    "`pd.read_csv(ruta, chunksize=100000)`, que te va entregando trozos\n",
    "del tamaño que le pidas. Misma idea, más cómoda 🐼"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Escribir y volver a leer\n",
    "\n",
    "Escribe tres líneas en un archivo y vuelve a leerlas\n",
    "contando cuántas son."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El modo que borra\n",
    "\n",
    "Escribe con `'w'` dos veces y comprueba que la\n",
    "segunda borró la primera. Después hazlo con `'a'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Reproduce el mojibake\n",
    "\n",
    "Toma el texto \"Cañete y José\" y muéstralo mal leído a\n",
    "propósito."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Detectar la codificación\n",
    "\n",
    "Escribe una función que pruebe UTF-8 y después latin-1, y\n",
    "devuelva cuál funcionó."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La coma dentro del campo\n",
    "\n",
    "Escribe un CSV donde un campo tenga una coma y comprueba que\n",
    "al leerlo siguen siendo tres columnas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. DictReader y un total\n",
    "\n",
    "Con el CSV anterior, suma los montos convirtiéndolos a\n",
    "número."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. JSON con tildes\n",
    "\n",
    "Guarda un diccionario con eñes en JSON, primero sin\n",
    "`ensure_ascii=False` y después con él."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Contar filas sin cargar el archivo\n",
    "\n",
    "Cuenta las líneas de un archivo recorriéndolo, sin usar\n",
    "`read()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Guardar el resultado con la fecha en el nombre\n",
    "\n",
    "Un informe que se sobreescribe cada vez es un informe que\n",
    "alguien va a perder. Ponle la fecha al archivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 9\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Contar sin cargar, y encontrar la fila rota\n",
    "\n",
    "Recorre un archivo con una fila mal formada y localízala\n",
    "sin que el programa se caiga."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 10\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La columna fantasma que aparece sola"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una que no rompe nada y que vas a reconocer al instante en cuanto la veas una vez, porque está en la mitad de los CSV que circulan por las empresas 👻"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Guardas el archivo limpio para mandárselo al equipo. Lo abres para comprobar y se ve bien.\n",
    "\n",
    "```\n",
    "datos.to_csv('ventas_limpias.csv')\n",
    "\n",
    "# y al releerlo al dia siguiente:\n",
    "d = pd.read_csv('ventas_limpias.csv')\n",
    "print(d.columns.tolist())\n",
    "# ['Unnamed: 0', 'id_cliente', 'ciudad']\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",
    "Abres un CSV con acentos y salen símbolos raros. ¿Qué falta?\n",
    "\n",
    "a) Decirle con qué codificación está escrito el archivo\n",
    "\n",
    "b) Abrirlo en modo binario\n",
    "\n",
    "c) Quitarle los acentos al archivo\n",
    "\n",
    "d) Usar pandas en vez de open"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🔒 `with open(...)` siempre, que cierra solo.\n",
    "\n",
    "- 🌍 `encoding='utf-8'` al leer y al escribir, siempre.\n",
    "\n",
    "- 👀 Si ves \"Ã\" seguida de un símbolo, es UTF-8 leído como latin-1.\n",
    "\n",
    "- 📄 Prueba UTF-8 primero y latin-1 después, en ese orden.\n",
    "\n",
    "- 🧾 Para CSV usa el módulo `csv`, que resuelve solo las comas\n",
    "dentro de un campo.\n",
    "\n",
    "- 🔤 Todo lo que sale de un CSV es texto hasta que tú lo conviertas.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un archivo sin codificación declarada funciona hasta que cambia de\n",
    "computadora."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y cuando ese archivo limpio ya no es para ti sino para que alguien lo\n",
    "mire, el siguiente paso suele ser un tablero. La\n",
    "[guía de Power BI](https://missyera.com/guias/power-bi-desde-cero/) arranca justo\n",
    "ahí, conectando el CSV que acabas de dejar decente 📈\n",
    "\n",
    "En el capítulo 11 entra NumPy, y con él la razón por la que un cálculo sobre\n",
    "un millón de números puede tardar un segundo o tardar dos minutos.\n",
    "\n",
    "Que tengas un hermoso día! 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 10 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/archivos/\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
}
