<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Schema Evolution el josiete.com</title><link>https://www.josiete.com/tags/schema-evolution/</link><description>Contenido reciente en Schema Evolution el josiete.com</description><generator>Hugo</generator><language>es-es</language><lastBuildDate>Sat, 10 Oct 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://www.josiete.com/tags/schema-evolution/index.xml" rel="self" type="application/rss+xml"/><item><title>Schema Drift en ETL: cuando los datos cambian y el código no se entera</title><link>https://www.josiete.com/posts/schema-drift-evolucion-esquemas-etl/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0200</pubDate><guid>https://www.josiete.com/posts/schema-drift-evolucion-esquemas-etl/</guid><description>&lt;p&gt;&lt;img src="https://www.josiete.com/images/schema-drift-etl-timeline.svg" alt="Línea temporal de septiembre: los datos pasan de Schema A a B y C antes de que el ETL cambie de v1.0 a v1.1 y v1.2; las franjas naranjas señalan periodos cuya compatibilidad debe comprobarse"&gt;&lt;/p&gt;&#10;&lt;p&gt;Cada mañana, un ETL recibe un CSV, convierte la edad a entero y carga los clientes. Ayer funcionaba con este fichero:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csv" data-lang="csv"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;id&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;name&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;age&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;1&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;John&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;25&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;2&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;Mary&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;30&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hoy, el proveedor añade el país:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csv" data-lang="csv"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;id&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;name&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;age&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;country&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;1&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;John&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;25&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;USA&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;2&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;Mary&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;30&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;UK&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unos días después, llega otra sorpresa:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csv" data-lang="csv"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;id&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;name&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;age&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;country&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;1&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;John&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;25&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;USA&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;2&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;Mary&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;unknown&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;UK&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Una columna adicional puede ser inocua si seleccionamos campos por nombre y permitimos extras. Si desempaquetamos cada fila en tres variables, puede romper la carga. &lt;code&gt;unknown&lt;/code&gt; puede provocar un error al convertir la edad, convertirse silenciosamente en nulo o hacer que una inferencia posterior trate toda la columna como texto.&lt;/p&gt;&#10;&lt;p&gt;La compatibilidad depende del contrato y del código. Incluso el último caso puede ser un incumplimiento de datos puntuales, sin que el proveedor haya cambiado el esquema declarado. La pregunta es: &lt;strong&gt;si sabemos qué versión del código procesó un fichero, ¿podemos saber también qué estructura tenían los datos y cuándo dejaron de ser compatibles con nuestro ETL?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 id="detectar-un-cambio-no-es-aprobarlo"&gt;Detectar un cambio no es aprobarlo&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Schema Drift&lt;/strong&gt; describe la divergencia entre la estructura esperada y la observada: columnas nuevas, campos ausentes o tipos diferentes. En un CSV, cuyos valores son texto, los tipos observados dependen de cómo los interpretamos.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Schema Evolution&lt;/strong&gt; es gestionar esa evolución: acordar versiones, decidir qué cambios admitimos y adaptar consumidores. Añadir un campo opcional puede conservar compatibilidad; eliminar una clave que necesitamos normalmente la rompe.&lt;/p&gt;&#10;&lt;p&gt;Un &lt;strong&gt;Data Contract&lt;/strong&gt; recoge el acuerdo entre productor y consumidor: campos, tipos, nulabilidad, formatos, unidades, expectativas de calidad y condiciones de entrega. Para hacerlo operativo necesitamos comprobaciones y una política ante incumplimientos. Registrar un cambio, aceptarlo y transformar los datos son decisiones distintas.&lt;/p&gt;&#10;&lt;p&gt;Por ejemplo, podemos admitir columnas nuevas, rechazar la ausencia de &lt;code&gt;id&lt;/code&gt; y poner en cuarentena filas con edades inválidas. Esa política debe quedar versionada: «el fichero se pudo leer» aporta menos información que «cumplió el contrato 2.1».&lt;/p&gt;&#10;&lt;h2 id="git-cuenta-solo-una-parte-de-la-historia"&gt;Git cuenta solo una parte de la historia&lt;/h2&gt;&#10;&lt;p&gt;Git permite identificar el código desplegado, pero no registra automáticamente qué envió un proveedor externo. Imaginemos esta secuencia, donde A, B y C representan estructuras observadas, no contratos ya aprobados:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Fecha de recepción&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Esquema recibido&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Versión ETL&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;01/09&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schema A&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v1.0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12/09&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schema B&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v1.0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;15/09&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schema B&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v1.1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20/09&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schema C&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v1.1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;25/09&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schema C&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v1.2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Si v1.0 solo soportaba A y v1.1 solo A y B, entre el 12 y el 15, y entre el 20 y el 25, hay lotes potencialmente afectados. Las franjas del diagrama marcan esos periodos de revisión; no demuestran por sí mismas una incompatibilidad. Debemos consultar contratos, validaciones y resultados de cada ejecución.&lt;/p&gt;&#10;&lt;p&gt;Por cada lote conviene guardar:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Código ejecutado:&lt;/strong&gt; commit Git o artefacto inmutable, además de una etiqueta legible.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Esquema observado y contrato aplicado:&lt;/strong&gt; identificadores de versiones y sus descriptores.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Identidad del fichero:&lt;/strong&gt; origen, identificador del lote y hash del contenido; si existe, versión del objeto almacenado.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Recepción y procesamiento:&lt;/strong&gt; fechas separadas, con zona horaria y un identificador de ejecución.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Resultado:&lt;/strong&gt; comprobaciones, rechazos y salidas producidas.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;También hay dos relojes: cuándo cambió el origen y cuándo lo detectamos. Una inspección nocturna solo demuestra lo observado esa noche. Si no hay eventos del proveedor ni ficheros históricos, quizá podamos acotar el cambio entre dos observaciones, pero no asignarle una hora exacta.&lt;/p&gt;&#10;&lt;p&gt;El historial de metadatos tampoco conserva automáticamente el contenido original. Para reproducir el cálculo hacen falta los datos, las dependencias y la configuración del ETL, sujetos a la política de retención correspondiente.&lt;/p&gt;&#10;&lt;h2 id="los-fallos-que-dejan-el-pipeline-en-verde"&gt;Los fallos que dejan el pipeline en verde&lt;/h2&gt;&#10;&lt;p&gt;Un importe pasa de euros a céntimos: &lt;code&gt;19.95&lt;/code&gt; se convierte en &lt;code&gt;1995&lt;/code&gt;. Ambos pueden ser números válidos. Una lista de columnas y tipos no detecta el cambio de unidad; hacen falta unidades declaradas, reglas de negocio o una comparación estadística que señale el salto.&lt;/p&gt;&#10;&lt;p&gt;Una fecha cambia de &lt;code&gt;2026-10-09&lt;/code&gt; a &lt;code&gt;09/10/2026&lt;/code&gt;. Si el contrato exige un formato, podemos rechazarla. Si el esquema dice únicamente «texto», todo parece correcto; un parser permisivo puede además confundir día y mes en fechas ambiguas.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;La Coruña&lt;/code&gt; aparece como &lt;code&gt;La CoruÃ±a&lt;/code&gt; por una interpretación incorrecta de bytes. Sigue siendo texto, e incluso puede estar almacenado después en un fichero UTF-8 perfectamente válido. Fijar la codificación ayuda, pero no repara una corrupción previa. Este tema conecta con los &lt;a href="https://www.josiete.com/posts/errores-codificacion-unicode/"&gt;errores de codificación y Unicode&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Estos problemas requieren combinar estructura, semántica y &lt;strong&gt;Data Quality&lt;/strong&gt;. Un rango de edades, una moneda explícita o un catálogo de localidades detectan cosas que el análisis del esquema no cubre. Y una distribución diferente puede reflejar un cambio real del negocio: una alerta pide investigar, no corregir automáticamente.&lt;/p&gt;&#10;&lt;h2 id="qué-aporta-cada-herramienta"&gt;Qué aporta cada herramienta&lt;/h2&gt;&#10;&lt;h3 id="openmetadata-memoria-compartida-del-ecosistema"&gt;OpenMetadata: memoria compartida del ecosistema&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://open-metadata.org/"&gt;OpenMetadata&lt;/a&gt; centraliza metadatos de activos, propietarios y relaciones. Su &lt;a href="https://docs.open-metadata.org/v2.0.x/how-to-guides/guide-for-data-users/versions"&gt;historial de versiones&lt;/a&gt; incluye cambios de esquema y otros metadatos. Usa versiones mayores y menores; eliminar una columna es un ejemplo documentado de cambio incompatible. Esa clasificación del catálogo no reemplaza la compatibilidad específica de nuestro consumidor.&lt;/p&gt;&#10;&lt;p&gt;Las &lt;a href="https://docs.open-metadata.org/v2.0.x/how-to-guides/data-quality-observability/alerts-notifications/data-observability-alerts"&gt;alertas de observabilidad&lt;/a&gt; permiten vigilar columnas añadidas, eliminadas o actualizadas, resultados de pruebas y estados de pipelines. Podemos filtrar eventos y enviarlos a correo, canales de chat o webhooks. Sus &lt;a href="https://docs.open-metadata.org/v2.0.x/how-to-guides/data-quality-observability/quality/tests-ui"&gt;pruebas de tablas&lt;/a&gt; comprueban, entre otras cosas, columnas esperadas y recuentos; el perfilado aporta métricas adicionales.&lt;/p&gt;&#10;&lt;p&gt;Encaja cuando varios equipos necesitan un catálogo común sobre bases de datos, almacenamiento, pipelines y analítica. Exige desplegar la plataforma y configurar ingestión y conectores. Un cambio que todavía no se ha ingerido queda fuera de su conocimiento: para controlar cada CSV al recibirlo necesitamos instrumentar esa entrada.&lt;/p&gt;&#10;&lt;h3 id="openlineage-unir-datos-código-y-ejecuciones"&gt;OpenLineage: unir datos, código y ejecuciones&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://openlineage.io/docs/spec/object-model/"&gt;OpenLineage&lt;/a&gt; define un estándar abierto de trazabilidad. Un &lt;strong&gt;job&lt;/strong&gt; identifica el trabajo lógico; un &lt;strong&gt;run&lt;/strong&gt;, una ejecución concreta; un &lt;strong&gt;dataset&lt;/strong&gt;, una entrada o salida. Las &lt;strong&gt;facets&lt;/strong&gt; añaden metadatos a esas entidades y a las entradas o salidas de una ejecución.&lt;/p&gt;&#10;&lt;p&gt;La &lt;a href="https://openlineage.io/docs/spec/facets/dataset-facets/schema/"&gt;facet de esquema&lt;/a&gt; describe campos y tipos. La &lt;a href="https://openlineage.io/docs/spec/facets/job-facets/source-code-location/"&gt;facet de ubicación del código&lt;/a&gt; admite información del repositorio y versión. Si nuestro productor de eventos las rellena, podemos relacionar un run con sus datasets y con el código ejecutado. Hay que emitir la referencia efectiva del artefacto desplegado, no asumir que coincide con la rama actual.&lt;/p&gt;&#10;&lt;p&gt;Los clientes e integraciones publican eventos que un backend recibe y conserva. OpenLineage no inspecciona por sí solo un CSV ni decide si un cambio es compatible. Hace falta un lector o integración que aporte el esquema y un sistema que compare observaciones, almacene el historial y alerte. Su utilidad aumenta cuando queremos seguir el impacto a través de varios motores.&lt;/p&gt;&#10;&lt;h3 id="datahub-catálogo-e-historial-con-diferencias-entre-ediciones"&gt;DataHub: catálogo e historial, con diferencias entre ediciones&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://docs.datahub.com/docs/schema-history"&gt;DataHub&lt;/a&gt; ofrece historial de esquemas para revisar campos añadidos, eliminados y cambios de tipo. Esa capacidad está disponible en Core, su versión abierta, y en Cloud. El catálogo y el linaje permiten estudiar dependencias, siempre que ingiramos esos metadatos.&lt;/p&gt;&#10;&lt;p&gt;Los &lt;a href="https://docs.datahub.com/docs/managed-datahub/observe/data-contract"&gt;Data Contracts&lt;/a&gt; agrupan aserciones sobre un activo. La &lt;a href="https://docs.datahub.com/docs/managed-datahub/managed-datahub-overview"&gt;comparación oficial de Core y Cloud&lt;/a&gt; incluye contratos en ambas ediciones; en Core podemos ejecutar comprobaciones fuera de DataHub y publicar sus resultados, por ejemplo desde dbt o Great Expectations.&lt;/p&gt;&#10;&lt;p&gt;Las &lt;a href="https://docs.datahub.com/docs/managed-datahub/observe/schema-assertions"&gt;Schema Assertions nativas&lt;/a&gt; pertenecen a DataHub Cloud Observe. Permiten exigir que el esquema contenga determinadas columnas y tipos o que coincida exactamente. Se evalúan ante cambios del esquema ingerido; los tipos comparados son de alto nivel. Cloud añade monitorización nativa y detección de anomalías. Tener el modelo abierto de aserciones no implica disponer del ejecutor comercial.&lt;/p&gt;&#10;&lt;p&gt;Es una opción para organizaciones con múltiples fuentes y necesidades de gobierno y descubrimiento. La operación del catálogo y sus ingestas tiene más coste que incorporar una librería a un ETL pequeño.&lt;/p&gt;&#10;&lt;h3 id="frictionless-empezar-por-el-fichero-que-acaba-de-llegar"&gt;Frictionless: empezar por el fichero que acaba de llegar&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://framework.frictionlessdata.io/"&gt;Frictionless&lt;/a&gt; permite describir, leer y validar datos tabulares desde Python. Puede inspeccionar un CSV, inferir nombres y tipos, guardar un descriptor de esquema y validar otra entrega contra él. Es una pieza útil si queremos comprobar archivos cerca del punto de recepción, sin desplegar primero un catálogo.&lt;/p&gt;&#10;&lt;p&gt;Este ejemplo usa &lt;strong&gt;Frictionless 5.20.0&lt;/strong&gt;, contrastado con las referencias de &lt;a href="https://framework.frictionlessdata.io/docs/framework/resource.html"&gt;Resource&lt;/a&gt;, &lt;a href="https://framework.frictionlessdata.io/docs/framework/schema.html"&gt;Schema&lt;/a&gt; y &lt;a href="https://framework.frictionlessdata.io/docs/guides/validating-data.html"&gt;validación&lt;/a&gt;. Ejecútalo en un directorio de trabajo nuevo: crea tres ficheros.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python3 -m venv .venv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source .venv/bin/activate&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python -m pip install frictionless&lt;span style="color:#f92672"&gt;==&lt;/span&gt;5.20.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pathlib &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Path&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; frictionless &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Resource, Schema&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Path(&lt;span style="color:#e6db74"&gt;&amp;#34;clientes.csv&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write_text(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;id,name,age,country&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;1,John,25,USA&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;2,Mary,30,UK&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; encoding&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Path(&lt;span style="color:#e6db74"&gt;&amp;#34;clientes-cambio.csv&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write_text(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;id,name,age,country&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;1,John,25,USA&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;2,Mary,unknown,UK&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; encoding&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;original &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Resource(path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;clientes.csv&amp;#34;&lt;/span&gt;, encoding&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;original&lt;span style="color:#f92672"&gt;.&lt;/span&gt;infer()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print([(field&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name, field&lt;span style="color:#f92672"&gt;.&lt;/span&gt;type) &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; field &lt;span style="color:#f92672"&gt;in&lt;/span&gt; original&lt;span style="color:#f92672"&gt;.&lt;/span&gt;schema&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fields])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;original&lt;span style="color:#f92672"&gt;.&lt;/span&gt;schema&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_json(&lt;span style="color:#e6db74"&gt;&amp;#34;clientes.schema.json&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;report &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Resource(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;clientes-cambio.csv&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; encoding&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; schema&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Schema(&lt;span style="color:#e6db74"&gt;&amp;#34;clientes.schema.json&amp;#34;&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;validate()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(report&lt;span style="color:#f92672"&gt;.&lt;/span&gt;valid)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(report&lt;span style="color:#f92672"&gt;.&lt;/span&gt;flatten([&lt;span style="color:#e6db74"&gt;&amp;#34;rowNumber&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;fieldNumber&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;]))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Se infieren &lt;code&gt;id&lt;/code&gt; y &lt;code&gt;age&lt;/code&gt; como &lt;code&gt;integer&lt;/code&gt;, y &lt;code&gt;name&lt;/code&gt; y &lt;code&gt;country&lt;/code&gt; como &lt;code&gt;string&lt;/code&gt;. El segundo fichero devuelve &lt;code&gt;False&lt;/code&gt; y &lt;code&gt;[[3, 3, 'type-error']]&lt;/code&gt;: la tercera fila, contando la cabecera, contiene una edad incompatible.&lt;/p&gt;&#10;&lt;p&gt;El detalle decisivo es &lt;strong&gt;validar contra el esquema anterior&lt;/strong&gt;. Si inferimos de nuevo sobre cada entrega y aceptamos lo inferido, podemos normalizar el problema como un nuevo tipo. La inferencia es una propuesta, no un contrato: depende de la muestra y de la &lt;a href="https://framework.frictionlessdata.io/docs/framework/detector.html"&gt;configuración del detector&lt;/a&gt;. Identificadores como &lt;code&gt;00123&lt;/code&gt; deberían declararse como texto cuando ese significado importe.&lt;/p&gt;&#10;&lt;p&gt;Frictionless también valida restricciones y admite comprobaciones adicionales. El historial, la política de compatibilidad, el registro de runs y las alertas tendríamos que implementarlos o integrarlos. Para JSON tabular puede aprovechar sus lectores; para documentos anidados necesitamos definir cómo representar objetos, arrays y campos opcionales.&lt;/p&gt;&#10;&lt;h3 id="whylogs-observar-el-contenido-mediante-perfiles"&gt;whylogs: observar el contenido mediante perfiles&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/whylabs/whylogs"&gt;whylogs&lt;/a&gt; genera perfiles compactos con propiedades estadísticas, valores ausentes y métricas configurables. Podemos conservar perfiles de distintos lotes, compararlos y aplicar restricciones para investigar cambios de distribución, rangos o proporciones de nulos.&lt;/p&gt;&#10;&lt;p&gt;El perfil ayuda a ver, por ejemplo, que los importes saltaron de escala. No demuestra que ahora estén en céntimos. También registra propiedades por columna que permiten observar cambios de tipos o presencia, pero un perfil no sustituye un contrato ni decide la compatibilidad de un esquema. Las estadísticas resumidas o aproximadas requieren interpretar la señal.&lt;/p&gt;&#10;&lt;p&gt;La librería funciona localmente. El almacenamiento histórico, las comparaciones periódicas y el envío de alertas necesitan una aplicación o integración; el servicio comercial WhyLabs es una opción adicional. Los perfiles reducen la necesidad de conservar valores crudos, aunque eso no garantiza por sí solo que todo metadato sea inocuo desde el punto de vista de la confidencialidad.&lt;/p&gt;&#10;&lt;h2 id="comparar-responsabilidades-no-solo-marcar-casillas"&gt;Comparar responsabilidades, no solo marcar casillas&lt;/h2&gt;&#10;&lt;p&gt;En esta tabla, «integración» significa que hay que proporcionar o conectar esa pieza. La complejidad es una valoración orientativa para este caso de uso, no una medida de rendimiento.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Herramienta&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Objetivo principal&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Cambios de esquema&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Historial de esquemas&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Trazabilidad de ejecuciones&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Análisis estadístico&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Despliegue&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Software abierto&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OpenMetadata&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Catálogo y observabilidad&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sobre metadatos ingeridos; pruebas y alertas configuradas&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Versiones de activos y metadatos&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linaje y pipelines mediante conectores; detalle por lote requiere instrumentación&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Perfilado configurable&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Plataforma e ingestas: mayor complejidad&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sí; servicios comerciales adicionales&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OpenLineage&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Estándar de eventos de linaje&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Transporta esquema; comparación en integración/backend&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Depende del backend y los eventos conservados&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Jobs, runs, datasets y facets emitidos&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Puede transportar métricas; cálculo externo&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Instrumentación más backend&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sí&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DataHub&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Catálogo y gobierno&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Historial en Core; Schema Assertions nativas en Cloud Observe&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Disponible en Core y Cloud&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linaje y metadatos de trabajos según integración&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Perfiles ingeridos; anomalías nativas en Cloud&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Plataforma en Core o servicio Cloud&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Core abierto; Cloud comercial&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Frictionless&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Describir y validar ficheros&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Inferencia y validación contra esquema; comparación histórica a integrar&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Descriptores exportables; registro a construir&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Asociación con runs a construir&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Estadísticas básicas y comprobaciones; no monitor estadístico continuo&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Librería/CLI: menor complejidad&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sí, MIT&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;whylogs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Perfiles de datos&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Señales por columna; comparación y contrato a integrar&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Perfiles almacenables; versionado estructural a integrar&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Asociación con runs a integrar&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Su función central&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Librería: menor complejidad; monitorización adicional&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sí; WhyLabs es un servicio adicional&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Estas herramientas se complementan. Frictionless puede validar la entrada; whylogs perfilarla; OpenLineage transportar la relación con la ejecución; y un catálogo conservar el contexto compartido. No necesitamos las cinco para empezar.&lt;/p&gt;&#10;&lt;h2 id="hace-falta-un-llm"&gt;¿Hace falta un LLM?&lt;/h2&gt;&#10;&lt;p&gt;Un LLM puede ayudar a interpretar un cambio o proponer una transformación. Muchos controles básicos se resuelven con inferencia de tipos, reglas, expresiones regulares y análisis estadístico, con resultados fáciles de revisar.&lt;/p&gt;&#10;&lt;p&gt;Incorporarlo introduce coste de infraestructura, latencia y resultados que no necesariamente son deterministas. También exige decidir qué datos o metadatos puede recibir y validar las transformaciones propuestas. Existen modelos locales: evitar un servicio externo cambia las necesidades de infraestructura, pero no elimina estos problemas. Una sugerencia de conversión nunca debería convertirse automáticamente en una nueva regla de negocio.&lt;/p&gt;&#10;&lt;h2 id="una-arquitectura-ligera-que-podríamos-construir"&gt;Una arquitectura ligera que podríamos construir&lt;/h2&gt;&#10;&lt;p&gt;Una librería Python independiente del motor ETL podría inspeccionar CSV/JSON y mantener metadatos históricos sin ejecutar transformaciones. Es una propuesta conceptual:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CSV / JSON → Inspección → Esquema observado + validación&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Registro de versiones&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Lote + hash + run + commit + fechas&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cambio desconocido → Alerta → Revisión&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Primero normalizaríamos el descriptor y generaríamos una &lt;strong&gt;huella estructural&lt;/strong&gt;. Habría que decidir si el orden de columnas importa, cómo representar tipos anidados y qué propiedades entran en la huella. Guardaríamos también la versión del algoritmo y sus opciones de inferencia, para no confundir un cambio de detector con uno de los datos. La huella del esquema y el hash del fichero cumplen funciones diferentes.&lt;/p&gt;&#10;&lt;p&gt;Después registraríamos observaciones inmutables: mismo esquema en varios lotes no significa mismo contenido. El contrato aprobado tendría su propio identificador. Una estructura desconocida activaría una revisión; una conocida aún necesitaría validación de filas y reglas semánticas.&lt;/p&gt;&#10;&lt;p&gt;Con las asociaciones entre lotes, runs y código podríamos consultar qué ejecuciones combinaron Schema C con v1.1. Esos lotes serían &lt;strong&gt;candidatos a revisión&lt;/strong&gt;, no pruebas automáticas de resultados incorrectos. Una política explícita decidiría si detener la carga, aislar filas o continuar con una alerta. Una base SQLite podría servir de inicio local; coordinación entre procesos y retención pedirían decisiones adicionales.&lt;/p&gt;&#10;&lt;h2 id="poder-reconstruir-lo-que-ocurrió"&gt;Poder reconstruir lo que ocurrió&lt;/h2&gt;&#10;&lt;p&gt;Versionar el código es necesario, pero no basta para explicar un pipeline. También necesitamos conocer los datos recibidos, su estructura, el contrato aplicado y el resultado de cada ejecución. Los catálogos, los eventos de linaje y las librerías de validación y perfilado cubren partes distintas de esa historia.&lt;/p&gt;&#10;&lt;p&gt;Si mañana cambia un fichero sin avisar, ¿podrías identificar qué lotes se procesaron con cada esquema y versión del ETL, y justificar cuáles necesitan revisarse?&lt;/p&gt;&#10;&lt;h2 id="créditos-del-artículo"&gt;Créditos del artículo&lt;/h2&gt;&#10;&lt;p&gt;La idea y la supervisión editorial de este artículo son de José Pérez. Su elaboración se ha apoyado en IA, con la participación detallada en estos créditos.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Metadato&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Valor&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Edición&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;José Pérez&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Investigación&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Asistida por IA&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Redacción&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OpenAI Codex&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Modelo&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;GPT-6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tiempo de generación&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;No registrado&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Revisión humana&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Pendiente&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Fuentes verificadas&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sí&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;La verificación de fuentes se realizó con asistencia de IA, contrastando las afirmaciones técnicas con documentación oficial. La revisión humana del texto final está pendiente de confirmación del autor.&lt;/p&gt;&#10;</description></item></channel></rss>