Programar lo no determinístico: el nuevo paradigma de la ingeniería de software

De la generación automatizada de código a la construcción de sistemas capaces de producir, evaluar, corregir y garantizar software de manera autónoma.

Ilustración de trayectorias de generación dispersas que convergen en una compuerta de validación, de la que sale un único camino aceptado
En este artículo
  1. Introducción
  2. El problema de trabajar con sistemas no determinísticos
  3. Programar sistemas que programan
  4. La arquitectura determinística alrededor de la generación no determinística
  5. La validación ya no puede depender exclusivamente de los tests
  6. El contexto y las restricciones se convierten en parte de la arquitectura
  7. La concurrencia y la consistencia a escala enterprise
  8. La capacidad de autoevaluarse y mejorar
  9. La nueva responsabilidad del developer y del arquitecto
  10. ¿Cómo medimos que realmente funciona?
  11. El futuro no es un developer que programa menos
  12. Conclusión: programar lo no determinístico

Introducción

No estamos hablando simplemente de automatizar la programación. Estamos hablando de un cambio en el paradigma de ingeniería con el que construimos software.

Mi hipótesis es que, durante las próximas décadas, buena parte del trabajo de developers y arquitectos de software consistirá en diseñar sistemas capaces de controlar, evaluar y garantizar resultados producidos por otros sistemas no determinísticos: los modelos de inteligencia artificial.

La generación de código a gran escala ya es una realidad. Modelos como Claude, junto con herramientas como Claude Code, están demostrando que es posible delegar procesos de desarrollo cada vez más complejos, desde modificaciones puntuales hasta implementaciones que involucran múltiples componentes de un sistema.

El problema ya no es únicamente conseguir que un modelo genere código funcional.

El verdadero desafío es conseguir que un sistema pueda generar cantidades masivas de código, tomar decisiones técnicas, modificar arquitecturas existentes y evolucionar aplicaciones sin comprometer su integridad.

Y para conseguirlo necesitamos algo más que mejores modelos.

Necesitamos una nueva forma de hacer ingeniería.

1. El problema de trabajar con sistemas no determinísticos

Los modelos de lenguaje no funcionan como un compilador tradicional.

Ante un mismo contexto, pueden producir implementaciones diferentes, elegir distintas estrategias, modificar componentes de maneras inesperadas o encontrar soluciones que parecen correctas, pero que no cumplen completamente con los requisitos originales.

Incluso cuando obtenemos resultados satisfactorios, no podemos asumir que la siguiente ejecución producirá exactamente el mismo resultado.

Esta característica tiene consecuencias importantes cuando hablamos de desarrollo enterprise.

En un proyecto pequeño, un developer puede revisar manualmente una implementación, identificar problemas y corregirlos. Pero cuando comenzamos a generar código a escala, con múltiples agentes trabajando en paralelo sobre diferentes módulos, ese modelo de supervisión deja de ser viable.

No podemos depender de que un desarrollador revise cada función, cada modificación y cada decisión que toma un modelo.

Tampoco podemos confiar exclusivamente en que el propio modelo evalúe la calidad de lo que acaba de producir.

Necesitamos cambiar el enfoque.

En lugar de intentar eliminar la incertidumbre de los modelos, tenemos que diseñar sistemas capaces de trabajar con ella.

2. Programar sistemas que programan

Imaginemos una plataforma de ingeniería basada en modelos como Claude.

Recibe un requerimiento funcional y tiene que convertirlo en una implementación real dentro de un sistema enterprise.

El proceso podría ser completamente automatizado:

  1. Análisis: interpreta el requerimiento, consulta la documentación y construye el contexto necesario.
  2. Planificación: determina qué componentes deben modificarse, identifica dependencias y propone una estrategia de implementación.
  3. Generación: produce código, pruebas, documentación y modificaciones necesarias.
  4. Validación: ejecuta pruebas funcionales, análisis estático, controles de seguridad y verificaciones arquitectónicas.
  5. Evaluación: compara los resultados con los requerimientos y contratos originales.
  6. Corrección: identifica los problemas, ajusta la implementación y vuelve a ejecutar las validaciones.
  7. Integración: prepara los cambios para su incorporación al sistema cuando se cumplen las condiciones establecidas.
  8. Autoevaluación y mejora: analiza los resultados, identifica patrones de error y propone mejoras para futuras ejecuciones.

Lo interesante es que nosotros no necesitamos programar cada una de las soluciones que produce este sistema.

Necesitamos programar el sistema que determina cómo se producen, bajo qué condiciones, qué decisiones pueden tomar los modelos y cómo se verifica que los resultados sean aceptables.

Esta es una diferencia fundamental.

No estamos automatizando solamente la escritura de código. Estamos construyendo sistemas de producción de software.

3. La arquitectura determinística alrededor de la generación no determinística

Acá está, probablemente, el desafío técnico más importante.

Si el componente encargado de producir código es no determinístico, los mecanismos encargados de controlar sus resultados no pueden depender de que ese componente se comporte siempre de la misma manera.

Necesitamos separar claramente dos responsabilidades:

Diagrama // Dos responsabilidades separadas

Generación de soluciones

  • Probabilística y no determinística
  • El modelo tiene libertad para encontrar una solución
  • Siempre dentro de los límites establecidos

Validación y autorización de resultados

  • Condiciones explícitas, verificables y reproducibles siempre que sea posible
  • El sistema determina si la solución puede avanzar
  • No depende de que el modelo se comporte igual

Esto no significa que toda evaluación pueda reducirse a una condición booleana. Algunas propiedades, como la calidad semántica o la experiencia de usuario, requieren evaluaciones más complejas.

Pero sí significa que debemos establecer límites claros sobre qué puede decidir un modelo y qué condiciones debe cumplir obligatoriamente una implementación.

Por ejemplo, podemos establecer que:

  • Un agente puede modificar determinados módulos, pero no otros.
  • Una implementación debe respetar contratos de API existentes.
  • Ninguna modificación puede introducir dependencias no autorizadas.
  • Los cambios deben superar pruebas funcionales y de integración.
  • Las vulnerabilidades críticas bloquean automáticamente la integración.
  • Las modificaciones sobre componentes sensibles requieren aprobación adicional.
  • Cada cambio debe tener trazabilidad sobre su origen, contexto y validaciones.

El modelo tiene libertad para encontrar una solución dentro de los límites establecidos.

El sistema tiene la responsabilidad de determinar si esa solución puede avanzar.

El ciclo de control

Una arquitectura de este tipo podría representarse de la siguiente manera:

Input → Context → Generation → Evaluation → Validation → Acceptance → Deployment

Interactivo // El ciclo de control

Recorre el ciclo etapa por etapa, incluido lo que pasa cuando una implementación falla.

  1. Input: llega un requerimiento funcional.
  2. Context: se construye el contexto necesario: documentación, contratos, decisiones vigentes.
  3. Generation: el modelo produce una solución dentro de los límites establecidos.
  4. Evaluation: los resultados se comparan con los requerimientos y contratos originales.
  5. Validation: pruebas, análisis estático, seguridad y controles arquitectónicos. Si falla, el sistema identifica qué falló y vuelve a generar con ese contexto, y la nueva implementación pasa otra vez por todos los controles.
  6. Acceptance: el cambio avanza solo si cumple las condiciones; el ciclo de corrección tiene límites y criterios de finalización.
  7. Deployment: el cambio se incorpora al sistema, con trazabilidad sobre su origen, contexto y validaciones.

Pero hay una parte fundamental: el ciclo no termina cuando una implementación falla.

El resultado de cada evaluación debe convertirse en información útil para el siguiente intento.

Si un modelo produce una implementación que no supera las pruebas, el sistema debe poder identificar qué falló, proporcionar el contexto correspondiente y solicitar una nueva solución.

Sin embargo, hay una condición: corregir un problema no significa aceptar automáticamente el resultado.

Cada nueva implementación debe pasar nuevamente por los controles correspondientes. Una corrección puede resolver un defecto e introducir otros.

Por eso, el ciclo de generación y corrección necesita límites, criterios de finalización y mecanismos para evitar iteraciones infinitas o degradación progresiva de la solución.

4. La validación ya no puede depender exclusivamente de los tests

Creo que uno de los errores que podemos cometer al construir estos sistemas es asumir que alcanza con pedirle a un modelo que genere código y sus respectivos tests.

Eso no garantiza nada por sí solo.

Un modelo puede generar una implementación y pruebas que compartan el mismo error conceptual. Puede producir tests que validen el comportamiento que implementó, pero no el comportamiento que realmente necesitaba el negocio.

Por eso, necesitamos independencia entre generación y evaluación.

La validación debería combinar diferentes mecanismos:

Tests determinísticos: pruebas unitarias, integración, contratos y regresión que permitan verificar comportamientos específicos.

Análisis estático: herramientas que detecten problemas de tipado, calidad, dependencias, vulnerabilidades y convenciones.

Validación arquitectónica: controles que comprueben que las modificaciones respetan los límites y las decisiones estructurales del sistema.

Evaluación semántica: mecanismos que permitan comprobar si la implementación realmente satisface los requisitos originales, incluyendo escenarios que no pueden expresarse fácilmente mediante tests convencionales.

Pruebas de comportamiento: ejecución de escenarios reales o simulados para detectar comportamientos inesperados.

Evaluación independiente: cuando se utilizan otros modelos para revisar resultados, sus evaluaciones deben contrastarse con criterios objetivos y no convertirse en la única autoridad de aceptación.

Interactivo // Combinar mecanismos de validación

Activa los mecanismos que describe el artículo y observa qué riesgos de la generación quedan cubiertos. Ninguno alcanza por sí solo.

Mecanismos de validación

Riesgos de la generación no determinística

  • Regresiones en comportamientos conocidos
  • Vulnerabilidades
  • Dependencias no autorizadas
  • Violación de límites estructurales
  • Contratos rotos entre componentes
  • No satisface el requisito original
  • Comportamientos inesperados
  • Código y tests con el mismo error conceptual

Un explicador simplificado: cada mecanismo se asocia con los riesgos que cubre más directamente. En la práctica, las coberturas se superponen y ninguna es total.

La combinación de estos mecanismos permite construir una confianza basada en evidencia, en lugar de depender de la confianza que tengamos en un modelo particular.

Y esto es especialmente importante cuando hablamos de software enterprise, donde un error puede tener consecuencias operativas, económicas, legales o de seguridad.

5. El contexto y las restricciones se convierten en parte de la arquitectura

Otro aspecto que considero fundamental es el contexto.

Un modelo puede ser extraordinariamente capaz, pero si recibe información incompleta, contradictoria o desactualizada, puede producir una implementación técnicamente válida que resulte incorrecta para nuestro sistema.

Por eso, el contexto no debería ser simplemente un conjunto de documentos que le proporcionamos al agente.

Debe formar parte de la arquitectura de ingeniería.

Necesitamos construir mecanismos capaces de proporcionar información actualizada sobre:

  • Estructura y responsabilidades de los componentes.
  • Contratos entre servicios.
  • Modelos de datos y relaciones.
  • Dependencias y versiones.
  • Decisiones arquitectónicas vigentes.
  • Reglas de seguridad.
  • Requerimientos funcionales y no funcionales.
  • Historial de cambios y problemas conocidos.

También necesitamos controlar la evolución de ese contexto.

Si un modelo trabaja con una versión anterior de un contrato o una decisión arquitectónica que ya no está vigente, puede generar cambios incompatibles con el sistema actual.

Esto nos lleva a otro concepto importante: el contexto debe ser versionable, trazable y verificable.

No podemos esperar que los modelos comprendan correctamente un sistema complejo si nosotros no somos capaces de representar su conocimiento de manera estructurada.

6. La concurrencia y la consistencia a escala enterprise

Hay un problema adicional que aparece cuando dejamos de trabajar con un agente y comenzamos a trabajar con decenas o cientos de procesos de generación simultáneos.

La concurrencia.

Imaginemos que varios agentes trabajan sobre el mismo repositorio:

  • Uno modifica el sistema de autenticación.
  • Otro implementa un nuevo módulo de pagos.
  • Otro modifica la capa de persistencia.
  • Otro actualiza las APIs.
  • Otro genera pruebas de integración.

Cada uno puede producir una implementación correcta de manera individual.

Pero eso no significa que todas las implementaciones sean compatibles entre sí.

Pueden aparecer conflictos de dependencias, modificaciones incompatibles, inconsistencias de contratos y regresiones que solamente se manifiestan cuando los componentes se integran.

Por eso, la fábrica de software necesita mecanismos de coordinación y aislamiento.

Algunas de las capacidades necesarias serán:

  • Entornos de trabajo aislados.
  • Control de versiones y cambios concurrentes.
  • Gestión de dependencias entre tareas.
  • Validación de contratos entre componentes.
  • Integración incremental.
  • Pruebas de regresión sobre el sistema completo.
  • Estrategias de rollback.
  • Control de versiones de los modelos, prompts, herramientas y contextos utilizados.

La unidad de trabajo ya no puede ser simplemente una tarea asignada a un agente.

Tiene que ser un cambio controlado dentro de un sistema mucho más grande.

7. La capacidad de autoevaluarse y mejorar

Hay una posibilidad que me parece especialmente interesante: construir sistemas que no solamente generen y corrijan código, sino que también puedan mejorar sus propios procesos de generación.

Imaginemos que una plataforma registra que determinado tipo de implementación produce errores recurrentes.

Puede identificar patrones, analizar las causas, detectar problemas en el contexto proporcionado, revisar las instrucciones utilizadas o proponer modificaciones en los mecanismos de validación.

Incluso puede experimentar con diferentes estrategias de generación y comparar sus resultados.

Pero acá hay una distinción importante.

Un sistema que propone mejoras sobre su propio funcionamiento no debería tener automáticamente autorización para modificar sus mecanismos de control.

De lo contrario, podríamos terminar con un sistema que aprende a evitar sus propias restricciones en lugar de mejorar su calidad.

Por eso, las mejoras deben evaluarse con criterios independientes, compararse contra versiones anteriores y pasar por mecanismos de aceptación antes de incorporarse al proceso productivo.

El objetivo es construir un ciclo de mejora continua que aprenda de sus resultados sin perder las garantías que justifican su autonomía.

8. La nueva responsabilidad del developer y del arquitecto

Todo esto cambia la naturaleza de nuestra responsabilidad profesional.

Si estamos construyendo sistemas que generan software, nuestro trabajo no consiste únicamente en verificar que una implementación particular sea correcta.

Tenemos que garantizar que el proceso que produce miles de implementaciones sea confiable.

Eso implica tomar decisiones sobre:

Arquitectura: definir los límites dentro de los cuales pueden operar los modelos y agentes.

Ingeniería de contexto: garantizar que dispongan de información correcta, suficiente y actualizada.

Diseño de controles: establecer mecanismos independientes para verificar resultados.

Seguridad: definir permisos, aislamiento, políticas de acceso y límites de autonomía.

Observabilidad: poder reconstruir qué hizo el sistema, por qué lo hizo, con qué información y cuáles fueron los resultados.

Evaluación: medir la calidad real de las implementaciones y la eficacia de los mecanismos de control.

Gobernanza: establecer qué decisiones pueden automatizarse y cuáles requieren responsabilidad humana.

Hay algo que considero fundamental: no podemos delegar la responsabilidad sobre la calidad del software simplemente porque el código haya sido producido por una inteligencia artificial.

El sistema puede generar, evaluar, corregir y desplegar.

Pero somos nosotros quienes debemos diseñar las condiciones bajo las cuales puede hacerlo.

9. ¿Cómo medimos que realmente funciona?

Si queremos llevar este modelo a entornos enterprise, necesitamos dejar de medir el éxito por la cantidad de código generado o la velocidad de los agentes.

La métrica importante es la confiabilidad del proceso completo.

Algunas métricas que considero fundamentales:

MétricaQué queremos conocer
First-pass acceptance rateQué porcentaje de cambios supera las validaciones en el primer intento
Defect escape rateCuántos defectos llegan a producción
Regression rateCuántos cambios introducen regresiones
Human intervention rateCuántos cambios necesitan intervención humana
False acceptance rateCuántos cambios incorrectos son aceptados
Recovery timeCuánto tarda el sistema en recuperarse de un fallo
Cost per accepted changeCuánto cuesta producir un cambio que realmente cumple los requisitos

Hay una métrica que me parece especialmente importante: la tasa de falsos negativos de nuestros mecanismos de validación, es decir, los errores que el sistema deja pasar.

Podemos tener una fábrica que genere miles de cambios y que rechace correctamente la mayoría de los defectuosos. Pero si algunos cambios incorrectos llegan a producción sin ser detectados, todavía tenemos un problema.

Por eso, la confiabilidad no puede medirse solamente por la cantidad de implementaciones exitosas.

También debemos medir nuestra capacidad para detectar las implementaciones incorrectas.

Y, fundamentalmente, debemos establecer qué nivel de riesgo es aceptable según el tipo de sistema y el impacto potencial de un fallo.

10. El futuro no es un developer que programa menos. Es un developer que diseña sistemas de producción de software

Mi visión es que este paradigma va a definir buena parte de nuestra profesión durante las próximas décadas.

Vamos a trabajar cada vez más con modelos capaces de generar código, arquitecturas, documentación, pruebas e incluso propuestas de infraestructura.

Nuestra especialización estará cada vez más vinculada a entender esos modelos, conocer sus capacidades y limitaciones, y construir sistemas que permitan aprovecharlos sin perder el control sobre los resultados.

No significa que dejaremos de necesitar conocimientos profundos de ingeniería de software. Los vamos a necesitar para poder diseñar los mecanismos que permitan verificar lo que producen estos sistemas.

Tampoco significa que todo pueda automatizarse o que podamos garantizar matemáticamente que nunca habrá errores.

Significa que debemos dejar de pensar en la generación de código como una actividad aislada y comenzar a diseñar procesos completos de producción, validación y evolución de software.

La verdadera innovación no estará solamente en conseguir modelos capaces de escribir más código o resolver problemas más complejos.

Estará en nuestra capacidad de construir sistemas que puedan utilizar esos modelos para producir software confiable, de manera continua y a una escala que hoy sería difícil de gestionar manualmente.

Conclusión: programar lo no determinístico

Creo que estamos frente a un nuevo paradigma de ingeniería.

Pasamos a construir sistemas determinísticos de control, evaluación y garantía alrededor de procesos de generación inherentemente no determinísticos.

Y esto abre una nueva especialización para developers y arquitectos: diseñar los mecanismos que permitan que sistemas de inteligencia artificial produzcan software de manera autónoma, segura y confiable.

El desafío ya no es escribir código, ni definir cada decisión arquitectónica, ni administrar directamente cada componente de infraestructura.

El desafío es cómo construimos control de calidad sobre código que no escribimos, sobre arquitecturas que no definimos y sobre infraestructuras que no poseemos.

Y, sobre todo, cómo garantizamos que todo eso funcione.

Ese es el problema de ingeniería que creo que tenemos por delante.

Y, en mi opinión, es hacia donde se dirige el desarrollo de software.

Compartir LinkedIn X

Escrito por Sebastián Larrauri

Claude Forward Deployed Engineer y arquitecto de sistemas de IA. Claude Certified Developer – Foundations (CCDV-F) y titular del Claude Code Partner Badge, con las certificaciones Associate y Architect en progreso.

Artículos relacionados

Ver todos los artículos Agendar discovery de arquitectura de IA (15 min)