{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Que GitHub trabaje mientras duermes\n",
    "\n",
    "Actions explicado sin jerga: qué es, para qué sirve y cuándo no vale la pena\n",
    "\n",
    "Cuaderno de práctica del capítulo 31 de **Git 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/git-desde-cero/github-actions/\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": [
    "Este es el último ladrillo, y es el que convierte un repositorio en algo que\n",
    "trabaja solo. La idea en una frase: **cuando pase esto, corre esto** ⚙️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Para qué sirve de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Revisar que lo que alguien propone no rompa nada, antes de aceptarlo.\n",
    "\n",
    "- Publicar la web sola cada vez que subes, como en\n",
    "19.\n",
    "\n",
    "- Tareas de calendario: un reporte todos los lunes a las siete.\n",
    "\n",
    "- Comprobaciones aburridas que nadie se acuerda de hacer, como que ningún CSV\n",
    "llegue con la ciudad vacía.\n",
    "\n",
    "Y para qué no: si tu proyecto es una carpeta con dos scripts que corres tú\n",
    "una vez al mes, montar Actions es trabajo que no se paga 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El archivo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vive en una ruta exacta, `.github/workflows/`, y termina en\n",
    "`.yml`. Vamos a escribir uno que revise el CSV de ventas en cada\n",
    "push:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "mkdir reporte-ventas\n",
    "cd reporte-ventas\n",
    "git init -q\n",
    "printf 'ciudad,monto\\nLima,1200\\nArequipa,890\\n' > ventas.csv\n",
    "mkdir -p .github/workflows\n",
    "cat > .github/workflows/revisa.yml <<'FIN'\n",
    "name: Revisa el CSV de ventas\n",
    "\n",
    "on:\n",
    "  push:\n",
    "  pull_request:\n",
    "\n",
    "jobs:\n",
    "  revisar:\n",
    "    runs-on: ubuntu-latest\n",
    "    steps:\n",
    "      - uses: actions/checkout@v4\n",
    "      - name: Ningun monto vacio\n",
    "        run: |\n",
    "          tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "          echo \"CSV correcto\"\n",
    "FIN\n",
    "git add .\n",
    "git commit -q -m \"Se revisa el CSV de ventas en cada push\"\n",
    "git ls-files"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelo de arriba abajo y se entiende sin saber nada: se llama así, se dispara\n",
    "con un push o un pull request, corre en una máquina Linux, se baja tu código y\n",
    "ejecuta esos comandos 📖"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Probar la revisión aquí antes de subirla"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que corre allá son comandos normales, así que se pueden probar en tu\n",
    "máquina primero. Es lo que yo hago siempre, porque esperar a que GitHub falle\n",
    "para descubrir un error de dedo es lentísimo:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "echo \"CSV correcto\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "printf 'Cusco,\\n' >> ventas.csv\n",
    "tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$'\n",
    "echo \"encontro una fila vacia, la revision fallaria\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con eso ya sabes que la revisión detecta lo que tiene que detectar, y recién\n",
    "ahí la subes 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "git checkout -q ventas.csv\n",
    "cat ventas.csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuándo se dispara"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Escribes | Se ejecuta |\n",
    "|---|---|\n",
    "| `on: push` | Cada vez que subes |\n",
    "| `on: pull_request` | Cuando alguien propone un cambio |\n",
    "| `on: schedule` con un cron | A una hora fija |\n",
    "| `on: workflow_dispatch` | Cuando le das a un botón |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El de `pull_request` es el que más cambia la vida de un equipo:\n",
    "la revisión automática corre sobre la propuesta y quien revisa ya sabe si\n",
    "funciona antes de leer una línea, que es justo lo que le faltaba a la trampa de\n",
    "23 ✅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las claves no van dentro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si tu automatización necesita una clave, va en\n",
    "*Settings > Secrets and variables > Actions*, y en el archivo se\n",
    "usa por su nombre:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "      - name: Manda el reporte\n",
    "        env:\n",
    "          TOKEN: ${{ secrets.TOKEN_DEL_REPORTE }}\n",
    "        run: python3 manda_reporte.py\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese archivo se puede subir tranquilamente: no lleva el valor, lleva el\n",
    "nombre. Y GitHub tapa el valor en los registros si por accidente se imprime 🔐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuánto cuesta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En repositorios públicos, Actions es gratis. En privados hay minutos\n",
    "incluidos al mes y después se paga, así que conviene mirar el consumo antes de\n",
    "poner algo a correr cada cinco minutos ⏱️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Montas una automatización que sube el reporte a un servicio y pegas la clave dentro del archivo de configuración, que total es tu repositorio.\n",
    "\n",
    "```\n",
    "name: Reporte diario\n",
    "on:\n",
    "  schedule:\n",
    "    - cron: '0 12 * * *'\n",
    "jobs:\n",
    "  reporte:\n",
    "    runs-on: ubuntu-latest\n",
    "    steps:\n",
    "      - run: curl -H \"Authorization: Bearer sk-abc123...\" ...\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 se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tu automatización necesita la clave de un servicio. ¿Dónde la pones?\n",
    "\n",
    "a) En los secrets del repositorio, y la uso por su nombre\n",
    "\n",
    "b) Dentro del archivo `.yml`\n",
    "\n",
    "c) En un archivo aparte que agrego al `.gitignore`\n",
    "\n",
    "d) La escribo cada vez que ejecuto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Escribe tu primera automatización\n",
    "\n",
    "Una revisión que comprueba que el catálogo no tenga\n",
    "ciudades vacías."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Prueba la revisión aquí\n",
    "\n",
    "Los mismos comandos, en tu máquina."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Comprueba que detecta el error\n",
    "\n",
    "Mete una bodega sin ciudad."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Devuelve el archivo a como estaba\n",
    "\n",
    "Con lo del capítulo de deshacer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Agrega un disparo por calendario\n",
    "\n",
    "Que además corra todos los lunes a las 12 UTC."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Comprueba que no metiste claves\n",
    "\n",
    "La revisión de siempre, ahora sobre los archivos de\n",
    "automatización."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Pide un archivo de workflow que no existe\n",
    "\n",
    "Los nombres de esta carpeta son literales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cuando pase esto, corre esto. Prueba los comandos en tu máquina\n",
    "antes, y las claves siempre en los secrets.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 31 de **Git desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/git-desde-cero/github-actions/\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
}
