Desarrollo Asistido por IA: Una Guía Práctica

Cómo producir software fiable cuando la IA escribe la mayor parte del código.

14 min read

Resumen Ejecutivo

La IA ahora puede escribir código funcional más rápido que los humanos. El cuello de botella ha pasado de "¿podemos producir código?" a "¿podemos verificar que realmente funciona?" Esta guía proporciona prácticas concretas para mantener la calidad y evitar modos de fallo comunes.

Hacer

  • Revisar los tests generados por IA tan cuidadosamente como revisarías el código — los tests definen lo que significa "correcto"
  • Usar revisión adversarial — hacer que un modelo de IA intente romper la salida de otro
  • Verificar las dependencias manualmente — la IA alucina nombres de paquetes que los atacantes ocupan
  • Generar documentación junto con el código — la depuración futura depende de ello
  • Enfocar la atención humana en la arquitectura, no en la implementación — límites de estado, modos de fallo, flujo de datos
  • Usar testing basado en propiedades — dejar que las máquinas exploren casos límite en los que no pensarías
  • Requerir observabilidad desde el inicio — si no puedes monitorizarlo, no puedes verificar que funciona
  • Testear tus tests con mutation testing — demostrar que tus tests realmente atrapan fallos

No Hacer

  • Confiar en que pasar los tests significa comportamiento correcto — la IA puede escribir tests que afirman lo incorrecto
  • Asumir que el código generado por IA sigue tus patrones arquitectónicos — verificar estructura, no solo función
  • Saltarse la lectura de package.json, go.mod, o archivos de dependencias — los ataques a la cadena de suministro son reales y van en aumento
  • Depender de la IA para explicar sistemas que ella construyó — las herramientas de resumen alucinan conexiones
  • Tratar la verificación dinámica como gratuita — equilibrar la exhaustividad contra los costes de infraestructura
  • Mergear código sin los dashboards para monitorizarlo — la observabilidad es parte de "hecho"

Contexto: Por Qué Este Enfoque

La revisión de código tradicional — un humano leyendo cada línea — no escala cuando la IA genera código más rápido de lo que los humanos pueden leerlo. Pero abandonar la verificación completamente lleva a sistemas que parecen correctos mientras están fundamentalmente rotos.

La solución es cambiar qué verifican los humanos:

  • Antes: Los humanos inspeccionan detalles de implementación (cómo está construido)
  • Después: Los humanos verifican comportamiento y restricciones (qué hace, qué no debe hacer)

Esto significa leer tests, trazas, métricas y arquitectura — no diffs. Las prácticas a continuación operacionalizan este cambio.


Práctica 1: Revisar Tests, No Solo Código

Cuando la IA genera tanto la implementación como los tests, emerge un bucle peligroso: los tests podrían afirmar comportamiento incorrecto, y la implementación los pasa perfectamente.

El Problema

1// Test generado por IA que "pasa" pero no verifica nada útil
2describe('calculateOrderTotal', () => {
3  it('should calculate the total', () => {
4    const result = calculateOrderTotal(mockOrder);
5    expect(result).toBeDefined(); // Esto no nos dice nada
6  });
7});
1// Test generado por IA que codifica el requisito equivocado
2describe('applyDiscount', () => {
3  it('should apply 10% discount for premium users', () => {
4    const result = applyDiscount(100, { isPremium: true });
5    expect(result).toBe(90); // Pero el requisito de negocio era 15%
6  });
7});

El segundo ejemplo es más peligroso. El test pasa. El código funciona exactamente como se testea. Pero el test codifica la regla de negocio equivocada, y ninguna cantidad de verificación dinámica atrapará esto — porque la verificación misma está equivocada.

La Práctica

Al revisar tests generados por IA, pregunta:

  1. ¿Estos tests codifican los requisitos reales? Cruza referencia con specs, tickets o expectativas de stakeholders.
  2. ¿Qué no se está testeando? Busca brechas — casos de error, condiciones límite, puntos de integración.
  3. ¿Las afirmaciones son significativas? toBeDefined(), toBeTruthy(), o verificar que una función "no lanza" son a menudo inútiles.
  4. ¿Una implementación rota pasaría igualmente? Si sí, el test no tiene valor.

El tiempo de revisión humana se desplaza de la implementación a la especificación. Este es el nuevo cuello de botella.


Práctica 2: Usar Revisión Adversarial

Hacer que un modelo de IA intente romper, criticar o encontrar fallos en la salida de otro modelo.

La Práctica

1Prompt para revisión adversarial:
2
3"Revisa el siguiente pull request. Tu objetivo es encontrar:
41. Errores de lógica o comportamiento incorrecto
52. Vulnerabilidades de seguridad
63. Manejo de errores faltante
74. Violaciones de los patrones arquitectónicos descritos abajo
85. Casos límite que no se manejan
96. Dependencias que parecen sospechosas o innecesarias
10
11Sé adversarial. Asume que el código tiene bugs y encuéntralos.
12
13[pegar código aquí]
14[pegar contexto arquitectónico aquí]"

Esto no garantiza atrapar todos los problemas, pero atrapa un porcentaje significativo que una sola pasada de generación pierde. Diferentes modelos tienen diferentes puntos ciegos.

Limitaciones

La revisión adversarial entre modelos de IA no atrapará fiablemente:

  • Requisitos que están fundamentalmente malinterpretados (ambos modelos comparten el mismo contexto equivocado)
  • Vulnerabilidades de seguridad novedosas fuera de la distribución de entrenamiento
  • Deriva arquitectónica sutil a lo largo del tiempo

Es una capa de defensa, no una solución completa.


Práctica 3: Verificar Dependencias Manualmente

Los modelos de IA frecuentemente alucinan nombres de paquetes. Los atacantes registran estos nombres alucinados e inyectan malware.

El Problema

1// La IA podría generar esto en package.json
2{
3  "dependencies": {
4    "fast-json-sanitizer": "^2.1.0"  // Este paquete no existe
5  }                                    // O peor: un atacante lo registró
6}

Cuando ejecutas npm install, ahora estás ejecutando código controlado por atacantes.

La Práctica

Para cada PR que añade o modifica dependencias:

  1. Verificar que el paquete existe y es legítimo — comprobar npm/PyPI/crates.io directamente
  2. Comprobar el publicador — ¿es el mantenedor esperado?
  3. Mirar los conteos de descargas y actividad de mantenimiento — paquetes recién registrados con pocas descargas son sospechosos
  4. Revisar qué hace realmente el paquete — ¿coincide con lo que necesitas?

Esta es un área donde la revisión estática (leer el archivo de dependencias) sigue siendo esencial. La verificación dinámica no atrapará un paquete malicioso diseñado para pasar tus tests.

Herramientas

  • npm audit / yarn audit atrapan vulnerabilidades conocidas pero no paquetes maliciosos nuevos
  • Socket.dev y herramientas similares pueden señalar patrones de dependencias sospechosos
  • Los archivos lock (package-lock.json, go.sum) siempre deberían revisarse para cambios inesperados

Práctica 4: Generar Documentación Junto con el Código

Cuando la IA genera código que no has leído en detalle, la depuración se vuelve casi imposible sin documentación.

El Problema

A las 3 AM, el sistema falla. Necesitas entender un módulo que nunca has leído. La IA que lo generó no está disponible (o alucina cuando le pides que lo explique). Estás ciego.

La Práctica

Para cada feature o módulo significativo, genera y mantén:

  1. Declaración de propósito — ¿qué hace este módulo y por qué existe?
  2. Diagrama de flujo de datos — ¿qué entra, qué sale, cuál es el camino feliz?
  3. Modos de fallo — ¿qué puede salir mal y cómo debería manejarse?
  4. Dependencias — ¿de qué depende esto y qué depende de esto?
  5. Decisiones clave — ¿por qué se construyó de esta manera y no de otra?
1# Módulo OrderProcessor
2
3## Propósito
4Transforma datos de carrito crudos en pedidos validados, aplicando reglas de precios y verificaciones de inventario.
5
6## Flujo de Datos
71. Recibe CartDTO del servicio de checkout
82. Valida inventario vía InventoryService
93. Calcula precio final vía PricingEngine
104. Persiste en OrderRepository
115. Emite evento OrderCreated
12
13## Modos de Fallo
14- Inventario no disponible: Devuelve 409 Conflict, el carrito permanece intacto
15- Timeout del servicio de precios: Reintenta 3x con backoff exponencial, luego falla con 503
16- Fallo de escritura en base de datos: Registra en cola de cartas muertas para recuperación manual
17
18## Dependencias
19- InventoryService (llamada síncrona)
20- PricingEngine (llamada síncrona)
21- OrderRepository (PostgreSQL)
22- EventBus (async, Kafka)
23
24## Decisiones Clave
25- Se eligió verificación de inventario síncrona sobre consistencia eventual porque
26  vender de más tiene mayor coste de negocio que fallos ocasionales de checkout
27- El precio se calcula del lado del servidor (no confiado desde el cliente) por seguridad

Esta documentación es tu "comprensión justo a tiempo" cuando algo se rompe. Genérala mientras construyes, no después.


Práctica 5: Enfocar la Revisión Humana en la Arquitectura

No puedes leer cada línea. Enfócate en lo que más importa: la forma del sistema, no los detalles de implementación.

Qué Revisar

Revisar EstoNo Esto
Límites de servicios y APIsImplementaciones de funciones internas
Enfoque de gestión de estadoActualizaciones de estado individuales
Estrategia de manejo de erroresCada bloque try/catch
Flujo de datos entre componentesTransformaciones de datos dentro de componentes
Elecciones de dependenciasCómo se usan las dependencias
Límites de seguridadCada validación de entrada

Preguntas para la Revisión Arquitectónica

  1. ¿Esto introduce nuevas dependencias? ¿Están justificadas?
  2. ¿Esto cambia los límites de servicios? ¿Afectará a otros equipos?
  3. ¿Esto crea nuevo estado? ¿Dónde vive? ¿Cómo se gestiona?
  4. ¿Esto cambia los modos de fallo? ¿Qué pasa cuando se rompe?
  5. ¿Esto viola patrones existentes? La consistencia importa para la mantenibilidad.

Los detalles de implementación pueden estar equivocados y aún así arreglarse fácilmente. Los errores arquitectónicos se acumulan y se vuelven caros.


Práctica 6: Usar Testing Basado en Propiedades

Los tests unitarios estándar verifican ejemplos específicos. El testing basado en propiedades genera miles de entradas aleatorias y verifica que las propiedades se mantienen en todas ellas.

¿Qué Es el Testing Basado en Propiedades?

En lugar de escribir:

1test('sort returns sorted array', () => {
2  expect(sort([3, 1, 2])).toEqual([1, 2, 3]);
3  expect(sort([5, 4])).toEqual([4, 5]);
4});

Escribes:

1import { fc } from 'fast-check';
2
3test('sort returns sorted array', () => {
4  fc.assert(
5    fc.property(fc.array(fc.integer()), (arr) => {
6      const sorted = sort(arr);
7
8      // Propiedad 1: La salida tiene la misma longitud que la entrada
9      expect(sorted.length).toBe(arr.length);
10
11      // Propiedad 2: La salida contiene los mismos elementos
12      expect(sorted.slice().sort()).toEqual(arr.slice().sort());
13
14      // Propiedad 3: La salida está realmente ordenada
15      for (let i = 0; i < sorted.length - 1; i++) {
16        expect(sorted[i]).toBeLessThanOrEqual(sorted[i + 1]);
17      }
18    })
19  );
20});

El framework genera cientos de arrays aleatorios y verifica que tus propiedades se mantienen para todos. Encuentra casos límite que no pensarías en testear.

Por Qué Esto Importa para Código Generado por IA

La IA tiende a manejar bien el camino feliz. El testing basado en propiedades explora automáticamente los bordes — entradas vacías, entradas enormes, números negativos, strings unicode, valores nulos — sin que tengas que enumerar cada caso.

Herramientas

  • JavaScript/TypeScript: fast-check
  • Python: Hypothesis
  • Go: gopter, rapid
  • Rust: proptest, quickcheck

Práctica 7: Requerir Observabilidad Desde el Inicio

Si no puedes monitorizar una feature en producción, no puedes verificar que funciona. La observabilidad es parte de la definición de "hecho."

La Práctica

Cada feature se envía con:

  1. Logs estructurados para operaciones clave
  2. Métricas para tasas de éxito/fallo, latencia, throughput
  3. Trazas conectando requests a través de servicios
  4. Alertas para comportamiento anómalo
  5. Dashboards visualizando lo anterior

Ejemplo: Observabilidad Mínima para un Nuevo Endpoint

1async function processOrder(req: Request, res: Response) {
2  const span = tracer.startSpan('processOrder');
3  const startTime = Date.now();
4
5  try {
6    logger.info('Processing order', {
7      orderId: req.body.orderId,
8      userId: req.user.id
9    });
10
11    const result = await orderService.process(req.body);
12
13    metrics.increment('orders.processed', { status: 'success' });
14    metrics.histogram('orders.latency', Date.now() - startTime);
15
16    span.setStatus({ code: SpanStatusCode.OK });
17    res.json(result);
18
19  } catch (error) {
20    logger.error('Order processing failed', {
21      orderId: req.body.orderId,
22      error: error.message
23    });
24
25    metrics.increment('orders.processed', { status: 'failure' });
26    span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
27
28    res.status(500).json({ error: 'Processing failed' });
29  } finally {
30    span.end();
31  }
32}

Cuando la IA genera código, incluye requisitos de observabilidad en el prompt:

1"Genera un endpoint de procesamiento de pedidos. Incluye:
2- Logging estructurado para todas las operaciones
3- Métricas para éxito/fallo y latencia
4- Tracing OpenTelemetry
5- Manejo de errores que preserve el contexto para depuración"

Práctica 8: Testear Tus Tests con Mutation Testing

Si no estás leyendo la implementación, necesitas saber que tus tests realmente atrapan bugs. El mutation testing lo demuestra.

¿Qué Es el Mutation Testing?

El mutation testing modifica automáticamente tu código (introduce "mutantes") y verifica si tus tests fallan. Si los tests siguen pasando cuando el código está roto, tus tests no están atrapando lo que deberían.

Ejemplo

Tu código:

1function isEligibleForDiscount(user: User): boolean {
2  return user.orderCount >= 5 && user.accountAge > 30;
3}

El mutation testing podría crear:

1// Mutante 1: Cambió >= a >
2return user.orderCount > 5 && user.accountAge > 30;
3
4// Mutante 2: Cambió && a ||
5return user.orderCount >= 5 || user.accountAge > 30;
6
7// Mutante 3: Cambió 5 a 6
8return user.orderCount >= 6 && user.accountAge > 30;

Si tus tests pasan con cualquiera de estos mutantes, tienes una brecha. Un usuario con exactamente 5 pedidos debería ser elegible, pero el Mutante 1 lo rechazaría — y si tus tests no atrapan eso, nunca lo sabrías.

Herramientas

  • JavaScript/TypeScript: Stryker
  • Python: mutmut, cosmic-ray
  • Java: PITest
  • Go: go-mutesting

Cuándo Usar

El mutation testing es computacionalmente caro — ejecuta toda tu suite de tests muchas veces. Úsalo:

  • En lógica de negocio crítica (pagos, elegibilidad, precios)
  • Cuando estás delegando la generación de tests a la IA
  • Como verificación periódica en lugar de en cada commit

Práctica 9: Identificar y Proteger Caminos Críticos

No todo el código es igualmente importante. Identifica los caminos donde el fallo es catastrófico y aplica verificación desproporcionada allí.

La Práctica

Para tu sistema, identifica:

  1. Caminos de dinero — cualquier cosa que involucre pagos, precios, facturación
  2. Límites de seguridad — autenticación, autorización, acceso a datos
  3. Integridad de datos — escrituras a almacenamiento persistente, especialmente las irreversibles
  4. Compromisos externos — cualquier cosa que dispare acciones del mundo real (emails, envíos, llamadas API a terceros)

Estos caminos obtienen:

  • Testing más exhaustivo (basado en propiedades, no solo basado en ejemplos)
  • Revisión humana incluso cuando otro código no la tiene
  • Verificaciones adicionales en tiempo de ejecución y monitorización
  • Feature flags y rollouts graduales

Ejemplo: Identificando Caminos Críticos

1Caminos críticos de sistema e-commerce:
2├── Checkout
3│   ├── Cálculo de precio (debe coincidir exactamente con el precio mostrado)
4│   ├── Procesamiento de pago (no debe cobrar doble)
5│   └── Decremento de inventario (no debe sobrevender)
6├── Autenticación
7│   ├── Login (no debe filtrar si el email existe)
8│   ├── Restablecimiento de contraseña (debe expirar tokens correctamente)
9│   └── Gestión de sesión (debe invalidar en logout)
10└── Fulfillment de pedidos
11    ├── Validación de dirección (debe ser enviable)
12    └── Reserva de inventario (debe manejar pedidos concurrentes)

La IA puede escribir el código para estos caminos. La IA puede escribir los tests. Pero la revisión humana de los tests y la arquitectura sigue siendo esencial porque el coste del fallo es alto.


Resumen: La Pila de Verificación

Cuando la IA escribe la mayor parte del código, la verificación ocurre en capas:

CapaQuéQuién
GeneraciónLa IA escribe el códigoIA (Claude, GPT, Gemini, etc.)
Revisión AdversarialUna IA diferente intenta romperloIA
Testing AutomatizadoUnitario, integración, basado en propiedades, mutaciónMáquinas
Verificación de DependenciasVerificar que los paquetes son legítimosHumano (asistido por herramientas)
Revisión de TestsVerificar que los tests codifican requisitos correctosHumano
Revisión ArquitectónicaVerificar que la forma del sistema es correctaHumano
ObservabilidadVerificar comportamiento en producciónMáquinas + Interpretación humana
ResponsabilidadDecidir qué riesgos son aceptablesHumano

El objetivo no es eliminar la participación humana sino enfocarla donde produce más valor: definir la corrección, revisar la verificación y aceptar la responsabilidad.


Esta guía fue escrita por Claude (Opus 4.5) con aportaciones de Gemini 3 y GPT-5.2, basada en conversaciones con Will Worth. Refleja prácticas para un panorama en rápida evolución y debería actualizarse a medida que cambien las herramientas y capacidades.