En el artículo anterior contaba cómo montamos un quality gate sobre el diff para el código que escriben los agentes. Terminaba con la fiabilidad del código nuevo en A y la global en E, por el código legacy. Este artículo va de esa E.
La app es Angular 21 + Ionic 8, unas 184.000 líneas. Sonar tenía 3.521 incidencias abiertas y un 56,4 % de cobertura, y estimaba la deuda en 317 horas de trabajo. Nadie iba a dedicarlas, porque la deuda nunca gana a un evolutivo en el planning.
Un workflow de Claude Code con 246 subagentes la bajó a 158 incidencias y subió la cobertura al 77,6 %, en una PR de 1.027 ficheros. Fueron 19 horas de reloj y unos 600 $ en tokens. Yo le dediqué un par de horas como mucho. La PR lleva 16 días mergeada y no hay ningún bug ni revert atribuidos a ella.
El dato que más me sorprendió está en el título: los agentes que arreglaban el código y escribían los tests costaron unos 44 $. Todo lo demás se fue en poder fiarse del resultado. Este artículo va de esa parte, con cifras por capa, porque en mi opinión es la que casi nadie publica.
Qué estaba optimizando
Que un agente sepa cambiar un || por un ?? ya lo sabemos. La pregunta de verdad es otra: si al final tengo una PR de 1.027 ficheros escrita por una IA, ¿cómo la apruebo? Nadie va a leer ese diff, y una PR que no se puede revisar es vibe coding descontrolado.
Así que el objetivo no era el número de incidencias. Era llegar a una PR que una persona pudiera aprobar sin leer el código y sin que aparecieran regresiones después. La revisión humana no desaparece: pasa de leer líneas a comprobar las pruebas de cada cambio y leer con calma los pocos que alteran el comportamiento.
El equipo de Claude Code cuenta algo parecido: optimizan el mínimo tiempo humano de revisión que no aumente reversiones ni incidentes. Aquí lo aplico a un caso concreto, una limpieza masiva, y añado lo que ellos no publican: cuánto cuesta cada capa de verificación y cuántos de sus hallazgos son reales.
Un lote, de principio a fin
Darle la lista de incidencias a una sola sesión no funciona: no cabe en un contexto, no paraleliza y no deja rastro por cambio. Los workflows de Claude Code son un script JavaScript que lanza subagentes. La diferencia con pedirle a un agente que los lance es que el control de flujo es código: qué se paraleliza, qué se revisa y qué pasa si algo falla no depende de que un modelo se acuerde.
La unidad de trabajo es el lote. Un script reparte el escaneo de Sonar en 52 lotes por carpeta, pesando incidencias, líneas sin cubrir y specs que faltan. Este es uno real, recortado:
{ "id": "14", "scope": "src/app/common/validators", "agentType": "angular-implementer", "issueCount": 7, "specsToCreate": 3, "files": [ { "path": "src/app/common/validators/greater-than.validator.ts", "needsSpec": true, "issues": [ {"rule": "typescript:S6606", "line": 16, "coverage": "covered"}, {"rule": "typescript:S6582", "line": 28, "coverage": "covered"} ] } ] } El implementer sigue una escalera de garantías en cada fichero:
- Si no tiene spec, escribe una de caracterización que pase sobre el código sin tocar. Fija lo que el código hace hoy, no lo que debería hacer.
- Si la línea que va a cambiar no está cubierta (el campo
coverage), añade antes un test que fije su salida. - El refactor tiene que ser equivalente.
- Si el cambio es intencionado porque hay un bug real, lleva un test que pasa de rojo a verde y una nota con la prueba manual.
- Si algo queda en rojo, revierte ese cambio y lo marca como skipped. No se debilita ningún test.
En el historial del lote 14 queda así, de más antiguo a más nuevo:
test(validators): caracterizar emailValidator y su regex test(validators): caracterizar greaterThan y lessThan validators refactor(validators): quitar escape innecesario en regex de email refactor(validators): usar ?? y optional chaining en greaterThan y lessThan - const value = control?.value?.toString() || '' + const value = control?.value?.toString() ?? '' - if (compareControl && compareControl.invalid) { + if (compareControl?.invalid) { Cada lote deja una nota con cuatro secciones: cambios de comportamiento, TODOs pendientes, falsos positivos y skipped. Un cambio de comportamiento anotado tiene esta pinta:
payment-manager.component.tsgetData():signature ?? ''sustituye asignature ? signature : ''. Idéntico paranull,undefined,''y cualquier string. Difiere solo para0/false, que no puede llegar desde elSignatureComponent.
Hay 168 notas así, listadas en la PR. El patrón no es perfecto: en tres casos el test y el cambio tuvieron que ir en el mismo commit, porque el test ya llamaba a la API nueva, y la nota lo dice.
Una advertencia sobre la cobertura: sube del 56 al 77 %, pero la mayor parte son tests de caracterización, que fijan también los bugs que el código ya tenía. Sube la red para detectar cambios, no la confianza en que el comportamiento sea el correcto.
Cuánto cuesta fiarse, capa por capa
Cada capa de verificación encontró cosas que las anteriores no habían visto. Esta es la tabla que me habría gustado leer antes de empezar: cuántos hallazgos entregó cada capa, cuántos eran reales y cuánto costó, a precios de lista de la API con la caché de 1 hora de Claude Code.
| Capa | Agentes | Hallazgos | Reales | Coste aprox. | $ por hallazgo real |
|---|---|---|---|---|---|
| Implementers (arreglo + tests) | 66 | — | — | 44 $ | — |
| Revisores por lote (Sonnet) | 124 | sin medir | 1 bloqueante | 30 $ | 30 $ |
| Integración, gates y reescaneo | 5 | 6 incidencias nuevas | 6 | 39 $ | 7 $ |
| E2E contra preproducción | 1 | 10 fallos | 0 regresiones | 10 $ | — |
| Auditoría con otro modelo (Fable) | 8 | 30 | 7 | 69 $ | 10 $ |
/code-review al máximo | 31 | 15 | 13 | 375 $ | 29 $ |
El plan, el autofix mecánico y la sesión principal suman otros 34 $. Total: unos 600 $.
Lo que leo en la tabla:
- Arreglar es barato; verificar, no. El 7 % del gasto fue en hacer el cambio. El resto, en poder demostrar que el cambio era correcto.
- La capa más cara encontró el fallo más grave. Un agente cambió una regex por otra con lookbehind para contentar a Sonar, y el lookbehind no existe en Safari antes de la 16.4. En los iPads más viejos eso rompía el alta de clientes. Ningún test unitario lo ve, porque los tests corren en Chrome. Lo encontró el
/code-reviewal máximo, después de que todas las capas anteriores dieran el visto bueno. - La precisión varía mucho. La auditoría con otro modelo acertó en el 23 % de sus hallazgos; el code-review, en el 87 %. Cada hallazgo se confirmaba o se refutaba con un test antes de tocar código, así que los falsos positivos costaron tokens, pero no cambios.
- De los revisores por lote no sé si compensan. Pararon un bloqueante real, pero no medí cuántos de sus hallazgos no bloqueantes se aplicaron. Es lo primero que mediría la próxima vez.
- La verificación también hay que verificarla. El reescaneo final cazó 6 incidencias nuevas que habían metido los propios arreglos del code-review.
No probé a repetir el mismo revisor, así que no puedo decir que no sirva. Lo que sí vi es que cada cambio de modelo o de corte (por lote, por área, por ángulo) sacó cosas nuevas.
Sobre los tokens: el 97,7 % de la entrada fue lectura de caché. Cada turno de un agente relee su contexto entero, así que lo que se paga es el número de turnos, no el tamaño del cambio. Por eso el code-review, con buscadores que encadenan cientos de turnos, es la capa más cara. Elegir el modelo por rol (Haiku para implementar UI, Sonnet para servicios y revisores, Opus para el plan y la integración) fue lo que más contuvo el coste. Con una suscripción de Claude Code lo que se paga es la cuota, no estas cifras.
Qué hace el revisor humano
La PR no pide leer el diff. Pide esto, en este orden:
- Elegir commits
refactor(...)al azar y comprobar que eltest(...)anterior toca el mismo fichero y pasa sobre el código de antes. - Leer las 168 notas de cambios de comportamiento, que van en el primer comentario de la PR. Son lo único que debería cambiar algo, y cada una dice por qué es equivalente o qué cambia.
- Leer con calma los pocos puntos marcados para revisión particular: un cambio de seguridad en el modo embebido, los cambios que se revirtieron a propósito y por qué, y los TODOs que se borraron y deberían ser tickets.
- Revisar la lista de bugs que los agentes encontraron y no tocaron, porque estaban fuera de alcance.
- Pasar el ciclo de regresión manual: 41 casos de QA.
El punto 1 es por muestreo; el 2, completo. Si el muestreo encuentra un refactor sin su test previo, la confianza en el resto cae y toca revisar el lote entero.
Un Sonar en local como oráculo
Es la pieza que más subestimé. Nuestro Sonar es Community Build, que no tiene ramas, así que analizar la rama en el servidor pisaría el análisis de development. Levanté un SonarQube Community en Docker con los perfiles de fábrica y comprobé que reproducía la línea base del servidor con las mismas cifras: 121 incidencias de fiabilidad, 3.400 de mantenibilidad y 56,4 % de cobertura.
Con eso el plan sale de datos y no de intuición, y cada fase mide su trabajo con la misma vara que usará el CI. Si vas a pedirle a unos agentes que mejoren una métrica, dales la forma de medirla ellos mismos.
Lo que salió mal
Un agente hizo push, abrió una PR y movió el ticket de Jira. Nadie se lo pidió. Tenía instalada una skill para terminar ramas y la siguió hasta el final. La PR salió con el gate en rojo, porque era un snapshot a medias. Ahora los prompts prohíben cualquier acción externa y los worktrees tienen un pushurl inválido:
git config --worktree remote.origin.pushurl no-push://bloqueado Prohibirlo en el prompt no basta, hay que impedirlo.
Reanudar el run relanzó lotes que ya estaban hechos. resumeFromRunId reutiliza el prefijo más largo de llamadas que no han cambiado. Con dos carriles dentro de un parallel(), el orden no es estable, las claves de caché de los revisores no coincidieron y volvió a lanzar lotes terminados. Lo paré al minuto. Para continuar un run a medias, me ha funcionado mejor escribir un script nuevo solo con lo pendiente. Por eso fueron cuatro runs y no uno.
Los carriles iban desequilibrados. Repartí los lotes por orden y uno de los carriles terminó horas antes que el otro. La versión que dejé en el repo los alterna y deja las deprecaciones, que son lo más pesado, para el final.
Datos reales en los fixtures. Los agentes copiaron a las specs un identificador de comercial real, emails y teléfonos que habían visto en el código. El code-review lo cazó y los cambiamos por datos sintéticos. Ahora hay una regla en las instrucciones del repo, pero ninguna comprobación automática.
Las prohibiciones viven en el prompt. Pedí no usar NOSONAR, eslint-disable, @ts-ignore, as any ni it.skip, y justificar cada falso positivo en sonar-project.properties. Se cumplió, pero lo vigilaban los revisores, no el gate. Es el mismo error que el del push, solo que esta vez no explotó.
Un arreglo que no arreglaba nada. Un agente anotó como bug corregido un cambio que, con la lista que llega en producción, no tenía ningún efecto. Un test en verde puede estarlo por el motivo equivocado.
Casos de QA duplicados. Para la regresión manual, Claude creó en la herramienta de test 7 casos que ya existían. Los existentes no estaban enlazados a requisitos ni tenían tags. Cuando le señalé la carpeta de plantillas, reutilizó 40 y creó uno solo para lo que de verdad faltaba. Un agente solo reutiliza lo que puede encontrar; si no, se inventa la regresión.
Un cambio de seguridad que ningún test reproduce. Pasar postMessage(msg, '*') al origen del padre es lo correcto. Pero en Firefox o con Referrer-Policy: no-referrer el origen puede no estar disponible y el canal con la app que nos embebe se queda mudo. Eso solo se comprueba a mano, en Safari y en Firefox.
Cómo aplicarlo en tu repo
Nada de esto depende de Sonar ni de Claude Code. Sirve para cualquier cambio masivo hecho por agentes: deprecaciones, un salto de versión, una regla de lint nueva.
Antes de lanzar nada
- Monta un oráculo que reproduzca la métrica del CI en local y comprueba que da la misma línea base que el servidor.
- Reparte el trabajo en lotes pequeños por carpeta y elige el modelo por rol: barato para implementar, otro distinto para auditar.
- Bloquea por infraestructura cualquier acción externa: push, PRs, tickets. No basta con prohibirlo en el prompt.
- Deja escrita la regla de datos de prueba sintéticos antes de que el primer agente escriba un fixture.
- Ten la regresión manual localizable: casos en carpetas por funcionalidad, enlazados a requisitos y con tags.
En cada lote
- Escalera de garantías: caracterización, test de la línea, refactor equivalente, rojo → verde solo para bugs reales y revertir lo que quede en rojo.
- Un commit
test(...)antes de cadarefactor(...). - Una nota por lote con cambios de comportamiento, TODOs, falsos positivos y skipped.
- Nada que esconda el problema (
NOSONAR,eslint-disable,as any,it.skip), y que lo compruebe el gate, no solo el revisor.
Antes de abrir la PR
- Gates completos y reescaneo con el oráculo, también después de aplicar los arreglos de la auditoría.
- E2E repitiendo cada fallo sobre la rama base, para separar regresiones de lo que ya fallaba.
- Al menos una revisión con otro modelo y otra con otro corte (por área, por ángulo). Cada hallazgo se confirma o se refuta con un test antes de tocar código.
- Presupuesta la verificación: aquí costó más de diez veces lo que el arreglo.
En la PR y después
- La descripción lleva las notas de comportamiento, los puntos de revisión particular, lo revertido y los bugs encontrados sin tocar.
- La revisión humana es por muestreo de commits, lectura completa de las notas y QA.
- Mide reversiones y bugs atribuidos a la PR, y qué aportó cada capa, para recortar la siguiente vez.
Lo que todavía no sé
- Si los revisores por lote compensan sus 30 $, porque no medí cuántos de sus hallazgos no bloqueantes se aplicaron.
- Si lotes más pequeños o un code-review menos exhaustivo habrían dado la misma garantía por menos dinero.
- Cuánto tardó de verdad la revisión humana de la PR. No lo cronometré, y es justo la métrica que todo esto pretende bajar.
- Qué pasa con los 84 tests E2E de sincronización que no llegaron a ejecutarse, en parte por una credencial que faltaba en el entorno.
El workflow ha quedado en el repo, parametrizado para repetirlo con otras migraciones masivas. La próxima ejecución me dirá si lo aprendido aquí se sostiene.
Si has hecho algo parecido, me interesa sobre todo cómo lo revisó tu equipo y cuánto te costó cada capa de verificación. Es el dato que menos se publica y el que más ayuda a decidir.


