{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# El proyecto final\n",
    "\n",
    "Junta todo el libro en un repositorio que puedas enseñar\n",
    "\n",
    "Cuaderno de práctica del capítulo 32 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/proyecto-final/\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",
    "Este capítulo son comandos de terminal, no Python. La celda de abajo baja el\n",
    "ayudante que los ejecuta y que **recuerda en qué carpeta quedaste**, que es lo\n",
    "que hace falta para que un `cd` de una celda siga valiendo en la siguiente.\n",
    "\n",
    "A partir de ahí, cada celda de comandos empieza por `%%consola`."
   ]
  },
  {
   "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: \"LmdpdGlnbm9yZQpib2RlZ2FzLmNzdg==\",\n",
    "    2: \"LmdpdGlnbm9yZQpMSUNFTlNFClJFQURNRS5tZApib2RlZ2FzLmNzdg==\",\n",
    "    3: \"NzYwCmFjNWZjM2EgU2UgYWdyZWdhIGVsIGNhbGN1bG8gZGVsIHRvdGFsIHBvciBib2RlZ2EKMWM2Y2E5ZSBTZSBhZ3JlZ2FuIGVsIFJFQURNRSB5IGxhIGxpY2VuY2lhIE1JVApkMTZhYjM1IENhdGFsb2dvIGluaWNpYWwgZGUgYm9kZWdhcw==\",\n",
    "    4: \"RGVsZXRlZCBicmFuY2ggY2FsY3VsYS1lbC10b3RhbCAod2FzIGFjNWZjM2EpLgpjOGY5MjcwIEVudHJhIGVsIGNhbGN1bG8gZGVsIHRvdGFsCmFjNWZjM2EgU2UgYWdyZWdhIGVsIGNhbGN1bG8gZGVsIHRvdGFsIHBvciBib2RlZ2EKMWM2Y2E5ZSBTZSBhZ3JlZ2FuIGVsIFJFQURNRSB5IGxhIGxpY2VuY2lhIE1JVApkMTZhYjM1IENhdGFsb2dvIGluaWNpYWwgZGUgYm9kZWdhcwoqIG1haW4=\",\n",
    "    5: \"djEuMAp2MS4wCmM4ZjkyNzAgKEhFQUQgLT4gbWFpbiwgdGFnOiB2MS4wKSBFbnRyYSBlbCBjYWxjdWxvIGRlbCB0b3RhbA==\",\n",
    "    6: \"YzhmOTI3MGYzYTM5NzQ2YzhiZmZkNDRhMjhjZDE4YmQ0ZjNjYmFiNQlIRUFECmM4ZjkyNzBmM2EzOTc0NmM4YmZmZDQ0YTI4Y2QxOGJkNGYzY2JhYjUJcmVmcy9oZWFkcy9tYWluCjZjYzgxNmIxNDZiMTNmNDVhZTJiM2E1MzRjYjFlMTE5MWZhODc4MWEJcmVmcy90YWdzL3YxLjAKYzhmOTI3MGYzYTM5NzQ2YzhiZmZkNDRhMjhjZDE4YmQ0ZjNjYmFiNQlyZWZzL3RhZ3MvdjEuMF57fQ==\",\n",
    "    7: \"PT0gcXVlIHNlIHB1YmxpY2EgPT0KLmdpdGlnbm9yZQpMSUNFTlNFClJFQURNRS5tZApib2RlZ2FzLmNzdgp0b3RhbC5zaAo9PSBoaXN0b3JpYSA9PQpjOGY5MjcwIEVudHJhIGVsIGNhbGN1bG8gZGVsIHRvdGFsCmFjNWZjM2EgU2UgYWdyZWdhIGVsIGNhbGN1bG8gZGVsIHRvdGFsIHBvciBib2RlZ2EKMWM2Y2E5ZSBTZSBhZ3JlZ2FuIGVsIFJFQURNRSB5IGxhIGxpY2VuY2lhIE1JVApkMTZhYjM1IENhdGFsb2dvIGluaWNpYWwgZGUgYm9kZWdhcwo9PSB2ZXJzaW9uZXMgPT0KdjEuMAo9PSBuYWRhIHN1ZWx0byA9PQpsaW1waW8=\",\n",
    "    8: \"T24gYnJhbmNoIG1haW4KWW91ciBicmFuY2ggaXMgdXAgdG8gZGF0ZSB3aXRoICdvcmlnaW4vbWFpbicuCgpub3RoaW5nIHRvIGNvbW1pdCwgd29ya2luZyB0cmVlIGNsZWFu\",\n",
    "}, lenguaje=\"bash\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Treinta y un capítulos después, toca juntarlo. Y no en un resumen: en un\n",
    "repositorio que puedas mandar por WhatsApp y que hable bien de ti 🏆"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las siete piezas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Pieza | Capítulo | Qué demuestra |\n",
    "|---|---|---|\n",
    "| README con instrucciones | 18 | Que piensas en quien llega |\n",
    "| `.gitignore` | 9 | Que sabes qué no va |\n",
    "| Licencia | 16 | Que sabes lo que estás publicando |\n",
    "| Commits legibles | 5 | Cómo trabajas |\n",
    "| Una rama fusionada | 10 | Que no trabajas todo en main |\n",
    "| Una versión etiquetada | 24 | Que piensas en quien lo usa |\n",
    "| Revisión automática | 31 | Que compruebas lo que entregas |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 1: la base"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "mkdir ventas-miss-yera\n",
    "cd ventas-miss-yera\n",
    "git init -q\n",
    "printf 'ciudad,monto\\nLima,1200\\nArequipa,890\\nCusco,760\\n' > ventas.csv\n",
    "printf 'API_KEY=no-deberia-estar-aqui\\n' > .env\n",
    "printf '.env\\n__pycache__/\\n' > .gitignore\n",
    "git add ventas.csv .gitignore\n",
    "git commit -q -m \"Primeras ventas por ciudad\"\n",
    "git status --short\n",
    "git ls-files"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en que el `.env` existe en la carpeta y no está en la\n",
    "lista. Primera pieza colocada 🚫"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 2: el README y la licencia"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "cat > README.md <<'FIN'\n",
    "# Ventas Miss Yera\n",
    "\n",
    "Calcula el total de ventas por ciudad a partir de un CSV.\n",
    "\n",
    "## Necesitas\n",
    "- Bash\n",
    "\n",
    "## Usar\n",
    "```\n",
    "bash total.sh ventas.csv\n",
    "```\n",
    "\n",
    "Devuelve el monto total de todas las ciudades.\n",
    "FIN\n",
    "printf 'MIT License\\n\\nCopyright (c) 2026 Gera Flores\\n' > LICENSE\n",
    "git add README.md LICENSE\n",
    "git commit -q -m \"Se agregan el README y la licencia MIT\"\n",
    "git ls-files"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 3: el trabajo, en su rama"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "git switch -q -c calcula-el-total\n",
    "cat > total.sh <<'FIN'\n",
    "tail -n +2 \"$1\" | cut -d, -f2 | paste -sd+ | bc\n",
    "FIN\n",
    "git add total.sh\n",
    "git commit -q -m \"Se agrega el calculo del total por ciudad\n",
    "\n",
    "Lee el CSV, se salta la cabecera y suma la segunda columna.\"\n",
    "bash total.sh ventas.csv\n",
    "git log --oneline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Funciona. Y el commit tiene cuerpo, que es lo que va a leer quien mire tu\n",
    "historia dentro de un año 📖"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 4: fusionar dejando rastro"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "git switch -q main\n",
    "git merge -q --no-ff -m \"Entra el calculo del total por ciudad\" calcula-el-total\n",
    "git branch -d calcula-el-total\n",
    "git log --oneline --topo-order\n",
    "git branch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `--no-ff` deja escrito que hubo una rama, y borrarla después\n",
    "deja la lista limpia. Así se ve un repositorio donde alguien trabajó ordenado 🌿\n",
    "\n",
    "Ese `--topo-order` es el único invitado nuevo de este capítulo, y\n",
    "está por un motivo concreto: sin él, `git log` ordena por la hora de\n",
    "cada commit, y como los tuyos los vas a hacer en otro orden que yo, la lista te\n",
    "saldría distinta a la de aquí. Con él manda la forma de la historia y no el\n",
    "reloj, así que a ti te sale exactamente esto 🕰️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 5: la revisión automática"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\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",
    "      - run: |\n",
    "          tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "          echo \"CSV correcto\"\n",
    "FIN\n",
    "git add .github\n",
    "git commit -q -m \"Se revisa el CSV en cada push\"\n",
    "tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "echo \"la revision pasa\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 6: la primera versión"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "git tag -a v1.0 -m \"Primera version del reporte por ciudad\"\n",
    "git tag\n",
    "git describe --tags\n",
    "git log --oneline --decorate -1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 7: publicarlo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "cd ..\n",
    "git init -q --bare servidor.git\n",
    "cd ventas-miss-yera\n",
    "git remote add origin ../servidor.git\n",
    "git push -q -u origin main\n",
    "git push -q origin --tags\n",
    "git ls-remote origin"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con GitHub la única diferencia es la dirección, y ahí lo que sigue es entrar\n",
    "a la web, comprobar que el repositorio esté como quieres y decidir si va público\n",
    "o privado. Todo lo demás ya está hecho 🚀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La revisión final"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "echo \"== que se publica ==\" && git ls-files\n",
    "echo \"== historia ==\" && git log --oneline --topo-order\n",
    "echo \"== versiones ==\" && git tag\n",
    "echo \"== nada suelto ==\" && git status --short && echo \"limpio\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Siete archivos, historia legible, una versión marcada y nada pendiente. Eso\n",
    "es un repositorio que se puede enseñar 🎓"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Terminas tu proyecto, lo subes a GitHub y lo pones en tu currículum. Nadie lo abre dos veces.\n",
    "\n",
    "```\n",
    "$ git ls-files\n",
    "analisis.py\n",
    "datos.csv\n",
    "notebook.ipynb\n",
    "prueba2.py\n",
    "prueba_final.py\n",
    "prueba_final_v2.py\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",
    "Vas a poner un repositorio en tu currículum. ¿Qué mira primero quien lo abra?\n",
    "\n",
    "a) El README y si se entiende qué hace\n",
    "\n",
    "b) La cantidad de commits\n",
    "\n",
    "c) El lenguaje que aparece arriba\n",
    "\n",
    "d) Las estrellas que tenga"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Arranca tu repositorio con lo que no va\n",
    "\n",
    "Base del catálogo, con `.gitignore` desde el\n",
    "primer commit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Ponle portada y licencia\n",
    "\n",
    "Las dos piezas que se miran primero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Trabaja en una rama\n",
    "\n",
    "El cálculo del total, con su commit bien escrito."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Fusiona dejando rastro\n",
    "\n",
    "Con `--no-ff` y limpiando la rama."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Marca la versión\n",
    "\n",
    "Una etiqueta anotada para la v1.0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Publícalo con sus etiquetas\n",
    "\n",
    "Recuerda que el push normal no se lleva las etiquetas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Haz la revisión final\n",
    "\n",
    "Lo que miro antes de mandarle un repositorio a\n",
    "alguien."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Intenta guardar cuando ya no queda nada\n",
    "\n",
    "Con todo commiteado, prueba a hacer otro commit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**README, gitignore, licencia, commits legibles, una rama, una versión\n",
    "y una revisión. Eso es un repositorio que habla bien de ti.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Git era el requisito. Esto es para lo que lo querías"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nadie aprende Git por Git. Se aprende porque sin él no puedes trabajar con nadie ni volver atrás cuando algo se rompe, y las dos cosas son la base de construir algo que dure.\n",
    "\n",
    "Con esto ya puedes entrar a [el curso de ingeniería de IA en producción](https://missyera.com/cursos/ingenieria-de-ia/) sin que la herramienta te estorbe, que es justo el motivo por el que este libro existe: ahí vas a trabajar sobre un repositorio de verdad durante cuatro semanas, y quien llega sin saber Git se pasa la primera peleándose con el control de versiones en vez de con el sistema.\n",
    "\n",
    "Y si no vas a ese curso, Git te sirve igual: es la herramienta que te deja volver atrás cuando rompiste algo, y eso vale en cualquier trabajo técnico 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 32 de **Git desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/git-desde-cero/proyecto-final/\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
}
