Pilar 3: AI Verification Playbook — confía en la velocidad, verifica la corrección

Línea de apertura (opcional, para el feed): Claude generó el controlador REST en 20 segundos. Pasó code review. Llegó a producción. Dos días después, el campo monto devolvía la ciudad del cliente. Aquí está el playbook de verificación sistemática que lo habría atrapado en 30 segundos.
Parte de la serie "Los 6 pilares del desarrollo asistido por IA" — basado en el material de Thor Henning Hetland (Totto) / Ægis. Adaptado a proyectos Java.
El Pilar 1 le da contexto al modelo. El Pilar 2 elige el modelo correcto para cada tarea. El Pilar 3 responde la pregunta que los dos anteriores no pueden responder por sí solos:
¿Y si el código se ve perfecto… pero está mal?
La IA genera rápido. Eso no está en discusión. El problema es que también alucina con confianza: inventa campos, desalinea DTOs, implementa la lógica que "tiene sentido" en lugar de la que dice la especificación. Y lo hace con el mismo estilo limpio y bien formateado que el código correcto.
La verificación sistemática no es desconfianza en la IA. Es el cinturón de seguridad: no lo usas porque desconfíes del conductor, lo usas porque el mundo tiene variables que ningún conductor controla.
El costo de no verificar
Antes de ver las técnicas, la economía del problema:
| Enfoque | Tiempo | Costo | Bugs detectados |
|---|---|---|---|
| Sin verificación | 0 min | $0 | 0 (hasta que explota) |
| Revisión manual de PR | 30 min | ~$50 | ~60% |
| Verificación sistemática | 5 min | ~$0.10 | ~95% |
| War room en producción | 2–8 h | $200–$800+ | 100%… después del daño |
La regla que vale la pena memorizar:
Si verificar tarda más que reimplementar, lo estás haciendo mal.
5 minutos de verificación sistemática evitan 2 a 8 horas de war room. El ROI es de 10× a 50× en tiempo, y mucho más en reputación y confianza del equipo.
El toolkit de 6 estrategias
No es una herramienta mágica. Son seis técnicas complementarias. Ninguna alucinación sobrevive a una verificación sistemática que combina las correctas para cada contexto.
Estrategia 1 — Round-trip: la invariante más barata
Configuración: 5 minutos · Costo: cercano a cero
La idea es deceptivamente simple: lo que entra debe salir igual. Si parseas un objeto, lo serializas de vuelta y lo vuelves a parsear, el resultado debe ser idéntico al original. Si no lo es, la IA inventó o perdió algo.
En Java, el caso más común es la serialización JSON con Jackson. Cuando Claude genera un nuevo DTO o modifica uno existente, esta prueba lo atrapa en 30 segundos:
// PedidoRoundTripTest.java
@SpringBootTest
class PedidoRoundTripTest {
@Autowired
private ObjectMapper objectMapper;
@Test
void roundTrip_PedidoDTO_preservaTodosLosCampos() throws Exception {
PedidoDTO original = PedidoDTO.builder()
.id(1L)
.numero("PED-2024-001")
.monto(new BigDecimal("1250.75"))
.estado(EstadoPedido.CONFIRMADO)
.clienteId(42L)
.build();
// Claude generó la serialización: ¿inventó un campo? ¿perdió uno?
String json = objectMapper.writeValueAsString(original);
PedidoDTO restaurado = objectMapper.readValue(json, PedidoDTO.class);
assertEquals(original, restaurado,
"El round-trip JSON debe preservar todos los campos sin excepción");
}
@Test
void roundTrip_PedidoDTO_conCamposOpcionales() throws Exception {
// Probar con nulls — la IA a veces "adivina" valores por defecto
PedidoDTO conNulos = PedidoDTO.builder()
.id(2L)
.numero("PED-2024-002")
.monto(new BigDecimal("0.01"))
.estado(EstadoPedido.PENDIENTE)
.clienteId(null) // campo opcional
.build();
String json = objectMapper.writeValueAsString(conNulos);
PedidoDTO restaurado = objectMapper.readValue(json, PedidoDTO.class);
assertEquals(conNulos, restaurado);
assertNull(restaurado.getClienteId());
}
}
Para operaciones bidireccionales (por ejemplo, exportar e importar datos entre sistemas), el round-trip se extiende a dos niveles:
Lo que detecta en proyectos Java reales:
@JsonPropertycon nombre incorrecto (la IA usa el nombre del campo Java, no el del contrato)Campos añadidos que no están en la especificación OpenAPI
BigDecimalconvertido aDoublesilenciosamente (pérdida de precisión en montos)LocalDateTimesin@JsonFormat— diferente representación en cada JVM
Estrategia 2 — Propiedades: prueba la regla, no el ejemplo
Configuración: moderada · Costo: $0
Los unit tests clásicos prueban casos específicos. La IA es muy buena generando tests que ella misma pasa. Las pruebas de propiedades son distintas: definen una regla que debe cumplirse para cualquier input posible, no solo para los tres ejemplos que el modelo eligió.
En Java el motor más directo es jqwik, que funciona sobre JUnit 5:
<!-- pom.xml -->
<dependency>
<groupId>net.jqwik</groupId>
<artifactId>jqwik</artifactId>
<version>1.9.2</version>
<scope>test</scope>
</dependency>
// PedidoPropiedadesTest.java
import net.jqwik.api.*;
import net.jqwik.api.constraints.*;
class PedidoPropiedadesTest {
// Propiedad 1: el monto nunca puede ser negativo
@Property
void monto_siempreNoNegativo(
@ForAll @BigRange(min = "0", max = "999999.99") BigDecimal monto) {
PedidoDTO pedido = new PedidoDTO(1L, "TEST-001", monto, EstadoPedido.PENDIENTE, null);
assertTrue(pedido.getMonto().compareTo(BigDecimal.ZERO) >= 0,
"Un pedido no puede tener monto negativo: " + monto);
}
// Propiedad 2: round-trip para cualquier monto válido
@Property
void roundTrip_cualquierMontoValido(
@ForAll @BigRange(min = "0.01", max = "999999.99") BigDecimal monto)
throws Exception {
PedidoDTO original = new PedidoDTO(1L, "PROP-TEST", monto, EstadoPedido.CONFIRMADO, 10L);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(original);
PedidoDTO restaurado = mapper.readValue(json, PedidoDTO.class);
assertEquals(0, original.getMonto().compareTo(restaurado.getMonto()),
"El monto no debe perder precisión en la serialización");
}
// Propiedad 3: no puede haber referencia a clienteId inexistente
@Property
void lineaPedido_clienteIdDebeTenerValorPositivoSiPresente(
@ForAll @Positive Long clienteId) {
PedidoDTO pedido = new PedidoDTO(1L, "PROP-002", BigDecimal.TEN,
EstadoPedido.PENDIENTE, clienteId);
assertTrue(pedido.getClienteId() > 0,
"clienteId debe ser positivo si está presente");
}
}
La diferencia con los unit tests convencionales:
"La IA es buena inventando tests que ella misma pasa. Las propiedades no le preguntan un caso: le preguntan siempre."
Un alumno que memoriza 3 ejercicios del libro saca 100 en el examen de esos 3 ejercicios. En el examen real con miles de variaciones, se cae. Las propiedades son el examen real.
Estrategia 3 — El patrón Haiku verify
Configuración: 5 minutos · Costo: $0.07 por ejecución
Este patrón usa Haiku 4.5 (el modelo más barato) en tres pasos en cadena para verificar lo que generó un modelo más caro:
| Paso | Acción | Costo |
|---|---|---|
| 1 | Haiku 4.5 genera el código | $0.04 |
| 2 | Haiku 4.5 genera las pruebas round-trip | $0.02 |
| 3 | Haiku 4.5 contrasta implementación vs. especificación OpenAPI | $0.01 |
| 4 | Maven / Gradle ejecuta las pruebas | $0.00 |
| 5 | Revisión humana aleatoria de puntos críticos | 3 min |
Total: $0.07 + 3 minutos vs. revisión manual completa: $50 + 30 minutos.
La secuencia de prompts para un controlador Spring Boot:
Prompt 1 (Sonnet 5 — genera el ejemplar):
"Implementa PedidoController con los endpoints POST /pedidos y
GET /pedidos/{id} según esta especificación OpenAPI: [spec aquí].
Usa Spring Boot 3, jakarta.*, y el patrón de respuesta ApiResponse<T>
que ya existe en el proyecto."
Prompt 2 (Haiku 4.5 — genera pruebas):
"Dado este PedidoController [código], escribe pruebas MockMvc que
verifiquen el contrato de la API: status codes, estructura del JSON
de respuesta, y que ningún campo del DTO se pierda o cambie de nombre."
Prompt 3 (Haiku 4.5 — contrasta vs. spec):
"Compara este PedidoController [código] con esta especificación OpenAPI
[spec]. Lista cualquier discrepancia: campos extra, campos faltantes,
tipos incorrectos, o comportamientos no especificados."
Tú revisas los puntos críticos que señala el paso 3. El resto lo verifica la máquina.
"Modelo barato + verificación fuerte > modelo caro + fe ciega."
Estrategia 4 — Corpus real: no uses datos de juguete
La debilidad de los tests con datos inventados: la IA elige los datos que mejor funcionan con su implementación. El corpus real usa archivos o registros reales de producción —anonimizados— para descubrir edge cases que nadie anticipó.
En proyectos Java, esto significa mantener un directorio src/test/resources/corpus/ con muestras representativas:
src/test/resources/corpus/
├── pedidos/
│ ├── pedido-monto-maximo.json # monto = 999999.99
│ ├── pedido-cliente-nulo.json # clienteId ausente
│ ├── pedido-caracteres-especiales.json # nombres con ñ, acentos
│ ├── pedido-estado-cancelado.json # flujo de cancelación
│ └── pedido-multiples-lineas.json # 50+ líneas de detalle
// PedidoCorpusTest.java
@ParameterizedTest
@MethodSource("archivosDePedidosReales")
void corpus_cadaArchivoPasaRoundTrip(Path archivoJson) throws Exception {
String jsonOriginal = Files.readString(archivoJson);
PedidoDTO pedido = objectMapper.readValue(jsonOriginal, PedidoDTO.class);
String jsonReserializado = objectMapper.writeValueAsString(pedido);
PedidoDTO restaurado = objectMapper.readValue(jsonReserializado, PedidoDTO.class);
assertEquals(pedido, restaurado,
"El archivo " + archivoJson.getFileName() + " falló el round-trip");
}
static Stream<Path> archivosDePedidosReales() throws IOException {
return Files.list(Path.of("src/test/resources/corpus/pedidos"));
}
Cinco archivos reales descubren más bugs que cien tests inventados.
La anatomía de una alucinación — historia real (adaptada a Java)
Este es el patrón que más se repite en producción y que más fácilmente pasa la revisión humana.
Acto 1 — El DTO de aspecto perfecto
Claude genera PedidoDTO para un endpoint de migración Spring Boot 2 → 3. El código es limpio, está bien comentado, compila sin warnings:
// Lo que generó Claude
public record PedidoDTO(
Long id,
String referencia, // ← campo inventado por la IA
String numero,
BigDecimal monto,
EstadoPedido estado
) {}
Pasó la revisión del PR. Pasó los tests unitarios generados por la propia IA. El test de PedidoServiceTest usaba exactamente los mismos campos que el DTO generado.
Acto 2 — La falla oculta
La especificación OpenAPI decía:
PedidoDTO:
type: object
properties:
id:
type: integer
numero:
type: string
monto:
type: number
estado:
type: string
enum: [PENDIENTE, CONFIRMADO, CANCELADO]
Sin campo referencia. La IA lo inventó porque en el servicio adyacente existía un campo con ese nombre. El sistema compiló, los tests pasaron, el deploy fue a producción.
En producción: el API recibía JSONs sin el campo referencia. Jackson lo deserializaba a null. Downstream, el procesador de pagos esperaba ese campo como parte del contrato interno y fallaba silenciosamente.
Acto 3 — Lo que lo hubiera capturado en 30 segundos
@Test
void roundTrip_PedidoDTO_contraCorpusReal() throws Exception {
// Este JSON viene del corpus — formato real del cliente
String jsonReal = """
{
"id": 101,
"numero": "PED-2024-001",
"monto": 450.00,
"estado": "CONFIRMADO"
}
""";
PedidoDTO pedido = objectMapper.readValue(jsonReal, PedidoDTO.class);
String jsonReserializado = objectMapper.writeValueAsString(pedido);
// Si 'referencia' es un campo requerido (no nullable),
// la deserialización habría fallado aquí mismo.
// Si es nullable, el round-trip habría devuelto null
// en lugar de ausente, alertando la discrepancia.
assertThat(jsonReserializado).doesNotContain("referencia");
}
Capturado en 30 segundos. Previno más de 4 horas de war room.
La pregunta que vale plantear en el próximo PR de 400 líneas generado por IA: ¿quién lo habría visto en la revisión manual un viernes a las 6 PM?
Incorporar la verificación al pipeline Java
La verificación sistemática no es responsabilidad de un desarrollador individual en cada PR. Es una política del pipeline. Así queda integrado en un proyecto Spring Boot típico:
El checklist Pre-PR para código generado por IA
Agrégalo como comentario en el PR template de tu repositorio:
## Checklist — código generado con IA
- [ ] ¿Hay test round-trip para cada DTO nuevo o modificado?
- [ ] ¿El corpus de archivos reales cubre este componente?
- [ ] ¿Haiku verificó la implementación contra la spec OpenAPI?
- [ ] ¿El test diferencial compara el nuevo comportamiento con el anterior?
- [ ] ¿Spot-check humano de 3 min en los campos críticos (montos, IDs, estados)?
- [ ] ¿`CLAUDE.md` tiene la regla: "todo parser nuevo requiere round-trip"?
La línea para CLAUDE.md
## Reglas de verificación (obligatorias)
Todo componente nuevo que parsee, serialice o transforme datos DEBE incluir:
1. Test round-trip con JUnit 5 + Jackson (o el serializador del proyecto)
2. Al menos un archivo del corpus real en `src/test/resources/corpus/`
3. Test de propiedades con jqwik para invariantes de negocio críticas
Si la spec OpenAPI cambia, el test Haiku verify confirma alineación antes del merge.
Métricas de éxito
| KPI | Meta | Qué medir en Java |
|---|---|---|
| Tasa de captura pre-producción | >95% | Bugs de IA encontrados antes del merge / total |
| Tiempo de verificación | <10% del tiempo de desarrollo | Si la feature tomó 2h → verificar ≤12 min |
| ROI | 10×–50× | Tiempo evitado en war room / tiempo invertido en verificación |
| Falsos positivos | <5% | Tests que fallan sin bug real — si molesta siempre, nadie lo usa |
| Bugs de IA en producción | Cercano a cero | El objetivo final |
Cuando llega un bug de IA a producción, la pregunta correcta no es "¿quién lo aprobó en el PR?". La pregunta es: "¿qué estrategia de verificación faltaba?". La respuesta se convierte en una nueva regla en CLAUDE.md.
Acciones inmediatas
Hoy (30 minutos)
Identifica los 2–3 DTOs más críticos de tu proyecto Spring Boot (los que mueven dinero, estados de negocio, o integran con sistemas externos)
Escribe un test round-trip para cada uno — si ya tienes
@SpringBootTestes cuestión de agregar el testAgrega al
CLAUDE.mddel proyecto: "Todo DTO nuevo requiere test round-trip"
Esta semana
Crea
src/test/resources/corpus/con 5–10 JSONs reales anonimizadosIntegra el checklist Pre-PR al template del repositorio
Implementa el patrón Haiku verify para los endpoints de mayor tráfico
Este mes
Añade jqwik para las invariantes de negocio críticas (montos, estados, referencias)
Conecta los corpus tests al CI/CD — si falla el round-trip, el pipeline no avanza
Meta: tasa de captura pre-producción >95%
La ecuación que cambia la perspectiva
"Haiku barato + verificación fuerte es matemáticamente superior a un modelo caro con fe ciega."
La IA sin verificación es velocidad hacia el precipicio. Con el playbook de verificación, es el 10× de productividad que promete — sin pagar el precio en producción.
El Pilar 1 te da el contexto. El Pilar 2 te ahorra dinero. El Pilar 3 hace que los dos anteriores sean seguros de usar.
¿Qué estrategia de verificación usas hoy en tu proyecto Java? ¿Round-trip, propiedades, algo diferente? Cuéntame en los comentarios.
Enjoy!
Joe





