Tag: Agentic Harness

  • Claude Code Mods: un mod que no deja tocar tus tests

    Claude Code Mods: un mod que no deja tocar tus tests

    La primera versión de mi mod de Claude Code solo vigilaba Bash.

    Lo probé en Windows. Le pedí al agente que cambiara un test firmado de 409 a 201. Intentó apagar el candado y no pudo. Entonces tiró de PowerShell, la otra terminal que Claude Code tiene en Windows. Esa vez falló, pero no gracias a mí: mi mod no la vigilaba.

    Ese es el problema de fondo. Con un test en rojo, el camino más corto al verde no es arreglar el código: es cambiar lo que espera el test. Los Claude Code Mods, que llegaron el 1 de octubre de 2026, son la primera herramienta que me deja cerrar esas puertas desde dentro.

    En corto: un mod de Claude Code es un plugin con código TypeScript o JavaScript que se ejecuta dentro de Claude Code y puede observar, reescribir o responder a cada evento, como un middleware. Con menos de 80 líneas puedes bloquear cualquier Edit o Write sobre tus tests firmados y deshacer lo que el agente cambie desde la terminal. No va en sandbox y la API todavía puede cambiar entre versiones: úsalo como primera barrera, nunca como la última.

    ¿Qué son los Claude Code Mods?

    Los Claude Code Mods son plugins cuyo hooks/hooks.json tiene la clave modules apuntando a un archivo TypeScript o JavaScript que Claude Code ejecuta en su propio proceso, enganchado a eventos como una llamada a herramienta, un prompt o el dibujado de la interfaz.

    Llegaron con Claude Code v2.1.287, publicada el 1 de octubre de 2026 según el CHANGELOG oficial. Cómo funcionan está en la documentación oficial. Si tu claude --version es más antigua, no carga ninguno.

    Cada hook recibe $, la API del motor; e, el evento; y next, lo que iba a pasar. Si vienes de Express, ya lo entiendes:

    return next(e)                       // observar: que siga igual
    return next({ ...e, text: nuevo })   // reescribir: que siga, pero cambiado
    return { deny: 'No.' }               // responder (en tool.call): no llamas a next y no pasa
    

    El problema: el agente "arregla" el test

    Con un test en rojo, Claude Code tiende a cambiar la aserción en lugar de arreglar el código. No es una manía mía: en el repo de Claude Code está el issue #7074, abierto en septiembre de 2025 y cerrado como duplicado, que lo describe tal cual: el agente modifica los tests para que pasen en lugar de arreglar la implementación, cambia las aserciones y debilita validaciones. Que sea un duplicado dice bastante.

    En mi demo, una API de reservas en Bun, los tests de test/contrato/ son los que he revisado y firmado. Uno dice que si dos personas reservan el mismo hueco, una recibe 201 y la otra 409. El código falla a propósito.

    El agente puede tocar ese archivo por tres puertas: Edit, Write y la terminal (sed, echo > o PowerShell). Escribir "no toques los tests" en el CLAUDE.md no cierra ninguna: es una petición, no un control. Lo conté en hooks vs permisos en Claude Code.

    Cómo crear un mod de Claude Code paso a paso

    Crear un mod de Claude Code son tres archivos: el manifiesto del plugin, un hooks.json con modules y el módulo que exporta register(on). Sin compilar ni bundler.

    El mod vive fuera del proyecto, en su carpeta. Primero, .claude-plugin/plugin.json (el nombre no puede empezar por claude-):

    {
      "name": "tests-lock",
      "version": "0.1.0",
      "description": "Stops Claude from editing the signed tests in test/contrato/ and warns when a shell command changes them",
      "author": { "name": "Dominicode" }
    }
    

    Lo que convierte este plugin en un mod está en hooks/hooks.json:

    {
      "description": "tests-lock hooks module",
      "modules": ["./register.ts"]
    }
    

    Primera puerta: Edit y Write

    const PROTECTED = 'test/contrato/'
    const REASON =
      'test/contrato/ es el contrato firmado y no se cambia para que pase. ' +
      'Arregla el código, no el test. Si crees que el contrato está mal, para y pregúntame.'
    
    export function register(on) {
      // 1. Edit y Write: bloquear antes de que se toque un test firmado
      on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
        if (!locked || !isProtected(e.file_path)) return next(e)
        // Crear un test nuevo sí se permite; cambiar uno que ya existe, no
        if (!(await $.fs.exists(e.file_path))) return next(e)
        // ...contador y aviso en pantalla
        return { deny: REASON }
      }).catch(async () => ({ deny: 'tests-lock ha fallado y, por seguridad, no se edita nada en ' + PROTECTED }))
    }
    

    El agente lee el deny como el error de la herramienta, así que lo escribo como instrucción. Crear un test nuevo sí se permite. Es un extracto: locked, isProtected y el resto están en el código completo, al final del post. Y el .catch hace que falle cerrado: si el hook revienta, la respuesta es "no". Sin él, Claude Code se salta el hook y la edición pasa.

    Segunda puerta: la terminal

    Esto es lo que justifica el mod. No sé de antemano qué toca un comando, y adivinarlo con regex es perder. Así que compruebo: miro git antes, dejo correr el comando y miro git después.

    // Terminal (Bash, y PowerShell en Windows): se mira git antes y después
    on('tool.call', { tool: ['Bash', 'PowerShell'] }, async ($, e, next) => {
      if (!locked) return next(e)
      const before = await changedTests($)
      const result = await next(e)
      const touched = (await changedTests($)).filter((f) => !before.includes(f))
      if (touched.length === 0) return result
      // Solo se restauran los que estaban limpios antes del comando
      await $.process.run(['git', 'checkout', 'HEAD', '--', ...touched])
      return {
        ...result,
        context: [...(result.context ?? []), 'Tu último comando cambió ' + touched.join(', ') + ' y se ha deshecho. ' + REASON],
      }
    })
    

    changedTests lanza git diff --name-only HEAD -- test/contrato/ con $.process.run, sin shell. El await next(e) ejecuta el comando y mi código sigue después.

    context es texto que el agente lee tras el resultado y tú no ves. En mis pruebas, el agente citó el aviso y el archivo volvió a esperar 409. Fíjate en 'PowerShell': la añadí después de la historia del principio. Con Claude Code Mods, cada herramienta que no nombras es una puerta abierta.

    Para cargarlo: claude --plugin-dir ~/mods/tests-lock. En /plugin lo ves cargado, y al guardar se recarga en caliente.

    Un mod también pinta. Con ui.render sobre AbovePrompt dibujas una franja encima del prompt; por ejemplo, con el último resultado de bun test, Vitest o Jest.

    El estado que lee esa franja vive en atom, read y update de 'claude-code'. Un hook de settings no puede dibujar.

    Probar Claude Code Mods: claude plugin validate y claude plugin test

    Un mod es código que corre dentro de tu herramienta de trabajo, así que se prueba como cualquier código.

    claude plugin validate . lee el mod sin ejecutarlo y lista eventos y llamadas. Salida real:

    ❯ ./register.ts hooks: session.start, tool.call{tool=Edit|Write}, tool.call{tool=Bash|PowerShell}, command.run{command=tests-lock}
    ❯ ./register.ts calls: $.command.register, $.fs.exists, $.process.run, $.ui.status (via showStatus), $.ui.toast
    ✔ Validation passed
    

    claude plugin test corre los *.test.ts con 'claude-code/testing', sin sesión ni red. Cada on es un stub que responde en lugar de Claude Code:

    import { expect, test } from 'claude-code/testing'
    
    test('editar un test firmado se bloquea', async ($, on) => {
      on('ui.status', () => ({ value: undefined }))
      on('ui.toast', () => ({ value: undefined }))
      on('fs.exists', () => ({ value: true }))
      on('tool.call', () => ({ result: 'edited' }))
    
      const r = await $.tool.call({ tool: 'Edit', file_path: 'C:\\proyectos\\agendo\\test\\contrato\\reservas.test.ts', old_string: '409', new_string: '201' })
      expect(r.deny).toMatch(/contrato firmado/)
    })
    

    Cinco tests en verde: bloqueo, test nuevo, código normal, comando de Bash deshecho e interruptor /tests-lock off.

    Claude Code mods vs hooks de settings: cuál usar

    Un hook de settings es un script que Claude Code lanza desde fuera y vive en el repo; un mod corre dentro de Claude Code, guarda estado, puede dibujar y actuar después de que la herramienta termine.

    Hook de settings.json Mod
    Dónde vive En .claude/settings.json, versionado en git En un plugin que cada dev instala
    Qué escribes Un script en cualquier lenguaje TypeScript o JavaScript
    Estado entre llamadas No, salvo un archivo temporal Sí, variables del módulo
    Actuar tras la herramienta Con un segundo hook (PostToolUse) En el mismo hook, tras await next(e)
    Interfaz y comandos propios No Sí
    Tests Los que montes tú claude plugin test sin sesión
    Limitación o riesgo Repartir estado entre dos scripts es frágil Sin sandbox, API que aún puede cambiar entre versiones, instalación por máquina

    Mi regla: si todo el equipo tiene que cumplirla sin instalar nada, hook de settings en el repo. Si es tu herramienta y necesita estado, interfaz o mirar después del comando, mod. Lo de la terminal también sale con PreToolUse, PostToolUse y un archivo temporal; el mod lo junta en un archivo con tests.

    Cuándo NO usar Claude Code Mods

    No uses Claude Code Mods como única barrera ni instales un mod que no hayas leído.

    No va en sandbox. La documentación lo dice: corre con tus permisos, lee tus variables de entorno y claves, ve cada prompt y puede aprobar llamadas que un hook tuyo bloqueó.

    El sandbox de Claude Code no cubre lo que lanza un mod. Antes de instalar uno, claude plugin validate y lee los calls. Si algo va raro, claude --safe-mode arranca sin tus mods.

    La API todavía puede cambiar. La documentación da los mods por activados por defecto desde la v2.1.287, pero la cabecera de los tipos avisa: "this surface may change between releases without notice".

    Compara con el último commit. Lo no commiteado no cuenta como firmado. Y por cómo está escrito, si un mismo comando cambia el test y hace commit, el git diff posterior sale limpio. Lo deduzco del código; aún no lo he demostrado en sesión real.

    Al recargar, el estado se reinicia. El contador vuelve a cero y el candado se enciende.

    Solo protege donde está instalado. Otro compañero o el CI no lo tienen. La última barrera es el CI ejecutando los tests firmados en cada PR. Es la lógica de sacar la regla fuera del prompt: el agente propone, el sistema controla. Si aún no has montado tu primer agente, empieza por construir un agente de IA desde cero.

    Lo que puedes hacer hoy con tu primer mod

    Para empezar con Claude Code Mods: copia el register.ts completo del final del post, cambia test/contrato/ por tu carpeta, commitea los tests y carga el mod con --plugin-dir. Luego pide al agente que cambie una aserción, por Edit y por la terminal. Si estás en Windows y solo vigilas Bash, ya sabes por dónde se cuela.

    Si estás empezando con agentes y quieres hacerlo sin perder el control, tienes gratis el ebook El Developer Agéntico. Lo monto entero en el canal de YouTube de Dominicode. Para el flujo completo, está el curso Construye con IA. Y los tests que merece la pena firmar salen de una spec: lo cuento en Spec-Driven Development.

    Preguntas frecuentes

    ¿Qué versión de Claude Code necesito para usar mods?

    La v2.1.287 o posterior, publicada el 1 de octubre de 2026. Compruébala con claude --version. Vienen activados por defecto y se cargan desde un plugin instalado o con --plugin-dir en desarrollo.

    ¿En qué se diferencian los hooks de Claude Code de los mods?

    Un hook de settings es un script externo configurado en settings.json que recibe un JSON y devuelve una decisión. Un mod corre dentro de Claude Code, comparte variables entre hooks, puede dibujar, registrar comandos y actuar después de que una herramienta termine.

    ¿Un plugin de Claude Code con mod es seguro de instalar?

    Solo si confías en quien lo escribió. No va en sandbox y corre con tus permisos. Antes de instalarlo, ejecuta claude plugin validate sobre su carpeta y revisa los hooks y calls que lista.

    ¿Puede el agente saltarse el mod usando la terminal?

    Si el mod solo vigila Edit y Write, sí. Este mira git antes y después de cada comando de Bash y PowerShell y restaura los tests firmados que cambien. En Windows hay que vigilar las dos herramientas. Con un límite: si el mismo comando cambia el test y hace commit, el diff sale limpio. Por eso el CI es la última barrera.

    ¿Puedo crear un mod de Claude Code sin escribir el código?

    Sí. La documentación oficial propone pedírselo a Claude en una sesión: Claude Code trae una skill para escribir mods. Revisa el resultado con claude plugin validate antes de cargarlo.

    Código completo del mod

    hooks/register.ts de tests-lock, las 78 líneas tal cual las uso:

    // tests-lock: lo firmado en test/contrato/ no se toca para que pase.
    const PROTECTED = 'test/contrato/'
    const REASON =
      'test/contrato/ es el contrato firmado y no se cambia para que pase. ' +
      'Arregla el código, no el test. Si crees que el contrato está mal, para y pregúntame.'
    
    let locked = true
    let blocked = 0
    
    function isProtected(path: string): boolean {
      return ('/' + path.replaceAll('\\', '/')).includes('/' + PROTECTED)
    }
    
    // Archivos de test/contrato/ que hoy no coinciden con el último commit
    async function changedTests($): Promise<string[]> {
      try {
        const r = await $.process.run(['git', 'diff', '--name-only', 'HEAD', '--', PROTECTED])
        if (r.exitCode !== 0) return []
        return r.stdout.split('\n').map((l) => l.trim()).filter(Boolean)
      } catch {
        return []
      }
    }
    
    function showStatus($) {
      $.ui.status(locked ? `${PROTECTED} protegido · ${blocked} intento(s) frenado(s)` : `${PROTECTED} SIN proteger`)
    }
    
    export function register(on) {
      on('session.start', async ($, e, next) => {
        showStatus($)
        await $.command.register({
          name: 'tests-lock',
          description: 'Protege test/contrato/: on, off o status',
          argumentHint: '[on|off|status]',
          immediate: true,
        })
        return next(e)
      })
    
      // 1. Edit y Write: bloquear antes de que se toque un test firmado
      on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
        if (!locked || !isProtected(e.file_path)) return next(e)
        // Crear un test nuevo sí se permite; cambiar uno que ya existe, no
        if (!(await $.fs.exists(e.file_path))) return next(e)
        blocked += 1
        showStatus($)
        $.ui.toast('Claude ha intentado editar ' + e.file_path)
        return { deny: REASON }
      }).catch(async () => ({ deny: 'tests-lock ha fallado y, por seguridad, no se edita nada en ' + PROTECTED }))
    
      // 2. Terminal (Bash, y PowerShell en Windows): no se sabe de antemano qué toca un comando,
      //    así que se mira git antes y después
      on('tool.call', { tool: ['Bash', 'PowerShell'] }, async ($, e, next) => {
        if (!locked) return next(e)
        const before = await changedTests($)
        const result = await next(e)
        const touched = (await changedTests($)).filter((f) => !before.includes(f))
        if (touched.length === 0) return result
        // Solo se restauran los que estaban limpios antes del comando
        await $.process.run(['git', 'checkout', 'HEAD', '--', ...touched])
        blocked += 1
        showStatus($)
        $.ui.toast('Un comando ha cambiado ' + touched.join(', ') + '. Restaurado.')
        return {
          ...result,
          context: [...(result.context ?? []), 'Tu último comando cambió ' + touched.join(', ') + ' y se ha deshecho. ' + REASON],
        }
      })
    
      on('command.run', { command: 'tests-lock' }, async ($, e) => {
        const arg = e.args.trim()
        if (arg === 'on') locked = true
        if (arg === 'off') locked = false
        showStatus($)
        return { text: (locked ? 'Protegido: ' : 'Sin proteger: ') + PROTECTED + ' · intentos frenados: ' + blocked }
      })
    }
    

    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Cómo calculo cuánto me cuesta de verdad un agente que falla

    Cómo calculo cuánto me cuesta de verdad un agente que falla

    El miércoles que casi aprobé el email con el precio desactualizado —lo monté un agente en MailerLite con el precio de la semana anterior, listo para salir a toda la lista— se lo conté a un colega dos días después.

    Me preguntó algo que no supe responder bien: "¿por qué revisaste ese y los cuarenta y dos thumbnails de la noche anterior los aprobaste sin mirar ni uno?". Dije "depende del riesgo". Se quedó esperando un número. No lo tenía.

    "Depende" no es un criterio, es decidir a ojo. Y a ojo, tarde o temprano, fallas por el lado que más duele.

    Me senté a calcular, en números y no en corazonadas, cuánto cuesta un agente de IA que falla. La misma cuenta que hago hoy antes de aprobar lo que entrega cualquier agente.

    En corto: para decidir si reviso lo que entrega un agente de IA antes de aprobarlo, comparo dos números: el coste esperado de que falle sin que yo lo vea (probabilidad × daño) contra el coste de verificarlo yo mismo. Si el primero es mayor, reviso siempre. Si es menor, delegar sin mirar es la decisión más barata — no la más cómoda.

    ¿Qué es el coste esperado de no verificar una tarea delegada?

    El coste esperado de no verificar es la probabilidad de que la tarea delegada falle, multiplicada por todo lo que te cuesta cuando falla: detectarlo tarde, arreglarlo y el daño que ya hizo antes de que te dieras cuenta.

    El coste de verificar es más simple: es el tiempo que te cuesta a ti —o a quien revise— leer, entender y aprobar esa tarea antes de que se vuelva irreversible.

    La regla de decisión sale sola al poner los dos números uno al lado del otro. Si el coste esperado de no verificar es mayor que el coste de verificar, revisas siempre. Si es menor, delegas sin revisión — no por pereza, sino porque es la opción matemáticamente más barata.

    Este principio —revisar antes de que algo se vuelva irreversible— es el corazón del método que dejé completo y gratis en el ebook Revisión por Contrato: cómo definir de antemano qué necesita aprobación humana y qué no.

    Por qué no tomo prestado el "100x más caro en producción" de la industria

    Antes de construir esta fórmula tuve la tentación de usar el número que todo el mundo repite: que un bug en producción cuesta cien veces más arreglarlo que uno detectado en desarrollo. Aparece en charlas, en posts de blog, en pitch decks de herramientas de testing.

    Ese número, rastreado hasta la fuente, puede que no exista tal como se cita. Un hilo de Hacker News de 2021, con 159 puntos y 130 comentarios —"The 'bugs are 100x more expensive to fix in production' study might not exist"— sigue la cadena de citas hasta el estudio original y no encuentra una medición limpia detrás, solo cita tras cita.

    El usuario nerdponx lo resume mejor de lo que yo podría: "No es un caso de investigación falsificada. Es un caso de alguien citando algo apócrifo como si fuera un hecho, y luego otra gente citando esa cita" —traducido del inglés.

    Por eso, abajo, no uso ningún multiplicador prestado de la industria. Los números de probabilidad y de coste son míos, ilustrativos, pensados para mostrar el mecanismo — no una estadística que puedas citar como un dato medido.

    El cálculo real: el email con el precio equivocado

    Uso $50 la hora como referencia ilustrativa de mi propio tiempo — no es una tarifa de mercado. Cambia el número por el tuyo: el mecanismo de la fórmula no varía.

    Releer el asunto y el precio antes de aprobar el envío me costó entre 2 y 3 minutos: unos $2,50. Ese es el coste de verificar.

    La probabilidad de que el precio hubiera cambiado justo esa semana, sin que yo lo notara, era baja — pongamos, ilustrativamente, un 5%.

    Pero si fallaba, el coste no era pequeño. Arreglarlo —corrección más responder a quien preguntara— son unas 2 horas: $100. Y está el daño ya hecho antes de detectarlo: la credibilidad del precio frente a miles de bandejas que ya leyeron una cifra falsa. No tiene cifra exacta, pero tiene un piso conservador — ilustrativamente, $200. Total si falla: ≈ $300.

    Coste esperado de no verificar = 5% × $300 = $15.

    $15 es mayor que $2,50. La fórmula dice: verifica siempre. Y lo que gana la decisión no es la probabilidad —era baja— sino la magnitud del daño si el evento raro ocurre.

    El contraste: 42 thumbnails, probabilidad alta, coste casi cero

    La noche anterior había dejado al mismo agente generando cuarenta y dos thumbnails para posts antiguos: prompt, imagen, nombre de archivo, carpeta correcta. Los aprobé todos sin abrir ni una carpeta.

    Aquí la probabilidad de que algo saliera mal era, ilustrativamente, alta — un 30%: con cuarenta y dos piezas generadas en patrón, algo se cuela con frecuencia.

    Pero si falla, el coste es casi nada. Se ve al abrir la carpeta —dos minutos, unos $1,70— y se regenera con un comando. No hay corrección pública ni daño reputacional: nadie fuera de mí ve el error antes de que lo arregle.

    Coste esperado de no verificar = 30% × $1,70 ≈ $0,51.

    Verificar las 42 imágenes una por una, a un minuto cada una, cuesta $35. $0,51 es muchísimo menor que $35. La fórmula dice: delega sin revisar. Aquí la probabilidad alta no importa, porque el daño y la irreversibilidad son casi cero.

    Elemento del cálculo Email de lanzamiento 42 thumbnails
    Coste de verificar antes de aprobar 3 min ≈ $2,50 42 min (1 min c/u) ≈ $35
    Probabilidad ilustrativa de que falle 5% — evento infrecuente 30% — patrón repetido, muchas piezas
    Coste si falla (arreglar + daño ya hecho) ≈ $300 (corrección + credibilidad) ≈ $1,70 (se regenera con un comando)
    Coste esperado de NO verificar (P × coste si falla) ≈ $15 ≈ $0,51
    Qué inclina la balanza La magnitud del daño, no la probabilidad Lo barato y rápido que es detectar y arreglar
    Decisión de la fórmula Verificar siempre Delegar sin revisar

    La fila que importa es la penúltima. No decide la probabilidad: decide qué tan caro sale el daño si el evento raro ocurre, y qué tan barato es deshacerlo si no.

    Es el mismo cálculo que aplico al diseñar los agentes que uso a diario, y es lo que enseño paso a paso en Construye con IA: no solo montar el agente, también decidir qué parte de su trabajo se aprueba sin mirar y cuál se revisa siempre.

    Si esto te suena al criterio de blast radius que ya usaba antes de tener la fórmula, es porque es el mismo criterio con números detrás. Para el ángulo más amplio de por qué creo que el techo de los agentic systems es económico y no técnico, lo desarrollé en este otro post.

    Cuándo esta fórmula no sirve

    No es una máquina de la verdad. Tiene límites que conviene decir en voz alta antes de que alguien la use como excusa para no pensar.

    Estimar la probabilidad es subjetivo, y es fácil engañarte a ti mismo minimizándola. Si el agente acertó las últimas diez veces, tu cerebro te dirá que la probabilidad de fallo es más baja de lo real. Ese sesgo no lo corrige la fórmula — el número lo pones tú, con tu sesgo incluido.

    El coste del daño reputacional no tiene un número exacto. La fórmula te obliga a poner una cifra, pero esa cifra es una estimación conservadora, no una medición. Si la subestimas para que el cálculo te dé el resultado que ya querías, el cálculo miente a tu favor.

    Esta fórmula evalúa una tarea aislada, no un sistema. No sustituye un gate automático —tests, tipos, criterios de aceptación que corran solos— que verifique sin depender de que te acuerdes de hacer la cuenta cada vez. Es la capa manual para cuando ese gate todavía no existe.

    Qué hacer hoy con esto

    No necesitas una hoja de cálculo. Coge las tres tareas que más delegas esta semana a un agente y, para cada una:

    1. Escribe cuánto te cuesta verificarla antes de aprobarla.
    2. Ponle una probabilidad ilustrativa a que falle.
    3. Estima cuánto costaría si falla —arreglo más daño ya hecho antes de detectarlo.
    4. Multiplica los puntos 2 y 3: ese es tu coste esperado de no verificar.

    Compara ese resultado con el coste de verificar del punto 1. Donde gane verificar, sigue revisando sin culpa —no es desconfianza en el agente, es aritmética—. Donde pierda, suelta esa tarea del todo.

    Si quieres ver cómo aplico esta cuenta cada semana en un negocio real que opero solo, sin nadie más que revise detrás de mí, en Dominicode Labs comparto los números actualizados y las tareas concretas que delego o audito según van cambiando mis agentes.


    Preguntas frecuentes

    ¿Cómo calculo cuánto me cuesta que un agente de IA falle?

    Multiplica la probabilidad de que la tarea falle por el coste total si falla: detectarlo tarde, arreglarlo y el daño ya hecho antes de que lo notes. Ese resultado es el coste esperado de no verificar. Compáralo con el coste de verificar antes de aprobar — el que sea menor gana.

    ¿Qué diferencia hay entre esta fórmula y el criterio de blast radius?

    Ninguna en el fondo — son el mismo criterio en dos niveles. El blast radius es la versión cualitativa: cuánto daño hace algo y qué tan reversible es. Esta fórmula es la versión cuantitativa: le pones números a ese daño y a esa probabilidad para comparar dos tareas objetivamente, no a ojo.

    ¿Cómo estimo la probabilidad de que una tarea delegada a un agente falle?

    Con honestidad, sabiendo que es una estimación y no una medición. Fíjate en cuántas veces has visto fallar ese tipo de tarea, cuánto contexto de negocio necesita que pueda haber cambiado sin que el agente lo sepa, y desconfía de tu propia racha reciente de aciertos.

    ¿Esta fórmula sustituye tener tests automáticos o un gate de verificación?

    No. Decide sobre una tarea puntual, cuando no existe todavía un mecanismo automático que verifique el resultado por ti. Si puedes construir ese gate —tests, tipos, criterios de aceptación que corran solos— constrúyelo: es más fiable que cualquier cálculo manual que dependa de que te acuerdes de hacerlo cada vez.

    ¿Qué hago si no puedo poner un número exacto al daño reputacional?

    Pon un piso conservador y dilo explícitamente: es una estimación, no una medición. El objetivo no es acertar la cifra exacta, sino evitar el error más común, que es tratar el daño reputacional como si costara cero solo porque no tiene un precio de catálogo.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Graph engineering vs harness engineering: no son alternativas

    Graph engineering vs harness engineering: no son alternativas

    Hace unas semanas alguien me enseñó el diagrama de su sistema multi-agente. Doce nodos, aristas condicionales, un router en el centro y el estado compartido dibujado en un lateral con su leyenda de colores. Bonito de verdad.

    Le hice una sola pregunta: cuando el nodo que implementa escribe el código, ¿qué comprueba que ese código compila antes de que el grafo avance al siguiente nodo?

    Silencio.

    Ese silencio es toda la diferencia entre graph engineering y harness engineering. Y explica por qué la pregunta que me llega cada semana —"¿por cuál apuesto?"— está mal planteada desde el principio.

    No son alternativas. Son ejes ortogonales. Puedes tener mucho de uno y nada del otro, y la mayoría de equipos está exactamente en ese caso.

    Qué "graph engineering" estamos comparando: recuperación u orquestación

    El término se usa para dos cosas distintas, y mezclarlas hace daño.

    El primer uso es de recuperación de contexto: tratar tu código como un grafo de dependencias para que el agente navegue por él en lugar de tragarse el repositorio entero en cada petición. De eso escribí en qué es graph engineering, y no es de lo que va este post.

    El segundo uso —el que se popularizó en 2026— es de orquestación: modelar la ejecución multi-agente como un grafo. Nodos que son agentes o pasos, aristas que son routing, un estado compartido que fluye por esas aristas. Este post va de ese.

    Graph engineering, en su sentido de orquestación, es la disciplina de modelar la ejecución de un sistema multi-agente como un grafo dirigido: cada nodo es un agente o un paso, cada arista es una decisión de routing, y el estado compartido viaja por esas aristas. Responde a una pregunta concreta: qué se ejecuta, en qué orden y con qué estado.

    Qué es harness engineering

    Harness engineering es la disciplina de diseñar todo lo que rodea al modelo —las herramientas que puede llamar, los permisos que tiene y los comandos que deciden si su salida es válida— para que el agente falle rápido y en voz alta en lugar de entregar código plausible que no funciona.

    El término lo acuñó Mitchell Hashimoto —cofundador de HashiCorp, el que creó Terraform— el 5 de febrero de 2026, y lo hizo admitiendo que ni siquiera sabía si ya existía un nombre para esto: "I don't know if there is a broad industry-accepted term for this yet, but I've grown to calling this 'harness engineering'". Dos meses después, el 2 de abril de 2026, Birgitta Böckeler le dedicó un artículo entero en martinfowler.com, que suele ser la señal de que un término ha venido para quedarse.

    La ecuación que lo resume la formuló LangChain en The Anatomy of an Agent Harness:

    Agente = Modelo + Harness

    El modelo lo ponen Anthropic, OpenAI o Google. Tú no lo controlas, y cambia cada pocos meses sin pedirte permiso.

    Lo único que construyes de verdad es el harness: las herramientas que expones, los permisos que concedes, el linter, los tests, el pipeline de CI, el AGENTS.md, los hooks que se disparan antes y después de cada edición.

    Harness engineering responde a otra pregunta distinta: qué se le permite hacer y cómo compruebas que lo hizo bien.

    Ninguna de las dos preguntas es un subconjunto de la otra. Por eso son ejes.

    Graph engineering vs harness engineering: tabla comparativa

    La diferencia en una frase: graph engineering modela la ejecución; harness engineering modela las restricciones y la verificación. Uno decide qué corre y en qué orden. El otro decide qué se le permite tocar y qué comando declara que ha terminado.

    Graph engineering Harness engineering
    Qué modela La ejecución Las restricciones y la verificación
    Pregunta que responde ¿Qué corre, en qué orden, con qué estado? ¿Qué puede tocar y cómo sé que funcionó?
    Artefactos Nodos, aristas, routing condicional, estado compartido Tools, permisos, tests, linters, CI, AGENTS.md, hooks
    Dónde vive En el framework de orquestación En tu repo y en tu pipeline
    Herramientas típicas LangGraph, Google ADK 2.0, Microsoft Agent Framework tsc, ESLint, Vitest, git worktrees, GitHub Actions
    Falla cuando… La tarea necesita más de un rol y no hay estructura El agente entrega algo plausible que no compila
    Cómo se ve el fallo Un loop que da vueltas sin converger Un PR limpio, ordenado y equivocado
    Visibilidad Alta: se dibuja en una slide Baja: vive en un script de package.json

    Y este es el cuadrante que sale de cruzar los dos ejes, que es donde duele:

    Harness pobre Harness sólido
    Sin grafo Un loop suelto que acaba rompiendo main Un agente lento pero fiable para una tarea acotada
    Con grafo Basura ordenada, y además a escala Un sistema que puedes dejar corriendo sin mirarlo

    Un grafo perfecto con un harness pésimo produce basura ordenada. Con trazas preciosas, eso sí. Cada paso registrado, cada transición visible, y un resultado que no funciona.

    Un harness excelente sin grafo se atasca en cuanto la tarea necesita más de un rol —investigar, implementar, revisar— y todo intenta caber en un único bucle que se queda sin contexto a mitad de camino.

    La mayoría de los equipos invierte en el eje del grafo. Es lo visible, lo que se dibuja, lo que impresiona en una demo. Y descuida el harness, que es lo que de verdad mueve la aguja.

    Nada de esto es nuevo, y conviene decirlo

    Ninguna capacidad de graph engineering apareció en 2026.

    LangGraph, AutoGen y ADK ya orquestaban por grafo antes de que el término existiera. Lo que cambió fue el vocabulario, no la tecnología.

    Google publicó ADK 2.0 para Python el 19 de mayo de 2026 —el ADK ya había llegado a disponibilidad general un año antes, con la 1.0 de mayo de 2025— y ahí consolidó la idea en su arquitectura: los agentes se modelan como nodos de un grafo de workflow. Go recibió su 2.0 el 30 de junio de 2026 y TypeScript el 21 de agosto de 2026, así que desde entonces construyes workflows basados en grafo de forma nativa también desde Node, sin salir del lenguaje en el que están todos los ejemplos de este post.

    Cuando un término se pone de moda, la reacción sana no es migrar. Es preguntarte qué problema tuyo resuelve hoy.

    Si tu agente de un solo loop se atasca porque la tarea necesita roles separados, el grafo te ayuda: sobre cuándo dar ese salto escribí en LangGraph con TypeScript: grafo de estados vs. loop.

    Si tu agente entrega cosas que no compilan, el grafo no te va a salvar. Vas a tener el mismo problema, solo que mejor enrutado.

    La diferencia, en código

    Un nodo de grafo es una función que recibe el estado compartido y devuelve el trozo de estado que cambia. Nada más.

    // EJE 1: GRAPH — qué se ejecuta y con qué estado
    // state.ts
    
    export type Verdict = { ok: boolean; failures: { name: string; output: string }[] }
    
    export type BuildState = {
      spec: string
      plan?: string
      patch?: string
      verdict?: Verdict
      attempts: number
    }
    
    export type GraphNode = (state: BuildState) => Promise<Partial<BuildState>>
    
    // Tu cliente de LLM: la SDK de Anthropic, la de OpenAI, el AI SDK de Vercel…
    declare const model: {
      generate(input: { system: string; prompt: string }): Promise<string>
    }
    
    export const implement: GraphNode = async (state) => {
      const errores = state.verdict?.failures
        .map((f) => `[${f.name}]\n${f.output}`)
        .join('\n\n')
    
      const patch = await model.generate({
        system: 'Implementa la tarea. Devuelve un diff unificado.',
        prompt: [
          state.spec,
          `Plan:\n${state.plan ?? '(sin plan)'}`,
          errores ? `Intento anterior fallido:\n${errores}` : '',
        ].join('\n\n'),
      })
    
      return { patch, attempts: state.attempts + 1 }
    }
    

    Fíjate en lo que este nodo no sabe: si lo que ha escrito sirve para algo. Devuelve un diff y se queda tan tranquilo. El grafo enrutará al siguiente nodo con la misma confianza tanto si el parche compila como si es una invención con buena sintaxis.

    El otro eje es este:

    // EJE 2: HARNESS — qué se permite y cómo se comprueba
    // harness.ts
    
    import { execa } from 'execa'
    import type { Verdict } from './state'
    
    type Check = { name: string; cmd: string; args: string[] }
    
    const CHECKS: Check[] = [
      { name: 'types', cmd: 'npx', args: ['tsc', '--noEmit'] },
      { name: 'lint', cmd: 'npx', args: ['eslint', '.', '--max-warnings=0'] },
      { name: 'tests', cmd: 'npx', args: ['vitest', 'run'] },
    ]
    
    export async function verify(cwd: string): Promise<Verdict> {
      const failures: Verdict['failures'] = []
    
      for (const check of CHECKS) {
        const result = await execa(check.cmd, check.args, { cwd, reject: false })
    
        if (result.exitCode !== 0) {
          failures.push({
            name: check.name,
            output: `${result.stdout}\n${result.stderr}`.trim().slice(-4000),
          })
        }
      }
    
      return { ok: failures.length === 0, failures }
    }
    

    Y así es como se cruzan los dos ejes. El harness envuelve al nodo: el grafo decide quién trabaja, el harness decide qué cuenta como "terminado".

    // wire.ts
    import { verify } from './harness'
    import type { BuildState, GraphNode } from './state'
    
    declare const WORKTREE: string
    declare function applyPatch(cwd: string, patch: string): Promise<void>
    
    const withHarness =
      (node: GraphNode): GraphNode =>
      async (state) => {
        const update = await node(state)
        if (update.patch === undefined) return update
    
        // Siempre sobre un worktree aislado, nunca sobre tu rama de trabajo
        await applyPatch(WORKTREE, update.patch)
        const verdict = await verify(WORKTREE)
    
        return { ...update, verdict }
      }
    
    // El routing condicional ahora decide con evidencia, no con optimismo
    const routeAfterImplement = (state: BuildState): 'review' | 'implement' | 'giveUp' => {
      if (state.verdict?.ok) return 'review'
      return state.attempts >= 3 ? 'giveUp' : 'implement'
    }
    

    El detalle que lo cambia todo está en routeAfterImplement. Sin verdict, esa función solo puede enrutar por número de intentos o por lo que el propio modelo diga de sí mismo. Con verdict, enruta por hechos.

    Y el array failures que devuelve verify no es para tu log: va de vuelta al prompt del siguiente intento. Un harness que detecta el fallo pero no se lo cuenta al agente es media pieza.

    Quita el grafo y te queda un agente que, al menos, sabe cuándo ha fallado. Quita el harness y te queda un grafo que enruta con total seguridad hacia una conclusión falsa.

    En cuál invertir primero: harness antes que grafo

    Gástala entera en el harness. Esta es mi opinión y la defiendo: el grafo es un problema de estructura que puedes resolver más tarde, cuando sepas qué roles necesitas de verdad. El harness es un problema de confianza, y sin confianza no vas a dejar corriendo nada.

    Cuatro pasos concretos, en este orden:

    1. Escribe qué significa "terminado" como comandos. Un único script verify que devuelva exit code. Si no existe, no tienes harness: tienes esperanza.
    2. Cierra los permisos. Lista blanca de herramientas y un git worktree aislado. El agente no escribe en tu rama. Ojo con un detalle que te va a morder: un worktree recién creado no trae node_modules, así que instala antes de verificar o el harness te dará falsos rojos que no tienen nada que ver con el parche.
    3. Mueve el verificador a CI. Lo que solo corre en tu máquina no protege a nadie; el montaje completo está en test harness para agentes de IA.
    4. Convierte la revisión en un contrato en lugar de en una lectura a ojo. El método está en revisión por contrato y, desarrollado paso a paso, en el ebook gratuito Revisión por Contrato (30 páginas, sin coste).

    Cuando los cuatro estén en su sitio, añade el grafo. Vas a notar la diferencia en la primera semana, porque los nodos empezarán a fallar rápido y en voz alta en lugar de fallar en silencio.

    La única conclusión que te llevas

    Abre hoy tu repo y responde por escrito a una pregunta: ¿qué comando decide que el agente ha terminado?

    Si la respuesta es "lo miro yo en el PR", tu cuello de botella no es la orquestación. Es que no tienes harness, y ningún diagrama lo va a arreglar.

    Ese comando es tu trabajo de esta semana. El grafo puede esperar.

    Definir el "terminado" antes de escribir código es exactamente de lo que va el libro de Spec-Driven Development: la especificación es la parte del harness que decide si el resultado vale, y se escribe antes que nada. Y si quieres ver los dos ejes montados sobre un producto real, de la idea al deploy, eso es lo que construimos paso a paso en Construye con IA.

    Preguntas frecuentes

    ¿Necesito un framework de grafos para montar un sistema multi-agente?

    No. Un grafo es un diccionario de nodos y una función de routing: unas cuantas decenas de líneas de TypeScript, no mucho más que los ejemplos de este post.

    Los frameworks —LangGraph, ADK 2.0, Microsoft Agent Framework— te dan persistencia del estado, checkpoints, reanudación tras un fallo y trazabilidad. Eso es lo que estás comprando, no el concepto de grafo. Si tu proceso cabe en memoria y dura dos minutos, escríbelo a mano y ahórrate la dependencia.

    ¿El harness no es simplemente tener tests?

    Los tests son una pieza del harness, la más obvia. Pero el harness también decide qué herramientas ve el agente, qué ficheros puede tocar, qué comandos puede ejecutar y qué pasa cuando un check falla.

    Un agente con una suite de tests excelente y acceso de escritura a producción no tiene un buen harness. Tiene un buen día, hasta que deje de tenerlo.

    ¿"Graph engineering" no era lo del grafo de dependencias del código?

    También, y por eso genera tanta confusión: el término se usa para dos cosas.

    En su sentido de recuperación de contexto, graph engineering es indexar tu código como grafo de dependencias para que el agente navegue por ahí en vez de por embeddings sueltos. En su sentido de orquestación —el de este post— es modelar la ejecución multi-agente como grafo. Cuando alguien lo use, pregunta a cuál de los dos se refiere antes de discutir. La versión larga del sentido de recuperación —cómo se indexa, con qué herramientas y cuándo no compensa— está en qué es graph engineering.

    Si ya uso Claude Code o Codex, ¿tengo harness?

    Tienes el harness que trae la herramienta: permisos, hooks, ejecución de comandos, lectura del AGENTS.md. Es un buen punto de partida y está mejor pensado que lo que la mayoría montaría desde cero.

    Lo que no trae es la parte específica de tu proyecto: qué comando verifica tu código, qué invariantes de tu dominio no se pueden romper, qué rutas están prohibidas. Esa parte la escribes tú, y es la que separa un agente útil de un generador de PRs.

    ¿Cómo sé si mi problema es del grafo o del harness?

    Mira el modo de fallo. Si el agente da vueltas, repite trabajo, pierde el hilo a mitad o mezcla roles que deberían estar separados, tu problema es de estructura: te falta grafo.

    Si el agente termina rápido, entrega algo que parece correcto y luego no compila, no pasa los tests o rompe un caso que nadie había mirado, tu problema es de verificación: te falta harness. Ese segundo caso es el habitual, y además es el caro, porque el tiempo se te va entero en revisar a mano lo que la máquina genera. De ese cuello de botella hablé en por qué verificar es el nuevo cuello de botella.

    ¿Puedo aplicar harness engineering sin agentes autónomos?

    Sí, y es donde más rápido se nota. Si usas la IA solo como autocompletado avanzado en el editor, el harness sigue siendo tu red: tsc en modo estricto, linter sin warnings, tests que se ejecutan al guardar.

    La diferencia es el margen de error. Con un humano al mando, un harness flojo produce fricción. Con un agente corriendo solo durante veinte minutos, produce un desastre repartido en veinte commits.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Tutorial de harness engineering: la regla fuera del prompt

    Tutorial de harness engineering: la regla fuera del prompt

    El ticket dice: "Un cliente pagó 4,95 € de envío en un pedido de 45 €. Debería haber sido gratis".

    Se lo pasas al agente. Lee shipping.ts, encuentra FREE_SHIPPING_THRESHOLD = 50 y concluye que el código está bien. El pedido era de 45. Cerrado.

    Solo que negocio bajó el umbral a 39 € hace dos meses. Esa decisión está en un fichero de catálogo. En el código, no. En el prompt, tampoco. Este tutorial de harness engineering va de eso: de que el agente no tenga que adivinar ni tú que acordarte.

    En corto: la regla de negocio no se escribe en el prompt ni se deja solo en el código: vive en un catálogo del servicio y un harness mínimo la inyecta en cada ejecución. Ese mismo harness decide qué ficheros puede tocar el agente, rechaza cualquier cambio fuera de la lista y usa los tests como feedback para reintentar un número limitado de veces. El agente propone; el harness controla, y nunca despliega.


    ¿Qué significa sacar la regla de negocio fuera del prompt?

    Sacar la regla de negocio del prompt significa que el valor correcto (un umbral, un límite, un plazo) vive en una fuente de verdad versionada que el harness lee e inyecta, en lugar de depender de que la persona que escribe el prompt se acuerde de mencionarlo.

    Harness engineering es la disciplina de diseñar el sistema que rodea al modelo (qué contexto recibe, qué puede tocar y cómo se verifica su trabajo) para que un agente de IA produzca resultados predecibles.

    No voy a repetir la teoría: la anatomía completa está en qué es un agent harness y el origen del término en harness engineering con Codex de OpenAI. Y si quieres la visión de por qué todo tu ciclo de desarrollo es, en realidad, una fábrica de contexto, léete SDLC context engineering.

    Aquí vamos a lo concreto: un servicio, un bug, unas 150 líneas de TypeScript.

    Tres sitios donde puede vivir la regla

    Antes del código, la decisión. El umbral de envío gratis puede vivir en tres sitios, y cada uno falla de una forma distinta.

    En el prompt Hardcodeada en el código En el catálogo, inyectada por el harness
    Quién la mantiene Quien escribe el prompt ese día Quien tocó el fichero la última vez El owner del servicio
    Qué pasa cuando cambia Depende de que alguien se acuerde Nadie se entera hasta que llega un ticket Cambias una línea del YAML y el siguiente run la usa
    La ve el agente Solo si la escribes Sí, pero la toma como verdad aunque esté mal Siempre, marcada como fuente que prevalece
    La ven los tests No Solo si el test repite el número Sí, si el test lee el mismo catálogo
    Riesgo principal Olvido. Cada prompt es un punto de fallo Divergencia silenciosa con negocio Catálogo desactualizado tratado como verdad

    La tercera columna no es perfecta. Pero es la única en la que el error tiene un solo sitio donde corregirse.

    El servicio de ejemplo del tutorial de harness engineering

    Estructura mínima:

    catalog/shipping-service.yaml
    src/shipping.ts
    src/shipping.test.ts
    harness/context.ts
    harness/run.ts
    

    El catálogo. En empresas grandes esto no es un YAML suelto: vive en un developer portal. Backstage lo modela con un catalog-info.yaml por servicio y Port lo expone como entidades con API. Si no tienes nada de eso, un fichero versionado en el repo sirve igual para empezar:

    # catalog/shipping-service.yaml
    name: shipping-service
    owner: team-checkout
    rules:
      freeShippingThreshold:
        value: 39
        unit: EUR
        source: "Decisión de negocio Q3-2026 (OPS-412)"
      standardShippingCost:
        value: 4.95
        unit: EUR
    agent:
      editableFiles:
        - src/shipping.ts
      testCommand: "npx vitest run src/shipping.test.ts"
      maxAttempts: 3
    

    Fíjate en el bloque agent. Qué ficheros puede tocar el agente lo decide el owner del servicio, no el agente ni quien lanza la tarea.

    El código con el bug:

    // src/shipping.ts
    const FREE_SHIPPING_THRESHOLD = 50;
    const STANDARD_SHIPPING = 4.95;
    
    export function shippingCost(subtotal: number): number {
      if (subtotal < 0) throw new RangeError('subtotal negativo');
      return subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : STANDARD_SHIPPING;
    }
    

    Paso 1: cargar el contexto de servicio para el agente

    El primer trabajo del harness es construir el contexto de servicio para el agente: leer el catálogo, validarlo y fallar si está incompleto.

    // harness/context.ts
    import { readFileSync } from 'node:fs';
    import { parse } from 'yaml';
    
    export interface Rule {
      value: number | string;
      unit?: string;
      source?: string;
    }
    
    export interface ServiceContext {
      name: string;
      owner: string;
      rules: Record<string, Rule>;
      agent: { editableFiles: string[]; testCommand: string; maxAttempts: number };
    }
    
    export function loadServiceContext(path: string): ServiceContext {
      const raw = parse(readFileSync(path, 'utf8'));
      if (
        !raw?.name ||
        !raw?.rules ||
        !Array.isArray(raw?.agent?.editableFiles) ||
        typeof raw?.agent?.testCommand !== 'string' ||
        !Number.isInteger(raw?.agent?.maxAttempts)
      ) {
        throw new Error(`Catálogo inválido: ${path}`);
      }
      return raw as ServiceContext;
    }
    

    Si el catálogo está roto, el harness para. No arranca con contexto a medias. En producción yo validaría esto con un schema de Zod en vez de con cinco condiciones a mano, pero la idea es la misma: el contexto entra validado o no entra.

    Los tests leen el umbral del mismo catálogo que el harness, así que nunca se quedan desfasados respecto a la regla de negocio:

    // src/shipping.test.ts
    import { describe, it, expect } from 'vitest';
    import { loadServiceContext } from '../harness/context';
    import { shippingCost } from './shipping';
    
    const { rules } = loadServiceContext('catalog/shipping-service.yaml');
    const threshold = Number(rules.freeShippingThreshold.value);
    const standard = Number(rules.standardShippingCost.value);
    
    describe('shippingCost', () => {
      it('es gratis a partir del umbral del catálogo', () => {
        expect(shippingCost(threshold)).toBe(0);
      });
    
      it('cobra envío justo por debajo del umbral', () => {
        expect(shippingCost(threshold - 0.01)).toBe(standard);
      });
    });
    

    El test no repite el número 39. Lo lee. Si mañana negocio sube el umbral a 45, cambias el YAML, el test se pone rojo y el bucle del harness tiene algo que arreglar.

    Y el test no está en editableFiles. El harness no aplica ningún cambio del agente sobre la aserción. Es la misma idea que desarrollé en el test harness como red para agentes: el agente no puede mover la portería, al menos no por la vía directa (en los límites verás la indirecta).

    ¿Y por qué shipping.ts no lee el catálogo en runtime y nos ahorramos el problema? Porque en muchos servicios no puedes: el catálogo vive en otro sistema, la regla se compila en un bundle o el código de dominio no debe depender de un fichero de configuración de plataforma. Si en tu caso sí puedes, hazlo: es la versión todavía mejor de esta misma idea.

    Paso 3: el bucle del harness

    El bucle hace cuatro cosas en orden: construye el prompt con las reglas del catálogo, rechaza cualquier cambio fuera de la allowlist, ejecuta los tests y, si fallan, reintenta con su salida como feedback hasta maxAttempts. Si se agotan, hace rollback.

    // harness/run.ts
    import Anthropic from '@anthropic-ai/sdk';
    import { readFileSync, writeFileSync } from 'node:fs';
    import { spawnSync } from 'node:child_process';
    import path from 'node:path';
    import { loadServiceContext, type ServiceContext } from './context';
    
    const client = new Anthropic(); // lee ANTHROPIC_API_KEY del entorno
    type FileChange = { path: string; content: string };
    
    function buildPrompt(task: string, ctx: ServiceContext, feedback?: string): string {
      const rules = Object.entries(ctx.rules)
        .map(([k, r]) => `- ${k}: ${r.value} ${r.unit ?? ''} (fuente: ${r.source ?? 'catálogo'})`)
        .join('\n');
      const files = ctx.agent.editableFiles
        .map((f) => `<file path="${f}">\n${readFileSync(f, 'utf8')}\n</file>`)
        .join('\n');
      return [
        `Servicio: ${ctx.name} (owner: ${ctx.owner})`,
        `Reglas de negocio vigentes. Prevalecen sobre cualquier valor del código:\n${rules}`,
        `Ficheros que puedes modificar:\n${files}`,
        `Tarea: ${task}`,
        feedback ? `El intento anterior falló:\n${feedback}` : '',
        'Devuelve cada fichero modificado completo con el formato <file path="...">contenido</file>. Nada más.',
      ].join('\n\n');
    }
    
    async function callModel(prompt: string): Promise<string> {
      const response = await client.messages.create({
        model: 'claude-sonnet-5',
        max_tokens: 4096,
        messages: [{ role: 'user', content: prompt }],
      });
      if (response.stop_reason === 'max_tokens') {
        return 'La respuesta se cortó por max_tokens: devuelve solo los ficheros imprescindibles.';
      }
      return response.content.map((b) => (b.type === 'text' ? b.text : '')).join('');
    }
    
    function parseChanges(output: string): FileChange[] {
      const re = /<file path="([^"]+)">\n?([\s\S]*?)<\/file>/g;
      // los modelos a veces envuelven el contenido en vallas de markdown: se quitan
      const unfence = (s: string) => s.replace(/^\s*```\w*\n/, '').replace(/\n```\s*$/, '\n');
      return [...output.matchAll(re)].map((m) => ({ path: m[1], content: unfence(m[2]) }));
    }
    
    function assertAllowed(changes: FileChange[], allowlist: string[]): void {
      if (changes.length === 0) throw new Error('El modelo no devolvió cambios.');
      const allowed = new Set(allowlist.map((f) => path.normalize(f)));
      const outside = changes.filter((c) => !allowed.has(path.normalize(c.path)));
      if (outside.length > 0) {
        throw new Error(`Rechazado. Fuera de la allowlist: ${outside.map((c) => c.path).join(', ')}`);
      }
    }
    
    function runTests(command: string): { ok: boolean; output: string } {
      // El proceso hijo no hereda nada que parezca un secreto
      const env = Object.fromEntries(
        Object.entries(process.env).filter(([k]) => !/KEY|TOKEN|SECRET|PASSWORD/i.test(k)),
      );
      // timeout: un test colgado cuenta como intento fallido (status null), no bloquea el harness
      const r = spawnSync(command, { shell: true, encoding: 'utf8', env, timeout: 120_000 });
      return { ok: r.status === 0, output: `${r.stdout}\n${r.stderr}`.slice(-4000) };
    }
    
    export async function runHarness(task: string, catalogPath: string) {
      const ctx = loadServiceContext(catalogPath);
      const originals = new Map(ctx.agent.editableFiles.map((f) => [f, readFileSync(f, 'utf8')]));
      let feedback: string | undefined;
      let succeeded = false;
    
      try {
        for (let attempt = 1; attempt <= ctx.agent.maxAttempts; attempt++) {
          const changes = parseChanges(await callModel(buildPrompt(task, ctx, feedback)));
          try {
            assertAllowed(changes, ctx.agent.editableFiles);
          } catch (err) {
            feedback = (err as Error).message;
            continue;
          }
          for (const c of changes) writeFileSync(path.normalize(c.path), c.content);
          const tests = runTests(ctx.agent.testCommand);
          if (tests.ok) {
            succeeded = true;
            return { status: 'ready-for-review' as const, attempt };
          }
          feedback = tests.output;
        }
        return { status: 'failed' as const, lastFeedback: feedback };
      } finally {
        // rollback también si el modelo o el disco lanzan a mitad
        if (!succeeded) for (const [f, content] of originals) writeFileSync(f, content);
      }
    }
    

    Y la llamada, en un harness/main.ts que ejecutas con npx tsx harness/main.ts (tsx resuelve los imports sin extensión y el top-level await):

    // harness/main.ts
    import { runHarness } from './run';
    
    const result = await runHarness(
      'Un pedido de 45 € pagó envío y debería haber sido gratis. Corrige shippingCost.',
      'catalog/shipping-service.yaml',
    );
    console.log(result);
    

    Fíjate en lo que no dice la tarea: no dice "el umbral es 39". Quien abre el ticket no tiene por qué saberlo. El harness lo sabe porque lo lee del catálogo.

    Qué controla el harness y qué no controla el modelo

    Repasa el bucle con los ojos de quien lo audita.

    El contexto. El modelo recibe la regla con su fuente y la instrucción de que prevalece sobre el código. Ya no tiene que elegir entre un 50 que ve y un 39 que nadie le ha dicho.

    El radio de acción. Si el modelo devuelve src/shipping.test.ts o ../catalog/shipping-service.yaml, no están en la allowlist y el cambio entero se rechaza (path.normalize solo evita que ./src/shipping.ts se rechace por la forma de escribir la ruta). Se rechaza completo, no se aplica a medias. El motivo del rechazo vuelve como feedback en el siguiente intento.

    La verificación. El agente no decide cuándo ha terminado. Termina cuando vitest sale con código 0. Si falla, la salida de los tests (los últimos 4.000 caracteres, para no inflar el contexto) vuelve al prompt.

    El final. Tres intentos y rollback, también si la API falla a mitad de bucle: el finally restaura los ficheros pase lo que pase. El mejor resultado posible es ready-for-review: el harness no hace git push, no abre PR contra main y no tiene credenciales de despliegue. Filtrar variables de entorno es una red de seguridad, no la garantía. La garantía real es que el proceso del harness nunca tenga esas credenciales cargadas.

    Esta forma de pensar el trabajo con agentes, con contexto explícito, límites y verificación antes de que un humano mire, es la que seguimos en Construye con IA para pasar de idea a producto sin que el agente decida cosas que no le tocan.

    Lo que dice la gente que ya lo hace

    OpenAI popularizó el término con Harness engineering: Leveraging Codex in an agent-first world. El hilo de Hacker News sobre ese post tiene más de 200 comentarios, y los que aportan algo coinciden en lo mismo. Un usuario resume su receta y el primer punto es literalmente: "Give Claude/Codex a way to verify its own work (browser, smoke tests, e2e tests, high-fidelity local environment)".

    Otro avisa de lo que pasa sin ese control. Sin supervisión, "it'll start creating slop or hardcoding solutions". Aquí el número sigue en el código: el agente cambiará 50 por 39, y eso también es hardcodear. La diferencia es que ahora el hardcodeo tiene un vigilante. Si el número del código se separa del catálogo, el test que lee el catálogo se pone rojo. Si quieres eliminar la copia, el siguiente paso es que shipping.ts lea el umbral del catálogo en runtime.

    Límites de este enfoque de harness engineering

    El catálogo puede mentir y el agente se lo cree. Le has dicho al modelo que el catálogo prevalece sobre el código. Si alguien deja el YAML desactualizado, el harness propaga el error con toda la confianza del mundo, y el test también, porque lee el mismo fichero. En el mismo hilo de HN alguien lo dice de la documentación en general: "Become outdated fast". El catálogo necesita un owner con nombre y apellidos, y los cambios de regla tienen que pasar por revisión como cualquier otro código.

    La allowlist por fichero es gruesa. Permitir src/shipping.ts permite todo lo que hay en src/shipping.ts. El agente puede cambiar el umbral y, de paso, reescribir el manejo de errores. La allowlist limita dónde toca el agente, no qué hace. Para eso sigue haciendo falta revisar el diff.

    Los tests ejecutan código del agente. La allowlist controla lo que escribe el harness, no lo que hace shipping.ts cuando vitest lo importa. Ese código puede escribir en el test o leer un .env del disco. Dos defensas baratas: después de los tests, comprueba con git status --porcelain que solo cambiaron ficheros de la allowlist, y ejecuta los tests en un contenedor sin credenciales ni acceso de escritura fuera de src/.

    Los tests solo verifican lo que cubren. Dos tests sobre el umbral no dicen nada de redondeos, divisas o pedidos con descuento. Un cambio que pasa en verde no está bien: simplemente no rompe lo que mides.

    Los reintentos cuestan. Cada intento reenvía los ficheros permitidos completos, las reglas y la salida de los tests. Con un fichero pequeño da igual. Con cinco ficheros de 800 líneas y maxAttempts: 5, el coste se multiplica y el modelo empieza a arrastrar contexto de intentos fallidos. Si en tres intentos no pasa, el problema suele estar en la tarea o en los tests, no en la falta de insistencia.

    Devolver ficheros completos no escala. Para ficheros grandes vas a querer diffs o herramientas de edición en lugar de ficheros enteros, y entonces la validación de la allowlist se hace sobre las rutas del diff. La idea no cambia; cambia el parser.

    El feedback es de un solo intento. Si un intento se rechaza por la allowlist, ese mensaje sustituye a la salida de los tests del intento anterior, y el siguiente prompt enseña el fichero ya modificado, no el original. Para tareas acotadas basta; para tareas largas conviene acumular el historial de feedback.

    Qué hacer hoy

    Elige una regla de negocio que hoy vive como constante en tu código y que alguien de fuera de ingeniería puede cambiar: un umbral, un plazo, un límite de reintentos. Muévela a un fichero de catálogo versionado, haz que su test la lea de ahí y quita el número del test.

    Solo con eso, sin agente, ya tienes una regla con un único sitio de verdad. Luego conectar el harness es un centenar largo de líneas.

    Si quieres los fundamentos de cómo funcionan los agentes por dentro (bucles, herramientas, memoria, seguridad), tienes gratis el ebook El Developer Agéntico. Y si quieres llevar esta disciplina más atrás, a la especificación antes de que exista el código, el libro de Spec-Driven Development es el siguiente paso.

    Preguntas frecuentes

    ¿Por qué no basta con poner la regla de negocio en el prompt?

    Porque depende de que quien escribe el prompt la conozca y se acuerde. Cada tarea nueva es una oportunidad de olvidarla. Si el harness la lee de una fuente de verdad, la regla llega siempre, aunque el ticket lo haya escrito alguien que no sabe que existe.

    ¿Necesito Backstage o Port para aplicar esto?

    No. Un fichero YAML o JSON versionado en el repo del servicio es suficiente para empezar. Backstage o Port tienen sentido cuando hay decenas de servicios y varios equipos y necesitas un catálogo centralizado con owners, API y búsqueda. El harness solo necesita una función que devuelva el contexto validado, venga de donde venga.

    ¿Qué pasa si el agente intenta modificar los tests para que pasen?

    El harness rechaza el cambio completo porque el fichero de test no está en la allowlist, y el motivo vuelve como feedback en el siguiente intento. Por eso los tests nunca deben estar en la lista de ficheros editables cuando el objetivo es corregir código contra ellos.

    ¿Por qué el código no lee directamente el catálogo en runtime?

    Si puedes, hazlo: es la versión más sólida de la idea, porque elimina la copia del valor. En muchos servicios no es viable (el catálogo vive en otro sistema, la regla se compila en un bundle o el dominio no debe depender de la configuración de plataforma). En esos casos, el test que lee el catálogo es lo que evita que el código y la regla se separen.

    ¿Cuántos reintentos debería permitir el harness?

    Entre dos y tres para tareas acotadas como esta. Más intentos rara vez arreglan lo que los primeros no arreglaron, y el coste en tokens crece con cada uno. Si falla de forma sistemática, revisa la tarea, el contexto o los tests antes de subir el límite.

    ¿Por qué el harness no despliega si los tests pasan?

    Porque unos tests verdes solo demuestran que no se ha roto lo que está cubierto. El despliegue necesita una revisión humana del diff y el pipeline de CI habitual. El harness entrega un cambio listo para revisar; quien tiene permisos de despliegue es otra persona, u otro sistema con sus propios controles.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Multiagente vs agente único: framework con 3 casos reales

    Multiagente vs agente único: framework con 3 casos reales

    Hace tres semanas monté un pipeline de cuatro agentes para una feature que no necesitaba ni uno: planner, coder, reviewer y tester, cada uno con su propio prompt y su propia ventana de contexto. Se sentía como arquitectura seria.

    Tardó el triple que si lo hubiera hecho con un solo agente bien instruido. Gasté más tokens coordinando handoffs entre roles que resolviendo el problema real. Y el resultado no fue mejor — fue distinto, y peor: el reviewer contradecía decisiones que el coder ya había tomado con buen criterio.

    Ese día entendí que la pregunta multiagente vs un solo agente no se responde por instinto ni por moda. Se responde con criterios concretos, y esos criterios ya los han medido equipos que sí llevaron el experimento a producción, no solo a un demo.

    En corto: multiagente vale la pena cuando las subtareas son realmente paralelas, casi no comparten contexto, y el resultado justifica pagar entre 4 y 15 veces más tokens. Si tus subtareas dependen unas de otras —como pasa en la mayoría del código— un solo agente bien instruido gana en velocidad, coste y calidad. Orquestar varios agentes no es gratis: se gana con datos, no con intuición.

    ¿Qué es un sistema multiagente?

    Un sistema multiagente es una arquitectura de IA donde varios agentes —con roles, prompts o ventanas de contexto distintas— colaboran, en paralelo o en secuencia, para completar una tarea que en teoría podría resolver un solo agente con suficiente contexto y herramientas.

    La palabra clave es "en teoría". Que se pueda dividir un problema en roles no significa que dividirlo mejore el resultado — ya expliqué por qué el mega-prompt falla cuando se resuelve metiendo más subagentes sin criterio. La mayoría de los equipos confunden "se puede separar" con "conviene separar" — y esa confusión se siente más profesional, con nombres de rol y diagramas de flujo, aunque cada agente adicional solo añade un punto de fallo más y una llamada más al modelo que pagar.

    Tres equipos distintos, en dominios distintos, ya midieron esto en producción. Sus resultados no coinciden en la respuesta, pero sí coinciden en el criterio.

    Tres casos reales que no dicen lo mismo (y por eso sirven)

    Caso Qué se comparó Resultado multiagente Resultado agente único Quién ganó y por qué
    Anthropic — Claude Research Preguntas "breadth-first" con varios subagentes en paralelo vs. Claude Opus 4 solo 90,2% mejor que el agente único en su eval interna, pero consume ~15x los tokens de una conversación normal Consume ~4x los tokens de una conversación normal; peor en exploración amplia de fuentes independientes Multiagente. Las subtareas eran genuinamente independientes y el valor de la respuesta justificaba el coste. Anthropic aclara que en coding, con dependencias fuertes, esto no aplica
    Cognition (equipo de Devin) — "Don't Build Multi-Agents" Subagentes paralelos construyendo partes de una misma app vs. un agente lineal con historial comprimido Subagentes con contexto fragmentado tomaron decisiones incompatibles entre sí sin verlo (estilos, assets, nombres) Un agente que arrastra el contexto de cada decisión previa, con resúmenes comprimidos en tareas largas Agente único. El código depende de decisiones implícitas coherentes que solo sobreviven con contexto compartido real
    Uncle Bob — harness SwarmForge Harness con 6 roles fijos vs. un solo agente en la misma tarea de desarrollo 3-4 horas, calidad aceptable 40 minutos, calidad superior, consumo de tokens muchísimo menor Agente único. La orquestación rígida sumaba overhead sin sumar calidad; los gates de verificación siguieron siendo necesarios en ambos casos

    Nota lo que no dicen estos tres casos: no dicen "el multiagente está muerto" ni "siempre gana un solo agente". Dicen que cada arquitectura gana cuando el problema tiene una forma concreta, y esa forma se puede medir antes de construir nada.

    El propio lanzamiento del post de Cognition generó un hilo largo en Hacker News donde varios ingenieros coinciden en algo puntual: los subagentes sirven para aislar contexto que ensucia el prompt principal, no para repartir trabajo autónomo sin supervisión. Un comentarista lo resume mejor que cualquier framework: "si necesitas pensar distinto sobre el problema, necesitas un agente distinto" — si el razonamiento es el mismo con datos distintos, es el mismo agente.

    El framework: cinco criterios, no una moneda al aire

    De los tres casos sale un patrón repetido. Lo convertí en cinco criterios que reviso antes de escribir el primer prompt de orquestación.

    Criterio Señal de que multiagente vale la pena Señal de que gana un solo agente Peso
    Paralelismo real de las subtareas Las subtareas son independientes: ninguna necesita ver el resultado de otra hasta el final Las subtareas se encadenan paso a paso (editar y refactorizar código) Alto
    Coste en tokens vs. ahorro de tiempo humano El resultado justifica pagar 4x-15x tokens (investigación, análisis masivo, due diligence) La tarea es rutinaria y el ahorro de tiempo no compensa el gasto extra Alto
    Dependencias y decisiones implícitas compartidas Ninguna decisión de un agente condiciona lo que hace otro Una decisión de un paso condiciona el siguiente (arquitectura, nombres, estilo) Alto
    Especialización de contexto vs. overhead de coordinación Cada rol necesita un contexto tan distinto que mezclarlo degrada el prompt (buscar en la web vs. escribir código) Los roles comparten casi todo el contexto — separarlos solo añade handoffs Medio
    Necesidad real de "pensar distinto" La tarea exige un tipo de razonamiento distinto por fase (buscar, sintetizar, criticar) Todo el trabajo usa el mismo tipo de razonamiento aunque tenga varios pasos Medio
    Riesgo si te equivocas de opción Pagas 4x-15x tokens sin ganar calidad, y los agentes pueden contradecirse entre sí sin que nadie lo note Te quedas corto si el paralelismo era real: pierdes el tiempo que sí te habría ahorrado dividir el trabajo —

    La heurística que uso es simple: si tres o más criterios de peso "Alto" apuntan a multiagente, lo construyo. Si no, empiezo con un solo agente bien instruido y solo divido cuando el contexto se vuelve imposible de manejar en una ventana — no antes, por elegancia arquitectónica.

    Esto es exactamente la lógica que enseño en el curso Construye con IA: empezar con la arquitectura más simple que resuelve el problema, y añadir complejidad solo cuando el proyecto la reclama, no cuando la moda lo sugiere.

    Un matiz que los tres casos comparten: la verificación no depende de la arquitectura. Tengas uno o seis agentes, necesitas gates deterministas —tests, tipos, contratos— que confirmen que lo entregado hace lo que dice. Si todavía revisas el código de tus agentes leyendo el diff en vez de contra un contrato, el ebook gratuito de Revisión por Contrato explica cómo montar ese gate en una tarde.

    Cuándo el framework no aplica

    Este framework tiene límites reales, y fingir que no los tiene sería venderte una solución perfecta que no existe.

    No sabes de antemano si tus subtareas son paralelas. El framework asume que puedes evaluar el paralelismo antes de construir. En dominios nuevos eso no es cierto: el acoplamiento oculto entre pasos solo aparece con el sistema corriendo en producción, no en el diseño en papel.

    Los números no son universales. El 15x de tokens y el 90,2% de mejora de Anthropic salieron de research breadth-first, no de tu chatbot de soporte ni de tu pipeline de facturación. Úsalos como orden de magnitud, no como cifra que se traslada directo a tu caso — mide en tu propio proyecto antes de decidir.

    La observabilidad cambia el cálculo. Un equipo con buen tracing, replay y evals puede sostener un sistema multiagente incluso cuando el framework diría que no, porque detecta y corrige fallos en minutos. Un developer solo, sin esa infraestructura, paga el precio completo de la complejidad sin la red de seguridad que la justifica.

    Qué hacer con esto hoy

    Coge tu proyecto actual y pon los cinco criterios en una hoja. Cuenta cuántos "Alto" apuntan a multiagente.

    Si son menos de tres, borra el orquestador que ya empezaste a montar y quédate con un solo agente con mejor contexto. Vas a tardar menos en notar la diferencia que en terminar de escribir el segundo rol.

    Si son tres o más, constrúyelo — pero mide tokens y tiempo desde el primer día, porque ese es el dato que te dirá si acertaste. Si quieres ver estos patrones aplicados a proyectos reales, en Dominicode Labs los revisamos cada semana con la comunidad.

    Preguntas frecuentes

    ¿Cuándo es mejor usar multiagente en vez de un solo agente?

    Cuando las subtareas son genuinamente independientes, no requieren compartir contexto para tomar decisiones coherentes, y el valor del resultado justifica pagar varias veces más tokens. El caso de Anthropic es el más claro: preguntas amplias donde explorar diez fuentes en paralelo vale más que explorarlas una a una.

    ¿Cuánto más cuesta un sistema multiagente en tokens?

    En el caso público de Anthropic, un agente simple usa alrededor de 4 veces los tokens de una conversación normal, y un sistema multiagente completo llega a unas 15 veces. No es una cifra universal, pero sirve como orden de magnitud: si tu tarea no genera un valor claramente superior a ese multiplicador, la orquestación no se paga sola.

    ¿El multiagente sirve para tareas de programación?

    Rara vez, por una razón concreta: escribir código depende de decisiones que se encadenan (nombres, estilo, arquitectura) y que un agente necesita "ver" para no contradecirlas. Cognition/Devin y Uncle Bob con SwarmForge llegaron a la misma conclusión en dominios distintos: un agente único con buen contexto superó a la orquestación.

    ¿Cómo empiezo si no sé si mi tarea es paralelizable?

    Empieza siempre con un solo agente bien instruido. Si notas que el contexto no cabe en una ventana razonable, o que mezclas dos tipos de razonamiento completamente distintos en el mismo prompt, ahí tienes la señal real para dividir — no antes.

    ¿Qué pasa si ya construí un sistema multiagente y no está funcionando?

    Revisa si el problema es de coordinación (agentes que se contradicen) o de coste (tokens que no se justifican con el resultado). En el primer caso, el diagnóstico de Cognition aplica directo: necesitas más contexto compartido, no más agentes. En el segundo, vuelve a los cinco criterios y cuenta cuántos apuntaban realmente a multiagente antes de construirlo.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Verificar código generado por IA sin leer todo: 4 técnicas

    Verificar código generado por IA sin leer todo: 4 técnicas

    Hace unas semanas aprobé un pull request generado por un agente que arreglaba el cálculo de un descuento por antigüedad. Para verificar código generado por IA hice lo que hacemos casi todos: los tests pasaban en verde, el type checker no se quejó, el diff tenía 40 líneas legibles con nombres razonables.

    Le di merge.

    Dos días después, un usuario premium con 14 meses de antigüedad reportó un descuento del 10% en vez del 20%. El código funcionaba. Hacía exactamente lo que decía que hacía — solo que eso no era lo que yo había pedido. La condición antiguedad > 12 estaba invertida en un if, y ni el compilador ni los tests que el propio agente había escrito lo detectaron.

    (Esta anécdota es ilustrativa del tipo de fallo que describo aquí — no es un incidente puntual verificable con fecha exacta, es el patrón que se repite.)

    Esto no es una historia sobre un mal agente. Es sobre un mal proceso: mi "verificación" fue leer por encima y confiar en dos gates que no estaban hechos para atrapar ese error. Leer por encima no escala cuando el agente te entrega cinco PRs al día.

    En corto: verificar código generado por IA sin leer cada línea es posible con cuatro filtros baratos: deja que el type checker haga de primer gate, pide los tests desde la especificación antes de enseñarle el código al agente, usa un segundo agente con un prompt distinto como revisor, y concentra tu lectura manual en las líneas que tocan auth, dinero o validación de input. Ninguno sustituye un sistema completo — son parches rápidos que aplicas hoy, sin instalar nada.

    ¿Qué es un gate de verificación (y por qué no es lo mismo que "revisar")?

    Un gate de verificación es un chequeo binario que pasa o falla sin que tengas que interpretarlo — no depende de tu criterio ni de tu concentración a las 11 de la noche. "Revisar" es leer código y juzgar si parece correcto. Un gate ejecuta algo que responde sí o no, con evidencia. "Leí el diff y se veía bien" no es un gate, es una opinión — y las opiniones fallan justo cuando el código está bien escrito y hace lo contrario de lo que pide la spec.

    Eso importa porque el código de un agente está optimizado para parecer correcto: nombres claros, formato limpio, estructura familiar. Tu ojo entrenado para detectar "código feo" falla exactamente cuando el código es bonito y está mal.

    El problema real: el código pasa la vista y falla en producción

    No soy el único con esta fricción. En un hilo de Hacker News con 298 comentarios titulado "When AI writes the software, who verifies it?", el usuario roadbuster lo describe así (traducido del inglés):

    "El LLM genera felizmente tests que simplemente refuerzan el comportamiento existente del código. En ningún momento nadie se detiene a preguntar si el código generado implementa el comportamiento funcional deseado."

    Es lo que me pasó con el descuento: el agente escribió el código y luego tests que confirmaban que ese código hacía lo que hacía — no lo que yo había pedido. El test nunca falla porque nació del mismo malentendido que la implementación.

    En el mismo hilo, Karrot_Kream admite el punto incómodo: sigue auditando a mano los tests que genera la IA, aunque reconoce que es "la parte de programar que menos le gusta". Nadie quiere hacer esta parte. Por eso casi nadie la hace bien.

    4 técnicas que puedes aplicar hoy, sin instalar nada nuevo

    Ninguna requiere adoptar un framework, escribir un AGENTS.md o cambiar tu flujo. Son filtros que añades a lo que ya haces.

    Técnica Qué detecta Qué NO detecta Esfuerzo
    Type checker en modo estricto Tipos incompatibles, propiedades inexistentes, APIs alucinadas, nulls sin manejar Lógica de negocio incorrecta que tipa perfectamente bien Ninguno extra — actívalo en strict
    Tests desde la spec, antes de ver el código Que el comportamiento coincida con lo pedido, no con lo que el agente decidió escribir Casos que la spec no contempló; spec vaga produce tests vagos Bajo — un prompt aparte, antes de la implementación
    Segunda pasada con otro agente/prompt como revisor Inconsistencias entre spec y código, casos límite obvios, errores sin manejar Puede repetir el sesgo del primero si comparte el mismo chat Medio — exige prompt adversarial, en sesión nueva
    Diffing dirigido a zonas críticas (auth, dinero, validación) Regresiones graves justo donde más caro sale que fallen Todo lo que quede fuera del filtro — no es lectura completa Bajo — un comando de git, pero exige definir bien qué es "crítico"

    1. Deja que el type checker sea tu primer filtro

    TypeScript, mypy o el compilador que uses no mienten ni se cansan. Si el agente alucina una propiedad, el gate lo para antes de tu revisión:

    function getDiscount(user: User): number {
      if (user.subscription.tier === 'premium') {
        return user.subscription.discountRate; // no existe en el tipo
      }
      return 0;
    }
    
    error TS2339: Property 'discountRate' does not exist on type 'Subscription'.
    

    Esto no habría parado mi bug del descuento invertido — el tipo estaba bien, la lógica no. Pero sí para buena parte de las alucinaciones típicas: APIs inventadas, campos que no existen, nulls sin manejar. Es gratis y ya lo tienes. Actívalo en modo estricto si no lo has hecho.

    2. Pide los tests desde la spec, antes de enseñarle el código

    Esta es la técnica que me habría salvado del bug del descuento. En vez de pedir código y luego tests, invierte el orden:

    Esta es la especificación (no hay código todavía):
    
    "calcularDescuento(usuario) devuelve 20% si el usuario es premium
    y lleva más de 12 meses activo, 10% si es premium con menos de
    12 meses, y 0% en cualquier otro caso."
    
    Escribe los tests de aceptación en Vitest. Todavía no has visto
    ninguna implementación.
    

    Cuando el agente escribe primero el código y luego "sus" tests, el test hereda cualquier malentendido de la spec. Cuando nace de la spec en una pasada separada, se convierte en un juez independiente que detecta el error porque no lo comparte.

    3. Usa un segundo agente con un prompt distinto como revisor

    No el mismo chat. Una sesión nueva, sin el contexto de cómo se escribió el código, con un prompt deliberadamente escéptico:

    Eres un revisor senior, escéptico por defecto. No escribiste este código
    y no asumes que está bien solo porque compila y pasa los tests actuales.
    
    Busca: casos límite no cubiertos, lógica que no coincide con la
    especificación, manejo de errores ausente, datos sensibles sin validar.
    
    Especificación: [pegar]
    Código: [pegar diff]
    
    No digas "se ve bien". Señala línea y motivo, o di qué revisaste
    y por qué no encontraste problema ahí.
    

    Este patrón de usar un agente distinto para tareas que exigen otro punto de vista es parte de lo que enseño paso a paso en Construye con IA: montar flujos con varios agentes que se corrigen entre sí, no uno solo que se audita a sí mismo.

    4. Diffing dirigido: lee solo lo que puede doler de verdad

    No leas las 340 líneas del PR. Filtra por lo que toca zonas donde un error sale caro:

    git diff --stat main...feature/discount-calc
    
    git diff main...feature/discount-calc -- '**/auth/**' '**/payment/**' '**/*valida*' '**/*schema*'
    

    El criterio de qué es "crítico" no es intuición — es cuánto cuesta que falle y qué tan rápido te enteras.

    Mi criterio, sin "depende"

    El type checker en estricto y el diffing dirigido van siempre, en cualquier PR — son gratis. Los tests desde la spec los reservo para lógica de negocio real, no para un CRUD trivial. El segundo agente como revisor solo cuando ya tengo un flujo agéntico corriendo — si no, revisarlo yo mismo es más rápido.

    Si solo añades una cosa hoy, que sea el type checker en strict más el diffing dirigido: eliminan la categoría de bugs más tonta sin coste de configuración extra.

    Lo que estas técnicas no te van a coger

    Sé honesto contigo mismo, porque yo no lo fui con el descuento: estos cuatro filtros no son un sistema, son parches.

    No dejan rastro. Dentro de tres meses no hay ningún documento que diga qué se verificó, con qué criterio, y quién lo aprobó — repites el proceso de memoria, y la memoria falla en el PR número 40 de la semana.

    Dependen de que definas bien qué es "crítico" o qué pide realmente la spec. Si tu filtro de diffing no incluye la carpeta correcta, o tu spec es ambigua, el agujero sigue ahí y nadie te avisa — el chequeo "pasó" porque nunca miró donde tenía que mirar.

    Y el segundo agente como revisor puede convertirse en un espejo del primero si comparte contexto o sesgo. Un revisor que piensa igual que quien escribió el código no es un revisor — es una segunda opinión de la misma persona.

    Si tu proyecto mueve dinero real, datos de usuarios o decisiones irreversibles, estas cuatro técnicas son el piso mínimo, no el techo. El post Revisar código generado por IA: el método Revisión por Contrato explica el sistema completo que sí deja rastro: qué se construye, por dónde no puede salirse el agente, y quién dice que está bien, con un AGENTS.md real. Si además quieres el sistema montado con harness de verificación end-to-end, el workshop SDD + Agentic Engineering cubre eso paso a paso.

    Qué puedes hacer hoy

    Abre tu tsconfig.json o tu mypy.ini ahora mismo y confirma que estás en modo estricto. Es la línea más barata que vas a escribir esta semana.

    Después, la próxima vez que le pidas código a un agente, invierte el orden: pide primero los tests desde la especificación, en una pasada separada, antes de pedir la implementación. Esa sola inversión habría parado mi bug del descuento.

    Estas cuatro técnicas son la entrada. Cuando el proyecto crezca lo suficiente como para que un parche ya no alcance, el siguiente paso es un sistema real de verificación — contrato, carril y veredicto — que dejo completo, con ejemplos y el AGENTS.md entero, en Revisar código generado por IA: el método Revisión por Contrato. Y si quieres ver cómo aplico esto semana a semana en proyectos reales, en Dominicode Labs comparto el detalle con la comunidad.

    Preguntas frecuentes

    ¿El type checker es suficiente para confiar en código generado por IA?

    No. Detecta que las piezas encajan en forma — tipos correctos, propiedades que existen, nulls manejados. No detecta que la lógica de negocio sea la que pediste. Mi bug del descuento invertido tipaba perfectamente bien: es un filtro necesario y gratuito, no una prueba de corrección.

    ¿Qué diferencia hay entre pedir tests antes o después de ver la implementación?

    Cuando el mismo agente escribe primero el código y luego los tests, estos heredan cualquier malentendido de la spec, porque nacen de la misma lectura equivocada. Generados en una pasada separada, se convierten en un juez independiente que detecta el error porque no lo comparte.

    ¿Puedo usar el mismo agente que escribió el código para revisarlo después?

    Puedes, pero pierdes gran parte del valor: en el mismo chat, con el mismo contexto, el agente tiende a justificar sus decisiones en vez de cuestionarlas. Usa una sesión nueva y un prompt explícitamente escéptico para que la segunda pasada aporte un punto de vista distinto, no un eco del primero.

    ¿Estas técnicas sirven si mi proyecto no tiene tests todavía?

    El type checker y el diffing dirigido sí, sin cambios — no dependen de una suite existente. Los tests desde la spec además te dan una forma barata de empezar a construirla: cada vez que le pides código a un agente, generas primero el test de aceptación, y en unos meses tienes cobertura real sin haber dedicado un sprint a escribirla.

    ¿Cuándo necesito algo más que estas 4 técnicas?

    Cuando el error de un agente puede costarte dinero real, datos de usuarios, o algo que no se deshace con un revert. Ahí un parche puntual no basta — necesitas un sistema que deje rastro de qué se verificó y con qué criterio, que es justo lo que cubre el método de Revisión por Contrato.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Verificar código generado por IA: 112 posts con el schema roto

    Verificar código generado por IA: 112 posts con el schema roto

    El 10 de septiembre de 2026 le pedí a un script que auditara la FAQ de todo el blog de Dominicode. No esperaba encontrar gran cosa: llevo meses aprobando cada post yo mismo antes de publicarlo, y la sección de preguntas frecuentes siempre se veía perfecta — pregunta en negrita, respuesta debajo, todo alineado en el editor de WordPress y en el navegador.

    El script devolvió 112 posts con el schema FAQPage roto.

    Es el mismo problema que tienes al intentar verificar código generado por IA con solo leer el resultado: se ve perfecto y sigue roto.

    No roto a medias. En un grupo de esos 112, el JSON-LD que se genera para Google y para cualquier motor que lea structured data tenía las preguntas literalmente llamadas "Respuesta:". Ciento doce posts publicados, revisados por mí uno por uno antes de publicarlos, y ninguno cumplía el contrato real que el frontend del blog necesita para generar ese schema.

    Nadie lo había visto leyendo el HTML. Yo tampoco.

    En corto: leer el diff o el HTML de código generado por IA no es lo mismo que verificar que cumple el contrato que otro sistema necesita para consumirlo — solo confirma que "se ve bien". Verificar código generado por IA por contrato significa ejecutar el mismo parser o extractor que usará el consumidor final antes de dar el visto bueno. Lo descubrimos auditando nuestro propio blog: 112 posts aprobados a simple vista tenían el schema FAQPage roto, invisible en el navegador, durante meses.

    ¿Qué es "verificar por contrato" y por qué no es lo mismo que leer el código?

    Verificar por contrato es comprobar que un cambio produce exactamente lo que el sistema que lo consume necesita — no que "se vea bien" para un humano que lo lee. Leer un diff o un post publicado confirma que el resultado es legible. No confirma que un parser o un test automatizado pueda procesarlo.

    Son dos preguntas distintas. "¿Se ve bien?" la responde cualquiera en cinco segundos. "¿Cumple el contrato de quien lo consume?" solo la responde ejecutar ese sistema —o replicar su lógica exacta— contra el resultado.

    Ya escribí el método completo, con el AGENTS.md entero, en Revisión por Contrato: cómo verificar código de agentes de IA. Este post es la prueba de que el método no es teoría: es lo que evitó que 112 posts siguieran rotos indefinidamente.

    El blog no usa ningún plugin de WordPress para generar el schema FAQPage. Lo genera el frontend en Next.js parseando el HTML del post con una función propia (extractFaqsFromContent, en app/lib/api.ts).

    Su contrato es estricto: la sección FAQ debe abrir con un <h2> que case con "FAQ" o "Preguntas frecuentes", cerrar en el primer <hr> o el siguiente <h2> —lo que llegue antes— y dentro de esa sección solo reconoce <h3> como pregunta y <p> como respuesta. Cualquier otra estructura, por bien que se vea en pantalla, no existe para ese parser.

    Seis formas de "verse bien" que rompían el contrato

    El catálogo acumuló seis formas distintas de escribir la FAQ, heredadas de plantillas de distintas épocas. Ninguna usaba <h3> + <p> dentro de la sección — todas se veían impecables en WordPress.

    Forma heredada Dónde vivía el id del ancla Qué producía el schema real
    A — <li> con enlace + <div id><p><strong>Respuesta:</strong> R</p></div> La respuesta Todas las preguntas literalmente "Respuesta:"
    B — igual que A, pero <strong>P</strong> cuelga del <div>, fuera del <p> La respuesta 0 preguntas — el extractor solo mira dentro de <p>
    C — <div class="faq-question"> con el enlace + <div id> con texto suelto La respuesta 0 preguntas — no hay ni <h3> ni <p>
    D — <p><a href="#id">P</a></p> como pregunta La respuesta 0 preguntas — se lee como párrafo suelto y se descarta
    E — <h3 id="id">P</h3> suelto, sin enlace de índice La propia pregunta La respuesta vive en un <div> sin <p>; la pregunta se pierde sin dejar rastro
    F — <section id="id"> que solo envuelve al <h3> La propia pregunta Mismo problema que E: el <div> de respuesta queda fuera de lo que ve el extractor

    Seis formas, un solo fallo compartido: la pregunta y la respuesta nunca vivían dentro de <h3> + <p> a la vez. El contrato no pedía nada exótico — pedía dos etiquetas concretas, en el lugar concreto.

    Por qué nadie lo vio en 112 revisiones

    Esto no es negligencia mía en particular. Es lo que le pasa a cualquier revisión manual cuando el criterio de "correcto" no es visual.

    En un hilo de Hacker News sobre por qué las code reviews casi nunca encuentran bugs, un comentario cita un dato de Wikipedia: menos del 15% de los comentarios que se dejan en una revisión de código señalan errores reales. El resto es estilo y preferencia —lo que un humano sí evalúa mirando.

    Una FAQ con formato bonito no activa ninguna alarma en un revisor humano. Activa un montón en un parser que busca <h3> y no encuentra ninguno.

    Hay un detalle que le añade ironía al caso: Google dejó de mostrar el rich snippet de FAQ en el buscador el 7 de mayo de 2026, cuatro meses antes de que reparáramos el nuestro, y sin anunciarlo —solo lo cambió en la documentación.

    ¿Reparar un schema que ya no produce un desplegable en el SERP es tiempo perdido? No: el FAQPage sigue siendo structured data válida, y sigue siendo el tipo de dato limpio y extraíble que un motor generativo necesita para leer y citar tu contenido sin tener que adivinar dónde empieza cada respuesta.

    Que Google apagara el escaparate visual no cambia que el contrato de fondo siga decidiendo si tu contenido es citable.

    Cómo se reparó — sin confiar en que "debería funcionar"

    El script scripts/fix-faq-schema.mjs corre por post individual o con --all, en modo dry-run por defecto — solo escribe con --apply explícito. Antes de tocar nada, replica el contrato exacto del extractor del frontend para diagnosticar si un post está roto. Después de reparar, vuelve a correr ese mismo contrato contra el HTML reparado, no contra lo que "debería" haber quedado.

    Aborta ese post concreto — sin tocar los demás — si detecta cualquiera de estas condiciones:

    • Aparece un <h1> inesperado en el cuerpo.
    • Hay un <p> metido dentro de un bloque <pre>.
    • Cambia el número de <pre>, <h2> o <img> respecto al original.
    • Se pierde algún enlace externo que existía antes de reparar.
    • El extractor, tras reparar, devuelve menos preguntas que antes de tocar nada.
    • El diagnóstico, tras reparar, sigue devolviendo fatal o bad en vez de pasar a ok.
    node scripts/fix-faq-schema.mjs --all          # dry-run: solo diagnostica
    node scripts/fix-faq-schema.mjs --all --apply  # repara de verdad
    

    El guardarraíl más importante es el último paso: después de escribir en WordPress, el script vuelve a leer el post ya guardado en el servidor y comprueba que el schema sale bien ahí —no en la respuesta que WordPress devolvió al hacer el POST—. No confía en que la escritura funcionó. Verifica que funcionó.

    El resultado, verificado — no asumido

    Categoría Antes de reparar Después de reparar
    Schema roto 112 posts 0 posts
    Schema válido 375 posts 487 posts
    Sin sección FAQ (no aplica) 106 posts 106 posts

    De los 487 posts con schema válido, 7 quedaron con un aviso cosmético menor — alguna pregunta sin signo de interrogación — que no bloquea el schema y no tiene impacto real. El resto: cero posts rotos.

    Cuándo esto no es suficiente

    Verificar por contrato no es magia, y sería deshonesto venderlo como si lo fuera.

    No arregla un contrato mal definido desde el principio. Si el contrato replicado por el script hubiera asumido, por ejemplo, que el extractor acepta <h4> cuando en realidad solo acepta <h3>, el script habría dado el visto bueno a posts que seguían rotos.

    Verificar contra un contrato equivocado da la misma falsa confianza que no verificar nada. El contrato hay que sacarlo del código real que consume el resultado, no de la memoria de quien escribió la plantilla hace dos años.

    La propia herramienta de verificación puede fallar, y necesita sus propios guardarraíles. Un script que repara HTML a golpe de expresiones regulares puede corromper contenido de formas que no están en su lista de comprobaciones.

    Por eso aborta ante solapes de edición, cambios en el número de imágenes o enlaces perdidos, o si el diagnóstico sigue en rojo después de reparar —pero esa lista la escribimos nosotros, pensando en lo que podía salir mal. Un caso que no anticipamos no queda cubierto. Esto no termina en un script que se audita a sí mismo una vez y ya: se sigue vigilando.

    Qué puedes hacer hoy

    Si mantienes contenido o código que un sistema automatizado consume después de ti —un schema, un feed, la salida de un agente que escribe en tu repo— deja de revisarlo leyendo el resultado final.

    Escribe (o pide a un agente que escriba) una función de verificación que replique exactamente lo que ese consumidor necesita, y corre esa función antes de aprobar nada. Es el mismo principio que explico con el AGENTS.md completo —contrato, carril y veredicto— en el ebook gratuito Revisión por Contrato.

    Si quieres ver cómo aplicamos esto a proyectos más grandes, con guardarraíles reales y no solo el argumento, en Dominicode Labs seguimos publicando los scripts y los casos según van pasando — este incluido.

    Preguntas frecuentes

    ¿Qué diferencia hay entre revisar código y verificarlo por contrato?

    Revisar código es leer el resultado —un diff, un HTML, una pantalla— y juzgar si parece correcto. Verificar código generado por IA por contrato es ejecutar, o replicar, el mismo proceso que usará el sistema que consume ese resultado, y comprobar que produce lo esperado.

    La revisión detecta si algo se ve bien; la verificación detecta si funciona para quien lo necesita, que casi nunca es un humano leyendo por encima.

    ¿Por qué el HTML se veía perfecto si el schema estaba roto?

    Porque "verse bien" y "cumplir el contrato" son criterios distintos. El navegador y el editor de WordPress renderizan cualquier combinación de etiquetas de forma legible, aunque esa combinación no sea la que un parser automatizado espera.

    El fallo solo existe desde el punto de vista del extractor, no desde el punto de vista de quien lee la página.

    ¿El schema FAQPage sigue sirviendo de algo si Google ya no muestra el rich snippet?

    Sigue siendo structured data válida, y sigue siendo el tipo de dato limpio y extraíble que un sistema automatizado —no un humano— necesita para leer tu contenido sin ambigüedad.

    Que Google retirara el desplegable visual del buscador en mayo de 2026 no cambia que ese contrato de fondo siga importando para cualquier motor que consuma tu página en vez de un lector.

    ¿Cómo verifico código generado por IA cuando lo escribe un agente en mi propio repo?

    Igual que aquí: define el contrato exacto que ese código debe cumplir —qué test tiene que pasar, qué estructura tiene que respetar— antes de que el agente escriba una sola línea.

    Después no apruebes el resultado leyendo el diff: corre ese contrato contra lo que el agente entregó. Si estás construyendo ese agente desde cero, el mismo principio aplica en cada uno de los 5 pasos. El método completo de verificación, con ejemplos de AGENTS.md, está en el post sobre Revisión por Contrato.

    ¿Qué pasa si el propio script de verificación tiene un error?

    Puede pasar, y por eso no basta con escribirlo una vez y confiar en él para siempre. Este en concreto se protege con guardarraíles explícitos —aborta si cambia el número de imágenes o enlaces, y relee el contenido ya guardado en el servidor en vez de asumir que la escritura funcionó.

    Pero esa lista de guardarraíles la definió una persona, y solo cubre lo que esa persona anticipó. Verificar por contrato reduce el margen de error; no lo elimina.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Qué delegué a un agente de IA (y qué audité línea por línea)

    Qué delegué a un agente de IA (y qué audité línea por línea)

    Martes por la noche dejo un agente generando cuarenta y dos thumbnails para posts antiguos: prompt, imagen, nombre de archivo, carpeta correcta. Es de las tareas más fáciles de delegar a un agente de IA que tengo: patrón repetido, blast radius bajo, nadie la ve hasta que yo la reviso. Me voy a dormir sin abrir ni una carpeta.

    Miércoles tengo otro agente con el email del próximo lanzamiento montado en MailerLite: asunto, enlaces con UTM, la lista completa como destino. Solo falta el clic de "Enviar ahora". Con el cursor encima del botón, estoy a punto de aprobarlo con la misma mano suelta con la que aprobé los thumbnails el día anterior.

    No lo hago. Releo el asunto: el precio es el de la semana pasada, subió el martes y el agente trabajó con el contexto que tenía guardado. Si sale así, miles de bandejas reciben una cifra que ya no es verdad.

    Misma semana, misma herramienta —Claude Code—, dos posturas distintas frente al mismo tipo de trabajo. De eso va este post.

    En corto: delego a un agente de IA sin supervisión estrecha cuando el error es barato, reversible y fácil de detectar —thumbnails, primer borrador, research, formateo de archivos con patrón claro—. Audito línea por línea cualquier cosa que toque producción real: publicar, hacer commit o push, tocar dinero, o mandar un email a toda la lista. El criterio no es "confío en el agente": es cuánto cuesta que salga mal comparado con cuánto cuesta verificarlo antes de que salga.


    ¿Qué es el blast radius de una tarea delegada a un agente de IA?

    El blast radius de una tarea delegada a un agente es cuánto daño hace si sale mal, multiplicado por cuánta gente o cuánto dinero toca antes de que alguien lo note. No mide qué tan bueno es el modelo. Mide el sistema alrededor: qué tan fácil es deshacer lo que hizo y qué tan rápido te enteras si lo hizo mal.

    Renombrar cuarenta y dos imágenes tiene blast radius casi cero: si un nombre sale mal, lo veo al abrir la carpeta y lo arreglo en diez segundos. Mandar un email a toda la lista tiene blast radius alto: si el asunto está mal, ya salió, no hay deshacer.

    Trabajo solo en Dominicode, sin nadie que revise detrás de mí. Cada tarea que delego sin mirar es una tarea que, si falla, la descubro yo tarde, o la descubre el lector. Esa asimetría fija la frontera.

    Lo que audito siempre, sin excepción

    El email del miércoles no fue un accidente: es la categoría completa donde nunca delego la revisión, pase lo que pase el resto de la semana. Cualquier acción que toque producción real, dinero o algo irreversible.

    Ahí entra publicar en WordPress —el agente solo puede dejar el post en draft, nunca en publish—. Entra hacer commit y push a una rama compartida. Entra cualquier cifra de facturación o de precio. Y entra, desde esta semana, cualquier email a la lista completa sin que yo lea la última línea con los ojos abiertos.

    El agente no mintió: trabajó con el contexto que tenía. El precio cambió después del borrador y nadie le avisó. Eso no se arregla con "mejor prompt" — se arregla con un humano revisando antes de que salga, siempre. El método completo —contrato, carril y veredicto— lo dejo entero y gratis en el ebook Revisión por Contrato.

    Mi semana real: qué se delega y qué se audita

    Tarea Postura Blast radius Reversibilidad
    Generar thumbnails y portadas del blog Delego sin mirar Bajo — se ve al abrir la carpeta Total — se regenera con un comando
    Primer borrador de un post o guion Delego sin mirar Bajo — nadie lo lee hasta que yo lo apruebo Total — vive en un .md local
    Research y resumen de una discusión técnica Delego, verifico la fuente citada Bajo, y verificar el enlace cuesta un minuto Total
    Renombrar o formatear archivos con patrón repetido Delego sin mirar Bajo Alta — está versionado en git
    Commit y push Reviso el diff siempre Medio-alto — lo ve cualquiera que haga pull Media — revertir cuesta tiempo y ruido
    Publicar un post en WordPress Audito siempre; el agente solo deja draft Alto — lo ve el lector final Baja — un post mal publicado ya lo indexó Google
    Email de lanzamiento a toda la lista Audito línea por línea, incluidas cifras Muy alto — miles de bandejas de entrada Cero — no existe "deshacer enviar"
    Cualquier cosa que toque dinero o facturación Audito siempre, sin excepción Muy alto Depende del banco, no de mí
    Delegar sin gate automático (tests/tipos) que verifique el resultado No delego Alto, aunque no lo parece a simple vista Depende de si lo detectas a tiempo

    Esta tabla no es universal — cambia con tu stack. Si tu WordPress publica en directo sin pasar por borrador, esa fila sube dos puestos en tu lista de "auditar siempre". El criterio se traslada; los números, no.

    Es el flujo que enseño paso a paso en Construye con IA: cómo montar agentes que generan contenido, research y primeros borradores sin tener que mirar cada línea mientras trabajan. Si todavía no has montado uno, aquí explico cómo construir un agente de IA desde cero en 5 pasos.

    Lo que dice Hacker News cuando se discute esto mismo

    No soy el único con esta fricción. En mayo de 2026, un post de Simon Willison sobre dónde termina el vibe coding y empieza la ingeniería agéntica llegó a 787 puntos y más de 800 comentarios en Hacker News — casi todo el hilo discute esta frontera. Los comentarios citados abajo están traducidos del inglés; el enlace de cada uno lleva al original.

    Amber-chen lo resume en una frase que podría ser el resumen de este post:

    "La distinción entre 'vibe coding' e 'ingeniería agéntica' importa. La diferencia clave es si estás revisando y entendiendo el código que produce el agente. Cuando uso agentes para tareas no triviales, siempre reviso el diff antes de hacer commit — esa es la parte de ingeniería. El peligro es saltarse ese paso y confiar sin más en el resultado."

    arian_ apunta al problema real, que no es de habilidad sino de infraestructura:

    "La distancia entre 'vibe coding' e 'ingeniería agéntica' es la misma distancia entre pedirle a alguien que haga una tarea y poder demostrar que la hizo bien. Uno es intuición. El otro es rendición de cuentas. Seguimos construyendo agentes más potentes sin construir la infraestructura de auditoría para verificar qué hicieron de verdad."

    bhagyeshsp añade la pieza que falta: la distancia de responsabilidad entre quien produce el resultado y quien responde por él es lo que decide cuánto puedes soltar sin revisión. Cuanto más lejos estás de responder tú mismo por algo, menos deberías delegarlo sin mirar.

    No es cuestión de fe en el modelo. Es cuestión de quién responde si sale mal, y qué tan caro sale.

    El criterio, en tres preguntas — no en "cuánto confío"

    Cada vez que un agente termina una tarea, me hago tres preguntas, en este orden:

    1. ¿Cuál es el blast radius si esto sale mal? ¿Lo ve un archivo local o lo ve un lector, un cliente, un banco?
    2. ¿Es reversible? ¿Lo deshago en diez segundos o ya salió por la puerta?
    3. ¿Cuesta más verificarlo que hacerlo yo mismo? Si sí, delegar no ahorra nada — es teatro de productividad.

    La tercera es la que menos se hace la gente, y la más incómoda: hay tareas donde revisar línea por línea tarda casi lo mismo que hacerlas a mano. Ahí delegar no es progreso, es mover el trabajo de sitio y añadir riesgo encima. Por eso creo que el techo de los agentic systems no es la capacidad del modelo, sino el coste de verificar cada tarea.

    Cuándo NO delegar a un agente de IA

    Tres situaciones donde no delego sin mirar, aunque la tarea parezca sencilla:

    1. Cuando no hay un gate automático que verifique el resultado. Sin tests, sin tipos, sin criterios de aceptación que corran solos, no hay diferencia real entre dejar que un agente haga commit sin revisión y dejar que lo haga alguien el primer día en el puesto. Confianza sin verificación no es confianza, es esperanza.
    2. Cuando el error es barato de cometer pero caro o imposible de deshacer, aunque la probabilidad sea baja. Un email masivo, un post publicado, una cifra de facturación: la baja probabilidad no compensa un coste irreversible.
    3. Cuando el agente no tiene el contexto de negocio que cambió esta semana: un precio, una decisión editorial que solo existe en mi cabeza. Esto no se arregla con más contexto en el prompt — se arregla con un humano revisando antes de que la acción sea irreversible.

    Nada de esto es un argumento contra usar agentes. Es un argumento contra tratarlos todos igual.

    Qué hacer hoy con esto

    No necesitas una política de veinte páginas. Escribe, para las cinco tareas que más delegas esta semana, una columna de blast radius y una de reversibilidad. Las que salgan bajas en ambas, suéltalas del todo. Las que salgan altas en cualquiera de las dos, revísalas siempre, aunque el agente lleve un mes acertando.

    Si quieres ver cómo aplico esto cada semana en un negocio real que opero solo, sin equipo detrás que revise por mí, en Dominicode Labs comparto el criterio actualizado y los agentes concretos que uso para cada tarea.


    Preguntas frecuentes

    ¿Cómo decido qué tareas delegar a un agente de IA sin supervisión?

    Con tres preguntas: cuál es el blast radius si sale mal, si es reversible, y si verificarlo cuesta más que hacerlo tú mismo. Si el daño es bajo, se puede deshacer y verificar sale barato, delega sin mirar. Si cualquiera falla, revisa antes de que salga.

    ¿Qué es el blast radius aplicado a un agente de IA?

    Es cuánto daño hace una acción del agente si sale mal, multiplicado por cuánta gente o cuánto dinero toca antes de que alguien lo note. No mide la capacidad del modelo: mide el sistema alrededor, qué tan fácil es deshacer el error y qué tan rápido te enteras.

    ¿Puedo dejar que un agente de IA haga commit o push directamente a producción?

    No sin un gate automático que verifique el resultado antes —tests, tipos, criterios de aceptación—. Sin eso, un push sin revisión es como dejarlo hacer a alguien el primer día en el puesto: puede salir bien, pero no lo sabes hasta que ya pasó.

    ¿Qué diferencia hay entre vibe coding e ingeniería agéntica, según Hacker News?

    Según el hilo que generó el post de Simon Willison, la diferencia no está en la herramienta ni en el modelo: está en si revisas y entiendes lo que el agente produjo antes de aceptarlo. Vibe coding es confiar sin mirar. Ingeniería agéntica añade la disciplina de revisar el diff y responder por él.

    ¿Delegar a un agente de IA ahorra tiempo real si después tengo que revisarlo?

    Depende de cuánto tarde la revisión frente a hacer la tarea tú mismo. Si verificar te lleva casi lo mismo que escribirlo de cero, delegar no ahorra tiempo: mueve el trabajo y añade el riesgo de confiar de más porque "las últimas veces salió bien".


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • npm install es un acto de fe: cómo auditar tus dependencias

    npm install es un acto de fe: cómo auditar tus dependencias

    El viernes pasado revisé el package-lock.json de un proyecto Next.js de un cliente. 34 dependencias directas en el package.json. Corrí npm ls --all y conté los paquetes reales instalados en node_modules.

    1.847.

    Ninguno de esos 1.813 paquetes "extra" lo instalé yo a propósito. Los trajo alguien más, en algún punto de la cadena, sin que yo lo decidiera ni lo viera venir. Y cada uno tuvo la oportunidad de ejecutar código en mi máquina en el momento exacto en que corrí npm install.

    Eso es lo que nadie te explica: auditar dependencias de un proyecto no es un extra de seguridad corporativa. Es lo único que se interpone entre tu máquina y un supply chain attack real, hoy, en el registro que uses.

    En corto: auditar dependencias de un proyecto significa revisar qué código de terceros se ejecuta cuando instalas o construyes tu app — no solo si tiene vulnerabilidades conocidas en una base de datos. Un supply chain attack en npm o pip no ataca tu código: compromete un paquete del que dependes y usa tu propio npm install o pip install como vector de entrada.

    La defensa combina lockfiles con versiones exactas, revisión de scripts de instalación y herramientas de auditoría automatizada — ninguna de las tres por separado basta.

    ¿Qué es un supply chain attack en dependencias de software?

    Un supply chain attack de dependencias es un ataque que no compromete tu aplicación directamente, sino un paquete de terceros que tu aplicación instala, para que el código malicioso llegue a producción disfrazado de una actualización legítima.

    No hace falta que un atacante encuentre un fallo en tu código. Le basta con comprometer una cuenta de npm, publicar una versión maliciosa de un paquete popular, y esperar a que miles de proyectos corran npm install esa semana.

    El vector es el propio gestor de paquetes. npm install y pip install no solo copian archivos: ejecutan código. En npm, cualquier paquete puede declarar un script postinstall en su package.json que corre automáticamente, sin preguntar, apenas termina la instalación.

    En pip, cuando instalas desde una distribución fuente (sdist) en lugar de un wheel precompilado, se ejecuta código de build arbitrario durante el propio pip install — el clásico setup.py, o el hook de un backend moderno como flit_core, hatchling o poetry-core si el paquete usa pyproject.toml.

    Dos incidentes reales que conviene conocer

    No es teórico. Esto ya pasó, más de una vez, en el ecosistema que probablemente usas hoy.

    El 23 de septiembre de 2025, CISA publicó una alerta sobre "Shai-Hulud", un gusano autorreplicante que comprometió más de 500 paquetes de npm.

    El malware escaneaba el entorno en busca de tokens de GitHub y credenciales de AWS, GCP y Azure, las exfiltraba a repositorios públicos controlados por el atacante, y usaba las cuentas de mantenedores comprometidas para inyectarse en más paquetes — sin intervención humana en cada salto. Meses después, una variante llamada "Shai-Hulud 2.0" amplió el radio de impacto a decenas de miles de repositorios de GitHub.

    En octubre de 2021, alguien secuestró la cuenta npm del mantenedor de ua-parser-js, una librería con más de 8 millones de descargas semanales, y publicó versiones que instalaban un minero de criptomonedas y un troyano que robaba contraseñas de navegadores.

    El propio mantenedor documentó el incidente en vivo en el issue de GitHub — vale la pena leerlo para ver cuánto pánico genera algo así cuando ya es tarde.

    Y aunque no es un paquete de npm ni de pip, el caso de xz-utils (marzo de 2024, CVE-2024-3094) es la referencia obligada del ecosistema open source en general.

    Un atacante bajo el alias "Jia Tan" pasó casi tres años construyendo reputación como colaborador legítimo antes de insertar un backdoor en una librería de compresión usada por millones de servidores Linux. Lo descubrió un ingeniero, Andres Freund, porque notó que SSH tardaba casi el triple de lo normal en conectar —0,8 segundos en vez de 0,3—. Nadie lo detectó por auditoría automática.

    Señales de alerta en un paquete

    No todas las dependencias merecen el mismo nivel de escrutinio. Estas son las señales que sí justifican parar y mirar más de cerca:

    Señal Por qué importa Cómo comprobarlo
    Pocas descargas semanales pero pide permisos amplios (red, sistema de archivos) Paquete de bajo perfil es más fácil de comprometer sin que nadie lo note curl https://api.npmjs.org/downloads/point/last-week/<paquete> o npmtrends.com
    Cambio reciente de mantenedor o de email de contacto Precede a la mayoría de los secuestros de cuenta documentados (caso ua-parser-js, event-stream) Historial de mantenedores en npmjs.com o PyPI
    Script postinstall que descarga binarios de una URL externa Ejecuta código fuera del propio paquete, sin pasar por revisión de npm/PyPI Leer scripts.postinstall en el package.json publicado
    Código minificado en el paquete publicado que no existe en el repo de GitHub El repo público sirve de fachada; el código real vive solo en el tarball Comparar npm pack del paquete contra el código fuente en GitHub
    Salto de versión sin changelog ni commits nuevos Publicación fuera del ciclo normal, típica de una cuenta comprometida Fecha de publicación en npm/PyPI vs. último commit en GitHub
    Nombre casi idéntico a un paquete popular (reqeusts en vez de requests) Typosquatting: apuesta a que alguien escriba mal el nombre Verificar el nombre exacto antes de instalar, no solo el autocompletado

    Lockfiles: por qué ^ y ~ no protegen a nadie

    Un lockfile (package-lock.json, pnpm-lock.yaml, poetry.lock, o requirements.txt generado con hashes) fija la versión exacta de cada dependencia, directa y transitiva, que se instaló la última vez que corriste el instalador.

    El problema está en el package.json. Si declaras una dependencia como ^4.2.1, npm puede instalar cualquier versión 4.x.x posterior sin que tú lo pidas explícitamente. Con ~4.2.1 acepta cualquier parche 4.2.x. Ambos rangos existen para facilitarte la vida — y ambos significan que una versión comprometida publicada hoy puede entrar en tu build mañana, sin que cambies una sola línea de código.

    El lockfile resuelve esto solo si lo respetas. npm install puede actualizar el lockfile si detecta que hay versiones nuevas dentro del rango permitido. npm ci, en cambio, instala exactamente lo que dice el lockfile y falla si no coincide con el package.json — es el comando correcto para CI/CD, no npm install.

    En pip, el equivalente es fijar versiones exactas en requirements.txt (requests==2.31.0, no requests>=2.31.0) y, si quieres ir un paso más allá, generar hashes con pip-compile --generate-hashes para que pip install rechace un paquete si el hash no coincide con el que fijaste. Poetry hace esto por defecto con poetry.lock.

    No es casualidad que la alerta de CISA sobre Shai-Hulud recomendara explícitamente revisar package-lock.json y fijar versiones anteriores al 16 de septiembre de 2025. El lockfile fue, literalmente, el mecanismo de contención.

    El riesgo real de los scripts de instalación

    Un script postinstall en npm corre con los mismos permisos que tu usuario del sistema. Puede leer tu .env, tus llaves SSH, tus credenciales de AWS guardadas en ~/.aws/credentials, y enviarlas a donde quiera — exactamente lo que hizo Shai-Hulud.

    Puedes desactivarlos con npm install --ignore-scripts. La contrapartida: algunos paquetes legítimos (compiladores nativos, binarios de Electron) sí necesitan su postinstall para funcionar, así que desactivarlo a ciegas en todos lados puede romper el build. Úsalo como paso de auditoría — instala con --ignore-scripts, revisa qué scripts se habrían ejecutado, y decide caso por caso.

    En pip el riesgo tiene otra forma. Si el paquete se instala desde un wheel precompilado, no se ejecuta código arbitrario: es una copia de archivos. Si se instala desde una distribución fuente (sdist), pip ejecuta setup.py como Python real durante la instalación. La mitigación más simple es forzar wheels con pip install --only-binary=:all: cuando el paquete lo permita, y mirar con lupa cualquier dependencia que solo publique sdist.

    Herramientas para auditar, sin inventar magia

    Ninguna herramienta automática sustituye la lectura humana de un postinstall sospechoso, pero sin ellas no auditas nada a escala. Estas cuatro son reales y verificables, sin funciones inventadas:

    • npm audit y pnpm audit comparan tus dependencias contra bases de datos de vulnerabilidades conocidas (CVE) y te dicen si hay una versión con parche disponible.
    • pip-audit, mantenido por la Python Packaging Authority, hace lo mismo para proyectos Python contra la base de datos OSV.
    • Socket.dev va más allá de las CVE conocidas: analiza el comportamiento del paquete — si tiene scripts de instalación, si hace llamadas de red, si accede a archivos sensibles, si el código está ofuscado — y da una puntuación de riesgo antes de que el CVE exista siquiera.
    • Snyk combina escaneo de vulnerabilidades con monitoreo continuo y se integra directamente en el flujo de PR de GitHub.

    Ninguna de estas herramientas detecta un ataque de tipo Shai-Hulud el mismo día. Todas dependen de que alguien, en algún punto, reporte el paquete malicioso primero.

    Checklist: auditar un proyecto real en menos de una hora

    1. Corre npm audit o pip-audit sobre el proyecto completo. Anota las vulnerabilidades críticas y altas — no todas, esas.
    2. Revisa el package.json o requirements.txt buscando rangos ^, ~ o >= en dependencias que no necesiten actualizarse automáticamente. Fíjalas a versión exacta.
    3. Confirma que el CI usa npm ci, no npm install. Es un cambio de una línea con impacto real.
    4. Lista los paquetes con postinstall (npm ls no lo muestra directo; revisa el package.json de cada dependencia sospechosa en node_modules, o usa Socket.dev para verlo agregado).
    5. Verifica cuándo se actualizó cada dependencia crítica por última vez y si el mantenedor cambió recientemente — la tabla de señales de arriba te dice dónde mirar.
    6. Si el proyecto usa un agente de IA para instalar dependencias (Cursor, Claude Code, Copilot en modo agéntico), revisa el diff del package.json antes de aceptar el commit. Un agente puede instalar un paquete typosquateado con la misma confianza que uno legítimo si el nombre es parecido.

    Ese último punto es donde converge el problema técnico con el flujo de trabajo actual. Si dejas que un agente proponga e instale dependencias sin revisión, necesitas el mismo nivel de disciplina que aplicarías a un PR de un desarrollador que no conoces — es literalmente la lógica que enseño en el ebook gratuito "Revisión por Contrato": no confías en el resultado porque "suena bien", confías en él porque pasó un contrato de revisión explícito.

    Es también una cuestión de coste: revisar el diff de un package.json antes de aceptarlo cuesta minutos; limpiar un supply chain attack ya en producción cuesta muchísimo más. Desarrollo esa cuenta —cuándo compensa verificar y cuándo no— en El futuro de los agentic systems: coste por tarea, no benchmarks.

    El caso específico de dependencias de IA

    Todo lo anterior aplica a cualquier paquete, pero los modelos de IA tienen una superficie de riesgo propia: from_pretrained(), load_dataset() y hf_hub_download() descargan pesos y datasets de gigabytes desde un hub externo, muchas veces sin pasar por tu lockfile ni por npm audit.

    Ya escribí sobre ese caso específico — qué cambia con Hugging Face bajo NVIDIA, por qué fijar un commit SHA no es opcional cuando hablamos de modelos, y el checklist de 5 pasos para auditarlo — en NVIDIA compra Hugging Face: tu from_pretrained() es el riesgo. Si tu proyecto carga modelos en runtime, léelo después de este.

    Qué esta auditoría NO cubre

    Esta auditoría tiene tres límites reales, y ser honesto aquí importa más que sonar completo.

    npm audit y pip-audit solo detectan vulnerabilidades ya reportadas. Un paquete recién comprometido, como Shai-Hulud el primer día, no aparece en ninguna base de datos hasta que alguien lo reporta — y eso puede tardar horas o días, tiempo suficiente para que el código malicioso corra en cientos de builds.

    El código ofuscado bien hecho pasa controles automáticos. Herramientas como Socket.dev mejoran mucho la detección de comportamiento sospechoso, pero un atacante paciente (como demostró el caso xz-utils) puede esconder la parte maliciosa en binarios de test que ni siquiera están en el repositorio público, y ningún escáner estático la va a encontrar si no sabe qué buscar.

    Y esta auditoría no resuelve el problema humano de fondo: alguien tiene que revisar los resultados. Un npm audit limpio en un dashboard que nadie mira no protege nada.

    Qué hacer hoy con esto

    No necesitas auditar los 1.847 paquetes de tu node_modules esta semana. Necesitas tres cosas, en este orden: fija las versiones de tus dependencias directas, cambia npm install por npm ci en tu pipeline de CI, y corre npm audit o pip-audit una vez antes de tu próximo deploy.

    Eso te cubre contra el 80% de los incidentes reales — el resto es disciplina continua, no una tarea de una sola vez. Si construyes con agentes de IA y quieres que esa disciplina esté integrada en tu flujo desde el primer prompt, es exactamente lo que trabajamos en el curso Construye con IA: de la idea al producto con Claude Code.

    Y si quieres seguir esta conversación con otros developers que ya están aplicando esto en producción, en Dominicode Labs compartimos los checklists y las decisiones reales de auditoría, no solo la teoría.

    Preguntas frecuentes

    ¿Qué es un supply chain attack en el contexto de npm o pip?

    Es un ataque que compromete un paquete del que depende tu proyecto — no tu código directamente — para que el código malicioso llegue a tu máquina o a producción disfrazado de una instalación o actualización legítima. El vector de entrada es tu propio npm install o pip install.

    ¿Un lockfile me protege completamente contra estos ataques?

    No completamente, pero reduce mucho el riesgo. Un lockfile fija versiones exactas y evita que una actualización automática dentro de un rango ^ o ~ traiga una versión comprometida sin que tú lo notes. No te protege si tú mismo actualizas el lockfile aceptando la versión maliciosa, ni si la dependencia ya estaba comprometida antes de que la instalaras por primera vez.

    ¿Es seguro desactivar todos los scripts postinstall con –ignore-scripts?

    Reduce el riesgo de ejecución de código no revisado, pero puede romper paquetes legítimos que necesitan compilar binarios nativos o descargar assets en la instalación. Úsalo primero como herramienta de auditoría para ver qué scripts se ejecutarían, y decide caso por caso antes de dejarlo activo en producción de forma permanente.

    ¿npm audit o pip-audit son suficientes para considerar un proyecto auditado?

    No. Ambos solo detectan vulnerabilidades ya reportadas en una base de datos pública. Un paquete recién comprometido, sin CVE asignado todavía, pasa desapercibido para estas herramientas. Complementan una auditoría real; no la sustituyen.

    ¿Cómo audito las dependencias de modelos de IA como Hugging Face de forma distinta?

    Los modelos se descargan en runtime con funciones como from_pretrained(), muchas veces fuera del alcance de tu lockfile y de npm audit. El enfoque es distinto: fijar el commit SHA del modelo, activar modo offline para detectar descargas implícitas, y espejar localmente lo crítico. Lo cubro con checklist completo en el post sobre NVIDIA y Hugging Face.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Mejores herramientas de code review con IA 2026: precios y límites

    Mejores herramientas de code review con IA 2026: precios y límites

    En marzo de 2026 revisé una pull request de 340 líneas en un proyecto de un cliente. El bot de code review había dejado 23 comentarios. Los leí todos. Todos eran correctos.

    Mergeamos. Dos días después, producción se cayó por esa PR.

    El bot revisó el diff perfectamente. Lo que no hizo —lo que ninguna de las herramientas de code review con IA que he probado desde entonces hizo— fue preguntarse si ese endpoint debía existir.

    Nadie había escrito qué significaba "hecho" para esa tarea. El bot revisó el código contra la nada, y la nada siempre aprueba.

    En corto: las mejores herramientas de code review con IA en 2026 son CodeRabbit (24 $/dev/mes anual, la más pulida en PRs de GitHub), Greptile (30 $/asiento/mes, contexto de todo el repo y pago por créditos) y Claude Code con /review (incluida en cualquier plan de pago desde 17 $/mes con facturación anual, la que más control te da sobre el criterio: revisa contra tus reglas, no contra las suyas).

    GitHub Copilot code review (desde 10 $/usuario/mes) y Cursor BugBot son las opciones por defecto si ya pagas esas plataformas. Ninguna de las cinco decide contra qué se revisa tu código: eso lo defines tú antes, o no lo define nadie.

    ¿Qué es una herramienta de code review con IA?

    Una herramienta de code review con IA es un sistema que lee automáticamente el diff de una pull request, lo analiza con un modelo de lenguaje y publica comentarios sobre bugs, seguridad y calidad antes de que un humano lo mire.

    Esa definición es más importante de lo que parece por una palabra: diff. Todas estas herramientas parten del cambio, no del objetivo. Saben qué cambiaste. No saben qué querías conseguir.

    Comparativa de herramientas de code review con IA: 5 opciones y el baseline humano

    Precios consultados el 19 de septiembre de 2026 en las páginas oficiales de cada producto. Cambian a menudo — verifica antes de meter la tarjeta. La última fila no es una herramienta: es el coste del revisor humano, para que compares contra algo y no contra cero. Las licencias van en dólares porque así las publican los fabricantes; el coste humano va en euros por ser el mercado de referencia.

    Herramienta Precio Qué detecta bien Qué NO cubre Cuándo compensa
    CodeRabbit Essentials 24 $/dev/mes (anual) · 30 $/dev/mes (mensual). Team 48 $/dev/mes · Advanced 72 $/dev/mes. Gratis en repos open source públicos Bugs concretos en el diff, resúmenes de PR, linters y SAST integrados, 1-click fixes Si la feature era necesaria; decisiones de arquitectura que cruzan varios repos en el plan base (Essentials analiza 1 repo; Team, hasta 5) Equipos de 3-15 devs con muchas PRs pequeñas en GitHub
    Greptile Gratis (50 créditos/mes, 1 dev activo) · Pro 30 $/asiento/mes con 50 créditos, 1 $ por crédito extra Bugs reales con contexto de todo el repo; 1 review = 1 crédito, review TREX = 3 Su propia tasa de falsos positivos: no la publica ni ella ni ninguna competidora. Y no sustituye el juicio de producto Monorepos grandes donde el bug vive lejos del diff
    GitHub Copilot code review Pro 10 $/usuario/mes · Pro+ 39 $ · Max 100 $. El plan Free no lo incluye. Consume créditos de IA de GitHub Lo básico y estándar, dentro de github.com sin instalar nada No lee tus respuestas a sus propios comentarios y puede repetir comentarios que ya descartaste (doc oficial) Ya pagas Copilot y quieres una red de seguridad sin añadir otra factura — consume tus créditos de IA
    Cursor BugBot Incluido en Pro 20 $/mes, Pro+ 60 $, Ultra 200 $ (−20 % anual), con facturación por uso. Cursor no publica tarifa por revisión: consultar pricing oficial Bugs y problemas de seguridad en el diff, autofix vía Cloud Agent, reglas personalizadas Por defecto solo mira el código cambiado desde la revisión anterior; autofix limitado a 3 intentos por PR y sin "crear rama" en GitLab, Bitbucket y Azure DevOps Tu equipo ya vive dentro de Cursor y no quiere otra factura
    Claude Code (/review) Incluido en todo plan de pago: Pro 17 $/mes (anual) o 20 $/mes · Max desde 100 $/mes · Team 20 $/asiento (anual) Lo que tú le digas: corre como subagente contra tus reglas, tu AGENTS.md y tu spec Lo que no le digas que mire: sin contrato escrito revisa según su criterio, igual que las demás Ya tienes specs o reglas escritas y quieres revisar contra ellas, no contra el gusto del modelo
    Review manual humano 0 € de licencia. Ejemplo orientativo: 4 h/semana × 4 semanas = 16 h/mes; a 40 €/h son 640 €/mes por revisor Intención, producto, contexto de negocio, deuda técnica que importa Errores mecánicos cuando la PR es larga: la atención humana se degrada con la longitud del diff Siempre, encima de cualquier herramienta. No es una alternativa, es la capa que decide

    Análisis de cada herramienta de code review con IA: a favor y en contra

    Estas son las cinco herramientas de la tabla en detalle, con el argumento a favor y la objeción real de cada una. No es un ranking: el orden es el mismo de la tabla.

    CodeRabbit

    A favor: es la más pulida de las cinco en el flujo de GitHub. Los resúmenes de PR son útiles de verdad, la integración con linters y SAST evita duplicar herramientas, y el plan gratis para repos open source públicos no tiene truco.

    En contra: el precio escala rápido. El análisis multi-repo está en Team (48 $/dev/mes anual): Essentials analiza un repo, Team hasta cinco. Para un equipo de ocho devs son 4.608 $/año. Y sigue siendo una opinión sobre el diff.

    Greptile

    A favor: el contexto de repositorio completo es su gran ventaja. Encuentra el bug que está en el archivo que no tocaste. El modelo de créditos (50 incluidos por asiento, 1 $ el extra) es honesto para equipos que no revisan 500 PRs al mes.

    En contra: no hay dato público de falsos positivos, ni suyo ni de la competencia, así que el ruido lo vas a medir tú. Si tu equipo ya ignora al bot, ninguna herramienta arregla eso. El descuento del 50 % para startups pre-Series A ayuda, pero no arregla el ruido.

    GitHub Copilot code review

    A favor: cero fricción. Si ya pagas Copilot Pro (10 $/usuario/mes), lo activas y ya está. Vive dentro de github.com y no añade otro proveedor a tu superficie de seguridad.

    En contra: es la menos profunda del grupo, y la documentación oficial lo admite sin rodeos: no ve tus respuestas a sus comentarios y puede repetir los que ya descartaste. Además, ahora consume los créditos de IA de tu plan, así que no es tan "gratis" como parece.

    Cursor BugBot

    A favor: si tu equipo escribe en Cursor, BugBot cierra el círculo sin cambiar de contexto. Las reglas personalizadas por equipo, repo y proyecto son potentes.

    En contra: el precio es opaco. "Facturación por uso" sin tarifa pública es lo contrario de lo que necesitas para presupuestar. Y aquí aparece el problema de fondo que señaló Daksh Gupta, CEO de Greptile —parte interesada, conviene decirlo—: cuando el que escribe el código y el que lo revisa comparten modelo, harness y prompts, "fallan de formas parecidas". Cursor revisando código de Cursor es el juez y la parte.

    Claude Code (/review + revisión agéntica)

    A favor: CodeRabbit, Greptile Pro y BugBot te dejan añadir reglas encima de su criterio; aquí no hay criterio de fábrica que corregir. /review es una skill incluida —alias de /code-review— que corre en un subagente forkeado desde la v2.1.218, y puedes apuntarla a tus specs, tus reglas y tu definición de "hecho". Sin factura nueva si ya pagas Claude, aunque cada review consume los límites de uso de tu plan. Lo desarrollo en detalle en agentic code review con Claude Code.

    En contra: no es un producto de code review, es un motor. No hay dashboard, no hay métricas de equipo, no hay onboarding para el junior. Y si no le das contra qué revisar, te devuelve opiniones genéricas igual que los demás.

    Review manual humano

    A favor: es el único revisor que sabe por qué existe la feature.

    En contra: no escala con la velocidad a la que los agentes generan código. Ese es exactamente el cuello de botella de verificar código de IA: generar es gratis, verificar no.

    El hilo que conviene leer antes de pagar

    En enero de 2026, el propio CEO de Greptile publicó un artículo titulado "There is an AI code review bubble". Llegó a portada de Hacker News con 351 puntos y 249 comentarios a 19 de septiembre de 2026, y la discusión es más valiosa que cualquier tabla comparativa, incluida la mía.

    El comentario más votado, de trjordan, lo dice sin anestesia (traduzco del inglés, igual que el resto de citas de este hilo):

    "Si has llegado al punto de depender de un code review con IA para cazar bugs, has perdido el hilo. El propósito de una PR es compartir conocimiento y detectar huecos estructurales."

    Otro usuario, candiddevmike, sostiene en su opinión que ninguna de estas herramientas aporta un review significativo más allá de lo que encontraría un linter. Y cuando Gupta defendió Greptile citando que los autores de PRs habían respondido "great catch" 9.078 veces en siete días, tadfisher le contestó lo único que había que contestar: "una cifra así es un dato, no una evidencia". Sin el denominador —cuántos comentarios publicó Greptile esos siete días— 9.078 no se puede interpretar.

    Ese hilo no dice que las herramientas sean inútiles. Dice que estamos midiendo lo que no toca.

    Lo que ninguna de estas herramientas compra: el contrato

    Todas estas herramientas revisan el código. Ninguna decide contra qué se revisa.

    Un bot que comenta el diff sigue siendo una opinión sobre el diff. Una opinión rápida, barata y a menudo acertada — pero una opinión. Lo que falta antes es el contrato: qué tiene que cumplir ese código para considerarse terminado, qué casos límite son obligatorios, qué se rompe si cambia esta firma, qué comportamiento está garantizado a quien consume esto.

    Si ese contrato no está escrito, el bot inventa uno por ti. Y el contrato que inventa un modelo es el promedio de GitHub, no el de tu producto.

    Por eso comprar la herramienta no resuelve el problema. Resuelve la mitad mecánica y deja intacta la mitad que causa los incidentes. La secuencia correcta es la inversa: primero defines el contrato, luego eliges quién lo verifica — y entonces cualquiera de estas cinco herramientas se vuelve mucho más útil, porque le estás dando un criterio en vez de pedirle que adivine el tuyo. Ese es el método que explico en revisión por contrato para código de agentes, y el punto de partida está en el ebook gratuito Revisión por Contrato (30 páginas, sin coste).

    Cuándo NO necesitas ninguna de estas herramientas

    Como regla de pulgar, si haces menos de unas 20 PRs al mes. A ese volumen, 30 $/dev/mes por un revisor automático es peor inversión que dedicar dos horas a escribir la definición de "hecho" de tu equipo. El coste fijo de la herramienta no se amortiza y el ruido sí se acumula.

    Si tu equipo ya ignora los comentarios del bot. Esto pasa más de lo que se admite. Cuando la mayoría de los comentarios son nits de estilo, el equipo aprende a hacer scroll y el hallazgo bueno se pierde con el resto. Añadir una segunda herramienta empeora el problema. Mide cuántos hallazgos se resuelven antes del merge, no cuántos comentarios se publican.

    Si no tienes tests ni CI. Un bot de review encima de un pipeline inexistente es teatro. Primero el harness que rompe el build, después el revisor que opina. Integrar las revisiones de IA en el pipeline de CI/CD importa más que elegir marca.

    Si el problema real es que nadie sabe qué estáis construyendo. Ninguna herramienta de esta tabla arregla una spec inexistente. Ese es un problema de método, y lo trato entero en el libro de Spec-Driven Development.

    Qué hacer hoy

    Elige por contexto, no por ranking: si vives en GitHub, CodeRabbit; si tu bug vive lejos del diff, Greptile; si ya pagas Cursor o Copilot, usa lo que tienes; si quieres revisar contra tus propias reglas, Claude Code.

    Pero antes de pagar nada, haz esto: abre la última PR que rompió algo en producción y escribe en tres líneas qué contrato debería haber cumplido ese código. Si no puedes escribirlo, ninguna herramienta de esta tabla te habría salvado.

    Eso es justo lo que trabajamos en el workshop Contract Based Review Method: tres horas en nueve módulos para ir de un GitHub Issue a una pull request verificada, con el contrato escrito antes de que ningún bot opine. Sale el 2 de octubre de 2026, bajo demanda.

    Preguntas frecuentes

    ¿Cuál es la mejor herramienta de code review con IA en 2026?

    No hay una mejor en absoluto, hay una mejor por contexto. CodeRabbit es la opción más sólida para equipos en GitHub con muchas PRs pequeñas (24 $/dev/mes anual). Greptile gana en monorepos grandes donde el bug está fuera del diff (30 $/asiento/mes). Claude Code es la más flexible si ya tienes reglas o specs escritas, porque revisas contra tu criterio y no contra el del modelo.

    ¿Merece la pena pagar CodeRabbit o Greptile si ya tengo GitHub Copilot?

    Solo si tu problema es la profundidad del review. Copilot code review viene incluido desde el plan Pro (10 $/usuario/mes) y cubre lo básico, pero la documentación oficial reconoce que no lee tus respuestas a sus comentarios y que puede repetir los descartados. Si eso te frustra a diario, la herramienta especializada se paga sola. Si no, estás pagando dos veces por lo mismo.

    ¿Puede un code review con IA sustituir al revisor humano?

    No, y las cifras del sector no dicen lo contrario: las más citadas las publica quien vende la herramienta. La IA es buena cazando errores mecánicos en el diff; el humano es el único que sabe si la feature debía existir. El reparto sensato es: la IA revisa lo mecánico, el humano revisa la intención y la arquitectura.

    ¿Cuánto cuesta realmente añadir code review con IA a un equipo de 8 developers?

    Con CodeRabbit Essentials anual son 24 $ × 8 = 192 $/mes, unos 2.304 $/año. Con Greptile Pro, 30 $ × 8 = 240 $/mes, más los créditos extra si pasáis de 50 revisiones por asiento. Con Claude Code no hay factura nueva si el equipo ya tiene plan de pago, pero cada review consume los límites de uso del plan. Compáralo siempre con el coste del tiempo humano que esperas ahorrar, no con cero.

    ¿Qué es la revisión por contrato y en qué se diferencia de usar un bot?

    La revisión por contrato consiste en escribir, antes de generar el código, qué tiene que cumplir para darse por terminado: comportamiento garantizado, casos límite obligatorios y qué se rompe si cambia. El bot revisa el diff contra su criterio; la revisión por contrato revisa el diff contra el tuyo. Son complementarias, pero el orden importa: sin contrato, el bot inventa uno.

    ¿Existe alguna herramienta de code review con IA gratuita?

    Sí, con límites. CodeRabbit es gratis de forma permanente en repositorios open source públicos. Greptile tiene un plan Starter gratuito con 50 créditos al mes para un developer activo, y acceso libre para proyectos con licencia MIT o Apache. GitHub Copilot en su plan Free no incluye code review — ahí hay que pasar a Pro.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.