Serie MLOps y Producción · de la ingeniería de datos al monitoreo.
El problema que vas a resolver
Laura utiliza un servicio de alertas cuya referencia actual es corrida_02. El equipo propone otro paquete de aplicación y modelo. Antes de sustituir lo que funciona, alguien debe revisar el código, los datos, los archivos y una predicción conocida. También debe comprobar que se cumplen los mínimos acordados y que la persona responsable acepta la entrega.
Vas a convertir esa revisión en etapas ejecutables. Si una falla, las siguientes quedarán omitidas. Si todas las comprobaciones técnicas pasan, la decisión podrá quedar pendiente de revisión humana. La referencia de Laura permanecerá igual hasta que se cumplan ambas condiciones.
Ficha
En este capítulo
Pregunta
¿Quién comprueba una propuesta antes de cambiar el modelo que utiliza Laura?
Datos
Los mismos 600 envíos sintéticos y un paquete candidato identificado
Herramientas
Python, scikit-learn, joblib, FastAPI y un YAML documental
Lo que produces
Etapas de validación, compuerta de métricas, registro local y reversión comprobada
Lectura y nivel
Unos 60 minutos; introducción a comprobaciones de entrega
Condiciones
CPU local; ningún workflow remoto ni despliegue externo se ejecuta
Objetivos de aprendizaje
Al terminar este capítulo podrás:
Distinguir integración continua, entrega continua y despliegue continuo.
Escribir comprobaciones pequeñas para el código, los datos y el modelo.
Detener la cadena de controles en cuanto algo falla.
Fijar la regla de aceptación antes de medir.
Mantener la aprobación humana como una etapa separada de las métricas.
Volver a la versión anterior cuando una entrega sale mal.
Primero aplicarás tres controles a un paquete pequeño. Después prepararás el candidato y sus archivos, fijando la regla antes de medir. Recorrerás las etapas con datos incorrectos, con revisión pendiente y con permiso de demostración. Finalmente volverás a la versión anterior y leerás cómo representa ese recorrido un archivo de GitHub Actions.
TipLo que necesitas recordar
Concepto
En una línea
Dónde se explica
Pipeline y esquema
La entrada conserva columnas conocidas y una preparación aprendida.
En la salida de una fábrica revisas una etiqueta, una medida y el resultado de una operación. Puedes describir qué se acepta antes de revisar el primer producto. Si la etiqueta está incompleta, el paquete vuelve para corregirse; si una medida queda fuera de rango, tampoco continúa como si todo estuviera bien.
Para los envíos de Laura usarás tres condiciones pequeñas: deben estar los campos esperados, la distancia debe estar en el rango declarado y la decisión observada debe corresponder al umbral 0.45. Puedes comprobarlas con lápiz en el paquete de 520 km y una probabilidad de juguete de 0.76.
NotaPrueba automática
Una prueba automática compara un comportamiento observado con una condición esperada y falla si no coincide. En los ejemplos pequeños usarás assert para expresar esas condiciones. El fallo identifica qué necesitas corregir antes de continuar.
Las comprobaciones se aplican en un orden declarado antes de permitir la salida.
La primera celda registra las bibliotecas que se utilizarán. Los archivos auxiliares reúnen el motor de etapas y el servicio ya conocido. Su código puede leerse más adelante, después de que hayas visto qué comprueba cada parte.
Las versiones de python, pandas, scikit-learn, joblib y fastapi identifican el entorno de ejecución. La semilla 42 mantiene la reconstrucción de los envíos. La preparación de los archivos ocurrirá después de comprobar la lista pequeña.
def comprobar_juguete(paquete, probabilidad, alerta_observada):"""Recibe paquete y decisión; comprueba tres condiciones de la lista de salida."""assertset(paquete) == {"distancia_km", "peso_kg", "servicio"}, "Faltan campos o sobran"assert0<= paquete["distancia_km"] <=3000, "Distancia fuera del rango"assert alerta_observada == (probabilidad >=0.45), "La decisión usa otro corte"paquete = {"distancia_km": 520.0, "peso_kg": None, "servicio": "económico"}comprobar_juguete(paquete, 0.76, True)print("Lista correcta: 3 comprobaciones superadas")casos = [({"servicio": "económico"}, 0.76, True), ({**paquete, "distancia_km": -5}, 0.76, True), (paquete, 0.46, False)]for caso, probabilidad, decision in casos:try: comprobar_juguete(caso, probabilidad, decision)exceptAssertionErroras error:print("AssertionError:", str(error).splitlines()[0])
Lista correcta: 3 comprobaciones superadas
AssertionError: Faltan campos o sobran
AssertionError: Distancia fuera del rango
AssertionError: La decisión usa otro corte
La lista correcta supera las 3 comprobaciones. Los casos provocados devuelven AssertionError: Faltan campos o sobran, AssertionError: Distancia fuera del rango y AssertionError: La decisión usa otro corte. El tercer caso tiene probabilidad 0.46 y una decisión sin alerta, por lo que incumple el corte declarado de 0.45.
Las tres condiciones preguntan cosas distintas. Un paquete puede tener todos sus campos y una distancia imposible. También puede tener datos válidos y recibir una decisión que utiliza otro corte. Por eso conviene escribir mensajes que distingan qué comportamiento incumplió la expectativa.
En aplicaciones de Python se ejecutan normalmente mediante herramientas de pruebas. Aquí los assert hacen visibles las condiciones con pocas líneas. Una comprobación de operación que deba mantenerse activa bajo cualquier forma de iniciar Python puede expresarse mediante validaciones y excepciones explícitas.
2) Integrar cambios y preparar entregas
NotaIntegración continua y entrega continua
La integración continua, o CI, ejecuta comprobaciones al integrar cambios para detectar problemas pronto. La entrega continua prepara versiones que pueden entregarse después de superar controles. El despliegue continuo añade el paso de ponerlas en uso automáticamente cuando se cumplen las condiciones definidas.
Para Laura, CI puede comprobar que una modificación sigue aceptando el JSON del paquete y devolviendo la versión del modelo. La entrega puede dejar el paquete listo para una revisión responsable. Ponerlo a atender recepciones reales es otro paso, que debe corresponder a los permisos y al proceso operativo de la empresa.
El código es una fuente de cambios: renombrar una columna o modificar una validación puede alterar el servicio. Los datos son otra: nuevas rutas o cambios de captura pueden afectar el modelo que entrenas. Cada cambio necesita una identidad y unas comprobaciones apropiadas a lo que modificó.
Cambio
Comprobación pequeña
Información que conservas
Regla de alerta
El valor en el límite genera la decisión acordada.
Corte y versión del código.
Captura de distancia
El campo conserva kilómetros y rango.
Esquema y procedencia del lote.
Archivo del modelo
Coincide con el manifiesto y puede cargarse.
Versión, entorno y huella.
Candidato para entrega
Cumple los mínimos acordados y el caso de servicio.
Evaluación y decisión de revisión.
El capítulo 7 organizó trabajos que se repiten. Aquí mantendrás la misma idea de etapas, pero las condiciones responden a aceptar una propuesta. El entrenamiento ya habrá producido el candidato cuando comiencen sus controles de entrega.
Preparar una candidata sin afirmar una mejora nueva
La referencia inicial seguirá siendo corrida_02, como en el capítulo 8. La candidata se llamará corrida_04 para distinguir el paquete que vas a someter al recorrido de revisión. Reproducirá la misma configuración aprendida, de modo que puedas concentrarte en los controles y conservar una predicción conocida.
Un identificador nuevo registra otra propuesta de paquete; por sí solo no declara mayor calidad predictiva. La historia mantiene la referencia anterior disponible mientras se revisa. La entrega que realizarás será únicamente un cambio en un registro local de demostración.
3) Declarar la aceptación antes de medir
NotaCompuerta de aceptación
Una compuerta de aceptación permite continuar solamente si se cumplen condiciones escritas. La de este ejemplo exige F1 de al menos 0.50 y sensibilidad de al menos 0.60, con el umbral de alerta 0.45 que ya acompaña al modelo. La regla se guarda antes de evaluar la candidata.
Esos mínimos proceden del recorrido de entrega de los capítulos 7 y 8. F1 resume precisión y sensibilidad; el segundo límite mantiene visible la proporción de tardíos detectados. En una operación real el equipo debe acordarlos según lo que necesita revisar Laura y el costo de cada tipo de error.
La preparación siguiente crea los datos sintéticos y guarda la regla. Ajusta los paquetes con los 450 envíos de desarrollo y conserva los 150 de prueba por separado. También deja los archivos de ambas versiones y un registro cuya referencia actual continúa en corrida_02.
El expediente contiene 600 envíos, 67 pesos faltantes y 24.3 % de tardíos. Conserva 450 filas de desarrollo y 150 de prueba. Antes de evaluar quedaron escritos F1 mínimo de 0.50 y sensibilidad mínima de 0.60. La referencia inicial es corrida_02 y la candidata identificada es corrida_04.
La preparación pertenece al expediente de demostración. Las etapas de CI que vendrán ahora leen y comprueban ese expediente. No entrenarán otra regresión al probar cada caso ni al volver a consultar una evaluación ya conservada.
Una modificación de texto del servicio puede necesitar pruebas de entrada y de respuesta sin exigir un entrenamiento nuevo. Un conjunto de datos corregido puede requerir producir y evaluar otra candidata. Distinguir los dos recorridos evita confundir comprobar un paquete con volver a buscar un modelo.
4) Conocer las ocho etapas antes de ejecutarlas
Las comprobaciones técnicas preceden a la revisión de entrega.
Las etapas están declaradas en una lista. Cada función recibe un contexto con la carpeta del expediente, la candidata y el estado de la aprobación de demostración. Las primeras completan comprobaciones técnicas; las últimas deciden si puede cambiar la referencia local.
Etapa
Qué comprueba o produce
Código
Tres casos alrededor del umbral de alerta.
Datos
Columnas, rangos y categorías del historial.
Carga
Entorno, huellas y correspondencia con los datos.
Servicio
Salud, paquete de Laura y entrada incompleta.
Evaluación
Una medición final o la verificación de su recibo conservado.
Compuerta
F1 y sensibilidad frente a la regla previa.
Aprobación
Una decisión separada de las métricas.
Entrega
Referencia local nueva con conservación de la anterior.
El motor llama a cada función en orden. Si encuentra un error, registra su primera línea y deja las siguientes omitidas. La aprobación puede quedar pendiente, que expresa una espera diferente de una distancia inválida. La tabla conservará ambas situaciones sin tratarlas como éxitos.
Qué significa conservar una evaluación
El recibo de evaluación vincula la candidata con la huella del modelo, la de prueba y el umbral. La primera ejecución válida calcula las probabilidades y guarda sus métricas. Otra llamada comprueba esas identidades y reutiliza el documento en vez de volver a predecir sobre prueba.
Esto permite repetir controles o completar una revisión pendiente sin presentar varias consultas como nuevos experimentos. Si cambia el modelo, la prueba o la política, el recibo deja de describir ese experimento y la función lo rechaza. Un estudio nuevo necesita su propia selección y una evaluación final apropiada.
5) Hacer fallar la calidad de datos
Crea una copia del historial y cambia una distancia a −5 km. Ese valor incumple el rango acordado. Mantendrás intacta la fuente sana y pasarás la ruta de la copia solamente al primer contexto de prueba. El motor debería completar código, fallar en datos y omitir todo lo que dependa de ello.
Código termina en ok y datos falla con Distancia fuera de 0 a 3000 km. Las otras 6 etapas quedan omitidas. La referencia sigue siendo corrida_02, por lo que el error de −5 km no produjo una entrega ni una evaluación nueva.
La referencia actual sigue disponible aunque una propuesta falle. Esa es una diferencia práctica para Laura: el equipo puede corregir el nuevo paquete sin reemplazar el que está utilizando. El archivo de etapas permite encontrar dónde se detuvo esta propuesta.
La comprobación de rangos tampoco sustituye a la procedencia. Por eso la etapa de carga revisa además la huella del historial frente a la ficha. Una fuente con valores plausibles puede ser otra fuente; ambas preguntas son necesarias antes de asociar una evaluación con una candidata.
6) Pasar controles y dejar la revisión pendiente
NotaAprobación humana
La aprobación humana es una decisión explícita de una persona responsable después de examinar los controles y el contexto de uso. Tener métricas aceptables deja el paquete listo para esa revisión. En la demostración primero conservarás esa decisión como pendiente y después simularás el permiso para estudiar el siguiente paso.
Los controles técnicos pueden terminar bien sin que la entrega esté aprobada.
Ahora utiliza el historial sano y deja la aprobación sin conceder. Las etapas técnicas deben llegar hasta la compuerta. La primera evaluación guardará el recibo de prueba y sus métricas. Después observarás el estado pendiente y comprobarás qué referencia conserva el registro.
También probarás la compuerta con dos números ficticios: F1 de 0.49 y sensibilidad de 0.70. Esa prueba de función permite ver que el mínimo de F1 se exige, sin modificar la evaluación real ni presentar los valores ficticios como resultados del modelo.
contexto_revision = crear_contexto(CARPETA)ci_revision = ejecutar_etapas(ETAPAS, contexto_revision)display(ci_revision)evaluacion = leer_json(CARPETA /"evaluacion_candidata.json")display(pd.DataFrame([{"f1": evaluacion["f1"], "sensibilidad": evaluacion["sensibilidad"],"filas": evaluacion["filas"], "umbral": evaluacion["umbral"]}]).round(6))print("Referencia sin aprobación:", leer_json(CARPETA /"registro_modelos.json")["actual"])# Prueba de la compuerta con números ficticios, sin alterar la evaluación real.try: exigir_metricas({"f1": 0.49, "sensibilidad": 0.70}, regla)exceptAssertionErroras error:print("AssertionError:", str(error).splitlines()[0])
etapa
estado
detalle
0
codigo
ok
Tres casos del corte correctos
1
datos
ok
600 filas con esquema y rangos correctos
2
carga
ok
Entorno, datos e integridad correctos
3
servicio
ok
Salud 200, paquete conocido 200, incompleta 422
4
evaluacion
ok
Única evaluación de prueba guardada
5
compuerta
ok
F1 y sensibilidad cumplen los mínimos
6
aprobacion
pendiente
Pendiente de aprobación humana
7
entrega
omitida
Etapa previa detenida
f1
sensibilidad
filas
umbral
0
0.527473
0.666667
150
0.45
Referencia sin aprobación: corrida_02
AssertionError: F1 inferior al mínimo declarado
Las primeras 6 etapas quedan en ok. La evaluación de los 150 envíos de prueba obtiene F1 de 0.527473 y sensibilidad de 0.666667 con umbral 0.45, superando los mínimos de 0.50 y 0.60. Aprobación queda pendiente, entrega queda omitida y la referencia sigue en corrida_02. La prueba ficticia de F1 0.49 devuelve AssertionError: F1 inferior al mínimo declarado, sin cambiar la evaluación real.
La espera de aprobación expresa una decisión de operación. Quien revisa puede comprobar el caso de Laura, las falsas alarmas y lo que hará la empresa con cada alerta. En pocas palabras: las métricas dicen si el paquete puede entregarse; una persona decide si se entrega.
La prueba ficticia deja intacta la regla guardada y el recibo real. Si un resultado observado no alcanzara los mínimos, el proceso conservaría el rechazo. Cambiar una condición de aceptación requiere acordar otra política, identificarla y evaluar el proceso correspondiente.
7) Completar una entrega solo local y conservar el resultado
Para estudiar el recorrido completo crearás un contexto con aprobacion_simulada=True. Ese nombre deja claro qué representa el permiso: no documenta una revisión real del autor ni una autorización de despliegue. Permite observar el cambio de referencia en la carpeta del ejemplo.
Antes de ejecutar tomarás la huella del recibo de evaluación. El motor repetirá los controles y verificará ese recibo. Al finalizar compararás la huella para comprobar que no cambió, y revisarás actual y anterior en el registro de modelos.
huella_evaluacion_antes = huella(CARPETA /"evaluacion_candidata.json")# Es un permiso explícitamente simulado para estudiar una entrega solo local.contexto_entrega = crear_contexto(CARPETA, aprobacion_simulada=True)ci_entrega = ejecutar_etapas(ETAPAS, contexto_entrega)display(ci_entrega)registro_entrega = leer_json(CARPETA /"registro_modelos.json")print("Actual:", registro_entrega["actual"], "Anterior:", registro_entrega["anterior"])print("Evaluación intacta:", huella_evaluacion_antes == huella(CARPETA /"evaluacion_candidata.json"))ci_fallo.to_csv(CARPETA /"etapas-fallo.csv", index=False)ci_revision.to_csv(CARPETA /"etapas-revision.csv", index=False)ci_entrega.to_csv(CARPETA /"etapas-entrega.csv", index=False)
Las 8 etapas terminan en ok con el permiso explícitamente simulado. Evaluación informa Evaluación conservada y verificada, y la comparación de huellas devuelve True. La referencia actual pasa a corrida_04 y anterior conserva corrida_02. El cambio pertenece solamente al registro local de la demostración.
El historial distingue la propuesta y el paso que la puso como referencia local. Los archivos de la versión anterior permanecen. Eso permite investigar qué ocurriría si una comprobación operativa posterior descubre un problema y necesitas recuperar lo que Laura utilizaba antes.
Las tablas de fallo, revisión y entrega se guardan como CSV de la demostración. Puedes contrastarlas sin reconstruir la memoria de las celdas: una se detuvo por calidad, otra esperó una decisión y otra completó el cambio local autorizado por el permiso simulado.
8) Volver a la referencia anterior
NotaReversión
Una reversión, o rollback, vuelve a una versión anterior identificada después de una entrega que necesita retirarse. Requiere conservar los archivos anteriores, poder comprobarlos y saber qué referencia restaurar. La decisión y su motivo deben quedar en la historia.
Supón un incidente de servicio después de la entrega local. El ejercicio no cambia las métricas de prueba ni inventa una degradación observada. Simula la decisión de recuperar la referencia anterior, verifica que sus archivos pueden cargarse y deja el motivo registrado.
El registro conserva una referencia anterior para poder restaurarla.
La función revertir lee anterior, verifica su paquete y cambia actual. La referencia que se retiró pasa a anterior y la historia conserva el movimiento. Así puedes inspeccionar tanto la decisión de entrega como la de retorno.
restaurado = revertir(CARPETA, "Incidente de servicio simulado después de la entrega")print("Referencia restaurada:", restaurado["actual"])print("Referencia anterior al retorno:", restaurado["anterior"])display(pd.DataFrame(restaurado["historia"]))print("Archivos de ambas versiones:", all( (CARPETA /"modelos"/ nombre /"pipeline_envios.joblib").exists()for nombre in ["corrida_02", "corrida_04"]))
Referencia restaurada: corrida_02
Referencia anterior al retorno: corrida_04
accion
de
a
motivo
0
entrega local simulada
corrida_02
corrida_04
NaN
1
reversion local simulada
NaN
corrida_02
Incidente de servicio simulado después de la e...
Archivos de ambas versiones: True
La reversión deja actual en corrida_02 y anterior en corrida_04. La historia conserva 2 movimientos: entrega local simulada y reversión local simulada con su motivo. La comprobación de los archivos de ambas versiones devuelve True, por lo que el retorno conserva los paquetes identificados.
Cambiar una referencia local es el mecanismo pequeño que estudiaste. En un servicio real también tendrías que conseguir que los procesos carguen la versión restaurada y comprobar su disponibilidad. El capítulo 9 mostró qué significa verificar salud y una predicción después del arranque.
9) Leer el mismo recorrido en GitHub Actions
NotaWorkflow de CI y YAML
Un workflow de CI describe qué trabajos ejecutar y ante qué eventos. YAML es el formato de texto con el que declararás esos trabajos en GitHub Actions. Un runner es la máquina que los ejecuta. La relación needs indica que un trabajo espera el éxito del otro.
El archivo validacion_modelo.yml está junto al notebook como ejemplo documental. No está instalado en .github/workflows ni se ha ejecutado en GitHub. Sus dos trabajos representan los controles y una entrega de demostración posterior a revisión.
El ejemplo se inicia manualmente y reconstruye el expediente sintético mediante preparar_demostracion.py. Esa preparación permite reproducirlo sin archivos externos. Un flujo operativo recibiría un paquete candidato identificado de su proceso de entrenamiento, con una política de evaluación adecuada.
Después, verificar_candidato.py --fase validar utiliza las seis primeras etapas del mismo motor. Si pasa, conserva el expediente como resultado del trabajo. El segundo trabajo recupera ese contenido, espera la protección del entorno configurado y ejecuta la entrega local de demostración.
# Ejemplo documental: se conserva aquí, fuera de .github/workflows.name: Revisar candidato de envíos# La reconstrucción sintética solo se inicia manualmente en este ejemplo.on:workflow_dispatch:permissions:contents: readdefaults:run:working-directory: 11-cicd-machine-learningjobs:validar:runs-on: ubuntu-24.04steps:-uses: actions/checkout@v7-uses: actions/setup-python@v7with:python-version:"3.13.14"-name: Instalar las versiones de la comprobaciónrun: python -m pip install -r requirements-ci.lock # Este expediente sintético permite reproducir el ejemplo sin datos externos.-name: Preparar la demostraciónrun: python preparar_demostracion.py # Código, datos, carga, servicio, evaluación conservada y compuerta.-name: Ejecutar los controlesrun: python verificar_candidato.py --fase validar-uses: actions/upload-artifact@v7with:name: paquete-revisadopath: 11-cicd-machine-learning/artefactos/validacion-envios/if-no-files-found: errorentrega_local: # Este trabajo depende del éxito completo de validar.needs: validarruns-on: ubuntu-24.04 # Debe configurarse previamente con revisores obligatorios en el repositorio.environment: revision-modelosteps:-uses: actions/checkout@v7-uses: actions/setup-python@v7with:python-version:"3.13.14"-run: python -m pip install -r requirements-ci.lock-uses: actions/download-artifact@v8with:name: paquete-revisadopath: 11-cicd-machine-learning/artefactos/validacion-envios/ # La opción es solo para la entrega simulada del ejemplo, sin servicio externo.-name: Revalidar y completar la entrega localrun: python verificar_candidato.py --fase entregar --aprobar-demostracion
La declaración environment: revision-modelo relaciona el trabajo con un entorno de GitHub. Para exigir una revisión humana, una persona con permisos debe configurar revisores obligatorios y las reglas correspondientes en el repositorio. Escribir el nombre del entorno en YAML por sí solo no crea esa protección.
El uso de versiones de acciones y de Python identifica las herramientas de ejecución declaradas. El archivo de dependencias de CI conserva las bibliotecas para las pruebas, incluido el cliente HTTP. Se encuentra junto al notebook para que puedas revisar qué combinación propone este ejemplo.
La etapa final sigue siendo una actualización local simulada. El YAML no contiene credenciales de un servidor ni instrucciones para publicar un servicio externo. Los eventos que inicien controles en una operación real y la forma de entregar el paquete requieren la configuración de ese proyecto.
10) Leer el motor cuando quieras ampliar una etapa
El archivo control_calidad.py contiene las funciones ejecutadas en el notebook y en el programa de entrada del YAML. Puedes seguir primero ETAPAS y luego la función que responde a cada nombre. El motor solamente organiza llamadas, estados y detención; cada prueba describe su condición concreta.
"""Controles y motor secuencial del paquete sintético de envíos.Las funciones reciben un contexto con carpeta, identidad candidata y aprobaciónsimulada. La evaluación de prueba se conserva y se verifica antes de reutilizarla."""from pathlib import Pathimport hashlibimport jsonimport warningsimport numpy as npimport pandas as pdfrom starlette.exceptions import StarletteDeprecationWarning# Se conserva httpx instalado y se silencia únicamente su aviso de migración.warnings.filterwarnings("ignore", category=StarletteDeprecationWarning, message="Using `httpx` with `starlette.testclient` is deprecated.*")from fastapi.testclient import TestClientfrom sklearn.metrics import f1_score, recall_scorefrom compatibilidad_envios import cargar_verificadofrom servicio_envios import crear_appdef leer_json(ruta):"""Recibe una ruta JSON y devuelve su contenido."""return json.loads(Path(ruta).read_text(encoding="utf-8"))def guardar_json(ruta, contenido):"""Recibe una ruta y datos JSON; reemplaza el archivo mediante uno provisional.""" ruta = Path(ruta) temporal = ruta.with_suffix(".tmp") _ = temporal.write_text(json.dumps(contenido, indent=2, ensure_ascii=False), encoding="utf-8") _ = temporal.replace(ruta)def huella(ruta):"""Recibe una ruta y devuelve la huella completa de sus bytes."""return hashlib.sha256(Path(ruta).read_bytes()).hexdigest()def alertar(probabilidad, umbral):"""Recibe probabilidad y corte y devuelve si corresponde una alerta."""returnbool(probabilidad >= umbral)def prueba_codigo(ctx):"""Comprueba tres casos del corte; recibe contexto y devuelve un detalle."""assert alertar(0.45, 0.45), "El límite debe generar alerta"assertnot alertar(0.44, 0.45), "Un valor inferior no debe alertar"assert alertar(0.80, 0.45), "Un valor superior debe alertar"return"Tres casos del corte correctos"def prueba_datos(ctx):"""Lee los datos del contexto; comprueba esquema y rangos y devuelve detalle.""" datos = pd.read_csv(ctx["datos"])assertlist(datos.columns) == ["distancia_km", "peso_kg", "servicio", "llega_tarde"]assert datos.distancia_km.between(0, 3000).all(), "Distancia fuera de 0 a 3000 km"assert np.isfinite(datos.distancia_km).all(), "Distancia no finita"assert datos.peso_kg.dropna().between(0, 70).all(), "Peso fuera de 0 a 70 kg"assert datos.servicio.isin(["express", "estándar", "económico"]).all()assert datos.llega_tarde.isin([0, 1]).all(), "Respuesta no binaria"returnf"{len(datos)} filas con esquema y rangos correctos"def prueba_carga(ctx):"""Carga la candidata verificada; conserva modelo y registro en el contexto.""" registro, modelo = cargar_verificado(ctx["registro"])assert huella(ctx["datos"]) == registro["huella_datos"], "Cambió el historial" ctx["modelo"] = modelo ctx["ficha"] = registroreturn"Entorno, datos e integridad correctos"def prueba_servicio(ctx):"""Prueba salud, un envío conocido y una entrada incompleta en la aplicación.""" app = crear_app(ctx["registro"])with TestClient(app, raise_server_exceptions=False) as cliente: salud = cliente.get("/salud") respuesta = cliente.post("/v1/predecir", json={"distancia_km": 520.0, "peso_kg": None, "servicio": "económico"}) incompleta = cliente.post("/v1/predecir", json={"servicio": "económico"})assert salud.status_code ==200and salud.json()["listo"]assert respuesta.status_code ==200assert incompleta.status_code ==422assert respuesta.json()["version_modelo"] == ctx["candidata"]assertabs(respuesta.json()["probabilidad_tarde"] -0.762617) <0.000001return"Salud 200, paquete conocido 200, incompleta 422"def evaluar_modelo(ctx):"""Mide una vez o verifica un recibo existente y conserva sus métricas.""" carpeta = ctx["carpeta"] ruta = carpeta /"evaluacion_candidata.json" ruta_prueba = carpeta /"prueba.csv" identidad = {"version_modelo": ctx["candidata"],"huella_pipeline": ctx["ficha"]["huella_pipeline"],"huella_prueba": huella(ruta_prueba), "umbral": ctx["ficha"]["umbral"]}if ruta.exists(): resultado = leer_json(ruta)for nombre, valor in identidad.items():assert resultado[nombre] == valor, "La evaluación pertenece a otro experimento" ctx["evaluacion"] = resultadoreturn"Evaluación conservada y verificada" prueba = pd.read_csv(ruta_prueba) probabilidades = ctx["modelo"].predict_proba(prueba.drop(columns="llega_tarde"))[:, 1] alertas = (probabilidades >= identidad["umbral"]).astype(int) resultado = {**identidad, "f1": f1_score(prueba.llega_tarde, alertas),"sensibilidad": recall_score(prueba.llega_tarde, alertas),"filas": len(prueba)} guardar_json(ruta, resultado) ctx["evaluacion"] = resultadoreturn"Única evaluación de prueba guardada"def exigir_metricas(evaluacion, regla):"""Recibe métricas y límites declarados; rechaza si alguna queda por debajo."""assert evaluacion["f1"] >= regla["f1_minimo"], "F1 inferior al mínimo declarado"assert evaluacion["sensibilidad"] >= regla["sensibilidad_minima"], "Sensibilidad insuficiente"def compuerta(ctx):"""Lee la regla previa y comprueba las métricas del contexto.""" exigir_metricas(ctx["evaluacion"], leer_json(ctx["carpeta"] /"regla.json"))return"F1 y sensibilidad cumplen los mínimos"def aprobacion(ctx):"""Exige la aprobación simulada explícita antes de cambiar la referencia local."""ifnot ctx.get("aprobacion_simulada", False):raisePermissionError("Pendiente de aprobación humana")return"Aprobación simulada para esta demostración local"def entregar(ctx):"""Cambia solo la referencia del registro local y conserva la anterior.""" ruta = ctx["carpeta"] /"registro_modelos.json" registro = leer_json(ruta) registro["anterior"] = registro["actual"] registro["actual"] = ctx["candidata"] registro["historia"].append({"accion": "entrega local simulada","de": registro["anterior"], "a": registro["actual"]}) guardar_json(ruta, registro)return"Referencia local actualizada, sin publicación"ETAPAS = [("codigo", prueba_codigo), ("datos", prueba_datos), ("carga", prueba_carga), ("servicio", prueba_servicio), ("evaluacion", evaluar_modelo), ("compuerta", compuerta), ("aprobacion", aprobacion), ("entrega", entregar)]def ejecutar_etapas(etapas, ctx):"""Ejecuta la lista hasta el primer fallo o pausa y devuelve su tabla de estados.""" filas = [] detenido =Falsefor nombre, funcion in etapas:if detenido: filas.append({"etapa": nombre, "estado": "omitida", "detalle": "Etapa previa detenida"})continuetry: detalle = funcion(ctx) filas.append({"etapa": nombre, "estado": "ok", "detalle": detalle})exceptExceptionas error: estado ="pendiente"ifisinstance(error, PermissionError) else"fallo" filas.append({"etapa": nombre, "estado": estado, "detalle": str(error).splitlines()[0]}) detenido =Truereturn pd.DataFrame(filas)def crear_contexto(carpeta, aprobacion_simulada=False):"""Recibe carpeta y permiso de demostración; devuelve el contexto de la candidata.""" carpeta = Path(carpeta)return {"carpeta": carpeta, "datos": carpeta /"datos.csv", "candidata": "corrida_04","registro": carpeta /"modelos/corrida_04/registro_modelo.json","aprobacion_simulada": aprobacion_simulada}def revertir(carpeta, motivo):"""Verifica la versión anterior, cambia la referencia local y devuelve el registro.""" carpeta = Path(carpeta) ruta = carpeta /"registro_modelos.json" registro = leer_json(ruta) destino = registro["anterior"] _ = cargar_verificado(carpeta /"modelos"/ destino /"registro_modelo.json") registro["anterior"] = registro["actual"] registro["actual"] = destino registro["historia"].append({"accion": "reversion local simulada", "a": destino,"motivo": motivo}) guardar_json(ruta, registro)return registro
Los archivos servicio_envios.py y compatibilidad_envios.py conservan las comprobaciones de los capítulos 9 y 10. El expediente de artefactos se genera en una carpeta fija y se vacía al empezar el notebook. Para limpiar la demostración puedes retirar esa carpeta cuando termines de revisar sus tablas e historia.
Algunas ampliaciones de operación
Tema
Idea
Pregunta para la entrega de Laura
Canary
Enviar una parte pequeña del trabajo a una versión nueva.
¿Qué señales decidirán ampliar o retirar esa parte?
Blue-green
Mantener dos conjuntos de servicio y cambiar la referencia de tráfico.
¿Cómo verificas la versión que empezará a atender?
Ramas protegidas
Exigir condiciones antes de integrar código.
¿Qué pruebas deben pasar y quién revisa el cambio?
Secretos
Proporcionar credenciales con acceso controlado.
¿Qué trabajo necesita realmente permiso para entregar?
Caché
Reutilizar contenido que reduce preparación repetida.
¿Cómo evitas confundir dependencias reutilizadas con un modelo distinto?
Estas opciones amplían el modo de ejecutar y entregar. Antes de escoger una, identifica qué problema del servicio necesitas resolver. El recorrido básico que ya comprobaste conserva la condición previa, el resultado observado y la decisión que permite continuar.
NotaAlcance del experimento
El historial es sintético y corrida_04 reproduce la configuración conocida para estudiar el proceso de aceptación. No se afirma una mejora nueva respecto de corrida_02. La prueba se calcula una vez para esa candidata y después se verifica su recibo; nuevos experimentos requieren datos de evaluación apropiados. Los límites son ilustrativos y la aprobación del notebook es explícitamente simulada. Los assert son controles didácticos y no deben desactivarse mediante ejecución optimizada de Python; las validaciones operativas deben mantenerse activas. El registro y la reversión son locales. El YAML es documental, necesita configuración de repositorio y no se ejecutó en GitHub ni publicó nada.
Errores frecuentes
Lo que ves
Qué pasó
Cómo se arregla
Datos falla y carga queda omitida
El historial incumple el esquema o los rangos.
Corrige la fuente antes de continuar.
La ficha rechaza el historial
Los bytes no corresponden a la huella registrada.
Recupera el expediente identificado.
El caso conocido cambió
La aplicación, el archivo o la política no corresponden.
Localiza el cambio con versiones y huellas.
La evaluación pertenece a otro experimento
Cambió candidata, prueba o umbral.
Diseña y registra la evaluación que corresponde.
La compuerta rechaza el F1
No se alcanzó el mínimo previo.
Conserva el resultado y revisa el experimento.
Aprobación queda pendiente
Falta la decisión responsable.
Mantén la referencia actual mientras se revisa.
No puedes revertir
Falta la versión anterior o sus archivos.
Conserva el registro y verifica la restauración antes de necesitarla.
Lista de verificación
Las condiciones se escribieron antes de medir.
Código, datos y modelo tienen controles propios.
El modelo y el historial coinciden con sus fichas.
El servicio atiende el caso conocido y rechaza una entrada incompleta.
La evaluación final tiene identidad y se conserva sin recalcularla al reanudar.
Una etapa fallida omite las posteriores.
La aprobación está separada de la compuerta técnica.
La entrega conserva una referencia anterior comprobable.
El retorno deja un motivo y una historia revisable.
Ejercicios
Añade a la lista de juguete un control de peso válido o ausente. Diseña un paquete que pase campos y distancia, pero falle ese control.
Indica qué estados esperas si falla servicio en la lista de ocho etapas. Explica qué referencia debe conservar Laura.
Calcula si una evaluación con F1 de 0.51 y sensibilidad de 0.59 pasa la regla del capítulo. Justifica cada condición.
Describe qué documentos revisarías antes de una aprobación humana y por qué una etiqueta de etapa en verde no sustituye esa decisión.
Explica qué debe existir para restaurar corrida_02 después de entregar corrida_04 y qué comprobación realizarías sobre el servicio real.
TipOrientaciones
Conserva los tres campos y una distancia válida, y usa un peso negativo. El nuevo control debe admitir None o un número en el rango acordado.
Código, datos y carga quedarían correctas; servicio fallaría; evaluación, compuerta, aprobación y entrega quedarían omitidas. La referencia actual permanece sin cambiar.
F1 supera 0.50, pero sensibilidad queda por debajo de 0.60. La candidata se rechaza porque deben cumplirse las dos condiciones.
Revisa versiones y huellas, datos, evaluación, caso de servicio y contexto operativo. Las pruebas observan condiciones declaradas; la aprobación asigna responsabilidad a la decisión de uso.
Conserva su registro, el pipeline y un entorno compatible, además de la referencia anterior. Tras cargarla, comprueba salud y una predicción conocida con la versión esperada.
Glosario
Término
Significado
Prueba automática
Comparación ejecutable entre comportamiento observado y esperado.
CI
Integración continua con comprobaciones de cambios.
Entrega continua
Preparación de paquetes que pueden entregarse tras controles.
Despliegue continuo
Puesta en uso automática cuando se cumplen condiciones.
Compuerta
Condiciones declaradas que permiten o detienen el avance.
Aprobación humana
Decisión responsable separada de las métricas.
Reversión o rollback
Retorno a una versión anterior identificada.
Workflow de CI
Trabajos y eventos que organizan las comprobaciones.
YAML
Formato de texto de la declaración de trabajos.
Runner
Máquina que ejecuta un trabajo del workflow.
Canary
Entrega inicial a una parte pequeña del trabajo.
Blue-green
Dos conjuntos de servicio entre los que se cambia el tráfico.
Rama protegida
Rama con condiciones previas de integración.
Caché
Contenido reutilizable para reducir preparación repetida.
Qué aprendiste
Pasaste de tres condiciones sobre un paquete a una cadena de controles que conserva sus resultados. Un fallo de datos detuvo las etapas siguientes. El recorrido sano quedó pendiente de aprobación antes de completar una entrega local, y la reversión recuperó la referencia anterior con su historia disponible.
Laura necesita saber también qué ocurre después de comenzar a utilizar una versión. En Monitoreo y estabilidad en producción seguirás los paquetes a lo largo de las semanas para revisar las señales que llegan antes y después de conocer los retrasos reales.