LABS · JULIÁN LÓPEZ JIMÉNEZ · RECURSO GRATUITO

En mi ordenador pasa, en el pipeline falla

Tres diagnósticos de CI y una práctica real descargable para investigar un archivo ausente, una regresión y un fallo silenciado.

Diagnóstico: evidencias preparadas, sin ejecutar Python y servicios

Resuelve causa, evidencia, corrección mínima y prueba funcional antes de abrir la solución. Después realiza la práctica real.

Archivo ausente

Predice la causa antes de abrir las pistas.

Caso preparado
No son resultados de tu equipo o de GitHub

Local: datos.json
Commit: Datos.json
Ubuntu: FileNotFoundError: datos.json
Causa principal
  1. El nombre versionado no coincide exactamente con la ruta leída.
  2. La conexión al servicio falló antes de ejecutar este paso.
  3. El resultado pertenece a otra ejecución anterior a este cambio.
Evidencia determinante
  1. Una captura de otra ejecución sin SHA.
  2. El nombre del workflow sin evento, código de salida ni logs.
  3. Ubuntu: FileNotFoundError: datos.json
Corrección mínima
  1. Cambiar dependencias sin contrastar versión, comando y contrato.
  2. Unificar el nombre exacto y usar una ruta derivada de __file__.
  3. Reintentar sin modificar ni investigar la causa.
Cómo verificarlo
  1. Revisar git ls-files y repetir en checkout limpio, además del caso sensible a mayúsculas.
  2. Observar solo que el último icono es verde.
  3. Comprobar únicamente un archivo de otra release.
Pista 1 de 3

Separa ejecución, evidencia y decisión de entrega.

Pista 2 de 3

El nombre versionado no coincide exactamente con la ruta leída.

Pista 3 de 3

Unificar el nombre exacto y usar una ruta derivada de __file__.

Solución razonada
  1. El nombre versionado no coincide exactamente con la ruta leída.
  2. Ubuntu: FileNotFoundError: datos.json
  3. Unificar el nombre exacto y usar una ruta derivada de __file__.
  4. Revisar git ls-files y repetir en checkout limpio, además del caso sensible a mayúsculas.

El nombre versionado no coincide exactamente con la ruta leída. Unificar el nombre exacto y usar una ruta derivada de __file__. Revisar git ls-files y repetir en checkout limpio, además del caso sensible a mayúsculas. Repite después la práctica real; las notas requieren revisión personal.

El verde insuficiente

Predice la causa antes de abrir las pistas.

Caso preparado
No son resultados de tu equipo o de GitHub

El precio de dos unidades a 125 céntimos sale como 125.
pytest: expected 250, got 125; 1 failed.
Causa principal
  1. La conexión al servicio falló antes de ejecutar este paso.
  2. El resultado pertenece a otra ejecución anterior a este cambio.
  3. La implementación ignora la cantidad.
Evidencia determinante
  1. El nombre del workflow sin evento, código de salida ni logs.
  2. pytest: expected 250, got 125; 1 failed.
  3. Una captura de otra ejecución sin SHA.
Corrección mínima
  1. Restaurar precio * cantidad, manteniendo el contrato del test.
  2. Reintentar sin modificar ni investigar la causa.
  3. Cambiar dependencias sin contrastar versión, comando y contrato.
Cómo verificarlo
  1. Observar solo que el último icono es verde.
  2. Comprobar únicamente un archivo de otra release.
  3. Repetir el caso de dos unidades y los rechazos de cero y booleanos.
Pista 1 de 3

Separa ejecución, evidencia y decisión de entrega.

Pista 2 de 3

La implementación ignora la cantidad.

Pista 3 de 3

Restaurar precio * cantidad, manteniendo el contrato del test.

Solución razonada
  1. La implementación ignora la cantidad.
  2. pytest: expected 250, got 125; 1 failed.
  3. Restaurar precio * cantidad, manteniendo el contrato del test.
  4. Repetir el caso de dos unidades y los rechazos de cero y booleanos.

La implementación ignora la cantidad. Restaurar precio * cantidad, manteniendo el contrato del test. Repetir el caso de dos unidades y los rechazos de cero y booleanos. Repite después la práctica real; las notas requieren revisión personal.

El fallo silenciado

Predice la causa antes de abrir las pistas.

Caso preparado
No son resultados de tu equipo o de GitHub

python -m pytest devuelve 1.
Paso: continue-on-error: true
Trabajo: success.
Causa principal
  1. El resultado pertenece a otra ejecución anterior a este cambio.
  2. La política permite continuar tras una prueba fallida.
  3. La conexión al servicio falló antes de ejecutar este paso.
Evidencia determinante
  1. Trabajo: success.
  2. Una captura de otra ejecución sin SHA.
  3. El nombre del workflow sin evento, código de salida ni logs.
Corrección mínima
  1. Reintentar sin modificar ni investigar la causa.
  2. Cambiar dependencias sin contrastar versión, comando y contrato.
  3. Retirar continue-on-error del paso que decide la calidad.
Cómo verificarlo
  1. Comprobar únicamente un archivo de otra release.
  2. Introducir el defecto de cantidad y confirmar que package y staging no se ejecutan.
  3. Observar solo que el último icono es verde.
Pista 1 de 3

Separa ejecución, evidencia y decisión de entrega.

Pista 2 de 3

La política permite continuar tras una prueba fallida.

Pista 3 de 3

Retirar continue-on-error del paso que decide la calidad.

Solución razonada
  1. La política permite continuar tras una prueba fallida.
  2. Trabajo: success.
  3. Retirar continue-on-error del paso que decide la calidad.
  4. Introducir el defecto de cantidad y confirmar que package y staging no se ejecutan.

La política permite continuar tras una prueba fallida. Retirar continue-on-error del paso que decide la calidad. Introducir el defecto de cantidad y confirmar que package y staging no se ejecutan. Repite después la práctica real; las notas requieren revisión personal.

Qué necesitas: Navegador con JavaScript para diagnosticar, sin cuenta ni tarjeta. Para la práctica gratuita: Python 3.12+ y terminal. Git/GitHub propios solo para el taller remoto; puede requerir cuota según tu plan.

Tu resultado: Distinguir un fallo útil de un falso verde y reproducir tres casos rojo/verde en tu equipo.

Archivo ausente

El runner solo conoce los archivos del commit. Investiga el nombre y la ruta antes de instalar paquetes. La práctica gratuita reproduce un fichero ausente en cualquier sistema; contrasta Datos.json frente a datos.json en Linux para observar literalmente la distinción de mayúsculas.

python practicar.py ruta
Necesito una pista

El nombre versionado no coincide exactamente con la ruta leída.

Ver solución de este reto
Unificar el nombre exacto y usar una ruta derivada de __file__.

Revisar git ls-files y repetir en checkout limpio, además del caso sensible a mayúsculas.

Comprueba: Observa rechazo inicial y éxito al corregir; compara con la evidencia preparada. El guion usa una carpeta temporal y no modifica tu proyecto.

El verde insuficiente

Una función que ignora cantidad puede pasar un ejemplo de una sola unidad. Comprueba dos y tres unidades para distinguirla. El proyecto gratuito es mínimo: función de céntimos y dos tests, sin API ni SQLite. Primero observa el rojo y después conserva el mismo contrato al reparar.

python practicar.py rojo
Necesito una pista

La implementación ignora la cantidad.

Ver solución de este reto
Restaurar precio * cantidad, manteniendo el contrato del test.

Repetir el caso de dos unidades y los rechazos de cero y booleanos.

Comprueba: Observa rechazo inicial y éxito al corregir; compara con la evidencia preparada. El guion usa una carpeta temporal y no modifica tu proyecto.

El fallo silenciado

El proceso de pruebas puede terminar con fallo y una política errónea permitir continuar. El guion muestra el código real, explica el falso verde y repara la implementación. No modifica tus workflows ni usa shell para ocultar la salida. En tu repositorio no añadas continue-on-error a las pruebas de calidad.

python practicar.py salida
Necesito una pista

La política permite continuar tras una prueba fallida.

Ver solución de este reto
Retirar continue-on-error del paso que decide la calidad.

Introducir el defecto de cantidad y confirmar que package y staging no se ejecutan.

Comprueba: Observa rechazo inicial y éxito al corregir; compara con la evidencia preparada. El guion usa una carpeta temporal y no modifica tu proyecto.

Archivo: app.py

Crea este archivo con su nombre y ruta exactos dentro de una carpeta nueva. Crea .github/workflows para el YAML; no pegues YAML dentro de Python. El ZIP gratuito contiene la misma estructura.

def total(precio, cantidad):
    return precio * cantidad

Comprueba: Conserva el nombre indicado y repite los tres comandos de práctica desde la raíz del proyecto.

Archivo: practicar.py

Crea este archivo con su nombre y ruta exactos dentro de una carpeta nueva. Crea .github/workflows para el YAML; no pegues YAML dentro de Python. El ZIP gratuito contiene la misma estructura.

"""Casos rojos/verdes aislados; no modifica app.py ni ejecuta shell."""
from pathlib import Path
import argparse
import json
import subprocess
import sys
import tempfile

HERE = Path(__file__).resolve().parent


def require(ok, message):
    if not ok:
        raise ValueError(message)


def exercise(case):
    with tempfile.TemporaryDirectory(prefix='labs-pipeline-') as directory:
        folder = Path(directory)
        if case in ('rojo', 'salida'):
            original = (HERE / 'app.py').read_text(encoding='utf-8')
            broken = original.replace('return precio * cantidad', 'return precio')
            require(broken != original, 'No se reconoce el ejemplo original de total; usa una copia nueva del laboratorio')
            (folder / 'app.py').write_text(broken, encoding='utf-8')
            program = 'import app\nif app.total(125,2)!=250: raise AssertionError("expected 250 for two units")'
            red = subprocess.run([sys.executable, '-B', '-c', program], cwd=folder, capture_output=True, text=True, timeout=15)
            require(red.returncode != 0 and 'AssertionError' in red.stderr, 'No se reprodujo el fallo esperado; revisa la salida y el intérprete')
            # Simula la política, no cambia el código de salida del proceso hijo.
            print('Fallo real:', red.returncode, red.stderr.strip().splitlines()[-1])
            if case == 'salida':
                print('Política errónea: ignorar ese fallo permitiría continuar. No lo hagas en CI.')
            (folder / 'app.py').write_text(original, encoding='utf-8')
            # Evitar caché de timestamp/longitud entre rojo y verde.
            green = subprocess.run([sys.executable, '-B', '-c', program], cwd=folder, capture_output=True, text=True, timeout=15)
            require(green.returncode == 0, 'La reparación no supera la comprobación: ' + green.stderr)
            return {'case':case, 'red':red.returncode, 'green':green.returncode, 'isolated':True}
        if case == 'ruta':
            (folder / 'Datos.json').write_text('{"unidades":2}', encoding='utf-8')
            # Nombre inequívocamente ausente incluso en Windows.
            program = 'from pathlib import Path; print(Path("no-versionado.json").read_text())'
            red = subprocess.run([sys.executable,'-c',program],cwd=folder,capture_output=True,text=True,timeout=15)
            require(red.returncode != 0 and 'FileNotFoundError' in red.stderr, 'No se reprodujo el archivo ausente esperado')
            green = subprocess.run([sys.executable,'-c',program.replace('no-versionado.json','Datos.json')],cwd=folder,capture_output=True,text=True,timeout=15)
            require(green.returncode == 0 and 'unidades' in green.stdout, 'No se pudo leer el archivo reparado')
            return {'case':case,'red':red.returncode,'green':0,'note':'Ausente en ambos SO; la distinción Datos/datos debe comprobarse en Linux.'}
        if case == 'entrada':
            data = '"; NO_EJECUTAR $(texto)\nñ'
            result = subprocess.run([sys.executable,'-X','utf8','-c','import sys; print(sys.argv[1],end="")',data],cwd=folder,capture_output=True,text=True,encoding='utf-8',timeout=15)
            require(result.returncode == 0 and result.stdout == data, 'La entrada no se conservó como texto literal')
            return {'case':case,'literal':True,'shell':False}
        raise ValueError('Caso desconocido')


if __name__ == '__main__':
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('case',choices=['rojo','ruta','salida','entrada'])
    args=parser.parse_args()
    try:
        print(json.dumps(exercise(args.case),ensure_ascii=False))
    except (OSError, ValueError, subprocess.TimeoutExpired) as error:
        parser.exit(1, 'Práctica no completada: ' + str(error) + '\n')

Comprueba: Conserva el nombre indicado y repite los tres comandos de práctica desde la raíz del proyecto.

Archivo: test_total.py

Crea este archivo con su nombre y ruta exactos dentro de una carpeta nueva. Crea .github/workflows para el YAML; no pegues YAML dentro de Python. El ZIP gratuito contiene la misma estructura.

import unittest
from app import total
class Precio(unittest.TestCase):
    def test_dos(self):
        self.assertEqual(total(125,2),250)
    def test_tres(self):
        self.assertEqual(total(99,3),297)

Comprueba: Conserva el nombre indicado y repite los tres comandos de práctica desde la raíz del proyecto.

Archivo: README.md

Crea este archivo con su nombre y ruta exactos dentro de una carpeta nueva. Crea .github/workflows para el YAML; no pegues YAML dentro de Python. El ZIP gratuito contiene la misma estructura.

# En mi ordenador pasa, en el pipeline falla

Gratis, sin cuenta ni tarjeta para descargar y practicar. Python 3.12+.
En una carpeta nueva, ejecuta `python practicar.py ruta`, `python practicar.py rojo`, `python practicar.py salida` y `python -m unittest -v`.
Cada guion observa primero un rechazo real y después una reparación en una carpeta temporal. No ejecuta una orden remota ni modifica app.py original.
Los mensajes Fallo real y red: 1 son esperados: la práctica termina correctamente solo cuando también obtiene green: 0. Si ves Práctica no completada, lee la causa y usa una copia nueva antes de modificar código. Las comprobaciones no se desactivan con Python optimizado.
En Windows comprueba python --version; si utilizas el lanzador py, sustituye python por py -3.12. La práctica gratuita usa la biblioteca estándar, sin instalar pytest ni dependencias. Los comandos se ejecutan en una terminal, cada uno en su propia línea; no pegues YAML en Python.
El caso ruta usa un archivo ausente en cualquier SO; la distinción de mayúsculas del diagnóstico necesita Linux para contrastarse literalmente.

## Tu primera ejecución real en GitHub
En un repositorio docente propio y sin datos privados, copia app.py, test_total.py y el archivo oculto .github/workflows/gratis.yml a la raíz. Revisa sus permisos y la cuota/visibilidad de tu cuenta antes de hacer push. No subas toda tu carpeta personal ni el repositorio privado de Labs. Cambia `return precio * cantidad` por `return precio`, confirma y abre una PR: el test debe fallar. Restaura la multiplicación en otro commit y contrasta el verde con la misma prueba. No quites tests ni fuerces salida cero.
Guarda el enlace del run, SHA, evento, comando, número de casos y causa corregida. Esto no despliega nada. El playground es diagnóstico preparado, no un runner de GitHub.

Comprueba: Conserva el nombre indicado y repite los tres comandos de práctica desde la raíz del proyecto.

Archivo: .github/workflows/gratis.yml

Crea este archivo con su nombre y ruta exactos dentro de una carpeta nueva. Crea .github/workflows para el YAML; no pegues YAML dentro de Python. El ZIP gratuito contiene la misma estructura.

name: Primer rojo y verde
on: [push, pull_request]
permissions:
  contents: read
jobs:
  pruebas:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
        with:
          python-version: "3.12"
      - run: python -m unittest -v

Comprueba: Conserva el nombre indicado y repite los tres comandos de práctica desde la raíz del proyecto.

Tu turno

Predice qué paso rechazaría el cambio antes de ejecutar cada caso. Repite con un precio y una cantidad distintos.

Ver solución y explicación
python practicar.py ruta
python practicar.py rojo
python practicar.py salida
python -m unittest -v

En la práctica real, el rojo es intencional: comprueba también green: 0; Práctica no completada indica que debes revisar la causa. El playground muestra evidencias preparadas; no ejecuta YAML ni GitHub. La práctica real usa subprocesos Python y archivos temporales. Descargar no exige cuenta; guardar es manual, no avanza ni concede XP.

Explícalo con tus palabras

Repite con datos distintos antes de marcarlo como practicado. Esta es una autoevaluación, no una certificación.

Si quieres continuar

El curso añade matriz, artefactos, confianza, API con SQLite, humo y retorno entre releases. La cuenta no es necesaria para completar este recurso.

Ver el curso Tu primer pipeline: de los tests al despliegue y su temario

Fuente: Recurso original de Labs. Puedes guardar este HTML, abrirlo sin conexión o imprimirlo como PDF desde tu navegador. No guarda tus respuestas ni el progreso de tu cuenta.