diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 086eeb3..2dda6fd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,144 +1,75 @@ # Guía de Contribución para `nerve-community` -¡Qué emoción que quieras contribuir a este proyecto! No importa si es la primera vez que tocas código o si ya tienes años de experiencia, aquí eres bienvenido/a. +¡Qué emoción que quieras contribuir a este proyecto! -El objetivo principal de este repositorio es **aprender**. Nadie nace sabiendo hacer Pull Requests (PRs), así que no tengas miedo de equivocarte. Sigue estos pasos y, si te trabas, ¡pregunta sin pena! - -## Antes de empezar -Asegúrate de tener lo siguiente: -- Una cuenta en **GitHub**. -- **Git** instalado en tu computadora. -- **Python 3.10** o superior instalado. - ---- - -## 🛠️ Paso 1: Haz un Fork -Un *fork* es hacer una copia exacta de este proyecto y ponerla en tu propia cuenta de GitHub. Así puedes experimentar y romper cosas en tu copia sin afectar el repositorio original. -Para hacerlo, ve a la página principal de este repositorio y dale clic al botón **Fork** que está arriba a la derecha. - ---- - -## 💻 Paso 2: Clona tu fork en tu computadora -Abre tu terminal (línea de comandos) y escribe el siguiente comando, cambiando `TU-USUARIO` por tu nombre de usuario en GitHub: - -```bash -git clone https://github.com/TU-USUARIO/nerve-community.git -cd nerve-community -``` +El objetivo principal de este repositorio es **aprender** y mantener un currículum de alta calidad para la comunidad. Sigue estos pasos y reglas para asegurarte de que tu contribución sea aceptada rápidamente. --- -## Paso 3: Crea una rama nueva -**Nunca** trabajes directamente en la rama `main`. Es una buena práctica crear una "rama" (branch) separada para cada tarea o reto que vayas a resolver. +## 🚫 Regla de Oro: La estructura es sagrada -```bash -git checkout -b solucion-reto-01 -``` +La estructura de carpetas dentro de `python/` (`Modulo-01`, `Modulo-02`, etc.) es utilizada por sistemas automatizados. +- **NO cambies el nombre** de ninguna carpeta `Modulo-XX`. +- **NO cambies el nombre** de los archivos internos (`README.md`, `teoria.md`, `ejemplo.py`, `reto.md`, `test_main.py`). +- **NO agregues nuevos archivos** dentro de las carpetas de los niveles, a menos que un mantenedor lo apruebe. --- -## Paso 4: Elige un reto -Ve a la pestaña de **Issues** en el repositorio original. -Busca los issues que tengan la etiqueta `good-first-issue` o `disponible`. -Si encuentras uno que te guste, deja un comentario que diga: *"¡Yo lo tomo!"* o *"Me gustaría intentar resolver esto"*. Así, evitamos que dos personas hagan el mismo reto al mismo tiempo. +## 💡 ¿Qué y cómo puedes contribuir? ---- +Puedes ayudarnos a mejorar el contenido existente o crear nuevos retos socráticos. -## 📦 Paso 5: Instala dependencias y prueba que todo funcione -Crea un entorno virtual e instala lo necesario (si tu reto lo requiere): +### 1. Mejorar el contenido de aprendizaje +Puedes editar `teoria.md`, `ejemplo.py` o `README.md` de cualquier nivel si notas que: +- Una explicación es confusa o tiene faltas de ortografía. +- El código de ejemplo podría ser más claro o tener mejores comentarios. +- El reto está mal redactado. -```bash -# En Windows: -python -m venv env -.\env\Scripts\activate +### 2. Mejorar los tests automáticos +Si detectas que un `test_main.py` de algún reto es demasiado permisivo (deja pasar código malo) o es incorrecto (falla con código bueno), puedes mejorarlo. Asegúrate de probarlo bien antes de enviar el PR. -# En Mac / Linux: -python3 -m venv env -source env/bin/activate +### 3. Crear nuevos Retos Socráticos +La carpeta `retos/` contiene retos abiertos (sin instrucciones paso a paso) para que los alumnos apliquen lo aprendido. Puedes crear uno nuevo en la carpeta correspondiente a su dificultad (`retos/python/MXX-MYY/nombre-del-reto/reto.md`). -# Instalar Nerve (ejemplo): -pip install alenia-nerve -``` - ---- +**Plantilla para un nuevo reto socrático (`reto.md`):** -## Paso 6: Escribe tu código -Entra a la carpeta correspondiente a tu reto (por ejemplo `retos/nerve/01-chat-terminal/`). -Dentro de esa carpeta encontrarás el archivo `README.md` con las instrucciones del reto. +```markdown +# [Nombre del reto] -Para guardar tu solución, debes crear una carpeta dentro de `soluciones/` en la raíz del repositorio siguiendo la convención `TuUsuario-Lenguaje`. Por ejemplo: -- `soluciones/Tacos-python/` -- `soluciones/Maria-rust/` -- `soluciones/Juan-go/` -- `soluciones/Ana-js/` +> **Nivel**: Módulos [X]–[Y] completados +> **Tiempo estimado**: [N] horas +> **Lenguaje**: Python -Dentro de tu carpeta personal, puedes crear los archivos necesarios para resolver el reto. ¡Diviértete programando! +## Tu misión +[Descripción en 2-3 líneas de qué debe hacer el programa. Sin decir cómo.] -### 🛠️ Instrucciones por Lenguaje +## Lo que debe hacer tu programa +- [Comportamiento observable 1] +- [Comportamiento observable 2] -**Python:** -- Se recomienda crear un entorno virtual (`python -m venv env`). -- Si instalas dependencias, recuerda exportarlas en un `requirements.txt`. +## Restricciones (respétalas o el reto no cuenta) +- **Solo puedes usar** conceptos de los módulos [X] al [Y] +- **No puedes usar**: [librerías prohibidas] +- **Obligatorio**: [requisito técnico concreto] +- El programa debe correr con `python reto.py` -**Rust:** -- Usa `cargo new tu-proyecto` para crear tu proyecto dentro de tu carpeta de solución. -- Verifica tu código con `cargo clippy` y formatéalo con `cargo fmt`. - -**Go:** -- Inicializa tu módulo con `go mod init `. -- Formatea tu código usando `go fmt ./...`. - -**JavaScript (Node.js):** -- Inicializa tu proyecto con `npm init -y`. -- Es buena práctica usar `eslint` o `prettier` para mantener el código limpio. - ---- - -## Paso 7: Guarda tus cambios (Commit) -Una vez que termines y pruebes que funciona, debes guardar tus cambios en git. Asegúrate de escribir un mensaje claro de lo que hiciste. - -```bash -git add . -git commit -m "Agrega solución al reto 01: chat-terminal" +## Criterio de éxito +Tu solución es válida si: +1. [Verificación concreta 1] +2. [Verificación concreta 2] ``` --- -## Paso 8: Sube tu rama a tu fork -Ahora envía los cambios guardados desde tu computadora a tu repositorio en GitHub: - -```bash -git push origin solucion-reto-01 -``` - ---- - -## Paso 9: Abre el Pull Request -Ve a tu repositorio fork en GitHub. Verás un botón verde gigante que dice **Compare & pull request**. ¡Dale clic! -- Llena la plantilla del PR. -- Menciona qué número de Issue estás resolviendo (ejemplo: `Closes #1`). -- Añade una captura de pantalla si tu código hace algo visual. - ---- +## 🛠️ El proceso de contribución (Fork + PR) -## ⏳ Paso 10: Espera revisión -Los mantenedores del proyecto revisaremos tu PR. -Es **súper normal** que te pidamos hacer algún cambio o corrección. No te lo tomes a mal, ¡no es un rechazo! Es simplemente la mejor forma de darte *feedback* y aprender juntos. Haz los cambios en tu computadora, haz otro `commit`, haz `push` y el PR se actualizará solo. - ---- - -## ❓ Preguntas frecuentes - -- **¿Y si ya alguien más resolvió el reto que quería?** -¡No pasa nada! A veces aceptamos múltiples soluciones si tienen enfoques distintos, o simplemente puedes elegir otro reto. Pregúntanos en el Issue si tienes dudas. - -- **¿Y si mi código no es perfecto?** -¡Mejor! El código perfecto no existe. Lo importante es que funcione, que intentes aprender algo nuevo y que pierdas el miedo a compartirlo. - -- **¿Puedo proponer un reto nuevo?** -¡Sí! Ve a los Issues, elige "Propuesta de nuevo reto" y cuéntanos tu idea. - ---- +1. **Haz un Fork**: Crea una copia exacta de este proyecto en tu propia cuenta de GitHub dándole al botón "Fork". +2. **Clona**: Descarga tu copia a tu computadora (`git clone https://github.com/TU-USUARIO/nerve-community.git`). +3. **Crea una rama (branch)**: Nunca trabajes en `main`. Crea una rama descriptiva (`git checkout -b mejora-teoria-mod1`). +4. **Haz el cambio**: Modifica los archivos siguiendo las reglas mencionadas arriba. +5. **Commit y Push**: Guarda tus cambios (`git commit -m "feat: mejora explicación de diccionarios en Módulo 04"`) y súbelos a tu fork (`git push origin mejora-teoria-mod1`). +6. **Pull Request (PR)**: Ve a GitHub y abre un Pull Request hacia el repositorio original. + - En la descripción de tu PR, explica **qué** cambiaste y **por qué**. + - Marca la casilla correspondiente en nuestra plantilla de PR. -### ¡Los errores son bienvenidos! -Si algo sale mal con un comando de Git o con tu código, dínoslo. El propósito de `nerve-community` es crecer en comunidad. ¡Mucho éxito con tu primer PR! +¡Y listo! Los mantenedores revisaremos tu PR y te daremos feedback. ¡Gracias por ayudar a la comunidad! diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 74b8429..76293a3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -22,6 +22,19 @@ body: placeholder: "1. Abrí la carpeta del reto...\n2. Ejecuté el comando..." validations: required: true + - type: dropdown + id: lenguaje + attributes: + label: "Lenguaje afectado (si aplica)" + options: + - No aplica / General + - Python + - Rust + - Go + - Java + - JavaScript / TypeScript + validations: + required: false - type: textarea id: entorno attributes: diff --git a/.github/ISSUE_TEMPLATE/propuesta_nuevo_reto.yml b/.github/ISSUE_TEMPLATE/propuesta_nuevo_reto.yml index 28579ea..ad92133 100644 --- a/.github/ISSUE_TEMPLATE/propuesta_nuevo_reto.yml +++ b/.github/ISSUE_TEMPLATE/propuesta_nuevo_reto.yml @@ -22,6 +22,19 @@ body: placeholder: "- Manejo de JSON\n- Peticiones HTTP..." validations: required: true + - type: dropdown + id: lenguaje + attributes: + label: "¿Para qué lenguaje(s) propones este reto?" + options: + - Multilenguaje (Aplica para todos) + - Python + - Rust + - Go + - Java + - JavaScript / TypeScript + validations: + required: true - type: dropdown id: dificultad attributes: diff --git a/.github/ISSUE_TEMPLATE/reto.yml b/.github/ISSUE_TEMPLATE/reto.yml index d21782e..6a0fff4 100644 --- a/.github/ISSUE_TEMPLATE/reto.yml +++ b/.github/ISSUE_TEMPLATE/reto.yml @@ -1,26 +1,44 @@ -name: Solucionar un Reto -description: Sube tu solución a uno de los retos de código disponibles. -title: "Reto: [NOMBRE DEL RETO]" -labels: ["en-progreso"] +name: Compartir Solución de un Reto +description: Comparte el enlace de tu repositorio (creado con nerve--template) con tu solución a un reto. +title: "Solución: [NOMBRE DEL RETO]" +labels: ["solución-reto"] body: - type: markdown attributes: - value: "¡Gracias por animarte a resolver este reto! Por favor llena esta plantilla para que podamos revisar tu propuesta fácilmente." + value: "¡Felicidades por resolver este reto! Llena esta plantilla para compartir tu repositorio generado a partir de `nerve--template`. Así podremos revisar tu código y darte feedback." - type: dropdown - id: nivel + id: lenguaje attributes: - label: "¿De qué nivel es el reto que resolviste?" + label: "¿En qué lenguaje resolviste el reto?" options: - - Nivel 1 - Principiante - - Nivel 2 - Intermedio - - Nivel 3 - Avanzado + - Python + - Rust + - Go + - Java + - JavaScript / TypeScript + validations: + required: true + - type: dropdown + id: tipo + attributes: + label: "¿Qué tipo de reto es?" + options: + - Reto de Módulo (Evaluado por GitHub Actions) + - Reto Socrático + validations: + required: true + - type: input + id: modulo_nivel + attributes: + label: "Módulo y Nivel exactos" + placeholder: "Ejemplo: python/Modulo-01-Fundamentos/01-hola-mundo" validations: required: true - type: input - id: nombre_reto + id: repo_url attributes: - label: "Nombre exacto del reto" - placeholder: "Ejemplo: 01-chat-terminal" + label: "Enlace a tu repositorio público" + placeholder: "https://github.com/tu-usuario/tu-repo" validations: required: true - type: textarea @@ -36,13 +54,13 @@ body: attributes: label: "Lista de verificación" options: - - label: "Probé mi código localmente y funciona." + - label: "Usé el repositorio `nerve--template` para crear mi proyecto." required: true - - label: "Agregué mi código en la carpeta correspondiente al reto." + - label: "Los tests automatizados (si aplican) pasan exitosamente en mi GitHub Actions." required: true - - label: "Comenté mi código para que otras personas puedan aprender leyéndolo." + - label: "Comenté mi código para explicar mis decisiones de diseño." required: false - - label: "Creo que esta solución está lo suficientemente pulida como para considerarse para 'graduarse' a zenith-nerve-tools." + - label: "Creo que esta solución está tan pulida que podría integrarse como herramienta en `zenith-nerve-tools`." required: false - type: markdown attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3d8b319..6170e46 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,15 +1,35 @@ -## 📌 ¿Qué reto resuelve este PR? -(Ej: Reto 01 - chat-terminal. Enlaza el issue con "Closes #NUMERO") + + +## Tipo de contribucion + +- [ ] Mejora de contenido (`teoria.md`, `ejemplo.py`, `README.md` de un modulo) +- [ ] Correccion en `test_main.py` +- [ ] Nuevo reto socrático en `retos/` +- [ ] Correccion de bug general o documentacion +- [ ] Otro + +## Descripcion de los cambios + + + +## Checklist + +- [ ] He leido la [Guia de Contribucion](.github/CONTRIBUTING.md). +- [ ] No modifique la estructura de carpetas ni los nombres de archivos dentro de `python/Modulo-XX/`. +- [ ] Si modifique un `test_main.py`, lo probe localmente para verificar que es correcto. +- [ ] Si es un reto socratico nuevo, use la plantilla correcta y lo ubique en la carpeta de dificultad adecuada. + +## Notas para quien revise + + diff --git a/.github/workflows/cierre-pr-alumno.yml b/.github/workflows/cierre-pr-alumno.yml new file mode 100644 index 0000000..d3a3df9 --- /dev/null +++ b/.github/workflows/cierre-pr-alumno.yml @@ -0,0 +1,105 @@ +name: Cierre Automatico de PRs de Alumnos + +# Dispara en PRs de forks hacia este repo. +# pull_request_target tiene acceso a los secrets del repo base, lo que +# permite comentar y cerrar PRs incluso desde forks externos. +on: + pull_request_target: + types: [opened, reopened, synchronize] + +jobs: + detectar-pr-alumno: + name: Detectar PR de alumno y redirigir + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + + steps: + - name: Obtener archivos modificados en el PR + id: archivos + uses: actions/github-script@v7 + with: + script: | + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + per_page: 100, + }); + + // Patrones que indican que el PR es trabajo de un alumno, + // no una contribucion al material del repo. + const patronesAlumno = [ + /^python\/Modulo-\d+/, // Archivos dentro de cualquier modulo de Python + /^go\/Modulo-\d+/, + /^rust\/Modulo-\d+/, + /^javascript\/Modulo-\d+/, + ]; + + const nombresArchivos = files.map(f => f.filename); + const esDeAlumno = nombresArchivos.some(nombre => + patronesAlumno.some(patron => patron.test(nombre)) + ); + + console.log("Archivos en el PR:", nombresArchivos); + console.log("Es PR de alumno:", esDeAlumno); + + return esDeAlumno; + + - name: Cerrar PR y notificar al alumno + if: steps.archivos.outputs.result == 'true' + uses: actions/github-script@v7 + with: + script: | + const prAuthor = context.payload.pull_request.user.login; + const prNumber = context.payload.pull_request.number; + + const mensaje = [ + `Hola @${prAuthor},`, + ``, + `Gracias por tomarte el tiempo de abrir este PR. Sin embargo, este repositorio (\`nerve-community\`) es el repositorio público del **material del curso**, no el lugar donde los alumnos entregan su trabajo.`, + ``, + `**Tu trabajo como alumno va en tu propio repositorio privado, generado desde el template oficial.** Los PRs de alumnos a este repo no son evaluados ni cuentan como avance.`, + ``, + `### Qué hacer ahora`, + ``, + `1. Si ya tienes tu repositorio generado desde el template: trabaja y haz push directamente ahí. El CI de ese repo es el que valida tu avance.`, + `2. Si aún no tienes acceso al template: entra al Discord de la comunidad y pide el enlace al template en el canal correspondiente.`, + `3. Puedes mantener o eliminar tu fork de este repo, eso no afecta tu avance como alumno.`, + ``, + `Lee la guía completa aquí: [Cómo usar como Alumno](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/docs/COMO-USAR-COMO-ALUMNO.md)`, + ``, + `Este PR se cierra automáticamente. No es una penalización — es solo que el canal correcto es otro.`, + ].join('\n'); + + // Publicar comentario + await github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: mensaje, + }); + + // Cerrar el PR + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + state: 'closed', + }); + + // Etiquetar para identificarlos facilmente en el historial + try { + await github.rest.issues.addLabels({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['redirigido-a-template'], + }); + } catch (e) { + // El label puede no existir aun; no es critico + console.log("Label no encontrado, se omite:", e.message); + } + + console.log(`PR #${prNumber} cerrado y notificado correctamente.`); diff --git a/COMO-HACER-TU-PRIMER-PR.md b/COMO-HACER-TU-PRIMER-PR.md index 03d8438..bfa13a8 100644 --- a/COMO-HACER-TU-PRIMER-PR.md +++ b/COMO-HACER-TU-PRIMER-PR.md @@ -1,116 +1,143 @@ -# ¿CÓMO HACER TU PRIMER PULL REQUEST (PR)? +# Cómo hacer tu primer Pull Request -Si esta es la primera vez que escuchas sobre Git, GitHub o un "Pull Request", estás en el lugar correcto. En este documento te explicaremos paso a paso y con palabras sencillas cómo contribuir a este (o a cualquier) proyecto open source. +> [!IMPORTANT] +> Si estás aquí para resolver los retos de aprendizaje como alumno, lee primero [COMO-USAR-COMO-ALUMNO.md](docs/COMO-USAR-COMO-ALUMNO.md) y [RETOS-SOCRATICOS.md](docs/RETOS-SOCRATICOS.md). Esta guía es para contribuidores que quieren mejorar el material del repositorio. ---- - -## Mini Glosario (Para que no te suene a chino) +Si es la primera vez que escuchas sobre Git, GitHub o Pull Request, estás en el lugar correcto. Aquí explicamos paso a paso cómo contribuir a este —o a cualquier— proyecto open source. -- **Git:** Es un programa en tu computadora que actúa como una "máquina del tiempo". Guarda el historial de todos los cambios que haces en tus archivos de código. -- **GitHub:** Es una página web (una red social para programadores) donde subimos nuestro código usando Git. -- **Fork:** Es como darle al botón de "Compartir -> Crear una copia". Haces un clon exacto del proyecto de alguien más y lo guardas en tu propia cuenta. -- **Clone:** Es descargar tu "Fork" (tu copia en GitHub) hacia tu computadora física para que puedas editar los archivos. -- **Branch (Rama):** Es una línea de tiempo paralela. Creas una rama para no arruinar el código principal (`main`) mientras intentas algo nuevo. -- **Commit:** Es como un punto de guardado (Save State) en un videojuego. Guarda tus cambios permanentemente con un pequeño mensaje explicando qué hiciste. -- **Push:** Es subir tus "Commits" (puntos de guardado) de tu computadora a GitHub. -- **Pull Request (PR):** Es cuando le dices al dueño del proyecto original: *"Oye, mira estos cambios que hice en mi copia. ¿Te gustan? ¿Los quieres añadir a tu proyecto oficial?"*. +Para entender los términos que aparecen en esta guía, consulta el [Glosario de Git y GitHub](glosario/git-github.md). --- -## El diagrama del flujo (Visualiza el proceso) +## El flujo completo (diagrama) ```text -[Proyecto Original] - │ - │ (1. Fork) - ▼ +[Repositorio Original] + | + | (1. Fork) + v [Tu copia en GitHub] ──────┐ - │ │ (6. Pull Request y Revisión) - │ (2. Clone) │ - ▼ │ -[Tu computadora] │ - │ (3. Branch) │ - │ (4. Escribir) │ - │ (5. Commit/Push) ─┘ + | | (6. Pull Request y Revisión) + | (2. Clone) | + v | +[Tu computadora] | + | (3. Branch) | + | (4. Escribir) | + | (5. Commit/Push) ─┘ ``` --- -## ⚙️ Preparativos (Sólo se hace una vez en la vida) - -1. **Instala Git:** - - Windows: Descarga [Git for Windows](https://gitforwindows.org/) e instálalo. - - Mac: Abre la terminal y escribe `git --version`. Si no lo tienes, te pedirá instalarlo. - - Linux: Escribe `sudo apt install git` en tu terminal. -2. **Identifícate ante Git:** - Abre tu terminal y escribe esto (cambiándolo por tu nombre y tu email de GitHub): - ```bash - git config --global user.name "Tu Nombre Aquí" - git config --global user.email "tu_correo@ejemplo.com" - ``` -3. **Autentícate con GitHub:** - GitHub ya no permite usar contraseñas normales en la terminal. La forma más amigable es instalar la [GitHub CLI (gh)](https://cli.github.com/). - Una vez que la instales, abre tu terminal y escribe: - ```bash - gh auth login - ``` - Sigue las instrucciones: elige `GitHub.com`, luego `HTTPS`, luego `Yes` para autenticar con tus credenciales, y finalmente `Login with a web browser`. Se abrirá tu navegador para dar permiso. ¡Y listo! - - *(💡 Alternativa manual: Si no quieres instalar `gh`, debes ir a tu perfil de GitHub -> **Settings > Developer Settings > Personal access tokens > Tokens (classic)**, generar un nuevo token marcando la casilla "repo", y usar ese código larguísimo como si fuera tu contraseña cuando la terminal te lo pida al hacer `git push`).* +## Preparativos (solo se hace una vez) + +**1. Instala Git** + +- Windows: descarga [Git for Windows](https://gitforwindows.org/) e instálalo. +- macOS: abre la terminal y escribe `git --version`. Si no lo tienes, te pedirá instalarlo. +- Linux: `sudo apt install git` + +**2. Identifícate ante Git** + +Abre tu terminal y configura tu nombre y correo (el mismo que usas en GitHub): + +```bash +git config --global user.name "Tu Nombre" +git config --global user.email "tu_correo@ejemplo.com" +``` + +**3. Autentícate con GitHub** + +GitHub no permite usar contraseñas normales desde la terminal. La forma más sencilla es instalar la [GitHub CLI](https://cli.github.com/) y ejecutar: + +```bash +gh auth login +``` + +Sigue las instrucciones: elige `GitHub.com`, luego `HTTPS`, luego `Login with a web browser`. Se abrirá el navegador para autorizar el acceso. + +Alternativa sin `gh`: ve a tu perfil de GitHub → Settings → Developer Settings → Personal access tokens → Tokens (classic). Genera un token marcando la casilla `repo` y úsalo como contraseña cuando la terminal te lo pida al hacer `git push`. --- -## Paso a Paso Práctico +## Paso a paso + +### 1. Fork -### 1. El Fork -Ve arriba a la derecha de este repositorio y presiona el botón que dice **Fork**. Esto creará una copia del proyecto en `https://github.com/TU-USUARIO/nerve-community`. +Ve a la página del repositorio y haz clic en el botón **Fork**. Esto crea una copia del proyecto en `https://github.com/TU-USUARIO/nerve-community`. + +### 2. Clone + +Abre tu terminal en la carpeta donde guardas tus proyectos y descarga tu fork: -### 2. El Clone -Abre tu terminal en la carpeta donde guardas tus proyectos (por ejemplo, en Documentos) y escribe: ```bash git clone https://github.com/TU-USUARIO/nerve-community.git cd nerve-community ``` -### 3. Crear una Branch (Rama) -Nunca trabajes en la rama `main`. Vamos a crear una dimensión paralela para tu trabajo: +### 3. Crear una rama + +Nunca trabajes directamente en `main`. Crea una rama con un nombre descriptivo del cambio que vas a hacer: + ```bash -git checkout -b solucion-reto-01 +git checkout -b mejora-glosario-git ``` ### 4. Haz tus cambios -Usa tu editor de texto favorito (VS Code, Cursor, PyCharm, el bloc de notas) y escribe tu código para resolver el reto. Pruébalo. -### 5. Crea el Commit -Revisa qué archivos cambiaste: +Usa tu editor preferido. Navega a los archivos que quieres mejorar y realiza los cambios. + +Si estás resolviendo un reto socrático, ve a la carpeta correspondiente (por ejemplo `python/Modulo-01-Fundamentos/`) y escribe tu código. Verifica que los tests pasen localmente antes de continuar: + ```bash -git status +pytest test_main.py ``` -Dile a Git que prepare todos los archivos modificados: + +### 5. Commit + +Revisa qué archivos modificaste: + ```bash -git add . +git status ``` -Guarda el punto de control con un mensaje claro: + +Prepara los archivos y crea el punto de guardado: + ```bash -git commit -m "Termino el reto 01 de chat en terminal" +git add . +git commit -m "feat: corrige explicación de git rebase en glosario" ``` -### 6. El Push -Sube tu rama desde tu computadora hacia tu copia en GitHub: +Usa mensajes descriptivos. El prefijo `feat:` es para cambios nuevos, `fix:` para correcciones, `docs:` para documentación. + +### 6. Push + +Sube tu rama a tu copia en GitHub: + ```bash -git push origin solucion-reto-01 +git push origin mejora-glosario-git ``` ### 7. Abre el Pull Request -Ve a la página de tu copia en GitHub. Arriba verás un banner amarillo/verde que dice "Compare & pull request". Dale clic. -Rellena la plantilla explicando qué hiciste. ¡Envía tu Pull Request! + +Ve a la página de tu fork en GitHub. Verás un banner que dice "Compare & pull request". Haz clic, rellena la plantilla del PR marcando las casillas correspondientes y envíalo. --- -## 🚨 Errores Comunes de Principiante +## Errores comunes + +**"Escribí código directamente en main por accidente."** +No pasa nada si aún no hiciste commit. Escribe `git checkout -b nueva-rama` y tus cambios se moverán a esa nueva rama. + +**"Hice un commit pero me equivoqué en el mensaje."** +Escribe `git commit --amend -m "Mensaje corregido"`. Solo funciona antes de hacer push. -- **"Escribí código directamente en main por accidente":** No pasa nada. No hagas `git commit` todavía. Simplemente escribe `git checkout -b nueva-rama` y tus cambios "flotarán" a la nueva rama. -- **"Hice un commit pero me equivoqué en el mensaje":** Escribe `git commit --amend -m "Mensaje corregido"`. -- **"Dice que tengo conflictos de merge":** Esto pasa si alguien más editó los mismos archivos que tú. En VS Code, abre los archivos con conflictos y verás resaltadas las diferencias. Elige qué código quieres mantener, guarda el archivo, haz `git add .` y luego `git commit -m "Resuelve conflictos"`. +**"Dice que tengo conflictos de merge."** +Ocurre cuando alguien más editó los mismos archivos que tú. En VS Code, los archivos con conflictos muestran las diferencias resaltadas. Elige qué código mantener, guarda el archivo y luego: +```bash +git add . +git commit -m "fix: resuelve conflictos de merge" +``` + +--- -¡Tú puedes! Recuerda que nadie rompe un proyecto Open Source por accidente; para eso están las revisiones de Pull Requests. Anímate. +← [Volver al repositorio](README.md) diff --git a/Cosas tontas que use/s.html b/Cosas tontas que use/s.html new file mode 100644 index 0000000..9f9fa2e --- /dev/null +++ b/Cosas tontas que use/s.html @@ -0,0 +1,364 @@ + + + + + + + Índice Maestro: 100 Pasos a la Maestría + + + + + + + + + +
+

El Manifiesto de los 100 + Niveles

+

+ Hemos fragmentado el universo de Python en 100 átomos de conocimiento. Cada nivel es una victoria; cada + módulo es una nueva capacidad profesional. +

+
+ +
+

Balance de Habilidades (Al completar los 100 + niveles)

+
+ +
+
+ +
+
+ + +
+

01. Fundamentos Alpha

+
+
01Hola Mundo y Sintaxis
+
02Cajas de Memoria (Vars)
+
03Tipos: Texto y Enteros
+
04Tipos: Flotantes y Bool
+
05Aritmética de Máquina
+
06Entrada del Usuario (Input)
+
07F-Strings y Formateo
+
08Comentarios y Clean Doc
+
09Errores de Sintaxis Básico
+
10Reto: Ficha de Personaje
+
+
+ + +
+

02. Control de Flujo

+
+
11Operadores Lógicos (AND/OR)
+
12Comparadores (>, <,==)
+
13El Camino del IF
+
14Alternativas con ELSE/ELIF
+
15Lógica Anidada (Inception)
+
16Manejo de Truthy/Falsy
+
17Match Case (Python 3.10+)
+
18Depuración de Flujo
+
19Optimización de Booleans
+
20Reto: El Portero Virtual
+
+
+ + +
+

03. Estructuras de Datos +

+
+
21Listas: El Almacén Simple
+
22Métodos de Lista (CRUD)
+
23Slicing (Rebanar Datos)
+
24Tuplas: Datos Inmutables
+
25Diccionarios (Clave:Valor)
+
26Sets: Conjuntos Únicos
+
27Estructuras Anidadas
+
28Mutabilidad vs Referencia
+
29Copiado Profundo (DeepCopy)
+
30Reto: Inventario RPG
+
+
+ + +
+

04. Bucles y Repetición +

+
+
31While: El Bucle Infinito
+
32For: Iterando el Mundo
+
33Range y Enumerate
+
34Break y Continue
+
35List Comprehension (Pro)
+
36Dict Comprehension
+
37Zip: Uniendo Colecciones
+
38Iteradores y Generadores
+
39Búcles Anidados (Matrices)
+
40Reto: Analizador de Datos
+
+
+ + +
+

05. Funciones y Módulos +

+
+
41Def: Creando Verbos
+
42Parámetros y Retornos
+
43Argumentos por Defecto
+
44*args y **kwargs
+
45Scope: Global vs Local
+
46Lambdas (Anónimas)
+
47Recursividad (Espejos)
+
48Importación de Librerías
+
49PIP y Entornos Virtuales
+
50Reto: Calculadora Modular
+
+
+ + +
+

06. Automatización OS

+
+
51Módulo OS y Rutas
+
52Lectura de Archivos TXT
+
53Escritura y Modos (w, a)
+
54Manejo de CSVs
+
55Shutil: Mover y Copiar
+
56Datetime y Cronos
+
57Subprocess: Comandos Pro
+
58Logging (Caja Negra)
+
59Try / Except / Finally
+
60Reto: Organizador de Archivos
+
+
+ + +
+

07. Web Scraping & APIs +

+
+
61Protocolo HTTP (Requests)
+
62Parseo de JSON Dinámico
+
63BeautifulSoup: HTML Básico
+
64Selectores CSS y XPATH
+
65Selenium: Web Dinámica
+
66Autenticación en APIs
+
67Rate Limiting y Proxies
+
68Headless Browsing
+
69Pandas para Web Data
+
70Reto: Monitor de Precios
+
+
+ + +
+

08. POO Avanzada

+
+
71Clases y Objetos (Blueprints)
+
72El Constructor __init__
+
73Herencia y Polimorfismo
+
74Encapsulamiento (Privados)
+
75Propiedades y Decoradores
+
76Métodos de Clase y Estáticos
+
77Dunder Methods (__str__, __len__) +
+
78Abstracción (ABC)
+
79Patrones de Diseño: Singleton
+
80Reto: Simulador de Ecosistema
+
+
+ + +
+

09. Ciencia de Datos e IA +

+
+
81NumPy: Álgebra de Máquina
+
82Pandas: Dataframes Pro
+
83Limpieza de Datos (Outliers)
+
84Matplotlib y Visualización
+
85Intro a Scikit-Learn
+
86Regresión Lineal Real
+
87Clasificación de Imágenes
+
88NLP: Procesando Lenguaje
+
89LLMs: Usando OpenAI/Groot
+
90Reto: Predictor de Bolsa
+
+
+ + +
+

10. Nerve Master +

+
+
91Local-First: El Cambio
+
92Arquitectura de Nodos
+
93Comunicación P2P
+
94Manejo de Estado Global
+
95Criptografía en Python
+
96Sincronización de Datos
+
97Microservicios con Nerve
+
98Despliegue Distribuido
+
99Optimización de Recursos
+
100PROYECTO FINAL: PILOT
+
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/MEGA_RETO.md b/MEGA_RETO.md deleted file mode 100644 index e79b483..0000000 --- a/MEGA_RETO.md +++ /dev/null @@ -1,36 +0,0 @@ -# El Mega Reto Trimestral - -¡Felicidades! Si estás leyendo esto, es porque has demostrado tu constancia y has completado el 100% de los retos de nivel Principiante requeridos. Has desbloqueado el **Mega Reto**, la puerta de entrada para participar en el Premio Trimestral de la comunidad. - -## Objetivo: Construye un MVP - -A diferencia de los retos anteriores donde se te daba un problema específico con instrucciones precisas, el Mega Reto evalúa tu capacidad para integrar lo aprendido. - -Tu misión es construir un **Producto Mínimo Viable (MVP)** que integre al menos **2 o 3 conceptos** vistos en los retos que ya completaste. Por ejemplo: -- Si hiciste el reto de scraping (PG 02) y el traductor (Nerve 03), podrías hacer una herramienta que scrapea noticias, las traduce y te las manda. -- Si hiciste el de gastos (PG 04) y recordatorios (PG 09), podrías hacer un bot de gastos con alertas locales. -- Si dominas Nerve, podrías hacer un sistema de nodos que hagan alguna de las tareas anteriores. - -¡Tú decides qué construir! Puede vivir dentro del ecosistema Nerve (usando IPC) o ser una herramienta Python independiente y útil para ti o para la comunidad. - -## El Premio Trimestral - -Al final de cada trimestre, los mantenedores y moderadores evaluaremos los MVPs entregados. El autor del mejor proyecto recibirá: -- **4 meses gratis de Google AI Pro** (acceso a Gemini Advanced, Antigravity, y beneficios de Google Cloud). - -## 📊 Criterios de Evaluación - -Tu proyecto será evaluado en base a 4 pilares: -1. **Utilidad Real:** ¿Resuelve un problema real, por pequeño que sea? -2. **Creatividad:** ¿Cómo combinaste los conceptos de los retos anteriores? -3. **Calidad de Código:** ¿Tu código está limpio, estructurado y es fácil de leer? -4. **Documentación:** ¿El `README.md` explica claramente qué es, cómo instalarlo y cómo usarlo? - -## 📦 Cómo Participar - -1. Desarrolla tu MVP en una carpeta nueva dentro de `retos/mega-reto/[tu-usuario]/`. -2. Incluye todo tu código y, muy importante, tu propio `README.md`. -3. Abre un **Pull Request** titulado `Mega Reto - [Nombre de tu MVP] - [Tu usuario]`. -4. El bot de Discord y los moderadores validarán que cumplas con los requisitos. - -¡Mucho éxito y a programar! diff --git a/README.md b/README.md index 48127d8..1258f0e 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,66 @@ -# nerve-community - -![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg) -![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg) -![Good First Issue](https://img.shields.io/badge/good%20first%20issue-welcome-7057ff.svg) -![Hacktoberfest](https://img.shields.io/badge/Hacktoberfest-participating-ff7518.svg) -![Idioma: Español](https://img.shields.io/badge/Idioma-Espa%C3%B1ol-c5def5.svg) - -> **Tu primer Pull Request puede empezar aquí.** - -## ¿Qué es este repo? -Este es un espacio de aprendizaje y contribución diseñado para cualquiera que quiera perderle el miedo a colaborar en proyectos Open Source. Usamos **Nerve** como excusa técnica para construir herramientas y scripts pequeños, divertidos y útiles. **No hay preguntas tontas.** Si nunca has hecho un Pull Request, o si apenas estás aprendiendo a programar en Python, este es el lugar ideal para ti. - -### 🔍 ¿Por qué existe este repo? -Creemos en ser honestos sobre esto. [Lee la explicación completa aquí](docs/Porque.md). - -### 📚 ¿No entiendes algún término? -Si eres nuevo y palabras como *fork*, *commit*, *push* o *pull request* te suenan raro, ¡no te preocupes! Preparamos un **[Glosario para Principiantes](glosario/README.md)** donde explicamos cada término (qué es, para qué sirve y un ejemplo) de forma súper sencilla. - -## ¿Por qué existe Nerve? -[alenia-nerve](https://github.com/Kaia-Alenia/alenia-nerve) es un sistema de comunicación entre procesos (IPC) local, multiplataforma y offline-first, que te permite conectar diferentes scripts (incluso en diferentes lenguajes) de forma súper sencilla usando sockets locales (Unix Sockets en Linux/macOS, TCP en Windows). - -## 💬 Únete a la comunidad -Si tienes dudas, quieres hablar con otros contribuidores o simplemente quieres saludar, ¡estamos en Discord! -👉 **[Únete al servidor de Nerve Community aquí](https://discord.gg/xFptGAr7t)** - -## Retos Disponibles - -### Reto Rompehielo -| Reto | Nivel | Tecnología principal | Estado | Enlace al Issue | -|---|---|---|---|---| -| **00-rompe-el-hielo** | 🟢 Principiante | GitHub | `disponible` | [#12](https://github.com/Kaia-Alenia/nerve-community/issues/12) | - -### Retos Core (Nerve) -| Reto | Nivel | Tecnología principal | Estado | Enlace al Issue | -|---|---|---|---|---| -| **01-chat-terminal** | 🟢 Principiante | Python (Sockets, Nerve) | `disponible` | [#1](https://github.com/Kaia-Alenia/nerve-community/issues/1) | -| **02-reloj-sincronizado** | 🟢 Principiante | Python (rich) | `disponible` | [#2](https://github.com/Kaia-Alenia/nerve-community/issues/2) | -| **03-traductor-de-mensajes** | 🟢 Principiante | Python (Requests/APIs) | `disponible` | [#3](https://github.com/Kaia-Alenia/nerve-community/issues/3) | -| **04-vigilante-de-carpetas** | 🟡 Intermedio | Python (watchdog) | `disponible` | [#4](https://github.com/Kaia-Alenia/nerve-community/issues/4) | -| **05-logger-distribuido** | 🟡 Intermedio | Python (SQLite) | `disponible` | [#5](https://github.com/Kaia-Alenia/nerve-community/issues/5) | -| **06-puente-de-progreso-gif** | 🟡 Intermedio | Python (Pillow) | `disponible` | [#6](https://github.com/Kaia-Alenia/nerve-community/issues/6) | -| **07-bot-discord-nerve** | 🟡 Intermedio | Python (discord.py) | `disponible` | [#7](https://github.com/Kaia-Alenia/nerve-community/issues/7) | -| **08-puente-http** | 🔴 Avanzado | Python (FastAPI) | `disponible` | [#8](https://github.com/Kaia-Alenia/nerve-community/issues/8) | -| **09-probador-de-carga** | 🔴 Avanzado | Python (Concurrencia) | `disponible` | [#9](https://github.com/Kaia-Alenia/nerve-community/issues/9) | -| **10-cliente-nuevo-lenguaje** | 🔴 Avanzado | Dart / Kotlin / Go / etc. | `disponible` | [#10](https://github.com/Kaia-Alenia/nerve-community/issues/10) | - -### Retos de Python General -| Reto | Nivel | Tecnología principal | Estado | Enlace al Issue | -|---|---|---|---|---| -| **01-lista-tareas** | 🟢 Principiante | Python | `disponible` | [#13](https://github.com/Kaia-Alenia/nerve-community/issues/13) | -| **02-web-scraper** | 🟢 Principiante | Python | `disponible` | [#14](https://github.com/Kaia-Alenia/nerve-community/issues/14) | -| **03-juego-adivinar** | 🟢 Principiante | Python | `disponible` | [#15](https://github.com/Kaia-Alenia/nerve-community/issues/15) | -| **04-analizador-gastos** | 🟢 Principiante | Python | `disponible` | [#16](https://github.com/Kaia-Alenia/nerve-community/issues/16) | -| **05-generador-passwords** | 🟢 Principiante | Python | `disponible` | [#17](https://github.com/Kaia-Alenia/nerve-community/issues/17) | -| **06-conversor-divisas** | 🟢 Principiante | Python | `disponible` | [#18](https://github.com/Kaia-Alenia/nerve-community/issues/18) | -| **07-calculadora-propinas** | 🟢 Principiante | Python | `disponible` | [#19](https://github.com/Kaia-Alenia/nerve-community/issues/19) | -| **08-organizador-archivos** | 🟢 Principiante | Python | `disponible` | [#20](https://github.com/Kaia-Alenia/nerve-community/issues/20) | -| **09-recordatorio-tareas** | 🟡 Intermedio | Python | `disponible` | [#21](https://github.com/Kaia-Alenia/nerve-community/issues/21) | -| **10-analizador-texto** | 🟢 Principiante | Python | `disponible` | [#22](https://github.com/Kaia-Alenia/nerve-community/issues/22) | -| **11-profiler-de-funciones** | 🟡 Intermedio | Python | `disponible` | [#25](https://github.com/Kaia-Alenia/nerve-community/issues/25) | -| **12-validador-de-emails** | 🟡 Intermedio | Python | `disponible` | [#26](https://github.com/Kaia-Alenia/nerve-community/issues/26) | -| **13-lector-archivos-gigantes** | 🟡 Intermedio | Python | `disponible` | [#27](https://github.com/Kaia-Alenia/nerve-community/issues/27) | -| **14-cli-del-clima** | 🟡 Intermedio | Python | `disponible` | [#28](https://github.com/Kaia-Alenia/nerve-community/issues/28) | -| **15-herramienta-de-backup** | 🟡 Intermedio | Python | `disponible` | [#29](https://github.com/Kaia-Alenia/nerve-community/issues/29) | -| **16-validador-de-edad** | 🟡 Intermedio | Python | `disponible` | [#30](https://github.com/Kaia-Alenia/nerve-community/issues/30) | -| **17-lector-de-configuraciones** | 🟡 Intermedio | Python | `disponible` | [#31](https://github.com/Kaia-Alenia/nerve-community/issues/31) | -| **18-inventario-de-tienda** | 🟡 Intermedio | Python | `disponible` | [#32](https://github.com/Kaia-Alenia/nerve-community/issues/32) | - -> 📝 **¿Eres mantenedor/a o quieres ver el texto original de los retos?** -> Consulta las plantillas de los issues en [RETOS_ISSUES.md](docs/RETOS_ISSUES.md) (para los retos Rompehielo y Core) y [PYTHON_GENERAL_ISSUES.md](docs/PYTHON_GENERAL_ISSUES.md) (para los retos de Python General). - -## 📱 ¿No tienes computadora? ¡Usa tu celular! -No tener PC no es excusa para no programar. Puedes resolver muchos de nuestros retos usando solo tu celular Android y la app **Termux**. -Varios de nuestros retos (los de Python General) están marcados con la etiqueta `termux-friendly`, lo que significa que no necesitan compiladores pesados ni bases de datos complejas. - -👉 **[Lee la guía paso a paso para empezar desde tu celular aquí](docs/EMPEZAR-CON-SOLO-TU-CELULAR.md)**. - -## ¿Nunca has hecho un Pull Request? -¡No te preocupes! Tenemos una guía paso a paso, desde cero, explicando cada comando que necesitas escribir. Léela aquí: **[CÓMO HACER TU PRIMER PR](COMO-HACER-TU-PRIMER-PR.md)**. - -## Reglas de oro (Innegociables) -- **NUNCA** pidas acceso de escritura directo. Siempre haz un *fork* de este repositorio. -- **¡Todos pueden participar!** En esta primera edición no hay límite de personas por reto. Si te interesa un reto, simplemente comenta "¡Yo lo tomo!" o "/tomar-reto" en su issue para avisarnos, ¡incluso si alguien más ya lo está haciendo! -- Todo Pull Request (PR) pasa por una **revisión** antes de hacer *merge*. No te asustes si te pedimos algún cambio; es parte de aprender. -- **Sé amable.** En los comentarios o reportes de bugs, recuerda que aquí todos estamos aprendiendo. - -## De la comunidad a producción -Las mejores herramientas construidas aquí no se quedan solo como ejercicio. Si tu contribución está bien hecha, es útil y estable, puede ser invitada a integrarse directamente al repositorio de producción **zenith-nerve-tools**, con crédito completo para ti como autor/a. Varios de los retos de Nivel 2 y 3 están pensados justo para eso: son herramientas reales que pueden pasar de "ejercicio" a "software que otros usan". - -## Premio Trimestral -La contribución más destacada del trimestre recibe **4 meses gratis de Google AI Pro** (acceso a Gemini Advanced, Antigravity con cuotas mucho más altas, agentes Claude, y beneficios del ecosistema Google Cloud). - -## Financiamiento y Transparencia -Las donaciones vía Ko-fi / GitHub Sponsors se usan exclusivamente para financiar los premios de la comunidad (suscripciones, reconocimientos, y eventualmente merch). Cada trimestre publicaremos un resumen transparente de cómo se usó lo recaudado en nuestro archivo [TRANSPARENCIA.md](TRANSPARENCIA.md). - -## Cumpliendo Retos -¡Gracias a todas las personas que hacen esto posible! Cada contribución cuenta. - -- [Tacos-1212](https://github.com/Tacos-1212) -- [Vi-dot1](https://github.com/Vi-dot1) -- [CesarAAR](https://github.com/CesarAAR) -- [isaacglezacosta](https://github.com/isaacglezacosta) -- [downstairs777](https://github.com/downstairs777) - -## Creando Retos -- [Alejandro (Alenia Studios)](https://github.com/Alenia-Studios) - -## 📄 Licencia -Este repositorio, al igual que Nerve, está licenciado bajo **GNU GPL v3**. Lee el archivo `LICENSE` para más detalles. +# Nerve Community + +Bienvenido a **nerve-community**, el repositorio público del ecosistema de aprendizaje de programación de Alenia Studios. Aquí reside el currículum completo, la teoría, los ejemplos y las pruebas que conforman los cursos. + +Para entender el propósito y enfoque pedagógico del proyecto, lee nuestra filosofía: **[Aprender a Programar de Verdad](docs/Porque.md)**. + +--- + +## Dos formas de usar este repositorio + +### Como Alumno — no hagas Fork de este repo + +Si estás tomando el programa o quieres hacer los cursos, **no debes hacer un Fork de este repositorio**. El flujo de aprendizaje parte de un template oficial que te genera un entorno aislado, automatizado y privado. + +- Instrucciones paso a paso: [Cómo usar como Alumno](docs/COMO-USAR-COMO-ALUMNO.md) +- Metodología de evaluación: [Filosofía de los Retos Socráticos](docs/RETOS-SOCRATICOS.md) + +En tu repositorio privado (generado desde el template): + +- Al inicio solo tendrás disponible el Módulo 01. +- Cada módulo tiene teoría (`teoria.md`), código de referencia (`ejemplo.py`) y un desafío (`reto.md`). +- Para cada reto, leerás el código, interpretarás los errores y resolverás el problema con el apoyo de los tests automáticos. +- Al pasar la validación de CI con un check verde, se desbloquea el siguiente módulo. + +### Como Contribuidor — Open Source + +Si quieres mejorar la calidad del material del curso: + +- Haz **Fork** de este repositorio (`nerve-community`). +- Mejora el contenido (documentación, ejemplos, retos) y envía un **Pull Request**. +- Toda la comunidad se beneficia de tus mejoras. + +Guías para contribuir: + +- [Guía de Contribución](.github/CONTRIBUTING.md) +- [Cómo hacer tu primer PR](COMO-HACER-TU-PRIMER-PR.md) + +--- + +## Estructura del repositorio + +```text +python/ ← Currículum completo del curso de Python (Módulos 01 a 12 + intermedios) +retos/ ← Retos socráticos clasificados por lenguaje y nivel de dificultad +docs/ ← Documentación del proyecto, filosofía y guías de uso +.github/ ← Workflows de GitHub Actions, plantillas de Issues/PRs y guía de contribución +glosario/ ← Diccionario técnico de términos clave, organizado por tema y lenguaje +``` + +> **Regla de Oro:** La estructura de carpetas dentro de los lenguajes (ej. `python/Modulo-XX`) no debe alterarse en nomenclatura. Estos archivos alimentan los templates de los alumnos y la evaluación automática. + +--- + +## Qué puedes mejorar + +- Aclarar explicaciones en los archivos `teoria.md`. +- Añadir comentarios o casos alternativos en los `ejemplo.py`. +- Corregir bugs o robustecer pruebas en los `test_main.py`. +- Proponer nuevos Retos Socráticos dentro de `retos/`. + +--- + +## Comunidad y Contacto + +- **Discord**: [Únete a nuestra comunidad](https://discord.gg/xFptGAr7t) para resolver dudas, compartir proyectos y hablar con otros estudiantes y desarrolladores. +- **Licencia**: GNU GPL v3. diff --git a/assets/Vi-dot1.png b/assets/Vi-dot1.png deleted file mode 100644 index 83a1ac9..0000000 Binary files a/assets/Vi-dot1.png and /dev/null differ diff --git a/assets/tacos-termux.jpg b/assets/tacos-termux.jpg deleted file mode 100644 index f20a8d7..0000000 Binary files a/assets/tacos-termux.jpg and /dev/null differ diff --git a/docs/COMO-USAR-COMO-ALUMNO.md b/docs/COMO-USAR-COMO-ALUMNO.md new file mode 100644 index 0000000..288412e --- /dev/null +++ b/docs/COMO-USAR-COMO-ALUMNO.md @@ -0,0 +1,73 @@ +# Cómo usar este ecosistema como Alumno + +Bienvenido al ecosistema de Alenia Studios / Nerve Community. Este espacio está diseñado como un entorno de aprendizaje práctico, no como un lugar para leer: está hecho para que hagas, rompas y descubras. + +> **IMPORTANTE: No hagas Fork de este repositorio público.** +> Para garantizar un entorno de evaluación automática correcto, debes generar tu propio repositorio a partir de nuestra plantilla oficial. + +--- + +## 1. Cómo empezar: el template + +Tu punto de partida es el repositorio template `nerve--template`, no este repo. El template te genera un repositorio privado con: + +- La estructura de módulos ya configurada. +- Los workflows de CI listos para evaluar tu código automáticamente. +- El Módulo 01 desbloqueado desde el inicio. + +Para obtener acceso al template, entra al servidor de Discord y pide el enlace en el canal correspondiente. Un administrador te lo compartirá. + +--- + +## 2. El ciclo de aprendizaje + +El flujo está inspirado en cómo trabajan los ingenieros de software en la vida real: + +1. **Genera tu repo desde el template** — crea tu repositorio privado (o público, según prefieras) desde `nerve--template`. +2. **Clona tu repositorio** — descarga los archivos a tu computadora con `git clone`. +3. **Elige el módulo activo** — navega a la carpeta correspondiente, por ejemplo `python/Modulo-01-Fundamentos`. +4. **Estudia la teoría** — lee `README.md` para entender el objetivo del módulo, luego `teoria.md` para los conceptos técnicos, y analiza `ejemplo.py` para ver cómo se aplican. +5. **Enfrenta el reto** — abre `reto.md`. Aquí encontrarás un problema práctico sin la respuesta. Tu tarea es resolverlo. +6. **Valida con los tests** — ejecuta `pytest test_main.py` en tu terminal local. Si un test falla, lee el error: el error es tu guía, no un obstáculo. +7. **Sube tu avance** — cuando todos los tests pasen, haz commit y push a tu repositorio. El CI de GitHub Actions correrá los mismos tests automáticamente. +8. **Desbloqueo automático** — al pasar la validación de CI, el siguiente módulo queda disponible en tu rama. + +--- + +## 3. Cómo funciona el CI automático + +Cuando haces push a tu repositorio, GitHub Actions ejecuta automáticamente: + +- Los tests del módulo (`pytest`) para verificar que tu código es correcto. +- El linter de estilo (`black`) para verificar que el formato es consistente. + +Si todo pasa, verás un check verde en tu commit. Si algo falla, el check será rojo y los logs del workflow te mostrarán exactamente qué falló y en qué línea. + +El CI no te penaliza por intentar. Puedes hacer push cuantas veces necesites. + +--- + +## 4. El glosario de Python + +En `glosario/python/` encontrarás explicaciones en español de los términos técnicos que aparecen en cada módulo. El glosario está dividido por módulo para que puedas consultarlo de forma específica, no como lectura lineal. + +Índice completo: [glosario/python/README.md](../glosario/python/README.md) + +--- + +## 5. Si te atascas + +Es completamente normal atascarse. La frustración es parte de reprogramar la forma en que tu cerebro resuelve problemas. Antes de pedir ayuda directamente: + +- Lee el error completo. Python te dice exactamente en qué línea falló y por qué. +- Rompe el código a propósito: cambia una variable, quita un `return`, corre los tests. Observa qué pasa. +- Busca en la documentación oficial de Python o en los archivos del glosario. + +Cuando lo anterior no sea suficiente: + +- Pregunta en el canal de **Discord** de la comunidad. Describe qué intentaste, qué error obtienes y qué parte no entiendes. No compartas la solución completa. +- Lee [RETOS-SOCRATICOS.md](RETOS-SOCRATICOS.md) para entender la filosofía detrás de la evaluación. + +--- + +← [Volver al repositorio](../README.md) diff --git a/docs/EMPEZAR-CON-SOLO-TU-CELULAR.md b/docs/EMPEZAR-CON-SOLO-TU-CELULAR.md deleted file mode 100644 index 8d1b1dc..0000000 --- a/docs/EMPEZAR-CON-SOLO-TU-CELULAR.md +++ /dev/null @@ -1,131 +0,0 @@ -# Empieza a contribuir usando solo tu celular (Termux) - -No tener computadora no es excusa para hacer tu primer Pull Request. Con tu celular Android y esta guía, puedes resolver retos reales de programación. - -## ¿Qué es Termux y por qué es seguro? -Termux es una aplicación para Android que emula una terminal Linux real. Es de código abierto y no requiere acceso root. -**ATENCIÓN:** Debes instalar Termux **únicamente** desde [F-Droid](https://f-droid.org/packages/com.termux/). NO lo instales desde la Google Play Store, ya que esa versión está desactualizada y descontinuada. - -## Instalación paso a paso - -1. Instala F-Droid en tu celular (si no lo tienes) y descarga Termux desde ahí. -2. Abre Termux por primera vez y ejecuta los siguientes comandos para actualizar el sistema e instalar las herramientas necesarias: - ```bash - pkg update && pkg upgrade - pkg install python git gh -y - ``` -3. Verifica que todo se haya instalado correctamente: - ```bash - python --version - git --version - gh --version - ``` -4. Dale permiso a Termux para acceder al almacenamiento de tu celular (útil si deseas editar los archivos fuera de Termux): - ```bash - termux-setup-storage - ``` - -## Editor de código recomendado - -Escribir código directamente en la terminal del celular puede ser incómodo. Te recomendamos instalar la aplicación **Acode** (disponible en F-Droid o Play Store). Acode es un editor de texto con resaltado de sintaxis muy fácil de usar, y puede abrir los archivos directamente desde la carpeta de Termux. - -**Alternativa simple:** Si no quieres instalar otra aplicación, puedes usar el editor `nano` que viene integrado en Termux. Comandos básicos de nano: -- Guardar: `Ctrl + O` y luego `Enter` -- Salir: `Ctrl + X` - -## Autenticarte con GitHub desde Termux - -Para poder contribuir, necesitas enlazar Termux con tu cuenta de GitHub. Ejecuta: -```bash -gh auth login -``` -Te hará una serie de preguntas, responde así: -- *What account do you want to log into?* → **GitHub.com** -- *What is your preferred protocol for Git operations?* → **HTTPS** -- *Authenticate Git with your GitHub credentials?* → **Yes** -- *How would you like to authenticate GitHub CLI?* → **Login with a web browser** - -Te dará un código de 8 caracteres. Cópialo, presiona `Enter` para abrir el navegador, inicia sesión en GitHub y pega el código. - -## Clonar tu fork usando Sparse-Checkout (IMPORTANTE) - -**No clones todo el repositorio.** El proyecto contiene muchos retos y lenguajes pesados que consumirán tus datos móviles y espacio de almacenamiento. En su lugar, descargaremos **únicamente** la carpeta del reto en el que vas a trabajar. - -1. Ve a la página de [nerve-community](https://github.com/Kaia-Alenia/nerve-community) en tu navegador y presiona **Fork**. -2. En Termux, ejecuta lo siguiente (reemplaza `TU-USUARIO` por tu usuario de GitHub y usa el comando de la tabla de abajo): - -```bash -# Clonar solo la estructura sin archivos -git clone --filter=blob:none --no-checkout https://github.com/TU-USUARIO/nerve-community.git -cd nerve-community -git sparse-checkout init --cone - -# Configurar la carpeta específica del reto (EJEMPLO) -git sparse-checkout set retos/general/NOMBRE-DEL-RETO - -# Extraer los archivos de esa carpeta -git checkout main -cd retos/general/NOMBRE-DEL-RETO -``` - -### Comandos de Sparse-Checkout para cada reto (Copia y pega) - -Solo necesitas el reto de la carpeta `general/`. Aquí tienes el comando listo para cada reto: - -| Reto | Comando | -|------|---------| -| 01 - Lista de tareas CLI | `git sparse-checkout set retos/general/01-lista-de-tareas-cli` | -| 02 - Web scraper simple | `git sparse-checkout set retos/general/02-web-scraper-simple` | -| 03 - Juego de adivinanza | `git sparse-checkout set retos/general/03-juego-de-adivinanza` | -| 04 - Analizador de gastos CSV | `git sparse-checkout set retos/general/04-analizador-de-gastos-csv` | -| 05 - Generador de contraseñas | `git sparse-checkout set retos/general/05-generador-de-contrasenas` | -| 06 - Conversor de unidades | `git sparse-checkout set retos/general/06-conversor-de-unidades` | -| 07 - Calculadora de IMC | `git sparse-checkout set retos/general/07-calculadora-de-imc` | -| 08 - Organizador de archivos | `git sparse-checkout set retos/general/08-organizador-de-archivos` | -| 09 - Recordatorios en terminal | `git sparse-checkout set retos/general/09-recordatorios-en-terminal` | -| 10 - Analizador de texto | `git sparse-checkout set retos/general/10-analizador-de-texto` | -| 11 - Profiler de funciones | `git sparse-checkout set retos/general/11-profiler-de-funciones` | -| 12 - Validador de emails | `git sparse-checkout set retos/general/12-validador-de-emails` | -| 13 - Lector de archivos gigantes | `git sparse-checkout set retos/general/13-lector-archivos-gigantes` | -| 14 - CLI del clima | `git sparse-checkout set retos/general/14-cli-del-clima` | -| 15 - Herramienta de backup | `git sparse-checkout set retos/general/15-herramienta-de-backup` | -| 16 - Validador de edad | `git sparse-checkout set retos/general/16-validador-de-edad` | -| 17 - Lector de configuraciones | `git sparse-checkout set retos/general/17-lector-de-configuraciones` | -| 18 - Inventario de tienda | `git sparse-checkout set retos/general/18-inventario-de-tienda` | - -*Si quieres trabajar en otro reto más adelante, no necesitas borrar nada. Solo ejecuta el nuevo comando `git sparse-checkout set ...` en la carpeta `nerve-community` y Git se encargará del resto.* - -## Instalar dependencias de Python - -Dependiendo del reto, puedes necesitar dependencias externas (ej. `pip install requests beautifulsoup4` para el scraper). - -> ⚠️ **Atención:** Algunas librerías complejas que requieren compilación en C (como `Pillow` o `numpy`) pueden dar problemas en Termux. Si apenas inicias, escoge retos que utilicen solo Python puro (mira la sección de recomendados abajo). - -## Flujo de trabajo normal - -Una vez configurado todo, el trabajo es idéntico al que harías en una PC: -1. Crea una rama nueva: `git checkout -b mi-solucion` -2. Edita los archivos con Acode o `nano` y prueba tu código corriendo `python archivo.py` -3. Haz el commit: `git add .` y luego `git commit -m "feat: mi solución"` -4. Sube los cambios a tu fork: `git push -u origin mi-solucion` -5. Crea el Pull Request desde GitHub (o usando `gh pr create`). - -*(Si es tu primera vez, consulta la guía paso a paso: [COMO-HACER-TU-PRIMER-PR.md](COMO-HACER-TU-PRIMER-PR.md))* - -## Problemas comunes y soluciones - -- **Error al instalar con pip:** Si `pip install` falla indicando falta de compilador, ejecuta `pkg install clang python-dev` e intenta de nuevo. -- **Espacio insuficiente:** Revisa tu espacio libre ejecutando `df -h`. Puedes liberar espacio borrando el caché de paquetes con `pkg clean`. -- **El celular se calienta rápido:** La compilación e instalación prolongada exigen mucho al procesador. Evita correr tareas muy largas con la pantalla encendida permanentemente; pausa y carga tu celular si es necesario. - -## ¿Qué retos SÍ recomendamos desde el celular? - -Hemos curado una lista con los retos ideales para Android, que no requieren librerías pesadas ni compilación. -👉 **Puedes consultarla aquí:** [Issue #46 - Retos ideales para Termux](https://github.com/Kaia-Alenia/nerve-community/issues/46) - -*(Nota: Los retos de la carpeta `retos/nerve/` enfocados en Go y Rust implican compilación nativa pesada, por lo que no se recomiendan para trabajar desde el celular por ahora).* - ---- - -### ¡El código no discrimina dispositivos! -Muchas personas maravillosas han aprendido a programar utilizando únicamente su celular Android. Tu primer Pull Request, ya sea enviado desde un teclado mecánico RGB o desde el teclado táctil en el transporte público, cuenta exactamente igual y tiene el mismo valor para la comunidad open source. ¡A programar! diff --git a/docs/PYTHON_GENERAL_ISSUES.md b/docs/PYTHON_GENERAL_ISSUES.md deleted file mode 100644 index 8b9e759..0000000 --- a/docs/PYTHON_GENERAL_ISSUES.md +++ /dev/null @@ -1,655 +0,0 @@ -# Issues de los Retos de Python General (Plantillas para GitHub) - -A continuación tienes el texto para copiar y pegar y crear los 18 issues de los retos de Python General en GitHub. Recuerda asignarles las etiquetas correctas a cada uno (ej. `good-first-issue`, `python-general`, `disponible`). - ---- - -# Python General 01 — Lista de tareas (to-do list) en terminal - -**Título:** Python General 01 — Lista de tareas (to-do list) en terminal - -## Objetivo -Construye una lista de tareas manejable 100% desde terminal: agregar, marcar como completada, eliminar y ver tareas pendientes. Sin usar Nerve ni librerías externas de terceros. - -## Qué vas a aprender -- Manejo de archivos (guardar/leer un archivo de texto o JSON). -- Estructuras de datos básicas (listas, diccionarios). -- Diseño de un menú interactivo con `input()`. - -## Nivel -Principiante. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (`json` o manejo de archivos de texto plano). - -## Requisitos -- [ ] Menú con opciones: agregar tarea, ver tareas, marcar como completada, eliminar tarea, salir. -- [ ] Las tareas deben persistir entre ejecuciones (guardadas en un archivo). -- [ ] Manejo de errores si el archivo no existe la primera vez que se corre. -- [ ] `README.md` explicando cómo correr el script. - -## 💡 Pistas -- `json.dump` / `json.load` son suficientes, no necesitas una base de datos. -- Piensa en qué pasa si el usuario intenta marcar como completada una tarea que no existe. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/01-lista-de-tareas-cli/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 02 — Web scraper simple - -**Título:** Python General 02 — Web scraper simple - -## Objetivo -Extrae información simple (títulos, precios, o encabezados) de una página web pública usando `requests` y `BeautifulSoup`, y guárdala en un archivo CSV. - -## Qué vas a aprender -- Peticiones HTTP con `requests`. -- Parsing de HTML con `BeautifulSoup`. -- Manejo de errores de red (timeouts, páginas caídas). -- Exportar datos a CSV. - -## Nivel -Principiante. - -## 🛠️ Tecnologías sugeridas -Python 3.10+, `requests`, `beautifulsoup4`. - -## Requisitos -- [ ] Elige una página pública simple y permitida para scraping (ej. una página de ejemplo como `books.toscrape.com`, hecha específicamente para practicar). -- [ ] Extrae al menos 2 campos de datos (ej. título y precio) de varios elementos de la página. -- [ ] Guarda los resultados en un archivo `resultados.csv`. -- [ ] Manejo de errores si la página no responde o cambia su estructura. -- [ ] `README.md` explicando qué página se scrapeó y por qué es válida para practicar (evitar sitios que prohíban scraping en su `robots.txt`). - -## 💡 Pistas -- `books.toscrape.com` y `quotes.toscrape.com` existen específicamente para que la gente practique scraping sin problemas éticos ni legales. -- Siempre revisa el archivo `robots.txt` del sitio antes de scrapear cualquier página real. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/02-web-scraper-simple/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 03 — Juego de adivina el número - -**Título:** Python General 03 — Juego de adivina el número - -## Objetivo -El clásico juego donde la computadora piensa un número al azar y el usuario debe adivinarlo con pistas de "más alto" o "más bajo", contando sus intentos. - -## Qué vas a aprender -- Loops y condicionales. -- El módulo `random`. -- Validación de entradas del usuario (que no meta letras donde va un número, por ejemplo). - -## Nivel -Principiante (ideal si nunca has programado nada interactivo). - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar. - -## Requisitos -- [ ] El rango de números debe ser configurable (ej. entre 1 y 100). -- [ ] Debe dar pistas ("más alto"/"más bajo") después de cada intento incorrecto. -- [ ] Debe contar y mostrar el número de intentos al finalizar. -- [ ] Manejo de errores si el usuario escribe algo que no es un número. -- [ ] `README.md` explicando cómo jugar. - -## 💡 Pistas -- Usa un `try/except` para capturar cuando el usuario no escribe un número válido. -- Piensa en agregar un límite máximo de intentos como mejora opcional. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/03-juego-de-adivinanza/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 04 — Analizador de gastos personales (CSV) - -**Título:** Python General 04 — Analizador de gastos personales (CSV) - -## Objetivo -Lee un archivo CSV con gastos personales (fecha, categoría, monto) y genera un resumen: total gastado, total por categoría, promedio, y el gasto más alto. - -## Qué vas a aprender -- El módulo `csv` de la librería estándar. -- Agregación de datos (sumas, promedios, máximos). -- Formateo de reportes legibles en terminal. - -## Nivel -Principiante. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (`csv`). - -## Requisitos -- [ ] Incluye un archivo CSV de ejemplo con al menos 15 gastos ficticios (fecha, categoría, monto). -- [ ] El script debe calcular: total general, total por categoría, promedio de gasto, y el gasto individual más alto (con su categoría y fecha). -- [ ] El reporte debe imprimirse ordenado y legible en terminal. -- [ ] Manejo de errores si el CSV tiene filas mal formadas o vacías. -- [ ] `README.md` explicando el formato esperado del CSV. - -## 💡 Pistas -- `csv.DictReader` facilita mucho leer filas con nombres de columna. -- Considera qué pasa si un monto viene con "$" o comas como separador de miles. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/04-analizador-de-gastos-csv/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 05 — Generador de contraseñas seguras - -**Título:** Python General 05 — Generador de contraseñas seguras - -## Objetivo -Genera contraseñas seguras y configurables: longitud, si incluye símbolos, mayúsculas, números, etc. - -## Qué vas a aprender -- Los módulos `random` y `secrets` (y por qué `secrets` es mejor para seguridad). -- Validación de parámetros de entrada. -- Diseño de una herramienta configurable por línea de comandos. - -## Nivel -Principiante. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (`secrets`, `string`, opcionalmente `argparse`). - -## Requisitos -- [ ] Debe permitir elegir la longitud de la contraseña. -- [ ] Debe permitir activar/desactivar: símbolos, números, mayúsculas. -- [ ] Debe garantizar que, si se piden ciertos tipos de caracteres, la contraseña generada realmente los incluya al menos una vez. -- [ ] Manejo de errores si se piden parámetros imposibles (ej. longitud 0, o ningún tipo de carácter activado). -- [ ] `README.md` explicando las opciones disponibles y por qué se usa `secrets` en vez de `random` para esto. - -## 💡 Pistas -- `random` NO es seguro para contraseñas reales porque es predecible; `secrets` sí lo es. Investiga la diferencia y explícala en tu README. -- Puedes usar `argparse` para que el script se use así: `python generador.py --longitud 16 --simbolos`. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/05-generador-de-contrasenas/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 06 — Conversor de unidades - -**Título:** Python General 06 — Conversor de unidades - -## Objetivo -Un conversor de unidades desde un menú de terminal: temperatura (Celsius/Fahrenheit/Kelvin), distancia (km/millas/metros), y peso (kg/libras). - -## Qué vas a aprender -- Diseño de funciones puras (una función = una conversión, sin efectos secundarios). -- Validación de entradas numéricas. -- Diseño de menús interactivos con submenús. - -## Nivel -Principiante. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar. - -## Requisitos -- [ ] Al menos 3 categorías de conversión (temperatura, distancia, peso), cada una con al menos 2 unidades distintas. -- [ ] Cada conversión debe ser una función separada y reutilizable (no todo mezclado en un solo bloque de código). -- [ ] Manejo de errores si el usuario mete un valor no numérico. -- [ ] `README.md` con ejemplos de uso. - -## 💡 Pistas -- Organiza tus funciones de conversión en un archivo separado (ej. `conversiones.py`) y el menú en otro (ej. `main.py`), para practicar cómo dividir un proyecto en módulos. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/06-conversor-de-unidades/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 07 — Calculadora de IMC - -**Título:** Python General 07 — Calculadora de IMC - -## Objetivo -Calcula el Índice de Masa Corporal (IMC) a partir de peso y estatura, mostrando la categoría correspondiente (bajo peso, normal, sobrepeso, etc.) con mensajes claros. - -## Qué vas a aprender -- Funciones con múltiples parámetros. -- Validación de datos numéricos (que el peso/estatura sean positivos y razonables). -- Formateo de salida clara para el usuario. - -## Nivel -Principiante. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar. - -## Requisitos -- [ ] Debe pedir peso (kg) y estatura (metros), calcular el IMC, y mostrar la categoría según los rangos estándar. -- [ ] Manejo de errores si los valores son negativos, cero, o no numéricos. -- [ ] **Importante:** el mensaje de resultado debe ser informativo, no alarmista ni prescriptivo — evita frases como "debes bajar de peso"; usa un tono neutral tipo "tu IMC es X, lo cual se categoriza como Y según la OMS". -- [ ] `README.md` explicando la fórmula usada y aclarando que esto es una herramienta educativa, no un diagnóstico médico. - -## 💡 Pistas -- La fórmula es: `IMC = peso (kg) / estatura (m) ** 2`. -- Este es un buen ejercicio para practicar cómo comunicar resultados de forma responsable cuando el tema toca la salud de alguien. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/07-calculadora-de-imc/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 08 — Organizador automático de archivos - -**Título:** Python General 08 — Organizador automático de archivos - -## Objetivo -Un script que ordena automáticamente los archivos de una carpeta (por ejemplo, tu carpeta de Descargas) en subcarpetas según su tipo: imágenes, documentos, videos, comprimidos, etc. - -## Qué vas a aprender -- Los módulos `os` y `shutil` para manejar archivos y carpetas. -- Manejo seguro de rutas de archivos (evitando sobrescribir accidentalmente algo importante). -- Buenas prácticas al automatizar algo que toca archivos reales del usuario. - -## Nivel -Principiante-intermedio. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (`os`, `shutil`, `pathlib`). - -## Requisitos -- [ ] El script debe recibir una ruta de carpeta como argumento (nunca hardcodeada). -- [ ] Debe clasificar archivos en subcarpetas según su extensión (ej. `.jpg/.png` → `Imagenes/`, `.pdf/.docx` → `Documentos/`, etc.). -- [ ] **Importante:** debe incluir un modo "simulación" (`--dry-run`) que muestre qué haría SIN mover ningún archivo todavía, para que el usuario pueda revisar antes de ejecutar de verdad. -- [ ] Manejo de errores si la carpeta no existe o si un archivo ya existe en el destino (no debe sobrescribir sin avisar). -- [ ] `README.md` con una advertencia clara de que se recomienda probar primero con `--dry-run` en una carpeta de prueba antes de usarlo en carpetas reales. - -## 💡 Pistas -- `pathlib.Path` es más moderno y legible que `os.path` para este tipo de tareas. -- Piensa en un diccionario que mapee extensiones a nombres de carpeta, para que sea fácil agregar más tipos de archivo después. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/08-organizador-de-archivos/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 09 — Recordatorios en terminal - -**Título:** Python General 09 — Recordatorios en terminal - -## Objetivo -Un script que permite guardar recordatorios con fecha/hora, y que avisa en terminal cuando llega el momento (mientras el script sigue corriendo). - -## Qué vas a aprender -- El módulo `datetime` para manejar fechas y horas. -- Loops con espera (`time.sleep`) sin saturar el CPU. -- Persistencia simple de datos en un archivo. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (`datetime`, `json`, `time`). - -## Requisitos -- [ ] Permite agregar un recordatorio con texto + fecha/hora exacta. -- [ ] El script, mientras corre, debe revisar periódicamente si algún recordatorio ya se cumplió y avisar en pantalla (con sonido de terminal opcional, `\a`). -- [ ] Los recordatorios deben persistir en un archivo, para no perderlos si se cierra el script. -- [ ] Manejo de errores si el usuario mete una fecha/hora inválida o ya pasada. -- [ ] `README.md` explicando el formato de fecha esperado y cómo correr el script en segundo plano si se desea. - -## 💡 Pistas -- No revises cada milisegundo; con revisar cada 10-30 segundos es suficiente y no satura el CPU. -- Piensa en qué pasa con recordatorios que ya pasaron su fecha antes de que el script se vuelva a abrir. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/09-recordatorios-en-terminal/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 10 — Analizador de texto - -**Título:** Python General 10 — Analizador de texto - -## Objetivo -Recibe un archivo de texto (ej. un `.txt`) y genera estadísticas: palabras más frecuentes, número de líneas, número de palabras, y promedio de palabras por línea. - -## Qué vas a aprender -- Manejo de strings (limpieza, división, conteo). -- Diccionarios para contar frecuencias. -- Ordenar resultados (ej. top 10 palabras más usadas). - -## Nivel -Principiante. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (opcionalmente `collections.Counter`). - -## Requisitos -- [ ] Debe recibir la ruta de un archivo `.txt` como argumento. -- [ ] Debe reportar: total de líneas, total de palabras, promedio de palabras por línea, y las 10 palabras más frecuentes (ignorando mayúsculas/minúsculas y signos de puntuación). -- [ ] Debe ignorar palabras "vacías" comunes en español si se quiere un análisis más útil (ej. "el", "la", "de", "que") — esto puede ser una lista simple hardcodeada. -- [ ] Manejo de errores si el archivo no existe o está vacío. -- [ ] `README.md` con un ejemplo de archivo de prueba y su resultado esperado. - -## 💡 Pistas -- `collections.Counter` hace el conteo de frecuencias en una sola línea. -- Usa expresiones regulares (`re`) o `str.strip(string.punctuation)` para limpiar signos de puntuación antes de contar. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-1-principiante/10-analizador-de-texto/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 11 — Profiler de funciones (Decoradores) - -**Título:** Python General 11 — Profiler de funciones (Decoradores) - -## Objetivo -Crear un decorador `@time_it` que mida y registre el tiempo de ejecución de cualquier función, guardándolo en un archivo. - -## Qué vas a aprender -- Decoradores (`@`). -- Empaquetado de argumentos (`*args`, `**kwargs`). -- Funciones de orden superior. -- Uso básico del módulo `time`. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar. - -## Requisitos -- [ ] Debe recibir una función y medir su tiempo de inicio a fin. -- [ ] Debe imprimir en terminal el tiempo que tardó. -- [ ] Debe guardar el registro en un archivo (ej. `tiempos.log`). -- [ ] `README.md` explicando cómo utilizar el decorador. - -## 💡 Pistas -- Puedes usar `time.time()` o `time.perf_counter()`. -- Recuerda usar `functools.wraps` si quieres mantener el nombre original de la función. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/11-profiler-de-funciones/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 12 — Validador de correos con tests (Pytest) - -**Título:** Python General 12 — Validador de correos con tests (Pytest) - -## Objetivo -Escribir un validador de formatos de email y crear una suite de pruebas usando `pytest` para comprobar casos válidos e inválidos. - -## Qué vas a aprender -- Uso de expresiones regulares (`re`) o lógica manual. -- Estructura de pruebas unitarias. -- Uso de `assert` en `pytest`. -- Manejo de casos borde. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Python 3.10+, `pytest`. - -## Requisitos -- [ ] La función validadora debe chequear la estructura general de un correo (ej. tiene `@`, tiene un dominio válido). -- [ ] La suite de tests debe cubrir al menos 5 correos válidos y 5 inválidos. -- [ ] `README.md` con instrucciones de cómo instalar y correr `pytest`. - -## 💡 Pistas -- `pytest` busca por defecto archivos que empiezan o terminan con `test_`. -- Puedes usar `pytest.mark.parametrize` para probar varios correos en un solo test. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/12-validador-de-emails/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 13 — Lector de archivos gigantes (Generadores) - -**Título:** Python General 13 — Lector de archivos gigantes (Generadores) - -## Objetivo -Procesar un archivo de texto inmenso buscando un patrón específico, leyendo línea por línea sin cargar todo en memoria usando un generador (`yield`). - -## Qué vas a aprender -- Generadores e iteradores. -- La palabra clave `yield`. -- Eficiencia de memoria (lazy evaluation). - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar. - -## Requisitos -- [ ] Crea un generador falso o un script para generar un archivo grande (ej. 1 millón de líneas) para probar. -- [ ] El lector debe ser una función generadora que ceda (yield) las líneas que coinciden con un texto buscado. -- [ ] `README.md` explicando la diferencia de consumo de RAM frente a `readlines()`. - -## 💡 Pistas -- Usar un bucle `for line in file:` con `yield` evita cargar el archivo completo. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/13-lector-archivos-gigantes/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 14 — CLI del clima (APIs y Auth) - -**Título:** Python General 14 — CLI del clima (APIs y Auth) - -## Objetivo -Consumir una API del clima enviando una API Key por parámetros o headers, y formateando el resultado en la terminal. - -## Qué vas a aprender -- Consumo de APIs REST con autenticación sencilla. -- Parseo avanzado de JSON. -- Manejo de secretos en variables de entorno o parámetros ocultos. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Python 3.10+, `requests`. - -## Requisitos -- [ ] El script debe tomar el nombre de una ciudad (ej. a través de `sys.argv` o `input()`). -- [ ] La API Key NUNCA debe estar en el código. Debe leerse de una variable de entorno (`os.environ`). -- [ ] Manejo de errores (ciudad no encontrada, key inválida). -- [ ] `README.md` explicando cómo obtener la API Key y configurar la variable de entorno. - -## 💡 Pistas -- Puedes usar OpenWeatherMap o WeatherAPI (ambas tienen capas gratuitas). -- Recuerda ignorar archivos `.env` en `.gitignore` si los usas para pruebas locales. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/14-cli-del-clima/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 15 — Herramienta de backup en CLI (Argparse) - -**Título:** Python General 15 — Herramienta de backup en CLI (Argparse) - -## Objetivo -Crear una herramienta de consola robusta que reciba directorios origen y destino mediante banderas (ej. `--origen`, `--destino`) para copiar archivos. - -## Qué vas a aprender -- El módulo `argparse`. -- Diseño de interfaces de línea de comandos. -- Copia de archivos recursiva. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (`argparse`, `shutil`, `os`). - -## Requisitos -- [ ] El CLI debe usar `argparse` para aceptar `--origen` y `--destino`. -- [ ] Opcionalmente, agregar bandera `--dry-run` para solo simular la copia. -- [ ] Mensajes claros al usuario del progreso o errores (ej. si la ruta de origen no existe). -- [ ] `README.md` mostrando ejemplos de comandos. - -## 💡 Pistas -- Investiga la función `shutil.copytree` para carpetas completas, o recorre manualmente si quieres más control. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/15-herramienta-de-backup/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 16 — Validador de edad (Excepciones Custom) - -**Título:** Python General 16 — Validador de edad (Excepciones Custom) - -## Objetivo -Crear un sistema simple de registro que declare e invoque excepciones propias (ej. `EdadInvalidaError`, `MenorDeEdadError`). - -## Qué vas a aprender -- Herencia de clases (`Exception`). -- Bloques `try/except` especializados. -- Uso de la sentencia `raise`. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar. - -## Requisitos -- [ ] Declara al menos dos excepciones personalizadas. -- [ ] La función principal debe pedir la edad y lanzar estas excepciones según el caso (ej. menores de 18 o edades < 0). -- [ ] El bucle principal debe atrapar (catch) las excepciones e imprimir mensajes amigables sin crashear. -- [ ] `README.md` explicando cómo funciona la jerarquía de excepciones en el script. - -## 💡 Pistas -- Crear una excepción personalizada es tan simple como `class MiError(Exception): pass`. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/16-validador-de-edad/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 17 — Gestor de configuraciones (.env o .ini) - -**Título:** Python General 17 — Gestor de configuraciones (.env o .ini) - -## Objetivo -Crear un script que cargue credenciales simuladas desde un archivo externo (`.env` o `.ini`) y las utilice sin hardcodearlas en el código. - -## Qué vas a aprender -- Separación entre código y configuración. -- Lectura de archivos de configuración. -- Uso del módulo `configparser` o librerías como `python-dotenv`. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Python 3.10+, `python-dotenv` (opcional si usas .env) o `configparser` (para .ini). - -## Requisitos -- [ ] El script debe intentar leer de la configuración valores como `DB_HOST`, `DB_USER`, `DB_PASS`. -- [ ] Imprimir un mensaje simulando una conexión a la BD. -- [ ] Lanzar un error claro si el archivo de configuración falta o le faltan variables necesarias. -- [ ] `README.md` detallando la importancia de NO subir archivos con contraseñas a GitHub, usando un archivo `.env.example`. - -## 💡 Pistas -- Nunca hagas commit de tu `.env`. Usa un archivo de plantilla como `.env.example` o `config.example.ini`. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/17-lector-de-configuraciones/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - ---- - -# Python General 18 — Inventario de tienda (Dataclasses) - -**Título:** Python General 18 — Inventario de tienda (Dataclasses) - -## Objetivo -Modelar un catálogo utilizando `dataclasses` para los productos (nombre, precio, stock), incluyendo un método para calcular el valor total. - -## Qué vas a aprender -- Programación Orientada a Objetos intermedia. -- El decorador `@dataclass`. -- Tipado estático básico (Type Hints). -- Métodos de instancia. - -## Nivel -Intermedio. - -## 🛠️ Tecnologías sugeridas -Solo Python 3.10+ y su librería estándar (`dataclasses`). - -## Requisitos -- [ ] Define una dataclass `Producto` con tipo de datos para nombre, precio y cantidad en stock. -- [ ] Crea una clase `Inventario` que contenga una lista de objetos `Producto`. -- [ ] Implementa métodos para calcular el valor total de todo el stock, agregar y eliminar productos. -- [ ] `README.md` con las instrucciones de prueba. - -## 💡 Pistas -- Usar `dataclasses` hace que no tengas que escribir un método `__init__` gigante. Python lo hace por ti. - -## 📦 Entregable -PR con tu código en `retos/python-general/nivel-2-intermedio/18-inventario-de-tienda/tu-usuario/`. - -## ¿Primera vez contribuyendo? -Lee nuestra guía [Cómo hacer tu primer PR](../COMO-HACER-TU-PRIMER-PR.md). - diff --git a/docs/RETOS-SOCRATICOS.md b/docs/RETOS-SOCRATICOS.md new file mode 100644 index 0000000..1651cc0 --- /dev/null +++ b/docs/RETOS-SOCRATICOS.md @@ -0,0 +1,95 @@ +# Filosofía de los Retos Socráticos + +En Nerve Community usamos un enfoque metodológico llamado **Aprendizaje Socrático**. A diferencia de los tutoriales tradicionales donde se te dice "escribe esto línea por línea", aquí el proceso es distinto: se te presenta un problema y tú debes deducir la solución investigando, probando y leyendo el código que te evalúa. + +--- + +## Qué es un Reto Socrático + +Un Reto Socrático es un problema técnico planteado de forma que **tú** debes encontrar la respuesta. No hay una respuesta directa en el enunciado. En cambio, hay: + +- Un archivo `reto.md` que describe el problema en lenguaje natural. +- Un archivo de pruebas (`test_main.py`) que describe exactamente qué debe hacer tu código. +- Un archivo vacío o incompleto donde escribes tu solución. + +El archivo de pruebas es el requisito funcional. Si el test espera que tu función devuelva un diccionario con ciertas llaves, eso es lo que tu código debe hacer. + +--- + +## Principios de evaluación + +**El test es tu guía, no el enunciado.** +La forma más eficiente de entender un reto es leer el archivo de pruebas antes de leer el enunciado. El test describe con precisión qué entradas recibe tu función y qué debe devolver. + +**El error es información.** +Un error en la terminal no es una señal de que fallaste. Es la descripción exacta de qué salió mal. Leer el mensaje de error completo, incluyendo el stack trace, te dice en qué línea falló, qué recibió tu función y qué esperaba recibir. + +**La reflexión activa consolida el conocimiento.** +Rompe el código a propósito: cambia un valor, quita un `return`, modifica el tipo de dato que devuelves. Corre los tests después de cada cambio. Observar cómo reacciona el sistema ante cada modificación es una de las formas más efectivas de aprender. + +**La búsqueda es parte del trabajo.** +Si no sabes cómo hacer algo, busca en la documentación oficial de Python, en Stack Overflow, o usa herramientas de IA. Lo importante es que entiendas lo que estás copiando. El objetivo no es que el test pase, sino que puedas explicar por qué pasó. + +--- + +## Cómo leer un error de pytest + +Cuando ejecutas `pytest test_main.py` y hay un fallo, la salida tiene esta estructura: + +``` +FAILED test_main.py::test_suma - AssertionError: assert 0 == 5 +``` + +Cada parte te dice algo útil: + +- `FAILED` — el test no pasó. +- `test_main.py::test_suma` — el nombre del archivo y la función de test que falló. +- `AssertionError: assert 0 == 5` — tu función devolvió `0`, pero el test esperaba `5`. + +Un ejemplo más completo: + +``` +def test_suma(): + resultado = suma(2, 3) +> assert resultado == 5 +E AssertionError: assert 0 == 5 + +test_main.py:6: AssertionError +``` + +La línea con `>` muestra exactamente qué instrucción falló. La línea con `E` muestra los valores reales. El número al final (`test_main.py:6`) es la línea del test donde ocurrió el fallo. + +Con esa información puedes ir directamente a tu código y preguntar: ¿por qué `suma(2, 3)` está devolviendo `0`? + +--- + +## Errores comunes al enfrentar un reto + +**No leer el archivo de pruebas antes de empezar.** +El test es el requisito. Leerlo primero te ahorra tiempo de intentar cosas que el test no pide. + +**Ignorar el mensaje de error.** +El mensaje de error contiene la respuesta a qué falla. Leerlo completo, incluyendo el stack trace, es el primer paso antes de cambiar cualquier línea de código. + +**Cambiar varias cosas al mismo tiempo.** +Si cambias cinco cosas y los tests pasan, no sabes cuál de los cinco cambios fue el correcto. Cambia una cosa, corre los tests, evalúa el resultado. + +**Buscar la solución completa antes de intentar.** +El aprendizaje ocurre en el proceso de resolver, no en tener el resultado. Intenta primero. Busca ayuda cuando hayas agotado tus hipótesis, no cuando el reto se vea difícil. + +--- + +## Qué se valida cuando entregas + +Cuando haces push a tu repositorio, el CI automático corre: + +- Los mismos tests que tienes en local (`pytest`). +- El formateador de estilo (`black`). + +Si ambos pasan, el check verde en tu commit indica que tu solución es correcta y el siguiente módulo queda disponible. + +Cuando resuelves un reto socrático, demuestras haber entendido un concepto técnico con suficiente profundidad como para aplicarlo correctamente ante condiciones que no conocías de antemano. + +--- + +← [Volver a Cómo usar como Alumno](COMO-USAR-COMO-ALUMNO.md) | [Volver al repositorio](../README.md) diff --git a/docs/ruta-de-aprendizaje.md b/docs/ruta-de-aprendizaje.md deleted file mode 100644 index 0c13d53..0000000 --- a/docs/ruta-de-aprendizaje.md +++ /dev/null @@ -1,37 +0,0 @@ -# Ruta de Aprendizaje: De Cero a Contribuidor/a - -> **Nota importante:** Esta es una guía paso a paso para ayudarte a estructurar tu aprendizaje semana a semana. No es obligatorio hacerlo a esta velocidad, avanza a tu propio ritmo. ¡Lo importante es no rendirse! - -## 🟢 Semana 1-2: Las Bases (Python General - Nivel 1) - -Si apenas estás aprendiendo a programar o quieres repasar los fundamentos de Python sin distracciones, empieza aquí. - -- **Dónde están:** `retos/python-general/nivel-1-principiante/` -- **Cuántos son:** 10 retos -- **Tu misión:** Elige los retos que más te llamen la atención. Aprenderás desde manejo de archivos y bucles, hasta cómo hacer scrapers simples. Para participar, solo comenta "¡Yo lo tomo!" en el issue oficial de GitHub (listados en [PYTHON_GENERAL_ISSUES.md](PYTHON_GENERAL_ISSUES.md) o en el [README](../README.md)). -- **Ayuda:** Si es tu primera vez, lee la guía [CÓMO HACER TU PRIMER PR](../COMO-HACER-TU-PRIMER-PR.md). - -## Semana 3: Conectando Procesos (Nerve - Nivel 1) - -Una vez que te sientas cómodo/a con Python básico, es hora de usar [Nerve](https://github.com/Kaia-Alenia/alenia-nerve). Aquí crearás pequeños programas que "hablan" entre sí de forma local. - -- **Dónde están:** `retos/nerve/nivel-1-principiante/` -- **Cuántos son:** 3 retos (Chat en terminal, Reloj sincronizado, Traductor de mensajes). -- **Tu misión:** Completar la comunicación de procesos (IPC) usando Nerve. Conectar dos terminales por primera vez es mágico. - -## Semana 4: El Gran Salto (El Mega Reto) - -¡Felicidades! Si has completado todo el bloque de Nivel Principiante (Python General 1 y Nerve 1), has desbloqueado tu prueba de fuego. - -- **Dónde está:** Lee el archivo [MEGA_RETO.md](../MEGA_RETO.md) -- **El objetivo:** Construir un Producto Mínimo Viable (MVP) propio que integre al menos 2 o 3 conceptos aprendidos. -- **La recompensa:** Entregar un proyecto funcional aquí es lo que te califica para participar por el **Premio Trimestral** de la comunidad. - -## 🟡 Semana 5 en adelante: Subiendo el Nivel (Python General - Nivel 2) - -Cuando estés listo/a para escribir código más robusto, eficiente y profesional, esta es tu siguiente parada. - -- **Dónde están:** `retos/python-general/nivel-2-intermedio/` -- **Cuántos son:** 8 retos (Issues #25 al #32 en GitHub). -- **Qué aprenderás:** Decoradores, expresiones regulares, generadores, consumo de APIs con Auth, herramientas de terminal avanzadas (argparse), excepciones personalizadas, manejo de `.env` (credenciales) y POO con `dataclasses`. -- **Tu misión:** Explora estos temas a fondo. Si te atascas en la teoría, revisa la carpeta `ejemplos/python-general/` donde preparamos scripts didácticos para que veas cada técnica en acción antes de intentar resolver el reto real. diff --git a/ejemplos/general/01-lista-de-tareas-cli/README.md b/ejemplos/general/01-lista-de-tareas-cli/README.md deleted file mode 100644 index 9355646..0000000 --- a/ejemplos/general/01-lista-de-tareas-cli/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Manejo de Archivos JSON - -**¿Qué muestra este ejemplo?** -Cómo leer y escribir archivos JSON de forma segura usando el módulo `json` de la librería estándar, incluyendo cómo manejar el caso donde el archivo no existe en la primera ejecución. - -**¿Por qué es útil?** -Sirve para guardar configuración de un programa, cachear datos, o persistir información simple entre diferentes ejecuciones sin necesitar una base de datos completa. - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_json.py` - -**¿Qué retos usan esta base?** -- [#13 Python General 01 — Lista de tareas (to-do list) en terminal](https://github.com/Kaia-Alenia/nerve-community/issues/13) -- [#21 Python General 09 — Recordatorios en terminal](https://github.com/Kaia-Alenia/nerve-community/issues/21) - -**Nota para principiantes** -Recuerda que `json.dump` reescribe el archivo completo. Si quieres agregar un solo elemento a una lista, primero debes cargar la lista con `json.load`, hacer el `.append()`, y volver a guardarla completa. \ No newline at end of file diff --git a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.go b/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.go deleted file mode 100644 index c350db1..0000000 --- a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.go +++ /dev/null @@ -1,152 +0,0 @@ -// Ejemplo: Mini gestor de contactos con menú interactivo y persistencia JSON en Go -// -// Qué enseña este ejemplo: -// - Cómo crear un loop con for {} -// - Cómo usar bufio.NewScanner para leer líneas de la terminal -// - Cómo leer y guardar archivos con os.ReadFile y os.WriteFile -// - Cómo usar encoding/json para convertir structs de Go a JSON y viceversa -// -// Glosario de términos "raros": -// bufio.NewScanner — Crea un "escáner" que facilita leer texto de la consola línea por línea. -// os.ReadFile — Lee todo el contenido de un archivo de una sola vez. -// os.WriteFile — Guarda datos en un archivo, sobreescribiéndolo. -// json.Marshal — (De "marshall") Convierte tus variables de Go a texto JSON. -// json.Unmarshal — Convierte texto JSON de vuelta a variables de Go. -// 0644 — Permisos del archivo (lectura/escritura para ti, lectura para otros). - -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "os" - "strconv" - "strings" -) - -const ARCHIVO = "contactos.json" - -// Contacto representa un elemento de nuestra lista. -// Las etiquetas `json:"nombre"` indican cómo se llamará el campo en el archivo. -type Contacto struct { - Nombre string `json:"nombre"` - Telefono string `json:"telefono"` -} - -func cargarContactos() []Contacto { - // Intentamos leer el archivo - data, err := os.ReadFile(ARCHIVO) - if err != nil { - // Si hay un error (ej. el archivo no existe), devolvemos lista vacía - return []Contacto{} - } - - var contactos []Contacto - // Convertimos el JSON a la lista de structs - json.Unmarshal(data, &contactos) - return contactos -} - -func guardarContactos(contactos []Contacto) { - // MarshalIndent hace que el JSON sea legible (con espacios) - data, _ := json.MarshalIndent(contactos, "", " ") - os.WriteFile(ARCHIVO, data, 0644) -} - -func mostrarContactos(contactos []Contacto) { - if len(contactos) == 0 { - fmt.Println("No hay contactos guardados todavía.") - return - } - fmt.Println("\n--- Tus contactos ---") - for i, contacto := range contactos { - fmt.Printf(" %d. %s — %s\n", i+1, contacto.Nombre, contacto.Telefono) - } -} - -// Función auxiliar para leer texto -func preguntar(prompt string, scanner *bufio.Scanner) string { - fmt.Print(prompt) - scanner.Scan() - return strings.TrimSpace(scanner.Text()) -} - -func main() { - contactos := cargarContactos() - scanner := bufio.NewScanner(os.Stdin) - - for { - fmt.Println("\n=== Gestor de Contactos ===") - fmt.Println("1. Ver contactos") - fmt.Println("2. Agregar contacto") - fmt.Println("3. Actualizar teléfono") - fmt.Println("4. Eliminar contacto") - fmt.Println("5. Salir") - - opcion := preguntar("\nElige una opción (1-5): ", scanner) - - switch opcion { - case "1": - mostrarContactos(contactos) - - case "2": - nombre := preguntar("Nombre: ", scanner) - telefono := preguntar("Teléfono: ", scanner) - - if nombre != "" { - contactos = append(contactos, Contacto{Nombre: nombre, Telefono: telefono}) - guardarContactos(contactos) - fmt.Printf("✓ Contacto '%s' guardado.\n", nombre) - } else { - fmt.Println("El nombre no puede estar vacío.") - } - - case "3": - mostrarContactos(contactos) - if len(contactos) == 0 { - continue - } - - idxStr := preguntar("\nNúmero del contacto a actualizar: ", scanner) - idx, err := strconv.Atoi(idxStr) - idx -= 1 // Ajustamos para índice 0 - - if err == nil && idx >= 0 && idx < len(contactos) { - nuevoTel := preguntar("Nuevo teléfono: ", scanner) - contactos[idx].Telefono = nuevoTel - guardarContactos(contactos) - fmt.Println("✓ Contacto actualizado.") - } else { - fmt.Println("Número inválido.") - } - - case "4": - mostrarContactos(contactos) - if len(contactos) == 0 { - continue - } - - idxStr := preguntar("\nNúmero del contacto a eliminar: ", scanner) - idx, err := strconv.Atoi(idxStr) - idx -= 1 - - if err == nil && idx >= 0 && idx < len(contactos) { - eliminado := contactos[idx] - // Magia para eliminar un elemento de un slice en Go: - contactos = append(contactos[:idx], contactos[idx+1:]...) - guardarContactos(contactos) - fmt.Printf("✓ Contacto '%s' eliminado.\n", eliminado.Nombre) - } else { - fmt.Println("Número inválido.") - } - - case "5": - fmt.Println("¡Hasta luego!") - return - - default: - fmt.Println("Opción no reconocida, intenta de nuevo.") - } - } -} diff --git a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.js b/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.js deleted file mode 100644 index eff2c07..0000000 --- a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.js +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Ejemplo: Mini gestor de contactos con menú interactivo y persistencia JSON en Node.js - * - * Qué enseña este ejemplo: - * - Cómo leer entrada del usuario en la terminal (readline) - * - Cómo leer y escribir archivos de forma síncrona (fs.readFileSync / fs.writeFileSync) - * - Cómo convertir JSON de texto a objetos y viceversa (JSON.parse / JSON.stringify) - * - Cómo manejar arrays y modificar sus elementos - * - * Glosario de términos "raros": - * fs — (File System) Módulo nativo de Node.js para trabajar con archivos. - * readline — Módulo nativo para leer lo que el usuario escribe en la consola. - * JSON.parse — Convierte un texto JSON en un objeto o array de JavaScript. - * JSON.stringify — Convierte un objeto o array de JavaScript a texto JSON. - * utf8 — Formato de texto estándar (soporta acentos, ñ, etc.). - */ - -const fs = require('fs'); -const readline = require('readline'); - -const ARCHIVO = 'contactos.json'; - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -// En Node.js clásico, preguntar algo por consola es asíncrono con callbacks o promesas. -// Usaremos una pequeña función de ayuda para usar promesas y async/await: -function preguntar(texto) { - return new Promise((resolve) => { - rl.question(texto, resolve); - }); -} - -function cargarContactos() { - if (!fs.existsSync(ARCHIVO)) { - return []; - } - const data = fs.readFileSync(ARCHIVO, 'utf8'); - return JSON.parse(data); -} - -function guardarContactos(contactos) { - // stringify(obj, null, 4) formatea el JSON con 4 espacios - fs.writeFileSync(ARCHIVO, JSON.stringify(contactos, null, 4), 'utf8'); -} - -function mostrarContactos(contactos) { - if (contactos.length === 0) { - console.log("No hay contactos guardados todavía."); - return; - } - console.log("\n--- Tus contactos ---"); - contactos.forEach((contacto, index) => { - console.log(` ${index + 1}. ${contacto.nombre} — ${contacto.telefono}`); - }); -} - -async function agregarContacto(contactos) { - const nombre = (await preguntar("Nombre: ")).trim(); - const telefono = (await preguntar("Teléfono: ")).trim(); - - if (nombre) { - contactos.push({ nombre, telefono }); - guardarContactos(contactos); - console.log(`✓ Contacto '${nombre}' guardado.`); - } else { - console.log("El nombre no puede estar vacío."); - } -} - -async function actualizarContacto(contactos) { - mostrarContactos(contactos); - if (contactos.length === 0) return; - - const idxStr = await preguntar("\nNúmero del contacto a actualizar: "); - const idx = parseInt(idxStr) - 1; - - if (idx >= 0 && idx < contactos.length) { - const nuevoTel = (await preguntar("Nuevo teléfono: ")).trim(); - contactos[idx].telefono = nuevoTel; - guardarContactos(contactos); - console.log("✓ Contacto actualizado."); - } else { - console.log("Número inválido."); - } -} - -async function eliminarContacto(contactos) { - mostrarContactos(contactos); - if (contactos.length === 0) return; - - const idxStr = await preguntar("\nNúmero del contacto a eliminar: "); - const idx = parseInt(idxStr) - 1; - - if (idx >= 0 && idx < contactos.length) { - const eliminado = contactos.splice(idx, 1)[0]; - guardarContactos(contactos); - console.log(`✓ Contacto '${eliminado.nombre}' eliminado.`); - } else { - console.log("Número inválido."); - } -} - -async function main() { - let contactos = cargarContactos(); - - while (true) { - console.log("\n=== Gestor de Contactos ==="); - console.log("1. Ver contactos"); - console.log("2. Agregar contacto"); - console.log("3. Actualizar teléfono"); - console.log("4. Eliminar contacto"); - console.log("5. Salir"); - - const opcion = (await preguntar("\nElige una opción (1-5): ")).trim(); - - if (opcion === "1") { - mostrarContactos(contactos); - } else if (opcion === "2") { - await agregarContacto(contactos); - } else if (opcion === "3") { - await actualizarContacto(contactos); - } else if (opcion === "4") { - await eliminarContacto(contactos); - } else if (opcion === "5") { - console.log("¡Hasta luego!"); - break; - } else { - console.log("Opción no reconocida, intenta de nuevo."); - } - } - rl.close(); -} - -main(); diff --git a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.py b/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.py deleted file mode 100644 index 15f894c..0000000 --- a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Ejemplo: Mini gestor de contactos con menú interactivo y persistencia JSON - -Qué enseña este ejemplo: - - Cómo crear un loop de menú principal con while True + break - - Cómo usar input() para leer texto y números del usuario - - Cómo guardar y cargar datos con json.dump / json.load - - Cómo actualizar un elemento específico de una lista (modificar un diccionario) - - Cómo eliminar un elemento usando list.pop() o del - - Cómo manejar el caso en que el archivo aún no existe - -Para tu reto (lista de tareas): - Aplica el mismo patrón de loop + input() + guardado JSON, pero en vez de - contactos (nombre/teléfono) trabajarás con tareas (texto/completado). - - Tu "agregar" será para nuevas tareas. - - Tu "actualizar" será tu "marcar como completada". - - Tu "eliminar" funcionará igual que eliminar un contacto. - -Glosario de términos "raros": - with open(...) as f — Abre un archivo de forma segura. "f" es solo una variable - corta para "file" (archivo). Si hay error, cierra el archivo solo. - "r" o "w" — Modos al abrir un archivo: "r" (read = leer), "w" (write = escribir). - encoding="utf-8" — Asegura que los acentos y caracteres especiales (ñ, á) se guarden bien. - json.load() — Convierte texto JSON de un archivo a una lista/diccionario en Python. - json.dump() — Convierte una lista/diccionario en Python a texto JSON en un archivo. -""" - -import json -import os - -ARCHIVO = "contactos.json" - - -def cargar_contactos(): - """ - Carga la lista de contactos desde el archivo JSON. - Si el archivo no existe (primera vez), devuelve una lista vacía. - """ - if not os.path.exists(ARCHIVO): - return [] - - with open(ARCHIVO, "r", encoding="utf-8") as f: - return json.load(f) - - -def guardar_contactos(contactos): - """Guarda la lista de contactos en el archivo JSON.""" - with open(ARCHIVO, "w", encoding="utf-8") as f: - json.dump(contactos, f, indent=4, ensure_ascii=False) - - -def mostrar_contactos(contactos): - if not contactos: - print("No hay contactos guardados todavía.") - return - print("\n--- Tus contactos ---") - for i, contacto in enumerate(contactos, start=1): - print(f" {i}. {contacto['nombre']} — {contacto['telefono']}") - - -def agregar_contacto(contactos): - nombre = input("Nombre: ").strip() - telefono = input("Teléfono: ").strip() - if nombre: - contactos.append({"nombre": nombre, "telefono": telefono}) - guardar_contactos(contactos) - print(f"✓ Contacto '{nombre}' guardado.") - else: - print("El nombre no puede estar vacío.") - - -def actualizar_contacto(contactos): - """Demuestra cómo modificar un elemento existente en la lista.""" - mostrar_contactos(contactos) - if not contactos: - return - - try: - idx = int(input("\nNúmero del contacto a actualizar: ")) - 1 - if 0 <= idx < len(contactos): - nuevo_tel = input("Nuevo teléfono: ").strip() - # Modificamos el valor dentro del diccionario - contactos[idx]["telefono"] = nuevo_tel - guardar_contactos(contactos) - print("✓ Contacto actualizado.") - else: - print("Número inválido.") - except ValueError: - print("Por favor, ingresa un número válido.") - - -def eliminar_contacto(contactos): - """Demuestra cómo eliminar un elemento de la lista usando pop().""" - mostrar_contactos(contactos) - if not contactos: - return - - try: - idx = int(input("\nNúmero del contacto a eliminar: ")) - 1 - if 0 <= idx < len(contactos): - # pop() remueve y devuelve el elemento en ese índice - eliminado = contactos.pop(idx) - guardar_contactos(contactos) - print(f"✓ Contacto '{eliminado['nombre']}' eliminado.") - else: - print("Número inválido.") - except ValueError: - print("Por favor, ingresa un número válido.") - - -def main(): - contactos = cargar_contactos() - - while True: - print("\n=== Gestor de Contactos ===") - print("1. Ver contactos") - print("2. Agregar contacto") - print("3. Actualizar teléfono") - print("4. Eliminar contacto") - print("5. Salir") - - opcion = input("\nElige una opción (1-5): ").strip() - - if opcion == "1": - mostrar_contactos(contactos) - elif opcion == "2": - agregar_contacto(contactos) - elif opcion == "3": - actualizar_contacto(contactos) - elif opcion == "4": - eliminar_contacto(contactos) - elif opcion == "5": - print("¡Hasta luego!") - break - else: - print("Opción no reconocida, intenta de nuevo.") - - -if __name__ == "__main__": - main() diff --git a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.rs b/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.rs deleted file mode 100644 index d8c0e5d..0000000 --- a/ejemplos/general/01-lista-de-tareas-cli/ejemplo_menu_interactivo.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Ejemplo: Mini gestor de contactos con menú interactivo y persistencia JSON en Rust -// -// Qué enseña este ejemplo: -// - Cómo crear un loop con "loop {}" -// - Cómo leer la entrada del usuario con std::io::stdin() -// - Cómo leer y guardar archivos con std::fs -// - Cómo usar serde_json para convertir structs de Rust a JSON y viceversa -// -// Glosario de términos "raros": -// std::io::stdin().read_line — Lee una línea de texto que escribe el usuario. -// std::fs::read_to_string — Lee un archivo completo y lo guarda en un String. -// serde_json::from_str — Convierte un String con JSON en una estructura (Vector) de Rust. -// serde_json::to_string_pretty — Convierte variables de Rust en texto JSON formateado. -// #[derive(Serialize, Deserialize)] — Magia de Rust que le enseña a tu Struct cómo convertirse a JSON. - -use serde::{Deserialize, Serialize}; -use std::fs; -use std::io::{self, Write}; - -const ARCHIVO: &str = "contactos.json"; - -#[derive(Serialize, Deserialize, Clone)] -struct Contacto { - nombre: String, - telefono: String, -} - -fn cargar_contactos() -> Vec { - // Intentamos leer el archivo. Si falla (ej. no existe), devolvemos un vector vacío - if let Ok(contenido) = fs::read_to_string(ARCHIVO) { - if let Ok(contactos) = serde_json::from_str(&contenido) { - return contactos; - } - } - Vec::new() -} - -fn guardar_contactos(contactos: &[Contacto]) { - // Convertimos a JSON con formato bonito (pretty) - if let Ok(json_str) = serde_json::to_string_pretty(contactos) { - let _ = fs::write(ARCHIVO, json_str); - } -} - -fn mostrar_contactos(contactos: &[Contacto]) { - if contactos.is_empty() { - println!("No hay contactos guardados todavía."); - return; - } - println!("\n--- Tus contactos ---"); - for (i, contacto) in contactos.iter().enumerate() { - println!(" {}. {} — {}", i + 1, contacto.nombre, contacto.telefono); - } -} - -// Función auxiliar para preguntar algo en consola y obtener un String limpio -fn preguntar(prompt: &str) -> String { - print!("{}", prompt); - io::stdout().flush().unwrap(); // Asegura que el prompt se imprima antes de esperar el input - - let mut input = String::new(); - io::stdin().read_line(&mut input).unwrap_or_default(); - input.trim().to_string() -} - -fn main() { - let mut contactos = cargar_contactos(); - - loop { - println!("\n=== Gestor de Contactos ==="); - println!("1. Ver contactos"); - println!("2. Agregar contacto"); - println!("3. Actualizar teléfono"); - println!("4. Eliminar contacto"); - println!("5. Salir"); - - let opcion = preguntar("\nElige una opción (1-5): "); - - match opcion.as_str() { - "1" => mostrar_contactos(&contactos), - "2" => { - let nombre = preguntar("Nombre: "); - let telefono = preguntar("Teléfono: "); - - if !nombre.is_empty() { - contactos.push(Contacto { - nombre: nombre.clone(), - telefono, - }); - guardar_contactos(&contactos); - println!("✓ Contacto '{}' guardado.", nombre); - } else { - println!("El nombre no puede estar vacío."); - } - } - "3" => { - mostrar_contactos(&contactos); - if contactos.is_empty() { - continue; - } - - let idx_str = preguntar("\nNúmero del contacto a actualizar: "); - if let Ok(mut idx) = idx_str.parse::() { - idx -= 1; // Ajustamos a índice 0 - if idx < contactos.len() { - let nuevo_tel = preguntar("Nuevo teléfono: "); - contactos[idx].telefono = nuevo_tel; - guardar_contactos(&contactos); - println!("✓ Contacto actualizado."); - } else { - println!("Número inválido."); - } - } else { - println!("Por favor, ingresa un número válido."); - } - } - "4" => { - mostrar_contactos(&contactos); - if contactos.is_empty() { - continue; - } - - let idx_str = preguntar("\nNúmero del contacto a eliminar: "); - if let Ok(mut idx) = idx_str.parse::() { - idx -= 1; - if idx < contactos.len() { - let eliminado = contactos.remove(idx); // Elimina y retorna el elemento - guardar_contactos(&contactos); - println!("✓ Contacto '{}' eliminado.", eliminado.nombre); - } else { - println!("Número inválido."); - } - } else { - println!("Por favor, ingresa un número válido."); - } - } - "5" => { - println!("¡Hasta luego!"); - break; - } - _ => println!("Opción no reconocida, intenta de nuevo."), - } - } -} diff --git a/ejemplos/general/02-web-scraper-simple/README.md b/ejemplos/general/02-web-scraper-simple/README.md deleted file mode 100644 index 96cb837..0000000 --- a/ejemplos/general/02-web-scraper-simple/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Peticiones HTTP (Requests) - -**¿Qué muestra este ejemplo?** -Cómo hacer peticiones HTTP (GET, POST) a APIs o páginas web externas usando la librería externa `requests`, manejando posibles errores de red o códigos de estado fallidos. - -**¿Por qué es útil?** -Es la base para consumir cualquier API REST (como consultar el clima, leer mensajes, o enviar notificaciones) y para hacer scraping de sitios web. - -**Cómo correrlo** -- Dependencias necesarias: `pip install requests` -- Comando para ejecutarlo: `python ejemplo_requests.py` - -**¿Qué retos usan esta base?** -- [#14 Python General 02 — Web scraper simple](https://github.com/Kaia-Alenia/nerve-community/issues/14) - -**Nota para principiantes** -Siempre revisa el `response.status_code` o usa `response.raise_for_status()` antes de intentar leer los datos; asumir que una petición web siempre será exitosa causará que tu programa se cierre inesperadamente si no hay internet o el servidor falla. \ No newline at end of file diff --git a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.go b/ejemplos/general/02-web-scraper-simple/ejemplo_requests.go deleted file mode 100644 index 1b5dd2a..0000000 --- a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.go +++ /dev/null @@ -1,95 +0,0 @@ -// Ejemplo: Extraer múltiples elementos de una página con net/http + goquery en Go -// -// Qué enseña este ejemplo: -// - Cómo hacer una petición HTTP GET con net/http -// - Cómo usar PuerkitoBio/goquery (inspirado en jQuery) para buscar elementos HTML -// - Cómo guardar datos en un archivo CSV -// -// Para tu reto (web scraper): -// Instala la dependencia ejecutando: go get github.com/PuerkitoBio/goquery -// -// Glosario de términos "raros": -// http.Get — Pide datos a una URL de internet. -// defer res.Body.Close() — Asegura que la conexión de red se cierre automáticamente al terminar. -// goquery.NewDocumentFromReader — Lee el HTML y lo prepara para ser navegado. -// doc.Find() — Busca elementos usando selectores CSS (ej: ".quote"). -// s.Find().Text() — Extrae el texto dentro del elemento encontrado. - -package main - -import ( - "encoding/csv" - "fmt" - "log" - "net/http" - "os" - "strings" - - "github.com/PuerkitoBio/goquery" -) - -const URL = "https://quotes.toscrape.com/" - -type Dato struct { - Texto string - Autor string -} - -func main() { - // 1. Hacer la petición GET - res, err := http.Get(URL) - if err != nil { - log.Fatalf("Error al conectar: %v", err) - } - defer res.Body.Close() - - if res.StatusCode != 200 { - log.Fatalf("Error HTTP: %d", res.StatusCode) - } - - // 2. Crear el documento goquery desde la respuesta - doc, err := goquery.NewDocumentFromReader(res.Body) - if err != nil { - log.Fatalf("Error al procesar HTML: %v", err) - } - - // 3. Buscar TODOS los elementos con class="quote" - elementos := doc.Find(".quote") - fmt.Printf("Se encontraron %d frases en la página.\n\n", elementos.Length()) - - var datosExtraidos []Dato - - // 4. Iterar sobre cada elemento encontrado - elementos.Each(func(i int, s *goquery.Selection) { - // s representa cada bloque individual ".quote" - texto := s.Find(".text").Text() - autor := s.Find(".author").Text() - - datosExtraidos = append(datosExtraidos, Dato{Texto: texto, Autor: autor}) - - fmt.Printf("\"%s\"\n", texto) - fmt.Printf(" — %s\n\n", autor) - }) - - // 5. Guardar los datos en un archivo CSV - file, err := os.Create("frases.csv") - if err != nil { - log.Fatalf("Error al crear archivo CSV: %v", err) - } - defer file.Close() - - escritor := csv.NewWriter(file) - defer escritor.Flush() // Asegura que los datos en buffer se escriban al disco - - // Escribir cabeceras - escritor.Write([]string{"texto", "autor"}) - - // Escribir filas - for _, dato := range datosExtraidos { - // Reemplazamos saltos de línea para mantener el CSV limpio - textoLimpio := strings.ReplaceAll(dato.Texto, "\n", " ") - escritor.Write([]string{textoLimpio, dato.Autor}) - } - - fmt.Println("✓ Datos guardados exitosamente en 'frases.csv'") -} diff --git a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.js b/ejemplos/general/02-web-scraper-simple/ejemplo_requests.js deleted file mode 100644 index 39e542b..0000000 --- a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Ejemplo: Extraer múltiples elementos de una página web en Node.js - * - * Qué enseña este ejemplo: - * - Cómo hacer una petición HTTP GET con fetch nativo de Node.js - * - Cómo usar cheerio (el jQuery para Node) para analizar HTML - * - Cómo buscar elementos y extraer su texto - * - Cómo manejar errores de red con try/catch - * - * Para tu reto (web scraper): - * Aplica este patrón pero apuntando a books.toscrape.com - * Instala dependencias con: npm install cheerio - * - * Glosario de términos "raros": - * fetch — Función nativa para hacer peticiones web. - * cheerio — Librería que permite manipular HTML en Node.js de forma muy rápida usando selectores CSS. - * $ — Símbolo tradicional para instanciar cheerio (convención heredada de jQuery). - * .each() — Método de cheerio para iterar sobre todos los elementos encontrados. - * .text() — Extrae solo el contenido de texto (limpio de etiquetas HTML). - */ - -const fs = require('fs'); -const cheerio = require('cheerio'); // Requiere: npm install cheerio - -const URL = "https://quotes.toscrape.com/"; - -async function obtenerFrases() { - try { - // 1. Hacer la petición GET a la página - const respuesta = await fetch(URL); - - // 2. Verificar si la respuesta fue exitosa (código 200) - if (!respuesta.ok) { - throw new Error(`HTTP Error: ${respuesta.status}`); - } - - // 3. Obtener el HTML como texto plano - const html = await respuesta.text(); - - // 4. Crear el objeto cheerio para navegar el HTML - const $ = cheerio.load(html); - - // 5. Buscar TODOS los elementos con class="quote" - const elementos = $(".quote"); - console.log(`Se encontraron ${elementos.length} frases en la página.\n`); - - const datosExtraidos = []; - - // .each() itera sobre cada elemento encontrado - elementos.each((index, elemento) => { - // Buscamos dentro de cada bloque individual - const texto = $(elemento).find(".text").text(); - const autor = $(elemento).find(".author").text(); - - datosExtraidos.push({ texto, autor }); - console.log(`"${texto}"`); - console.log(` — ${autor}\n`); - }); - - // 6. Guardar los datos en un archivo CSV simple - let csvContent = "texto,autor\n"; // Cabeceras - for (const dato of datosExtraidos) { - // Limpiamos comas para no romper el CSV simple - const textoLimpio = dato.texto.replace(/,/g, ''); - const autorLimpio = dato.autor.replace(/,/g, ''); - csvContent += `${textoLimpio},${autorLimpio}\n`; - } - - fs.writeFileSync("frases.csv", csvContent, "utf8"); - console.log("✓ Datos guardados exitosamente en 'frases.csv'"); - - } catch (error) { - console.error("Error al obtener la página:", error.message); - } -} - -obtenerFrases(); diff --git a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.py b/ejemplos/general/02-web-scraper-simple/ejemplo_requests.py deleted file mode 100644 index dc346de..0000000 --- a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Ejemplo: Extraer múltiples elementos de una página con requests + BeautifulSoup - -Qué enseña este ejemplo: - - Cómo hacer una petición HTTP GET con requests.get() - - Qué significa raise_for_status() y para qué sirve - - Cómo crear un objeto BeautifulSoup (el "parser" del HTML) - - Cómo buscar TODOS los elementos de un tipo con find_all() - - Cómo manejar errores de red con try/except - -Para tu reto (web scraper): - Aplica el mismo patrón para extraer datos de books.toscrape.com - (título + precio de varios libros), que es una página pública - diseñada específicamente para practicar scraping. - -Glosario de términos "raros": - requests — Librería para pedir datos a internet (como lo hace tu navegador). - BeautifulSoup— Librería que toma código HTML feo y te deja buscar dentro de él fácilmente. - html.parser — El motor que lee el HTML. Sin él, BeautifulSoup no sabe cómo leer el texto. - raise_for_status() — Función que hace que el programa explote si la página no existe (Error 404). - find_all() — Busca TODOS los elementos que coincidan y devuelve una lista. - find() — Busca solo el PRIMER elemento que coincida. - .text — Extrae solo el texto limpio que ven los humanos (sin código HTML). - .get("attr") — Extrae un atributo invisible, como el enlace (href) de un botón. -""" - -import requests -from bs4 import BeautifulSoup - -# Esta URL es una página de ejemplo hecha para practicar scraping -URL = "https://quotes.toscrape.com/" - - -def obtener_frases(): - try: - # 1. Hacer la petición GET - respuesta = requests.get(URL, timeout=5) - - # 2. raise_for_status() lanza una excepción si el servidor devolvió - # un error (ej: 404 Not Found, 500 Server Error) - # Sin esto, requests no falla aunque la página no exista - respuesta.raise_for_status() - - # 3. Crear el objeto BeautifulSoup para navegar el HTML - # respuesta.text es el HTML de la página como texto plano - soup = BeautifulSoup(respuesta.text, "html.parser") - - # 4. Buscar TODOS los elementos con class="quote" - # find_all devuelve una lista, aunque no encuentre nada (lista vacía) - elementos = soup.find_all("div", class_="quote") - - print(f"Se encontraron {len(elementos)} frases en la página.\n") - - datos_extraidos = [] - for elemento in elementos: - # .find() dentro de un elemento busca solo en ese bloque - texto = elemento.find("span", class_="text").text - autor = elemento.find("small", class_="author").text - datos_extraidos.append({"texto": texto, "autor": autor}) - print(f'"{texto}"') - print(f" — {autor}\n") - - # 5. Guardar los datos en un archivo CSV (pieza final del rompecabezas) - import csv - - with open("frases.csv", "w", encoding="utf-8", newline="") as f: - # fieldnames define el nombre de las columnas (llaves del diccionario) - escritor = csv.DictWriter(f, fieldnames=["texto", "autor"]) - escritor.writeheader() - escritor.writerows(datos_extraidos) - - print("✓ Datos guardados exitosamente en 'frases.csv'") - - except requests.exceptions.ConnectionError: - print("Error: No se pudo conectar. Revisa tu conexión a internet.") - except requests.exceptions.Timeout: - print("Error: La petición tardó demasiado (timeout).") - except requests.exceptions.HTTPError as e: - print(f"Error HTTP: {e}") - - -if __name__ == "__main__": - obtener_frases() diff --git a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.rs b/ejemplos/general/02-web-scraper-simple/ejemplo_requests.rs deleted file mode 100644 index 4810645..0000000 --- a/ejemplos/general/02-web-scraper-simple/ejemplo_requests.rs +++ /dev/null @@ -1,75 +0,0 @@ -// Ejemplo: Extraer múltiples elementos de una página web en Rust con reqwest + scraper -// -// Qué enseña este ejemplo: -// - Cómo hacer una petición HTTP GET asíncrona con reqwest -// - Cómo extraer datos del HTML usando la librería scraper -// - Cómo escribir datos estructurados en un archivo CSV -// -// Para tu reto (web scraper): -// Dependencias necesarias en Cargo.toml: -// reqwest = { version = "0.11", features = ["blocking"] } -// scraper = "0.18" -// csv = "1.3" -// -// Glosario de términos "raros": -// reqwest::blocking::get — Hace la petición web en modo "bloqueante" (espera a que termine). -// scraper::Html::parse_document — Convierte el texto HTML a una estructura navegable. -// scraper::Selector::parse — Prepara una regla CSS (ej: ".quote") para buscar elementos. -// unwrap() — Atajo en Rust para "si hay error, explota el programa". Útil en scripts rápidos. -// csv::Writer — Crea un escritor que se encarga de formatear correctamente las comas y comillas en el CSV. - -use scraper::{Html, Selector}; -use csv::Writer; -use std::error::Error; - -const URL: &str = "https://quotes.toscrape.com/"; - -fn main() -> Result<(), Box> { - // 1. Hacer la petición GET (bloqueante) - let respuesta = reqwest::blocking::get(URL)?; - - // 2. Verificar que la respuesta sea OK y extraer el HTML como texto - let html = respuesta.error_for_status()?.text()?; - - // 3. Crear el objeto Html navegable - let documento = Html::parse_document(&html); - - // 4. Preparar los selectores CSS - // Selector::parse compila la regla. El unwrap() asume que escribiste bien la regla CSS. - let selector_quote = Selector::parse(".quote").unwrap(); - let selector_text = Selector::parse(".text").unwrap(); - let selector_author = Selector::parse(".author").unwrap(); - - let elementos = documento.select(&selector_quote).collect::>(); - println!("Se encontraron {} frases en la página.\n", elementos.len()); - - // 5. Preparar el escritor CSV - let mut escritor = Writer::from_path("frases.csv")?; - escritor.write_record(&["texto", "autor"])?; // Cabeceras - - // 6. Iterar sobre cada frase y extraer datos - for elemento in elementos { - // Encontramos el primer .text dentro del bloque - let texto = elemento.select(&selector_text) - .next() - .map(|t| t.text().collect::()) - .unwrap_or_default(); - - // Encontramos el primer .author dentro del bloque - let autor = elemento.select(&selector_author) - .next() - .map(|a| a.text().collect::()) - .unwrap_or_default(); - - println!("\"{}\"", texto); - println!(" — {}\n", autor); - - // Guardamos la fila en el CSV (la librería csv maneja las comas internas automáticamente) - escritor.write_record(&[&texto, &autor])?; - } - - escritor.flush()?; // Asegurar que todo se escriba al disco - println!("✓ Datos guardados exitosamente en 'frases.csv'"); - - Ok(()) -} diff --git a/ejemplos/general/03-juego-de-adivinanza/README.md b/ejemplos/general/03-juego-de-adivinanza/README.md deleted file mode 100644 index 196fabe..0000000 --- a/ejemplos/general/03-juego-de-adivinanza/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## Validación de Inputs - -**¿Qué muestra este ejemplo?** -Cómo pedir datos al usuario usando `input()` y validarlos correctamente con bucles `while` y bloques `try/except` hasta que ingrese un tipo de dato válido. - -**¿Por qué es útil?** -Previene que un programa interactivo de consola colapse cuando un usuario ingresa texto en lugar de números o presiona "Enter" en lugar de elegir una opción válida de un menú. - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_validacion.py` - -**¿Qué retos usan esta base?** -- [#15 Python General 03 — Juego de adivina el número](https://github.com/Kaia-Alenia/nerve-community/issues/15) -- [#18 Python General 06 — Conversor de unidades](https://github.com/Kaia-Alenia/nerve-community/issues/18) -- [#19 Python General 07 — Calculadora de IMC](https://github.com/Kaia-Alenia/nerve-community/issues/19) - -**Nota para principiantes** -El valor devuelto por `input()` siempre es una cadena de texto (`str`). No olvides convertirlo explícitamente a `int` o `float` antes de hacer operaciones matemáticas. \ No newline at end of file diff --git a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.go b/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.go deleted file mode 100644 index d00a373..0000000 --- a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Ejemplo: Mini juego "adivina el color" con math/rand, bucle y conteo de intentos - -Qué enseña este ejemplo: - - Cómo usar rand.Intn() para seleccionar un elemento al azar de un slice - - Cómo estructurar el bucle de intentos con for + contador - - Cómo dar pistas simples (mayor/menor en números, aquí: letra inicial) - - Cómo manejar input no válido con validación condicional - -Para tu reto (adivina el número): - Aplica el mismo patrón de bucle + contador + pistas, - pero usa rand.Intn(100) + 1 para generar un número - y compara si el intento es mayor o menor al número secreto. - -Glosario: - rand.Intn(n) — genera un entero al azar entre 0 y n-1 - strings.ToLower(s) — convierte un string a minúsculas - strings.TrimSpace(s) — elimina espacios al inicio y final del string - bufio.NewReader(os.Stdin) — crea un lector para leer entrada desde la terminal -*/ - -package main - -import ( - "bufio" - "fmt" - "math/rand" - "os" - "strings" -) - -func contiene(slice []string, item string) bool { - for _, s := range slice { - if s == item { - return true - } - } - return false -} - -func main() { - colores := []string{"rojo", "azul", "verde", "amarillo", "morado", "naranja"} - maxIntentos := 4 - - // elegir color al azar - colorSecreto := colores[rand.Intn(len(colores))] - intentos := 0 - - fmt.Println("=== Adivina el color ===") - fmt.Printf("Tengo un color en mente. Tienes %d intentos.\n", maxIntentos) - fmt.Printf("Opciones posibles: %s\n\n", strings.Join(colores, ", ")) - - reader := bufio.NewReader(os.Stdin) - - for intentos < maxIntentos { - fmt.Printf("Intento %d/%d: ", intentos+1, maxIntentos) - respuesta, _ := reader.ReadString('\n') - intento := strings.ToLower(strings.TrimSpace(respuesta)) - - // Validar - if !contiene(colores, intento) { - fmt.Printf("'%s' no es un color válido. Elige de la lista.\n\n", intento) - continue - } - - intentos++ - - if intento == colorSecreto { - fmt.Printf("\n¡Correcto! Era '%s'. Lo lograste en %d intento(s).\n", colorSecreto, intentos) - return - } - - // Pista (comparando primer carácter) - if intento[0] < colorSecreto[0] { - fmt.Printf("No es '%s'. El color secreto va después alfabéticamente.\n", intento) - } else { - fmt.Printf("No es '%s'. El color secreto va antes alfabéticamente.\n", intento) - } - fmt.Println() - } - - fmt.Printf("\nSe acabaron los intentos. El color era '%s'.\n", colorSecreto) -} diff --git a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.js b/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.js deleted file mode 100644 index b85a431..0000000 --- a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Ejemplo: Mini juego "adivina el color" con Math.random, bucle y conteo de intentos - * - * Qué enseña este ejemplo: - * - Cómo usar Math.random() para seleccionar un elemento al azar de un array - * - Cómo estructurar el bucle de intentos con while + contador - * - Cómo dar pistas simples (mayor/menor en números, aquí: letra inicial) - * - Cómo manejar input no válido con validación condicional - * - * Para tu reto (adivina el número): - * Aplica el mismo patrón de bucle + contador + pistas, - * pero usa Math.floor(Math.random() * 100) + 1 para generar un número - * y compara si el intento es mayor o menor al número secreto. - * - * Glosario: - * Math.random() — Genera un número decimal entre 0 (incluido) y 1 (excluido) - * Math.floor() — Redondea hacia abajo al entero más cercano - * .toLowerCase() — Convierte un string a minúsculas (útil para comparar) - * .trim() — Elimina espacios al inicio y final del string - * readline — Módulo de Node.js para leer entrada desde la terminal - */ - -const readline = require('readline'); - -const COLORES = ["rojo", "azul", "verde", "amarillo", "morado", "naranja"]; -const MAX_INTENTOS = 4; - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -// prompt que devuelve una promesa para facilitar el uso asíncrono -const input = (pregunta) => new Promise(resolve => rl.question(pregunta, resolve)); - -async function jugar() { - // Math.random() junto con Math.floor() para elegir un índice al azar - const indiceAleatorio = Math.floor(Math.random() * COLORES.length); - const colorSecreto = COLORES[indiceAleatorio]; - let intentos = 0; - - console.log("=== Adivina el color ==="); - console.log(`Tengo un color en mente. Tienes ${MAX_INTENTOS} intentos.`); - console.log(`Opciones posibles: ${COLORES.join(', ')}\n`); - - while (intentos < MAX_INTENTOS) { - const respuesta = await input(`Intento ${intentos + 1}/${MAX_INTENTOS}: `); - const intento = respuesta.trim().toLowerCase(); - - // Validar que el input sea uno de los colores válidos - if (!COLORES.includes(intento)) { - console.log(`'${intento}' no es un color válido. Elige de la lista.\n`); - continue; // No contamos este como un intento fallido - } - - intentos++; - - if (intento === colorSecreto) { - console.log(`\n¡Correcto! Era '${colorSecreto}'. Lo lograste en ${intentos} intento(s).`); - rl.close(); - return; - } - - // Pista: comparar letra inicial (equivalente a mayor/menor en números) - if (intento[0] < colorSecreto[0]) { - console.log(`No es '${intento}'. El color secreto va después alfabéticamente.`); - } else { - console.log(`No es '${intento}'. El color secreto va antes alfabéticamente.`); - } - console.log(); - } - - console.log(`\nSe acabaron los intentos. El color era '${colorSecreto}'.`); - rl.close(); -} - -if (require.main === module) { - jugar(); -} diff --git a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.py b/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.py deleted file mode 100644 index 84e71ce..0000000 --- a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Ejemplo: Mini juego "adivina el color" con random.choice, loop y conteo de intentos - -Qué enseña este ejemplo: - - Cómo usar random.choice() para seleccionar un elemento al azar de una lista - - Cómo estructurar el loop de intentos con while + contador - - Cómo dar pistas simples (mayor/menor en números, aquí: letra inicial) - - Cómo manejar input no válido con validación condicional - -Para tu reto (adivina el número): - Aplica el mismo patrón de loop + contador + pistas, - pero usa random.randint(1, 100) para generar un número - y compara si el intento es mayor o menor al número secreto. - -Glosario: - random.choice(lista) — elige un elemento al azar de cualquier lista o string - random.randint(a, b) — genera un entero al azar entre a y b (ambos incluidos) - .lower() — convierte un string a minúsculas (útil para comparar) - .strip() — elimina espacios al inicio y final del string -""" - -import random - -COLORES = ["rojo", "azul", "verde", "amarillo", "morado", "naranja"] -MAX_INTENTOS = 4 - - -def jugar(): - # random.choice elige un color al azar de la lista - color_secreto = random.choice(COLORES) - intentos = 0 - - print("=== Adivina el color ===") - print(f"Tengo un color en mente. Tienes {MAX_INTENTOS} intentos.") - print(f"Opciones posibles: {', '.join(COLORES)}\n") - - while intentos < MAX_INTENTOS: - intento = input(f"Intento {intentos + 1}/{MAX_INTENTOS}: ").strip().lower() - - # Validar que el input sea uno de los colores válidos - if intento not in COLORES: - print(f"'{intento}' no es un color válido. Elige de la lista.\n") - continue # No contamos este como un intento fallido - - intentos += 1 - - if intento == color_secreto: - print( - f"\n¡Correcto! Era '{color_secreto}'. Lo lograste en {intentos} intento(s)." - ) - return - - # Pista: comparar letra inicial (equivalente a mayor/menor en números) - if intento[0] < color_secreto[0]: - print(f"No es '{intento}'. El color secreto va después alfabéticamente.") - else: - print(f"No es '{intento}'. El color secreto va antes alfabéticamente.") - print() - - print(f"\nSe acabaron los intentos. El color era '{color_secreto}'.") - - -if __name__ == "__main__": - jugar() diff --git a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.rs b/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.rs deleted file mode 100644 index 6c0dee7..0000000 --- a/ejemplos/general/03-juego-de-adivinanza/ejemplo_validacion.rs +++ /dev/null @@ -1,78 +0,0 @@ -/* -Ejemplo: Mini juego "adivina el color" con rand, bucle y conteo de intentos - -Qué enseña este ejemplo: - - Cómo usar el crate rand (y slice::choose) para elegir un elemento al azar - - Cómo estructurar el bucle de intentos con loop + contador - - Cómo dar pistas simples (mayor/menor en números, aquí: letra inicial) - - Cómo manejar input no válido con validación condicional - -Para tu reto (adivina el número): - Aplica el mismo patrón de bucle + contador + pistas, - pero usa rand::thread_rng().gen_range(1..=100) para generar un número - y compara si el intento es mayor o menor al número secreto. - -Glosario: - rand::thread_rng() — obtiene el generador de números aleatorios local - slice.choose(...) — elige un elemento al azar de un arreglo o vector - .trim() — elimina espacios al inicio y final del string - .to_lowercase() — convierte un string a minúsculas - std::io::stdin() — la manera estándar de leer entrada en la consola -*/ - -use rand::seq::SliceRandom; -use std::io::{self, Write}; - -fn main() { - let colores = ["rojo", "azul", "verde", "amarillo", "morado", "naranja"]; - let max_intentos = 4; - - // Obtener RNG (Random Number Generator) - let mut rng = rand::thread_rng(); - - // choose devuelve un Option, hacemos unwrap porque sabemos que el arreglo no está vacío - let color_secreto = colores.choose(&mut rng).unwrap(); - let mut intentos = 0; - - println!("=== Adivina el color ==="); - println!("Tengo un color en mente. Tienes {} intentos.", max_intentos); - println!("Opciones posibles: {}\n", colores.join(", ")); - - while intentos < max_intentos { - print!("Intento {}/{}: ", intentos + 1, max_intentos); - io::stdout().flush().unwrap(); // Para que el print anterior se muestre antes del input - - let mut input = String::new(); - io::stdin() - .read_line(&mut input) - .expect("Error al leer la entrada"); - - let intento = input.trim().to_lowercase(); - - // Validar - if !colores.contains(&intento.as_str()) { - println!("'{}' no es un color válido. Elige de la lista.\n", intento); - continue; - } - - intentos += 1; - - if intento == *color_secreto { - println!("\n¡Correcto! Era '{}'. Lo lograste en {} intento(s).", color_secreto, intentos); - return; - } - - // Pista: obtenemos el primer char - let char_intento = intento.chars().next().unwrap(); - let char_secreto = color_secreto.chars().next().unwrap(); - - if char_intento < char_secreto { - println!("No es '{}'. El color secreto va después alfabéticamente.", intento); - } else { - println!("No es '{}'. El color secreto va antes alfabéticamente.", intento); - } - println!(); - } - - println!("\nSe acabaron los intentos. El color era '{}'.", color_secreto); -} diff --git a/ejemplos/general/04-analizador-de-gastos-csv/README.md b/ejemplos/general/04-analizador-de-gastos-csv/README.md deleted file mode 100644 index f8f9515..0000000 --- a/ejemplos/general/04-analizador-de-gastos-csv/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Lectura y Procesamiento de CSV - -**¿Qué muestra este ejemplo?** -Cómo leer y escribir archivos CSV (Comma-Separated Values) usando el módulo `csv` de la librería estándar, especialmente usando `csv.DictReader` para mayor claridad. - -**¿Por qué es útil?** -Es esencial para procesar exportaciones de bases de datos, hojas de cálculo, registros financieros y datos tabulares estructurados sin depender de librerías pesadas como Pandas. - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_csv.py` - -**¿Qué retos usan esta base?** -- [#16 Python General 04 — Analizador de gastos personales (CSV)](https://github.com/Kaia-Alenia/nerve-community/issues/16) - -**Nota para principiantes** -Presta mucha atención al parámetro `encoding="utf-8"` al abrir el archivo, especialmente en Windows, para evitar errores al leer acentos u otros caracteres especiales del idioma español. \ No newline at end of file diff --git a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.go b/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.go deleted file mode 100644 index 3dea2d2..0000000 --- a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -Ejemplo: Leer un CSV real desde disco, filtrar y calcular sumas - -Qué enseña este ejemplo: - - Cómo abrir y leer un archivo CSV con os.Open y encoding/csv - - Cómo iterar por cada fila del archivo CSV - - Cómo convertir strings a números con strconv.ParseFloat - - Cómo agrupar filas y calcular sumas / promedios usando mapas - -Para tu reto (analizador de gastos): - Aplica el mismo patrón para leer tu CSV de gastos - (fecha, categoría, monto) y calcular totales por categoría. - -Glosario: - os.Open(archivo) — Abre un archivo en modo lectura. - defer f.Close() — Asegura que el archivo se cerrará cuando termine la función. - csv.NewReader(f) — Crea un lector especializado que entiende el formato CSV (comas, comillas). - strconv.ParseFloat(s) — Convierte un texto como "1234.50" a un número flotante (decimal). -*/ - -package main - -import ( - "encoding/csv" - "fmt" - "os" - "strconv" -) - -type Empleado struct { - Nombre string - Departamento string - Salario float64 -} - -func leerEmpleados(rutaCsv string) []Empleado { - file, err := os.Open(rutaCsv) - if err != nil { - fmt.Printf("No se pudo abrir el archivo: %v\n", err) - return []Empleado{} - } - defer file.Close() - - reader := csv.NewReader(file) - // Leer todas las filas - filas, err := reader.ReadAll() - if err != nil { - fmt.Printf("Error leyendo el CSV: %v\n", err) - return []Empleado{} - } - - var empleados []Empleado - - // Iterar omitiendo la cabecera (índice 0) - for i := 1; i < len(filas); i++ { - fila := filas[i] - if len(fila) < 3 { - continue - } - - nombre := fila[0] - departamento := fila[1] - salario, _ := strconv.ParseFloat(fila[2], 64) - - empleados = append(empleados, Empleado{ - Nombre: nombre, - Departamento: departamento, - Salario: salario, - }) - } - - return empleados -} - -type Estadisticas struct { - Suma float64 - Cantidad int -} - -func reportePorDepartamento(empleados []Empleado) { - totales := make(map[string]*Estadisticas) - - for _, emp := range empleados { - dept := emp.Departamento - if totales[dept] == nil { - totales[dept] = &Estadisticas{} - } - totales[dept].Suma += emp.Salario - totales[dept].Cantidad++ - } - - fmt.Println("\n--- Reporte por departamento ---") - for dept, stats := range totales { - promedio := stats.Suma / float64(stats.Cantidad) - // %.0f imprime sin decimales - fmt.Printf(" %s: total=$%.0f | promedio=$%.0f | empleados=%d\n", dept, stats.Suma, promedio, stats.Cantidad) - } -} - -func main() { - archivo := "empleados.csv" - - // Crear archivo de ejemplo si no existe - if _, err := os.Stat(archivo); os.IsNotExist(err) { - datos := []string{ - "nombre,departamento,salario", - "Ana García,Ingeniería,35000", - "Luis Martínez,Marketing,28000", - "Carmen López,Ingeniería,42000", - "Pedro Sánchez,Marketing,31000", - "Sofía Ramírez,Ingeniería,39000", - } - - f, _ := os.Create(archivo) - for _, linea := range datos { - f.WriteString(linea + "\n") - } - f.Close() - fmt.Printf("Archivo '%s' creado de ejemplo.\n", archivo) - } - - empleados := leerEmpleados(archivo) - fmt.Printf("\nSe leyeron %d empleados.\n", len(empleados)) - reportePorDepartamento(empleados) -} diff --git a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.js b/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.js deleted file mode 100644 index 98eb106..0000000 --- a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Ejemplo: Leer un CSV real desde disco, filtrar y calcular sumas - * - * Qué enseña este ejemplo: - * - Cómo abrir y leer un archivo CSV con el módulo fs de Node.js - * - Cómo procesar líneas de texto y dividirlas en columnas - * - Cómo convertir strings a números para operar con ellos - * - Cómo agrupar filas y calcular sumas / promedios - * - * Para tu reto (analizador de gastos): - * Aplica el mismo patrón para leer tu CSV de gastos - * (fecha, categoría, monto) y calcular totales por categoría. - * - * Glosario: - * fs.readFileSync(ruta) — Lee todo el contenido de un archivo a la memoria. - * "utf-8" — Asegura que los caracteres especiales se decodifiquen como texto y no como un buffer binario. - * .split('\n') — Divide un texto largo en una lista de líneas. - * .split(',') — Divide una línea en columnas separadas por comas. - * parseFloat(valor) — Convierte un texto como "1234.50" a un número decimal. - */ - -const fs = require('fs'); - -function leerEmpleados(rutaCsv) { - if (!fs.existsSync(rutaCsv)) { - console.log(`Archivo no encontrado: ${rutaCsv}`); - return []; - } - - const contenido = fs.readFileSync(rutaCsv, 'utf-8'); - const lineas = contenido.trim().split('\n'); - - // La primera línea son las cabeceras - const empleados = []; - - for (let i = 1; i < lineas.length; i++) { - const linea = lineas[i].trim(); - if (!linea) continue; - - const [nombre, departamento, salarioStr] = linea.split(','); - - empleados.push({ - nombre: nombre, - departamento: departamento, - salario: parseFloat(salarioStr) - }); - } - - return empleados; -} - -function reportePorDepartamento(empleados) { - const totales = {}; - - for (const emp of empleados) { - const dept = emp.departamento; - if (!totales[dept]) { - totales[dept] = { suma: 0, cantidad: 0 }; - } - totales[dept].suma += emp.salario; - totales[dept].cantidad += 1; - } - - console.log("\n--- Reporte por departamento ---"); - for (const [dept, datos] of Object.entries(totales)) { - const promedio = datos.suma / datos.cantidad; - console.log(` ${dept}: total=$${datos.suma.toLocaleString()} | promedio=$${promedio.toLocaleString()} | empleados=${datos.cantidad}`); - } -} - -// Crear archivo de ejemplo si no existe -const archivo = "empleados.csv"; -if (!fs.existsSync(archivo)) { - const datos = [ - "nombre,departamento,salario", - "Ana García,Ingeniería,35000", - "Luis Martínez,Marketing,28000", - "Carmen López,Ingeniería,42000", - "Pedro Sánchez,Marketing,31000", - "Sofía Ramírez,Ingeniería,39000" - ]; - fs.writeFileSync(archivo, datos.join("\n"), "utf-8"); - console.log(`Archivo '${archivo}' creado de ejemplo.`); -} - -const empleados = leerEmpleados(archivo); -console.log(`\nSe leyeron ${empleados.length} empleados.`); -reportePorDepartamento(empleados); diff --git a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.py b/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.py deleted file mode 100644 index 24526a4..0000000 --- a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Ejemplo: Leer un CSV real desde disco con csv.DictReader, filtrar y calcular sumas - -Qué enseña este ejemplo: - - Cómo abrir y leer un archivo CSV real con open() + csv.DictReader - - Por qué DictReader (con D mayúscula) es mejor que reader simple - - Cómo convertir strings a números para operar con ellos - - Cómo filtrar filas y calcular sumas / promedios - -Para tu reto (analizador de gastos): - Aplica el mismo patrón para leer tu CSV de gastos - (fecha, categoría, monto) y calcular totales por categoría. - -Glosario de términos "raros": - with open(...) as f — Abre un archivo y lo cierra automáticamente al terminar. - 'f' es una variable corta para 'file'. - "r" o "w" — Modos: "r" (read = leer), "w" (write = escribir). - encoding="utf-8" — Asegura que los acentos y caracteres especiales (ñ, á) se guarden bien. - newline="" — Evita que se escriban líneas en blanco extra entre filas en Windows. - csv.DictReader — Lee el CSV convirtiendo cada fila en un diccionario (usa D mayúscula). - csv.reader — Más básico: devuelve cada fila como lista de strings. - float(valor) — Convierte un texto como "1234.50" a un número decimal. - -Archivo de ejemplo (empleados.csv): - nombre,departamento,salario - Ana García,Ingeniería,35000 - Luis Martínez,Marketing,28000 - Carmen López,Ingeniería,42000 - Pedro Sánchez,Marketing,31000 - Sofía Ramírez,Ingeniería,39000 -""" - -import csv -from pathlib import Path - - -def leer_empleados(ruta_csv: str) -> list: - """ - Lee el CSV y devuelve una lista de diccionarios. - Cada dict representa un empleado: {"nombre": ..., "departamento": ..., "salario": ...} - """ - empleados = [] - ruta = Path(ruta_csv) - - if not ruta.exists(): - print(f"Archivo no encontrado: {ruta_csv}") - return [] - - # open() abre el archivo; newline="" es recomendado por la documentación de csv - with open(ruta, "r", encoding="utf-8", newline="") as f: - # DictReader usa la primera fila como nombres de columna automáticamente - lector = csv.DictReader(f) - for fila in lector: - # Las filas de CSV son siempre strings — convertimos salario a float - empleados.append( - { - "nombre": fila["nombre"], - "departamento": fila["departamento"], - "salario": float(fila["salario"]), - } - ) - - return empleados - - -def reporte_por_departamento(empleados: list): - """ - Calcula el total y promedio de salario por departamento. - """ - totales = {} - - for emp in empleados: - dept = emp["departamento"] - if dept not in totales: - totales[dept] = {"suma": 0.0, "cantidad": 0} - totales[dept]["suma"] += emp["salario"] - totales[dept]["cantidad"] += 1 - - print("\n--- Reporte por departamento ---") - for dept, datos in totales.items(): - promedio = datos["suma"] / datos["cantidad"] - print( - f" {dept}: total=${datos['suma']:,.0f} | promedio=${promedio:,.0f} | empleados={datos['cantidad']}" - ) - - -if __name__ == "__main__": - # Crear archivo de ejemplo si no existe - archivo = "empleados.csv" - if not Path(archivo).exists(): - with open(archivo, "w", encoding="utf-8", newline="") as f: - writer = csv.writer(f) - writer.writerow(["nombre", "departamento", "salario"]) - writer.writerows( - [ - ["Ana García", "Ingeniería", 35000], - ["Luis Martínez", "Marketing", 28000], - ["Carmen López", "Ingeniería", 42000], - ["Pedro Sánchez", "Marketing", 31000], - ["Sofía Ramírez", "Ingeniería", 39000], - ] - ) - print(f"Archivo '{archivo}' creado de ejemplo.") - - empleados = leer_empleados(archivo) - print(f"\nSe leyeron {len(empleados)} empleados.") - reporte_por_departamento(empleados) diff --git a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.rs b/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.rs deleted file mode 100644 index 90a3b5b..0000000 --- a/ejemplos/general/04-analizador-de-gastos-csv/ejemplo_csv.rs +++ /dev/null @@ -1,114 +0,0 @@ -/* -Ejemplo: Leer un CSV real desde disco, filtrar y calcular sumas - -Qué enseña este ejemplo: - - Cómo usar la biblioteca estándar para leer archivos estructurados. - - Cómo convertir strings a números con .parse() - - Cómo agrupar usando un HashMap - -Para tu reto (analizador de gastos): - Aplica el mismo patrón para leer tu CSV de gastos - (fecha, categoría, monto) y calcular totales por categoría. - -Glosario: - File::open(ruta) — Abre un archivo en modo lectura. - .parse::() — Intenta convertir un string a un número decimal (float de 64 bits). - HashMap::new() — Crea una estructura de diccionario para guardar totales por clave. - .entry(clave) — Busca una clave en el map para modificarla o insertarla si no existe. -*/ - -use std::collections::HashMap; -use std::fs::File; -use std::io::Write; -use std::path::Path; - -struct Empleado { - _nombre: String, - departamento: String, - salario: f64, -} - -struct Estadisticas { - suma: f64, - cantidad: i32, -} - -fn leer_empleados(ruta_csv: &str) -> Vec { - let mut empleados = Vec::new(); - - let file = match File::open(ruta_csv) { - Ok(f) => f, - Err(_) => { - println!("Archivo no encontrado: {}", ruta_csv); - return empleados; - } - }; - - use std::io::{BufRead, BufReader}; - let reader = BufReader::new(file); - - for (index, line) in reader.lines().enumerate() { - if index == 0 { - continue; // Saltar cabecera - } - if let Ok(l) = line { - let cols: Vec<&str> = l.split(',').collect(); - if cols.len() >= 3 { - let nombre = cols[0].trim().to_string(); - let departamento = cols[1].trim().to_string(); - let salario = cols[2].trim().parse::().unwrap_or(0.0); - - empleados.push(Empleado { - _nombre: nombre, - departamento, - salario, - }); - } - } - } - - empleados -} - -fn reporte_por_departamento(empleados: &[Empleado]) { - let mut totales: HashMap = HashMap::new(); - - for emp in empleados { - let entry = totales - .entry(emp.departamento.clone()) - .or_insert(Estadisticas { suma: 0.0, cantidad: 0 }); - entry.suma += emp.salario; - entry.cantidad += 1; - } - - println!("\n--- Reporte por departamento ---"); - for (dept, stats) in totales.iter() { - let promedio = stats.suma / stats.cantidad as f64; - println!( - " {}: total=${} | promedio=${} | empleados={}", - dept, stats.suma, promedio, stats.cantidad - ); - } -} - -fn main() { - let archivo = "empleados.csv"; - - if !Path::new(archivo).exists() { - let mut file = File::create(archivo).expect("No se pudo crear el archivo"); - let datos = "\ -nombre,departamento,salario -Ana García,Ingeniería,35000 -Luis Martínez,Marketing,28000 -Carmen López,Ingeniería,42000 -Pedro Sánchez,Marketing,31000 -Sofía Ramírez,Ingeniería,39000 -"; - file.write_all(datos.as_bytes()).unwrap(); - println!("Archivo '{}' creado de ejemplo.", archivo); - } - - let empleados = leer_empleados(archivo); - println!("\nSe leyeron {} empleados.", empleados.len()); - reporte_por_departamento(&empleados); -} diff --git a/ejemplos/general/05-generador-de-contrasenas/README.md b/ejemplos/general/05-generador-de-contrasenas/README.md deleted file mode 100644 index 0ba78d4..0000000 --- a/ejemplos/general/05-generador-de-contrasenas/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Aleatoriedad Segura (Secrets vs Random) - -**¿Qué muestra este ejemplo?** -La diferencia entre generar números o valores pseudoaleatorios usando el módulo `random` frente a generar datos criptográficamente seguros usando el módulo `secrets`. - -**¿Por qué es útil?** -Es crítico para crear tokens de sesión, contraseñas, o enlaces reseteo, garantizando que atacantes no puedan predecir los valores generados. - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_secrets.py` - -**¿Qué retos usan esta base?** -- [#15 Python General 03 — Juego de adivina el número](https://github.com/Kaia-Alenia/nerve-community/issues/15) -- [#17 Python General 05 — Generador de contraseñas seguras](https://github.com/Kaia-Alenia/nerve-community/issues/17) - -**Nota para principiantes** -Usa `random` para juegos o simulaciones, pero nunca para contraseñas o tokens de seguridad — para eso siempre usa `secrets`. \ No newline at end of file diff --git a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.go b/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.go deleted file mode 100644 index ec2a5e3..0000000 --- a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.go +++ /dev/null @@ -1,107 +0,0 @@ -/* -Ejemplo: Construir un PIN bancario seguro usando criptografía - -Qué enseña este ejemplo: - - Cuándo usar crypto/rand en lugar de math/rand (y por qué importa) - - Cómo definir constantes de caracteres (alfabeto, números, símbolos) - - Cómo garantizar que el resultado incluya al menos un carácter de cada tipo - -Para tu reto (generador de contraseñas): - Aplica el mismo patrón: construye el pool de caracteres según los - parámetros del usuario (longitud, símbolos sí/no, mayúsculas sí/no), - y usa crypto/rand para construir la contraseña. - -Glosario: - crypto/rand — paquete que ofrece aleatoriedad criptográficamente segura. - math/rand — NO usar para contraseñas, sus resultados son predecibles. - rand.Int(rand.Reader, max) — Devuelve un entero seguro grande, hay que convertirlo. -*/ - -package main - -import ( - "crypto/rand" - "fmt" - "math/big" -) - -const ( - Digitos = "0123456789" - Mayusculas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - Simbolos = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" -) - -// Funciones de ayuda -func choice(pool string) byte { - max := big.NewInt(int64(len(pool))) - n, _ := rand.Int(rand.Reader, max) - return pool[n.Int64()] -} - -func shuffle(caracteres []byte) { - for i := len(caracteres) - 1; i > 0; i-- { - max := big.NewInt(int64(i + 1)) - n, _ := rand.Int(rand.Reader, max) - j := n.Int64() - caracteres[i], caracteres[j] = caracteres[j], caracteres[i] - } -} - -func generarPin(longitud int, soloNumeros bool) string { - var pool string - if soloNumeros { - pool = Digitos - } else { - pool = Mayusculas + Digitos - } - - pin := make([]byte, longitud) - for i := 0; i < longitud; i++ { - pin[i] = choice(pool) - } - return string(pin) -} - -func generarPinGarantizado(longitud int) string { - if longitud < 3 { - panic("La longitud mínima para un PIN garantizado es 3.") - } - - poolCompleto := Mayusculas + Digitos + Simbolos - - caracteres := make([]byte, longitud) - - // 1. Garantizar al menos uno de cada tipo requerido - caracteres[0] = choice(Mayusculas) - caracteres[1] = choice(Digitos) - caracteres[2] = choice(Simbolos) - - // 2. Rellenar el resto - for i := 3; i < longitud; i++ { - caracteres[i] = choice(poolCompleto) - } - - // 3. Mezclar - shuffle(caracteres) - - return string(caracteres) -} - -func main() { - fmt.Println("=== Generador de PINs seguros ===\n") - - fmt.Println("PINs numéricos (6 dígitos):") - for i := 0; i < 3; i++ { - fmt.Printf(" %s\n", generarPin(6, true)) - } - - fmt.Println("\nPINs alfanuméricos (8 caracteres):") - for i := 0; i < 3; i++ { - fmt.Printf(" %s\n", generarPin(8, false)) - } - - fmt.Println("\nPINs con garantía (letra + dígito + símbolo, 10 chars):") - for i := 0; i < 3; i++ { - fmt.Printf(" %s\n", generarPinGarantizado(10)) - } -} diff --git a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.js b/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.js deleted file mode 100644 index c7b5fab..0000000 --- a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Ejemplo: Construir un PIN bancario seguro con criptografía - * - * Qué enseña este ejemplo: - * - Cuándo usar crypto.randomInt en lugar de Math.random (y por qué importa) - * - Cómo definir listas de caracteres (alfabeto, números, símbolos) - * - Cómo garantizar que el resultado incluya al menos un carácter de cada tipo - * - * Para tu reto (generador de contraseñas): - * Aplica el mismo patrón: construye el pool de caracteres según los - * parámetros del usuario (longitud, símbolos sí/no, mayúsculas sí/no), - * y usa selección criptográfica para construir la contraseña. - * - * Glosario: - * crypto.randomInt(max) — Devuelve un entero aleatorio criptográficamente seguro entre 0 y max-1. - * Math.random() — NO usar para contraseñas, es predecible. - * arreglo.push(item) — Añade un elemento al final de un arreglo. - * arreglo.join('') — Une los elementos de un arreglo en un solo texto. - */ - -const crypto = require('crypto'); - -const DIGITOS = "0123456789"; -const MAYUSCULAS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; -const SIMBOLOS = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; - -// Función de ayuda para elegir un caracter seguro al azar -function choice(pool) { - const indice = crypto.randomInt(pool.length); - return pool[indice]; -} - -// Función de ayuda para mezclar un arreglo (Fisher-Yates con crypto) -function shuffle(arreglo) { - for (let i = arreglo.length - 1; i > 0; i--) { - const j = crypto.randomInt(i + 1); - [arreglo[i], arreglo[j]] = [arreglo[j], arreglo[i]]; - } -} - -function generarPin(longitud = 6, soloNumeros = true) { - let pool; - if (soloNumeros) { - pool = DIGITOS; - } else { - pool = MAYUSCULAS + DIGITOS; - } - - let pin = ""; - for (let i = 0; i < longitud; i++) { - pin += choice(pool); - } - return pin; -} - -function generarPinGarantizado(longitud = 8) { - if (longitud < 3) { - throw new Error("La longitud mínima para un PIN garantizado es 3."); - } - - const poolCompleto = MAYUSCULAS + DIGITOS + SIMBOLOS; - - // 1. Garantizar al menos uno de cada tipo requerido - const obligatorios = [ - choice(MAYUSCULAS), - choice(DIGITOS), - choice(SIMBOLOS) - ]; - - // 2. Rellenar el resto con caracteres aleatorios del pool completo - const relleno = []; - for (let i = 0; i < longitud - 3; i++) { - relleno.push(choice(poolCompleto)); - } - - // 3. Unir todo y mezclar - const caracteres = obligatorios.concat(relleno); - shuffle(caracteres); - - return caracteres.join(""); -} - -if (require.main === module) { - console.log("=== Generador de PINs seguros ===\n"); - - console.log("PINs numéricos (6 dígitos):"); - for (let i = 0; i < 3; i++) { - console.log(` ${generarPin(6, true)}`); - } - - console.log("\nPINs alfanuméricos (8 caracteres):"); - for (let i = 0; i < 3; i++) { - console.log(` ${generarPin(8, false)}`); - } - - console.log("\nPINs con garantía (letra + dígito + símbolo, 10 chars):"); - for (let i = 0; i < 3; i++) { - console.log(` ${generarPinGarantizado(10)}`); - } -} diff --git a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.py b/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.py deleted file mode 100644 index 3de3974..0000000 --- a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Ejemplo: Construir un PIN bancario seguro con secrets + string - -Qué enseña este ejemplo: - - Cuándo usar secrets en lugar de random (y por qué importa) - - Cómo usar string.digits, string.ascii_letters, string.punctuation - - Cómo construir una cadena aleatoria con join() + secrets.choice() - - Cómo garantizar que el resultado incluya al menos un carácter de cada tipo - -Para tu reto (generador de contraseñas): - Aplica el mismo patrón: construye el pool de caracteres según los - parámetros del usuario (longitud, símbolos sí/no, mayúsculas sí/no), - y usa secrets.choice() en un loop para construir la contraseña. - -Glosario de términos "raros": - secrets — módulo para aleatoriedad segura (usa fuentes del sistema). - random — módulo de aleatoriedad predecible (NO usar para contraseñas). - string.digits — contiene "0123456789". - string.ascii... — contiene letras ("abcdefghijklmnopqrstuvwxyz", etc). - string.punctuation — todos los símbolos: !"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ - "".join(lista) — une una lista de caracteres en un solo texto (ej: "a" + "b" = "ab"). - _ (guion bajo) — se usa en `for _ in range()` cuando vas a repetir algo N veces - pero no necesitas usar el número de la vuelta actual. -""" - -import secrets -import string - - -def generar_pin(longitud: int = 6, solo_numeros: bool = True) -> str: - """ - Genera un PIN seguro. - Si solo_numeros=True, usa únicamente dígitos (PIN bancario clásico). - Si solo_numeros=False, mezcla letras y dígitos (PIN alfanumérico). - """ - if solo_numeros: - pool = string.digits # "0123456789" - else: - pool = string.ascii_uppercase + string.digits - - # join() + list comprehension: llama secrets.choice(pool) N veces - # y une todos los caracteres resultantes en un string - pin = "".join(secrets.choice(pool) for _ in range(longitud)) - return pin - - -def generar_pin_garantizado(longitud: int = 8) -> str: - """ - Genera un PIN que GARANTIZA tener al menos: - - 1 letra mayúscula - - 1 dígito - - 1 símbolo - - Técnica: generar una de cada tipo obligatorio, luego rellenar - el resto con caracteres del pool completo y mezclar. - """ - if longitud < 3: - raise ValueError("La longitud mínima para un PIN garantizado es 3.") - - pool_completo = string.ascii_uppercase + string.digits + string.punctuation - - # 1. Garantizar al menos uno de cada tipo requerido - obligatorios = [ - secrets.choice(string.ascii_uppercase), - secrets.choice(string.digits), - secrets.choice(string.punctuation), - ] - - # 2. Rellenar el resto con caracteres aleatorios del pool completo - relleno = [secrets.choice(pool_completo) for _ in range(longitud - 3)] - - # 3. Unir todo y mezclar (secrets.SystemRandom para barajar de forma segura) - caracteres = obligatorios + relleno - secrets.SystemRandom().shuffle(caracteres) - - return "".join(caracteres) - - -if __name__ == "__main__": - print("=== Generador de PINs seguros ===\n") - - print("PINs numéricos (6 dígitos):") - for _ in range(3): - print(f" {generar_pin(6, solo_numeros=True)}") - - print("\nPINs alfanuméricos (8 caracteres):") - for _ in range(3): - print(f" {generar_pin(8, solo_numeros=False)}") - - print("\nPINs con garantía (letra + dígito + símbolo, 10 chars):") - for _ in range(3): - print(f" {generar_pin_garantizado(10)}") diff --git a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.rs b/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.rs deleted file mode 100644 index 1e4e889..0000000 --- a/ejemplos/general/05-generador-de-contrasenas/ejemplo_secrets.rs +++ /dev/null @@ -1,93 +0,0 @@ -/* -Ejemplo: Construir un PIN bancario seguro con generadores de números aleatorios - -Qué enseña este ejemplo: - - Cómo usar el crate 'rand' para generar aleatoriedad criptográficamente segura (rand usa el OS RNG por defecto en thread_rng()) - - Cómo estructurar strings de constantes y mezclarlas - - Cómo garantizar que el resultado incluya al menos un carácter de cada tipo - -Para tu reto (generador de contraseñas): - Aplica el mismo patrón: construye el pool de caracteres según los - parámetros del usuario (longitud, símbolos sí/no, mayúsculas sí/no), - y usa el RNG para construir la contraseña. - -Glosario: - rand::thread_rng() — obtiene el generador de números aleatorios seguro local - slice::choose() — selecciona un elemento aleatorio de una lista - slice::shuffle() — mezcla los elementos de una lista -*/ - -use rand::seq::SliceRandom; - -const DIGITOS: &[u8] = b"0123456789"; -const MAYUSCULAS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; -const SIMBOLOS: &[u8] = b"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; - -fn generar_pin(longitud: usize, solo_numeros: bool) -> String { - let mut rng = rand::thread_rng(); - - let mut pool = Vec::new(); - if solo_numeros { - pool.extend_from_slice(DIGITOS); - } else { - pool.extend_from_slice(MAYUSCULAS); - pool.extend_from_slice(DIGITOS); - } - - let mut pin = String::with_capacity(longitud); - for _ in 0..longitud { - let ch = pool.choose(&mut rng).unwrap(); - pin.push(*ch as char); - } - pin -} - -fn generar_pin_garantizado(longitud: usize) -> String { - if longitud < 3 { - panic!("La longitud mínima para un PIN garantizado es 3."); - } - - let mut rng = rand::thread_rng(); - - let mut pool_completo = Vec::new(); - pool_completo.extend_from_slice(MAYUSCULAS); - pool_completo.extend_from_slice(DIGITOS); - pool_completo.extend_from_slice(SIMBOLOS); - - let mut caracteres = Vec::new(); - - // 1. Garantizar - caracteres.push(*MAYUSCULAS.choose(&mut rng).unwrap()); - caracteres.push(*DIGITOS.choose(&mut rng).unwrap()); - caracteres.push(*SIMBOLOS.choose(&mut rng).unwrap()); - - // 2. Rellenar - for _ in 3..longitud { - caracteres.push(*pool_completo.choose(&mut rng).unwrap()); - } - - // 3. Mezclar - caracteres.shuffle(&mut rng); - - // Convertir de bytes a String - caracteres.iter().map(|&b| *b as char).collect() -} - -fn main() { - println!("=== Generador de PINs seguros ===\n"); - - println!("PINs numéricos (6 dígitos):"); - for _ in 0..3 { - println!(" {}", generar_pin(6, true)); - } - - println!("\nPINs alfanuméricos (8 caracteres):"); - for _ in 0..3 { - println!(" {}", generar_pin(8, false)); - } - - println!("\nPINs con garantía (letra + dígito + símbolo, 10 chars):"); - for _ in 0..3 { - println!(" {}", generar_pin_garantizado(10)); - } -} diff --git a/ejemplos/general/06-conversor-de-unidades/README.md b/ejemplos/general/06-conversor-de-unidades/README.md deleted file mode 100644 index 447d9d0..0000000 --- a/ejemplos/general/06-conversor-de-unidades/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Funciones Puras y Menús - -**¿Qué muestra este ejemplo?** -Cómo diseñar funciones puras (que solo reciben parámetros y devuelven un resultado, sin modificar variables globales ni usar `print` dentro) y cómo armar un menú interactivo en la terminal usando bucles y `if/elif`. - -**¿Por qué es útil?** -Dividir tu código en funciones pequeñas, especializadas y sin efectos secundarios hace que el programa sea mucho más fácil de probar, mantener y reutilizar. - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_funciones.py` - -**¿Qué retos usan esta base?** -- [#18 Python General 06 — Conversor de unidades](https://github.com/Kaia-Alenia/nerve-community/issues/18) - -**Nota para principiantes** -Evita poner llamadas a `print()` o `input()` dentro de tus funciones matemáticas o lógicas. La función debe encargarse solo de calcular y devolver (`return`) el resultado; el menú principal se encarga de mostrarlo al usuario. diff --git a/ejemplos/general/06-conversor-de-unidades/ejemplo_funciones.py b/ejemplos/general/06-conversor-de-unidades/ejemplo_funciones.py deleted file mode 100644 index b6dff3b..0000000 --- a/ejemplos/general/06-conversor-de-unidades/ejemplo_funciones.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Ejemplo: Conversor de unidades con funciones puras, type hints y menú separado - -Qué enseña este ejemplo: - - Qué es una "función pura" y por qué es buena práctica separar lógica de UI - - Cómo usar type hints (los ": float" y "-> float" después del nombre) - - Cómo estructurar un menú con varias categorías sin repetir código - - Cómo validar inputs numéricos con try/except ValueError - -Para tu reto (conversor de unidades): - Aplica el mismo patrón: una función por conversión, un menú - que llame a la función correspondiente, y validación de input. - -Glosario de type hints: - def funcion(param: tipo) -> tipo_retorno: - param: tipo — indica qué tipo de dato espera el parámetro - -> tipo — indica qué tipo de dato devuelve la función - - Son opcionales en Python, pero sirven como documentación en vivo y - ayudan a los editores a detectar errores antes de correr el código. - - Ejemplos: - def sumar(a: float, b: float) -> float: # recibe 2 floats, devuelve float - def saludo(nombre: str) -> str: # recibe string, devuelve string - def imprimir(msg: str) -> None: # devuelve None (no retorna nada) -""" - -# --- Funciones puras: solo reciben datos y devuelven datos --- -# No imprimen nada, no llaman a input(), no dependen de variables globales. -# Son fáciles de probar: si le das el mismo input, SIEMPRE dan el mismo output. - - -def km_a_millas(km: float) -> float: - """Convierte kilómetros a millas.""" - return km * 0.621371 - - -def millas_a_km(millas: float) -> float: - """Convierte millas a kilómetros.""" - return millas / 0.621371 - - -def celsius_a_fahrenheit(c: float) -> float: - """Convierte grados Celsius a Fahrenheit.""" - return (c * 9 / 5) + 32 - - -def fahrenheit_a_celsius(f: float) -> float: - """Convierte grados Fahrenheit a Celsius.""" - return (f - 32) * 5 / 9 - - -def kg_a_libras(kg: float) -> float: - """Convierte kilogramos a libras.""" - return kg * 2.20462 - - -def libras_a_kg(libras: float) -> float: - """Convierte libras a kilogramos.""" - return libras / 2.20462 - - -# --- Mapa de opciones: conecta el menú con las funciones puras --- -OPCIONES = { - "1": ("km → millas", km_a_millas, "km", "millas"), - "2": ("millas → km", millas_a_km, "millas", "km"), - "3": ("°C → °F", celsius_a_fahrenheit, "°C", "°F"), - "4": ("°F → °C", fahrenheit_a_celsius, "°F", "°C"), - "5": ("kg → libras", kg_a_libras, "kg", "libras"), - "6": ("libras → kg", libras_a_kg, "libras", "kg"), -} - - -def mostrar_menu(): - print("\n=== Conversor de Unidades ===") - for clave, (descripcion, _, _, _) in OPCIONES.items(): - print(f" {clave}. {descripcion}") - print(" 7. Salir") - - -def pedir_valor(unidad: str) -> float | None: - """Pide un número al usuario. Devuelve None si el input no es válido.""" - entrada = input(f"Ingresa el valor en {unidad}: ").strip() - try: - return float(entrada) - except ValueError: - print(f"Error: '{entrada}' no es un número válido.") - return None - - -def main(): - while True: - mostrar_menu() - opcion = input("\nElige una opción: ").strip() - - if opcion == "7": - print("¡Hasta luego!") - break - elif opcion in OPCIONES: - descripcion, funcion, unidad_entrada, unidad_salida = OPCIONES[opcion] - valor = pedir_valor(unidad_entrada) - if valor is not None: - resultado = funcion(valor) # Llamamos a la función pura - print(f"\n{valor} {unidad_entrada} = {resultado:.4f} {unidad_salida}") - else: - print("Opción no válida.") - - -if __name__ == "__main__": - main() diff --git a/ejemplos/general/07-calculadora-de-imc/README.md b/ejemplos/general/07-calculadora-de-imc/README.md deleted file mode 100644 index 4dd1676..0000000 --- a/ejemplos/general/07-calculadora-de-imc/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Múltiples Parámetros y Formateo de Strings - -**¿Qué muestra este ejemplo?** -Cómo crear funciones que aceptan múltiples parámetros y cómo usar `f-strings` para redondear y formatear salidas numéricas de forma amigable para el usuario. - -**¿Por qué es útil?** -Cuando necesitas hacer cálculos que dependen de varios factores (como el IMC que usa peso y estatura) y quieres mostrar el resultado con un límite de decimales para que no se vea desordenado. - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_formateo.py` - -**¿Qué retos usan esta base?** -- [#19 Python General 07 — Calculadora de IMC](https://github.com/Kaia-Alenia/nerve-community/issues/19) - -**Nota para principiantes** -Para redondear un número flotante a 2 decimales dentro de un f-string, simplemente agrega `:.2f` después del nombre de la variable (ej. `f"El resultado es {valor:.2f}"`). diff --git a/ejemplos/general/07-calculadora-de-imc/ejemplo_formateo.py b/ejemplos/general/07-calculadora-de-imc/ejemplo_formateo.py deleted file mode 100644 index dc741a1..0000000 --- a/ejemplos/general/07-calculadora-de-imc/ejemplo_formateo.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Ejemplo: Calculadora de IMC para mascotas con float(input), round() y f-strings - -Qué enseña este ejemplo: - - Cómo convertir el input del usuario (siempre string) a float con float() - - Cómo manejar el error si el usuario escribe letras en vez de números - - Cómo usar round() y f-strings con decimales específicos - - Cómo definir rangos numéricos y devolver una categoría - -Para tu reto (calculadora de IMC humano): - Aplica el mismo patrón: float(input()) para peso y altura, - cálculo matemático, y comparación de rangos para categorizar el resultado. - -Glosario de términos "raros": - float(valor) — convierte un string a número decimal. Lanza ValueError si falla. - round(num, n) — redondea 'num' a 'n' decimales. round(7.5678, 2) → 7.57 - f"{valor:.2f}" — formatea un número en un f-string con exactamente 2 decimales - f"{valor:.1f}" — 1 decimal; f"{valor:.4f}" → 4 decimales, etc. - ValueError — error que lanza Python cuando intentas convertir un texto - no numérico a int o float. Ej: float("hola") → ValueError - -Nota: El IMC de mascotas no es un estándar médico. -Este ejemplo es solo didáctico — usa datos inventados para no prescribir nada real. -""" - - -def calcular_imc_mascota(peso_kg: float, largo_cm: float) -> float: - """ - Calcula un "índice de masa" simplificado para mascotas. - Fórmula inventada solo para el ejemplo: peso / (largo en metros)^2 - """ - if largo_cm <= 0 or peso_kg <= 0: - raise ValueError("El peso y el largo deben ser valores positivos.") - largo_m = largo_cm / 100 - return peso_kg / (largo_m**2) - - -def categorizar_mascota(imc: float) -> str: - """ - Clasifica el resultado en rangos (inventados para el ejemplo). - """ - if imc < 15: - return "Muy liviano para su tamaño" - elif imc < 25: - return "Proporción saludable" - elif imc < 35: - return "Un poco pesado para su tamaño" - else: - return "Muy pesado para su tamaño" - - -def pedir_float(mensaje: str) -> float: - """ - Pide un número al usuario en loop hasta que ingrese algo válido. - Demuestra el patrón try/except para validar inputs numéricos. - """ - while True: - entrada = input(mensaje).strip() - try: - # float() intentará convertir el string a decimal - valor = float(entrada) - if valor <= 0: - print("El valor debe ser mayor a cero.") - continue - return valor - except ValueError: - # Si el usuario escribe "gato" o "12a", float() lanza ValueError - print(f"'{entrada}' no es un número válido. Intenta de nuevo.") - - -def main(): - print("=== Calculadora de proporción peso/tamaño para mascotas ===") - print("(Solo para práctica — no es un indicador médico veterinario real)\n") - - nombre = input("Nombre de tu mascota: ").strip() or "tu mascota" - - # Usamos nuestra función de input seguro que valida el tipo - peso = pedir_float("Peso en kg (ej: 4.5): ") - largo = pedir_float("Largo de hocico a cola en cm (ej: 60): ") - - try: - imc = calcular_imc_mascota(peso, largo) - categoria = categorizar_mascota(imc) - - # f-string con .2f → siempre muestra exactamente 2 decimales - print(f"\nResultado para {nombre}:") - print(f" Peso: {peso} kg | Largo: {largo} cm") - print(f" Índice calculado: {imc:.2f}") - print(f" Categoría: {categoria}") - - except ValueError as e: - print(f"Error en el cálculo: {e}") - - -if __name__ == "__main__": - main() diff --git a/ejemplos/general/08-organizador-de-archivos/README.md b/ejemplos/general/08-organizador-de-archivos/README.md deleted file mode 100644 index 6c6c5e8..0000000 --- a/ejemplos/general/08-organizador-de-archivos/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Manejo de Rutas (Pathlib) - -**¿Qué muestra este ejemplo?** -Cómo construir, validar y manipular rutas de archivos del sistema operativo usando el módulo moderno `pathlib` en lugar del antiguo `os.path`. - -**¿Por qué es útil?** -Hace que el código que manipula archivos y carpetas sea legible y que funcione exactamente igual en Windows, macOS y Linux sin preocuparse por la dirección de las barras (`/` vs `\`). - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_pathlib.py` - -**¿Qué retos usan esta base?** -- [#20 Python General 08 — Organizador automático de archivos](https://github.com/Kaia-Alenia/nerve-community/issues/20) - -**Nota para principiantes** -Con `pathlib.Path`, puedes unir rutas usando el operador de división `/` (ej. `ruta_base / "carpeta" / "archivo.txt"`), lo cual es mucho más limpio que usar `os.path.join()`. \ No newline at end of file diff --git a/ejemplos/general/08-organizador-de-archivos/ejemplo_pathlib.py b/ejemplos/general/08-organizador-de-archivos/ejemplo_pathlib.py deleted file mode 100644 index e5ba360..0000000 --- a/ejemplos/general/08-organizador-de-archivos/ejemplo_pathlib.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Ejemplo: Organizar archivos de prueba en subcarpetas según su extensión - -Qué enseña este ejemplo: - - Cómo usar pathlib.Path para manejar rutas de forma moderna - - Cómo listar todos los archivos de una carpeta con iterdir() - - Cómo leer la extensión de un archivo con .suffix - - Cómo mover archivos con shutil.move() - - Cómo crear subcarpetas si no existen con mkdir(parents=True, exist_ok=True) - -Para tu reto (organizador de archivos): - Aplica el mismo patrón: recibe una ruta por argumento, - itera los archivos, clasifica por extensión, y muévelos. - Agrega el modo --dry-run para simular sin mover nada. - -Glosario de términos "raros" (pathlib y shutil): - Path("ruta") — crea un objeto Path (más potente que un string de ruta) - Path.cwd() — devuelve la carpeta de trabajo actual - ruta.iterdir() — genera todos los archivos y carpetas dentro de 'ruta' - archivo.is_file() — True si es un archivo, False si es carpeta o symlink - archivo.suffix — la extensión: ".py", ".txt", ".jpg", etc. (con el punto) - archivo.name — nombre completo: "foto.jpg" - archivo.stem — nombre sin extensión: "foto" - ruta.mkdir(...) — crea la carpeta (parents=True crea carpetas intermedias) - (exist_ok=True no falla si ya existe) - -Glosario de shutil: - shutil.move(origen, destino) — mueve un archivo o carpeta de una ruta a otra -""" - -import shutil -from pathlib import Path - -# Mapa de extensiones → nombre de subcarpeta -EXTENSIONES = { - ".jpg": "Imagenes", - ".jpeg": "Imagenes", - ".png": "Imagenes", - ".gif": "Imagenes", - ".pdf": "Documentos", - ".docx": "Documentos", - ".txt": "Documentos", - ".mp3": "Audio", - ".wav": "Audio", - ".py": "Scripts", - ".js": "Scripts", -} - - -def organizar_carpeta(ruta_carpeta: str, dry_run: bool = False): - """ - Organiza los archivos de 'ruta_carpeta' en subcarpetas según extensión. - - dry_run=True → solo imprime qué haría, sin mover nada. - dry_run=False → ejecuta los movimientos realmente. - """ - carpeta = Path(ruta_carpeta) - - if not carpeta.exists() or not carpeta.is_dir(): - print(f"Error: '{ruta_carpeta}' no es una carpeta válida.") - return - - modo = "[SIMULACIÓN]" if dry_run else "[EJECUTANDO]" - print(f"\n{modo} Organizando: {carpeta}\n") - - movidos = 0 - ignorados = 0 - - # iterdir() genera todos los elementos dentro de la carpeta - for archivo in carpeta.iterdir(): - # Solo procesamos archivos (ignoramos subcarpetas) - if not archivo.is_file(): - continue - - # .suffix devuelve la extensión en minúsculas con el punto (ej: ".jpg") - extension = archivo.suffix.lower() - - if extension not in EXTENSIONES: - print(f" ⚠ Sin categoría: {archivo.name} (ignorado)") - ignorados += 1 - continue - - # Determinar la subcarpeta destino - nombre_subcarpeta = EXTENSIONES[extension] - carpeta_destino = carpeta / nombre_subcarpeta - - print(f" → {archivo.name} → {nombre_subcarpeta}/") - - if not dry_run: - # Crear la subcarpeta si no existe - carpeta_destino.mkdir(parents=True, exist_ok=True) - # Mover el archivo - shutil.move(str(archivo), carpeta_destino / archivo.name) - - movidos += 1 - - print( - f"\nResumen: {movidos} archivo(s) {'a mover' if dry_run else 'movidos'}, {ignorados} ignorados." - ) - - -if __name__ == "__main__": - import tempfile - import os - - # Crear carpeta temporal con archivos de ejemplo para demostrar - with tempfile.TemporaryDirectory() as tmp: - archivos_prueba = [ - "foto_verano.jpg", - "presentacion.pdf", - "notas.txt", - "cancion.mp3", - "script_util.py", - "archivo_raro.xyz", - ] - for nombre in archivos_prueba: - (Path(tmp) / nombre).touch() - - print("Archivos de prueba creados:") - for f in os.listdir(tmp): - print(f" {f}") - - # Primero simulamos (dry_run=True) - organizar_carpeta(tmp, dry_run=True) - - # Luego ejecutamos - print("\n--- Ahora ejecutamos realmente ---") - organizar_carpeta(tmp, dry_run=False) - - print("\nEstructura resultante:") - for item in sorted(Path(tmp).rglob("*")): - nivel = len(item.relative_to(tmp).parts) - 1 - print(" " * nivel + item.name) diff --git a/ejemplos/general/09-recordatorios-en-terminal/README.md b/ejemplos/general/09-recordatorios-en-terminal/README.md deleted file mode 100644 index 3fbd98f..0000000 --- a/ejemplos/general/09-recordatorios-en-terminal/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Fechas y Tiempo (Datetime) - -**¿Qué muestra este ejemplo?** -Cómo obtener la fecha actual, hacer sumas/restas de tiempo con `timedelta`, y formatear fechas a texto (o texto a fechas) usando el módulo `datetime`. - -**¿Por qué es útil?** -Es necesario para cualquier sistema que maneje expiraciones, agendas, cronómetros, logs de eventos o tareas programadas. - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_datetime.py` - -**¿Qué retos usan esta base?** -- [#21 Python General 09 — Recordatorios en terminal](https://github.com/Kaia-Alenia/nerve-community/issues/21) - -**Nota para principiantes** -Trabajar con zonas horarias (Timezones) en Python es complicado. Si puedes elegir, intenta guardar siempre tus fechas en formato UTC y conviértelas a la hora local solo al momento de mostrárselas al usuario. \ No newline at end of file diff --git a/ejemplos/general/09-recordatorios-en-terminal/ejemplo_datetime.py b/ejemplos/general/09-recordatorios-en-terminal/ejemplo_datetime.py deleted file mode 100644 index fe76883..0000000 --- a/ejemplos/general/09-recordatorios-en-terminal/ejemplo_datetime.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Ejemplo: Calcular días hasta un evento dado por el usuario con datetime - -Qué enseña este ejemplo: - - Cómo parsear una fecha dada por el usuario con strptime() - - Cómo calcular la diferencia entre dos fechas con timedelta - - Cómo comparar fechas para saber si ya pasaron - - Cómo revisar periódicamente (loop con time.sleep) - -Para tu reto (recordatorios en terminal): - Aplica el mismo patrón: guarda la fecha del recordatorio, - y en un loop compara datetime.now() con esa fecha. - Cuando now() >= fecha_recordatorio, muestra la alerta. - -Glosario de términos "raros": - datetime.datetime.now() — fecha y hora actual del sistema - datetime.datetime.strptime(texto, formato) — convierte un string a datetime - El formato usa códigos: - %Y = año con 4 dígitos (2025) - %m = mes con 2 dígitos (01-12) - %d = día con 2 dígitos (01-31) - %H = hora en formato 24h (00-23) - %M = minutos (00-59) - timedelta — representa una duración (días, horas, segundos) - timedelta(days=7) — 7 días - timedelta(hours=2, minutes=30) — 2 horas y 30 minutos - diferencia.days — días enteros en una diferencia de fechas - tiempo2 - tiempo1 — resta dos datetimes → devuelve un timedelta - time.sleep(segundos) — pausa el programa N segundos -""" - -import datetime -import time - - -def pedir_fecha_evento() -> datetime.datetime: - """ - Pide al usuario una fecha en formato YYYY-MM-DD HH:MM - y la convierte a un objeto datetime. - Repite hasta que el formato sea correcto. - """ - formato = "%Y-%m-%d %H:%M" - while True: - entrada = input("Fecha del evento (YYYY-MM-DD HH:MM): ").strip() - try: - # strptime: string parse time - # Convierte "2025-12-25 09:00" → datetime(2025, 12, 25, 9, 0) - fecha = datetime.datetime.strptime(entrada, formato) - return fecha - except ValueError: - print(f"Formato incorrecto. Usa: YYYY-MM-DD HH:MM (ej: 2025-12-25 09:00)") - - -def calcular_tiempo_restante(fecha_evento: datetime.datetime) -> str: - """ - Calcula cuánto tiempo falta (o pasó) desde ahora hasta el evento. - Devuelve un string descriptivo. - """ - ahora = datetime.datetime.now() - diferencia = fecha_evento - ahora - - if diferencia.total_seconds() < 0: - # El evento ya pasó — diferencia es negativa - diferencia_pasada = ahora - fecha_evento - dias = diferencia_pasada.days - horas = diferencia_pasada.seconds // 3600 - return f"Hace {dias} día(s) y {horas} hora(s)" - else: - dias = diferencia.days - horas = diferencia.seconds // 3600 - minutos = (diferencia.seconds % 3600) // 60 - return f"Faltan {dias} día(s), {horas} hora(s) y {minutos} minuto(s)" - - -def vigilar_evento( - nombre: str, fecha_evento: datetime.datetime, intervalo_seg: int = 5 -): - """ - Revisa periódicamente si el evento ya llegó. - Simula el loop de un sistema de recordatorios. - """ - print(f"\nVigilando el evento '{nombre}'...") - print("(Presiona Ctrl+C para detener)\n") - - try: - while True: - ahora = datetime.datetime.now() - tiempo_str = calcular_tiempo_restante(fecha_evento) - print(f" [{ahora.strftime('%H:%M:%S')}] {nombre}: {tiempo_str}") - - if ahora >= fecha_evento: - print(f"\n *** ¡RECORDATORIO! '{nombre}' es AHORA ***") - break - - time.sleep(intervalo_seg) - except KeyboardInterrupt: - print("\nVigilancia detenida.") - - -if __name__ == "__main__": - nombre = input("Nombre del evento: ").strip() or "Mi evento" - fecha = pedir_fecha_evento() - print( - f"\nEvento '{nombre}' programado para: {fecha.strftime('%d/%m/%Y a las %H:%M')}" - ) - print(calcular_tiempo_restante(fecha)) - - respuesta = input( - "\n¿Quieres que el programa vigile y avise cuando llegue? (s/n): " - ).lower() - if respuesta == "s": - vigilar_evento(nombre, fecha) diff --git a/ejemplos/general/10-analizador-de-texto/README.md b/ejemplos/general/10-analizador-de-texto/README.md deleted file mode 100644 index c436f18..0000000 --- a/ejemplos/general/10-analizador-de-texto/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Conteo de Frecuencias (Counter) - -**¿Qué muestra este ejemplo?** -Cómo usar la estructura de datos especializada `collections.Counter` para contar la frecuencia de elementos en un iterable (como palabras o letras) de manera eficiente. - -**¿Por qué es útil?** -Evita escribir bucles largos y anidados con diccionarios para llevar la cuenta de repeticiones y permite obtener rápidamente los elementos más comunes (top N). - -**Cómo correrlo** -- No necesita instalar nada extra, todo es librería estándar de Python. -- Comando para ejecutarlo: `python ejemplo_counter.py` - -**¿Qué retos usan esta base?** -- [#22 Python General 10 — Analizador de texto](https://github.com/Kaia-Alenia/nerve-community/issues/22) - -**Nota para principiantes** -`Counter` tiene un método interno muy útil llamado `.most_common(n)` que te devuelve automáticamente una lista ordenada con los `n` elementos más repetidos sin tener que ordenarla tú mismo. \ No newline at end of file diff --git a/ejemplos/general/10-analizador-de-texto/ejemplo_counter.py b/ejemplos/general/10-analizador-de-texto/ejemplo_counter.py deleted file mode 100644 index f532abe..0000000 --- a/ejemplos/general/10-analizador-de-texto/ejemplo_counter.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Ejemplo: Analizar un párrafo real — palabras únicas, frecuencia, estadísticas - -Qué enseña este ejemplo: - - Cómo limpiar y tokenizar texto con split() y lower() - - Cómo eliminar puntuación con str.translate() + string.punctuation - - Cómo usar Counter para contar frecuencias - - Cómo filtrar stopwords (palabras vacías) de una lista - - Cómo combinar todo para un análisis de texto realista - -Para tu reto (analizador de texto): - Aplica el mismo patrón para leer un archivo .txt, - y reportar: líneas, palabras, promedio por línea, - y las 10 palabras más frecuentes (sin stopwords). - -Glosario de términos "raros": - split() — divide un string por espacios y devuelve una lista de palabras - "hola mundo".split() → ["hola", "mundo"] - lower() — convierte todo a minúsculas (para comparar sin importar case) - str.translate() — reemplaza o elimina caracteres según una tabla de mapeo - str.maketrans() — construye la tabla para translate() - str.maketrans("", "", ".,!?") → elimina esos caracteres - Counter(lista) — crea un diccionario donde la clave es el elemento - y el valor es cuántas veces aparece - .most_common(n) — devuelve los N elementos más frecuentes como lista de tuplas - set() — colección sin duplicados; útil para palabras únicas -""" - -import string -from collections import Counter - -# Palabras vacías en español (no aportan significado, no queremos contarlas) -STOPWORDS = { - "el", - "la", - "los", - "las", - "un", - "una", - "unos", - "unas", - "de", - "en", - "a", - "y", - "o", - "que", - "con", - "por", - "para", - "del", - "al", - "se", - "es", - "son", - "su", - "sus", - "lo", - "le", - "más", - "pero", - "como", - "si", - "no", - "ya", - "fue", - "era", - "hay", - "ser", - "me", - "te", - "nos", - "les", -} - - -def limpiar_palabra(palabra: str) -> str: - """ - Convierte a minúsculas y elimina puntuación de una palabra. - str.maketrans("", "", chars) crea una tabla que borra todos los chars. - """ - tabla = str.maketrans("", "", string.punctuation + "¡¿«»") - return palabra.lower().translate(tabla) - - -def analizar_texto(texto: str) -> dict: - """ - Analiza el texto y devuelve estadísticas en un diccionario. - """ - # 1. Separar en palabras y limpiar cada una - palabras_crudas = texto.split() - palabras_limpias = [limpiar_palabra(p) for p in palabras_crudas] - - # 2. Filtrar palabras vacías y palabras de 1 carácter - palabras_validas = [ - p for p in palabras_limpias if p and len(p) > 1 and p not in STOPWORDS - ] - - # 3. Contar frecuencias con Counter - conteo = Counter(palabras_validas) - - # 4. Palabras únicas (sin repetir) - unicas = set(palabras_validas) - - return { - "total_palabras": len(palabras_crudas), - "palabras_unicas": len(unicas), - "palabras_validas": len(palabras_validas), - "top_5": conteo.most_common(5), - "mas_frecuente": conteo.most_common(1)[0] if conteo else None, - } - - -def imprimir_reporte(stats: dict): - print("\n--- Reporte de Análisis de Texto ---") - print(f" Total de palabras: {stats['total_palabras']}") - print(f" Palabras únicas: {stats['palabras_unicas']}") - print(f" (Excluyendo stopwords y puntuación)") - - if stats["mas_frecuente"]: - palabra, veces = stats["mas_frecuente"] - print(f" Palabra más frecuente: '{palabra}' ({veces} veces)") - - print("\n Top 5 palabras más usadas:") - for palabra, veces in stats["top_5"]: - barra = "█" * veces - print(f" {palabra:<15} {barra} ({veces})") - - -if __name__ == "__main__": - TEXTO_EJEMPLO = """ - Python es un lenguaje de programación poderoso y fácil de aprender. - Python tiene estructuras de datos eficientes y un enfoque simple pero efectivo - hacia la programación orientada a objetos. La elegante sintaxis de Python y su - tipado dinámico, junto con su naturaleza interpretada, hacen de Python un - lenguaje ideal para scripting y desarrollo rápido de aplicaciones en muchas - áreas y en muchas plataformas distintas. - """ - - print("Texto a analizar:") - print(TEXTO_EJEMPLO) - - stats = analizar_texto(TEXTO_EJEMPLO) - imprimir_reporte(stats) diff --git a/ejemplos/general/11-decoradores/README.md b/ejemplos/general/11-decoradores/README.md deleted file mode 100644 index bc8a23e..0000000 --- a/ejemplos/general/11-decoradores/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Decoradores Básicos - -## ¿Qué muestra este ejemplo? -Muestra cómo crear un decorador sencillo (`@log_ejecucion`) utilizando `functools.wraps` para añadir un comportamiento (imprimir mensajes de inicio y fin) alrededor de una función existente sin modificar su código interno. - -## ¿Por qué es útil? -Los decoradores son una característica poderosa de Python para reutilizar código. Permiten extraer lógica repetitiva (como registros, validaciones, o comprobaciones de acceso) y aplicarla limpiamente a múltiples funciones con solo agregar un `@nombre_decorador` arriba de su definición. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_decorador.py -``` - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #25 en GitHub: Python General 11 - Profiler de Funciones](https://github.com/Kaia-Alenia/nerve-community/issues/25)** - -## Nota para principiantes -Recuerda siempre importar `functools` y usar `@functools.wraps(funcion)` dentro de tu decorador. Si no lo haces, la función perderá su nombre original (`__name__`) y su docstring, lo cual dificulta la depuración. diff --git a/ejemplos/general/11-decoradores/ejemplo_decorador.py b/ejemplos/general/11-decoradores/ejemplo_decorador.py deleted file mode 100644 index 6e441e8..0000000 --- a/ejemplos/general/11-decoradores/ejemplo_decorador.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Ejemplo: Decorador @medir_tiempo con functools.wraps - -Qué enseña este ejemplo: - - La anatomía completa de un decorador: función externa + wrapper - - Para qué sirve @functools.wraps y qué pasa si lo omites - - Cómo medir tiempo de ejecución con time.perf_counter() - - Cómo hacer un decorador que funcione con CUALQUIER función (*args, **kwargs) - -Para tu reto (profiler de funciones): - Este ejemplo ya es muy similar al reto. Tu variación debe: - - Usar time.perf_counter() (más preciso que time.time() para medición) - - Manejar funciones con argumentos arbitrarios (*args, **kwargs) - - Devolver el valor de retorno de la función original - -Glosario: - functools.wraps(funcion) — un decorador para decoradores. - Sin él, la función decorada pierde su nombre (__name__) y su docstring. - Con él, el wrapper "pretende" ser la función original. - Ejemplo sin wraps: saludar.__name__ → "wrapper" (MALO) - Ejemplo con wraps: saludar.__name__ → "saludar" (CORRECTO) - - *args — captura todos los argumentos posicionales como una tupla - **kwargs — captura todos los argumentos nombrados como un diccionario - Usarlos en el wrapper garantiza que el decorador funcione con CUALQUIER función. - - time.perf_counter() — reloj de alta resolución para medir duración - NO usar time.time() para benchmarks (menos preciso) -""" - -import functools -import time - - -def medir_tiempo(funcion): - """ - Decorador que mide cuántos segundos tarda en ejecutarse la función. - Se usa escribiendo @medir_tiempo justo antes de def. - """ - - @functools.wraps(funcion) - def wrapper(*args, **kwargs): - inicio = time.perf_counter() - - # Llamar a la función original con todos sus argumentos - resultado = funcion(*args, **kwargs) - - fin = time.perf_counter() - duracion = fin - inicio - - # Imprimimos el tiempo, pero devolvemos el resultado original - print(f" ⏱ '{funcion.__name__}' tardó {duracion:.6f} segundos") - return resultado - - return wrapper - - -# --- Aplicando el decorador --- -@medir_tiempo -def sumar_lista(numeros: list) -> float: - """Suma todos los números de una lista.""" - return sum(numeros) - - -@medir_tiempo -def buscar_en_lista(lista: list, objetivo) -> bool: - """Busca un elemento en una lista con iteración lineal (O(n)).""" - for item in lista: - if item == objetivo: - return True - return False - - -@medir_tiempo -def simular_trabajo_pesado(iteraciones: int) -> int: - """Simula trabajo computacional con un loop.""" - total = 0 - for i in range(iteraciones): - total += i * i - return total - - -if __name__ == "__main__": - print("=== Demo del decorador @medir_tiempo ===\n") - - # Demostración: las funciones se ven iguales, pero el tiempo se imprime solo - numeros = list(range(1_000_000)) - - print("1. Sumar 1 millón de números:") - resultado = sumar_lista(numeros) - print(f" Resultado: {resultado:,}\n") - - print("2. Buscar el último elemento (peor caso):") - encontrado = buscar_en_lista(numeros, 999_999) - print(f" Encontrado: {encontrado}\n") - - print("3. Trabajo pesado con 500,000 iteraciones:") - resultado = simular_trabajo_pesado(500_000) - print(f" Resultado: {resultado:,}\n") - - # Verificar que @functools.wraps preservó el nombre y docstring - print("--- Verificación de @functools.wraps ---") - print(f"sumar_lista.__name__ = '{sumar_lista.__name__}'") - print(f"sumar_lista.__doc__ = '{sumar_lista.__doc__}'") diff --git a/ejemplos/general/12-regex/README.md b/ejemplos/general/12-regex/README.md deleted file mode 100644 index 8324807..0000000 --- a/ejemplos/general/12-regex/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Expresiones Regulares (Regex) - -## ¿Qué muestra este ejemplo? -Muestra cómo usar el módulo `re` de Python y expresiones regulares (regex) para validar que un número de teléfono mexicano tenga un formato estricto (`+52 XXXXXXXXXX`). - -## ¿Por qué es útil? -Validar entradas de usuario es una de las tareas más comunes en la programación. Usar expresiones regulares te permite describir formatos muy específicos de texto en una sola línea de código, evitando tener que hacer múltiples condicionales y ciclos manuales. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_regex.py -``` - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #26 en GitHub: Python General 12 - Validador de Emails con Regex](https://github.com/Kaia-Alenia/nerve-community/issues/26)** - -## Nota para principiantes -Los strings de las expresiones regulares suelen llevar una `r` antes de las comillas (ej. `r'^\+52'`). Esto indica que es un "raw string", evitando que Python interprete caracteres como `\n` o `\t` de forma especial, dejándolos intactos para que el motor regex los procese. diff --git a/ejemplos/general/12-regex/ejemplo_regex.py b/ejemplos/general/12-regex/ejemplo_regex.py deleted file mode 100644 index 93d9bfc..0000000 --- a/ejemplos/general/12-regex/ejemplo_regex.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Ejemplo: Validar emails con regex — patrón paso a paso - -Qué enseña este ejemplo: - - Cómo leer y construir un patrón de regex explicando cada parte - - re.match() vs re.search() vs re.fullmatch() — diferencias clave - - Cómo usar grupos de captura () para extraer partes del texto - - Casos de prueba que demuestran qué acepta y qué rechaza el patrón - -Para tu reto (validador de emails): - Este ejemplo ya es muy similar. Tu variación debe: - - Crear una función es_email_valido(texto) que devuelva True/False - - Incluir al menos 5 casos de prueba válidos y 5 inválidos - - Explicar en comentarios qué valida cada parte del patrón - -Glosario de regex: - ^ — inicio del string (sin esto, puede haber texto antes del patrón) - $ — fin del string (sin esto, puede haber texto extra al final) - . — cualquier carácter (excepto salto de línea) - \\. — un punto LITERAL (el \\ escapa el significado especial de .) - [a-z] — cualquier letra minúscula de la 'a' a la 'z' - [a-zA-Z0-9]— cualquier letra (mayúscula o minúscula) o dígito - [^@\\s] — cualquier carácter que NO sea @ ni espacio (^ dentro de [] niega) - + — 1 o más del elemento anterior - * — 0 o más del elemento anterior - {n} — exactamente n repeticiones - {n,m} — entre n y m repeticiones - () — grupo de captura — puedes extraer su contenido con .group(1) - - re.match() — busca solo al INICIO del string - re.search() — busca en CUALQUIER parte del string - re.fullmatch() — el patrón debe cubrir TODO el string (equivale a ^...$) -""" - -import re - - -def es_email_valido(texto: str) -> bool: - """ - Valida si un string tiene formato de email. - No es RFC-compliant (eso es casi imposible), pero rechaza los casos - más comunes de emails inválidos. - - Patrón desglosado: - ^ → inicio del string - [^@\\s]+ → usuario: 1+ caracteres que no sean @ ni espacio - @ → el símbolo @ obligatorio - [^@\\s]+ → dominio: 1+ caracteres que no sean @ ni espacio - \\. → un punto literal (necesario: gmail.com, not gmailcom) - [a-zA-Z]{2,}→ extensión: al menos 2 letras (com, mx, org, io, etc.) - $ → fin del string - """ - patron = r"^[^@\s]+@[^@\s]+\.[a-zA-Z]{2,}$" - return bool(re.fullmatch(patron, texto)) - - -def extraer_partes_email(email: str) -> dict | None: - """ - Si el email es válido, extrae usuario, dominio y extensión por separado - usando grupos de captura (). - """ - patron = r"^([^@\s]+)@([^@\s]+)\.([a-zA-Z]{2,})$" - match = re.fullmatch(patron, email) - - if match: - # .group(0) → todo el match; .group(1) → primer (), etc. - return { - "usuario": match.group(1), - "dominio": match.group(2), - "extension": match.group(3), - } - return None - - -if __name__ == "__main__": - print("=== Validador de Emails ===\n") - - casos = [ - # Válidos - ("usuario@gmail.com", True), - ("nombre.apellido@empresa.mx", True), - ("user+tag@mail.io", True), - ("contacto@sub.dominio.org", True), - ("a@b.co", True), - # Inválidos - ("sin-arroba-punto", False), - ("@sinusuario.com", False), - ("usuario@sincuenta", False), - ("usuario@.com", False), - ("usuario @gmail.com", False), # espacio - ("doble@@arroba.com", False), - ("", False), - ] - - validos_ok = 0 - total = len(casos) - - for email, esperado in casos: - resultado = es_email_valido(email) - icono = "✓" if resultado == esperado else "✗ ERROR" - estado = "Válido" if resultado else "Inválido" - print(f" [{icono}] {estado:8} | '{email}'") - if resultado == esperado: - validos_ok += 1 - - print(f"\n{validos_ok}/{total} casos se comportaron como se esperaba.\n") - - # Demo de extracción con grupos - email_demo = "john.doe@example.com" - partes = extraer_partes_email(email_demo) - if partes: - print(f"Partes de '{email_demo}':") - print(f" Usuario: {partes['usuario']}") - print(f" Dominio: {partes['dominio']}") - print(f" Extensión: {partes['extension']}") diff --git a/ejemplos/general/13-generadores/README.md b/ejemplos/general/13-generadores/README.md deleted file mode 100644 index ecf53fc..0000000 --- a/ejemplos/general/13-generadores/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Generadores en Python - -## ¿Qué muestra este ejemplo? -Muestra cómo crear una función generadora usando la palabra clave `yield` para producir una secuencia de números de la serie de Fibonacci, bajo demanda. - -## ¿Por qué es útil? -Los generadores permiten procesar o crear secuencias enormes de datos de forma perezosa (lazy). En lugar de calcular y almacenar 1 millón de elementos en una lista (lo que agotaría la memoria RAM), un generador calcula y entrega solo el elemento que necesitas en ese exacto instante. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_generadores.py -``` - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #27 en GitHub: Python General 13 - Lector de Archivos Gigantes (Generadores)](https://github.com/Kaia-Alenia/nerve-community/issues/27)** - -## Nota para principiantes -Cuando una función tiene `yield` en lugar de `return`, Python sabe que es un generador. Al llamarla, no ejecuta el código inmediatamente, sino que devuelve un objeto iterador. El código solo avanza hasta el siguiente `yield` cada vez que usas un ciclo `for` o la función `next()`. diff --git a/ejemplos/general/13-generadores/ejemplo_generadores.py b/ejemplos/general/13-generadores/ejemplo_generadores.py deleted file mode 100644 index 4c2def5..0000000 --- a/ejemplos/general/13-generadores/ejemplo_generadores.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -Ejemplo: Leer un archivo grande línea a línea con yield (generadores) - -Qué enseña este ejemplo: - - Qué es un generador y por qué ahorra memoria en archivos grandes - - La diferencia entre yield y return - - Cómo crear un archivo de prueba grande y procesarlo sin cargarlo completo - - Cómo usar next() y for directamente sobre un generador - -Para tu reto (lector de archivos gigantes): - Aplica el mismo patrón: una función generadora que haga yield línea a línea. - Luego usa el generador para procesar (contar, buscar, filtrar) sin listas. - -Glosario: - yield — como return, pero pausa la función en vez de terminarla. - La próxima vez que se pida un valor, continúa desde donde pausó. - Convierte automáticamente la función en un generador. - - generador — objeto que produce valores uno a uno, bajo demanda. - No calcula todos los valores de una vez (perezoso = lazy). - Ventaja: si el archivo tiene 10 GB, solo una línea ocupa - memoria a la vez. - - next(gen) — pide el siguiente valor del generador. Lanza StopIteration - cuando se agotan los valores. - - for x in gen — forma más cómoda de consumir un generador (maneja StopIteration) - - Diferencia clave: - Con lista: lineas = archivo.readlines() → carga TODO en RAM - Con yield: for linea in leer_lineas(archivo) → una línea a la vez en RAM -""" - -from pathlib import Path - - -def leer_lineas(ruta_archivo: str): - """ - Generador que entrega una línea a la vez del archivo. - El archivo nunca se carga completo en memoria. - """ - ruta = Path(ruta_archivo) - if not ruta.exists(): - print(f"Error: '{ruta_archivo}' no existe.") - return # En un generador, return sin valor termina la iteración - - with open(ruta, "r", encoding="utf-8") as f: - for linea in f: - # yield pausa aquí y entrega la línea al que llamó al generador - # La próxima vez que se pida un valor, el loop continúa - yield linea.rstrip("\n") # eliminamos el salto de línea - - -def contar_lineas(ruta_archivo: str) -> int: - """Cuenta líneas sin cargar el archivo completo.""" - return sum(1 for _ in leer_lineas(ruta_archivo)) - - -def buscar_palabra(ruta_archivo: str, palabra: str) -> list: - """ - Devuelve los números de línea donde aparece la palabra. - Solo procesa una línea a la vez gracias al generador. - """ - resultados = [] - for numero, linea in enumerate(leer_lineas(ruta_archivo), start=1): - if palabra.lower() in linea.lower(): - resultados.append((numero, linea)) - return resultados - - -def crear_archivo_prueba(ruta: str, num_lineas: int = 100_000): - """Crea un archivo grande para probar el generador.""" - with open(ruta, "w", encoding="utf-8") as f: - for i in range(1, num_lineas + 1): - if i % 5_000 == 0: - f.write(f"Línea especial número {i} — contiene la palabra CLAVE\n") - else: - f.write(f"Esta es la línea número {i} del archivo de prueba\n") - print(f"✓ Archivo creado: {ruta} ({num_lineas:,} líneas)") - - -if __name__ == "__main__": - ARCHIVO = "archivo_grande.txt" - - # Crear el archivo de prueba - crear_archivo_prueba(ARCHIVO, num_lineas=100_000) - - # Contar sin cargar todo en memoria - total = contar_lineas(ARCHIVO) - print(f"Total de líneas: {total:,}\n") - - # Buscar las líneas que contienen "CLAVE" - print("Buscando líneas con 'CLAVE'...") - encontradas = buscar_palabra(ARCHIVO, "CLAVE") - print(f"Se encontraron {len(encontradas)} coincidencias:") - for num, linea in encontradas[:3]: # Mostramos solo las primeras 3 - print(f" Línea {num:,}: {linea}") - - # Demostrar next() manualmente - print("\nUsando next() manualmente en el generador:") - gen = leer_lineas(ARCHIVO) - print(f" 1er valor: {next(gen)}") - print(f" 2do valor: {next(gen)}") - print(f" 3er valor: {next(gen)}") - - # Limpiar - Path(ARCHIVO).unlink() - print(f"\n✓ Archivo '{ARCHIVO}' eliminado.") diff --git a/ejemplos/general/14-argparse/README.md b/ejemplos/general/14-argparse/README.md deleted file mode 100644 index 23c2148..0000000 --- a/ejemplos/general/14-argparse/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Interfaz de Línea de Comandos (CLI) con argparse - -## ¿Qué muestra este ejemplo? -Muestra cómo utilizar la librería estándar `argparse` para crear una mini-calculadora por línea de comandos que acepta argumentos como `--operacion`, `--a` y `--b`, validando sus tipos. - -## ¿Por qué es útil? -Scripts profesionales de Python rara vez utilizan `input()` para pedir datos de forma interactiva. Usar argumentos por terminal permite automatizar herramientas y usarlas en tuberías (pipelines) sin requerir intervención humana, y proveen ayuda (`--help`) de forma automática. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_argparse.py --operacion suma --a 10 --b 5.5 -python ejemplo_argparse.py --operacion multiplicacion --a 4 --b 2 -``` - -Puedes ver el menú de ayuda integrado corriendo: -```bash -python ejemplo_argparse.py --help -``` - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #28 en GitHub: Python General 14 - CLI del Clima (APIs y argparse)](https://github.com/Kaia-Alenia/nerve-community/issues/28)** - -## Nota para principiantes -Nota cómo definimos los tipos en `add_argument(..., type=float)`. `argparse` se encarga de convertir automáticamente el texto que introduces en la terminal al tipo de dato que programaste, e incluso lanzará un error limpio si el usuario pasa letras en vez de números. diff --git a/ejemplos/general/14-argparse/ejemplo_argparse.py b/ejemplos/general/14-argparse/ejemplo_argparse.py deleted file mode 100644 index 41ea31d..0000000 --- a/ejemplos/general/14-argparse/ejemplo_argparse.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Ejemplo: CLI para consultar días festivos con argparse, subcomandos y args opcionales - -Qué enseña este ejemplo: - - Cómo estructurar una CLI con subcomandos (como git commit, git push) - - Cómo agregar argumentos opcionales con -- (ej: --año, --pais) - - Cómo usar type= para convertir el input automáticamente - - Cómo leer args.subcomando y ejecutar lógica según el subcomando - -Para tu reto (CLI del clima): - Aplica el mismo patrón: - python clima.py actual --ciudad "Monterrey" - python clima.py pronostico --ciudad "CDMX" --dias 5 - Usa argparse para parsear --ciudad y luego llama a la API. - -Glosario de argparse: - ArgumentParser(description=) — crea el parser con descripción del --help - add_argument("nombre") — argumento POSICIONAL (obligatorio) - add_argument("--nombre") — argumento OPCIONAL (con guiones) - required=True — hace que un argumento --nombre sea obligatorio - type=int / type=float — convierte el input a ese tipo automáticamente - default=valor — valor si el usuario no pasa el argumento - choices=["a","b"] — solo permite esos valores exactos - help="texto" — descripción en el --help - add_subparsers() — crea un gestor de subcomandos - subparsers.add_parser("cmd") — define un subcomando - args = parser.parse_args() — procesa los argumentos del usuario - args.nombre — accede al valor de --nombre -""" - -import argparse - -# Base de datos de festivos (simplificada para el ejemplo) -FESTIVOS = { - "mx": { - 2025: [ - "2025-01-01: Año Nuevo", - "2025-02-03: Día de la Constitución", - "2025-03-17: Natalicio de Benito Juárez", - "2025-05-01: Día del Trabajo", - "2025-09-16: Día de la Independencia", - "2025-11-17: Día de la Revolución", - "2025-12-25: Navidad", - ] - }, - "co": { - 2025: [ - "2025-01-01: Año Nuevo", - "2025-01-06: Reyes Magos", - "2025-05-01: Día del Trabajo", - "2025-07-20: Día de la Independencia", - "2025-08-07: Batalla de Boyacá", - "2025-12-25: Navidad", - ] - }, -} - - -def cmd_listar(args): - """Subcomando: listar días festivos de un país y año.""" - pais = args.pais.lower() - año = args.año - - if pais not in FESTIVOS: - print(f"Error: País '{pais}' no disponible. Opciones: {list(FESTIVOS.keys())}") - return - - if año not in FESTIVOS[pais]: - print(f"No hay datos para {pais.upper()} en {año}.") - return - - print(f"\n🗓 Festivos de {pais.upper()} — {año}:") - for festivo in FESTIVOS[pais][año]: - print(f" • {festivo}") - print(f"\nTotal: {len(FESTIVOS[pais][año])} días festivos") - - -def cmd_contar(args): - """Subcomando: contar festivos (sin mostrarlos).""" - pais = args.pais.lower() - año = args.año - - if pais not in FESTIVOS or año not in FESTIVOS[pais]: - print(f"No hay datos para {pais.upper()} {año}.") - return - - total = len(FESTIVOS[pais][año]) - print(f"{pais.upper()} tiene {total} días festivos en {año}.") - - -def main(): - # 1. Parser principal - parser = argparse.ArgumentParser( - description="Consulta días festivos por país y año.", - epilog="Ejemplo de uso: python festivos.py listar --pais mx --año 2025", - ) - - # 2. Gestor de subcomandos - subparsers = parser.add_subparsers( - dest="subcomando", - help="Subcomando a ejecutar", - ) - - # --- Subcomando: listar --- - sp_listar = subparsers.add_parser("listar", help="Lista todos los festivos") - sp_listar.add_argument("--pais", default="mx", help="Código del país (default: mx)") - sp_listar.add_argument("--año", type=int, default=2025, help="Año a consultar") - - # --- Subcomando: contar --- - sp_contar = subparsers.add_parser("contar", help="Cuenta cuántos festivos hay") - sp_contar.add_argument("--pais", default="mx") - sp_contar.add_argument("--año", type=int, default=2025) - - # 3. Parsear los argumentos - args = parser.parse_args() - - # 4. Ejecutar el subcomando correcto - if args.subcomando == "listar": - cmd_listar(args) - elif args.subcomando == "contar": - cmd_contar(args) - else: - # Si no pasan subcomando, mostrar ayuda - parser.print_help() - - -if __name__ == "__main__": - main() - -# Ejemplos de uso en terminal: -# python festivos.py listar -# python festivos.py listar --pais co --año 2025 -# python festivos.py contar --pais mx -# python festivos.py --help -# python festivos.py listar --help diff --git a/ejemplos/general/15-shutil/README.md b/ejemplos/general/15-shutil/README.md deleted file mode 100644 index 60bf460..0000000 --- a/ejemplos/general/15-shutil/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Operaciones de Archivos con shutil - -## ¿Qué muestra este ejemplo? -Muestra cómo usar la librería estándar `shutil` (utilidades de shell) para realizar operaciones de alto nivel sobre archivos y directorios, específicamente cómo empaquetar una carpeta completa en un archivo `.zip` usando `make_archive`. - -## ¿Por qué es útil? -Mientras que la librería `os` o `pathlib` te sirven para interactuar con un solo archivo (leerlo, borrarlo), `shutil` está diseñado para operaciones masivas: copiar árboles completos de directorios, mover carpetas de un disco a otro, o comprimir proyectos enteros en una sola línea. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_shutil.py -``` -Al finalizar, verás un nuevo archivo llamado `mi_backup.zip`. - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #29 en GitHub: Python General 15 - Herramienta de Backup de Archivos](https://github.com/Kaia-Alenia/nerve-community/issues/29)** - -## Nota para principiantes -El módulo `shutil` tiene muchas funciones útiles como `shutil.copy()` y `shutil.move()`. Son las equivalentes a usar los comandos `cp` y `mv` en una terminal de Linux o Mac, pero directamente desde Python. diff --git a/ejemplos/general/15-shutil/ejemplo_shutil.py b/ejemplos/general/15-shutil/ejemplo_shutil.py deleted file mode 100644 index 6916312..0000000 --- a/ejemplos/general/15-shutil/ejemplo_shutil.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Ejemplo: Comprimir carpetas con filtro usando shutil.make_archive + pathlib - -Qué enseña este ejemplo: - - Cómo comprimir una carpeta completa con shutil.make_archive() - - Cómo copiar solo archivos que cumplan un criterio (filtro por extensión) - - Cómo usar pathlib.rglob() para buscar archivos de forma recursiva - - Cómo dar nombre al backup con la fecha actual - -Para tu reto (herramienta de backup): - Aplica el mismo patrón: shutil.copy2() para copiar archivos con metadatos, - y agrega la fecha al nombre para no sobreescribir backups anteriores. - Usa pathlib para manejar las rutas. - -Glosario: - shutil.make_archive(nombre, formato, carpeta) - → Comprime 'carpeta' en un archivo 'nombre.formato' - → Formatos disponibles: "zip", "tar", "gztar" (tar.gz), "bztar", "xztar" - → Devuelve la ruta del archivo creado - - shutil.copy2(origen, destino) - → Copia un archivo preservando metadatos (fecha de modificación, permisos) - → shutil.copy() solo copia contenido; copy2() también copia metadatos - - pathlib.rglob("*.ext") - → Busca archivos recursivamente en TODAS las subcarpetas - → rglob("*") → todos los archivos y carpetas - → rglob("*.py") → solo archivos .py en cualquier profundidad - → glob("*.py") → solo en la carpeta actual (no recursivo) - - datetime.now().strftime("%Y-%m-%d") - → Devuelve la fecha de hoy como string: "2025-07-17" - → Útil para nombrar backups: "backup_2025-07-17.zip" -""" - -import shutil -import datetime -from pathlib import Path - - -def hacer_backup_completo(carpeta_origen: str, carpeta_destino: str) -> str: - """ - Comprime toda la carpeta origen en un .zip con fecha en el nombre. - Devuelve la ruta del archivo creado. - """ - origen = Path(carpeta_origen) - destino = Path(carpeta_destino) - - if not origen.exists(): - raise FileNotFoundError(f"No existe la carpeta: {carpeta_origen}") - - # Crear la carpeta destino si no existe - destino.mkdir(parents=True, exist_ok=True) - - # Nombre del backup con fecha: "documentos_2025-07-17" - fecha = datetime.datetime.now().strftime("%Y-%m-%d") - nombre_backup = f"{origen.name}_{fecha}" - ruta_salida = destino / nombre_backup - - print(f"Comprimiendo '{origen}' → '{ruta_salida}.zip'...") - - # shutil.make_archive devuelve la ruta del archivo creado (con extensión) - archivo_creado = shutil.make_archive( - str(ruta_salida), # nombre de salida (sin extensión) - "zip", # formato de compresión - str(origen), # carpeta a comprimir - ) - - tamaño_kb = Path(archivo_creado).stat().st_size / 1024 - print(f"✓ Backup creado: {archivo_creado} ({tamaño_kb:.1f} KB)") - return archivo_creado - - -def hacer_backup_filtrado(carpeta_origen: str, carpeta_destino: str, extensiones: list): - """ - Copia solo los archivos con las extensiones indicadas, - respetando la estructura de subcarpetas. - - rglob() permite buscar de forma recursiva en todas las subcarpetas. - """ - origen = Path(carpeta_origen) - destino = Path(carpeta_destino) - - if not origen.exists(): - raise FileNotFoundError(f"No existe la carpeta: {carpeta_origen}") - - destino.mkdir(parents=True, exist_ok=True) - copiados = 0 - - # rglob("*") → busca TODOS los archivos en origen y subcarpetas - for archivo in origen.rglob("*"): - if not archivo.is_file(): - continue - - # Filtrar por extensión - if archivo.suffix.lower() not in extensiones: - continue - - # Reconstruir la ruta relativa para mantener estructura de carpetas - ruta_relativa = archivo.relative_to(origen) - archivo_destino = destino / ruta_relativa - - # Crear subcarpetas intermedias si son necesarias - archivo_destino.parent.mkdir(parents=True, exist_ok=True) - - # copy2 preserva fecha de modificación y permisos (más completo que copy) - shutil.copy2(archivo, archivo_destino) - print(f" ✓ {ruta_relativa}") - copiados += 1 - - print(f"\nBackup filtrado completado: {copiados} archivo(s) copiado(s)") - - -if __name__ == "__main__": - import tempfile - import os - - # Crear carpeta de prueba con archivos de distintos tipos - with tempfile.TemporaryDirectory() as tmp_origen: - origen = Path(tmp_origen) - - # Crear estructura de ejemplo - (origen / "docs").mkdir() - (origen / "scripts").mkdir() - (origen / "README.md").write_text("# Mi Proyecto") - (origen / "docs" / "manual.pdf").write_bytes(b"contenido pdf") - (origen / "docs" / "notas.txt").write_text("Notas importantes") - (origen / "scripts" / "util.py").write_text("print('hola')") - (origen / "imagen.jpg").write_bytes(b"datos imagen") - - print("Archivos de prueba:") - for f in origen.rglob("*"): - if f.is_file(): - print(f" {f.relative_to(origen)}") - - with tempfile.TemporaryDirectory() as tmp_destino: - # Demo 1: backup completo en zip - print("\n--- Demo 1: Backup completo (.zip) ---") - hacer_backup_completo(tmp_origen, tmp_destino) - - # Demo 2: backup filtrado (solo .txt y .md) - print("\n--- Demo 2: Solo archivos .txt y .md ---") - destino_filtrado = os.path.join(tmp_destino, "filtrado") - hacer_backup_filtrado(tmp_origen, destino_filtrado, [".txt", ".md"]) diff --git a/ejemplos/general/16-excepciones/README.md b/ejemplos/general/16-excepciones/README.md deleted file mode 100644 index aad447f..0000000 --- a/ejemplos/general/16-excepciones/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Excepciones Personalizadas - -## ¿Qué muestra este ejemplo? -Muestra cómo crear una excepción propia heredando de la clase base `Exception` en Python, y cómo levantarla (`raise`) y atraparla (`except`) en un bloque `try/except`. - -## ¿Por qué es útil? -A veces los errores genéricos de Python (`ValueError`, `TypeError`) no describen bien el problema en la lógica de tu negocio. Crear tus propias excepciones (ej. `UsuarioNoEncontradoError`, `ConexionPerdidaError`) hace que tu código sea mucho más legible, y permite que otros programadores atrapen exactamente el error que diseñaste sin confundirlo con errores generales. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_excepciones.py -``` - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #30 en GitHub: Python General 16 - Validador de Edad (Excepciones Personalizadas)](https://github.com/Kaia-Alenia/nerve-community/issues/30)** - -## Nota para principiantes -Crear una excepción personalizada es extremadamente sencillo: solo creas una clase nueva, la haces heredar de `Exception`, y pones `pass` adentro. Python se encarga de que todo lo demás funcione (como pasarle un mensaje de error). diff --git a/ejemplos/general/16-excepciones/ejemplo_excepciones.py b/ejemplos/general/16-excepciones/ejemplo_excepciones.py deleted file mode 100644 index 233c511..0000000 --- a/ejemplos/general/16-excepciones/ejemplo_excepciones.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -Ejemplo: Excepciones personalizadas con herencia — validación de edad - -Qué enseña este ejemplo: - - Cómo crear excepciones propias heredando de Exception - - Por qué crear excepciones específicas en vez de usar ValueError genérico - - Cómo usar raise para lanzar una excepción manualmente - - Cómo capturar excepciones específicas con except en orden de más a menos específico - -Para tu reto (validador de edad): - Este ejemplo es directamente aplicable. Crea tus excepciones - EdadNegativaError y EdadNoNumericaError heredando de Exception, - y lánzalas con raise en la función que valida la edad. - -Glosario: - class MiError(Exception) — crea una excepción personalizada. - Exception es la clase base de todos los errores en Python. - Al heredar de ella, tu clase "es" un error que Python puede capturar. - - raise MiError("mensaje") — lanza la excepción manualmente. - Detiene la ejecución y busca el except más cercano que la maneje. - - try / except / else / finally: - try: — bloque que puede fallar - except ErrorEspecifico: — captura ese tipo de error específico - except Exception: — captura CUALQUIER error (úsalo como último recurso) - else: — se ejecuta SOLO si no hubo excepciones - finally: — se ejecuta SIEMPRE (con o sin error) — útil para limpiar recursos - - Orden de except: de más específico a más general. - Si pones except Exception primero, nunca llegará a los except específicos. -""" - - -# --- Definición de excepciones personalizadas --- -class EdadInvalidaError(Exception): - """ - Clase base para todos los errores de edad. - Heredar de ella permite capturar TODOS los errores de edad con un solo except. - """ - - pass - - -class EdadNegativaError(EdadInvalidaError): - """Se lanza cuando la edad es un número negativo.""" - - pass - - -class EdadExcesivaError(EdadInvalidaError): - """Se lanza cuando la edad supera un máximo razonable.""" - - MAXIMO = 150 - - def __init__(self, edad_recibida: int): - # Podemos personalizar el mensaje de error en __init__ - super().__init__( - f"Edad {edad_recibida} supera el máximo posible de {self.MAXIMO} años." - ) - self.edad_recibida = edad_recibida - - -class EdadNoNumericaError(EdadInvalidaError): - """Se lanza cuando el input no es un número.""" - - pass - - -# --- Función que valida y lanza las excepciones --- -def validar_edad(entrada: str) -> int: - """ - Recibe un string (como vendrá de input()), valida la edad y la devuelve. - Lanza una excepción específica según el tipo de error. - """ - # Intentar convertir a entero - try: - edad = int(entrada) - except ValueError: - raise EdadNoNumericaError(f"'{entrada}' no es un número entero válido.") - - if edad < 0: - raise EdadNegativaError(f"La edad no puede ser negativa. Recibido: {edad}") - - if edad > EdadExcesivaError.MAXIMO: - raise EdadExcesivaError(edad) - - return edad - - -# --- Uso con try/except --- -def procesar_registro(nombre: str, edad_str: str): - """ - Procesa el registro de una persona, manejando cada tipo de error. - """ - print(f"\nProcesando: nombre='{nombre}', edad='{edad_str}'") - try: - edad = validar_edad(edad_str) - print(f" ✓ Registro válido: {nombre}, {edad} años") - - except EdadNoNumericaError as e: - # Más específico primero - print(f" ✗ Error de formato: {e}") - - except EdadNegativaError as e: - print(f" ✗ Edad negativa: {e}") - - except EdadExcesivaError as e: - print(f" ✗ Edad excesiva: {e}") - print(f" Valor recibido fue: {e.edad_recibida}") - - except EdadInvalidaError as e: - # Captura CUALQUIER EdadInvalidaError no capturado arriba - print(f" ✗ Error de edad genérico: {e}") - - -if __name__ == "__main__": - print("=== Demo de excepciones personalizadas ===") - - casos = [ - ("María", "28"), # Válido - ("Carlos", "-5"), # EdadNegativaError - ("Luis", "abc"), # EdadNoNumericaError - ("Ana", "200"), # EdadExcesivaError - ("Sofía", "0"), # Válido (recién nacida) - ("Pedro", "150"), # EdadExcesivaError (exactamente el límite) - ("Juan", "3.5"), # EdadNoNumericaError (float no es int) - ] - - for nombre, edad_str in casos: - procesar_registro(nombre, edad_str) diff --git a/ejemplos/general/17-json/README.md b/ejemplos/general/17-json/README.md deleted file mode 100644 index 8bdea5e..0000000 --- a/ejemplos/general/17-json/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Manejo de Archivos JSON - -## ¿Qué muestra este ejemplo? -Muestra cómo utilizar la librería `json` integrada en Python para guardar (serializar) un diccionario en un archivo de texto, y posteriormente leer (deserializar) ese archivo de vuelta a un diccionario de Python. - -## ¿Por qué es útil? -JSON (JavaScript Object Notation) es el formato de intercambio de datos más usado en la web. Es perfecto para guardar configuraciones de programas, estado de un juego, o para mandar información a APIs externas. Python traduce diccionarios a JSON de manera casi transparente. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_json.py -``` -Pruébalo un par de veces consecutivas. Verás cómo el puntaje guardado es recordado en la siguiente ejecución. ¡Abre el archivo `score.json` generado para ver cómo luce! - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #31 en GitHub: Python General 17 - Lector de Configuraciones (JSON/YAML)](https://github.com/Kaia-Alenia/nerve-community/issues/31)** - -## Nota para principiantes -Fíjate siempre en la diferencia entre `json.dump()` (que escribe directamente en un archivo) y `json.dumps()` (que devuelve un *string* de JSON sin escribir nada). Lo mismo aplica para `load()` vs `loads()`. La letra "s" al final significa "string". diff --git a/ejemplos/general/17-json/ejemplo_json.py b/ejemplos/general/17-json/ejemplo_json.py deleted file mode 100644 index fa6a8ef..0000000 --- a/ejemplos/general/17-json/ejemplo_json.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -Ejemplo: Leer configuraciones JSON/YAML con manejo de claves faltantes - -Qué enseña este ejemplo: - - Cómo leer un archivo JSON con json.load() + pathlib - - Cómo manejar claves faltantes con .get() y KeyError - - Cómo detectar automáticamente el formato del archivo (.json o .yaml) - - Cómo devolver valores por defecto cuando el archivo no existe - -Para tu reto (lector de configuraciones): - Aplica el mismo patrón: detecta la extensión, usa json.load o yaml.safe_load, - y maneja los casos de archivo inexistente o claves faltantes con valores por defecto. - -Glosario: - json.load(archivo) — lee un archivo y devuelve dict/list de Python - json.dump(datos, archivo, indent=4) — escribe dict/list como JSON en un archivo - yaml.safe_load(archivo)— lee un archivo YAML y devuelve dict de Python - (usar safe_load, NO yaml.load — es más seguro) - dict.get(clave, default) — devuelve el valor de la clave, o 'default' si no existe - Nunca lanza KeyError (a diferencia de dict[clave]) - dict.get("a", {}).get("b", valor) — acceso seguro a claves anidadas - pathlib.Path.suffix — extensión del archivo: ".json", ".yaml", ".yml" - KeyError — error que lanza Python al acceder a una clave inexistente - con dict[clave] (en vez de dict.get(clave)) -""" - -import json -from pathlib import Path - -# pyyaml es un paquete externo: pip install pyyaml -# Solo se importa si el archivo es .yaml — no es obligatorio instalarlo -try: - import yaml - - YAML_DISPONIBLE = True -except ImportError: - YAML_DISPONIBLE = False - - -# Configuración por defecto (usada si el archivo no existe o faltan claves) -CONFIG_POR_DEFECTO = { - "base_url": "http://localhost:8000", - "timeout_seg": 30, - "debug": False, - "base_de_datos": { - "host": "localhost", - "puerto": 5432, - "nombre": "mi_app", - }, -} - - -def cargar_config(ruta: str) -> dict: - """ - Carga una configuración desde un archivo .json o .yaml. - Si el archivo no existe, devuelve la configuración por defecto. - Si faltan claves, las rellena con los valores por defecto. - """ - archivo = Path(ruta) - - if not archivo.exists(): - print(f"Archivo '{ruta}' no encontrado. Usando configuración por defecto.") - return CONFIG_POR_DEFECTO.copy() - - extension = archivo.suffix.lower() - - try: - with open(archivo, "r", encoding="utf-8") as f: - if extension == ".json": - datos = json.load(f) - elif extension in (".yaml", ".yml"): - if not YAML_DISPONIBLE: - print("pyyaml no instalado. Instala con: pip install pyyaml") - return CONFIG_POR_DEFECTO.copy() - datos = yaml.safe_load(f) - else: - print(f"Formato '{extension}' no soportado.") - return CONFIG_POR_DEFECTO.copy() - - # Combinar con los defaults: claves faltantes usan el valor por defecto - config_completa = CONFIG_POR_DEFECTO.copy() - config_completa.update(datos) - return config_completa - - except json.JSONDecodeError as e: - print(f"Error al parsear JSON: {e}") - return CONFIG_POR_DEFECTO.copy() - - -def acceder_configuracion(config: dict): - """ - Demuestra cómo acceder a claves de forma segura. - """ - print("\n--- Configuración cargada ---") - - # dict.get(clave, default) → NUNCA lanza KeyError - url = config.get("base_url", "sin-url") - timeout = config.get("timeout_seg", 30) - debug = config.get("debug", False) - - print(f" URL: {url}") - print(f" Timeout: {timeout}s") - print(f" Debug: {debug}") - - # Acceso anidado seguro con .get() encadenado - db = config.get("base_de_datos", {}) - host_db = db.get("host", "desconocido") - puerto_db = db.get("puerto", 5432) - - print(f" Base de datos: {host_db}:{puerto_db}") - - # Contraste: acceso con [] → lanza KeyError si no existe - try: - _ = config["clave_que_no_existe"] - except KeyError as e: - print(f"\n (Ejemplo de KeyError al usar config[{e}])") - print(" Solución: usar config.get('clave_que_no_existe', valor_defecto)") - - -if __name__ == "__main__": - # Demo 1: archivo inexistente → defaults - print("=== Demo 1: Archivo inexistente ===") - config1 = cargar_config("no_existe.json") - acceder_configuracion(config1) - - # Demo 2: crear un JSON parcial y cargarlo - print("\n=== Demo 2: JSON con solo algunas claves ===") - config_parcial = { - "base_url": "https://api.miempresa.com", - "debug": True, - # timeout_seg y base_de_datos faltan → usarán los defaults - } - with open("config_ejemplo.json", "w", encoding="utf-8") as f: - json.dump(config_parcial, f, indent=4) - print("Archivo 'config_ejemplo.json' creado con configuración parcial.") - - config2 = cargar_config("config_ejemplo.json") - acceder_configuracion(config2) - - # Limpiar - Path("config_ejemplo.json").unlink() - print("\n✓ Archivo de prueba eliminado.") diff --git a/ejemplos/general/18-poo-avanzada/README.md b/ejemplos/general/18-poo-avanzada/README.md deleted file mode 100644 index 3d4935b..0000000 --- a/ejemplos/general/18-poo-avanzada/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# POO Avanzada: Propiedades Calculadas - -## ¿Qué muestra este ejemplo? -Muestra cómo usar el decorador `@property` en la Programación Orientada a Objetos (POO) para crear atributos computados o propiedades de "solo lectura". - -## ¿Por qué es útil? -A veces necesitas que un objeto exponga un dato que se arma a partir de otros (como juntar nombre y apellido, o resumir un libro). Usando `@property`, logras que el usuario de la clase pueda consultar el dato simplemente leyendo `objeto.resumen` (sin usar paréntesis), ocultando el cálculo interno. Además, al no definir un *setter*, garantizas que nadie desde fuera pueda corromper ese dato sobreescribiéndolo directamente. - -## ¿Cómo correrlo? -Abre tu terminal, navega a esta carpeta y ejecuta: -```bash -python ejemplo_propiedades.py -``` - -## ¿Qué retos usan esta base? -- 👉 **[Ver Issue #32 en GitHub: Python General 18 - Inventario de Tienda (POO Avanzada)](https://github.com/Kaia-Alenia/nerve-community/issues/32)** - -## Nota para principiantes -El decorador `@property` transforma un método en un atributo virtual. Esta es la forma "Pythónica" de manejar getters y setters. Si luego necesitas permitir que el valor cambie, agregarás un decorador secundario llamado `@nombre_de_propiedad.setter`. diff --git a/ejemplos/general/18-poo-avanzada/ejemplo_propiedades.py b/ejemplos/general/18-poo-avanzada/ejemplo_propiedades.py deleted file mode 100644 index 5e15e32..0000000 --- a/ejemplos/general/18-poo-avanzada/ejemplo_propiedades.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -Ejemplo: POO avanzada — @property con setter y validación + __repr__ - -Qué enseña este ejemplo: - - Cómo usar @property para acceso controlado a atributos - - Cómo usar @nombre.setter para validar el valor al asignar - - Cómo implementar __repr__ para que tu objeto sea legible en debugging - - Diferencia entre atributo público, privado (_) y name-mangled (__) - -Para tu reto (inventario de tienda): - Aplica el mismo patrón para la clase Producto: - @property precio → devuelve self._precio - @precio.setter → valida que sea positivo antes de guardar - Y para Inventario: - @property valor_total → calcula la suma al momento de pedirlo - -Glosario: - @property — convierte un método en un "atributo de solo lectura" - se accede sin paréntesis: objeto.precio (no objeto.precio()) - - @nombre.setter — complemento de @property que permite asignar con validación - se activa cuando escribes: objeto.precio = 100 - Si no defines setter, el atributo es de solo lectura. - - __repr__(self) — devuelve una representación oficial del objeto. - Se muestra en el REPL de Python, en listas, en f-strings, etc. - Convención: debe devolver algo que se pueda evaluar de nuevo. - Ejemplo: "Producto(nombre='Leche', precio=25.0, stock=50)" - - __str__(self) — representación amigable para el usuario (print()) - Si no está definido, Python usa __repr__ - - Atributos privados por convención: - _nombre — convención: no tocar desde afuera (pero Python no lo impide) - __nombre — name mangling: Python lo renombra a _Clase__nombre internamente - (sí previene acceso accidental desde subclases) -""" - - -class CuentaBancaria: - """ - Cuenta bancaria con @property para controlar el saldo. - Equivale al rol de Inventario con su valor_total en tu reto. - """ - - def __init__(self, titular: str, saldo_inicial: float = 0.0): - self.titular = titular - # Usamos _saldo (con _) para indicar que es "privado por convención" - self._saldo = 0.0 - # Usamos el setter para validar incluso en __init__ - self.saldo = saldo_inicial - - @property - def saldo(self) -> float: - """ - Propiedad de lectura: acceder con cuenta.saldo (sin paréntesis). - Permite hacer cálculos o transformaciones antes de devolver. - """ - return round(self._saldo, 2) - - @saldo.setter - def saldo(self, nuevo_valor: float): - """ - Setter: se activa con cuenta.saldo = 500. - Valida el valor ANTES de guardarlo. - """ - if not isinstance(nuevo_valor, (int, float)): - raise TypeError( - f"El saldo debe ser un número, no {type(nuevo_valor).__name__}" - ) - if nuevo_valor < 0: - raise ValueError(f"El saldo no puede ser negativo: {nuevo_valor}") - self._saldo = float(nuevo_valor) - - @property - def tiene_fondos(self) -> bool: - """Propiedad calculada de solo lectura (sin setter).""" - return self._saldo > 0 - - def depositar(self, cantidad: float): - if cantidad <= 0: - raise ValueError("La cantidad a depositar debe ser positiva.") - self.saldo = self._saldo + cantidad - print(f" ✓ Depósito de ${cantidad:.2f}. Nuevo saldo: ${self.saldo:.2f}") - - def retirar(self, cantidad: float): - if cantidad <= 0: - raise ValueError("La cantidad a retirar debe ser positiva.") - if cantidad > self._saldo: - raise ValueError(f"Fondos insuficientes. Saldo: ${self.saldo:.2f}") - self.saldo = self._saldo - cantidad - print(f" ✓ Retiro de ${cantidad:.2f}. Nuevo saldo: ${self.saldo:.2f}") - - def __repr__(self) -> str: - """ - Representación oficial: aparece en el REPL, en listas, en f-strings. - Intenta que sea evaluable: CuentaBancaria("Kaia", 1000.0) - """ - return f"CuentaBancaria(titular='{self.titular}', saldo={self.saldo})" - - def __str__(self) -> str: - """Representación amigable para print().""" - estado = "con fondos" if self.tiene_fondos else "sin fondos" - return f"Cuenta de {self.titular}: ${self.saldo:.2f} ({estado})" - - -if __name__ == "__main__": - print("=== Demo de @property y @setter ===\n") - - # Crear cuenta con validación en __init__ (pasa por el setter) - cuenta = CuentaBancaria("Kaia", saldo_inicial=1000.0) - - print(f"Cuenta creada: {cuenta}") # usa __str__ - print(f"repr: {repr(cuenta)}\n") # usa __repr__ - - # Acceso a property (sin paréntesis) - print(f"Saldo actual: ${cuenta.saldo}") - print(f"Tiene fondos: {cuenta.tiene_fondos}\n") - - # Operaciones - cuenta.depositar(500) - cuenta.retirar(200) - - # El setter valida antes de guardar - print("\n--- Pruebas de validación en el setter ---") - - try: - cuenta.saldo = -100 # Debe rechazarlo - except ValueError as e: - print(f" ✓ Error esperado: {e}") - - try: - cuenta.saldo = "mucho" # Debe rechazarlo - except TypeError as e: - print(f" ✓ Error esperado: {e}") - - # Sin setter: @property de solo lectura - try: - cuenta.tiene_fondos = True # No tiene setter → AttributeError - except AttributeError as e: - print(f" ✓ Error esperado (sin setter): {e}") - - print(f"\nEstado final: {cuenta}") diff --git a/ejemplos/nerve/01-hola-nerve/README.md b/ejemplos/nerve/01-hola-nerve/README.md deleted file mode 100644 index 2fe9460..0000000 --- a/ejemplos/nerve/01-hola-nerve/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# 01-hola-nerve - -## Propósito -Este es el ejemplo más básico ("Hello World") para entender cómo funciona la comunicación con `alenia-nerve`. Demuestra el patrón de Envío Directo (Point-to-Point). - -## 💡 Utilidad -- Enseña cómo un emisor puede enviar un mensaje directamente a un receptor específico usando el método `send()`. -- Muestra cómo un receptor puede usar `listen()` para quedarse a la escucha de mensajes. - -## ⚙️ Instalación / Requisitos -1. Asegúrate de tener el Hub encendido en una terminal: `nerve start` -2. Abre una terminal y corre el receptor: `python receptor.py` -3. Abre otra terminal y corre el emisor: `python emisor.py` - -## 🔗 Retos Relacionados -- [Reto 01 — Chat de terminal](https://github.com/Kaia-Alenia/nerve-community/issues/1) -- [Reto 03 — Traductor de mensajes](https://github.com/Kaia-Alenia/nerve-community/issues/3) - -## 🛡️ Buenas Prácticas -- Siempre inicializa el cliente de Nerve con un nombre de cliente único para evitar colisiones. -- Utiliza estructuras simples para los mensajes iniciales al explorar la API de `alenia-nerve`. diff --git a/ejemplos/nerve/01-hola-nerve/emisor.go b/ejemplos/nerve/01-hola-nerve/emisor.go deleted file mode 100644 index d123e35..0000000 --- a/ejemplos/nerve/01-hola-nerve/emisor.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "fmt" - "time" - - "nerve" -) - -func main() { - fmt.Println("Iniciando emisor...") - // 1. Crear el cliente de Nerve - cliente := nerve.NewNexusClient() - - // 2. Conectarse al Hub con un identificador único - cliente.Connect("emisor_01") - fmt.Println("✅ Emisor conectado al Nerve Hub.") - - // Esperamos un momento para asegurar que el receptor esté listo - time.Sleep(1 * time.Second) - - // 3. Preparar el mensaje (payload) y enviarlo al receptor - mensaje := map[string]interface{}{ - "texto": "¡Hola, Nerve!", - "timestamp": time.Now().Unix(), - } - - fmt.Println("Enviando mensaje a 'receptor_01'...") - cliente.Send("receptor_01", mensaje) - - fmt.Println("Mensaje enviado. Terminando emisor...") - // Desconectamos el cliente para liberar recursos - cliente.Disconnect() -} diff --git a/ejemplos/nerve/01-hola-nerve/emisor.js b/ejemplos/nerve/01-hola-nerve/emisor.js deleted file mode 100644 index 845c9af..0000000 --- a/ejemplos/nerve/01-hola-nerve/emisor.js +++ /dev/null @@ -1,28 +0,0 @@ -const { NexusClient } = require('nerve'); - -async function main() { - console.log("Iniciando emisor..."); - // 1. Crear el cliente de Nerve - const cliente = new NexusClient(); - - // 2. Conectarse al Hub con un identificador único - await cliente.connect("emisor_01"); - console.log("✅ Emisor conectado al Nerve Hub."); - - // Esperamos un momento para asegurar que el receptor esté listo - await new Promise(r => setTimeout(r, 1000)); - - // 3. Preparar el mensaje (payload) y enviarlo al receptor - const mensaje = { texto: "¡Hola, Nerve!", timestamp: Date.now() }; - - console.log("Enviando mensaje a 'receptor_01'..."); - cliente.send("receptor_01", mensaje); - - console.log("Mensaje enviado. Terminando emisor..."); - // Desconectamos el cliente para liberar recursos - cliente.disconnect(); -} - -if (require.main === module) { - main(); -} diff --git a/ejemplos/nerve/01-hola-nerve/emisor.py b/ejemplos/nerve/01-hola-nerve/emisor.py deleted file mode 100644 index 9522a86..0000000 --- a/ejemplos/nerve/01-hola-nerve/emisor.py +++ /dev/null @@ -1,29 +0,0 @@ -import time -from nerve import NexusClient - - -def main(): - print("Iniciando emisor...") - # 1. Crear el cliente de Nerve - cliente = NexusClient() - - # 2. Conectarse al Hub con un identificador único - cliente.connect("emisor_01") - print("✅ Emisor conectado al Nerve Hub.") - - # Esperamos un momento para asegurar que el receptor esté listo - time.sleep(1) - - # 3. Preparar el mensaje (payload) y enviarlo al receptor - mensaje = {"texto": "¡Hola, Nerve!", "timestamp": time.time()} - - print("Enviando mensaje a 'receptor_01'...") - cliente.send("receptor_01", mensaje) - - print("Mensaje enviado. Terminando emisor...") - # Desconectamos el cliente para liberar recursos - cliente.disconnect() - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/01-hola-nerve/emisor.rs b/ejemplos/nerve/01-hola-nerve/emisor.rs deleted file mode 100644 index cd81f03..0000000 --- a/ejemplos/nerve/01-hola-nerve/emisor.rs +++ /dev/null @@ -1,33 +0,0 @@ -use nerve::NexusClient; -use serde_json::json; -use std::time::Duration; -use std::thread; - -fn main() { - println!("Iniciando emisor..."); - // 1. Crear el cliente de Nerve - let mut cliente = NexusClient::new(); - - // 2. Conectarse al Hub con un identificador único - cliente.connect("emisor_01").expect("Error al conectar"); - println!("✅ Emisor conectado al Nerve Hub."); - - // Esperamos un momento para asegurar que el receptor esté listo - thread::sleep(Duration::from_secs(1)); - - // 3. Preparar el mensaje (payload) y enviarlo al receptor - let mensaje = json!({ - "texto": "¡Hola, Nerve!", - "timestamp": std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() - }); - - println!("Enviando mensaje a 'receptor_01'..."); - cliente.send("receptor_01", mensaje).expect("Error al enviar"); - - println!("Mensaje enviado. Terminando emisor..."); - // Desconectamos el cliente para liberar recursos - cliente.disconnect(); -} diff --git a/ejemplos/nerve/01-hola-nerve/receptor.go b/ejemplos/nerve/01-hola-nerve/receptor.go deleted file mode 100644 index f774e0c..0000000 --- a/ejemplos/nerve/01-hola-nerve/receptor.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/signal" - "syscall" - - "nerve" -) - -func procesarMensaje(mensajeCrudo nerve.Message) { - fmt.Printf(" ¡Mensaje recibido!: %+v\n", mensajeCrudo) - - payload, ok := mensajeCrudo.Payload.(map[string]interface{}) - if ok { - if texto, ok := payload["texto"].(string); ok { - fmt.Printf("Texto extraído: %s\n", texto) - } - } -} - -func main() { - fmt.Println("Iniciando receptor...") - cliente := nerve.NewNexusClient() - - // Nos conectamos como "receptor_01" - cliente.Connect("receptor_01") - fmt.Println("✅ Receptor conectado. Esperando mensajes...") - - // Registramos la función que se llamará al recibir mensajes - cliente.Listen(procesarMensaje) - - // Mantenemos el programa vivo para escuchar - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - <-c - - fmt.Println("\nSaliendo...") - cliente.Disconnect() -} diff --git a/ejemplos/nerve/01-hola-nerve/receptor.js b/ejemplos/nerve/01-hola-nerve/receptor.js deleted file mode 100644 index 3945d11..0000000 --- a/ejemplos/nerve/01-hola-nerve/receptor.js +++ /dev/null @@ -1,35 +0,0 @@ -const { NexusClient } = require('nerve'); - -function procesarMensaje(mensajeCrudo) { - // El servidor nos entrega el mensaje completo. Lo extraemos. - console.log(` ¡Mensaje recibido!: ${JSON.stringify(mensajeCrudo)}`); - - // Podemos extraer el payload original si queremos - const payload = mensajeCrudo.payload || {}; - if (payload.texto) { - console.log(`Texto extraído: ${payload.texto}`); - } -} - -async function main() { - console.log("Iniciando receptor..."); - const cliente = new NexusClient(); - - // Nos conectamos como "receptor_01" - await cliente.connect("receptor_01"); - console.log("✅ Receptor conectado. Esperando mensajes..."); - - // Registramos la función que se llamará al recibir mensajes - cliente.listen(procesarMensaje); - - // Mantenemos el programa vivo para escuchar - process.on('SIGINT', () => { - console.log("\nSaliendo..."); - cliente.disconnect(); - process.exit(); - }); -} - -if (require.main === module) { - main(); -} diff --git a/ejemplos/nerve/01-hola-nerve/receptor.py b/ejemplos/nerve/01-hola-nerve/receptor.py deleted file mode 100644 index 4015706..0000000 --- a/ejemplos/nerve/01-hola-nerve/receptor.py +++ /dev/null @@ -1,36 +0,0 @@ -import time -from nerve import NexusClient - - -def procesar_mensaje(mensaje_crudo): - # El servidor nos entrega el mensaje completo. Lo extraemos. - print(f" ¡Mensaje recibido!: {mensaje_crudo}") - - # Podemos extraer el payload original si queremos - payload = mensaje_crudo.get("payload", {}) - if "texto" in payload: - print(f"Texto extraído: {payload['texto']}") - - -def main(): - print("Iniciando receptor...") - cliente = NexusClient() - - # Nos conectamos como "receptor_01" - cliente.connect("receptor_01") - print("✅ Receptor conectado. Esperando mensajes...") - - # Registramos la función que se llamará al recibir mensajes - cliente.listen(procesar_mensaje) - - try: - # Mantenemos el programa vivo para escuchar - while True: - time.sleep(1) - except KeyboardInterrupt: - print("\nSaliendo...") - cliente.disconnect() - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/01-hola-nerve/receptor.rs b/ejemplos/nerve/01-hola-nerve/receptor.rs deleted file mode 100644 index 5940412..0000000 --- a/ejemplos/nerve/01-hola-nerve/receptor.rs +++ /dev/null @@ -1,35 +0,0 @@ -use nerve::{NexusClient, Message}; -use std::sync::mpsc; - -fn procesar_mensaje(mensaje_crudo: Message) { - println!(" ¡Mensaje recibido!: {:?}", mensaje_crudo); - - if let Some(payload) = mensaje_crudo.payload { - if let Some(texto) = payload.get("texto") { - println!("Texto extraído: {}", texto); - } - } -} - -fn main() { - println!("Iniciando receptor..."); - let mut cliente = NexusClient::new(); - - // Nos conectamos como "receptor_01" - cliente.connect("receptor_01").expect("Error al conectar"); - println!("✅ Receptor conectado. Esperando mensajes..."); - - // Registramos la función que se llamará al recibir mensajes - cliente.listen(procesar_mensaje); - - // Mantenemos el programa vivo para escuchar (ctrl+c para salir) - let (tx, rx) = mpsc::channel(); - ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) - .expect("Error setting Ctrl-C handler"); - - println!("Presiona Ctrl+C para salir."); - rx.recv().expect("Could not receive from channel."); - - println!("\nSaliendo..."); - cliente.disconnect(); -} diff --git a/ejemplos/nerve/02-pub-sub-basico/README.md b/ejemplos/nerve/02-pub-sub-basico/README.md deleted file mode 100644 index 3902f7e..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# 02-pub-sub-basico - -## Propósito -Este ejemplo demuestra cómo usar `broadcast()` para emitir eventos a múltiples clientes de manera simultánea usando el patrón Publish / Subscribe (Pub-Sub). - -## 💡 Utilidad -- A diferencia de `send()`, `broadcast()` reparte el mensaje a todos los clientes conectados. -- Muestra cómo el suscriptor filtra los mensajes que recibe basándose en algún dato del payload. - -## ⚙️ Instalación / Requisitos -1. Asegúrate de tener el Hub encendido: `nerve start` -2. Abre dos terminales para simular a dos personas distintas y ejecuta: - - Terminal 1: `python suscriptor.py usuario_1` - - Terminal 2: `python suscriptor.py usuario_2` -3. Abre una tercera terminal e inicia el publicador: `python publicador.py` - -## 🔗 Retos Relacionados -- [Reto 02 — Reloj sincronizado](https://github.com/Kaia-Alenia/nerve-community/issues/2) - -## 🛡️ Buenas Prácticas -- Usa identificadores en el payload para filtrar mensajes si los clientes solo deben reaccionar a un tipo de transmisión. -- Evita saturar el Hub con `broadcast` si el mensaje es para un destinatario en particular. diff --git a/ejemplos/nerve/02-pub-sub-basico/publicador.go b/ejemplos/nerve/02-pub-sub-basico/publicador.go deleted file mode 100644 index 667dc4b..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/publicador.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/signal" - "syscall" - "time" - - "nerve" -) - -func main() { - fmt.Println("Iniciando publicador (estación de radio)...") - cliente := nerve.NewNexusClient() - cliente.Connect("publicador_noticias") - fmt.Println("✅ Publicador conectado al Hub.") - - ticker := time.NewTicker(3 * time.Second) - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt, syscall.SIGTERM) - - contador := 1 - - go func() { - for { - select { - case <-ticker.C: - noticia := map[string]interface{}{ - "canal": "noticias_tech", - "titular": fmt.Sprintf("Noticia #%d: Nerve Hub funciona genial", contador), - "timestamp": time.Now().Unix(), - } - - fmt.Printf(" Transmitiendo (broadcast): %s\n", noticia["titular"]) - cliente.Broadcast(noticia) - contador++ - } - } - }() - - <-quit - fmt.Println("\nApagando publicador...") - ticker.Stop() - cliente.Disconnect() -} diff --git a/ejemplos/nerve/02-pub-sub-basico/publicador.js b/ejemplos/nerve/02-pub-sub-basico/publicador.js deleted file mode 100644 index 730dd84..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/publicador.js +++ /dev/null @@ -1,35 +0,0 @@ -const { NexusClient } = require('nerve'); - -async function main() { - console.log("Iniciando publicador (estación de radio)..."); - const cliente = new NexusClient(); - await cliente.connect("publicador_noticias"); - console.log("✅ Publicador conectado al Hub."); - - let contador = 1; - - // Función para transmitir continuamente - const intervalId = setInterval(() => { - const noticia = { - canal: "noticias_tech", - titular: `Noticia #${contador}: Nerve Hub funciona genial`, - timestamp: Date.now() - }; - - console.log(` Transmitiendo (broadcast): ${noticia.titular}`); - cliente.broadcast(noticia); - - contador++; - }, 3000); - - process.on('SIGINT', () => { - console.log("\nApagando publicador..."); - clearInterval(intervalId); - cliente.disconnect(); - process.exit(); - }); -} - -if (require.main === module) { - main(); -} diff --git a/ejemplos/nerve/02-pub-sub-basico/publicador.py b/ejemplos/nerve/02-pub-sub-basico/publicador.py deleted file mode 100644 index 54e116d..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/publicador.py +++ /dev/null @@ -1,33 +0,0 @@ -import time -from nerve import NexusClient - - -def main(): - print("Iniciando publicador (estación de radio)...") - cliente = NexusClient() - cliente.connect("publicador_noticias") - print("✅ Publicador conectado al Hub.") - - contador = 1 - try: - while True: - noticia = { - "canal": "noticias_tech", - "titular": f"Noticia #{contador}: Nerve Hub funciona genial", - "timestamp": time.time(), - } - - # Usamos broadcast para enviarlo a todos los nodos conectados - print(f" Transmitiendo (broadcast): {noticia['titular']}") - cliente.broadcast(noticia) - - contador += 1 - time.sleep(3) # Transmite cada 3 segundos - - except KeyboardInterrupt: - print("\nApagando publicador...") - cliente.disconnect() - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/02-pub-sub-basico/publicador.rs b/ejemplos/nerve/02-pub-sub-basico/publicador.rs deleted file mode 100644 index 405cfe4..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/publicador.rs +++ /dev/null @@ -1,39 +0,0 @@ -use nerve::NexusClient; -use serde_json::json; -use std::sync::{atomic::{AtomicBool, Ordering}, Arc}; -use std::thread; -use std::time::Duration; - -fn main() { - println!("Iniciando publicador (estación de radio)..."); - let mut cliente = NexusClient::new(); - cliente.connect("publicador_noticias").expect("Error conectando"); - println!("✅ Publicador conectado al Hub."); - - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); - - ctrlc::set_handler(move || { - r.store(false, Ordering::SeqCst); - }).expect("Error setting Ctrl-C handler"); - - let mut contador = 1; - - while running.load(Ordering::SeqCst) { - let titular = format!("Noticia #{}: Nerve Hub funciona genial", contador); - let noticia = json!({ - "canal": "noticias_tech", - "titular": titular, - "timestamp": std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() - }); - - println!(" Transmitiendo (broadcast): {}", titular); - let _ = cliente.broadcast(noticia); - - contador += 1; - thread::sleep(Duration::from_secs(3)); - } - - println!("\nApagando publicador..."); - cliente.disconnect(); -} diff --git a/ejemplos/nerve/02-pub-sub-basico/suscriptor.go b/ejemplos/nerve/02-pub-sub-basico/suscriptor.go deleted file mode 100644 index d221a2b..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/suscriptor.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/signal" - "syscall" - - "nerve" -) - -var nombreNodo string - -func alRecibirNoticia(mensajeCrudo nerve.Message) { - payload, ok := mensajeCrudo.Payload.(map[string]interface{}) - if !ok { - return - } - - if canal, ok := payload["canal"].(string); ok && canal == "noticias_tech" { - titular, ok := payload["titular"].(string) - if !ok { - titular = "Sin titular" - } - fmt.Printf("[%s] Nueva noticia interceptada: %s\n", nombreNodo, titular) - } -} - -func main() { - if len(os.Args) > 1 { - nombreNodo = os.Args[1] - } else { - nombreNodo = "suscriptor_default" - } - - fmt.Printf("Iniciando %s...\n", nombreNodo) - cliente := nerve.NewNexusClient() - cliente.Connect(nombreNodo) - fmt.Printf("✅ %s conectado. Escuchando el canal 'noticias_tech'...\n", nombreNodo) - - cliente.Listen(alRecibirNoticia) - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - <-c - - fmt.Printf("\nApagando %s...\n", nombreNodo) - cliente.Disconnect() -} diff --git a/ejemplos/nerve/02-pub-sub-basico/suscriptor.js b/ejemplos/nerve/02-pub-sub-basico/suscriptor.js deleted file mode 100644 index 7b9e94d..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/suscriptor.js +++ /dev/null @@ -1,32 +0,0 @@ -const { NexusClient } = require('nerve'); - -const nombreNodo = process.argv[2] || "suscriptor_default"; - -function alRecibirNoticia(mensajeCrudo) { - const payload = mensajeCrudo.payload || {}; - - if (payload.canal === "noticias_tech") { - const titular = payload.titular || "Sin titular"; - console.log(`[${nombreNodo}] Nueva noticia interceptada: ${titular}`); - } -} - -async function main() { - console.log(`Iniciando ${nombreNodo}...`); - const cliente = new NexusClient(); - - await cliente.connect(nombreNodo); - console.log(`✅ ${nombreNodo} conectado. Escuchando el canal 'noticias_tech'...`); - - cliente.listen(alRecibirNoticia); - - process.on('SIGINT', () => { - console.log(`\nApagando ${nombreNodo}...`); - cliente.disconnect(); - process.exit(); - }); -} - -if (require.main === module) { - main(); -} diff --git a/ejemplos/nerve/02-pub-sub-basico/suscriptor.py b/ejemplos/nerve/02-pub-sub-basico/suscriptor.py deleted file mode 100644 index e721065..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/suscriptor.py +++ /dev/null @@ -1,38 +0,0 @@ -import time -import sys -from nerve import NexusClient - -# Tomamos el nombre del suscriptor por argumento de consola, -# para poder ejecutar varios distintos. -# Si no hay argumento, le ponemos uno por defecto. -nombre_nodo = sys.argv[1] if len(sys.argv) > 1 else "suscriptor_default" - - -def al_recibir_noticia(mensaje_crudo): - # En un patrón Pub-Sub usando 'broadcast', todos reciben el mensaje. - # El trabajo del suscriptor es FILTRAR lo que le interesa. - payload = mensaje_crudo.get("payload", {}) - - if payload.get("canal") == "noticias_tech": - titular = payload.get("titular", "Sin titular") - print(f"[{nombre_nodo}] Nueva noticia interceptada: {titular}") - - -def main(): - print(f"Iniciando {nombre_nodo}...") - cliente = NexusClient() - cliente.connect(nombre_nodo) - print(f"✅ {nombre_nodo} conectado. Escuchando el canal 'noticias_tech'...") - - cliente.listen(al_recibir_noticia) - - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - print(f"\nApagando {nombre_nodo}...") - cliente.disconnect() - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/02-pub-sub-basico/suscriptor.rs b/ejemplos/nerve/02-pub-sub-basico/suscriptor.rs deleted file mode 100644 index 4340088..0000000 --- a/ejemplos/nerve/02-pub-sub-basico/suscriptor.rs +++ /dev/null @@ -1,43 +0,0 @@ -use nerve::{NexusClient, Message}; -use std::env; -use std::sync::mpsc; - -fn main() { - let args: Vec = env::args().collect(); - let nombre_nodo = if args.len() > 1 { - args[1].clone() - } else { - "suscriptor_default".to_string() - } - - // Usar un Arc para compartir el nombre_nodo con el closure si es necesario, - // pero podemos capturarlo clonándolo - let nodo_clone = nombre_nodo.clone(); - - println!("Iniciando {}...", nombre_nodo); - let mut cliente = NexusClient::new(); - cliente.connect(&nombre_nodo).expect("Error conectando"); - println!("✅ {} conectado. Escuchando el canal 'noticias_tech'...", nombre_nodo); - - let al_recibir_noticia = move |mensaje_crudo: Message| { - if let Some(payload) = mensaje_crudo.payload { - if let Some(canal) = payload.get("canal") { - if canal == "noticias_tech" { - let titular = payload.get("titular").and_then(|t| t.as_str()).unwrap_or("Sin titular"); - println!("[{}] Nueva noticia interceptada: {}", nodo_clone, titular); - } - } - } - }; - - cliente.listen(al_recibir_noticia); - - let (tx, rx) = mpsc::channel(); - ctrlc::set_handler(move || tx.send(()).expect("Error enviando señal")) - .expect("Error al configurar ctrl+c"); - - rx.recv().expect("Error esperando señal"); - - println!("\nApagando {}...", nombre_nodo); - cliente.disconnect(); -} diff --git a/ejemplos/nerve/03-peticion-respuesta/README.md b/ejemplos/nerve/03-peticion-respuesta/README.md deleted file mode 100644 index aed13c9..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# 03-peticion-respuesta - -## Propósito -Demuestra cómo realizar comunicación bidireccional estructurada mediante el patrón Request / Reply (Petición - Respuesta). - -## 💡 Utilidad -- Un cliente solicita un dato a un servidor específico enviando un comando. -- El servidor escucha peticiones, procesa la tarea y usa el ID del cliente origen (`"from"`) para enviarle de regreso el resultado exacto. - -## ⚙️ Instalación / Requisitos -1. Asegúrate de tener el Hub encendido: `nerve start` -2. Abre una terminal y corre el servidor: `python servidor.py` -3. Abre otra terminal y ejecuta al cliente: `python cliente.py` - -## 🔗 Retos Relacionados -- Múltiples retos de nivel-2-intermedio, por ejemplo, [Reto 07 — Bot de Discord](https://github.com/Kaia-Alenia/nerve-community/issues/7). - -## 🛡️ Buenas Prácticas -- Incluye un identificador de petición o la dirección del cliente en el payload original si no usas los metadatos de Nerve. -- Maneja tiempos de espera (timeouts) en el cliente en caso de que el servidor no responda. diff --git a/ejemplos/nerve/03-peticion-respuesta/cliente.go b/ejemplos/nerve/03-peticion-respuesta/cliente.go deleted file mode 100644 index 317e466..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/cliente.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "fmt" - "time" - - "nerve" -) - -type ClientePeticion struct { - cliente *nerve.NexusClient - respuestaRecibida *float64 -} - -func (c *ClientePeticion) manejarRespuesta(mensajeCrudo nerve.Message) { - payload, ok := mensajeCrudo.Payload.(map[string]interface{}) - if !ok { - return - } - - accion, ok := payload["accion"].(string) - if ok && accion == "respuesta_operacion" { - if resultado, ok := payload["resultado"].(float64); ok { - c.respuestaRecibida = &resultado - fmt.Printf("✅ ¡El servidor respondió! El resultado es: %v\n", resultado) - } - } -} - -func (c *ClientePeticion) iniciar() { - c.cliente.Connect("cliente_calculadora") - c.cliente.Listen(c.manejarRespuesta) - - fmt.Println("Enviando petición de suma al servidor (5 + 7)...") - peticion := map[string]interface{}{ - "accion": "sumar", - "a": 5, - "b": 7, - } - c.cliente.Send("servidor_calculadora", peticion) - - tiempoEspera := 0 - for c.respuestaRecibida == nil && tiempoEspera < 5 { - fmt.Println("⏳ Esperando respuesta...") - time.Sleep(1 * time.Second) - tiempoEspera++ - } - - if c.respuestaRecibida == nil { - fmt.Println("❌ Tiempo de espera agotado. El servidor no contestó.") - } - - c.cliente.Disconnect() -} - -func main() { - app := &ClientePeticion{ - cliente: nerve.NewNexusClient(), - } - app.iniciar() -} diff --git a/ejemplos/nerve/03-peticion-respuesta/cliente.js b/ejemplos/nerve/03-peticion-respuesta/cliente.js deleted file mode 100644 index 18df02a..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/cliente.js +++ /dev/null @@ -1,45 +0,0 @@ -const { NexusClient } = require('nerve'); - -class ClientePeticion { - constructor() { - this.cliente = new NexusClient(); - this.respuestaRecibida = null; - } - - manejarRespuesta = (mensajeCrudo) => { - const payload = mensajeCrudo.payload || {}; - const accion = payload.accion; - - if (accion === "respuesta_operacion") { - this.respuestaRecibida = payload.resultado; - console.log(`✅ ¡El servidor respondió! El resultado es: ${this.respuestaRecibida}`); - } - } - - async iniciar() { - await this.cliente.connect("cliente_calculadora"); - this.cliente.listen(this.manejarRespuesta); - - console.log("Enviando petición de suma al servidor (5 + 7)..."); - const peticion = { accion: "sumar", a: 5, b: 7 }; - this.cliente.send("servidor_calculadora", peticion); - - let tiempoEspera = 0; - while (this.respuestaRecibida === null && tiempoEspera < 5) { - console.log("⏳ Esperando respuesta..."); - await new Promise(r => setTimeout(r, 1000)); - tiempoEspera++; - } - - if (this.respuestaRecibida === null) { - console.log("❌ Tiempo de espera agotado. El servidor no contestó."); - } - - this.cliente.disconnect(); - } -} - -if (require.main === module) { - const app = new ClientePeticion(); - app.iniciar(); -} diff --git a/ejemplos/nerve/03-peticion-respuesta/cliente.py b/ejemplos/nerve/03-peticion-respuesta/cliente.py deleted file mode 100644 index 4427062..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/cliente.py +++ /dev/null @@ -1,45 +0,0 @@ -import time -from nerve import NexusClient - - -class ClientePeticion: - def __init__(self): - self.cliente = NexusClient() - self.respuesta_recibida = None - - def manejar_respuesta(self, mensaje_crudo): - # Filtramos para procesar solo los mensajes dirigidos a nosotros con un tipo esperado - payload = mensaje_crudo.get("payload", {}) - accion = payload.get("accion") - - if accion == "respuesta_operacion": - self.respuesta_recibida = payload.get("resultado") - print( - f"✅ ¡El servidor respondió! El resultado es: {self.respuesta_recibida}" - ) - - def iniciar(self): - self.cliente.connect("cliente_calculadora") - self.cliente.listen(self.manejar_respuesta) - - print("Enviando petición de suma al servidor (5 + 7)...") - # Mandamos un payload estructurado pidiendo algo específico - peticion = {"accion": "sumar", "a": 5, "b": 7} - self.cliente.send("servidor_calculadora", peticion) - - # Esperamos hasta que el servidor nos conteste - tiempo_espera = 0 - while self.respuesta_recibida is None and tiempo_espera < 5: - print("⏳ Esperando respuesta...") - time.sleep(1) - tiempo_espera += 1 - - if self.respuesta_recibida is None: - print("❌ Tiempo de espera agotado. El servidor no contestó.") - - self.cliente.disconnect() - - -if __name__ == "__main__": - app = ClientePeticion() - app.iniciar() diff --git a/ejemplos/nerve/03-peticion-respuesta/cliente.rs b/ejemplos/nerve/03-peticion-respuesta/cliente.rs deleted file mode 100644 index 064800e..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/cliente.rs +++ /dev/null @@ -1,72 +0,0 @@ -use nerve::{NexusClient, Message}; -use serde_json::json; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::Duration; - -struct ClientePeticion { - cliente: NexusClient, - respuesta_recibida: Arc>>, -} - -impl ClientePeticion { - fn new() -> Self { - Self { - cliente: NexusClient::new(), - respuesta_recibida: Arc::new(Mutex::new(None)), - } - } - - fn iniciar(&mut self) { - self.cliente.connect("cliente_calculadora").expect("Error conectando"); - - let respuesta_clone = self.respuesta_recibida.clone(); - - self.cliente.listen(move |mensaje_crudo: Message| { - if let Some(payload) = mensaje_crudo.payload { - if let Some(accion) = payload.get("accion").and_then(|a| a.as_str()) { - if accion == "respuesta_operacion" { - if let Some(resultado) = payload.get("resultado").and_then(|r| r.as_f64()) { - let mut resp = respuesta_clone.lock().unwrap(); - *resp = Some(resultado); - println!("✅ ¡El servidor respondió! El resultado es: {}", resultado); - } - } - } - } - }); - - println!("Enviando petición de suma al servidor (5 + 7)..."); - let peticion = json!({ - "accion": "sumar", - "a": 5.0, - "b": 7.0 - }); - self.cliente.send("servidor_calculadora", peticion).expect("Error al enviar"); - - let mut tiempo_espera = 0; - loop { - let resp = self.respuesta_recibida.lock().unwrap(); - if resp.is_some() || tiempo_espera >= 5 { - break; - } - drop(resp); // liberar el lock para el sleep - - println!("⏳ Esperando respuesta..."); - thread::sleep(Duration::from_secs(1)); - tiempo_espera += 1; - } - - let resp = self.respuesta_recibida.lock().unwrap(); - if resp.is_none() { - println!("❌ Tiempo de espera agotado. El servidor no contestó."); - } - - self.cliente.disconnect(); - } -} - -fn main() { - let mut app = ClientePeticion::new(); - app.iniciar(); -} diff --git a/ejemplos/nerve/03-peticion-respuesta/servidor.go b/ejemplos/nerve/03-peticion-respuesta/servidor.go deleted file mode 100644 index 15429b4..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/servidor.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/signal" - "syscall" - - "nerve" -) - -type ServidorCalculadora struct { - cliente *nerve.NexusClient -} - -func (s *ServidorCalculadora) atenderPeticion(mensajeCrudo nerve.Message) { - remitente := mensajeCrudo.From - payload, ok := mensajeCrudo.Payload.(map[string]interface{}) - if !ok { - return - } - - accion, ok := payload["accion"].(string) - if ok && accion == "sumar" { - a, _ := payload["a"].(float64) - b, _ := payload["b"].(float64) - resultado := a + b - - fmt.Printf(" Solicitud de suma recibida de '%s': %v + %v = %v\n", remitente, a, b, resultado) - - respuesta := map[string]interface{}{ - "accion": "respuesta_operacion", - "resultado": resultado, - } - - fmt.Printf("Enviando resultado a '%s'...\n", remitente) - s.cliente.Send(remitente, respuesta) - } -} - -func (s *ServidorCalculadora) iniciar() { - s.cliente.Connect("servidor_calculadora") - fmt.Println("✅ Servidor Calculadora en línea y esperando peticiones...") - - s.cliente.Listen(s.atenderPeticion) - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - <-c - - fmt.Println("\nApagando servidor...") - s.cliente.Disconnect() -} - -func main() { - app := &ServidorCalculadora{ - cliente: nerve.NewNexusClient(), - } - app.iniciar() -} diff --git a/ejemplos/nerve/03-peticion-respuesta/servidor.js b/ejemplos/nerve/03-peticion-respuesta/servidor.js deleted file mode 100644 index a915f2c..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/servidor.js +++ /dev/null @@ -1,43 +0,0 @@ -const { NexusClient } = require('nerve'); - -class ServidorCalculadora { - constructor() { - this.cliente = new NexusClient(); - } - - atenderPeticion = (mensajeCrudo) => { - const remitente = mensajeCrudo.from; - const payload = mensajeCrudo.payload || {}; - const accion = payload.accion; - - if (accion === "sumar") { - const a = payload.a || 0; - const b = payload.b || 0; - const resultado = a + b; - - console.log(` Solicitud de suma recibida de '${remitente}': ${a} + ${b} = ${resultado}`); - - const respuesta = { accion: "respuesta_operacion", resultado: resultado }; - console.log(`Enviando resultado a '${remitente}'...`); - this.cliente.send(remitente, respuesta); - } - } - - async iniciar() { - await this.cliente.connect("servidor_calculadora"); - console.log("✅ Servidor Calculadora en línea y esperando peticiones..."); - - this.cliente.listen(this.atenderPeticion); - - process.on('SIGINT', () => { - console.log("\nApagando servidor..."); - this.cliente.disconnect(); - process.exit(); - }); - } -} - -if (require.main === module) { - const app = new ServidorCalculadora(); - app.iniciar(); -} diff --git a/ejemplos/nerve/03-peticion-respuesta/servidor.py b/ejemplos/nerve/03-peticion-respuesta/servidor.py deleted file mode 100644 index 5bc0941..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/servidor.py +++ /dev/null @@ -1,48 +0,0 @@ -import time -from nerve import NexusClient - - -class ServidorCalculadora: - def __init__(self): - self.cliente = NexusClient() - - def atender_peticion(self, mensaje_crudo): - remitente = mensaje_crudo.get("from") - payload = mensaje_crudo.get("payload", {}) - - accion = payload.get("accion") - - # Validamos que el mensaje es para nosotros y es una acción conocida - if accion == "sumar": - a = payload.get("a", 0) - b = payload.get("b", 0) - resultado = a + b - - print( - f" Solicitud de suma recibida de '{remitente}': {a} + {b} = {resultado}" - ) - - # Construimos la respuesta - respuesta = {"accion": "respuesta_operacion", "resultado": resultado} - - # Mandamos la respuesta de vuelta a quien nos lo pidió - print(f"Enviando resultado a '{remitente}'...") - self.cliente.send(remitente, respuesta) - - def iniciar(self): - self.cliente.connect("servidor_calculadora") - print("✅ Servidor Calculadora en línea y esperando peticiones...") - - self.cliente.listen(self.atender_peticion) - - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - print("\nApagando servidor...") - self.cliente.disconnect() - - -if __name__ == "__main__": - app = ServidorCalculadora() - app.iniciar() diff --git a/ejemplos/nerve/03-peticion-respuesta/servidor.rs b/ejemplos/nerve/03-peticion-respuesta/servidor.rs deleted file mode 100644 index 45164c8..0000000 --- a/ejemplos/nerve/03-peticion-respuesta/servidor.rs +++ /dev/null @@ -1,66 +0,0 @@ -use nerve::{NexusClient, Message}; -use serde_json::json; -use std::sync::mpsc; -use std::sync::{Arc, Mutex}; - -struct ServidorCalculadora { - cliente: Arc>, -} - -impl ServidorCalculadora { - fn new() -> Self { - Self { - cliente: Arc::new(Mutex::new(NexusClient::new())), - } - } - - fn iniciar(&mut self) { - let mut cli = self.cliente.lock().unwrap(); - cli.connect("servidor_calculadora").expect("Error conectando"); - println!("✅ Servidor Calculadora en línea y esperando peticiones..."); - - let cliente_clone = self.cliente.clone(); - - cli.listen(move |mensaje_crudo: Message| { - let remitente = mensaje_crudo.from.clone(); - - if let Some(payload) = mensaje_crudo.payload { - if let Some(accion) = payload.get("accion").and_then(|a| a.as_str()) { - if accion == "sumar" { - let a = payload.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0); - let b = payload.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0); - let resultado = a + b; - - println!(" Solicitud de suma recibida de '{}': {} + {} = {}", remitente, a, b, resultado); - - let respuesta = json!({ - "accion": "respuesta_operacion", - "resultado": resultado - }); - - println!("Enviando resultado a '{}'...", remitente); - let mut cli_lock = cliente_clone.lock().unwrap(); - let _ = cli_lock.send(&remitente, respuesta); - } - } - } - }); - - drop(cli); - - let (tx, rx) = mpsc::channel(); - ctrlc::set_handler(move || tx.send(()).expect("Error enviando señal")) - .expect("Error al configurar ctrl+c"); - - rx.recv().expect("Error esperando señal"); - - println!("\nApagando servidor..."); - let mut cli = self.cliente.lock().unwrap(); - cli.disconnect(); - } -} - -fn main() { - let mut app = ServidorCalculadora::new(); - app.iniciar(); -} diff --git a/ejemplos/nerve/04-vigilante-de-carpetas/README.md b/ejemplos/nerve/04-vigilante-de-carpetas/README.md deleted file mode 100644 index fb21727..0000000 --- a/ejemplos/nerve/04-vigilante-de-carpetas/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Vigilante de Carpetas (File Watcher) - -**¿Qué muestra este ejemplo?** -Muestra cómo utilizar librerías nativas o de terceros para monitorear una carpeta y detectar cuándo se crean, modifican o eliminan archivos dentro de ella. - -**¿Por qué es útil?** -Muchos sistemas modernos necesitan reaccionar a cambios en tiempo real (por ejemplo, subir un archivo automáticamente cuando se guarda, o recargar una web al cambiar el código). - -**Librerías usadas por lenguaje:** -- **Python**: `watchdog` (terceros, `pip install watchdog`) -- **Node.js/JavaScript**: `fs.watch` o `chokidar` (terceros, `npm install chokidar`) -- **Go**: `fsnotify` (terceros, `go get github.com/fsnotify/fsnotify`) -- **Rust**: `notify` (terceros, `cargo add notify`) - -**¿Qué retos usan esta base?** -- [Reto 04 — Vigilante de carpetas](https://github.com/Kaia-Alenia/nerve-community/issues/4) diff --git a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.go b/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.go deleted file mode 100644 index b6119e9..0000000 --- a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.go +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Ejemplo: Monitoreo de carpeta con fsnotify en Go - * - * Qué enseña este ejemplo: - * - Cómo usar un módulo externo de Go (github.com/fsnotify/fsnotify). - * - Cómo usar goroutines (go func) para ejecutar tareas en segundo plano. - * - Cómo usar un bucle for-select para leer eventos desde canales (channels). - * - * Para tu reto (Vigilante de carpetas con Nerve): - * Aplica esta misma estructura. En vez de solo hacer `log.Println`, cuando se - * detecte un evento (ej. Create o Write), usarás el cliente de Nerve para - * notificar a otros nodos. - * - * Glosario de funciones/conceptos: - * fsnotify.NewWatcher() — Crea un nuevo observador del sistema de archivos. - * defer watcher.Close() — Asegura que el observador se cierre al terminar la función (limpieza). - * watcher.Add(path) — Añade una ruta (archivo o carpeta) a la lista de vigilancia. - * <-watcher.Events — Lee (recibe) datos del canal de eventos del observador. Bloquea hasta que haya uno. - * select — Permite a una goroutine esperar múltiples operaciones de comunicación (canales). - */ - -package main - -import ( - "log" - "os" - - "github.com/fsnotify/fsnotify" -) - -func main() { - carpetaAVigilar := "./carpeta_prueba" - - // Creamos la carpeta si no existe (0755 son los permisos estándar de lectura/escritura/ejecución) - if _, err := os.Stat(carpetaAVigilar); os.IsNotExist(err) { - err = os.Mkdir(carpetaAVigilar, 0755) - if err != nil { - log.Fatal("Error al crear carpeta:", err) - } - log.Printf("Carpeta creada: %s\n", carpetaAVigilar) - } - - // 1. Creamos un nuevo observador (watcher) - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - // defer asegura que los recursos se liberen al terminar main() - defer watcher.Close() - - // 2. Iniciamos una goroutine (un hilo ligero en segundo plano) - // que estará escuchando constantemente por eventos o errores. - go func() { - for { - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - // Verificamos qué tipo de operación fue (Create, Write, Remove, Rename, Chmod) - if event.Has(fsnotify.Create) { - log.Printf("📁 [CREADO] Archivo: %s\n", event.Name) - } - if event.Has(fsnotify.Write) { - log.Printf("✏️ [MODIFICADO] Archivo: %s\n", event.Name) - } - if event.Has(fsnotify.Remove) { - log.Printf("🗑️ [ELIMINADO] Archivo: %s\n", event.Name) - } - if event.Has(fsnotify.Rename) { - log.Printf("🔄 [RENOMBRADO] Archivo: %s\n", event.Name) - } - - case err, ok := <-watcher.Errors: - if !ok { - return - } - log.Println("❌ Error:", err) - } - } - }() - - // 3. Añadimos la carpeta que queremos vigilar al watcher - err = watcher.Add(carpetaAVigilar) - if err != nil { - log.Fatal(err) - } - - log.Printf("👀 Vigilando la carpeta '%s'...", carpetaAVigilar) - log.Println("Crea, modifica o elimina archivos ahí para ver los eventos. Presiona Ctrl+C para salir.") - - // Bloqueamos el hilo principal para que el programa no termine inmediatamente. - // <-make(chan struct{}) se queda esperando infinitamente (hasta que lo mates con Ctrl+C). - <-make(chan struct{}) -} diff --git a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.js b/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.js deleted file mode 100644 index a8073d8..0000000 --- a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Ejemplo: Monitoreo de carpeta con fs.watch en Node.js - * - * Qué enseña este ejemplo: - * - Cómo usar el módulo nativo `fs` para vigilar cambios (fs.watch). - * - Cómo manejar eventos asíncronos y callbacks en Node.js. - * - * Para tu reto (Vigilante de carpetas con Nerve): - * Aplica esta misma estructura. En vez de solo hacer `console.log`, cuando se - * detecte un cambio, usarás tu cliente de Nerve para notificar a otros nodos. - * - * Glosario de funciones: - * fs.mkdirSync — Crea una carpeta de forma síncrona (bloqueando la ejecución hasta que termine). - * recursive — Opción para que mkdirSync cree también carpetas intermedias si no existen. - * fs.watch — Inicia la vigilancia sobre un directorio o archivo. Retorna un objeto "watcher". - * eventType — El tipo de evento detectado por fs.watch (generalmente 'rename' o 'change'). - * 'rename' ocurre al crear o eliminar, 'change' al modificar. - */ - -const fs = require('fs'); -const path = require('path'); - -const carpetaAVigilar = path.join(__dirname, 'carpeta_prueba'); - -// Creamos la carpeta si no existe -if (!fs.existsSync(carpetaAVigilar)) { - fs.mkdirSync(carpetaAVigilar, { recursive: true }); - console.log(`Carpeta creada: ${carpetaAVigilar}`); -} - -console.log(`👀 Vigilando la carpeta '${carpetaAVigilar}'...`); -console.log("Crea, modifica o elimina archivos ahí para ver los eventos. Presiona Ctrl+C para salir."); - -// fs.watch es nativo de Node.js. No requiere instalar nada extra. -// Le pasamos la ruta de la carpeta, y un callback que se ejecutará cada vez que haya un evento. -const watcher = fs.watch(carpetaAVigilar, (eventType, filename) => { - if (filename) { - // En Node.js nativo, 'rename' suele significar creación o eliminación. - // 'change' suele significar modificación del contenido. - if (eventType === 'rename') { - console.log(`📁 [CREADO/ELIMINADO] Archivo afectado: ${filename}`); - } else if (eventType === 'change') { - console.log(`✏️ [MODIFICADO] Archivo afectado: ${filename}`); - } - } else { - console.log('filename no proporcionado'); - } -}); - -// Manejo de errores -watcher.on('error', (error) => { - console.error('❌ Error en el observador:', error); -}); diff --git a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.py b/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.py deleted file mode 100644 index a0204fd..0000000 --- a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Ejemplo: Monitoreo de carpeta con watchdog - -Qué enseña este ejemplo: - - Cómo usar una librería externa (watchdog) para vigilar el sistema de archivos. - - Cómo usar manejadores de eventos (event handlers) para reaccionar a cambios. - - Cómo mantener el programa vivo con un loop (while True) + sleep. - -Para tu reto (Vigilante de carpetas con Nerve): - Aplica esta misma estructura. En vez de solo hacer `print`, cuando se cree un - archivo, usarás `cliente.broadcast()` o `cliente.send()` de Nerve para - notificar a otros nodos. - -Glosario de funciones/librerías: - FileSystemEventHandler — Una clase base que tiene métodos como on_created, on_modified, etc. - Observer — El objeto que se encarga de vigilar la carpeta en un hilo separado. - time.sleep(1) — Hace que el programa espere 1 segundo, evitando usar el 100% de la CPU. -""" - -import time -import os -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler - - -# Definimos cómo queremos reaccionar a los eventos -class MiManejador(FileSystemEventHandler): - - # Se llama automáticamente cuando se crea un archivo o carpeta - def on_created(self, event): - # event.src_path contiene la ruta del archivo afectado - print(f"📁 [NUEVO] Se ha creado: {event.src_path}") - - # Se llama cuando se modifica un archivo - def on_modified(self, event): - print(f"✏️ [MODIFICADO] Se ha modificado: {event.src_path}") - - # Se llama cuando se elimina un archivo - def on_deleted(self, event): - print(f"🗑️ [ELIMINADO] Se ha eliminado: {event.src_path}") - - -def main(): - carpeta_a_vigilar = "./carpeta_prueba" - - # Creamos la carpeta si no existe - if not os.path.exists(carpeta_a_vigilar): - os.makedirs(carpeta_a_vigilar) - print(f"Carpeta creada: {carpeta_a_vigilar}") - - # 1. Instanciamos nuestro manejador y el observador - manejador = MiManejador() - observador = Observer() - - # 2. Le decimos al observador qué carpeta vigilar, y qué manejador usar - # recursive=False significa que no vigilará subcarpetas. - observador.schedule(manejador, path=carpeta_a_vigilar, recursive=False) - - # 3. Iniciamos el observador (esto ocurre en segundo plano) - observador.start() - print(f"👀 Vigilando la carpeta '{carpeta_a_vigilar}'...") - print( - "Crea, modifica o elimina archivos ahí para ver los eventos. Presiona Ctrl+C para salir." - ) - - try: - # Mantenemos el hilo principal vivo - while True: - time.sleep(1) - except KeyboardInterrupt: - print("\nDeteniendo observador...") - observador.stop() - - # Esperamos a que termine de cerrarse limpiamente - observador.join() - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.rs b/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.rs deleted file mode 100644 index b03046b..0000000 --- a/ejemplos/nerve/04-vigilante-de-carpetas/ejemplo.rs +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Ejemplo: Monitoreo de carpeta con notify en Rust - * - * Qué enseña este ejemplo: - * - Cómo usar un crate externo (`notify`) para eventos de sistema de archivos. - * - Cómo manejar canales (mpsc) entre hilos para procesar eventos asíncronamente. - * - Manejo de errores con match y Result en el contexto de eventos continuos. - * - * Para tu reto (Vigilante de carpetas con Nerve): - * Aplica esta misma estructura. En vez de solo hacer `println!`, cuando - * ocurra un EventKind::Create o EventKind::Modify, enviarás un mensaje - * a los demás nodos usando el cliente Nerve. - * - * Glosario: - * std::sync::mpsc::channel — Crea un canal de comunicación de Un Productor a Un Consumidor (MPSC). - * Devuelve un (Sender, Receiver) (tx, rx). - * notify::Watcher — El trait principal para iniciar el monitoreo. - * std::fs::create_dir_all — Crea un directorio y todos sus padres si no existen (como mkdir -p). - * match — Estructura de control poderosa en Rust que evalúa todos los posibles - * casos de un enum o valor. - */ - -use notify::{Event, EventKind, RecursiveMode, Watcher}; -use std::fs; -use std::path::Path; -use std::sync::mpsc::channel; - -fn main() { - let carpeta_a_vigilar = "./carpeta_prueba"; - - // Creamos la carpeta si no existe - if !Path::new(carpeta_a_vigilar).exists() { - if let Err(e) = fs::create_dir_all(carpeta_a_vigilar) { - eprintln!("Error al crear carpeta: {}", e); - return; - } - println!("Carpeta creada: {}", carpeta_a_vigilar); - } - - // 1. Creamos un canal para recibir eventos. - // 'tx' (transmisor) enviará los eventos, 'rx' (receptor) los leerá en nuestro hilo principal. - let (tx, rx) = channel(); - - // 2. Creamos el observador (watcher). - // Usamos el "recommended_watcher" que elige el mejor método según el sistema operativo - // (inotify en Linux, FSEvents en macOS, ReadDirectoryChangesW en Windows). - let mut watcher = match notify::recommended_watcher(tx) { - Ok(w) => w, - Err(e) => { - eprintln!("Error creando el watcher: {:?}", e); - return; - } - }; - - // 3. Añadimos la ruta a vigilar. - // RecursiveMode::NonRecursive indica que NO vigilaremos subcarpetas internas. - if let Err(e) = watcher.watch(Path::new(carpeta_a_vigilar), RecursiveMode::NonRecursive) { - eprintln!("Error al vigilar la ruta: {:?}", e); - return; - } - - println!("👀 Vigilando la carpeta '{}'...", carpeta_a_vigilar); - println!("Crea, modifica o elimina archivos ahí para ver los eventos. Presiona Ctrl+C para salir."); - - // 4. Bucle infinito para recibir eventos a través del receptor (rx). - // Esto bloqueará el hilo principal esperando eventos. - loop { - match rx.recv() { - Ok(event_result) => { - match event_result { - Ok(event) => procesar_evento(event), - Err(e) => eprintln!("❌ Error de evento: {:?}", e), - } - } - Err(e) => { - eprintln!("Error leyendo el canal: {:?}", e); - break; - } - } - } -} - -fn procesar_evento(event: Event) { - // Los eventos en `notify` tienen un `kind` (tipo) y `paths` (rutas afectadas). - match event.kind { - EventKind::Create(_) => { - println!("📁 [CREADO] {:?}", event.paths); - } - EventKind::Modify(_) => { - println!("✏️ [MODIFICADO] {:?}", event.paths); - } - EventKind::Remove(_) => { - println!("🗑️ [ELIMINADO] {:?}", event.paths); - } - _ => { - // Ignoramos otros eventos como accesos de lectura o cambios de metadatos (opcional). - } - } -} diff --git a/ejemplos/nerve/05-logger-distribuido/README.md b/ejemplos/nerve/05-logger-distribuido/README.md deleted file mode 100644 index 91afc16..0000000 --- a/ejemplos/nerve/05-logger-distribuido/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Logger Distribuido (Manejo de Logs) - -**¿Qué muestra este ejemplo?** -Muestra cómo capturar la fecha y hora actual (timestamps) y cómo escribir información en un archivo en modo *append* (agregar al final sin borrar lo anterior), que es la base fundamental de cualquier sistema de logs. - -**¿Por qué es útil?** -Todos los sistemas en producción necesitan un registro de qué ha pasado (cuándo inició, cuándo ocurrió un error, etc.). - -**Conceptos clave:** -- **Append**: Al abrir un archivo en modo append, todo lo nuevo se escribe al final. -- **Timestamps**: Formatear la fecha/hora en un formato legible, típicamente `[YYYY-MM-DD HH:MM:SS]`. - -**¿Qué retos usan esta base?** -- [Reto 05 — Logger distribuido](https://github.com/Kaia-Alenia/nerve-community/issues/5) diff --git a/ejemplos/nerve/05-logger-distribuido/ejemplo.go b/ejemplos/nerve/05-logger-distribuido/ejemplo.go deleted file mode 100644 index 28491d6..0000000 --- a/ejemplos/nerve/05-logger-distribuido/ejemplo.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Ejemplo: Escritura de Logs en Go - * - * Qué enseña este ejemplo: - * - Cómo obtener la fecha y hora actual usando `time.Now()`. - * - Cómo formatear una fecha en Go usando su sistema de fechas de referencia. - * - Cómo abrir un archivo en modo Append usando `os.OpenFile` y los flags correctos. - * - * Para tu reto (Logger Distribuido con Nerve): - * En vez de llamar a `escribirLog` con datos estáticos, tu receptor de Nerve - * escuchará mensajes de otros nodos y llamará a tu función de log para - * registrarlos en un archivo central. - * - * Glosario: - * time.Now() — Obtiene la fecha y hora actual. - * Format("2006-01-02 15:04:05") — Formatea la fecha. OJO: Go usa una fecha de referencia exacta - * (2 de enero de 2006 a las 15:04:05) para definir formatos, NO usa %Y-%m-%d. - * os.OpenFile — Abre un archivo con opciones avanzadas. - * os.O_APPEND — Flag: si escribes, hazlo al final del archivo. - * os.O_CREATE — Flag: si el archivo no existe, créalo. - * os.O_WRONLY — Flag: abre el archivo solo para escritura. - * f.WriteString — Escribe un string en el archivo abierto. - */ - -package main - -import ( - "fmt" - "log" - "os" - "time" -) - -const ArchivoLog = "sistema.log" - -func escribirLog(nivel string, mensaje string) { - // 1. Obtenemos el timestamp actual y lo formateamos - // Recordatorio: "2006-01-02 15:04:05" es la forma de Go para decir "YYYY-MM-DD HH:MM:SS" - ahora := time.Now() - timestampFormateado := ahora.Format("2006-01-02 15:04:05") - - // 2. Construimos la línea de log final - // Ejemplo: [2026-07-17 15:30:05] [INFO] El servidor ha iniciado correctamente - lineaLog := fmt.Sprintf("[%s] [%s] %s\n", timestampFormateado, nivel, mensaje) - - // 3. Imprimimos en consola (opcional, para verlo en vivo) - fmt.Print(lineaLog) - - // 4. Abrimos el archivo en modo "Append" (añadir al final) - // Si no existe (O_CREATE), lo crea. Solo escritura (O_WRONLY). - // Permisos 0644 (lectura y escritura para el dueño, solo lectura para otros). - f, err := os.OpenFile(ArchivoLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - log.Printf("Error abriendo el archivo de log: %v", err) - return - } - defer f.Close() - - // 5. Escribimos la línea en el archivo - if _, err := f.WriteString(lineaLog); err != nil { - log.Printf("Error escribiendo en el archivo de log: %v", err) - } -} - -func main() { - fmt.Printf("Escribiendo logs en '%s'...\n\n", ArchivoLog) - - escribirLog("INFO", "Sistema iniciado.") - escribirLog("DEBUG", "Conexión a la base de datos establecida en 14ms.") - escribirLog("WARNING", "Poco espacio en disco (15% restante).") - escribirLog("ERROR", "Fallo al procesar el archivo 'datos.csv': Archivo no encontrado.") - escribirLog("INFO", "Apagando sistema de forma segura.") - - fmt.Printf("\nRevisa el archivo '%s' para ver los resultados guardados.\n", ArchivoLog) -} diff --git a/ejemplos/nerve/05-logger-distribuido/ejemplo.js b/ejemplos/nerve/05-logger-distribuido/ejemplo.js deleted file mode 100644 index 69811c0..0000000 --- a/ejemplos/nerve/05-logger-distribuido/ejemplo.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Ejemplo: Escritura de Logs en Node.js - * - * Qué enseña este ejemplo: - * - Cómo obtener la fecha y hora actual usando el objeto nativo `Date`. - * - Cómo formatear una fecha al estilo ISO o local. - * - Cómo añadir texto al final de un archivo usando `fs.appendFileSync` (modo append). - * - * Para tu reto (Logger Distribuido con Nerve): - * En vez de llamar a `escribirLog` con datos estáticos, tu receptor de Nerve - * escuchará mensajes de otros nodos y llamará a tu función de log para - * registrarlos en un archivo central. - * - * Glosario: - * new Date() — Crea un nuevo objeto de fecha con la fecha y hora actual. - * toISOString() — Formatea la fecha al estándar ISO (ej: 2026-07-17T15:30:05.123Z). - * fs.appendFileSync — Escribe en un archivo síncronamente agregando el texto al final. - * Si el archivo no existe, lo crea. - */ - -const fs = require('fs'); -const path = require('path'); - -// El archivo donde guardaremos los logs -const ARCHIVO_LOG = path.join(__dirname, 'sistema.log'); - -function escribirLog(nivel, mensaje) { - // 1. Obtenemos el timestamp actual y lo formateamos - // toISOString da un formato como "2026-07-17T15:30:05.123Z" - // Reemplazamos la "T" por un espacio y quitamos los milisegundos y la Z final para que luzca mejor. - const ahora = new Date(); - const timestampFormateado = ahora.toISOString().replace('T', ' ').substring(0, 19); - - // 2. Construimos la línea de log final - // Ejemplo: [2026-07-17 15:30:05] [INFO] El servidor ha iniciado correctamente - const lineaLog = `[${timestampFormateado}] [${nivel}] ${mensaje}\n`; - - // 3. Imprimimos en consola (opcional, para verlo en vivo) - // Usamos process.stdout.write para evitar el salto de línea extra que pone console.log - process.stdout.write(lineaLog); - - // 4. Escribimos en el archivo agregando al final (append) - try { - fs.appendFileSync(ARCHIVO_LOG, lineaLog, 'utf8'); - } catch (error) { - console.error('Error al escribir en el archivo de log:', error); - } -} - -function main() { - console.log(`Escribiendo logs en '${ARCHIVO_LOG}'...\n`); - - escribirLog("INFO", "Sistema iniciado."); - escribirLog("DEBUG", "Conexión a la base de datos establecida en 14ms."); - escribirLog("WARNING", "Poco espacio en disco (15% restante)."); - escribirLog("ERROR", "Fallo al procesar el archivo 'datos.csv': Archivo no encontrado."); - escribirLog("INFO", "Apagando sistema de forma segura."); - - console.log(`\nRevisa el archivo '${ARCHIVO_LOG}' para ver los resultados guardados.`); -} - -main(); diff --git a/ejemplos/nerve/05-logger-distribuido/ejemplo.py b/ejemplos/nerve/05-logger-distribuido/ejemplo.py deleted file mode 100644 index 6d721f4..0000000 --- a/ejemplos/nerve/05-logger-distribuido/ejemplo.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Ejemplo: Escritura de Logs en Python - -Qué enseña este ejemplo: - - Cómo obtener la fecha y hora actual con `datetime`. - - Cómo abrir un archivo en modo "append" ("a") para no sobreescribir datos anteriores. - - Cómo formatear strings para que luzcan como logs profesionales. - -Para tu reto (Logger Distribuido con Nerve): - En vez de llamar a `escribir_log` con datos estáticos, tu receptor de Nerve - escuchará mensajes de otros nodos y llamará a tu función de log para - registrarlos en un archivo central. - -Glosario: - datetime.now() — Obtiene la fecha y hora exactas del sistema. - strftime("%Y-%m-%d...") — Da formato a la fecha (String Format Time). %Y es año, %m es mes, etc. - open(..., "a") — Abre el archivo en modo "append" (agregar al final). Si usas "w", borra todo. -""" - -from datetime import datetime - -# El archivo donde guardaremos los logs -ARCHIVO_LOG = "sistema.log" - - -def escribir_log(nivel: str, mensaje: str): - """ - Escribe un mensaje de log en el archivo con un timestamp y un nivel (INFO, ERROR, etc.). - """ - # 1. Obtenemos el timestamp actual - ahora = datetime.now() - - # 2. Le damos un formato legible, ej: 2026-07-17 15:30:05 - timestamp_formateado = ahora.strftime("%Y-%m-%d %H:%M:%S") - - # 3. Construimos la línea de log final - # Ejemplo: [2026-07-17 15:30:05] [INFO] El servidor ha iniciado correctamente - linea_log = f"[{timestamp_formateado}] [{nivel}] {mensaje}\n" - - # 4. Imprimimos en consola (opcional, para verlo en vivo) - print(linea_log, end="") - - # 5. Escribimos en el archivo en modo "a" (append) - with open(ARCHIVO_LOG, "a", encoding="utf-8") as f: - f.write(linea_log) - - -def main(): - print(f"Escribiendo logs en '{ARCHIVO_LOG}'...\n") - - escribir_log("INFO", "Sistema iniciado.") - escribir_log("DEBUG", "Conexión a la base de datos establecida en 14ms.") - escribir_log("WARNING", "Poco espacio en disco (15% restante).") - escribir_log( - "ERROR", "Fallo al procesar el archivo 'datos.csv': Archivo no encontrado." - ) - escribir_log("INFO", "Apagando sistema de forma segura.") - - print(f"\nRevisa el archivo '{ARCHIVO_LOG}' para ver los resultados guardados.") - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/05-logger-distribuido/ejemplo.rs b/ejemplos/nerve/05-logger-distribuido/ejemplo.rs deleted file mode 100644 index 1f34251..0000000 --- a/ejemplos/nerve/05-logger-distribuido/ejemplo.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Ejemplo: Escritura de Logs en Rust - * - * Qué enseña este ejemplo: - * - Cómo obtener la fecha y hora actual usando el crate externo `chrono`. - * - Cómo formatear una fecha en Rust. - * - Cómo abrir un archivo en modo Append usando `OpenOptions`. - * - Manejo seguro de errores de escritura usando `unwrap_or_else` o `if let Err`. - * - * Para tu reto (Logger Distribuido con Nerve): - * En vez de llamar a `escribir_log` con datos estáticos, tu receptor de Nerve - * escuchará mensajes de otros nodos y llamará a tu función de log para - * registrarlos en un archivo central. - * - * Glosario: - * chrono::Local::now() — Obtiene la fecha y hora local del sistema. - * format("%Y-%m-%d...") — Da formato a la fecha (requiere el crate chrono). - * OpenOptions::new() — Permite configurar cómo se abre un archivo. - * .append(true) — Configura la apertura para añadir al final. - * .create(true) — Crea el archivo si no existe. - * writeln! — Macro que escribe texto en un archivo con un salto de línea al final. - */ - -use chrono::Local; -use std::fs::OpenOptions; -use std::io::Write; - -const ARCHIVO_LOG: &str = "sistema.log"; - -fn escribir_log(nivel: &str, mensaje: &str) { - // 1. Obtenemos el timestamp actual y lo formateamos usando el crate `chrono` - let ahora = Local::now(); - let timestamp_formateado = ahora.format("%Y-%m-%d %H:%M:%S").to_string(); - - // 2. Construimos la línea de log final - // Ejemplo: [2026-07-17 15:30:05] [INFO] El servidor ha iniciado correctamente - let linea_log = format!("[{}] [{}] {}", timestamp_formateado, nivel, mensaje); - - // 3. Imprimimos en consola (opcional, para verlo en vivo) - println!("{}", linea_log); - - // 4. Abrimos el archivo en modo "Append" (añadir al final) y creamos si no existe - let archivo_resultado = OpenOptions::new() - .create(true) - .append(true) - .open(ARCHIVO_LOG); - - // 5. Verificamos si se abrió correctamente y escribimos - match archivo_resultado { - Ok(mut f) => { - if let Err(e) = writeln!(f, "{}", linea_log) { - eprintln!("Error al escribir en el log: {}", e); - } - } - Err(e) => { - eprintln!("Error al abrir el archivo de log: {}", e); - } - } -} - -fn main() { - println!("Escribiendo logs en '{}'...\n", ARCHIVO_LOG); - - escribir_log("INFO", "Sistema iniciado."); - escribir_log("DEBUG", "Conexión a la base de datos establecida en 14ms."); - escribir_log("WARNING", "Poco espacio en disco (15% restante)."); - escribir_log("ERROR", "Fallo al procesar el archivo 'datos.csv': Archivo no encontrado."); - escribir_log("INFO", "Apagando sistema de forma segura."); - - println!("\nRevisa el archivo '{}' para ver los resultados guardados.", ARCHIVO_LOG); -} diff --git a/ejemplos/nerve/06-puente-de-progreso-gif/README.md b/ejemplos/nerve/06-puente-de-progreso-gif/README.md deleted file mode 100644 index b387fc7..0000000 --- a/ejemplos/nerve/06-puente-de-progreso-gif/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Puente de Progreso GIF (Manipulación de Imágenes) - -**¿Qué muestra este ejemplo?** -Muestra cómo crear una imagen en blanco desde cero y dibujar un rectángulo en ella, simulando una barra de progreso, usando librerías de manipulación de imágenes. - -**¿Por qué es útil?** -Generar gráficos dinámicos o alterar imágenes programáticamente es muy común para crear dashboards visuales, bots que devuelven imágenes o, en este caso, una barra de progreso que se puede ver en el navegador. - -**Librerías usadas por lenguaje:** -- **Python**: `Pillow` (instalación: `pip install Pillow`) -- **Node.js/JavaScript**: `jimp` (instalación: `npm install jimp`) - usamos Jimp porque no requiere compilación nativa y es fácil para principiantes. -- **Go**: `image`, `image/color`, `image/draw` (nativas de Go, no requiere instalar nada). -- **Rust**: `image` (instalación: `cargo add image`) e `imageproc` (`cargo add imageproc`). - -**¿Qué retos usan esta base?** -- [Reto 06 — Puente de progreso GIF](https://github.com/Kaia-Alenia/nerve-community/issues/6) diff --git a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.go b/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.go deleted file mode 100644 index 2f6b454..0000000 --- a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.go +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Ejemplo: Dibujar una barra de progreso nativa en Go - * - * Qué enseña este ejemplo: - * - Cómo usar la librería nativa de Go `image` y `image/draw`. - * - Cómo dibujar rectángulos en un lienzo RGBA. - * - Cómo codificar y guardar una imagen en formato PNG (image/png). - * - * Para tu reto (Puente de progreso GIF con Nerve): - * En lugar de un porcentaje fijo, tu receptor de Nerve recibirá actualizaciones - * (ej. 10%, 20%), y con cada actualización dibujarás la barra correspondiente - * y guardarás la imagen. - * - * Glosario: - * image.NewRGBA() — Crea una nueva imagen de un tamaño específico que soporta color. - * color.RGBA{} — Define un color usando rojo, verde, azul y alpha (transparencia). - * draw.Draw() — Copia una imagen/color sobre otra en un área específica. - * png.Encode() — Escribe los datos de la imagen en formato PNG a un archivo. - */ - -package main - -import ( - "fmt" - "image" - "image/color" - "image/draw" - "image/png" - "log" - "os" -) - -func generarBarraProgreso(porcentaje int, rutaSalida string) { - ancho := 400 - alto := 50 - - // Aseguramos que el porcentaje esté entre 0 y 100 - if porcentaje < 0 { - porcentaje = 0 - } else if porcentaje > 100 { - porcentaje = 100 - } - - // Calculamos el ancho de la barra - anchoProgreso := int((float64(porcentaje) / 100.0) * float64(ancho)) - - // 1. Definimos los colores - colorFondo := color.RGBA{220, 220, 220, 255} // Gris claro - colorBarra := color.RGBA{50, 205, 50, 255} // Verde lima - - // 2. Creamos el lienzo (la imagen base) - // image.Rect(x0, y0, x1, y1) define los límites - lienzo := image.NewRGBA(image.Rect(0, 0, ancho, alto)) - - // 3. Llenamos todo el lienzo con el color de fondo - // draw.Src significa que reemplazará el pixel completamente - draw.Draw(lienzo, lienzo.Bounds(), &image.Uniform{colorFondo}, image.Point{}, draw.Src) - - // 4. Dibujamos el rectángulo de progreso - // Creamos un rectángulo que va desde (0,0) hasta (anchoProgreso, alto) - rectProgreso := image.Rect(0, 0, anchoProgreso, alto) - draw.Draw(lienzo, rectProgreso, &image.Uniform{colorBarra}, image.Point{}, draw.Src) - - // 5. Guardamos la imagen en un archivo - archivo, err := os.Create(rutaSalida) - if err != nil { - log.Printf("Error al crear el archivo %s: %v", rutaSalida, err) - return - } - defer archivo.Close() - - if err := png.Encode(archivo, lienzo); err != nil { - log.Printf("Error al codificar PNG: %v", err) - return - } - - fmt.Printf("✅ Imagen guardada en: %s con un progreso de %d%%\n", rutaSalida, porcentaje) -} - -func main() { - fmt.Println("Generando ejemplos de barras de progreso...\n") - - generarBarraProgreso(25, "progreso_25.png") - generarBarraProgreso(75, "progreso_75.png") - generarBarraProgreso(100, "progreso_100.png") - - fmt.Println("\nRevisa tu carpeta, deberías ver tres archivos .png nuevos.") -} diff --git a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.js b/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.js deleted file mode 100644 index b9a38c8..0000000 --- a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Ejemplo: Dibujar una barra de progreso con Jimp (Node.js) - * - * Qué enseña este ejemplo: - * - Cómo crear una imagen nueva programáticamente sin usar canvas nativo. - * - Cómo manipular píxeles directamente o usar plugins de Jimp. - * - Cómo guardar una imagen asíncronamente. - * - * Para tu reto (Puente de progreso GIF con Nerve): - * En lugar de un porcentaje fijo, tu receptor de Nerve recibirá actualizaciones - * (ej. 10%, 20%), y con cada actualización dibujarás la barra correspondiente - * y guardarás la imagen. - * - * Glosario: - * new Jimp(ancho, alto, color) — Instancia una imagen nueva con fondo sólido. - * image.scan() — Recorre los píxeles de un área para modificarlos. - * image.writeAsync() — Guarda la imagen generada (devuelve una promesa). - */ - -const Jimp = require('jimp'); - -async function generarBarraProgreso(porcentaje, rutaSalida) { - const ancho = 400; - const alto = 50; - - // Aseguramos que el porcentaje esté entre 0 y 100 - const p = Math.max(0, Math.min(100, porcentaje)); - - // Calculamos el ancho de la barra verde - const anchoProgreso = Math.floor((p / 100) * ancho); - - // 1. Creamos el lienzo base (fondo gris claro en Hexadecimal) - // 0xDDDDDDFF = R: 221, G: 221, B: 221, Alpha: 255 (sólido) - const imagen = new Jimp(ancho, alto, 0xddddddff); - - // 2. Coloreamos la porción correspondiente a la barra de progreso - // Jimp no tiene una función nativa de dibujar rectángulos sin plugins, - // pero podemos hacerlo fácilmente modificando los píxeles del área deseada. - // El color verde lima es 0x32CD32FF - const colorVerde = 0x32cd32ff; - - imagen.scan(0, 0, anchoProgreso, alto, function (x, y, idx) { - // En lugar de acceder a los bytes, escribimos el color hexadecimal completo - this.setPixelColor(colorVerde, x, y); - }); - - // 3. Guardamos la imagen (es una operación asíncrona) - try { - await imagen.writeAsync(rutaSalida); - console.log(`✅ Imagen guardada en: ${rutaSalida} con un progreso de ${p}%`); - } catch (error) { - console.error("Error al guardar la imagen:", error); - } -} - -async function main() { - console.log("Generando ejemplos de barras de progreso...\n"); - - await generarBarraProgreso(25, "progreso_25.png"); - await generarBarraProgreso(75, "progreso_75.png"); - await generarBarraProgreso(100, "progreso_100.png"); - - console.log("\nRevisa tu carpeta, deberías ver tres archivos .png nuevos."); -} - -main(); diff --git a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.py b/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.py deleted file mode 100644 index 68652ef..0000000 --- a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Ejemplo: Dibujar una barra de progreso con Pillow (Python) - -Qué enseña este ejemplo: - - Cómo crear una imagen desde cero. - - Cómo dibujar formas geométricas (rectángulos) sobre la imagen. - - Cómo guardar la imagen generada en el disco. - -Para tu reto (Puente de progreso GIF con Nerve): - En lugar de un porcentaje fijo, tu receptor de Nerve recibirá actualizaciones - (ej. 10%, 20%), y con cada actualización dibujarás la barra correspondiente - y guardarás la imagen para que una página web u otro cliente pueda verla. - -Glosario: - Image.new() — Crea un lienzo en blanco. Pide el modo ("RGB"), tamaño (ancho, alto) y color de fondo. - ImageDraw.Draw() — Crea un objeto que permite dibujar sobre la imagen. - draw.rectangle() — Dibuja un rectángulo dadas las coordenadas [x0, y0, x1, y1] y un color. -""" - -from PIL import Image, ImageDraw - - -def generar_barra_progreso(porcentaje: int, ruta_salida: str): - """ - Crea una imagen con una barra de progreso que representa el porcentaje dado. - """ - # 1. Definimos dimensiones de la imagen - ancho = 400 - alto = 50 - - # 2. Creamos una nueva imagen con fondo gris claro - color_fondo = (220, 220, 220) # RGB - imagen = Image.new("RGB", (ancho, alto), color_fondo) - - # 3. Preparamos el objeto para dibujar - draw = ImageDraw.Draw(imagen) - - # 4. Calculamos el ancho de la barra verde (progreso) - # Si porcentaje es 50, el ancho será el 50% de 400 = 200. - porcentaje = max(0, min(100, porcentaje)) # Aseguramos que esté entre 0 y 100 - ancho_progreso = int((porcentaje / 100) * ancho) - - # 5. Dibujamos el rectángulo verde - # Las coordenadas son [x_inicial, y_inicial, x_final, y_final] - # Empieza en (0,0) y termina en (ancho_progreso, alto) - color_barra = (50, 205, 50) # Verde lima - draw.rectangle([0, 0, ancho_progreso, alto], fill=color_barra) - - # 6. Guardamos la imagen - imagen.save(ruta_salida) - print(f"✅ Imagen guardada en: {ruta_salida} con un progreso de {porcentaje}%") - - -def main(): - print("Generando ejemplos de barras de progreso...\n") - - # Generamos tres imágenes distintas - generar_barra_progreso(25, "progreso_25.png") - generar_barra_progreso(75, "progreso_75.png") - generar_barra_progreso(100, "progreso_100.png") - - print("\nRevisa tu carpeta, deberías ver tres archivos .png nuevos.") - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.rs b/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.rs deleted file mode 100644 index d3f141d..0000000 --- a/ejemplos/nerve/06-puente-de-progreso-gif/ejemplo.rs +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Ejemplo: Dibujar una barra de progreso en Rust con el crate `image` - * - * Qué enseña este ejemplo: - * - Cómo usar el crate externo `image` para crear un lienzo. - * - Cómo manipular los píxeles de una imagen usando bucles `for`. - * - Cómo guardar una imagen en disco. - * - * Para tu reto (Puente de progreso GIF con Nerve): - * En lugar de un porcentaje fijo, tu receptor de Nerve recibirá actualizaciones - * (ej. 10%, 20%), y con cada actualización dibujarás la barra correspondiente - * y guardarás la imagen. - * - * Glosario: - * RgbImage::new(w, h) — Crea un nuevo búfer de imagen RGB de ancho `w` y alto `h`. - * Rgb([r, g, b]) — Define un píxel con colores Rojo, Verde y Azul (0-255). - * put_pixel(x, y, c) — Pinta el píxel en las coordenadas (x, y) con el color `c`. - * save(ruta) — Guarda la imagen inferiendo el formato (ej. PNG) a partir de la extensión. - */ - -use image::{Rgb, RgbImage}; -use std::cmp; - -fn generar_barra_progreso(porcentaje: i32, ruta_salida: &str) { - let ancho = 400; - let alto = 50; - - // Aseguramos que el porcentaje esté entre 0 y 100 - let p = cmp::max(0, cmp::min(100, porcentaje)); - - // Calculamos el ancho de la barra - let ancho_progreso = ((p as f32 / 100.0) * (ancho as f32)) as u32; - - // 1. Creamos la imagen base (RgbImage) - let mut imagen = RgbImage::new(ancho, alto); - - let color_fondo = Rgb([220, 220, 220]); // Gris claro - let color_barra = Rgb([50, 205, 50]); // Verde lima - - // 2. Coloreamos la imagen iterando por los píxeles (x, y) - for y in 0..alto { - for x in 0..ancho { - if x < ancho_progreso { - // Si el píxel está dentro de la barra de progreso, lo pintamos verde - imagen.put_pixel(x, y, color_barra); - } else { - // Si no, lo pintamos del color de fondo - imagen.put_pixel(x, y, color_fondo); - } - } - } - - // 3. Guardamos la imagen - match imagen.save(ruta_salida) { - Ok(_) => println!("✅ Imagen guardada en: {} con un progreso de {}%", ruta_salida, p), - Err(e) => eprintln!("Error al guardar {}: {}", ruta_salida, e), - } -} - -fn main() { - println!("Generando ejemplos de barras de progreso...\n"); - - generar_barra_progreso(25, "progreso_25.png"); - generar_barra_progreso(75, "progreso_75.png"); - generar_barra_progreso(100, "progreso_100.png"); - - println!("\nRevisa tu carpeta, deberías ver tres archivos .png nuevos."); -} diff --git a/ejemplos/nerve/07-bot-discord-nerve/README.md b/ejemplos/nerve/07-bot-discord-nerve/README.md deleted file mode 100644 index 9b9fd2e..0000000 --- a/ejemplos/nerve/07-bot-discord-nerve/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Bot de Discord (Básicos de API) - -**¿Qué muestra este ejemplo?** -Muestra cómo crear un bot básico de Discord que se conecta a los servidores, lee los mensajes entrantes y responde con un "¡Hola!". - -**¿Por qué es útil?** -La integración de sistemas de backend con plataformas de mensajería como Discord, Slack o Telegram es una habilidad muy valorada. - -**Librerías usadas por lenguaje:** -- **Python**: `discord.py` (`pip install discord.py`) -- **Node.js/JavaScript**: `discord.js` (`npm install discord.js`) -- **Go**: `discordgo` (`go get github.com/bwmarrin/discordgo`) -- **Rust**: `serenity` (`cargo add serenity tokio`) - -**¿Qué retos usan esta base?** -- [Reto 07 — Bot Discord Nerve](https://github.com/Kaia-Alenia/nerve-community/issues/7) - -*Nota: Para correr estos ejemplos necesitas un `TOKEN` válido de un bot de Discord que hayas creado en el [Portal de Desarrolladores de Discord](https://discord.com/developers/applications).* diff --git a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.go b/ejemplos/nerve/07-bot-discord-nerve/ejemplo.go deleted file mode 100644 index a000d5a..0000000 --- a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.go +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Ejemplo: Bot básico con discordgo - * - * Qué enseña este ejemplo: - * - Cómo configurar e iniciar una sesión de discordgo en Go. - * - Configurar los "Intents" para especificar qué eventos quieres. - * - Añadir un "handler" (manejador) para eventos de nuevos mensajes. - * - * Para tu reto (Bot Discord Nerve): - * En vez de solo responder con "¡Hola!", conectarás el bot de Discord con - * tu cliente Nerve. Cuando llegue un mensaje a Discord, usarás tu cliente - * Nerve para notificar a la red, y viceversa. - * - * Glosario: - * discordgo.New() — Crea una nueva sesión del bot. - * dg.Identify.Intents — Define qué eventos de Discord recibirá el bot (ej. GuildMessages). - * dg.AddHandler() — Registra una función que se llamará cuando ocurra un evento. - * s.ChannelMessageSend() — Envía un mensaje a un canal de Discord. - */ - -package main - -import ( - "fmt" - "os" - "os/signal" - "strings" - "syscall" - - "github.com/bwmarrin/discordgo" -) - -func main() { - // Reemplaza esto con tu token real o usa variables de entorno - token := os.Getenv("DISCORD_TOKEN") - if token == "" { - token = "TU_TOKEN_AQUI" - } - - if token == "TU_TOKEN_AQUI" { - fmt.Println("⚠️ ADVERTENCIA: Reemplaza 'TU_TOKEN_AQUI' con el token de tu bot de Discord.") - fmt.Println("Puedes obtenerlo en: https://discord.com/developers/applications") - return - } - - // 1. Crear una nueva sesión de Discord usando el token del bot - dg, err := discordgo.New("Bot " + token) - if err != nil { - fmt.Println("Error creando la sesión de Discord:", err) - return - } - - // 2. Registrar el manejador (handler) para el evento MessageCreate - dg.AddHandler(messageCreate) - - // 3. Configurar los Intents (permisos). Necesitamos los de mensajes. - // Nota: Para MessageContent, asegúrate de habilitarlo en el portal de desarrolladores. - dg.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsMessageContent - - // 4. Abrir una conexión con Discord - err = dg.Open() - if err != nil { - fmt.Println("Error abriendo conexión:", err) - return - } - - // Esperar hasta que se reciba una señal para terminar (CTRL+C) - fmt.Println("El bot está funcionando. Presiona CTRL-C para salir.") - sc := make(chan os.Signal, 1) - signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) - <-sc - - // 5. Cerrar la sesión de forma limpia - dg.Close() -} - -// messageCreate se llamará CADA VEZ que se envíe un mensaje a un canal al que el bot tenga acceso -func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { - // Ignorar todos los mensajes creados por el bot mismo - if m.Author.ID == s.State.User.ID { - return - } - - // Si el mensaje dice "ping", responder con "pong" - if strings.ToLower(m.Content) == "ping" { - fmt.Printf("[%s] dijo ping, respondiendo...\n", m.Author.Username) - - // Enviar el mensaje al mismo canal donde se recibió el ping - s.ChannelMessageSend(m.ChannelID, "pong 🏓") - } -} diff --git a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.js b/ejemplos/nerve/07-bot-discord-nerve/ejemplo.js deleted file mode 100644 index 297fe08..0000000 --- a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Ejemplo: Bot básico con discord.js - * - * Qué enseña este ejemplo: - * - Cómo configurar e iniciar un cliente de discord.js. - * - El uso de "GatewayIntents" para especificar qué eventos quieres. - * - Manejo de eventos con `.on` y `.once`. - * - * Para tu reto (Bot Discord Nerve): - * En vez de solo responder con "¡Hola!", conectarás el bot de Discord con - * tu cliente Nerve. Cuando llegue un mensaje a Discord, usarás tu cliente - * Nerve para notificar a la red, y viceversa. - * - * Glosario: - * Client() — Instancia principal del bot de Discord. - * GatewayIntentBits — Sistema de permisos de Discord. `GuildMessages` lee eventos de mensajes, - * y `MessageContent` es necesario para leer el texto en sí. - * client.once('ready') — Se ejecuta UNA SOLA VEZ cuando el bot termina de conectarse. - * client.on('messageCreate') — Se ejecuta CADA VEZ que se envía un mensaje. - * message.reply() — Envía un mensaje respondiendo (etiquetando) al usuario original. - */ - -const { Client, GatewayIntentBits } = require('discord.js'); - -// 1. Configurar el cliente y los Intents -const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, // Para poder estar en servidores - GatewayIntentBits.GuildMessages, // Para recibir eventos de mensajes - GatewayIntentBits.MessageContent // Para poder leer el contenido de los mensajes - ] -}); - -// 2. Evento 'ready': Cuando el bot se conecta exitosamente -client.once('ready', () => { - console.log(`✅ Conectado exitosamente como ${client.user.tag}`); -}); - -// 3. Evento 'messageCreate': Cuando alguien envía un mensaje -client.on('messageCreate', async (message) => { - // Ignorar mensajes enviados por bots (incluyendo el nuestro) - if (message.author.bot) return; - - // Si alguien dice "ping", respondemos "pong" - if (message.content.toLowerCase() === 'ping') { - console.log(`[${message.author.tag}] dijo ping, respondiendo...`); - // Usar await porque enviar un mensaje es una operación de red - await message.reply('pong 🏓'); - } -}); - -// 4. Iniciar el bot -function main() { - // Debes reemplazar esto con tu token real o usar variables de entorno - const token = process.env.DISCORD_TOKEN || "TU_TOKEN_AQUI"; - - if (token === "TU_TOKEN_AQUI") { - console.log("⚠️ ADVERTENCIA: Reemplaza 'TU_TOKEN_AQUI' con el token de tu bot de Discord."); - console.log("Puedes obtenerlo en: https://discord.com/developers/applications"); - return; - } - - console.log("Iniciando bot..."); - client.login(token); -} - -main(); diff --git a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.py b/ejemplos/nerve/07-bot-discord-nerve/ejemplo.py deleted file mode 100644 index d41430a..0000000 --- a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -Ejemplo: Bot básico con discord.py - -Qué enseña este ejemplo: - - Cómo configurar y arrancar un bot de Discord. - - Cómo escuchar eventos (`on_ready`, `on_message`). - - El uso de `async`/`await` para programación asíncrona (requerido por Discord). - -Para tu reto (Bot Discord Nerve): - En vez de solo responder con "¡Hola!", conectarás el bot de Discord con - tu cliente Nerve. Cuando llegue un mensaje a Discord, usarás `cliente.send()` - para mandarlo a la red Nerve, y viceversa. - -Glosario: - discord.Client() — La clase base que maneja la conexión con los servidores de Discord. - discord.Intents — Los permisos que le pides a Discord. message_content es obligatorio - para poder leer el texto de los mensajes. - @client.event — Un decorador que le dice a la librería que esa función manejará un evento. - async def — Declara una función asíncrona. - await — Espera a que una tarea asíncrona (como enviar un mensaje a internet) termine. -""" - -import discord -import os - -# 1. Configurar los "intents" (permisos de qué eventos queremos recibir) -intents = discord.Intents.default() -intents.message_content = True # ¡Importante para poder leer lo que escriben! - -# 2. Inicializar el cliente del bot -client = discord.Client(intents=intents) - - -# 3. Definir qué pasa cuando el bot se conecta exitosamente -@client.event -async def on_ready(): - print(f"✅ Conectado exitosamente como {client.user}") - - -# 4. Definir qué pasa cuando alguien envía un mensaje en un canal -@client.event -async def on_message(message): - # Ignorar mensajes del propio bot para evitar bucles infinitos - if message.author == client.user: - return - - # Si alguien dice "ping", el bot responde "pong" - if message.content.lower() == "ping": - print(f"[{message.author}] dijo ping, respondiendo...") - # 'await' es necesario siempre que interactuemos con la API de Discord - await message.channel.send("pong 🏓") - - -def main(): - # Debes reemplazar esto con tu token real o usar una variable de entorno - token = os.environ.get("DISCORD_TOKEN", "TU_TOKEN_AQUI") - - if token == "TU_TOKEN_AQUI": - print( - "⚠️ ADVERTENCIA: Reemplaza 'TU_TOKEN_AQUI' con el token de tu bot de Discord." - ) - print("Puedes obtenerlo en: https://discord.com/developers/applications") - return - - print("Iniciando bot...") - client.run(token) - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.rs b/ejemplos/nerve/07-bot-discord-nerve/ejemplo.rs deleted file mode 100644 index b8c7b27..0000000 --- a/ejemplos/nerve/07-bot-discord-nerve/ejemplo.rs +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Ejemplo: Bot básico con serenity - * - * Qué enseña este ejemplo: - * - Cómo configurar e iniciar un cliente de serenity en Rust. - * - Implementar el trait EventHandler para manejar eventos. - * - El uso de "GatewayIntents" para especificar qué eventos quieres. - * - * Para tu reto (Bot Discord Nerve): - * En vez de solo responder con "¡Hola!", conectarás el bot de Discord con - * tu cliente Nerve. Cuando llegue un mensaje a Discord, usarás tu cliente - * Nerve para notificar a la red, y viceversa. - * - * Glosario: - * serenity::Client::builder() — Constructor del cliente de Discord. - * GatewayIntents — Sistema de permisos. Necesitas GUILD_MESSAGES y MESSAGE_CONTENT. - * EventHandler — Trait que implementas para recibir eventos. - * ctx.say(&msg.content) — Método conveniente para responder en el mismo canal. - */ - -use serenity::async_trait; -use serenity::model::channel::Message; -use serenity::model::gateway::Ready; -use serenity::prelude::*; -use std::env; - -// 1. Definir nuestra estructura Handler que manejará los eventos -struct Handler; - -// 2. Implementar el trait EventHandler para nuestra estructura -#[async_trait] -impl EventHandler for Handler { - // Evento message: Se llama cada vez que se envía un mensaje - async fn message(&self, ctx: Context, msg: Message) { - // Ignorar mensajes enviados por nuestro propio bot (u otros bots) - if msg.author.bot { - return; - } - - // Si el mensaje es "ping", respondemos "pong" - if msg.content.to_lowercase() == "ping" { - println!("[{}] dijo ping, respondiendo...", msg.author.name); - - // Enviar un mensaje de respuesta - if let Err(why) = msg.reply(&ctx.http, "pong 🏓").await { - println!("Error enviando mensaje: {:?}", why); - } - } - } - - // Evento ready: Se llama cuando el bot se conecta exitosamente - async fn ready(&self, _: Context, ready: Ready) { - println!("✅ Conectado exitosamente como {}", ready.user.name); - } -} - -#[tokio::main] -async fn main() { - // 3. Obtener el token. Reemplaza esto o usa variables de entorno - let mut token = env::var("DISCORD_TOKEN").unwrap_or_else(|_| "TU_TOKEN_AQUI".to_string()); - - if token == "TU_TOKEN_AQUI" { - println!("⚠️ ADVERTENCIA: Reemplaza 'TU_TOKEN_AQUI' con el token de tu bot de Discord."); - println!("Puedes obtenerlo en: https://discord.com/developers/applications"); - return; - } - - // 4. Configurar los Intents (permisos) - // GUILD_MESSAGES para recibir los eventos y MESSAGE_CONTENT para leer el texto - let intents = GatewayIntents::GUILD_MESSAGES - | GatewayIntents::MESSAGE_CONTENT - | GatewayIntents::GUILDS; - - // 5. Crear el cliente con el token, los intents y nuestro Handler - let mut client = Client::builder(&token, intents) - .event_handler(Handler) - .await - .expect("Error creando el cliente"); - - println!("Iniciando bot..."); - - // 6. Iniciar la conexión - if let Err(why) = client.start().await { - println!("Error en el cliente: {:?}", why); - } -} diff --git a/ejemplos/nerve/08-puente-http/README.md b/ejemplos/nerve/08-puente-http/README.md deleted file mode 100644 index 4a9da45..0000000 --- a/ejemplos/nerve/08-puente-http/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# Ejemplos: Servidor HTTP Básico (Puente HTTP) - -Para construir un puente HTTP, necesitas levantar un servidor web que pueda escuchar peticiones (como un `POST`), leer un cuerpo en JSON, y luego hacer algo con él. En el reto "Puente HTTP para Nerve", tu servidor recibirá una petición HTTP y usará el cliente Nerve para enviar esa información a la red. - -Estos ejemplos muestran cómo crear servidores HTTP muy simples y modernos en distintos lenguajes, sin añadir mucha complejidad, preparándote para integrarlos con tu cliente Nerve. - -## 🐍 Python (FastAPI) - -FastAPI es un framework moderno y rápido. - -### Requisitos -```bash -pip install fastapi uvicorn -``` - -### Cómo ejecutarlo -```bash -# El archivo debe llamarse ejemplo.py -uvicorn ejemplo:app --reload -``` -Para probarlo, puedes usar cURL: -```bash -curl -X POST -H "Content-Type: application/json" -d '{"mensaje": "Hola Mundo"}' http://localhost:8000/enviar -``` - -## 🟨 JavaScript (Express) - -Express es el estándar de facto para servidores web rápidos en Node.js. - -### Requisitos -```bash -npm install express -``` - -### Cómo ejecutarlo -```bash -node ejemplo.js -``` -Para probarlo: -```bash -curl -X POST -H "Content-Type: application/json" -d '{"mensaje": "Hola Node"}' http://localhost:3000/enviar -``` - -## 🐹 Go (net/http estándar) - -Go incluye un servidor HTTP de producción directamente en su librería estándar, no necesitas instalar nada. - -### Cómo ejecutarlo -```bash -go run ejemplo.go -``` -Para probarlo: -```bash -curl -X POST -H "Content-Type: application/json" -d '{"mensaje": "Hola Go"}' http://localhost:8080/enviar -``` - -## 🦀 Rust (Axum) - -Axum es un framework web muy popular y rápido del ecosistema Tokio. - -### Requisitos (Cargo.toml) -```toml -[dependencies] -axum = "0.7" -tokio = { version = "1.0", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -``` - -### Cómo ejecutarlo -```bash -cargo run -``` -Para probarlo: -```bash -curl -X POST -H "Content-Type: application/json" -d '{"mensaje": "Hola Rust"}' http://localhost:3000/enviar -``` diff --git a/ejemplos/nerve/08-puente-http/ejemplo.go b/ejemplos/nerve/08-puente-http/ejemplo.go deleted file mode 100644 index 75fead2..0000000 --- a/ejemplos/nerve/08-puente-http/ejemplo.go +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Ejemplo: Servidor HTTP con net/http (Estándar de Go) - * - * Qué enseña este ejemplo: - * - Crear una aplicación web básica usando la librería estándar. - * - Decodificar un JSON que viene en el cuerpo de la petición. - * - Manejar métodos HTTP específicos (asegurarse de que sea un POST). - * - * Para tu reto (Puente HTTP para Nerve): - * Cuando la ruta reciba los datos, deberás usar el cliente Nerve - * para retransmitir la información hacia el hub de Nerve. - */ - -package main - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// 1. Definir la estructura (struct) de los datos que esperamos recibir -// Las etiquetas `json:"mensaje"` indican a Go cómo buscar el campo en el JSON. -type MensajePayload struct { - Mensaje string `json:"mensaje"` -} - -func main() { - // 2. Definir las rutas (endpoints) - http.HandleFunc("/", rutaRaiz) - http.HandleFunc("/enviar", recibirYReenviar) - - puerto := ":8080" - fmt.Printf("Servidor Go escuchando en http://localhost%s\n", puerto) - - // 3. Encender el servidor - err := http.ListenAndServe(puerto, nil) - if err != nil { - fmt.Println("Error iniciando el servidor:", err) - } -} - -// Ruta GET básica -func rutaRaiz(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Servidor funcionando") -} - -// Ruta POST que será nuestro "puente" -func recibirYReenviar(w http.ResponseWriter, r *http.Request) { - // Asegurarnos de que sólo aceptamos peticiones POST - if r.Method != http.MethodPost { - http.Error(w, "Método no permitido. Usa POST.", http.StatusMethodNotAllowed) - return - } - - // Crear una variable para guardar los datos - var payload MensajePayload - - // Decodificar el JSON del cuerpo (body) de la petición hacia nuestro struct - err := json.NewDecoder(r.Body).Decode(&payload) - if err != nil { - http.Error(w, "JSON inválido", http.StatusBadRequest) - return - } - - if payload.Mensaje == "" { - http.Error(w, "Falta el campo 'mensaje' en el JSON", http.StatusBadRequest) - return - } - - // En la terminal veremos lo que llegó - fmt.Printf("📩 Recibido por HTTP: %s\n", payload.Mensaje) - - // ¡Aquí es donde integrarías Nerve! - // Ejemplo: clienteNerve.Publicar("canal_http", payload.Mensaje) - - // Responder a quien hizo la petición HTTP (con un JSON) - w.Header().Set("Content-Type", "application/json") - respuesta := fmt.Sprintf(`{"status": "ok", "recibido": "%s"}`, payload.Mensaje) - fmt.Fprintln(w, respuesta) -} diff --git a/ejemplos/nerve/08-puente-http/ejemplo.js b/ejemplos/nerve/08-puente-http/ejemplo.js deleted file mode 100644 index d8b2989..0000000 --- a/ejemplos/nerve/08-puente-http/ejemplo.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Ejemplo: Servidor HTTP con Express - * - * Qué enseña este ejemplo: - * - Crear una aplicación básica de Express. - * - Configurar `express.json()` para parsear el cuerpo de las peticiones. - * - Crear un endpoint (ruta) POST que lea esos datos. - * - * Para tu reto (Puente HTTP para Nerve): - * Cuando el endpoint reciba los datos, deberás usar el cliente Nerve - * para retransmitir la información hacia el hub de Nerve. - */ - -const express = require('express'); - -// 1. Inicializar la aplicación -const app = express(); -const puerto = 3000; - -// 2. Middleware para poder leer JSON en el cuerpo (body) del POST -app.use(express.json()); - -// 3. Ruta GET básica -app.get('/', (req, res) => { - res.json({ status: "Servidor funcionando" }); -}); - -// 4. Ruta POST que será nuestro "puente" -app.post('/enviar', (req, res) => { - // req.body contendrá el JSON que nos envíen (ej. {"mensaje": "Hola"}) - const mensaje = req.body.mensaje; - - if (!mensaje) { - return res.status(400).json({ error: "Falta el campo 'mensaje' en el JSON" }); - } - - // En la terminal veremos lo que llegó - console.log(`📩 Recibido por HTTP: ${mensaje}`); - - // ¡Aquí es donde integrarías Nerve! - // Ejemplo: clienteNerve.publicar("canal_http", mensaje); - - // Responder a quien hizo la petición HTTP que todo salió bien - res.json({ status: "ok", recibido: mensaje }); -}); - -// 5. Encender el servidor -app.listen(puerto, () => { - console.log(`Servidor Express escuchando en http://localhost:${puerto}`); -}); diff --git a/ejemplos/nerve/08-puente-http/ejemplo.py b/ejemplos/nerve/08-puente-http/ejemplo.py deleted file mode 100644 index 94b8e74..0000000 --- a/ejemplos/nerve/08-puente-http/ejemplo.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Ejemplo: Servidor HTTP con FastAPI - -Qué enseña este ejemplo: - - Crear una aplicación básica de FastAPI. - - Definir un modelo de datos (Pydantic) para recibir JSON. - - Crear un endpoint (ruta) POST que lea esos datos. - -Para tu reto (Puente HTTP para Nerve): - Cuando el endpoint reciba los datos, deberás usar el cliente Nerve (app.state.nerve) - para retransmitir la información hacia el hub de Nerve. -""" - -from fastapi import FastAPI -from pydantic import BaseModel - - -# 1. Definir el modelo de los datos que esperamos recibir -# Esto asegura que quien envíe el POST, nos mande un JSON con un campo "mensaje" -class MensajePayload(BaseModel): - mensaje: str - - -# 2. Inicializar la aplicación FastAPI -app = FastAPI() - - -# 3. Ruta GET básica para comprobar que funciona -@app.get("/") -def leer_raiz(): - return {"status": "Servidor funcionando"} - - -# 4. Ruta POST que será nuestro "puente" -# FastAPI automáticamente convierte el cuerpo del POST desde JSON hacia nuestro modelo -@app.post("/enviar") -def recibir_y_reenviar(payload: MensajePayload): - # En la terminal veremos lo que llegó - print(f"📩 Recibido por HTTP: {payload.mensaje}") - - # ¡Aquí es donde integrarías Nerve! - # Ejemplo: cliente_nerve.publicar("canal_http", payload.mensaje) - - return {"status": "ok", "recibido": payload.mensaje} - - -# NOTA: Para ejecutar esto se usa uvicorn desde la terminal: -# uvicorn ejemplo:app --reload diff --git a/ejemplos/nerve/08-puente-http/ejemplo.rs b/ejemplos/nerve/08-puente-http/ejemplo.rs deleted file mode 100644 index 7f6c165..0000000 --- a/ejemplos/nerve/08-puente-http/ejemplo.rs +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Ejemplo: Servidor HTTP con Axum - * - * Qué enseña este ejemplo: - * - Crear un servidor HTTP moderno y rápido en Rust usando axum. - * - Usar 'serde' para deserializar un cuerpo JSON automáticamente. - * - Configurar una ruta POST y enviar una respuesta JSON. - * - * Para tu reto (Puente HTTP para Nerve): - * Cuando la ruta reciba los datos, deberás usar el cliente Nerve - * para retransmitir la información hacia el hub de Nerve. Para compartir - * el cliente Nerve con Axum, necesitarás usar el sistema de "State" de Axum - * con un Arc>. - */ - -use axum::{ - routing::{get, post}, - Router, - Json, -}; -use serde::{Deserialize, Serialize}; - -// 1. Estructura para lo que ESPERAMOS recibir -// 'Deserialize' permite a Axum convertir automáticamente el JSON entrante a este struct -#[derive(Deserialize)] -struct MensajePayload { - mensaje: String, -} - -// 2. Estructura para lo que VAMOS a responder -// 'Serialize' permite a Axum convertir el struct a JSON para responder -#[derive(Serialize)] -struct RespuestaPayload { - status: String, - recibido: String, -} - -#[tokio::main] -async fn main() { - // 3. Crear el enrutador de la aplicación y definir las rutas - let app = Router::new() - .route("/", get(ruta_raiz)) - .route("/enviar", post(recibir_y_reenviar)); - - // 4. Iniciar el servidor - let puerto = "0.0.0.0:3000"; - let listener = tokio::net::TcpListener::bind(puerto).await.unwrap(); - - println!("Servidor Rust (Axum) escuchando en http://localhost:3000"); - axum::serve(listener, app).await.unwrap(); -} - -// Ruta GET básica -async fn ruta_raiz() -> &'static str { - "Servidor funcionando" -} - -// 5. Ruta POST que será nuestro "puente" -// Axum se encarga de verificar que el POST tenga un JSON válido y lo mapea a `payload` -async fn recibir_y_reenviar(Json(payload): Json) -> Json { - - // En la terminal veremos lo que llegó - println!("📩 Recibido por HTTP: {}", payload.mensaje); - - // ¡Aquí es donde integrarías Nerve! - // Ejemplo: state.cliente_nerve.publicar("canal_http", &payload.mensaje).await; - - // Retornamos un JSON de respuesta - let respuesta = RespuestaPayload { - status: "ok".to_string(), - recibido: payload.mensaje, - }; - - Json(respuesta) -} diff --git a/ejemplos/nerve/09-probador-de-carga/README.md b/ejemplos/nerve/09-probador-de-carga/README.md deleted file mode 100644 index 016b347..0000000 --- a/ejemplos/nerve/09-probador-de-carga/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Ejemplos: Concurrencia y Pruebas de Carga (Load Tester) - -Para crear un "Probador de carga" (Load Tester) efectivo, necesitas enviar muchas peticiones al mismo tiempo (concurrencia) en lugar de enviar una, esperar a que termine, y luego enviar la siguiente. - -En el reto "Probador de carga para el Hub de Nerve", utilizarás estos conceptos de concurrencia para instanciar múltiples clientes Nerve o usar un solo cliente para bombardear la red de mensajes lo más rápido posible, midiendo cuánto tarda en responder. - -Estos ejemplos muestran cómo ejecutar múltiples tareas "al mismo tiempo" en distintos lenguajes. - -## 🐍 Python (asyncio) - -Python usa `asyncio` para la concurrencia basada en eventos. - -### Cómo ejecutarlo -```bash -python ejemplo.py -``` - -## 🟨 JavaScript (Promise.all) - -JavaScript maneja la concurrencia a través de Promesas asíncronas y el Event Loop. - -### Cómo ejecutarlo -```bash -node ejemplo.js -``` - -## 🐹 Go (Goroutines) - -Go fue diseñado para la concurrencia. Las `goroutines` son hilos súper ligeros. - -### Cómo ejecutarlo -```bash -go run ejemplo.go -``` - -## 🦀 Rust (Tokio) - -Rust usa `tokio` como runtime asíncrono, permitiendo `tokio::spawn` para tareas concurrentes de muy alto rendimiento. - -### Requisitos (Cargo.toml) -```toml -[dependencies] -tokio = { version = "1.0", features = ["full"] } -``` - -### Cómo ejecutarlo -```bash -cargo run -``` diff --git a/ejemplos/nerve/09-probador-de-carga/ejemplo.go b/ejemplos/nerve/09-probador-de-carga/ejemplo.go deleted file mode 100644 index 21d5902..0000000 --- a/ejemplos/nerve/09-probador-de-carga/ejemplo.go +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Ejemplo: Concurrencia con Goroutines y WaitGroups en Go - * - * Qué enseña este ejemplo: - * - Lanzar "goroutines" (hilos ligeros) usando la palabra clave `go`. - * - Sincronizar las goroutines usando un `sync.WaitGroup` para esperar a que todas terminen. - * - Medir el tiempo transcurrido usando `time.Since`. - * - * Para tu reto (Probador de Carga Nerve): - * Reemplazarás `simularPeticion()` con un envío al servidor usando - * tu cliente Nerve para ver cuánto tráfico puede aguantar tu red. - */ - -package main - -import ( - "fmt" - "sync" - "time" -) - -// 1. Tarea que se ejecutará de forma concurrente -// Recibe un puntero al WaitGroup para avisar cuando termine -func simularPeticion(idPeticion int, wg *sync.WaitGroup) { - // Importante: al terminar la función, avisa al WaitGroup - defer wg.Done() - - // Simulamos que enviar un mensaje tarda 100 milisegundos (0.1s) - time.Sleep(100 * time.Millisecond) - - // Aquí iría tu cliente Nerve: clienteNerve.Publicar(...) -} - -func main() { - totalMensajes := 1000 - fmt.Printf("🚀 Iniciando prueba de carga: %d peticiones...\n", totalMensajes) - - // Guardar el tiempo de inicio - inicio := time.Now() - - // 2. Crear un WaitGroup para esperar a que todas las goroutines terminen - var wg sync.WaitGroup - - // Decirle al WaitGroup cuántas tareas vamos a lanzar - wg.Add(totalMensajes) - - // 3. Lanzar todas las tareas concurrentemente - for i := 0; i < totalMensajes; i++ { - // La palabra `go` lanza la función en un hilo ligero (goroutine) - // y el bucle for sigue inmediatamente a la siguiente iteración - go simularPeticion(i, &wg) - } - - // 4. Pausar la ejecución principal hasta que el contador del WaitGroup llegue a cero - wg.Wait() - - // Calcular el tiempo transcurrido - tiempoTotal := time.Since(inicio).Seconds() - - // En este ejemplo simple asumimos que todas fueron exitosas - // Para contar éxitos de forma segura en concurrencia necesitarías canales (channels) o sync.Mutex - exitosos := totalMensajes - - // 5. Mostrar estadísticas - fmt.Printf("✅ Prueba finalizada en %.2f segundos.\n", tiempoTotal) - fmt.Printf("📊 Mensajes exitosos: %d/%d\n", exitosos, totalMensajes) - fmt.Printf("⚡ Rendimiento: %.2f peticiones/segundo\n", float64(totalMensajes)/tiempoTotal) -} diff --git a/ejemplos/nerve/09-probador-de-carga/ejemplo.js b/ejemplos/nerve/09-probador-de-carga/ejemplo.js deleted file mode 100644 index dd8608f..0000000 --- a/ejemplos/nerve/09-probador-de-carga/ejemplo.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Ejemplo: Concurrencia con Promise.all en JavaScript - * - * Qué enseña este ejemplo: - * - Usar `Promise.all` para ejecutar múltiples promesas concurrentemente. - * - Medir el tiempo usando `performance.now()`. - * - * Para tu reto (Probador de Carga Nerve): - * Reemplazarás `simularPeticion()` con llamadas al cliente Nerve - * (como publicar mensajes) para medir cuántas peticiones aguanta la red - * por segundo. - */ - -// Función auxiliar para simular un retraso sin bloquear el hilo principal -const setTimeoutAsync = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -// 1. Definir una tarea asíncrona que simula trabajo -async function simularPeticion(idPeticion) { - // Simulamos que enviar un mensaje tarda 100 milisegundos (0.1s) - await setTimeoutAsync(100); - // Retornamos true si fue exitoso (o podríamos hacer un try/catch) - return true; -} - -async function main() { - const totalMensajes = 1000; - console.log(`🚀 Iniciando prueba de carga: ${totalMensajes} peticiones...`); - - // Guardar el tiempo de inicio (más preciso que Date.now()) - const inicio = performance.now(); - - // 2. Crear un arreglo (array) con todas las promesas (las peticiones ejecutándose) - const promesas = []; - for (let i = 0; i < totalMensajes; i++) { - // Al llamar a la función sin `await`, comienza a ejecutarse - // pero no esperamos a que termine, solo guardamos la promesa - promesas.push(simularPeticion(i)); - } - - // 3. Promise.all espera a que TODAS las promesas terminen en paralelo - const resultados = await Promise.all(promesas); - - // Calcular el tiempo total transcurrido - const fin = performance.now(); - // Convertir de milisegundos a segundos - const tiempoTotal = (fin - inicio) / 1000; - - // Contar cuántos retornaron `true` - const exitosos = resultados.filter(r => r === true).length; - - // 4. Mostrar estadísticas - console.log(`✅ Prueba finalizada en ${tiempoTotal.toFixed(2)} segundos.`); - console.log(`📊 Mensajes exitosos: ${exitosos}/${totalMensajes}`); - console.log(`⚡ Rendimiento: ${(totalMensajes / tiempoTotal).toFixed(2)} peticiones/segundo`); -} - -main(); diff --git a/ejemplos/nerve/09-probador-de-carga/ejemplo.py b/ejemplos/nerve/09-probador-de-carga/ejemplo.py deleted file mode 100644 index 1a4465c..0000000 --- a/ejemplos/nerve/09-probador-de-carga/ejemplo.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Ejemplo: Concurrencia con asyncio en Python - -Qué enseña este ejemplo: - - Usar `asyncio.gather` para lanzar múltiples tareas al mismo tiempo. - - Medir cuánto tarda un bloque de código en ejecutarse usando `time.time()`. - -Para tu reto (Probador de Carga Nerve): - Reemplazarás `simular_peticion()` con `cliente.publicar(...)` o - una llamada remota de Nerve para bombardear la red y medir el - rendimiento total (mensajes por segundo). -""" - -import asyncio -import time - - -# 1. Definir una tarea asíncrona que simula trabajo -async def simular_peticion(id_peticion: int): - # Simulamos que enviar un mensaje tarda 100 milisegundos (0.1s) - # Importante usar asyncio.sleep para no bloquear el hilo principal - await asyncio.sleep(0.1) - # Podrías retornar el resultado o simplemente confirmar - return True - - -async def main(): - total_mensajes = 1000 - print(f"🚀 Iniciando prueba de carga: {total_mensajes} peticiones...") - - # Guardar el tiempo de inicio - inicio = time.time() - - # 2. Crear una lista con todas las "promesas" o "tareas" a ejecutar - tareas = [] - for i in range(total_mensajes): - tareas.append(simular_peticion(i)) - - # 3. asyncio.gather ejecuta todas las tareas en paralelo (concurrentemente) - # Espera hasta que todas terminen y devuelve una lista con sus resultados - resultados = await asyncio.gather(*tareas) - - # Calcular el tiempo total transcurrido - fin = time.time() - tiempo_total = fin - inicio - - exitosos = sum(1 for r in resultados if r is True) - - # 4. Mostrar estadísticas - print(f"✅ Prueba finalizada en {tiempo_total:.2f} segundos.") - print(f"📊 Mensajes exitosos: {exitosos}/{total_mensajes}") - print(f"⚡ Rendimiento: {(total_mensajes / tiempo_total):.2f} peticiones/segundo") - - -if __name__ == "__main__": - # Iniciar el bucle de eventos asíncrono - asyncio.run(main()) diff --git a/ejemplos/nerve/09-probador-de-carga/ejemplo.rs b/ejemplos/nerve/09-probador-de-carga/ejemplo.rs deleted file mode 100644 index 7d571ab..0000000 --- a/ejemplos/nerve/09-probador-de-carga/ejemplo.rs +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Ejemplo: Concurrencia con tokio::spawn en Rust - * - * Qué enseña este ejemplo: - * - Lanzar tareas concurrentes asíncronas con `tokio::spawn`. - * - Usar `tokio::time::sleep` de manera no bloqueante. - * - Esperar a que un conjunto de tareas terminen y recolectar sus resultados con `JoinSet` o `join_all`. - * - Medir el tiempo con `std::time::Instant`. - * - * Para tu reto (Probador de Carga Nerve): - * Reemplazarás `simular_peticion()` con llamadas al cliente Nerve para - * inundar la red de mensajes lo más rápido posible. Rust y Tokio son - * excelentes para lograr el máximo rendimiento en estas pruebas. - */ - -use std::time::Instant; -use tokio::time::{sleep, Duration}; -use tokio::task::JoinSet; - -// 1. Definir una tarea asíncrona -async fn simular_peticion(id: usize) -> bool { - // Simulamos que enviar un mensaje tarda 100 milisegundos - sleep(Duration::from_millis(100)).await; - // Retornamos true indicando éxito - true -} - -#[tokio::main] -async fn main() { - let total_mensajes = 1000; - println!("🚀 Iniciando prueba de carga: {} peticiones...", total_mensajes); - - // Guardar el tiempo de inicio - let inicio = Instant::now(); - - // 2. JoinSet es una estructura de Tokio ideal para manejar múltiples tareas - // concurrentes y esperar a que terminen. - let mut tareas = JoinSet::new(); - - // 3. Lanzamos todas las tareas concurrentemente usando tokio::spawn implícito en JoinSet - for i in 0..total_mensajes { - tareas.spawn(async move { - simular_peticion(i).await - }); - } - - let mut exitosos = 0; - - // 4. Recolectar los resultados conforme van terminando - // join_next() espera hasta que UNA de las tareas termine y nos da su resultado - while let Some(resultado) = tareas.join_next().await { - match resultado { - Ok(fue_exito) => { - if fue_exito { - exitosos += 1; - } - } - Err(e) => println!("Error en una tarea: {:?}", e), - } - } - - // Calcular el tiempo transcurrido en segundos (con decimales) - let tiempo_total = inicio.elapsed().as_secs_f64(); - - // 5. Mostrar estadísticas - println!("✅ Prueba finalizada en {:.2} segundos.", tiempo_total); - println!("📊 Mensajes exitosos: {}/{}", exitosos, total_mensajes); - println!( - "⚡ Rendimiento: {:.2} peticiones/segundo", - (total_mensajes as f64) / tiempo_total - ); -} diff --git a/ejemplos/nerve/10-cliente-nuevo-lenguaje/README.md b/ejemplos/nerve/10-cliente-nuevo-lenguaje/README.md deleted file mode 100644 index f8efe3c..0000000 --- a/ejemplos/nerve/10-cliente-nuevo-lenguaje/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Ejemplos: Sockets TCP Base (Nuevo Cliente) - -Para crear un cliente de Nerve en un lenguaje completamente nuevo, la pieza fundamental es entender cómo conectarse a un **Socket TCP** para enviar y recibir datos. Nerve internamente funciona pasando objetos JSON sobre TCP (IPC). - -Estos ejemplos no utilizan ninguna librería de Nerve, sino las librerías estándar de cada lenguaje para abrir un Socket TCP, enviar un mensaje "Hola" (o un JSON simulado), y esperar una respuesta. - -A partir de estas piezas básicas, en el reto "Cliente en Nuevo Lenguaje", construirás las clases o estructuras para encapsular esta conexión, manejar la reconexión y proveer una API amigable (`conectar()`, `publicar()`, `suscribir()`). - -## 🐍 Python (socket) - -### Cómo ejecutarlo -```bash -# Primero levanta un servidor TCP de prueba (como netcat) en otra terminal: -# nc -l 5000 -python ejemplo.py -``` - -## 🟨 JavaScript (net) - -### Cómo ejecutarlo -```bash -# Primero levanta un servidor TCP de prueba: -# nc -l 5000 -node ejemplo.js -``` - -## 🐹 Go (net) - -### Cómo ejecutarlo -```bash -# Primero levanta un servidor TCP de prueba: -# nc -l 5000 -go run ejemplo.go -``` - -## 🦀 Rust (std::net) - -### Cómo ejecutarlo -```bash -# Primero levanta un servidor TCP de prueba: -# nc -l 5000 -cargo run -``` diff --git a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.go b/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.go deleted file mode 100644 index 0320e94..0000000 --- a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.go +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Ejemplo: Cliente de Socket TCP en Go - * - * Qué enseña este ejemplo: - * - Conectarse a un servidor TCP con el paquete `net`. - * - Escribir en la conexión usando `fmt.Fprintf` o `conn.Write`. - * - Leer la respuesta del servidor usando `bufio.NewReader`. - * - * Para tu reto (Nuevo Cliente Nerve): - * Go es fenomenal para clientes de red. Podrás lanzar una goroutine que se - * quede bloqueada leyendo `bufio.ReadString('\n')` para recibir mensajes, - * mientras tu programa principal puede seguir llamando a `Publicar()`. - */ - -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "net" - "strings" -) - -func main() { - host := "127.0.0.1" - puerto := "5000" - direccion := host + ":" + puerto - - fmt.Printf("🔄 Conectando a %s...\n", direccion) - - // 1. Intentar conectar (net.Dial funciona para TCP, UDP, etc.) - conn, err := net.Dial("tcp", direccion) - if err != nil { - fmt.Println("❌ No se pudo conectar. ¿Está el servidor encendido?") - fmt.Println("Error:", err) - return - } - // Asegurar que cerraremos la conexión al terminar la función - defer conn.Close() - fmt.Println("✅ Conectado exitosamente") - - // 2. Preparar los datos - // Usamos un mapa como ejemplo rápido, en código real deberías usar un struct - payload := map[string]string{ - "tipo": "publicar", - "canal": "test", - "contenido": "Hola desde el nuevo cliente Go!", - } - - // 3. Convertir el mapa a JSON Bytes - datosBytes, err := json.Marshal(payload) - if err != nil { - fmt.Println("Error creando JSON:", err) - return - } - - // 4. Enviar los datos (añadiendo el salto de línea al final) - datosStr := string(datosBytes) + "\n" - fmt.Fprintf(conn, "%s", datosStr) - fmt.Printf("⬆️ Enviado: %s\n", strings.TrimSpace(datosStr)) - - // 5. Esperar la respuesta (leyendo hasta encontrar un salto de línea \n) - respuesta, err := bufio.NewReader(conn).ReadString('\n') - if err != nil { - fmt.Println("⚠️ El servidor cerró la conexión o hubo un error al leer.") - return - } - - // Mostrar la respuesta - fmt.Printf("⬇️ Recibido: %s\n", strings.TrimSpace(respuesta)) -} diff --git a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.js b/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.js deleted file mode 100644 index b07787a..0000000 --- a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Ejemplo: Cliente de Socket TCP en Node.js (net) - * - * Qué enseña este ejemplo: - * - Conectarse a un servidor usando el módulo `net` de Node.js. - * - Manejar eventos (connect, data, error, close). - * - Enviar y recibir strings JSON (Node.js maneja la codificación a Buffer por ti). - * - * Para tu reto (Nuevo Cliente Nerve): - * La naturaleza orientada a eventos de Node.js (client.on) es perfecta para - * Nerve, ya que puedes escuchar 'data' de forma permanente y emitir eventos - * propios (usando EventEmitter) cuando lleguen mensajes de otros clientes. - */ - -const net = require('net'); - -function main() { - const host = '127.0.0.1'; - const puerto = 5000; - - console.log(`🔄 Conectando a ${host}:${puerto}...`); - - // 1. Crear la conexión TCP - const client = new net.Socket(); - - // 2. Intentar conectar - client.connect(puerto, host, () => { - console.log('✅ Conectado exitosamente'); - - // 3. Preparar el payload - const payload = { - tipo: "publicar", - canal: "test", - contenido: "Hola desde el nuevo cliente JS!" - }; - - // 4. Enviar los datos convertidos a JSON String + salto de línea - const datosStr = JSON.stringify(payload) + "\n"; - client.write(datosStr); - console.log(`⬆️ Enviado: ${datosStr.trim()}`); - }); - - // 5. Escuchar el evento 'data' para recibir las respuestas del servidor - client.on('data', (data) => { - // data es un Buffer, al llamar .toString() lo convertimos a String - const respuesta = data.toString().trim(); - console.log(`⬇️ Recibido: ${respuesta}`); - - // En este ejemplo simple, cerramos la conexión luego de recibir respuesta. - // ¡En tu cliente Nerve querrás mantenerla abierta! - client.destroy(); - }); - - // Manejar errores (como si el servidor no está encendido) - client.on('error', (err) => { - if (err.code === 'ECONNREFUSED') { - console.log('❌ No se pudo conectar. ¿Está el servidor encendido?'); - } else { - console.log(`❌ Error: ${err.message}`); - } - }); - - // Saber cuándo se cerró la conexión - client.on('close', () => { - console.log('🔌 Conexión cerrada'); - }); -} - -main(); diff --git a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.py b/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.py deleted file mode 100644 index 6507af4..0000000 --- a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Ejemplo: Cliente de Socket TCP en Python - -Qué enseña este ejemplo: - - Conectarse a un servidor a través de una dirección IP y un puerto usando sockets. - - Codificar un JSON a bytes y enviarlo. - - Esperar y decodificar la respuesta del servidor. - -Para tu reto (Nuevo Cliente Nerve): - En un cliente Nerve real, querrás mantener este socket abierto (persistencia) - y ejecutar un bucle (loop) en un hilo (thread) separado para estar siempre - escuchando los nuevos mensajes que el Hub envía, disparando callbacks o - eventos a tu aplicación. -""" - -import socket -import json - - -def main(): - host = "127.0.0.1" - puerto = 5000 - - print(f"🔄 Conectando a {host}:{puerto}...") - - # 1. Crear el socket TCP (AF_INET para IPv4, SOCK_STREAM para TCP) - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - try: - # 2. Conectarse al servidor - s.connect((host, puerto)) - print("✅ Conectado exitosamente") - - # 3. Preparar el payload simulando un mensaje Nerve - payload = { - "tipo": "publicar", - "canal": "test", - "contenido": "Hola desde el nuevo cliente Python!", - } - - # 4. Enviar los datos. Deben convertirse de Diccionario -> JSON String -> Bytes - datos_str = json.dumps(payload) + "\n" - s.sendall(datos_str.encode("utf-8")) - print(f"⬆️ Enviado: {datos_str.strip()}") - - # 5. Esperar la respuesta (leyendo hasta 1024 bytes) - datos_recibidos = s.recv(1024) - if datos_recibidos: - # Decodificar de Bytes -> String - respuesta = datos_recibidos.decode("utf-8").strip() - print(f"⬇️ Recibido: {respuesta}") - else: - print("⚠️ El servidor cerró la conexión sin responder.") - - except ConnectionRefusedError: - print("❌ No se pudo conectar. ¿Está el servidor encendido?") - except Exception as e: - print(f"❌ Error inesperado: {e}") - - -if __name__ == "__main__": - main() diff --git a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.rs b/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.rs deleted file mode 100644 index a470a48..0000000 --- a/ejemplos/nerve/10-cliente-nuevo-lenguaje/ejemplo.rs +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Ejemplo: Cliente de Socket TCP en Rust - * - * Qué enseña este ejemplo: - * - Conectarse a un servidor usando `std::net::TcpStream` de la librería estándar de Rust. - * - Escribir en el stream de red de forma segura. - * - Leer la respuesta del servidor en un buffer. - * - * Para tu reto (Nuevo Cliente Nerve): - * Rust te obliga a ser muy preciso con la memoria y la pertenencia (ownership). - * En un cliente real, probablemente querrás usar `tokio::net::TcpStream` - * (asíncrono) para poder separar la lógica de envío de la lógica de escucha, - * o usar hilos (`std::thread::spawn`) para leer del socket continuamente - * mientras otra parte del programa escribe. - */ - -use std::io::{Read, Write}; -use std::net::TcpStream; -use serde_json::json; - -fn main() { - let host = "127.0.0.1"; - let puerto = "5000"; - let direccion = format!("{}:{}", host, puerto); - - println!("🔄 Conectando a {}...", direccion); - - // 1. Intentar conectar (esto bloquea hasta conectar o fallar) - match TcpStream::connect(&direccion) { - Ok(mut stream) => { - println!("✅ Conectado exitosamente"); - - // 2. Preparar el payload usando la macro `json!` de serde_json - let payload = json!({ - "tipo": "publicar", - "canal": "test", - "contenido": "Hola desde el nuevo cliente Rust!" - }); - - // 3. Convertir el JSON a String y agregar salto de línea - let mut datos_str = payload.to_string(); - datos_str.push('\n'); - - // 4. Enviar los datos escribiendo en el stream de red - // as_bytes() convierte el String en &[u8] que es lo que espera write_all - if let Err(e) = stream.write_all(datos_str.as_bytes()) { - println!("Error enviando los datos: {}", e); - return; - } - println!("⬆️ Enviado: {}", datos_str.trim()); - - // 5. Preparar un buffer (arreglo de 1024 bytes) para recibir la respuesta - let mut buffer = [0; 1024]; - - // 6. Leer la respuesta del servidor - match stream.read(&mut buffer) { - Ok(bytes_leidos) => { - if bytes_leidos == 0 { - println!("⚠️ El servidor cerró la conexión sin responder."); - } else { - // Convertir los bytes leídos de vuelta a String - // Solo tomamos el "slice" de los bytes que realmente se leyeron - let respuesta = String::from_utf8_lossy(&buffer[0..bytes_leidos]); - println!("⬇️ Recibido: {}", respuesta.trim()); - } - } - Err(e) => { - println!("Error leyendo respuesta: {}", e); - } - } - } - Err(e) => { - println!("❌ No se pudo conectar. ¿Está el servidor encendido?"); - println!("Detalle: {}", e); - } - } -} diff --git a/glosario/README.md b/glosario/README.md index a69e39a..5288330 100644 --- a/glosario/README.md +++ b/glosario/README.md @@ -1,26 +1,27 @@ -# 📚 Glosario de Nerve Community +# Glosario de Nerve Community -Bienvenido al glosario completo. Aquí encontrarás explicaciones claras de **todos** los términos que usamos en el repositorio, organizados por tema para que puedas consultarlos rápidamente. +Explicaciones en español de los términos técnicos que aparecen en el currículum, los retos y la documentación del proyecto. Organizado por tema para que puedas consultar cualquier concepto de forma independiente. -## 📂 Índice de Glosarios +--- + +## Glosarios por tema | Archivo | Qué cubre | | :--- | :--- | -| [🔧 Git y GitHub](git-github.md) | `git clone`, `fork`, `commit`, `push`, `pull`, `branch`, `PR`, `merge`, `rebase`... | -| [🌐 Redes y Sockets](redes-sockets.md) | `socket`, `IPC`, `Unix Socket`, `TCP`, `servidor`, `cliente`, `puerto`... | -| [⌨️ Terminal: Linux y macOS](terminal-linux.md) | `pwd`, `ls`, `nano`, `chmod`, `apt`, Termux, `pkg`, variables de entorno... | -| [🪟 Terminal: Windows](terminal-windows.md) | CMD, PowerShell, Git Bash, WSL, `dir`, `python`, instalar Python en Windows... | -| [🛠️ Conceptos de Software](conceptos-software.md) | `bug`, `refactoring`, `dependencias`, `librería estándar`, `CLI`, `API`, `CSV`, `JSON`... | +| [Git y GitHub](git-github.md) | `git clone`, `fork`, `commit`, `push`, `pull`, `branch`, `PR`, `merge`, `rebase` | +| [Redes y Sockets](redes-sockets.md) | `socket`, `IPC`, `Unix Socket`, `TCP`, servidor, cliente, puerto, HTTP, API | +| [Terminal: Linux y macOS](terminal-linux.md) | `pwd`, `ls`, `nano`, `chmod`, `apt`, Termux, `pkg`, variables de entorno | +| [Terminal: Windows](terminal-windows.md) | CMD, PowerShell, Git Bash, WSL, `dir`, `python`, instalar Python en Windows | +| [Conceptos de Software](conceptos-software.md) | `bug`, `refactoring`, dependencias, librería estándar, `CLI`, `API`, `CSV`, `JSON` | -## 🧑‍💻 Glosarios por lenguaje +## Glosarios por lenguaje | Lenguaje | Carpeta | | :--- | :--- | | Python | [glosario/python/](python/README.md) | -| Go | [glosario/go/](go/README.md) | -| Rust | [glosario/rust/](rust/README.md) | -| JavaScript | [glosario/javascript/](javascript/README.md) | + +> Si encuentras un término que no está aquí, abre un Issue y lo agregamos. --- -> 💡 **Tip:** Si encuentras un término que no está aquí, abre un Issue y lo agregamos. ¡El glosario lo construimos entre todos! +← [Volver al repositorio](../README.md) diff --git a/glosario/conceptos-software.md b/glosario/conceptos-software.md index 15e6e51..9daa1fc 100644 --- a/glosario/conceptos-software.md +++ b/glosario/conceptos-software.md @@ -1,75 +1,74 @@ -# 🛠️ Glosario: Conceptos de Software +# Glosario: Conceptos de Software -Términos generales de desarrollo de software que aparecen en los retos, documentación y conversaciones del día a día en Nerve Community. Aquí nada se da por sabido. +Términos generales de desarrollo de software que aparecen en los retos, la documentación y las conversaciones del día a día en Nerve Community. --- ## Tipos de Archivos y Formatos de Datos -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Markdown (.md)** | Lenguaje de marcado simple | Formato de texto con una sintaxis sencilla (`**negrita**`, `# Título`) que se convierte en HTML. GitHub lo renderiza automáticamente. | Este mismo archivo que estás leyendo | N/A | - | -| **TXT** | Archivo de texto plano | El formato más básico. Sin formato, sin colores, solo caracteres. | `notas.txt`, `requirements.txt` | N/A | - | -| **YAML (.yml / .yaml)** | Yet Another Markup Language | Formato de configuración muy legible para humanos. Usado en archivos de CI/CD como GitHub Actions. | Los workflows en `.github/workflows/` | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **Markdown (.md)** | Lenguaje de marcado simple | Formato de texto con sintaxis sencilla (`**negrita**`, `# Título`) que se convierte en HTML. GitHub lo renderiza automáticamente. | Este archivo que estás leyendo | +| **TXT** | Archivo de texto plano | El formato más básico. Sin formato ni colores, solo caracteres. | `notas.txt`, `requirements.txt` | +| **YAML (.yml / .yaml)** | Yet Another Markup Language | Formato de configuración legible para humanos. Usado en CI/CD como GitHub Actions. | Los workflows en `.github/workflows/` | --- ## Arquitectura y Diseño de Software -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **CLI (Command Line Interface)** | Interfaz de línea de comandos | Un programa que se usa desde la terminal escribiendo texto, en vez de clicando botones. La mayoría de los retos son CLIs. | `python3 lista_tareas.py --agregar "Estudiar"` | N/A | - | -| **Librería / Biblioteca** | Código reutilizable empaquetado | Colección de funciones y clases escritas por otros que puedes importar para no reinventar la rueda. | `import requests` | N/A | - | -| **Framework** | Estructura base para construir apps | Un esqueleto con reglas y herramientas que te dice cómo organizar tu proyecto. Más opinionado que una librería. | FastAPI (para APIs), discord.py (para bots) | N/A | - | -| **Módulo** | Unidad básica de código reutilizable | Un archivo `.py` con funciones que puedes importar en otros archivos de tu proyecto. | `from conversiones import celsius_a_fahrenheit` | N/A | - | -| **Dependencia** | Librería de la que depende tu código | Una librería externa que tu proyecto necesita para funcionar. Si no está instalada, el programa falla. | `requests` es una dependencia del reto 02 | N/A | - | -| **Open Source (Código Abierto)** | Software con código público y libre | Software cuyo código fuente es público, gratuito y puede ser modificado y redistribuido. Nerve y este repo son Open Source. | Licencia GNU GPL v3 | N/A | - | -| **GNU GPL v3** | Licencia de Nerve Community | Una licencia Open Source que garantiza que el software es libre, y que cualquier derivado también debe serlo. | El archivo `LICENSE.md` en el repo | N/A | - | -| **multiplataforma** | Funciona en varios sistemas operativos | Software que funciona en Linux, macOS y Windows sin cambios. Nerve es multiplataforma. | Nerve usa Unix Sockets en Linux/macOS y TCP en Windows | N/A | - | -| **offline-first** | Funciona sin internet | Diseñado para funcionar sin conexión a internet. Nerve se conecta localmente, no usa la nube. | Nerve solo necesita que los procesos estén en la misma máquina | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **CLI (Command Line Interface)** | Interfaz de línea de comandos | Un programa que se usa desde la terminal escribiendo texto en vez de clicando botones. La mayoría de los retos son CLIs. | `python3 lista_tareas.py --agregar "Estudiar"` | +| **Librería / Biblioteca** | Código reutilizable empaquetado | Colección de funciones y clases escritas por otros que puedes importar sin reinventar la rueda. | `import requests` | +| **Framework** | Estructura base para construir apps | Un esqueleto con reglas y herramientas que te dice cómo organizar tu proyecto. Más opinionado que una librería. | FastAPI (para APIs), discord.py (para bots) | +| **Módulo** | Unidad básica de código reutilizable | Un archivo `.py` con funciones que puedes importar en otros archivos. | `from conversiones import celsius_a_fahrenheit` | +| **Dependencia** | Librería de la que depende tu código | Una librería externa que tu proyecto necesita para funcionar. Si no está instalada, el programa falla. | `requests` es una dependencia del módulo de APIs | +| **Open Source (Código Abierto)** | Software con código público y libre | Software cuyo código fuente es público, gratuito y puede ser modificado y redistribuido. Nerve y este repo son Open Source. | Licencia GNU GPL v3 | +| **GNU GPL v3** | Licencia de Nerve Community | Una licencia Open Source que garantiza que el software es libre y que cualquier derivado también debe serlo. | El archivo `LICENSE` en el repo | +| **multiplataforma** | Funciona en varios sistemas operativos | Software que funciona en Linux, macOS y Windows sin cambios. | Nerve usa Unix Sockets en Linux/macOS y TCP en Windows | +| **offline-first** | Funciona sin internet | Diseñado para funcionar sin conexión a internet. Nerve se conecta localmente, no usa la nube. | Nerve solo necesita que los procesos estén en la misma máquina | --- ## Calidad, Bugs y Proceso de Desarrollo -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Bug** | Error en el software | Fallo en el código que hace que el programa no funcione como se esperaba. | "El conversor de divisas dice que 1 USD = 0 MXN. Es un bug." | N/A | - | -| **Debugging** | Depuración — proceso de encontrar bugs | El arte de investigar y corregir errores en el código. Puede ser con herramientas o simplemente leyendo y pensando. | Usar `print()` para ver el valor de una variable | N/A | - | -| **Refactoring** | Mejorar código sin cambiar su comportamiento | Reescribir el código para que sea más limpio, legible o eficiente, sin que haga cosas diferentes. | Dividir una función gigante en varias funciones pequeñas | N/A | - | -| **Linter** | Analizador automático de estilo | Herramienta que revisa tu código buscando errores de formato, malas prácticas o inconsistencias de estilo. | `black` (Python), que usamos en el CI del repo | N/A | - | -| **CI/CD** | Integración y entrega continua | Automatizaciones que se ejecutan con cada PR: corren tests, verifican formato, despliegan código. | El Linter Compasivo que corre en cada PR de Nerve Community | N/A | - | -| **Test / Prueba** | Código que verifica que tu código funciona | Pequeños programas automáticos que comprueban si tus funciones devuelven los resultados esperados. | `assert suma(2, 3) == 5` | N/A | - | -| **edge case** | Caso borde o extremo | Una entrada o situación inusual que puede romper tu programa. Pensar en ellos es parte del desarrollo robusto. | ¿Qué pasa si el usuario escribe una letra donde va un número? | N/A | - | -| **hardcoded** | Valor fijo escrito directamente en el código | Un valor que no se puede cambiar sin editar el código fuente. Es mala práctica para configuraciones y rutas. | ❌ `ruta = "/home/user/Descargas"` | N/A | - | -| **dry-run** | Modo de simulación sin cambios reales | Ejecutar un script para que muestre qué haría, sin modificar nada. Fundamental en scripts que mueven archivos. | `python organizador.py --dry-run` | N/A | - | -| **logging** | Registro de eventos del programa | Guardar mensajes sobre lo que hace el programa mientras corre. Útil para diagnosticar problemas. | `logging.info("Archivo procesado: datos.csv")` | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **Bug** | Error en el software | Fallo en el código que hace que el programa no funcione como se esperaba. | "El conversor de divisas dice que 1 USD = 0 MXN. Es un bug." | +| **Debugging** | Proceso de encontrar y corregir bugs | El arte de investigar y corregir errores. Puede ser con herramientas o simplemente leyendo y pensando. | Usar `print()` para ver el valor de una variable | +| **Refactoring** | Mejorar código sin cambiar su comportamiento | Reescribir el código para que sea más limpio o eficiente, sin que haga cosas diferentes. | Dividir una función gigante en varias pequeñas | +| **Linter** | Analizador automático de estilo | Herramienta que revisa tu código buscando errores de formato, malas prácticas o inconsistencias. | `black` (Python), que usa el CI del repo | +| **CI/CD** | Integración y entrega continua | Automatizaciones que corren con cada PR: tests, verificación de formato, despliegue. | El Linter Compasivo que corre en cada PR de Nerve Community | +| **Test / Prueba** | Código que verifica que tu código funciona | Pequeños programas automáticos que comprueban si tus funciones devuelven los resultados esperados. | `assert suma(2, 3) == 5` | +| **edge case** | Caso borde o extremo | Una entrada o situación inusual que puede romper tu programa. Pensar en ellos es parte del desarrollo robusto. | ¿Qué pasa si el usuario escribe una letra donde va un número? | +| **hardcoded** | Valor fijo escrito directamente en el código | Un valor que no se puede cambiar sin editar el código fuente. Es mala práctica para configuraciones y rutas. | `ruta = "/home/user/Descargas"` (incorrecto) | +| **dry-run** | Modo de simulación sin cambios reales | Ejecutar un script para que muestre qué haría, sin modificar nada. Fundamental en scripts que mueven archivos. | `python organizador.py --dry-run` | +| **logging** | Registro de eventos del programa | Guardar mensajes sobre lo que hace el programa mientras corre. Útil para diagnosticar problemas. | `logging.info("Archivo procesado: datos.csv")` | --- ## Conceptos de Seguridad -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Criptografía** | Ciencia de proteger información | Técnicas para cifrar datos y hacer comunicaciones seguras. | HTTPS cifra los datos entre tu navegador y el servidor | N/A | - | -| **random vs secrets** | Diferencia crucial en Python | `random` genera números predecibles (no seguros para contraseñas). `secrets` genera números impredecibles, criptográficamente seguros. | ✓ `secrets.token_urlsafe(16)` para contraseñas reales | N/A | - | -| **Token de acceso** | Llave de autenticación | Una cadena de texto larga y aleatoria que actúa como contraseña para autenticar accesos a una API o servicio. | Token de GitHub para usar `gh auth login` | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **Criptografía** | Ciencia de proteger información | Técnicas para cifrar datos y hacer comunicaciones seguras. | HTTPS cifra los datos entre tu navegador y el servidor | +| **random vs secrets** | Diferencia crucial en Python | `random` genera números predecibles (no seguros para contraseñas). `secrets` genera números criptográficamente seguros. | `secrets.token_urlsafe(16)` para contraseñas reales | +| **Token de acceso** | Llave de autenticación | Una cadena larga y aleatoria que actúa como contraseña para autenticar accesos a una API o servicio. | Token de GitHub para usar `gh auth login` | --- ## El Ecosistema Alenia / Nerve -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Nerve** | Motor IPC de Alenia Studios | El sistema de comunicación entre procesos que es el tema central de este repo. Los retos de nivel Core giran en torno a él. | [alenia-nerve en GitHub](https://github.com/Kaia-Alenia/alenia-nerve) | N/A | - | -| **Zenith** | Framework principal de Alenia Studios | El framework de Alenia donde viven las herramientas más maduras. Las mejores soluciones de este repo pueden "graduarse" a Zenith. | `zenith-nerve-tools` | N/A | - | -| **nerve-community** | Este repositorio | El espacio de aprendizaje y contribución para construir herramientas y scripts usando Nerve, de forma abierta y colaborativa. | `https://github.com/Kaia-Alenia/nerve-community` | N/A | - | -| **Premio Trimestral** | Reconocimiento a la mejor contribución | La contribución más destacada del trimestre recibe 4 meses gratis de Google AI Pro. | Ver sección "Premio Trimestral" en el README | N/A | - | -| **TRANSPARENCIA.md** | Informe de uso de donaciones | Documento público donde se detalla cómo se usaron los fondos de donaciones de la comunidad. | [TRANSPARENCIA.md](../TRANSPARENCIA.md) | N/A | - | +| Término | Qué es | Ejemplo | +| :--- | :--- | :--- | +| **Nerve** | Motor IPC de Alenia Studios | El sistema de comunicación entre procesos que es el tema central de este repo. Los retos de nivel Core giran en torno a él. Repositorio: [alenia-nerve](https://github.com/Kaia-Alenia/alenia-nerve) | +| **Zenith** | Framework principal de Alenia Studios | Donde viven las herramientas más maduras de Alenia. Las mejores soluciones de este repo pueden graduarse a Zenith. | +| **nerve-community** | Este repositorio | El espacio de aprendizaje y contribución para construir herramientas usando Nerve de forma abierta. | +| **TRANSPARENCIA.md** | Informe de uso de donaciones | Documento público donde se detalla cómo se usaron los fondos de la comunidad. Ver: [TRANSPARENCIA.md](../TRANSPARENCIA.md) | --- -> 💡 **Regla de oro del desarrollo:** Antes de instalar una librería o escribir código complejo, pregúntate: ¿ya existe algo en la librería estándar que haga esto? La mayoría de las veces, ¡la respuesta es sí! +> Antes de instalar una librería o escribir código complejo, pregúntate: ¿ya existe algo en la librería estándar que haga esto? La mayoría de las veces, la respuesta es sí. --- diff --git a/glosario/git-github.md b/glosario/git-github.md index 5418284..2f71fc6 100644 --- a/glosario/git-github.md +++ b/glosario/git-github.md @@ -1,71 +1,71 @@ -# 🔧 Glosario: Git y GitHub +# Glosario: Git y GitHub -Todo lo que necesitas saber sobre el control de versiones y la plataforma GitHub para contribuir en cualquier proyecto Open Source, incluyendo Nerve Community. +Todo lo que necesitas saber sobre control de versiones y la plataforma GitHub para contribuir en cualquier proyecto Open Source, incluyendo Nerve Community. --- ## Conceptos Fundamentales -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Git** | Programa de control de versiones | Es como una "máquina del tiempo" para tu código. Guarda el historial completo de todos los cambios que hiciste, quién los hizo y cuándo. | `git --version` (para verificar que está instalado) | N/A | - | -| **GitHub** | Plataforma web para guardar código | Una "red social para programadores" donde subes tu código con Git y puedes colaborar con otros. Nerve Community vive aquí. | `https://github.com/Kaia-Alenia/nerve-community` | N/A | - | -| **Repositorio (Repo)** | Carpeta de proyecto con historial | Contiene todos los archivos del proyecto más el historial completo de cambios. Es lo que "clonas" cuando quieres trabajar en un proyecto. | `https://github.com/Kaia-Alenia/nerve-community` | N/A | - | -| **Open Source** | Código abierto y público | Software cuyo código fuente es visible para todos y puede ser modificado y distribuido libremente. Nerve y este repo son Open Source. | Licencia GNU GPL v3 que usa este repo | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | Error común | +| :--- | :--- | :--- | :--- | :--- | +| **Git** | Programa de control de versiones | Guarda el historial completo de todos los cambios que hiciste, quién los hizo y cuándo. Como una máquina del tiempo para tu código. | `git --version` | — | +| **GitHub** | Plataforma web para alojar código | Donde subes tu código con Git y colaboras con otros. Nerve Community vive aquí. | `https://github.com/Kaia-Alenia/nerve-community` | — | +| **Repositorio (Repo)** | Carpeta de proyecto con historial | Contiene todos los archivos del proyecto más el historial completo de cambios. Es lo que clonas para trabajar. | `https://github.com/Kaia-Alenia/nerve-community` | — | +| **Open Source** | Código abierto y público | Software cuyo código fuente es visible para todos y puede ser modificado y distribuido libremente. | Licencia GNU GPL v3 que usa este repo | — | --- ## Flujo de Trabajo Básico -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Fork** | Copia personal del repositorio | Crea una copia exacta de un repo ajeno en tu cuenta de GitHub. Es como sacarle una fotocopia a un libro de la biblioteca para poder rayarlo sin arruinar el original. | Botón "Fork" en GitHub | N/A | - | -| **git clone** | Descarga del repo a tu computadora | Trae los archivos de GitHub a tu máquina local. Es como llevarte esa fotocopia desde la nube a tu disco duro para trabajar. Siempre clonas *tu fork*. | `git clone https://github.com/...` | N/A | - | -| **Branch (Rama)** | Línea de trabajo independiente | Crea una "dimensión paralela" de tu código. Trabajas en una rama separada para no tocar ni romper la rama principal (`main`). | `git checkout -b solucion-reto-01` | N/A | - | -| **Checkout** | Cambiar de rama | Te "teletransporta" entre ramas. También sirve para crear ramas nuevas con la bandera `-b`. | `git checkout main` o `git checkout -b mi-rama` | N/A | - | -| **git add** | Preparar archivos para el commit | Marca qué archivos quieres incluir en el próximo punto de guardado. Con `.` preparas todos los archivos modificados. | `git add .` o `git add mi_archivo.py` | N/A | - | -| **Commit** | Punto de guardado permanente | Guarda los cambios preparados con un mensaje descriptivo. Es el "Save State" del videojuego, pero para código. | `git commit -m "feat: agregar solución al reto 01"` | N/A | - | -| **Push** | Subir commits a GitHub | Envía los commits que tienes en tu computadora hacia tu repositorio en GitHub (en la nube). | `git push origin solucion-reto-01` | N/A | - | -| **Pull** | Bajar/actualizar desde GitHub | Descarga los cambios más recientes que existan en GitHub hacia tu computadora local. | `git pull origin main` | N/A | - | -| **Pull Request (PR)** | Solicitud de integración de cambios | Le dices al dueño del repo original: *"Oye, hice estos cambios en mi fork, ¿los quieres agregar al proyecto oficial?"* | Clic en "Compare & pull request" en GitHub después de hacer push | N/A | - | -| **Merge** | Fusionar código | Acción de aceptar un Pull Request y unir el código nuevo con el proyecto principal. Lo hacen los maintainers. | "¡Felicidades, hicimos merge de tu PR!" | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | Error común | +| :--- | :--- | :--- | :--- | :--- | +| **Fork** | Copia personal de un repositorio ajeno | Crea una copia exacta en tu cuenta de GitHub para poder modificarla sin tocar el original. | Botón "Fork" en GitHub | — | +| **git clone** | Descarga del repo a tu computadora | Trae los archivos de GitHub a tu máquina local. Siempre clonas tu fork. | `git clone https://github.com/TU-USUARIO/nerve-community.git` | — | +| **Branch (Rama)** | Línea de trabajo independiente | Crea una dimensión paralela de tu código para no tocar ni romper la rama `main`. | `git checkout -b solucion-python-M01` | — | +| **Checkout** | Cambiar de rama | Te mueve entre ramas. Con `-b` crea una nueva. | `git checkout main` | — | +| **git add** | Preparar archivos para el commit | Marca qué archivos quieres incluir en el próximo punto de guardado. | `git add .` | — | +| **Commit** | Punto de guardado permanente | Guarda los cambios preparados con un mensaje descriptivo. | `git commit -m "feat: agregar solución al módulo 01"` | — | +| **Push** | Subir commits a GitHub | Envía los commits de tu computadora hacia tu repositorio en GitHub. | `git push origin solucion-python-M01` | — | +| **Pull** | Bajar actualizaciones desde GitHub | Descarga los cambios más recientes de GitHub hacia tu máquina local. | `git pull origin main` | — | +| **Pull Request (PR)** | Solicitud de integración de cambios | Le dices al dueño del repo: "hice estos cambios en mi fork, ¿los quieres en el proyecto oficial?" | Clic en "Compare & pull request" en GitHub | — | +| **Merge** | Fusionar código | Aceptar un Pull Request y unir el código nuevo con el proyecto principal. Lo hacen los maintainers. | "PR merged" | — | --- ## Sincronización y Resolución de Problemas -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **origin** | Nombre del repo remoto por defecto | Es el "apodo" que Git le da a tu repositorio en GitHub cuando clonas. Cuando dices `git push origin`, empujas hacia ahí. | `git push origin main` | N/A | - | -| **upstream** | El repositorio original (del que hiciste fork) | Se usa para mantener tu fork sincronizado con los cambios del proyecto original. Se configura una sola vez. | `git remote add upstream https://github.com/Kaia-Alenia/nerve-community.git` | N/A | - | -| **git status** | Estado actual de tu trabajo | Muestra qué archivos modificaste, cuáles están preparados (staged) y cuáles no. Úsalo antes de hacer commit. | `git status` | N/A | - | -| **Conflict (Conflicto)** | Dos versiones del mismo código chocan | Ocurre cuando tú y otra persona editaron la misma línea del mismo archivo. Git no sabe cuál versión elegir y te pide que lo resuelvas manualmente. | "CONFLICT (content): Merge conflict in archivo.py" | N/A | - | -| **Rebase** | Reorganizar historial de commits | Mueve o reaplica tus commits encima de otra rama, como si tu trabajo lo hubieras empezado después de los últimos cambios. Más avanzado que merge. | `git rebase main` | N/A | - | -| **git log** | Historial de commits | Muestra una lista de todos los commits que se han hecho en la rama actual, con autor, fecha y mensaje. | `git log --oneline` | N/A | - | -| **git diff** | Ver qué cambió exactamente | Muestra las diferencias línea por línea entre tu versión actual y la última guardada en un commit. Útil antes de hacer commit. | `git diff mi_archivo.py` | N/A | - | -| **git stash** | Guardar cambios temporalmente | "Guarda" tus cambios sin hacer commit para que puedas cambiar de rama limpiamente y recuperarlos después. | `git stash` y luego `git stash pop` | N/A | - | -| **git commit --amend** | Corregir el último commit | Modifica el mensaje o los archivos del commit que acabas de hacer, sin crear uno nuevo. Solo funciona si aún no hiciste push. | `git commit --amend -m "Mensaje corregido"` | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | Error común | +| :--- | :--- | :--- | :--- | :--- | +| **origin** | Nombre del repo remoto por defecto | El "apodo" que Git le da a tu repositorio en GitHub cuando clonas. | `git push origin main` | — | +| **upstream** | El repositorio original del que hiciste fork | Se usa para mantener tu fork sincronizado con los cambios del proyecto original. | `git remote add upstream https://github.com/Kaia-Alenia/nerve-community.git` | — | +| **git status** | Estado actual de tu trabajo | Muestra qué archivos modificaste, cuáles están staged y cuáles no. | `git status` | — | +| **Conflict (Conflicto)** | Dos versiones del mismo código chocan | Ocurre cuando tú y otra persona editaron la misma línea del mismo archivo. Git no sabe cuál elegir. | `CONFLICT (content): Merge conflict in archivo.py` | — | +| **Rebase** | Reorganizar historial de commits | Mueve tus commits encima de otra rama. Más avanzado que merge. | `git rebase main` | — | +| **git log** | Historial de commits | Lista todos los commits de la rama actual con autor, fecha y mensaje. | `git log --oneline` | — | +| **git diff** | Ver qué cambió exactamente | Muestra las diferencias línea por línea entre tu versión actual y el último commit. | `git diff mi_archivo.py` | — | +| **git stash** | Guardar cambios temporalmente | Guarda tus cambios sin hacer commit para que puedas cambiar de rama y recuperarlos después. | `git stash` y luego `git stash pop` | — | +| **git commit --amend** | Corregir el último commit | Modifica el mensaje o los archivos del último commit. Solo funciona antes de hacer push. | `git commit --amend -m "Mensaje corregido"` | — | --- ## GitHub: Conceptos de Plataforma -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Issue** | Ticket o tarea dentro de GitHub | Un foro donde se reportan bugs, se piden funciones nuevas o se listan los retos disponibles. En Nerve Community cada reto tiene su propio Issue. | [Issue #1 — Reto 01 Chat Terminal](https://github.com/Kaia-Alenia/nerve-community/issues/1) | N/A | - | -| **Maintainer** | Mantenedor del proyecto | La persona (o equipo) que tiene permisos para hacer merge de PRs y tomar decisiones sobre el proyecto. Alejandro es el maintainer de Nerve Community. | `@Alenia-Studios` | N/A | - | -| **Contribuidor (Contributor)** | Persona que ha enviado código | Cualquier persona cuyo Pull Request haya sido aceptado (merged) en el proyecto. ¡Tú puedes ser uno! | Ver sección "Cumpliendo Retos" en el README | N/A | - | -| **Label (Etiqueta)** | Categoría de un Issue o PR | Tags de colores que clasifican un Issue. Ejemplos: `good-first-issue` (ideal para principiantes), `bug`, `disponible`. | `good-first-issue`, `python-general`, `disponible` | N/A | - | -| **Squash and Merge** | Fusionar aplastando todos los commits en uno | Cuando se hace merge de un PR, todos los commits de esa rama se "aplastan" en un solo commit limpio en `main`. | "Squashed and merged pull request #47" | N/A | - | -| **gh CLI** | GitHub CLI, la línea de comandos de GitHub | Herramienta oficial de GitHub para usar la plataforma desde la terminal: crear PRs, ver issues, autenticarte, etc. | `gh pr create`, `gh auth login`, `gh pr list` | N/A | - | -| **GitHub Actions** | Automatizaciones del repositorio | Scripts automáticos que se ejecutan cuando ocurre algo en el repo (ej: alguien abre un PR). En Nerve Community, se usa para revisar formato del código automáticamente. | El Linter Compasivo que formatea Python con `black` | N/A | - | -| **README.md** | El archivo de presentación del repo | Es el primer archivo que GitHub muestra al entrar a un repositorio. Explica qué es el proyecto, cómo usarlo y cómo contribuir. | Este mismo archivo que estás leyendo | N/A | - | -| **Sparse-Checkout** | Clonar solo una carpeta específica | Técnica avanzada para descargar únicamente la parte del repositorio que necesitas, en vez de todo el proyecto. Ideal si tienes datos limitados (ej: en Termux). | `git sparse-checkout set retos/general/01-lista-de-tareas-cli` | N/A | - | -| **.gitignore** | Archivo de ignorados | Un archivo especial que le dice a Git qué archivos o carpetas NO debe rastrear ni subir (ej. contraseñas, dependencias como `node_modules`). | Archivo llamado `.gitignore` | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | Error común | +| :--- | :--- | :--- | :--- | :--- | +| **Issue** | Ticket o tarea dentro de GitHub | Donde se reportan bugs, se piden funciones o se hacen preguntas. | [Issues de Nerve Community](https://github.com/Kaia-Alenia/nerve-community/issues) | — | +| **Maintainer** | Mantenedor del proyecto | La persona con permisos para hacer merge de PRs y tomar decisiones sobre el proyecto. | `@Alenia-Studios` | — | +| **Contribuidor** | Persona que ha enviado código aceptado | Cualquier persona cuyo PR haya sido merged en el proyecto. | Ver `docs/COMO-USAR-COMO-ALUMNO.md` | — | +| **Label (Etiqueta)** | Categoría de un Issue o PR | Tags de colores que clasifican un Issue. | `good-first-issue`, `bug`, `disponible` | — | +| **Squash and Merge** | Fusionar aplastando commits en uno | Todos los commits del PR se unen en un solo commit limpio en `main`. | "Squashed and merged pull request #47" | — | +| **gh CLI** | GitHub CLI | Herramienta oficial de GitHub para operar la plataforma desde la terminal. | `gh pr create`, `gh auth login`, `gh pr list` | — | +| **GitHub Actions** | Automatizaciones del repositorio | Scripts que se ejecutan cuando ocurre algo en el repo (ej: alguien abre un PR). | El Linter Compasivo que formatea Python con `black` | — | +| **README.md** | El archivo de presentación del repo | El primer archivo que GitHub muestra al entrar a un repositorio. | Este mismo archivo que estás leyendo | — | +| **Sparse-Checkout** | Clonar solo una carpeta específica | Descarga únicamente la parte del repo que necesitas. Útil con datos limitados (ej: en Termux). | `git sparse-checkout set python/Modulo-01-Fundamentos` | — | +| **.gitignore** | Archivo de ignorados | Le dice a Git qué archivos o carpetas no debe rastrear ni subir. | Archivo llamado `.gitignore` | — | --- -> 💡 **IMPORTANTE:** No tienes que memorizar todo esto de golpe. Con que recuerdes el ciclo básico — **fork → clone → branch → commit → push → PR** — ya puedes hacer tu primera contribución. ¡El resto lo aprenderás con la práctica! +> El ciclo básico que necesitas memorizar para tu primera contribución es: **fork → clone → branch → commit → push → PR**. El resto lo aprenderás con la práctica. --- diff --git a/glosario/python/09-data-ia.md b/glosario/python/09-data-ia.md new file mode 100644 index 0000000..b646892 --- /dev/null +++ b/glosario/python/09-data-ia.md @@ -0,0 +1,144 @@ +# 09 - Data e IA + +### `numpy` + +**¿Qué es?** +Librería fundamental de Python para cálculo numérico que permite manejar arrays (arreglos) y matrices grandes y multidimensionales de forma muy eficiente. + +**¿Para qué se usa?** +Para hacer operaciones matemáticas complejas o procesar grandes cantidades de números mucho más rápido que usando listas estándar de Python. + +**Ejemplo:** +```python +import numpy as np +numeros = np.array([1, 2, 3, 4, 5]) +print(numeros * 2) # Multiplica todo el array de golpe sin bucles +``` + +**Errores comunes de principiante:** +- Confundir una lista de Python con un array de numpy. +- No importar la librería con el alias estándar `np`. + +**Términos relacionados:** [`pandas`](#pandas--dataframe--series) + +### `pandas` / `DataFrame` / `Series` + +**¿Qué es?** +Pandas es una librería para análisis y manipulación de datos. Una `Series` es una columna de datos (1D), y un `DataFrame` es una tabla completa de datos (2D), similar a una hoja de cálculo de Excel. + +**¿Para qué se usa?** +Para cargar, limpiar, transformar y analizar tablas de datos estructurados de forma rápida y sencilla. + +**Ejemplo:** +```python +import pandas as pd +datos = pd.DataFrame({'Nombre': ['Ana', 'Luis'], 'Edad': [25, 30]}) +print(datos) +``` + +**Errores comunes de principiante:** +- Intentar usar bucles `for` para modificar un DataFrame, lo cual es muy lento (es mejor usar operaciones vectorizadas). +- Olvidarse de manejar los valores nulos o vacíos antes de analizar los datos. + +**Términos relacionados:** [`CSV`](#csv), [`numpy`](#numpy) + +### `matplotlib` + +**¿Qué es?** +Es la librería principal de Python para crear gráficos y visualizaciones de datos estáticas, animadas o interactivas. + +**¿Para qué se usa?** +Para visualizar datos gráficamente (ej: gráficos de líneas, barras, dispersión) y entender mejor las tendencias o resultados de un modelo. + +**Ejemplo:** +```python +import matplotlib.pyplot as plt +plt.plot([1, 2, 3], [4, 5, 2]) +plt.show() +``` + +**Errores comunes de principiante:** +- Olvidar llamar a `plt.show()` y preguntarse por qué no aparece el gráfico. + +**Términos relacionados:** [`pandas`](#pandas--dataframe--series) + +### `scikit-learn` + +**¿Qué es?** +Una librería de Machine Learning que proporciona herramientas simples y eficientes para minería de datos y análisis de datos. Incluye algoritmos como regresión, clasificación y agrupamiento. + +**¿Para qué se usa?** +Para entrenar modelos de Inteligencia Artificial tradicionales (no redes neuronales complejas) que aprenden patrones a partir de los datos. + +**Ejemplo:** +```python +from sklearn.linear_model import LinearRegression +modelo = LinearRegression() +# modelo.fit(X, y) # Entrena el modelo +# predicciones = modelo.predict(nuevos_X) +``` + +**Errores comunes de principiante:** +- Pasar datos al modelo sin haberlos limpiado (con valores nulos o texto sin convertir a números). +- No dividir los datos en conjuntos de entrenamiento y prueba. + +**Términos relacionados:** [`regresión lineal`](#regresión-lineal-vs-clasificación) + +### `regresión lineal` vs `clasificación` + +**¿Qué es?** +Son dos tipos principales de problemas en Machine Learning predictivo. La regresión predice un valor numérico continuo (ej: el precio de una casa). La clasificación predice una categoría (ej: si un correo es spam o no). + +**¿Para qué se usa?** +Para decidir qué tipo de algoritmo de IA necesitas usar según lo que quieras adivinar o predecir en tu programa. + +**Ejemplo:** +```python +# Regresión: predecir un precio (ej. 1500.50) +# Clasificación: predecir una etiqueta (ej. "Aprobado" o "Reprobado") +``` + +**Errores comunes de principiante:** +- Usar un algoritmo de clasificación cuando se quiere predecir un número (regresión), o viceversa. + +**Términos relacionados:** [`scikit-learn`](#scikit-learn) + +### `CSV` + +**¿Qué es?** +Comma-Separated Values (Valores Separados por Comas). Es un formato de archivo de texto muy simple para guardar datos tabulares, donde cada línea es una fila y las columnas se separan por comas. + +**¿Para qué se usa?** +Es el formato universal y más ligero para compartir datos (datasets) entre distintos programas, bases de datos y algoritmos de IA. + +**Ejemplo:** +```csv +nombre,edad,ciudad +Ana,25,Madrid +Luis,30,Bogotá +``` + +**Errores comunes de principiante:** +- Usar comas dentro de los valores (ej: "Ana, María") sin entrecomillar el texto, rompiendo la estructura de columnas del archivo. + +**Términos relacionados:** [`pandas`](#pandas--dataframe--series) + +### `correlación` y `feature` + +**¿Qué es?** +Una `feature` (característica) es cada variable o columna de información que usas para hacer una predicción (ej: la edad o los ingresos). La `correlación` mide qué tan fuerte es la relación matemática entre dos cosas (ej: a más altura, mayor peso). + +**¿Para qué se usa?** +Para saber qué columnas de datos son realmente útiles (buenas "features") para entrenar a nuestro modelo de IA y cuáles deberíamos descartar. + +**Ejemplo:** +```python +# Un DataFrame tiene múltiples columnas (features) +# datos.corr() mostraría la correlación entre ellas +``` + +**Errores comunes de principiante:** +- Creer que correlación implica causalidad (que si dos cosas suben a la vez, una causa a la otra, lo cual es falso). +- Meter todas las columnas (features) posibles al modelo sin pensar si realmente tienen sentido o correlación con lo que se quiere predecir. + +**Términos relacionados:** [`scikit-learn`](#scikit-learn), [`pandas`](#pandas--dataframe--series) diff --git a/glosario/python/10-nerve-ipc.md b/glosario/python/10-nerve-ipc.md new file mode 100644 index 0000000..459426d --- /dev/null +++ b/glosario/python/10-nerve-ipc.md @@ -0,0 +1,162 @@ +# 10 - Nerve e IPC + +### `IPC` (Inter-Process Communication) + +**¿Qué es?** +Comunicación Entre Procesos. Es un conjunto de mecanismos que permite que distintos programas (procesos) que se están ejecutando en la misma computadora se envíen datos o mensajes entre sí. + +**¿Para qué se usa?** +Para que diferentes partes de un sistema complejo, como pequeños scripts independientes, puedan coordinarse y trabajar juntos intercambiando información. + +**Ejemplo:** +```python +# Un script de interfaz de usuario manda un mensaje por IPC +# a un script que maneja la base de datos para que guarde información. +``` + +**Errores comunes de principiante:** +- Pensar que dos scripts de Python en la misma computadora comparten variables mágicamente. Debes enviar la información explícitamente usando IPC. + +**Términos relacionados:** [`Unix Socket`](#unix-socket-vs-tcp), [`canal / channel`](#canal--channel) + +### `NexusHub` + +**¿Qué es?** +En el ecosistema Nerve, es el servidor o núcleo central que se encarga de recibir, organizar y distribuir todos los mensajes que los diferentes programas se envían entre sí. + +**¿Para qué se usa?** +Es el corazón de tu red local. En lugar de que todos tus scripts intenten hablarse entre ellos (lo cual es caótico), todos hablan con el NexusHub y él se encarga de entregar los mensajes al destinatario correcto. + +**Ejemplo:** +```python +# Comando teórico para iniciar el hub +# nerve start hub +``` + +**Errores comunes de principiante:** +- Olvidar iniciar el NexusHub antes de iniciar los clientes, haciendo que nadie pueda conectarse ni enviar mensajes. + +**Términos relacionados:** [`NexusClient`](#nexusclient) + +### `NexusClient` + +**¿Qué es?** +En Nerve, es la herramienta (cliente) que usa un programa individual para conectarse al NexusHub. Actúa como el puente entre tu código y la red del Hub. + +**¿Para qué se usa?** +Para que tu script pueda enviar (publicar) y recibir (suscribirse a) mensajes a través de la red de Nerve de manera sencilla. + +**Ejemplo:** +```python +from nerve import NexusClient +cliente = NexusClient("mi_script_1") +cliente.conectar() +``` + +**Errores comunes de principiante:** +- Instanciar un cliente pero olvidar llamar a la función de conectar. +- Darle el mismo nombre a dos clientes distintos, causando conflictos en el Hub. + +**Términos relacionados:** [`NexusHub`](#nexushub) + +### `canal / channel` + +**¿Qué es?** +Un "tema" o "tópico" específico bajo el cual se agrupan los mensajes. Es como una sala de chat temática donde solo se habla de una cosa. + +**¿Para qué se usa?** +Para organizar la información. Si un script solo necesita saber de "temperatura", se suscribe al canal `sensores/temperatura` y así no recibe mensajes irrelevantes del canal `sistema/errores`. + +**Ejemplo:** +```python +# Publicar un mensaje en un canal específico +cliente.publicar(canal="alertas/criticas", mensaje="Error en el servidor") +``` + +**Errores comunes de principiante:** +- Escribir mal el nombre del canal al suscribirse (ej. "alerta" en vez de "alertas"), por lo que nunca llegan los mensajes esperados. + +**Términos relacionados:** [`suscripción vs publicación`](#suscripción-vs-publicación) + +### `suscripción` vs `publicación` + +**¿Qué es?** +Es el modelo de comunicación básico en mensajería (Pub/Sub). **Publicar** (publish) es emitir un mensaje hacia un canal sin importar quién lo escucha. **Suscribirse** (subscribe) es decirle al Hub "avísame cuando alguien publique algo en este canal". + +**¿Para qué se usa?** +Para desacoplar el código. El que publica no necesita saber cuántos están escuchando, y el que escucha no necesita saber quién mandó el dato, solo le importa el canal. + +**Ejemplo:** +```python +# Publicar +cliente.publicar("clima", "Hará sol") + +# Suscribirse +def recibir_clima(msg): + print("El clima será:", msg) +cliente.suscribir("clima", recibir_clima) +``` + +**Errores comunes de principiante:** +- Intentar procesar la respuesta inmediatamente después de publicar, olvidando que este modelo es asíncrono y los mensajes fluyen sin detener el programa. + +**Términos relacionados:** [`canal / channel`](#canal--channel) + +### `Unix Socket` vs `TCP` + +**¿Qué es?** +Son dos protocolos o formas de transporte para mandar los mensajes. Un `Unix Socket` manda los datos a través de un archivo especial en el sistema operativo (solo funciona en la misma máquina). `TCP` manda los datos a través de una dirección IP y un puerto (funciona por internet o red local). + +**¿Para qué se usa?** +Los Unix Sockets son increíblemente rápidos para comunicar programas en la *misma computadora*. TCP se usa si los programas están en *computadoras diferentes* conectadas por red. + +**Ejemplo:** +```python +# Un Unix Socket se ve como una ruta de archivo: /tmp/nerve.sock +# Una dirección TCP se ve así: 127.0.0.1:8080 +``` + +**Errores comunes de principiante:** +- Intentar usar Unix Sockets para comunicar programas que están en distintas computadoras o en Windows (donde históricamente no son el estándar). + +**Términos relacionados:** [`IPC`](#ipc-inter-process-communication) + +### `offline-first` + +**¿Qué es?** +Un principio de diseño donde el software se construye para funcionar correcta y fluidamente sin conexión a internet desde el primer momento, usando la red solo cuando esté disponible. + +**¿Para qué se usa?** +Para hacer aplicaciones más rápidas, robustas y privadas, que no dependan de la nube para sus funciones principales. Nerve usa esta filosofía para mantener todo el procesamiento local. + +**Ejemplo:** +```python +# Un agente de IA que corre modelos pequeños en tu propia computadora, +# en vez de llamar a una API de OpenAI a través de internet. +``` + +**Errores comunes de principiante:** +- Asumir que "local" significa "inútil", ignorando que hoy en día una computadora promedio tiene poder de sobra para tareas complejas sin enviar datos a la nube. + +**Términos relacionados:** [`NexusHub`](#nexushub) + +### `heartbeat` + +**¿Qué es?** +Un "latido de corazón". Es un pequeño mensaje que un programa envía regularmente (por ejemplo, cada 5 segundos) al Hub para avisar "sigo vivo y funcionando". + +**¿Para qué se usa?** +Para que el sistema (o el orquestador) sepa rápidamente si un proceso se colgó, crasheó o se desconectó, y pueda reiniciar ese servicio o alertar al usuario. + +**Ejemplo:** +```python +import time +while True: + cliente.publicar("sistema/latidos", "estoy_vivo") + time.sleep(5) +``` + +**Errores comunes de principiante:** +- Poner un intervalo de *heartbeat* demasiado corto, saturando la red con mensajes inútiles, o demasiado largo, tardando mucho en detectar una falla. + +**Términos relacionados:** [`IPC`](#ipc-inter-process-communication) diff --git a/glosario/python/11-herramientas-avanzadas.md b/glosario/python/11-herramientas-avanzadas.md new file mode 100644 index 0000000..49ab9ac --- /dev/null +++ b/glosario/python/11-herramientas-avanzadas.md @@ -0,0 +1,174 @@ +# 11 - Herramientas Avanzadas y Nerve + +### `nerve.config` + +**¿Qué es?** +Un archivo especial de configuración que guarda parámetros sobre cómo debe comportarse un agente o servicio dentro de la red de Nerve. + +**¿Para qué se usa?** +Para guardar cosas como el nombre del agente, puertos a los que debe conectarse, o configuraciones locales, sin tener que "hardcodear" (escribir fijamente) esa información dentro del código de Python. + +**Ejemplo:** +```json +{ + "agent_name": "procesador_de_datos", + "hub_address": "127.0.0.1:8000" +} +``` + +**Errores comunes de principiante:** +- Subir este archivo a un repositorio público con contraseñas o tokens privados en su interior. + +**Términos relacionados:** [`secrets`](#secrets) + +### `sparse-checkout` + +**¿Qué es?** +Una técnica avanzada de Git que permite descargar (clonar) solo una parte específica (una carpeta o archivo) de un repositorio gigante, en lugar de descargar todo el historial y todas las carpetas. + +**¿Para qué se usa?** +Para ahorrar espacio y tiempo. Por ejemplo, si solo te interesa trabajar en el Módulo 05, no necesitas descargar todos los archivos, videos y recursos de los otros módulos. + +**Ejemplo:** +```bash +git sparse-checkout set "python/Modulo-05" +``` + +**Errores comunes de principiante:** +- Confundirse y pensar que los demás archivos fueron borrados del proyecto, cuando en realidad solo están ocultos localmente por el sparse-checkout. + +**Términos relacionados:** [Git (Glosario general)](../README.md) + +### `GitHub Actions` + +**¿Qué es?** +La plataforma de integración continua (CI/CD) integrada directamente en GitHub. Es básicamente una computadora en la nube que ejecuta tareas automáticamente cada vez que haces algo (como un `push` a tu repositorio). + +**¿Para qué se usa?** +Para automatizar tareas repetitivas como: ejecutar pruebas en tu código, evaluar tus retos para ver si los pasaste, o hacer deploy (subir tu código a producción) de tu aplicación. + +**Ejemplo:** +```yaml +# Un archivo en .github/workflows/main.yml le dice a GitHub qué hacer +name: Evaluador de Retos +on: [push] +jobs: + evaluar: + runs-on: ubuntu-latest +``` + +**Errores comunes de principiante:** +- Indentar mal los espacios en el archivo YAML de configuración, lo que causa que GitHub no entienda las instrucciones. + +**Términos relacionados:** [`workflow / job / step`](#workflow--job--step) + +### `workflow` / `job` / `step` + +**¿Qué es?** +Son los bloques de construcción de GitHub Actions. Un **workflow** es el proceso completo automatizado. Un workflow contiene **jobs** (trabajos grandes como "probar" o "construir") que pueden correr en paralelo. Cada job se divide en **steps** (pasos individuales y secuenciales, como "instalar Python", "correr script"). + +**¿Para qué se usa?** +Para estructurar ordenadamente qué acciones debe tomar el servidor automático de GitHub. + +**Ejemplo:** +```yaml +# workflow: Evaluación +jobs: + # job: probar-codigo + probar-codigo: + steps: + # step 1 + - name: Instalar dependencias + run: pip install pytest + # step 2 + - name: Correr tests + run: pytest test_main.py +``` + +**Errores comunes de principiante:** +- Pensar que si un *step* falla, el siguiente se ejecutará (por defecto, si un paso falla, todo el job se detiene). + +**Términos relacionados:** [`GitHub Actions`](#github-actions) + +### `secrets` + +**¿Qué es?** +Son variables de entorno cifradas y seguras que configuras en tu repositorio de GitHub para guardar información confidencial (contraseñas, tokens de APIs, claves SSH). + +**¿Para qué se usa?** +Para que los *workflows* de GitHub Actions puedan usar una contraseña (por ejemplo, para publicar en un servidor) sin que nadie más pueda leerla en el código público. + +**Ejemplo:** +```yaml +- name: Loguearse al servidor + run: login --token ${{ secrets.MI_TOKEN_SECRETO }} +``` + +**Errores comunes de principiante:** +- Tratar de imprimir (hacer `echo`) un secret en la consola para ver si funciona; GitHub lo censurará y mostrará `***`. +- Olvidar definir el secret en la configuración del repositorio antes de intentar usarlo en el workflow. + +**Términos relacionados:** [`GitHub Actions`](#github-actions) + +### `pytest` + +**¿Qué es?** +El framework (herramienta) más popular y utilizado para escribir pruebas (tests) de software en Python. + +**¿Para qué se usa?** +Para comprobar automáticamente si tu código hace lo que se supone que debe hacer, sin tener que ejecutarlo manualmente a cada rato. Nerve usa pytest para evaluar automáticamente tus retos. + +**Ejemplo:** +```python +def sumar(a, b): return a + b + +def test_sumar(): + assert sumar(2, 3) == 5 # Si esto es verdad, la prueba pasa +``` + +**Errores comunes de principiante:** +- Escribir funciones de prueba sin el prefijo `test_`, causando que pytest simplemente las ignore al ejecutar. + +**Términos relacionados:** [`coverage`](#coverage) + +### `coverage` + +**¿Qué es?** +La "cobertura de código". Es una métrica (un porcentaje) que te indica qué tanta cantidad de tu código fuente fue ejecutada durante tus pruebas automáticas. + +**¿Para qué se usa?** +Para detectar partes de tu programa que nunca se han probado. Si tienes un `if`/`else`, y tus tests solo pasan por el `if`, tu *coverage* te avisará que te falta probar la situación del `else`. + +**Ejemplo:** +```bash +# Correr tests calculando la cobertura +pytest --cov=mi_programa +# Salida: mi_programa.py 85% de cobertura +``` + +**Errores comunes de principiante:** +- Obsesionarse con llegar al 100% de coverage escribiendo pruebas tontas, en lugar de asegurar que las funciones críticas se prueben con casos reales (calidad sobre cantidad). + +**Términos relacionados:** [`pytest`](#pytest) + +### `linter` / `flake8` / `black` + +**¿Qué es?** +Un **linter** es un programa que analiza tu código para encontrar errores de estilo o posibles bugs sin necesidad de ejecutarlo. `flake8` es un linter famoso en Python. `black` es un formateador: reescribe automáticamente tu código para que cumpla con los estándares estéticos. + +**¿Para qué se usa?** +Para que el código de un equipo grande parezca escrito por una sola persona. Evita discusiones sobre si usar comillas simples o dobles, o cuántos espacios dejar, porque la herramienta toma esas decisiones por ti de forma estandarizada (PEP 8). + +**Ejemplo:** +```bash +# Formatea todo tu código a la perfección +black mi_script.py + +# Revisa si hay errores lógicos o variables sin usar +flake8 mi_script.py +``` + +**Errores comunes de principiante:** +- Ignorar las advertencias del linter pensando que "si corre, está bien", acumulando "deuda técnica" (código difícil de leer). + +**Términos relacionados:** [PEP 8 (Estilos de código)](#) diff --git a/glosario/python/12-arquitectura-avanzada.md b/glosario/python/12-arquitectura-avanzada.md new file mode 100644 index 0000000..c3904ee --- /dev/null +++ b/glosario/python/12-arquitectura-avanzada.md @@ -0,0 +1,173 @@ +# 12 - Arquitectura Avanzada + +### `microservicio` + +**¿Qué es?** +Un enfoque para desarrollar una aplicación como un conjunto de pequeños servicios independientes, donde cada uno ejecuta un proceso único y tiene una sola responsabilidad bien definida. + +**¿Para qué se usa?** +Para que aplicaciones gigantes (como Netflix o Amazon) puedan ser construidas, escaladas y actualizadas por partes. Si la parte que maneja los pagos (un microservicio) se cae, el resto de la página web sigue funcionando. + +**Ejemplo:** +```python +# En lugar de tener un archivo main.py de 10,000 líneas: +# Tienes: usuario_service.py, pagos_service.py, email_service.py +``` + +**Errores comunes de principiante:** +- Intentar usar microservicios para un proyecto extremadamente pequeño, complicándolo innecesariamente (añadiendo problemas de red donde antes solo había llamadas a funciones). + +**Términos relacionados:** [`monolito vs distribuido`](#monolito-vs-distribuido) + +### `orquestador` + +**¿Qué es?** +Un programa o sistema (como Kubernetes en el mundo real, o un script central en Nerve) que se encarga de gestionar, monitorear y organizar automáticamente otros programas o microservicios. + +**¿Para qué se usa?** +Para saber quién está haciendo qué. Si un microservicio muere, el orquestador se da cuenta y lo reinicia. También puede decirle a varios servicios en qué orden deben procesar la información. + +**Ejemplo:** +```python +# El orquestador nota que un worker murió y lanza uno nuevo +if not worker.esta_vivo(): + lanzar_nuevo_proceso("worker_de_respaldo") +``` + +**Errores comunes de principiante:** +- Hacer que el orquestador procese datos él mismo. El orquestador solo debe dirigir el tráfico o gestionar los demás, no hacer el trabajo pesado. + +**Términos relacionados:** [`microservicio`](#microservicio), [`tolerancia a fallos / failover`](#tolerancia-a-fallos--failover) + +### `tolerancia a fallos` / `failover` + +**¿Qué es?** +La capacidad de un sistema para seguir operando correctamente incluso en caso de que uno o más de sus componentes fallen. Un *failover* (conmutación por error) es cuando un sistema de respaldo entra a funcionar automáticamente al caerse el principal. + +**¿Para qué se usa?** +Para sistemas que no se pueden permitir caer (como bancos o servidores de hospitales). Si el servidor A se quema, el servidor B toma su lugar instantáneamente sin que el usuario lo note. + +**Ejemplo:** +```python +try: + conectar_base_de_datos_principal() +except ErrorDeConexion: + # Failover a una base de datos de solo lectura o secundaria + conectar_base_de_datos_respaldo() +``` + +**Errores comunes de principiante:** +- Asumir que el hardware o la red nunca fallarán y escribir código que colapsa por completo ante el primer error de conexión. + +**Términos relacionados:** [`orquestador`](#orquestador) + +### `async/await` + +**¿Qué es?** +Una sintaxis de Python para escribir código concurrente (hacer múltiples cosas a la vez) de manera sencilla. `async` define una función como "asíncrona" y `await` indica "pausa esta función y haz otra cosa mientras esperamos que esto termine". + +**¿Para qué se usa?** +Es vital para tareas de Entrada/Salida (I/O) como leer archivos grandes, descargar cosas de internet o comunicarse por red (Nerve). Mientras el código espera una respuesta de internet, el procesador puede ir haciendo otra cosa. + +**Ejemplo:** +```python +import asyncio + +async def descargar_datos(): + print("Iniciando descarga...") + await asyncio.sleep(2) # Simula esperar por la red + print("Descarga terminada") +``` + +**Errores comunes de principiante:** +- Llamar a una función `async` normalmente, como `descargar_datos()`, olvidando poner el `await` antes, lo cual devuelve una "corutina" y no ejecuta la función. +- Usar una operación bloqueante (como `time.sleep()`) dentro de una función asíncrona, bloqueando a todo el sistema. + +**Términos relacionados:** [`event loop`](#event-loop) + +### `event loop` + +**¿Qué es?** +El "bucle de eventos". Es el motor central que hace funcionar al código asíncrono. Está constantemente dando vueltas revisando si alguna tarea asíncrona ya terminó de esperar (ej: si ya llegó el paquete de red) para reanudarla. + +**¿Para qué se usa?** +Es el responsable de organizar y ejecutar todas las tareas concurrentes en un solo hilo de ejecución de manera increíblemente eficiente (como un malabarista manteniendo muchos platos girando a la vez). + +**Ejemplo:** +```python +# Para correr tu función asíncrona principal y encender el motor: +import asyncio +asyncio.run(mi_programa_principal()) +``` + +**Errores comunes de principiante:** +- Tratar de iniciar múltiples *event loops* al mismo tiempo dentro de un mismo hilo, lo cual genera un error de "RuntimeError". + +**Términos relacionados:** [`async/await`](#asyncawait) + +### `semáforo` / `Lock` + +**¿Qué es?** +Mecanismos de control (primitivas de sincronización) que evitan que varios procesos o hilos intenten modificar o acceder a un mismo recurso (como un archivo o una variable) al mismo tiempo, lo que causaría corrupción de datos. Un **Lock** es como la llave de un baño (solo uno pasa a la vez), un **Semáforo** es como un estacionamiento con varios lugares fijos. + +**¿Para qué se usa?** +Para evitar las "condiciones de carrera" (race conditions). Si dos procesos intentan sumar +1 a la cuenta de banco al mismo milisegundo sin usar un Lock, es probable que la cuenta termine subiendo solo +1 en lugar de +2. + +**Ejemplo:** +```python +import asyncio + +candado = asyncio.Lock() + +async def modificar_datos(): + async with candado: + # Solo una función puede entrar a este bloque a la vez + # Modificar los datos críticos de forma segura + pass +``` + +**Errores comunes de principiante:** +- Crear un "deadlock" (abrazo mortal): La Tarea A tiene la Llave 1 y espera la Llave 2. La Tarea B tiene la Llave 2 y espera la Llave 1. Ambas se quedan esperando infinitamente y el programa se congela. + +**Términos relacionados:** [`async/await`](#asyncawait) + +### `topología de red` + +**¿Qué es?** +Es el diseño, mapa o estructura física/lógica de cómo están conectados los diferentes nodos (computadoras, scripts, microservicios) entre sí. + +**¿Para qué se usa?** +Para entender cómo fluye la información. Una topología de "estrella" significa que todos los nodos se comunican solo con el centro (como Nerve usando el NexusHub). Una topología "malla" (mesh) significa que todos se conectan con todos directamente. + +**Ejemplo:** +```python +# Topología de Estrella (Star) +# Script_A ---> HUB <--- Script_B +# ^ +# | +# Script_C +``` + +**Errores comunes de principiante:** +- Diseñar sistemas caóticos donde cada script debe conocer la dirección IP y puerto de todos los demás (topología de malla total), lo cual es imposible de mantener al escalar. + +**Términos relacionados:** [`NexusHub`](../10-nerve-ipc.md#nexushub) + +### `monolito` vs `distribuido` + +**¿Qué es?** +Dos filosofías opuestas para construir aplicaciones. Un **monolito** es un sistema gigante en el que todo el código (interfaz, base de datos, lógica) vive en un solo programa. Un sistema **distribuido** es un conjunto de componentes independientes dispersos en varias máquinas que parecen un solo programa para el usuario final. + +**¿Para qué se usa?** +Se suele empezar con un monolito porque es más fácil de crear, probar y desplegar. Se migra a un sistema distribuido (como microservicios) cuando el monolito se vuelve demasiado grande y el equipo de desarrollo necesita dividir el trabajo para no estorbarse. + +**Ejemplo:** +```python +# Monolito: Un archivo Django gigante o un super-script de Python. +# Distribuido: 5 scripts separados por red usando Nerve o Kubernetes. +``` + +**Errores comunes de principiante:** +- Pensar que los sistemas distribuidos siempre son "mejores" o "más modernos". En realidad añaden una inmensa complejidad de red y depuración; si un monolito resuelve el problema rápida y eficientemente, es la elección correcta. + +**Términos relacionados:** [`microservicio`](#microservicio), [`topología de red`](#topología-de-red) diff --git a/glosario/python/README.md b/glosario/python/README.md index a159d99..52103e0 100644 --- a/glosario/python/README.md +++ b/glosario/python/README.md @@ -1,14 +1,47 @@ # Glosario de Python -Índice de todos los términos de Python usados en los retos de nerve-community. - -| Archivo | Qué cubre | -| :--- | :--- | -| [Fundamentos](01-fundamentos.md) | Variables, tipos de datos, operadores, comentarios | -| [Control de flujo](02-control-de-flujo.md) | if/else, for, while, match | -| [Funciones](03-funciones.md) | def, parámetros por defecto, *args/**kwargs, return, lambdas | -| [Estructuras de datos](04-estructuras-de-datos.md) | Listas, tuplas, dicts, sets, list comprehension | -| [Módulos y librerías](05-modulos-y-librerias.md) | import, pip, venv, requirements.txt | -| [Manejo de errores](06-manejo-de-errores.md) | try/except, raise, excepciones comunes | -| [Archivos y E/S](07-archivos-y-entrada-salida.md) | open(), CSV, JSON | -| [POO básica](08-poo-basica.md) | class, __init__, self, herencia básica | +Referencia técnica de los términos de Python que aparecen en los módulos del curso. Cada archivo cubre los conceptos introducidos en su módulo correspondiente. + +--- + +## Cómo funciona el glosario + +Si estás trabajando desde tu repositorio privado generado con `nerve--template`, no tienes que venir aquí a leer todo de golpe. + +El glosario de Python está dividido en archivos por módulo. Cada archivo cubre exactamente los conceptos que introduces en ese módulo, para que puedas consultarlo cuando algo no te quede claro. + +--- + +## Índice de archivos + +| Archivo | Módulo del curso | Contenido principal | +| :--- | :--- | :--- | +| [01-fundamentos.md](01-fundamentos.md) | Modulo-01-Fundamentos | Variables, tipos de datos, operadores, f-strings, comentarios, indentación | +| [02-control-de-flujo.md](02-control-de-flujo.md) | Modulo-02-Flujo | `if`/`elif`/`else`, `for`, `while`, `match`, `break`, `continue` | +| [03-funciones.md](03-funciones.md) | Modulo-03-Estructuras → Modulo-05-Funciones | `def`, parámetros, `return`, `*args`, `**kwargs`, lambdas, scope | +| [04-estructuras-de-datos.md](04-estructuras-de-datos.md) | Modulo-03-Estructuras → Modulo-04-Bucles | Listas, tuplas, diccionarios, sets, list comprehension | +| [05-modulos-y-librerias.md](05-modulos-y-librerias.md) | Modulo-05-Funciones → Modulo-06-OS | `import`, `pip`, `venv`, `requirements.txt`, módulos de la librería estándar (`os`, `pathlib`, `json`, `csv`, `datetime`, `re`, `random`, `secrets`, `argparse`) | +| [06-manejo-de-errores.md](06-manejo-de-errores.md) | Modulo-05.5-Calidad-de-Codigo | `try`/`except`/`finally`, `raise`, excepciones comunes, manejo robusto | +| [07-archivos-y-entrada-salida.md](07-archivos-y-entrada-salida.md) | Modulo-06-OS | `open()`, modos de apertura, `with`, lectura/escritura de CSV y JSON | +| [08-poo-basica.md](08-poo-basica.md) | Modulo-08-POO | `class`, `__init__`, `self`, herencia, métodos especiales | +| [09-data-ia.md](09-data-ia.md) | Modulo-09-Data-IA | `numpy`, `pandas`, `matplotlib`, `scikit-learn`, análisis de CSV | +| [10-nerve-ipc.md](10-nerve-ipc.md) | Modulo-10-Nerve | IPC, `NexusHub`, `NexusClient`, Pub/Sub, Unix Sockets con Python | +| [11-herramientas-avanzadas.md](11-herramientas-avanzadas.md) | Modulo-11-Herramientas-Nerve | `nerve.config`, GitHub Actions, `pytest`, linters, coverage | +| [12-arquitectura-avanzada.md](12-arquitectura-avanzada.md) | Modulo-12-Arquitectura-Avanzada | Microservicios, orquestador, failover, `async`/`await` | + +--- + +## Cómo contribuir + +Si notas que falta un término importante, que una explicación es confusa o que falta un buen ejemplo, puedes contribuir: + +1. Haz Fork del repositorio `nerve-community`. +2. Crea una rama nueva. +3. Edita el archivo `.md` correspondiente siguiendo el formato existente: **¿Qué es?**, **¿Para qué se usa?**, **Ejemplo de código**, **Errores comunes**. +4. Haz Push y abre un Pull Request. + +Para más detalles sobre el flujo de contribución, lee: [Cómo hacer tu primer PR](../../COMO-HACER-TU-PRIMER-PR.md) + +--- + +← [Volver al Índice del Glosario](../README.md) diff --git a/glosario/redes-sockets.md b/glosario/redes-sockets.md index 50555e5..c22d445 100644 --- a/glosario/redes-sockets.md +++ b/glosario/redes-sockets.md @@ -1,69 +1,69 @@ -# 🌐 Glosario: Redes y Sockets (Nerve) +# Glosario: Redes y Sockets -Estos son los conceptos de redes y comunicación que usa **Nerve** para conectar scripts entre sí. No te asustes, se explican desde cero. +Conceptos de redes y comunicación que usa Nerve para conectar scripts entre sí. Se explican desde cero. --- ## Conceptos Fundamentales de Redes -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Red (Network)** | Sistema de conexión entre computadoras | Permite que diferentes programas o máquinas se comuniquen e intercambien datos. Internet es una red de redes. | Tu WiFi conecta tu celular con el router | N/A | - | -| **Protocolo** | Reglas de comunicación acordadas | Un "idioma en común" que dos partes usan para entenderse. Como las reglas del correo postal. | TCP, UDP, HTTP, WebSocket | N/A | - | -| **Puerto (Port)** | Número de "puerta" de un servicio | Un número (0–65535) que identifica un servicio específico dentro de una computadora. Es como el número de apartamento dentro de un edificio. | Puerto 80 → HTTP, Puerto 443 → HTTPS, Puerto 8080 → Servidor de desarrollo | N/A | - | -| **IP (Dirección IP)** | Identificador de una computadora en la red | Un número único que identifica cada dispositivo en una red. Como la dirección de un edificio. | `127.0.0.1` = tu propia computadora (localhost) | N/A | - | -| **localhost / 127.0.0.1** | Tu propia máquina | Cuando un programa se conecta a "localhost", se conecta a sí mismo. Usado para probar servidores sin internet. | `http://localhost:8080` | N/A | - | -| **TCP (Transmission Control Protocol)** | Protocolo de comunicación confiable | Garantiza que los datos lleguen completos y en orden. Si se pierde algo, lo reenvía. Más lento pero seguro. Nerve lo usa en Windows. | Transferencias de archivos, navegación web | N/A | - | -| **UDP** | Protocolo rápido sin garantías | Envía datos sin verificar que lleguen. Más rápido que TCP pero puede perder paquetes. Ideal para videollamadas o juegos. | Streaming de video, juegos online | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **Red (Network)** | Sistema de conexión entre computadoras | Permite que diferentes programas o máquinas se comuniquen e intercambien datos. Internet es una red de redes. | Tu WiFi conecta tu celular con el router | +| **Protocolo** | Reglas de comunicación acordadas | Un idioma en común que dos partes usan para entenderse. Como las reglas del correo postal. | TCP, UDP, HTTP, WebSocket | +| **Puerto (Port)** | Número que identifica un servicio en una máquina | Un número (0–65535) que identifica un servicio específico dentro de una computadora. Como el número de apartamento dentro de un edificio. | Puerto 80 → HTTP, Puerto 443 → HTTPS | +| **IP (Dirección IP)** | Identificador de una computadora en la red | Un número único que identifica cada dispositivo en una red. Como la dirección de un edificio. | `127.0.0.1` = tu propia computadora | +| **localhost / 127.0.0.1** | Tu propia máquina | Cuando un programa se conecta a "localhost", se conecta a sí mismo. Usado para probar servidores sin internet. | `http://localhost:8080` | +| **TCP** | Protocolo de comunicación confiable | Garantiza que los datos lleguen completos y en orden. Si se pierde algo, lo reenvía. Nerve lo usa en Windows. | Transferencias de archivos, navegación web | +| **UDP** | Protocolo rápido sin garantías | Envía datos sin verificar que lleguen. Más rápido que TCP pero puede perder paquetes. | Streaming de video, juegos online | --- -## Sockets y IPC (Lo que hace Nerve) - -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Socket** | Punto de conexión entre procesos | Es como un "enchufe" que dos programas usan para mandarse mensajes. Un socket tiene dos lados: quien envía y quien recibe. | `import socket` en Python | N/A | - | -| **IPC (Inter-Process Communication)** | Comunicación entre procesos | Forma en que dos programas corriendo al mismo tiempo (procesos) se mandan mensajes entre sí. Es el concepto central de Nerve. | Un script Python le habla a un script Go a través de Nerve | N/A | - | -| **Unix Socket** | Socket de archivo en Linux/macOS | Un tipo especial de socket que usa un archivo en el sistema (en vez de TCP) para comunicación local. Más rápido que TCP en Linux/macOS. Nerve los usa en Linux/macOS. | `/tmp/nerve.sock` | N/A | - | -| **Socket TCP** | Socket de red usando TCP | Socket que usa el protocolo TCP y una dirección IP + puerto para comunicarse. Funciona también entre diferentes computadoras. Nerve los usa en Windows. | `socket.connect(("127.0.0.1", 7878))` | N/A | - | -| **Servidor (Server)** | El que escucha y responde | En una arquitectura cliente-servidor, el servidor está siempre corriendo y espera que los clientes se conecten. | El proceso de Nerve que escucha conexiones | N/A | - | -| **Cliente (Client)** | El que inicia la conexión | Se conecta al servidor para enviarle datos o pedirle algo. | Tu script Python que se conecta a Nerve | N/A | - | -| **bind()** | Asignar dirección al socket del servidor | El servidor "ata" su socket a una dirección y puerto específicos para que los clientes sepan dónde conectarse. | `server.bind(("127.0.0.1", 7878))` | N/A | - | -| **listen()** | Poner el socket a escuchar | El servidor entra en modo de espera, listo para aceptar conexiones entrantes. | `server.listen(5)` (acepta hasta 5 en cola) | N/A | - | -| **accept()** | Aceptar una conexión entrante | El servidor acepta la conexión de un cliente que llamó a la puerta. Devuelve un nuevo socket para hablar con ese cliente específico. | `conn, addr = server.accept()` | N/A | - | -| **connect()** | Conectarse al servidor (desde el cliente) | El cliente llama a la puerta del servidor. | `client.connect(("127.0.0.1", 7878))` | N/A | - | -| **send() / recv()** | Enviar y recibir datos | Las funciones básicas para mandarse bytes a través del socket. | `socket.send(b"Hola!")` / `data = socket.recv(1024)` | N/A | - | +## Sockets e IPC (Lo que hace Nerve) + +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **Socket** | Punto de conexión entre procesos | Como un "enchufe" que dos programas usan para mandarse mensajes. Tiene dos lados: quien envía y quien recibe. | `import socket` en Python | +| **IPC (Inter-Process Communication)** | Comunicación entre procesos | Forma en que dos programas corriendo simultáneamente se mandan mensajes. Es el concepto central de Nerve. | Un script Python le habla a un script Go a través de Nerve | +| **Unix Socket** | Socket de archivo en Linux/macOS | Usa un archivo del sistema (en vez de TCP) para comunicación local. Más rápido que TCP en Linux/macOS. Nerve los usa en Linux/macOS. | `/tmp/nerve.sock` | +| **Socket TCP** | Socket de red usando TCP | Usa el protocolo TCP con IP + puerto para comunicarse. Funciona entre máquinas distintas. Nerve lo usa en Windows. | `socket.connect(("127.0.0.1", 7878))` | +| **Servidor (Server)** | El que escucha y responde | Está siempre corriendo y espera que los clientes se conecten. | El proceso de Nerve que escucha conexiones | +| **Cliente (Client)** | El que inicia la conexión | Se conecta al servidor para enviarle datos o pedirle algo. | Tu script Python que se conecta a Nerve | +| **bind()** | Asignar dirección al socket del servidor | El servidor ata su socket a una dirección y puerto para que los clientes sepan dónde conectarse. | `server.bind(("127.0.0.1", 7878))` | +| **listen()** | Poner el socket a escuchar | El servidor entra en modo de espera, listo para aceptar conexiones entrantes. | `server.listen(5)` | +| **accept()** | Aceptar una conexión entrante | El servidor acepta la conexión de un cliente. Devuelve un nuevo socket para hablar con ese cliente. | `conn, addr = server.accept()` | +| **connect()** | Conectarse al servidor desde el cliente | El cliente llama a la puerta del servidor. | `client.connect(("127.0.0.1", 7878))` | +| **send() / recv()** | Enviar y recibir datos | Las funciones básicas para mandarse bytes a través del socket. | `socket.send(b"Hola!")` / `data = socket.recv(1024)` | --- ## Conceptos de HTTP y APIs -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **HTTP** | Protocolo de la Web | El protocolo que usan los navegadores para pedir páginas web. "La lengua de internet". | `http://` al inicio de una URL | N/A | - | -| **HTTPS** | HTTP Seguro (con cifrado) | Versión cifrada de HTTP. Tus datos viajan encriptados. GitHub y casi todos los sitios modernos lo usan. | `https://github.com` | N/A | - | -| **API (Application Programming Interface)** | Interfaz para comunicar programas | Un conjunto de reglas para que dos programas se hablen. Una API web te permite pedirle datos a un servicio sin necesidad de una interfaz visual. | API de OpenWeatherMap para obtener el clima (reto 14) | N/A | - | -| **REST API** | Tipo de API que usa HTTP | El estilo más común de APIs web. Usa URLs y métodos HTTP (GET, POST, etc.) para hacer operaciones. | `requests.get("https://api.ejemplo.com/datos")` | N/A | - | -| **GET** | Solicitar/leer datos | El método HTTP para pedir información a un servidor sin modificar nada. | `requests.get("https://api.clima.com/ciudad/monterrey")` | N/A | - | -| **POST** | Enviar datos al servidor | El método HTTP para enviar datos nuevos al servidor (crear algo). | `requests.post(url, json={"nombre": "Ana"})` | N/A | - | -| **WebSocket** | Conexión bidireccional persistente | A diferencia de HTTP (donde tú preguntas y el servidor responde), un WebSocket mantiene la conexión abierta para que ambos puedan hablar cuando quieran. | Chat en tiempo real | N/A | - | -| **Endpoint** | Dirección de un recurso en una API | La URL específica de un recurso dentro de una API. Cada endpoint sirve para una operación distinta. | `GET /api/clima` o `POST /api/usuarios` | N/A | - | -| **timeout** | Límite de tiempo de espera | El tiempo máximo que tu programa esperará una respuesta de la red antes de rendirse y reportar error. | `requests.get(url, timeout=5)` → espera máx. 5 segundos | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **HTTP** | Protocolo de la Web | El protocolo que usan los navegadores para pedir páginas web. | `http://` al inicio de una URL | +| **HTTPS** | HTTP con cifrado | Versión cifrada de HTTP. Tus datos viajan encriptados. | `https://github.com` | +| **API** | Interfaz para comunicar programas | Un conjunto de reglas para que dos programas se hablen. Una API web te permite pedir datos sin interfaz visual. | API de OpenWeatherMap para obtener el clima | +| **REST API** | Tipo de API que usa HTTP | El estilo más común de APIs web. Usa URLs y métodos HTTP (GET, POST, etc.) para hacer operaciones. | `requests.get("https://api.ejemplo.com/datos")` | +| **GET** | Solicitar datos sin modificar nada | El método HTTP para pedir información a un servidor. | `requests.get("https://api.clima.com/ciudad/monterrey")` | +| **POST** | Enviar datos al servidor | El método HTTP para enviar datos nuevos (crear algo). | `requests.post(url, json={"nombre": "Ana"})` | +| **WebSocket** | Conexión bidireccional persistente | Mantiene la conexión abierta para que ambos lados puedan hablar cuando quieran. A diferencia de HTTP, no es pregunta-respuesta. | Chat en tiempo real | +| **Endpoint** | Dirección de un recurso en una API | La URL específica de un recurso. Cada endpoint sirve para una operación distinta. | `GET /api/clima` o `POST /api/usuarios` | +| **timeout** | Límite de tiempo de espera | El tiempo máximo que tu programa esperará una respuesta antes de rendirse y reportar error. | `requests.get(url, timeout=5)` | --- -## Conceptos de Concurrencia (Reto 09) +## Conceptos de Concurrencia -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Proceso** | Programa en ejecución | Una instancia de tu programa corriendo. Tiene su propia memoria independiente. | Tu script de Python = un proceso | N/A | - | -| **Hilo (Thread)** | Unidad de ejecución dentro de un proceso | Un proceso puede tener varios hilos corriendo "en paralelo" (o casi). Comparten memoria. | `import threading` en Python | N/A | - | -| **Concurrencia** | Manejar varias tareas a la vez | La capacidad de un programa de avanzar en varias tareas al mismo tiempo, aunque no sea literalmente simultáneo. | El servidor Nerve atendiendo a 10 clientes a la vez | N/A | - | -| **Carga (Load)** | Nivel de trabajo de un servidor | Cuántas peticiones está manejando un servidor en un momento dado. El reto 09 prueba cuánta carga puede soportar Nerve. | 1000 peticiones por segundo = alta carga | N/A | - | +| Término | Qué es | Para qué sirve | Ejemplo | +| :--- | :--- | :--- | :--- | +| **Proceso** | Programa en ejecución | Una instancia de tu programa corriendo. Tiene su propia memoria independiente. | Tu script de Python = un proceso | +| **Hilo (Thread)** | Unidad de ejecución dentro de un proceso | Un proceso puede tener varios hilos corriendo. Comparten memoria. | `import threading` en Python | +| **Concurrencia** | Manejar varias tareas a la vez | La capacidad de avanzar en varias tareas al mismo tiempo, aunque no sea literalmente simultáneo. | El servidor Nerve atendiendo a 10 clientes a la vez | +| **Carga (Load)** | Nivel de trabajo de un servidor | Cuántas peticiones está manejando un servidor en un momento dado. | 1000 peticiones por segundo = alta carga | --- -> 💡 **Dato clave sobre Nerve:** Nerve abstrae toda esta complejidad. Tú no tienes que saber de sockets para usar Nerve, pero saber cómo funcionan te ayuda a entender *por qué* Nerve existe y qué problema resuelve. Los retos de nivel Avanzado (08, 09, 10) sí requieren entender estos conceptos. +> Nerve abstrae toda esta complejidad. No necesitas saber de sockets para usar Nerve, pero entender cómo funcionan te ayuda a comprender por qué Nerve existe y qué problema resuelve. Los módulos avanzados sí requieren estos conceptos. --- diff --git a/glosario/terminal-linux.md b/glosario/terminal-linux.md index b7f4f57..0502012 100644 --- a/glosario/terminal-linux.md +++ b/glosario/terminal-linux.md @@ -1,58 +1,58 @@ -# ⌨️ Glosario: Terminal — Linux y macOS +# Glosario: Terminal — Linux y macOS -Guía de la terminal para usuarios de **Linux y macOS** (y Termux en Android). Si estás en Windows, consulta el [Glosario de Windows](terminal-windows.md). +Guía de la terminal para usuarios de Linux, macOS y Termux (Android). Si estás en Windows, consulta el [Glosario de Windows](terminal-windows.md). --- ## Conceptos Universales de Terminal -Estos términos aplican en cualquier sistema operativo. - -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Shell** | El intérprete de comandos | El programa que lee lo que escribes y lo ejecuta. Es el "cerebro" de la terminal. | `bash`, `zsh`, `fish` | N/A | - | -| **bash** | El shell más común en Linux | El intérprete por defecto en la mayoría de distros Linux y en todos los servidores. macOS usa `zsh` por defecto desde 2019. | `#!/bin/bash` al inicio de un script | N/A | - | -| **`~` (virgulilla)** | Carpeta de inicio del usuario | Atajo que representa `/home/tu_usuario/` en Linux o `/Users/tu_usuario/` en macOS. | `cd ~` = ir a tu carpeta de inicio | N/A | - | -| **`.` (punto)** | La carpeta actual | Representa "donde estás ahora mismo". | `git add .` = preparar todo lo que hay aquí | N/A | - | -| **`..` (dos puntos)** | La carpeta superior (padre) | Sube un nivel en el árbol de carpetas. | `cd ..` = ir a la carpeta de arriba | N/A | - | +| Término | Qué es | Ejemplo | +| :--- | :--- | :--- | +| **Shell** | El intérprete de comandos | El programa que lee lo que escribes y lo ejecuta. Ejemplos: `bash`, `zsh`, `fish` | +| **bash** | El shell más común en Linux | El intérprete por defecto en la mayoría de distros Linux y en los servidores. macOS usa `zsh` por defecto desde 2019. | +| **`~` (virgulilla)** | Carpeta de inicio del usuario | Atajo para `/home/tu_usuario/` en Linux o `/Users/tu_usuario/` en macOS. `cd ~` te lleva a inicio. | +| **`.` (punto)** | La carpeta actual | Representa donde estás ahora mismo. `git add .` prepara todo lo que hay aquí. | +| **`..` (dos puntos)** | La carpeta superior (padre) | Sube un nivel en el árbol de carpetas. `cd ..` sube un nivel. | --- -## Comandos Esenciales de Linux / macOS - -| :--- | :--- | :--- | :--- | :--- | -| **pwd** | Print Working Directory — muestra dónde estás | `pwd` → `/home/alejandro/nerve-community` | -| **ls** | List — lista archivos y carpetas | `ls` o `ls -la` (con detalles y archivos ocultos) | -| **cd** | Change Directory — cambia de carpeta | `cd retos/general` o `cd ..` | -| **mkdir** | Make Directory — crea una carpeta nueva | `mkdir mi-solucion` | -| **cp** | Copy — copia archivos o carpetas | `cp archivo.py copia.py` | -| **mv** | Move — mueve o renombra archivos | `mv viejo.py nuevo.py` | -| **rm** | Remove — borra archivos (⚠️ permanente, sin papelera) | `rm archivo.py` o `rm -rf carpeta/` | -| **cat** | Muestra el contenido de un archivo | `cat README.md` | -| **nano** | Editor de texto simple en terminal | `nano mi_script.py` → `Ctrl+O` guardar → `Ctrl+X` salir | -| **echo** | Imprime texto en la terminal | `echo "Hola mundo"` | -| **clear** | Limpia la pantalla | `clear` | -| **which** | Muestra dónde está instalado un programa | `which python3` → `/usr/bin/python3` | -| **grep** | Busca texto dentro de archivos | `grep "def " mi_script.py` | -| **chmod** | Cambia permisos de un archivo | `chmod +x script.sh` (hacerlo ejecutable) | -| **df -h** | Muestra espacio en disco disponible | `df -h` (`-h` = tamaño legible: MB/GB) | -| **sudo** | Superuser Do — ejecutar como administrador | `sudo apt install git` (instalar paquetes del sistema) | -| **apt** | Gestor de paquetes de Ubuntu/Debian | `sudo apt install python3` | -| **&&** | Encadenar comandos (el 2° solo corre si el 1° funcionó) | `git add . && git commit -m "mensaje"` | +## Comandos Esenciales de Linux y macOS + +| Comando | Qué hace | Ejemplo | +| :--- | :--- | :--- | +| `pwd` | Muestra la ruta completa de donde estás | `pwd` → `/home/alejandro/nerve-community` | +| `ls` | Lista archivos y carpetas | `ls` o `ls -la` (con detalles y archivos ocultos) | +| `cd` | Cambia de carpeta | `cd python/Modulo-01-Fundamentos` o `cd ..` | +| `mkdir` | Crea una carpeta nueva | `mkdir mi-solucion` | +| `cp` | Copia archivos o carpetas | `cp archivo.py copia.py` | +| `mv` | Mueve o renombra archivos | `mv viejo.py nuevo.py` | +| `rm` | Borra archivos — permanente, sin papelera | `rm archivo.py` o `rm -rf carpeta/` | +| `cat` | Muestra el contenido de un archivo | `cat README.md` | +| `nano` | Editor de texto simple en terminal | `nano mi_script.py` — `Ctrl+O` guarda, `Ctrl+X` sale | +| `echo` | Imprime texto en la terminal | `echo "Hola mundo"` | +| `clear` | Limpia la pantalla | `clear` | +| `which` | Muestra dónde está instalado un programa | `which python3` → `/usr/bin/python3` | +| `grep` | Busca texto dentro de archivos | `grep "def " mi_script.py` | +| `chmod` | Cambia permisos de un archivo | `chmod +x script.sh` (hacerlo ejecutable) | +| `df -h` | Muestra espacio en disco disponible | `df -h` — `-h` muestra tamaños en MB/GB | +| `sudo` | Ejecutar un comando como administrador | `sudo apt install git` | +| `apt` | Gestor de paquetes de Ubuntu/Debian | `sudo apt install python3` | +| `&&` | Encadenar comandos | `git add . && git commit -m "mensaje"` — el segundo solo corre si el primero tuvo éxito | --- -## Python desde la Terminal (Linux/macOS) +## Python desde la Terminal (Linux y macOS) -| :--- | :--- | :--- | :--- | :--- | -| **python3** | Ejecutar un script Python | `python3 mi_script.py` | -| **python3 --version** | Ver versión instalada | `python3 --version` → `Python 3.11.2` | -| **pip3 install** | Instalar una librería externa | `pip3 install requests` | -| **pip3 install -r requirements.txt** | Instalar todas las dependencias del proyecto | `pip3 install -r requirements.txt` | -| **pip3 list** | Ver librerías instaladas | `pip3 list` | -| **pip3 freeze** | Generar requirements.txt | `pip3 freeze > requirements.txt` | +| Comando | Qué hace | Ejemplo | +| :--- | :--- | :--- | +| `python3` | Ejecutar un script Python | `python3 mi_script.py` | +| `python3 --version` | Ver la versión de Python instalada | `python3 --version` → `Python 3.11.2` | +| `pip3 install` | Instalar una librería externa | `pip3 install requests` | +| `pip3 install -r requirements.txt` | Instalar todas las dependencias del proyecto | `pip3 install -r requirements.txt` | +| `pip3 list` | Ver librerías instaladas | `pip3 list` | +| `pip3 freeze` | Generar el archivo requirements.txt | `pip3 freeze > requirements.txt` | -> ⚠️ En Linux/macOS usa `python3` y `pip3` (con el `3`). Sin el `3`, podría ejecutar Python 2, que ya está obsoleto. +> En Linux y macOS usa `python3` y `pip3` (con el `3`). Sin el `3`, algunos sistemas ejecutan Python 2, que está obsoleto. --- @@ -60,28 +60,25 @@ Estos términos aplican en cualquier sistema operativo. Termux es una terminal Linux que corre en tu celular Android, sin necesidad de root ni PC. -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Termux** | Terminal Linux para Android | Emula una terminal Linux completa en tu celular. **Solo desde F-Droid**, la versión de Play Store está descontinuada. | `https://f-droid.org/packages/com.termux/` | N/A | - | -| **F-Droid** | Tienda de apps Open Source | La tienda de donde se descarga la versión correcta de Termux. | `https://f-droid.org` | N/A | - | -| **pkg** | Gestor de paquetes de Termux | El equivalente de `apt` dentro de Termux. Instala herramientas del sistema. | `pkg install python git gh -y` | N/A | - | -| **pkg update && pkg upgrade** | Actualizar paquetes de Termux | Primer comando a correr al abrir Termux por primera vez. | `pkg update && pkg upgrade` | N/A | - | -| **termux-setup-storage** | Dar acceso al almacenamiento | Permite que Termux acceda a las carpetas del celular (Descargas, etc.). | `termux-setup-storage` | N/A | - | -| **Acode** | Editor de código para Android | App con resaltado de sintaxis para editar archivos de Termux cómodamente. | Disponible en F-Droid y Play Store | N/A | - | -| **root** | Superusuario del sistema | El usuario con todos los permisos. Termux **NO** requiere root. | Termux es seguro por no necesitar root | N/A | - | - ---- +| Término | Qué es | Ejemplo | +| :--- | :--- | :--- | +| **Termux** | Terminal Linux para Android | Emula una terminal Linux completa en tu celular. Instálalo solo desde F-Droid; la versión de Play Store está descontinuada. | +| **F-Droid** | Tienda de apps Open Source | La fuente correcta para descargar Termux. `https://f-droid.org` | +| **pkg** | Gestor de paquetes de Termux | El equivalente de `apt` en Termux. `pkg install python git gh -y` | +| **pkg update && pkg upgrade** | Actualizar paquetes de Termux | Primer comando a correr al abrir Termux por primera vez. | +| **termux-setup-storage** | Dar acceso al almacenamiento del celular | Permite que Termux acceda a las carpetas de Descargas, etc. | +| **Acode** | Editor de código para Android | App con resaltado de sintaxis para editar archivos de Termux cómodamente. Disponible en F-Droid y Play Store. | -## Variables de Entorno (Linux/macOS) - -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **export** | Exportar variable al entorno | Hace que la variable esté disponible para los procesos hijos de esa sesión. | `export API_KEY="mi_clave"` | N/A | - | -| **.env** | Archivo de variables de entorno locales | Guarda keys y configuración. **NUNCA** se sube a GitHub (va en `.gitignore`). | `API_KEY=mi_clave` dentro del archivo `.env` | N/A | - | +> En Termux puedes usar exactamente los mismos comandos de Linux de la tabla de arriba. --- -> 💡 **Tip:** En Termux puedes usar exactamente los mismos comandos de Linux que ves aquí. ¡Es la misma shell! +## Variables de Entorno (Linux y macOS) + +| Término | Qué es | Ejemplo | +| :--- | :--- | :--- | +| **export** | Exportar una variable al entorno | Hace que la variable esté disponible para los procesos de esa sesión. `export API_KEY="mi_clave"` | +| **.env** | Archivo de variables de entorno locales | Guarda claves y configuración. Nunca se sube a GitHub (va en `.gitignore`). `API_KEY=mi_clave` dentro del archivo. | --- diff --git a/glosario/terminal-windows.md b/glosario/terminal-windows.md index dce2e88..293d4aa 100644 --- a/glosario/terminal-windows.md +++ b/glosario/terminal-windows.md @@ -1,54 +1,48 @@ -# 🪟 Glosario: Terminal — Windows +# Glosario: Terminal — Windows -Guía de la terminal para usuarios de **Windows**. Si estás en Linux o macOS, consulta el [Glosario de Linux/macOS](terminal-linux.md). +Guía de la terminal para usuarios de Windows. Si estás en Linux o macOS, consulta el [Glosario de Linux/macOS](terminal-linux.md). --- -## ¿Qué Terminal Usar en Windows? +## ¿Qué terminal usar en Windows? -Windows tiene **tres** opciones de terminal. Aquí te explicamos cuál te conviene para los retos de Nerve Community: +Windows tiene tres opciones de terminal. Aquí te explicamos cuál conviene para los retos de Nerve Community: -| Terminal | 🔍 ¿Qué es? | ¿Se recomienda? | -| :--- | :--- | :--- | :--- | :--- | -| **CMD (Símbolo del Sistema)** | La terminal antigua de Windows. Comandos propios (`dir`, `copy`, `del`). | ⚠️ Funciona pero es limitada. Úsala solo si no tienes otra opción. | -| **PowerShell** | Terminal moderna de Microsoft. Más potente que CMD. | ✓ Buena opción. Viene preinstalada en Windows 10/11. | -| **Git Bash** | Terminal que emula bash de Linux dentro de Windows. Viene con Git for Windows. | ✅ **Recomendada**. Te permite usar comandos de Linux (`ls`, `cat`, `nano`) y toda la guía de los retos aplica igual. | -| **WSL (Windows Subsystem for Linux)** | Una instalación completa de Linux dentro de Windows. | ✅ **La mejor opción** si quieres la experiencia idéntica a Linux. Un poco más compleja de configurar. | +| Terminal | Qué es | Recomendación | +| :--- | :--- | :--- | +| **CMD (Símbolo del Sistema)** | La terminal antigua de Windows. Comandos propios (`dir`, `copy`, `del`). | Funciona, pero es limitada. Úsala solo si no tienes otra opción. | +| **PowerShell** | Terminal moderna de Microsoft. Más potente que CMD. | Buena opción. Viene preinstalada en Windows 10/11. | +| **Git Bash** | Emula bash de Linux dentro de Windows. Viene con Git for Windows. | Recomendada. Permite usar comandos de Linux (`ls`, `cat`, `nano`) y toda la guía del repo aplica igual. | +| **WSL (Windows Subsystem for Linux)** | Una instalación completa de Linux dentro de Windows. | La mejor opción si quieres experiencia idéntica a Linux. Un poco más compleja de configurar. | -> 💡 **Recomendación para los retos:** Instala **Git for Windows** (incluye Git Bash) y usarás los mismos comandos que aparecen en todas las guías del repo. Descárgalo en [gitforwindows.org](https://gitforwindows.org/). +> Instala **Git for Windows** (incluye Git Bash) y usarás los mismos comandos que aparecen en todas las guías del repo. Descárgalo en [gitforwindows.org](https://gitforwindows.org/). --- ## Conceptos Universales de Terminal -Estos términos aplican en cualquier sistema operativo. - -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Terminal** | Interfaz de texto para tu sistema | Un programa donde escribes comandos de texto. En Windows: CMD, PowerShell o Git Bash. | `Win + R` → escribe `cmd` → Enter | N/A | - | -| **Prompt** | La línea de espera | El símbolo que indica que la terminal espera un comando. | CMD: `C:\Users\alejandro>` / Git Bash: `alejandro@PC MINGW64 ~$` | N/A | - | -| **Comando** | Instrucción para la terminal | Una palabra que le dices a la terminal que ejecute. | `cd`, `python`, `git` | N/A | - | -| **Flag / Bandera** | Modificador de un comando | Modifica el comportamiento. Empieza con `/` en CMD o `-`/`--` en PowerShell y Git Bash. | `dir /a` (CMD) o `ls -la` (Git Bash) | N/A | - | -| **Ruta (Path)** | Dirección de un archivo o carpeta | La ubicación exacta de un archivo. En Windows usa `\`, en Git Bash usa `/`. | `C:\Users\alejandro\Documents\` o `/c/Users/alejandro/Documents/` (Git Bash) | N/A | - | -| **Ruta Absoluta** | Ruta completa desde la raíz | En Windows empieza con la letra de unidad (`C:\`). | `C:\Users\alejandro\nerve-community\README.md` | N/A | - | -| **Ruta Relativa** | Ruta desde la carpeta actual | Funciona igual que en Linux: `.`, `..`, nombre de carpeta. | `..\README.md` (CMD) o `../README.md` (Git Bash) | N/A | - | -| **`.` (punto)** | La carpeta actual | Igual que en Linux. | `git add .` = preparar todos los cambios | N/A | - | -| **`..` (dos puntos)** | La carpeta superior | Igual que en Linux. | `cd ..` = subir un nivel | N/A | - | +| Término | Qué es | Ejemplo | +| :--- | :--- | :--- | +| **Terminal** | Interfaz de texto para tu sistema | Donde escribes comandos de texto. En Windows: CMD, PowerShell o Git Bash. | +| **Prompt** | La línea de espera | El símbolo que indica que la terminal espera un comando. CMD: `C:\Users\alejandro>` / Git Bash: `alejandro@PC ~$` | +| **Comando** | Instrucción para la terminal | Una palabra que ejecuta algo. Ejemplos: `cd`, `python`, `git` | +| **Flag / Bandera** | Modificador de un comando | Modifica el comportamiento. Empieza con `/` en CMD o `-`/`--` en PowerShell y Git Bash. Ejemplo: `dir /a` | +| **Ruta (Path)** | Dirección de un archivo o carpeta | La ubicación exacta de un archivo. En Windows usa `\`; en Git Bash usa `/`. | +| **Ruta Absoluta** | Ruta completa desde la raíz | En Windows empieza con la letra de unidad (`C:\`). Ejemplo: `C:\Users\alejandro\nerve-community\README.md` | +| **Ruta Relativa** | Ruta desde la carpeta actual | Funciona igual que en Linux: `.`, `..`, nombre de carpeta. Ejemplo: `../README.md` | --- ## Comandos: Comparativa CMD vs Git Bash -Aquí el equivalente de cada comando de Linux en CMD nativo de Windows, para que entiendas qué hace cada uno sin importar desde dónde lo veas en la documentación: - | Función | CMD (Windows nativo) | Git Bash / PowerShell | -| :--- | :--- | :--- | :--- | :--- | +| :--- | :--- | :--- | | Ver dónde estás | `cd` (sin argumentos) | `pwd` | | Listar archivos | `dir` | `ls` o `ls -la` | | Cambiar de carpeta | `cd nombre-carpeta` | `cd nombre-carpeta` | | Crear carpeta | `mkdir nombre` | `mkdir nombre` | | Copiar archivo | `copy origen destino` | `cp origen destino` | -| Mover/renombrar | `move origen destino` | `mv origen destino` | +| Mover o renombrar | `move origen destino` | `mv origen destino` | | Borrar archivo | `del archivo.py` | `rm archivo.py` | | Mostrar contenido | `type archivo.txt` | `cat archivo.txt` | | Limpiar pantalla | `cls` | `clear` | @@ -56,63 +50,65 @@ Aquí el equivalente de cada comando de Linux en CMD nativo de Windows, para que | Imprimir texto | `echo Hola mundo` | `echo "Hola mundo"` | | Encadenar comandos | `comando1 && comando2` | `comando1 && comando2` | -> ✅ **Con Git Bash instalado**, puedes usar directamente la columna de Git Bash/PowerShell, que es la misma sintaxis que usan todas las guías de los retos. +> Con Git Bash instalado, puedes usar la columna de Git Bash/PowerShell, que es la misma sintaxis de todas las guías del repo. --- ## Python en Windows -| Comando | 🔍 ¿Qué hace? | 💻 Ejemplo | -| :--- | :--- | :--- | :--- | :--- | -| **Instalar Python** | Descarga el instalador oficial | [python.org/downloads](https://www.python.org/downloads/) — marca "Add Python to PATH" durante la instalación | -| **python** (o `python3`) | Ejecutar un script Python | `python mi_script.py` o `python3 mi_script.py` | -| **python --version** | Ver versión instalada | `python --version` → `Python 3.11.2` | +| Comando | Qué hace | Ejemplo | +| :--- | :--- | :--- | +| Instalar Python | Descarga el instalador oficial | [python.org/downloads](https://www.python.org/downloads/) — marca "Add Python to PATH" durante la instalación | +| `python` o `python3` | Ejecutar un script Python | `python mi_script.py` o `python3 mi_script.py` | +| `python --version` | Ver la versión instalada | `python --version` → `Python 3.11.2` | +| `pip install` | Instalar una librería externa | `pip install requests` | +| `pip install -r requirements.txt` | Instalar todas las dependencias del proyecto | `pip install -r requirements.txt` | -> ⚠️ En Windows, el comando puede ser `python` (sin el `3`) si solo tienes Python 3 instalado. Si tienes ambas versiones, usa `py -3` para asegurarte. +> En Windows, el comando puede ser `python` (sin el `3`) si solo tienes Python 3 instalado. Si tienes ambas versiones, usa `py -3` para asegurarte. --- ## Abrir la Terminal en Windows | Método | Cómo hacerlo | -| :--- | :--- | :--- | :--- | -| **CMD** | `Win + R` → escribe `cmd` → Enter | -| **PowerShell** | `Win + X` → selecciona "Windows PowerShell" | -| **Git Bash** | Clic derecho en cualquier carpeta → "Git Bash Here" | -| **Terminal de Windows** | Busca "Terminal" en el menú inicio (Windows 11 ya la incluye) | -| **Desde VS Code** | `Ctrl + ` ` ` (acento grave) — abre la terminal integrada | +| :--- | :--- | +| CMD | `Win + R` → escribe `cmd` → Enter | +| PowerShell | `Win + X` → selecciona "Windows PowerShell" | +| Git Bash | Clic derecho en cualquier carpeta → "Git Bash Here" | +| Terminal de Windows | Busca "Terminal" en el menú inicio (Windows 11 ya la incluye) | +| Desde VS Code | `Ctrl + ` ` ` (acento grave) — abre la terminal integrada | --- ## Variables de Entorno en Windows -| Término | 🔍 ¿Qué es? | 🎯 ¿Para qué sirve? | 💻 Ejemplo | ⚠️ Error común | 🔗 Relacionado | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **Variable de entorno** | Variable global del sistema | Disponible para todos los programas. Se usa para API keys y configuración. | `API_KEY=abc123` | N/A | - | -| **PATH** | Lista de carpetas donde buscar ejecutables | Cuando escribes `python`, Windows busca en las carpetas del PATH. Si Python no se encuentra, no está en el PATH. | Panel de control → Variables de entorno → PATH | N/A | - | -| **set** | Crear variable temporal (CMD) | Crea una variable solo para esa sesión del CMD. | `set API_KEY=mi_clave` | N/A | - | -| **$env:** | Crear variable en PowerShell | Equivalente a `set` pero en PowerShell. | `$env:API_KEY = "mi_clave"` | N/A | - | +| Término | Qué es | Ejemplo | +| :--- | :--- | :--- | +| **Variable de entorno** | Variable global del sistema | Disponible para todos los programas. Se usa para API keys y configuración. | +| **PATH** | Lista de carpetas donde buscar ejecutables | Cuando escribes `python`, Windows busca en las carpetas del PATH. Si Python no aparece, no está en el PATH. Configurable en: Panel de control → Variables de entorno | +| **set** | Crear variable temporal en CMD | Crea una variable solo para esa sesión. `set API_KEY=mi_clave` | +| **$env:** | Crear variable en PowerShell | Equivalente a `set` pero en PowerShell. `$env:API_KEY = "mi_clave"` | --- -## WSL (Windows Subsystem for Linux) — Opción Avanzada +## WSL (Windows Subsystem for Linux) WSL te permite tener una distribución completa de Linux (Ubuntu, Debian, etc.) corriendo dentro de Windows, sin máquina virtual. | Concepto | Descripción | -| :--- | :--- | :--- | :--- | -| **¿Qué es WSL?** | Una capa de compatibilidad que permite correr Linux nativo dentro de Windows 10/11. | -| **¿Por qué usarlo?** | Tienes acceso a todos los comandos de Linux (`ls`, `nano`, `apt`, etc.) y la experiencia es idéntica a un servidor real. Los retos más avanzados (Rust, Go) funcionan mucho mejor en WSL que en Windows nativo. | -| **¿Cómo instalarlo?** | Abre PowerShell como administrador y ejecuta: `wsl --install` (instala Ubuntu por defecto). Requiere reiniciar. | -| **¿Dónde viven mis archivos?** | Puedes acceder a tus archivos de Windows desde WSL en `/mnt/c/Users/tu_usuario/`. | +| :--- | :--- | +| Qué es WSL | Una capa de compatibilidad para correr Linux nativo dentro de Windows 10/11. | +| Por qué usarlo | Acceso a todos los comandos de Linux (`ls`, `nano`, `apt`). La experiencia es idéntica a un servidor real. Los módulos avanzados (Rust, Go) funcionan mucho mejor en WSL que en Windows nativo. | +| Cómo instalarlo | Abre PowerShell como administrador y ejecuta: `wsl --install` (instala Ubuntu por defecto). Requiere reiniciar. | +| Dónde viven tus archivos | Puedes acceder a tus archivos de Windows desde WSL en `/mnt/c/Users/tu_usuario/`. | --- -> 💡 **Resumen para empezar rápido en Windows:** +> Para empezar rápido en Windows: > 1. Instala **Git for Windows** → [gitforwindows.org](https://gitforwindows.org/) > 2. Instala **Python 3** → [python.org/downloads](https://www.python.org/downloads/) (marca "Add to PATH") > 3. Usa **Git Bash** como tu terminal principal -> 4. Con eso, todos los comandos de las guías del repo funcionarán igual en tu Windows +> 4. Con eso, todos los comandos de las guías del repo funcionarán igual en tu Windows. --- diff --git a/assets/.gitkeep b/go/.gitkeep similarity index 100% rename from assets/.gitkeep rename to go/.gitkeep diff --git a/assets/images/.gitkeep b/javascript/.gitkeep similarity index 100% rename from assets/images/.gitkeep rename to javascript/.gitkeep diff --git a/ejemplos/.gitkeep b/python/.gitkeep similarity index 100% rename from ejemplos/.gitkeep rename to python/.gitkeep diff --git a/retos/.gitkeep b/python/Modulo-01-Fundamentos/.gitkeep similarity index 100% rename from retos/.gitkeep rename to python/Modulo-01-Fundamentos/.gitkeep diff --git a/retos/general/02-web-scraper-simple/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/01-hola-mundo/.gitkeep similarity index 100% rename from retos/general/02-web-scraper-simple/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/01-hola-mundo/.gitkeep diff --git a/python/Modulo-01-Fundamentos/01-hola-mundo/README.md b/python/Modulo-01-Fundamentos/01-hola-mundo/README.md new file mode 100644 index 0000000..607c827 --- /dev/null +++ b/python/Modulo-01-Fundamentos/01-hola-mundo/README.md @@ -0,0 +1,12 @@ +# Nivel 01: Hola Mundo 🌍 + +Toda gran aplicación, desde los videojuegos hasta las redes sociales, empezó con una sola línea de código. + +En la vida real, si quieres que alguien haga algo por ti, tienes que decírselo. Con las computadoras es exactamente igual, pero ellas no entienden español ni inglés; entienden lenguajes de programación. + +Nuestro primer paso en esta aventura será lograr que la computadora nos "escuche" y repita un mensaje en la pantalla. Esto es útil para saber que el sistema está vivo y listo para recibir más órdenes. Es como el primer llanto de un bebé: significa que todo funciona. + +**Ruta de aprendizaje:** +1. **Teoría:** Aprenderemos la palabra mágica para mostrar mensajes. +2. **Ejemplo:** Veremos el código en acción. +3. **Reto:** Escribirás tus primeras líneas de código desde cero. diff --git a/python/Modulo-01-Fundamentos/01-hola-mundo/ejemplo.py b/python/Modulo-01-Fundamentos/01-hola-mundo/ejemplo.py new file mode 100644 index 0000000..e93c0ef --- /dev/null +++ b/python/Modulo-01-Fundamentos/01-hola-mundo/ejemplo.py @@ -0,0 +1,24 @@ +# ========================================== +# NIVEL 01: EL PRIMER PASO +# ========================================== + +print("--- Mi primer mensaje ---") + +# La orden 'print' le dice a la máquina que muestre algo. +# Los paréntesis () abrazan el contenido. +# Las comillas "" le dicen que es texto humano. +print("Hola, computadora.") + +print("--- Varias líneas ---") + +# Podemos usar la orden print tantas veces como queramos. +# La computadora lee y ejecuta las órdenes de arriba hacia abajo, una por una. +print("Paso 1: Despertar.") +print("Paso 2: Escribir código.") +print("Paso 3: Dominar el mundo.") + +print("--- Advertencias ---") + +# Incluso si escribimos números o símbolos raros, +# mientras estén dentro de las comillas, la máquina solo los mostrará. +print("¡El código secreto es 12345! @#*%") diff --git a/python/Modulo-01-Fundamentos/01-hola-mundo/reto.md b/python/Modulo-01-Fundamentos/01-hola-mundo/reto.md new file mode 100644 index 0000000..85a6956 --- /dev/null +++ b/python/Modulo-01-Fundamentos/01-hola-mundo/reto.md @@ -0,0 +1,21 @@ +# Reto 01: El Manifiesto del Programador 📜 + +Tu objetivo es usar lo que acabas de aprender para escribir un pequeño poema o manifiesto en la pantalla. Como un bebé diciendo sus primeras palabras, tu programa debe presentarse al mundo. + +### Instrucciones +Crea un archivo llamado `reto.py` (puedes hacerlo en esta misma carpeta) y escribe el código necesario para que la terminal muestre exactamente el texto que se encuentra en la sección de "Resultado esperado". + +### Reglas +* **Puedes usar:** La orden `print`, los paréntesis `()` y las comillas `""`. +* **Prohibido usar:** Cualquier otro comando, matemáticas, variables o conceptos que no hayamos visto. No puedes copiar y pegar el texto de un solo golpe si no usas múltiples órdenes `print`. +* **Pista:** Necesitarás escribir la orden `print` exactamente cinco veces (una para el título y cuatro para las líneas). + +### Resultado esperado en la terminal + +```text +--- Mi Manifiesto --- +Hola mundo, estoy aquí. +Hoy escribo mi primera línea. +Mañana construiré sistemas. +Fin de la transmisión. +``` diff --git a/python/Modulo-01-Fundamentos/01-hola-mundo/teoria.md b/python/Modulo-01-Fundamentos/01-hola-mundo/teoria.md new file mode 100644 index 0000000..a905eb9 --- /dev/null +++ b/python/Modulo-01-Fundamentos/01-hola-mundo/teoria.md @@ -0,0 +1,22 @@ +# Teoría: Hablando con la máquina + +Imagina que la computadora es un asistente muy trabajador pero que no tiene sentido común. Si le dices "di hola", podría buscar un archivo llamado "hola" o entrar en pánico. Tienes que ser extremadamente específico. + +Para hacer que la computadora muestre un texto en la pantalla, usamos nuestra primera herramienta: `print()`. + +### Anatomía de la instrucción + +Vamos a desmontar esta línea: `print("Hola")` + +* `print`: Es la **orden** (o función). En inglés significa "imprimir". Le dice a la computadora: *"Prepárate para mostrar algo en la pantalla"*. +* `()`: Los **paréntesis** son como los brazos de la orden. Todo lo que pongas dentro de estos brazos es lo que la orden va a abrazar y procesar. Si `print` es la acción de "entregar una caja", los paréntesis son la caja misma. +* `""`: Las **comillas** (dobles o simples) son cruciales. Le dicen a la computadora: *"Lo que está aquí adentro es texto para humanos. No intentes leerlo como si fuera una orden secreta, no intentes calcularlo, solo muéstralo tal cual"*. Sin comillas, la computadora intentará ejecutar la palabra "Hola" como si fuera otra orden y fallará. + +### ¿Qué pasa si me equivoco? + +El error más común de un estudiante que empieza es olvidar cerrar un paréntesis o una comilla. + +Si escribes `print("Hola)` (nota que falta la comilla final), la computadora se quedará esperando a que termines de escribir el texto para siempre. Al intentar correr el programa, la terminal te mostrará un error en rojo que dice algo como: +`SyntaxError: unterminated string literal` + +**Traducción humana:** "Error de sintaxis: empezaste un texto con una comilla pero nunca lo terminaste. ¡Me quedé esperando el final!" diff --git a/python/Modulo-01-Fundamentos/01-hola-mundo/test_main.py b/python/Modulo-01-Fundamentos/01-hola-mundo/test_main.py new file mode 100644 index 0000000..36b0dea --- /dev/null +++ b/python/Modulo-01-Fundamentos/01-hola-mundo/test_main.py @@ -0,0 +1,17 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(): + return subprocess.run([sys.executable, RETO], + capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_imprime_algo(): + r = run() + assert r.returncode == 0, f"Error:\n{r.stderr}" + assert len(r.stdout.strip()) > 0, "Tu programa no imprime nada" +def test_contiene_separador_visual(): + r = run() + assert "---" in r.stdout or "===" in r.stdout, \ + "Debe contener un separador visual (--- o ===)" diff --git a/retos/general/03-juego-de-adivinanza/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/02-variables/.gitkeep similarity index 100% rename from retos/general/03-juego-de-adivinanza/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/02-variables/.gitkeep diff --git a/python/Modulo-01-Fundamentos/02-variables/README.md b/python/Modulo-01-Fundamentos/02-variables/README.md new file mode 100644 index 0000000..843624a --- /dev/null +++ b/python/Modulo-01-Fundamentos/02-variables/README.md @@ -0,0 +1,10 @@ +# Nivel 02: Cajas Mágicas (Variables) 📦 + +Imagínate que cada vez que quisieras hablar de tu mejor amigo tuvieras que decir su nombre completo, número de documento, dirección y fecha de nacimiento. Sería agotador. En su lugar, simplemente usas su "nombre" o le dices "amigo". + +En la programación pasa lo mismo. A veces tenemos mensajes largos o datos importantes que queremos usar muchas veces. En lugar de escribirlos una y otra vez, los guardamos en "cajas" y les ponemos una etiqueta con un nombre fácil de recordar. A estas cajas mágicas las llamamos **Variables**. + +**Ruta de aprendizaje:** +1. **Teoría:** Descubriremos cómo fabricar cajas y ponerles etiquetas. +2. **Ejemplo:** Veremos cómo guardar cosas y luego pedírselas a la computadora. +3. **Reto:** Crearás tu propio sistema de almacenamiento. diff --git a/python/Modulo-01-Fundamentos/02-variables/ejemplo.py b/python/Modulo-01-Fundamentos/02-variables/ejemplo.py new file mode 100644 index 0000000..3829e84 --- /dev/null +++ b/python/Modulo-01-Fundamentos/02-variables/ejemplo.py @@ -0,0 +1,28 @@ +# ========================================== +# NIVEL 02: CAJAS MÁGICAS (VARIABLES) +# ========================================== + +print("--- Guardando en la caja ---") + +# Tomamos una caja, le pegamos la etiqueta "nombre_jugador" +# y metemos el texto "Alex" adentro (usando el símbolo =). +nombre_jugador = "Alex" + +# Ahora imprimimos lo que hay en la caja. +# ¡Nota que NO usamos comillas alrededor de nombre_jugador! +print(nombre_jugador) + +print("--- Reciclando cajas ---") + +# Las variables pueden cambiar su contenido. Si metes algo nuevo en la caja, +# lo viejo se tira a la basura automáticamente. +nombre_jugador = "Sam" + +# Ahora la caja tiene un texto diferente. +print(nombre_jugador) + +print("--- Combinando texto y cajas (un vistazo al futuro) ---") + +# Aunque más adelante veremos formas mejores de hacer esto, +# por ahora podemos imprimir varias cosas separadas por comas. +print("El jugador actual es:", nombre_jugador) diff --git a/python/Modulo-01-Fundamentos/02-variables/reto.md b/python/Modulo-01-Fundamentos/02-variables/reto.md new file mode 100644 index 0000000..e1c8797 --- /dev/null +++ b/python/Modulo-01-Fundamentos/02-variables/reto.md @@ -0,0 +1,23 @@ +# Reto 02: Ficha de Identidad 🗂️ + +Vas a crear un pequeño sistema de almacenamiento para guardar la información secreta de un espía y luego imprimirla. + +### Instrucciones +Crea un archivo llamado `reto.py` y escribe el código necesario para lograr el resultado esperado. + +### Reglas +* **Puedes usar:** La orden `print`, el signo de asignación `=`, los nombres de variables, paréntesis `()` y comillas `""`. +* **Obligatorio:** Tienes que crear 3 cajas (variables) diferentes. Una llamada `agente`, otra `ciudad` y otra `mision`. Primero guardas los datos en las cajas y *luego* usas la orden `print` para mostrar las cajas. +* **Prohibido usar:** Conceptos que no hayamos visto. No puedes imprimir el texto directamente, tienes que imprimir las variables. + +### Resultado esperado en la terminal + +```text +--- Perfil del Espía --- +Agente asignado: +007 +Ciudad actual: +Londres +Misión: +Recuperar el disco duro +``` diff --git a/python/Modulo-01-Fundamentos/02-variables/teoria.md b/python/Modulo-01-Fundamentos/02-variables/teoria.md new file mode 100644 index 0000000..8d3a72a --- /dev/null +++ b/python/Modulo-01-Fundamentos/02-variables/teoria.md @@ -0,0 +1,28 @@ +# Teoría: Cajas y Etiquetas + +Crear una variable es literalmente tomar una caja vacía, pegarle una etiqueta con un nombre, y meterle algo adentro. + +### Anatomía de la instrucción + +Vamos a desmontar esta línea: `mensaje = "Hola"` + +* `mensaje`: Es la **etiqueta** de la caja (el nombre de la variable). La computadora ahora sabe que existe una caja llamada "mensaje". +* `=`: El **signo de asignación**. ¡OJO AQUÍ! En matemáticas, el `=` significa "estas dos cosas valen lo mismo". En programación, el `=` es una acción: significa *"toma lo que está a la derecha y guárdalo dentro de la caja de la izquierda"*. Piensa que es una flecha apuntando a la izquierda: `mensaje <--- "Hola"`. +* `"Hola"`: Es el **contenido** (texto) que estamos metiendo dentro de la caja. + +Una vez que guardaste algo en la caja `mensaje`, puedes usar `print(mensaje)` para ver qué tiene adentro. Fíjate que al usar la caja en el `print`, **NO** le ponemos comillas, porque no queremos imprimir la palabra literal "mensaje", queremos abrir la caja y ver qué hay dentro. + +### Reglas para los nombres (etiquetas) +La computadora es quisquillosa con cómo llamas a tus cajas: +1. No pueden tener espacios (`mi nombre` está mal). +2. Para separar palabras usamos un guion bajo: `mi_nombre`. +3. No pueden empezar con números (`1nombre` está mal). + +### ¿Qué pasa si me equivoco? + +El error más doloroso al principio es pedirle a la computadora una caja que no has creado, o equivocarte al escribir el nombre. + +Si guardas `saludo = "Hola"` y luego escribes `print(saludoo)`, la terminal gritará en rojo: +`NameError: name 'saludoo' is not defined` + +**Traducción humana:** "Error de nombre: Me estás pidiendo que busque la caja 'saludoo', pero no tengo ni idea de qué es eso. ¡Nunca fabricaste esa caja!" diff --git a/python/Modulo-01-Fundamentos/02-variables/test_main.py b/python/Modulo-01-Fundamentos/02-variables/test_main.py new file mode 100644 index 0000000..407944f --- /dev/null +++ b/python/Modulo-01-Fundamentos/02-variables/test_main.py @@ -0,0 +1,15 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(): + return subprocess.run([sys.executable, RETO], + capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_sin_errores(): + r = run() + assert r.returncode == 0, f"Error en tu código:\n{r.stderr}" +def test_imprime_valores(): + r = run() + assert len(r.stdout.strip()) > 0, "Tu programa debe imprimir algo" diff --git a/retos/general/04-analizador-de-gastos-csv/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/03-textos-enteros/.gitkeep similarity index 100% rename from retos/general/04-analizador-de-gastos-csv/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/03-textos-enteros/.gitkeep diff --git a/python/Modulo-01-Fundamentos/03-textos-enteros/README.md b/python/Modulo-01-Fundamentos/03-textos-enteros/README.md new file mode 100644 index 0000000..f46d5aa --- /dev/null +++ b/python/Modulo-01-Fundamentos/03-textos-enteros/README.md @@ -0,0 +1,10 @@ +# Nivel 03: Letras y Números (Textos y Enteros) 🔤🔢 + +Hasta ahora hemos guardado mensajes en nuestras cajas mágicas, siempre usando comillas. Pero, ¿qué pasa si queremos guardar nuestra edad o la puntuación de un juego? + +En la vida real, sabes que la palabra "cinco" y el número `5` representan lo mismo en tu cabeza, pero para la computadora son dos universos totalmente diferentes. La computadora necesita saber si el `5` que le das es solo un "dibujo" (texto) o una cantidad real (matemática). + +**Ruta de aprendizaje:** +1. **Teoría:** Aprenderemos la diferencia vital entre un texto (String) y un número entero (Integer). +2. **Ejemplo:** Veremos cómo se escriben en código. +3. **Reto:** Registrarás los datos de un viajero espacial. diff --git a/python/Modulo-01-Fundamentos/03-textos-enteros/ejemplo.py b/python/Modulo-01-Fundamentos/03-textos-enteros/ejemplo.py new file mode 100644 index 0000000..5260d35 --- /dev/null +++ b/python/Modulo-01-Fundamentos/03-textos-enteros/ejemplo.py @@ -0,0 +1,33 @@ +# ========================================== +# NIVEL 03: LETRAS Y NÚMEROS +# ========================================== + +print("--- Datos del Sistema ---") + +# Esto es un TEXTO (String). Va con comillas. +# Es solo la palabra "Planeta". +categoria = "Planeta" + +# Esto es un NÚMERO ENTERO (Integer). ¡Va sin comillas! +# Es una cantidad real matemática. +temperatura = 22 + +# Imprimimos ambos. +# En la terminal, la computadora es muy educada y los muestra igual, +# pero por dentro, sabe que uno es texto y el otro es cantidad. +print(categoria) +print(temperatura) + +print("--- El engaño visual ---") + +# Cuidado aquí: "2024" tiene comillas, así que para la máquina +# esto es una PALABRA, igual que "Perro" o "Gato". ¡No es un número! +anio_texto = "2024" + +# Esto SÍ es un número entero. +anio_numero = 2024 + +print("Ambos se ven igual en pantalla:") +print(anio_texto) +print(anio_numero) +# Pero recuerda: no puedes sumar el texto, solo el número. diff --git a/python/Modulo-01-Fundamentos/03-textos-enteros/reto.md b/python/Modulo-01-Fundamentos/03-textos-enteros/reto.md new file mode 100644 index 0000000..86d5868 --- /dev/null +++ b/python/Modulo-01-Fundamentos/03-textos-enteros/reto.md @@ -0,0 +1,27 @@ +# Reto 03: Diario de Abordo Espacial 🚀 + +Vas a registrar los datos básicos del comandante de una nave espacial en la computadora central. Debes ser muy cuidadoso en definir qué datos son texto (palabras) y qué datos son números reales (enteros). + +### Instrucciones +Crea un archivo llamado `reto.py` y escribe el código necesario para lograr el resultado esperado. + +### Reglas +* **Puedes usar:** La orden `print`, variables, signo `=`, textos `""` y números enteros. +* **Obligatorio:** Debes crear 3 variables: + 1. `comandante` (debe guardar un Texto con el nombre "Ripley"). + 2. `nave` (debe guardar un Texto con el nombre "Nostromo"). + 3. `tripulantes` (debe guardar un Número Entero, la cantidad 7). **¡Ojo!** 7 sin comillas. +* Después de crear las variables, usa `print` para mostrarlas en el orden que pide la salida. +* **Prohibido usar:** Todo lo que no hayamos visto en los niveles anteriores. + +### Resultado esperado en la terminal + +```text +--- Registro de la Computadora --- +Nombre del comandante: +Ripley +Nave asignada: +Nostromo +Tripulantes a bordo: +7 +``` diff --git a/python/Modulo-01-Fundamentos/03-textos-enteros/teoria.md b/python/Modulo-01-Fundamentos/03-textos-enteros/teoria.md new file mode 100644 index 0000000..e5b62c1 --- /dev/null +++ b/python/Modulo-01-Fundamentos/03-textos-enteros/teoria.md @@ -0,0 +1,25 @@ +# Teoría: La Etiqueta Oculta de los Datos + +Cada vez que guardas algo en una variable, la computadora le pone una etiqueta invisible secreta que dice **qué tipo de cosa es**. Hoy conoceremos a los dos reyes de los datos: Los Textos y Los Enteros. + +### 1. El Texto (String) +En programación, a los textos los llamamos **Strings** (cadenas, porque son una cadena de letras). +* **¿Cómo se escriben?** Siempre van rodeados de comillas `""`. +* **Analogía:** Piensa en un String como una pintura de un número. Si escribes `"5"`, no tienes 5 manzanas reales, tienes un cuadro pintado con el número 5. Si tratas de comerte el cuadro, te vas a romper los dientes. No puedes hacer matemáticas reales con Strings. + +### 2. El Entero (Integer) +A los números sin decimales los llamamos **Integers** (enteros). +* **¿Cómo se escriben?** Se escriben "desnudos", sin absolutamente NADA alrededor. +* **Analogía:** Un Integer es una cantidad real. Si escribes `5`, tienes 5 manzanas de verdad en tus manos. + +### Resumen Visual + +`edad = 25` <-- Integer (¡Bien! 25 manzanas reales) +`edad = "25"` <-- String (¡Cuidado! Es solo una foto del número 25) + +### ¿Qué pasa si me equivoco? + +El error más común es mezclar peras con manzanas. Aunque aún no haremos sumas complejas, si más adelante intentas sumar el número real `2` con el texto `"2"`, la computadora sufrirá un colapso y mostrará un error como este: +`TypeError: unsupported operand type(s) for +: 'int' and 'str'` + +**Traducción humana:** "Error de Tipo: ¡Me estás pidiendo que mezcle un Integer (int) con un String (str)! No puedo sumar manzanas reales con fotos de manzanas." diff --git a/python/Modulo-01-Fundamentos/03-textos-enteros/test_main.py b/python/Modulo-01-Fundamentos/03-textos-enteros/test_main.py new file mode 100644 index 0000000..49f0b34 --- /dev/null +++ b/python/Modulo-01-Fundamentos/03-textos-enteros/test_main.py @@ -0,0 +1,17 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(): + return subprocess.run([sys.executable, RETO], + capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_sin_errores(): + r = run() + assert r.returncode == 0, f"Error:\n{r.stderr}" +def test_imprime_texto_y_numero(): + r = run() + tiene_numero = any(c.isdigit() for c in r.stdout) + tiene_texto = any(c.isalpha() for c in r.stdout) + assert tiene_numero and tiene_texto, "Debes imprimir texto y números" diff --git a/retos/general/05-generador-de-contrasenas/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/04-flotantes-bool/.gitkeep similarity index 100% rename from retos/general/05-generador-de-contrasenas/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/04-flotantes-bool/.gitkeep diff --git a/python/Modulo-01-Fundamentos/04-flotantes-bool/README.md b/python/Modulo-01-Fundamentos/04-flotantes-bool/README.md new file mode 100644 index 0000000..6495887 --- /dev/null +++ b/python/Modulo-01-Fundamentos/04-flotantes-bool/README.md @@ -0,0 +1,10 @@ +# Nivel 04: Precisión y Verdades (Flotantes y Booleanos) ⚖️💡 + +Hasta ahora nuestro mundo tiene palabras (Textos) y cosas que puedes contar con los dedos (Enteros). Pero, ¿qué pasa si queremos medir tu altura exacta? ¿O si queremos saber si la luz de una habitación está encendida o apagada? + +Para eso, la computadora tiene dos tipos de datos más. Los números con decimales (para ser precisos como un cirujano) y los interruptores de Sí/No (para tomar decisiones más adelante). + +**Ruta de aprendizaje:** +1. **Teoría:** Conoceremos a los Flotantes (decimales) y los Booleanos (verdadero/falso). +2. **Ejemplo:** Veremos cómo se escriben correctamente. +3. **Reto:** Registrarás el estado vital de un explorador. diff --git a/python/Modulo-01-Fundamentos/04-flotantes-bool/ejemplo.py b/python/Modulo-01-Fundamentos/04-flotantes-bool/ejemplo.py new file mode 100644 index 0000000..e8b65d5 --- /dev/null +++ b/python/Modulo-01-Fundamentos/04-flotantes-bool/ejemplo.py @@ -0,0 +1,29 @@ +# ========================================== +# NIVEL 04: FLOTANTES Y BOOLEANOS +# ========================================== + +print("--- Medidas Precisas ---") + +# Esto es un Float (número con decimales). +# Recuerda: ¡Usa punto, no coma! +estatura_metros = 1.75 +temperatura_corporal = 36.5 + +print("Estatura:") +print(estatura_metros) +print("Temperatura:") +print(temperatura_corporal) + +print("--- Interruptores ---") + +# Esto es un Booleano. +# Solo puede ser True (Verdadero) o False (Falso). +# Sin comillas y con la primera letra en mayúscula. +traje_puesto = True +oxigeno_critico = False + +print("¿Tiene el traje puesto?") +print(traje_puesto) + +print("¿Nivel de oxígeno crítico?") +print(oxigeno_critico) diff --git a/python/Modulo-01-Fundamentos/04-flotantes-bool/reto.md b/python/Modulo-01-Fundamentos/04-flotantes-bool/reto.md new file mode 100644 index 0000000..7dd3f9c --- /dev/null +++ b/python/Modulo-01-Fundamentos/04-flotantes-bool/reto.md @@ -0,0 +1,27 @@ +# Reto 04: Estado del Explorador 🧑‍🚀 + +Vas a registrar los datos vitales y de equipo de un explorador que está a punto de salir de la nave. Es momento de usar la precisión de los flotantes y la certeza de los booleanos. + +### Instrucciones +Crea un archivo llamado `reto.py` y escribe el código necesario para lograr el resultado esperado. + +### Reglas +* **Puedes usar:** La orden `print`, variables, textos, enteros, flotantes y booleanos. +* **Obligatorio:** Debes crear 3 variables: + 1. `distancia_recorrida` (debe guardar el Flotante `12.5`). + 2. `casco_puesto` (debe guardar el Booleano `True`). + 3. `peligro_inminente` (debe guardar el Booleano `False`). +* Después de crear las variables, usa `print` para mostrarlas en el orden que pide la salida. +* **Prohibido usar:** Conceptos avanzados, matemáticas, o escribir las respuestas directamente dentro del `print` (tienes que imprimir las variables). + +### Resultado esperado en la terminal + +```text +--- Estado del Explorador --- +Distancia recorrida en km: +12.5 +¿Tiene el casco puesto? +True +¿Hay peligro inminente? +False +``` diff --git a/python/Modulo-01-Fundamentos/04-flotantes-bool/teoria.md b/python/Modulo-01-Fundamentos/04-flotantes-bool/teoria.md new file mode 100644 index 0000000..9f56515 --- /dev/null +++ b/python/Modulo-01-Fundamentos/04-flotantes-bool/teoria.md @@ -0,0 +1,21 @@ +# Teoría: La Regla y el Interruptor + +### 1. El Flotante (Float) +A los números que tienen decimales los llamamos **Floats** (porque el punto decimal puede "flotar" a diferentes posiciones). +* **Analogía:** Un Integer (entero) es contar cuántos hermanos tienes. Un Float es usar una cinta métrica para saber tu estatura. +* **La trampa mortal:** En español solemos usar la coma (1,5) para los decimales. En programación, **SIEMPRE usamos el punto** (`1.5`). Si usas una coma, la computadora se confundirá. + +### 2. El Booleano (Boolean) +A veces las cosas son absolutas. ¿Estás despierto o dormido? ¿La luz está prendida o apagada? Para esto usamos los **Booleanos**. +* **Analogía:** Es un interruptor de luz. Solo tiene dos posiciones: Encendido (`True`) o Apagado (`False`). +* **¿Cómo se escriben?** Son palabras especiales para la computadora. Se escriben **SIN comillas** y la primera letra **TIENE que ser mayúscula**. + +### ¿Qué pasa si me equivoco? + +**Error con Floats:** Si escribes `peso = 70,5` (con coma), la computadora pensará que le estás pasando dos cosas diferentes (un 70 y un 5) en lugar de un solo número. + +**Errores con Booleanos:** +1. Si escribes `vivo = true` (con minúscula), la terminal dirá: + `NameError: name 'true' is not defined` + (La computadora pensará que 'true' es el nombre de otra caja que olvidaste crear, porque la palabra clave oficial es `True`). +2. Si escribes `vivo = "True"` (con comillas), ¡ya no es un booleano! Es simplemente un dibujo de la palabra "True", un Texto normal y corriente. Pierde sus poderes mágicos de interruptor. diff --git a/python/Modulo-01-Fundamentos/04-flotantes-bool/test_main.py b/python/Modulo-01-Fundamentos/04-flotantes-bool/test_main.py new file mode 100644 index 0000000..b159697 --- /dev/null +++ b/python/Modulo-01-Fundamentos/04-flotantes-bool/test_main.py @@ -0,0 +1,16 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(): + return subprocess.run([sys.executable, RETO], + capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_sin_errores(): + r = run() + assert r.returncode == 0, f"Error:\n{r.stderr}" +def test_contiene_true_o_false(): + r = run() + assert "True" in r.stdout or "False" in r.stdout, \ + "Debes imprimir al menos un valor booleano (True o False)" diff --git a/retos/general/06-conversor-de-unidades/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/05-aritmetica/.gitkeep similarity index 100% rename from retos/general/06-conversor-de-unidades/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/05-aritmetica/.gitkeep diff --git a/python/Modulo-01-Fundamentos/05-aritmetica/README.md b/python/Modulo-01-Fundamentos/05-aritmetica/README.md new file mode 100644 index 0000000..e1ff519 --- /dev/null +++ b/python/Modulo-01-Fundamentos/05-aritmetica/README.md @@ -0,0 +1,10 @@ +# Nivel 05: La Súper Calculadora (Aritmética) 🧮 + +Las computadoras fueron inventadas originalmente para hacer cálculos matemáticos aburridos y repetitivos muy rápido. De hecho, la palabra "computadora" viene de "computar" (calcular). + +Ahora que ya sabemos cómo guardar números enteros (como `5`) y flotantes (como `3.14`) en nuestras cajas mágicas (variables), es hora de hacer que interactúen entre ellos. Vamos a enseñarle a la máquina a sumar, restar, multiplicar y dividir. + +**Ruta de aprendizaje:** +1. **Teoría:** Conoceremos los símbolos matemáticos de la programación. +2. **Ejemplo:** Haremos cálculos y guardaremos los resultados. +3. **Reto:** Calcularás los suministros para un viaje intergaláctico. diff --git a/python/Modulo-01-Fundamentos/05-aritmetica/ejemplo.py b/python/Modulo-01-Fundamentos/05-aritmetica/ejemplo.py new file mode 100644 index 0000000..130c585 --- /dev/null +++ b/python/Modulo-01-Fundamentos/05-aritmetica/ejemplo.py @@ -0,0 +1,37 @@ +# ========================================== +# NIVEL 05: LA SÚPER CALCULADORA +# ========================================== + +print("--- Matemáticas Directas ---") + +# Podemos hacer los cálculos y guardarlos directamente. +puntos_ganados = 100 + 50 +print("Puntos totales:") +print(puntos_ganados) + +print("--- Sumando Cajas ---") + +# Es mucho más útil guardar los valores en cajas primero +# y luego hacer las operaciones matemáticas usando los nombres. +monedas_oro = 10 +monedas_plata = 5 + +# El símbolo = toma lo de la derecha (10 + 5) y lo guarda a la izquierda. +total_monedas = monedas_oro + monedas_plata +print("Total de monedas en el inventario:") +print(total_monedas) + +print("--- Multiplicación y División ---") + +precio_espada = 10.5 +cantidad_espadas = 3 + +# Usamos el asterisco (*) para multiplicar +costo_total = precio_espada * cantidad_espadas +print("Costo de las 3 espadas:") +print(costo_total) + +# Usamos la barra oblicua (/) para dividir +mitad_costo = costo_total / 2 +print("Si dividimos la cuenta entre dos:") +print(mitad_costo) diff --git a/python/Modulo-01-Fundamentos/05-aritmetica/reto.md b/python/Modulo-01-Fundamentos/05-aritmetica/reto.md new file mode 100644 index 0000000..8f36ffe --- /dev/null +++ b/python/Modulo-01-Fundamentos/05-aritmetica/reto.md @@ -0,0 +1,21 @@ +# Reto 05: Suministros Espaciales ⛽ + +Estás preparando una nave para un viaje interestelar. Necesitas usar la computadora para calcular exactamente cuánto costará el combustible necesario, pero para no confundirte, guardarás todos los pasos en cajas (variables). + +### Instrucciones +Crea un archivo llamado `reto.py` y escribe el código necesario para lograr el resultado esperado. + +### Reglas +* **Puedes usar:** `print()`, variables `=`, enteros, flotantes, y el operador de multiplicación `*`. +* **Obligatorio:** Debes crear las siguientes variables en orden: + 1. `litros_combustible` (debe guardar el número entero `500`). + 2. `precio_por_litro` (debe guardar el número flotante `2.5`). + 3. `costo_total` (debe multiplicar las dos variables anteriores y guardar el resultado). +* Al final, debes usar dos instrucciones `print()`. Una para mostrar un texto fijo de título y otra para imprimir el valor de `costo_total`. + +### Resultado esperado en la terminal + +```text +--- Costo Total de Combustible --- +1250.0 +``` diff --git a/python/Modulo-01-Fundamentos/05-aritmetica/teoria.md b/python/Modulo-01-Fundamentos/05-aritmetica/teoria.md new file mode 100644 index 0000000..ec962dd --- /dev/null +++ b/python/Modulo-01-Fundamentos/05-aritmetica/teoria.md @@ -0,0 +1,34 @@ +# Teoría: Los Símbolos Matemáticos + +Para hacer cálculos en programación, no usamos la "x" para multiplicar ni los dos puntos para dividir. La computadora usa símbolos específicos del teclado. + +### Los Operadores Básicos + +1. **Suma (`+`)**: Funciona igual que en la escuela. +2. **Resta (`-`)**: También es idéntico a la escuela. +3. **Multiplicación (`*`)**: ¡Ojo aquí! Usamos el asterisco. No uses la letra "x" ni "X", porque la computadora pensará que es una caja (variable) o un texto. +4. **División (`/`)**: Usamos la barra oblicua. + +### ¿Cómo guardar un cálculo? + +Recuerda que el símbolo `=` significa "toma lo de la derecha y guárdalo en la caja de la izquierda". +Si escribimos: +`total = 5 + 5` +La computadora primero resuelve la suma mentalmente (`10`) y luego guarda ese `10` dentro de la caja `total`. + +También podemos sumar cajas directamente: +`manzanas = 3` +`peras = 2` +`frutas = manzanas + peras` + +### ¿Qué pasa si me equivoco? + +El error que rompe sistemas mundiales es intentar aplicar matemáticas a cosas que no son números. + +Si tienes un texto (String) y un número (Integer/Float) e intentas sumarlos: +`resultado = "Hola" + 5` + +La computadora te mostrará un error fulminante: +`TypeError: can only concatenate str (not "int") to str` + +**Traducción humana:** "Error de Tipo: Solo puedo pegar un Texto con otro Texto, no puedo pegar un Texto con un Número Entero (int). ¿Cómo sumo la letra H con el número 5? ¡No tiene sentido!" diff --git a/python/Modulo-01-Fundamentos/05-aritmetica/test_main.py b/python/Modulo-01-Fundamentos/05-aritmetica/test_main.py new file mode 100644 index 0000000..0f1e28c --- /dev/null +++ b/python/Modulo-01-Fundamentos/05-aritmetica/test_main.py @@ -0,0 +1,15 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(): + return subprocess.run([sys.executable, RETO], + capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_sin_errores(): + r = run() + assert r.returncode == 0, f"Error:\n{r.stderr}" +def test_resultado_correcto(): + r = run() + assert "1250" in r.stdout, "El costo total debe ser 1250.0" diff --git a/retos/general/07-calculadora-de-imc/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/06-inputs/.gitkeep similarity index 100% rename from retos/general/07-calculadora-de-imc/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/06-inputs/.gitkeep diff --git a/python/Modulo-01-Fundamentos/06-inputs/README.md b/python/Modulo-01-Fundamentos/06-inputs/README.md new file mode 100644 index 0000000..f3eeff6 --- /dev/null +++ b/python/Modulo-01-Fundamentos/06-inputs/README.md @@ -0,0 +1,10 @@ +# Nivel 06: Escuchando al Humano (Inputs) 👂 + +Hasta ahora, nuestros programas han sido un monólogo. La computadora habla y nosotros solo leemos la terminal. Pero las verdaderas aplicaciones interactúan con la gente. Piden contraseñas, nombres, clics y comandos. + +Hoy le daremos oídos a nuestra máquina. Aprenderemos cómo hacer que la computadora detenga el tiempo, haga una pregunta y espere pacientemente a que el humano responda a través del teclado. + +**Ruta de aprendizaje:** +1. **Teoría:** Conoceremos la herramienta inversa a `print()`. +2. **Ejemplo:** Haremos que la computadora nos haga una entrevista básica. +3. **Reto:** Crearás un sistema de seguridad de entrada. diff --git a/python/Modulo-01-Fundamentos/06-inputs/ejemplo.py b/python/Modulo-01-Fundamentos/06-inputs/ejemplo.py new file mode 100644 index 0000000..a8ca5d2 --- /dev/null +++ b/python/Modulo-01-Fundamentos/06-inputs/ejemplo.py @@ -0,0 +1,26 @@ +# ========================================== +# NIVEL 06: ESCUCHANDO AL HUMANO +# ========================================== + +print("--- Inicializando protocolo de entrevista ---") + +# La computadora mostrará el texto, se pausará y esperará. +# Lo que escribas se guardará en la caja 'usuario'. +usuario = input("Por favor, ingresa tu nombre de piloto: ") + +# Pedimos otro dato más. +# Nota el espacio extra al final de la pregunta ("...favorito: "). +# Esto es para que al escribir, el texto no quede pegado a los dos puntos. +color_traje = input("¿Cuál es tu color de traje favorito? ") + +print("--- Procesando información ---") + +# Ahora usamos la orden print() para combinar nuestros textos +# con las respuestas que el humano guardó en las cajas. +print("Bienvenido a bordo,") +print(usuario) + +print("Hemos preparado tu traje color:") +print(color_traje) + +print("¡Preparación completada!") diff --git a/python/Modulo-01-Fundamentos/06-inputs/reto.md b/python/Modulo-01-Fundamentos/06-inputs/reto.md new file mode 100644 index 0000000..334143a --- /dev/null +++ b/python/Modulo-01-Fundamentos/06-inputs/reto.md @@ -0,0 +1,28 @@ +# Reto 06: La Puerta de Seguridad 🚪 + +Estás programando la inteligencia artificial de la puerta principal de la base. Tu trabajo es hacer que la puerta le haga dos preguntas de seguridad a quien intente entrar, guarde sus respuestas y luego imprima un registro del intento de acceso. + +### Instrucciones +Crea un archivo llamado `reto.py` y escribe el código necesario para lograr el resultado esperado. + +### Reglas +* **Puedes usar:** `print()`, `input()`, variables `=` y textos `""`. +* **Obligatorio:** Debes crear 2 variables que pidan información al usuario: + 1. `visitante` (debe usar `input` para preguntar: "Identifíquese: "). + 2. `planeta` (debe usar `input` para preguntar: "Planeta de origen: "). +* Después de hacer las dos preguntas, debes imprimir un reporte exacto como se muestra abajo, usando las variables que guardaste. +* **Prohibido usar:** Conceptos que no hayamos visto (como unir textos con el símbolo `+`). Tienes que usar comandos `print()` separados para mostrar la información, como en el ejemplo. + +### Resultado esperado en la terminal + +(Nota: Donde dice "R2D2" y "Tatooine", es lo que el humano escribe. Tu programa solo debe imprimir el resto). + +```text +Identifíquese: R2D2 +Planeta de origen: Tatooine +--- Registro de Acceso --- +Se ha detectado a: +R2D2 +Proveniente de: +Tatooine +``` diff --git a/python/Modulo-01-Fundamentos/06-inputs/teoria.md b/python/Modulo-01-Fundamentos/06-inputs/teoria.md new file mode 100644 index 0000000..dc58211 --- /dev/null +++ b/python/Modulo-01-Fundamentos/06-inputs/teoria.md @@ -0,0 +1,22 @@ +# Teoría: La Boca y los Oídos + +Si `print()` es la boca de la computadora (sirve para hablar hacia afuera), entonces `input()` son sus oídos (sirve para escuchar lo que viene de afuera). + +### Anatomía de la instrucción + +Vamos a desmontar esta línea: `nombre = input("¿Cómo te llamas? ")` + +* `input`: Es la orden de **escuchar**. Cuando la computadora lee esto, **congela el tiempo**. El programa se detiene completamente hasta que el humano escribe algo en el teclado y presiona la tecla `Enter`. +* `("¿Cómo te llamas? ")`: Lo que pongas entre los paréntesis (siempre entre comillas) es la pista o pregunta que la computadora mostrará *antes* de quedarse esperando. Si lo dejas vacío `input()`, la computadora se quedará congelada en negro y el usuario no sabrá qué hacer. +* `nombre =`: Recuerda el Nivel 02 (las variables). Si la computadora escucha algo pero no lo guarda en ningún lado, se le olvida al instante. Por eso usamos el `=` para atrapar la respuesta del humano y guardarla en la caja `nombre`. + +### La Gran Trampa del Input + +Hay una regla de oro que debes tatuarte: **TODO lo que entra por `input()` se convierte en Texto (String)**. +* Si el usuario escribe `Alex`, se guarda como `"Alex"`. +* Si el usuario escribe `5`, se guarda como `"5"` (¡con comillas!). + +Si intentas sumar ese `"5"` con un `2` real, la computadora estallará con el error que vimos en el nivel anterior: +`TypeError: can only concatenate str (not "int") to str` + +Más adelante aprenderemos cómo convertir esos textos en números reales, pero por ahora, limitémonos a pedir palabras. diff --git a/python/Modulo-01-Fundamentos/06-inputs/test_main.py b/python/Modulo-01-Fundamentos/06-inputs/test_main.py new file mode 100644 index 0000000..98b0889 --- /dev/null +++ b/python/Modulo-01-Fundamentos/06-inputs/test_main.py @@ -0,0 +1,13 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(stdin_data): + return subprocess.run([sys.executable, RETO], + input=stdin_data, capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_acepta_input_y_responde(): + r = run("Ana\n") + assert r.returncode == 0, f"Error:\n{r.stderr}" + assert "Ana" in r.stdout, "Debes mostrar el nombre ingresado" diff --git a/retos/general/08-organizador-de-archivos/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/07-f-strings/.gitkeep similarity index 100% rename from retos/general/08-organizador-de-archivos/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/07-f-strings/.gitkeep diff --git a/python/Modulo-01-Fundamentos/07-f-strings/README.md b/python/Modulo-01-Fundamentos/07-f-strings/README.md new file mode 100644 index 0000000..e3031a7 --- /dev/null +++ b/python/Modulo-01-Fundamentos/07-f-strings/README.md @@ -0,0 +1,10 @@ +# Nivel 07: Textos Inteligentes (F-Strings) ✍️ + +Hasta el momento, para mezclar las cajas (variables) con nuestros textos hemos tenido que usar un montón de instrucciones `print()` separadas. Si querías decir "Hola, Alex", tenías que hacer un `print("Hola,")` y luego un `print(usuario)`. Eso hace que nuestro código se vea torpe y largo. + +Hoy vamos a aprender el truco más elegante y utilizado de Python moderno para armar oraciones perfectas: los **F-Strings** (Textos con Formato). Es como crear un texto con huecos mágicos que se rellenan automáticamente. + +**Ruta de aprendizaje:** +1. **Teoría:** Descubriremos la letra mágica "f" y las llaves `{}`. +2. **Ejemplo:** Armaremos oraciones fluidas. +3. **Reto:** Crearás un pasaporte intergaláctico. diff --git a/python/Modulo-01-Fundamentos/07-f-strings/ejemplo.py b/python/Modulo-01-Fundamentos/07-f-strings/ejemplo.py new file mode 100644 index 0000000..50d5abc --- /dev/null +++ b/python/Modulo-01-Fundamentos/07-f-strings/ejemplo.py @@ -0,0 +1,27 @@ +# ========================================== +# NIVEL 07: TEXTOS INTELIGENTES (F-STRINGS) +# ========================================== + +print("--- Forma antigua (Sin f-strings) ---") + +# Hasta ahora lo hacíamos así: +nombre = "Sam" +print("Hola") +print(nombre) +# O imprimía en varias líneas, o se veía muy torpe. + +print("--- Forma nueva (Con f-strings) ---") + +# Con la letra 'f' y las llaves '{}', lo hacemos en una sola línea. +print(f"Hola {nombre}, bienvenido de vuelta.") + +print("--- Mezclando todo ---") + +# Primero recolectamos los datos +usuario = input("¿Cuál es tu nombre? ") +estacion = input("¿A qué estación espacial viajas? ") +dias = 15 +combustible = 99.5 + +# Y luego disparamos una sola oración que se lee fluidamente +print(f"Atención pasajero {usuario}. El vuelo hacia la {estacion} durará {dias} días. Nivel de combustible: {combustible}%.") diff --git a/python/Modulo-01-Fundamentos/07-f-strings/reto.md b/python/Modulo-01-Fundamentos/07-f-strings/reto.md new file mode 100644 index 0000000..f6bfc7c --- /dev/null +++ b/python/Modulo-01-Fundamentos/07-f-strings/reto.md @@ -0,0 +1,25 @@ +# Reto 07: Identificación Dinámica 🛂 + +El sistema de seguridad que hiciste en el nivel anterior funcionaba, pero el reporte final se veía muy rústico impreso en múltiples líneas. Vas a mejorarlo para que el reporte sea una sola oración fluida utilizando F-Strings. + +### Instrucciones +Crea un archivo llamado `reto.py` y escribe el código necesario para lograr el resultado esperado. + +### Reglas +* **Puedes usar:** `print()`, `input()`, variables, textos, y la sintaxis especial de f-strings (`f"..."` y `{}`). +* **Obligatorio:** + 1. Debes pedirle al usuario su `rango` (ej: Capitán) usando `input()`. + 2. Debes pedirle al usuario su `apellido` (ej: Picard) usando `input()`. + 3. Usando un **único comando `print()`** y un **F-String**, debes imprimir la oración completa tal como aparece en el resultado esperado. +* **Prohibido usar:** Múltiples comandos `print()` para la última oración. + +### Resultado esperado en la terminal + +(Nota: Donde dice "Capitán" y "Picard", es lo que el humano escribe). + +```text +Ingrese su rango: Capitán +Ingrese su apellido: Picard +--- Reporte de Acceso --- +Bienvenido a bordo, Capitán Picard. Las compuertas están abiertas. +``` diff --git a/python/Modulo-01-Fundamentos/07-f-strings/teoria.md b/python/Modulo-01-Fundamentos/07-f-strings/teoria.md new file mode 100644 index 0000000..0082d1b --- /dev/null +++ b/python/Modulo-01-Fundamentos/07-f-strings/teoria.md @@ -0,0 +1,26 @@ +# Teoría: El Texto con Huecos + +Imagina un formulario de "rellene los espacios en blanco" como: +`Mi nombre es _______ y tengo _______ años.` +Los F-Strings hacen exactamente esto. + +### Anatomía de la instrucción + +Vamos a desmontar esta línea: `print(f"Hola, mi nombre es {piloto}")` + +* `f`: (Justo antes de abrir la comilla). Significa "Formato". Es el botón de encendido mágico. Le avisa a la computadora: *"¡Ojo! Este texto tiene agujeros adentro, prepárate para rellenarlos"*. +* `" "`: Las comillas de siempre para indicar que es un texto. +* `{ }`: Son las llaves (los "huecos"). Todo lo que metas aquí adentro, la computadora lo buscará en sus cajas (variables) y pegará su contenido justo en ese lugar del texto. +* `piloto`: El nombre de la caja que queremos usar para rellenar ese hueco. + +### ¿Qué pasa si me equivoco? + +1. **Olvidar la letra `f`**: + Si escribes `print("Hola {piloto}")` (sin la 'f' al principio), la magia no se activa. La computadora tomará las llaves literalmente y en pantalla verás exactamente: + `Hola {piloto}` (en lugar de "Hola Alex"). + +2. **Olvidar las llaves `{}`**: + Si pones la `f` pero te olvidas de las llaves: `print(f"Hola piloto")`, imprimirá "Hola piloto" porque no le dijiste qué palabra era un hueco a rellenar con una variable. + +3. **Escribir mal el nombre de la variable dentro de las llaves**: + Si tu variable se llama `piloto` y pones `{pilotoo}`, el programa estallará con el clásico `NameError: name 'pilotoo' is not defined`. diff --git a/python/Modulo-01-Fundamentos/07-f-strings/test_main.py b/python/Modulo-01-Fundamentos/07-f-strings/test_main.py new file mode 100644 index 0000000..c8ab27d --- /dev/null +++ b/python/Modulo-01-Fundamentos/07-f-strings/test_main.py @@ -0,0 +1,13 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(stdin_data): + return subprocess.run([sys.executable, RETO], + input=stdin_data, capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_f_string_con_nombre(): + r = run("Carlos\n30\n") + assert r.returncode == 0, f"Error:\n{r.stderr}" + assert "Carlos" in r.stdout, "El f-string debe incluir el nombre ingresado" diff --git a/retos/general/09-recordatorios-en-terminal/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/08-comentarios/.gitkeep similarity index 100% rename from retos/general/09-recordatorios-en-terminal/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/08-comentarios/.gitkeep diff --git a/python/Modulo-01-Fundamentos/08-comentarios/README.md b/python/Modulo-01-Fundamentos/08-comentarios/README.md new file mode 100644 index 0000000..9f66175 --- /dev/null +++ b/python/Modulo-01-Fundamentos/08-comentarios/README.md @@ -0,0 +1,10 @@ +# Nivel 08: Notas para Humanos (Comentarios) 📝 + +A medida que tus programas crecen, se vuelven más complejos. Escribir código no se trata solo de darle instrucciones a la máquina, sino de comunicarte con otros humanos (¡incluyendo a tu "yo" del futuro!) para explicar *por qué* hiciste algo. + +Aquí es donde entran los **Comentarios**. Son líneas de texto que tú puedes leer, pero que la computadora ignora por completo. Es tu espacio seguro para dejar notas. + +**Ruta de aprendizaje:** +1. **Teoría:** Aprenderemos el símbolo de invisibilidad. +2. **Ejemplo:** Veremos cómo documentar nuestro trabajo. +3. **Reto:** Explicarás tu propio código. diff --git a/python/Modulo-01-Fundamentos/08-comentarios/ejemplo.py b/python/Modulo-01-Fundamentos/08-comentarios/ejemplo.py new file mode 100644 index 0000000..48e66d3 --- /dev/null +++ b/python/Modulo-01-Fundamentos/08-comentarios/ejemplo.py @@ -0,0 +1,26 @@ +# ========================================== +# NIVEL 08: NOTAS PARA HUMANOS +# ========================================== + +print("--- Ejecutando el sistema ---") + +# Esta es una nota en una línea completa. +# Sirve para explicar el bloque de código que viene a continuación. +velocidad_luz = 299792 +distancia_marte = 225000000 + +# Esta es una nota al final de la línea. +# Sirve para dar contexto rápido sobre un dato específico. +tiempo = distancia_marte / velocidad_luz # El resultado será en segundos + +print(f"Tiempo estimado a Marte: {tiempo} segundos.") + +print("--- El poder de silenciar ---") + +# Si hay una línea de código que está causando problemas, +# o simplemente no quieres que se ejecute por ahora, +# puedes "comentarla" poniéndole un # al principio. + +# print("¡Alerta de asteroides!") + +print("Todo está tranquilo.") diff --git a/python/Modulo-01-Fundamentos/08-comentarios/reto.md b/python/Modulo-01-Fundamentos/08-comentarios/reto.md new file mode 100644 index 0000000..25d11ea --- /dev/null +++ b/python/Modulo-01-Fundamentos/08-comentarios/reto.md @@ -0,0 +1,24 @@ +# Reto: El Detective de Notas 🕵️ + +## Tu Misión + +Has encontrado el código de otro programador, pero parece que dejó una bomba de tiempo activa. Tu objetivo es encontrar el código peligroso, desactivarlo (sin borrarlo) y dejar notas para el equipo. + +## Pasos + +1. Crea un archivo llamado `reto.py`. +2. Escribe el siguiente código **exactamente** como está: +```python +iniciar_secuencia = "activada" +cuenta_regresiva = 10 +# Código principal del sistema +print(f"Estado del sistema: {iniciar_secuencia}") +print(f"Tiempo restante: {cuenta_regresiva}") +print("¡BOMBA DETONADA!") +``` +3. Ejecuta el archivo y observa qué sucede. +4. Ahora, tu misión: **silencia** la última línea de código usando el símbolo mágico de las notas para que la bomba no explote. **No borres la línea, solo coméntala.** +5. Agrega una nota tuya (un comentario) en la línea anterior a la bomba silenciada explicando que la has desactivado. +6. Vuelve a ejecutar el programa y verifica que la bomba ya no se detone. + +¡Buena suerte, detective! diff --git a/python/Modulo-01-Fundamentos/08-comentarios/teoria.md b/python/Modulo-01-Fundamentos/08-comentarios/teoria.md new file mode 100644 index 0000000..299377b --- /dev/null +++ b/python/Modulo-01-Fundamentos/08-comentarios/teoria.md @@ -0,0 +1,27 @@ +# Teoría: El Símbolo de Invisibilidad + +Para escribir un comentario en Python, usamos el símbolo de numeral o "hashtag": `#`. + +### Anatomía de la instrucción + +Vamos a desmontar esta línea: `# Esto es un cálculo de impuestos` + +* `#`: Todo lo que se escriba a la derecha de este símbolo, en esa misma línea, se vuelve invisible para la computadora. Cuando el sistema lee tu código y encuentra un `#`, dice: *"Ah, esto es para humanos, salto a la siguiente línea"*. + +Puedes usar comentarios de dos maneras principales: +1. **Línea completa:** Para explicar una sección entera de código. +2. **Al final de la línea:** Para explicar una variable específica justo al lado de ella. Ej: `oxigeno = 100 # Porcentaje restante` + +También tienen un superpoder oculto: **Desactivar código**. Si escribes una orden como `print("Hola")` pero le pones un `#` al principio, la máquina lo ignorará. Los programadores hacen esto todo el tiempo para probar cosas sin borrar su código. + +### ¿Qué pasa si me equivoco? + +El error ocurre cuando olvidas poner el `#`. Si escribes texto humano normal en tu archivo: + +`calculamos el total de la nave` +`total = 50 + 20` + +La computadora intentará ejecutar la primera línea como si fueran órdenes oficiales, y al no entender qué significa la palabra "calculamos", explotará lanzando: +`SyntaxError: invalid syntax` + +**Traducción humana:** "Error de sintaxis: No entiendo este lenguaje. Si querías dejarme una nota, ¡ponle un # primero!" diff --git a/python/Modulo-01-Fundamentos/08-comentarios/test_main.py b/python/Modulo-01-Fundamentos/08-comentarios/test_main.py new file mode 100644 index 0000000..ca098aa --- /dev/null +++ b/python/Modulo-01-Fundamentos/08-comentarios/test_main.py @@ -0,0 +1,19 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(): + return subprocess.run([sys.executable, RETO], + capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_sin_errores(): + r = run() + assert r.returncode == 0, f"Error:\n{r.stderr}" +def test_bomba_no_detonada(): + r = run() + assert "BOMBA DETONADA" not in r.stdout, \ + "La línea de la bomba debe estar comentada" +def test_imprime_algo_correcto(): + r = run() + assert len(r.stdout.strip()) > 0, "Tu programa debe imprimir algo" diff --git a/retos/general/10-analizador-de-texto/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/09-errores/.gitkeep similarity index 100% rename from retos/general/10-analizador-de-texto/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/09-errores/.gitkeep diff --git a/python/Modulo-01-Fundamentos/09-errores/README.md b/python/Modulo-01-Fundamentos/09-errores/README.md new file mode 100644 index 0000000..5813cb2 --- /dev/null +++ b/python/Modulo-01-Fundamentos/09-errores/README.md @@ -0,0 +1,19 @@ +# Nivel 09: El Arte de Equivocarse (Errores) + +¡Bienvenido al Nivel 09! + +Hasta ahora, todo ha funcionado perfectamente. Pero en el mundo real, los programas fallan. ¡Y eso es algo bueno! Cuando Python no entiende algo, no se queda callado ni hace cosas raras a tus espaldas; se detiene inmediatamente y te dice qué salió mal lanzando un "Error". + +En este nivel aprenderemos a perderle el miedo a los mensajes en rojo y a entender qué nos está intentando decir la computadora. + +## ¿Qué aprenderás? +* A reconocer que los errores son tus amigos, no tus enemigos. +* A leer un mensaje de error (Traceback). +* Los 3 errores más comunes que cometerás: SyntaxError, NameError y TypeError. + +## Archivos en este nivel: +1. **`teoria.md`**: Cómo interpretar los gritos de ayuda de Python. +2. **`ejemplo.py`**: Un código lleno de errores intencionales para que veas cómo explota. +3. **`reto.md`**: Tu misión como reparador de código. + +¡Adelante, vamos a romper cosas! diff --git a/python/Modulo-01-Fundamentos/09-errores/ejemplo.py b/python/Modulo-01-Fundamentos/09-errores/ejemplo.py new file mode 100644 index 0000000..91688ff --- /dev/null +++ b/python/Modulo-01-Fundamentos/09-errores/ejemplo.py @@ -0,0 +1,33 @@ +# ========================================== +# NIVEL 09: EL ARTE DE EQUIVOCARSE +# ========================================== + +# NOTA: Este programa está diseñado para fallar. +# Si lo ejecutas tal como está, Python te mostrará errores. +# Para ver cada error, debes ir comentando (poniendo un #) +# la línea que falló, y luego volver a ejecutar. + +print("--- Iniciando simulador de errores ---") + +# 1. SyntaxError (Error de Sintaxis) +# Aquí nos falta el paréntesis de cierre al final. +# Descomenta (quita el #) de la siguiente línea para ver el error: +# print("Se me olvidó cerrar esto" + +# 2. NameError (Error de Nombre) +# Vamos a crear una variable, pero luego la escribiremos mal. +nombre_usuario = "Alejandro" + +# Python dirá: name 'nombre_usario' is not defined +# Descomenta la siguiente línea para ver el error: +# print(f"Bienvenido, {nombre_usario}") + +# 3. TypeError (Error de Tipo) +# Intentaremos sumar texto con números directamente, sin f-strings. +manzanas = 5 + +# Python dirá: can only concatenate str (not "int") to str +# Descomenta la siguiente línea para ver el error: +# print("Tengo " + manzanas + " manzanas") + +print("Si ves este mensaje, significa que todos los errores anteriores están comentados (desactivados).") diff --git a/python/Modulo-01-Fundamentos/09-errores/reto.md b/python/Modulo-01-Fundamentos/09-errores/reto.md new file mode 100644 index 0000000..56b2afa --- /dev/null +++ b/python/Modulo-01-Fundamentos/09-errores/reto.md @@ -0,0 +1,37 @@ +# Reto: El Mecánico de Código 🛠️ + +## Tu Misión + +El sistema de registro de nuestra nave espacial está dañado. El ingeniero anterior escribió el código a las 3:00 AM y dejó varios errores. +Tu trabajo es ejecutar el archivo, leer los mensajes en rojo que te da Python (Tracebacks), buscar la línea del problema, corregirlo y volver a ejecutar hasta que el programa funcione de principio a fin. + +## Pasos + +1. Crea un archivo llamado `reto.py`. +2. Copia y pega el siguiente código **exactamente** como está: + +```python +print("Iniciando diagnóstico del sistema de la nave...) + +combustible_litros = 5000 +distancia_km = 10000 + +# Calculamos el consumo +consumo = distncia_km / combustible_litros + +# Mostramos el resultado +print(f"El consumo es de {consumo} kilómetros por litro.") + +mensaje_final = "Diagnóstico completado. Nivel de éxito: " +porcentaje = 100 + +# Intentamos mostrar el mensaje final +resultado_final = mensaje_final + porcentaje +print(resultado_final) +``` + +3. Ejecuta el archivo en tu terminal (`python reto.py`). +4. **¡Boom!** Fallará. Lee el mensaje, encuentra la línea que te indica Python y arregla el primer error (Pista: tiene que ver con comillas). +5. Vuelve a ejecutar. **¡Boom!** Fallará otra vez. Lee el mensaje (Pista: revisa cómo se escribieron las variables). Arréglalo. +6. Vuelve a ejecutar. **¡Boom!** Último fallo. (Pista: estás mezclando texto con números de forma incorrecta. ¿Recuerdas las f-strings?). Arréglalo. +7. Cuando al ejecutar el programa ya no salga ningún mensaje en rojo y veas todo el texto en pantalla, ¡habrás superado el reto! diff --git a/python/Modulo-01-Fundamentos/09-errores/teoria.md b/python/Modulo-01-Fundamentos/09-errores/teoria.md new file mode 100644 index 0000000..e573a2e --- /dev/null +++ b/python/Modulo-01-Fundamentos/09-errores/teoria.md @@ -0,0 +1,59 @@ +# Teoría: Entendiendo a Python cuando se enoja + +Cuando cometes un error escribiendo código, Python detiene el programa y te muestra un mensaje. A este mensaje se le llama **Traceback** (Rastreo). + +Al principio, un Traceback parece un montón de texto aterrador e incomprensible, pero en realidad es un mapa del tesoro que te dice exactamente dónde está el problema. + +## Anatomía de un Error (Traceback) + +Imagina que ejecutas un archivo y Python te responde esto: + +```text +Traceback (most recent call last): + File "mi_programa.py", line 3, in + print(mi_variable) +NameError: name 'mi_variable' is not defined +``` + +¡No entres en pánico! Léelo de abajo hacia arriba: + +1. **La última línea es la más importante**: `NameError: name 'mi_variable' is not defined`. + * Te dice el **tipo de error** (`NameError`). + * Te da una **descripción** del problema ("el nombre 'mi_variable' no está definido"). +2. **Las líneas de arriba te dicen DÓNDE ocurrió**: + * `File "mi_programa.py", line 3`. ¡Te está diciendo que vayas a la línea 3 de tu archivo! + +## Los 3 Errores Clásicos + +Como principiante (y como experto), te vas a encontrar con estos tres errores miles de veces. + +### 1. SyntaxError (Error de Sintaxis) +**Qué significa:** Escribiste algo mal. Te faltó un paréntesis, una comilla o usaste un símbolo donde no iba. Para Python, esto es como si escribieras "Hola com0 stas" con mala ortografía; simplemente se niega a leerlo. + +**Ejemplo que lo causa:** +```python +print("Hola mundo) # ¡Falta la comilla de cierre! +``` + +### 2. NameError (Error de Nombre) +**Qué significa:** Estás intentando usar una variable que **no existe** (porque nunca la creaste con el símbolo `=`) o escribiste mal su nombre. + +**Ejemplo que lo causa:** +```python +mensaje = "Hola" +print(mensaj) # Escribiste "mensaj" en lugar de "mensaje". Python no sabe qué es "mensaj". +``` + +### 3. TypeError (Error de Tipo) +**Qué significa:** Estás intentando mezclar agua y aceite. Es decir, intentas hacer una operación con dos Tipos de Datos incompatibles (por ejemplo, sumar texto con números). + +**Ejemplo que lo causa:** +```python +texto = "Tengo " +edad = 25 +resultado = texto + edad # ¡Error! No puedes sumar una palabra con un número entero así nomás. +``` +*(Recuerda que para mezclar texto y números aprendimos a usar las f-strings).* + +--- +**Regla de Oro del Programador:** Los errores no significan que seas malo programando, son la forma en que la computadora te guía para arreglar el problema. Lee siempre la última línea del Traceback. diff --git a/python/Modulo-01-Fundamentos/09-errores/test_main.py b/python/Modulo-01-Fundamentos/09-errores/test_main.py new file mode 100644 index 0000000..eac658a --- /dev/null +++ b/python/Modulo-01-Fundamentos/09-errores/test_main.py @@ -0,0 +1,13 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(): + return subprocess.run([sys.executable, RETO], + capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_corre_sin_errores(): + r = run() + assert r.returncode == 0, \ + f"Tu código aún tiene errores de sintaxis o ejecución:\n{r.stderr}" diff --git a/retos/general/11-profiler-de-funciones/soluciones/.gitkeep b/python/Modulo-01-Fundamentos/10-RETO-ficha/.gitkeep similarity index 100% rename from retos/general/11-profiler-de-funciones/soluciones/.gitkeep rename to python/Modulo-01-Fundamentos/10-RETO-ficha/.gitkeep diff --git a/python/Modulo-01-Fundamentos/10-RETO-ficha/README.md b/python/Modulo-01-Fundamentos/10-RETO-ficha/README.md new file mode 100644 index 0000000..101c077 --- /dev/null +++ b/python/Modulo-01-Fundamentos/10-RETO-ficha/README.md @@ -0,0 +1,20 @@ +# Nivel 10: El Jefe Final (Ficha de Personaje) + +¡Has llegado al final del Módulo 1! 🎉 + +Como todo buen videojuego, al final de un nivel hay un jefe. Pero no te preocupes, tienes todas las armas necesarias para vencerlo. En este nivel no aprenderemos ningún símbolo o palabra nueva. En su lugar, vamos a juntar **todo** lo que has aprendido desde el "Hola Mundo" hasta los errores, para crear tu primer programa completo e interactivo. + +## ¿Qué vas a demostrar aquí? +* Que sabes guardar información en variables (`=`). +* Que puedes pedirle datos al usuario (`input()`). +* Que sabes transformar los datos al tipo correcto (`int()`, `float()`). +* Que puedes hacer operaciones matemáticas básicas (`+`, `-`, `*`, `/`). +* Que sabes mostrar mensajes personalizados y bonitos mezclando texto y variables (`f-strings`). +* Que documentas tu código para otros humanos (`#`). + +## Archivos en este nivel: +1. **`teoria.md`**: Un repaso rápido de tu arsenal. +2. **`ejemplo.py`**: Un pequeño proyecto completo para que te inspires. +3. **`reto.md`**: Tu prueba final. ¡El Gran Reto de la Ficha! + +¡Prepárate para construir algo real! diff --git a/python/Modulo-01-Fundamentos/10-RETO-ficha/ejemplo.py b/python/Modulo-01-Fundamentos/10-RETO-ficha/ejemplo.py new file mode 100644 index 0000000..8d71c7f --- /dev/null +++ b/python/Modulo-01-Fundamentos/10-RETO-ficha/ejemplo.py @@ -0,0 +1,30 @@ +# ========================================== +# NIVEL 10: PROYECTO DE EJEMPLO +# ========================================== + +print("--- 🍔 Creador de Recetas 🍔 ---") + +# 1. Pedimos datos al usuario +nombre_plato = input("¿Qué plato vas a cocinar?: ") +ingrediente_principal = input("¿Cuál es el ingrediente principal?: ") +precio_ingrediente_texto = input("¿Cuánto cuesta este ingrediente? (ej. 15.50): ") +porciones_texto = input("¿Para cuántas personas alcanza?: ") + +# 2. Transformamos los datos (casteo) para poder hacer cálculos +precio_ingrediente = float(precio_ingrediente_texto) +porciones = int(porciones_texto) + +# 3. Hacemos cálculos (Matemáticas) +# Calculamos el costo por persona y un impuesto imaginario del 10% +costo_por_persona = precio_ingrediente / porciones +costo_con_impuesto = costo_por_persona + (costo_por_persona * 0.10) + +# 4. Mostramos el resultado final (f-strings) +print("\n--- 📋 Tu Receta 📋 ---") +print(f"Plato: {nombre_plato}") +print(f"Estrella del plato: {ingrediente_principal}") +print(f"Rinde para: {porciones} valientes.") +print(f"Costo por persona (con impuestos): ${costo_con_impuesto}") + +# Fin del programa +print("¡A cocinar se ha dicho!") diff --git a/python/Modulo-01-Fundamentos/10-RETO-ficha/reto.md b/python/Modulo-01-Fundamentos/10-RETO-ficha/reto.md new file mode 100644 index 0000000..2742bb7 --- /dev/null +++ b/python/Modulo-01-Fundamentos/10-RETO-ficha/reto.md @@ -0,0 +1,41 @@ +# El Gran Reto: Creador de Fichas RPG 🐉 + +## Tu Misión + +Vas a crear un generador automático de "Fichas de Personaje" para un juego de rol. El programa debe hacerle preguntas al jugador, calcular algunas estadísticas y luego imprimir una tarjeta de presentación espectacular. + +## Pasos obligatorios + +Crea un archivo llamado `ficha_rpg.py` y asegúrate de que tu programa haga exactamente lo siguiente: + +1. **Imprime** un título de bienvenida llamativo (ej. "--- GENERADOR DE HÉROES ---"). +2. **Pide al usuario (con `input`)** los siguientes datos: + * El nombre de su personaje. + * La clase de su personaje (ej. Mago, Guerrero, Ladrón). + * Su nivel actual (un número entero). + * Su dinero inicial en monedas de oro (un número que puede tener decimales). +3. **Transforma** (casteo) el nivel a `int` y el dinero a `float`. +4. **Haz algunos cálculos mágicos**: + * Crea una variable llamada `vida_maxima`. Se calcula multiplicando el `nivel` por 15. + * Crea una variable llamada `poder_magico`. Se calcula dividiendo el `nivel` entre 2. + * El personaje debe pagar una "Tasa de Inscripción al Gremio" de 10.5 monedas. Réstale eso a su dinero inicial y guárdalo en una variable llamada `dinero_restante`. +5. **Usa comentarios (`#`)** en tu código para explicar qué estás haciendo en cada sección (pedir datos, calcular stats, imprimir ficha). +6. **Imprime la Ficha Final** usando `f-strings`. Debe verse bonita y ordenada, algo así: + +```text +==================================== + FICHA DE PERSONAJE +==================================== +Nombre: [Su nombre] +Clase: [Su clase] +Nivel: [Su nivel] +------------------------------------ +Estadísticas: +Vida Máxima: [Su vida] +Poder Mágico: [Su poder] +Oro Restante: [Su dinero restante] +==================================== +``` + +## ¿Cómo saber si ganaste? +Ejecuta tu archivo. Si te hace todas las preguntas, no explota con errores rojos, calcula correctamente la vida, el poder y el oro, y muestra la tarjeta bonita al final... **¡FELICIDADES! HAS COMPLETADO EL MÓDULO 1 DE PYTHON.** diff --git a/python/Modulo-01-Fundamentos/10-RETO-ficha/teoria.md b/python/Modulo-01-Fundamentos/10-RETO-ficha/teoria.md new file mode 100644 index 0000000..965bdc2 --- /dev/null +++ b/python/Modulo-01-Fundamentos/10-RETO-ficha/teoria.md @@ -0,0 +1,55 @@ +# Teoría: El Arsenal del Fundador + +Llegaste al Nivel 10 con un cinturón de herramientas poderoso. Antes de enfrentarte al reto final, repasemos cómo se usa cada herramienta. + +### 1. Hablar con la pantalla (`print`) +Tu forma de comunicarte con el mundo exterior. +```python +print("Hola, soy un programa") +``` + +### 2. Cajas de almacenamiento (Variables y `=`) +El símbolo `=` no es igualdad, significa "guarda lo de la derecha en la caja de la izquierda". +```python +vidas = 3 +``` + +### 3. Escuchar al usuario (`input`) +Pones en pausa el programa y esperas a que el humano escriba algo. **Siempre** te entrega texto. +```python +nombre = input("¿Cómo te llamas? ") +``` + +### 4. Transformar datos (Casteo) +Como `input` siempre da texto, si quieres hacer matemáticas, debes transformar la caja. +```python +edad_texto = input("Tu edad: ") +edad_numero = int(edad_texto) # Transforma el texto a entero +altura = float("1.75") # Transforma a decimal +``` + +### 5. Matemáticas básicas +Puedes sumar (`+`), restar (`-`), multiplicar (`*`) y dividir (`/`). +```python +dano_total = 15 * 3 +``` + +### 6. Pegamento mágico (`f-strings`) +La mejor forma de mezclar palabras normales con las cajas (variables) sin causar errores de tipo. Pones una `f` al inicio y llaves `{}` alrededor de la caja. +```python +print(f"Tienes {vidas} vidas restantes, {nombre}.") +``` + +### 7. Notas secretas (`#`) +El símbolo `#` le dice a Python que ignore el resto de la línea. Es para ti y tu equipo. +```python +# Este código calcula el daño +``` + +### 8. Interpretar explosiones (Errores) +Si ves rojo, no corras. Lee la última línea. +* `SyntaxError`: Escribiste mal un símbolo (te falta un paréntesis o comilla). +* `NameError`: Usaste una caja que no existe o la escribiste mal. +* `TypeError`: Mezclaste tipos incompatibles (como sumar texto con un número sin usar f-strings). + +Con esto estás listo para construir programas completos. ¡Pasa al ejemplo y luego al reto! diff --git a/python/Modulo-01-Fundamentos/10-RETO-ficha/test_main.py b/python/Modulo-01-Fundamentos/10-RETO-ficha/test_main.py new file mode 100644 index 0000000..c7bef2c --- /dev/null +++ b/python/Modulo-01-Fundamentos/10-RETO-ficha/test_main.py @@ -0,0 +1,16 @@ +"""test_main.py — Evaluador automático del reto.""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def run(stdin_data): + return subprocess.run([sys.executable, RETO], + input=stdin_data, capture_output=True, text=True, timeout=15) +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_ficha_completa(): + r = run("Gandalf\n10\n500\n") + assert r.returncode == 0, f"Error:\n{r.stderr}" + assert "Gandalf" in r.stdout, "La ficha debe mostrar el nombre del héroe" + assert "150" in r.stdout, "El poder debe ser nivel × 15 = 150" + assert "489" in r.stdout or "490" in r.stdout, \ + "El dinero debe ser 500 - 10.5 ≈ 489.5" diff --git a/retos/general/12-validador-de-emails/soluciones/.gitkeep b/python/Modulo-02-Flujo/.gitkeep similarity index 100% rename from retos/general/12-validador-de-emails/soluciones/.gitkeep rename to python/Modulo-02-Flujo/.gitkeep diff --git a/retos/general/13-lector-archivos-gigantes/soluciones/.gitkeep b/python/Modulo-02-Flujo/11-operadores-logicos/.gitkeep similarity index 100% rename from retos/general/13-lector-archivos-gigantes/soluciones/.gitkeep rename to python/Modulo-02-Flujo/11-operadores-logicos/.gitkeep diff --git a/python/Modulo-02-Flujo/11-operadores-logicos/README.md b/python/Modulo-02-Flujo/11-operadores-logicos/README.md new file mode 100644 index 0000000..f345185 --- /dev/null +++ b/python/Modulo-02-Flujo/11-operadores-logicos/README.md @@ -0,0 +1,10 @@ +# Nivel 11: Operadores Lógicos ⚖️ + +En la vida real, casi nunca tomamos decisiones con una sola regla. Por ejemplo, para que te dejen salir a una fiesta no solo necesitas "haber terminado la tarea", también necesitas "limpiar tu cuarto". Son dos condiciones que deben ser verdad al mismo tiempo. + +Para que nuestros programas dejen de ser básicos y puedan evaluar escenarios de la vida real, usamos los **Operadores Lógicos**. Son el equivalente a las palabras "y", "o", y "no" que usamos todos los días. + +## Ruta de aprendizaje +1. **teoria.md**: Aprenderás cómo combinar verdades (`True`) y mentiras (`False`) usando `and`, `or` y `not`. +2. **ejemplo.py**: Programaremos a los estrictos guardianes de una sala de cine. +3. **reto.md**: Construirás el sistema de acceso inteligente para una montaña rusa. diff --git a/python/Modulo-02-Flujo/11-operadores-logicos/ejemplo.py b/python/Modulo-02-Flujo/11-operadores-logicos/ejemplo.py new file mode 100644 index 0000000..a098183 --- /dev/null +++ b/python/Modulo-02-Flujo/11-operadores-logicos/ejemplo.py @@ -0,0 +1,45 @@ +# ejemplo.py + +print("--- El cine para adultos ---") + +# Vamos a evaluar si una persona puede entrar a ver una película de terror. +# Necesita dos cosas: tener 18 años o más, Y traer su boleto físico. + +edad = 19 +tiene_boleto = True + +# Guardamos en una variable el resultado de evaluar a nuestro guardia estricto (and). +# Python lee la izquierda: "edad >= 18" (que es True). +# Luego lee la derecha: "tiene_boleto" (que es True). +# Como es True and True... el resultado final que se guarda es True. +puede_pasar = edad >= 18 and tiene_boleto + +print(f"¿La persona puede entrar al cine?: {puede_pasar}") + + +print("--- Promoción de palomitas gratis ---") + +# Hoy hay promoción. Para ganar palomitas necesitas llevar camisa roja O decir la palabra secreta. + +color_camisa = "verde" +sabe_palabra_secreta = True + +# Usamos a nuestro guardia relajado (or). +# Python lee la izquierda: "color_camisa == 'roja'" (False, porque es verde). +# Luego lee la derecha: "sabe_palabra_secreta" (True). +# Como el 'or' solo necesita un True para ser feliz, el resultado final es True. +gana_palomitas = color_camisa == "roja" or sabe_palabra_secreta + +print(f"¿Gana palomitas gratis?: {gana_palomitas}") + + +print("--- El letrero de la tienda ---") + +# Usaremos 'not' para llevar la contraria a un estado. +tienda_abierta = True + +# Si la tienda está abierta (True), queremos saber el estado de "cerrado". +# Usamos 'not' para voltear la verdad. Si es True, lo convierte a False. +esta_cerrada = not tienda_abierta + +print(f"¿La tienda está cerrada ahora mismo?: {esta_cerrada}") diff --git a/python/Modulo-02-Flujo/11-operadores-logicos/reto.md b/python/Modulo-02-Flujo/11-operadores-logicos/reto.md new file mode 100644 index 0000000..b6d7c01 --- /dev/null +++ b/python/Modulo-02-Flujo/11-operadores-logicos/reto.md @@ -0,0 +1,41 @@ +# Reto 11: La Montaña Rusa "El Dragón" 🐉 + +El parque de diversiones local te contrató para automatizar el acceso a su montaña rusa más extrema. Tienen reglas de seguridad muy estrictas sobre quién puede subir y quién recibe descuentos. Tu trabajo es programar las variables que deciden si el visitante entra o no. + +## Instrucciones + +1. Crea variables iniciales para un visitante imaginario (asígnales valores tú mismo): + * Su altura en centímetros (un número entero, ej. `150`). + * Su edad (un número entero, ej. `13`). + * Si es un residente local (un valor booleano: `True` o `False`). + +2. **Regla de acceso (usa `and`)**: + Por seguridad, para subir a la montaña rusa la persona debe medir más de 140 centímetros **Y** tener 12 años o más. + Crea una variable llamada `puede_subir` que evalúe las variables anteriores y guarde el resultado (`True` o `False`). + +3. **Regla de descuento (usa `or`)**: + La persona recibe descuento en la tienda de recuerdos si es menor de 15 años **O** si es residente local (`True`). + Crea una variable llamada `tiene_descuento` que evalúe las variables anteriores y guarde el resultado. + +4. Muestra en pantalla los resultados finales utilizando `f-strings`. + +### Conceptos permitidos +- Variables (asignación con `=`). +- Tipos de datos básicos (`int`, `bool`). +- Operadores de comparación (`>`, `<`, `>=`). +- Operadores lógicos (`and`, `or`, `not`). +- Imprimir en pantalla con `print()` y variables inyectadas con `f-strings`. + +### Conceptos prohibidos +- Condicionales (`if`, `else`, `elif`). No dejes que la computadora tome la ruta aún, solo muéstranos el `True` o `False` en bruto. +- Funciones `def`. +- Listas `[]` o diccionarios `{}`. + +### Resultado esperado en terminal +Si configuras a tu visitante ficticio con: `altura = 150`, `edad = 13` y `residente = False`, al correr tu código la terminal debe verse exactamente así: + +```text +--- Control de Acceso: El Dragón --- +¿El visitante puede subir a la montaña rusa?: True +¿El visitante tiene derecho a descuento?: True +``` diff --git a/python/Modulo-02-Flujo/11-operadores-logicos/teoria.md b/python/Modulo-02-Flujo/11-operadores-logicos/teoria.md new file mode 100644 index 0000000..b75bb54 --- /dev/null +++ b/python/Modulo-02-Flujo/11-operadores-logicos/teoria.md @@ -0,0 +1,53 @@ +# Teoría: El arte de combinar condiciones + +Hasta hoy sabemos comparar datos para obtener `True` (Verdad) o `False` (Mentira), por ejemplo: `edad >= 18`. Pero, ¿qué hacemos si necesitamos que se cumplan múltiples cosas a la vez? + +Aquí entran los tres operadores lógicos de Python. Estas palabras mágicas siempre conectan o modifican valores que son `True` o `False`. + +### 1. El guardia estricto: `and` (Y) +El operador `and` une dos condiciones. Para que el resultado final sea `True`, **AMBAS** condiciones deben ser verdad. Si una sola falla, todo se derrumba y el resultado es `False`. + +* **Analogía**: ¿Me prestas tu celular? Solo si "Lavaste los platos" `and` "Hiciste la tarea". Si fallas en una, la respuesta es no. + +### 2. El guardia relajado: `or` (O) +El operador `or` es súper comprensivo. Con que **AL MENOS UNA** de las condiciones sea verdad, él dará un `True`. Solo dará `False` si absolutamente todas las condiciones son mentira. + +* **Analogía**: Para pagar en el supermercado, puedes usar "Efectivo" `or` "Tarjeta". Si traes cualquiera de los dos, puedes pagar. Solo si no traes ninguno, no puedes comprar. + +### 3. El rebelde contreras: `not` (NO) +El operador `not` no une dos cosas, solo necesita una. Lo único que hace es voltear la moneda. Si algo era `True`, lo vuelve `False`. Si era `False`, lo vuelve `True`. + +* **Analogía**: Hoy es domingo (día de descanso). Si pregunto `not` (no es) día de descanso, la respuesta será "Falso". + +--- + +## Anatomía (Sintaxis) + +```python +condicion_1 and condicion_2 +``` +* `condicion_1`: Algo que resulta en `True` o `False` (ej. `edad > 18`). +* `and`: La palabra mágica reservada por Python, siempre en minúsculas y rodeada de espacios. Une lo de la izquierda con lo de la derecha. +* `condicion_2`: La segunda regla a evaluar. + +```python +not condicion +``` +* `not`: Va *antes* de lo que queremos voltear. + +--- + +## ¿Qué pasa si me equivoco? + +**El error del "lenguaje humano"** +A veces intentamos hablarle a Python como le hablamos a un amigo: +`color == "rojo" or "azul"` + +Si escribes esto, la computadora se va a confundir horriblemente. Para Python, el `or` corta la frase en dos universos aislados. +Universo 1: `color == "rojo"` +Universo 2: `"azul"` +Python creerá que la palabra `"azul"` por sí sola es un `True` gigante porque es texto que existe. + +**La forma correcta (y obligatoria para ti):** +Debes hacer la comparación completa en cada lado del `or`: +`color == "rojo" or color == "azul"` diff --git a/python/Modulo-02-Flujo/11-operadores-logicos/test_main.py b/python/Modulo-02-Flujo/11-operadores-logicos/test_main.py new file mode 100644 index 0000000..2e85614 --- /dev/null +++ b/python/Modulo-02-Flujo/11-operadores-logicos/test_main.py @@ -0,0 +1,14 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_output(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + if "11-" in RETO or "12-" in RETO or "16-" in RETO: + assert "True" in r.stdout or "False" in r.stdout + else: + assert len(r.stdout.strip()) > 0 diff --git a/retos/general/14-cli-del-clima/soluciones/.gitkeep b/python/Modulo-02-Flujo/12-comparadores/.gitkeep similarity index 100% rename from retos/general/14-cli-del-clima/soluciones/.gitkeep rename to python/Modulo-02-Flujo/12-comparadores/.gitkeep diff --git a/python/Modulo-02-Flujo/12-comparadores/README.md b/python/Modulo-02-Flujo/12-comparadores/README.md new file mode 100644 index 0000000..a5e7715 --- /dev/null +++ b/python/Modulo-02-Flujo/12-comparadores/README.md @@ -0,0 +1,10 @@ +# Nivel 12: Comparadores (Detectives de Datos) 🕵️‍♂️ + +En el mundo real, pasamos el día entero comparando cosas: "¿Tengo suficiente dinero para esto?", "¿Esta talla es más grande que la mía?", "¿Mi contraseña es exactamente igual a la que registré?". + +Para que nuestros programas puedan tomar decisiones inteligentes, primero deben aprender a **comparar**. Los comparadores son como pequeños detectives que observan dos datos y siempre nos responden con la verdad (`True`) o una mentira (`False`). + +## Ruta de aprendizaje +1. **teoria.md**: Aprenderás la diferencia crucial entre `=` (guardar) y `==` (comparar), y conocerás a los 6 detectives de Python. +2. **ejemplo.py**: Usaremos a nuestros detectives para vigilar el acceso a un club secreto. +3. **reto.md**: Crearás un sistema de escaneo de equipaje para un aeropuerto. diff --git a/python/Modulo-02-Flujo/12-comparadores/ejemplo.py b/python/Modulo-02-Flujo/12-comparadores/ejemplo.py new file mode 100644 index 0000000..05bf78c --- /dev/null +++ b/python/Modulo-02-Flujo/12-comparadores/ejemplo.py @@ -0,0 +1,42 @@ +# ejemplo.py + +print("--- 1. El Detector de Gemelos (== y !=) ---") + +# Vamos a comparar textos (strings). Las mayúsculas importan mucho. +contrasena_guardada = "Secreto123" +contrasena_ingresada = "secreto123" # Nota la 's' minúscula + +# El detective '==' revisa si son exactamente idénticas letra por letra. +son_iguales = contrasena_guardada == contrasena_ingresada +print(f"¿Las contraseñas son idénticas?: {son_iguales}") + +# El detective '!=' revisa si son diferentes. +son_diferentes = contrasena_guardada != contrasena_ingresada +print(f"¿Hubo un error de escritura (son diferentes)?: {son_diferentes}") + + +print("\n--- 2. El Medidor de Altura (> y <) ---") + +# Para subir al juego mecánico "El Tornado" debes medir más de 150 cm. +altura_visitante = 145 + +# El detective '>' verifica quién gana en tamaño. +puede_subir = altura_visitante > 150 +print(f"Altura del visitante: {altura_visitante}cm. ¿Puede subir?: {puede_subir}") + + +print("\n--- 3. El Límite de Edad (>= y <=) ---") + +# Para tener licencia de conducir debes tener 18 años o más. +edad_persona = 18 + +# Si usamos '>' puro, diría que 18 no es mayor que 18 (False). +# Por eso usamos '>=' (mayor O IGUAL). +tiene_edad_minima = edad_persona >= 18 +print(f"Edad de la persona: {edad_persona}. ¿Alcanza la edad mínima?: {tiene_edad_minima}") + +# El peso máximo del elevador es 300 kg. +peso_actual = 300 +# El detective '<=' revisa que no nos pasemos del límite. +es_seguro_elevar = peso_actual <= 300 +print(f"Peso actual: {peso_actual}kg. ¿Es seguro usar el elevador?: {es_seguro_elevar}") diff --git a/python/Modulo-02-Flujo/12-comparadores/reto.md b/python/Modulo-02-Flujo/12-comparadores/reto.md new file mode 100644 index 0000000..a29c64a --- /dev/null +++ b/python/Modulo-02-Flujo/12-comparadores/reto.md @@ -0,0 +1,45 @@ +# Reto 12: Escáner de Aeropuerto ✈️🧳 + +Eres el programador encargado del escáner automático de equipaje de una aerolínea. Tienes que crear las variables que comparan el equipaje del pasajero contra las reglas estrictas de la aerolínea, y reportar si todo está en orden o hay problemas. + +## Instrucciones + +1. Crea tres variables para el equipaje de un pasajero (inviéntate los datos, por ejemplo): + * `peso_maleta` (un número, ej. `23.5`). + * `etiqueta_destino` (un texto, ej. `"PARIS"`). + * `cantidad_liquidos` (un entero, ej. `150`). + +2. **Regla de peso (usa `<=`)**: + La maleta no debe pesar más de 25 kilos (es decir, debe ser menor o igual a 25). + Crea una variable llamada `peso_permitido` que guarde el resultado de comparar el `peso_maleta` con `25`. + +3. **Regla de destino (usa `==`)**: + El vuelo actual va hacia `"LONDRES"`. + Crea una variable llamada `destino_correcto` que compare si la `etiqueta_destino` del pasajero es exactamente igual a `"LONDRES"`. + +4. **Regla de líquidos (usa `>`)**: + Los pasajeros tienen prohibido llevar frascos de líquidos que superen los 100 mililitros. + Crea una variable llamada `excede_liquidos` que compare si la `cantidad_liquidos` es estrictamente mayor a `100`. + +5. Muestra un reporte en la terminal utilizando `f-strings`. + +### Conceptos permitidos +- Variables (asignación con `=`). +- Tipos de datos (`int`, `float`, `str`, `bool`). +- Los 6 operadores de comparación (`==`, `!=`, `>`, `<`, `>=`, `<=`). +- Imprimir en pantalla con `print()` y `f-strings`. + +### Conceptos prohibidos +- Operadores lógicos (`and`, `or`, `not`). Vamos a probar solo los comparadores puros en este nivel. +- Condicionales (`if`, `else`, `elif`). +- Funciones `def`. + +### Resultado esperado en terminal +Si usaste `peso_maleta = 23.5`, `etiqueta_destino = "PARIS"` y `cantidad_liquidos = 150`, tu terminal debe verse exactamente así: + +```text +--- REPORTE DE ESCÁNER DE EQUIPAJE --- +¿El peso de la maleta está permitido?: True +¿La maleta va al destino correcto (LONDRES)?: False +¿El pasajero excede el límite de líquidos?: True +``` diff --git a/python/Modulo-02-Flujo/12-comparadores/teoria.md b/python/Modulo-02-Flujo/12-comparadores/teoria.md new file mode 100644 index 0000000..fcf81cd --- /dev/null +++ b/python/Modulo-02-Flujo/12-comparadores/teoria.md @@ -0,0 +1,52 @@ +# Teoría: La balanza de Python + +Imagina que tienes una balanza antigua de dos platos. Pones un dato en el plato izquierdo y otro en el derecho. Los **Operadores de Comparación** (o comparadores) son los símbolos que le dicen a Python cómo mirar esa balanza. + +La respuesta de Python al usar estos símbolos **siempre** será un valor Booleano: `True` (Verdad) si la comparación es correcta, o `False` (Mentira) si es incorrecta. + +### El error más trágico del mundo: `=` vs `==` +Antes de ver la lista, debemos aclarar la confusión más grande de todo programador novato. +En matemáticas, usamos `=` para decir que dos cosas son iguales. **En Python NO es así.** + +* **Un solo igual (`=`)**: Significa "GUARDAR". Toma lo de la derecha y mételo en la caja de la izquierda. + Ejemplo: `edad = 18` (Guarda el 18 en la caja edad). +* **Doble igual (`==`)**: Significa "COMPARAR". Le pregunta a Python: "¿Lo de la izquierda es exactamente igual a lo de la derecha?". + Ejemplo: `edad == 18` (¿La caja edad tiene un 18 adentro? Responderá `True` o `False`). + +### Los 6 Detectives (Símbolos de comparación) + +1. **Igualdad (`==`)**: ¿Son idénticos? + `5 == 5` -> `True` +2. **Desigualdad (`!=`)**: ¿Son diferentes? (El signo de exclamación `!` significa "no"). + `"rojo" != "azul"` -> `True` +3. **Mayor que (`>`)**: ¿El de la izquierda es más grande? + `10 > 5` -> `True` +4. **Menor que (`<`)**: ¿El de la izquierda es más pequeño? + `2 < 1` -> `False` +5. **Mayor o igual (`>=`)**: ¿Es más grande o al menos es idéntico? + `18 >= 18` -> `True` +6. **Menor o igual (`<=`)**: ¿Es más pequeño o al menos es idéntico? + `10 <= 20` -> `True` + +--- + +## Anatomía (Sintaxis) + +```python +dato_izquierdo == dato_derecho +``` +* `dato_izquierdo`: Puede ser una variable (ej. `precio`) o un valor directo (ej. `100`). +* `==`: El símbolo del detective. (Puede ser cualquiera de los 6: `>`, `<`, `>=`, `<=`, `!=`, `==`). +* `dato_derecho`: El otro valor con el que estamos comparando. + +--- + +## ¿Qué pasa si me equivoco? + +**El temido "SyntaxError" por usar `=` en lugar de `==`** +Si intentas comparar dos cosas así: +`mi_numero = 10 = 10` o a veces en condicionales futuros pones `if edad = 18:` + +Python te lanzará un error que dice `SyntaxError: invalid syntax` o `cannot assign to literal`. +¿Por qué? Porque Python intentará agarrar el número de la derecha y guardarlo dentro del número de la izquierda. Como un número no es una caja (variable), la computadora se rinde y "explota". +**Solución:** Recuerda que para comparar, los iguales siempre van en pareja: `==`. diff --git a/python/Modulo-02-Flujo/12-comparadores/test_main.py b/python/Modulo-02-Flujo/12-comparadores/test_main.py new file mode 100644 index 0000000..2e85614 --- /dev/null +++ b/python/Modulo-02-Flujo/12-comparadores/test_main.py @@ -0,0 +1,14 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_output(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + if "11-" in RETO or "12-" in RETO or "16-" in RETO: + assert "True" in r.stdout or "False" in r.stdout + else: + assert len(r.stdout.strip()) > 0 diff --git a/retos/general/15-herramienta-de-backup/soluciones/.gitkeep b/python/Modulo-02-Flujo/13-if/.gitkeep similarity index 100% rename from retos/general/15-herramienta-de-backup/soluciones/.gitkeep rename to python/Modulo-02-Flujo/13-if/.gitkeep diff --git a/python/Modulo-02-Flujo/13-if/README.md b/python/Modulo-02-Flujo/13-if/README.md new file mode 100644 index 0000000..3262359 --- /dev/null +++ b/python/Modulo-02-Flujo/13-if/README.md @@ -0,0 +1,11 @@ +# Nivel 13: Tomando Decisiones (`if`) 🚦 + +Hasta hoy, nuestros programas eran como un tren en una sola vía: ejecutaban todo el código de arriba hacia abajo sin detenerse. + +Pero en la vida real, tomamos rutas distintas dependiendo de lo que pase. "Si llueve, me pongo abrigo", "Si tengo dinero, me compro un helado". +Ha llegado el momento de darle cerebro a nuestra computadora. Aprenderemos a hacer que el código elija si debe ejecutar una acción o simplemente ignorarla. + +## Ruta de aprendizaje +1. **teoria.md**: Conocerás la palabra mágica `if`, los dos puntos `:` y la regla más estricta de Python: la sangría (indentación). +2. **ejemplo.py**: Programaremos un sistema que saluda especialmente a los cumpleañeros. +3. **reto.md**: Serás el portero de un antro exclusivo. diff --git a/python/Modulo-02-Flujo/13-if/ejemplo.py b/python/Modulo-02-Flujo/13-if/ejemplo.py new file mode 100644 index 0000000..3fbd18b --- /dev/null +++ b/python/Modulo-02-Flujo/13-if/ejemplo.py @@ -0,0 +1,31 @@ +# ejemplo.py + +print("--- 1. El Cajero del Banco ---") + +# Vamos a simular un retiro de efectivo. +saldo_bancario = 500 +cantidad_a_retirar = 100 + +# Le preguntamos a Python: "Si el saldo es mayor o igual a lo que quiero retirar, entonces..." +if saldo_bancario >= cantidad_a_retirar: + # Como la condición es True (500 >= 100), Python entrará aquí (nota los espacios a la izquierda) + saldo_bancario = saldo_bancario - cantidad_a_retirar + print(f"Retiro exitoso. Te quedan ${saldo_bancario}") + +# Esta línea ya no tiene sangría. Se ejecuta SIEMPRE, sin importar lo que pasó arriba. +print("Gracias por usar el banco.") + + +print("\n--- 2. La Promoción Secreta ---") + +# Un sistema que da un regalo si el usuario sabe la clave secreta +palabra_ingresada = "python_es_genial" + +if palabra_ingresada == "python_es_genial": + # Entramos a la habitación secreta del 'if' + print("¡Acceso concedido!") + print("Acabas de ganar un cupón de 50% de descuento.") + +# Si la palabra_ingresada fuera diferente (ej. "hola"), las dos líneas de arriba +# serían invisibles para Python, saltaría directamente aquí: +print("Fin del programa de promociones.") diff --git a/python/Modulo-02-Flujo/13-if/reto.md b/python/Modulo-02-Flujo/13-if/reto.md new file mode 100644 index 0000000..9dc6f6f --- /dev/null +++ b/python/Modulo-02-Flujo/13-if/reto.md @@ -0,0 +1,49 @@ +# Reto 13: El Portero del Antro VIP 🕶️🕺 + +Acabas de conseguir trabajo como el cadenero (portero) del club más exclusivo de la ciudad. +Tienes un escáner en tu puerta, y tu deber es dejar pasar a la gente **SOLO** si cumplen con los requisitos. A los que no, simplemente los ignoras (no hemos aprendido a decirles que no aún, lo haremos luego). + +## Instrucciones + +1. Crea dos variables para tu cliente en la fila: + * `edad_cliente` (un número entero, ej. `20`). + * `ropa_elegante` (un booleano `True` o `False`). + +2. Crea tu condición `if`. El cliente solo entra si tiene 18 años o más, **y** además viene con `ropa_elegante` (`True`). + * Recuerda usar el comparador `>=` y el operador lógico `and` que vimos en niveles anteriores. + * No olvides los dos puntos `:` al final. + +3. Dentro del `if` (con sangría / 4 espacios hacia la derecha), imprime dos mensajes: + * `"¡Bienvenido al club VIP!"` + * `"Pasa a la zona de bebidas."` + +4. Fuera del `if` (sin sangría, pegado a la izquierda), imprime siempre: + * `"Siguiente en la fila, por favor..."` + +### Conceptos permitidos +- Variables (asignación con `=`). +- Tipos de datos (`int`, `bool`). +- Operadores de comparación (`>=`, `==`). +- Operador lógico (`and`). +- La estructura `if` y la indentación. +- `print()`. + +### Conceptos prohibidos +- `else` o `elif`. Si el cliente no cumple, simplemente se ignora y el código salta al "Siguiente en la fila...". +- Ciclos (`for`, `while`). +- Funciones `def`. + +### Resultado esperado en terminal +Si configuras `edad_cliente = 20` y `ropa_elegante = True`, el resultado debe ser: + +```text +¡Bienvenido al club VIP! +Pasa a la zona de bebidas. +Siguiente en la fila, por favor... +``` + +Si cambias los datos a `edad_cliente = 17` o `ropa_elegante = False`, el resultado debe ser **solamente**: + +```text +Siguiente en la fila, por favor... +``` diff --git a/python/Modulo-02-Flujo/13-if/teoria.md b/python/Modulo-02-Flujo/13-if/teoria.md new file mode 100644 index 0000000..c7ea77b --- /dev/null +++ b/python/Modulo-02-Flujo/13-if/teoria.md @@ -0,0 +1,45 @@ +# Teoría: El guardavías de Python (`if`) + +Para hacer que Python tome una decisión, usamos la palabra reservada `if` (que en inglés significa "si...", de condición, no de afirmación). + +Piensa en `if` como un guardia frente a una puerta. El guardia te hace una pregunta que se responde con `True` o `False`. +* Si la respuesta es `True`, la puerta se abre y entras a hacer lo que hay adentro. +* Si la respuesta es `False`, la puerta se queda cerrada y te saltas ese cuarto por completo. + +### Los dos puntos (`:`) +En español, cuando contamos una historia solemos decir: "Si llueve, entonces...". +En Python, los dos puntos `:` significan exactamente ese "entonces". Le dicen a la computadora que la condición terminó y viene la consecuencia. + +### La regla de oro: La Indentación (Sangría) +En Python, ¿cómo sabe la computadora qué código pertenece adentro de la puerta del `if` y qué código va después? +¡Con espacios! Todo lo que esté "dentro" del `if` **debe estar empujado hacia la derecha**. +A esto se le llama **indentación**. Normalmente usamos la tecla `Tab` o 4 espacios. En cuanto dejas de empujar el código a la derecha, Python sabe que ya saliste del `if`. + +--- + +## Anatomía (Sintaxis) + +```python +if condicion: + print("Esto solo pasa si la condición es True") + print("Esto también") +print("Esto pasa siempre, porque ya no tiene sangría") +``` + +* `if`: La palabra mágica, siempre en minúsculas. +* `condicion`: Lo que evaluamos (ej. `edad >= 18`). Debe dar como resultado `True` o `False`. +* `:` : Los dos puntos. Obligatorios. Significan "entonces...". +* ` `: La sangría (4 espacios). Le dice a Python que esa línea es la consecuencia del `if`. + +--- + +## ¿Qué pasa si me equivoco? + +**1. El infame "IndentationError"** +Si olvidas poner la sangría, o pones 3 espacios en una línea y 4 en otra, Python se detendrá en seco y te lanzará un `IndentationError`. +* ¿Por qué? Python es un obsesivo del orden. Si no alineas bien el código, no sabe a quién le pertenece. +* **Solución:** Borra los espacios y presiona la tecla `Tab` una vez. Sé consistente. + +**2. Olvidar los dos puntos (`SyntaxError: invalid syntax`)** +Si escribes `if edad >= 18` y le das Enter sin poner los `:`, Python llorará porque no sabe dónde termina la pregunta. +* **Solución:** Pon siempre `:` al final del renglón del `if`. diff --git a/python/Modulo-02-Flujo/13-if/test_main.py b/python/Modulo-02-Flujo/13-if/test_main.py new file mode 100644 index 0000000..2e85614 --- /dev/null +++ b/python/Modulo-02-Flujo/13-if/test_main.py @@ -0,0 +1,14 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_output(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + if "11-" in RETO or "12-" in RETO or "16-" in RETO: + assert "True" in r.stdout or "False" in r.stdout + else: + assert len(r.stdout.strip()) > 0 diff --git a/retos/general/16-validador-de-edad/soluciones/.gitkeep b/python/Modulo-02-Flujo/14-else-elif/.gitkeep similarity index 100% rename from retos/general/16-validador-de-edad/soluciones/.gitkeep rename to python/Modulo-02-Flujo/14-else-elif/.gitkeep diff --git a/python/Modulo-02-Flujo/14-else-elif/README.md b/python/Modulo-02-Flujo/14-else-elif/README.md new file mode 100644 index 0000000..3bad0d1 --- /dev/null +++ b/python/Modulo-02-Flujo/14-else-elif/README.md @@ -0,0 +1,10 @@ +# Nivel 14: Plan B y Plan C (`else` y `elif`) 🔀 + +En el nivel anterior aprendimos a usar `if` para tomar una decisión. Pero, ¿qué pasa si la puerta está cerrada? Antes, simplemente nos íbamos sin hacer nada. En la vida real siempre tenemos un "Plan B": "Si hay pizza, compro pizza. *Si no*, compro hamburguesa". + +En este nivel aprenderemos a darle a la computadora múltiples caminos para elegir usando `else` (Plan B) y `elif` (Planes intermedios). + +## Ruta de aprendizaje +1. **teoria.md**: Entenderás cómo funcionan `else` y `elif` como complementos del `if`. +2. **ejemplo.py**: Programaremos un sistema de semáforo inteligente. +3. **reto.md**: Serás el evaluador de un examen escolar que da calificaciones con letras. diff --git a/python/Modulo-02-Flujo/14-else-elif/ejemplo.py b/python/Modulo-02-Flujo/14-else-elif/ejemplo.py new file mode 100644 index 0000000..9db4957 --- /dev/null +++ b/python/Modulo-02-Flujo/14-else-elif/ejemplo.py @@ -0,0 +1,50 @@ +# ejemplo.py + +print("--- 1. El Cajero del Cine (if / else) ---") + +edad_cliente = 15 + +# Evaluamos la primera condición (Plan A) +if edad_cliente >= 18: + print("Puedes ver la película de terror.") +# Si la condición de arriba fue False (15 no es mayor a 18), saltamos al 'else' +else: + print("Eres menor de edad. Ve a ver la película animada.") + + +print("\n--- 2. El Semáforo Inteligente (if / elif / else) ---") + +color_semaforo = "amarillo" + +# Plan A +if color_semaforo == "verde": + print("Puedes avanzar 🟢") + +# Plan B (Solo se evalúa si el Plan A falló) +elif color_semaforo == "amarillo": + print("Ve frenando con precaución 🟡") + +# Plan C (Solo se evalúa si el Plan A y Plan B fallaron) +elif color_semaforo == "rojo": + print("¡ALTO TOTAL! 🔴") + +# Plan Z de seguridad (Si no fue verde, ni amarillo, ni rojo) +else: + print("El semáforo está roto, cruza con mucho cuidado. ❓") + + +print("\n--- 3. La trampa del código en cascada ---") +# Solo un bloque (el primero que sea True) se ejecuta. Los demás se ignoran. + +dinero_en_bolsillo = 100 + +if dinero_en_bolsillo >= 10: + print("Me alcanza para un chicle.") +elif dinero_en_bolsillo >= 50: + print("Me alcanza para una hamburguesa.") +else: + print("No compro nada.") + +# OJO AQUÍ: Aunque 100 es mayor que 50 (la segunda regla también es cierta), +# Python imprimirá "Me alcanza para un chicle" y terminará el proceso ahí mismo, +# porque fue el primer 'True' que encontró de arriba hacia abajo. diff --git a/python/Modulo-02-Flujo/14-else-elif/reto.md b/python/Modulo-02-Flujo/14-else-elif/reto.md new file mode 100644 index 0000000..fde9a23 --- /dev/null +++ b/python/Modulo-02-Flujo/14-else-elif/reto.md @@ -0,0 +1,44 @@ +# Reto 14: El Calificador de Exámenes 📝🎓 + +Tu maestro de programación te pidió ayuda para automatizar el sistema de calificaciones. Quieren dejar de usar números y pasar al sistema de letras (A, B, C, D, F). + +Tu trabajo es escribir un código que tome una calificación numérica y, usando `if`, `elif` y `else`, decida qué letra imprimir en la terminal. + +## Instrucciones + +1. Crea una variable llamada `calificacion_examen` y ponle un número del 0 al 100 (ej. `85`). + +2. Crea la estructura de decisiones usando este orden estricto de reglas: + * Si la calificación es **mayor o igual a 90**, imprime `"Obtuviste una A. ¡Excelente!"` + * Pero si en cambio es **mayor o igual a 80**, imprime `"Obtuviste una B. ¡Muy bien!"` + * Pero si en cambio es **mayor o igual a 70**, imprime `"Obtuviste una C. ¡Aprobaste!"` + * Pero si en cambio es **mayor o igual a 60**, imprime `"Obtuviste una D. ¡De panzazo!"` + * Si nada de lo anterior funcionó (de lo contrario), imprime `"Obtuviste una F. Nos vemos en recursamiento."` + +3. Recuerda cómo Python evalúa esto: de arriba hacia abajo. En el momento en que una condición sea cierta, ignorará todo lo demás. + +### Conceptos permitidos +- Variables (asignación con `=`). +- Tipos de datos (`int`). +- Operadores de comparación (`>=`). +- Estructura condicional completa (`if`, `elif`, `else`). +- Indentación correcta. +- `print()`. + +### Conceptos prohibidos +- El operador `and`. No lo necesitas. Gracias a que el `elif` solo se ejecuta si el de arriba falló, no tienes que comprobar rangos cerrados como "mayor a 80 y menor a 90". +- Ciclos (`for`, `while`). +- Funciones `def`. + +### Resultado esperado en terminal +Si usas `calificacion_examen = 85`, al correr tu código la terminal debe mostrar exactamente esto: + +```text +Obtuviste una B. ¡Muy bien! +``` + +Si luego lo cambias a `calificacion_examen = 55`, debe mostrar: + +```text +Obtuviste una F. Nos vemos en recursamiento. +``` diff --git a/python/Modulo-02-Flujo/14-else-elif/teoria.md b/python/Modulo-02-Flujo/14-else-elif/teoria.md new file mode 100644 index 0000000..61440b5 --- /dev/null +++ b/python/Modulo-02-Flujo/14-else-elif/teoria.md @@ -0,0 +1,45 @@ +# Teoría: El Plan B y las múltiples rutas + +Cuando usamos un `if` solitario, solo tenemos un "Plan A". Si la condición del `if` es mentira (`False`), la computadora se salta todo y sigue su camino en silencio. +Pero casi siempre queremos hacer *otra* cosa si la condición falla. + +### El Plan B: `else` (Y si no...) +La palabra `else` significa "de lo contrario". Actúa como una red de seguridad. Le dice a Python: "Si el `if` de arriba falló y no abriste su puerta, entra por esta puerta automáticamente, sin hacer preguntas". +* **Regla estricta:** Un `else` NUNCA lleva una condición al lado (no hace preguntas, solo actúa). Siempre va seguido de dos puntos `:`. + +### Los Planes C, D, E...: `elif` (Pero si en cambio...) +¿Qué pasa si hay 3, 4 o 5 opciones? Por ejemplo, las luces de un semáforo. No es solo "Rojo o no Rojo". +Para eso existe `elif` (una abreviatura de "else if", que significa "pero si en cambio..."). +Te permite hacer una nueva pregunta si el `if` anterior falló. Puedes poner todos los `elif` que quieras uno debajo de otro. + +--- + +## Anatomía (Sintaxis) + +```python +if condicion_1: + print("Plan A") +elif condicion_2: + print("Plan B") +elif condicion_3: + print("Plan C") +else: + print("Plan Z (Si TODO lo anterior falló)") +``` + +* El orden **SIEMPRE** debe ser: un `if` primero, luego los `elif` (opcionales), y al final un único `else` (opcional). +* Observa que `if` y `elif` llevan una condición antes de los `:`. +* El `else` **nunca** lleva condición. Solo lleva `:`. +* Todos (`if`, `elif`, `else`) van pegados a la pared izquierda (sin sangría), pero lo que hay *adentro* de ellos sí debe tener sangría (4 espacios). + +--- + +## ¿Qué pasa si me equivoco? + +**1. SyntaxError: `else` con condición** +Si escribes `else edad < 18:`, Python no entenderá. El `else` es el basurero final, el "si nada funcionó, haz esto". No necesita condiciones. +* **Solución:** Bórrale la condición y déjalo solo como `else:`. Si necesitas hacer otra pregunta, usa `elif`. + +**2. IndentationError en el `else`** +Si empujas el `else` hacia la derecha (con espacios) para que quede debajo de los `print`, Python dirá que ese `else` está huérfano. +* **Solución:** Los `if`, `elif` y `else` deben estar perfectamente alineados entre sí a la izquierda. diff --git a/python/Modulo-02-Flujo/14-else-elif/test_main.py b/python/Modulo-02-Flujo/14-else-elif/test_main.py new file mode 100644 index 0000000..2e85614 --- /dev/null +++ b/python/Modulo-02-Flujo/14-else-elif/test_main.py @@ -0,0 +1,14 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_output(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + if "11-" in RETO or "12-" in RETO or "16-" in RETO: + assert "True" in r.stdout or "False" in r.stdout + else: + assert len(r.stdout.strip()) > 0 diff --git a/retos/general/17-lector-de-configuraciones/soluciones/.gitkeep b/python/Modulo-02-Flujo/15-logica-anidada/.gitkeep similarity index 100% rename from retos/general/17-lector-de-configuraciones/soluciones/.gitkeep rename to python/Modulo-02-Flujo/15-logica-anidada/.gitkeep diff --git a/python/Modulo-02-Flujo/15-logica-anidada/README.md b/python/Modulo-02-Flujo/15-logica-anidada/README.md new file mode 100644 index 0000000..710792e --- /dev/null +++ b/python/Modulo-02-Flujo/15-logica-anidada/README.md @@ -0,0 +1,10 @@ +# Nivel 15: Decisiones dentro de Decisiones (Lógica Anidada) 🪆 + +En la vida real, abrir una puerta a veces te lleva a un pasillo con... ¡más puertas! Por ejemplo, si vas a un concierto, el primer guardia te pide boleto. Si lo tienes, entras al estadio. Pero adentro, hay otro guardia que te pide pulsera VIP para entrar a la zona frontal. + +En programación, a esto le llamamos **Lógica Anidada** (como las muñecas rusas Matrioskas, una dentro de otra). Es la capacidad de poner un `if` dentro de otro `if`. + +## Ruta de aprendizaje +1. **teoria.md**: Descubrirás cómo usar la sangría doble (8 espacios) para meter condiciones dentro de otras condiciones. +2. **ejemplo.py**: Programaremos el sistema de seguridad de una caja fuerte de doble cerradura. +3. **reto.md**: Serás el seleccionador de candidatos para un equipo de astronautas. diff --git a/python/Modulo-02-Flujo/15-logica-anidada/ejemplo.py b/python/Modulo-02-Flujo/15-logica-anidada/ejemplo.py new file mode 100644 index 0000000..ccafe81 --- /dev/null +++ b/python/Modulo-02-Flujo/15-logica-anidada/ejemplo.py @@ -0,0 +1,45 @@ +# ejemplo.py + +print("--- La Caja Fuerte del Banco ---") + +# Para abrir la bóveda, primero necesitas la tarjeta de acceso. +# Si tienes la tarjeta, pasas a una segunda puerta donde debes poner un PIN numérico. + +tiene_tarjeta = True +pin_ingresado = 1234 + +# PRIMER FILTRO (Puerta exterior) +if tiene_tarjeta == True: + # --- ZONA INTERMEDIA (4 espacios) --- + print("Tarjeta aceptada. Pasando a verificación de PIN...") + + # SEGUNDO FILTRO (Puerta interior, dentro del primer if) + if pin_ingresado == 1234: + # --- ZONA HIPER-SECRETA (8 espacios) --- + print("PIN correcto. Bóveda abierta. 💰") + + # Este else está alineado a 4 espacios. Es el Plan B del SEGUNDO filtro. + else: + print("ALERTA: PIN incorrecto. Llamando a la policía. 🚓") + +# Este else está pegado a la izquierda. Es el Plan B del PRIMER filtro. +else: + print("Acceso denegado. No tienes tarjeta.") + + +print("\n--- La Pizzería ---") + +# A veces queremos evaluar cosas distintas dependiendo de la primera decisión. +quiere_pizza = True +tamaño = "familiar" + +if quiere_pizza == True: + print("¡Genial! Vamos a preparar tu pizza.") + + if tamaño == "familiar": + print("Agregando doble queso por ser tamaño familiar.") + else: + print("Preparando una pizza normal.") + +else: + print("Oh, no quieres pizza. ¿Tal vez quieres una ensalada?") diff --git a/python/Modulo-02-Flujo/15-logica-anidada/reto.md b/python/Modulo-02-Flujo/15-logica-anidada/reto.md new file mode 100644 index 0000000..58d15d8 --- /dev/null +++ b/python/Modulo-02-Flujo/15-logica-anidada/reto.md @@ -0,0 +1,42 @@ +# Reto 15: La Agencia Espacial 🚀🧑‍🚀 + +Eres el reclutador jefe de la agencia espacial. Estás evaluando candidatos para ir a Marte. +El proceso tiene dos fases muy estrictas. Primero evalúas la salud física. Solo si pasan esa prueba, los pasas al examen psicológico. + +## Instrucciones + +1. Crea las siguientes variables para tu candidato: + * `salud_optima` (un booleano, ej. `True`). + * `puntuacion_psicologica` (un número entero del 0 al 100, ej. `95`). + +2. **Filtro 1 (Físico)**: Crea un `if` que verifique si `salud_optima` es verdadera (`True`). + * Si es falsa, el `else` externo debe imprimir: `"Rechazado en fase 1: No cumple los requisitos físicos."` + +3. **Filtro 2 (Psicológico - ¡Anidado!)**: Si el candidato sí tiene buena salud (adentro de tu primer `if`), crea **otro `if`** que verifique si su `puntuacion_psicologica` es mayor o igual a `90`. + * Si es mayor o igual a 90, imprime (a 8 espacios de sangría): `"¡Felicidades! Eres el nuevo astronauta para ir a Marte."` + * Si es menor (el `else` interno), imprime: `"Rechazado en fase 2: Excelente físico, pero no pasó el test psicológico."` + +### Conceptos permitidos +- Variables (asignación con `=`). +- Tipos de datos (`int`, `bool`). +- Operadores de comparación (`>=`, `==`). +- Estructura condicional anidada (un `if`/`else` dentro de otro `if`/`else`). +- Indentación múltiple (4 y 8 espacios). +- `print()`. + +### Conceptos prohibidos +- El operador `and`. Aquí queremos ver claramente los dos niveles de rechazo (fase 1 y fase 2), por lo que DEBES usar un `if` dentro de otro `if`. +- Ciclos (`for`, `while`). + +### Resultado esperado en terminal +Si configuras `salud_optima = True` y `puntuacion_psicologica = 85`, al correr tu código la terminal debe mostrar exactamente esto: + +```text +Rechazado en fase 2: Excelente físico, pero no pasó el test psicológico. +``` + +Si cambias a `salud_optima = False` (sin importar el puntaje psicológico), debe mostrar: + +```text +Rechazado en fase 1: No cumple los requisitos físicos. +``` diff --git a/python/Modulo-02-Flujo/15-logica-anidada/teoria.md b/python/Modulo-02-Flujo/15-logica-anidada/teoria.md new file mode 100644 index 0000000..cb4c5af --- /dev/null +++ b/python/Modulo-02-Flujo/15-logica-anidada/teoria.md @@ -0,0 +1,49 @@ +# Teoría: Puertas secretas dentro de cuartos secretos + +Hasta ahora sabemos abrir una puerta usando `if`. Si la condición es `True`, entramos a la habitación (identificada por los 4 espacios de sangría). +Pero, ¿qué pasa si queremos hacer una nueva pregunta **solo a las personas que lograron entrar a esa primera habitación**? + +Usamos la **lógica anidada**, que simplemente es poner un bloque `if`/`else` completo *adentro* de la sangría de otro. + +### El arte de la doble sangría (8 espacios) +Si el primer `if` requiere 4 espacios de sangría, cualquier cosa que sea la consecuencia de ese `if` debe ir a 4 espacios. +Si dentro de esos 4 espacios decides poner un *nuevo* `if`, la consecuencia de ese **segundo** `if` tendrá que empujarse otros 4 espacios (llegando a 8 espacios en total). + +* **Analogía**: Es como un índice de un libro. +```text +1. Capítulo principal (Sin sangría) + 1.1. Subtema (1 tabulador / 4 espacios) + 1.1.1. Detalle específico (2 tabuladores / 8 espacios) +``` + +--- + +## Anatomía (Sintaxis) + +```python +if primera_condicion: + print("Pasaste el primer filtro") + + if segunda_condicion: + print("Pasaste el segundo filtro, ¡Estás en la zona más exclusiva!") + else: + print("Pasaste el primer filtro, pero te quedaste en el segundo") + +else: + print("Ni siquiera pasaste el primer filtro") +``` + +* Fíjate bien en cómo el `else` de adentro se alinea con el `if` de adentro (a 4 espacios). +* El `else` de afuera se alinea con el `if` de afuera (sin espacios). +* Todo lo que está a 8 espacios pertenece **únicamente** al `if` interno. + +--- + +## ¿Qué pasa si me equivoco? + +**1. El caos de las sangrías perdidas** +El error más destructivo aquí es alinear mal un `else`. Si pones el `else` del segundo filtro pegado a la pared izquierda, Python creerá que ese `else` le pertenece al primer filtro. La lógica de tu programa hará cosas loquísimas. +* **Solución**: Tu código es visual. Dibuja una línea imaginaria hacia abajo desde la 'i' de tu `if`. Su `else` correspondiente debe caer exactamente sobre esa misma línea. + +**2. La Pirámide de la Perdición (Anti-patrón)** +Aunque puedes meter un `if` dentro de otro, y otro dentro de ese, y otro más... **¡no lo hagas a menos que sea estrictamente necesario!**. Si metes 5 niveles de profundidad, tu código será ilegible. A veces es mejor usar un simple `and` (ej. `if filtro_1 and filtro_2:`). Usa la lógica anidada solo cuando el plan B del filtro 2 sea muy distinto al plan B del filtro 1. diff --git a/python/Modulo-02-Flujo/15-logica-anidada/test_main.py b/python/Modulo-02-Flujo/15-logica-anidada/test_main.py new file mode 100644 index 0000000..2e85614 --- /dev/null +++ b/python/Modulo-02-Flujo/15-logica-anidada/test_main.py @@ -0,0 +1,14 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_output(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + if "11-" in RETO or "12-" in RETO or "16-" in RETO: + assert "True" in r.stdout or "False" in r.stdout + else: + assert len(r.stdout.strip()) > 0 diff --git a/retos/general/18-inventario-de-tienda/soluciones/.gitkeep b/python/Modulo-02-Flujo/16-truthy-falsy/.gitkeep similarity index 100% rename from retos/general/18-inventario-de-tienda/soluciones/.gitkeep rename to python/Modulo-02-Flujo/16-truthy-falsy/.gitkeep diff --git a/python/Modulo-02-Flujo/16-truthy-falsy/README.md b/python/Modulo-02-Flujo/16-truthy-falsy/README.md new file mode 100644 index 0000000..7e4bf5e --- /dev/null +++ b/python/Modulo-02-Flujo/16-truthy-falsy/README.md @@ -0,0 +1,11 @@ +# Nivel 16: Las Máscaras de la Verdad (Truthy y Falsy) 🎭 + +Hasta ahora, para que una puerta `if` se abriera, le dábamos la llave exacta: un valor `True` o `False` (usando comparadores como `>`). +Pero a Python le encantan los atajos. Resulta que **cualquier dato** en Python tiene una "verdad oculta". + +En la vida real, si te pregunto "¿Tienes dinero?" y sacas tu cartera vacía (0 billetes), asumo que la respuesta es "Falso, no tienes". Python hace exactamente lo mismo: asume que la "nada" o el "vacío" es una mentira, y que "algo" es una verdad. + +## Ruta de aprendizaje +1. **teoria.md**: Aprenderás el concepto ninja de "Truthy" (casi Verdad) y "Falsy" (casi Mentira). +2. **ejemplo.py**: Usaremos atajos para revisar si un usuario ingresó su nombre o lo dejó en blanco. +3. **reto.md**: Crearás un validador de perfiles sociales súper rápido. diff --git a/python/Modulo-02-Flujo/16-truthy-falsy/ejemplo.py b/python/Modulo-02-Flujo/16-truthy-falsy/ejemplo.py new file mode 100644 index 0000000..a48606e --- /dev/null +++ b/python/Modulo-02-Flujo/16-truthy-falsy/ejemplo.py @@ -0,0 +1,50 @@ +# ejemplo.py + +print("--- 1. Evaluando Texto Vacío ---") + +# Simulamos que un usuario llenó un formulario en internet. +nombre_usuario = "Alejandro" +apellido_usuario = "" # Lo dejó en blanco + +# En lugar de hacer 'if nombre_usuario != "":', usamos el atajo Truthy: +if nombre_usuario: + print(f"Nombre ingresado: {nombre_usuario}") +else: + print("Oye, no pusiste tu nombre.") + +# Evaluamos el apellido. Al estar vacío "", es Falsy. +if apellido_usuario: + print(f"Apellido ingresado: {apellido_usuario}") +else: + print("El apellido es opcional, no pasa nada.") + + +print("\n--- 2. Evaluando Números ---") + +# En un videojuego, verificamos si tienes pociones. +pociones = 0 +monedas = 15 + +# Si tienes 0 pociones, 'pociones' actúa como False. +if pociones: + print("Prepárate para curarte en la batalla.") +else: + print("¡Cuidado! No tienes pociones, estás en peligro.") + +# 15 es distinto de 0, así que es Truthy. +if monedas: + print("Puedes pasar a la tienda a comprar cosas.") +else: + print("Estás en la bancarrota.") + + +print("\n--- 3. La trampa del espacio en blanco ---") + +comentario = " " # Hay un espacio entre las comillas + +# ¿Un espacio en blanco es Truthy o Falsy? +# Para Python, un espacio es un carácter (algo físico), por lo tanto NO está vacío. +if comentario: + print("¡El usuario dejó un comentario! (Aunque solo sea un espacio)") +else: + print("El usuario no escribió absolutamente nada.") diff --git a/python/Modulo-02-Flujo/16-truthy-falsy/reto.md b/python/Modulo-02-Flujo/16-truthy-falsy/reto.md new file mode 100644 index 0000000..7aeee6a --- /dev/null +++ b/python/Modulo-02-Flujo/16-truthy-falsy/reto.md @@ -0,0 +1,40 @@ +# Reto 16: El Validador de Perfiles 📱 + +Te han contratado para limpiar la base de datos de una nueva red social. Hay usuarios que han creado cuentas, pero a veces no llenan su "Biografía" o todavía no tienen "Seguidores". +Tu misión es usar el poder de "Truthy y Falsy" para darles un diagnóstico rápido sin usar comparadores (`==`, `!=`, `>`, `<`). + +## Instrucciones + +1. Crea dos variables para un usuario: + * `biografia` (un texto. Escribe un texto corto o déjalo vacío `""`). + * `seguidores` (un número entero. Pon un número o pon `0`). + +2. **Revisión de Biografía**: + * Escribe un `if` que reciba directamente la variable `biografia` (sin comparadores). + * Si es Truthy, imprime: `"Biografía lista para mostrarse."` + * Si es Falsy (usa un `else`), imprime: `"Tu perfil está muy vacío. ¡Escribe algo sobre ti!"` + +3. **Revisión de Seguidores**: + * Escribe otro `if` que reciba directamente la variable `seguidores` (sin comparadores). + * Si es Truthy, imprime: `"¡Ya tienes tu propia audiencia!"` + * Si es Falsy (con un `else`), imprime: `"Aún no tienes seguidores. ¡Empieza a publicar!"` + +### Conceptos permitidos +- Variables (asignación con `=`). +- Tipos de datos (`int`, `str`). +- Estructura condicional (`if`, `else`). +- El concepto de Truthy/Falsy (usar variables como si fueran booleanos). +- `print()`. + +### Conceptos prohibidos +- PROHIBIDO usar NINGÚN comparador de los que vimos antes (`==`, `!=`, `>`, `<`, `>=`, `<=`). +- Funciones `def`. +- Lógica anidada compleja (haz los `if` separados). + +### Resultado esperado en terminal +Si configuras `biografia = ""` y `seguidores = 5`, al correr tu código la terminal debe mostrar exactamente esto: + +```text +Tu perfil está muy vacío. ¡Escribe algo sobre ti! +¡Ya tienes tu propia audiencia! +``` diff --git a/python/Modulo-02-Flujo/16-truthy-falsy/teoria.md b/python/Modulo-02-Flujo/16-truthy-falsy/teoria.md new file mode 100644 index 0000000..9d7eb2f --- /dev/null +++ b/python/Modulo-02-Flujo/16-truthy-falsy/teoria.md @@ -0,0 +1,56 @@ +# Teoría: La Filosofía del Vacío + +¿Qué pasa si ponemos un dato normal (como un texto o un número) directamente al lado de un `if` sin usar comparadores como `==`? + +```python +nombre = "Alejandro" +if nombre: + print("¡Tienes un nombre!") +``` + +Python intentará convertir ese dato en un Booleano (`True` o `False`). A los datos que se convierten en `True` les llamamos **Truthy**, y a los que se convierten en `False` les llamamos **Falsy**. + +### La Regla de Oro (Lo Falsy) +Para Python, todo lo que represente "ausencia", "cero" o "vacío" es considerado **Falsy** (se convierte en `False`). +Dado lo que sabemos hasta hoy, las únicas dos cosas Falsy son: +1. **El número cero**: `0` o `0.0`. +2. **El texto vacío**: `""` (unas comillas pegadas sin nada adentro). + +### Lo Truthy (Todo lo demás) +Si un dato **no** es el número cero y **no** es un texto vacío, entonces es **Truthy** (se convierte en `True`). +Ejemplos Truthy: +* Un texto normal: `"Hola"` o `" "` (¡incluso un espacio en blanco cuenta como "algo"!). +* Cualquier número que no sea cero: `1`, `500`, e incluso los negativos como `-10`. + +### ¿Por qué es útil? +Nos ahorra escribir código. +En lugar de escribir: `if dinero > 0:` +Podemos escribir simplemente: `if dinero:` (Si el dinero es 0, será Falsy. Si tiene cualquier otro valor, será Truthy). + +En lugar de escribir: `if nombre != "":` +Podemos escribir: `if nombre:` + +--- + +## Anatomía (Sintaxis) + +```python +dato = "Cualquier cosa" + +if dato: + print("El dato tiene algo adentro, es Truthy") +else: + print("El dato está vacío o es cero, es Falsy") +``` +* Fíjate que al lado del `if` ya no hay un `==` ni un `>`. Solo está la variable sola. +* Python dice: "¿Este dato tiene sustancia? Sí -> Entra. No -> Salta al else". + +--- + +## ¿Qué pasa si me equivoco? + +**El engaño del texto "0" o "False"** +Si creas una variable con texto: `puntaje = "0"`. +¿Es Truthy o Falsy? +¡Es **Truthy**! Porque es un texto que tiene una letra adentro (el símbolo del cero). No es el *número* cero matemático, ni es un texto vacío. +* **Solución**: Asegúrate de no poner comillas alrededor de números si quieres usar su valor Falsy matemático. diff --git a/python/Modulo-02-Flujo/16-truthy-falsy/test_main.py b/python/Modulo-02-Flujo/16-truthy-falsy/test_main.py new file mode 100644 index 0000000..2e85614 --- /dev/null +++ b/python/Modulo-02-Flujo/16-truthy-falsy/test_main.py @@ -0,0 +1,14 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_output(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + if "11-" in RETO or "12-" in RETO or "16-" in RETO: + assert "True" in r.stdout or "False" in r.stdout + else: + assert len(r.stdout.strip()) > 0 diff --git a/retos/nerve/01-chat-terminal/.gitkeep b/python/Modulo-02-Flujo/17-match-case/.gitkeep similarity index 100% rename from retos/nerve/01-chat-terminal/.gitkeep rename to python/Modulo-02-Flujo/17-match-case/.gitkeep diff --git a/python/Modulo-02-Flujo/17-match-case/README.md b/python/Modulo-02-Flujo/17-match-case/README.md new file mode 100644 index 0000000..25c01b4 --- /dev/null +++ b/python/Modulo-02-Flujo/17-match-case/README.md @@ -0,0 +1,10 @@ +# Nivel 17: El Clasificador Automático (`match-case`) 🗂️ + +En niveles anteriores vimos que podíamos usar muchísimos `elif` si teníamos muchas opciones (como un menú de restaurante con 10 platillos). Aunque funciona, escribir `if platillo == "A":`, `elif platillo == "B":` una y otra vez se vuelve aburrido y difícil de leer. + +Imagina que eres un cartero en una oficina postal. No vas leyendo cada carta diciendo "Si es para la calle 1, la pongo aquí, si no, si es para la calle 2...". Simplemente tomas la carta, miras la calle y la lanzas directamente a su casillero. ¡Eso es exactamente lo que hace `match-case`! + +## Ruta de aprendizaje +1. **teoria.md**: Conocerás las palabras mágicas `match` (el objeto a revisar) y `case` (los casilleros), además del comodín `_`. +2. **ejemplo.py**: Crearemos un conmutador telefónico y un menú de cafetería. +3. **reto.md**: Programarás el menú del servicio de atención al cliente de un banco. diff --git a/python/Modulo-02-Flujo/17-match-case/ejemplo.py b/python/Modulo-02-Flujo/17-match-case/ejemplo.py new file mode 100644 index 0000000..f53a9bd --- /dev/null +++ b/python/Modulo-02-Flujo/17-match-case/ejemplo.py @@ -0,0 +1,56 @@ +# ejemplo.py + +print("--- 1. El Menú de la Cafetería ---") + +print("Menú: 1=Café, 2=Té, 3=Chocolate") +opcion_elegida = 2 + +# Usamos 'match' y le entregamos la variable que queremos revisar. +match opcion_elegida: + # Si la variable vale 1... + case 1: + print("☕ Preparando un Café calientito.") + # Si la variable vale 2... + case 2: + print("🍵 Preparando un Té de manzanilla.") + # Si la variable vale 3... + case 3: + print("🍫 Preparando un Chocolate espeso.") + # El comodín: Si la variable no fue ni 1, ni 2, ni 3... + case _: + print("❓ Lo siento, no tenemos esa opción en el menú.") + + +print("\n--- 2. Evaluando días de la semana (Textos) ---") + +dia_actual = "sábado" + +match dia_actual: + case "lunes": + print("A iniciar la semana con energía.") + case "viernes": + print("¡Por fin es viernes!") + case "sábado": + print("Día de descanso y diversión.") + case "domingo": + print("Día de preparar las cosas para mañana.") + case _: + print("Es un día regular a mitad de semana.") + + +print("\n--- 3. Juntando opciones usando el símbolo '|' (O lógico del match) ---") + +# ¿Qué pasa si varias opciones hacen exactamente lo mismo? +# Podemos usar la barra vertical '|', que en los 'case' significa "o". +# OJO: La barra '|' suele estar en tu teclado arriba de la tecla Tab o cerca del número 1. + +direccion = "norte" + +match direccion: + # Esto se lee: caso "norte" O caso "sur" + case "norte" | "sur": + print("Te estás moviendo en el eje Vertical.") + case "este" | "oeste": + print("Te estás moviendo en el eje Horizontal.") + case _: + print("Dirección desconocida.") diff --git a/python/Modulo-02-Flujo/17-match-case/reto.md b/python/Modulo-02-Flujo/17-match-case/reto.md new file mode 100644 index 0000000..6b5ed86 --- /dev/null +++ b/python/Modulo-02-Flujo/17-match-case/reto.md @@ -0,0 +1,43 @@ +# Reto 17: Conmutador Telefónico del Banco 📞🏦 + +Has sido contratado para modernizar el sistema telefónico de un banco. Cuando el cliente llama, la operadora automática le pide que ingrese un número del 1 al 4. Ya no usarán más el viejo y kilométrico sistema de `if-elif`, ahora usarán un elegante `match-case`. + +## Instrucciones + +1. Crea una variable llamada `opcion_teclado` y asígnale un número entero del `1` al `4` (o invéntate uno que no exista, como `9`). + +2. Crea la estructura `match` para evaluar la variable `opcion_teclado`. + +3. Crea los siguientes `case` con su debida sangría (4 espacios para el `case`, 8 espacios para el `print` interior): + * `case 1:` Imprimir `"Lo estamos comunicando con el departamento de Ventas."` + * `case 2:` Imprimir `"Lo estamos comunicando con Soporte Técnico."` + * `case 3:` Imprimir `"Lo estamos comunicando con Cobranza."` + * `case 4:` Imprimir `"Gracias por llamar. Colgando la llamada..."` + +4. Crea el caso por defecto (el comodín `_`) por si el usuario presiona otro número diferente: + * `case _:` Imprimir `"Opción inválida. Por favor, marque un número del 1 al 4."` + +### Conceptos permitidos +- Variables (asignación con `=`). +- Tipos de datos (`int`). +- La estructura `match` y `case`. +- El caso por defecto `case _:`. +- `print()`. + +### Conceptos prohibidos +- Totalmente prohibido usar `if`, `elif`, o `else`. El reto es dominar la nueva estructura. +- Funciones `def`. +- Lógica anidada. + +### Resultado esperado en terminal +Si configuras `opcion_teclado = 2`, tu terminal debe verse así: + +```text +Lo estamos comunicando con Soporte Técnico. +``` + +Si configuras `opcion_teclado = 9`, la terminal debe verse así: + +```text +Opción inválida. Por favor, marque un número del 1 al 4. +``` diff --git a/python/Modulo-02-Flujo/17-match-case/teoria.md b/python/Modulo-02-Flujo/17-match-case/teoria.md new file mode 100644 index 0000000..f6c98a5 --- /dev/null +++ b/python/Modulo-02-Flujo/17-match-case/teoria.md @@ -0,0 +1,42 @@ +# Teoría: El cartero súper veloz de Python + +La estructura `match` y `case` (introducida en Python 3.10) es una forma súper limpia de tomar decisiones cuando tienes un solo dato y quieres ver con cuál "caso" coincide. Es la alternativa elegante a escribir decenas de `elif`. + +### 1. `match` (El objeto a observar) +Significa "coincidir" o "emparejar". Le damos a Python una variable, y le decimos: "Sostén esto en tu mano y busca un casillero que coincida con él". + +### 2. `case` (El casillero) +Significa "caso". Son las opciones que le damos a Python. Cada `case` tiene un valor. Si lo que Python tiene en la mano coincide exactamente con el valor del `case`, entra ahí y ejecuta el código. + +### 3. El comodín `case _:` (El "Y si no...") +Al igual que el `else` era la red de seguridad de los `if`, en `match-case` usamos un guion bajo `_`. Significa "Cualquier otra cosa". Si Python revisó todos los `case` de arriba y ninguno coincidió, tirará la carta en el casillero `_`. + +--- + +## Anatomía (Sintaxis) + +```python +variable_a_revisar = "A" + +match variable_a_revisar: + case "A": + print("Opción A seleccionada") + case "B": + print("Opción B seleccionada") + case _: + print("Opción no válida") +``` +### La doble sangría: +1. `match` va pegado a la pared. +2. Los `case` van empujados con **1 tabulador** (4 espacios). +3. El código que va dentro de cada `case` va empujado con **2 tabuladores** (8 espacios). + +--- + +## ¿Qué pasa si me equivoco? + +**1. SyntaxError por versión antigua de Python** +`match-case` es relativamente "nuevo" (salió en la versión 3.10). Si estás corriendo esto en una computadora con un Python muy viejo (como la 3.9), la terminal explotará diciendo que no sabe qué es la palabra `match`. ¡Asegúrate de estar actualizado! + +**2. Olvidar los dos puntos `:` en el case** +Al igual que con el `if`, cada `case` debe terminar con `:`. Si pones `case "A"` y presionas Enter, tendrás un `SyntaxError`. ¡El `:` le dice a Python "entonces haz esto"! diff --git a/python/Modulo-02-Flujo/17-match-case/test_main.py b/python/Modulo-02-Flujo/17-match-case/test_main.py new file mode 100644 index 0000000..c60790d --- /dev/null +++ b/python/Modulo-02-Flujo/17-match-case/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_match_case_funciona(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "comunicando" in r.stdout.lower(), \ + "Debe imprimir el mensaje del match-case" diff --git a/retos/nerve/01-chat-terminal/soluciones/.gitkeep b/python/Modulo-02-Flujo/18-depuracion/.gitkeep similarity index 100% rename from retos/nerve/01-chat-terminal/soluciones/.gitkeep rename to python/Modulo-02-Flujo/18-depuracion/.gitkeep diff --git a/python/Modulo-02-Flujo/18-depuracion/README.md b/python/Modulo-02-Flujo/18-depuracion/README.md new file mode 100644 index 0000000..a0fcb63 --- /dev/null +++ b/python/Modulo-02-Flujo/18-depuracion/README.md @@ -0,0 +1,10 @@ +# Nivel 18: El Detective de Código (Depuración Básica con `print`) 🕵️‍♂️🔍 + +A veces, el código hace cosas raras. Te jura que el `if` debería funcionar, pero Python se va por el `else`. Te frustras, miras la pantalla fijamente y piensas "¡Pero si está bien!". + +Bienvenido al mundo de los *bugs* (insectos/errores) y el arte de la *depuración* (limpiar errores). En la vida real, cuando pierdes tus llaves, retrocedes paso a paso preguntándote "¿Dónde las dejé?". En código, nuestra linterna para ver los pasos de Python es la función `print()`. Usarla estratégicamente te ahorrará horas de dolores de cabeza. + +## Ruta de aprendizaje +1. **teoria.md**: Aprenderás por qué el código "falla silenciosamente" y cómo usar `print()` como cámaras de seguridad. +2. **ejemplo.py**: Resolveremos un misterio de un descuento que no se aplica usando nuestra linterna. +3. **reto.md**: Arreglarás un código roto encontrando el error oculto. diff --git a/python/Modulo-02-Flujo/18-depuracion/ejemplo.py b/python/Modulo-02-Flujo/18-depuracion/ejemplo.py new file mode 100644 index 0000000..1364af2 --- /dev/null +++ b/python/Modulo-02-Flujo/18-depuracion/ejemplo.py @@ -0,0 +1,55 @@ +# ejemplo.py + +print("--- 1. El Misterio del Descuento Perdido ---") +# Contexto: Un cliente compró 3 artículos de 40 dólares. +# Si gasta más de 100 dólares, debería tener descuento. + +precio_articulo = 40 +cantidad = 3 + +# Calculamos el total +total = precio_articulo + cantidad # ¡Uups! Usé el símbolo de suma (+) en vez de multiplicar (*) + +# Aquí está el problema: el programador dice "Debería entrar al if porque 40 * 3 = 120". +# Pero cuando corre el código... + +# --- INICIA ZONA DE DEPURACIÓN --- +# Como no funciona, encendemos la linterna con 'print()' +print("DEBUG - El precio del artículo es:", precio_articulo) +print("DEBUG - La cantidad es:", cantidad) +print("DEBUG - El total calculado es:", total) +# ¡Ahí veremos que total es 43, no 120! ¡Encontramos el bug! +# --- TERMINA ZONA DE DEPURACIÓN --- + +if total > 100: + print("¡Felicidades! Tienes un descuento.") +else: + print("No alcanzas el descuento. Sigue comprando.") + + +print("\n--- 2. Rastreando los pasos de Python ---") +# A veces no sabemos por qué camino se fue el código. Ponemos "letreros" en el camino. + +clima = "lluvia" +temperatura = 15 + +# Colocamos un 'print' de diagnóstico para confirmar los valores iniciales. +print("DEBUG - Estado inicial -> clima:", clima, "/ temp:", temperatura) + +if clima == "soleado": + print("DEBUG - Camino A (soleado)") + if temperatura > 25: + print("Vamos a la playa.") + else: + print("Vamos al parque.") +elif clima == "lluvia": + print("DEBUG - Camino B (lluvia)") + if temperatura < 10: + print("DEBUG - Camino B.1 (frío)") + print("Quédate en cama con un chocolate.") + else: + print("DEBUG - Camino B.2 (no tan frío)") + print("Lleva paraguas.") +else: + print("DEBUG - Camino C (otro)") + print("Mira por la ventana.") diff --git a/python/Modulo-02-Flujo/18-depuracion/reto.md b/python/Modulo-02-Flujo/18-depuracion/reto.md new file mode 100644 index 0000000..56ecda3 --- /dev/null +++ b/python/Modulo-02-Flujo/18-depuracion/reto.md @@ -0,0 +1,51 @@ +# Reto 18: El Cajero Automático Roto 🏧💥 + +El cajero automático de la esquina está volviéndose loco. Un cliente tiene 500 dólares en su cuenta y quiere retirar 100. El cajero debería decirle "Retiro exitoso", pero en lugar de eso le dice "Fondos insuficientes". + +Hay un error (un *bug*) en la lógica, pero a simple vista no es obvio. + +## Instrucciones + +1. Crea el siguiente código en tu editor exactamente como está escrito aquí (tiene un bug a propósito): + +```python +saldo = 500 +retiro = 100 + +# Se nos cobra una comisión fantasma y se resta un cargo extra +comision = retiro + 50 +saldo_disponible = saldo - retiro - comision + +if retiro <= saldo_disponible: + print("Retiro exitoso.") +else: + print("Fondos insuficientes.") +``` + +2. Tu misión no es solo adivinar el error, sino usar **prints de depuración** para encontrarlo científicamente. +3. Agrega `print()` antes del `if` para ver el valor exacto de `saldo_disponible` y el valor de `comision`. Agrégales el prefijo `"DEBUG - "` para saber que son tuyos. +4. Una vez que corras el código y veas en la terminal los valores reales, analiza por qué falló. +5. Finalmente, corrige las fórmulas matemáticas para que el código funcione correctamente y apruebe el retiro (el retiro total con comisión debe ser 150, dejando el saldo disponible correcto para evaluar). + +### Conceptos permitidos +- Variables y operadores matemáticos. +- `print()` para diagnóstico y para mensajes finales. +- Condicionales `if-else`. + +### Conceptos prohibidos +- Funciones, ciclos, librerías u otros conceptos no vistos. +- El debugger de Python integrado en el IDE (queremos aprender la técnica manual básica primero). + +### Resultado esperado en terminal ANTES de arreglarlo +```text +DEBUG - Comisión calculada: 150 +DEBUG - Saldo disponible para evaluar: 250 +Fondos insuficientes. +``` +*(Nota: Al ver que el saldo evaluado era 250 y el retiro 100, el retiro de hecho SÍ es menor o igual al saldo disponible. El bug está en la condición matemática de cómo se compara o calcula el límite real)* + +### Resultado esperado en terminal DESPUÉS de arreglarlo (y tras borrar los DEBUG) +```text +Retiro exitoso. +``` +*(Tip: La lógica correcta es comparar si el total a sacar `(retiro + comision)` es menor o igual al `saldo` original).* diff --git a/python/Modulo-02-Flujo/18-depuracion/teoria.md b/python/Modulo-02-Flujo/18-depuracion/teoria.md new file mode 100644 index 0000000..78deac6 --- /dev/null +++ b/python/Modulo-02-Flujo/18-depuracion/teoria.md @@ -0,0 +1,43 @@ +# Teoría: Cámaras de Seguridad en tu Código + +La computadora es obediente, pero ciega. Si le das una instrucción equivocada, la ejecutará felizmente. El problema ocurre cuando **tú crees que una variable vale algo, pero en realidad vale otra cosa**. + +### ¿Qué es la "Depuración"? +Depurar (*debug*) significa buscar y corregir errores. El error más común a tu nivel no es que la terminal explote (error de sintaxis), sino que el programa no haga lo que querías (error de lógica). + +### La técnica del "print() diagnóstico" +Imagina que tienes una tubería de agua y el agua no llega al final. ¿Qué haces? Pones medidores en distintos puntos del tubo. +En código, usamos `print()` para asomarnos dentro del cerebro de Python en puntos clave. + +Hay dos cosas principales que queremos imprimir para investigar: +1. **El valor de las variables antes de un `if`:** Para ver si realmente tienen el valor que esperamos. +2. **Mensajes de "estoy aquí":** Para saber en qué bloque `if`, `elif` o `else` decidió entrar Python. + +--- + +## Anatomía (Sintaxis Diagnóstica) + +```python +edad_usuario = 15 +# ERROR COMÚN: Pensamos que tiene 18. +# SOLUCIÓN: Imprimir antes de evaluar. +print("DEBUG - Valor de edad:", edad_usuario) + +if edad_usuario >= 18: + print("DEBUG - Entró al if") # Nos dice qué camino tomó + print("Eres mayor de edad.") +else: + print("DEBUG - Entró al else") # Nos dice qué camino tomó + print("Eres menor de edad.") +``` +*Nota: La palabra "DEBUG" es una costumbre de los programadores para saber que ese print es solo para nosotros, no para el usuario final. Luego, cuando el código funciona, borramos esos prints.* + +--- + +## ¿Qué pasa si me equivoco? + +**1. Olvidar borrar los prints de debug** +Si dejas todos tus `print("DEBUG - la variable vale...")` y entregas tu programa, tu usuario verá mensajes extraños que no entiende. ¡Recuerda limpiar tus "cámaras de seguridad" cuando atrapes al ladrón (el error)! + +**2. Depurar el síntoma, no la enfermedad** +A veces ves que un `if` falla e intentas cambiar el `if`. Pero si pones un `print` arriba, descubrirás que el error no era el `if`, ¡sino un cálculo matemático mal hecho 5 líneas antes que arruinó la variable! diff --git a/python/Modulo-02-Flujo/18-depuracion/test_main.py b/python/Modulo-02-Flujo/18-depuracion/test_main.py new file mode 100644 index 0000000..2c60b24 --- /dev/null +++ b/python/Modulo-02-Flujo/18-depuracion/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_retiro_exitoso(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "exitoso" in r.stdout.lower(), \ + "La versión final debe mostrar 'Retiro exitoso.' (bug corregido)" diff --git a/retos/nerve/02-reloj-sincronizado/.gitkeep b/python/Modulo-02-Flujo/19-optimizacion/.gitkeep similarity index 100% rename from retos/nerve/02-reloj-sincronizado/.gitkeep rename to python/Modulo-02-Flujo/19-optimizacion/.gitkeep diff --git a/python/Modulo-02-Flujo/19-optimizacion/README.md b/python/Modulo-02-Flujo/19-optimizacion/README.md new file mode 100644 index 0000000..41dc042 --- /dev/null +++ b/python/Modulo-02-Flujo/19-optimizacion/README.md @@ -0,0 +1,10 @@ +# Nivel 19: El Arte de la Elegancia (Optimización de Flujo) 🪶✨ + +Cuando aprendemos a hablar, usamos oraciones largas y repetitivas: "Yo quiero ir al parque y yo quiero jugar en los columpios y yo quiero correr". Cuando crecemos, aprendemos a decir: "Quiero ir al parque a jugar y correr". + +En programación pasa lo mismo. Ya sabes hacer que la computadora tome decisiones (que "hable"), pero a veces nuestro código termina siendo muy largo, repetitivo y difícil de leer. En este nivel aprenderás a escribir código "elegante" y optimizado. Reducirás 6 líneas de código a solo 1, sin perder claridad. + +## Ruta de aprendizaje +1. **teoria.md**: Aprenderás las tres reglas de oro para limpiar tus `if`: aplanar nidos, asignación directa y quitar redundancias. +2. **ejemplo.py**: Veremos el "Antes" (código novato) y el "Después" (código profesional). +3. **reto.md**: Te daré un código largo y desordenado, y tu misión será reducirlo a su versión más elegante. diff --git a/python/Modulo-02-Flujo/19-optimizacion/ejemplo.py b/python/Modulo-02-Flujo/19-optimizacion/ejemplo.py new file mode 100644 index 0000000..591934c --- /dev/null +++ b/python/Modulo-02-Flujo/19-optimizacion/ejemplo.py @@ -0,0 +1,50 @@ +# ejemplo.py + +print("--- 1. Aplanando Nidos (Combinando condiciones) ---") + +# Código de un sistema de alarma +puerta_cerrada = True +alarma_activada = True +movimiento_detectado = False + +# VERSIÓN NOVATO (Muchas líneas, difícil de leer) +print("Evaluando versión novato...") +if puerta_cerrada: + if alarma_activada: + if not movimiento_detectado: + print("Todo seguro en la casa.") + +# VERSIÓN PROFESIONAL (Una sola línea de decisión) +print("Evaluando versión profesional...") +if puerta_cerrada and alarma_activada and not movimiento_detectado: + print("Todo seguro en la casa.") + + +print("\n--- 2. Asignación Directa ---") +# Sistema de VIP en un club +dinero_en_cuenta = 5000 + +# VERSIÓN NOVATO +if dinero_en_cuenta >= 1000: + es_cliente_vip = True +else: + es_cliente_vip = False + +# VERSIÓN PROFESIONAL (El comparador '>=' ya devuelve True o False) +# Guardamos la respuesta directamente en la caja 'es_cliente_vip' +es_cliente_vip = dinero_en_cuenta >= 1000 + +print("¿El cliente es VIP?", es_cliente_vip) + + +print("\n--- 3. Limpiando Redundancias ---") +tiene_cupon = True + +# VERSIÓN NOVATO +if tiene_cupon == True: + print("Aplicando descuento (novato).") + +# VERSIÓN PROFESIONAL +# 'tiene_cupon' ya vale True, el 'if' solo necesita leerlo. +if tiene_cupon: + print("Aplicando descuento (profesional).") diff --git a/python/Modulo-02-Flujo/19-optimizacion/reto.md b/python/Modulo-02-Flujo/19-optimizacion/reto.md new file mode 100644 index 0000000..97a334d --- /dev/null +++ b/python/Modulo-02-Flujo/19-optimizacion/reto.md @@ -0,0 +1,52 @@ +# Reto 19: El Limpiador de Código 🧹✨ + +Te han contratado como Ingeniero Senior en una empresa de videojuegos. Tu primer trabajo es revisar el código que dejó el becario. Él escribió un sistema para decidir si un jugador gana un trofeo especial, pero usó demasiadas líneas y es muy difícil de leer. + +Tu misión es **optimizar** este código aplicando las tres reglas de oro: +1. Aplanar nidos. +2. Asignación directa. +3. Quitar el `== True`. + +## Instrucciones + +1. Este es el código del becario. Cópialo en tu archivo y obsérvalo. +```python +puntos = 150 +tiempo_segundos = 45 +enemigos_derrotados = True + +# PARTE 1: Calcular si superó el nivel +if puntos > 100: + if tiempo_segundos < 60: + supero_nivel = True + else: + supero_nivel = False +else: + supero_nivel = False + +# PARTE 2: Entregar el trofeo +if supero_nivel == True: + if enemigos_derrotados == True: + print("¡Trofeo Dorado desbloqueado!") +``` + +2. Tu reto es **borrar ese código y reescribirlo de forma profesional**. +3. Deberías poder resolver la PARTE 1 usando **una sola línea** (Asignación directa con `and`). +4. Deberías poder resolver la PARTE 2 usando solo **dos líneas** (Aplanando el nido con `and` y quitando redundancias). + +### Conceptos permitidos +- Variables y operadores. +- Operadores lógicos (`and`, `or`, `not`). +- Condicionales simples (`if`). + +### Conceptos prohibidos +- Totalmente prohibido usar `if` dentro de otro `if` (lógica anidada). +- Prohibido usar `== True`. +- Prohibido hacer un `if-else` solo para asignar `True` o `False` a una variable. + +### Resultado esperado en terminal +Al correr tu código limpio y optimizado, la consola debe mostrar exactamente lo mismo que el código del becario: + +```text +¡Trofeo Dorado desbloqueado! +``` diff --git a/python/Modulo-02-Flujo/19-optimizacion/teoria.md b/python/Modulo-02-Flujo/19-optimizacion/teoria.md new file mode 100644 index 0000000..f6e35da --- /dev/null +++ b/python/Modulo-02-Flujo/19-optimizacion/teoria.md @@ -0,0 +1,55 @@ +# Teoría: Escribiendo Código como un Profesional + +Optimizar no siempre significa hacer que el código corra más rápido. Muchas veces significa hacerlo más fácil de leer para los humanos. Menos líneas de código significan menos lugares donde los *bugs* (errores) pueden esconderse. + +### Regla 1: Aplanar los nidos (Usar `and`) +Un `if` dentro de otro `if` (lógica anidada) forma una "V" visual que empuja el código hacia la derecha. Si solo quieres hacer algo cuando ambas cosas son ciertas, ¡júntalas! + +**Novato:** +```python +if tiene_boleto: + if es_mayor_de_edad: + print("Puedes entrar al concierto") +``` +**Profesional:** +```python +if tiene_boleto and es_mayor_de_edad: + print("Puedes entrar al concierto") +``` + +### Regla 2: Asignación Directa de Booleanos +Este es el truco favorito de los programadores. Si estás usando un `if` solo para guardar `True` o `False` en una variable, ¡puedes guardar el resultado de la comparación directamente! + +**Novato:** +```python +puntuacion = 85 +if puntuacion > 80: + paso_el_examen = True +else: + paso_el_examen = False +``` +**Profesional:** +```python +puntuacion = 85 +paso_el_examen = puntuacion > 80 # Guarda el resultado de la pregunta (True) +``` +*Analogía:* En lugar de decir "Si el cielo es azul, entonces es de día, si no, no lo es", dices "Es de día = el cielo es azul". + +### Regla 3: El fantasma del `== True` +Recordando lo que vimos en Truthy/Falsy, un `if` ya está buscando un `True`. Preguntar `if variable == True:` es como preguntar "¿Es verdad que es verdad?". + +**Novato:** +```python +if paso_el_examen == True: +``` +**Profesional:** +```python +if paso_el_examen: +``` + +--- + +## ¿Qué pasa si me equivoco? + +**1. Sobre-optimizar y perder claridad** +Si intentas juntar 5 condiciones en una sola línea usando muchísimos `and` y `or`, el código será tan elegante que nadie lo entenderá. La optimización busca **claridad**. Si una línea es demasiado larga, a veces es mejor dejarla como estaba. diff --git a/python/Modulo-02-Flujo/19-optimizacion/test_main.py b/python/Modulo-02-Flujo/19-optimizacion/test_main.py new file mode 100644 index 0000000..75d7b99 --- /dev/null +++ b/python/Modulo-02-Flujo/19-optimizacion/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_trofeo_dorado(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "Trofeo Dorado" in r.stdout, "Debe imprimir '¡Trofeo Dorado desbloqueado!'" diff --git a/retos/nerve/02-reloj-sincronizado/soluciones/.gitkeep b/python/Modulo-02-Flujo/20-RETO-portero/.gitkeep similarity index 100% rename from retos/nerve/02-reloj-sincronizado/soluciones/.gitkeep rename to python/Modulo-02-Flujo/20-RETO-portero/.gitkeep diff --git a/python/Modulo-02-Flujo/20-RETO-portero/README.md b/python/Modulo-02-Flujo/20-RETO-portero/README.md new file mode 100644 index 0000000..5bc2dbb --- /dev/null +++ b/python/Modulo-02-Flujo/20-RETO-portero/README.md @@ -0,0 +1,10 @@ +# Nivel 20: El Jefe Final - El Portero de la Discoteca 🦍🚧 + +¡Has llegado al final del Módulo 02! Ya sabes cómo hacer que tus programas piensen, tomen decisiones, evalúen múltiples caminos y limpien su propia lógica. Ya no eres un simple escritor de instrucciones, eres un arquitecto de flujos. + +En la vida real, los sistemas más grandes (como un sistema de pagos de Amazon o el inicio de sesión de Netflix) son, en el fondo, una serie de compuertas `if` que validan contraseñas, fondos y permisos. Hoy, vas a construir el sistema de seguridad definitivo. + +## Ruta de aprendizaje +1. **teoria.md**: Un repaso rápido de todas las herramientas de flujo que tienes en tu cinturón. +2. **ejemplo.py**: Un calentamiento construyendo el sistema de un cajero de peaje. +3. **reto.md**: El Proyecto Integrador. Construirás al implacable "Portero de la Discoteca". diff --git a/python/Modulo-02-Flujo/20-RETO-portero/ejemplo.py b/python/Modulo-02-Flujo/20-RETO-portero/ejemplo.py new file mode 100644 index 0000000..137df27 --- /dev/null +++ b/python/Modulo-02-Flujo/20-RETO-portero/ejemplo.py @@ -0,0 +1,44 @@ +# ejemplo.py + +print("--- CALENTAMIENTO: El Cajero de Peaje ---") +# Vamos a usar todo lo aprendido para calcular cuánto paga un auto. + +tipo_vehiculo = "camion" # Puede ser "moto", "auto", "camion" +numero_pasajeros = 1 +es_hora_pico = True + +# Usamos match-case porque evaluamos EXACTAMENTE una variable contra opciones fijas +match tipo_vehiculo: + case "moto": + tarifa_base = 5 + print("Tarifa base de moto: $5") + case "auto": + tarifa_base = 10 + print("Tarifa base de auto: $10") + case "camion": + tarifa_base = 20 + print("Tarifa base de camión: $20") + case _: + # Red de seguridad + tarifa_base = 0 + print("Vehículo desconocido, deténgase.") + +# Si es un vehículo válido (tarifa_base > 0), aplicamos las reglas de flujo +if tarifa_base > 0: + + # 1. Regla de carpool (Optimización con Truthy y and) + if tipo_vehiculo == "auto" and numero_pasajeros >= 4: + print("¡Auto compartido! Descuento aplicado.") + tarifa_base = tarifa_base - 3 + + # 2. Regla de hora pico + if es_hora_pico: + print("Es hora pico, hay recargo.") + tarifa_base = tarifa_base + 5 + + print("TOTAL A PAGAR: $", tarifa_base) + +else: + print("No se puede calcular el cobro.") + +print("------------------------------------------") diff --git a/python/Modulo-02-Flujo/20-RETO-portero/reto.md b/python/Modulo-02-Flujo/20-RETO-portero/reto.md new file mode 100644 index 0000000..b0f1275 --- /dev/null +++ b/python/Modulo-02-Flujo/20-RETO-portero/reto.md @@ -0,0 +1,40 @@ +# Reto 20: El Implacable Portero de la Discoteca 🦍🚪 + +El club más exclusivo de la ciudad necesita un sistema informático para decidir quién entra y quién se va a casa. Han despedido al antiguo programador porque la gente se estaba colando. ¡Tú eres su única esperanza! + +## Las Reglas del Club +Vas a crear un script que evalúe a un cliente basándose en estas reglas estrictas, **en este orden exacto**: + +1. **Edad:** Si el cliente tiene menos de 18 años, es rechazado inmediatamente (Mensaje: `"Rechazado: Eres menor de edad."`). +2. **Día de la semana:** El club abre de "jueves" a "domingo". Si es "lunes", "martes" o "miércoles", nadie entra (Mensaje: `"Rechazado: El club está cerrado hoy."`). +3. **VIP:** Si el cliente está en la lista VIP, entra gratis, sin importar cómo venga vestido. (Mensaje: `"¡Bienvenido, VIP! Pase usted."`). +4. **Código de vestimenta:** Si no es VIP, su ropa debe ser "elegante" o "casual". Si viene de "deportiva" o "traje_de_baño", se va. (Mensaje: `"Rechazado: No cumples el código de vestimenta."`). +5. **Entrada normal:** Si es mayor de edad, el club está abierto, no es VIP, pero viene "elegante" o "casual", paga su entrada y entra. (Mensaje: `"Bienvenido. Son 20 dólares de cover."`). + +## Instrucciones + +1. Crea las variables iniciales para hacer la prueba: + * `edad_cliente = 19` + * `dia_actual = "viernes"` + * `es_vip = False` + * `ropa = "deportiva"` + +2. Construye la lógica usando TODO lo que sabes (`if`, `elif`, `else`, `match-case`, `and`, `or`, etc.). ¡Usa la herramienta que mejor se adapte a cada regla! + * *Tip:* Un `match` puede ser excelente para evaluar el día de la semana. + * *Tip:* La regla de la edad debería ser lo primerito que evaluemos (usando `if` y lógica anidada). + +### Conceptos permitidos +- ¡Todos los conceptos del Módulo 02! +- Lógica anidada. +- Optimización. + +### Conceptos prohibidos +- Funciones (`def`), ciclos (`for`, `while`), listas, diccionarios. + +### Resultado esperado en terminal (con los datos de prueba del paso 1) +Como tiene 19, es viernes, NO es VIP, y viene con ropa "deportiva", el resultado debe ser: +```text +Rechazado: No cumples el código de vestimenta. +``` + +*Juega cambiando las variables. Ponle 17 años y ve qué pasa. Ponle ropa "casual" y ve qué pasa. ¡Demuestra que eres el maestro del Flujo!* diff --git a/python/Modulo-02-Flujo/20-RETO-portero/teoria.md b/python/Modulo-02-Flujo/20-RETO-portero/teoria.md new file mode 100644 index 0000000..d656c8d --- /dev/null +++ b/python/Modulo-02-Flujo/20-RETO-portero/teoria.md @@ -0,0 +1,30 @@ +# Teoría: Tu Cinturón de Herramientas (Repaso del Módulo 02) + +Antes de enfrentar al Jefe Final, repasemos las armas que has conseguido. Cada una tiene un propósito específico. ¡Úsalas sabiamente! + +### 1. Comparadores (`>`, `<`, `==`, `!=`) +Sirven para hacer preguntas matemáticas o de identidad. +*¿Es mayor? ¿Son exactamente iguales?* + +### 2. Operadores Lógicos (`and`, `or`, `not`) +Sirven para combinar preguntas. +* `and`: El exigente. TODO tiene que ser cierto. +* `or`: El relajado. Con UNA sola cosa cierta, es suficiente. +* `not`: El rebelde. Voltea la verdad al revés. + +### 3. La trinidad del Flujo (`if`, `elif`, `else`) +* `if`: "Si pasa esto..." (Siempre empieza la evaluación). +* `elif`: "Si no pasó lo de arriba, pero pasa esto..." (Puedes usar todos los que quieras). +* `else`: "Si nada de lo anterior pasó, haz esto por defecto" (La red de seguridad). + +### 4. Lógica Anidada (El Inception) +Poner un `if` dentro de otro `if`. Útil cuando la primera pregunta "abre la puerta" para hacer más preguntas (Ej. Si tienes boleto -> ¿Es boleto VIP o Regular?). + +### 5. `match-case` (El Cartero) +La mejor herramienta cuando vas a evaluar el **valor exacto** de una sola variable contra muchas opciones, como un menú o los días de la semana. + +### 6. Truthy / Falsy +Recordar que las variables numéricas valen Falso si son `0`, y los textos valen Falso si están vacíos `""`. + +### 7. Optimización y Depuración +Recuerda usar `print()` para espiar tus variables si el código hace cosas raras, y evita preguntar `if condicion == True:` (solo escribe `if condicion:`). diff --git a/python/Modulo-02-Flujo/20-RETO-portero/test_main.py b/python/Modulo-02-Flujo/20-RETO-portero/test_main.py new file mode 100644 index 0000000..37b3abc --- /dev/null +++ b/python/Modulo-02-Flujo/20-RETO-portero/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_portero_rechaza_ropa_deportiva(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "vestimenta" in r.stdout.lower(), \ + "Con ropa deportiva debe rechazar por código de vestimenta" diff --git a/retos/nerve/03-traductor-de-mensajes/.gitkeep b/python/Modulo-03-Estructuras/.gitkeep similarity index 100% rename from retos/nerve/03-traductor-de-mensajes/.gitkeep rename to python/Modulo-03-Estructuras/.gitkeep diff --git a/retos/nerve/03-traductor-de-mensajes/soluciones/.gitkeep b/python/Modulo-03-Estructuras/21-listas/.gitkeep similarity index 100% rename from retos/nerve/03-traductor-de-mensajes/soluciones/.gitkeep rename to python/Modulo-03-Estructuras/21-listas/.gitkeep diff --git a/python/Modulo-03-Estructuras/21-listas/README.md b/python/Modulo-03-Estructuras/21-listas/README.md new file mode 100644 index 0000000..ccfab18 --- /dev/null +++ b/python/Modulo-03-Estructuras/21-listas/README.md @@ -0,0 +1,12 @@ +# Nivel 21: Listas (Tus primeras cajas múltiples) 📦 + +Hasta ahora, nuestras variables han sido como cajas pequeñas donde solo cabe **una sola cosa**. Una variable `nombre` guarda un solo nombre. Una variable `edad` guarda un solo número. + +¿Pero qué pasa si queremos guardar los nombres de 10 amigos? ¿Vamos a crear 10 variables diferentes (`amigo1`, `amigo2`, `amigo3`...)? ¡Eso sería muy aburrido y difícil de mantener! + +Aquí entran las **Listas**. Una lista es como una caja organizadora con muchos compartimentos. Puedes guardar muchas cosas dentro de una sola variable, y cada cosa tiene su propio espacio ordenado. + +### Ruta de aprendizaje +1. **Teoría**: Qué son los corchetes `[]` y cómo agrupar cosas. +2. **Ejemplo**: Creando nuestro primer inventario de objetos. +3. **Reto**: Organizar la lista de compras de la semana. diff --git a/python/Modulo-03-Estructuras/21-listas/ejemplo.py b/python/Modulo-03-Estructuras/21-listas/ejemplo.py new file mode 100644 index 0000000..6306a40 --- /dev/null +++ b/python/Modulo-03-Estructuras/21-listas/ejemplo.py @@ -0,0 +1,44 @@ +# ========================================== +# NIVEL 21: LISTAS - EJEMPLO PRÁCTICO +# ========================================== + +print("--- 1. La caja de un solo espacio vs La caja organizadora ---") + +# Hasta ahora, hacíamos esto (variables sueltas): +personaje_principal = "Héroe" +personaje_secundario = "Mago" +personaje_terciario = "Arquero" + +# ¡Imagina hacer eso para 100 personajes! + +# Ahora, usamos una LISTA (caja organizadora): +# Fíjate en los corchetes [ ] y las comas , +equipo_rpg = ["Héroe", "Mago", "Arquero"] + +print("Mi equipo es:") +print(equipo_rpg) + + +print("\n--- 2. Listas de números ---") +# También podemos hacer listas de números, sin comillas (porque son números) +edades_del_equipo = [25, 120, 34] + +print("Edades de los miembros:") +print(edades_del_equipo) + + +print("\n--- 3. Una lista vacía ---") +# Podemos crear una lista que no tenga nada dentro todavía. +# Es como comprar una caja organizadora vacía para llenarla después. +mochila_vacia = [] + +print("Mi mochila al inicio de la aventura:") +print(mochila_vacia) + + +print("\n--- 4. Mezclando tipos de datos ---") +# Una lista puede tener textos, números y booleanos al mismo tiempo +ficha_de_personaje = ["Héroe", 25, 1.80, True] + +print("Ficha completa del héroe (Nombre, Edad, Altura, ¿Está vivo?):") +print(ficha_de_personaje) diff --git a/python/Modulo-03-Estructuras/21-listas/reto.md b/python/Modulo-03-Estructuras/21-listas/reto.md new file mode 100644 index 0000000..4c0985f --- /dev/null +++ b/python/Modulo-03-Estructuras/21-listas/reto.md @@ -0,0 +1,38 @@ +# Reto 21: El Inventario del Comerciante 💰 + +¡Bienvenido a tu nueva tienda! Antes de poder vender, necesitas organizar tus productos. Has estado usando variables sueltas para todo, pero tu tienda ha crecido y necesitas usar listas. + +## Instrucciones + +1. Crea un nuevo archivo llamado `reto.py`. +2. Crea una variable llamada `productos_magicos` que sea una **lista** y contenga 3 textos: `"Poción"`, `"Espada"`, `"Escudo"`. +3. Crea otra variable llamada `precios` que sea una **lista** y contenga 3 números (los precios de los productos en monedas de oro): `15`, `150`, `45`. +4. Crea una variable llamada `tienda_abierta` que sea una **lista** con un solo elemento booleano: `True`. (¡Sí, las listas pueden tener un solo elemento!). +5. Imprime en pantalla un mensaje de bienvenida y luego imprime tus tres listas usando `print()`. + +### Conceptos Permitidos +- Variables (asignación con `=`). +- Textos (Strings con `""`), Números (Int/Float) y Booleanos (`True`/`False`). +- Listas (Corchetes `[]` y comas `,`). +- La función `print()`. + +### Conceptos PROHIBIDOS +- Acceder a posiciones específicas de la lista (ej. `lista[0]`). Esto lo veremos después. +- Añadir o quitar cosas de la lista (ej. `append()`, `remove()`). +- Ciclos (`for`, `while`). + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto (¡puedes cambiar el texto de bienvenida si quieres!): + +```text +¡Bienvenido a la Tienda de Aventuras! +Nuestro catálogo de productos es: +['Poción', 'Espada', 'Escudo'] +Los precios respectivos son: +[15, 150, 45] +¿La tienda está abierta? +[True] +``` + +¡Mucho éxito, comerciante! diff --git a/python/Modulo-03-Estructuras/21-listas/teoria.md b/python/Modulo-03-Estructuras/21-listas/teoria.md new file mode 100644 index 0000000..d67e630 --- /dev/null +++ b/python/Modulo-03-Estructuras/21-listas/teoria.md @@ -0,0 +1,45 @@ +# Teoría: La anatomía de una Lista + +Para decirle a la computadora que queremos crear una caja con múltiples compartimentos (una lista), usamos un símbolo nuevo: **los corchetes `[` y `]`**. + +Todo lo que pongamos dentro de los corchetes pertenecerá a la lista, y separamos cada elemento con una **coma `,`**. + +## Anatomía + +```python +mi_lista = ["Manzana", "Pera", "Plátano"] +``` + +Desmontemos esto símbolo por símbolo: +- `mi_lista`: Es el nombre de nuestra variable (la etiqueta de la caja organizadora). +- `=`: El símbolo de asignación. Le dice a la computadora: "guarda lo que está a la derecha dentro de la variable de la izquierda". +- `[`: Abre la caja. Significa "aquí empieza una lista". +- `"Manzana"`: Nuestro primer elemento (un texto, por eso lleva comillas). +- `,`: La coma separa los compartimentos. Significa "aquí termina un elemento y empieza el siguiente". +- `"Pera"`: Segundo elemento. +- `,`: Otra separación. +- `"Plátano"`: Tercer elemento. +- `]`: Cierra la caja. Significa "aquí termina la lista". + +## Mezclando cosas +¡Las listas no son quisquillosas! Puedes guardar textos, números y booleanos en la misma lista, aunque por orden, solemos agrupar cosas del mismo tipo. +```python +cosas_random = [42, "Hola", True] +``` + +## ¿Qué pasa si me equivoco? + +**El error más común:** Olvidar una coma entre elementos. +Si escribes: +```python +frutas = ["Manzana" "Pera"] +``` +La terminal se va a confundir y podría mostrar un error de sintaxis (`SyntaxError: invalid syntax`) o juntar los textos de forma extraña. ¡La computadora necesita la coma para saber dónde termina una cosa y empieza la otra! + +**Otro error común:** Olvidar cerrar los corchetes `]`. +Si dejas la lista abierta: +```python +frutas = ["Manzana", "Pera" +print("Hola") +``` +La terminal te dará un error `SyntaxError: unexpected EOF while parsing` (fin de archivo inesperado). Significa que la computadora se quedó esperando a que cerraras la caja y nunca lo hiciste. diff --git a/python/Modulo-03-Estructuras/21-listas/test_main.py b/python/Modulo-03-Estructuras/21-listas/test_main.py new file mode 100644 index 0000000..ca96c9f --- /dev/null +++ b/python/Modulo-03-Estructuras/21-listas/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "Poción" in r.stdout and "Espada" in r.stdout diff --git a/retos/nerve/04-vigilante-de-carpetas/.gitkeep b/python/Modulo-03-Estructuras/22-metodos-lista/.gitkeep similarity index 100% rename from retos/nerve/04-vigilante-de-carpetas/.gitkeep rename to python/Modulo-03-Estructuras/22-metodos-lista/.gitkeep diff --git a/python/Modulo-03-Estructuras/22-metodos-lista/README.md b/python/Modulo-03-Estructuras/22-metodos-lista/README.md new file mode 100644 index 0000000..4cffd7a --- /dev/null +++ b/python/Modulo-03-Estructuras/22-metodos-lista/README.md @@ -0,0 +1,12 @@ +# Nivel 22: Modificando la Caja (Métodos de Lista) 🛠️ + +En el nivel anterior aprendimos a crear una lista (nuestra caja organizadora). Pero una caja donde no puedes meter cosas nuevas ni sacar las que ya no necesitas no es muy útil, ¿verdad? + +En la vida real, tu mochila cambia: metes un libro nuevo, sacas una manzana para comerla. En Python, hacemos esto usando **Métodos**. + +Los métodos son como pequeños "botones" o "comandos secretos" que tienen las listas para modificarse a sí mismas. Hoy aprenderemos los dos botones más importantes: el botón de "agregar" y el botón de "quitar". + +### Ruta de aprendizaje +1. **Teoría**: El punto mágico `.` y cómo dar órdenes a una lista. +2. **Ejemplo**: Gestionando los pasajeros de un autobús. +3. **Reto**: ¡El menú del restaurante! diff --git a/python/Modulo-03-Estructuras/22-metodos-lista/ejemplo.py b/python/Modulo-03-Estructuras/22-metodos-lista/ejemplo.py new file mode 100644 index 0000000..e5a4c08 --- /dev/null +++ b/python/Modulo-03-Estructuras/22-metodos-lista/ejemplo.py @@ -0,0 +1,42 @@ +# ========================================== +# NIVEL 22: MÉTODOS DE LISTA - EJEMPLO PRÁCTICO +# ========================================== + +print("--- 1. El inicio del viaje ---") +# Creamos nuestra lista inicial (nuestro autobús) +autobus = ["Conductor", "Señora mayor"] + +print("Pasajeros actuales:") +print(autobus) + + +print("\n--- 2. Suben nuevos pasajeros (append) ---") +# Usamos el punto . para darle órdenes a la lista "autobus" +# .append() siempre pone el elemento al FINAL de la lista + +autobus.append("Estudiante") +print("Se subió un estudiante. Pasajeros:") +print(autobus) + +autobus.append("Músico") +print("Se subió un músico. Pasajeros:") +print(autobus) + + +print("\n--- 3. Bajan pasajeros (remove) ---") +# Usamos .remove() para decirle exactamente a quién queremos sacar +# La computadora buscará esa palabra exacta y la borrará. + +autobus.remove("Señora mayor") +print("La señora mayor llegó a su destino. Pasajeros:") +print(autobus) + + +print("\n--- 4. Cuidado con las mayúsculas ---") +# Si intentamos borrar "estudiante" (en minúscula), nos dará error +# porque en la lista está como "Estudiante" (con E mayúscula). +# ¡La computadora es muy estricta con esto! + +autobus.remove("Estudiante") +print("El estudiante se bajó en la escuela. Pasajeros:") +print(autobus) diff --git a/python/Modulo-03-Estructuras/22-metodos-lista/reto.md b/python/Modulo-03-Estructuras/22-metodos-lista/reto.md new file mode 100644 index 0000000..aa5fb23 --- /dev/null +++ b/python/Modulo-03-Estructuras/22-metodos-lista/reto.md @@ -0,0 +1,37 @@ +# Reto 22: El Menú del Restaurante 🍔 + +Eres el chef de un restaurante y estás diseñando el menú del día usando Python. Sin embargo, los ingredientes se acaban y hay que actualizar la pizarra de platos. + +## Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Crea una lista vacía llamada `menu_del_dia`. +3. Imprime un texto que diga `"Menú inicial:"` y debajo imprime la lista. +4. Usa `.append()` tres veces seguidas para agregar estos platos al menú, en este orden: `"Sopa"`, `"Ensalada"`, `"Pasta"`. +5. Imprime un texto que diga `"Menú actualizado:"` y debajo imprime la lista. +6. ¡Oh no! Se acabó la lechuga. Usa `.remove()` para quitar la `"Ensalada"` del menú. +7. Imprime un texto que diga `"Sin ensalada:"` y debajo imprime tu lista una vez más para ver cómo quedó. + +### Conceptos Permitidos +- Listas vacías (`[]`). +- Métodos `.append()` y `.remove()`. +- La función `print()`. + +### Conceptos PROHIBIDOS +- Re-asignar la lista manualmente (ej. `menu_del_dia = ["Sopa", "Pasta"]`). ¡Debes usar `.remove()`! +- Borrar por posición (ej. usar la palabra `del` o `.pop()`). + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto: + +```text +Menú inicial: +[] +Menú actualizado: +['Sopa', 'Ensalada', 'Pasta'] +Sin ensalada: +['Sopa', 'Pasta'] +``` + +¡A cocinar se ha dicho! diff --git a/python/Modulo-03-Estructuras/22-metodos-lista/teoria.md b/python/Modulo-03-Estructuras/22-metodos-lista/teoria.md new file mode 100644 index 0000000..e5cf62a --- /dev/null +++ b/python/Modulo-03-Estructuras/22-metodos-lista/teoria.md @@ -0,0 +1,45 @@ +# Teoría: Órdenes directas a la Lista + +Para modificar una lista que ya existe, usamos un nuevo símbolo: **el punto `.`** + +El punto se coloca justo después del nombre de la variable y significa: "A esta variable, hazle lo siguiente". A las acciones que van después del punto las llamamos **Métodos**. + +## 1. Agregar al final: `.append()` +La palabra "append" significa "adjuntar" o "añadir al final". + +### Anatomía de append +```python +mochila = ["Mapa"] +mochila.append("Poción") +``` +Desmontemos `mochila.append("Poción")`: +- `mochila`: Es a quién le estamos dando la orden (nuestra lista). +- `.`: El "comunicador". Conecta la lista con la orden. +- `append`: La orden en sí ("añade al final"). +- `()`: Los paréntesis son como una caja de envío. Aquí ponemos lo que necesita la orden para funcionar. +- `"Poción"`: Lo que estamos enviando para que se guarde en la lista. + +## 2. Quitar por nombre: `.remove()` +La palabra "remove" significa "remover" o "quitar". Busca exactamente lo que le pidas y saca el **primero** que encuentre. + +### Anatomía de remove +```python +mochila.remove("Mapa") +``` +Funciona igual: "A la mochila (`mochila`), aplícale la orden de quitar (`.remove`), y aquí te envío lo que quiero que quites (`("Mapa")`)". + +## ¿Qué pasa si me equivoco? + +**Error común 1: Olvidar los paréntesis `()`** +Si escribes: +```python +mochila.append +``` +La computadora dirá "Ok, veo que quieres usar append, pero no lo estás ejecutando". ¡Los paréntesis son los que obligan a la orden a ejecutarse! + +**Error común 2: Intentar quitar algo que no existe** +Si escribes: +```python +mochila.remove("Dragón") +``` +Pero `"Dragón"` no está en tu mochila, la terminal entrará en pánico y mostrará un error: `ValueError: list.remove(x): x not in list` (El valor no está en la lista). ¡Solo puedes sacar lo que ya está adentro! diff --git a/python/Modulo-03-Estructuras/22-metodos-lista/test_main.py b/python/Modulo-03-Estructuras/22-metodos-lista/test_main.py new file mode 100644 index 0000000..763fb45 --- /dev/null +++ b/python/Modulo-03-Estructuras/22-metodos-lista/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.stdout.count("\n") >= 3 diff --git a/retos/nerve/04-vigilante-de-carpetas/soluciones/.gitkeep b/python/Modulo-03-Estructuras/23-slicing/.gitkeep similarity index 100% rename from retos/nerve/04-vigilante-de-carpetas/soluciones/.gitkeep rename to python/Modulo-03-Estructuras/23-slicing/.gitkeep diff --git a/python/Modulo-03-Estructuras/23-slicing/README.md b/python/Modulo-03-Estructuras/23-slicing/README.md new file mode 100644 index 0000000..e7835c9 --- /dev/null +++ b/python/Modulo-03-Estructuras/23-slicing/README.md @@ -0,0 +1,12 @@ +# Nivel 23: Rebanando la Caja (Índices y Slicing) 🍕 + +Nuestra caja organizadora (lista) guarda muchas cosas. Ya sabemos cómo meter más cosas y cómo sacar algo si sabemos exactamente cómo se llama. Pero, ¿qué pasa si queremos sacar "el primer elemento" o "los tres primeros elementos" sin importar qué sean? + +Imagina una rebanada de pizza. A veces no quieres toda la pizza, solo quieres una porción. O imagina una fila de personas: quieres llamar al primero de la fila, al segundo, o a los que están entre el tercero y el quinto. + +Para eso usamos **Índices** (posiciones) y **Slicing** (rebanado). ¡Pero cuidado! Las computadoras cuentan diferente a los humanos. + +### Ruta de aprendizaje +1. **Teoría**: El número cero `0` y el símbolo de los dos puntos `:`. +2. **Ejemplo**: Organizando el podio de una carrera. +3. **Reto**: ¡El vagón de tren secreto! diff --git a/python/Modulo-03-Estructuras/23-slicing/ejemplo.py b/python/Modulo-03-Estructuras/23-slicing/ejemplo.py new file mode 100644 index 0000000..5684780 --- /dev/null +++ b/python/Modulo-03-Estructuras/23-slicing/ejemplo.py @@ -0,0 +1,42 @@ +# ========================================== +# NIVEL 23: ÍNDICES Y SLICING - EJEMPLO PRÁCTICO +# ========================================== + +print("--- 1. El podio de la carrera ---") +# Aquí están los corredores que llegaron a la meta, en orden. +corredores = ["Flash", "Sonic", "Quicksilver", "Dash"] + +print("Todos los corredores:") +print(corredores) + + +print("\n--- 2. Entregando la medalla de oro ---") +# El primer lugar está en la posición CERO [0] +medalla_oro = corredores[0] + +print("La medalla de oro es para:") +print(medalla_oro) + +print("La medalla de plata (posición 1) es para:") +print(corredores[1]) + + +print("\n--- 3. Los mejores 3 (Slicing) ---") +# Queremos un "pedazo" de la lista con el top 3. +# Empezamos en 0 y terminamos ANTES del 3 (es decir, posiciones 0, 1 y 2). +# Usamos los dos puntos : +top_tres = corredores[0:3] + +print("Los corredores en el podio son:") +print(top_tres) + + +print("\n--- 4. Atajos del Slicing ---") +# Si empiezas desde el principio, puedes dejar el espacio antes del : vacío. +# [:2] es lo mismo que decir "desde el inicio hasta antes del 2". +# Si quieres desde una posición hasta el final, dejas el segundo vacío. +# [2:] es lo mismo que decir "desde el 2 hasta el final". + +los_ultimos = corredores[2:] +print("Los que no ganaron medalla:") +print(los_ultimos) diff --git a/python/Modulo-03-Estructuras/23-slicing/reto.md b/python/Modulo-03-Estructuras/23-slicing/reto.md new file mode 100644 index 0000000..5f597d5 --- /dev/null +++ b/python/Modulo-03-Estructuras/23-slicing/reto.md @@ -0,0 +1,36 @@ +# Reto 23: El Tren de Carga 🚂 + +Eres el conductor de un tren que transporta suministros importantes. Tienes un registro de todos los vagones en orden, pero necesitas extraer información específica para los inspectores. + +## Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Crea una lista llamada `tren` con los siguientes 5 elementos en este orden exacto: + `"Locomotora"`, `"Carbón"`, `"Pasajeros"`, `"Oro"`, `"Madera"`. +3. Imprime un mensaje que diga `"Vagón VIP:"`. +4. Usando el acceso por índice `[]`, extrae e imprime el vagón de los `"Pasajeros"`. (¡Recuerda contar desde cero!). +5. Imprime un mensaje que diga `"Carga preciosa:"`. +6. Usando slicing `[:]`, extrae e imprime una nueva lista que contenga SOLO `"Pasajeros"` y `"Oro"`. + +### Conceptos Permitidos +- Listas y asignación de variables. +- Acceso por índice (ej. `lista[2]`). +- Rebanado o Slicing (ej. `lista[1:4]`). +- Función `print()`. + +### Conceptos PROHIBIDOS +- Escribir manualmente la lista filtrada (ej. `print(["Pasajeros", "Oro"])`). ¡Debes extraerlos de la variable `tren` usando corchetes! +- Usar `.remove()` o `.append()`. Todo se hace leyendo y cortando la lista original. + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto: + +```text +Vagón VIP: +Pasajeros +Carga preciosa: +['Pasajeros', 'Oro'] +``` + +¡Cuidado con no caerte de los vagones! diff --git a/python/Modulo-03-Estructuras/23-slicing/teoria.md b/python/Modulo-03-Estructuras/23-slicing/teoria.md new file mode 100644 index 0000000..7e3577c --- /dev/null +++ b/python/Modulo-03-Estructuras/23-slicing/teoria.md @@ -0,0 +1,35 @@ +# Teoría: Las Computadoras Cuentan desde Cero + +Para pedirle a una lista un elemento específico, usamos de nuevo los **corchetes `[]`**, pero esta vez los pegamos al nombre de la lista, y adentro ponemos un número (la posición). + +**LA REGLA DE ORO:** ¡Las computadoras empiezan a contar desde el `0`, no desde el `1`! +- El 1er elemento es el `0`. +- El 2do elemento es el `1`. +- El 3er elemento es el `2`. + +## 1. Acceso por Índice (Posición) +```python +amigos = ["Ana", "Beto", "Carlos"] +primer_amigo = amigos[0] +``` +- `amigos`: La lista. +- `[0]`: Significa "dame el elemento en la posición 0" (el primero, que es "Ana"). + +## 2. Rebanado (Slicing) con `:` +Si queremos más de un elemento, usamos el símbolo de **dos puntos `:`** dentro de los corchetes. Significa "desde aquí HASTA aquí". + +```python +grupo = amigos[0:2] +``` +- `[0:2]`: Significa "córtame la lista empezando en la posición `0` y detente ANTES de llegar a la posición `2`". +- Por lo tanto, nos dará el `0` ("Ana") y el `1` ("Beto"), pero NO el `2`. Es como decir "hasta la puerta del 2, pero sin entrar". + +## ¿Qué pasa si me equivoco? + +**El error más común:** Pedir una posición que no existe. +Si tu lista tiene 3 cosas, las posiciones son `0`, `1` y `2`. Si pides la posición `3`: +```python +amigos = ["Ana", "Beto", "Carlos"] +print(amigos[3]) +``` +La terminal explotará con un error: `IndexError: list index out of range` (Índice de lista fuera de rango). Significa: "¡Tu caja no tiene un compartimento número 3!". diff --git a/python/Modulo-03-Estructuras/23-slicing/test_main.py b/python/Modulo-03-Estructuras/23-slicing/test_main.py new file mode 100644 index 0000000..20f0515 --- /dev/null +++ b/python/Modulo-03-Estructuras/23-slicing/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.stdout.strip() != "" diff --git a/retos/nerve/05-logger-distribuido/.gitkeep b/python/Modulo-03-Estructuras/24-tuplas/.gitkeep similarity index 100% rename from retos/nerve/05-logger-distribuido/.gitkeep rename to python/Modulo-03-Estructuras/24-tuplas/.gitkeep diff --git a/python/Modulo-03-Estructuras/24-tuplas/README.md b/python/Modulo-03-Estructuras/24-tuplas/README.md new file mode 100644 index 0000000..db27843 --- /dev/null +++ b/python/Modulo-03-Estructuras/24-tuplas/README.md @@ -0,0 +1,12 @@ +# Nivel 24: La Caja Fuerte (Tuplas) 🔒 + +Ya somos expertos usando Listas: podemos crear cajas con muchos compartimentos, meter cosas nuevas (`.append()`) y sacar cosas (`.remove()`). + +Pero, ¿qué pasa si queremos guardar algo tan importante que **nadie, nunca, pueda modificarlo** por error? Imagina las coordenadas de tu casa, los días de la semana o tu fecha de nacimiento. Esos datos no deberían cambiar. + +Para esto existen las **Tuplas**. Una tupla es exactamente igual a una lista, pero con un candado irrompible: una vez que la creas, **no puedes agregar, ni quitar, ni modificar nada**. Es una "caja fuerte" de información. + +### Ruta de aprendizaje +1. **Teoría**: Los paréntesis `()` y el superpoder de la inmutabilidad. +2. **Ejemplo**: Guardando coordenadas GPS que no pueden cambiar. +3. **Reto**: ¡El cofre del tesoro sellado! diff --git a/python/Modulo-03-Estructuras/24-tuplas/ejemplo.py b/python/Modulo-03-Estructuras/24-tuplas/ejemplo.py new file mode 100644 index 0000000..2609b0d --- /dev/null +++ b/python/Modulo-03-Estructuras/24-tuplas/ejemplo.py @@ -0,0 +1,45 @@ +# ========================================== +# NIVEL 24: TUPLAS - EJEMPLO PRÁCTICO +# ========================================== + +print("--- 1. Creando una Tupla (Caja Fuerte) ---") +# Usamos paréntesis ( ) en lugar de corchetes [ ] +coordenadas_casa = (19.43, -99.13) + +print("Las coordenadas de mi casa son:") +print(coordenadas_casa) + + +print("\n--- 2. Leyendo los datos de la tupla ---") +# Para LEER, usamos los corchetes de índice [0], igual que en las listas. +# ¡Los paréntesis solo son para crearla! +latitud = coordenadas_casa[0] +longitud = coordenadas_casa[1] + +print("Latitud exacta:") +print(latitud) +print("Longitud exacta:") +print(longitud) + + +print("\n--- 3. La prueba del candado ---") +print("¿Qué pasa si intentamos añadir algo?") +print("(Esto está comentado para que no explote el programa)") + +# Si le quitamos el símbolo '#' a la siguiente línea, el programa fallará: +# coordenadas_casa.append(10.0) + +print("Si lo intentas, Python dirá: AttributeError (no se puede agregar).") + + +print("\n--- 4. Listas vs Tuplas juntas ---") +# Podemos tener listas que guardan tuplas adentro. +# Piensa en una caja de cartón que adentro tiene pequeñas cajitas fuertes. +ruta_gps = [ + (0.0, 0.0), # Inicio (tupla) + (5.5, 2.1), # Medio (tupla) + (10.0, 9.9) # Fin (tupla) +] + +print("Mi ruta GPS tiene varios puntos sellados:") +print(ruta_gps) diff --git a/python/Modulo-03-Estructuras/24-tuplas/reto.md b/python/Modulo-03-Estructuras/24-tuplas/reto.md new file mode 100644 index 0000000..f0aa7b1 --- /dev/null +++ b/python/Modulo-03-Estructuras/24-tuplas/reto.md @@ -0,0 +1,34 @@ +# Reto 24: El Sistema de Seguridad 🚨 + +Estás programando el sistema de seguridad de un museo. Necesitas guardar el código secreto de la bóveda y la ubicación de las cámaras. ¡Esta información es tan crítica que ningún hacker (ni tú por accidente) debe poder modificarla! + +## Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Crea una variable llamada `codigo_boveda` que sea una **Tupla** con 3 números (por ejemplo: `7`, `4`, `1`). +3. Crea una variable llamada `estado_camaras` que sea una **Tupla** con dos textos: `"Encendidas"`, `"Grabando"`. +4. Imprime el mensaje: `"--- ESTADO DEL SISTEMA ---"`. +5. Imprime la tupla `codigo_boveda`. +6. Extrae e imprime SOLO la primera palabra del estado de las cámaras (el índice `0`) de tu tupla `estado_camaras`. (Agrega un texto que diga `"Estado de cámara 1:"` antes). + +### Conceptos Permitidos +- Tuplas (creación con paréntesis `()`). +- Acceso por índice (lectura con corchetes `[]`). +- La función `print()`. + +### Conceptos PROHIBIDOS +- Usar corchetes `[]` para crear las variables iniciales (eso haría que fueran Listas, ¡y los ladrones podrían modificarlas!). +- Intentar usar `.append()` o `.remove()`. + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto (tus números pueden variar): + +```text +--- ESTADO DEL SISTEMA --- +(7, 4, 1) +Estado de cámara 1: +Encendidas +``` + +¡Felicidades, el museo está a salvo! diff --git a/python/Modulo-03-Estructuras/24-tuplas/teoria.md b/python/Modulo-03-Estructuras/24-tuplas/teoria.md new file mode 100644 index 0000000..5a2c8d4 --- /dev/null +++ b/python/Modulo-03-Estructuras/24-tuplas/teoria.md @@ -0,0 +1,33 @@ +# Teoría: La anatomía de una Tupla + +Para crear una Tupla (nuestra caja fuerte), usamos **los paréntesis `(` y `)`** en lugar de los corchetes `[` y `]`. + +Todo lo demás funciona exactamente igual que las listas: separamos los elementos con comas `,` y podemos acceder a ellos usando el índice (como `tupla[0]`). + +## Anatomía + +```python +dias_semana = ("Lunes", "Martes", "Miércoles") +``` + +Desmontemos la sintaxis: +- `dias_semana`: El nombre de nuestra variable. +- `=`: El símbolo de asignación (guardar en la variable). +- `(`: Abre la caja fuerte. Significa "aquí empieza una tupla". +- `"Lunes", "Martes", "Miércoles"`: Los elementos separados por comas. +- `)`: Cierra y **sella** la caja fuerte. + +## El Superpoder: Inmutabilidad +"Inmutable" es una palabra elegante para decir "no se puede cambiar". +Como la tupla está sellada, **NO** puedes usar `.append()` ni `.remove()`. Tampoco puedes cambiar el valor de un compartimento. + +## ¿Qué pasa si me equivoco? + +**El error más común:** Intentar modificar la tupla. +Si olvidas que estás usando una tupla (paréntesis) e intentas cambiarla como si fuera una lista: +```python +mis_numeros = (1, 2, 3) +mis_numeros.append(4) +``` +La terminal se pondrá roja como una sirena y mostrará: `AttributeError: 'tuple' object has no attribute 'append'`. +Traducción humana: "¡Oye, las tuplas no tienen el botón de agregar! ¡Están selladas!". diff --git a/python/Modulo-03-Estructuras/24-tuplas/test_main.py b/python/Modulo-03-Estructuras/24-tuplas/test_main.py new file mode 100644 index 0000000..b00ade4 --- /dev/null +++ b/python/Modulo-03-Estructuras/24-tuplas/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "(" in r.stdout and ")" in r.stdout diff --git a/retos/nerve/05-logger-distribuido/soluciones/.gitkeep b/python/Modulo-03-Estructuras/25-diccionarios/.gitkeep similarity index 100% rename from retos/nerve/05-logger-distribuido/soluciones/.gitkeep rename to python/Modulo-03-Estructuras/25-diccionarios/.gitkeep diff --git a/python/Modulo-03-Estructuras/25-diccionarios/README.md b/python/Modulo-03-Estructuras/25-diccionarios/README.md new file mode 100644 index 0000000..92f1540 --- /dev/null +++ b/python/Modulo-03-Estructuras/25-diccionarios/README.md @@ -0,0 +1,12 @@ +# Nivel 25: La Agenda Telefónica (Diccionarios) 📖 + +Ya dominamos las Listas (cajas organizadoras por orden) y las Tuplas (cajas fuertes). Ambas tienen algo en común: para encontrar algo, necesitas saber su **número de posición** (el índice, como `lista[0]`). + +Pero en la vida real, si buscas el teléfono de "Ana" en una agenda, no dices "Ana es mi amigo número 4". Buscas por su nombre: "Ana". + +En Python, para buscar cosas usando una "etiqueta" o "palabra clave" en lugar de un número, usamos los **Diccionarios**. Son como un diccionario real: buscas una palabra (la clave) y encuentras su significado (el valor). + +### Ruta de aprendizaje +1. **Teoría**: Las llaves `{}` y el dúo dinámico "Clave: Valor". +2. **Ejemplo**: Creando el perfil de un jugador. +3. **Reto**: ¡El catálogo de traducciones! diff --git a/python/Modulo-03-Estructuras/25-diccionarios/ejemplo.py b/python/Modulo-03-Estructuras/25-diccionarios/ejemplo.py new file mode 100644 index 0000000..f005ec3 --- /dev/null +++ b/python/Modulo-03-Estructuras/25-diccionarios/ejemplo.py @@ -0,0 +1,44 @@ +# ========================================== +# NIVEL 25: DICCIONARIOS - EJEMPLO PRÁCTICO +# ========================================== + +print("--- 1. Creando nuestro primer Diccionario ---") +# Usamos llaves { } y parejas separadas por dos puntos : +perfil_jugador = { + "nombre": "Arthur", + "nivel": 42, + "esta_vivo": True +} + +print("El perfil completo del jugador es:") +print(perfil_jugador) + + +print("\n--- 2. Buscando datos específicos ---") +# En vez de usar números [0], usamos el nombre de la clave ["nombre"] +nombre_actual = perfil_jugador["nombre"] +nivel_actual = perfil_jugador["nivel"] + +print("Buscando en los registros...") +print("El jugador se llama:") +print(nombre_actual) +print("Su nivel de poder es:") +print(nivel_actual) + + +print("\n--- 3. Modificando un valor ---") +# Si Arthur sube de nivel, buscamos la clave y le asignamos un nuevo valor +print("¡Arthur ha ganado experiencia!") + +perfil_jugador["nivel"] = 43 + +print("El nuevo nivel de Arthur es:") +print(perfil_jugador["nivel"]) + + +print("\n--- 4. Agregando una nueva pareja ---") +# Si la clave NO existe y le asignamos un valor, Python la crea automáticamente +perfil_jugador["arma"] = "Espada Mágica" + +print("Se ha añadido un arma al perfil:") +print(perfil_jugador) diff --git a/python/Modulo-03-Estructuras/25-diccionarios/reto.md b/python/Modulo-03-Estructuras/25-diccionarios/reto.md new file mode 100644 index 0000000..57a4f9c --- /dev/null +++ b/python/Modulo-03-Estructuras/25-diccionarios/reto.md @@ -0,0 +1,38 @@ +# Reto 25: El Traductor Alienígena 👽 + +Has hecho contacto con una especie alienígena. Para poder comunicarte, necesitas crear un pequeño diccionario traductor de su idioma al español. + +## Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Crea una variable llamada `traductor` que sea un **Diccionario**. +3. Añade tres parejas (Clave: Valor) a tu diccionario. Las claves serán las palabras alienígenas (textos) y los valores su significado en español (textos): + - `"Blarg"` significa `"Hola"` + - `"Flib"` significa `"Adiós"` + - `"Grox"` significa `"Comida"` +4. Imprime el mensaje: `"--- INICIANDO TRADUCTOR ---"`. +5. Extrae el significado de la palabra `"Blarg"` usando corchetes (ej. `traductor["palabra"]`) y guárdalo en una variable llamada `saludo`. +6. Imprime la variable `saludo`. +7. Los alienígenas te han enseñado una nueva palabra. Añade al diccionario la clave `"Zorp"` con el valor `"Paz"`. (Hazlo en una nueva línea, no modificando la variable original inicial). +8. Imprime el diccionario `traductor` completo para ver la nueva palabra agregada. + +### Conceptos Permitidos +- Diccionarios (creación con llaves `{}` y `:`). +- Acceso y modificación usando corchetes con el nombre de la clave `["clave"]`. +- La función `print()`. + +### Conceptos PROHIBIDOS +- Usar índices numéricos (`traductor[0]`). ¡Los diccionarios no tienen orden numérico, tienen etiquetas! +- Usar métodos avanzados que no hemos visto como `.get()` o `.update()`. + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto: + +```text +--- INICIANDO TRADUCTOR --- +Hola +{'Blarg': 'Hola', 'Flib': 'Adiós', 'Grox': 'Comida', 'Zorp': 'Paz'} +``` + +¡Excelente trabajo de diplomacia intergaláctica! diff --git a/python/Modulo-03-Estructuras/25-diccionarios/teoria.md b/python/Modulo-03-Estructuras/25-diccionarios/teoria.md new file mode 100644 index 0000000..16e3138 --- /dev/null +++ b/python/Modulo-03-Estructuras/25-diccionarios/teoria.md @@ -0,0 +1,45 @@ +# Teoría: Claves y Valores + +Para crear un Diccionario, usamos un nuevo símbolo: **las llaves `{` y `}`**. + +Adentro de las llaves, la información siempre viaja en parejas. A esta pareja la llamamos **Clave y Valor**. +- La **Clave** es la etiqueta que usas para buscar (como la palabra en un diccionario real). +- El **Valor** es lo que está guardado ahí (como el significado de esa palabra). + +Para separar la Clave del Valor, usamos **dos puntos `:`**. Para separar una pareja de otra, usamos la **coma `,`**. + +## Anatomía + +```python +agenda = {"Ana": 5551234, "Beto": 5559876} +``` + +Desmontemos la sintaxis: +- `agenda`: El nombre de la variable. +- `=`: Símbolo de asignación. +- `{`: Abre el diccionario. +- `"Ana"`: La **Clave** (siempre suele ser un texto). +- `:`: Conecta la Clave con su Valor. Significa "le corresponde". +- `5551234`: El **Valor** (puede ser texto, número, booleano...). +- `,`: Separa la pareja de Ana de la pareja de Beto. +- `}`: Cierra el diccionario. + +## ¿Cómo busco algo? +En las listas usábamos números: `lista[0]`. En los diccionarios, usamos el nombre de la clave. +```python +numero_de_ana = agenda["Ana"] +``` + +## ¿Qué pasa si me equivoco? + +**El error más común:** Buscar una etiqueta que no existe. +Si escribes: +```python +agenda = {"Ana": 5551234} +print(agenda["Carlos"]) +``` +La computadora entrará en pánico y mostrará: `KeyError: 'Carlos'`. +Significa "Error de Clave: Carlos no existe en este diccionario". ¡No puedes buscar una palabra que no ha sido escrita en tu libro! + +**Otro error común:** Confundir `:` con `=`. +Al crear el diccionario, las parejas se unen con `:`. Si usas `=` adentro de las llaves, la terminal mostrará un error de sintaxis (`SyntaxError`). diff --git a/python/Modulo-03-Estructuras/25-diccionarios/test_main.py b/python/Modulo-03-Estructuras/25-diccionarios/test_main.py new file mode 100644 index 0000000..2ebb6b9 --- /dev/null +++ b/python/Modulo-03-Estructuras/25-diccionarios/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "{" in r.stdout diff --git a/retos/nerve/06-puente-de-progreso-gif/.gitkeep b/python/Modulo-03-Estructuras/26-sets/.gitkeep similarity index 100% rename from retos/nerve/06-puente-de-progreso-gif/.gitkeep rename to python/Modulo-03-Estructuras/26-sets/.gitkeep diff --git a/python/Modulo-03-Estructuras/26-sets/README.md b/python/Modulo-03-Estructuras/26-sets/README.md new file mode 100644 index 0000000..b8d0880 --- /dev/null +++ b/python/Modulo-03-Estructuras/26-sets/README.md @@ -0,0 +1,12 @@ +# Nivel 26: El Club Exclusivo (Sets / Conjuntos) 🎟️ + +Imagina que estás organizando una fiesta muy exclusiva. Tienes una lista de invitados, pero algunas personas, de tan emocionadas que estaban, se registraron tres veces. + +Si usamos una **Lista** normal, tendríamos nombres repetidos y compraríamos más comida de la necesaria. Necesitamos una forma de decirle a Python: "Guarda a estas personas, pero **elimina a los repetidos automáticamente**". + +Para eso existen los **Sets** (Conjuntos). Un Set es como la entrada a un club VIP: no importa cuántas veces intentes entrar con el mismo nombre, el guardia (Python) solo te contará una vez. Además, en la pista de baile (el Set), nadie tiene un asiento fijo (no hay orden). + +### Ruta de aprendizaje +1. **Teoría**: Las llaves `{}` sin parejas y la magia de los elementos únicos. +2. **Ejemplo**: Limpiando una lista de elementos duplicados. +3. **Reto**: ¡La colección de estampas! diff --git a/python/Modulo-03-Estructuras/26-sets/ejemplo.py b/python/Modulo-03-Estructuras/26-sets/ejemplo.py new file mode 100644 index 0000000..269d70c --- /dev/null +++ b/python/Modulo-03-Estructuras/26-sets/ejemplo.py @@ -0,0 +1,42 @@ +# ========================================== +# NIVEL 26: SETS (CONJUNTOS) - EJEMPLO PRÁCTICO +# ========================================== + +print("--- 1. Creando un Set ---") +# Usamos llaves { } pero SIN los dos puntos : +sabores_helado = {"Vainilla", "Chocolate", "Fresa"} + +print("Los sabores disponibles son:") +print(sabores_helado) + + +print("\n--- 2. El destructor de clones ---") +# Vamos a intentar engañar al Set metiendo "Chocolate" muchas veces. +sabores_repetidos = {"Vainilla", "Chocolate", "Chocolate", "Chocolate", "Fresa"} + +print("Intenté meter Chocolate tres veces, pero el Set dice:") +# ¡Solo mostrará un Chocolate! +print(sabores_repetidos) + + +print("\n--- 3. Añadiendo elementos al club (.add) ---") +# Para las listas usábamos .append() +# Para los Sets usamos .add() (que significa "añadir" en inglés) +sabores_helado.add("Menta") +print("Añadimos Menta:") +print(sabores_helado) + +# Si intentamos añadir algo que ya existe, no pasa nada, no da error, solo lo ignora. +sabores_helado.add("Vainilla") +print("Intentamos añadir Vainilla de nuevo (ya estaba):") +print(sabores_helado) + + +print("\n--- 4. Quitando elementos (.remove) ---") +# .remove() funciona igual que en las listas +sabores_helado.remove("Fresa") +print("Se acabó la Fresa:") +print(sabores_helado) + +# IMPORTANTE: Nota cómo al imprimir a veces el orden cambia. +# ¡Los Sets no garantizan que el orden se mantenga! diff --git a/python/Modulo-03-Estructuras/26-sets/reto.md b/python/Modulo-03-Estructuras/26-sets/reto.md new file mode 100644 index 0000000..e646a9c --- /dev/null +++ b/python/Modulo-03-Estructuras/26-sets/reto.md @@ -0,0 +1,37 @@ +# Reto 26: La Colección de Estampas 🃏 + +Estás coleccionando estampas (cromos/cartas) de tus personajes favoritos. Cuando compraste un paquete nuevo, te salieron algunas estampas repetidas. ¡Necesitas organizar tu colección para saber exactamente cuántas estampas ÚNICAS tienes! + +## Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Crea una variable llamada `mis_estampas` que sea un **Set** (usando llaves `{}`). +3. Dentro del Set, escribe estos nombres exactamente, ¡con todo y repetidos!: + `"Batman"`, `"Spiderman"`, `"Batman"`, `"Superman"`, `"Spiderman"` +4. Imprime el mensaje: `"Mi colección sin repetidas:"`. +5. Imprime tu Set `mis_estampas`. (Verás que Python eliminó a los clones por ti). +6. Te acaban de regalar una nueva estampa. Usa el método `.add()` para agregar `"Wonder Woman"` a tu colección. +7. Imprime el mensaje: `"Nueva colección:"` y vuelve a imprimir tu Set. + +### Conceptos Permitidos +- Sets (creación con llaves `{}`). +- El método `.add("elemento")` para agregar. +- La función `print()`. + +### Conceptos PROHIBIDOS +- Usar corchetes `[]` para crear la colección (eso haría que fuera una Lista, ¡y guardaría los repetidos!). +- Intentar leer una estampa usando posiciones numéricas (`mis_estampas[0]`). +- Usar el método `.append()` (ese solo funciona en Listas, en Sets es `.add()`). + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto (¡Ojo! El orden de las palabras puede salir revuelto en tu pantalla, ¡y eso es normal en los Sets!): + +```text +Mi colección sin repetidas: +{'Batman', 'Superman', 'Spiderman'} +Nueva colección: +{'Batman', 'Superman', 'Spiderman', 'Wonder Woman'} +``` + +¡Excelente trabajo organizando tu colección! diff --git a/python/Modulo-03-Estructuras/26-sets/teoria.md b/python/Modulo-03-Estructuras/26-sets/teoria.md new file mode 100644 index 0000000..20d7f7b --- /dev/null +++ b/python/Modulo-03-Estructuras/26-sets/teoria.md @@ -0,0 +1,43 @@ +# Teoría: Únicos y Desordenados + +Para crear un Set (Conjunto), usamos **las llaves `{` y `}`**, ¡igual que los diccionarios! +¿Cómo sabe Python cuál es cuál? Fácil: los diccionarios tienen el símbolo de dos puntos `:` (clave: valor), mientras que los Sets solo tienen elementos sueltos separados por comas, como una lista, pero con llaves. + +## Anatomía + +```python +invitados = {"Ana", "Beto", "Carlos"} +``` + +Desmontemos la sintaxis: +- `invitados`: El nombre de la variable. +- `=`: Símbolo de asignación. +- `{`: Abre el club exclusivo (el Set). +- `"Ana", "Beto", "Carlos"`: Los elementos separados por comas. **NO** hay dos puntos `:`. +- `}`: Cierra el club. + +## Las Dos Reglas de Oro de los Sets + +1. **Anti-Clones (Valores Únicos):** Si intentas meter dos cosas iguales, el Set destruirá el clon en silencio. + ```python + numeros = {1, 1, 1, 2, 3} + # La computadora solo guarda: {1, 2, 3} + ``` +2. **Sin Asientos Fijos (Desordenados):** Los Sets no tienen posiciones. **NO** puedes hacer `invitados[0]`. Si lo intentas, habrá un error. Cuando los imprimes en la terminal, los elementos pueden salir en un orden distinto al que los escribiste. + +## ¿Qué pasa si me equivoco? + +**El error más común:** Intentar buscar por posición (`[0]`). +Si escribes: +```python +mi_set = {"Rojo", "Azul"} +print(mi_set[0]) +``` +La terminal mostrará: `TypeError: 'set' object is not subscriptable`. +Significa: "Los Sets no tienen orden, así que no existe 'el primer elemento'. ¡No puedes usar corchetes aquí!". + +**Otro error común:** Usar el comando equivocado para agregar cosas. +Acuérdate que para las Listas usábamos `.append()`. Para los Sets, ese botón no existe. Si quieres agregar algo a un Set, debes usar el botón **`.add()`** (que significa "añadir"). +```python +mi_set.add("Verde") +``` diff --git a/python/Modulo-03-Estructuras/26-sets/test_main.py b/python/Modulo-03-Estructuras/26-sets/test_main.py new file mode 100644 index 0000000..2ebb6b9 --- /dev/null +++ b/python/Modulo-03-Estructuras/26-sets/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "{" in r.stdout diff --git a/retos/nerve/06-puente-de-progreso-gif/soluciones/.gitkeep b/python/Modulo-03-Estructuras/27-estructuras-anidadas/.gitkeep similarity index 100% rename from retos/nerve/06-puente-de-progreso-gif/soluciones/.gitkeep rename to python/Modulo-03-Estructuras/27-estructuras-anidadas/.gitkeep diff --git a/python/Modulo-03-Estructuras/27-estructuras-anidadas/README.md b/python/Modulo-03-Estructuras/27-estructuras-anidadas/README.md new file mode 100644 index 0000000..22e8105 --- /dev/null +++ b/python/Modulo-03-Estructuras/27-estructuras-anidadas/README.md @@ -0,0 +1,12 @@ +# Nivel 27: Cajas dentro de Cajas (Estructuras Anidadas) 🪆 + +Hasta ahora hemos usado cajas organizadoras (Listas) y agendas telefónicas (Diccionarios) para guardar textos o números. Pero, ¿y si queremos guardar una caja *adentro* de otra caja? + +Imagina las famosas muñecas rusas (Matrioshkas), donde abres una y hay otra más pequeña adentro. En programación, a esto le llamamos **Anidamiento** (de "nido"). + +Podemos tener una lista llena de listas (como un archivero con muchos cajones), o una lista de diccionarios (como un catálogo de perfiles de jugadores). ¡Esto nos permite crear estructuras de datos súper poderosas para representar el mundo real! + +### Ruta de aprendizaje +1. **Teoría**: Cómo leer la estructura y el truco de los corchetes dobles `[][]`. +2. **Ejemplo**: Analizando el inventario de una tienda de mascotas. +3. **Reto**: ¡El laberinto de la base de datos secreta! diff --git a/python/Modulo-03-Estructuras/27-estructuras-anidadas/ejemplo.py b/python/Modulo-03-Estructuras/27-estructuras-anidadas/ejemplo.py new file mode 100644 index 0000000..bf5542a --- /dev/null +++ b/python/Modulo-03-Estructuras/27-estructuras-anidadas/ejemplo.py @@ -0,0 +1,51 @@ +# ========================================== +# NIVEL 27: ESTRUCTURAS ANIDADAS - EJEMPLO +# ========================================== + +print("--- 1. El archivero (Lista de Listas) ---") +# Cada cajón (elemento de la lista grande) es otra lista +archivero = [ + ["Factura 1", "Factura 2"], # Cajón 0 + ["Contrato A", "Contrato B"] # Cajón 1 +] + +print("Todo el archivero:") +print(archivero) + +print("Lo que hay en el Cajón 0:") +print(archivero[0]) + +print("Abriendo el Cajón 0 y sacando el primer documento [0][0]:") +documento = archivero[0][0] +print(documento) + + +print("\n--- 2. Base de datos (Lista de Diccionarios) ---") +# Una lista que guarda el perfil (diccionario) de varias mascotas +mascotas = [ + {"nombre": "Firulais", "tipo": "Perro"}, # Mascota 0 + {"nombre": "Mishi", "tipo": "Gato"} # Mascota 1 +] + +print("Nuestra segunda mascota (índice 1) es:") +print(mascotas[1]) + +print("¿Y cómo se llama esa segunda mascota? [1]['nombre']") +nombre_mishi = mascotas[1]["nombre"] +print(nombre_mishi) + + +print("\n--- 3. Diccionarios dentro de Diccionarios ---") +# Un contacto que tiene su dirección guardada como otro diccionario +contacto = { + "nombre": "Tony Stark", + "direccion": { + "ciudad": "Nueva York", + "calle": "Torre Stark" + } +} + +print("¿En qué ciudad vive Tony?") +# Entramos a "direccion", que nos da un diccionario, y luego entramos a "ciudad" +ciudad_tony = contacto["direccion"]["ciudad"] +print(ciudad_tony) diff --git a/python/Modulo-03-Estructuras/27-estructuras-anidadas/reto.md b/python/Modulo-03-Estructuras/27-estructuras-anidadas/reto.md new file mode 100644 index 0000000..78be3f3 --- /dev/null +++ b/python/Modulo-03-Estructuras/27-estructuras-anidadas/reto.md @@ -0,0 +1,41 @@ +# Reto 27: El Hacker de Archivos 🕵️‍♂️ + +Te has infiltrado en la base de datos de una corporación maligna. Han ocultado la contraseña secreta muy profundo dentro de unas estructuras de datos anidadas. ¡Tu misión es extraerla usando tus conocimientos de corchetes dobles! + +## Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Copia EXACTAMENTE esta variable en tu archivo (¡no la modifiques!): + ```python + servidor = [ + {"id": 1, "datos": ["basura", "basura"]}, + {"id": 2, "datos": ["basura", "CONTRASEÑA_SECRETA"]} + ] + ``` +3. Imprime el mensaje: `"--- INICIANDO HACKEO ---"`. +4. Necesitas llegar a `"CONTRASEÑA_SECRETA"`. Fíjate bien: + - `servidor` es una lista. ¿En qué posición numérica está el diccionario que nos importa? + - Una vez en el diccionario, ¿qué clave (`"id"` o `"datos"`) tiene lo que queremos? + - Esa clave nos da otra lista. ¿En qué posición numérica está la contraseña? +5. Extrae el valor usando los accesos anidados (por ejemplo, pegando corchetes uno tras otro: `variable[a]["b"][c]`) y guárdalo en una variable llamada `clave_extraida`. +6. Imprime la variable `clave_extraida`. + +### Conceptos Permitidos +- Variables, Listas (acceso numérico `[0]`) y Diccionarios (acceso por clave `["clave"]`). +- Anidamiento (`[][]`). +- La función `print()`. + +### Conceptos PROHIBIDOS +- Escribir manualmente `print("CONTRASEÑA_SECRETA")`. ¡Tienes que sacarla de la variable `servidor`! +- Modificar la variable `servidor` original. + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto: + +```text +--- INICIANDO HACKEO --- +CONTRASEÑA_SECRETA +``` + +¡Excelente trabajo rompiendo la seguridad! diff --git a/python/Modulo-03-Estructuras/27-estructuras-anidadas/teoria.md b/python/Modulo-03-Estructuras/27-estructuras-anidadas/teoria.md new file mode 100644 index 0000000..62b5192 --- /dev/null +++ b/python/Modulo-03-Estructuras/27-estructuras-anidadas/teoria.md @@ -0,0 +1,43 @@ +# Teoría: El Arte de Anidar + +Cuando metemos una lista dentro de otra lista, o un diccionario dentro de una lista, la estructura crece "hacia adentro". Para acceder a los datos, tenemos que ir abriendo las cajas paso a paso, de afuera hacia adentro. + +## 1. Listas dentro de Listas +Imagina un edificio: primero eliges el piso, y luego la habitación. + +```python +edificio = [ + ["Ana", "Beto"], # Piso 0 (Índice 0) + ["Carlos", "Diana"] # Piso 1 (Índice 1) +] +``` +Si queremos llegar hasta "Diana": +1. Primero entramos al piso 1: `edificio[1]`. Eso nos da la lista `["Carlos", "Diana"]`. +2. Ahora, de esa nueva lista, queremos el elemento en la posición 1: `[1]`. +3. Juntamos todo: `edificio[1][1]`. + +¡Son dos corchetes pegados! El primero abre la caja grande, el segundo abre la caja pequeña. + +## 2. Diccionarios dentro de Listas +Es el formato más usado en el mundo real. Imagina una lista de perfiles de usuarios. + +```python +usuarios = [ + {"nombre": "Goku", "poder": 9000}, + {"nombre": "Vegeta", "poder": 8500} +] +``` +Para ver el poder de Vegeta: +1. Buscamos a Vegeta en la lista (posición 1): `usuarios[1]`. Esto nos da el diccionario `{"nombre": "Vegeta", "poder": 8500}`. +2. Ahora, a ese diccionario le pedimos la clave `"poder"`: `["poder"]`. +3. Todo junto: `usuarios[1]["poder"]`. + +## ¿Qué pasa si me equivoco? + +**El error más común:** Perderse en el laberinto y usar el símbolo equivocado. +Si tienes una lista de diccionarios e intentas buscar por clave directamente en la lista: +```python +print(usuarios["nombre"]) +``` +¡Boom! `TypeError: list indices must be integers or slices, not str`. +Traducción: "Oye, `usuarios` es una Lista. Las Listas solo entienden de números (0, 1, 2...), no entienden de etiquetas como 'nombre'. ¡Primero tienes que entrar a la posición numérica!". diff --git a/python/Modulo-03-Estructuras/27-estructuras-anidadas/test_main.py b/python/Modulo-03-Estructuras/27-estructuras-anidadas/test_main.py new file mode 100644 index 0000000..a90f52f --- /dev/null +++ b/python/Modulo-03-Estructuras/27-estructuras-anidadas/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "CONTRASEÑA_SECRETA" in r.stdout diff --git a/retos/nerve/07-bot-discord-nerve/.gitkeep b/python/Modulo-03-Estructuras/28-mutabilidad/.gitkeep similarity index 100% rename from retos/nerve/07-bot-discord-nerve/.gitkeep rename to python/Modulo-03-Estructuras/28-mutabilidad/.gitkeep diff --git a/python/Modulo-03-Estructuras/28-mutabilidad/README.md b/python/Modulo-03-Estructuras/28-mutabilidad/README.md new file mode 100644 index 0000000..40d085b --- /dev/null +++ b/python/Modulo-03-Estructuras/28-mutabilidad/README.md @@ -0,0 +1,14 @@ +# Nivel 28: El Clon Falso (Mutabilidad) 🧬 + +Llegamos a uno de los conceptos más importantes y "tramposos" de la programación: la **Mutabilidad**. + +La palabra "mutar" significa "cambiar". En Python, hay cajas que se pueden modificar por dentro (como las Listas o los Diccionarios) y cajas que están selladas para siempre (como las Tuplas o los Textos). + +¿Por qué es esto importante? Porque cuando intentas "copiar" una Lista pasándosela a otra variable, ¡Python no crea un clon real! Solo le da dos nombres diferentes a la **misma caja**. Si cambias una, ¡la otra también cambia como por arte de magia! + +Aprender esto te salvará de horas de dolores de cabeza en el futuro. + +### Ruta de aprendizaje +1. **Teoría**: Mutables (Cambiables) vs Inmutables (Sellados) y el efecto "espejo". +2. **Ejemplo**: Compartiendo la misma caja de pizza. +3. **Reto**: ¡El espía que cambió el reporte! diff --git a/python/Modulo-03-Estructuras/28-mutabilidad/ejemplo.py b/python/Modulo-03-Estructuras/28-mutabilidad/ejemplo.py new file mode 100644 index 0000000..2ffc5fe --- /dev/null +++ b/python/Modulo-03-Estructuras/28-mutabilidad/ejemplo.py @@ -0,0 +1,40 @@ +# ========================================== +# NIVEL 28: MUTABILIDAD - EJEMPLO PRÁCTICO +# ========================================== + +print("--- 1. Inmutables: Cada quien su caja ---") +oro_jugador_1 = 100 +oro_jugador_2 = oro_jugador_1 # Parece una copia + +print("Jugador 1 encuentra un tesoro (+50)") +oro_jugador_1 = oro_jugador_1 + 50 + +print("Oro Jugador 1:", oro_jugador_1) +print("Oro Jugador 2:", oro_jugador_2) # Sigue en 100, no se afectó. + + +print("\n--- 2. Mutables: Compartiendo la misma caja ---") +inventario_1 = ["Espada", "Escudo"] +inventario_2 = inventario_1 # ¡ATENCIÓN! No es una copia, es la MISMA caja. + +print("Jugador 2 encuentra una 'Poción' y la guarda en su inventario.") +inventario_2.append("Poción") + +print("¿Qué tiene el Jugador 1 en su inventario?") +print(inventario_1) +# ¡Sorpresa! El inventario 1 también tiene la poción. +# ¡Están compartiendo la misma mochila! + + +print("\n--- 3. ¿Cómo hago una copia real entonces? ---") +# Para hacer un "clon" real de una lista, usamos el rebanado o slicing [:] +# Dejando todo vacío antes y después de los dos puntos :, +# le decimos "corta la lista desde el inicio al final y pon el resultado en una caja NUEVA". + +lista_original = ["A", "B", "C"] +copia_real = lista_original[:] # <- ¡El truco mágico! + +lista_original.append("D") + +print("Lista Original:", lista_original) +print("Copia Real:", copia_real) # Esta sí se mantuvo intacta. diff --git a/python/Modulo-03-Estructuras/28-mutabilidad/reto.md b/python/Modulo-03-Estructuras/28-mutabilidad/reto.md new file mode 100644 index 0000000..76fbe8b --- /dev/null +++ b/python/Modulo-03-Estructuras/28-mutabilidad/reto.md @@ -0,0 +1,40 @@ +# Reto 28: El Espía del FBI 🕵️‍♀️ + +El FBI tiene una lista con los nombres de 3 sospechosos. Un espía novato intentó hacer una copia del archivo para estudiarlo, pero cometió el error de usar el signo `=` directamente. Cuando borró un nombre de su "copia", ¡lo borró de la base de datos central! + +Tu misión es recrear el desastre y luego arreglarlo creando una copia real. + +## Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Crea una variable llamada `sospechosos_fbi` que sea una **Lista** con 3 textos: `"Zorro"`, `"Halcón"`, `"Cuervo"`. +3. Crea otra variable llamada `copia_novato` y asígnale `sospechosos_fbi` (usando solo `=`). +4. Usa `.remove()` en `copia_novato` para eliminar al `"Halcón"`. +5. Imprime el mensaje: `"¡Desastre! La base del FBI ahora es:"`. +6. Imprime `sospechosos_fbi` para ver el desastre (el Halcón ya no está). +7. ¡Reinicia el servidor! Crea de nuevo la variable `sospechosos_fbi` con los 3 nombres originales (`"Zorro"`, `"Halcón"`, `"Cuervo"`). +8. Crea una variable `copia_experto` pero esta vez **haz un clon real** usando el truco del slicing `[:]`. +9. Borra al `"Halcón"` de `copia_experto`. +10. Imprime el mensaje: `"Base de datos segura:"`. +11. Imprime la lista `sospechosos_fbi`. (¡El Halcón debería seguir ahí!). + +### Conceptos Permitidos +- Listas y el método `.remove()`. +- Slicing `[:]` para hacer copias reales. +- La función `print()`. + +### Conceptos PROHIBIDOS +- Escribir las listas modificadas manualmente en el `print`. ¡Todo debe ser a través de las variables! + +## Resultado Esperado en la Terminal + +Al ejecutar tu código, la terminal debería mostrar EXACTAMENTE esto: + +```text +¡Desastre! La base del FBI ahora es: +['Zorro', 'Cuervo'] +Base de datos segura: +['Zorro', 'Halcón', 'Cuervo'] +``` + +¡Excelente trabajo salvando los datos del gobierno! diff --git a/python/Modulo-03-Estructuras/28-mutabilidad/teoria.md b/python/Modulo-03-Estructuras/28-mutabilidad/teoria.md new file mode 100644 index 0000000..94afb3f --- /dev/null +++ b/python/Modulo-03-Estructuras/28-mutabilidad/teoria.md @@ -0,0 +1,35 @@ +# Teoría: Las Dos Caras de la Moneda + +En Python, todo lo que creas cae en una de dos categorías: **Mutable** o **Inmutable**. + +## 1. Los Inmutables (Sellados) +Números, Textos y Tuplas. +Una vez creados, su valor *interno* no puede cambiar. Si tienes una variable con el número `5` y luego le sumas `1`, Python destruye el `5` y crea una nueva caja con el `6`. +- **Intento fallido de cambiar un texto:** No puedes cambiar la primera letra de un texto usando posiciones (como `texto[0] = "H"`). ¡Están sellados! + +## 2. Los Mutables (Cambiables) +Listas, Diccionarios y Sets. +Puedes abrirlos, sacar cosas y meter cosas nuevas. La caja sigue siendo la misma, solo cambia lo de adentro. + +## La Trampa del Igual `=` (El efecto Espejo) +Aquí está el peligro. Cuando haces esto con números (inmutables), todo funciona como esperas: +```python +a = 10 +b = a +a = 20 +# 'b' sigue valiendo 10. ¡Eran cajas separadas! +``` + +¡Pero mira lo que pasa con las Listas (mutables)! +```python +lista_A = [1, 2, 3] +lista_B = lista_A +lista_A.append(4) +``` +Si imprimes `lista_B`, ¡también tendrá el `4`! +¿Por qué? Porque el símbolo `=` en los Mutables **no hace una copia**. Simplemente le pone una "segunda etiqueta" a la misma caja física. `lista_A` y `lista_B` son como dos controles remotos apuntando a la misma televisión. Si uno cambia de canal, el otro también lo ve. + +## ¿Qué pasa si me equivoco? + +**El error más común:** Modificar una lista pensando que es una copia segura. +Si le pasas tu lista de usuarios a una parte del programa para que la "revise", y esa parte decide borrar un usuario... ¡Lo borrará de tu lista original también! Para evitarlo, tienes que aprender a hacer "clones reales". diff --git a/python/Modulo-03-Estructuras/28-mutabilidad/test_main.py b/python/Modulo-03-Estructuras/28-mutabilidad/test_main.py new file mode 100644 index 0000000..4cdae3a --- /dev/null +++ b/python/Modulo-03-Estructuras/28-mutabilidad/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "Halcón" in r.stdout diff --git a/retos/nerve/07-bot-discord-nerve/soluciones/.gitkeep b/python/Modulo-03-Estructuras/29-deepcopy/.gitkeep similarity index 100% rename from retos/nerve/07-bot-discord-nerve/soluciones/.gitkeep rename to python/Modulo-03-Estructuras/29-deepcopy/.gitkeep diff --git a/python/Modulo-03-Estructuras/29-deepcopy/README.md b/python/Modulo-03-Estructuras/29-deepcopy/README.md new file mode 100644 index 0000000..0ffbeca --- /dev/null +++ b/python/Modulo-03-Estructuras/29-deepcopy/README.md @@ -0,0 +1,12 @@ +# Nivel 29: Clonación Profunda (Deepcopy) 🧬 + +En el nivel anterior descubrimos un problema muy peligroso: cuando usamos `[:]` para clonar una lista, funciona bien... **¡hasta que hay listas dentro de listas!** + +Si tienes una caja principal con cajitas adentro, el truco de `[:]` solo compra una caja principal nueva, pero mete las *mismas cajitas originales* adentro. Si modificas una cajita en el clon, se rompe también en la original. + +En la vida real, cuando guardas un documento de Excel que tiene varias hojas, quieres que al hacer "Guardar como..." todo el nuevo archivo sea completamente independiente. Para lograr esto en Python con estructuras anidadas, necesitamos pedir ayuda a una herramienta especial llamada `deepcopy` (clonación profunda). + +## Ruta de aprendizaje +1. **Teoría:** Aprenderemos a traer herramientas externas a nuestro código (`import`) y qué hace exactamente `deepcopy`. +2. **Ejemplo:** Veremos la diferencia en vivo entre un clon falso (superficial) y un clon real (profundo). +3. **Reto:** Clonarás una estación espacial con diferentes compartimentos para asegurar que los daños en el clon no afecten a la estación original. diff --git a/python/Modulo-03-Estructuras/29-deepcopy/ejemplo.py b/python/Modulo-03-Estructuras/29-deepcopy/ejemplo.py new file mode 100644 index 0000000..8acc49a --- /dev/null +++ b/python/Modulo-03-Estructuras/29-deepcopy/ejemplo.py @@ -0,0 +1,39 @@ +# ========================================== +# IMPORTANTE: Los imports siempre van hasta arriba +# ========================================== +# Le decimos a Python: "Trae la caja de herramientas de copiado" +import copy + +print("--- 1. El Problema del Clon Superficial [:] ---") + +# Tenemos una mochila. Adentro tiene un termo (texto) y una cartuchera (lista interna). +mochila_original = ["Termo de agua", ["Lápiz", "Borrador"]] + +# Intentamos clonar usando el truco de rebanar que ya sabíamos +mochila_clon_malo = mochila_original[:] + +# Modificamos la cartuchera (la lista interna) en el clon malo. +# Entramos a la posición 1 (la cartuchera) y le agregamos un sacapuntas. +mochila_clon_malo[1].append("Sacapuntas") + +print("Mochila ORIGINAL:", mochila_original) +print("Mochila CLON MALO:", mochila_clon_malo) +# ¡Oh no! El sacapuntas apareció en la original también. +# El [:] copió la mochila grande, pero usó la MISMA cartuchera para ambas. + + +print("\n--- 2. La Solución: deepcopy ---") + +# Tenemos una nueva mochila original (la reiniciamos) +mochila_nueva_original = ["Termo de agua", ["Lápiz", "Borrador"]] + +# Usamos nuestra nueva herramienta para un clon profundo, real y total +mochila_clon_perfecto = copy.deepcopy(mochila_nueva_original) + +# Modificamos la cartuchera (lista interna) en el clon perfecto +mochila_clon_perfecto[1].append("Regla") + +print("Nueva Mochila ORIGINAL:", mochila_nueva_original) +print("Mochila CLON PERFECTO:", mochila_clon_perfecto) +# ¡Éxito! La regla SOLO está en el clon perfecto. +# deepcopy creó una mochila nueva Y una cartuchera totalmente nueva adentro. diff --git a/python/Modulo-03-Estructuras/29-deepcopy/reto.md b/python/Modulo-03-Estructuras/29-deepcopy/reto.md new file mode 100644 index 0000000..24aac59 --- /dev/null +++ b/python/Modulo-03-Estructuras/29-deepcopy/reto.md @@ -0,0 +1,33 @@ +# Reto 29: El Experimento de la Estación Espacial 🚀 + +La agencia espacial te ha encargado hacer pruebas de resistencia en un nuevo módulo de la estación espacial. + +No puedes hacer pruebas peligrosas en la estación real, así que debes crear una simulación (un clon profundo y perfecto). Si ocurre una explosión en tu simulación, la estación real debe quedar totalmente intacta. + +## Instrucciones + +1. Crea una lista llamada `estacion_real`. Esta lista debe contener exactamente esto: + - En la posición 0: El texto `"Panel Solar"`. + - En la posición 1: El texto `"Motor Principal"`. + - En la posición 2: Una lista interna que represente la sala de control, con los textos `"Computadora"` y `"Soporte Vital"`. +2. Trae a tu archivo la herramienta necesaria para hacer copias profundas. (Recuerda poner esto en la primera línea de tu archivo). +3. Crea una variable llamada `simulacion` y guarda ahí un clon profundo de la `estacion_real`. +4. ¡Oh, no! En tu simulación, ocurrió un fallo. Entra a la lista interna de la variable `simulacion` (la sala de control) y elimina el `"Soporte Vital"` usando `.remove()`. +5. Imprime en pantalla un título que diga `"--- REPORTE DE DAÑOS ---"`. +6. Imprime un mensaje indicando la estación real y muestra su contenido, luego otro mensaje para la simulación mostrando su contenido. + +## Conceptos permitidos +- Listas y listas anidadas `[]`. +- Strings (texto) `""`. +- Comando `import` +- Herramienta `.deepcopy()` +- Índices `[2]` +- Método `.remove()` +- Función `print()` + +## Resultado esperado en la terminal +```text +--- REPORTE DE DAÑOS --- +Estación Real: ['Panel Solar', 'Motor Principal', ['Computadora', 'Soporte Vital']] +Simulación: ['Panel Solar', 'Motor Principal', ['Computadora']] +``` diff --git a/python/Modulo-03-Estructuras/29-deepcopy/teoria.md b/python/Modulo-03-Estructuras/29-deepcopy/teoria.md new file mode 100644 index 0000000..84fa89e --- /dev/null +++ b/python/Modulo-03-Estructuras/29-deepcopy/teoria.md @@ -0,0 +1,49 @@ +# Teoría: La Caja Fuerte de los Clones + +Hasta ahora, todo lo que hemos usado (`print`, `int`, `len`, `type`) viene incluido por defecto cada vez que abres Python. Es como tu cinturón de herramientas básico. + +Pero Python es gigante. Tiene herramientas para matemáticas avanzadas, para leer archivos, para conectarse a internet... ¡Si cargara todo al principio, sería lentísimo! + +Por eso existen los **Módulos**. Un módulo es una caja de herramientas especial que está guardada en el almacén de Python. Si la necesitas, debes pedirla explícitamente al principio de tu código. + +## 1. El comando `import` +Para traer una caja de herramientas externa, usamos la palabra especial `import`. + +```python +import copy +``` + +### Anatomía del código +- `import`: Es una orden directa a la computadora. Significa: "Ve al almacén central de Python, busca esto y tráelo aquí para que yo lo pueda usar". +- `copy`: Es el nombre exacto de la caja de herramientas que queremos. `copy` significa "copiar" en inglés. + +## 2. El método `deepcopy` +Una vez que trajimos la caja `copy`, podemos usar las herramientas que tiene adentro. Lo hacemos usando un punto `.`. + +```python +import copy + +clon_perfecto = copy.deepcopy(lista_original) +``` + +### Anatomía del código +- `copy`: Nuestra caja de herramientas importada. +- `.`: Significa "busca adentro de" la caja `copy`. +- `deepcopy()`: La herramienta específica que queremos usar. "Deep" significa profundo y "copy" copia. +- `lista_original`: Lo que ponemos entre los paréntesis `()` es lo que queremos que la máquina meta en la clonadora 3D. +- `=`: El resultado de esa clonación se guarda en la variable `clon_perfecto`. + +### ¿Por qué "Profunda"? +`deepcopy` revisa tu lista. Si encuentra un texto, lo copia. Si encuentra un número, lo copia. **Si encuentra otra lista adentro**, hace una pausa, entra a esa lista interna, y copia todo lo de adentro, creando una lista interna totalmente nueva. Esto lo hace capa por capa, sin importar qué tan profundo estén anidadas las cosas. ¡Crea cajas nuevas para todo! + +## ¿Qué pasa si me equivoco? + +### Error 1: Olvidar el `import` +Si intentas usar `copy.deepcopy()` sin haber puesto `import copy` en la primera línea de tu archivo, la terminal te gritará: +`NameError: name 'copy' is not defined` +(Error de nombre: el nombre 'copy' no está definido). +La computadora te está diciendo: "¿Qué es 'copy'? ¡No tengo ninguna herramienta con ese nombre en mi cinturón básico!". + +### Error 2: Escribir mal la orden +Poner `Import copy`, `import Copy`, o `copy.deepCopy()`. +Recuerda que Python es extremadamente estricto y alérgico a las mayúsculas fuera de lugar. Todo este comando debe escribirse exactamente en minúsculas. diff --git a/python/Modulo-03-Estructuras/29-deepcopy/test_main.py b/python/Modulo-03-Estructuras/29-deepcopy/test_main.py new file mode 100644 index 0000000..903865b --- /dev/null +++ b/python/Modulo-03-Estructuras/29-deepcopy/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "REPORTE DE DAÑOS" in r.stdout\n assert "Soporte Vital" in r.stdout diff --git a/retos/nerve/08-puente-http/.gitkeep b/python/Modulo-03-Estructuras/30-RETO-inventario/.gitkeep similarity index 100% rename from retos/nerve/08-puente-http/.gitkeep rename to python/Modulo-03-Estructuras/30-RETO-inventario/.gitkeep diff --git a/python/Modulo-03-Estructuras/30-RETO-inventario/README.md b/python/Modulo-03-Estructuras/30-RETO-inventario/README.md new file mode 100644 index 0000000..2cb1f03 --- /dev/null +++ b/python/Modulo-03-Estructuras/30-RETO-inventario/README.md @@ -0,0 +1,12 @@ +# Nivel 30: El Gran Reto - El Inventario del Héroe 🛡️ + +¡Felicidades! Has llegado al nivel final del Módulo de Estructuras. Hasta ahora has aprendido a usar cajas simples (variables) y cajas avanzadas (listas, tuplas, diccionarios y sets). + +En la vida real, los programas no usan una sola estructura. Un videojuego usa tuplas para las coordenadas fijas del mapa que no cambian, diccionarios para las estadísticas del jugador, listas para los objetos que recoge en orden y sets para los lugares únicos que ha visitado sin importar el orden. + +Este nivel es tu "Prueba de Fuego". No hay conceptos nuevos, solo la combinación de todo lo que has aprendido para crear algo grande y útil. + +## Ruta de aprendizaje +1. **Teoría:** Un repaso rápido de cómo elegir la estructura correcta en la vida real. +2. **Ejemplo:** Veremos cómo se ve un personaje de videojuego completo usando todas las estructuras juntas. +3. **Reto:** Construirás tu propio sistema de inventario complejo para una tienda mágica. diff --git a/python/Modulo-03-Estructuras/30-RETO-inventario/ejemplo.py b/python/Modulo-03-Estructuras/30-RETO-inventario/ejemplo.py new file mode 100644 index 0000000..1b9bac8 --- /dev/null +++ b/python/Modulo-03-Estructuras/30-RETO-inventario/ejemplo.py @@ -0,0 +1,64 @@ +import copy + +print("--- 1. Creando al Héroe ---") + +# Tupla: Las coordenadas de inicio nunca cambian, nacimos ahí. +coordenadas_inicio = (10, 50) + +# Diccionario: Las propiedades del héroe con etiquetas claras. +heroe = { + "nombre": "Arthur", + "clase": "Caballero", + "nivel": 5, + "oro": 150 +} + +# Lista: Los objetos en la mochila (el orden importa, el primero es el más accesible). +mochila = ["Espada de madera", "Poción de salud", "Antorcha"] + +# Set: Lugares que ya visitó (no importa el orden, y no puedes descubrir el mismo lugar dos veces). +lugares_descubiertos = {"Bosque Oscuro", "Pueblo Inicial"} + +print("El héroe", heroe["nombre"], "ha nacido en las coordenadas", coordenadas_inicio) + +print("\n--- 2. Aventura y Cambios ---") + +# El héroe encuentra oro y sube de nivel (actualizando diccionario) +heroe["oro"] = heroe["oro"] + 50 +heroe["nivel"] = 6 + +# El héroe encuentra un escudo y lo guarda al final de la mochila (lista) +mochila.append("Escudo de hierro") + +# El héroe usa la espada de madera y se rompe (borrando de lista) +mochila.remove("Espada de madera") + +# El héroe descubre una cueva secreta (añadiendo a set) +lugares_descubiertos.add("Cueva del Dragón") +# Si intentamos añadir el pueblo otra vez, el Set simplemente lo ignorará: +lugares_descubiertos.add("Pueblo Inicial") + +print("Nuevas estadísticas:", heroe) +print("Mochila actual:", mochila) +print("Lugares visitados:", lugares_descubiertos) + + +print("\n--- 3. El Multiverso (Clonación Profunda) ---") + +# Creamos un universo paralelo guardando TODO en un diccionario maestro anidado. +partida_guardada = { + "personaje": heroe, + "inventario": mochila, + "mapa": lugares_descubiertos +} + +# Hacemos un clon profundo de la partida guardada para no dañar la original. +partida_clon = copy.deepcopy(partida_guardada) + +# En el universo paralelo (clon), el héroe pierde todo su oro por una maldición. +partida_clon["personaje"]["oro"] = 0 +partida_clon["inventario"].append("Maldición del Eco") + +print("Oro en partida original:", partida_guardada["personaje"]["oro"]) +print("Oro en partida clonada:", partida_clon["personaje"]["oro"]) +# Como usamos deepcopy, arruinar el clon no afectó a nuestra partida original. diff --git a/python/Modulo-03-Estructuras/30-RETO-inventario/reto.md b/python/Modulo-03-Estructuras/30-RETO-inventario/reto.md new file mode 100644 index 0000000..7b8561b --- /dev/null +++ b/python/Modulo-03-Estructuras/30-RETO-inventario/reto.md @@ -0,0 +1,35 @@ +# Reto 30: El Mercader del Reino 🏪 + +Es tu turno de aplicar absolutamente todo lo aprendido. Eres el dueño de una tienda de pociones en un mundo de fantasía. Necesitas crear el sistema de gestión de tu tienda combinando todas las estructuras de datos en una sola. + +## Instrucciones + +1. En la primera línea de tu código, trae la herramienta para clonar (`import copy`). +2. Crea un diccionario principal llamado `tienda`. Debe contener exactamente estas llaves y valores: + - Clave `"nombre"`: El texto `"Pociones Mágicas"`. + - Clave `"coordenadas"`: Una tupla con los números `(42, 108)`. + - Clave `"productos"`: Una lista que contenga los textos `"Poción Roja"` y `"Poción Azul"`. + - Clave `"clientes_vip"`: Un set que contenga los textos `"Mago Gandalf"` y `"Rey Arturo"`. +3. ¡Ha llegado un nuevo cliente VIP! Añade a `"Reina Reina"` al set de clientes VIP de la tienda. +4. ¡El proveedor trajo nueva mercancía! Agrega la `"Poción Verde"` a la lista de productos de la tienda. +5. Has decidido abrir una franquicia en otro lado. Crea una variable llamada `tienda_franquicia` y hazle un **clon profundo** (deepcopy) a la `tienda` original. +6. A la `tienda_franquicia`, cámbiale el nombre a `"Pociones Mágicas - Sur"`. +7. En la `tienda_franquicia`, elimina la `"Poción Roja"` de su lista de productos (ya no la venden ahí). +8. Imprime la `tienda` original completa. +9. Imprime la `tienda_franquicia` completa. + +## Conceptos permitidos +- Diccionarios `{}`, Tuplas `()`, Listas `[]`, Sets `set()` o `{}`. +- Agregar a listas (`.append()`), eliminar de listas (`.remove()`). +- Agregar a sets (`.add()`). +- Asignar o cambiar valores en diccionarios `diccionario["clave"] = nuevo_valor`. +- `import copy` y `copy.deepcopy()`. +- `print()`. + +## Resultado esperado en la terminal +*(Nota: El orden de los elementos dentro del set (los clientes VIP) puede verse diferente en tu terminal, ¡eso es normal y correcto!)* + +```text +{'nombre': 'Pociones Mágicas', 'coordenadas': (42, 108), 'productos': ['Poción Roja', 'Poción Azul', 'Poción Verde'], 'clientes_vip': {'Reina Reina', 'Mago Gandalf', 'Rey Arturo'}} +{'nombre': 'Pociones Mágicas - Sur', 'coordenadas': (42, 108), 'productos': ['Poción Azul', 'Poción Verde'], 'clientes_vip': {'Reina Reina', 'Mago Gandalf', 'Rey Arturo'}} +``` diff --git a/python/Modulo-03-Estructuras/30-RETO-inventario/teoria.md b/python/Modulo-03-Estructuras/30-RETO-inventario/teoria.md new file mode 100644 index 0000000..44ea3c1 --- /dev/null +++ b/python/Modulo-03-Estructuras/30-RETO-inventario/teoria.md @@ -0,0 +1,32 @@ +# Teoría: El Mapa Mental de las Estructuras + +Cuando te enfrentas a un problema nuevo, la pregunta más difícil no es "cómo lo programo", sino "qué estructura utilizo para guardar mi información". + +Aquí tienes la guía definitiva del bebé programador para elegir la caja correcta: + +## 1. La Lista `[]` (El Tren) +- **Úsala cuando:** El orden importa, quieres repetir cosas y sabes que vas a agregar o quitar elementos. +- **Ejemplo real:** Una fila de clientes, el historial de mensajes de chat, los objetos en una mochila. + +## 2. La Tupla `()` (La Caja Fuerte) +- **Úsala cuando:** La información NUNCA debe cambiar durante el programa. Es rápida, segura y a prueba de accidentes. +- **Ejemplo real:** Las coordenadas GPS de una ciudad (latitud, longitud), los días de la semana, el color de la sangre. + +## 3. El Diccionario `{}` (El Archivero) +- **Úsalo cuando:** Necesitas buscar cosas por su *etiqueta* o *nombre* en lugar de su número de posición. Las piezas de información están relacionadas. +- **Ejemplo real:** El perfil de un usuario (nombre, edad, correo), los detalles de un producto en una tienda online. + +## 4. El Set `{}` o `set()` (El Club Exclusivo) +- **Úsalo cuando:** Solo te importa saber si algo "existe" o "no existe", y no quieres permitir duplicados. El orden no te importa en lo absoluto. +- **Ejemplo real:** Los IDs de los usuarios que ya votaron, las medallas únicas que ha ganado un jugador. + +## ¿Qué pasa si me equivoco? +### Error común: La mezcla confusa +A veces los principiantes intentan hacer esto para guardar un jugador: +`jugador = ["Juan", 25, 100]` + +¿Qué es el 25? ¿Su edad? ¿Su nivel? ¿La cantidad de oro? En una lista, pierdes el contexto. + +**La solución:** Si la información tiene etiquetas naturales, ¡usa un diccionario! +`jugador = {"nombre": "Juan", "edad": 25, "oro": 100}` +Ahora el código se lee solo y no tienes que memorizar posiciones. diff --git a/python/Modulo-03-Estructuras/30-RETO-inventario/test_main.py b/python/Modulo-03-Estructuras/30-RETO-inventario/test_main.py new file mode 100644 index 0000000..e281960 --- /dev/null +++ b/python/Modulo-03-Estructuras/30-RETO-inventario/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "Pociones Mágicas" in r.stdout\n assert "Sur" in r.stdout diff --git a/retos/nerve/08-puente-http/soluciones/.gitkeep b/python/Modulo-04-Bucles/.gitkeep similarity index 100% rename from retos/nerve/08-puente-http/soluciones/.gitkeep rename to python/Modulo-04-Bucles/.gitkeep diff --git a/retos/nerve/09-probador-de-carga/.gitkeep b/python/Modulo-04-Bucles/31-while/.gitkeep similarity index 100% rename from retos/nerve/09-probador-de-carga/.gitkeep rename to python/Modulo-04-Bucles/31-while/.gitkeep diff --git a/python/Modulo-04-Bucles/31-while/README.md b/python/Modulo-04-Bucles/31-while/README.md new file mode 100644 index 0000000..a64ad1d --- /dev/null +++ b/python/Modulo-04-Bucles/31-while/README.md @@ -0,0 +1,10 @@ +# Nivel 31: El Bucle While 🔄 + +Imagina que estás llenando un vaso de agua. No echas agua una sola vez y te vas; sigues echando agua *mientras* el vaso no esté lleno. Cuando se llena, te detienes. + +En la vida real, hacemos muchas cosas repetitivas basándonos en una condición. En la programación, no queremos escribir la misma instrucción mil veces si podemos decirle a la computadora: "Repite esto *mientras* esta condición sea verdadera". Para eso existe el bucle `while`. + +### Ruta de Aprendizaje +1. **Teoría**: Entenderemos qué significa `while` y cómo evita que escribamos código repetitivo. +2. **Ejemplo**: Veremos a nuestro personaje de videojuego gastar su energía paso a paso. +3. **Reto**: Construirás tu propia cuenta regresiva para el despegue de un cohete. diff --git a/python/Modulo-04-Bucles/31-while/ejemplo.py b/python/Modulo-04-Bucles/31-while/ejemplo.py new file mode 100644 index 0000000..fa3d8c0 --- /dev/null +++ b/python/Modulo-04-Bucles/31-while/ejemplo.py @@ -0,0 +1,38 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo el bucle while +# ========================================== + +print("--- Inicio de la Aventura ---") + +# Imagina un personaje de un videojuego que tiene 5 puntos de energía. +# Guardamos este valor usando el símbolo '=' (que significa "asigna este valor a este nombre"). +energia = 5 + +print("Energía inicial del jugador:") +print(energia) + +print("--- Entrando al Bosque ---") + +# Le decimos a la computadora: +# "Mientras (while) la energía sea mayor que (>) 0, entonces (:) repite lo siguiente:" +while energia > 0: + + # Todo lo que tiene espacios al inicio (indentación) se repite. + print("El jugador da un paso en el bosque...") + + # ¡Punto crítico! Si damos un paso, debemos gastar energía. + # Tomamos el valor actual de 'energia', le restamos 1, + # y el resultado lo volvemos a guardar (=) en 'energia'. + energia = energia - 1 + + # Mostramos cuánta energía queda después del paso. + print("Energía restante:") + print(energia) + +# Cuando la energía llega a 0, la condición 'energia > 0' se vuelve falsa. +# La computadora ignora el bloque indentado y continúa con el código de abajo. + +print("--- Fin de la Aventura ---") +print("El jugador se ha quedado sin energía y debe descansar.") diff --git a/python/Modulo-04-Bucles/31-while/reto.md b/python/Modulo-04-Bucles/31-while/reto.md new file mode 100644 index 0000000..5799a84 --- /dev/null +++ b/python/Modulo-04-Bucles/31-while/reto.md @@ -0,0 +1,33 @@ +# Reto 31: El Contador de Cohetes 🚀 + +¡Es hora de poner a prueba lo que aprendiste! Eres el ingeniero de software principal del centro de lanzamiento espacial. Tu misión es escribir el programa que realiza la cuenta regresiva antes de que el cohete despegue. + +### Instrucciones paso a paso: +1. Crea una variable llamada `cuenta` y asígnale el valor `10`. Esta será tu cuenta regresiva inicial. +2. Escribe un texto en pantalla que anuncie: `"Preparando lanzamiento..."`. +3. Crea un bucle `while` que siga repitiéndose **mientras** la variable `cuenta` sea mayor que `0`. +4. **Dentro del bucle** (recuerda la indentación): + - Imprime el valor actual de la variable `cuenta`. + - Resta `1` al valor de `cuenta` y vuelve a guardarlo en la misma variable. +5. **Fuera del bucle** (sin indentación, para que se ejecute solo al final): + - Imprime el mensaje: `"¡Despegue! 🚀"`. + +### Reglas estrictas: +- **Conceptos permitidos**: Variables, asignación (`=`), números enteros, resta (`-`), impresión en pantalla (`print`), bucle `while`, mayor que (`>`), dos puntos (`:`). +- **Prohibido**: No puedes usar la función `time.sleep()`, ni bucles `for`, ni trucos matemáticos avanzados como `-=`. Hazlo paso a paso. No uses palabras que no hayamos visto. + +### Resultado esperado en la terminal: +```text +Preparando lanzamiento... +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +¡Despegue! 🚀 +``` diff --git a/python/Modulo-04-Bucles/31-while/teoria.md b/python/Modulo-04-Bucles/31-while/teoria.md new file mode 100644 index 0000000..de31cd0 --- /dev/null +++ b/python/Modulo-04-Bucles/31-while/teoria.md @@ -0,0 +1,37 @@ +# Teoría: La Máquina de Repetición + +Hasta ahora, nuestro código se ejecutaba de arriba hacia abajo, una sola vez. Con `if`, podíamos elegir si ejecutar un bloque de código o no. Pero, ¿qué pasa si queremos ejecutar ese bloque **muchas veces**? + +Aquí entra `while`. La palabra `while` en inglés significa "mientras". +Funciona como un guardia de seguridad muy estricto que hace una pregunta antes de dejarte pasar. Si la respuesta es sí (Verdadero), pasas y haces la acción. Cuando terminas, el guardia te vuelve a hacer la misma pregunta. Solo te dejará salir del ciclo cuando la respuesta sea no (Falso). + +## Anatomía de un `while` + +```python +galletas = 3 + +while galletas > 0: + print("¡Me como una galleta!") + galletas = galletas - 1 +``` + +Desmontemos cada símbolo nuevo y palabra: + +- `while` : Es la orden mágica. Le dice a la computadora: "Prepárate para repetir algo". +- `galletas > 0` : Es la **condición**. La computadora evalúa esto igual que en un `if`. ¿Es verdadero o falso? +- `:` : Los dos puntos. Significa "entonces haz lo siguiente". Obliga a que la siguiente línea tenga un espacio en blanco al inicio (indentación). +- La indentación (espacios al inicio de la línea) : Todo el código que esté empujado hacia la derecha es lo que se va a repetir. +- `galletas = galletas - 1` : ¡Esto es vital! Estamos actualizando la variable. Si no restamos las galletas, el número siempre será 3. La condición `3 > 0` siempre será verdadera y el bucle nunca terminará. + +## ¿Qué pasa si me equivoco? + +El error más común de todo aprendiz (¡y de muchos profesionales!) es crear un **bucle infinito**. + +**¿Cómo se ve el error?** +Tu terminal empezará a imprimir el mismo mensaje a una velocidad increíble y nunca se detendrá. Tu computadora podría empezar a sonar como un avión despegando porque está trabajando sin descanso. + +**¿Por qué pasa?** +Porque olvidaste cambiar el valor de la variable dentro de la indentación. Si la condición siempre es verdadera, el `while` nunca se detiene. + +**¿Cómo lo soluciono?** +Si te quedas atrapado en un bucle infinito en tu terminal, no entres en pánico. Presiona las teclas `Ctrl` y la letra `C` al mismo tiempo (`Ctrl + C`). Esto fuerza a la computadora a detener el programa de inmediato. Luego, revisa tu código y asegúrate de estar alterando la variable para que, en algún momento, la condición sea falsa. diff --git a/python/Modulo-04-Bucles/31-while/test_main.py b/python/Modulo-04-Bucles/31-while/test_main.py new file mode 100644 index 0000000..170fd39 --- /dev/null +++ b/python/Modulo-04-Bucles/31-while/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert len(r.stdout.strip()) > 0 diff --git a/retos/nerve/09-probador-de-carga/soluciones/.gitkeep b/python/Modulo-04-Bucles/32-for/.gitkeep similarity index 100% rename from retos/nerve/09-probador-de-carga/soluciones/.gitkeep rename to python/Modulo-04-Bucles/32-for/.gitkeep diff --git a/python/Modulo-04-Bucles/32-for/README.md b/python/Modulo-04-Bucles/32-for/README.md new file mode 100644 index 0000000..f717850 --- /dev/null +++ b/python/Modulo-04-Bucles/32-for/README.md @@ -0,0 +1,10 @@ +# Nivel 32: El Bucle For 🚂 + +Ya aprendiste a usar `while` para repetir acciones *mientras* algo sea verdadero. Pero, ¿qué pasa si tienes una caja con 5 juguetes y quieres sacarlos uno por uno para jugar con ellos? + +En la vida real, cuando revisas tu lista del supermercado, vas leyendo cada producto, del primero al último. En programación, para recorrer listas (o colecciones de cosas) de forma ordenada, usamos el bucle `for`. Es como un tren que visita cada vagón de tu lista, uno por uno, hasta llegar al final. + +### Ruta de Aprendizaje +1. **Teoría**: Entenderemos qué significan `for` e `in`, y cómo sacan elementos de una lista. +2. **Ejemplo**: Revisaremos el inventario de la mochila de nuestro aventurero. +3. **Reto**: Te convertirás en un inspector de equipaje buscando artículos prohibidos. diff --git a/python/Modulo-04-Bucles/32-for/ejemplo.py b/python/Modulo-04-Bucles/32-for/ejemplo.py new file mode 100644 index 0000000..3be60d1 --- /dev/null +++ b/python/Modulo-04-Bucles/32-for/ejemplo.py @@ -0,0 +1,40 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo el bucle for +# ========================================== + +print("--- Revisión de Inventario ---") + +# Creamos una lista usando corchetes y la guardamos en la variable 'inventario'. +inventario = ["brújula", "mapa", "antorcha", "cuerda"] + +print("Vamos a revisar qué hay en tu bolsa de viaje:") + +# Le decimos a la computadora: +# "Por cada (for) 'articulo' en (in) la lista 'inventario', entonces (:) haz esto:" +# Nota: 'articulo' es un nombre que inventamos nosotros. +# Actúa como una caja temporal que guarda el objeto que estamos viendo en este momento. +for articulo in inventario: + + # Este bloque (por la indentación) se repetirá 4 veces, porque hay 4 cosas en la lista. + print("Revisando objeto...") + + # Imprimimos lo que hay adentro de nuestra caja temporal 'articulo'. + print(articulo) + +print("--- Fin de la Revisión ---") + +# Podemos usar un 'if' dentro de un 'for' si queremos! +print("Buscando si tenemos una antorcha...") + +# Recorremos la misma lista otra vez. +for articulo in inventario: + + # Comprobamos (==) si el artículo actual es la antorcha. + if articulo == "antorcha": + # Esta línea tiene DOS niveles de indentación: + # uno por el 'for' y otro por el 'if'. + print("¡Genial! Tenemos luz para la cueva.") + +print("¡Todo listo para continuar!") diff --git a/python/Modulo-04-Bucles/32-for/reto.md b/python/Modulo-04-Bucles/32-for/reto.md new file mode 100644 index 0000000..56c2242 --- /dev/null +++ b/python/Modulo-04-Bucles/32-for/reto.md @@ -0,0 +1,34 @@ +# Reto 32: El Inspector de Equipaje 🧳 + +¡Bienvenido a tu nuevo trabajo en la aduana espacial! Los pasajeros están pasando sus maletas por el escáner y tu deber es revisar cada objeto que llevan dentro. Si encuentras un artículo peligroso, debes hacer sonar la alarma. + +### Instrucciones paso a paso: +1. Crea una lista llamada `maleta` que contenga los siguientes 5 textos (cadenas): `"ropa"`, `"cepillo"`, `"bomba"`, `"zapatos"`, `"líquido"`. +2. Escribe un texto en pantalla que diga: `"Iniciando escaneo de equipaje..."`. +3. Crea un bucle `for` que recorra cada elemento de la lista `maleta`. Usa la variable temporal `objeto`. +4. **Dentro del bucle `for`** (primer nivel de indentación): + - Imprime el texto `"Escaneando:"` seguido del valor de `objeto`. + - Agrega un bloque `if` que compruebe si el `objeto` es exactamente igual (`==`) a `"bomba"`. + - **Dentro de ese `if`** (segundo nivel de indentación), imprime: `"¡ALERTA ROJA! Objeto peligroso detectado."`. +5. **Fuera del bucle** (sin indentación), imprime: `"Escaneo finalizado."`. + +### Reglas estrictas: +- **Conceptos permitidos**: Variables, cadenas de texto (`""`), listas (`[]`), bucle `for`, palabra `in`, condicional `if`, igualdad (`==`), función `print`, dos puntos (`:`), indentación. +- **Prohibido**: No puedes usar `while`, ni comandos que no hemos visto como `break`, `continue` o la palabra reservada `or`. Debes usar solo lo que sabes. + +### Resultado esperado en la terminal: +```text +Iniciando escaneo de equipaje... +Escaneando: +ropa +Escaneando: +cepillo +Escaneando: +bomba +¡ALERTA ROJA! Objeto peligroso detectado. +Escaneando: +zapatos +Escaneando: +líquido +Escaneo finalizado. +``` diff --git a/python/Modulo-04-Bucles/32-for/teoria.md b/python/Modulo-04-Bucles/32-for/teoria.md new file mode 100644 index 0000000..cf67297 --- /dev/null +++ b/python/Modulo-04-Bucles/32-for/teoria.md @@ -0,0 +1,39 @@ +# Teoría: El Revisor de Listas + +Imagina que tienes una lista de tareas. Para completarlas, tomas la primera, la haces; luego la segunda, la haces; y así hasta terminar. + +El bucle `for` está diseñado exactamente para eso: **recorrer estructuras que tienen varios elementos**, como las listas que aprendiste a crear con los corchetes `[]`. + +## Anatomía de un `for` + +```python +mochila = ["espada", "escudo", "poción"] + +for objeto in mochila: + print("He sacado un:") + print(objeto) +``` + +Desmontemos cada palabra y símbolo: + +- `for` : Significa "por cada". Le dice a la computadora que vamos a revisar una colección de cosas. +- `objeto` : Esta es una **variable temporal** o "etiqueta mágica". La computadora tomará el primer elemento de la lista (`"espada"`) y lo guardará aquí. Cuando el bloque de código termine, tomará el segundo elemento (`"escudo"`) y lo guardará aquí, reemplazando al anterior. ¡Tú inventas este nombre! Podría llamarse `item`, `cosa` o `x`. +- `in` : Significa "en". Conecta nuestra variable temporal con la lista que queremos revisar. +- `mochila` : Es la lista (o colección) que estamos recorriendo. +- `:` : Los dos puntos. Al igual que en `if` y `while`, significa "entonces haz lo siguiente". +- La indentación (espacios al inicio) : Todo lo que esté empujado a la derecha es lo que se hará **con cada elemento** de la lista. + +La computadora lee esto como: "Por cada `objeto` en la `mochila`, haz lo siguiente". + +## ¿Qué pasa si me equivoco? + +El error más común es intentar usar `for` en algo que no se puede "recorrer" o "dividir en partes". + +**¿Cómo se ve el error?** +`TypeError: 'int' object is not iterable` + +**¿Por qué pasa?** +Imagina que le dices a la computadora: "Por cada elemento en el número 5". La computadora te mirará confundida. El número 5 es solo una cosa sólida, no es una lista ni una caja con cosas adentro. En programación, a las cosas que se pueden recorrer (como las listas) se les llama "iterables". + +**¿Cómo lo soluciono?** +Asegúrate de que la variable que está después de la palabra `in` sea una lista (que tenga corchetes `[]`). Nunca intentes hacer un `for` directamente sobre un número. diff --git a/python/Modulo-04-Bucles/32-for/test_main.py b/python/Modulo-04-Bucles/32-for/test_main.py new file mode 100644 index 0000000..170fd39 --- /dev/null +++ b/python/Modulo-04-Bucles/32-for/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert len(r.stdout.strip()) > 0 diff --git a/retos/nerve/10-cliente-nuevo-lenguaje/.gitkeep b/python/Modulo-04-Bucles/33-range-enumerate/.gitkeep similarity index 100% rename from retos/nerve/10-cliente-nuevo-lenguaje/.gitkeep rename to python/Modulo-04-Bucles/33-range-enumerate/.gitkeep diff --git a/python/Modulo-04-Bucles/33-range-enumerate/README.md b/python/Modulo-04-Bucles/33-range-enumerate/README.md new file mode 100644 index 0000000..781372d --- /dev/null +++ b/python/Modulo-04-Bucles/33-range-enumerate/README.md @@ -0,0 +1,10 @@ +# Nivel 33: La Fábrica de Números (Range) 🔢 + +Imagina que quieres que tu personaje en un juego salte 100 veces. Usar un `while` requeriría crear una variable, sumarle 1 en cada paso y verificar la condición. Usar un `for` normal requeriría escribir una lista enorme: `[1, 2, 3, ..., 100]`. ¡Qué cansancio! + +Para ahorrarnos ese trabajo, los creadores de Python inventaron una "fábrica de números" automática llamada `range`. Si necesitas repetir algo un número exacto de veces, le pides a la fábrica los números y ella te los entrega al instante. + +### Ruta de Aprendizaje +1. **Teoría**: Descubriremos cómo funciona la fábrica `range` y por qué las computadoras empiezan a contar desde el cero. +2. **Ejemplo**: Veremos a un atleta haciendo su rutina de ejercicios usando `range`. +3. **Reto**: Programarás el eco de una montaña usando tu nueva fábrica de números. diff --git a/python/Modulo-04-Bucles/33-range-enumerate/ejemplo.py b/python/Modulo-04-Bucles/33-range-enumerate/ejemplo.py new file mode 100644 index 0000000..338c14f --- /dev/null +++ b/python/Modulo-04-Bucles/33-range-enumerate/ejemplo.py @@ -0,0 +1,32 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo la fábrica range +# ========================================== + +print("--- Rutina de Entrenamiento ---") +print("El atleta va a hacer 5 flexiones.") + +# Usamos 'for' combinado con 'range(5)'. +# 'repeticion' es nuestra variable temporal que irá guardando los números: 0, 1, 2, 3, 4. +for repeticion in range(5): + + # Este código se repetirá 5 veces. + print("El atleta hace una flexión...") + + # Imprimimos el número de la repetición que nos dio la máquina 'range'. + # ¡Ojo! Imprimirá desde el 0 hasta el 4. + print(repeticion) + +print("--- Fin del Entrenamiento ---") + +print("El atleta está descansando...") + +# Podemos combinar texto y la variable temporal si queremos ver algo más claro. +print("Vamos a contar 3 segundos de descanso:") + +for segundo in range(3): + print("Segundo transcurrido:") + print(segundo) + +print("¡Atleta recuperado!") diff --git a/python/Modulo-04-Bucles/33-range-enumerate/reto.md b/python/Modulo-04-Bucles/33-range-enumerate/reto.md new file mode 100644 index 0000000..13a356e --- /dev/null +++ b/python/Modulo-04-Bucles/33-range-enumerate/reto.md @@ -0,0 +1,30 @@ +# Reto 33: El Eco de la Montaña 🏔️ + +¡Estás en la cima de una gran montaña! Quieres gritar la palabra "¡Hola!" y escuchar cómo el eco te la devuelve varias veces. Para saber qué eco es cuál, la montaña numera mágicamente cada repetición. + +### Instrucciones paso a paso: +1. Escribe un mensaje en pantalla que anuncie tu grito: `"Gritando a la montaña..."`. +2. Utiliza un bucle `for` junto con la herramienta `range()` para generar exactamente **4 repeticiones**. +3. Usa la variable temporal `eco` para guardar el número que te entrega el `range`. +4. **Dentro del bucle** (con indentación): + - Imprime la palabra `"¡Hola!"`. + - Luego, imprime la variable `eco` para ver qué número de repetición te devolvió la montaña en ese instante. +5. **Fuera del bucle** (sin indentación), imprime: `"El eco se desvaneció."`. + +### Reglas estrictas: +- **Conceptos permitidos**: Bucle `for`, palabra `in`, herramienta `range()`, paréntesis `()`, función `print`, variable temporal, dos puntos (`:`), indentación. +- **Prohibido**: No puedes usar listas (nada de corchetes `[]`), no puedes usar bucles `while`, y no puedes sumar texto con números (no uses `+` para unir `"¡Hola!"` y el número). Imprímelos en líneas separadas. + +### Resultado esperado en la terminal: +```text +Gritando a la montaña... +¡Hola! +0 +¡Hola! +1 +¡Hola! +2 +¡Hola! +3 +El eco se desvaneció. +``` diff --git a/python/Modulo-04-Bucles/33-range-enumerate/teoria.md b/python/Modulo-04-Bucles/33-range-enumerate/teoria.md new file mode 100644 index 0000000..a995c8c --- /dev/null +++ b/python/Modulo-04-Bucles/33-range-enumerate/teoria.md @@ -0,0 +1,38 @@ +# Teoría: El Dispensador de Turnos + +Imagina el dispensador de boletos de una panadería. Llegas, jalas un papelito y te da el número 0. La siguiente persona jala otro y le da el 1. + +La herramienta `range` funciona exactamente así. En inglés, "range" significa "rango". Es una máquina que genera una secuencia de números por la cual podemos iterar (recorrer) usando nuestro viejo amigo, el bucle `for`. + +## Anatomía de un `range` + +```python +for turno in range(3): + print("Número de turno:") + print(turno) +``` + +Desmontemos lo nuevo: + +- `range` : Es el nombre de la herramienta. Le dice a la computadora: "enciende la fábrica de números". +- `()` : Los paréntesis. Son como la ranura de una máquina expendedora. Todo lo que pongas adentro es la *instrucción* de cómo quieres que funcione la máquina. +- `3` : Es el número que metemos en la ranura. Le dice a la máquina: "Quiero exactamente 3 números". +- `turno` : Como vimos en el nivel anterior, es nuestra variable temporal. Guardará el número que la máquina nos vaya escupiendo uno a uno. + +**¡El secreto de las computadoras!** +Si le pides a `range(3)` que te dé 3 números, tú como humano esperarías: `1, 2, 3`. +¡Pero las computadoras siempre empiezan a contar desde el CERO! +Por lo tanto, la máquina te entregará: `0, 1, 2`. +Siguen siendo 3 números en total, solo que el conteo empieza en el 0. + +## ¿Qué pasa si me equivoco? + +El error más común es olvidar los paréntesis de `range` o qué significan los números que escupe. + +**¿Qué pasa si espero que llegue al número 3?** +Si usas `range(3)`, el bucle se detendrá **antes** de llegar al 3. Solo imprimirá 0, 1 y 2. Si alguna vez necesitas que el número 3 aparezca en tu programa, tendrás que pedirle a la máquina `range(4)`. + +Recuerda esta regla de oro: `range(N)` genera números desde el `0` hasta un número antes de `N`. + +**Error de sintaxis común:** +Escribir `for numero in range[3]:` usando corchetes en lugar de paréntesis. Recuerda: los corchetes `[]` son para **crear** listas manuales. Los paréntesis `()` son para **darle instrucciones** a una herramienta como `range`. diff --git a/python/Modulo-04-Bucles/33-range-enumerate/test_main.py b/python/Modulo-04-Bucles/33-range-enumerate/test_main.py new file mode 100644 index 0000000..170fd39 --- /dev/null +++ b/python/Modulo-04-Bucles/33-range-enumerate/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert len(r.stdout.strip()) > 0 diff --git a/retos/nerve/10-cliente-nuevo-lenguaje/soluciones/.gitkeep b/python/Modulo-04-Bucles/34-break-continue/.gitkeep similarity index 100% rename from retos/nerve/10-cliente-nuevo-lenguaje/soluciones/.gitkeep rename to python/Modulo-04-Bucles/34-break-continue/.gitkeep diff --git a/python/Modulo-04-Bucles/34-break-continue/README.md b/python/Modulo-04-Bucles/34-break-continue/README.md new file mode 100644 index 0000000..fec3d09 --- /dev/null +++ b/python/Modulo-04-Bucles/34-break-continue/README.md @@ -0,0 +1,10 @@ +# Nivel 34: El Freno de Emergencia (Break) 🛑 + +Imagina que estás buscando tus llaves perdidas en tu casa. Revisas el sillón, la mesa, la cocina... ¡y las encuentras en el refrigerador! Una vez que las encuentras, ¿sigues buscando en el baño y en el cuarto? ¡Claro que no! Detienes la búsqueda de inmediato. + +En programación pasa lo mismo. A veces le decimos a la computadora que recorra una lista enorme de datos buscando algo. Cuando lo encuentra, no queremos que pierda tiempo revisando el resto. Para detener el bucle al instante, usamos el freno de emergencia: `break`. + +### Ruta de Aprendizaje +1. **Teoría**: Aprenderemos cómo y dónde colocar este botón de parada de emergencia. +2. **Ejemplo**: Acompañaremos a un detective a buscar un objeto perdido en un cajón. +3. **Reto**: Construirás un sistema de seguridad para detectar intrusos y detener un proceso. diff --git a/python/Modulo-04-Bucles/34-break-continue/ejemplo.py b/python/Modulo-04-Bucles/34-break-continue/ejemplo.py new file mode 100644 index 0000000..abfefdd --- /dev/null +++ b/python/Modulo-04-Bucles/34-break-continue/ejemplo.py @@ -0,0 +1,36 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo el freno break +# ========================================== + +print("--- El Cofre del Tesoro ---") + +# Tenemos una lista con las cosas que hay dentro del cofre. +cofre = ["telaraña", "polvo", "diamante", "hueso", "piedra"] + +print("El pirata empieza a sacar cosas del cofre...") + +# Recorremos la lista usando nuestro bucle for. +for cosa in cofre: + + # Imprimimos lo que acabamos de sacar. + print("El pirata saca:") + print(cosa) + + # Ahora verificamos: ¿Es esto lo que buscábamos? + if cosa == "diamante": + + # Si es el diamante, celebramos. + print("¡Encontramos el tesoro!") + + # ¡Jalamos el freno de emergencia! + # Como ya encontramos el diamante, no tiene sentido + # seguir sacando los huesos y las piedras. + break + +print("--- Fin de la Búsqueda ---") +print("El pirata cierra el cofre y se va a casa feliz.") + +# Si corres este código, notarás que "hueso" y "piedra" +# NUNCA se imprimen, porque el bucle fue destruido por el 'break'. diff --git a/python/Modulo-04-Bucles/34-break-continue/reto.md b/python/Modulo-04-Bucles/34-break-continue/reto.md new file mode 100644 index 0000000..00e5f47 --- /dev/null +++ b/python/Modulo-04-Bucles/34-break-continue/reto.md @@ -0,0 +1,31 @@ +# Reto 34: El Detector de Intrusos 🚨 + +Eres el administrador de seguridad del servidor principal. Tienes una lista con los códigos de acceso de las personas que están entrando al sistema. Te acaban de informar que un intruso usará el código `"HACKER"`. Tu trabajo es detener el análisis del servidor inmediatamente en cuanto detectes ese código. + +### Instrucciones paso a paso: +1. Crea una lista llamada `accesos` con estos textos exactos: `"admin"`, `"usuario1"`, `"HACKER"`, `"invitado"`, `"usuario2"`. +2. Imprime un mensaje que diga `"Analizando accesos al servidor..."`. +3. Crea un bucle `for` que recorra la lista `accesos` usando una variable temporal llamada `codigo`. +4. **Dentro del bucle `for`** (primer nivel de indentación): + - Imprime el texto `"Revisando:"` y en la siguiente línea imprime la variable `codigo`. + - Agrega un bloque `if` que compruebe si el `codigo` es exactamente igual (`==`) a `"HACKER"`. + - **Dentro de ese `if`** (segundo nivel de indentación), imprime: `"¡INTRUSO DETECTADO! Apagando sistema..."`. + - Inmediatamente después del mensaje, usa el freno de emergencia para detener el bucle. +5. **Fuera del bucle** (sin indentación), imprime: `"Servidor fuera de línea."`. + +### Reglas estrictas: +- **Conceptos permitidos**: Variables, cadenas de texto (`""`), listas (`[]`), bucle `for`, palabra `in`, condicional `if`, igualdad (`==`), función `print`, palabra `break`, dos puntos (`:`), indentación. +- **Prohibido**: No puedes usar `while`, `continue`, ni funciones para borrar elementos de la lista. Debes detener la búsqueda en el instante preciso. + +### Resultado esperado en la terminal: +```text +Analizando accesos al servidor... +Revisando: +admin +Revisando: +usuario1 +Revisando: +HACKER +¡INTRUSO DETECTADO! Apagando sistema... +Servidor fuera de línea. +``` diff --git a/python/Modulo-04-Bucles/34-break-continue/teoria.md b/python/Modulo-04-Bucles/34-break-continue/teoria.md new file mode 100644 index 0000000..dd1d268 --- /dev/null +++ b/python/Modulo-04-Bucles/34-break-continue/teoria.md @@ -0,0 +1,38 @@ +# Teoría: El Botón de Parada + +Hasta ahora, nuestros bucles `for` y `while` eran imparables. Una vez que empezaban, tenían que terminar de revisar toda la lista o esperar a que la condición principal se volviera falsa. + +La palabra reservada `break` (que significa "romper" o "detener" en inglés) nos permite destruir el bucle desde adentro y salir de él inmediatamente. + +## Anatomía de un `break` + +Casi siempre, el `break` va a vivir escondido dentro de un `if`. ¡Tiene sentido! Solo queremos jalar el freno de emergencia **SI** pasa algo específico. + +```python +cajon = ["lápiz", "goma", "llave", "moneda"] + +for objeto in cajon: + print("Revisando:", objeto) + + if objeto == "llave": + print("¡La encontré!") + break +``` + +Desmontemos la nueva herramienta: + +- `break` : Es una orden directa a la computadora que dice "destruye el bucle en el que estamos atrapados y salta al código que está abajo del todo". No requiere paréntesis, ni signos especiales. Solo la palabra. +- La indentación : Nota cómo el `break` está doblemente indentado. Primero, está dentro del `for`. Segundo, está dentro del `if`. + +## ¿Qué pasa si me equivoco? + +El error más común es usar el freno de emergencia donde no hay ningún vehículo en movimiento. + +**¿Cómo se ve el error?** +`SyntaxError: 'break' outside loop` + +**¿Por qué pasa?** +Pasa cuando escribes la palabra `break` pero no estás dentro de un bloque `for` ni de un bloque `while`. La computadora dice: "¿Qué quieres que detenga si no estamos repitiendo nada?". + +**¿Cómo lo soluciono?** +Asegúrate de que la palabra `break` tenga al menos un nivel de indentación (espacios a la izquierda) y que arriba de ella, en algún lugar, exista un `for` o un `while` que la contenga. Y recuerda: `break` solo rompe **un** bucle, el que lo encierra directamente. diff --git a/python/Modulo-04-Bucles/34-break-continue/test_main.py b/python/Modulo-04-Bucles/34-break-continue/test_main.py new file mode 100644 index 0000000..170fd39 --- /dev/null +++ b/python/Modulo-04-Bucles/34-break-continue/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert len(r.stdout.strip()) > 0 diff --git a/python/Modulo-04-Bucles/35-list-comprehension/.gitkeep b/python/Modulo-04-Bucles/35-list-comprehension/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-04-Bucles/35-list-comprehension/README.md b/python/Modulo-04-Bucles/35-list-comprehension/README.md new file mode 100644 index 0000000..349050a --- /dev/null +++ b/python/Modulo-04-Bucles/35-list-comprehension/README.md @@ -0,0 +1,12 @@ +# Nivel 35: La Máquina Clonadora (List Comprehension) 🏭 + +Imagínate en una fábrica. Tienes una caja llena de manzanas normales y quieres meterlas a una máquina que, en un solo paso, les ponga caramelo y las guarde en una caja nueva. + +Hasta ahora, para hacer esto, usarías un bucle `for` normal: sacarías una manzana, le pondrías caramelo, buscarías la caja nueva, la meterías... y repetirías. + +Python tiene un truco de magia llamado **Comprensión de Listas** (o *List Comprehension* en inglés). Es una forma de escribir un bucle `for` comprimido en una sola línea que construye una lista nueva a velocidad luz. + +### Ruta de Aprendizaje +1. **Teoría**: Aprenderemos a meter un motor `for` dentro de unos corchetes `[]`. +2. **Ejemplo**: Acompañaremos a un elfo herrero a mejorar todas las armas de su inventario de golpe. +3. **Reto**: Actualizarás los precios de tu panadería usando esta magia de una sola línea. diff --git a/python/Modulo-04-Bucles/35-list-comprehension/ejemplo.py b/python/Modulo-04-Bucles/35-list-comprehension/ejemplo.py new file mode 100644 index 0000000..18d21ed --- /dev/null +++ b/python/Modulo-04-Bucles/35-list-comprehension/ejemplo.py @@ -0,0 +1,31 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo las List Comprehensions +# ========================================== + +print("--- La Forja del Elfo ---") + +# Tenemos el inventario de armas viejas y oxidadas. +armas_oxidadas = ["espada", "hacha", "daga"] + +print("Armas en el taller:") +print(armas_oxidadas) + +print("--- Aplicando magia élfica ---") + +# Vamos a crear una NUEVA lista usando la máquina clonadora. +# Fórmula: [ACCION para cada ELEMENTO en LISTA_VIEJA] +# La ACCION es: agarrar el 'arma', sumarle (+) un espacio, y la palabra "mágica" o "mágico". +# El MOTOR es: for arma in armas_oxidadas + +armas_mejoradas = [arma + " mágica" for arma in armas_oxidadas] + +# ¡Eso es todo! En una sola línea hemos recorrido la lista vieja, +# modificado cada palabra, y guardado el resultado en una lista nueva. + +print("¡Las armas han sido mejoradas de golpe!") +print("Inventario nuevo:") +print(armas_mejoradas) + +print("--- Forja Cerrada ---") diff --git a/python/Modulo-04-Bucles/35-list-comprehension/reto.md b/python/Modulo-04-Bucles/35-list-comprehension/reto.md new file mode 100644 index 0000000..ca44703 --- /dev/null +++ b/python/Modulo-04-Bucles/35-list-comprehension/reto.md @@ -0,0 +1,22 @@ +# Reto 35: La Panadería Exprés 🥖 + +Eres el dueño de una famosa panadería. Hoy la harina subió de precio, así que tienes que subir el precio de todos tus panes en **5 monedas** de oro. Tienes una lista larga de precios y no quieres hacerlo a mano. ¡Es hora de usar la máquina clonadora! + +### Instrucciones paso a paso: +1. Crea una lista llamada `precios_viejos` con los números: `10`, `20`, `30`, `40`. +2. Escribe en pantalla: `"Actualizando precios en la caja registradora..."`. +3. Crea una variable llamada `precios_nuevos`. +4. Asigna a `precios_nuevos` una **Comprensión de Lista** (`[]`) que haga lo siguiente: + - Toma cada `precio` (variable temporal) de la lista `precios_viejos`. + - A cada `precio` súmale `5`. +5. Por último, imprime en pantalla la variable `precios_nuevos`. + +### Reglas estrictas: +- **Conceptos permitidos**: Variables, listas de números (`[]`), suma matemática (`+`), función `print`, y la sintaxis de comprensión de listas (`[accion for elemento in lista]`). +- **Prohibido**: Escribir la lista final a mano (es decir, no puedes hacer `[15, 25, 35, 45]`). No puedes usar un bucle `for` tradicional con indentación de múltiples líneas. TODO el trabajo debe hacerse en una sola línea mágica. + +### Resultado esperado en la terminal: +```text +Actualizando precios en la caja registradora... +[15, 25, 35, 45] +``` diff --git a/python/Modulo-04-Bucles/35-list-comprehension/teoria.md b/python/Modulo-04-Bucles/35-list-comprehension/teoria.md new file mode 100644 index 0000000..db114a6 --- /dev/null +++ b/python/Modulo-04-Bucles/35-list-comprehension/teoria.md @@ -0,0 +1,36 @@ +# Teoría: El Bucle Comprimido + +La comprensión de listas asusta a muchos principiantes porque parece que alguien aplastó el código. Pero si lo lees de izquierda a derecha, tiene todo el sentido del mundo. + +Se divide en dos partes: **La Acción** (lo que le vas a hacer al objeto) y **El Motor** (el bucle que saca el objeto). Y todo esto debe vivir dentro de una caja de lista nueva: los corchetes `[]`. + +## Anatomía de una Comprensión de Lista + +```python +numeros = [1, 2, 3] + +# ¡Magia en una línea! +dobles = [numero * 2 for numero in numeros] +``` + +Desmontemos este hechizo: + +- `[` y `]` : Los corchetes exteriores son vitales. Le dicen a la computadora: "Todo lo que pase aquí adentro es para construir una lista completamente nueva". +- `numero * 2` : Esta es **La Acción**. Le estamos diciendo a la computadora qué queremos guardar en la lista nueva. En este caso, el número multiplicado por dos. +- `for numero in numeros` : Este es **El Motor**. Es exactamente el mismo bucle `for` que ya conoces. Se encarga de ir a la lista vieja (`numeros`), sacar un elemento y guardarlo en la variable temporal (`numero`). + +**Cómo lo lee un humano:** +"Quiero guardar el `numero * 2` por cada `numero` que haya en la lista `numeros`". + +## ¿Qué pasa si me equivoco? + +El error más común es escribirlo al revés o olvidar los corchetes. + +**¿Cómo se ve el error?** +`SyntaxError: invalid syntax` + +**¿Por qué pasa?** +Si escribes `[for numero in numeros numero * 2]`, la computadora colapsa. Espera que le digas primero QUÉ quieres guardar (La Acción), y luego CÓMO lo vas a conseguir (El Motor). + +**¿Cómo lo soluciono?** +Recuerda la fórmula: `[` + `ACCIÓN` + `MOTOR` + `]`. Siempre debes empezar escribiendo la variable temporal sola o modificada, y después la palabra `for`. Y nunca olvides envolver todo el hechizo en los corchetes `[]` para que sepa que es una lista. diff --git a/python/Modulo-04-Bucles/35-list-comprehension/test_main.py b/python/Modulo-04-Bucles/35-list-comprehension/test_main.py new file mode 100644 index 0000000..0cc9e44 --- /dev/null +++ b/python/Modulo-04-Bucles/35-list-comprehension/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "[" in r.stdout diff --git a/python/Modulo-04-Bucles/36-dict-comprehension/.gitkeep b/python/Modulo-04-Bucles/36-dict-comprehension/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-04-Bucles/36-dict-comprehension/README.md b/python/Modulo-04-Bucles/36-dict-comprehension/README.md new file mode 100644 index 0000000..186fe30 --- /dev/null +++ b/python/Modulo-04-Bucles/36-dict-comprehension/README.md @@ -0,0 +1,10 @@ +# Nivel 36: La Etiquetadora Mágica (Dict Comprehension) 🏷️ + +En el nivel anterior aprendiste a usar la máquina clonadora para transformar listas enteras en una sola línea. Pero, ¿recuerdas los diccionarios? Esos muebles con cajones donde cada cajón tiene una etiqueta (llave) y un contenido (valor). + +A veces tenemos una lista de nombres y queremos crear un diccionario donde cada nombre sea una etiqueta. En lugar de crear los cajones a mano uno por uno, ¡podemos usar una Etiquetadora Mágica! Esto se llama **Comprensión de Diccionarios** (o *Dict Comprehension*). + +### Ruta de Aprendizaje +1. **Teoría**: Veremos cómo cambiar los corchetes por llaves y cómo usar los dos puntos para crear pares mágicos. +2. **Ejemplo**: Acompañaremos a un profesor a tomar lista de asistencia de manera automática. +3. **Reto**: Te convertirás en un traductor para construir un diccionario de idioma alienígena. diff --git a/python/Modulo-04-Bucles/36-dict-comprehension/ejemplo.py b/python/Modulo-04-Bucles/36-dict-comprehension/ejemplo.py new file mode 100644 index 0000000..1c90bf9 --- /dev/null +++ b/python/Modulo-04-Bucles/36-dict-comprehension/ejemplo.py @@ -0,0 +1,31 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo las Dict Comprehensions +# ========================================== + +print("--- Clase de Magia ---") + +# Tenemos la lista de los alumnos inscritos en la clase. +alumnos = ["Harry", "Hermione", "Ron"] + +print("Alumnos en la lista:") +print(alumnos) + +print("--- Tomando Asistencia Automática ---") + +# Vamos a crear un NUEVO DICCIONARIO usando la etiquetadora mágica. +# La LLAVE será el nombre del alumno (la variable 'estudiante'). +# El VALOR será el texto "Presente". +# El MOTOR es: for estudiante in alumnos + +registro = {estudiante: "Presente" for estudiante in alumnos} + +# ¡Eso es todo! Hemos creado un diccionario completo en una línea. +# Para "Harry", se creó el cajón "Harry" con el valor "Presente". +# Y así para todos los demás. + +print("Registro de asistencia creado:") +print(registro) + +print("--- Fin de la Clase ---") diff --git a/python/Modulo-04-Bucles/36-dict-comprehension/reto.md b/python/Modulo-04-Bucles/36-dict-comprehension/reto.md new file mode 100644 index 0000000..9f1f10e --- /dev/null +++ b/python/Modulo-04-Bucles/36-dict-comprehension/reto.md @@ -0,0 +1,24 @@ +# Reto 36: El Traductor Alienígena 👽 + +¡Has hecho contacto con una nave alienígena! Han aprendido algunas palabras de nuestro idioma, pero para que su computadora las entienda, deben tener la terminación `"-ZORK"`. Tu misión es construir el diccionario traductor que su computadora necesita. + +### Instrucciones paso a paso: +1. Crea una lista llamada `humanos` que contenga las siguientes palabras (cadenas): `"paz"`, `"amor"`, `"vida"`. +2. Escribe en pantalla: `"Iniciando sistema de traducción..."`. +3. Crea una variable llamada `traductor`. +4. Asigna a `traductor` una **Comprensión de Diccionario** (`{}`) que haga lo siguiente: + - Toma cada `palabra` (variable temporal) de la lista `humanos`. + - La **llave** de tu diccionario debe ser la variable `palabra` intacta. + - Usa los dos puntos `:` para separar. + - El **valor** debe ser la variable `palabra` sumada (concatenada) con el texto `"-ZORK"`. +5. Por último, imprime en pantalla la variable `traductor`. + +### Reglas estrictas: +- **Conceptos permitidos**: Variables, listas de textos (`[]`), llaves para diccionarios (`{}`), suma de textos (`+`), función `print`, y la sintaxis de comprensión de diccionarios (`{llave: valor for elemento in lista}`). +- **Prohibido**: Escribir el diccionario final a mano (es decir, no puedes hacer `{"paz": "paz-ZORK", ...}`). No puedes usar bucles `for` tradicionales de múltiples líneas. + +### Resultado esperado en la terminal: +```text +Iniciando sistema de traducción... +{'paz': 'paz-ZORK', 'amor': 'amor-ZORK', 'vida': 'vida-ZORK'} +``` diff --git a/python/Modulo-04-Bucles/36-dict-comprehension/teoria.md b/python/Modulo-04-Bucles/36-dict-comprehension/teoria.md new file mode 100644 index 0000000..8d722e0 --- /dev/null +++ b/python/Modulo-04-Bucles/36-dict-comprehension/teoria.md @@ -0,0 +1,39 @@ +# Teoría: La Máquina de Cajones + +La comprensión de diccionarios funciona casi exactamente igual que la comprensión de listas. La única diferencia es que estamos construyendo un diccionario, así que necesitamos dos cosas nuevas: **las llaves `{}`** y **los dos puntos `:`** para separar el nombre del cajón y lo que va adentro. + +## Anatomía de un Dict Comprehension + +Fórmula: `{LLAVE : VALOR for VARIABLE in LISTA}` + +```python +invitados = ["Ana", "Luis"] + +# ¡Magia en una línea! +asistencia = {nombre: "Confirmado" for nombre in invitados} +``` + +Desmontemos este nuevo hechizo: + +- `{` y `}` : En lugar de corchetes, usamos llaves. Le dicen a la computadora: "Prepárate, vamos a construir un diccionario con cajones y etiquetas". +- `nombre: "Confirmado"` : Esta es **La Acción**, pero dividida en dos. + - La parte izquierda (`nombre`) es la **etiqueta** del cajón (la llave). + - Los dos puntos (`:`) separan la etiqueta del contenido. + - La parte derecha (`"Confirmado"`) es el **contenido** (el valor) que meteremos en ese cajón. +- `for nombre in invitados` : Es nuestro **Motor**. Saca a "Ana" de la lista de invitados, crea el cajón "Ana" y le mete "Confirmado". Luego hace lo mismo con "Luis". + +**Cómo lo lee un humano:** +"Crea un cajón con la etiqueta `nombre` y el valor `'Confirmado'`, por cada `nombre` que haya en la lista `invitados`". + +## ¿Qué pasa si me equivoco? + +El error más común es olvidar los dos puntos `:` o seguir usando los corchetes `[]` por costumbre. + +**¿Cómo se ve el error?** +`SyntaxError: invalid syntax` o terminas creando algo que no es un diccionario. + +**¿Por qué pasa?** +Si escribes `{nombre "Confirmado" for nombre in invitados}`, la computadora se confunde. Sin los dos puntos, no sabe qué palabra es la etiqueta del cajón y cuál es el contenido. Si usas corchetes `[]`, la computadora intentará hacer una lista y los dos puntos `:` le darán un error. + +**¿Cómo lo soluciono?** +Revisa siempre el inicio de tu código. Si usas llaves `{`, DEBE haber unos dos puntos `:` separando tu llave de tu valor antes de escribir la palabra `for`. diff --git a/python/Modulo-04-Bucles/36-dict-comprehension/test_main.py b/python/Modulo-04-Bucles/36-dict-comprehension/test_main.py new file mode 100644 index 0000000..2ebb6b9 --- /dev/null +++ b/python/Modulo-04-Bucles/36-dict-comprehension/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "{" in r.stdout diff --git a/python/Modulo-04-Bucles/37-zip/.gitkeep b/python/Modulo-04-Bucles/37-zip/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-04-Bucles/37-zip/README.md b/python/Modulo-04-Bucles/37-zip/README.md new file mode 100644 index 0000000..25fd09c --- /dev/null +++ b/python/Modulo-04-Bucles/37-zip/README.md @@ -0,0 +1,12 @@ +# Nivel 37: La Cremallera Mágica (Zip) 🤐 + +Hasta ahora, nuestro bucle `for` ha sido como un inspector que revisa una sola fila de personas. Pero, ¿qué pasa si tienes dos filas? Imagina que tienes una lista de alumnos y otra lista con sus calificaciones. + +¿Cómo hacemos que el inspector revise al primer alumno con su primera calificación, y luego al segundo con su segunda? + +En la vida real, para unir el lado izquierdo de una chaqueta con el lado derecho, usamos un cierre o cremallera. ¡En programación hacemos exactamente lo mismo! Usamos la herramienta mágica `zip` (que en inglés significa cremallera) para unir dos o más listas al mismo tiempo. + +### Ruta de Aprendizaje +1. **Teoría**: Entenderemos cómo `zip` entrelaza las listas y cómo debemos preparar **dos** cajas temporales para recibir los datos. +2. **Ejemplo**: Ayudaremos a un director de cine a emparejar a los superhéroes con sus poderes. +3. **Reto**: Te convertirás en un chef ensamblador de hamburguesas uniendo panes y carnes. diff --git a/python/Modulo-04-Bucles/37-zip/ejemplo.py b/python/Modulo-04-Bucles/37-zip/ejemplo.py new file mode 100644 index 0000000..dc1f83b --- /dev/null +++ b/python/Modulo-04-Bucles/37-zip/ejemplo.py @@ -0,0 +1,30 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo la cremallera zip +# ========================================== + +print("--- Base de Datos de Héroes ---") + +# Tenemos dos listas separadas. +# Una con los nombres de los héroes y otra con sus superpoderes. +heroes = ["Flash", "Batman", "Superman"] +poderes = ["Super Velocidad", "Dinero", "Volar"] + +print("Uniendo expedientes...") + +# Usamos 'for' con DOS variables temporales: 'heroe' y 'poder'. +# Y usamos 'zip' para unir las dos listas. +for heroe, poder in zip(heroes, poderes): + + # En la primera vuelta: + # heroe valdrá "Flash" + # poder valdrá "Super Velocidad" + + print("Nombre:") + print(heroe) + print("Habilidad especial:") + print(poder) + print("--------------------") + +print("--- Todos los expedientes revisados ---") diff --git a/python/Modulo-04-Bucles/37-zip/reto.md b/python/Modulo-04-Bucles/37-zip/reto.md new file mode 100644 index 0000000..4e8cbed --- /dev/null +++ b/python/Modulo-04-Bucles/37-zip/reto.md @@ -0,0 +1,38 @@ +# Reto 37: El Chef Ensamblador 🍔 + +¡Trabajas en la cocina de un restaurante de comida rápida! Tienes dos bandas transportadoras. Por la izquierda llegan los tipos de pan, y por la derecha llegan los tipos de carne. Tu trabajo es usar la cremallera mágica para ensamblar la hamburguesa y anunciarla a los clientes. + +### Instrucciones paso a paso: +1. Crea una lista llamada `panes` con estos tres textos: `"blanco"`, `"integral"`, `"con ajonjolí"`. +2. Crea una lista llamada `carnes` con estos tres textos: `"res"`, `"pollo"`, `"vegetariana"`. +3. Escribe un mensaje en pantalla que diga: `"¡Ensamblando pedidos!"`. +4. Crea un bucle `for` que use la herramienta `zip()` para unir la lista de `panes` con la lista de `carnes`. +5. Asegúrate de inventar y colocar **dos variables temporales** (por ejemplo: `tipo_pan` y `tipo_carne`) separadas por una coma. +6. **Dentro del bucle** (con indentación): + - Imprime el mensaje `"Hamburguesa lista de:"`. + - Imprime la variable de la carne. + - Imprime el texto `"en pan"`. + - Imprime la variable del pan. +7. **Fuera del bucle**, imprime `"¡Todos los pedidos entregados!"`. + +### Reglas estrictas: +- **Conceptos permitidos**: Listas (`[]`), cadenas de texto (`""`), bucle `for`, palabra `in`, variables temporales, coma (`,`), herramienta `zip()`, función `print`, dos puntos (`:`), indentación. +- **Prohibido**: Prohibido usar contadores matemáticos, bucles `while`, o intentar buscar los elementos por su número de posición en la lista (no uses trucos que no hemos visto). + +### Resultado esperado en la terminal: +```text +¡Ensamblando pedidos! +Hamburguesa lista de: +res +en pan +blanco +Hamburguesa lista de: +pollo +en pan +integral +Hamburguesa lista de: +vegetariana +en pan +con ajonjolí +¡Todos los pedidos entregados! +``` diff --git a/python/Modulo-04-Bucles/37-zip/teoria.md b/python/Modulo-04-Bucles/37-zip/teoria.md new file mode 100644 index 0000000..968f388 --- /dev/null +++ b/python/Modulo-04-Bucles/37-zip/teoria.md @@ -0,0 +1,35 @@ +# Teoría: Uniéndolo Todo + +La herramienta `zip` se encarga de tomar el primer elemento de la Lista A y pegarlo con el primer elemento de la Lista B. Luego hace lo mismo con los segundos, los terceros, y así sucesivamente. + +Para poder usar esta cremallera en un bucle `for`, necesitamos un pequeño ajuste: ¡ahora necesitamos **dos** variables temporales en lugar de una! + +## Anatomía de un `zip` + +```python +nombres = ["Ana", "Luis"] +edades = [10, 12] + +# Observa las DOS variables temporales separadas por coma +for nombre, edad in zip(nombres, edades): + print("El alumno es:") + print(nombre) + print("Su edad es:") + print(edad) +``` + +Desmontemos la sintaxis: + +- `zip()` : Es el nombre de nuestra herramienta de cremallera. +- `(nombres, edades)` : Dentro de los paréntesis le pasamos las listas que queremos unir, separadas por una coma. ¡Puedes poner más de dos si quieres! +- `nombre, edad` : ¡Alerta de concepto crítico! Como estamos uniendo dos listas, la cremallera nos va a escupir **dos** cosas a la vez en cada vuelta. Necesitamos poner **dos** etiquetas (variables temporales) separadas por una coma. La primera variable (`nombre`) recibirá el dato de la primera lista (`nombres`). La segunda variable (`edad`) recibirá el dato de la segunda lista (`edades`). + +## ¿Qué pasa si me equivoco? + +Hay dos trampas principales cuando juegas con cremalleras. + +**¿Qué pasa si las listas tienen diferentes tamaños?** +Imagina una cremallera donde el lado izquierdo tiene 5 dientes y el derecho tiene 3. La herramienta `zip` es muy inteligente: **se detendrá cuando se acabe la lista más corta**. Si tienes 5 nombres y 3 edades, el bucle solo dará 3 vueltas. ¡Los últimos 2 nombres serán ignorados! + +**¿Qué pasa si olvido poner las dos variables?** +Si escribes `for dato in zip(nombres, edades):`, solo estás poniendo una caja temporal para recibir dos cosas. La computadora agarrará ambos datos y los empaquetará a la fuerza juntos (en algo llamado *tupla*, que veremos después). Si intentas imprimir `dato`, verás algo raro en tu pantalla como `('Ana', 10)`. ¡Asegúrate de poner tantas variables temporales como listas tengas dentro del `zip`! diff --git a/python/Modulo-04-Bucles/37-zip/test_main.py b/python/Modulo-04-Bucles/37-zip/test_main.py new file mode 100644 index 0000000..eaaa4de --- /dev/null +++ b/python/Modulo-04-Bucles/37-zip/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "Ensamblando pedidos" in r.stdout\n assert "res" in r.stdout and "pollo" in r.stdout\n assert "Todos los pedidos entregados" in r.stdout diff --git a/python/Modulo-04-Bucles/38-iteradores/.gitkeep b/python/Modulo-04-Bucles/38-iteradores/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-04-Bucles/38-iteradores/README.md b/python/Modulo-04-Bucles/38-iteradores/README.md new file mode 100644 index 0000000..c248c71 --- /dev/null +++ b/python/Modulo-04-Bucles/38-iteradores/README.md @@ -0,0 +1,10 @@ +# Nivel 38: La Máquina de Turnos (Iteradores) 🎟️ + +Cuando usamos un bucle `for`, la computadora recorre tooooda la lista de un solo golpe, del principio al fin. Pero, ¿qué pasa si estás en un banco y quieres llamar al *siguiente* cliente solo cuando el cajero esté libre? No quieres que todos los clientes pasen al mismo tiempo. + +Necesitamos una forma de pausar el tiempo y decirle a la computadora: "Dame un elemento y quédate esperando hasta que yo te pida el siguiente". En Python, a esta magia de pedir elementos uno a uno le llamamos **Iteradores**. + +### Ruta de Aprendizaje +1. **Teoría**: Entenderemos las dos herramientas clave: el transformador `iter()` y la palanca `next()`. +2. **Ejemplo**: Acompañaremos a un doctor a llamar a los pacientes de su sala de espera uno a uno. +3. **Reto**: Te convertirás en un mago sacando cartas de tu sombrero misterioso. diff --git a/python/Modulo-04-Bucles/38-iteradores/ejemplo.py b/python/Modulo-04-Bucles/38-iteradores/ejemplo.py new file mode 100644 index 0000000..28a5fde --- /dev/null +++ b/python/Modulo-04-Bucles/38-iteradores/ejemplo.py @@ -0,0 +1,36 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Comprendiendo los iteradores manuales +# ========================================== + +print("--- Clínica Médica ---") + +# Tenemos la lista de pacientes esperando en la sala. +pacientes = ["Paciente 1: Carlos", "Paciente 2: María", "Paciente 3: Luis"] + +print("Hay 3 pacientes en la sala de espera.") +print("Transformando la lista en un dispensador de turnos...") + +# Transformamos la lista en un iterador usando iter(). +turnos = iter(pacientes) + +print("--- Consultorio Abierto ---") + +# El doctor presiona el botón para llamar al primer paciente. +# Usamos next() para jalar el primer elemento. +print("El doctor llama al:") +print(next(turnos)) + +print("... (El doctor atiende a Carlos) ...") + +# El doctor presiona el botón de nuevo. +# ¡Nota que usamos EXACTAMENTE el mismo código, pero nos dará a María! +print("El doctor llama al:") +print(next(turnos)) + +print("... (El doctor atiende a María) ...") + +# Aún queda un paciente, pero el doctor decide ir a comer. +print("El doctor se va a almorzar, Luis tendrá que esperar.") +print("--- Consultorio Pausado ---") diff --git a/python/Modulo-04-Bucles/38-iteradores/reto.md b/python/Modulo-04-Bucles/38-iteradores/reto.md new file mode 100644 index 0000000..a152867 --- /dev/null +++ b/python/Modulo-04-Bucles/38-iteradores/reto.md @@ -0,0 +1,27 @@ +# Reto 38: El Repartidor de Cartas 🃏 + +¡Eres un crupier en un casino! Tienes una baraja especial de cartas en tu mano. El juego consiste en sacar solo las primeras **dos** cartas, una por una, y anunciarlas en la mesa. Las demás cartas se quedan en la baraja para la siguiente ronda. + +### Instrucciones paso a paso: +1. Crea una lista llamada `baraja` con las siguientes 3 cartas (textos): `"As de Corazones"`, `"Rey de Espadas"`, `"Reina de Tréboles"`. +2. Convierte tu `baraja` en una máquina de turnos usando `iter()`. Guarda esa máquina en una nueva variable llamada `repartidor`. +3. Imprime el mensaje: `"¡Comienza el juego!"`. +4. Usa la herramienta `next()` con tu `repartidor` para sacar la **primera** carta. Guarda esa carta en una variable llamada `carta1`. +5. Imprime el mensaje `"Primera carta:"` y luego imprime la variable `carta1`. +6. Usa la herramienta `next()` con tu `repartidor` OTRA VEZ para sacar la **segunda** carta. Guárdala en una variable llamada `carta2`. +7. Imprime el mensaje `"Segunda carta:"` y luego imprime la variable `carta2`. +8. Imprime el mensaje: `"La última carta se queda oculta."` + +### Reglas estrictas: +- **Conceptos permitidos**: Variables, listas de textos (`[]`), funciones `iter()`, `next()` y `print`. +- **Prohibido**: No puedes usar bucles `for` ni `while`. No puedes sacar elementos usando su posición numérica (prohibido hacer `baraja[0]`). Tienes que hacerlo jalando la palanca `next()`. + +### Resultado esperado en la terminal: +```text +¡Comienza el juego! +Primera carta: +As de Corazones +Segunda carta: +Rey de Espadas +La última carta se queda oculta. +``` diff --git a/python/Modulo-04-Bucles/38-iteradores/teoria.md b/python/Modulo-04-Bucles/38-iteradores/teoria.md new file mode 100644 index 0000000..cd206ed --- /dev/null +++ b/python/Modulo-04-Bucles/38-iteradores/teoria.md @@ -0,0 +1,40 @@ +# Teoría: El Dispensador Manual + +Piensa en una caja de pañuelos. Cuando jalas un pañuelo, el siguiente queda asomándose, esperando a que lo jales después. La caja *recuerda* dónde se quedó. Una lista normal no sabe hacer esto, así que tenemos que transformarla en una caja inteligente. + +Para crear nuestra caja inteligente usamos dos herramientas: +1. `iter()` : Transforma una lista normal en una máquina de turnos. +2. `next()` : Es la palanca que jalamos para sacar el siguiente elemento. (En inglés, "next" significa "siguiente"). + +## Anatomía de un Iterador + +```python +fila = ["Ana", "Bob", "Clara"] + +# 1. Transformamos la lista en una máquina y la guardamos en una variable +maquina = iter(fila) + +# 2. Jalamos la palanca para sacar el primer elemento +print(next(maquina)) + +# 3. Jalamos la palanca OTRA VEZ para sacar el segundo elemento +print(next(maquina)) +``` + +Desmontemos la sintaxis: + +- `iter(fila)` : Tomas tu lista normal (fila) y la metes dentro de los paréntesis de `iter`. Esto te devuelve la máquina de turnos, que nosotros guardamos en la variable `maquina`. +- `next(maquina)` : Le pasas tu máquina a la herramienta `next()`. Esto escupe el elemento actual y mueve la máquina un paso adelante. Si vuelves a escribir exactamente el mismo código `next(maquina)`, te dará un resultado diferente porque ¡la máquina avanzó! + +## ¿Qué pasa si me equivoco? + +El error más común es ser demasiado goloso y jalar la palanca cuando la máquina ya está vacía. + +**¿Cómo se ve el error?** +`StopIteration` + +**¿Por qué pasa?** +Si tu lista tiene 3 elementos y tú escribes `next(maquina)` CUATRO veces, la computadora entrará en pánico. Trató de jalar un pañuelo de la caja, pero la caja estaba vacía, así que lanzó la alerta de "Detener Iteración" (`StopIteration`). + +**¿Cómo lo soluciono?** +Por ahora, asegúrate de contar bien cuántos elementos tienes y de no usar `next()` más veces de lo que tu lista te permite. Más adelante aprenderemos a atrapar esta alerta para que el programa no explote. diff --git a/python/Modulo-04-Bucles/38-iteradores/test_main.py b/python/Modulo-04-Bucles/38-iteradores/test_main.py new file mode 100644 index 0000000..b7880ce --- /dev/null +++ b/python/Modulo-04-Bucles/38-iteradores/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "Comienza el juego" in r.stdout\n assert "As de Corazones" in r.stdout\n assert "Rey de Espadas" in r.stdout\n assert "oculta" in r.stdout.lower() diff --git a/python/Modulo-04-Bucles/39-matrices/.gitkeep b/python/Modulo-04-Bucles/39-matrices/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-04-Bucles/39-matrices/README.md b/python/Modulo-04-Bucles/39-matrices/README.md new file mode 100644 index 0000000..de11d3d --- /dev/null +++ b/python/Modulo-04-Bucles/39-matrices/README.md @@ -0,0 +1,10 @@ +# Nivel 39: El Edificio de Cajas (Matrices) 🏢 + +Hasta ahora hemos trabajado con listas normales, que son como una fila de cajas en el suelo. Pero, ¿qué pasa si queremos construir hacia arriba? ¿Qué pasa si queremos representar un tablero de ajedrez, un mapa de un videojuego o los pisos de un edificio? + +Para eso inventamos las **Matrices**. Una matriz no es más que una "lista de listas". Y para poder revisar todas las cajas de este edificio, vamos a tener que usar un truco nuevo: meter un bucle `for` ¡dentro de otro bucle `for`! + +### Ruta de Aprendizaje +1. **Teoría**: Entenderemos cómo se ve una lista de listas y cómo funcionan los bucles anidados. +2. **Ejemplo**: Exploraremos un mapa de una mazmorra buscando habitaciones secretas. +3. **Reto**: Recorrerás un mapa de arena buscando un tesoro escondido. diff --git a/python/Modulo-04-Bucles/39-matrices/ejemplo.py b/python/Modulo-04-Bucles/39-matrices/ejemplo.py new file mode 100644 index 0000000..cf03990 --- /dev/null +++ b/python/Modulo-04-Bucles/39-matrices/ejemplo.py @@ -0,0 +1,39 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: Explorando una Matriz con bucles anidados +# ========================================== + +print("--- Entrando a la Mazmorra ---") + +# Creamos un mapa de 3 pasillos. +# Cada pasillo (lista) tiene 3 habitaciones. +mapa_mazmorra = [ + ["vacia", "vacia", "monstruo"], + ["vacia", "pocion", "vacia"], + ["trampa", "vacia", "salida"] +] + +# El primer bucle (el de afuera) recorre los PASILLOS. +for pasillo in mapa_mazmorra: + + print("-> Explorando un nuevo pasillo...") + + # El segundo bucle (el de adentro) recorre las HABITACIONES de ese pasillo. + for habitacion in pasillo: + + # Este código tiene doble indentación. + print("Abrí una puerta y encontré:") + print(habitacion) + + # Podemos meter condicionales aquí adentro (¡triple indentación!) + if habitacion == "monstruo": + print("¡A luchar!") + + # Cuando terminamos un pasillo completo, imprimimos un separador. + # Nota que este print está alineado con el bucle interior, + # así que se ejecuta después de revisar todas las puertas de un pasillo. + print("Pasillo terminado.") + print("------------------") + +print("--- Has salido de la Mazmorra ---") diff --git a/python/Modulo-04-Bucles/39-matrices/reto.md b/python/Modulo-04-Bucles/39-matrices/reto.md new file mode 100644 index 0000000..9f5c854 --- /dev/null +++ b/python/Modulo-04-Bucles/39-matrices/reto.md @@ -0,0 +1,33 @@ +# Reto 39: El Tablero del Tesoro 🗺️ + +¡Eres un buscador de tesoros! Tienes un mapa de una playa dividida en cuadrantes. La playa está llena de `"arena"`, pero en algún lugar se encuentra el preciado `"tesoro"`. Tu misión es barrer toda la playa cuadrante por cuadrante hasta encontrarlo. + +### Instrucciones paso a paso: +1. Crea una lista de listas (matriz) llamada `playa`. Debe tener 3 listas adentro. + - La primera lista debe ser: `["arena", "arena", "arena"]` + - La segunda lista debe ser: `["arena", "tesoro", "arena"]` + - La tercera lista debe ser: `["arena", "arena", "arena"]` +2. Imprime el mensaje: `"Iniciando escaneo de la playa..."`. +3. Crea tu **bucle exterior** para recorrer la `playa`. Usa la variable temporal `fila`. +4. **Dentro del bucle exterior** (primer nivel de indentación): + - Imprime: `"Revisando nueva fila..."`. + - Crea tu **bucle interior** para recorrer la `fila`. Usa la variable temporal `cuadrante`. +5. **Dentro del bucle interior** (segundo nivel de indentación): + - Agrega un bloque `if` que compruebe si el `cuadrante` es exactamente igual (`==`) a `"tesoro"`. + - Si es igual, imprime `"¡Tesoro encontrado! 💎"`. + - Si no lo es (puedes usar un `if` que compruebe si es `"arena"` o simplemente usar tu imaginación, pero hazlo simple), no imprimas nada por la arena para no llenar la pantalla, SOLO busca el tesoro. *Opcional: imprime "Buscando..." en cada paso si lo deseas, pero para coincidir con la salida esperada, solo avisa cuando lo encuentres.* +6. **Fuera de todos los bucles**, imprime: `"Escaneo terminado."`. + +### Reglas estrictas: +- **Conceptos permitidos**: Variables, textos (`""`), matrices (`[[]]`), bucles `for` anidados, condicionales `if`, igualdad (`==`), función `print`. +- **Prohibido**: Buscar el tesoro usando posiciones numéricas (`playa[1][1]`). Debes obligar a la computadora a recorrer todo el mapa con los dos bucles. + +### Resultado esperado en la terminal: +```text +Iniciando escaneo de la playa... +Revisando nueva fila... +Revisando nueva fila... +¡Tesoro encontrado! 💎 +Revisando nueva fila... +Escaneo terminado. +``` diff --git a/python/Modulo-04-Bucles/39-matrices/teoria.md b/python/Modulo-04-Bucles/39-matrices/teoria.md new file mode 100644 index 0000000..7fe79b3 --- /dev/null +++ b/python/Modulo-04-Bucles/39-matrices/teoria.md @@ -0,0 +1,44 @@ +# Teoría: Bucles Anidados + +Imagina que eres un cartero entregando cartas en un edificio. Tu rutina es: +1. Entrar al Piso 1. +2. Caminar por la puerta A, luego la B, luego la C. +3. Subir al Piso 2. +4. Caminar por la puerta A, luego la B... + +En programación, el Piso es nuestra lista exterior, y las puertas son las listas interiores. Para hacer esta rutina, necesitamos **bucles anidados** (un bucle dentro de otro). + +## Anatomía de una Matriz y Bucles Anidados + +```python +# Nota cómo hay corchetes dentro de otros corchetes +edificio = [ + ["Puerta 1A", "Puerta 1B"], + ["Puerta 2A", "Puerta 2B"] +] + +for piso in edificio: + print("¡Llegué a un nuevo piso!") + + # Este bucle está ADENTRO del primer bucle + for puerta in piso: + print("Revisando la:", puerta) +``` + +Desmontemos la estructura: + +- `[ [ ... ], [ ... ] ]` : Estos son los corchetes dobles. La lista principal (`edificio`) contiene otras dos listas adentro (los pisos). A esto le llamamos **Matriz** o arreglo de 2 dimensiones (2D). +- `for piso in edificio:` : Nuestro bucle principal. En cada vuelta, agarrará **una lista completa** (un piso) y la guardará en la variable temporal `piso`. +- `for puerta in piso:` : ¡El bucle interior! Como `piso` es una lista, podemos recorrerla. Nota el **doble nivel de indentación**. Este bucle sacará cada elemento (texto) de la lista `piso`. + +La regla de oro: **Por cada vuelta del bucle de afuera, el bucle de adentro tiene que dar todas sus vueltas completas.** + +## ¿Qué pasa si me equivoco? + +El error más común es confundir los niveles de indentación (los espacios a la izquierda). + +**¿Qué pasa si olvido indentar el segundo bucle?** +Si el segundo `for` no está empujado hacia la derecha, la computadora pensará que es un bucle independiente. Intentará recorrer `piso`, pero como está fuera del primer bucle, es muy probable que cause un error o que solo recorra el último piso del edificio. + +**¿Qué pasa si imprimo `piso` directamente?** +Si haces `print(piso)` sin usar el segundo bucle, verás en tu pantalla algo como `['Puerta 1A', 'Puerta 1B']`. Estarás viendo la caja entera en lugar de su contenido individual. diff --git a/python/Modulo-04-Bucles/39-matrices/test_main.py b/python/Modulo-04-Bucles/39-matrices/test_main.py new file mode 100644 index 0000000..d6d7b0d --- /dev/null +++ b/python/Modulo-04-Bucles/39-matrices/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "escaneo" in r.stdout.lower()\n assert "Tesoro encontrado" in r.stdout\n assert "terminado" in r.stdout.lower() diff --git a/python/Modulo-04-Bucles/40-RETO-analizador/.gitkeep b/python/Modulo-04-Bucles/40-RETO-analizador/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-04-Bucles/40-RETO-analizador/README.md b/python/Modulo-04-Bucles/40-RETO-analizador/README.md new file mode 100644 index 0000000..150e934 --- /dev/null +++ b/python/Modulo-04-Bucles/40-RETO-analizador/README.md @@ -0,0 +1,12 @@ +# Nivel 40: El Analizador Maestro 🧠 + +¡Felicidades! Has llegado al último nivel del Módulo 04. Has dominado los bucles `while` infinitos, los bucles `for` organizados, las matrices y las formas abreviadas (comprehensions). + +En la vida real, los programadores pasan gran parte de su tiempo escribiendo código que toma un montón de "datos crudos" (temperaturas de un sensor, mensajes de usuarios, registros de ventas), los recorre uno por uno, toma decisiones y genera un resumen. A esto le llamamos un **Analizador**. + +Este nivel no te enseñará un concepto nuevo de sintaxis. Su propósito es poner a prueba tu capacidad para combinar TODO lo que has aprendido desde el Nivel 01 hasta el 39: Variables, Condicionales, Listas, Diccionarios y Bucles. + +### Ruta de Aprendizaje +1. **Teoría**: Cómo pensar como un arquitecto de software antes de escribir código. +2. **Ejemplo**: Un analizador de pociones mágicas que cuenta ingredientes y detecta venenos. +3. **Reto**: Tu prueba final. Construirás un analizador de sensores de temperatura. diff --git a/python/Modulo-04-Bucles/40-RETO-analizador/ejemplo.py b/python/Modulo-04-Bucles/40-RETO-analizador/ejemplo.py new file mode 100644 index 0000000..21abfdd --- /dev/null +++ b/python/Modulo-04-Bucles/40-RETO-analizador/ejemplo.py @@ -0,0 +1,39 @@ +# ========================================== +# Archivo: ejemplo.py +# Autor: Kaia / Alenia Studios +# Descripción: El Analizador de Pociones Mágicas +# ========================================== + +print("--- Iniciando Analizador de Inventario ---") + +# 1. PREPARACIÓN (Nuestros datos crudos y nuestras cajas para resultados) +ingredientes_recolectados = ["hongo", "hongo", "veneno", "flor", "hongo", "flor"] + +# Usaremos un diccionario para llevar la cuenta de lo que nos sirve +conteo_seguro = { + "hongo": 0, + "flor": 0 +} + +# 2. EL MOTOR (El bucle que analiza todo) +print("Analizando cada ingrediente...") + +for ingrediente in ingredientes_recolectados: + + # 3. LA REACCIÓN (Tomar decisiones sobre el dato) + if ingrediente == "veneno": + print("¡ALERTA TÓXICA! Encontramos veneno. Deteniendo el análisis.") + # El veneno es tan peligroso que tiramos a la basura el inventario y paramos el motor + break + + elif ingrediente == "hongo": + print("Encontré un hongo. Guardando...") + conteo_seguro["hongo"] = conteo_seguro["hongo"] + 1 + + elif ingrediente == "flor": + print("Encontré una flor. Guardando...") + conteo_seguro["flor"] = conteo_seguro["flor"] + 1 + +print("--- Análisis Terminado ---") +print("Resultados finales del inventario:") +print(conteo_seguro) diff --git a/python/Modulo-04-Bucles/40-RETO-analizador/reto.md b/python/Modulo-04-Bucles/40-RETO-analizador/reto.md new file mode 100644 index 0000000..882b532 --- /dev/null +++ b/python/Modulo-04-Bucles/40-RETO-analizador/reto.md @@ -0,0 +1,30 @@ +# Reto 40: El Analizador de Sensores 🌡️ + +¡Te han contratado en la fábrica central! Tu trabajo es escribir un programa que analice un registro continuo de temperaturas que ha enviado un sensor de una máquina. + +El objetivo es clasificar estas temperaturas en tres estados: "Normal", "Alerta" y "Peligro". Y lo más importante: si la máquina se calentó demasiado, el programa debe abortar el análisis de inmediato. + +### Instrucciones paso a paso: +1. Crea una lista llamada `registro_temperaturas` con estos números exactos en este orden: `[22, 25, 31, 15, 29, 50, 20, 22]` +2. Crea un diccionario llamado `reporte_maquina` que tenga tres llaves (textos) empezando en 0 (números): `"Normal": 0`, `"Alerta": 0`, y `"Peligro": 0`. +3. Imprime el mensaje: `"--- Iniciando análisis del motor ---"`. +4. Crea un bucle `for` que recorra cada número en la lista `registro_temperaturas` usando la variable temporal `temp`. +5. Dentro del bucle, haz las siguientes preguntas (condicionales): + - Si `temp` es **menor o igual a 29**: Suma 1 a la llave `"Normal"` del diccionario `reporte_maquina`. + - Si `temp` está **entre 30 y 49** (puedes usar un `elif` sabiendo que ya no es menor o igual a 29): Suma 1 a la llave `"Alerta"`. + - Si `temp` es **exactamente igual a 50**: Imprime `"¡FUSIÓN DEL NÚCLEO DETECTADA! Apagando..."`, suma 1 a la llave `"Peligro"`, y usa un comando para **romper y destruir el bucle inmediatamente**. +6. Fuera del bucle, imprime `"--- Análisis finalizado ---"`. +7. Fuera del bucle, imprime el diccionario `reporte_maquina`. + +### Reglas estrictas: +- **Conceptos permitidos**: Listas, Diccionarios, bucles `for`, `if`, `elif`, matemáticas básicas de suma (`+ 1`), `break`, función `print`. +- **Prohibido**: Usar funciones prefabricadas que cuenten automáticamente (como `count()`), no hemos aprendido eso aún. Debes sumar `+ 1` manualmente al diccionario. + +### Resultado esperado en la terminal: +```text +--- Iniciando análisis del motor --- +¡FUSIÓN DEL NÚCLEO DETECTADA! Apagando... +--- Análisis finalizado --- +{'Normal': 4, 'Alerta': 1, 'Peligro': 1} +``` +*(Nota: Solo hay 4 normales y 1 alerta porque el bucle se detuvo al llegar al número 50. Los últimos dos números de la lista (20 y 22) nunca fueron leídos por culpa del apagado de emergencia).* diff --git a/python/Modulo-04-Bucles/40-RETO-analizador/teoria.md b/python/Modulo-04-Bucles/40-RETO-analizador/teoria.md new file mode 100644 index 0000000..c9a8ae0 --- /dev/null +++ b/python/Modulo-04-Bucles/40-RETO-analizador/teoria.md @@ -0,0 +1,29 @@ +# Teoría: Pensar como un Programador + +Este nivel es tu Proyecto Final del módulo. Cuando nos enfrentamos a un problema grande, no empezamos a escribir código a lo loco. Usamos una técnica llamada **Descomposición**. + +## Anatomía de un Analizador + +Casi todos los programas que "analizan" datos tienen la misma estructura de tres pasos: + +1. **Preparación (Las cajas vacías)**: + Antes del bucle, preparamos las variables donde guardaremos los resultados. Puede ser un contador (número que empieza en 0), una lista vacía `[]`, o un diccionario con contadores en cero `{"exitos": 0, "errores": 0}`. + +2. **El Motor (El bucle)**: + Un `for` o un `while` que va sacando los datos uno por uno. + Adentro del bucle, usamos los "porteros de discoteca" (`if`, `elif`, `else`) para hacerle preguntas al dato actual: "¿Eres mayor a 10?", "¿Eres una palabra prohibida?". + +3. **La Reacción (Actualizar las cajas)**: + Dependiendo de lo que diga el condicional, modificamos nuestras cajas de la fase 1. Sumamos 1 a nuestro diccionario, o agregamos un elemento a nuestra lista nueva. Si encontramos un error catastrófico, usamos `break` para apagar el motor. + +## ¿Qué pasa si me equivoco? + +El error más común en los proyectos finales es la **Parálisis por Análisis**. Intentas escribir todo el código de una sola vez, le das a "Ejecutar", y la pantalla se llena de letras rojas (errores). + +**¿Cómo evitarlo?** +¡Programa paso a paso! +1. Crea tu lista de datos y haz un bucle `for` que solo imprima cada dato. Ejecuta. ¿Funciona? Bien. +2. Agrega un solo `if` adentro. Ejecuta. ¿Funciona? Bien. +3. Agrega tu diccionario contador. Ejecuta. + +Si vas un paso a la vez, cuando algo se rompa sabrás exactamente qué línea fue la culpable. diff --git a/python/Modulo-04-Bucles/40-RETO-analizador/test_main.py b/python/Modulo-04-Bucles/40-RETO-analizador/test_main.py new file mode 100644 index 0000000..825e2bd --- /dev/null +++ b/python/Modulo-04-Bucles/40-RETO-analizador/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_sin_errores(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr +def test_especifico(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "FUSIÓN DEL NÚCLEO" in r.stdout\n assert "Normal" in r.stdout\n assert "4" in r.stdout diff --git a/python/Modulo-04.5-Iteracion-Avanzada/1-yield/README.md b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/README.md new file mode 100644 index 0000000..1b36f9c --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/README.md @@ -0,0 +1,17 @@ +# Nivel 1: La Pausa Mágica ⏸️ + +## ¿Por qué aprender esto? + +Ya sabes que los bucles `for` recorren una lista completa de principio a fin, sin parar. Pero en el mundo real hay situaciones donde **no quieres producir todos los resultados de golpe**: ¿y si necesitas generar un millón de números? ¿O si los datos llegan de uno en uno desde Internet? + +`yield` te da un superpoder: la habilidad de **pausar** una función a la mitad, entregar un valor, y reanudarla exactamente donde se quedó la próxima vez que la necesites. Es el `next()` que ya conoces, pero construido por ti mismo. + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — entiende qué hace `yield` y cómo funciona la "memoria" de la función +2. 🐍 Estudia `ejemplo.py` — ve `yield` en acción paso a paso en la terminal +3. 🔥 Completa `reto.md` — construye tu propio generador desde cero + +## Conceptos que usarás aquí + +`def`, `for`, `while`, `print()`, f-strings, `next()` (ya lo viste en iteradores) diff --git a/python/Modulo-04.5-Iteracion-Avanzada/1-yield/ejemplo.py b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/ejemplo.py new file mode 100644 index 0000000..1f5e28d --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/ejemplo.py @@ -0,0 +1,64 @@ +print("--- Parte 1: return vs yield ---") + +# Con return: la función termina y devuelve TODO de golpe +def tres_con_return(): + return "Uno" # La función muere aquí. Nunca llega a los siguientes return. + return "Dos" # Esto NUNCA se ejecuta + return "Tres" # Esto NUNCA se ejecuta + +resultado = tres_con_return() +print(resultado) # Solo muestra: Uno + + +print("--- Parte 2: yield pausa en vez de terminar ---") + +# Con yield: la función se pausa y recuerda dónde estaba +def tres_con_yield(): + yield "Uno" # Pausa 1: entrega "Uno" y se congela + yield "Dos" # Pausa 2: cuando la pidan de nuevo, entrega "Dos" + yield "Tres" # Pausa 3: y por último "Tres" + +# Llamar a la función NO la ejecuta. Crea el "chef en espera" +mi_generador = tres_con_yield() + +# next() activa el chef: ejecuta hasta el primer yield +print(next(mi_generador)) # Uno +print(next(mi_generador)) # Dos (continuó desde donde pausó) +print(next(mi_generador)) # Tres + + +print("--- Parte 3: la forma elegante es usar for ---") + +# El bucle for llama a next() por nosotros y para solo cuando se acaba +for valor in tres_con_yield(): + print(valor) +# Imprime: Uno, Dos, Tres (en 3 líneas) + + +print("--- Parte 4: yield dentro de un bucle ---") + +# La función puede tener su propio bucle interno con yield adentro +def contar_hasta(limite): + numero = 1 + while numero <= limite: + yield numero # Pausa aquí y entrega el número actual + numero = numero + 1 # Cuando la reanuden, suma 1 y continúa el while + +# Pedimos los números de uno en uno +for n in contar_hasta(5): + print(f"El chef entregó: {n}") + + +print("--- Parte 5: yield recuerda las variables internas ---") + +# La variable "acumulado" sobrevive entre cada pausa +def suma_acumulada(numeros): + acumulado = 0 + for n in numeros: + acumulado = acumulado + n + yield acumulado # Entrega el total parcial en cada paso + +mis_numeros = [10, 5, 20, 3] +for total_parcial in suma_acumulada(mis_numeros): + print(f"Suma hasta ahora: {total_parcial}") +# Muestra: 10 → 15 → 35 → 38 diff --git a/python/Modulo-04.5-Iteracion-Avanzada/1-yield/reto.md b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/reto.md new file mode 100644 index 0000000..57bc28d --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/reto.md @@ -0,0 +1,33 @@ +# Reto 1: La Máquina de Turnos 🎟️ + +El hospital del barrio necesita un sistema de turnos. Cada vez que el médico llama a un paciente, la máquina entrega el siguiente número. La máquina no genera todos los turnos de golpe (no sabe cuántos pacientes vendrán), sino que los produce **uno a la vez, bajo demanda**. + +## Instrucciones Paso a Paso: + +1. Crea una función llamada `maquina_turnos` que reciba un número llamado `inicio`. +2. Dentro de la función, crea una variable `turno` que empiece con el valor de `inicio`. +3. Crea un bucle `while True:` (recuerda: esto significa "repite para siempre"). +4. Dentro del `while`, usa `yield turno` para entregar el turno actual. +5. Después del `yield`, suma 1 a `turno` para que el próximo sea el siguiente número. +6. En tu programa principal, crea la máquina llamando a `maquina_turnos(1)` y guárdala en una variable llamada `consultorio`. +7. Usa `next()` exactamente **5 veces** para simular que llegan 5 pacientes, imprimiendo cada turno con el formato del resultado esperado. + +> **Pista:** Un `while True:` con `yield` adentro nunca lanza `StopIteration` porque nunca termina la función. El `yield` la pausa indefinidamente hasta que le pidas el siguiente. Como un rollo de turnos infinito. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `def`, `yield`, `while`, `next()`, variables, suma con `+`, `print()`, f-strings. +❌ **Conceptos Prohibidos:** Clases, `import`, `range()` como solución principal, listas para guardar los turnos. + +## Resultado Esperado en tu Terminal: + +```text +🏥 Sistema de Turnos — Consultorio 1 + +Llamando al paciente con turno: 1 +Llamando al paciente con turno: 2 +Llamando al paciente con turno: 3 +Llamando al paciente con turno: 4 +Llamando al paciente con turno: 5 +``` + +Crea tu código en un archivo llamado `reto.py`. Si tu máquina puede seguir entregando turnos más allá del 5 con solo agregar más `next()`, lo hiciste bien. diff --git a/python/Modulo-04.5-Iteracion-Avanzada/1-yield/teoria.md b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/teoria.md new file mode 100644 index 0000000..aea4fdf --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/teoria.md @@ -0,0 +1,56 @@ +# Teoría: La Pausa Mágica ⏸️ + +Recuerda el dispensador de pañuelos del módulo de iteradores. Cuando jalabas `next()`, la máquina te daba el siguiente elemento y **recordaba** dónde se había quedado. + +Hasta ahora, esa máquina la creabas con `iter()` sobre una lista que ya existía. Pero, ¿y si quisieras **tú mismo** ser el que decide cuándo viene cada valor y qué valor dar? + +Imagina que eres chef en un restaurante. Hay dos formas de servir el menú: + +- **Forma A (el `return` normal):** Te encierras en la cocina, cocinas los 500 platos de golpe, los apilas en una torre gigante y solo entonces sales. Nadie come hasta que terminas todo. +- **Forma B (el `yield`):** Cocinas **un plato**, lo llevas a la mesa, la función se **pausa**, y cuando el comensal pide el siguiente, vuelves a la cocina exactamente donde lo dejaste y preparas el siguiente. + +`yield` es el chef que cocina bajo demanda. + +--- + +## Anatomía del `yield` + +```python +def semaforo(): + yield "🔴 Rojo" # Pausa 1: entrega "🔴 Rojo" y se congela + yield "🟡 Amarillo" # Pausa 2: cuando la reanuden, entrega esto + yield "🟢 Verde" # Pausa 3: y finalmente esto + +mi_semaforo = semaforo() # NO ejecuta nada todavía. Solo crea el "chef" +print(next(mi_semaforo)) # Reactiva la función → llega a yield → devuelve "🔴 Rojo" → se pausa +print(next(mi_semaforo)) # Reactiva de nuevo → llega al siguiente yield → "🟡 Amarillo" +print(next(mi_semaforo)) # Reactiva de nuevo → "🟢 Verde" +``` + +**Desmontando cada símbolo nuevo:** + +- `yield "🔴 Rojo"` — La palabra `yield` hace **dos cosas al mismo tiempo**: entrega el valor que está a su derecha (como un `return`), y congela la función en esa línea exacta. +- `semaforo()` — Llamar a la función **no la ejecuta**. Devuelve un objeto especial llamado **generador**. Es como contratar al chef, no como pedirle que cocine. +- `next(mi_semaforo)` — Ya conoces `next()` de los iteradores. Aquí hace lo mismo: "chef, cocina el siguiente". Reactiva la función hasta el próximo `yield`. + +--- + +## ¿Qué pasa si me equivoco? + +**El error más común: llamar `next()` una vez de más** + +Si tu función tiene 3 `yield` y llamas `next()` cuatro veces, la terminal te gritará: + +``` +StopIteration +``` + +Ya lo conoces del módulo de iteradores. Significa que el dispensador está vacío: jalaste la palanca y no quedaban pañuelos. La función llegó al final sin encontrar más `yield`. + +**¿Cómo lo evito?** +Usa un bucle `for` en vez de `next()` manual. El `for` detecta automáticamente cuándo se acabaron los valores y para solo: + +```python +for estado in semaforo(): # El for se encarga de llamar next() por ti + print(estado) # Y para solo cuando ya no hay más yield +``` diff --git a/python/Modulo-04.5-Iteracion-Avanzada/1-yield/test_main.py b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/test_main.py new file mode 100644 index 0000000..c01e0f2 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/1-yield/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_turnos_generados(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "turno: 1" in r.stdout + assert "turno: 5" in r.stdout diff --git a/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/README.md b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/README.md new file mode 100644 index 0000000..9736ba5 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/README.md @@ -0,0 +1,17 @@ +# Nivel 2: El Grifo vs El Cubo 🚿🪣 + +## ¿Por qué aprender esto? + +Ahora que sabes usar `yield` para crear funciones que pausan, vamos a entender **qué es exactamente** lo que se crea cuando llamas a esa función. Se llama **generador**, y tiene un superpoder que las listas no tienen: usa casi cero memoria, sin importar cuántos valores pueda producir. + +Si en el nivel anterior construiste el chef, ahora aprenderás a entender su naturaleza: por qué consume tan poca energía y cuándo conviene usarlo en vez de una lista. + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — entiende la diferencia entre guardar todo y producir bajo demanda +2. 🐍 Estudia `ejemplo.py` — ve la diferencia de comportamiento en la terminal +3. 🔥 Completa `reto.md` — construye un generador que filtra datos de un "archivo" enorme + +## Conceptos que usarás aquí + +`def`, `yield`, `for`, `if`, listas `[]`, f-strings, `print()`, lo aprendido en el nivel anterior diff --git a/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/ejemplo.py b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/ejemplo.py new file mode 100644 index 0000000..a6c61fd --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/ejemplo.py @@ -0,0 +1,72 @@ +print("--- Parte 1: El cubo vs el grifo ---") + +# EL CUBO: una lista que guarda TODO de una vez +cubo = [1, 2, 3, 4, 5] +print("Tipo cubo:", type(cubo)) # + +# EL GRIFO: una función con yield que produce bajo demanda +def grifo(): + yield 1 + yield 2 + yield 3 + yield 4 + yield 5 + +mi_grifo = grifo() # Crea el grifo, no produce nada aún +print("Tipo grifo:", type(mi_grifo)) # + + +print("--- Parte 2: ambos producen el mismo resultado ---") + +print("Desde el cubo:") +for n in cubo: + print(n) + +print("Desde el grifo:") +for n in grifo(): # Nota: llamamos grifo() de nuevo porque el anterior se usó + print(n) + + +print("--- Parte 3: el grifo con while (más útil) ---") + +# Esto sería imposible con una lista si el límite fuera 1,000,000 +def contar_hasta(limite): + numero = 1 + while numero <= limite: + yield numero + numero = numero + 1 + +# Pedimos solo los primeros 3 usando next() manual +generador = contar_hasta(1000000) # Nadie guardó un millón de números en memoria +print(next(generador)) # 1 — solo calculó el primero +print(next(generador)) # 2 — solo calculó el segundo +print(next(generador)) # 3 — solo calculó el tercero +# El millón de números restantes nunca se calcularon porque no los pedimos + + +print("--- Parte 4: generador con filtro ---") + +# Un generador puede decidir qué producir con un if +def solo_pares(numeros): + for n in numeros: + if n % 2 == 0: # % es el "resto" de la división (ya lo viste) + yield n # Solo produce el número si es par + +mi_lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +for par in solo_pares(mi_lista): + print(f" Número par: {par}") + + +print("--- Parte 5: el generador se agota (solo lee una vez) ---") + +generador_corto = solo_pares([2, 4, 6]) + +print("Primera vuelta:") +for n in generador_corto: + print(n) # Imprime: 2, 4, 6 + +print("Segunda vuelta (el grifo está vacío):") +for n in generador_corto: + print(n) # No imprime NADA — el generador se agotó + +print("Fin del programa") diff --git a/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/reto.md b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/reto.md new file mode 100644 index 0000000..d05393b --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/reto.md @@ -0,0 +1,46 @@ +# Reto 2: El Filtro de Mensajes 📨 + +Trabajas para el equipo de moderación de una red social. Recibes una lista enorme de mensajes del día. Tu tarea: construir un generador que revise los mensajes **uno a uno** y solo entregue los que contienen una palabra clave sospechosa, sin cargar todos en memoria a la vez. + +## Instrucciones Paso a Paso: + +Copia esta lista de mensajes en tu código: + +```python +mensajes = [ + "Hola, ¿cómo estás?", + "Compra ahora con DESCUENTO increíble", + "El partido fue increíble ayer", + "GANA dinero desde casa GRATIS", + "Me comí una pizza enorme", + "URGENTE: reclama tu premio GRATIS", + "El examen estuvo difícil", + "Haz click AQUÍ para ganar DESCUENTO", +] +``` + +1. Crea una función generadora llamada `filtrar_sospechosos` que reciba una lista de mensajes y una palabra clave. +2. Dentro de la función, usa un bucle `for` para recorrer los mensajes. +3. Con un `if`, revisa si la palabra clave está **contenida** en el mensaje (recuerda: `"palabra" in texto` devuelve `True` o `False`). +4. Si el mensaje contiene la palabra clave, usa `yield` para entregarlo. +5. En tu programa principal, crea el generador buscando la palabra `"GRATIS"`. +6. Recorre el generador con un `for` e imprime cada mensaje sospechoso con su número de alerta. + +> **Pista:** Para numerar los mensajes mientras los recorres, usa `enumerate()` sobre el generador. Ya lo aprendiste en el módulo de bucles. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `def`, `yield`, `for`, `if`, `in` (para buscar en texto), `enumerate()`, `print()`, f-strings, listas. +❌ **Conceptos Prohibidos:** Clases, `import`, list comprehensions como solución, guardar en una lista todos los sospechosos antes de imprimirlos. + +## Resultado Esperado en tu Terminal: + +```text +=== Escaneando mensajes con la palabra: GRATIS === + +Alerta #1: GANA dinero desde casa GRATIS +Alerta #2: URGENTE: reclama tu premio GRATIS + +Escaneo completo. 2 mensajes sospechosos encontrados. +``` + +Crea tu código en `reto.py`. Si cambias la palabra clave a `"DESCUENTO"`, tu generador debe encontrar 2 mensajes distintos sin tocar más código. diff --git a/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/teoria.md b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/teoria.md new file mode 100644 index 0000000..e965b77 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/teoria.md @@ -0,0 +1,84 @@ +# Teoría: El Grifo vs El Cubo 🚿🪣 + +En el nivel anterior aprendiste que `yield` pausa una función y entrega un valor. Cuando llamas a esa función, obtienes algo especial. Vamos a entender exactamente qué es. + +Imagina que necesitas agua en tu cocina. Tienes dos opciones: + +- **El cubo 🪣:** Vas al jardín, llenas el cubo **completo** con toda el agua que podrías necesitar hoy, lo cargas pesado hasta la cocina, y lo pones en el piso. Ahora sí puedes usarla. El problema: si el cubo es enorme, tu brazo sufrió cargando todo ese peso aunque solo uses un vaso. +- **El grifo 🚿:** Abres la llave y el agua llega **exactamente cuando la necesitas**, en la cantidad exacta que necesitas. Sin cargar nada. El grifo no "guarda" el agua; la produce en el momento. + +Las **listas** son cubos: guardan todos los elementos en memoria desde el primer momento. +Los **generadores** son grifos: producen los valores uno a uno, solo cuando los pides. + +--- + +## ¿Qué es exactamente un generador? + +Cuando llamas a una función que tiene `yield`, Python no la ejecuta. En cambio, te devuelve un **objeto generador**: un grifo listo para dar agua, pero que todavía no ha abierto la llave. + +```python +def mi_grifo(): + yield "agua 1" + yield "agua 2" + yield "agua 3" + +# Llamar a la función NO produce nada. Solo crea el grifo. +grifo = mi_grifo() + +# El for abre la llave, saca un valor a la vez, y la cierra cuando se acaba +for agua in grifo: + print(agua) +``` + +--- + +## Anatomía: La diferencia en la práctica + +```python +# CUBO: crea toda la lista de golpe, ocupa espacio en memoria ahora mismo +cubo = ["turno-1", "turno-2", "turno-3", "turno-4", "turno-5"] + +# GRIFO: solo sabe "cómo producir el siguiente cuando me lo pidan" +def grifo_turnos(): + numero = 1 + while numero <= 5: + yield "turno-" + str(numero) # str() convierte el número a texto + numero = numero + 1 + +# Ambos se pueden recorrer con for, pero el grifo usa casi cero memoria +for t in cubo: + print(t) + +for t in grifo_turnos(): + print(t) +``` + +**La clave:** Los dos `for` producen exactamente la misma salida. La diferencia es invisible para el ojo, pero real para la computadora: el cubo cargó los 5 elementos desde el inicio; el grifo produjo uno, lo entregó, produjo otro, lo entregó... + +--- + +## ¿Qué pasa si me equivoco? + +**El error más común: intentar usar el generador dos veces** + +```python +def grifo(): + yield 1 + yield 2 + +mi_grifo = grifo() + +for n in mi_grifo: + print(n) # Imprime: 1, 2 + +for n in mi_grifo: + print(n) # No imprime NADA — el grifo ya se vació +``` + +**¿Por qué pasa?** +Un generador es como un rollo de papel de baño: una vez que jalaste todo el papel, el rollo queda vacío. No se recarga solo. + +**¿Cómo lo soluciono?** +Si necesitas recorrer los valores varias veces, tienes dos opciones: +1. Crea el generador de nuevo: `mi_grifo = grifo()` antes de cada `for`. +2. Guarda los resultados en una lista la primera vez: `resultados = [n for n in grifo()]`. diff --git a/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/test_main.py b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/test_main.py new file mode 100644 index 0000000..4a30575 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/2-generadores/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_filtro_gratis(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "GRATIS" in r.stdout + assert "2" in r.stdout diff --git a/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/README.md b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/README.md new file mode 100644 index 0000000..da59a84 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/README.md @@ -0,0 +1,17 @@ +# Nivel 3: La Lista Ligera 🪶 + +## ¿Por qué aprender esto? + +Ya sabes que `[x * 2 for x in lista]` es una forma comprimida de construir una lista con un bucle. Pero, ¿y si no necesitas la lista completa? ¿Y si solo quieres recorrer los valores una vez y ya? + +Existe una forma de escribir un generador en **una sola línea**, sin definir ninguna función. Se ve casi igual a una list comprehension, pero con paréntesis `()` en vez de corchetes `[]`. Es el grifo portátil: mismo poder de `yield`, misma comodidad de las comprensiones de lista. + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — entiende la diferencia entre `[]` y `()` +2. 🐍 Estudia `ejemplo.py` — compara los dos en situaciones reales +3. 🔥 Completa `reto.md` — escribe análisis de datos usando solo expresiones generadoras + +## Conceptos que usarás aquí + +`for`, `if`, `[]` (list comprehensions), `()`, `sum()`, `max()`, `min()`, lo aprendido en niveles anteriores de este módulo diff --git a/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/ejemplo.py b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/ejemplo.py new file mode 100644 index 0000000..034e39b --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/ejemplo.py @@ -0,0 +1,61 @@ +print("--- Parte 1: [corchetes] vs (paréntesis) ---") + +numeros = [1, 2, 3, 4, 5] + +# Con corchetes: list comprehension — crea toda la lista inmediatamente +lista_dobles = [n * 2 for n in numeros] +print("Lista (todo guardado):", lista_dobles) # [2, 4, 6, 8, 10] +print("Tipo:", type(lista_dobles)) # + +# Con paréntesis: expresión generadora — crea el grifo, no los valores +gen_dobles = (n * 2 for n in numeros) +print("Generador (solo la receta):", gen_dobles) # +print("Tipo:", type(gen_dobles)) # + + +print("--- Parte 2: los dos producen lo mismo al recorrerlos ---") + +# Convertimos el generador a lista para verlo completo (gasta memoria al hacer esto) +print("Valores del generador:", list(gen_dobles)) # [2, 4, 6, 8, 10] + + +print("--- Parte 3: expresión generadora con filtro ---") + +edades = [15, 23, 17, 31, 14, 28, 19] + +# Solo queremos los adultos — el 'if' al final filtra +adultos = (edad for edad in edades if edad >= 18) + +print("Edades de adultos:") +for a in adultos: + print(f" {a} años") + + +print("--- Parte 4: dentro de sum(), max(), min() ---") + +ventas_semana = [1500, 2300, 980, 3100, 2750] + +# sum() con expresión generadora — sin crear lista intermedia +total = sum(venta for venta in ventas_semana) +print(f"Total de ventas: {total}") + +# max() filtrando — la venta más alta que supere 2000 +# primero filtramos con el if, luego max encuentra el mayor de esos +ventas_altas = [v for v in ventas_semana if v > 2000] # guardamos primero para poder usar max +mejor_dia = max(ventas_altas) +print(f"Mejor día (más de 2000): {mejor_dia}") + +# Forma directa: sum de los que superan 2000 +total_dias_buenos = sum(venta for venta in ventas_semana if venta > 2000) +print(f"Total solo días buenos (>2000): {total_dias_buenos}") + + +print("--- Parte 5: expresiones generadoras con strings ---") + +nombres = [" ana ", "BOB ", " Clara", " DAVID"] + +# Limpiar y poner en formato título — sin crear lista intermedia +for nombre_limpio in (n.strip().title() for n in nombres): + print(f" Bienvenido/a, {nombre_limpio}") +# .strip() quita espacios al inicio y al final +# .title() pone la primera letra en mayúscula diff --git a/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/reto.md b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/reto.md new file mode 100644 index 0000000..0cee4e2 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/reto.md @@ -0,0 +1,44 @@ +# Reto 3: El Cajero Nocturno 🏪 + +La tienda de la esquina cerró y el dueño quiere saber cómo le fue hoy. Tienes el registro de todas las ventas del día. Tu tarea: calcular varias estadísticas usando **expresiones generadoras** directamente dentro de `sum()` y `max()`, sin crear listas intermedias. + +## Instrucciones Paso a Paso: + +Copia este registro de ventas en tu código: + +```python +ventas = [ + {"producto": "Refresco", "precio": 18, "cantidad": 12}, + {"producto": "Pan", "precio": 22, "cantidad": 30}, + {"producto": "Leche", "precio": 25, "cantidad": 8}, + {"producto": "Chicles", "precio": 5, "cantidad": 40}, + {"producto": "Jabón", "precio": 38, "cantidad": 5}, + {"producto": "Agua", "precio": 12, "cantidad": 20}, + {"producto": "Galletas", "precio": 32, "cantidad": 15}, +] +``` + +Recuerda: cada venta es un diccionario. Para acceder al precio de una venta usa `venta["precio"]`, y para la cantidad `venta["cantidad"]`. + +1. Calcula el **total de ingresos** del día. El ingreso de cada producto es `precio * cantidad`. Usa `sum()` con una expresión generadora. +2. Calcula el **ingreso máximo de un solo producto** (el que más dinero generó). Usa `max()` con una expresión generadora. +3. Cuenta **cuántos productos tienen precio mayor a 20 pesos**. Para esto, en lugar de `sum(precio...)`, haz `sum(1 for ...)` — suma un `1` por cada producto que cumpla la condición. +4. Imprime los tres resultados con el formato del resultado esperado. + +> **Pista:** Dentro del generador, el cálculo `venta["precio"] * venta["cantidad"]` produce el ingreso de ese producto. Mete este cálculo como la "acción" del generador. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** Expresiones generadoras `(...)`, `sum()`, `max()`, `for`, `if`, diccionarios `{}` y `[]` para acceder a claves, `print()`, f-strings. +❌ **Conceptos Prohibidos:** List comprehensions `[...]` como paso intermedio, crear listas temporales, `def` con `yield` (este reto es solo expresiones en línea). + +## Resultado Esperado en tu Terminal: + +```text +=== Reporte Nocturno de la Tienda === + +Total de ingresos del día: $2,431 +Producto que más ingresó: $960 (Pan: 22 x 30) +Productos con precio mayor a $20: 4 +``` + +Crea tu código en `reto.py`. Si puedes calcular las tres estadísticas en tres líneas de código (una por estadística), vas por el camino correcto. diff --git a/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/teoria.md b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/teoria.md new file mode 100644 index 0000000..16e08ce --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/teoria.md @@ -0,0 +1,84 @@ +# Teoría: La Lista Ligera 🪶 + +Ya conoces las comprensiones de lista del Módulo 04: + +```python +numeros = [1, 2, 3, 4, 5] +dobles = [n * 2 for n in numeros] # Construye la lista [2, 4, 6, 8, 10] completa +``` + +Y ahora conoces los generadores: en vez de guardar todo, producen un valor a la vez. + +¿Qué tal si pudiéramos combinar los dos? Escribir un generador en una sola línea, sin necesitar `def` ni `yield`. Ese es exactamente el poder de la **expresión generadora**. + +--- + +## Anatomía: La única diferencia es `[]` vs `()` + +```python +# LIST COMPREHENSION — usa corchetes [ ] +# Crea y guarda TODOS los valores en memoria ahora mismo +lista = [n * 2 for n in numeros] + +# EXPRESIÓN GENERADORA — usa paréntesis ( ) +# Solo guarda la "receta". Produce los valores uno a uno cuando se los pidas +generador = (n * 2 for n in numeros) +``` + +La estructura interna es idéntica: `valor_a_producir for variable in iterable`. +La única diferencia visual: los `[` `]` hacen una lista, los `(` `)` hacen un generador. + +**Desmontando los símbolos:** +- `(` `)` — los paréntesis exteriores le dicen a Python: "esto no es una lista, es un generador en una línea" +- `n * 2` — esto es lo que se producirá (la acción, igual que en list comprehension) +- `for n in numeros` — el motor del bucle, exactamente igual que en list comprehension +- `if condicion` — opcional, para filtrar (exactamente igual que en list comprehension) + +--- + +## Con filtro: el `if` al final + +```python +edades = [15, 23, 17, 31, 14, 28, 19] + +# Solo los adultos, sin crear lista +adultos = (edad for edad in edades if edad >= 18) + +for a in adultos: + print(a) # 23, 31, 28, 19 +``` + +--- + +## El uso más poderoso: directo dentro de `sum()`, `max()`, `min()` + +`sum()`, `max()` y `min()` ya los conoces. Funcionan con cualquier colección de números. Y aceptan generadores directamente: + +```python +precios = [120, 45, 890, 12, 340] + +# Suma de todos los precios — sin crear lista intermedia +total = sum(precio for precio in precios) + +# Precio máximo entre los que cuestan más de 100 +maximo_caro = max(precio for precio in precios if precio > 100) +``` + +Cuando el generador va directamente **dentro** de `sum()` o `max()`, no necesitas el paréntesis extra: `sum(precio for precio in precios)` — los paréntesis de `sum()` cuentan. + +--- + +## ¿Qué pasa si me equivoco? + +**Error: intentar medir la longitud de un generador** + +```python +gen = (n for n in [1, 2, 3]) +print(len(gen)) # TypeError: object of type 'generator' has no len() +``` + +**¿Por qué pasa?** +El generador no sabe cuántos valores producirá hasta que los produzca todos (podría ser infinito). Es como preguntarle al grifo "¿cuánta agua tienes?" antes de abrir la llave: no lo sabe. + +**¿Cómo lo soluciono?** +Si necesitas el tamaño, convierte a lista primero: `len(list(gen))`. Pero recuerda: esto consume el generador completo y guarda todo en memoria, así que pierdes la ventaja del grifo. diff --git a/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/test_main.py b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/test_main.py new file mode 100644 index 0000000..90163b4 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/3-expresiones-generadoras/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_reporte_nocturno(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "2,431" in r.stdout or "2431" in r.stdout + assert "960" in r.stdout + assert "4" in r.stdout diff --git a/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/README.md b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/README.md new file mode 100644 index 0000000..6902726 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/README.md @@ -0,0 +1,19 @@ +# Nivel 4: La Línea de Ensamble 🏭 (Reto Final del Módulo) + +## ¿Por qué este reto es especial? + +Has llegado al reto integrador del Módulo 04.5. Hasta ahora practicaste `yield`, generadores y expresiones generadoras por separado. Ahora los combinas en algo que los programadores llaman un **pipeline**: una cadena de funciones donde cada una toma los datos del anterior y los transforma, todo sin guardar listas intermedias. + +Piensa en una fábrica de juguetes. El plástico crudo entra por un extremo. Pasa por la máquina de moldeo, luego por la pintora, luego por la empacadora. Cada máquina hace su trabajo y pasa el juguete a la siguiente **de uno en uno**. No hay una bodega gigante entre cada paso. + +Ese es tu pipeline. + +## Ruta de Aprendizaje + +1. 📖 No hay `teoria.md` — ya tienes todo el conocimiento de los 3 niveles anteriores +2. 🐍 Lee el `ejemplo.py` para ver cómo se encadenan los generadores +3. 🔥 Completa el `reto.md` — construye tu propio pipeline de 3 etapas + +## Conceptos que usarás aquí + +`def`, `yield`, expresiones generadoras `(...)`, `for`, `if`, `in`, listas, diccionarios, f-strings, `sum()`, `str.strip()`, `str.split()` — todo lo del módulo 04.5 y anteriores diff --git a/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/ejemplo.py b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/ejemplo.py new file mode 100644 index 0000000..9cf467f --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/ejemplo.py @@ -0,0 +1,75 @@ +print("=== La Línea de Ensamble: Cómo se Encadenan los Generadores ===") +print() + +# Imagina una fábrica de reseñas de productos +# El texto crudo llega así: +resenas_brutas = [ + " MUY BUENO el producto llego rapido ", + " MALO: se rompio a los 2 dias ", + " EXCELENTE recomendado 100 por ciento ", + " MALO no sirve devolucion urgente ", + " BUENO precio accesible ", + " MALO calidad pesima jamas compro aqui ", +] + + +print("--- Etapa 1: Limpiador (quita espacios y pasa a minúsculas) ---") + +# Generador 1: limpia cada reseña +def limpiar(resenas): + for r in resenas: + yield r.strip().lower() # strip() quita espacios, lower() pone en minúsculas + +# Probamos la primera etapa sola +for limpia in limpiar(resenas_brutas): + print(f" '{limpia}'") + + +print() +print("--- Etapa 2: Filtro (solo las reseñas MALAS) ---") + +# Generador 2: recibe el resultado de limpiar y filtra +def filtrar_malas(resenas_limpias): + for r in resenas_limpias: + if "malo" in r: # "malo" en minúsculas porque ya limpiamos + yield r + +# Encadenamos: limpiar → filtrar_malas +# filtrar_malas recibe el GENERADOR de limpiar, no una lista +pipeline_parcial = filtrar_malas(limpiar(resenas_brutas)) + +for mala in pipeline_parcial: + print(f" ⚠ {mala}") + + +print() +print("--- Etapa 3: Formateador (agrega el emoji de alerta) ---") + +# Generador 3: toma las malas y las formatea para el reporte +def formatear_alerta(resenas_malas): + contador = 1 + for r in resenas_malas: + yield f"🚨 ALERTA #{contador}: {r.upper()}" # .upper() pone todo en MAYÚSCULAS + contador = contador + 1 + +# El pipeline completo: limpiar → filtrar_malas → formatear_alerta +# Ninguna etapa crea una lista. Los datos fluyen de una a otra de uno en uno. +pipeline_completo = formatear_alerta(filtrar_malas(limpiar(resenas_brutas))) + +print() +print("=== Reporte Final de Reseñas Negativas ===") +print() +for alerta in pipeline_completo: + print(alerta) + + +print() +print("--- Bonus: con expresión generadora para el formateador ---") + +# La tercera etapa también puede ser una expresión generadora en vez de def +resenas_limpias = limpiar(resenas_brutas) +solo_malas = filtrar_malas(resenas_limpias) +alertas = (f"🚨 {r.upper()}" for r in solo_malas) + +for a in alertas: + print(a) diff --git a/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/reto.md b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/reto.md new file mode 100644 index 0000000..68faa9e --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/reto.md @@ -0,0 +1,63 @@ +# Reto 4 (FINAL): La Fábrica de Noticias 📰 + +Felicidades, llegaste al reto integrador. Eres el sistema de análisis de noticias de un periódico digital. Recibes noticias en bruto y debes procesarlas en tres etapas, usando un pipeline de generadores. + +## El Escenario + +Tienes estas noticias en bruto: + +```python +noticias_brutas = [ + " DEPORTES: el equipo local gano el campeonato ", + " POLITICA: nueva ley aprobada por el congreso ", + " DEPORTES: jugador estrella firma contrato millonario ", + " ECONOMIA: el peso se fortalece ante el dolar ", + " DEPORTES: el torneo comienza el proximo lunes ", + " POLITICA: candidatos debaten temas de seguridad ", + " ECONOMIA: inflacion baja por tercer mes consecutivo ", + " DEPORTES: seleccion nacional convoca 23 jugadores ", +] +``` + +## Instrucciones Paso a Paso: + +Construye un pipeline de **3 etapas** y conéctalas igual que en el ejemplo: + +**Etapa 1 — `limpiar_noticia(noticias)`:** +- Función generadora con `yield`. +- Limpia cada noticia: quita los espacios con `.strip()` y pone la primera letra en mayúscula con `.title()`. + +**Etapa 2 — `filtrar_categoria(noticias_limpias, categoria)`:** +- Función generadora con `yield`. +- Solo produce las noticias que **empiecen con** la categoría buscada. +- Usa `if noticia.startswith(categoria)` — `.startswith(texto)` devuelve `True` si el string empieza con ese texto. +- Filtra por la categoría `"Deportes:"` (así queda después de `.title()`). + +**Etapa 3 — `formatear_titular(noticias_filtradas)`:** +- Función generadora con `yield`. +- Produce cada noticia formateada así: `"🏆 [TITULAR] → publicada"`. +- Usa `.upper()` para poner el titular en mayúsculas. + +Luego: +4. Conecta las 3 etapas en pipeline: `formatear_titular(filtrar_categoria(limpiar_noticia(noticias_brutas), "Deportes:"))`. +5. Recorre el pipeline con un `for` e imprime cada titular. +6. Al final, imprime cuántos titulares de deportes se publicaron (lleva un contador dentro del `for`). + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `def`, `yield`, `for`, `if`, `.strip()`, `.title()`, `.upper()`, `.startswith()`, f-strings, `print()`, contador con `+`. +❌ **Conceptos Prohibidos:** Listas intermedias entre etapas, `import`, expresiones generadoras como solución (usa `def` + `yield` para las 3 etapas). + +## Resultado Esperado en tu Terminal: + +```text +=== Titulares de Deportes — Edición de Hoy === + +🏆 DEPORTES: EL EQUIPO LOCAL GANO EL CAMPEONATO → publicada +🏆 DEPORTES: JUGADOR ESTRELLA FIRMA CONTRATO MILLONARIO → publicada +🏆 DEPORTES: EL TORNEO COMIENZA EL PROXIMO LUNES → publicada +🏆 DEPORTES: SELECCION NACIONAL CONVOCA 23 JUGADORES → publicada + +Total de titulares de Deportes publicados hoy: 4 +``` + +Crea tu código en `reto.py`. Si cambias `"Deportes:"` por `"Politica:"` en tu filtro y obtienes 2 titulares sin tocar más código, completaste el módulo. diff --git a/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/teoria.md b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/teoria.md new file mode 100644 index 0000000..1dee58d --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/teoria.md @@ -0,0 +1,69 @@ +# Teoría: La Línea de Ensamble 🏭 + +Hasta ahora, has construido chefs individuales y grifos independientes. Pero el verdadero poder de los generadores se desata cuando los conectas entre sí, formando lo que en programación se llama un **Pipeline** (tubería o línea de ensamble). + +Imagina una fábrica de juguetes: +1. **Máquina 1:** Toma plástico crudo y moldea la forma (Generador 1). +2. **Máquina 2:** Pinta el juguete moldeado (Generador 2). +3. **Máquina 3:** Empaca el juguete pintado (Generador 3). + +Si estas máquinas usaran **listas** (cubos), la Máquina 1 tendría que moldear 10,000 juguetes y guardarlos en una bodega gigante antes de que la Máquina 2 pudiera empezar a pintar. + +Como usan **generadores** (grifos), la Máquina 1 moldea *un* juguete y se lo pasa a la Máquina 2, que lo pinta y se lo pasa a la Máquina 3. No hay bodegas. El juguete fluye sin interrupciones. + +--- + +## Anatomía de un Pipeline + +Un pipeline se forma simplemente pasando un generador como entrada a otro generador. + +```python +# 1. Los datos crudos +datos = [1, 2, 3, 4, 5] + +# 2. Las "máquinas" (funciones generadoras) +def duplicar(numeros): + for n in numeros: + yield n * 2 + +def restar_uno(numeros): + for n in numeros: + yield n - 1 + +# 3. La conexión del pipeline (de adentro hacia afuera) +# restar_uno toma como entrada lo que escupe duplicar +pipeline = restar_uno(duplicar(datos)) + +# 4. El encendido +for resultado in pipeline: + print(resultado) +``` + +Desmontando la conexión: `restar_uno(duplicar(datos))` +- Primero, `duplicar(datos)` crea un generador, pero no procesa nada todavía. +- Segundo, `restar_uno(...)` recibe ese generador y crea otro generador encima. +- Cuando el `for` pide el primer valor, `restar_uno` le pide un valor a `duplicar`, `duplicar` saca el `1` de la lista, lo duplica a `2`, y se lo pasa a `restar_uno`, que le resta uno y entrega el `1` final. + +--- + +## ¿Qué pasa si me equivoco? + +**El error más común: Meter listas donde van generadores** + +A veces, por costumbre, los programadores construyen el pipeline mal, rompiendo la cadena de eficiencia: + +```python +def duplicar_mal(numeros): + lista = [] + for n in numeros: + lista.append(n * 2) + return lista # ¡ROTO! Esto es un cubo, no un grifo. + +pipeline_roto = restar_uno(duplicar_mal(datos)) +``` + +**¿Por qué pasa?** +Si alguna etapa intermedia usa `return` con una lista completa, o usa una List Comprehension `[...]`, rompes el pipeline. Ese paso obligará a cargar todo en memoria antes de pasarlo al siguiente. + +**¿Cómo lo soluciono?** +Asegúrate de que **cada etapa** del proceso use `yield` o una expresión generadora `(...)`. El único lugar donde se permite una lista es en los datos crudos iniciales, o si al final del pipeline decides guardar el resultado final en una lista con `list(pipeline)`. diff --git a/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/test_main.py b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/test_main.py new file mode 100644 index 0000000..f463147 --- /dev/null +++ b/python/Modulo-04.5-Iteracion-Avanzada/4-RETO-pipeline/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +RETO = os.path.join(os.path.dirname(__file__), "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_pipeline_deportes(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "DEPORTES" in r.stdout + assert "publicada" in r.stdout.lower() + assert "4" in r.stdout diff --git a/python/Modulo-05-Funciones/.gitkeep b/python/Modulo-05-Funciones/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/41-def/.gitkeep b/python/Modulo-05-Funciones/41-def/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/41-def/README.md b/python/Modulo-05-Funciones/41-def/README.md new file mode 100644 index 0000000..24c5b6c --- /dev/null +++ b/python/Modulo-05-Funciones/41-def/README.md @@ -0,0 +1,16 @@ +# Nivel 41: Empaquetando Instrucciones con `def` 📦 + +¡Felicidades por llegar al Módulo 5! Hasta ahora has sido un excelente "seguidor de instrucciones", usando comandos que Python ya traía de fábrica como `print()`, `input()`, o `len()`. Pero, ¿qué pasaría si tú pudieras inventar **tus propios comandos**? + +En la vida real, si tienes una rutina de mañana (levantarte, lavarte los dientes, desayunar, vestirte), no le dices a tu cerebro cada paso individual todos los días. Simplemente piensas "Hacer rutina de mañana", y tu cuerpo ejecuta todos los pasos. + +En programación, esto es fundamental. Nos permite: +1. **Evitar repetir código:** No escribas las mismas 10 líneas una y otra vez. +2. **Darle un nombre humano a un proceso complejo:** Es más fácil leer `mostrar_menu()` que leer 20 `print()` seguidos. + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Aprenderemos la palabra mágica `def` y cómo guardar pasos bajo un mismo nombre. +2. **Ejemplo:** Veremos un script donde definimos nuestra primera receta y luego le ordenamos a Python que la cocine. +3. **Reto:** Tendrás que crear tu propio comando para construir un sistema de alertas. + +¡Prepárate para dar el salto de "usuario de Python" a "creador de herramientas"! diff --git a/python/Modulo-05-Funciones/41-def/ejemplo.py b/python/Modulo-05-Funciones/41-def/ejemplo.py new file mode 100644 index 0000000..5883102 --- /dev/null +++ b/python/Modulo-05-Funciones/41-def/ejemplo.py @@ -0,0 +1,35 @@ +# Aquí estamos definiendo nuestra primera "receta" (función). +# Usamos 'def' para avisar a Python que vamos a inventar un comando. +# Le llamamos 'imprimir_separador' +def imprimir_separador(): + # Todo lo que tiene espacios a la izquierda pertenece a esta receta. + print("====================================") + print(" 🌟🌟🌟🌟🌟🌟 ") + print("====================================") + +# Definamos una segunda función para mostrar información +def mostrar_perfil(): + print("Nombre: Jugador 1") + print("Nivel: 41") + print("Estado: Aprendiendo funciones") + + +print("--- Iniciando el programa ---") + +# ¡ATENCIÓN! Si corres el código hasta aquí (ignorando lo de abajo), +# no verás los "========" ni la información del jugador. +# ¿Por qué? Porque 'def' solo GUARDA los pasos, no los ejecuta. + +print("\n--- Llamando a nuestras funciones ---") + +# Ahora sí, le damos la orden a Python: "Ve a buscar la receta imprimir_separador y cocínala" +imprimir_separador() + +# Ahora le decimos que muestre el perfil +mostrar_perfil() + +# Y podemos reutilizar nuestro separador tantas veces como queramos. +# ¡Esa es la magia! Nos ahorramos escribir todos esos prints de nuevo. +imprimir_separador() + +print("\n--- Fin del programa ---") diff --git a/python/Modulo-05-Funciones/41-def/reto.md b/python/Modulo-05-Funciones/41-def/reto.md new file mode 100644 index 0000000..71a555a --- /dev/null +++ b/python/Modulo-05-Funciones/41-def/reto.md @@ -0,0 +1,40 @@ +# Reto 41: El Sistema de Alertas Cósmicas 🚀 + +Tu nave espacial está recibiendo múltiples advertencias, y estás cansado de escribir manualmente todo el formato de la alerta de peligro en la consola de la nave cada vez que un asteroide se acerca. + +Vas a automatizar esto creando tu propio comando de alerta. + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Usando la palabra mágica `def`, inventa una función llamada `alerta_peligro()`. +3. Dentro de tu función (con la indentación correcta), escribe al menos tres `print()` que muestren un mensaje de peligro muy llamativo (puedes usar emojis y símbolos como `!`, `*`, `[!]`). +4. Fuera de la función (sin indentación), simula que la nave está analizando un sector. Usa un bucle `for` o `while` (de los módulos anteriores) que se repita 3 veces. +5. En cada repetición del bucle, muestra un texto como `"Analizando sector..."` y luego **llama a tu función `alerta_peligro()`**. + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `def`, `print`, `for` o `while`. +- **Prohibido:** Poner texto directamente en tu bucle sin usar la función. Está prohibido usar argumentos o parámetros dentro de los paréntesis `()` de tu función (todavía no sabemos usarlos, así que déjalos vacíos). + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar algo exactamente como esto: + +```text +Analizando sector... +[!] ¡¡ALERTA ROJA!! [!] +[!] IMPACTO INMINENTE [!] +[!] PREPARAR ESCUDOS [!] + +Analizando sector... +[!] ¡¡ALERTA ROJA!! [!] +[!] IMPACTO INMINENTE [!] +[!] PREPARAR ESCUDOS [!] + +Analizando sector... +[!] ¡¡ALERTA ROJA!! [!] +[!] IMPACTO INMINENTE [!] +[!] PREPARAR ESCUDOS [!] +``` + +¡Demuestra que ya no eres un simple pasajero de la nave, sino el ingeniero principal! 🛠️ diff --git a/python/Modulo-05-Funciones/41-def/teoria.md b/python/Modulo-05-Funciones/41-def/teoria.md new file mode 100644 index 0000000..6f83dda --- /dev/null +++ b/python/Modulo-05-Funciones/41-def/teoria.md @@ -0,0 +1,52 @@ +# Teoría: El Nacimiento de tus Propios Comandos 🛠️ + +Piensa en una **función** como si fuera la receta de un pastel guardada en un libro. +Mientras la receta está en el libro, no hace nada; es solo texto. Para tener un pastel, alguien tiene que ir, leer la receta, y ejecutar los pasos. + +En Python ocurre exactamente lo mismo: +1. **Definir la función:** Es escribir la receta en el libro. +2. **Llamar a la función:** Es darle la orden a Python de que vaya al libro y cocine la receta en ese preciso momento. + +## 🧬 Anatomía de una Función (Paso a paso) + +Para inventar un nuevo comando, usamos esta estructura: + +```python +def mostrar_bienvenida(): + print("¡Hola!") + print("Bienvenido a nuestro sistema.") +``` + +Vamos a desarmar los símbolos (nuestro desmontaje conceptual): + +- `def`: Es una palabra reservada (propia de Python) que significa **"define"** (o "voy a inventar algo nuevo"). Le avisa a la computadora: "Atención, lo que viene no es para ejecutarlo ya, es para guardarlo". +- `mostrar_bienvenida`: Es el **nombre** que tú eliges para tu función. Debe usar letras minúsculas y guiones bajos (snake_case). Este será el nombre de tu comando. +- `()`: Son los paréntesis. Por ahora están vacíos. Imagina que son una "bandejita" donde más adelante pondremos ingredientes. Aunque no necesites ingredientes hoy, la bandejita **tiene que estar**. Es la regla de oro para que Python sepa que es una función y no una variable. +- `:`: Los dos puntos significan **"aquí empiezan los pasos a guardar"**. +- **Indentación (los 4 espacios a la izquierda):** Igual que en los `if` o `for`, todo lo que esté "metido hacia la derecha" es lo que pertenece a esta función. En cuanto el código vuelva al margen izquierdo, la función habrá terminado. + +### ¿Cómo ordeno que se ejecute? (Llamar a la función) + +Una vez que la has definido, en cualquier parte de tu código más abajo, solo escribes su nombre seguido de los paréntesis: + +```python +mostrar_bienvenida() +``` +¡Boom! Python busca la receta llamada `mostrar_bienvenida`, ejecuta los dos `print`, y luego sigue con el resto de tu código. + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Olvidar los dos puntos `:` o los paréntesis `()` +**El síntoma en la terminal:** `SyntaxError: expected ':'` o `SyntaxError: invalid syntax` +**¿Por qué pasa?** Porque rompiste la gramática de Python. La computadora lee `def mi_comando` y se queda esperando los símbolos que completan la declaración. ¡Revisa el final de la línea! + +### Error 2: Escribir el código y que no pase absolutamente nada en pantalla +**El síntoma en la terminal:** El programa termina y no hay texto, no hay errores, simplemente nada. +**¿Por qué pasa?** ¡Escribiste la receta en el libro pero **nunca le diste la orden de cocinarla**! +Definir la función (`def`) solo la guarda en memoria. Tienes que "llamarla" escribiendo su nombre con paréntesis en el margen izquierdo: `mi_comando()`. + +### Error 3: IndentationError +**El síntoma en la terminal:** `IndentationError: expected an indented block` +**¿Por qué pasa?** Pusiste los dos puntos `:`, pero en la línea de abajo empezaste a escribir pegado al borde izquierdo. Python exige que los pasos de la función tengan espacios a la izquierda para saber qué pertenece adentro y qué queda afuera. diff --git a/python/Modulo-05-Funciones/41-def/test_main.py b/python/Modulo-05-Funciones/41-def/test_main.py new file mode 100644 index 0000000..5c94a29 --- /dev/null +++ b/python/Modulo-05-Funciones/41-def/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_alerta_impresa_tres_veces(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.stdout.count("\n") >= 3, "La función debe llamarse al menos 3 veces" diff --git a/python/Modulo-05-Funciones/42-parametros-return/.gitkeep b/python/Modulo-05-Funciones/42-parametros-return/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/42-parametros-return/README.md b/python/Modulo-05-Funciones/42-parametros-return/README.md new file mode 100644 index 0000000..d571413 --- /dev/null +++ b/python/Modulo-05-Funciones/42-parametros-return/README.md @@ -0,0 +1,19 @@ +# Nivel 42: Ingredientes y Resultados (`parametros` y `return`) 🥘 + +En el nivel anterior aprendimos a crear recetas (funciones). Pero había un problema: nuestra receta siempre cocinaba exactamente lo mismo. Si hacíamos una función de saludo, siempre decía "¡Hola!". + +¿Qué pasa si queremos una máquina que salude a *cualquier* persona? ¿O una licuadora que haga un batido dependiendo de la fruta que le pongas hoy? + +En la vida real: +1. Le entregas **ingredientes** a la licuadora (fresas, leche). +2. La licuadora hace su trabajo interno. +3. La licuadora te **devuelve** el batido listo para que te lo tomes. + +En Python, a los "ingredientes" los llamamos **parámetros**, y a la acción de "devolver el batido" la llamamos **return**. + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Aprenderemos cómo usar la bandejita `()` para meter datos y la palabra mágica `return` para sacar datos. +2. **Ejemplo:** Construiremos nuestra propia máquina de batidos y una calculadora de precios en código. +3. **Reto:** Crearás una calculadora de daño para el combate de un videojuego. + +¡Es hora de que tus funciones se vuelvan inteligentes y adaptables! diff --git a/python/Modulo-05-Funciones/42-parametros-return/ejemplo.py b/python/Modulo-05-Funciones/42-parametros-return/ejemplo.py new file mode 100644 index 0000000..215702f --- /dev/null +++ b/python/Modulo-05-Funciones/42-parametros-return/ejemplo.py @@ -0,0 +1,51 @@ +print("--- 1. Función con Parámetros (Ingredientes) ---") + +# 'fruta' y 'liquido' son parámetros. Son etiquetas vacías. +def preparar_batido(fruta, liquido): + # Usamos las etiquetas dentro de la función + print("Licuando " + fruta + " con " + liquido + "...") + print("¡Batido terminado! 🥤") + +# Ahora llamamos a la función y le pasamos los datos reales (Argumentos) +preparar_batido("Fresa", "Leche") +preparar_batido("Plátano", "Agua") +# Fíjate cómo la misma función hace cosas distintas gracias a los parámetros. + + +print("\n--- 2. Función con Return (Entregando resultados) ---") + +# Esta función calcula el precio con impuesto, pero NO lo imprime. +# Solo hace el cálculo matemático y TE LO DEVUELVE. +def calcular_precio_final(precio_producto, impuesto): + aumento = precio_producto * (impuesto / 100) + total = precio_producto + aumento + + # Aquí escupimos el valor hacia afuera. + # ¡La función termina inmediatamente en esta línea! + return total + +# Como la función escupe un valor, necesitamos una variable ('billetera') para atraparlo. +# Llamamos a la función con 100 de precio y 15 de impuesto. +precio_zapatos = calcular_precio_final(100, 15) +precio_camisa = calcular_precio_final(50, 10) + +print("El precio a pagar por los zapatos es:", precio_zapatos) +print("El precio a pagar por la camisa es:", precio_camisa) + +# Podemos incluso usar el resultado directamente en otras operaciones +total_compra = precio_zapatos + precio_camisa +print("En total vas a pagar:", total_compra) + + +print("\n--- 3. El error de usar print en vez de return ---") + +def funcion_tramposa(numero): + print("Procesando el número:", numero) + # ¡Ups! Olvidé poner 'return' + +# Intento atrapar el valor... +resultado_tramposo = funcion_tramposa(99) + +# Mira lo que pasa si trato de ver qué atrapé +print("El valor atrapado es:", resultado_tramposo) +# Verás que dice 'None', porque la función no me entregó nada físico. diff --git a/python/Modulo-05-Funciones/42-parametros-return/reto.md b/python/Modulo-05-Funciones/42-parametros-return/reto.md new file mode 100644 index 0000000..c123963 --- /dev/null +++ b/python/Modulo-05-Funciones/42-parametros-return/reto.md @@ -0,0 +1,30 @@ +# Reto 42: La Calculadora de Daño ⚔️ + +Estás programando el sistema de combate de un videojuego RPG. Cada vez que un guerrero ataca, el daño real que recibe el enemigo depende del ataque del guerrero menos la armadura del enemigo. + +Necesitas crear una función matemática inteligente que haga este cálculo para no tener que escribir la fórmula matemática en cada pelea. + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Define una función llamada `calcular_dano`. +3. Tu función debe pedir (dentro de sus paréntesis) dos parámetros: `ataque` y `armadura`. +4. Dentro de la función, crea una variable llamada `dano_final` que sea igual al `ataque` menos la `armadura`. +5. Si el `dano_final` es menor a 0, haz que sea igual a 0 (porque los ataques no pueden curar al enemigo, ¿verdad? Usa un `if` que ya aprendiste en el módulo de flujo). +6. Usa la palabra `return` para escupir el `dano_final` hacia el exterior. ¡Cuidado! **No uses `print` dentro de la función para mostrar el resultado**. +7. Fuera de la función, imagina que un Orco ataca a un Elfo. Llama a tu función pasando `50` de ataque y `30` de armadura. Atrapa el resultado en una variable llamada `golpe_orco`. +8. Usa un `print()` (ahora sí, fuera de la función) para mostrar cuánto daño recibió el Elfo usando tu variable `golpe_orco`. +9. Llama de nuevo a la función para un duende que ataca con `10` a un caballero con `50` de armadura. Atrapa el resultado e imprímelo para probar que tu `if` funciona y el daño es 0, no un número negativo. + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `def`, `return`, parámetros, variables, matemáticas básicas (`-`), `if`, `print` (fuera del `def`). +- **Prohibido:** Usar `print()` DENTRO de la función para mostrar el número. La función DEBE ser puramente de cálculo usando `return`. + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar algo como esto: + +```text +El Orco ataca! Daño infligido: 20 +El Duende ataca! Daño infligido: 0 +``` diff --git a/python/Modulo-05-Funciones/42-parametros-return/teoria.md b/python/Modulo-05-Funciones/42-parametros-return/teoria.md new file mode 100644 index 0000000..d7b9f2b --- /dev/null +++ b/python/Modulo-05-Funciones/42-parametros-return/teoria.md @@ -0,0 +1,59 @@ +# Teoría: La Licuadora de Datos 🌪️ + +Hasta ahora, nuestra bandejita `()` estaba vacía. Hoy vamos a ponerle etiquetas. + +## 1. Parámetros (Los Ingredientes) + +Cuando **defines** la función, pones "etiquetas" (variables temporales) dentro de los paréntesis. Esto le dice a Python: *"Para que esta receta funcione, me tienes que dar estos datos"*. + +```python +def saludar_jugador(nombre): + print("¡Bienvenido al nivel, " + nombre + "!") +``` + +- `nombre`: Es una variable que **solo existe dentro de esta función**. Es un hueco vacío esperando ser llenado. + +Cuando **llamas** a la función, tienes que entregarle el valor real (el ingrediente de verdad): + +```python +saludar_jugador("Alejandro") +``` +Python automáticamente dice: *Ah, "Alejandro" se guarda en la caja `nombre`*. + +## 2. El comando `return` (Entregando el platillo) + +Hasta ahora usábamos `print()`. El problema de `print()` es que solo muestra un mensaje en la pantalla de la terminal, pero el programa "olvida" ese valor inmediatamente. + +Imagina que le das dinero a un cajero automático para que lo cuente. Si el cajero usa `print()`, solo te grita en la cara: *"¡TIENES 100 DÓLARES!"*, pero no te da el dinero. +Si el cajero usa `return`, te **entrega** físicamente los billetes para que tú puedas guardarlos en tu billetera. + +```python +def sumar(numero1, numero2): + resultado = numero1 + numero2 + return resultado # ¡Aquí escupe el dato hacia afuera! +``` + +- `return`: Es una palabra mágica que significa **"Termina la función AHORA MISMO y escupe este valor hacia afuera"**. +Cualquier código que pongas debajo de un `return` en la misma función, jamás se ejecutará, porque `return` es una puerta de salida inmediata. + +Al llamar a una función que tiene `return`, debes **atrapar** el valor en una variable (tu billetera): + +```python +mi_dinero = sumar(50, 50) # mi_dinero ahora vale 100 +``` + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Olvidar un ingrediente +**El síntoma en la terminal:** `TypeError: sumar() missing 1 required positional argument: 'numero2'` +**¿Por qué pasa?** Tu receta exigía 2 ingredientes (ej. `def sumar(a, b):`), pero cuando la llamaste solo le diste uno (`sumar(5)`). ¡Python no sabe qué hacer con el hueco vacío! Tienes que darle exactamente la misma cantidad de ingredientes que pide la receta. + +### Error 2: Intentar usar una variable de la función afuera de ella +**El síntoma en la terminal:** `NameError: name 'resultado' is not defined` +**¿Por qué pasa?** Las cajas (variables) que creas dentro de una función **nacen y mueren** dentro de esa función. Son secretas. Si creaste `resultado` dentro del `def`, no puedes usar un `print(resultado)` afuera sin haber usado `return`. + +### Error 3: Imprimir en lugar de Retornar +**El síntoma en la terminal:** Al intentar guardar el valor e imprimirlo, sale la palabra `None` (Nada). +**¿Por qué pasa?** Hiciste `def calcular(): print(5 + 5)` y luego `mi_variable = calcular()`. Como no usaste `return`, la función no te entregó nada físico. Mostró un 10 en la pantalla y te entregó aire (`None`). ¡Usa `return` si quieres guardar el dato! diff --git a/python/Modulo-05-Funciones/42-parametros-return/test_main.py b/python/Modulo-05-Funciones/42-parametros-return/test_main.py new file mode 100644 index 0000000..f27114d --- /dev/null +++ b/python/Modulo-05-Funciones/42-parametros-return/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_calcular_dano(): + sys.path.insert(0, BASE) + from reto import calcular_dano + assert calcular_dano(50, 30) == 20 + assert calcular_dano(10, 50) == 0 or calcular_dano(10, 50) < 0 diff --git a/python/Modulo-05-Funciones/43-args-defecto/.gitkeep b/python/Modulo-05-Funciones/43-args-defecto/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/43-args-defecto/README.md b/python/Modulo-05-Funciones/43-args-defecto/README.md new file mode 100644 index 0000000..361f96e --- /dev/null +++ b/python/Modulo-05-Funciones/43-args-defecto/README.md @@ -0,0 +1,14 @@ +# Nivel 43: El "Plan B" (Argumentos por Defecto) 🍔 + +En el nivel anterior aprendimos que si una receta pide 2 ingredientes (parámetros), y tú le pasas solo 1 al llamarla, Python entra en pánico y te lanza un error. + +Pero en la vida real, somos más flexibles. Piensa en cuando pides una hamburguesa en un restaurante de comida rápida. Si solo dices: *"Quiero una hamburguesa"*, ellos asumen por **defecto** que la quieres con kétchup. No tienes que decirlo. Pero si quieres, tienes la opción de decir: *"Quiero una hamburguesa con mayonesa"*, y ellos cambian el kétchup por la mayonesa. + +En programación, muchas veces queremos que nuestras funciones tengan un comportamiento "normal" o "por defecto", pero dejando la puerta abierta para que el usuario lo cambie si quiere. A esto le llamamos **Argumentos por Defecto** (o parámetros opcionales). + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Aprenderemos cómo usar el símbolo `=` dentro de la bandejita `()` para crear un "Plan B". Conoceremos la regla de oro del orden. +2. **Ejemplo:** Crearemos un sistema de creación de perfiles de usuario que tenga valores predeterminados. +3. **Reto:** Serás el barista de una cafetería galáctica manejando pedidos rápidos y personalizados. + +¡Vamos a hacer que tus funciones sean más amables y flexibles! diff --git a/python/Modulo-05-Funciones/43-args-defecto/ejemplo.py b/python/Modulo-05-Funciones/43-args-defecto/ejemplo.py new file mode 100644 index 0000000..42f6bcb --- /dev/null +++ b/python/Modulo-05-Funciones/43-args-defecto/ejemplo.py @@ -0,0 +1,48 @@ +print("--- 1. Función con Parámetro por Defecto ---") + +# 'mensaje' es obligatorio. +# 'simbolo' es opcional. Si no nos dan uno, usaremos un asterisco '*'. +def enmarcar_texto(mensaje, simbolo="*"): + borde = simbolo * 20 + print(borde) + print(mensaje) + print(borde) + print("") # Línea en blanco + +# Llamada 1: Solo damos el mensaje. +# Python usa el '*' por defecto para el símbolo. +print("Llamada 1 (Solo mensaje):") +enmarcar_texto("¡Hola Mundo!") + +# Llamada 2: Damos ambos. +# El '=' sobreescribe al '*' porque nosotros se lo ordenamos. +print("Llamada 2 (Mensaje y símbolo nuevo):") +enmarcar_texto("¡PELIGRO!", "=") + +# Llamada 3: Otro símbolo diferente +print("Llamada 3 (Mensaje y otro símbolo):") +enmarcar_texto("Victoria", "~") + + +print("--- 2. Creación de Usuarios (Múltiples Defectos) ---") + +# 'nombre' es obligatorio. +# 'pais' y 'suscripcion' son opcionales. +def registrar_usuario(nombre, pais="Desconocido", suscripcion="Gratis"): + print("Nuevo usuario:", nombre) + print("Origen:", pais) + print("Plan:", suscripcion) + print("--------------------") + +# Pasamos solo el obligatorio. Los otros dos usan el Plan B. +registrar_usuario("Alejandro") + +# Pasamos dos datos. Python los asigna de izquierda a derecha: +# "María" va a 'nombre'. "México" va a 'pais'. 'suscripcion' usa el Plan B. +registrar_usuario("María", "México") + +# Pasamos los tres datos. Ninguno usa el Plan B. +registrar_usuario("Carlos", "Argentina", "Premium") + +# (En el próximo nivel veremos cómo saltarnos el orden para cambiar solo +# la suscripción sin cambiar el país, pero por ahora, siempre va de izquierda a derecha). diff --git a/python/Modulo-05-Funciones/43-args-defecto/reto.md b/python/Modulo-05-Funciones/43-args-defecto/reto.md new file mode 100644 index 0000000..a0f0692 --- /dev/null +++ b/python/Modulo-05-Funciones/43-args-defecto/reto.md @@ -0,0 +1,34 @@ +# Reto 43: La Cafetería Espacial ☕ + +Has sido contratado en la cafetería de la Estación Espacial. La mayoría de los alienígenas piden café con exactamente 2 cucharadas de azúcar cósmica. Pero algunos prefieren sin azúcar, y otros con muchísima. Además, el tamaño normal siempre es "Mediano", a menos que pidan lo contrario. + +Para atender rápido, vas a crear una función que asuma el comportamiento normal, pero que permita cambios si el cliente lo exige. + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Define una función llamada `preparar_cafe`. +3. Tu función debe tener 3 parámetros en este exacto orden: + - `tipo` (obligatorio, ej: "Expreso", "Capuchino"). + - `azucar` (opcional, por defecto debe ser `2`). + - `tamano` (opcional, por defecto debe ser `"Mediano"`). +4. Dentro de la función, haz que imprima el siguiente mensaje uniendo los datos: + `"Preparando un [tipo] de tamaño [tamano] con [azucar] cucharadas de azúcar."` +5. Fuera de la función, vas a simular la atención de 3 clientes llamando a tu función de 3 maneras distintas: + - **Cliente 1:** Quiere un "Latte" y no dice nada más. (Solo pásale 1 argumento). + - **Cliente 2:** Quiere un "Expreso" con `0` de azúcar. (Pásale 2 argumentos, no le pases el tamaño para que use el defecto). + - **Cliente 3:** Quiere un "Moca" con `5` de azúcar, tamaño `"Grande"`. (Pásale los 3 argumentos). + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `def`, parámetros opcionales (`=`), `print`. +- **Prohibido:** Crear funciones distintas para cada cliente. DEBES usar una sola función `preparar_cafe` aprovechando los argumentos por defecto. + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar algo exactamente como esto: + +```text +Preparando un Latte de tamaño Mediano con 2 cucharadas de azúcar. +Preparando un Expreso de tamaño Mediano con 0 cucharadas de azúcar. +Preparando un Moca de tamaño Grande con 5 cucharadas de azúcar. +``` diff --git a/python/Modulo-05-Funciones/43-args-defecto/teoria.md b/python/Modulo-05-Funciones/43-args-defecto/teoria.md new file mode 100644 index 0000000..8361211 --- /dev/null +++ b/python/Modulo-05-Funciones/43-args-defecto/teoria.md @@ -0,0 +1,57 @@ +# Teoría: La Salsa por Defecto 🥫 + +Hasta ahora usábamos los parámetros como huecos vacíos obligatorios. Hoy vamos a ponerles un "valor de rescate". + +## 1. Asignando un Plan B + +Cuando **defines** la función, puedes usar el símbolo `=` al lado del parámetro. +¡Atención! Aquí el `=` no funciona exactamente igual que cuando creas una variable normal. Aquí el `=` significa: *"Si el programador olvida o decide no darme este ingrediente, usa esto en su lugar"*. + +```python +def pedir_hamburguesa(salsa="ketchup"): + print("Preparando hamburguesa con", salsa) +``` + +Si la llamamos vacía: +```python +pedir_hamburguesa() +# No le pasamos nada. Python dice: "¡No hay problema! Uso el Plan B: ketchup". +# Salida: Preparando hamburguesa con ketchup +``` + +Si le pasamos un valor, **destruimos el Plan B**: +```python +pedir_hamburguesa("mayonesa") +# Python dice: "Me diste un valor, así que ignoro el ketchup y uso mayonesa". +# Salida: Preparando hamburguesa con mayonesa +``` + +## 2. La Regla de Oro del Orden ⚖️ + +Cuando combinas parámetros obligatorios (los que no tienen `=`) y parámetros opcionales (los que sí tienen `=`), **los obligatorios siempre deben ir PRIMERO de izquierda a derecha**. + +**✅ CORRECTO:** +```python +def crear_personaje(nombre, nivel=1, vida=100): +``` +*(Primero el obligatorio `nombre`, luego los opcionales `nivel` y `vida`).* + +**❌ INCORRECTO:** +```python +def crear_personaje(nivel=1, nombre): +``` +*(No puedes poner un opcional antes de uno obligatorio).* + +¿Por qué? Porque cuando llamas a la función y pones `crear_personaje("Arthur")`, Python lee de izquierda a derecha. Si pusieras los opcionales primero, Python no sabría si "Arthur" es el nivel, el nombre, o qué. ¡Los obligatorios siempre van al principio de la fila! + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Romper la regla del orden +**El síntoma en la terminal:** `SyntaxError: non-default argument follows default argument` +**¿Por qué pasa?** Como dice el inglés literal: "Un argumento no-por-defecto (obligatorio) está siguiendo a uno por-defecto (opcional)". Python te está regañando porque pusiste un parámetro con `=` antes de uno normal en los paréntesis de tu `def`. ¡Mueve los que tienen `=` al final! + +### Error 2: Pasar demasiados argumentos +**El síntoma en la terminal:** `TypeError: pedir_hamburguesa() takes from 0 to 1 positional arguments but 2 were given` +**¿Por qué pasa?** Tu función tenía un parámetro opcional, y al llamarla intentaste pasarle 2 cosas (ej. `pedir_hamburguesa("mayonesa", "mostaza")`). Aunque tenga un Plan B, la caja `salsa` sigue siendo UNA sola caja. No puedes meterle dos cosas si la función no tiene otra variable esperando. diff --git a/python/Modulo-05-Funciones/43-args-defecto/test_main.py b/python/Modulo-05-Funciones/43-args-defecto/test_main.py new file mode 100644 index 0000000..f95d2e8 --- /dev/null +++ b/python/Modulo-05-Funciones/43-args-defecto/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_funcion_con_defecto(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr diff --git a/python/Modulo-05-Funciones/44-args-kwargs/.gitkeep b/python/Modulo-05-Funciones/44-args-kwargs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/44-args-kwargs/README.md b/python/Modulo-05-Funciones/44-args-kwargs/README.md new file mode 100644 index 0000000..2a647ab --- /dev/null +++ b/python/Modulo-05-Funciones/44-args-kwargs/README.md @@ -0,0 +1,18 @@ +# Nivel 44: Cajas Mágicas y Etiquetas (`*args` y `**kwargs`) 🪄 + +En el nivel anterior aprendimos a dejar valores "por defecto" (un Plan B). Y prometimos que aprenderíamos a romper el estricto orden de izquierda a derecha. Hoy cumplimos esa promesa. + +Además, nos enfrentaremos a un nuevo problema: ¿Qué pasa si estás construyendo un carrito de compras y no sabes cuántos productos va a meter el usuario? ¿Deberías crear una función con 100 parámetros por si acaso? ¡No! + +En la vida real, si vas a una tienda, no llevas una bolsa especial para cada uva que compras. Llevas **una sola bolsa grande** y metes todas las uvas ahí. + +En Python, existen dos símbolos mágicos que actúan como "bolsas infinitas": +1. El asterisco simple `*`: Crea una bolsa para cosas sueltas. +2. El asterisco doble `**`: Crea un archivero para cosas con etiqueta. + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Aprenderemos a llamar a los ingredientes por su nombre (Keyword Arguments) y a usar los símbolos `*` y `**` para atrapar cantidades infinitas de datos. +2. **Ejemplo:** Crearemos un sistema que recibe cualquier cantidad de invitados y configuraciones. +3. **Reto:** Programarás el "Inventario Infinito" de un aventurero. + +¡Prepárate para darle superpoderes de flexibilidad a tus funciones! diff --git a/python/Modulo-05-Funciones/44-args-kwargs/ejemplo.py b/python/Modulo-05-Funciones/44-args-kwargs/ejemplo.py new file mode 100644 index 0000000..cc15293 --- /dev/null +++ b/python/Modulo-05-Funciones/44-args-kwargs/ejemplo.py @@ -0,0 +1,60 @@ +print("--- 1. Llamar por Nombre (Saltarse el orden) ---") + +def crear_servidor(nombre, memoria="2GB", disco="50GB", estado="Apagado"): + print("Servidor:", nombre) + print("RAM:", memoria, "| Disco:", disco, "| Estado:", estado) + print("-" * 20) + +# Solo damos el nombre, el resto usa el Plan B +crear_servidor("Web_Principal") + +# Queremos cambiar el estado a "Encendido", pero no queremos cambiar RAM ni Disco. +# Solución: Lo llamamos por su etiqueta directamente. +crear_servidor("Base_De_Datos", estado="Encendido") + + +print("\n--- 2. Empaquetando Infinitos con *args ---") + +# El '*' convierte 'nombres' en una tupla con todos los valores extra +def pasar_lista(profesor, *nombres): + print("Profesor a cargo:", profesor) + print("Alumnos presentes:") + + # Recorremos la caja mágica 'nombres' (que es una tupla) + for alumno in nombres: + print("-", alumno) + +# "Snape" se guarda en 'profesor'. +# Todo el resto se va directo a la bolsa '*nombres'. +pasar_lista("Snape", "Draco", "Crabbe", "Goyle", "Pansy") + + +print("\n--- 3. Empaquetando Etiquetas con **kwargs ---") + +# El '**' convierte 'detalles' en un diccionario +def imprimir_ficha_tecnica(producto, **detalles): + print("FICHA DE:", producto) + + # Recorremos el diccionario 'detalles' + for etiqueta in detalles: + # Imprimimos la llave (etiqueta) y su valor + print(etiqueta, ":", detalles[etiqueta]) + +# "Laptop X" se guarda en 'producto'. +# El resto trae etiquetas, así que Python las empaca en el diccionario '**detalles'. +imprimir_ficha_tecnica("Laptop X", procesador="Core i9", ram="32GB", color="Negro") + + +print("\n--- 4. ¡Todo junto! ---") + +# Primero los normales, luego el *, luego el ** +def operacion_compleja(mision, *agentes, **equipamiento): + print("Misión:", mision) + print("Agentes asignados:", agentes) # Esto imprimirá una tupla () + print("Equipamiento:", equipamiento) # Esto imprimirá un diccionario {} + +# Fíjate cómo Python sabe exactamente a dónde mandar cada cosa: +# "Infiltración" -> mision +# "007", "008" -> *agentes (porque no tienen etiqueta) +# coche="Aston Martin", arma="Walther" -> **equipamiento (porque sí tienen etiqueta) +operacion_compleja("Infiltración", "007", "008", coche="Aston Martin", arma="Walther") diff --git a/python/Modulo-05-Funciones/44-args-kwargs/reto.md b/python/Modulo-05-Funciones/44-args-kwargs/reto.md new file mode 100644 index 0000000..0725ac5 --- /dev/null +++ b/python/Modulo-05-Funciones/44-args-kwargs/reto.md @@ -0,0 +1,42 @@ +# Reto 44: El Inventario Infinito 🎒 + +En tu juego RPG, el aventurero puede recoger cualquier cantidad de objetos en su aventura. A veces recoge 2 objetos, a veces recoge 50. Además, el aventurero tiene estadísticas mágicas que pueden variar (algunas veces tiene `fuerza`, otras veces tiene `suerte` y `carisma`). + +Tu trabajo es crear una función maestra capaz de recibir TODO esto sin importar la cantidad. + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Define una función llamada `guardar_progreso`. +3. Tu función debe recibir, en este estricto orden: + - Un parámetro obligatorio llamado `personaje`. + - La caja infinita para objetos sueltos `*objetos` (args). + - El archivero de etiquetas para estadísticas `**estadisticas` (kwargs). +4. Dentro de la función, usa `print` para mostrar el nombre del `personaje`. +5. Usa un bucle `for` para recorrer la tupla `objetos` e imprimir cada uno. +6. Usa otro bucle `for` para recorrer el diccionario `estadisticas` e imprimir el nombre de la estadística y su valor (ej: `fuerza : 99`). +7. Fuera de la función, llama a tu función pasándole los siguientes datos en una sola línea: + - Nombre: `"Geralt"` + - Objetos sueltos: `"Espada de Plata"`, `"Poción Curativa"`, `"Cabeza de Grifo"`. + - Estadísticas con etiqueta: `fuerza=150`, `magia=50`, `agilidad=80`. + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `def`, `*args`, `**kwargs`, bucles `for`, `print`. +- **Prohibido:** Modificar los valores de las tuplas o diccionarios dentro de la función. Solo debes recorrerlos y mostrarlos. + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar algo exactamente como esto: + +```text +Guardando progreso de: Geralt +--- Objetos en la mochila --- +- Espada de Plata +- Poción Curativa +- Cabeza de Grifo +--- Estadísticas --- +fuerza : 150 +magia : 50 +agilidad : 80 +``` +*(Nota: Asegúrate de añadir los `print("--- Objetos en la mochila ---")` y similares en tu código para que quede ordenado).* diff --git a/python/Modulo-05-Funciones/44-args-kwargs/teoria.md b/python/Modulo-05-Funciones/44-args-kwargs/teoria.md new file mode 100644 index 0000000..271a50c --- /dev/null +++ b/python/Modulo-05-Funciones/44-args-kwargs/teoria.md @@ -0,0 +1,60 @@ +# Teoría: Rompiendo las Reglas de la Cantidad 📦 + +## 1. Llamar por nombre (Keyword Arguments) + +Recuerda que Python asigna los valores de izquierda a derecha. Pero, si al llamar a la función le dices **exactamente el nombre de la etiqueta**, a Python ya no le importa el orden. + +```python +def registrar(nombre, pais="Desconocido", plan="Gratis"): + print(nombre, pais, plan) + +# Nos saltamos el país y cambiamos solo el plan llamándolo por su etiqueta: +registrar("Carlos", plan="Premium") +``` + +## 2. El Asterisco Simple `*` (La caja sin fondo: args) + +Si pones un `*` antes de un parámetro al definir tu función, le dices a Python: *"Agarra todos los valores sueltos extra que me envíen, y mételos en una **Tupla** (una lista que no se puede modificar)"*. +Por convención entre programadores, a esa caja le llamamos `args` (de argumentos). + +```python +def hacer_jugo(*args): + # 'args' ahora es una tupla con todas las frutas que manden + for fruta in args: + print("Licuando:", fruta) + +hacer_jugo("Manzana", "Pera", "Mango") # ¡3 valores a una sola variable! +``` + +## 3. El Asterisco Doble `**` (El archivero de etiquetas: kwargs) + +Si pones `**` antes de un parámetro, le dices a Python: *"Agarra todos los valores **que traigan su propia etiqueta** (Keyword Arguments) extra, y mételos en un **Diccionario**"*. +Por convención, le llamamos `kwargs` (Keyword Arguments). + +```python +def mostrar_datos(**kwargs): + # 'kwargs' ahora es un diccionario + for etiqueta in kwargs: + print(etiqueta, "->", kwargs[etiqueta]) + +mostrar_datos(edad=25, ciudad="Madrid", profesion="Hacker") +``` + +## 🧬 La Regla del Orden Supremo + +Si vas a usar todo junto, los paréntesis de tu `def` DEBEN seguir este orden estricto: +`def funcion(obligatorios, *args, **kwargs):` + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Positional argument follows keyword argument +**El síntoma en la terminal:** `SyntaxError: positional argument follows keyword argument` +**¿Por qué pasa?** Ocurre al **llamar** a la función. Si le pones etiqueta a un dato (ej. `edad=25`), TODOS los datos que pongas después hacia la derecha también deben tener etiqueta. +**❌ Mal:** `mostrar_datos(edad=25, "Madrid")` +**✅ Bien:** `mostrar_datos(25, ciudad="Madrid")` + +### Error 2: Olvidar los asteriscos al definir +**El síntoma en la terminal:** `TypeError: hacer_jugo() takes 1 positional argument but 3 were given` +**¿Por qué pasa?** Si escribes `def hacer_jugo(args):` (sin el `*`), Python cree que `args` es un parámetro normal que solo puede guardar 1 sola cosa. ¡El que hace la magia de atrapar valores infinitos es el símbolo `*`, no la palabra "args"! diff --git a/python/Modulo-05-Funciones/44-args-kwargs/test_main.py b/python/Modulo-05-Funciones/44-args-kwargs/test_main.py new file mode 100644 index 0000000..fb00915 --- /dev/null +++ b/python/Modulo-05-Funciones/44-args-kwargs/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_args_kwargs(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert len(r.stdout.strip()) > 0 diff --git a/python/Modulo-05-Funciones/45-scope/.gitkeep b/python/Modulo-05-Funciones/45-scope/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/45-scope/README.md b/python/Modulo-05-Funciones/45-scope/README.md new file mode 100644 index 0000000..2b44aee --- /dev/null +++ b/python/Modulo-05-Funciones/45-scope/README.md @@ -0,0 +1,16 @@ +# Nivel 45: Secretos y Megáfonos (El Scope) 🕵️‍♂️ + +Imagina que estás en tu habitación (una función). Si susurras un secreto, solo tú y los que están en la habitación lo saben. Pero si tu mamá grita desde el pasillo principal de la casa (fuera de las funciones), todos en todas las habitaciones pueden escucharla. + +En programación, las variables (las cajas donde guardamos datos) tienen un **tiempo de vida** y una **visibilidad**. A esto se le llama **Scope** (o ámbito). + +Hasta ahora hemos creado variables sin pensar mucho dónde viven. Pero cuando usamos funciones, las reglas cambian: +1. **Variables Locales (El Secreto):** Nacen dentro de una función y mueren cuando la función termina. El resto del código no sabe que existen. +2. **Variables Globales (El Megáfono):** Nacen en el código principal (afuera de las funciones) y cualquier función puede verlas. + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Aprenderemos la regla de Las Vegas ("lo que pasa en la función, se queda en la función") y conoceremos la palabra mágica `global`. +2. **Ejemplo:** Jugaremos con la puntuación de un juego para ver cómo las funciones pueden modificar una variable de todo el sistema. +3. **Reto:** Construirás un sistema bancario o de monedas para un personaje que interactúa con múltiples funciones. + +¡Es hora de entender dónde viven y mueren tus datos! diff --git a/python/Modulo-05-Funciones/45-scope/ejemplo.py b/python/Modulo-05-Funciones/45-scope/ejemplo.py new file mode 100644 index 0000000..927429d --- /dev/null +++ b/python/Modulo-05-Funciones/45-scope/ejemplo.py @@ -0,0 +1,48 @@ +print("--- 1. Scope Local (El Secreto) ---") + +def planear_sorpresa(): + regalo = "Un viaje a la luna" + print("Dentro de la función, el regalo es:", regalo) + +# Llamamos a la función +planear_sorpresa() + +# Si descomentas la línea de abajo, el programa EXPLOTARÁ con NameError +# porque 'regalo' ya no existe aquí afuera. +# print(regalo) + + +print("\n--- 2. Leer Variables Globales ---") + +# Esta variable vive en el pasillo principal. Todos la ven. +juego_actual = "Zelda" + +def mostrar_juego(): + # La función simplemente se asoma al pasillo y lee el valor. + print("Estamos jugando:", juego_actual) + +mostrar_juego() + + +print("\n--- 3. Modificar Variables Globales (La palabra mágica) ---") + +# Nuestro jugador empieza con 3 vidas (Global) +vidas_jugador = 3 + +def recibir_golpe(): + # Le avisamos a Python que NO queremos crear una variable local nueva. + # Queremos afectar a la variable 'vidas_jugador' que ya existe afuera. + global vidas_jugador + + # Ahora sí podemos modificarla + vidas_jugador = vidas_jugador - 1 + print("¡Ouch! Recibiste un golpe.") + +print("Vidas al iniciar:", vidas_jugador) + +# Recibimos dos golpes +recibir_golpe() +recibir_golpe() + +# Como usamos 'global', el cambio es permanente para todo el programa. +print("Vidas después del combate:", vidas_jugador) diff --git a/python/Modulo-05-Funciones/45-scope/reto.md b/python/Modulo-05-Funciones/45-scope/reto.md new file mode 100644 index 0000000..91b0523 --- /dev/null +++ b/python/Modulo-05-Funciones/45-scope/reto.md @@ -0,0 +1,36 @@ +# Reto 45: El Contador de Monedas 🪙 + +Estás programando la billetera de un personaje en un juego. El personaje puede encontrar cofres que le dan monedas, y puede comprar objetos que le restan monedas. + +Como el oro del jugador tiene que mantenerse guardado a través de todo el juego, la cantidad de monedas debe ser una variable global. + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. En la línea 1 (sin espacios a la izquierda), crea una variable global llamada `monedas_totales` y asígnale el valor `0`. +3. Define una función llamada `encontrar_cofre` que reciba 1 parámetro: `cantidad`. +4. Dentro de `encontrar_cofre`, usa la palabra mágica para avisar que vas a modificar `monedas_totales`. Luego, súmale la `cantidad` a las monedas totales e imprime `"¡Encontraste [cantidad] monedas!"`. +5. Define otra función llamada `comprar_pocion` que reciba 1 parámetro: `costo`. +6. Dentro de `comprar_pocion`, vuelve a avisar que usarás la variable global. Usa un `if` para revisar: + - Si `monedas_totales` es mayor o igual al `costo`: réstale el costo a las monedas totales e imprime `"Poción comprada por [costo] monedas."`. + - Si no (`else`): imprime `"No tienes suficiente oro para la poción."`. +7. Fuera de las funciones, realiza esta secuencia de comandos: + - Llama a `encontrar_cofre` y pásale `50`. + - Llama a `comprar_pocion` y pásale `20`. + - Llama a `comprar_pocion` y pásale `40`. + - Imprime el estado final del oro: `"Oro restante: [monedas_totales]"`. + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `def`, variables globales, `global`, parámetros, `if/else`, operaciones matemáticas (`+`, `-`). +- **Prohibido:** Crear variables locales dentro de las funciones para llevar la cuenta del dinero. Todo debe modificar la única variable global `monedas_totales`. + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar algo exactamente como esto: + +```text +¡Encontraste 50 monedas! +Poción comprada por 20 monedas. +No tienes suficiente oro para la poción. +Oro restante: 30 +``` diff --git a/python/Modulo-05-Funciones/45-scope/teoria.md b/python/Modulo-05-Funciones/45-scope/teoria.md new file mode 100644 index 0000000..dc221b4 --- /dev/null +++ b/python/Modulo-05-Funciones/45-scope/teoria.md @@ -0,0 +1,51 @@ +# Teoría: La Regla de Las Vegas 🎰 + +## 1. Scope Local (Variables Privadas) + +Toda variable que tú crees **dentro** de una función (con espacios a la izquierda), está atrapada en esa función. + +```python +def guardar_secreto(): + mensaje = "Soy Batman" # Esta variable nace aquí + print(mensaje) + # Al llegar aquí, la función termina y la variable 'mensaje' ¡se autodestruye! +``` +Si intentas hacer `print(mensaje)` afuera de la función, Python te dirá que esa variable no existe. + +## 2. Scope Global (Variables Públicas) + +Si creas una variable **totalmente pegada al margen izquierdo** (fuera de los `def`), es una variable Global. Todas las funciones pueden "leerla". + +```python +clima = "Soleado" # Variable Global + +def mirar_por_la_ventana(): + # La función puede leer 'clima' sin problemas + print("El clima de hoy es", clima) +``` + +## 3. El gran problema: Modificar una variable Global + +Python es muy protector. Te deja *leer* las variables globales desde adentro de una función, pero **no te deja modificarlas** directamente. Si intentas modificarlas, Python se confunde y cree que quieres crear una variable local nueva con el mismo nombre. + +Para modificar una variable global desde adentro de una habitación (función), tienes que gritar la palabra mágica **`global`**. + +```python +puntuacion = 0 # Global + +def ganar_puntos(): + global puntuacion # "¡Oye Python! Voy a modificar la variable del pasillo" + puntuacion = puntuacion + 10 +``` + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Intentar leer un secreto desde afuera +**El síntoma en la terminal:** `NameError: name 'mensaje' is not defined` +**¿Por qué pasa?** Intentaste imprimir o usar una variable que creaste dentro de un `def`, pero lo hiciste afuera en el pasillo principal. Recuerda: ¡las variables locales se autodestruyen cuando la función acaba! + +### Error 2: Modificar una global sin pedir permiso +**El síntoma en la terminal:** `UnboundLocalError: local variable 'puntuacion' referenced before assignment` +**¿Por qué pasa?** Hiciste algo como `puntuacion = puntuacion + 10` dentro de un `def`, pero olvidaste escribir la línea `global puntuacion` antes. Python intentó crear una variable local llamada "puntuacion", pero al mismo tiempo intentó sumarle 10 a algo que (en su mente local) aún no existía. ¡Usa `global` si quieres alterar la variable de afuera! diff --git a/python/Modulo-05-Funciones/45-scope/test_main.py b/python/Modulo-05-Funciones/45-scope/test_main.py new file mode 100644 index 0000000..6002ebc --- /dev/null +++ b/python/Modulo-05-Funciones/45-scope/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_scope_global(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "30" in r.stdout, "El oro restante debe ser 30" diff --git a/python/Modulo-05-Funciones/46-lambdas/.gitkeep b/python/Modulo-05-Funciones/46-lambdas/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/46-lambdas/README.md b/python/Modulo-05-Funciones/46-lambdas/README.md new file mode 100644 index 0000000..dbaeacb --- /dev/null +++ b/python/Modulo-05-Funciones/46-lambdas/README.md @@ -0,0 +1,14 @@ +# Nivel 46: Funciones de Bolsillo (Lambdas) 📝 + +Crear una función con `def` es como redactar un contrato formal. Tienes que darle un nombre oficial, abrir un bloque con indentación, escribir los pasos, y poner explícitamente un sello de `return` al final. ¡Es perfecto para cosas grandes! + +Pero a veces, solo necesitas hacer un cálculo matemático pequeñito, rápido y de un solo uso. No quieres redactar un contrato, solo quieres anotar una cuenta rápida en un "Post-it" (una nota adhesiva) y tirarla cuando termines. + +Para esos momentos, Python inventó las **Lambdas** (o funciones anónimas). Son funciones de una sola línea, sin nombre oficial, y súper rápidas de escribir. + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Aprenderemos la palabra mágica `lambda` y cómo comprime toda la anatomía de un `def` en una sola línea. +2. **Ejemplo:** Compararemos una función tradicional cara a cara con su versión en "Post-it". +3. **Reto:** Construirás herramientas matemáticas instantáneas para resolver problemas rápidos. + +¡Prepárate para escribir código mucho más veloz y compacto! diff --git a/python/Modulo-05-Funciones/46-lambdas/ejemplo.py b/python/Modulo-05-Funciones/46-lambdas/ejemplo.py new file mode 100644 index 0000000..1f79d39 --- /dev/null +++ b/python/Modulo-05-Funciones/46-lambdas/ejemplo.py @@ -0,0 +1,41 @@ +print("--- 1. Cara a Cara: def vs lambda ---") + +# Contrato formal (def) +def duplicar_formal(numero): + return numero * 2 + +# Post-it rápido (lambda) +# Sintaxis: variable = lambda ingredientes : lo_que_devuelve +duplicar_rapido = lambda numero : numero * 2 + +print("Con def:", duplicar_formal(5)) +print("Con lambda:", duplicar_rapido(5)) +# ¡Ambas hacen exactamente lo mismo! + + +print("\n--- 2. Múltiples ingredientes en una Lambda ---") + +# Queremos calcular el precio con un descuento +# Ingredientes: precio, descuento. Resultado: precio - descuento +aplicar_descuento = lambda precio, descuento : precio - descuento + +precio_zapatos = aplicar_descuento(100, 20) +print("Tus zapatos con descuento cuestan:", precio_zapatos) + + +print("\n--- 3. Lambdas con texto ---") + +# Las lambdas no son solo para matemáticas. También pueden unir textos. +# Ingredientes: nombre, apellido. Resultado: Unirlos con un espacio en medio. +crear_nombre_completo = lambda nombre, apellido : nombre + " " + apellido + +jugador = crear_nombre_completo("Arthur", "Pendragon") +print("Bienvenido al juego,", jugador) + + +print("\n--- 4. Un error común para evitar ---") +# Si intentas hacer esto: +# lambda x : x = x + 1 +# Python explotará. Las lambdas no están hechas para modificar variables (asignaciones con =). +# Solo están hechas para producir un resultado directo: +# lambda x : x + 1 (¡Así es correcto!) diff --git a/python/Modulo-05-Funciones/46-lambdas/reto.md b/python/Modulo-05-Funciones/46-lambdas/reto.md new file mode 100644 index 0000000..5afdae1 --- /dev/null +++ b/python/Modulo-05-Funciones/46-lambdas/reto.md @@ -0,0 +1,26 @@ +# Reto 46: La Calculadora Rápida ⚡ + +Te han asignado la tarea de hacer unas fórmulas matemáticas para el sistema de un dron topográfico. El problema es que la memoria del dron es diminuta, y tu jefe te ha prohibido usar la palabra `def`. ¡Quiere que todas las funciones ocupen una sola línea de código! + +Tendrás que usar `lambda` para crear funciones "de bolsillo". + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Crea una variable llamada `calcular_cuadrado` y asígnale una `lambda` que reciba 1 parámetro (un número) y devuelva ese número multiplicado por sí mismo. +3. Crea una variable llamada `calcular_area_triangulo` y asígnale una `lambda` que reciba 2 parámetros (base, altura). La lambda debe multiplicar la base por la altura, y dividir el resultado entre 2. +4. Fuera de las lambdas, llama a tu primera lambda pasándole el número `5`. Atrapa el resultado e imprímelo en pantalla con un mensaje descriptivo. +5. Llama a tu segunda lambda pasándole `10` de base y `4` de altura. Atrapa el resultado e imprímelo en pantalla con un mensaje descriptivo. + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `lambda`, variables, `print`, matemáticas (`*`, `/`). +- **Prohibido:** Usar la palabra `def`. Usar la palabra `return`. Que las operaciones de tus lambdas ocupen más de una línea. + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar algo exactamente como esto: + +```text +El cuadrado de 5 es: 25 +El área del triángulo (base 10, altura 4) es: 20.0 +``` diff --git a/python/Modulo-05-Funciones/46-lambdas/teoria.md b/python/Modulo-05-Funciones/46-lambdas/teoria.md new file mode 100644 index 0000000..12ca84c --- /dev/null +++ b/python/Modulo-05-Funciones/46-lambdas/teoria.md @@ -0,0 +1,44 @@ +# Teoría: El Post-it de Python 🟨 + +Una función Lambda hace exactamente lo mismo que un `def`, pero con reglas muy estrictas: +- **Solo puede tener una línea de código.** +- **No se usa la palabra `return`.** (Python asume automáticamente que el resultado de esa única línea es lo que quieres devolver). +- **No tiene nombre por defecto.** (Aunque puedes guardarla en una variable). + +## 🧬 Anatomía de una Lambda + +Mira cómo convertimos esto: +```python +def sumar(a, b): + return a + b +``` + +En esto (la versión Lambda): +```python +sumar = lambda a, b : a + b +``` + +Vamos a desarmar el "Post-it": +- `lambda`: Es la palabra mágica. Significa *"Voy a crear una función rápida de una línea"*. +- `a, b`: Son los parámetros (nuestra bandejita de ingredientes). Fíjate que **NO usan paréntesis**. +- `:`: Los dos puntos separan los ingredientes del cálculo matemático. +- `a + b`: Es la operación. Python calcula esto y **automáticamente hace el `return`** por ti. ¡No lo escribas! + +## ¿Por qué dice "anónima"? +Porque las lambdas nacieron para usarse sin nombre, pasándolas directamente a otros sistemas (lo veremos más adelante en tu carrera). Sin embargo, hoy las guardaremos en variables (como `sumar = lambda...`) para que aprendas su estructura y puedas usarlas fácilmente. + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Intentar poner múltiples líneas +**El síntoma en la terminal:** `SyntaxError: invalid syntax` +**¿Por qué pasa?** Las Lambdas son Post-its pequeñitos. No puedes meter bucles `for`, ni `while`, ni crear múltiples variables dentro de una lambda. **Debe ser una sola expresión matemática o de texto directa.** Si necesitas más de una línea, ¡usa un `def` formal! + +### Error 2: Escribir la palabra 'return' +**El síntoma en la terminal:** `SyntaxError: invalid syntax` +**¿Por qué pasa?** Escribir `lambda x: return x * 2` es un error. La magia de las lambdas es que el `return` es invisible y automático. Python ya sabe que tiene que devolver el resultado de la derecha. + +### Error 3: Poner paréntesis en los parámetros +**El síntoma en la terminal:** (Depende del contexto, a veces funciona como tupla, pero es una mala práctica visual). +**¿Por qué pasa?** Por costumbre del `def`. En `lambda (x, y):` los paréntesis no son necesarios para los parámetros. Escribe simplemente `lambda x, y :`. diff --git a/python/Modulo-05-Funciones/46-lambdas/test_main.py b/python/Modulo-05-Funciones/46-lambdas/test_main.py new file mode 100644 index 0000000..09bab42 --- /dev/null +++ b/python/Modulo-05-Funciones/46-lambdas/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_lambdas(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "25" in r.stdout, "El cuadrado de 5 es 25" + assert "20.0" in r.stdout, "El área del triángulo (base 10, altura 4) es 20.0" diff --git a/python/Modulo-05-Funciones/47-recursividad/.gitkeep b/python/Modulo-05-Funciones/47-recursividad/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/47-recursividad/README.md b/python/Modulo-05-Funciones/47-recursividad/README.md new file mode 100644 index 0000000..b6f038f --- /dev/null +++ b/python/Modulo-05-Funciones/47-recursividad/README.md @@ -0,0 +1,14 @@ +# Nivel 47: El Espejo Infinito (Recursividad) 🪞 + +Imagina que estás parado en medio de dos espejos enfrentados. Tu reflejo se repite una y otra vez, haciéndose cada vez más pequeño hasta que desaparece en el horizonte. + +O piensa en las muñecas rusas (Matryoshkas): abres una muñeca y adentro hay otra idéntica pero más pequeña. La abres y hay otra más pequeña... hasta que llegas a la última, que es sólida y ya no se puede abrir. + +En programación, **la recursividad** es cuando **una función se llama a sí misma** desde adentro de su propio código. Es una forma de resolver un problema grande dividiéndolo en versiones cada vez más pequeñas del mismo problema, hasta llegar a una versión tan simple que se resuelve directamente (la muñeca sólida del centro). + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Descubriremos las dos reglas de oro de la recursividad: el "Freno" (Caso Base) y el "Espejo" (Llamada Recursiva). +2. **Ejemplo:** Haremos una cuenta regresiva de cohete sin usar bucles `while` ni `for`. +3. **Reto:** Simularás el eco de una caverna usando la técnica del espejo infinito. + +¡Prepárate para expandir tu mente! Al principio marea un poco, pero es uno de los conceptos más elegantes de la programación. diff --git a/python/Modulo-05-Funciones/47-recursividad/ejemplo.py b/python/Modulo-05-Funciones/47-recursividad/ejemplo.py new file mode 100644 index 0000000..5b7e814 --- /dev/null +++ b/python/Modulo-05-Funciones/47-recursividad/ejemplo.py @@ -0,0 +1,57 @@ +print("--- 1. Cuenta Regresiva (Sin Bucles) ---") + +def lanzar_cohete(segundos): + # CASO BASE (El Freno) + if segundos <= 0: + print("🚀 ¡Fuego!") + # Con return, decimos "no más llamadas, salte de aquí" + return + + # ACCIÓN + print("Faltan", segundos, "segundos...") + + # LLAMADA RECURSIVA (El Espejo) + # Reducimos el problema restando 1 + lanzar_cohete(segundos - 1) + +lanzar_cohete(3) + + +print("\n--- 2. Sumando números hacia atrás ---") + +# Esta función suma un número con todos sus anteriores +# Ejemplo: si pasas 4, suma 4 + 3 + 2 + 1 +def suma_total(numero): + # CASO BASE: Si llegamos a 1, el total es simplemente 1. Freno de mano. + if numero == 1: + return 1 + + # LLAMADA RECURSIVA Y RETORNO + # Aquí es más complejo: el resultado es el número actual MÁS + # el resultado de llamar a la función con el número anterior. + return numero + suma_total(numero - 1) + +# ¿Cómo piensa Python al ejecutar suma_total(3)? +# 1. 3 + suma_total(2) +# 2. 3 + (2 + suma_total(1)) +# 3. 3 + (2 + 1) -> Porque suma_total(1) chocó con el freno y devolvió 1. +# 4. Total: 6. + +resultado = suma_total(3) +print("La suma de 3 + 2 + 1 es:", resultado) + + +print("\n--- 3. El peligro del Espejo Infinito ---") + +def agujero_negro(energia): + # Aquí NO HAY FRENO (No hay un 'if' que haga return) + print("Absorbiendo energía...", energia) + # agujero_negro(energia - 1) + + # Si descomentas la línea de arriba y corres esto, + # Python intentará crear la función tantas veces que + # gritará 'RecursionError' y se apagará para proteger tu PC. + pass + +agujero_negro(10) +print("El agujero negro está desactivado por seguridad.") diff --git a/python/Modulo-05-Funciones/47-recursividad/reto.md b/python/Modulo-05-Funciones/47-recursividad/reto.md new file mode 100644 index 0000000..bc37557 --- /dev/null +++ b/python/Modulo-05-Funciones/47-recursividad/reto.md @@ -0,0 +1,30 @@ +# Reto 47: El Eco de la Caverna 🗣️ + +Has entrado en una caverna mágica. Cuando gritas una palabra, la caverna la repite varias veces, pero cada vez que lo hace, el sonido es más débil hasta que desaparece. + +¡Vamos a simular esto SIN usar bucles `for` ni `while`! Vas a usar la técnica del espejo infinito (recursividad). + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. Define una función llamada `hacer_eco` que reciba dos parámetros: `palabra` y `veces`. +3. Lo primero que debe hacer tu función es el **Caso Base (el freno)**: usa un `if` para revisar si `veces` es igual a `0`. Si lo es, imprime `"..."` (el silencio final) y usa `return` para salirte de la función. +4. Si no entró al `if`, la función debe hacer su acción normal: imprimir la `palabra`. +5. Después del `print`, viene la **Llamada Recursiva (el espejo)**. Haz que la función se llame a sí misma (`hacer_eco(...)`), pasándole la *misma* `palabra`, pero el problema debe ser más pequeño: así que pásale `veces - 1`. +6. Fuera de tu función (en el pasillo principal del programa), llama a tu función así: `hacer_eco("¡Hola!", 3)`. + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `def`, parámetros, `if`, `return`, `print`, y llamar a la función desde sí misma. +- **Prohibido:** Usar `while` o `for`. El eco debe generarse 100% mediante recursividad. Olvidar el `if` (tu computadora se quejará con un error). + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar algo exactamente como esto: + +```text +¡Hola! +¡Hola! +¡Hola! +... +``` +*(Fíjate que la palabra sale 3 veces, y luego el "..." indica que el eco chocó con el freno y terminó).* diff --git a/python/Modulo-05-Funciones/47-recursividad/teoria.md b/python/Modulo-05-Funciones/47-recursividad/teoria.md new file mode 100644 index 0000000..2bdb947 --- /dev/null +++ b/python/Modulo-05-Funciones/47-recursividad/teoria.md @@ -0,0 +1,47 @@ +# Teoría: La Muñeca Rusa 🪆 + +Hasta ahora usábamos `for` o `while` para repetir cosas. La recursividad es otra forma de repetir, pero usando las mismas funciones que aprendimos. + +Toda función recursiva DEBE tener dos partes vitales. Si te olvidas de una, tu programa morirá. + +## 1. El Caso Base (El Freno) 🛑 +Es un `if`. Es la muñequita sólida del centro. Le dice a la función: *"¡Alto! Ya el problema es tan pequeño que no necesitas llamarte de nuevo. Simplemente devuelve un resultado o termina."* +**Sin esto, la función se llamará a sí misma por toda la eternidad.** + +## 2. La Llamada Recursiva (El Espejo) 🪞 +Es la parte donde la función se llama a sí misma por su propio nombre, pero pasándole un problema **más pequeño**. (Por ejemplo, si le pasaste el número 5, ahora se llama a sí misma pasándose un 4). + +## 🧬 Anatomía de la Recursividad + +```python +def cuenta_regresiva(numero): + # 1. EL FRENO (Caso Base) + if numero == 0: + print("¡Despegue!") + return # Salida de emergencia. Detiene la recursividad. + + # 2. ACCIÓN NORMAL + print(numero) + + # 3. EL ESPEJO (Llamada Recursiva) + # Me llamo a mí mismo, pero con un número más pequeño (numero - 1) + cuenta_regresiva(numero - 1) +``` + +Cuando ejecutas `cuenta_regresiva(3)`, pasa esto en cámara lenta: +1. Entra el `3`. Imprime `3`. Llama a `cuenta_regresiva(2)`. +2. La nueva función revisa si es 0. No. Imprime `2`. Llama a `cuenta_regresiva(1)`. +3. La nueva función revisa si es 0. No. Imprime `1`. Llama a `cuenta_regresiva(0)`. +4. La nueva función revisa si es 0. **¡SÍ!** Imprime "¡Despegue!" y pisa el freno (return). ¡Todo termina! + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Olvidar el Caso Base (El Freno) +**El síntoma en la terminal:** `RecursionError: maximum recursion depth exceeded` +**¿Por qué pasa?** A diferencia de un `while True` que se queda congelado girando por siempre, la recursividad es como meter cajas dentro de cajas dentro de cajas. La memoria RAM de tu computadora tiene un límite de "cajas" que puede apilar. Si no pones el `if` que pise el freno, Python creará cajas infinitas hasta quedarse sin memoria, y por seguridad, hace explotar el programa lanzando este error. + +### Error 2: Pasar siempre el mismo problema +**El síntoma en la terminal:** También `RecursionError`. +**¿Por qué pasa?** Pusiste el freno (`if numero == 0`), pero en tu llamada recursiva hiciste `cuenta_regresiva(numero)` en vez de `numero - 1`. Si entraste con un 5, te llamas a ti mismo con un 5, luego con un 5... ¡Jamás llegarás al 0! El problema que le pasas al espejo siempre debe ser **más pequeño** para que avance hacia el freno. diff --git a/python/Modulo-05-Funciones/47-recursividad/test_main.py b/python/Modulo-05-Funciones/47-recursividad/test_main.py new file mode 100644 index 0000000..b6ce253 --- /dev/null +++ b/python/Modulo-05-Funciones/47-recursividad/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_eco_recursivo(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.stdout.count("¡Hola!") == 3, "La palabra debe repetirse 3 veces" + assert "..." in r.stdout, "Debe terminar con '...'" diff --git a/python/Modulo-05-Funciones/48-import/.gitkeep b/python/Modulo-05-Funciones/48-import/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/48-import/README.md b/python/Modulo-05-Funciones/48-import/README.md new file mode 100644 index 0000000..34e3df2 --- /dev/null +++ b/python/Modulo-05-Funciones/48-import/README.md @@ -0,0 +1,12 @@ +# Nivel 48: Pidiendo Herramientas Prestadas (`import`) 🧰 + +Hasta ahora, hemos construido todas nuestras herramientas (funciones) desde cero con nuestras propias manos usando `def`. Pero seamos honestos: si quieres clavar un clavo en tu casa, no vas a la mina, extraes hierro, lo fundes y forjas un martillo. ¡Vas a la ferretería y compras un martillo que alguien más ya hizo! + +En Python, la filosofía es **"No reinventes la rueda"**. Miles de programadores brillantes ya han escrito código para hacer matemáticas complejas, generar números aleatorios, leer el tiempo o descargar cosas de internet, y lo han empaquetado en "cajas de herramientas" (llamadas **Módulos**). + +## 🗺️ Ruta de Aprendizaje +1. **Teoría:** Aprenderemos las palabras mágicas `import` y `from` para pedir prestadas estas cajas de herramientas. +2. **Ejemplo:** Usaremos las cajas `math` (para matemáticas) y `random` (para azar) que vienen incluidas gratis con Python. +3. **Reto:** Construirás un "Oráculo del Destino" usando el poder de lo aleatorio. + +¡Bienvenido al ecosistema donde nunca estás programando solo! diff --git a/python/Modulo-05-Funciones/48-import/ejemplo.py b/python/Modulo-05-Funciones/48-import/ejemplo.py new file mode 100644 index 0000000..7a9d017 --- /dev/null +++ b/python/Modulo-05-Funciones/48-import/ejemplo.py @@ -0,0 +1,40 @@ +# ¡REGLA DE ORO! Todos los imports van en las primeras líneas. +import math +from random import choice + + +print("--- 1. Usando la caja completa (math) ---") + +# math tiene herramientas geniales para matemáticas +numero = 16 + +# Usamos la sintaxis: caja.herramienta() +raiz_cuadrada = math.sqrt(numero) +# math.pow(base, exponente) eleva a una potencia +potencia = math.pow(2, 3) # 2 al cubo (8) + +print("La raíz de 16 es:", raiz_cuadrada) +print("2 elevado a 3 es:", potencia) + + +print("\n--- 2. Usando una herramienta suelta (random.choice) ---") + +# La herramienta 'choice' elige un elemento al azar de una lista. +# Fíjate que como hicimos "from random import choice", +# NO tenemos que escribir "random.choice", la usamos directamente. + +heroes = ["Mago", "Guerrero", "Arquero", "Ladrón"] + +print("Eligiendo tu clase al azar...") +clase_elegida = choice(heroes) +print("¡Felicidades, jugarás como:", clase_elegida, "!") + +# Si ejecutamos esto de nuevo, la suerte cambiará +print("Tirando los dados otra vez, jugarás como:", choice(heroes)) + + +print("\n--- 3. ¿Podemos crear nuestras propias herramientas? ---") +# La respuesta es SÍ. Un archivo .py que tú creas (como 'ejemplo.py') +# ES una caja de herramientas. +# En el futuro aprenderemos a tener múltiples archivos para que un archivo +# importe las funciones que creaste en otro. ¡Esa es la base de los sistemas enormes! diff --git a/python/Modulo-05-Funciones/48-import/reto.md b/python/Modulo-05-Funciones/48-import/reto.md new file mode 100644 index 0000000..b5e180f --- /dev/null +++ b/python/Modulo-05-Funciones/48-import/reto.md @@ -0,0 +1,32 @@ +# Reto 48: El Oráculo del Destino 🔮 + +Tu aldea necesita tomar una decisión importante, así que han construido un Oráculo digital. El Oráculo seleccionará aleatoriamente un presagio de una lista secreta para guiar el destino del pueblo. + +## 📝 Instrucciones + +1. Crea un archivo llamado `reto.py`. +2. En la línea 1, importa **solo la herramienta `choice`** desde la caja `random` usando la estructura `from ... import ...`. +3. Define una función llamada `predecir_futuro`. +4. Dentro de la función, crea una lista (que aprendiste en el módulo de Estructuras) llamada `presagios` que contenga estas 4 frases: + - `"Lloverá oro mañana."` + - `"Un dragón atacará al mediodía."` + - `"Encontrarás la paz en tu interior."` + - `"No salgas de casa hoy."` +5. Usa la herramienta `choice()` pasándole tu lista `presagios` para obtener un destino al azar, y usa `return` para devolver ese destino hacia afuera. +6. Fuera de la función, usa un bucle `for` que se repita 3 veces (para pedir 3 profecías distintas). +7. En cada repetición del bucle, llama a tu función, atrapa el resultado en una variable, y usa `print` para mostrar: `"El oráculo ha hablado: [resultado]"`. + +### 🚦 Reglas Estrictas +- **Conceptos permitidos:** `from`, `import`, `def`, listas (`[]`), `return`, bucles `for` o `while`, `print`. +- **Prohibido:** Crear los presagios fuera de la función. Escribir `import random` (debes importar solo la herramienta `choice` como dice la instrucción 2). + +## 🎯 Resultado Esperado en Terminal + +Cuando ejecutes tu código, la terminal debería mostrar 3 frases al azar de tu lista (obviamente pueden variar en cada ejecución): + +```text +El oráculo ha hablado: Un dragón atacará al mediodía. +El oráculo ha hablado: Lloverá oro mañana. +El oráculo ha hablado: Lloverá oro mañana. +``` +*(Nota: Como es azar, es normal si alguna frase se repite).* diff --git a/python/Modulo-05-Funciones/48-import/teoria.md b/python/Modulo-05-Funciones/48-import/teoria.md new file mode 100644 index 0000000..1141446 --- /dev/null +++ b/python/Modulo-05-Funciones/48-import/teoria.md @@ -0,0 +1,46 @@ +# Teoría: La Ferretería de Python 🛠️ + +Por defecto, Python es ligero. Solo te da un cinturón básico con herramientas pequeñas (como `print()` o `len()`). Si quieres más, tienes que ir a buscar la caja de herramientas específica. + +## 1. Traer la caja entera (`import`) + +La palabra mágica `import` significa *"Trae esta caja de herramientas y déjala en el suelo"*. + +```python +import math +``` +Cuando traes la caja entera, para usar una herramienta **tienes que decir en qué caja está**. Lo haces poniendo el nombre de la caja, un punto `.`, y luego la herramienta: + +```python +raiz = math.sqrt(9) # La caja 'math' tiene una herramienta 'sqrt' (raíz cuadrada) +``` + +## 2. Sacar solo una herramienta de la caja (`from ... import ...`) + +Si la caja de herramientas pesa mucho y tú solo necesitas el martillo, puedes usar la estructura `from` (desde) e `import` (importar). + +```python +from random import randint +``` +Significa: *"Desde la caja `random`, sácame solo la herramienta `randint`"*. +Como ya la tienes en la mano (y no en el suelo), **ya no usas el punto ni el nombre de la caja**: + +```python +dado = randint(1, 6) # La usas directamente +``` + +--- + +## 🚨 ¿Qué pasa si me equivoco? + +### Error 1: Escribir mal el nombre de la caja +**El síntoma en la terminal:** `ModuleNotFoundError: No module named 'matematicas'` +**¿Por qué pasa?** Le pediste a Python una caja que no existe. ¡Las cajas oficiales tienen nombres estrictos en inglés! Es `math`, no `matematicas` o `Math` (con mayúscula). + +### Error 2: Olvidar decir de qué caja viene la herramienta +**El síntoma en la terminal:** `NameError: name 'sqrt' is not defined` +**¿Por qué pasa?** Usaste `import math` (trajiste la caja al suelo), pero luego escribiste `resultado = sqrt(9)` sin el `math.`. Python no sabe de dónde salió ese `sqrt`. ¡Recuerda el punto! + +### Error 3: Escribir el import en medio del código +*(No es un error que explote, pero es una pésima práctica)*. +**Regla de oro:** Todos los `import` van en la **línea 1** de tu archivo, hasta arriba de todo. Piensa que antes de empezar a trabajar en la obra, tienes que bajar todas las cajas de herramientas de tu camión. No vas a buscar la caja a mitad de la construcción. diff --git a/python/Modulo-05-Funciones/48-import/test_main.py b/python/Modulo-05-Funciones/48-import/test_main.py new file mode 100644 index 0000000..f3d3a6f --- /dev/null +++ b/python/Modulo-05-Funciones/48-import/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_oraculo_formato(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert r.stdout.count("oráculo ha hablado") >= 3, "Debe imprimir 3 profecías" diff --git a/python/Modulo-05-Funciones/49-pip-venvs/.gitkeep b/python/Modulo-05-Funciones/49-pip-venvs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/49-pip-venvs/README.md b/python/Modulo-05-Funciones/49-pip-venvs/README.md new file mode 100644 index 0000000..6133cc0 --- /dev/null +++ b/python/Modulo-05-Funciones/49-pip-venvs/README.md @@ -0,0 +1,4 @@ +# Nivel 49: Pip y Entornos Virtuales (El Centro Comercial y tu Mochila) + +## Objetivo +Entender cómo instalar herramientas externas que otras personas programaron (usando `pip`) y cómo guardar estas herramientas en "mochilas" separadas para cada proyecto (entornos virtuales), evitando que se mezclen. diff --git a/python/Modulo-05-Funciones/49-pip-venvs/ejemplo.py b/python/Modulo-05-Funciones/49-pip-venvs/ejemplo.py new file mode 100644 index 0000000..3c0eac9 --- /dev/null +++ b/python/Modulo-05-Funciones/49-pip-venvs/ejemplo.py @@ -0,0 +1,30 @@ +# ========================================== +# EL MAGO Y SUS HERRAMIENTAS MÁGICAS +# ========================================== +# Historia: El mago acaba de comprar un "paquete" de colores en el centro comercial (pip). +# Para usarlo, primero tuvo que preparar su mochila mágica (entorno virtual) e instalarlo. + +# ------------------------------------------ +# INSTRUCCIONES ANTES DE CORRER ESTE CÓDIGO +# ------------------------------------------ +# Abre tu terminal en esta carpeta y escribe: +# 1. Crear el entorno: python -m venv mi_entorno +# 2. Activar entorno (Windows): mi_entorno\Scripts\activate +# Activar entorno (Mac/Linux): source mi_entorno/bin/activate +# 3. Instalar la herramienta: pip install colorama +# 4. Ahora sí, corre este archivo: python ejemplo.py +# ------------------------------------------ + +# Ahora usamos la herramienta externa 'colorama' que instalamos. +import colorama + +# Iniciamos colorama para que funcione en cualquier computadora +colorama.init() + +# colorama nos da variables con colores que podemos poner en nuestros textos +print(colorama.Fore.RED + "¡El dragón escupe fuego!") +print(colorama.Fore.BLUE + "El mago lanza un hechizo de agua.") +print(colorama.Fore.GREEN + "El bosque recupera su energía.") + +# Regresamos el color a la normalidad al final +print(colorama.Style.RESET_ALL + "La batalla ha terminado y el mundo vuelve a la normalidad.") diff --git a/python/Modulo-05-Funciones/49-pip-venvs/reto.md b/python/Modulo-05-Funciones/49-pip-venvs/reto.md new file mode 100644 index 0000000..41ee943 --- /dev/null +++ b/python/Modulo-05-Funciones/49-pip-venvs/reto.md @@ -0,0 +1,13 @@ +# Reto: El Pintor de Consolas + +## Tu Misión +El rey quiere un programa que muestre mensajes importantes en la pantalla, pero le aburre el color blanco de la terminal. Te ha pedido que crees un entorno virtual, instales `colorama` y hagas un programa que salude en 3 colores diferentes. + +## Instrucciones +1. Abre tu terminal. +2. Crea un entorno virtual llamado `mochila_rey`. +3. Activa el entorno virtual. +4. Instala el paquete `colorama` usando `pip`. +5. Crea un archivo llamado `reto.py`. +6. Escribe un código en Python que importe `colorama`, inicie la herramienta con `colorama.init()` y use `colorama.Fore.YELLOW` (amarillo), `colorama.Fore.CYAN` (celeste) y `colorama.Fore.MAGENTA` (morado) para imprimir tres mensajes distintos. +7. ¡Ejecuta tu código (`python reto.py`) y maravilla al rey! diff --git a/python/Modulo-05-Funciones/49-pip-venvs/teoria.md b/python/Modulo-05-Funciones/49-pip-venvs/teoria.md new file mode 100644 index 0000000..7066480 --- /dev/null +++ b/python/Modulo-05-Funciones/49-pip-venvs/teoria.md @@ -0,0 +1,33 @@ +# Teoría: Pip y Entornos Virtuales + +## ¿Qué son `pip` y los Entornos Virtuales? +Imagina que Python viene con una caja de herramientas básica. Pero a veces quieres construir algo avanzado y necesitas herramientas especiales (como una motosierra o un taladro) que alguien más ya inventó. + +- **`pip`**: Es como un centro comercial gigante en internet (llamado PyPI) donde puedes descargar herramientas (librerías) gratis. `pip` significa "Pip Installs Packages" (Pip instala paquetes). +- **Entorno Virtual (`venv`)**: Imagina que trabajas en dos proyectos. En uno necesitas pintura azul y en otro pintura roja. Si metes todo en la misma mochila, se puede hacer un desastre. Un entorno virtual es una "mochila" aislada solo para un proyecto. + +## Desmontaje Conceptual (Símbolos en la Terminal) +Hoy no usaremos tanto código de Python, sino **comandos en la terminal** (la pantalla negra). +1. `python -m venv nombre_del_entorno`: + - `python`: Llama a Python. + - `-m`: Significa "módulo" (vamos a usar un módulo interno). + - `venv`: El módulo que crea entornos virtuales. + - `nombre_del_entorno`: Es el nombre de la carpeta (mochila) que vas a crear (comúnmente se usa `env` o `venv`). +2. `source env/bin/activate` (Mac/Linux) o `env\Scripts\activate` (Windows): + - Esto "abre tu mochila" para que empieces a usar lo que hay adentro. + - Sabrás que está abierta porque en tu terminal aparecerá `(env)` al inicio. +3. `pip install nombre_del_paquete`: + - `install`: La orden para descargar e instalar algo. + - `nombre_del_paquete`: El nombre de la herramienta que quieres. (Ej: `colorama`, para ponerle color al texto). + +## Anatomía de un Proyecto +Así se ve tu carpeta antes y después de crear un entorno: + +**Antes:** +MiProyecto/ + L__ mi_codigo.py + +**Después:** +MiProyecto/ + L__ env/ (¡La mochila con herramientas!) + L__ mi_codigo.py diff --git a/python/Modulo-05-Funciones/49-pip-venvs/test_main.py b/python/Modulo-05-Funciones/49-pip-venvs/test_main.py new file mode 100644 index 0000000..b829198 --- /dev/null +++ b/python/Modulo-05-Funciones/49-pip-venvs/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_pintor_corre(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert "ModuleNotFoundError" not in r.stderr or "colorama" in r.stderr diff --git a/python/Modulo-05-Funciones/50-RETO-calculadora/.gitkeep b/python/Modulo-05-Funciones/50-RETO-calculadora/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-05-Funciones/50-RETO-calculadora/README.md b/python/Modulo-05-Funciones/50-RETO-calculadora/README.md new file mode 100644 index 0000000..cfda23e --- /dev/null +++ b/python/Modulo-05-Funciones/50-RETO-calculadora/README.md @@ -0,0 +1,4 @@ +# Nivel 50: Gran Reto - La Calculadora Alquímica (Jefe Final del Módulo 5) + +## Objetivo +Demostrar tu dominio sobre todas las herramientas de este módulo: funciones, parámetros, valores por defecto, argumentos infinitos (`*args`), variables globales/locales, funciones lambda y módulos externos. ¡Es hora del examen final de alquimia! diff --git a/python/Modulo-05-Funciones/50-RETO-calculadora/ejemplo.py b/python/Modulo-05-Funciones/50-RETO-calculadora/ejemplo.py new file mode 100644 index 0000000..4b35688 --- /dev/null +++ b/python/Modulo-05-Funciones/50-RETO-calculadora/ejemplo.py @@ -0,0 +1,35 @@ +# ========================================== +# PLANTILLA DEL JEFE FINAL: LA CALCULADORA +# ========================================== +# Historia: Eres el Alquimista Supremo. Necesitas crear una calculadora +# mágica que pueda sumar, restar, multiplicar y realizar operaciones complejas. +# Te han dejado la estructura vacía. Tu trabajo es llenarla. + +import math # Herramienta de Python para matemáticas avanzadas + +# 1. Crea una función que sume infinitos números usando *args +def sumar_infinito(*numeros): + # Escribe tu código aquí (recuerda usar un bucle for y devolver el resultado) + pass + +# 2. Crea una función con un parámetro por defecto +def multiplicar(a, b=2): + # Escribe tu código aquí (si no te dan 'b', se multiplicará por 2) + pass + +# 3. Crea una función lambda que divida dos números +dividir_rapido = lambda x, y: x / y # <-- ¡Este te lo regalamos! + +# 4. Crea una función que use math.sqrt() para sacar la raíz cuadrada +def raiz_cuadrada(numero): + # Escribe tu código aquí usando el módulo math + pass + +# === PRUEBAS DEL ALQUIMISTA === +# Descomenta las líneas de abajo quitando el '#' cuando termines tus funciones +# para ver si funcionan. + +# print(sumar_infinito(1, 2, 3, 4, 5)) # Debería mostrar 15 +# print(multiplicar(10)) # Debería mostrar 20 (porque b=2 por defecto) +# print(dividir_rapido(100, 4)) # Debería mostrar 25.0 +# print(raiz_cuadrada(81)) # Debería mostrar 9.0 diff --git a/python/Modulo-05-Funciones/50-RETO-calculadora/reto.md b/python/Modulo-05-Funciones/50-RETO-calculadora/reto.md new file mode 100644 index 0000000..c2174f1 --- /dev/null +++ b/python/Modulo-05-Funciones/50-RETO-calculadora/reto.md @@ -0,0 +1,14 @@ +# Reto: La Calculadora Alquímica + +## Tu Misión +Completa el archivo `ejemplo.py`. Contiene la estructura de una calculadora avanzada, pero los engranajes están vacíos. Debes usar todo lo que aprendiste en el módulo de funciones para hacer que funcione. + +## Instrucciones +1. Completa la función `sumar_infinito` para que use `*args` y sume todos los números que le envíen (pista: puedes usar una variable acumuladora y un bucle `for`, o la función secreta de Python `sum()`). +2. Completa la función `multiplicar` para que reciba dos parámetros, pero el segundo sea un parámetro por defecto (`b=2`). +3. Completa la función `raiz_cuadrada` usando `math.sqrt(numero)` (la función `sqrt` del módulo `math` que ya viene importado). +4. Descomenta las pruebas al final del archivo quitando el símbolo `#`. +5. Verifica que los resultados mostrados en pantalla sean los correctos. +6. **Bonus**: Crea una función extra llamada `historial` que use `**kwargs` para recibir el nombre de varias operaciones y sus resultados, y los imprima en pantalla. + +¡Felicidades por llegar al Nivel 50! ¡Has dominado las funciones y ya estás a la mitad de la montaña! diff --git a/python/Modulo-05-Funciones/50-RETO-calculadora/teoria.md b/python/Modulo-05-Funciones/50-RETO-calculadora/teoria.md new file mode 100644 index 0000000..836ce43 --- /dev/null +++ b/python/Modulo-05-Funciones/50-RETO-calculadora/teoria.md @@ -0,0 +1,13 @@ +# Teoría: Repaso del Módulo 5 + +¡Llegaste a la mitad de la montaña de 100 niveles! Respira profundo. Antes de subir más, debes demostrar que sabes usar tus nuevas habilidades: + +1. **`def`**: Crear tus propias fábricas de instrucciones. +2. **`return`**: Hacer que tu fábrica devuelva un producto en lugar de solo imprimirlo. +3. **Parámetros por defecto**: Si alguien no te da un ingrediente, usas tu Plan B. +4. **`*args` y `**kwargs`**: Cajas mágicas para recibir ingredientes infinitos. +5. **Alcance (`scope`)**: Lo que pasa en la fábrica (local), se queda en la fábrica, a menos que uses `global`. +6. **`lambda`**: Minimáquinas de una sola línea. +7. **`import`**: Traer herramientas de otras personas (o de Python). + +¡No hay conceptos nuevos aquí! Solo el conocimiento acumulado. diff --git a/python/Modulo-05-Funciones/50-RETO-calculadora/test_main.py b/python/Modulo-05-Funciones/50-RETO-calculadora/test_main.py new file mode 100644 index 0000000..6f2cace --- /dev/null +++ b/python/Modulo-05-Funciones/50-RETO-calculadora/test_main.py @@ -0,0 +1,12 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert True +def test_calculadora_alquimica(): + ejemplo = os.path.join(BASE, "ejemplo.py") + assert os.path.exists(ejemplo), "El archivo ejemplo.py debe existir" + r = subprocess.run([sys.executable, ejemplo], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, f"Error en ejemplo.py:\n{r.stderr}" + assert len(r.stdout.strip()) > 0, "ejemplo.py debe imprimir resultados" diff --git a/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/README.md b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/README.md new file mode 100644 index 0000000..32e0a87 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/README.md @@ -0,0 +1,19 @@ +# Nivel 1: Las Etiquetas de las Cajas 🏷️ + +## ¿Por qué aprender esto? + +Ya sabes crear funciones con `def`. Pero cuando una función se hace grande, o cuando alguien más lee tu código, surge una pregunta molesta: *¿Qué tipo de dato le tengo que pasar aquí? ¿Un número? ¿Un texto? ¿Una lista?* + +Los **Type Hints** (pistas de tipo) son etiquetas que le pegas a tus funciones para que **tu editor y tus compañeros de equipo sepan de un vistazo qué espera cada parámetro y qué devuelve la función**, sin tener que leer el código completo. + +No cambian *cómo* funciona tu código. Son como el rotulado de una caja: no cambia lo que hay adentro, pero evita que metan piedras en una caja que dice "¡Frágil – Vidrio!". + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — aprende a leer y escribir la sintaxis `: Tipo` y `-> Tipo` +2. 🐍 Estudia `ejemplo.py` — ve funciones antes y después de tener etiquetas +3. 🔥 Completa `reto.md` — etiqueta un conjunto de funciones desde cero + +## Conceptos que usarás aquí + +`def`, `return`, parámetros, `str`, `int`, `float`, `bool`, `list`, `dict` (ya los conoces todos) diff --git a/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/ejemplo.py b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/ejemplo.py new file mode 100644 index 0000000..c3ddc7f --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/ejemplo.py @@ -0,0 +1,58 @@ +print("--- Parte 1: funciones sin etiqueta (el problema) ---") + +# Sin type hints: no sabes qué pasar ni qué esperar de vuelta +def calcular_area(base, altura): + return base * altura + +# ¿Funciona con texto? La función no lo dice, así que lo pruebo y puede fallar +print(calcular_area(5, 10)) # 50 — bien +# print(calcular_area("5", 10)) # 50505050... ¡catástrofe silenciosa! + + +print("--- Parte 2: las mismas funciones CON etiquetas ---") + +# Ahora cualquiera que lea esto sabe: pasa dos float, recibirás un float +def calcular_area_v2(base: float, altura: float) -> float: + return base * altura + +print(calcular_area_v2(5.0, 10.0)) # 50.0 + + +print("--- Parte 3: etiquetando con str, int y bool ---") + +# -> str : esta función devuelve texto +def crear_saludo(nombre: str, veces: int) -> str: + saludo = "Hola, " + nombre + "! " + return saludo * veces # Repite el texto 'veces' veces + +print(crear_saludo("Ana", 3)) + + +# -> bool : esta función devuelve Verdadero o Falso +def puede_votar(edad: int) -> bool: + return edad >= 18 + +print(puede_votar(20)) # True +print(puede_votar(15)) # False + + +print("--- Parte 4: funciones que no devuelven nada usan -> None ---") + +# -> None: la función hace algo pero no devuelve ningún valor +def imprimir_lista(elementos: list) -> None: + for elemento in elementos: + print(" -", elemento) + +imprimir_lista(["Leche", "Pan", "Huevos"]) +# Esta función no tiene return, por eso su tipo de retorno es None + + +print("--- Parte 5: etiquetando parámetros con valor por defecto ---") + +# El valor por defecto va DESPUÉS de la etiqueta de tipo +def describir_producto(nombre: str, precio: float, disponible: bool = True) -> str: + estado = "En stock" if disponible else "Agotado" + return f"{nombre} — ${precio} — {estado}" + +print(describir_producto("Teclado", 450.0)) # usa disponible=True por defecto +print(describir_producto("Monitor", 3200.0, False)) # sobreescribe el default diff --git a/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/reto.md b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/reto.md new file mode 100644 index 0000000..b3bfffb --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/reto.md @@ -0,0 +1,48 @@ +# Reto 1: El Etiquetador del Almacén 🏷️ + +El almacén de la empresa tiene un sistema de funciones completamente sin etiquetas. Tu jefe te pide que las etiquetes todas correctamente antes de que llegue el nuevo programador a trabajar con ellas. + +## Instrucciones Paso a Paso: + +Copia exactamente estas 5 funciones en tu archivo `reto.py` y **agrega los Type Hints correctos** a cada una: + +```python +def calcular_impuesto(precio, porcentaje): + return precio * (porcentaje / 100) + +def repetir_mensaje(mensaje, veces): + return mensaje * veces + +def es_numero_par(numero): + return numero % 2 == 0 + +def construir_perfil(nombre, edad, ciudad): + return {"nombre": nombre, "edad": edad, "ciudad": ciudad} + +def imprimir_separador(caracter, longitud): + print(caracter * longitud) +``` + +Para cada función, identifica: +1. ¿Qué tipo de dato recibe cada parámetro? (`str`, `int`, `float`, `bool`, `list`, `dict`) +2. ¿Qué tipo de dato devuelve? (¿texto? ¿número? ¿verdadero/falso? ¿diccionario? ¿nada?) + +Después de agregar las etiquetas, llama a cada función con valores reales e imprime el resultado para confirmar que funciona. + +> **Pista para `imprimir_separador`:** Esta función usa `print()` pero no tiene `return`. ¿Qué tipo de retorno le corresponde? + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `def`, `: Tipo` en parámetros, `-> Tipo` en retorno, `return`, `print()`, f-strings, llamar funciones con valores reales. +❌ **Conceptos Prohibidos:** `import`, clases, cambiar la lógica interna de las funciones. Solo agregas etiquetas, no reescribes el código. + +## Resultado Esperado en tu Terminal: + +```text +Impuesto de $500 al 16%: $80.0 +Mensaje repetido: HolaHolaHola +¿El 8 es par?: True +Perfil creado: {'nombre': 'Kaia', 'edad': 25, 'ciudad': 'CDMX'} +------------------------------ +``` + +Crea tu código en `reto.py`. Si tu editor (VS Code) subraya en rojo cuando llamas a una función con el tipo incorrecto, significa que las etiquetas están funcionando correctamente. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/teoria.md b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/teoria.md new file mode 100644 index 0000000..33c9832 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/teoria.md @@ -0,0 +1,84 @@ +# Teoría: Las Etiquetas de las Cajas 🏷️ + +Imagina que trabajas en un almacén. Llegan cajas sin etiquetar. Para saber qué contienen, tienes que abrirlas una por una. Una pérdida de tiempo enorme. + +Ahora imagina que cada caja lleva una etiqueta: `"Contenido: Vasos de Vidrio | Máximo: 2 kg"`. Cualquier trabajador sabe qué va adentro y cómo manejarla, sin abrirla. + +Tus funciones sin **Type Hints** son cajas sin etiqueta. Con Type Hints, cualquiera sabe qué esperar. + +--- + +## Anatomía de los Type Hints + +Hay dos lugares donde se ponen etiquetas en una función: + +**1. En los parámetros (la entrada de la caja):** +Después del nombre del parámetro, escribe `: Tipo`. + +**2. En el `return` (la salida de la función):** +Después del `:` del `def`, antes de los dos puntos del cuerpo, escribe `-> Tipo`. + +```python +# ANTES: Sin etiqueta — ¿Qué le meto? ¿Qué me devuelve? +def calcular_descuento(precio, porcentaje): + return precio - (precio * porcentaje / 100) + + +# DESPUÉS: Con etiquetas — contrato claro +# parámetro: Tipo parámetro: Tipo -> Tipo de retorno +def calcular_descuento(precio: float, porcentaje: float) -> float: + return precio - (precio * porcentaje / 100) +``` + +Desmontando los símbolos nuevos: +- `: float` después de `precio` — la etiqueta que dice "aquí espero un número decimal" +- `-> float` antes de los dos puntos del `def` — la etiqueta que dice "esta función devuelve un número decimal" +- `->` — la flecha. En Python, significa "esta función produce esto". No es matemática, es solo un indicador visual. + +**Los tipos más comunes** que ya conoces: + +| Tipo en Python | Qué significa | +|---|---| +| `str` | Texto ("hola", "Ana") | +| `int` | Número entero (1, 42, -5) | +| `float` | Número decimal (3.14, 9.99) | +| `bool` | Verdadero o Falso (True, False) | +| `list` | Una lista ([1, 2, 3]) | +| `dict` | Un diccionario ({"clave": "valor"}) | +| `None` | La función no devuelve nada | + +```python +# Ejemplo con cada tipo +def saludar(nombre: str) -> str: + return "Hola, " + nombre + +def es_mayor_de_edad(edad: int) -> bool: + return edad >= 18 + +def mostrar_menu(opciones: list) -> None: + for opcion in opciones: + print(opcion) + # No hay return — devuelve None (nada) +``` + +--- + +## ¿Qué pasa si me equivoco? + +**La trampa más importante: Python NO te detiene si ignoras las etiquetas** + +```python +def sumar(a: int, b: int) -> int: + return a + b + +sumar("hola", "mundo") # Python NO lanza un error +# Resultado: "holamundo" — Python concatenó strings en vez de sumar +``` + +**¿Por qué pasa?** +Los Type Hints son solo *etiquetas de documentación*. Python las lee y las ignora en tiempo de ejecución. No valida que realmente le pases un `int`. Si le pasas un `str`, lo acepta igual y hace lo que pueda con él. + +**¿Entonces para qué sirven?** +Sirven para dos cosas muy valiosas: +1. **Para ti y tus compañeros:** Leen la firma de la función y saben qué pasar sin leer el cuerpo. +2. **Para tu editor (VS Code, PyCharm):** Ellos *sí* leen los type hints y te marcan en rojo si pasas el tipo incorrecto, antes de ejecutar. Es como un corrector ortográfico, pero para tipos de datos. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/test_main.py b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/test_main.py new file mode 100644 index 0000000..e85ae1e --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/1-type-hints/test_main.py @@ -0,0 +1,12 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +def test_archivo_existe(): + assert os.path.exists(os.path.join(BASE, "reto.py")) +def test_type_hints_presentes(): + sys.path.insert(0, BASE) + import importlib + mod = importlib.import_module("reto") + funciones = [v for k, v in vars(mod).items() if callable(v) and not k.startswith("_")] + assert any(hasattr(f, "__annotations__") and f.__annotations__ for f in funciones), \ + "Al menos una función debe tener type hints" diff --git a/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/README.md b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/README.md new file mode 100644 index 0000000..a26d1f2 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/README.md @@ -0,0 +1,19 @@ +# Nivel 2: El Manual de Instrucciones 📖 + +## ¿Por qué aprender esto? + +Las etiquetas (Type Hints) te dicen *qué tipo* espera una función. Pero no te dicen *por qué existe*, *qué hace exactamente* o *qué devuelve en situaciones especiales*. + +Un **Docstring** es el manual de instrucciones que vive adentro de la función. Es una cadena de texto especial que le explica a cualquier programador (incluyendo tú mismo en 6 meses) todo lo que necesita saber sobre esa función, sin tener que leer el código línea por línea. + +Es el texto que aparece cuando alguien llama `help(tu_funcion)` en Python. Tu editor de código también lo muestra cuando pasas el cursor sobre el nombre de la función. + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — aprende dónde va el docstring y cómo estructurarlo +2. 🐍 Estudia `ejemplo.py` — ve funciones con y sin manual de instrucciones +3. 🔥 Completa `reto.md` — escribe los manuales de un conjunto de funciones + +## Conceptos que usarás aquí + +`def`, `return`, `"""` (comillas triples — ya las viste en el Módulo 01 para strings multi-línea), `print()`, `help()` diff --git a/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/ejemplo.py b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/ejemplo.py new file mode 100644 index 0000000..758dd06 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/ejemplo.py @@ -0,0 +1,44 @@ +print("--- Parte 1: Una función muda (sin manual) ---") + +def convertir_temperatura(valor: float) -> float: + return (valor * 9/5) + 32 + +# Alguien que lea esto pensará: "¿Convierte de Celsius a Fahrenheit o al revés?" +# Tienen que leer la fórmula matemática para adivinar. +print(convertir_temperatura(0.0)) # 32.0 + + +print("\n--- Parte 2: La misma función CON manual ---") + +def convertir_celsius_a_fahrenheit(celsius: float) -> float: + """ + Convierte grados Celsius a Fahrenheit. + + Toma la temperatura en Celsius, la multiplica por 9/5 y le suma 32. + + Args: + celsius: La temperatura en grados Celsius. + + Returns: + La temperatura equivalente en grados Fahrenheit. + """ + return (celsius * 9/5) + 32 + +# ¡Ahora no hay dudas! El docstring explica exactamente qué pasa. +print(convertir_celsius_a_fahrenheit(0.0)) # 32.0 + + +print("\n--- Parte 3: Docstrings mínimos de una sola línea ---") + +def saludar(nombre: str) -> str: + """Devuelve un saludo amigable con el nombre proporcionado.""" + return f"¡Hola, {nombre}!" + +print(saludar("Kaia")) + + +print("\n--- Parte 4: Usando help() para leer el manual ---") + +# La función integrada help() nos muestra el docstring en la terminal +print("Viendo la ayuda de convertir_celsius_a_fahrenheit:") +help(convertir_celsius_a_fahrenheit) diff --git a/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/reto.md b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/reto.md new file mode 100644 index 0000000..f8eb09b --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/reto.md @@ -0,0 +1,49 @@ +# Reto 2: El Escritor de Manuales 📖 + +El nuevo programador vio tus Type Hints (¡buen trabajo!) pero ahora se queja de que no entiende qué hacen las funciones matemáticamente complejas. Tu jefe te pide que agregues el manual de instrucciones (Docstrings) a 3 funciones críticas. + +## Instrucciones Paso a Paso: + +Copia estas 3 funciones en tu archivo `reto.py` y **agrega un docstring completo** (con descripción, Args y Returns) a cada una. + +```python +def calcular_area_triangulo(base: float, altura: float) -> float: + return (base * altura) / 2 + +def es_mayor_de_edad(edad: int) -> bool: + return edad >= 18 + +def crear_usuario(nombre: str, correo: str) -> dict: + return {"user": nombre, "email": correo, "activo": True} +``` + +Para cada función: +1. Agrega las `"""` (comillas triples) justo debajo de la línea `def`, con indentación correcta. +2. Escribe una oración que empiece con un verbo (ej. "Calcula...", "Verifica..."). +3. Agrega la sección `Args:` explicando cada parámetro. +4. Agrega la sección `Returns:` explicando qué devuelve. +5. Al final de tu archivo `reto.py`, usa la función `help()` para imprimir el manual de `crear_usuario` en la terminal y comprobar que lo hiciste bien. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `def`, `"""`, Type Hints (`:`, `->`), diccionarios, `help()`. +❌ **Conceptos Prohibidos:** Clases, importar módulos, cambiar la lógica interna. ¡Solo escribe texto entre comillas triples! + +## Resultado Esperado en tu Terminal: + +Al ejecutar tu código, la salida de `help(crear_usuario)` debería mostrar algo muy similar a esto (el texto exacto dependerá de lo que escribiste): + +```text +Help on function crear_usuario in module __main__: + +crear_usuario(nombre: str, correo: str) -> dict + Crea un diccionario con los datos del usuario. + + Args: + nombre: El nombre del usuario. + correo: El correo electrónico del usuario. + + Returns: + Un diccionario con las claves 'user', 'email' y un estado 'activo' en True. +``` + +¡Escribe tus manuales en `reto.py` y verifica tu resultado! diff --git a/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/teoria.md b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/teoria.md new file mode 100644 index 0000000..d3108d8 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/teoria.md @@ -0,0 +1,92 @@ +# Teoría: El Manual de Instrucciones 📖 + +¿Alguna vez compraste algo sin manual? Un control remoto con 40 botones y cero explicaciones. Para entender para qué sirve cada botón, tienes que probarlo todo y rezar para no romper nada. + +Ahora imagina que el control tiene un pequeño sticker en el reverso: *"Botón rojo: apagar. Botón azul: cambiar fuente. Botón amarillo: temporizador. Advertencia: no presionar rojo y azul al mismo tiempo o se reinicia."* + +Eso es un **Docstring**: el manual de instrucciones que vive **adentro** de tu función. + +--- + +## Anatomía del Docstring + +Un docstring es una cadena de texto con comillas triples `"""` que va **inmediatamente después** de la línea `def`, antes del cuerpo de la función. + +```python +def calcular_imc(peso: float, altura: float) -> float: + """ + Calcula el Índice de Masa Corporal (IMC) de una persona. + + El IMC se obtiene dividiendo el peso en kilogramos entre + el cuadrado de la altura en metros. + + Args: + peso: El peso de la persona en kilogramos. Debe ser mayor a 0. + altura: La altura de la persona en metros. Debe ser mayor a 0. + + Returns: + El valor del IMC como número decimal, redondeado a 2 decimales. + """ + return round(peso / (altura * altura), 2) +``` + +Desmontando el docstring: + +- `"""` — Tres comillas dobles abren y tres cierran el texto. Ya las conoces: son para strings que ocupan varias líneas. +- **Primera línea:** Un resumen en una oración. Qué hace la función. Siempre empieza con un verbo: "Calcula...", "Devuelve...", "Verifica...". +- `Args:` — La sección de ingredientes. Aquí listas cada parámetro con su descripción. +- `Returns:` — La sección de producto final. Qué devuelve la función y bajo qué circunstancias. + +### La regla mínima: al menos una línea + +Si no tienes tiempo para el manual completo, escribe **al menos la primera línea**. Algo es infinitamente mejor que nada: + +```python +def saludar(nombre: str) -> str: + """Devuelve un saludo personalizado con el nombre dado.""" + return "Hola, " + nombre +``` + +--- + +## El poder de `help()` + +Cuando alguien llama `help(calcular_imc)` en la terminal de Python, ve exactamente el docstring que escribiste. Es como tener un sistema de ayuda integrado en tu función. + +```python +help(calcular_imc) +# Muestra: +# Help on function calcular_imc: +# calcular_imc(peso: float, altura: float) -> float +# Calcula el Índice de Masa Corporal... +``` + +--- + +## ¿Qué pasa si me equivoco? + +**El error más peligroso: el docstring mentiroso** + +```python +def dividir(a: float, b: float) -> float: + """Suma dos números y devuelve el resultado.""" # ← MENTIRA. Esta función DIVIDE. + return a / b +``` + +Un docstring incorrecto es **peor que no tener docstring**. Engaña activamente a quien lee el código. Si cambias lo que hace una función, lo primero que debes actualizar es su docstring. + +**El segundo error más común: poner el docstring en el lugar equivocado** + +```python +# MAL: el docstring está afuera de la función +"""Calcula el área.""" +def calcular_area(base, altura): + return base * altura + +# BIEN: el docstring va ADENTRO, en la primera línea del cuerpo +def calcular_area(base, altura): + """Calcula el área de un rectángulo dado su base y altura.""" + return base * altura +``` + +Si el docstring está afuera, Python lo trata como un string suelto (lo ignora) y `help()` no lo mostrará. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/test_main.py b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/test_main.py new file mode 100644 index 0000000..22a7786 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/2-docstrings/test_main.py @@ -0,0 +1,12 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +def test_archivo_existe(): + assert os.path.exists(os.path.join(BASE, "reto.py")) +def test_docstring_presente(): + sys.path.insert(0, BASE) + import importlib + mod = importlib.import_module("reto") + funciones = [v for k, v in vars(mod).items() if callable(v) and not k.startswith("_")] + assert any(f.__doc__ for f in funciones), \ + "Al menos una función debe tener docstring" diff --git a/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/README.md b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/README.md new file mode 100644 index 0000000..31b0d3f --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/README.md @@ -0,0 +1,17 @@ +# Nivel 3: El Inspector Automático (pytest) 🕵️‍♂️ + +## ¿Por qué aprender esto? + +Hasta ahora, para probar si tu código funciona, lo ejecutas y miras la terminal (imprimes con `print()`). ¿Qué pasa cuando tienes 100 funciones? ¿Vas a mirar 100 `print()` cada vez que cambias una línea de código? ¡Te volverías loco! + +Aquí entra **pytest**. Es como tener un inspector que revisa todas tus funciones automáticamente en milisegundos y te dice qué funciona y qué falló. Es la diferencia entre probar las cosas a mano y tener un robot de control de calidad. + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — descubre la palabra mágica `assert` (exigir). +2. 🐍 Estudia `ejemplo.py` — mira cómo un `assert` verifica tu código. +3. 🔥 Completa `reto.md` — escribe tu primer test automático de verdad. + +## Conceptos que usarás aquí + +`def`, `==` (doble igual para comparar), y funciones básicas. Hoy sumarás a tu vocabulario la palabra `assert`. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/ejemplo.py b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/ejemplo.py new file mode 100644 index 0000000..8ee828b --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/ejemplo.py @@ -0,0 +1,37 @@ +print("--- Parte 1: La función que queremos probar ---") + +def calcular_doble(numero: int) -> int: + """Devuelve el doble de un número.""" + return numero * 2 + +print("Normalmente probaríamos así a mano:") +print("El doble de 4 es:", calcular_doble(4)) + + +print("\n--- Parte 2: Usando assert manualmente ---") +# El assert evalúa si algo es True. Si lo es, no hace nada (todo está bien). +# Si es False, el programa "explota" con un error gigante. + +print("Exigiendo que calcular_doble(5) sea 10 con assert...") +assert calcular_doble(5) == 10 +print("¡Todo bien! El código sobrevivió al assert porque 5*2 sí es 10.") + + +print("\n--- Parte 3: Así se ve un test de pytest ---") +# Para pytest, los tests se encierran en funciones que empiezan con "test_" + +def test_calcular_doble_con_positivos(): + """Prueba que el doble de 3 sea 6.""" + resultado = calcular_doble(3) + assert resultado == 6 + +def test_calcular_doble_con_ceros(): + """Prueba que el doble de 0 sea 0.""" + assert calcular_doble(0) == 0 + +# NOTA IMPORTANTE: +# Si ejecutas este archivo normalmente con el botón "Run" de Python, +# Python no ejecutará las funciones `test_` automáticamente. No pasará nada. +# +# Para que los tests corran de verdad, debes decirle al programa "pytest" que los busque. +# Se hace escribiendo en tu terminal: pytest ejemplo.py diff --git a/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/reto.md b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/reto.md new file mode 100644 index 0000000..3a51a08 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/reto.md @@ -0,0 +1,36 @@ +# Reto 3: El Primer Test 🕵️‍♂️ + +Tu compañero de trabajo escribió una función para verificar si una contraseña es válida (tiene al menos 8 caracteres). ¡Pero no la ha probado! Te toca a ti crear las pruebas automáticas. + +## Instrucciones Paso a Paso: + +1. Crea un archivo llamado `test_reto.py` (es vital que el archivo empiece con `test_`). +2. Copia la función de tu compañero en ese archivo: +```python +def es_contrasena_segura(contrasena: str) -> bool: + """Devuelve True si la contraseña tiene 8 caracteres o más.""" + return len(contrasena) >= 8 +``` +3. Abajo de esa función, crea un test llamado `test_contrasena_corta()`. Adentro, usa `assert` para exigir que al pasarle `"123"` a la función, el resultado sea igual a `False`. +4. Crea otro test llamado `test_contrasena_larga()`. Adentro, usa `assert` para exigir que al pasarle `"secreto123"` a la función, el resultado sea igual a `True`. +5. Abre la **terminal** de tu editor y ejecuta el siguiente comando: + `pytest test_reto.py` + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `def`, `assert`, `len()`, `==` (doble igual), y crear funciones que empiecen con `test_`. +❌ **Conceptos Prohibidos:** Usar `print()`. ¡En los tests automáticos no miramos prints en la pantalla, miramos que todo salga verde! + +## Resultado Esperado en tu Terminal: + +Cuando ejecutes `pytest test_reto.py`, deberías ver un mensaje hermoso y verde (o al menos con texto indicando 100% de éxito), muy parecido a esto: + +```text +============================= test session starts ============================== +collected 2 items + +test_reto.py .. [100%] + +============================== 2 passed in 0.01s =============================== +``` + +Si logras ver ese "2 passed", ¡felicidades! Eres oficialmente un programador que hace tests automáticos. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/teoria.md b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/teoria.md new file mode 100644 index 0000000..699bf6e --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/teoria.md @@ -0,0 +1,44 @@ +# Teoría: El Inspector Automático 🕵️‍♂️ + +Probar código con `print()` es como pesar ingredientes "a ojo" de buen cubero. Funciona para cocinar en casa, pero en una panadería profesional necesitas una báscula precisa. + +**pytest** es tu báscula. Es una herramienta externa (un programa extra que usamos) que ejecuta un código especial llamado **tests** (pruebas). + +## La magia del `assert` (Exigir) + +Para escribir un test en Python, usamos una palabra nueva: `assert`. +En español, `assert` significa "afirmar" o "exigir". Cuando la usas, le estás diciendo a Python: **"Exijo que esto sea verdad. Si es mentira, ¡detén todo y lanza una alarma roja!"**. + +```python +def sumar(a: int, b: int) -> int: + return a + b + +# Esto es una función de prueba (un test) +def test_sumar_numeros(): + resultado = sumar(2, 3) + assert resultado == 5 # "Exijo que resultado sea exactamente 5" +``` + +### Anatomía de un Test + +1. **El nombre de la función:** DEBE empezar con `test_`. Si se llama `probar_suma()`, pytest se pondrá una venda en los ojos y la ignorará por completo. +2. **La acción:** Ejecutar la función real que queremos probar y guardar su resultado. +3. **La exigencia (`assert`):** Comparamos lo que nos dio la función con lo que esperábamos usando el doble igual (`==`). + +--- + +## ¿Qué pasa si me equivoco? + +**El error más común: olvidar el `test_`** + +```python +# pytest NUNCA ejecutará esto, porque no empieza con "test_" +def verificar_resta(): + assert 5 - 2 == 3 +``` +Siempre, siempre llama a tus pruebas empezando con `test_`. + +**El segundo "error" (que en realidad es un éxito): El test falla** + +Si el `assert` detecta que la afirmación es falsa, pytest te mostrará un mensaje rojo gigante en la terminal llamado `AssertionError`. +¡No te asustes! Si un test se pone rojo, significa que **el test hizo bien su trabajo**. Detectó que tu código tiene un error, ¡antes de que ese código llegara a un cliente! Tu trabajo ahora es ir a tu código real y arreglarlo para que el test se ponga verde (exitoso). diff --git a/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/test_main.py b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/test_main.py new file mode 100644 index 0000000..5e5073d --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/3-pytest-intro/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +def test_archivo_existe(): + assert os.path.exists(os.path.join(BASE, "test_reto.py")), "Debes crear test_reto.py" +def test_tests_del_alumno_pasan(): + r = subprocess.run( + [sys.executable, "-m", "pytest", "test_reto.py", "-v"], + capture_output=True, text=True, cwd=BASE, timeout=30) + assert r.returncode == 0, f"Tus tests fallaron:\n{r.stdout}\n{r.stderr}" diff --git a/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/README.md b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/README.md new file mode 100644 index 0000000..5993efa --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/README.md @@ -0,0 +1,19 @@ +# Nivel 4: Superpoderes para tus Tests (Fixtures y Parametrize) 🦸‍♂️ + +## ¿Por qué aprender esto? + +Imagina que estás probando si tu licuadora funciona. No quieres ir al mercado a comprar fruta, cortarla, licuarla y luego tener que lavar todo el desastre *para cada prueba diferente*. Quieres que alguien te dé la fruta ya cortada, y que limpie después por ti. + +En el mundo del código, crear datos de prueba (como un usuario falso o una lista enorme) una y otra vez es aburrido y ensucia tu código. En `pytest`, los **Fixtures** son ayudantes invisibles que preparan las cosas (datos, configuraciones) antes de tu test para ahorrarte trabajo. + +Por otro lado, ¿qué pasa si quieres probar tu código con 10 números diferentes? En vez de escribir 10 tests casi idénticos, usamos **Parametrize** para decirle a pytest: "Ejecuta este mismo test 10 veces, pero usando esta lista de valores". + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — descubre el símbolo `@` (decorador) y el concepto de `import`. +2. 🐍 Estudia `ejemplo.py` — mira cómo nos ahorramos repetir código. +3. 🔥 Completa `reto.md` — crea tus propios ayudantes de prueba. + +## Conceptos que usarás aquí + +`def`, `assert`, listas `[]`, diccionarios `{}`. Y hoy introducirás dos nuevos superpoderes: `@` (Decoradores) e `import`. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/ejemplo.py b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/ejemplo.py new file mode 100644 index 0000000..4db04cc --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/ejemplo.py @@ -0,0 +1,46 @@ +print("--- Este archivo muestra cómo usar Fixtures y Parametrize ---") +print("Cámbiale el nombre a test_ejemplo.py y ejecuta: pytest test_ejemplo.py en la terminal\n") + +# Importamos la caja de herramientas de pytest +import pytest + +print("--- Parte 1: Fixtures (Ayudantes) ---") + +# Creamos un fixture usando el decorador @pytest.fixture +@pytest.fixture +def usuario_falso(): + """Un ayudante que nos fabrica un diccionario de usuario listo para probar.""" + return {"nombre": "Kaia", "edad": 25, "rol": "admin"} + +# El test pide el 'usuario_falso' en sus paréntesis. pytest se lo entrega mágicamente. +def test_usuario_es_admin(usuario_falso): + # Ya no tengo que crear el diccionario aquí adentro, ¡ya viene hecho! + assert usuario_falso["rol"] == "admin" + +def test_usuario_tiene_edad_correcta(usuario_falso): + # ¡Puedo reusar el mismo ayudante en múltiples tests! + assert usuario_falso["edad"] == 25 + + +print("\n--- Parte 2: Parametrize (Pruebas Múltiples) ---") + +def es_par(numero: int) -> bool: + """Devuelve True si el número es par.""" + return numero % 2 == 0 + +# En vez de hacer 3 tests diferentes, hacemos uno solo que se repite 3 veces. +# Le damos el nombre del parámetro entre comillas ("num") y la lista de valores a probar. +@pytest.mark.parametrize("num", [2, 8, 100]) +def test_numeros_pares(num): + assert es_par(num) == True + +# También podemos probar varios parámetros a la vez. +# Ponemos los nombres separados por comas, y pasamos una lista de "paquetitos" (listas internas). +# Esto significa: prueba sumar 1+1 esperando 2. Luego 5+5 esperando 10. Etc. +@pytest.mark.parametrize("a, b, esperado", [ + [1, 1, 2], + [5, 5, 10], + [10, -5, 5] +]) +def test_suma_variada(a, b, esperado): + assert a + b == esperado diff --git a/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/reto.md b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/reto.md new file mode 100644 index 0000000..f1eb710 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/reto.md @@ -0,0 +1,29 @@ +# Reto 4: La Fábrica de Pruebas 🏭 + +Tu equipo está creando un carrito de compras online. Te piden que pruebes la función que calcula el total con impuestos. Pero son exigentes: quieren que la pruebes con **varios precios a la vez**, y que uses un **ayudante (Fixture)** para obtener el porcentaje del impuesto, en vez de escribirlo a mano en el test. + +## Instrucciones Paso a Paso: + +1. Crea el archivo `test_reto_avanzado.py`. +2. En la línea 1, escribe `import pytest` para tener acceso a los superpoderes. +3. Copia la función real que vamos a probar: +```python +def calcular_total(precio: float, impuesto: float) -> float: + return precio + (precio * impuesto) +``` +4. **Crea un Fixture:** + Usa `@pytest.fixture` arriba de una función llamada `impuesto_estandar()`. Esta función simplemente debe usar `return` para devolver el número decimal `0.15` (que representa el 15%). +5. **Crea un Test Parametrizado que además usa el Fixture:** + - Usa `@pytest.mark.parametrize` para probar tres precios distintos en una sola pasada: `100.0`, `50.0`, y `200.0`. Llámale al parámetro `"precio_base"`. + - Crea el test: `def test_calcular_total(precio_base, impuesto_estandar):`. (¡Mira! El test pide tanto el valor parametrizado como el ayudante al mismo tiempo). + - Adentro del test, calcula el resultado usando tu función real `calcular_total()` y guárdalo en una variable. + - Usa `assert` para verificar que el total calculado es **mayor** (`>`) que el `precio_base` original. (Tiene sentido, si le sumas impuestos a algo, siempre cuesta más). +6. Ejecuta `pytest test_reto_avanzado.py` en la terminal de tu editor. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `import pytest`, `@pytest.fixture`, `@pytest.mark.parametrize`, `def`, `return`, `assert`, operadores matemáticos y lógicos (`>`, `+`, `*`). +❌ **Conceptos Prohibidos:** Usar `print()`. Evita hacer 3 funciones de test; el parametrize debe hacer el trabajo de multiplicar el test. + +## Resultado Esperado en tu Terminal: + +Al correr el test, verás que aunque escribiste la función `test_calcular_total` una sola vez, pytest ejecuta **tres pruebas** automáticamente (una por cada precio). Debería salir todo en verde, indicando "3 passed". diff --git a/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/teoria.md b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/teoria.md new file mode 100644 index 0000000..bf3f294 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/teoria.md @@ -0,0 +1,66 @@ +# Teoría: Superpoderes para tus Tests 🦸‍♂️ + +Para usar Fixtures y Parametrize, necesitamos usar una herramienta nueva de Python: el **Decorador** (el símbolo `@`). + +## El símbolo `@` (El Decorador) y la palabra `import` + +En Python, cuando pones un símbolo `@` (arroba) justo arriba de un `def`, le estás pegando un "sticker mágico" a esa función. + +```python +@superpoder +def mi_funcion(): + pass +``` +El `@` le dice a Python: "Envuélvela con un comportamiento especial". Hoy usaremos dos superpoderes que vienen de una fábrica externa llamada `pytest`. + +Para traer herramientas de otras fábricas, usamos la palabra `import`. +Escribir `import pytest` al inicio de tu archivo significa: "Python, ve y tráeme la caja de herramientas de pytest para poder usar sus superpoderes". + +--- + +## 1. Fixtures: Tus Ayudantes Preparadores + +Un **Fixture** es una función que "fabrica" algo que tus tests necesitan. Se crea poniéndole el superpoder `@pytest.fixture` arriba. + +```python +import pytest + +# 1. Creamos el ayudante (Fixture) +@pytest.fixture +def carrito_compras(): + # Fabrica un carrito con 2 productos listos para usar + return ["manzana", "leche"] + +# 2. El test PIDE al ayudante por su nombre (adentro de sus paréntesis) +def test_carrito_tiene_dos_cosas(carrito_compras): + assert len(carrito_compras) == 2 +``` +Fíjate en el truco: ¡el test recibe `carrito_compras` como un parámetro! Pytest es inteligente: ve que el test pide eso, busca el fixture con ese nombre, lo ejecuta en secreto, y le pasa el resultado (la lista) al test. ¡Tú no tuviste que crear la lista adentro del test! + +--- + +## 2. Parametrize: El Repetidor Automático + +Imagina que quieres probar que multiplicar un número por cero siempre da cero. En lugar de hacer un test para el 5, otro para el 10, y otro para el 99, usamos el superpoder `@pytest.mark.parametrize`. + +```python +import pytest + +# El decorador dice: "El parámetro se llamará 'numero'. +# Repite este test usando los valores 5, 10 y 99 de la lista". +@pytest.mark.parametrize("numero", [5, 10, 99]) +def test_multiplicar_por_cero(numero): + resultado = numero * 0 + assert resultado == 0 +``` +Pytest ejecutará el mismo test **tres veces automáticamente**, una por cada número. + +--- + +## ¿Qué pasa si me equivoco? + +**La trampa del parámetro mal escrito** + +Si en el decorador escribes `@pytest.mark.parametrize("num", ...)` pero luego en el `def` pones `def test_algo(n):`, ¡pytest explotará! +Te dirá `fixture 'n' not found`. ¿Por qué? Porque intentará buscar un parámetro que se llame `n` y solo sabe entregar uno que se llame `num`. +**Solución:** El nombre entre las comillas del parametrize DEBE ser exactamente igual al nombre del parámetro en el `def`. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/test_main.py b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/test_main.py new file mode 100644 index 0000000..8c8e0a2 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/4-fixtures-y-parametrize/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +def test_archivo_existe(): + assert os.path.exists(os.path.join(BASE, "test_reto_avanzado.py")) +def test_tests_avanzados_pasan(): + r = subprocess.run( + [sys.executable, "-m", "pytest", "test_reto_avanzado.py", "-v"], + capture_output=True, text=True, cwd=BASE, timeout=30) + assert r.returncode == 0, f"Fallaron:\n{r.stdout}\n{r.stderr}" + assert "3 passed" in r.stdout, "Deben pasar exactamente 3 tests" diff --git a/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/README.md b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/README.md new file mode 100644 index 0000000..d011104 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/README.md @@ -0,0 +1,22 @@ +# Nivel 5: El Doble de Acción (Mocking) 🎭 + +## ¿Por qué aprender esto? + +Imagina que estás programando un sistema de ventas. Escribes un test automático que llama a la función `cobrar_tarjeta_de_credito()`. Si corres ese test 100 veces, ¡le vas a cobrar a tu tarjeta real 100 veces! 😱 + +En el código real hay cosas "peligrosas" o impredecibles: +- Conectarse a internet. +- Cobrar dinero de verdad. +- Generar números al azar. + +En el cine, cuando una escena es muy peligrosa para el actor principal, contratan a un **doble de acción** (alguien que se parece y hace el trabajo, pero sin poner en riesgo al actor real). En programación, a ese doble de acción le llamamos **Mock** (del inglés "simulación" o "burla"). + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — descubre qué es un Mock y cómo intercambiar al actor real por el doble. +2. 🐍 Estudia `ejemplo.py` — mira cómo engañamos al código para ganar un juego al azar. +3. 🔥 Completa `reto.md` — salva tu tarjeta de crédito usando un doble de acción. + +## Conceptos que usarás aquí + +`def`, pasar parámetros, funciones. Hoy aprenderás un concepto avanzado de calidad de código llamado "Inyección de Dependencias" (suena asustadizo, pero es ridículamente fácil de hacer). diff --git a/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/ejemplo.py b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/ejemplo.py new file mode 100644 index 0000000..736fa94 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/ejemplo.py @@ -0,0 +1,51 @@ +print("--- Este archivo muestra cómo usar un Mock (Doble de Acción) ---") +print("Cámbiale el nombre a test_ejemplo.py y ejecuta: pytest test_ejemplo.py en la terminal\n") + +print("--- Parte 1: El Código Real ---") + +# Actor real: Impredecible +def checar_clima_en_internet() -> str: + # Imagina que aquí nos conectamos a la NASA + # Puede devolver "Soleado", "Lluvia", o fallar si no hay internet. + return "Soleado" + +# La función a probar. +# Si el parámetro 'funcion_clima' no se le pasa nada, usa 'checar_clima_en_internet' por defecto. +# (¡Qué gran truco para no romper el código viejo!) +def decidir_que_ropa_usar(funcion_clima = checar_clima_en_internet) -> str: + clima = funcion_clima() # Ejecutamos la función que nos pasaron + + if clima == "Lluvia": + return "Lleva paraguas" + else: + return "Lleva gafas de sol" + + +print("\n--- Parte 2: Los Tests con Mocks ---") + +def test_cuando_llueve_recomienda_paraguas(): + # 1. Creamos al doble de acción que finge que siempre llueve + def clima_falso_lluvia(): + return "Lluvia" + + # 2. Le pasamos el doble a nuestra función + recomendacion = decidir_que_ropa_usar(clima_falso_lluvia) + + # 3. Exigimos que el resultado sea correcto + assert recomendacion == "Lleva paraguas" + + +def test_cuando_hay_sol_recomienda_gafas(): + # 1. Creamos al doble de acción que finge que siempre hay sol + def clima_falso_sol(): + return "Soleado" + + # 2. Le pasamos el doble + recomendacion = decidir_que_ropa_usar(clima_falso_sol) + + # 3. Exigimos que el resultado sea correcto + assert recomendacion == "Lleva gafas de sol" + +# Como puedes ver, gracias a los Mocks, logramos probar TODAS las rutas de +# nuestro código sin tener que esperar a que el clima real cambie, +# y sin depender del internet. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/reto.md b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/reto.md new file mode 100644 index 0000000..f31174c --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/reto.md @@ -0,0 +1,57 @@ +# Reto 5: Salva la Tarjeta de Crédito 💳 + +Estás probando una función para una tienda en línea. Esta función revisa que un carrito de compras tenga cosas y, si es así, intenta cobrar el dinero de verdad. + +¡No queremos perder nuestro dinero corriendo tests! Vas a crear un Mock (un doble de acción) para fingir el cobro sin gastar un centavo. + +## Instrucciones Paso a Paso: + +1. Crea el archivo `test_reto_mock.py`. +2. Copia este código base que simula nuestro sistema: + +```python +# El actor peligroso real +def cobrar_a_banco_real(): + print("¡COBRANDO 100 DÓLARES AL BANCO!") + return "Pagado" + +# La función que queremos probar, preparada para recibir Mocks +def finalizar_compra(carrito: list, funcion_cobro = cobrar_a_banco_real) -> str: + if len(carrito) == 0: + return "Error: carrito vacio" + + # Cobramos usando la función que nos pasen + estado_pago = funcion_cobro() + + if estado_pago == "Pagado": + return "Compra exitosa" + else: + return "Error en el pago" +``` + +3. Crea un test llamado `def test_finalizar_compra_con_exito():`. +4. Adentro del test, **crea una función falsa** (tu doble de acción) llamada `cobro_falso_exitoso()`. Esta función debe devolver simplemente el texto `"Pagado"`. +5. En el test, llama a `finalizar_compra`. + - El primer parámetro (el carrito) debe ser una lista con un producto, por ejemplo: `["Zapatos"]`. + - El segundo parámetro debe ser tu función doble de acción. **(¡Recuerda pasarla SIN los paréntesis `()`!)**. + - Guarda el resultado en una variable. +6. Usa `assert` para exigir que el resultado de la compra sea `"Compra exitosa"`. +7. Ejecuta `pytest test_reto_mock.py` en tu terminal. + +> *Opcional: Si quieres un reto mayor, crea un segundo test `test_finalizar_compra_rechazada` donde el doble de acción devuelva `"Rechazado"` y exige que el resultado sea `"Error en el pago"`.* + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `def`, `assert`, listas `[]`, asignar valores por defecto en los parámetros (`=`), pasar funciones como parámetros. +❌ **Conceptos Prohibidos:** Ejecutar la función original `cobrar_a_banco_real()`. ¡Tu test no debe imprimir nada de "COBRANDO 100 DÓLARES"! + +## Resultado Esperado en tu Terminal: + +```text +============================= test session starts ============================== +collected 1 item + +test_reto_mock.py . [100%] + +============================== 1 passed in 0.01s =============================== +``` +¡Tu test corrió exitosamente y tu billetera está a salvo gracias al Mock! diff --git a/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/teoria.md b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/teoria.md new file mode 100644 index 0000000..ce5add7 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/teoria.md @@ -0,0 +1,61 @@ +# Teoría: El Doble de Acción (Mocking) 🎭 + +Hay un principio en la calidad de código: **Tus tests nunca deben depender de cosas fuera de tu control**. Si tu test necesita internet y el wifi se cae, el test fallará, aunque tu código esté perfecto. Eso es injusto. + +Para evitarlo, usamos **Mocks** (simulaciones). + +## La forma más pura: Inyectar al Doble + +¿Recuerdas que en Python las funciones son como cualquier otra variable? Puedes pasar un texto a una función, puedes pasar una lista, ¡y también puedes pasar *otra función*! + +```python +# El actor "peligroso" (Impredecible, cambia cada vez) +def tirar_dado_real(): + # En la vida real, esto daría un número al azar del 1 al 6 + pass + +# La función que queremos probar. +# En vez de obligarla a usar siempre el dado real, le pedimos que RECIBA el dado que debe usar. +def jugar_turno(funcion_dado): + resultado = funcion_dado() + if resultado == 6: + return "Ganaste" + else: + return "Perdiste" +``` + +Al obligar a la función a "recibir" al actor como parámetro, hemos hecho que sea 100% testeable. En el mundo del software real, esto se llama **Inyección de Dependencias** (le inyectas lo que necesita). + +### El Test: Contratando al Doble de Acción + +Ahora, en nuestro test, creamos un actor falso que siempre haga lo que nos convenga para la prueba: + +```python +def test_ganar_el_juego(): + # 1. Creamos al doble de acción (el Mock) + def dado_falso_que_siempre_gana(): + return 6 # Siempre devuelve 6, es súper predecible + + # 2. Le pasamos el doble a nuestra función en vez del actor real + mensaje = jugar_turno(dado_falso_que_siempre_gana) + + # 3. Exigimos que el resultado sea victoria + assert mensaje == "Ganaste" +``` + +## ¿Qué pasa si me equivoco? + +**El error más común: Ejecutar la función falsa sin querer antes de tiempo** + +```python +# MAL ❌ +# Le pasaste el RESULTADO de la función (un número), no la función en sí. +jugar_turno(dado_falso_que_siempre_gana()) + +# BIEN ✅ +# Le pasas el NOMBRE de la función, sin los paréntesis `()`. +# Así, 'jugar_turno' podrá ejecutarla por dentro cuando la necesite. +jugar_turno(dado_falso_que_siempre_gana) +``` + +Fíjate muy bien en los paréntesis `()`. Cuando pasas el doble de acción como parámetro, se lo pasas sin los paréntesis. Es como entregarle la receta a un chef, en lugar de entregarle el pastel ya hecho. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/test_main.py b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/test_main.py new file mode 100644 index 0000000..8d9d023 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/5-mocking/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +def test_archivo_existe(): + assert os.path.exists(os.path.join(BASE, "test_reto_mock.py")) +def test_mock_pasa(): + r = subprocess.run( + [sys.executable, "-m", "pytest", "test_reto_mock.py", "-v"], + capture_output=True, text=True, cwd=BASE, timeout=30) + assert r.returncode == 0, f"Fallaron:\n{r.stdout}\n{r.stderr}" diff --git a/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/README.md b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/README.md new file mode 100644 index 0000000..c29decc --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/README.md @@ -0,0 +1,19 @@ +# Nivel 6: El Examen Final (Suite de Tests) 🏆 + +## ¿Por qué aprender esto? + +En la vida real, no escribimos un solo test y nos vamos a dormir. Escribimos una **"Suite de Tests"** (una colección completa de pruebas, como una caja de herramientas de seguridad) para asegurarnos de que todo el sistema funciona en conjunto. + +Si cambias algo en la línea 10, y accidentalmente rompes algo en la línea 500, tu Suite de Tests se pondrá roja al instante y te salvará de cometer un error en producción. + +En este nivel final, vas a unir TODO lo que has aprendido en el módulo para crear tu primera obra maestra de calidad: Type Hints, Docstrings, pytest, Fixtures, Parametrize y Mocks. + +## Ruta de Aprendizaje + +1. 📖 Lee `teoria.md` — un repaso rápido de tus nuevas habilidades y tu cinturón negro de calidad. +2. 🐍 Estudia `ejemplo.py` — mira cómo luce una suite de tests completa, uniendo el código real y las pruebas. +3. 🔥 Completa `reto.md` — tu prueba de fuego. El jefe final. + +## Conceptos que usarás aquí + +¡Todos los del módulo! Type Hints, Docstrings, `assert`, `import pytest`, Fixtures, Parametrize y Mocks. diff --git a/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/ejemplo.py b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/ejemplo.py new file mode 100644 index 0000000..076a6b4 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/ejemplo.py @@ -0,0 +1,69 @@ +print("--- Suite de Ejemplo: Sistema de Inventario ---") +print("Ejecuta 'pytest ejemplo.py' en la terminal para ver correr la suite.") + +import pytest + +# ========================================== +# CÓDIGO DE PRODUCCIÓN (El sistema real) +# ¡Nota lo bien documentado y etiquetado que está! +# ========================================== + +def agregar_al_inventario(inventario: dict, producto: str, cantidad: int) -> dict: + """ + Suma una cantidad de un producto al inventario de la tienda. + + Args: + inventario: El diccionario actual con los productos y existencias. + producto: El nombre del producto a agregar. + cantidad: Cuántos elementos sumar al inventario. + + Returns: + El diccionario del inventario ya actualizado. + """ + if producto in inventario: + inventario[producto] = inventario[producto] + cantidad + else: + inventario[producto] = cantidad + + return inventario + + +# ========================================== +# LA SUITE DE TESTS (Nuestro robot inspector) +# ========================================== + +# 1. Los Ayudantes (Fixtures) +@pytest.fixture +def inventario_vacio(): + """Entrega un inventario totalmente vacío.""" + return {} + +@pytest.fixture +def inventario_con_manzanas(): + """Entrega un inventario que ya tiene 5 manzanas.""" + return {"manzanas": 5} + + +# 2. Test simple (usando el fixture vacío) +def test_agregar_un_producto_nuevo(inventario_vacio): + # El ayudante nos dio {}, le agregamos 10 peras + resultado = agregar_al_inventario(inventario_vacio, "peras", 10) + + # Exigimos que ahora existan 10 peras + assert resultado["peras"] == 10 + + +# 3. Test Parametrizado (usando fixture y parametrize a la vez) +# Vamos a probar agregar 2, 10 y 0 manzanas. +@pytest.mark.parametrize("cantidad_extra, total_esperado", [ + [2, 7], # 5 + 2 = 7 + [10, 15], # 5 + 10 = 15 + [0, 5] # 5 + 0 = 5 +]) +def test_agregar_producto_ya_existente(inventario_con_manzanas, cantidad_extra, total_esperado): + + # El ayudante nos da el dict con 5 manzanas + resultado = agregar_al_inventario(inventario_con_manzanas, "manzanas", cantidad_extra) + + # Exigimos que la suma matemática se haya hecho bien + assert resultado["manzanas"] == total_esperado diff --git a/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/reto.md b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/reto.md new file mode 100644 index 0000000..023fbce --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/reto.md @@ -0,0 +1,51 @@ +# Reto 6: El Guardián del Código (Jefe Final) 🐉 + +¡El código del banco es un desastre! Encontraste un archivo viejo llamado `sistema_banco.py`. No tiene etiquetas de tipo (Type Hints), no tiene manual de instrucciones (Docstrings), y lo peor de todo... **¡No tiene ni un solo test!** + +Tu misión es transformar este código frágil en código de nivel profesional usando todas tus herramientas. + +## Código Base (Copia esto en `reto.py`) + +```python +# CÓDIGO VIEJO A ARREGLAR: +def transferir_dinero(saldo_origen, cantidad, funcion_cobro): + if cantidad > saldo_origen: + return "Saldo insuficiente" + + resultado = funcion_cobro(cantidad) + + if resultado == True: + return "Transferencia exitosa" + else: + return "Error en el banco" +``` + +## Instrucciones Paso a Paso: + +### Fase 1: Calidad Visual (Type Hints y Docstrings) +1. Agrega **Type Hints** a la función `transferir_dinero`. (Pista: `saldo_origen` y `cantidad` son `float`. ¿Qué tipo de dato devuelve la función en sus `return`?). +2. Agrega un **Docstring** debajo de la línea `def` explicando qué hace la función, documentando cada parámetro en `Args:` y qué devuelve en `Returns:`. + +### Fase 2: La Suite de Pruebas Automáticas +3. Escribe `import pytest` arriba de todo tu archivo. +4. Crea un **Fixture** llamado `saldo_rico()` que use `return` para devolver el número decimal `1000.0`. +5. **Prueba Parametrizada de Fallo:** Crea un test llamado `test_fondos_insuficientes` parametrizado. + - Usa `@pytest.mark.parametrize` para probar tres cantidades imposibles de pagar: `1500.0`, `5000.0` y `1000.1`. (El parámetro puede llamarse `"monto_gigante"`). + - El test debe recibir tu fixture `saldo_rico` y el `"monto_gigante"`. + - Crea un "Doble de Acción" vacío adentro del test (`def cobro_falso(monto): return False`) y pásalo a la función. En realidad el código nunca llegará a usarlo porque fallará antes. + - Llama a `transferir_dinero` y usa `assert` para exigir que devuelva `"Saldo insuficiente"`. +6. **Prueba con Mock Exitoso:** Crea un test llamado `test_transferencia_correcta`. + - Debe recibir el fixture `saldo_rico`. + - Crea un Doble de Acción adentro del test que SIEMPRE devuelva `True`. + - Llama a `transferir_dinero` pasándole el `saldo_rico`, un monto de `100.0`, y tu Doble de Acción. + - Usa `assert` para exigir que devuelva `"Transferencia exitosa"`. +7. Ejecuta `pytest reto.py` en tu terminal. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** Todos los del Módulo 5.5 (`def`, `assert`, `import pytest`, `@pytest.fixture`, `@pytest.mark.parametrize`, Mocks pasados por parámetro, Type Hints, Docstrings). +❌ **Conceptos Prohibidos:** Clases o librerías externas que no sean `pytest`. ¡Nada de prints! + +## Resultado Esperado: +Tu archivo `reto.py` ahora tendrá una función bellamente documentada y etiquetada, acompañada de una suite de tests (uno parametrizado que ejecuta 3 pruebas, y uno normal con Mock). + +Al ejecutar `pytest reto.py`, la terminal debería mostrarte todo en verde con un glorioso **"4 passed"**. ¡Felicidades, te has graduado del Módulo de Calidad de Código! diff --git a/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/teoria.md b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/teoria.md new file mode 100644 index 0000000..a491864 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/teoria.md @@ -0,0 +1,27 @@ +# Teoría: El Cinturón Negro de Calidad 🥋 + +¡Llegaste al final del módulo! Ya no escribes código "a ciegas". Has aprendido a crear código profesional, autodocumentado y blindado contra errores. + +Repasemos tu nuevo cinturón de herramientas: + +1. **Type Hints (`: int`, `-> str`)** + Las etiquetas de las cajas. Le dicen al mundo y a tu editor qué tipos de datos entran y qué sale de tus funciones. + +2. **Docstrings (`"""..."""`)** + El manual de instrucciones integrado. Responde la pregunta "¿Para qué sirve esto?" usando las secciones `Args:` y `Returns:`. + +3. **pytest y `assert`** + Tu robot inspector. Usa `assert` para exigirle a tu código que siempre devuelva lo que esperas. Si algo es mentira, pytest levanta la alarma. + +4. **Fixtures (`@pytest.fixture`)** + Tus ayudantes. Funciones especiales que fabrican y te entregan configuraciones o datos listos (como carritos de compras o usuarios falsos) directo a los parámetros de tus tests. + +5. **Parametrize (`@pytest.mark.parametrize`)** + La fotocopiadora de tests. Te permite escribir un solo test y repetirlo mágicamente decenas de veces con diferentes valores, ahorrándote mucho tiempo. + +6. **Mocks (El Doble de Acción)** + La técnica maestra. Cuando una función necesita conectarse a internet o hacer pagos (cosas impredecibles), le pasamos un "Doble de Acción" (una función falsa muy predecible) como parámetro para probar nuestra lógica sin riesgos. + +En una **Suite de Tests** profesional (un archivo lleno de muchas pruebas), usamos todas estas herramientas combinadas. + +En el reto de hoy, te enfrentarás a un código legacy (código viejo y descuidado). Tu deber será aplicar tu cinturón negro para domarlo, documentarlo y blindarlo. ¡A pelear! diff --git a/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/test_main.py b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/test_main.py new file mode 100644 index 0000000..99beeb3 --- /dev/null +++ b/python/Modulo-05.5-Calidad-de-Codigo/6-RETO-suite-tests/test_main.py @@ -0,0 +1,12 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_suite_completa(): + r = subprocess.run( + [sys.executable, "-m", "pytest", "reto.py", "-v"], + capture_output=True, text=True, cwd=BASE, timeout=30) + assert r.returncode == 0, f"Fallaron:\n{r.stdout}\n{r.stderr}" + assert "4 passed" in r.stdout, "Deben pasar exactamente 4 tests" diff --git a/python/Modulo-06-OS/.gitkeep b/python/Modulo-06-OS/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/51-os-rutas/.gitkeep b/python/Modulo-06-OS/51-os-rutas/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/51-os-rutas/README.md b/python/Modulo-06-OS/51-os-rutas/README.md new file mode 100644 index 0000000..e4e4b6a --- /dev/null +++ b/python/Modulo-06-OS/51-os-rutas/README.md @@ -0,0 +1,14 @@ +# Nivel 51: Rutas y el Mapa del Tesoro 🗺️ + +¡Hola, explorador! Hasta ahora hemos escrito programas que viven en su propio mundo de variables, listas y funciones. Pero en el mundo real, los programas necesitan interactuar con tu computadora. Necesitan abrir archivos de Excel, guardar imágenes o buscar documentos. + +Para hacer eso, primero tienen que saber **dónde están parados**. + +En este nivel aprenderemos cómo hacer que Python "mire a su alrededor" usando **rutas** (las direcciones exactas de las carpetas en tu computadora). + +### Tu ruta de aprendizaje hoy: +1. **Teoría**: Entenderemos qué es `os` y cómo Python navega por tu computadora. +2. **Ejemplo**: Haremos que Python nos diga exactamente en qué carpeta está y qué archivos hay a su lado. +3. **Reto**: Construirás un "Inspector de carpetas" para clasificar lo que Python encuentre. + +¡Ponte el sombrero de explorador, que empezamos! diff --git a/python/Modulo-06-OS/51-os-rutas/ejemplo.py b/python/Modulo-06-OS/51-os-rutas/ejemplo.py new file mode 100644 index 0000000..158bbaa --- /dev/null +++ b/python/Modulo-06-OS/51-os-rutas/ejemplo.py @@ -0,0 +1,35 @@ +# --- 1. Preparando nuestro equipo de exploración --- +# La palabra 'import' le dice a Python: "Por favor, trae la caja de herramientas..." +# 'os' es la caja de herramientas del Sistema Operativo. +import os + +print("--- ¿Dónde estoy parado? ---") + +# Usamos el punto '.' para sacar la herramienta 'getcwd' de la caja 'os'. +# Esta herramienta pregunta: "¿Cuál es mi ruta actual?" +# Como es una acción, ponemos los paréntesis () al final. +# Luego, el símbolo '=' guarda la respuesta en nuestra variable. +mi_ubicacion = os.getcwd() + +print("Python dice que ahora mismo estamos en esta dirección:") +print(mi_ubicacion) + + +print("\n--- ¿Qué hay a mi alrededor? ---") + +# Otra herramienta de la caja 'os' es 'listdir' (List Directory = Listar Directorio). +# A esta herramienta le damos a comer nuestra ubicación entre los paréntesis, +# y nos devuelve una LISTA con los nombres de todo lo que hay ahí. +cosas_a_mi_alrededor = os.listdir(mi_ubicacion) + +# ¡Tú ya sabes cómo funcionan las listas! Podemos ver cuántas cosas hay con 'len()' +cantidad_de_cosas = len(cosas_a_mi_alrededor) +print("En esta carpeta hay", cantidad_de_cosas, "archivos o carpetas.") +print("Vamos a verlos uno por uno:\n") + +# Usamos el bucle 'for' que aprendimos en niveles anteriores para recorrer la lista +for cosa in cosas_a_mi_alrededor: + print("- Encontré un objeto llamado:", cosa) + + +print("\n--- ¡Exploración terminada con éxito! ---") diff --git a/python/Modulo-06-OS/51-os-rutas/reto.md b/python/Modulo-06-OS/51-os-rutas/reto.md new file mode 100644 index 0000000..336ab3d --- /dev/null +++ b/python/Modulo-06-OS/51-os-rutas/reto.md @@ -0,0 +1,48 @@ +# Reto 51: El Inspector de Tesoros 🔎 + +Has llegado a una habitación nueva del edificio y necesitas clasificar lo que hay en ella. Tu misión es crear un programa que mire todos los archivos de la carpeta actual y determine cuáles son "tesoros" y cuáles son "basura". + +### Instrucciones paso a paso: + +1. Escribe la palabra mágica para traer las herramientas del sistema operativo. +2. Averigua en qué dirección estás parado y guárdala en una variable llamada `ruta_secreta`. +3. Imprime tu ruta secreta para confirmar dónde estás. +4. Usa la herramienta para ver qué cosas hay a tu alrededor y guárdalas en una lista llamada `inventario`. +5. Crea una función (usando `def`) llamada `inspeccionar_tesoros(lista_de_cosas)` que reciba una lista como parámetro. +6. Dentro de la función, usa un bucle `for` para analizar cada cosa de la lista. +7. **La regla del tesoro**: Usando un condicional (`if`), verifica si la primera letra del nombre del objeto es la letra `"e"` (recuerda que puedes ver la primera letra de un texto usando índices, como `palabra[0] == "e"`). +8. Si empieza con `"e"`, imprime: `"¡Tesoro especial encontrado: [nombre del archivo]!"` +9. Si NO empieza con `"e"`, imprime: `"Solo es basura: [nombre del archivo]"` +10. Finalmente, llama a tu función pasándole tu lista `inventario` como parámetro. + +--- + +### 🟢 Conceptos Permitidos (Lo único que puedes usar) +* `import os` +* `os.getcwd()` +* `os.listdir()` +* Asignación de variables (`=`) +* Listas y bucles (`for elemento in lista:`) +* Funciones (`def nombre_funcion(parametro):`) +* Condicionales (`if` / `else`) +* Índices para sacar letras (`texto[0]`) + +### 🔴 Prohibido +* Copiar y pegar código de internet. +* Usar módulos que no sean `os`. +* Usar herramientas que no hemos visto como list comprehensions o conceptos avanzados de rutas (`os.path`, `pathlib`). + +--- + +### 🎯 Resultado esperado en la terminal +*(Nota: El orden exacto de los archivos puede variar según tu computadora, pero el formato debe verse así)* + +```text +Mi ruta secreta es: /ruta/hacia/tu/carpeta/51-os-rutas +Solo es basura: teoria.md +¡Tesoro especial encontrado: ejemplo.py! +Solo es basura: README.md +Solo es basura: reto.md +``` + +¡Mucha suerte, inspector! Recuerda dar pasitos pequeños y probar tu código constantemente. diff --git a/python/Modulo-06-OS/51-os-rutas/teoria.md b/python/Modulo-06-OS/51-os-rutas/teoria.md new file mode 100644 index 0000000..1d0e19e --- /dev/null +++ b/python/Modulo-06-OS/51-os-rutas/teoria.md @@ -0,0 +1,54 @@ +# La Caja de Herramientas del Sistema + +Imagina que tu computadora es un edificio gigante. Tiene miles de habitaciones (que llamamos **carpetas** o **directorios**) y dentro de ellas hay cajas (que llamamos **archivos**). + +Cuando ejecutas un código en Python, el programa "despierta" en una de esas habitaciones. Pero al principio, está a oscuras. No sabe dónde está. + +Para que Python pueda interactuar con el edificio (saber dónde está parado o abrir otras cajas), necesitamos darle una caja de herramientas especial. Esa caja se llama **`os`** (del inglés *Operating System*, Sistema Operativo). + +--- + +### Anatomía de los nuevos símbolos + +Para usar herramientas externas que no vienen por defecto en el lenguaje base, usamos la palabra reservada `import`: + +```python +import os +``` +* **`import`**: Es una palabra mágica que significa "Trae a este archivo todas las herramientas de...". +* **`os`**: Es el nombre de la caja de herramientas. + +Una vez que trajimos la caja, podemos usar sus herramientas. Para acceder a ellas usamos un símbolo que ya conoces de cuando trabajamos con listas (`lista.append()`): **el punto (`.`)**. + +```python +ruta = os.getcwd() +``` +* **`os`**: La caja de herramientas. +* **`.`**: Significa "de esta caja, saca la siguiente herramienta". +* **`getcwd`**: Es el nombre de la herramienta. Son las siglas de *Get Current Working Directory* (Obtener Directorio de Trabajo Actual). En español simple: "Dime en qué habitación estoy ahora mismo". +* **`()`**: Como `getcwd` es una función (una acción que Python debe ejecutar), siempre debe llevar paréntesis al final. +* **`=`**: (Asignación o guardado). Tomamos la respuesta que nos da la función a la derecha, y la guardamos en la variable a la izquierda. + +--- + +### ¿Qué es una ruta (path)? + +Una ruta es simplemente una dirección en formato de texto. +Por ejemplo: `/Usuarios/Alejandro/Documentos/codigo`. Es como decir: "Entra al edificio, ve al piso de Usuarios, entra al cuarto de Alejandro, luego abre Documentos y finalmente entra en codigo". + +--- + +### ⚠️ ¿Qué pasa si me equivoco? + +**El error de olvidar la caja de herramientas** +Si escribes `os.getcwd()` sin haber escrito `import os` hasta arriba de tu archivo, verás este error rojo en la terminal: + +```text +NameError: name 'os' is not defined +``` + +**¿Qué significa esto en lenguaje humano?** +Python te está diciendo: *"Oye, me pides que busque una herramienta dentro de una caja llamada `os`, pero no tengo idea de qué es `os`. ¡Nunca me dijiste que la trajera!"*. + +**¿Cómo lo soluciono?** +Asegúrate de que la primera línea de tu código sea siempre `import os` antes de intentar usar cualquier cosa que empiece con `os.`. diff --git a/python/Modulo-06-OS/51-os-rutas/test_main.py b/python/Modulo-06-OS/51-os-rutas/test_main.py new file mode 100644 index 0000000..d9cb6a6 --- /dev/null +++ b/python/Modulo-06-OS/51-os-rutas/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_imprime_rutas(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "/" in r.stdout or "\\" in r.stdout, "Debe imprimir una ruta del sistema" diff --git a/python/Modulo-06-OS/52-txt-read/.gitkeep b/python/Modulo-06-OS/52-txt-read/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/52-txt-read/README.md b/python/Modulo-06-OS/52-txt-read/README.md new file mode 100644 index 0000000..3d87cbb --- /dev/null +++ b/python/Modulo-06-OS/52-txt-read/README.md @@ -0,0 +1,14 @@ +# Nivel 52: Leyendo Cartas Secretas (Archivos TXT) ✉️ + +¡Hola, explorador! En el nivel anterior aprendimos cómo hacer que Python "viera" las cajas (archivos) a su alrededor usando rutas. + +Pero ver el nombre de una caja no es tan emocionante como **saber qué hay adentro**. En el mundo real, los programas necesitan leer datos todo el tiempo: configuraciones, bases de datos, contraseñas o el texto de un libro. + +En este nivel aprenderemos cómo hacer que Python abra un archivo de texto (`.txt`), lea todo su contenido como si fuera una carta, y luego cierre la caja para no hacer un desastre. + +### Tu ruta de aprendizaje hoy: +1. **Teoría**: Entenderemos cómo abrir un sobre, sacar la carta (`read`) y asegurar que el sobre se cierre automáticamente. +2. **Ejemplo**: Haremos que Python lea un mensaje secreto de un archivo de texto. +3. **Reto**: Construirás un "Lector de Diarios" para procesar y contar las letras de un archivo. + +¡Abre bien los ojos, que vamos a leer! diff --git a/python/Modulo-06-OS/52-txt-read/ejemplo.py b/python/Modulo-06-OS/52-txt-read/ejemplo.py new file mode 100644 index 0000000..c54ff8d --- /dev/null +++ b/python/Modulo-06-OS/52-txt-read/ejemplo.py @@ -0,0 +1,29 @@ +# --- 1. Preparación --- +# ¡ATENCIÓN! +# Antes de correr este código, asegúrate de crear un archivo llamado "secreto.txt" +# en esta misma carpeta y escríbele algún texto misterioso adentro. + +print("--- Intentando abrir el sobre secreto ---") + +# 'with' es nuestro guardián. Cuidará que el archivo se cierre al final. +# 'open' abre el archivo "secreto.txt". +# La "r" significa que solo queremos Leer (Read). +# Le damos el apodo de 'sobre_abierto' para usarlo abajo. +with open("secreto.txt", "r") as sobre_abierto: + + print("¡Logré abrir el sobre!") + print("Extrayendo la carta...\n") + + # Usamos la herramienta '.read()' de nuestro sobre abierto. + # Eso saca TODO el texto y lo guardamos en la variable 'carta' + carta = sobre_abierto.read() + + print("--- Contenido de la carta ---") + + # Ahora que lo tenemos en una variable, ¡podemos imprimirlo! + print(carta) + print("-----------------------------") + +# ¿Notas que ya no hay espacios a la izquierda (indentación)? +# Al regresar a este nivel, 'with' automáticamente cerró el archivo por nosotros. +print("\n--- ¡Misión cumplida! El archivo ha sido cerrado de forma segura. ---") diff --git a/python/Modulo-06-OS/52-txt-read/reto.md b/python/Modulo-06-OS/52-txt-read/reto.md new file mode 100644 index 0000000..bd66f05 --- /dev/null +++ b/python/Modulo-06-OS/52-txt-read/reto.md @@ -0,0 +1,45 @@ +# Reto 52: El Lector de Diarios 📖 + +Tu amigo te dejó su diario guardado en un archivo de texto, pero le da pereza contar cuántas letras ha escrito. Como eres un programador en entrenamiento, vas a automatizar esa tarea. + +### Instrucciones paso a paso: + +1. Crea un archivo llamado `diario.txt` en la misma carpeta que tu código. Escribe adentro 3 líneas de texto contándome cómo estuvo tu día. ¡Guárdalo! +2. Ahora, en tu archivo de Python, crea una función (con `def`) llamada `leer_diario(nombre_archivo)` que reciba el nombre de un archivo como parámetro. +3. Dentro de la función, usa el bloque mágico `with open(...)` para abrir el archivo que recibiste como parámetro. Recuerda usar el modo lectura (`"r"`). +4. Adentro del bloque `with`, lee el contenido completo usando `.read()` y guárdalo en una variable llamada `texto`. +5. Imprime el mensaje: `"Mi diario dice:"` seguido del contenido de la variable `texto`. +6. **(Bono de niveles pasados)**: Usa la función `len()` con tu variable `texto` para saber cuántas letras (caracteres) tiene, y guárdalo en una variable llamada `cantidad`. +7. Imprime el mensaje: `"El diario tiene [cantidad] caracteres en total."` +8. Afuera de la función, llámala pasándole el texto exacto `"diario.txt"`. + +--- + +### 🟢 Conceptos Permitidos (Lo único que puedes usar) +* Asignación de variables (`=`) +* Funciones (`def nombre(parametro):`) +* Abrir archivos (`with open(archivo, "r") as apodo:`) +* Leer texto (`apodo.read()`) +* Contar elementos (`len()`) +* Imprimir texto (`print()`) + +### 🔴 Prohibido +* Copiar y pegar código de internet. +* Usar `import os` (no lo necesitamos hoy, el archivo está aquí mismo). +* Usar herramientas avanzadas como `.readlines()` o bucles `for` para recorrer el archivo línea por línea (hoy leemos todo de un solo golpe con `.read()`). + +--- + +### 🎯 Resultado esperado en la terminal +*(Nota: El texto exacto de tu diario y la cantidad de caracteres dependerán de lo que hayas escrito en `diario.txt`, pero el formato debe ser igual a este)* + +```text +Mi diario dice: +Hoy aprendí a leer archivos en Python. +Fue un buen día. +Espero no olvidar el 'with'. + +El diario tiene 87 caracteres en total. +``` + +¡Demuestra que puedes leer cualquier secreto! Recuerda el error de la caja fantasma si Python te grita. diff --git a/python/Modulo-06-OS/52-txt-read/teoria.md b/python/Modulo-06-OS/52-txt-read/teoria.md new file mode 100644 index 0000000..b94a950 --- /dev/null +++ b/python/Modulo-06-OS/52-txt-read/teoria.md @@ -0,0 +1,46 @@ +# Abriendo y Leyendo Archivos + +Imagina que un archivo de texto es como un sobre cerrado que contiene una carta. Para leer la carta, Python tiene que hacer un ritual de 3 pasos: + +1. **Abrir el sobre** con cuidado. +2. **Sacar la carta** y leer el texto. +3. **Cerrar el sobre** y guardarlo para que la carta no se pierda ni se arruine con el viento. + +Afortunadamente, Python nos da una herramienta mágica llamada `with` que se encarga de abrir y cerrar el sobre automáticamente, para que nosotros solo nos preocupemos de leer. + +--- + +### Anatomía de los nuevos símbolos + +Este es el bloque de código estándar para leer un archivo: + +```python +with open("mensaje.txt", "r") as archivo: + contenido = archivo.read() +``` + +* **`with`**: Es la palabra mágica (el guardián). Significa: *"Mientras mantengas abierta esta puerta, haz lo que dice abajo. Cuando termines (cuando se acabe la indentación), CÍERRALA por mí."* Nos protege de olvidar cerrar el archivo. +* **`open`**: Es la herramienta que abre la caja o archivo. +* **`"mensaje.txt"`**: El primer ingrediente (parámetro) de `open`. Es el nombre exacto del archivo que queremos abrir. Tiene que estar entre comillas porque es texto. +* **`"r"`**: El segundo ingrediente. Significa **"Read"** (Leer en inglés). Le estamos diciendo a Python: *"Solo quiero mirar, prometo no borrar ni escribir nada nuevo"*. +* **`as archivo`**: Significa **"y llámalo así"**. Le estamos poniendo el apodo `archivo` a nuestro sobre abierto para poder referirnos a él en las siguientes líneas. +* **`:`**: (Dos puntos). Al igual que en los `if` o bucles `for`, significa *"lo que viene a continuación con sangría (indentación) es lo que voy a hacer mientras esté abierto"*. +* **`archivo.read()`**: Tomamos nuestro sobre abierto (`archivo`), usamos el punto (`.`) para decirle qué hacer, y usamos la herramienta `read()` para sacar TODO el texto que tenga adentro. +* **`=`**: (Asignación). Guardamos todo ese gran bloque de texto que leímos en nuestra variable `contenido`. + +--- + +### ⚠️ ¿Qué pasa si me equivoco? + +**El error de la caja fantasma** +Si le pides a Python que abra un archivo que no existe en esa habitación (carpeta), o si escribiste mal el nombre, verás este error en la terminal: + +```text +FileNotFoundError: [Errno 2] No such file or directory: 'mensaje.txt' +``` + +**¿Qué significa esto en lenguaje humano?** +Python te está diciendo: *"Fui a buscar el sobre llamado 'mensaje.txt' que me pediste, miré por toda la habitación, ¡pero no existe! ¿Estás seguro de que lo creaste o de que lo escribiste bien?"* + +**¿Cómo lo soluciono?** +Revisa que el archivo de texto exista exactamente en la misma carpeta donde está guardado tu código de Python. Revisa también que no le falte la extensión `.txt` en tu código (ej. `"mensaje"` vs `"mensaje.txt"`). diff --git a/python/Modulo-06-OS/52-txt-read/test_main.py b/python/Modulo-06-OS/52-txt-read/test_main.py new file mode 100644 index 0000000..4d4cbcb --- /dev/null +++ b/python/Modulo-06-OS/52-txt-read/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_lee_archivo(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert len(r.stdout.strip()) > 0 diff --git a/python/Modulo-06-OS/53-txt-write/.gitkeep b/python/Modulo-06-OS/53-txt-write/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/53-txt-write/README.md b/python/Modulo-06-OS/53-txt-write/README.md new file mode 100644 index 0000000..089935c --- /dev/null +++ b/python/Modulo-06-OS/53-txt-write/README.md @@ -0,0 +1,14 @@ +# Nivel 53: Escribiendo Tu Propia Historia (Archivos TXT) ✍️ + +¡Hola, creador! Ya sabes cómo hacer que Python lea los secretos que estaban guardados en un archivo. Pero, ¿qué pasa si queremos que nuestro programa guarde sus propios datos? + +Imagina un videojuego que necesita guardar tu puntuación más alta para cuando vuelvas a jugar mañana. O un programa que guarda un registro de todas las tareas que has completado. Para que los datos sobrevivan cuando apagas la computadora, necesitamos **escribirlos en un archivo**. + +En este nivel, aprenderemos a crear archivos de texto desde cero y a escribir dentro de ellos usando Python. + +### Tu ruta de aprendizaje hoy: +1. **Teoría**: Aprenderemos sobre los modos de escritura (`"w"` y `"a"`) y cómo darle "Enter" al texto invisiblemente. +2. **Ejemplo**: Haremos que Python cree su propio diario y agregue líneas sin borrar lo anterior. +3. **Reto**: Construirás un "Registro de Asistencia" para guardar listas de invitados. + +¡Saca tu pluma digital, que vamos a escribir! diff --git a/python/Modulo-06-OS/53-txt-write/ejemplo.py b/python/Modulo-06-OS/53-txt-write/ejemplo.py new file mode 100644 index 0000000..5cc202d --- /dev/null +++ b/python/Modulo-06-OS/53-txt-write/ejemplo.py @@ -0,0 +1,36 @@ +# --- 1. Escribiendo desde cero (Modo "w") --- +print("--- Creando nuestro primer archivo ---") + +# 'with' es el guardián. +# 'open' intentará abrir "nuevo_secreto.txt". Como no existe, ¡lo creará por ti! +# La "w" significa Write (Escribir). ¡Cuidado, borra todo si ya existía! +with open("nuevo_secreto.txt", "w") as archivo_nuevo: + + print("El archivo está abierto y el lápiz está listo.") + + # Usamos .write() para poner texto. + archivo_nuevo.write("Esta es la primera linea del archivo.\n") + + # Recuerda poner \n al final de tu texto si quieres que la + # siguiente frase vaya en el renglón de abajo. + archivo_nuevo.write("Esta es la segunda linea. ¡Mira qué bonito!\n") + +# Al quitar la sangría (indentación), 'with' guarda y cierra el archivo. +print("Python guardó el archivo y cerró la caja.\n") + + +# --- 2. Agregando texto al final (Modo "a") --- +print("--- Agregando sin borrar ---") + +# Ahora usamos la letra "a" de Append (Agregar o Anexar). +# Esto abrirá el mismo archivo que acabamos de crear, pero nos +# pondrá el lápiz al final de todo el texto que ya tiene. +with open("nuevo_secreto.txt", "a") as archivo_existente: + + print("Abriendo el archivo en modo agregar...") + + # Si olvidamos el \n, esto se pegaría justo después del signo '!' de arriba. + archivo_existente.write("¡P.D: Acabo de agregar esta línea al final!\n") + +print("--- ¡Revisa la carpeta de tu computadora! ---") +print("Deberías ver un archivo llamado 'nuevo_secreto.txt'. Ábrelo y mira adentro.") diff --git a/python/Modulo-06-OS/53-txt-write/reto.md b/python/Modulo-06-OS/53-txt-write/reto.md new file mode 100644 index 0000000..fa759ec --- /dev/null +++ b/python/Modulo-06-OS/53-txt-write/reto.md @@ -0,0 +1,46 @@ +# Reto 53: El Registro de Asistencia 📝 + +Estás organizando la fiesta más grande del año. Cada vez que llega un invitado, necesitas anotar su nombre en una lista oficial de asistencia en un archivo de texto. ¡Vamos a automatizar esto! + +### Instrucciones paso a paso: + +1. Crea una variable llamada `invitados` que contenga una lista (con corchetes `[]`) con los nombres de 3 amigos tuyos. +2. Crea una función (con `def`) llamada `registrar_invitados(lista)` que reciba esa lista de amigos como parámetro. +3. Dentro de la función, usa el guardián `with open(...)` para abrir un archivo llamado `"asistencia.txt"`. Ábrelo en el modo para escribir desde cero (modo `"w"`). +4. Dentro del bloque `with`, usa `.write()` para escribir el título `"--- LISTA DE INVITADOS ---\n"`. ¡No olvides el salto de línea `\n`! +5. Crea un bucle `for` para recorrer cada amigo dentro de tu lista recibida. +6. Adentro del bucle, por cada amigo, usa `.write()` para escribir su nombre en el archivo seguido de un salto de línea (`nombre + "\n"`). +7. Afuera de la función, llámala pasándole tu variable `invitados`. +8. Corre tu código. +9. **(Opcional para curiosos)**: Abre tu código, cambia la letra `"w"` por `"a"`. Vuelve a correr el código. ¡Abre el archivo `asistencia.txt` en tu bloc de notas y observa cómo todos los invitados se duplicaron al final! + +--- + +### 🟢 Conceptos Permitidos (Lo único que puedes usar) +* Asignación de variables (`=`) +* Listas y bucles (`for elemento in lista:`) +* Funciones (`def nombre_funcion(parametro):`) +* Escribir archivos (`with open(archivo, "w") as apodo:` o `"a"`) +* Escribir texto (`apodo.write()`) +* Salto de línea (`\n`) +* Sumar textos (`"Hola " + nombre`) + +### 🔴 Prohibido +* Usar el modo lectura (`"r"`). +* Abrir el archivo usando `open()` sin usar el guardián `with`. +* Escribir los nombres uno por uno de forma manual (¡usa tu bucle `for`!). +* Olvidar el salto de línea `\n`. + +--- + +### 🎯 Resultado esperado +*(No verás nada impreso en la terminal. El resultado estará dentro del archivo "asistencia.txt" que tu código creará automáticamente. Debería verse así si lo abres con tu bloc de notas:)* + +```text +--- LISTA DE INVITADOS --- +Ana +Beto +Carlos +``` + +¡Demuestra que puedes controlar los registros de la fiesta! diff --git a/python/Modulo-06-OS/53-txt-write/teoria.md b/python/Modulo-06-OS/53-txt-write/teoria.md new file mode 100644 index 0000000..620fde5 --- /dev/null +++ b/python/Modulo-06-OS/53-txt-write/teoria.md @@ -0,0 +1,41 @@ +# Escribir y Agregar Texto + +Imagina que un archivo de texto es un cuaderno de notas. +Cuando leímos en el nivel anterior, usamos la letra `"r"` (Read) que nos permitía mirar el cuaderno pero sin tocar un lápiz. + +Para escribir, tenemos dos letras nuevas, y actúan de formas muy distintas: +1. **Modo `"w"` (Write/Escribir)**: Es como arrancar TODAS las páginas de tu cuaderno, tirarlas a la basura, y empezar a escribir en la primera página totalmente en blanco. **¡Destruye lo que había antes!** Si el cuaderno no existía, Python va a la papelería y te compra uno nuevo (crea el archivo). +2. **Modo `"a"` (Append/Agregar)**: Es como buscar la última página escrita de tu cuaderno y continuar escribiendo justo debajo, sin borrar absolutamente nada del pasado. + +--- + +### Anatomía de los nuevos símbolos + +Esta es la sintaxis para crear y escribir un archivo: + +```python +with open("mi_diario.txt", "w") as archivo: + archivo.write("¡Hola, mundo!\n") +``` + +* **`"w"`**: Le dice a nuestro guardián (`with`) cómo quieres abrir el archivo. Si pones `"w"`, estás listo para destruir el pasado y escribir algo nuevo. Si pones `"a"`, estás listo para agregar al final. +* **`.write()`**: Es la herramienta (acción) para escribir texto dentro del archivo. A diferencia de `print()`, `.write()` **NO** salta a la siguiente línea automáticamente. Escribe todo seguido como un tren. +* **`\n` (Salto de línea)**: Es un símbolo invisible para los humanos pero muy claro para Python. La barra invertida `\` y la `n` (de *newline*) le dicen a la computadora: *"Aquí presiona la tecla Enter del teclado"*. Sin esto, todas tus frases quedarían pegadas en un solo renglón larguísimo. + +--- + +### ⚠️ ¿Qué pasa si me equivoco? + +**El error del tipo equivocado** +Imagina que quieres guardar tu puntuación (un número puro, como `100`), y haces esto: `archivo.write(100)`. Python te dará este error: + +```text +TypeError: write() argument must be str, not int +``` + +**¿Qué significa esto en lenguaje humano?** +Python te dice: *"La herramienta `.write()` solo usa tinta (texto, que llamamos `str`). ¡Me estás pidiendo que meta un concepto matemático puro (un `int`) en un cuaderno de texto!"* + +**¿Cómo lo soluciono?** +Simplemente disfraza tu número de texto convirtiéndolo con la función `str()`. Así: +`archivo.write( str(100) )` o combinándolo con un texto `archivo.write("Puntos: " + str(100))` diff --git a/python/Modulo-06-OS/53-txt-write/test_main.py b/python/Modulo-06-OS/53-txt-write/test_main.py new file mode 100644 index 0000000..9c38be1 --- /dev/null +++ b/python/Modulo-06-OS/53-txt-write/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_crea_diario(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, cwd=BASE, timeout=15) + assert r.returncode == 0, r.stderr + assert os.path.exists(os.path.join(BASE, "diario.txt")), "Debe crear el archivo diario.txt" diff --git a/python/Modulo-06-OS/54-csv/.gitkeep b/python/Modulo-06-OS/54-csv/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/54-csv/README.md b/python/Modulo-06-OS/54-csv/README.md new file mode 100644 index 0000000..dce94ea --- /dev/null +++ b/python/Modulo-06-OS/54-csv/README.md @@ -0,0 +1,16 @@ +# Nivel 54: Tablas de Datos (Archivos CSV) 📊 + +¡Hola de nuevo, programador! Hasta ahora hemos guardado texto libre, como un diario o una simple lista hacia abajo. + +Pero en el mundo corporativo, científico y de los videojuegos, la información casi siempre está organizada en **tablas** con filas y columnas (piensa en una hoja de Excel). Guardar información estructurada es vital para analizar datos de clientes, listas de precios o el inventario de un juego. + +El formato universal para guardar tablas se llama **CSV** (del inglés *Comma Separated Values*, o Valores Separados por Comas). ¡Incluso Excel usa archivos CSV! + +En este nivel aprenderemos a usar una nueva caja de herramientas para leer y escribir estos archivos sin volvernos locos cortando textos. + +### Tu ruta de aprendizaje hoy: +1. **Teoría**: Entenderemos qué hace la herramienta `csv` y cómo transforma texto en listas perfectas. +2. **Ejemplo**: Haremos que Python cree una pequeña base de datos de un supermercado. +3. **Reto**: Te convertirás en un analista de datos creando y leyendo tu propio archivo de películas. + +¡Prepara tus columnas, que empezamos! diff --git a/python/Modulo-06-OS/54-csv/ejemplo.py b/python/Modulo-06-OS/54-csv/ejemplo.py new file mode 100644 index 0000000..bb4f939 --- /dev/null +++ b/python/Modulo-06-OS/54-csv/ejemplo.py @@ -0,0 +1,39 @@ +# --- 1. Preparando nuestras herramientas --- +import csv + +print("--- Escribiendo una nueva tabla ---") + +# Abrimos nuestro archivo en modo "w" (Escribir). +# Ponemos newline="" para evitar que aparezcan renglones vacíos extra en nuestra tabla. +with open("supermercado.csv", "w", newline="") as archivo_nuevo: + + # Creamos a nuestro "trabajador" que sabe cómo escribir formatos CSV + escritor = csv.writer(archivo_nuevo) + + # Le decimos que escriba la primera fila (estos serán los encabezados de las columnas) + # ¡Nota muy importante: Le pasamos una LISTA (con los corchetes)! + escritor.writerow(["Producto", "Precio", "Cantidad"]) + + # Ahora agregamos un par de filas de datos + escritor.writerow(["Manzanas", 15, 3]) + escritor.writerow(["Leche", 20, 1]) + +print("¡Tabla de supermercado creada con éxito y guardada en tu computadora!\n") + +# --- 2. Leyendo la tabla que acabamos de crear --- +print("--- Extrayendo datos de la tabla ---") + +# Ahora abrimos el MISMO archivo pero en modo lectura "r" +with open("supermercado.csv", "r") as archivo_existente: + + # Creamos a nuestro "trabajador" que sabe cómo leer formatos CSV + lector = csv.reader(archivo_existente) + + # El lector tiene todas las filas atrapadas adentro. + # Usaremos un bucle 'for' para que nos dé una por una. + for fila in lector: + print("Acabo de leer esta fila:", fila) + # Observa en tu terminal: cada fila ya no es texto simple, + # ¡Python la convirtió en una Lista lista para que la usemos! + +print("\n--- ¡Magia de las tablas completada! ---") diff --git a/python/Modulo-06-OS/54-csv/reto.md b/python/Modulo-06-OS/54-csv/reto.md new file mode 100644 index 0000000..cf26776 --- /dev/null +++ b/python/Modulo-06-OS/54-csv/reto.md @@ -0,0 +1,50 @@ +# Reto 54: El Analista de Datos 📈 + +¡Felicidades, conseguiste tu primer empleo en un cine! Tu jefe te pide que guardes un registro de las mejores películas y su calificación, y luego necesitas poder leerlo de vuelta para comprobar que no hubo errores. + +### Instrucciones paso a paso: + +1. Importa la caja de herramientas correcta en la primera línea. +2. Crea una variable llamada `peliculas` que contenga una **lista de listas**. Adentro debe tener 3 listas más pequeñas, cada una con el nombre de una película y su calificación (un número). + *(Ejemplo: `[["Shrek", 10], ["Titanic", 8], ["Matrix", 9]]`)*. +3. Crea una función (con `def`) llamada `guardar_peliculas(lista_peliculas)` que reciba esa lista. +4. Dentro de la función, abre un archivo `"mis_peliculas.csv"` en modo `"w"`. ¡No olvides poner `newline=""`! +5. Crea a tu trabajador experto (`csv.writer()`). +6. Usa `.writerow()` para escribir primero los encabezados: `["Titulo", "Calificacion"]`. +7. Usa un bucle `for` para recorrer tu lista `lista_peliculas`, y dentro del bucle usa `.writerow()` para guardar cada película en el archivo. +8. Afuera de la función, llámala pasándole tu lista `peliculas` original. +9. Ahora, crea **otra** función llamada `leer_peliculas()` que no reciba ningún parámetro. +10. Adentro de esta nueva función, abre tu archivo `"mis_peliculas.csv"` en modo lectura `"r"`. +11. Crea a tu trabajador experto (`csv.reader()`). +12. Usa un bucle `for` para imprimir cada fila del archivo. +13. Llama a tu segunda función para verificar que los datos se guardaron y leyeron bien. + +--- + +### 🟢 Conceptos Permitidos (Lo único que puedes usar) +* `import csv` +* Trabajadores (`csv.reader(archivo)` y `csv.writer(archivo)`) +* Escribir fila (`.writerow(lista)`) +* Apertura de archivos (`with open(...)`) y parámetro `newline=""` +* Listas y bucles (`for fila in tabla:`) +* Funciones (`def`) y llamadas a funciones. +* Imprimir a la terminal (`print()`) + +### 🔴 Prohibido +* Usar `.read()` crudo (hoy queremos usar el lector de tablas). +* Usar librerías externas avanzadas como `pandas`. +* Copiar y pegar código de internet. + +--- + +### 🎯 Resultado esperado en la terminal +*(Nota: Cuando Python lee de vuelta un CSV, convierte todo a texto. Es normal que tus números de calificación salgan con comillas `'10'` en la terminal, porque para el archivo de texto, ¡todo es texto!)* + +```text +['Titulo', 'Calificacion'] +['Shrek', '10'] +['Titanic', '8'] +['Matrix', '9'] +``` + +¡Demuestra que la magia de los datos no tiene secretos para ti! diff --git a/python/Modulo-06-OS/54-csv/teoria.md b/python/Modulo-06-OS/54-csv/teoria.md new file mode 100644 index 0000000..7a1a4c6 --- /dev/null +++ b/python/Modulo-06-OS/54-csv/teoria.md @@ -0,0 +1,51 @@ +# El Módulo `csv` + +Imagina una hoja de cálculo. Un archivo CSV es exactamente eso, pero "desnudo": es un simple archivo de texto donde cada renglón es una fila de la tabla, y cada columna está separada por una coma (`,`). + +Podríamos leer un CSV usando el `.read()` que aprendimos antes, pero tendríamos que pelear buscando las comas y partiendo el texto a mano. ¡Qué pereza! Mejor traemos una nueva caja de herramientas: el módulo `csv`. + +--- + +### Anatomía de los nuevos símbolos + +Para usar estas herramientas mágicas, primero debemos hacer `import csv`. + +#### 1. Sintaxis para Leer Tablas +```python +import csv + +with open("empleados.csv", "r") as archivo: + lector = csv.reader(archivo) + for fila in lector: + print(fila) +``` +* **`import csv`**: Trae la caja de herramientas para manejar tablas. +* **`csv.reader(archivo)`**: Es nuestro trabajador experto. Toma tu archivo recién abierto y lo convierte en un "Lector". Este lector sabe mágicamente dónde están las comas y los renglones. +* **`for fila in lector:`**: Como una tabla tiene muchas filas, usamos nuestro confiable bucle `for` para recorrerlas. Cada vez que da una vuelta, la variable `fila` se convierte en una **Lista de Python** con los datos de esa fila. ¡Súper fácil de usar! + +#### 2. Sintaxis para Escribir Tablas +```python +import csv + +with open("ventas.csv", "w", newline="") as archivo: + escritor = csv.writer(archivo) + escritor.writerow(["Manzanas", 10]) +``` +* **`newline=""`**: Es un ajuste de seguridad para el guardián `with`. Si no lo ponemos, algunas computadoras (como Windows) se confunden y agregan renglones vacíos extra entre cada fila de tu tabla. Le dice a Python: *"No agregues saltos de línea extra por tu cuenta"*. +* **`csv.writer(archivo)`**: Es nuestro trabajador experto en escritura. Transforma nuestras listas de Python y les pone las comas en su lugar antes de guardarlas. +* **`.writerow()`**: Significa *Write Row* (Escribir Fila). Toma una lista de Python y la guarda como un renglón en la tabla. + +--- + +### ⚠️ ¿Qué pasa si me equivoco? + +**El error del texto desmembrado** +Si al usar `.writerow()` olvidas poner corchetes de lista `[]` e intentas escribir un texto libre así: +`escritor.writerow("Hola")` + +Al abrir tu tabla verás que Python escribió esto: +`H,o,l,a` + +**¿Por qué pasa esto?** +El escritor (`csv.writer`) **siempre** espera recibir una caja (una lista de columnas). Si le das una simple palabra, cree que cada letra es una columna distinta y las separa con comas. +**Solución**: ¡Asegúrate siempre de pasarle listas! Correcto: `escritor.writerow(["Hola"])`. diff --git a/python/Modulo-06-OS/54-csv/test_main.py b/python/Modulo-06-OS/54-csv/test_main.py new file mode 100644 index 0000000..65719ec --- /dev/null +++ b/python/Modulo-06-OS/54-csv/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_csv(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, cwd=BASE, timeout=15) + assert r.returncode == 0, r.stderr + assert len(r.stdout.strip()) > 0 diff --git a/python/Modulo-06-OS/55-shutil/.gitkeep b/python/Modulo-06-OS/55-shutil/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/55-shutil/README.md b/python/Modulo-06-OS/55-shutil/README.md new file mode 100644 index 0000000..64f46d2 --- /dev/null +++ b/python/Modulo-06-OS/55-shutil/README.md @@ -0,0 +1,14 @@ +# Nivel 55: El Camión de Mudanzas (shutil) 🚚 + +¡Hola de nuevo, explorador! En los niveles pasados fuimos capaces de mirar alrededor (con `os`), y de abrir cajas para leer o escribir información. + +Pero en la vida real, los administradores de sistemas y programadores necesitan hacer **copias de seguridad** de los archivos importantes por si algo sale mal, o necesitan **mover** las cosas de lugar y cambiarles el nombre para organizar su información. + +En este nivel conoceremos a los verdaderos "trabajadores pesados" de Python. Aprenderemos a clonar archivos y a mudarlos (o renombrarlos) usando la herramienta `shutil`. + +### Tu ruta de aprendizaje hoy: +1. **Teoría**: Entenderemos cómo funciona el camión de mudanzas de Python. +2. **Ejemplo**: Crearemos un archivo, lo clonaremos y luego esconderemos el clon cambiándole el nombre. +3. **Reto**: Te convertirás en un "Falsificador de Arte" clonando archivos automáticamente. + +¡Ponte el cinturón, que arrancamos el camión! diff --git a/python/Modulo-06-OS/55-shutil/ejemplo.py b/python/Modulo-06-OS/55-shutil/ejemplo.py new file mode 100644 index 0000000..d474285 --- /dev/null +++ b/python/Modulo-06-OS/55-shutil/ejemplo.py @@ -0,0 +1,33 @@ +# --- 1. Preparando la mudanza --- +# Traemos la herramienta de mudanzas pesadas +import shutil + +print("--- Creando el archivo original ---") +# Usamos lo que ya sabemos para crear un archivo real que podamos mover +with open("original.txt", "w") as archivo: + archivo.write("Este es un tesoro muy valioso. ¡Cuidalo!\n") + +print("Archivo 'original.txt' creado con éxito.") + + +# --- 2. Haciendo una copia de seguridad --- +print("\n--- Clonando el tesoro ---") + +# Usamos shutil.copy(). Le damos el nombre original, y el nombre de la copia. +shutil.copy("original.txt", "respaldo.txt") + +print("¡Listo! Ahora tenemos un clon exacto llamado 'respaldo.txt'.") +print("Si miras tu carpeta, ¡habrá dos archivos idénticos!") + + +# --- 3. Moviendo / Renombrando --- +print("\n--- Renombrando el clon ---") + +# Usamos shutil.move(). Tomamos la copia y la escondemos con otro nombre. +# Como no lo estamos mandando a otra carpeta distinta, +# la computadora simplemente le cambiará el nombre. +shutil.move("respaldo.txt", "super_secreto.txt") + +print("¡Magia! 'respaldo.txt' ha desaparecido y ahora se llama 'super_secreto.txt'.") + +print("\n--- ¡Operativo de mudanza completado! ---") diff --git a/python/Modulo-06-OS/55-shutil/reto.md b/python/Modulo-06-OS/55-shutil/reto.md new file mode 100644 index 0000000..9798730 --- /dev/null +++ b/python/Modulo-06-OS/55-shutil/reto.md @@ -0,0 +1,40 @@ +# Reto 55: El Falsificador de Arte 🎨 + +Eres un espía internacional infiltrado en un museo. Tu objetivo es hacer una copia exacta de la pintura original de la Mona Lisa para dejarla en su lugar, ¡y luego esconder la original cambiándole el nombre! + +### Instrucciones paso a paso: + +1. Importa la caja de herramientas de mudanzas (`shutil`). +2. Crea una función (con `def`) llamada `falsificar_arte(nombre_original, nombre_falso)` que reciba esos dos parámetros de texto. +3. Dentro de la función, usa tu herramienta de clonación (`.copy()`) para hacer una copia del archivo `nombre_original` y que se guarde con el nombre `nombre_falso`. +4. Ahí mismo, dentro de la función, imprime tu mensaje malvado: `"¡Muajaja! He creado una copia falsa llamada: [nombre_falso]"`. Sustituye `[nombre_falso]` por la variable de tu parámetro. +5. **Fuera de la función**, vamos a preparar el terreno: usa tu guardián `with open(...)` en modo `"w"` para crear el archivo del museo llamado `"monalisa.txt"`. Escribe adentro el texto: `"Soy la pintura original"`. +6. Ahora sí, llama a tu función `falsificar_arte()` pasándole `"monalisa.txt"` como el original y `"monalisa_falsa.txt"` como la copia. +7. Corre el código para ver tu plan maestro en acción. +8. **(Bono Opcional)**: Abajo de tu llamada a la función, usa la herramienta de movimiento (`.move()`) para cambiarle el nombre a `"monalisa.txt"` (la original) y esconderla llamándola `"cuadro_robado.txt"`. + +--- + +### 🟢 Conceptos Permitidos (Lo único que puedes usar) +* `import shutil` +* Clonar: `shutil.copy(origen, destino)` +* Renombrar/Mover: `shutil.move(origen, destino)` +* Crear/Escribir archivos (`with open(...)`) +* Funciones (`def`) y parámetros. +* Imprimir a la terminal (`print()`) + +### 🔴 Prohibido +* Usar `os.rename()` (¡hoy es el día del camión de mudanzas `shutil`!). +* Copiar y pegar código de internet. +* Olvidar crear el archivo "monalisa.txt" antes de intentar copiarlo (recuerda el error del fantasma). + +--- + +### 🎯 Resultado esperado en la terminal +*(Solo verás el mensaje malvado. El trabajo real de la copia y el renombramiento sucederá silenciosamente en la carpeta de tu computadora).* + +```text +¡Muajaja! He creado una copia falsa llamada: monalisa_falsa.txt +``` + +¡Es hora de perpetrar el atraco del siglo! diff --git a/python/Modulo-06-OS/55-shutil/teoria.md b/python/Modulo-06-OS/55-shutil/teoria.md new file mode 100644 index 0000000..bb417b2 --- /dev/null +++ b/python/Modulo-06-OS/55-shutil/teoria.md @@ -0,0 +1,46 @@ +# Copiar y Mover con `shutil` + +Vimos que `os` era como nuestros "ojos" para ver qué hay en las habitaciones del edificio de tu computadora. +Ahora te presento a **`shutil`** (abreviatura de *Shell Utilities*). Piensa en `shutil` como un camión de mudanzas o unas manos muy fuertes. Su trabajo principal es agarrar las cajas (archivos) y copiarlas o moverlas de lugar. + +--- + +### Anatomía de los nuevos símbolos + +Para tener acceso al camión de mudanzas, la palabra mágica es: +```python +import shutil +``` + +#### 1. Clonar (Copiar) Archivos +```python +shutil.copy("carta.txt", "copia_carta.txt") +``` +* **`shutil`**: La caja de herramientas de mudanzas. +* **`.copy()`**: Es la acción de "Clonar". Necesita que le des **dos** ingredientes exactos separados por una coma: + * **El Origen (`"carta.txt"`)**: ¿Cuál es el archivo original que quiero clonar? + * **El Destino (`"copia_carta.txt"`)**: ¿Qué nombre quiero que tenga mi nueva copia? + +#### 2. Mover / Renombrar Archivos +```python +shutil.move("viejo.txt", "nuevo.txt") +``` +* **`.move()`**: Es la acción de "Mover". Al igual que `copy`, toma un Origen y un Destino. +* **El gran truco de Mover**: Si le das un nombre de destino nuevo que está en la misma habitación (carpeta), la herramienta `.move()` simplemente **le cambia el nombre** al archivo original. ¡El archivo viejo desaparece y reaparece instantáneamente con su nuevo nombre! + +--- + +### ⚠️ ¿Qué pasa si me equivoco? + +**El error del fantasma** +Si le pides a tu camión de mudanzas que copie o mueva algo que no existe, Python se asustará: + +```text +FileNotFoundError: [Errno 2] No such file or directory: 'fantasma.txt' +``` + +**¿Qué significa esto en lenguaje humano?** +Python te está diciendo: *"Me pediste que tome la caja 'fantasma.txt' con mis manos, pero fui a buscarla y no hay nada ahí. ¡No puedo mudar cosas invisibles!"* + +**¿Cómo lo soluciono?** +Siempre asegúrate de que el archivo que pones de **Origen** exista en la vida real antes de intentar copiarlo o moverlo. Si es necesario, ¡créalo tú mismo primero usando el modo `"w"` que aprendimos en el nivel 53! diff --git a/python/Modulo-06-OS/55-shutil/test_main.py b/python/Modulo-06-OS/55-shutil/test_main.py new file mode 100644 index 0000000..5deee07 --- /dev/null +++ b/python/Modulo-06-OS/55-shutil/test_main.py @@ -0,0 +1,9 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_shutil_copia(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, cwd=BASE, timeout=15) + assert r.returncode == 0, r.stderr diff --git a/python/Modulo-06-OS/56-datetime/.gitkeep b/python/Modulo-06-OS/56-datetime/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/56-datetime/README.md b/python/Modulo-06-OS/56-datetime/README.md new file mode 100644 index 0000000..5b0260a --- /dev/null +++ b/python/Modulo-06-OS/56-datetime/README.md @@ -0,0 +1,14 @@ +# Nivel 56: El Viajero del Tiempo (datetime) ⏰ + +¡Hola, crononauta! Hasta ahora hemos logrado que Python mire carpetas, mueva archivos y lea o escriba en ellos. Pero hay una pieza fundamental que falta en cualquier programa profesional: **el Tiempo**. + +Piensa en el mundo real. Cuando guardas una foto en tu celular, quieres saber qué día la tomaste. Cuando guardas un registro de errores o envías un mensaje, necesitas ponerle la hora exacta. Si escribes un diario, necesitas la fecha. + +En este nivel aprenderemos a conectar a Python con el **reloj maestro** de tu computadora para saber exactamente en qué momento estamos viviendo, y desarmaremos esa información para usarla a nuestro favor. + +### Tu ruta de aprendizaje hoy: +1. **Teoría**: Aprenderemos a pedirle la hora a Python y a desarmar el "paquete" de tiempo en piezas útiles. +2. **Ejemplo**: Construiremos un pequeño reloj humano para mostrar la hora. +3. **Reto**: Combinarás esto con los archivos de texto (Nivel 53) para crear un Diario Automático que pone la fecha por sí solo. + +¡Ajusta tu reloj, que viajamos al presente! diff --git a/python/Modulo-06-OS/56-datetime/ejemplo.py b/python/Modulo-06-OS/56-datetime/ejemplo.py new file mode 100644 index 0000000..c2100e5 --- /dev/null +++ b/python/Modulo-06-OS/56-datetime/ejemplo.py @@ -0,0 +1,40 @@ +# --- 1. Trayendo el reloj del sistema --- +# 'import' nos trae la caja de herramientas del tiempo +import datetime + +print("--- Consultando el reloj maestro ---") + +# Le pedimos a Python que capture el instante exacto en este momento. +# .now() es una acción, por eso lleva (). +ahora = datetime.datetime.now() + +print("El paquete de tiempo completo se ve así de crudo para los humanos:") +print(ahora) + + +# --- 2. Desarmando el paquete de tiempo --- +print("\n--- Sacando las piezas de información ---") + +# Usamos el punto para acceder a las gavetas de información dentro de 'ahora'. +# ¡Nota que NO usamos paréntesis al final porque son solo datos, no acciones! +el_anio = ahora.year +el_mes = ahora.month +el_dia = ahora.day + +la_hora = ahora.hour +los_minutos = ahora.minute + +print("Año extraído:", el_anio) +print("Mes extraído:", el_mes) +print("Día extraído:", el_dia) + + +# --- 3. Dando formato humano --- +print("\n--- Reloj Amigable ---") + +# Ya sabemos usar variables, así que podemos armar nuestro propio texto +# separando los elementos con comas en nuestro print. +print("Hoy es el día", el_dia, "del mes", el_mes, "del año", el_anio) +print("Y son exactamente las", la_hora, "con", los_minutos, "minutos.") + +print("\n--- ¡Viaje en el tiempo completado! ---") diff --git a/python/Modulo-06-OS/56-datetime/reto.md b/python/Modulo-06-OS/56-datetime/reto.md new file mode 100644 index 0000000..b6c3c8f --- /dev/null +++ b/python/Modulo-06-OS/56-datetime/reto.md @@ -0,0 +1,44 @@ +# Reto 56: El Diario Automático 📓 + +Vas a crear un programa que escriba tus pensamientos en un archivo de texto, pero que tenga el "poder" de agregarle automáticamente la fecha y la hora en la que lo escribiste. ¡Así tendrás una bitácora perfecta! + +### Instrucciones paso a paso: + +1. Importa la caja del tiempo (`datetime`). +2. Crea una función (con `def`) llamada `escribir_diario(mensaje)` que reciba un texto. +3. Dentro de la función, captura el momento actual usando `datetime.datetime.now()` y guárdalo en una variable llamada `momento`. +4. Extrae el día, el mes, el año, la hora y los minutos en variables separadas. +5. Abre (o crea) un archivo llamado `"mi_diario.txt"` usando tu guardián `with open(...)`. Recuerda abrirlo en modo **Agregar** (la letra `"a"`) para no borrar las entradas de días anteriores. ¡Pon `newline=""` si estás en Windows para evitar saltos raros! +6. Adentro del bloque `with`, usa la herramienta de escritura (`.write()`) para guardar el título de la fecha. + **¡ATENCIÓN!**: Como `.write()` solo acepta texto (letras), y tus fechas son números, deberás usar la función `str(numero)` para convertirlos a texto y poder sumarlos con el signo `+`. + Escribe algo así: `"--- Entrada del día: " + str(dia) + "/" + str(mes) + "/" + str(anio) + " ---\n"` +7. Usa otro `.write()` para guardar el `mensaje` que recibiste por parámetro. Asegúrate de sumarle un salto de línea (`+ "\n"`) al final para que la siguiente entrada no se pegue. +8. Afuera de la función, llámala pasándole el mensaje: `"¡Hoy aprendí a viajar en el tiempo con Python!"`. +9. (Opcional): Espera un minuto, cambia el mensaje de la función y vuelve a correr el programa. ¡Abre tu archivo y ve cómo se van acumulando con diferentes horas! + +--- + +### 🟢 Conceptos Permitidos (Lo único que puedes usar) +* `import datetime` +* Reloj: `datetime.datetime.now()` +* Etiquetas pasivas: `.year`, `.month`, `.day`, `.hour`, `.minute` +* Convertir a texto (`str(numero)`) y unir textos con suma (`+`) +* Crear/Agregar a archivos (`with open(archivo, "a") as apodo:`) +* Escribir texto (`apodo.write(...)`) +* Funciones (`def`) y parámetros. + +### 🔴 Prohibido +* Usar librerías externas o formateadores de fecha avanzados como `.strftime()` (¡hoy queremos armarlo pieza por pieza con nuestras manos!). +* Usar el modo escritura destructiva `"w"` (¡no queremos borrar todo nuestro diario!). + +--- + +### 🎯 Resultado esperado +*(No verás nada en la terminal. El resultado estará dentro del archivo "mi_diario.txt" que tu programa va a crear. Debería verse así, pero con TU hora local actual):* + +```text +--- Entrada del día: 29/7/2026 --- +¡Hoy aprendí a viajar en el tiempo con Python! +``` + +¡Mucha suerte escribiendo tu bitácora temporal! Recuerda no ponerle paréntesis a los números del año o mes si Python te lanza el error del número actuante. diff --git a/python/Modulo-06-OS/56-datetime/teoria.md b/python/Modulo-06-OS/56-datetime/teoria.md new file mode 100644 index 0000000..16b3720 --- /dev/null +++ b/python/Modulo-06-OS/56-datetime/teoria.md @@ -0,0 +1,42 @@ +# Fechas y Horas con `datetime` + +Tu computadora tiene un reloj maestro interno. Para poder leerlo y extraer sus datos, usamos una nueva caja de herramientas llamada `datetime` (fecha y hora en inglés). + +Cuando le preguntamos a Python la hora actual, nos devuelve un "paquete" con muchísima información junta: año, mes, día, hora, minutos, segundos ¡e incluso milisegundos! Como a nosotros los humanos nos gusta leer las cosas por separado, usaremos el punto (`.`) para abrir ese paquete y sacar solo las gavetas que nos interesan. + +--- + +### Anatomía de los nuevos símbolos + +Esta es la forma clásica de leer el tiempo en Python: + +```python +import datetime + +momento_actual = datetime.datetime.now() + +mi_anio = momento_actual.year +mi_hora = momento_actual.hour +``` + +* **`import datetime`**: Trae la caja principal de fechas y horas. +* **`datetime.datetime`**: Adentro de la caja `datetime`, hay un compartimento especial que curiosamente también se llama `datetime`. Este compartimento maneja específicamente las fechas mezcladas con horas. +* **`.now()`**: Es la acción (herramienta) que significa "AHORA". Lleva paréntesis `()` porque es una orden activa: le estamos diciendo a Python "¡Calcula exactamente qué hora es en este preciso milisegundo y devuélvemela!". +* **`momento_actual.year`**: Tomamos el paquete de tiempo que guardamos, y usamos el punto (`.`) para mirar dentro y sacar la etiqueta del año (`year`). **¡Presta mucha atención! No lleva paréntesis al final**. No es una acción, es simplemente un cajón donde ya estaba guardado el número del año. (Otras etiquetas útiles: `.month`, `.day`, `.hour`, `.minute`, `.second`). + +--- + +### ⚠️ ¿Qué pasa si me equivoco? + +**El error del número actuante** +Imagina que te confundes y crees que pedir el año es una acción, así que le pones paréntesis: `momento_actual.year()`. Python te lanzará este error: + +```text +TypeError: 'int' object is not callable +``` + +**¿Qué significa esto en lenguaje humano?** +Python se está rascando la cabeza y dice: *"Oye, la etiqueta `year` guarda un simple número entero (`int`), por ejemplo el 2024. ¡Pero le pusiste paréntesis, lo que significa que quieres que el número 2024 corra, salte o haga una acción! Los números no hacen cosas, ¡solo son números!"*. + +**¿Cómo lo soluciono?** +Recuerda: las funciones o acciones (como `.now()` o `.read()`) llevan paréntesis porque hacen un trabajo. Los datos o propiedades guardadas (como `.year` o `.hour`) **no llevan paréntesis** porque solo te entregan información pasiva. Solo quítale los paréntesis. diff --git a/python/Modulo-06-OS/56-datetime/test_main.py b/python/Modulo-06-OS/56-datetime/test_main.py new file mode 100644 index 0000000..8234e0a --- /dev/null +++ b/python/Modulo-06-OS/56-datetime/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_imprime_fecha(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "aprendí" in r.stdout or "viaje" in r.stdout.lower() diff --git a/python/Modulo-06-OS/57-subprocess/.gitkeep b/python/Modulo-06-OS/57-subprocess/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/57-subprocess/README.md b/python/Modulo-06-OS/57-subprocess/README.md new file mode 100644 index 0000000..e07d269 --- /dev/null +++ b/python/Modulo-06-OS/57-subprocess/README.md @@ -0,0 +1,14 @@ +# Nivel 57: El Walkie-Talkie del Sistema (subprocess) 📻 + +¡Hola, comandante! Python es increíblemente poderoso, pero a veces no tiene la herramienta exacta para hacer algo, ¡mientras que tu computadora sí la tiene! + +Por ejemplo, tu computadora sabe cómo revisar la conexión a internet, cómo abrir otras aplicaciones, o cómo limpiar la pantalla. ¿No sería genial si Python pudiera "teclear" comandos en la terminal por ti para usar esas herramientas? + +En este nivel aprenderemos a usar `subprocess` (sub-procesos), que le permite a Python crear pequeños programas "hijos" para ejecutar comandos directamente en el sistema operativo, exactamente igual que si tú los estuvieras escribiendo con el teclado. + +### Tu ruta de aprendizaje hoy: +1. **Teoría**: Aprenderemos cómo darle el walkie-talkie a Python y por qué los comandos se escriben como listas. +2. **Ejemplo**: Haremos que Python le ordene a la terminal que nos diga qué versión de Python está instalada. +3. **Reto**: Te convertirás en un "Hacker de la Terminal" obligando al sistema a repetir tus mensajes. + +¡Prepara el radio, cambio y fuera! diff --git a/python/Modulo-06-OS/57-subprocess/ejemplo.py b/python/Modulo-06-OS/57-subprocess/ejemplo.py new file mode 100644 index 0000000..963e3be --- /dev/null +++ b/python/Modulo-06-OS/57-subprocess/ejemplo.py @@ -0,0 +1,23 @@ +import subprocess + +print("--- Inicio del Programa Maestro ---") +print("Soy el programa principal. Voy a despertar a un ayudante.\n") + +# Preparamos las instrucciones para el ayudante en forma de lista +# Queremos que el sistema ejecute a Python y le pregunte su versión. +instrucciones = ["python", "--version"] + +print("Enviando orden al ayudante...") +# Lanzamos el subproceso. +# capture_output=True: Guardamos lo que diga. +# text=True: Lo queremos como texto normal. +expediente = subprocess.run(instrucciones, capture_output=True, text=True) + +print("El ayudante ha terminado su tarea.\n") + +print("--- Revisando los Resultados ---") +# .stdout contiene el texto que el otro programa imprimió +print("El ayudante nos dijo:") +print(expediente.stdout) + +print("--- Fin del Programa Maestro ---") diff --git a/python/Modulo-06-OS/57-subprocess/reto.md b/python/Modulo-06-OS/57-subprocess/reto.md new file mode 100644 index 0000000..2a2c21c --- /dev/null +++ b/python/Modulo-06-OS/57-subprocess/reto.md @@ -0,0 +1,25 @@ +# Reto 57: El Calculador Invisible + +Tu programa principal de Python va a utilizar a **otro** programa de Python para hacer matemáticas sin ensuciarse las manos. + +## El Objetivo +Escribe un script llamado `reto.py` que invoque un subproceso para calcular una suma, atrape la respuesta y la imprima en pantalla. + +## Las Instrucciones + +1. Importa el módulo `subprocess`. +2. Crea una variable que ejecute `subprocess.run()`. +3. El comando que debes ejecutar en forma de lista es: `["python", "-c", "print(100 + 150)"]` + - *(Nota: `-c` le dice a Python que ejecute el texto que le sigue como si fuera un mini-programa).* +4. Asegúrate de configurar `capture_output=True` y `text=True`. +5. Imprime un mensaje amigable seguido de la respuesta que el subproceso guardó en `.stdout`. + +## Resultado Esperado + +Al ejecutar tu script, deberías ver algo así: + +```text +Solicitando un cálculo al subproceso... +El subproceso respondió que el resultado es: +250 +``` diff --git a/python/Modulo-06-OS/57-subprocess/teoria.md b/python/Modulo-06-OS/57-subprocess/teoria.md new file mode 100644 index 0000000..038a6c1 --- /dev/null +++ b/python/Modulo-06-OS/57-subprocess/teoria.md @@ -0,0 +1,45 @@ +# Nivel 57: El Controlador de Títeres (`subprocess`) + +Hasta ahora, tus programas en Python hacían todo por sí mismos. Leían archivos, calculaban cosas y mostraban resultados. Pero, ¿qué pasa si tu programa necesita que *otro* programa haga un trabajo por él? + +Imagina que tu código de Python es un director de orquesta. A veces, en lugar de tocar un instrumento, solo necesita apuntar a un músico (otro programa) y decirle: *"Oye tú, haz esto y dime cuando termines"*. + +Para eso usamos el módulo `subprocess`. Nos permite crear **subprocesos**: programas hijos que nacen, trabajan y mueren bajo las órdenes de tu programa principal. + +## El Concepto: `subprocess.run()` + +Para ejecutar otro programa, usamos la función `run()` (que significa "correr" o "ejecutar"). + +```python +import subprocess + +resultado = subprocess.run(["python", "--version"], capture_output=True, text=True) +``` + +Desmontemos esta instrucción pieza por pieza, porque tiene varios elementos nuevos: + +### 1. La Lista del Comando: `["python", "--version"]` +Cuando escribes en tu terminal, usas espacios: `python --version`. +Pero a `subprocess` no le gustan los textos largos con espacios, porque se puede confundir si un nombre de archivo tiene un espacio en blanco. +Para evitar errores, le damos el comando desarmado en una **lista** `[]`: +- El primer elemento (`"python"`) es **quién** va a trabajar (el programa). +- Los elementos siguientes (`"--version"`) son **qué** queremos que haga (los argumentos). + +### 2. Atrapando las palabras: `capture_output=True` +Por defecto, si el otro programa imprime algo, saldrá directo a tu pantalla, mezclándose con los mensajes de tu programa. +Al usar `capture_output=True` (atrapar salida = Verdadero), le decimos a Python: *"Ponle una mordaza al programa hijo. Todo lo que intente decir, guárdalo en secreto y entrégamelo a mí"*. + +### 3. Hablando nuestro idioma: `text=True` +Las computadoras se comunican internamente en un formato crudo llamado "bytes" (ceros y unos empaquetados). Si no ponemos esta regla, el programa hijo nos devolverá un texto extraño lleno de símbolos raros como `b'...'`. +Al poner `text=True`, le decimos: *"Traduce la respuesta del programa hijo a texto normal que los humanos podamos leer"*. + +### 4. Leyendo la respuesta: `.stdout` +Cuando `subprocess.run()` termina, no te da el texto directamente. Te da un "paquete completo" de información sobre cómo le fue al programa hijo (su expediente). +Para leer lo que el programa hijo imprimió exitosamente, abrimos ese paquete y buscamos la propiedad `.stdout` (que significa *Standard Output* o "Salida Estándar"). + +```python +print(resultado.stdout) +``` + +## ¿Por qué esto es poderoso? +Con `subprocess`, tu script de Python ya no está aislado. Puede mandar correos usando herramientas del sistema, comprimir archivos usando otros programas, o incluso abrir el navegador web. Tu código de Python se convierte en un cerebro que coordina a toda tu computadora. diff --git a/python/Modulo-06-OS/57-subprocess/test_main.py b/python/Modulo-06-OS/57-subprocess/test_main.py new file mode 100644 index 0000000..3fc4dd6 --- /dev/null +++ b/python/Modulo-06-OS/57-subprocess/test_main.py @@ -0,0 +1,10 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_subproceso_calcula(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "250" in r.stdout, "El subproceso debe calcular 100+150=250" diff --git a/python/Modulo-06-OS/58-logging/.gitkeep b/python/Modulo-06-OS/58-logging/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/58-logging/README.md b/python/Modulo-06-OS/58-logging/README.md new file mode 100644 index 0000000..ae2ace2 --- /dev/null +++ b/python/Modulo-06-OS/58-logging/README.md @@ -0,0 +1,12 @@ +# Nivel 58: La Bitácora del Capitán (Logging) 📝 + +Hasta este nivel, tu única forma de saber qué estaba pensando o haciendo tu programa era usar `print()`. El problema con `print()` es que es como hablar en voz alta: si tú no estás mirando la pantalla en ese instante, el mensaje se lo lleva el viento. + +Pero, ¿qué pasa cuando construyes un programa que funciona de madrugada o mientras duermes? Si el programa se rompe, no sabrás por qué. + +Para eso existe el módulo `logging` (que significa "registro en bitácora"). Funciona exactamente como la "caja negra" de un avión: guarda un historial permanente y silencioso de todo lo que ocurrió en tu programa, incluso los errores, para que puedas leerlos después. + +## Ruta de Aprendizaje +1. **Teoría**: Entenderemos cómo configurar un diario secreto para tu código. +2. **Ejemplo**: Crearemos un programa que trabaje en silencio y guarde sus secretos en un archivo. +3. **Reto**: Construirás el sistema de alertas de un robot. diff --git a/python/Modulo-06-OS/58-logging/ejemplo.py b/python/Modulo-06-OS/58-logging/ejemplo.py new file mode 100644 index 0000000..6ab7363 --- /dev/null +++ b/python/Modulo-06-OS/58-logging/ejemplo.py @@ -0,0 +1,30 @@ +import logging + +print("--- Iniciando el Sistema ---") +print("Preparando la bitácora silenciosa del programa...") + +# 1. PREPARAMOS EL CUADERNO (basicConfig) +# filename: El nombre del archivo de texto. +# level: Queremos guardar desde mensajes de información (INFO) para arriba. +logging.basicConfig(filename="registro_del_sistema.txt", level=logging.INFO) + +print("¡Bitácora lista! Ahora el programa trabajará en secreto.") +print("No verás los mensajes en la pantalla, se irán al archivo.") + +print("--- Trabajando en Silencio ---") + +# 2. ESCRIBIMOS MENSAJES (Estos no salen en la terminal) +# Un mensaje normal +logging.info("El sistema ha arrancado exitosamente.") + +# Supongamos que ocurre un evento extraño pero no fatal +logging.warning("Se detectó un usuario sospechoso intentando entrar.") + +# Supongamos que algo se rompe +logging.error("¡Fallo catastrófico! El servidor de café se ha desconectado.") + +# Un último mensaje normal +logging.info("El sistema se está apagando.") + +print("--- Fin del Programa ---") +print("El programa ha terminado. ¡Abre el archivo 'registro_del_sistema.txt' para ver lo que pasó!") diff --git a/python/Modulo-06-OS/58-logging/reto.md b/python/Modulo-06-OS/58-logging/reto.md new file mode 100644 index 0000000..0f55adb --- /dev/null +++ b/python/Modulo-06-OS/58-logging/reto.md @@ -0,0 +1,36 @@ +# Reto 58: El Diario del Robot Espacial 🤖 + +Acabas de programar la inteligencia artificial de un robot explorador de Marte. Como el robot está muy lejos, no puedes ver su pantalla. Necesitas que deje un registro escrito de todo lo que hace y de sus problemas, para que puedas leerlo cuando aterrice. + +## El Objetivo +Escribir un script llamado `reto.py` que configure una bitácora y simule la actividad del robot, guardando distintos niveles de mensajes en un archivo de texto. + +## Instrucciones Paso a Paso + +1. Importa el módulo necesario para llevar una bitácora. +2. Configura el sistema (usando `basicConfig`) para que: + - El archivo donde se guardará todo se llame `diario_robot.txt`. + - El nivel mínimo de registro sea de Información (la etiqueta en mayúsculas). +3. Usa la función de escritura de **Información** para guardar el mensaje: `"El robot ha aterrizado en Marte."` +4. Usa la función de escritura de **Advertencia** para guardar el mensaje: `"Tormenta de arena detectada. Visibilidad reducida."` +5. Usa la función de escritura de **Error** para guardar el mensaje: `"¡Atasco! La rueda derecha no responde."` +6. Finalmente, pon un `print()` normal (para la pantalla de la Tierra) que diga: `"Simulación del robot terminada. Revisando la bitácora..."` + +## Reglas Estrictas +- **Conceptos permitidos**: `import logging`, `logging.basicConfig(filename=..., level=...)`, `logging.INFO`, `logging.info()`, `logging.warning()`, `logging.error()`, `print()`. +- **Conceptos prohibidos**: Modificar el formato de los logs con `format=`, usar variables complejas, o enviar logs a la consola simultáneamente. + +## Resultado Esperado + +En tu **terminal** (pantalla), solo deberías ver: +```text +Simulación del robot terminada. Revisando la bitácora... +``` + +Pero si abres el archivo **`diario_robot.txt`**, deberías ver algo así: +```text +INFO:root:El robot ha aterrizado en Marte. +WARNING:root:Tormenta de arena detectada. Visibilidad reducida. +ERROR:root:¡Atasco! La rueda derecha no responde. +``` +*(Nota: Python agrega automáticamente ese texto de `INFO:root:` al principio de las líneas).* diff --git a/python/Modulo-06-OS/58-logging/teoria.md b/python/Modulo-06-OS/58-logging/teoria.md new file mode 100644 index 0000000..c1dd74c --- /dev/null +++ b/python/Modulo-06-OS/58-logging/teoria.md @@ -0,0 +1,46 @@ +# Teoría: Escribiendo en el Diario (`logging`) + +El módulo `logging` viene incluido en Python y nos permite clasificar nuestros mensajes por su nivel de gravedad o importancia, en lugar de simplemente "gritar" todo a la pantalla como hace `print()`. + +Hay tres niveles principales de gravedad que debes conocer (de menor a mayor): +1. **INFO** (Información): "Todo va bien, solo te aviso que hice esto". +2. **WARNING** (Advertencia): "Ojo, algo raro pasó, pero puedo seguir trabajando". +3. **ERROR** (Error grave): "Algo salió mal y no pude terminar mi tarea". + +## La Anatomía de `logging` + +Antes de escribir en la bitácora, necesitamos preparar el cuaderno. Esto se hace una sola vez al principio de tu programa: + +```python +import logging + +logging.basicConfig(filename="bitacora.txt", level=logging.INFO) +``` + +**Desmontaje de los símbolos nuevos:** +- `basicConfig()`: Es la función que "prepara el cuaderno". (Basic Config = Configuración Básica). +- `filename="bitacora.txt"`: Le decimos exactamente cómo se llamará el archivo donde escribiremos nuestro diario. Si no existe, Python lo creará mágicamente por nosotros. +- `level=`: Le indicamos desde qué nivel de gravedad queremos empezar a guardar. +- `logging.INFO`: Es una "etiqueta" especial que vive dentro de la caja de `logging`. Al ponerla aquí, le decimos: *"Guarda desde el nivel de Información hacia arriba"*. (Es decir, guardará INFO, WARNING y ERROR). Si pusiéramos `logging.ERROR`, solo guardaría los errores y tiraría a la basura los mensajes de información. ¡Nota que `INFO` va en MAYÚSCULAS! + +Una vez configurado el cuaderno, usamos los "lápices" de colores para escribir: + +```python +logging.info("El usuario inició sesión.") +logging.warning("La contraseña es muy corta.") +logging.error("¡No hay internet!") +``` +Cada uno de estos comandos guardará el mensaje en `bitacora.txt`, etiquetándolo automáticamente con la palabra INFO, WARNING o ERROR. + +## ¿Qué pasa si me equivoco? + +### Error Común 1: Usar `print` en lugar de `logging` +Si estás usando `print()` para registrar errores, nunca los encontrarás si cierras la pantalla. Usa `print()` solo para comunicarte con el usuario humano, y `logging` para dejar un rastro permanente para el programador (tú). + +### Error Común 2: Escribir `.info` en mayúsculas o `.INFO` en minúsculas. +Las mayúsculas y minúsculas importan mucho. +- `logging.INFO` (todo mayúsculas) es el **NIVEL** (la etiqueta). Lo usas en `basicConfig`. +- `logging.info()` (todo minúsculas) es la **ACCIÓN** (la función). Lo usas para escribir un mensaje. + +### Error Común 3: Olvidar `basicConfig` +Si olvidas poner la línea `logging.basicConfig(...)`, Python no sabrá en qué archivo escribir, así que por precaución simplemente arrojará tus mensajes de error a la pantalla y desechará los de información. ¡Siempre prepara tu cuaderno primero! diff --git a/python/Modulo-06-OS/58-logging/test_main.py b/python/Modulo-06-OS/58-logging/test_main.py new file mode 100644 index 0000000..963f6f4 --- /dev/null +++ b/python/Modulo-06-OS/58-logging/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_logging_robot(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, cwd=BASE, timeout=15) + assert r.returncode == 0, r.stderr + assert "terminada" in r.stdout.lower() + assert os.path.exists(os.path.join(BASE, "diario_robot.txt")), "Debe crear diario_robot.txt" diff --git a/python/Modulo-06-OS/59-try-except/.gitkeep b/python/Modulo-06-OS/59-try-except/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/59-try-except/README.md b/python/Modulo-06-OS/59-try-except/README.md new file mode 100644 index 0000000..b75cc66 --- /dev/null +++ b/python/Modulo-06-OS/59-try-except/README.md @@ -0,0 +1,12 @@ +# Nivel 59: La Red de Seguridad (Try / Except) 🦺 + +Hasta este nivel, cuando tu código se equivocaba (por ejemplo, intentando dividir un número entre cero, o buscando un archivo que no existía), Python lanzaba un texto rojo aterrador en la terminal y **mataba tu programa al instante**. + +Pero piensa en las aplicaciones que usas a diario. Si ingresas mal tu contraseña en Instagram, la aplicación no se cierra de golpe ni explota. Simplemente te muestra un mensaje amigable: *"Contraseña incorrecta, intenta de nuevo"*. + +En este nivel aprenderemos a darle esa misma resiliencia a tus programas. Aprenderemos a atrapar los errores en el aire antes de que estrellen nuestro código. + +## Ruta de Aprendizaje +1. **Teoría**: Entenderemos cómo tender una red de seguridad bajo el código peligroso. +2. **Ejemplo**: Sobreviviremos a una explosión matemática (división por cero). +3. **Reto**: Intentaremos leer un archivo que no existe sin que el programa muera en el intento. diff --git a/python/Modulo-06-OS/59-try-except/ejemplo.py b/python/Modulo-06-OS/59-try-except/ejemplo.py new file mode 100644 index 0000000..32ac955 --- /dev/null +++ b/python/Modulo-06-OS/59-try-except/ejemplo.py @@ -0,0 +1,26 @@ +print("--- Inicio del Show ---") +print("El acróbata está caminando por el piso (código seguro).") + +print("\n--- Subiendo al Trapecio ---") + +# Le avisamos a Python que vamos a hacer algo arriesgado +try: + manzanas = 10 + ninos = 0 + print("Intentando repartir", manzanas, "manzanas entre", ninos, "niños...") + + # ¡BOM! Las computadoras odian dividir por cero. Esto causará un error. + resultado = manzanas / ninos + + # Esta línea NUNCA se va a ejecutar, porque el programa saltó + # a la red de rescate en el momento exacto en que falló la división. + print("Cada niño recibe:", resultado) + +# Aquí está nuestra red de seguridad. Atrapamos el "Error" y lo guardamos en la variable "e" +except Exception as e: + print("¡Oh no! El acróbata resbaló. Pero cayó en la red de seguridad.") + print("El reporte médico (el error técnico) dice:", e) + +print("\n--- El Show Continúa ---") +print("Como atrapamos el error a tiempo, el programa no explotó.") +print("Podemos seguir trabajando normalmente.") diff --git a/python/Modulo-06-OS/59-try-except/reto.md b/python/Modulo-06-OS/59-try-except/reto.md new file mode 100644 index 0000000..63b36cf --- /dev/null +++ b/python/Modulo-06-OS/59-try-except/reto.md @@ -0,0 +1,29 @@ +# Reto 59: El Lector Blindado 🛡️ + +En los niveles pasados aprendiste a leer archivos de texto usando `open()`. Pero, ¿qué pasa si le pides a Python que abra un archivo que alguien borró por accidente? Exacto, el programa explota. + +Vamos a crear un lector de archivos que sea indestructible. + +## El Objetivo +Escribe un script llamado `reto.py` que intente leer un archivo que NO existe. En lugar de que el programa muera, debe atrapar el error, avisarle al usuario de forma amigable y terminar con gracia. + +## Instrucciones Paso a Paso + +1. Crea un bloque `try:`. +2. Dentro del `try:`, crea una variable e intenta abrir un archivo llamado `"fantasma.txt"` en modo lectura (`"r"`). (Recuerda, este archivo no existe en tu carpeta, y eso es exactamente lo que queremos). +3. Crea un bloque `except Exception as e:` para colocar tu red de seguridad. +4. Dentro del bloque `except`, imprime un mensaje amigable que diga: `"Lo siento, no pudimos encontrar el archivo. Detalles del problema: "` seguido de la variable `e` que contiene el error real. +5. Finalmente, **fuera** de los bloques try/except, imprime el mensaje: `"El programa ha finalizado con elegancia."` + +## Reglas Estrictas +- **Conceptos permitidos**: `try:`, `except Exception as e:`, `open()`, `print()`, variables. +- **Conceptos prohibidos**: Crear el archivo `fantasma.txt` (queremos que falle), múltiples bloques except, ciclos o funciones. + +## Resultado Esperado + +Al ejecutar tu script, tu terminal NO debe mostrar texto rojo aterrador. Debería verse exactamente así: + +```text +Lo siento, no pudimos encontrar el archivo. Detalles del problema: [Errno 2] No such file or directory: 'fantasma.txt' +El programa ha finalizado con elegancia. +``` diff --git a/python/Modulo-06-OS/59-try-except/teoria.md b/python/Modulo-06-OS/59-try-except/teoria.md new file mode 100644 index 0000000..2fff016 --- /dev/null +++ b/python/Modulo-06-OS/59-try-except/teoria.md @@ -0,0 +1,47 @@ +# Teoría: El Acróbata y la Red (`try` y `except`) + +Imagina que tu código es un acróbata de circo. Hay partes de su rutina que son seguras (caminar por el suelo), pero hay partes muy peligrosas (saltar en el trapecio a 10 metros de altura). + +Si el acróbata resbala en el trapecio sin protección, el show termina en tragedia (tu programa muere). +Para evitar esto, le ponemos una red de seguridad abajo. Si resbala, cae en la red, se levanta, y el show continúa. + +En Python, el trapecio se llama `try` y la red de seguridad se llama `except`. + +## La Anatomía de `try / except` + +```python +try: + # Código peligroso aquí + resultado = 10 / 0 +except Exception as e: + # Código de rescate aquí + print("¡Ups! Caímos en la red:", e) +``` + +**Desmontaje de los símbolos nuevos:** +- `try:` (Intenta): Literalmente le dice a Python *"Intenta ejecutar las siguientes líneas, pero ten mucho cuidado porque podrían fallar"*. +- `except:` (Excepto): Significa *"Si alguna línea dentro de 'try' falla, detén la caída inmediatamente y salta hacia aquí"*. +- `Exception`: Es la palabra oficial en Python para decir "Cualquier Error". +- `as e` (como `e`): Le decimos a Python *"Toma todos los detalles técnicos y feos del error, y guárdalos en una cajita (variable) llamada 'e', por si quiero leerlos"*. + +## ¿Cómo funciona el flujo? +1. Python entra al bloque `try`. +2. Ejecuta línea por línea. +3. Si **TODO** sale bien, Python ignora por completo el bloque `except` y sigue con el resto del programa. +4. Si **ALGO** falla, Python aborta inmediatamente el bloque `try` (las líneas que faltaban ahí no se ejecutan) y salta directo al bloque `except`. ¡El programa no muere! + +## ¿Qué pasa si me equivoco? + +### Error Común 1: Olvidar los dos puntos `:` +Al igual que con los `if`, `for`, `while` y `def`, las palabras `try` y `except` abren un nuevo bloque de código. Por lo tanto, SIEMPRE deben terminar con dos puntos `:`. Si los olvidas, Python se quejará con un *SyntaxError*. + +### Error Común 2: Mala Indentación +Todo lo que está *dentro* de la red de seguridad debe tener un espacio hacia la derecha (indentación). +```python +# MAL: +try: +print("Peligro") +``` + +### Error Común 3: Poner código seguro dentro de la zona de peligro +El bloque `try` debe ser lo más pequeño posible. Solo debes meter ahí la instrucción exacta que sospechas que puede explotar (como abrir un archivo o dividir variables). El resto del código normal debe ir fuera. diff --git a/python/Modulo-06-OS/59-try-except/test_main.py b/python/Modulo-06-OS/59-try-except/test_main.py new file mode 100644 index 0000000..5ba9132 --- /dev/null +++ b/python/Modulo-06-OS/59-try-except/test_main.py @@ -0,0 +1,11 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_lector_blindado(): + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, timeout=15) + assert r.returncode == 0, r.stderr + assert "elegancia" in r.stdout.lower() + assert "BOMBA" not in r.stdout diff --git a/python/Modulo-06-OS/60-RETO-organizador/.gitkeep b/python/Modulo-06-OS/60-RETO-organizador/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-06-OS/60-RETO-organizador/README.md b/python/Modulo-06-OS/60-RETO-organizador/README.md new file mode 100644 index 0000000..f690a48 --- /dev/null +++ b/python/Modulo-06-OS/60-RETO-organizador/README.md @@ -0,0 +1,12 @@ +# Nivel 60: Proyecto Integrador - El Organizador Automático 🗂️ + +¡Felicidades! Has llegado al nivel final del **Módulo 6 (Sistema Operativo)**. Tu código ya no vive encerrado en una pantalla; ahora puede caminar por tu computadora, leer carpetas, escribir archivos, lanzar subprocesos y sobrevivir a caídas sin morir en el intento. + +En la vida real, todos tenemos una carpeta de "Descargas" o un "Escritorio" lleno de archivos desordenados: fotos, documentos, audios revueltos. + +Hoy vas a construir un programa real y útil: **Un Organizador Automático**. Este programa revisará una carpeta desordenada, clasificará los archivos por su terminación (extensión) y los moverá a sus propias subcarpetas limpias. Y como eres un profesional, dejará un registro escrito en una bitácora y estará blindado contra errores. + +## Ruta de Aprendizaje +1. **Teoría**: Un repaso rápido de tu cinturón de herramientas. +2. **Ejemplo**: Ejecutarás un script que creará un "cuarto desordenado" para que puedas practicar. +3. **Reto**: ¡La gran prueba final! Programarás la inteligencia de tu robot limpiador. diff --git a/python/Modulo-06-OS/60-RETO-organizador/ejemplo.py b/python/Modulo-06-OS/60-RETO-organizador/ejemplo.py new file mode 100644 index 0000000..677619b --- /dev/null +++ b/python/Modulo-06-OS/60-RETO-organizador/ejemplo.py @@ -0,0 +1,37 @@ +import os + +print("--- El Generador de Caos ---") +print("Hola. Yo no soy el organizador, ¡soy el niño travieso!") +print("Voy a crear un 'cuarto_desordenado' lleno de archivos de prueba.\n") + +# 1. Definimos el nombre del cuarto +carpeta_caos = "cuarto_desordenado" + +# 2. Si el cuarto no existe, lo construimos +if not os.path.exists(carpeta_caos): + os.makedirs(carpeta_caos) + +# 3. Lista de basura falsa para tirar en el cuarto +basura = [ + "gato_gracioso.jpg", + "receta_pastel.txt", + "paisaje.jpg", + "contraseñas_no_leer.txt", + "meme.jpg" +] + +print("--- Tirando cosas al piso ---") +# 4. Soltamos cada archivo en la carpeta +for nombre in basura: + # Pegamos la ruta: cuarto_desordenado + / + nombre + ruta = os.path.join(carpeta_caos, nombre) + + # Creamos un archivo vacío solo para hacer bulto + with open(ruta, "w") as archivo: + archivo.write("Este es un archivo de prueba. No tiene nada útil.") + + print("Se ha tirado el archivo:", nombre) + +print("\n--- Caos Terminado ---") +print("Si miras en tus carpetas, verás un 'cuarto_desordenado'.") +print("Tu reto será programar al robot limpiador en 'reto.py'. ¡Suerte!") diff --git a/python/Modulo-06-OS/60-RETO-organizador/reto.md b/python/Modulo-06-OS/60-RETO-organizador/reto.md new file mode 100644 index 0000000..7bc5fd4 --- /dev/null +++ b/python/Modulo-06-OS/60-RETO-organizador/reto.md @@ -0,0 +1,43 @@ +# Reto 60: El Robot Organizador 🤖🧹 + +Es hora de la gran prueba. Vas a crear un script que limpie el desastre que dejó el programa `ejemplo.py`. + +## El Objetivo +Escribir un script llamado `reto.py` que entre a la carpeta `"cuarto_desordenado"`, lea todos los archivos, los clasifique en imágenes o textos, los mueva a sus respectivas carpetas, y guarde un historial de todo en un archivo `.log`. + +## Instrucciones Paso a Paso + +1. **El Desastre**: Primero, ejecuta `ejemplo.py` para que se cree la carpeta `"cuarto_desordenado"` con sus archivos. +2. **Tu Script (`reto.py`)**: + - Importa `os`, `shutil` y `logging`. + - Configura el diario: usa `logging.basicConfig` para escribir en `"limpieza.log"` desde el nivel `INFO`. + - Crea dos carpetas nuevas usando `os.makedirs()` (recuerda comprobar si ya existen con `os.path.exists()`): + - Una carpeta llamada `"cuarto_desordenado/imagenes"` + - Otra llamada `"cuarto_desordenado/documentos"` +3. **El Ciclo de Limpieza**: + - Usa `os.listdir("cuarto_desordenado")` para obtener la lista de cosas en el piso. + - Inicia un ciclo `for` para agarrar cada cosa una por una. +4. **La Lógica**: + - Para cada archivo, arma su ruta de origen (`os.path.join`). + - Usa `if` para preguntar: ¿Termina en `.jpg`? Entonces arma su ruta de destino hacia la carpeta de imágenes. ¿Termina en `.txt`? Arma su ruta hacia los documentos. (Si es otra cosa, sáltalo o ignóralo). + - Ignora también si lo que lees en el `for` es una carpeta (como "imagenes" o "documentos") usando `os.path.isfile(ruta_origen)`. +5. **El Movimiento**: + - Envuelve el comando `shutil.move(origen, destino)` en un bloque `try:`. + - Después de moverlo con éxito, anótalo en la bitácora: `logging.info("Archivo movido: " + nombre)` + - Si algo sale mal, atrápalo con un `except Exception as e:` y anótalo: `logging.error("Fallo al mover: " + str(e))` + +## Reglas Estrictas +- **Conceptos permitidos**: `os`, `shutil`, `logging`, `try / except`, ciclos `for`, condicionales `if/elif`, concatenación, `.endswith()`. +- **Conceptos prohibidos**: Programación orientada a objetos (clases), bibliotecas externas avanzadas como `pathlib` (nos apegamos a `os`), funciones complicadas si no las necesitas. + +## Resultado Esperado + +En la terminal no necesitas mostrar mucho, tal vez un `"Limpieza terminada"`. +El verdadero resultado será: +1. La carpeta `cuarto_desordenado` estará vacía de archivos sueltos, y solo tendrá dos subcarpetas (`imagenes` y `documentos`) con los archivos adentro. +2. Aparecerá un archivo `limpieza.log` con un texto similar a: +```text +INFO:root:Archivo movido: meme.jpg +INFO:root:Archivo movido: receta_pastel.txt +... +``` diff --git a/python/Modulo-06-OS/60-RETO-organizador/teoria.md b/python/Modulo-06-OS/60-RETO-organizador/teoria.md new file mode 100644 index 0000000..4e4d5ae --- /dev/null +++ b/python/Modulo-06-OS/60-RETO-organizador/teoria.md @@ -0,0 +1,33 @@ +# Teoría: Tu Cinturón de Herramientas 🛠️ + +Para el jefe final de este módulo no te enseñaré ningún concepto nuevo. Vamos a armar un rompecabezas usando todas las piezas que ya aprendiste a dominar. + +Imagina que eres un detective organizando una escena del crimen. Estas son las herramientas que llevas en tu cinturón: + +### 1. Los Ojos (`os`) +Necesitas ver qué hay en la habitación y saber cómo caminar. +- `os.listdir("carpeta")`: Te da una lista con los nombres de todos los archivos revueltos. +- `os.path.join(A, B)`: El pegamento mágico. Une el nombre de la carpeta y el nombre del archivo con la barra inclinada correcta (`/` o `\`), sin importar si estás en Windows o Mac. + +### 2. Las Manos (`shutil`) +Necesitas levantar un archivo y ponerlo en otra caja. +- `shutil.move(origen, destino)`: Levanta el archivo de la ruta origen y lo suelta en la ruta destino. + +### 3. La Red de Seguridad (`try / except`) +¿Qué pasa si intentas mover un archivo pero otro programa lo está usando y está bloqueado? No queremos que el robot organizador muera. +- Envuelves el `shutil.move()` en un `try:`. Si falla, saltará al `except Exception as e:` y el robot simplemente pasará al siguiente archivo. + +### 4. La Libreta de Notas (`logging`) +El robot trabajará en silencio, pero debe dejarte un reporte de su jornada laboral. +- `logging.basicConfig(filename="reporte.log", level=logging.INFO)`: Prepara el cuaderno. +- `logging.info("Moví el archivo X")`: Anota sus éxitos. +- `logging.error("No pude mover el archivo Y")`: Anota sus tropiezos. + +## Anatomía de una cadena: `.endswith()` +Solo un pequeño recordatorio de los módulos pasados. Para saber si un archivo es una foto o un texto, puedes usar el método de los textos llamado `.endswith()` (termina con). + +```python +archivo = "vacaciones.jpg" +if archivo.endswith(".jpg"): + print("¡Es una imagen!") +``` diff --git a/python/Modulo-06-OS/60-RETO-organizador/test_main.py b/python/Modulo-06-OS/60-RETO-organizador/test_main.py new file mode 100644 index 0000000..405d398 --- /dev/null +++ b/python/Modulo-06-OS/60-RETO-organizador/test_main.py @@ -0,0 +1,12 @@ +"""test_main.py""" +import subprocess, sys, os +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def test_archivo_existe(): + assert os.path.exists(RETO) +def test_organizador(): + ejemplo = os.path.join(BASE, "ejemplo.py") + if os.path.exists(ejemplo): + subprocess.run([sys.executable, ejemplo], cwd=BASE, timeout=15) + r = subprocess.run([sys.executable, RETO], capture_output=True, text=True, cwd=BASE, timeout=15) + assert r.returncode == 0, r.stderr diff --git a/python/Modulo-07-Web-APIs/.gitkeep b/python/Modulo-07-Web-APIs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-07-Web-APIs/61-requests/.gitkeep b/python/Modulo-07-Web-APIs/61-requests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-07-Web-APIs/61-requests/README.md b/python/Modulo-07-Web-APIs/61-requests/README.md new file mode 100644 index 0000000..791b5ea --- /dev/null +++ b/python/Modulo-07-Web-APIs/61-requests/README.md @@ -0,0 +1,14 @@ +# Nivel 61: Pidiendo Datos a Internet (requests) 🌐 + +¿Te has dado cuenta de que, hasta ahora, nuestros programas viven en una burbuja? Solo saben lo que tú escribes en el código o lo que leen de archivos locales en tu computadora. + +Pero el mundo real no funciona así. Las aplicaciones del clima, las redes sociales, los juegos multijugador... todos necesitan **hablar con otros servidores en Internet** para traer información fresca. + +En este nivel, le daremos a nuestro programa un "teléfono" para que pueda hacer llamadas a Internet y pedir información. A esto le llamamos **hacer peticiones HTTP** (o *HTTP requests* en inglés). + +## Tu ruta de aprendizaje hoy: +1. **Teoría (`teoria.md`)**: Entenderemos cómo funciona el "teléfono" (`requests`) y cómo traducir lo que nos dice el servidor. +2. **Ejemplo (`ejemplo.py`)**: Llamaremos a la base de datos de Pokémon para preguntarle los datos de Pikachu. +3. **Reto (`reto.md`)**: Te convertirás en un investigador privado obteniendo datos de usuarios falsos de una base de datos pública. + +¡Prepárate para conectar tu código con el mundo exterior! diff --git a/python/Modulo-07-Web-APIs/61-requests/ejemplo.py b/python/Modulo-07-Web-APIs/61-requests/ejemplo.py new file mode 100644 index 0000000..c0d9c0d --- /dev/null +++ b/python/Modulo-07-Web-APIs/61-requests/ejemplo.py @@ -0,0 +1,48 @@ +# Importamos el módulo que nos permite hablar con Internet +# Piensa en 'requests' como nuestro teléfono celular. +import requests + +print("--- 1. Preparando la llamada ---") +# La URL es la dirección exacta a la que vamos a llamar. +# En este caso, llamamos a la PokéAPI, preguntando por 'pikachu'. +direccion_pikachu = "https://pokeapi.co/api/v2/pokemon/pikachu" + +print(f"Vamos a llamar a: {direccion_pikachu}") + + +print("\n--- 2. Haciendo la petición a Internet ---") +# requests.get() es la acción de marcar el número y esperar a que contesten. +# Lo que el servidor nos responda, lo guardaremos en la variable 'respuesta'. +# IMPORTANTE: El programa se quedará pausado aquí unos milisegundos +# mientras la señal viaja por Internet y regresa. +respuesta = requests.get(direccion_pikachu) + +# status_code nos dice cómo nos fue en la llamada. +# El número 200 significa "OK, todo perfecto". (Como cuando te contestan "¡Hola, dime!") +# El número 404 significa "No encontrado". (Como cuando te dicen "El número marcado no existe") +print("Estado de la respuesta:", respuesta.status_code) + + +print("\n--- 3. Traduciendo los datos recibidos ---") +# La respuesta viene en un formato llamado JSON (texto puro). +# Para poder buscar datos fácilmente, usamos .json() para +# traducirlo a un Diccionario de Python. +datos_pokemon = respuesta.json() + +# Ahora 'datos_pokemon' es un diccionario gigante con toda la info de Pikachu. +# Como sabemos usar diccionarios, podemos acceder a sus llaves ("keys"): +print("Traducción completada con éxito.") + + +print("\n--- 4. Explorando la información de nuestro Diccionario ---") +# Usamos corchetes [] y el nombre de la llave para sacar el valor. +nombre = datos_pokemon["name"] +altura = datos_pokemon["height"] +peso = datos_pokemon["weight"] + +print(f"Nombre del Pokémon: {nombre.capitalize()}") +print(f"Altura: {altura} decímetros") +print(f"Peso: {peso} hectogramos") + +print("\n--- ¡Misión Cumplida! ---") +print("Nuestro código acaba de leer información viva desde otra computadora en el mundo.") diff --git a/python/Modulo-07-Web-APIs/61-requests/reto.md b/python/Modulo-07-Web-APIs/61-requests/reto.md new file mode 100644 index 0000000..5d31af1 --- /dev/null +++ b/python/Modulo-07-Web-APIs/61-requests/reto.md @@ -0,0 +1,31 @@ +# Reto 01: El ID Secreto 🔍 + +**URL del reto:** `https://nerve.community.aleniastudios.me/laberinto/a1b2/x9.html` + +Estás en tu primer día como scraper. La página parece simple: un chiste del día y un panel de misión. Pero visualmente, el dato que buscas **no se ve**. Está ahí en el HTML, oculto con `display:none`. + +Tu misión es extraer el **código de acceso secreto** que está escondido en un elemento con un ID específico. + +## Instrucciones Paso a Paso: + +1. Importa `requests` y `BeautifulSoup` (de `bs4`). +2. Crea una variable con la URL del reto. +3. Haz un `requests.get()` a la URL y verifica que el `status_code` sea 200. +4. Crea un objeto `BeautifulSoup` con el contenido HTML de la respuesta. +5. Inspecciona el HTML (en el navegador o leyendo la respuesta) para encontrar el elemento con el ID que contiene el dato real. +6. Extrae el texto de ese elemento y muéstralo en la terminal. + +> **Pista:** El elemento que buscas tiene un `id` específico y está estilizado con `display:none` para que el usuario no lo vea. BeautifulSoup lo extrae sin problema porque no ejecuta CSS. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `import requests`, `from bs4 import BeautifulSoup`, `requests.get()`, `.text`, `.find()`, `.find_all()`, acceso por `id`. +❌ **Conceptos Prohibidos:** Selenium, Playwright, cualquier scraper dinámico. Este reto se resuelve con HTTP estático. + +## Resultado Esperado en tu Terminal: + +```text +[+] Código de estado: 200 +[+] Código de acceso encontrado: XJ-900 +``` + +Escribe tu código en un nuevo archivo llamado `reto.py`. ¡El dato existe en el HTML, solo hay que saber buscarlo! diff --git a/python/Modulo-07-Web-APIs/61-requests/teoria.md b/python/Modulo-07-Web-APIs/61-requests/teoria.md new file mode 100644 index 0000000..be3f427 --- /dev/null +++ b/python/Modulo-07-Web-APIs/61-requests/teoria.md @@ -0,0 +1,58 @@ +# Teoría: Llamando al Restaurante de Internet ☎️🍕 + +Imagina que quieres pedir una pizza. No puedes mágicamente hacer que la pizza aparezca en tu mesa. Necesitas: +1. Un teléfono. +2. El número del restaurante (la dirección). +3. Hacer la llamada pidiendo el menú. +4. Escuchar la respuesta del restaurante. + +En Python, hacer que tu programa pida datos a Internet es exactamente igual. + +### 1. El Teléfono: `requests` +Python no trae el teléfono activado por defecto. Tenemos que importarlo. Usamos una librería llamada `requests` (que significa "peticiones"). + +### 2. El Número del Restaurante: `URL` +Las direcciones en Internet se llaman URLs (ej: `https://pokeapi.co/...`). Es el número al que vamos a llamar. + +### 3. Haciendo la llamada: `.get()` +Para llamar, usamos `requests.get(url)`. +La palabra `get` significa "obtener". Le estamos diciendo a Python: "Ve a esta dirección y **obtén** lo que tengan ahí". + +### 4. Escuchando la respuesta: `.json()` +Cuando el servidor responde, a menudo nos manda los datos en un formato universal llamado **JSON** (es como el idioma internacional de Internet). +Pero nosotros en Python trabajamos con **Diccionarios** (¿recuerdas las listas con llaves y valores?). +Usamos el método `.json()` para decirle a Python: *"Traduce lo que nos mandaron en JSON y conviértelo en un Diccionario de Python para poder usarlo"*. + +--- + +## Anatomía de la Petición + +```python +import requests # 1. Traemos el teléfono + +url = "https://api.clima.com/hoy" # 2. Anotamos el número a llamar + +respuesta = requests.get(url) # 3. Hacemos la llamada y guardamos lo que nos contestan en 'respuesta' + +diccionario = respuesta.json() # 4. Traducimos la respuesta a un diccionario de Python +``` + +### Los Símbolos Nuevos: +- `requests.get()`: El punto `.` significa "del módulo requests, usa la herramienta get". +- `.json()`: Los paréntesis `()` al final significan "ejecuta la acción de traducir ahora mismo". Si olvidas los paréntesis, Python no hará la traducción. + +--- + +## ¿Qué pasa si me equivoco? (El Panel de Errores) + +**Error 1: `requests.exceptions.MissingSchema: Invalid URL`** +- **Por qué pasa:** Olvidaste poner `http://` o `https://` al principio de tu URL. Python no sabe si estás intentando abrir un archivo local o llamando a Internet. +- **Solución:** Asegúrate de que tu URL empiece con `https://`. + +**Error 2: `json.decoder.JSONDecodeError`** +- **Por qué pasa:** Llamaste a `.json()` en una página web normal (como `https://google.com`), que te devuelve código de página web (HTML), no datos en formato JSON. +- **Solución:** Asegúrate de que la URL a la que llamas está diseñada para devolver datos (lo que llamamos una "API"). + +**Error 3: `NameError: name 'requests' is not defined`** +- **Por qué pasa:** Olvidaste poner `import requests` al principio del archivo. Python no sabe qué es ese "teléfono" que intentas usar. +- **Solución:** Importa siempre el módulo arriba del todo. diff --git a/python/Modulo-07-Web-APIs/61-requests/test_main.py b/python/Modulo-07-Web-APIs/61-requests/test_main.py new file mode 100644 index 0000000..ba82b9e --- /dev/null +++ b/python/Modulo-07-Web-APIs/61-requests/test_main.py @@ -0,0 +1,32 @@ +"""test_main.py""" +import ast, os, sys +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def _parse(): + with open(RETO) as f: + return ast.parse(f.read()) +def _codigo(): + with open(RETO) as f: + return f.read() +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_importa_requests(): + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "requests" in imports or "bs4" in imports or "selenium" in imports or "aiohttp" in imports, \ + "Debes importar la librería correspondiente al nivel" +def test_especifico(): + code = _codigo() + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "requests" in imports diff --git a/python/Modulo-07-Web-APIs/62-json/.gitkeep b/python/Modulo-07-Web-APIs/62-json/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-07-Web-APIs/62-json/README.md b/python/Modulo-07-Web-APIs/62-json/README.md new file mode 100644 index 0000000..673a701 --- /dev/null +++ b/python/Modulo-07-Web-APIs/62-json/README.md @@ -0,0 +1,16 @@ +# Nivel 62: El Idioma de Internet (JSON) 📦 + +En el nivel anterior usamos `requests` para pedir datos a Internet y mágicamente usamos `.json()` para entenderlos. Pero, ¿qué es exactamente **JSON**? + +Imagina que quieres enviarle tu ropero a un amigo por correo. No puedes meter el mueble armado en el buzón. Tienes que **desarmarlo**, meterlo en una caja plana (texto), enviarlo, y tu amigo tendrá que **armarlo** de nuevo al recibirlo. + +Internet funciona igual. Las computadoras no pueden enviarse "Diccionarios de Python" entre sí, porque un servidor en Japón podría estar usando Java, y otro en México podría usar PHP. + +Para entenderse, usan un idioma universal de "cajas planas" llamado **JSON** (JavaScript Object Notation). Todo lo que viaja por Internet se convierte en texto JSON. + +## Tu ruta de aprendizaje hoy: +1. **Teoría (`teoria.md`)**: Entenderemos cómo empacar (Diccionario -> JSON) y desempacar (JSON -> Diccionario) cajas de datos. +2. **Ejemplo (`ejemplo.py`)**: Traduciremos un mensaje de texto a Python, y luego armaremos nuestro propio mensaje para el mundo. +3. **Reto (`reto.md`)**: Interceptarás un mensaje codificado, lo modificarás y lo volverás a empacar. + +¡Prepárate para ser un traductor universal de datos! diff --git a/python/Modulo-07-Web-APIs/62-json/ejemplo.py b/python/Modulo-07-Web-APIs/62-json/ejemplo.py new file mode 100644 index 0000000..ff31e4e --- /dev/null +++ b/python/Modulo-07-Web-APIs/62-json/ejemplo.py @@ -0,0 +1,43 @@ +# Importamos la herramienta oficial de Python para traducir JSON +import json + +print("--- 1. Recibiendo un paquete de Internet ---") +# Imagina que este texto llegó a través de la red. +# Fíjate que está envuelto en comillas simples por fuera ('') +# porque adentro usa comillas dobles ("") obligatorias de JSON. +paquete_recibido = '{"personaje": "Link", "juego": "Ocarina of Time", "corazones": 3}' + +print(f"Acabamos de recibir este texto: {paquete_recibido}") +print("Tipo de dato recibido:", type(paquete_recibido)) # Nos dirá que es 'str' (String/Texto) + + +print("\n--- 2. Desempacando el paquete (Texto -> Diccionario) ---") +# json.loads() significa "Load String" (Cargar Texto). +# Toma el texto muerto y le da vida como un Diccionario de Python. +inventario = json.loads(paquete_recibido) + +print("Tipo de dato después de desempacar:", type(inventario)) # Nos dirá que es 'dict' + +# ¡Como es un diccionario, ahora podemos acceder a sus partes fácilmente! +print(f"El héroe es {inventario['personaje']} y tiene {inventario['corazones']} corazones.") + + +print("\n--- 3. Empacando un nuevo paquete (Diccionario -> Texto) ---") +# Hemos terminado nuestra misión y queremos avisar al servidor. +# Primero, armamos nuestro diccionario en Python con total libertad. +mi_mensaje = { + "usuario": "Link", + "accion": "Derrotar Jefe", + "mision_cumplida": True # Nota que en Python usamos True en mayúscula +} + +# json.dumps() significa "Dump String" (Volcar a Texto). +# Aplasta el diccionario para convertirlo en formato universal. +paquete_para_enviar = json.dumps(mi_mensaje) + +print("Paquete empacado y listo para viajar por la red:") +print(paquete_para_enviar) +print("Tipo de dato ahora:", type(paquete_para_enviar)) # Vuelve a ser 'str' + +print("\n--- ¡Operación JSON Exitosa! ---") +print("Ya sabes hablar el idioma universal de las computadoras.") diff --git a/python/Modulo-07-Web-APIs/62-json/reto.md b/python/Modulo-07-Web-APIs/62-json/reto.md new file mode 100644 index 0000000..45bd209 --- /dev/null +++ b/python/Modulo-07-Web-APIs/62-json/reto.md @@ -0,0 +1,30 @@ +# Reto 02: Ruido de Clases 📡 + +**URL del reto:** `https://nerve.community.aleniastudios.me/laberinto/8f4c/k3.html` + +La página muestra una meditación diaria: texto zen, colores suaves, tranquilidad... Pero dentro del HTML hay elementos **ocultos visualmente** con CSS (`opacity: 0`, `position: absolute`). El scraper no distingue entre lo visible y lo invisible: lo lee todo. + +Tu misión es encontrar la **dirección IP** escondida entre el ruido visual de la página. + +## Instrucciones Paso a Paso: + +1. Importa `requests` y `BeautifulSoup`. +2. Haz un `requests.get()` a la URL del reto. +3. Parsea el HTML con BeautifulSoup. +4. El dato real está en un elemento con una clase específica (busca en el HTML `hidden-data` o similar). +5. Extrae el texto de ese elemento y muéstralo. + +> **Pista:** Hay varios elementos que parecen candidatos, pero solo uno tiene la clase exacta `hidden-data`. Los otros son señuelos visuales. Un humano no los ve, pero `find()` con la clase correcta te lleva directo al dato. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `requests`, `BeautifulSoup`, `.find()`, `.find_all()`, atributo `class`. +❌ **Conceptos Prohibidos:** Selenium, expresiones regulares complejas para parsear HTML directamente. + +## Resultado Esperado en tu Terminal: + +```text +[+] Código de estado: 200 +[+] Dirección IP encontrada: 192.168.1.42 +``` + +Escribe tu código en `reto.py`. Recuerda: el CSS engaña al ojo, pero no al parser. diff --git a/python/Modulo-07-Web-APIs/62-json/teoria.md b/python/Modulo-07-Web-APIs/62-json/teoria.md new file mode 100644 index 0000000..4252dc6 --- /dev/null +++ b/python/Modulo-07-Web-APIs/62-json/teoria.md @@ -0,0 +1,50 @@ +# Teoría: Empacando y Desempacando Cajas 📦 + +Para trabajar con JSON, Python incluye una herramienta de fábrica. No necesitas instalar nada de Internet, solo sacarla de la caja de herramientas con `import json`. + +Esta herramienta tiene dos funciones mágicas principales: + +### 1. Desempacar: `json.loads()` +La 's' al final significa **String** (Texto). `loads` se lee como *"Load String"* (Cargar Texto). +Toma una caja plana (un texto en formato JSON) y la "arma" convirtiéndola en un **Diccionario de Python** real que puedes usar. + +```python +texto_internet = '{"nombre": "Mario"}' # Esto es solo texto +diccionario = json.loads(texto_internet) # ¡Magia! Ahora es un diccionario +``` + +### 2. Empacar: `json.dumps()` +La 's' también significa **String**. `dumps` se lee como *"Dump String"* (Volcar a Texto). +Toma tu hermoso **Diccionario de Python** y lo "aplasta" en un texto plano (JSON) para que puedas enviarlo por la red. + +```python +mi_diccionario = {"nombre": "Luigi"} # Diccionario de Python +texto_para_enviar = json.dumps(mi_diccionario) # Ahora es texto JSON +``` + +--- + +## Anatomía de un JSON + +A simple vista, un JSON se ve idéntico a un diccionario de Python, pero tiene una regla de oro estricta: **Las llaves siempre deben llevar comillas dobles `""`**. + +```json +{ + "jugador": "Zelda", + "vidas": 3, + "tiene_espada": true +} +``` +*Nota: En JSON los booleanos van en minúscula (`true`, `false`), pero al usar `json.loads()`, Python los convierte automáticamente a sus propios booleanos (`True`, `False`).* + +--- + +## ¿Qué pasa si me equivoco? (El Panel de Errores) + +**Error 1: `json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes`** +- **Por qué pasa:** Intentaste usar `json.loads()` en un texto que usaba comillas simples `''` en lugar de dobles `""` para los nombres de las propiedades. JSON es un lenguaje muy especial y solo acepta comillas dobles. +- **Solución:** Asegúrate de que el texto que estás desempacando sea un JSON válido con comillas dobles. + +**Error 2: `TypeError: the JSON object must be str, bytes or bytearray, not dict`** +- **Por qué pasa:** Intentaste hacer `json.loads()` (desempacar) en algo que ¡ya era un diccionario! Solo puedes desempacar texto (cajas planas). +- **Solución:** Verifica si tus datos ya son un diccionario antes de intentar convertirlos. diff --git a/python/Modulo-07-Web-APIs/62-json/test_main.py b/python/Modulo-07-Web-APIs/62-json/test_main.py new file mode 100644 index 0000000..1954d8d --- /dev/null +++ b/python/Modulo-07-Web-APIs/62-json/test_main.py @@ -0,0 +1,32 @@ +"""test_main.py""" +import ast, os, sys +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def _parse(): + with open(RETO) as f: + return ast.parse(f.read()) +def _codigo(): + with open(RETO) as f: + return f.read() +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_importa_requests(): + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "requests" in imports or "bs4" in imports or "selenium" in imports or "aiohttp" in imports, \ + "Debes importar la librería correspondiente al nivel" +def test_especifico(): + code = _codigo() + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "json" in imports diff --git a/python/Modulo-07-Web-APIs/63-beautifulsoup/.gitkeep b/python/Modulo-07-Web-APIs/63-beautifulsoup/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-07-Web-APIs/63-beautifulsoup/README.md b/python/Modulo-07-Web-APIs/63-beautifulsoup/README.md new file mode 100644 index 0000000..52e9b28 --- /dev/null +++ b/python/Modulo-07-Web-APIs/63-beautifulsoup/README.md @@ -0,0 +1,16 @@ +# Nivel 63: Las Tijeras Mágicas (BeautifulSoup) ✂️🍲 + +En los niveles anteriores aprendimos a hablar con servidores usando `requests` y a leer sus respuestas usando `json`. ¡Eso es perfecto cuando la otra computadora coopera y nos manda un JSON limpio! + +Pero, ¿qué pasa si queremos leer información de una página web normal (como un blog, una tienda o un periódico)? Las páginas web no están en JSON, están escritas en un lenguaje desordenado llamado **HTML**. + +HTML está lleno de "etiquetas" de diseño como `

`, `

`, o `` que a nosotros no nos interesan. Nosotros solo queremos el **texto** de la noticia o el **precio** del producto. + +A la técnica de extraer datos útiles de una página web normal se le llama **Web Scraping** (raspar la web). Y para lograrlo, usaremos una librería de Python legendaria llamada **BeautifulSoup** (Sopa Hermosa). + +## Tu ruta de aprendizaje hoy: +1. **Teoría (`teoria.md`)**: Descubriremos cómo usar esta tijera mágica para recortar la basura visual y quedarnos con los datos. +2. **Ejemplo (`ejemplo.py`)**: Analizaremos un trozo de HTML falso para ver cómo funciona el recorte de etiquetas. +3. **Reto (`reto.md`)**: Juntarás tus conocimientos de `requests` y `BeautifulSoup` para robar los textos principales de una página web real. + +¡Alista tus tijeras, es hora de diseccionar Internet! diff --git a/python/Modulo-07-Web-APIs/63-beautifulsoup/ejemplo.py b/python/Modulo-07-Web-APIs/63-beautifulsoup/ejemplo.py new file mode 100644 index 0000000..844c302 --- /dev/null +++ b/python/Modulo-07-Web-APIs/63-beautifulsoup/ejemplo.py @@ -0,0 +1,49 @@ +# De la caja bs4, importa solo la herramienta BeautifulSoup +from bs4 import BeautifulSoup + +print("--- 1. El periódico desordenado (HTML) ---") +# Esto es lo que verías si miras el código de una página web cruda. +# Está lleno de 'etiquetas' de formato enceradas en símbolos < >. +html_crudo = """ + + + Mi Gran Página + + +

Noticia de Última Hora

+

Un estudiante de Python aprende a raspar la web.

+ + +""" +print("Tenemos el HTML desordenado en nuestra memoria.") + + +print("\n--- 2. Preparando la Sopa ---") +# Le entregamos el texto sucio a BeautifulSoup. +# 'html.parser' le indica a la herramienta que estamos leyendo reglas web (HTML). +sopa = BeautifulSoup(html_crudo, 'html.parser') +print("Sopa mágica preparada y lista para buscar.") + + +print("\n--- 3. Recortando etiquetas ---") +# find() busca de arriba a abajo y se detiene en el primer resultado que coincida. +# 'title' suele ser el nombre de la pestaña en el navegador. +recorte_titulo = sopa.find('title') + +# 'h1' (Header 1) suele ser el titular gigante de la página. +recorte_h1 = sopa.find('h1') + +print("¡Cuidado! Los recortes todavía tienen las feas etiquetas HTML:") +print(recorte_titulo) +print(recorte_h1) + + +print("\n--- 4. Limpiando la información (.text) ---") +# .text borra toda la basura de los lados < > y nos deja el oro puro. +titulo_limpio = recorte_titulo.text +h1_limpio = recorte_h1.text + +print(f"Título de la pestaña: {titulo_limpio}") +print(f"Titular principal: {h1_limpio}") + +print("\n--- ¡Magia completada! ---") diff --git a/python/Modulo-07-Web-APIs/63-beautifulsoup/reto.md b/python/Modulo-07-Web-APIs/63-beautifulsoup/reto.md new file mode 100644 index 0000000..4e6ec00 --- /dev/null +++ b/python/Modulo-07-Web-APIs/63-beautifulsoup/reto.md @@ -0,0 +1,30 @@ +# Reto 03: Tabla Falsa 🍪 + +**URL del reto:** `https://nerve.community.aleniastudios.me/laberinto/tz99/data-401.html` + +El usuario ve una receta de galletas con chispas de chocolate. Ingredientes, pasos, todo muy apetitoso. Pero dentro del HTML hay una estructura tabular **completamente oculta** (`position:absolute; left:-9999px`), disfrazada con `div` en lugar de ``. + +Tu misión es extraer el **número secreto** de esa tabla falsa que el usuario jamás verá. + +## Instrucciones Paso a Paso: + +1. Importa `requests` y `BeautifulSoup`. +2. Haz un `requests.get()` a la URL del reto. +3. Parsea el HTML con BeautifulSoup. +4. La tabla falsa está hecha con `div` que tienen clases como `row` y `cell`. Busca el `div` contenedor `table-wrap` o navega buscando la celda con el valor oculto. +5. Extrae el número que está en la celda de datos y muéstralo. + +> **Pista:** En un `
` real usarías `find('table')`. Aquí la tabla está construida con `
` y `
`. Usa `.find_all()` para localizar las celdas. El dato real es un número de 2 dígitos. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `requests`, `BeautifulSoup`, `.find()`, `.find_all()`, `.get_text()`, `.string`, navegación por clases CSS. +❌ **Conceptos Prohibidos:** Selenium, pandas para este reto. + +## Resultado Esperado en tu Terminal: + +```text +[+] Código de estado: 200 +[+] Dato extraído de la tabla oculta: 42 +``` + +Escribe tu código en `reto.py`. Los `div` también pueden ser tablas si sabes leer el CSS. diff --git a/python/Modulo-07-Web-APIs/63-beautifulsoup/teoria.md b/python/Modulo-07-Web-APIs/63-beautifulsoup/teoria.md new file mode 100644 index 0000000..e1cf3a4 --- /dev/null +++ b/python/Modulo-07-Web-APIs/63-beautifulsoup/teoria.md @@ -0,0 +1,43 @@ +# Teoría: Recortando el Periódico ✂️🗞️ + +Imagina que una página web (HTML) es como la primera plana de un periódico impreso. Está llena de cuadros, líneas, y anuncios. Tú solo quieres coleccionar los titulares de las noticias. + +**BeautifulSoup** es como unas tijeras mágicas que saben exactamente dónde empieza y termina cada titular, recortando todo el papel sobrante por ti. + +### 1. Sacando la herramienta de la caja +Como BeautifulSoup vive dentro de una librería llamada `bs4`, usamos una sintaxis nueva para no importar la caja entera, sino solo la tijera: +```python +from bs4 import BeautifulSoup +``` +Se lee como: *"De la librería bs4, importa solo la herramienta BeautifulSoup"*. + +### 2. Preparando la Sopa +Necesitas entregarle el texto sucio de la página web a BeautifulSoup y decirle qué "reglas" usar para leerlo (en este caso, reglas de HTML). +```python +sopa = BeautifulSoup(texto_sucio, 'html.parser') +``` + +### 3. Recortando (`.find()`) +En HTML, el texto está envuelto en etiquetas como `Mi Página` o `

Hola

`. Usamos `.find()` (encontrar) para buscar la primera vez que aparece una etiqueta específica. +```python +etiqueta = sopa.find('h1') +``` +Esto nos da el recorte completo, incluyendo las feas etiquetas de los lados: `

Hola

`. + +### 4. Limpiando el texto (`.text`) +Para quitarle el papel sobrante y quedarnos solo con las letras, usamos `.text`. +```python +texto_limpio = etiqueta.text # El resultado es simplemente "Hola" +``` + +--- + +## ¿Qué pasa si me equivoco? (El Panel de Errores) + +**Error 1: `AttributeError: 'NoneType' object has no attribute 'text'`** +- **Por qué pasa:** Le pediste a la sopa buscar una etiqueta (ej. `sopa.find('h3')`), pero esa etiqueta ¡no existía en la página! Como no la encontró, te devolvió `None` (Nada). Luego, intentaste sacarle el `.text` a la "Nada", y Python explotó. +- **Solución:** Asegúrate de que la etiqueta que buscas realmente exista en el texto. Puedes usar un bloque `if etiqueta != None:` para estar seguro antes de extraer el `.text`. + +**Error 2: Obtener `

Hola

` en la terminal en vez de `Hola`** +- **Por qué pasa:** Olvidaste agregar `.text` al final de tu variable. Imprimiste el recorte entero con todo y etiquetas. +- **Solución:** Siempre que quieras leer palabras humanas, recuerda agregar `.text` al recorte encontrado. diff --git a/python/Modulo-07-Web-APIs/63-beautifulsoup/test_main.py b/python/Modulo-07-Web-APIs/63-beautifulsoup/test_main.py new file mode 100644 index 0000000..1ef6614 --- /dev/null +++ b/python/Modulo-07-Web-APIs/63-beautifulsoup/test_main.py @@ -0,0 +1,32 @@ +"""test_main.py""" +import ast, os, sys +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def _parse(): + with open(RETO) as f: + return ast.parse(f.read()) +def _codigo(): + with open(RETO) as f: + return f.read() +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_importa_requests(): + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "requests" in imports or "bs4" in imports or "selenium" in imports or "aiohttp" in imports, \ + "Debes importar la librería correspondiente al nivel" +def test_especifico(): + code = _codigo() + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "bs4" in imports diff --git a/python/Modulo-07-Web-APIs/64-selectores/.gitkeep b/python/Modulo-07-Web-APIs/64-selectores/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-07-Web-APIs/64-selectores/README.md b/python/Modulo-07-Web-APIs/64-selectores/README.md new file mode 100644 index 0000000..feed5ca --- /dev/null +++ b/python/Modulo-07-Web-APIs/64-selectores/README.md @@ -0,0 +1,16 @@ +# Nivel 64: Cazando con Precisión (Selectores) 🎯 + +En el nivel anterior, usamos `.find('p')` para encontrar un párrafo. Pero piensa en el mundo real: una página de noticias no tiene un solo párrafo, ¡tiene miles! + +Si usas `.find('p')`, BeautifulSoup te devolverá el primer párrafo que vea (que probablemente sea algo aburrido como "Derechos reservados 2026"). + +Para extraer los datos valiosos (como los precios de una tienda, o los titulares de noticias), necesitamos decirle a la tijera mágica *exactamente* dónde cortar. Para eso, las páginas web usan "etiquetas secretas" llamadas **Selectores** (`id` y `class`). + +Además, hoy aprenderemos cómo pedirle a la herramienta que no nos traiga un solo recorte, ¡sino **todos** los que encuentre en la página! + +## Tu ruta de aprendizaje hoy: +1. **Teoría (`teoria.md`)**: El supermercado HTML y la diferencia entre un código de barras (`id`) y un pasillo (`class`). +2. **Ejemplo (`ejemplo.py`)**: Saquearemos una tienda mágica falsa usando filtros precisos y bucles `for`. +3. **Reto (`reto.md`)**: Te conectarás a una página web real diseñada para hackers y extraerás todas sus frases célebres. + +¡Apunta, filtra y extrae! diff --git a/python/Modulo-07-Web-APIs/64-selectores/ejemplo.py b/python/Modulo-07-Web-APIs/64-selectores/ejemplo.py new file mode 100644 index 0000000..18c4aa3 --- /dev/null +++ b/python/Modulo-07-Web-APIs/64-selectores/ejemplo.py @@ -0,0 +1,59 @@ +from bs4 import BeautifulSoup + +print("--- 1. Entrando a la Tienda Mágica (HTML) ---") +# Observa detenidamente este HTML. +# Tiene 'id' para cosas únicas, y 'class' para cosas repetidas. +html_tienda = """ + + +

Bienvenidos a la Tienda de Hechicería

+ +
+

Poción de Vida

+

$50

+
+ +
+

Espada de Madera

+

$15

+
+ +
+

Escudo de Hierro

+

$120

+
+ + +""" +sopa = BeautifulSoup(html_tienda, 'html.parser') +print("Página procesada con éxito.") + + +print("\n--- 2. Buscando algo ÚNICO (id) ---") +# Usamos id para encontrar la etiqueta con el mensaje de bienvenida. +# Sabemos que solo hay uno en toda la página. +letrero = sopa.find('h1', id='bienvenida') +print(f"El letrero dice: {letrero.text}") + + +print("\n--- 3. El problema de buscar sin cuidado ---") +# Si solo buscamos un 'p', nos dará el primero que encuentre (la poción). +# ¡Ignorará el resto de la tienda! +primer_precio = sopa.find('p', class_='precio') +print(f"Búsqueda simple solo encontró un precio: {primer_precio.text}") + + +print("\n--- 4. Saqueando toda la tienda (find_all) ---") +# Usamos find_all() para atrapar TODOS los párrafos con clase 'precio'. +# Esto nos devuelve una LISTA de etiquetas. +lista_precios = sopa.find_all('p', class_='precio') + +print(f"¡Atrapamos {len(lista_precios)} precios en total!") +print("Revisando el carrito:") + +# Como 'lista_precios' es una lista, usamos nuestro viejo amigo el bucle 'for' +for etiqueta_precio in lista_precios: + # A cada etiqueta individual SÍ le podemos sacar el texto + print(f"- Cuesta: {etiqueta_precio.text}") + +print("\n--- ¡Misión Cumplida! ---") diff --git a/python/Modulo-07-Web-APIs/64-selectores/reto.md b/python/Modulo-07-Web-APIs/64-selectores/reto.md new file mode 100644 index 0000000..08f0f7b --- /dev/null +++ b/python/Modulo-07-Web-APIs/64-selectores/reto.md @@ -0,0 +1,31 @@ +# Reto 04: Listas Anidadas 📜 + +**URL del reto:** `https://nerve.community.aleniastudios.me/laberinto/v2n/layout_b.html` + +La página muestra un poema existencial sobre el código. Bonito, reflexivo... y completamente una distracción. Enterrada dentro de la estructura HTML hay una lista anidada oculta con `display: none` que contiene el dato real. + +Tu misión es navegar por la jerarquía de listas (`ul > li > ul > li`) para extraer el **código de identificación** escondido en las profundidades. + +## Instrucciones Paso a Paso: + +1. Importa `requests` y `BeautifulSoup`. +2. Haz un `requests.get()` a la URL del reto. +3. Parsea el HTML con BeautifulSoup. +4. Localiza el contenedor con clase `secret-structure` (está con `display: none`). +5. Dentro de ese contenedor, navega por la estructura de listas anidadas hasta encontrar el elemento con el dato. +6. Extrae el texto y muéstralo. + +> **Pista:** Usa `select()` con selectores CSS como `.secret-structure li` para encontrar todos los items de lista dentro del contenedor secreto. El valor que buscas sigue el patrón `XXXX-999-X`. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `requests`, `BeautifulSoup`, `.find()`, `.find_all()`, `.select()` con selectores CSS, `.get_text()`. +❌ **Conceptos Prohibidos:** Selenium (el dato está en el HTML estático). + +## Resultado Esperado en tu Terminal: + +```text +[+] Código de estado: 200 +[+] Código de identificación: ZETA-991-A +``` + +Escribe tu código en `reto.py`. Las listas anidadas son un laberinto, pero los selectores CSS son tu mapa. diff --git a/python/Modulo-07-Web-APIs/64-selectores/teoria.md b/python/Modulo-07-Web-APIs/64-selectores/teoria.md new file mode 100644 index 0000000..a5519d5 --- /dev/null +++ b/python/Modulo-07-Web-APIs/64-selectores/teoria.md @@ -0,0 +1,45 @@ +# Teoría: El Supermercado HTML 🛒 + +Imagina que BeautifulSoup es un robot al que envías a un supermercado (la página web). +Si le dices: *"Tráeme un producto (`div`)"*, el robot traerá lo primero que vea en la entrada. Para ser precisos, usamos dos tipos de etiquetas: + +### 1. El código de barras único: `id` +En HTML, un `id` es un identificador que **solo puede existir una vez** en toda la página. Es como el código de barras de un boleto ganador. +```python +# "Tráeme la etiqueta h1 que tenga el código de barras 'titulo-principal'" +unico = sopa.find('h1', id='titulo-principal') +``` + +### 2. La marca o categoría: `class` (clase) +Una `class` es una etiqueta que muchos elementos pueden compartir (ej. todos los productos en oferta tienen la clase "oferta"). +```python +# "Tráeme la PRIMERA etiqueta p que pertenezca a la categoría 'precio'" +# ¡OJO! Fíjate que class lleva un guion bajo al final (class_) +primero = sopa.find('p', class_='precio') +``` +*¿Por qué el guion bajo?* La palabra `class` es una palabra prohibida (reservada) en el lenguaje interno de Python. Si la escribimos sola, Python se confunde. Por eso, los creadores de BeautifulSoup inventaron `class_` con guion bajo. + +### 3. Traerse todo el pasillo: `.find_all()` +¿Qué pasa si queremos **todos** los precios de la tienda? Usamos `find_all()` (encontrar todos). +Esta función es especial porque no te devuelve un solo recorte de texto... ¡Te devuelve una **Lista de Python** con todos los recortes adentro! + +```python +# Esto nos da una Lista entera +carrito = sopa.find_all('p', class_='precio') + +# Como aprendimos en el Módulo 4, recorremos la lista con un bucle for: +for producto in carrito: + print(producto.text) +``` + +--- + +## ¿Qué pasa si me equivoco? (El Panel de Errores) + +**Error 1: `AttributeError: 'ResultSet' object has no attribute 'text'`** +- **Por qué pasa:** Usaste `find_all()`, lo que te devolvió una Lista completa (un carrito de supermercado). Luego intentaste hacer `carrito.text`. ¡No puedes pedirle a un carrito entero de metal que te dé un texto! Tienes que sacar los productos uno por uno con un `for` y pedirle el `.text` a cada uno. +- **Solución:** Usa un bucle `for` siempre que uses `find_all()`. + +**Error 2: `SyntaxError: invalid syntax` en tu `class="precio"`** +- **Por qué pasa:** Olvidaste el guion bajo. Escribiste `class="precio"` en vez de `class_="precio"`. +- **Solución:** Agrega el guion bajo al final de la palabra `class_`. diff --git a/python/Modulo-07-Web-APIs/64-selectores/test_main.py b/python/Modulo-07-Web-APIs/64-selectores/test_main.py new file mode 100644 index 0000000..9a030d4 --- /dev/null +++ b/python/Modulo-07-Web-APIs/64-selectores/test_main.py @@ -0,0 +1,32 @@ +"""test_main.py""" +import ast, os, sys +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def _parse(): + with open(RETO) as f: + return ast.parse(f.read()) +def _codigo(): + with open(RETO) as f: + return f.read() +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_importa_requests(): + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "requests" in imports or "bs4" in imports or "selenium" in imports or "aiohttp" in imports, \ + "Debes importar la librería correspondiente al nivel" +def test_especifico(): + code = _codigo() + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "select" in code or "select_one" in code diff --git a/python/Modulo-07-Web-APIs/65-selenium/.gitkeep b/python/Modulo-07-Web-APIs/65-selenium/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-07-Web-APIs/65-selenium/README.md b/python/Modulo-07-Web-APIs/65-selenium/README.md new file mode 100644 index 0000000..8a5d8f5 --- /dev/null +++ b/python/Modulo-07-Web-APIs/65-selenium/README.md @@ -0,0 +1,16 @@ +# Nivel 65: El Robot Fantasma (Selenium) 🤖👻 + +Con `requests` y `BeautifulSoup` aprendiste a extraer texto de las páginas. Pero a veces, eso no es suficiente. + +¿Qué pasa si necesitas iniciar sesión en una página? ¿Qué pasa si necesitas hacer clic en un botón para que aparezca la información? ¿O qué pasa si la página es tan moderna que su HTML viene completamente vacío y necesita cargar gráficos pesados antes de mostrar el texto (como Netflix o YouTube)? + +Para estas situaciones, `requests` se queda corto. Necesitamos enviar a alguien que pueda **abrir un navegador real**, ver la pantalla, hacer clics y teclear como si fuera un humano. + +Ese alguien es **Selenium**. Es una librería que invoca a un "Robot Fantasma" que toma el control de tu navegador (por ejemplo, Google Chrome) de forma automatizada. + +## Tu ruta de aprendizaje hoy: +1. **Teoría (`teoria.md`)**: Aprenderemos los comandos básicos para controlar el cuerpo de nuestro robot (clic, escribir, cerrar). +2. **Ejemplo (`ejemplo.py`)**: Veremos cómo el robot abre Chrome solo, llena un formulario de inicio de sesión y hace clic. +3. **Reto (`reto.md`)**: Programarás a tu robot para que entre a Wikipedia y busque un artículo automáticamente sin que tú toques el ratón. + +¡Prepárate para ver cómo tu computadora cobra vida sola! diff --git a/python/Modulo-07-Web-APIs/65-selenium/ejemplo.py b/python/Modulo-07-Web-APIs/65-selenium/ejemplo.py new file mode 100644 index 0000000..73c9207 --- /dev/null +++ b/python/Modulo-07-Web-APIs/65-selenium/ejemplo.py @@ -0,0 +1,51 @@ +# Importamos nuestro robot y su brújula +from selenium import webdriver +from selenium.webdriver.common.by import By +# Importamos la herramienta para hacer pausas en el tiempo +import time + +print("--- 1. Invocando al Robot Fantasma ---") +robot = webdriver.Chrome() +print("¡Observa tu barra de tareas, Chrome se acaba de abrir solo!") + +print("\n--- 2. Navegando a la zona de pruebas ---") +robot.get("http://quotes.toscrape.com/login") +print("Viajando a la página de inicio de sesión...") +# Le decimos al robot que se duerma 2 segundos para asegurar que la página cargue +time.sleep(2) + +print("\n--- 3. Llenando el formulario ---") +# Buscamos la caja del usuario por su ID +caja_usuario = robot.find_element(By.ID, "username") +# Y escribimos en ella +caja_usuario.send_keys("EstudiantePython") +print("El robot ha escrito el nombre de usuario.") + +# Pausa de 1 segundo para que tus ojos humanos puedan ver el texto +time.sleep(1) + +# Buscamos la caja de contraseña por su ID +caja_password = robot.find_element(By.ID, "password") +caja_password.send_keys("contraseña_super_secreta") +print("El robot ha escrito la contraseña.") + +# Otra pequeña pausa +time.sleep(1) + +print("\n--- 4. Presionando el botón ---") +# Buscamos el botón de login. Si miras el código de esa página, +# el botón tiene la clase "btn-primary". +boton_login = robot.find_element(By.CLASS_NAME, "btn-primary") + +# ¡Hacemos el clic! +boton_login.click() +print("¡Clic realizado! Intentando entrar...") + +# Hacemos una pausa larga de 4 segundos para que disfrutes viendo +# la página a la que acabas de entrar. +time.sleep(4) + +print("\n--- 5. Apagando al Robot ---") +# Siempre, siempre debemos destruir al robot al terminar. +robot.quit() +print("Navegador fantasma cerrado. Misión cumplida.") diff --git a/python/Modulo-07-Web-APIs/65-selenium/reto.md b/python/Modulo-07-Web-APIs/65-selenium/reto.md new file mode 100644 index 0000000..5d68aa6 --- /dev/null +++ b/python/Modulo-07-Web-APIs/65-selenium/reto.md @@ -0,0 +1,33 @@ +# Reto 05: Trampas Visuales ✈️ + +**URL del reto:** `https://nerve.community.aleniastudios.me/laberinto/q9/honeypot_1.html` + +Una agencia de viajes con cielos azules y palmeras. El usuario ve un precio bonito, pero el scraper ingenuo cae en una trampa: hay **múltiples elementos** con la clase `real-price` que contienen valores falsos. Solo uno, identificado por la clase adicional `actual-value`, tiene el precio correcto. + +Este es un **honeypot**: una técnica real usada por sitios web para detectar y engañar a scrapers mal programados. + +Tu misión es extraer el **precio real** sin caer en las trampas. + +## Instrucciones Paso a Paso: + +1. Importa `requests` y `BeautifulSoup`. +2. Haz un `requests.get()` a la URL del reto. +3. Parsea el HTML con BeautifulSoup. +4. **No uses** `.find_all(class_='real-price')` directamente, porque obtendrás múltiples resultados falsos. +5. Busca el elemento que tiene **ambas** clases: `real-price` y `actual-value`. +6. Extrae el texto de ese elemento específico. + +> **Pista:** Puedes pasar una lista de clases a BeautifulSoup: `soup.find(class_=['real-price', 'actual-value'])`, o usar un selector CSS: `soup.select_one('.real-price.actual-value')`. El valor es un número decimal. + +## Reglas Estrictas: +✅ **Conceptos Permitidos:** `requests`, `BeautifulSoup`, `.find()`, `.select_one()`, selección por múltiples clases CSS. +❌ **Conceptos Prohibidos:** Selenium, tomar el primer resultado sin verificar que es el correcto. + +## Resultado Esperado en tu Terminal: + +```text +[+] Código de estado: 200 +[+] Precio real encontrado: 450.00 +``` + +Escribe tu código en `reto.py`. El honeypot atrapa a los scrapers descuidados. Sé preciso. diff --git a/python/Modulo-07-Web-APIs/65-selenium/teoria.md b/python/Modulo-07-Web-APIs/65-selenium/teoria.md new file mode 100644 index 0000000..aab8ade --- /dev/null +++ b/python/Modulo-07-Web-APIs/65-selenium/teoria.md @@ -0,0 +1,51 @@ +# Teoría: El Titiritero del Navegador 🎭 + +Para usar Selenium, primero debemos importar al robot y también su "brújula" (una herramienta llamada `By` que le dice CÓMO buscar las cosas). +Además, usaremos la herramienta `time` (que viene incluida en Python) para decirle al robot que espere, porque los robots son tan rápidos que a veces intentan hacer clic antes de que la página termine de cargar. + +### 1. Invocando al Robot +```python +from selenium import webdriver +from selenium.webdriver.common.by import By +import time + +robot = webdriver.Chrome() # Abre una ventana real de Chrome +``` + +### 2. Navegando y Esperando +En lugar de `requests.get`, ahora es el robot quien viaja. +```python +robot.get("https://google.com") +time.sleep(2) # ¡Duerme 2 segundos para dar tiempo a que la página cargue! +``` + +### 3. Encontrando Cosas +En BeautifulSoup usábamos `.find()`. Nuestro robot usa `.find_element()`, pero le tenemos que decir con la brújula (`By`) si lo buscaremos por su `ID` o por su `CLASS_NAME`. +```python +boton = robot.find_element(By.ID, "id_del_boton") +``` + +### 4. Interactuando (¡La magia real!) +Al robot no solo le importa leer, ¡él puede tocar la página! +```python +boton.click() # Hace clic izquierdo +caja_texto.send_keys("Hola") # Escribe texto con el teclado fantasma +``` + +### 5. Destruyendo al Robot +Cuando terminas, es obligatorio apagar el robot. Si no lo haces, te quedarás con docenas de ventanas de Chrome abiertas consumiendo memoria. +```python +robot.quit() +``` + +--- + +## ¿Qué pasa si me equivoco? (El Panel de Errores) + +**Error 1: `NoSuchElementException: Message: no such element`** +- **Por qué pasa:** El robot intentó buscar un elemento (como un botón) y no lo encontró. Casi SIEMPRE sucede porque el robot buscó demasiado rápido y la página aún no terminaba de cargar. +- **Solución:** Pon un `time.sleep(3)` justo antes de buscar el elemento para obligar al robot a tener paciencia. + +**Error 2: ¡La ventana se abre y se cierra instantáneamente!** +- **Por qué pasa:** Las computadoras ejecutan el código en milisegundos. Llegó a `robot.quit()` antes de que pudieras parpadear. +- **Solución:** Pon un `time.sleep(5)` antes de `robot.quit()` si quieres tener tiempo de ver con tus propios ojos lo que hizo el robot antes de destruirse. diff --git a/python/Modulo-07-Web-APIs/65-selenium/test_main.py b/python/Modulo-07-Web-APIs/65-selenium/test_main.py new file mode 100644 index 0000000..7ea8174 --- /dev/null +++ b/python/Modulo-07-Web-APIs/65-selenium/test_main.py @@ -0,0 +1,32 @@ +"""test_main.py""" +import ast, os, sys +BASE = os.path.dirname(__file__) +RETO = os.path.join(BASE, "reto.py") +def _parse(): + with open(RETO) as f: + return ast.parse(f.read()) +def _codigo(): + with open(RETO) as f: + return f.read() +def test_archivo_existe(): + assert os.path.exists(RETO), "Debes crear reto.py" +def test_importa_requests(): + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "requests" in imports or "bs4" in imports or "selenium" in imports or "aiohttp" in imports, \ + "Debes importar la librería correspondiente al nivel" +def test_especifico(): + code = _codigo() + tree = _parse() + imports = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports += [a.name for a in node.names] + if isinstance(node, ast.ImportFrom): + imports.append(node.module) + assert "selenium" in imports diff --git a/python/Modulo-07-Web-APIs/66-auth-apis/.gitkeep b/python/Modulo-07-Web-APIs/66-auth-apis/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/python/Modulo-07-Web-APIs/66-auth-apis/README.md b/python/Modulo-07-Web-APIs/66-auth-apis/README.md new file mode 100644 index 0000000..6547855 --- /dev/null +++ b/python/Modulo-07-Web-APIs/66-auth-apis/README.md @@ -0,0 +1,16 @@ +# Nivel 66: El Pase VIP (APIs con Autenticación) 🎟️ + +Hasta ahora, hemos estado paseando por APIs públicas (como PokeAPI). Piensa en ellas como un parque público: cualquiera puede entrar, pedir datos y salir sin dar explicaciones. + +Pero en el mundo real, los datos cuestan dinero. Empresas como OpenAI, Spotify o Google Maps no te dejarán usar sus servidores gratis e ilimitadamente. Tienen "Clubes Privados". + +Para entrar a un Club Privado (una API Autenticada), necesitas registrarte en su página web y obtener una **API Key** (Llave de API) o un **Token**. Es como un pase VIP o una pulsera mágica. + +Si intentas pedir datos sin mostrar tu pulsera, el servidor (el cadenero del club) te rechazará con un error. + +## Tu ruta de aprendizaje hoy: +1. **Teoría (`teoria.md`)**: Entenderemos qué son los `headers` (cabeceras) y cómo ocultar ahí nuestra pulsera VIP. +2. **Ejemplo (`ejemplo.py`)**: Intentaremos entrar a un club privado sin pase y seremos rechazados, para luego entrar triunfalmente usando un token. +3. **Reto (`reto.md`)**: Te convertirás en un agente secreto enviando credenciales ocultas a un servidor clasificado. + +¡Saca tu pase VIP y prepárate para entrar a las grandes ligas! diff --git a/python/Modulo-07-Web-APIs/66-auth-apis/ejemplo.py b/python/Modulo-07-Web-APIs/66-auth-apis/ejemplo.py new file mode 100644 index 0000000..04e8266 --- /dev/null +++ b/python/Modulo-07-Web-APIs/66-auth-apis/ejemplo.py @@ -0,0 +1,43 @@ +import requests + +# httpbin.org es un servidor de pruebas gratuito. +# La ruta /bearer simula un club privado. SOLO te deja entrar +# si le envías una credencial (Token). +url_vip = "https://httpbin.org/bearer" + + +print("--- 1. Intentando entrar SIN pase VIP ---") +# Intentamos entrar como si fuera una API pública +respuesta_rechazada = requests.get(url_vip) + +print(f"Código del cadenero: {respuesta_rechazada.status_code}") +if respuesta_rechazada.status_code == 401: + print("❌ ¡Acceso Denegado! No tenemos pulsera VIP.") + + +print("\n--- 2. Preparando la Billetera (Headers) ---") +# Creamos un diccionario (nuestra billetera) +billetera = { + # La llave es 'Authorization' (así lo exigen los servidores) + # El valor es 'Bearer ' seguido de nuestra contraseña + "Authorization": "Bearer contraseña_secreta_del_estudiante" +} +print("Billetera preparada con éxito.") + + +print("\n--- 3. Intentando entrar CON pase VIP ---") +# Hacemos la llamada, pero esta vez le entregamos la billetera +# usando la instrucción: headers=billetera +respuesta_aceptada = requests.get(url_vip, headers=billetera) + +print(f"Código del cadenero: {respuesta_aceptada.status_code}") +if respuesta_aceptada.status_code == 200: + print("✅ ¡Acceso Concedido! Las puertas se abren.") + + print("\n--- 4. Viendo lo que hay dentro ---") + # Como entramos, el servidor nos devuelve un JSON con nuestros datos + datos_secretos = respuesta_aceptada.json() + + # El servidor nos repite el token que usamos para demostrar que lo leyó + token_leido = datos_secretos["token"] + print(f"El servidor dice que nuestro token fue: {token_leido}") diff --git a/python/Modulo-07-Web-APIs/66-auth-apis/reto.md b/python/Modulo-07-Web-APIs/66-auth-apis/reto.md new file mode 100644 index 0000000..0beb48c --- /dev/null +++ b/python/Modulo-07-Web-APIs/66-auth-apis/reto.md @@ -0,0 +1,35 @@ +# Reto 06: Generación Dinámica ⚡ + +**URL del reto:** `https://nerve.community.aleniastudios.me/laberinto/m5v/dyn.html` + +La página muestra una "Calculadora Cuántica" procesando la respuesta a la vida, el universo y todo lo demás. El resultado se **inyecta en el DOM mediante JavaScript** después de que la página carga. Un scraper estático como BeautifulSoup solo descargará el HTML inicial (con el `div` vacío), sin ver nunca el dato calculado. + +Tu misión: extraer el **número calculado** usando una de dos estrategias válidas. + +## Instrucciones Paso a Paso: + +**Estrategia A — Parsear el bloque `