Volver al blog
IngenieríaIA generativaOdoo 19

Por qué un módulo generado por IA tiene que instalarse antes de entregarse

Un módulo que pasa el análisis estático no es un módulo que funciona. Cómo funciona la cadena de puertas deterministas, la capa de lint y la validación en runtime de MigrateFelix, y por qué "un fallo honesto antes que un éxito falso" terminó siendo una restricción de base de datos y no una convención.

Oscar Alhdhair Vasquez Roncal 20 de agosto de 2026 7 min de lectura

"Compila" no es "funciona"

El código que genera un modelo de lenguaje tiene una propiedad incómoda: casi siempre parece correcto. La indentación está bien, los imports existen, los nombres de campo son plausibles, el XML cierra todas sus etiquetas. Si lo único que haces es analizarlo estáticamente, va a pasar.

Y después no instala.

Este es un __manifest__.py sintácticamente perfecto:

{
    "name": "Solicitudes de servicio",
    "version": "19.0.1.0.0",
    "depends": ["base", "mail"],
    "data": [
        "security/ir.model.access.csv",
        "views/service_request_views.xml",
    ],
    "license": "LGPL-3",
    "installable": True,
}

Y este es el CSV de permisos que lo acompaña, también impecable como archivo:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_service_request_user,service.request.user,model_x_service_request,base.group_user,1,1,1,0

Si el modelo se declaró como _name = "x_service.request", el XML ID que Odoo espera en esa columna es model_x_service_request — los puntos se vuelven guiones bajos. Un solo carácter de diferencia y el CSV sigue siendo un CSV válido, sigue teniendo las columnas correctas, y sigue reventando la instalación con un error de referencia externa que ningún parser de Python va a ver.

Esa clase de fallo no se detecta leyendo el archivo. Se detecta instalando.

La cadena de puertas: siete deterministas delante del modelo

La arquitectura de generación de MigrateFelix pone siete puertas deterministas por delante de la IA. Son deterministas a propósito: cuando una de ellas rechaza algo, rechaza siempre lo mismo por la misma razón, y esa razón se puede escribir en un mensaje de error que un desarrollador entiende. Un modelo de lenguaje juzgando su propio output no tiene esa propiedad.

Detrás de las puertas hay una capa de lint con diecisiete reglas. Un linter no es un validador — no te dice que el módulo funciona, te dice que no comete errores que ya sabemos identificar. Es barato, es rápido, y captura una cantidad sorprendente de cosas antes de que cueste dinero: cada módulo generado consume entre un dólar y un dólar y medio de inferencia, medido, no estimado, y alrededor del 39% de eso se va en el paso de consultoría. Cada iteración que evitas es plata que no gastas.

Pero ni las siete puertas ni las diecisiete reglas prueban nada. Solo reducen la probabilidad de llegar a la parte cara con algo obviamente roto.

La única puerta que prueba algo

La prueba es instalar el módulo en un Odoo 19 Enterprise real y ver si arranca.

No un contenedor simulado, no un parser del manifiesto, no un "verificamos que las dependencias existan". Una instancia de verdad, ejecutando el -i de verdad, con el registro de modelos cargándose de verdad. Es lento, cuesta recursos y complica muchísimo la infraestructura. Y es la única parte del pipeline cuyo resultado significa algo, porque es la única que reproduce exactamente lo que le va a pasar al usuario cuando instale lo que le entregamos.

Todo lo demás en la cadena es una heurística para no llegar hasta aquí con basura. Esto es el experimento.

Cuando falla: bisecar hasta el registro culpable

Un fallo de instalación en Odoo te da un traceback, y ese traceback muchas veces apunta al cargador de datos, no a lo que está mal. "No se pudo cargar el archivo X" cuando el problema es un registro dentro del archivo X que referencia algo que todavía no existe.

La puerta de validación en runtime bisecta: divide el conjunto de registros, vuelve a intentar, y repite hasta aislar el registro concreto que rompe la carga. Es la misma idea que git bisect aplicada a datos de instalación, y convierte "este módulo no instala" en "esta línea de este archivo no instala", que es una frase sobre la que sí se puede actuar.

El bucle de reparación estaba ciego

Aquí está el error que más me costó ver, y es mío.

La instalación de prueba corre en un subproceso. Cuando fallaba, el bucle de reparación recibía un fallo — pero no la excepción real. La cadena de excepciones se quedaba del otro lado del límite del subproceso, y lo que llegaba al paso de reparación era, en la práctica, "algo salió mal". El bucle entonces intentaba arreglar un módulo sin saber qué había pasado.

Es difícil exagerar lo malo que es esto. Un bucle de reparación que no lee el error no está reparando: está generando variantes al azar y esperando que una pase. Y lo peor es que desde fuera se ve idéntico a un bucle que funciona, porque a veces una variante pasa.

El arreglo fue propagar la cadena de excepciones real a través del límite del subproceso, para que el paso de reparación lea el mismo traceback que leería un humano. No es un cambio glamoroso. Cambió por completo lo que significa un intento de reparación.

"Un fallo honesto antes que un éxito falso"

Esa frase está escrita en el manual interno del proyecto, y llegó ahí por una razón concreta.

Había treinta trabajos en el historial con estado terminal de éxito y ningún artefacto asociado. Treinta veces el sistema dijo completed y no había módulo que entregar. Nadie lo notó durante mucho tiempo, y no lo notó porque nada lo comprobaba: el estado y el entregable eran dos cosas que vivían en tablas distintas y nadie había escrito la regla que las une.

El primer instinto es documentarlo. "El servicio debe verificar que el artefacto existe antes de marcar el trabajo como completado." Eso es una convención, y las convenciones se cumplen hasta que alguien añade un camino de código nuevo un viernes.

Ahora es una restricción a nivel de base de datos. Un trabajo no puede tener estado terminal de éxito sin una fila de artefacto — no porque el servicio se acuerde de comprobarlo, sino porque el motor rechaza la escritura. La diferencia entre las dos versiones es la diferencia entre una regla que se sigue y una regla que no se puede romper.

Tiene un coste real, y vale la pena decirlo: ahora hay caminos donde el sistema falla ruidosamente en lugar de degradarse en silencio, y algunos de esos fallos son incómodos para el usuario. Ese es exactamente el intercambio. Un usuario que ve un error sabe que tiene que hacer algo. Un usuario que ve completed y no tiene módulo no sabe nada, y encima confía.

Un hito, y lo que no significa

A mediados de agosto de 2026, dos módulos salieron con cero intentos de reparación. Primera vez en la historia del proyecto.

Es un dato con fecha y no es una tasa. Dos módulos no son una medición, son dos módulos; extrapolar un porcentaje de ahí sería exactamente el tipo de afirmación que este post está argumentando en contra. Lo anoto porque es la primera evidencia de que la cadena entera — puertas, lint, instalación real, bisección, reparación con visibilidad del error — puede producir algo correcto a la primera. No porque signifique que lo haga habitualmente.

Lo que me llevo

La lección no es sobre IA. Es sobre qué aceptas como evidencia.

El análisis estático te dice que el código tiene la forma de código que funciona. Solo la ejecución en el entorno de destino te dice que funciona. Cuando construyes un sistema que genera artefactos para otros, la tentación permanente es aceptar la señal barata, porque la señal cara es lenta y complica todo. Y funciona: el sistema pasa a estar verde mucho más rápido.

Simplemente deja de significar algo.

¿Listo para migrar tu módulo?

MigrateFelix automatiza el trabajo repetitivo. Sube tu ZIP y obtén resultados en minutos.

Prueba MigrateFelix gratis →