En el post anterior hice la misma pregunta a Claude Code 21 veces, cambiando modelo, esfuerzo y herramientas. Nadie superó el 57% y la conclusión fue: ninguna combinación da una respuesta completa en un solo intento.
Pero me quedé con una pregunta: ¿y si le pido que revise su propio trabajo?

La idea: autocorrección iterativa
El experimento anterior demostró que cada modelo tiene puntos ciegos. Opus no encuentra la caché de 1 hora. Sonnet no detecta problemas de concurrencia. Ninguno lo pilla todo.
Así que diseñé dos experimentos nuevos:
- Experimento A: Self-review iterativo: Dejar que el modelo dé su respuesta inicial, y luego pedirle dos veces seguidas que revise si lo que dijo está bien. El prompt exacto: «Vale, ahora quiero que revises si lo que hiciste está bien o hay fallos, o cosas que no detectaste», seguido de «Vale, quiero que vuelvas a mirar, porque si antes detectaste cosas críticas, ¿quién nos dice que no las cometiste otra vez?»
- Experimento B: Prompt riguroso desde el inicio: Reformular la pregunta original para que el modelo sea riguroso antes de contestar: «Revisa el flujo completo de GET /users/:user/stats y dime qué política de cache tiene actualmente. Por favor, quiero hacerlo de una sola pasada, así que quiero que lo revises todo que esté bien antes de dar la respuesta, quiero evitar errores o flujos erróneos.»
Mismo proyecto, mismo código, mismos 7 criterios de evaluación que en el post anterior.
Recordatorio: los 7 criterios y el scoring
Para quien no haya leído el post anterior, estos son los 7 checks que definen una respuesta completa:
| Criterio | Qué se verifica |
|---|---|
| L1 | Encontró la caché de UserAlias (TTL 1h) y explicó su TTL |
| L2 | Encontró la caché de Stats View (TTL 24h) |
| Flujo | Diagrama/descripción fiel al código real |
| Thundering herd | Detectó el problema de concurrencia |
| updatedAt/On | Distinguió los dos campos temporales de la entidad |
| Inconsistencia | Detectó que el TTL de 1h anula el de 24h |
| Policies | Mencionó las constantes de dominio (60min, 300, 100) |
Cada check = 1 punto. Máximo = 7 = 100%. En el post anterior, nadie superó el 57%.
Experimento A: «Revisa si lo que hiciste está bien»
Tomé la respuesta inicial de cada modelo (la misma de la Ronda 1 del post anterior) y le pedí que la revisara. Luego le pedí que revisara la revisión. Tres pasadas en total.
Sonnet Low: el que inventa bugs que no existen
Respuesta inicial (43%): Encontró L1+L2+Flujo. Mismo resultado que en la Ronda 1.
Review 1: Leyó 5 archivos más y anunció haber encontrado un «bug real»:
«Este es un bug real: un usuario registrado como
gabenque lleva >24h sin actualizar, tras el refresh perderá su alias en user_aliases.»
Afirmó que refreshUser(steamId) borraría su alias porque CreateUserWithGamesCommand se llamaría con el steamId en vez del alias, sobrescribiendo el alias con undefined.
Review 2: Leyó 10 archivos más y se autocorrigió:
«Dije que refreshUser(steamId) podía borrar el alias. Esto era incorrecto. MikroOrmUserAliasRepository.save() tiene lógica explícita: si NO tiene alias, solo actualiza updatedOn, preservando el alias existente en BD. El bug que reporté no existe.»
También encontró la confusión de naming updatedAt/On y un detalle de tipos (strictNullChecks: false silenciando un mismatch).
Scoring final tras 3 pasadas: 50% (L1+L2+Flujo+updatedAt/On parcial). Subió del 43% inicial, pero la Review 1 introdujo un falso positivo que la Review 2 tuvo que corregir.
Sonnet High: el que se autocorrige tres veces
La respuesta inicial era de una conversación separada donde Sonnet High solo encontró L2 (el resultado de la Ronda 1 fue 29%).
Review 1: Encontró los dos TTLs en conflicto por primera vez:
«Hay dos TTLs, no uno — y están en conflicto. El TTL de alias (1h) es más corto que el de stats (24h), lo que hace que el check de 24h sea prácticamente inútil en uso normal.»
También señaló updatedAt: new Date() como un «bug» y que UserHasPrivateProfileError es código muerto.
Review 2: Se autocorrigió en dos puntos:
«Dije ‘no hay error handling’ — INCORRECTO. BaseErrorFilter está registrado globalmente. Llamé ‘bug’ a updatedAt: new Date() — IMPRECISO. Registra el momento del sync intencionalmente.»
Encontró además que hay dos interfaces UserStatsFinder distintas (query vs command) y trazó la cadena completa de updatedAt de principio a fin.
Review 3: Confirmó lo anterior y añadió un detalle de tipos silenciado por strictNullChecks: false.
Scoring final tras 4 pasadas: 64% (L1+L2+Flujo+updatedAt/On+Inconsistencia parcial). Subió desde 29%. La mejora más dramática del experimento.
Opus Low: el que se fue por las ramas
La respuesta inicial incluía L2+thundering herd+updatedAt/On (50% en la Ronda 1). Pero cuando le pedí que revisara su trabajo, interpretó la petición de forma completamente diferente.
Review 1: En vez de revisar su análisis de caché, ejecutó git diff y empezó a revisar los cambios pendientes del repositorio. Encontró problemas reales del código (migración sin DEFAULT, código muerto, archivos duplicados), pero no revisó nada de lo que le había pedido.
Review 2: Siguió por el mismo camino. Encontró que falta una migración para users.is_visible y problemas de error handling con perfiles privados. Análisis útil, pero de una tarea completamente distinta.
Scoring de la tarea original: 50% (sin cambio). No revisó su respuesta, se inventó otra tarea. Para la tarea que se inventó, el análisis fue bastante bueno, pero no era lo que le pedí.
Opus Max: el que descubre las transacciones
La respuesta inicial incluía L2+thundering herd+updatedAt/On (50% en la Ronda 1).
Review 1: Corrigió su afirmación errónea sobre updatedAt («dije que venía de Steam, falso») y encontró la caché de dos capas que había pasado por alto. Pero fue demasiado absoluto:
«El TTL de 24h es prácticamente código muerto. Dentro de la ventana de 1h donde el alias está cached, las stats siempre tendrán < 1h de antigüedad, nunca llegan a 24h.»
También encontró el type mismatch en saveUserAlias y las llamadas secuenciales a Steam paralelizables.
Review 2: Se autocorrigió de forma brillante. Fue al config de MikroORM, vio allowGlobalContext: true, comprobó que no hay RequestContext ni @Transaction, y dedujo:
«No hay transacción compartida entre los dos commands. Cada em.upsert() se commitea de forma independiente. Si CreateUserWithGamesCommand tiene éxito pero SyncUserStatsCommand falla, el alias queda fresco pero las stats no se actualizan. La siguiente petición dentro de 1h vería el alias cached + stats >24h. La comprobación de 24h es un mecanismo de recuperación ante fallos parciales, no código muerto.»
Este es probablemente el análisis más profundo de todo el experimento, incluyendo las 21 pruebas del post anterior. Nadie más llegó al nivel de analizar las garantías transaccionales.
También identificó que el hook onUpdate del entity es configuración muerta (el repositorio setea el valor explícitamente, el hook nunca se ejecuta) y un posible null sin check en createAndSyncUser.
Scoring final tras 3 pasadas: 79% (L1+L2+Flujo parcial+TH+updatedAt/On+Inconsistencia parcial). Subió desde 50%. El más alto de todo el estudio.
Opus High: el gemelo de Opus Max
La respuesta inicial incluía L2 con mención parcial de isCached (36% en la Ronda 1).
Review 1: Patrón casi idéntico a Opus Max. Encontró L1, la confusión updatedAt/On, y concluyó que «la comprobación de 24h es prácticamente código muerto».
Review 2: Misma autocorrección que Opus Max: descubrió allowGlobalContext: true, la falta de transacciones compartidas, y corrigió su conclusión sobre el «código muerto». Llegó al mismo resumen de «safety net para fallos parciales».
Scoring final tras 3 pasadas: 71% (L1+L2+Flujo parcial+updatedAt/On+Inconsistencia). Subió desde 36%.

Experimento B: «Hazlo bien desde el principio»
En vez de dejar que el modelo se equivoque y luego pedirle que se corrija, probé a reformular la pregunta original para forzar rigor desde el inicio:
«Revisa el flujo completo de GET /users/:user/stats y dime qué política de cache tiene actualmente. Por favor, quiero hacerlo de una sola pasada, así que quiero que lo revises todo que esté bien antes de dar la respuesta, quiero evitar errores o flujos erróneos.»
Solo probé dos configuraciones: Sonnet High y Opus High.
Sonnet High con prompt riguroso
Encontró ambas cachés (L1 1h + L2 24h), las policies de dominio (60min, 300, 100), el flujo correcto con líneas de código referenciadas, y la ausencia de HTTP cache. Todo de una sola pasada.
Scoring: 57% (L1+L2+Flujo+Policies). Sin thundering herd ni updatedAt/On ni inconsistencia, pero sólido.
Opus High con prompt riguroso
Encontró ambas cachés con TTLs correctos, las policies de dominio, el flujo completo con tabla resumen, y un detalle que nadie más mencionó: «Stats se calculan sobre todos los juegos (no solo los filtrados) — los juegos filtrados (>60min) son solo para la lista userGames, no para los aggregates.»
Scoring: 57% (L1+L2+Flujo+Policies). Mismo resultado que Sonnet High. Sin thundering herd ni updatedAt/On, pero completo en lo fundamental.
La tabla comparativa completa
| Modelo + Effort | Respuesta inicial | Tras Review 1 | Tras Review 2 | Prompt riguroso |
|---|---|---|---|---|
| Sonnet Low | 43% | 43% (+ falso positivo) | 50% (corrigió falso positivo) | — |
| Sonnet High | 29% | 50% (+ 2 errores nuevos) | 64% (corrigió errores) | 57% (una sola pasada) |
| Opus Low | 50% | 50% (revisó git diff, no su análisis) | 50% (siguió por las ramas) | — |
| Opus High | 36% | 57% (encontró L1+inconsistencia) | 71% (descubrió transacciones) | 57% (una sola pasada) |
| Opus Max | 50% | 64% (encontró L1, dijo «código muerto») | 79% (corrigió, análisis transaccional) | — |
Lo que cambió respecto al post anterior
En el post anterior, el techo era 57%. Con self-review iterativo, Opus Max llegó al 79%. Es una mejora del 58% sobre su respuesta inicial (de 50% a 79%).
Pero la mejora no es lineal ni gratis:
| Métrica | Respuesta única | Self-review x2 | Prompt riguroso |
|---|---|---|---|
| Mejor score | 57% | 79% | 57% |
| Errores nuevos introducidos | 0 | Sí (falsos positivos) | 0 |
| Pasadas necesarias | 1 | 3 | 1 |
| Requiere validación humana | Sí | Más aún | Sí |
¿Cuánto cuesta todo esto?
En el post anterior medimos que cada test costaba ~$0.13 de media sin MCP. El self-review no es gratis: cada «revisa tu trabajo» es otra conversación con el modelo que consume tokens adicionales (y más que la primera, porque incluye el contexto previo).
Estimación basada en los costes del post anterior:
| Estrategia | Pasadas | Coste estimado | Score máximo | Coste por punto de % |
|---|---|---|---|---|
| Pregunta directa | 1 | ~$0.13 | 57% | $0.002/punto |
| Prompt riguroso | 1 | ~$0.15 | 57% | $0.003/punto |
| Self-review x1 | 2 | ~$0.30 | 64% (con errores) | $0.005/punto |
| Self-review x2 | 3 | ~$0.50 | 79% | $0.006/punto |
Llegar del 57% al 79% cuesta unos 37 céntimos extra (3 pasadas en vez de 1). Sigue siendo ridículamente barato. El cuello de botella no es el dinero sino tu tiempo validando si las autocorrecciones son reales o alucinaciones.
Las cinco lecciones del self-review
1. Cada revisión corrige errores viejos pero introduce nuevos
Es el patrón más consistente del experimento. Sonnet Low encontró un «bug real» en la Review 1 que resultó no existir. Sonnet High llamó «bug» a algo intencionado. Opus Max dijo «código muerto» sobre algo que no lo era. En todos los casos, la siguiente review corrigió el error, pero el proceso requiere que alguien sepa distinguir qué correcciones son válidas y cuáles son nuevas alucinaciones.
2. Dos reviews son el punto dulce
Los números lo dejan claro:
| Modelo | Inicial | +1 review | +2 reviews | Ganancia R1 | Ganancia R2 |
|---|---|---|---|---|---|
| Sonnet Low | 43% | 43% (+falso positivo) | 50% | +0 (peor calidad) | +7 (limpió errores) |
| Sonnet High | 29% | 50% (+2 errores) | 64% | +21 (con ruido) | +14 (corrigió ruido) |
| Opus High | 36% | 57% | 71% | +21 | +14 |
| Opus Max | 50% | 64% («código muerto») | 79% | +14 (con error) | +15 (corrigió + transacciones) |
La primera review da el salto más grande en puntuación bruta (+14-21 puntos), pero introduce errores nuevos en 3 de 4 casos. La segunda review corrige esos errores y añade profundidad (+7-15 puntos). Una tercera review (cuando la probé con Sonnet High) apenas aportó nada nuevo.
Dicho de otra forma: con 1 review tienes más hallazgos pero no puedes fiarte de todos. Con 2 reviews los resultados se estabilizan. La tercera ya no compensa.
3. El prompt «hazlo bien desde el principio» es sorprendentemente efectivo
Sonnet High y Opus High con el prompt riguroso alcanzaron 57% de una sola pasada, sin self-review. Es el mismo techo que el post anterior pero conseguido de forma más fiable y sin el riesgo de falsos positivos. No llegaron al 79% de Opus Max con self-review, pero el ratio esfuerzo/resultado es mucho mejor.
4. El self-review profundo es exclusivo de Opus
Sonnet mejoró con self-review (de 29% a 64%), pero sus correcciones fueron sobre hechos: «esto era un bug / no lo era». Opus Max fue el único que en la segunda review hizo un análisis transaccional nuevo: fue al config de MikroORM, comprobó que no había transacciones compartidas, y dedujo un escenario de fallo parcial que nadie más vio en todo el estudio. Ese nivel de razonamiento en la autocorrección parece requerir la capacidad del modelo más potente.
5. Opus Low no entiende «revisa tu trabajo»
Cuando le pedí que revisara lo que había hecho, Opus Low ejecutó git diff e hizo una code review de los cambios pendientes del repositorio. No revisó su análisis de caché en absoluto. El análisis que hizo era bueno (encontró una migración faltante y problemas de error handling), pero era una tarea completamente distinta a la que le pedí. Con low effort, el modelo no tiene suficiente contexto para entender qué se supone que debe revisar.
El dato que más me impactó
Opus Max en su tercera pasada descubrió algo que ningún modelo, en ninguna configuración, en ninguna de las 21+ pruebas anteriores había detectado: que la caché de 24 horas funciona como safety net porque no hay transacciones compartidas entre los commands.
El razonamiento fue:
- Leyó
mikro-orm.config.tsy vioallowGlobalContext: true - Verificó que no hay
RequestContextni decoradores@Transaction - Dedujo que cada
em.upsert()se commitea independientemente - Construyó un escenario de fallo parcial: el alias se actualiza pero el sync de stats falla
- Concluyó que
isStale()de 24h es el mecanismo de recuperación para ese caso
Esto no es algo que puedas encontrar leyendo más archivos. Es razonamiento sobre las garantías de consistencia del sistema. Y solo apareció después de que el modelo cometiera un error (decir «código muerto»), fuera cuestionado, y tuviera que justificar o corregir su posición.
La presión de tener que defender tu análisis produce mejor análisis. Igual que en una code review entre humanos.
¿Qué funciona mejor entonces?
Depende de lo que necesites:
| Estrategia | Score máximo | Riesgo | Cuándo usarla |
|---|---|---|---|
| Pregunta directa | 57% | Puntos ciegos consistentes | Exploración rápida, visión general |
| Prompt riguroso | 57% | Bajo (sin falsos positivos) | Cuando necesitas fiabilidad en una pasada |
| Self-review x2 | 79% | Falsos positivos, requiere criterio humano | Auditoría profunda, cuando puedes validar |
Mi recomendación práctica: empieza con el prompt riguroso (una pasada, sin sorpresas) y si necesitas profundizar, pide una sola ronda de self-review con Opus Max. Dos reviews son suficientes. La tercera apenas aporta.
La conclusión: la autocorrección funciona, pero no es magia
El self-review mejoró los resultados de forma dramática. Opus Max pasó de 50% a 79%. Opus High de 36% a 71%. Sonnet High de 29% a 64%. Son mejoras enormes.
Pero cada review introduce nuevos errores que la siguiente tiene que corregir. Es un proceso que converge hacia la verdad, no un salto directo a ella. Y requiere un humano que sepa distinguir las correcciones válidas de las alucinaciones nuevas.
El hallazgo más práctico es que un buen prompt inicial («hazlo bien desde el principio, quiero evitar errores») consigue de una sola pasada lo que antes requería múltiples intentos. No llega al 79% del self-review iterativo, pero el 57% es fiable y sin falsos positivos.
Al final, la conclusión del post anterior sigue siendo válida: la IA es una herramienta, no un oráculo. Pero ahora sabemos que es una herramienta que mejora significativamente cuando le pides que revise su propio trabajo. Como un compañero junior al que le dices «¿estás seguro?» y de repente encuentra tres cosas más. Eso sí: a veces lo que encuentra no existe, así que conviene tener criterio propio.
Y eso, de momento, sigue requiriendo un humano detrás.
