# Métricas de dificultad para la IA programando contra la API de Revit

**Pregunta:** ¿qué tan capaz es un modelo de IA (empezando por Claude Opus 5.5 en Claude Code) de programar contra la API .NET de Revit 2027, y por qué en el pasado esto fallaba tanto en comparación con tareas web de dificultad parecida?

Este documento acompaña al [plan de pruebas](checklist-206-pruebas.md). Cada prueba del checklist que requiera escribir código es también una **tarea de IA** que se mide.

---

## 1. Hipótesis de por qué la IA falla con Revit

Se formulan antes de medir para poder confirmarlas o descartarlas con datos:

| # | Hipótesis | Cómo se detecta en los datos |
|---|---|---|
| H1 | **No hay bucle de retroalimentación.** En web la IA ejecuta y ve el resultado; en Revit escribía a ciegas y dependía de que un humano probara | Comparar tareas con runner y sin runner (condición C2) |
| H2 | **API inventada (alucinación).** Hay menos código de Revit en los datos de entrenamiento que de React o Three.js | Errores de compilación `CS0117`/`CS1061`/`CS0246` cuyo símbolo **no existe** en el inventario `api-surface-2027.tsv` |
| H3 | **Deriva de versiones.** El código de ejemplo es de Revit 2015–2024: `ElementId(int)`, `IntegerValue`, `DisplayUnitType`, .NET Framework | Símbolos que existieron en versiones anteriores pero no en 2027 |
| H4 | **Reglas propias de Revit.** Transacciones, contexto de API válido, regeneración, hilo único | Excepciones en tiempo de ejecución de tipo `ModificationOutsideTransaction`, `InvalidOperation` o contexto |
| H5 | **Unidades y geometría.** Unidades internas en **pies**, referencias para cotas, orientación de caras | Tests que compilan y corren pero cuya aserción falla por factor 0,3048 o por referencias inválidas |
| H6 | **Fallos silenciosos.** El código "funciona" pero hace otra cosa | Aserción automática o verificación visual fallida tras una ejecución sin excepciones |
| H7 | **La interfaz de Revit interfiere** (añadida el 2026-10-18 tras el lote EXP): diálogos modales, transacciones internas y crashes que no dependen del código | Intervenciones humanas por diálogos o crashes y tiempo perdido en esperas |

## 2. Qué se mide por cada tarea

Una **tarea** es implementar y dejar en verde un test del checklist (por ejemplo `ELM-02`) o un componente (por ejemplo `RevitLab.Runner`).

| Campo | Qué es | Cómo se captura | Objetivo o autodeclarado |
|---|---|---|---|
| `id` | ID del checklist o del componente | Al iniciar la tarea | — |
| `modelo` | Modelo exacto, por ejemplo `claude-opus-5-5` | Historial JSONL de la sesión | **Objetivo** |
| `esfuerzo` | Nivel de razonamiento, si se conoce | Configuración de la sesión | Autodeclarado |
| `dificultad_prevista` | 1–5, asignada **antes** de empezar | La anota Claude al iniciar | Autodeclarado (fijado a priori) |
| `tokens_entrada`, `tokens_cache_escritura`, `tokens_cache_lectura`, `tokens_salida` | Tokens consumidos entre el inicio y el fin de la tarea, subagentes incluidos | Suma de `usage` en el JSONL de `~/.claude/projects/<proyecto>/` entre dos marcas de tiempo | **Objetivo** |
| `turnos_ia` | Número de respuestas del modelo durante la tarea | JSONL | **Objetivo** |
| `compilaciones` | Intentos de `dotnet build` hasta compilar | Script `lab build <id>`, que registra cada intento | **Objetivo** |
| `errores_compilacion` | Lista de códigos `CSxxxx` y símbolos implicados, por intento | Salida de `dotnet build` analizada por el script | **Objetivo** |
| `ejecuciones` | Ejecuciones en Revit hasta el resultado correcto | Registro del runner | **Objetivo** |
| `excepciones` | Tipo y mensaje de cada excepción en Revit | Registro del runner y journal | **Objetivo** |
| `intervenciones_humanas` | Veces que hizo falta una persona: abrir Revit, cerrar un diálogo, dar una pista, corregir a mano | `lab intervencion <id> "<motivo>"` | Semiobjetivo (lo registra Claude y tú puedes añadir) |
| `consultas_referencia` | Búsquedas en el inventario de la API o en la documentación | Llamadas a herramientas en el JSONL | **Objetivo** |
| `tiempo_reloj` | Minutos de principio a fin | Marcas de tiempo | **Objetivo** |
| `resultado` | `primer_intento` · `resuelto` (N iteraciones) · `resuelto_con_ayuda` · `no_resuelto` · `imposible_api` | Al cerrar la tarea | Verificado por el test, no por la IA |
| `causa_fallos` | Clasificación de cada fallo según la taxonomía (sección 3) | Automática para compilación; manual para el resto | Mixto |

**Regla de corte:** una tarea se declara `no_resuelto` si llega a **10 iteraciones** (compilación + ejecución) o **3 intervenciones humanas** sin quedar en verde. Así se evita que el costo crezca sin límite y se mide la capacidad real, no la persistencia.

**Distinción importante:** `imposible_api` significa que la API no lo permite (un hallazgo sobre Revit). `no_resuelto` significa que la API sí lo permite pero la IA no lo logró (un hallazgo sobre la IA). Sin esta separación las dos métricas se contaminan.

## 3. Taxonomía de fallos

| Código | Categoría | Ejemplo | Detección |
|---|---|---|---|
| **A1** | API inexistente (alucinación) | `wall.SetHeight(3)` | `CS0117`/`CS1061` y símbolo ausente del inventario → **automática** |
| **A2** | Firma o sobrecarga incorrecta | Argumentos en otro orden, tipo equivocado | `CS1501`/`CS1503`/`CS7036` → automática |
| **A3** | API de una versión anterior (deriva) | `new ElementId(123)` con `int`, `.IntegerValue` | Símbolo ausente en 2027 pero conocido en versiones viejas → semiautomática |
| **B1** | Error general de C#/.NET | Nulos, LINQ, tipos genéricos | Otros `CSxxxx` |
| **C1** | Regla de Revit: transacciones o contexto | Modificar sin `Transaction`, API desde otro hilo | Tipo de excepción en tiempo de ejecución |
| **C2** | Unidades | Metros pasados como pies | Aserción numérica con factor 0,3048 o 3,28 |
| **C3** | Geometría y referencias | Cota sin referencias válidas, cara equivocada | Excepción de argumento o aserción geométrica |
| **C4** | Regeneración o estado | Leer geometría antes de `Regenerate()` | Valores obsoletos en la aserción |
| **C5** | Rareza de la API (comportamiento no deducible de la firma) | Un parámetro `out` de `NewFootPrintRoof` que debe venir inicializado (`ArgumentNullException`) | Excepción en tiempo de ejecución cuya traza apunta a un método de la API con argumentos correctos |
| **D1** | Entorno: carga, manifiesto, dependencias | `.addin` mal formado, conflicto de DLL | Journal y el add-in no carga |
| **E1** | Fallo silencioso | Corre sin errores pero el resultado no es el pedido | Aserción o verificación visual fallida |

## 4. Condiciones experimentales

Para explicar por qué antes fallaba y ahora puede que no, algunas tareas se repiten en condiciones distintas:

| Condición | Retroalimentación | Referencia de la API | Para qué |
|---|---|---|---|
| **C0 (por defecto)** | Runner: la IA compila, ejecuta y ve el resultado | Inventario `api-surface-2027.tsv` disponible | Capacidad actual en las mejores condiciones |
| **C1 sin referencia** | Runner | La IA tiene **prohibido** consultar el inventario o la documentación | Mide H2 y H3 (cuánto ayuda la referencia) |
| **C2 a ciegas** | Solo compilación; tú pruebas en Revit y describes el resultado | Inventario | Reproduce cómo se trabajaba antes; mide H1 |
| **C3 a ciegas sin referencia** | Solo compilación | Sin referencia | El peor caso, el más parecido a tu experiencia pasada |

C1–C3 se aplican solo al **conjunto de referencia** (sección 5), no a las ~170 pruebas.

## 5. Conjunto de referencia (benchmark)

Doce tareas fijas, de dificultad creciente, con un enunciado escrito y congelado. Sirven para comparar condiciones y **modelos** (Opus, Sonnet, Haiku, Fable, u otras herramientas como Cursor o ChatGPT si quieres compararlas):

| # | ID | Tarea | Dificultad prevista |
|---|---|---|---|
| 1 | ENT-01 | Add-in mínimo que carga y muestra un `TaskDialog` | 1 |
| 2 | ELM-01 | Muro recto con aserción de longitud | 1 |
| 3 | PAR-01 | Leer y escribir parámetros de instancia y tipo | 2 |
| 4 | VIS-08 | Plano con cajetín y 4 viewports alineados | 2 |
| 5 | ELM-06 | Puerta alojada en un muro a una distancia dada | 3 |
| 6 | VIS-10 | Tabla con campos, filtro, ordenación y agrupación | 3 |
| 7 | CTX-04 | Ventana WPF no modal que modifica el modelo con `ExternalEvent` | 3 |
| 8 | EVT-03 | `IUpdater` que valida un parámetro al cambiar | 4 |
| 9 | ANO-02 | Cotas automáticas entre caras de muros paralelos | 4 |
| 10 | FAM-02 | Familia nueva con extrusión paramétrica y tipos | 4 |
| 11 | ELM-09 | Escalera por componentes con barandilla | 5 |
| 12 | DEV-03 | Cargador de hot reload con `AssemblyLoadContext` | 5 |

**Contaminación:** si un modelo repite una tarea en la misma sesión, ya ha visto la solución. Por eso cada repetición se hace en una **sesión nueva de Claude Code**, con el enunciado congelado y en una rama de git sin la solución (`git switch bench/<id>-base`). Las soluciones de referencia se guardan aparte y la IA no las ve.

## 6. Instrumentación (se construye junto con el runner)

- `tools/lab.ps1`, un script único que la IA usa en lugar de llamar directamente a `dotnet` o al runner:
  - `lab inicio <id> -dificultad <n> -condicion C0`: marca el inicio (timestamp y modelo).
  - `lab build <id>`: ejecuta `dotnet build`, registra el intento y los códigos de error, y busca cada símbolo faltante en el inventario para clasificarlo como A1 o A3.
  - `lab run <id>`: envía el test al runner, registra el estado, la excepción, el tiempo y la memoria.
  - `lab intervencion <id> "<motivo>"`.
  - `lab fin <id> <resultado>`: suma los tokens del JSONL entre el inicio y el fin (subagentes incluidos) y escribe el registro final.
- Salida: `pruebas/metricas/tareas.jsonl` (una línea por tarea) e `intentos.jsonl` (una línea por compilación o ejecución).
- `lab informe`: genera la tabla resumen de la sección 7.

**Límites de la medición, para leerla con honestidad:**
- La IA se mide a sí misma. Lo mitiga que compilaciones, ejecuciones y tokens se registran **automáticamente** por script, y que "resuelto" lo decide el test, no la IA.
- Los tokens de caché dominan el total (en esta sesión: unos 16 M leídos frente a 0,33 M generados). Se reportan por separado porque cuestan distinto.
- La dificultad prevista es subjetiva, pero se fija antes de empezar para poder compararla con la real.
- Comparar con otras herramientas (Cursor, ChatGPT) solo es justo con el mismo enunciado congelado y las mismas condiciones C0–C3.

## 7. Resumen (se rellena con `lab informe`)

| ID | Modelo | Condición | Dif. prevista | Compilaciones | Ejecuciones | Interv. | Tokens salida | Tokens totales | Resultado | Causas |
|---|---|---|---|---|---|---|---|---|---|---|
| RUNNER | claude-opus-5-5 | C0 | 4 | 2 (0 fallidas) | 1 | 1 | 24.864 | 4.275.813 | primer_intento | ninguna |
| LOTE-TRX (8 pruebas) | claude-opus-5-5 | C0 | 2 | 3 (0) | 3 (1 fallida) | 1 | 6.344 | 923.323 | resuelto | E1 |
| LOTE-CTX (6 pruebas) | claude-opus-5-5 | C0 | 3 | 1 (0) | 5 (0) | 0 | 5.934 | 963.664 | primer_intento | ninguna |
| LOTE-ELM (21 pruebas) | claude-opus-5-5 | C0 | 3 | 3 (0) | 3 (1 fallida) | 0 | 16.742 | 2.799.072 | resuelto | C5 |
| LOTE-PARVIS (23 pruebas) | claude-opus-5-5 | C0 | 3 | 2 (0) | 2 (1 fallida) | 0 | 18.138 | 3.127.911 | resuelto | E1 (autocorregido), C5 |

| LOTE-FAM (11 pruebas) | claude-opus-5-5 | C0 | 4 | 4 (0) | 4 (2 fallidas) | 0 | 21.695 | 3.486.472 | resuelto | C5, C1 |

| LOTE-EXP (13 pruebas) | claude-opus-5-5 | C0 | 3 | 7 (0) | 5+ (2 crashes) | 5 | 28.114 | 15.730.107 | resuelto | C1, B1, C5 + diagnóstico corregido |

| LOTE-GEOEVT (13 pruebas) | claude-opus-5-5 | C0 | 3 | 4 (1 fallida) | 4 (2 fallidas) | 0 | 16.423 | 3.681.328 | resuelto | A1, E1 ×2 |

| BENCH-ANO02 (#9) | claude-opus-5-5 | C0 | 4 | 1 (0) | 1 (0) | 0 | 2.310 | — | primer_intento | (V: textos superpuestos) |
| BENCH-ELM09 (#11) | claude-opus-5-5 | C0 | 5 | 3 (0) | 3 (2 fallidas) | 0 | 5.747 | — | resuelto | C5 ×2 |
| LOTE-ANOELM (14 pruebas) | claude-opus-5-5 | C0 | 3 | 4 (0) | 4 (3 fallidas) | 0 | 20.740 | 8.225.219 | resuelto | A2 (semántica), E1 |

| LOTE-DATWSHDIS (18 pruebas) | claude-opus-5-5 | C0 | 3 | 2 (0) | 3 (2 fallidas) | 0 | 16.273 | 5.570.207 | resuelto | D1, C5 |

| LOTE-LIMCTX (11 pruebas) | claude-opus-5-5 | C0 | 3 | 3 (0) | 8 (1 fallida) | 0 | 13.369 | 7.106.983 | resuelto | C1, B1 |

| LOTE-SUELTAS (4 pruebas) | claude-opus-5-5 | C0 | 3 | 3 (1) | 3 (0 fallidas) | 0 | 17.777 | 1.926.422 | resuelto | B1, C5 |

| RUNNER-2 (pipe + `[LabShutdown]`, 6 variantes de DEV-07) | claude-opus-5-5 | C0 | 3 | 4 (0) | 8 (0 fallidas) | 0 | 12.113 | 2.680.906 | resuelto | B1 (autocorregido) |

| LOTE-INT1 (sesión interactiva, bloque 1: 11 pruebas) | claude-opus-5-5 | C0 | 3 | 7 (1) | 19 (2 fallidas) | 0 | 24.326 | 10.488.479 | resuelto | A2, C5, E1 |

| LOTE-INT2 (sesión interactiva, bloque 2: 9 pruebas) | claude-opus-5-5 | C0 | 4 | 8 (2) | 11 (0 fallidas) | 0 | 25.445 | 9.002.728 | resuelto | B1 |

| LOTE-TRX08 (FailureDefinition propio; cambio de runner y contratos) | claude-opus-5-5 | C0 | 3 | 4 (0) | 4 (1 fallida) | 0 | — | 3.085.634 | resuelto | E1 |

| LOTE-ENT12 (Nice3point.TUnit.Revit) | claude-opus-5-5 | C0 | 3 | 1 (0) | 1 (0) | 0 | — | 3.723.275 | detenida | — |

Notas sobre ENT12 y TRX08 (2026-10-28): ENT-12 se detuvo **a propósito**: la librería ejecuta la API de Revit dentro del proceso de pruebas sin cargar los módulos de licencias de Autodesk, y por coherencia con SHL-02 no se siguió. La IA lo detectó comparando los módulos cargados con los de un Revit.exe normal, no leyendo la documentación (que no lo dice). TRX-08: un E1 propio (leer `.Id` de un elemento recién borrado) y una etiqueta de resultado engañosa en el control, ambos corregidos en una iteración.

| LOTE-ENT13 (Revit + journal) | claude-opus-5-5 | C0 | 3 | 1 manual (0) | 1 (0) | 0 | — | 9.578.766 | primer_intento | — |

| LOTE-CTX12 (dos instancias; cambio de runner y CLI) | claude-opus-5-5 | C0 | 3 | 2 (0) | 7 (1 fallida) | 0 | — | 6.610.004 | resuelto | diseño de prueba |

Notas sobre ENT13 y CTX12 (2026-10-28): el journal escrito a mano funcionó **a la primera** porque cada línea se copió de journals reales de la instalación (abrir archivo, salir, y el formato `Execute external command:<ClientId>:<clase>` de un comando por manifiesto, sacado de la ejecución del Add-In Manager). Generalización: con Revit, **el journal es la documentación más fiable** de cómo hace Revit cada cosa. CTX-12: un fallo de diseño de prueba propio (registro compartido entre dos procesos).

| LOTE-CRASH (variantes del crash de EXP-10, EXP-13, DEV-11; 5 crashes y reinicios automáticos) | claude-opus-5-5 | C0 | 3 | 4 (0) | 13 (1 fallida) | 0 | 24.661 | 13.990.223 | resuelto | B1 |

Notas sobre CRASH (2026-10-28): **método experimental** por variantes, con un factor cada vez y cada ciclo anotado en disco (el archivo sobrevive al crash). La primera réplica "mejorada" NO reproducía el crash (120 ciclos), y eso obligó a comparar con la prueba original en vez de dar el crash por resuelto: la diferencia era la carpeta de destino. **Honestidad con la causa:** la hipótesis "es Visual Studio" se refutó en parte con el siguiente experimento (sin VS también cae, más tarde), y una prueba causal sintética no fue concluyente; se registró como hipótesis, no como hallazgo. **Permisos:** el clasificador bloqueó `powershell -ExecutionPolicy Bypass` para un script propio (con razón); se ejecutó la misma lógica en línea sin cambiar la política.

| LOTE-ENT (sesión de reinicios: ENT-02/04/05/06/07/08/09/10/11, DEV-01; 6 arranques) | claude-opus-5-5 | C0 | 4 | 2+9 manuales (7) | 7 (0 fallidas) | 0 | 35.251 | 13.954.604 | resuelto | D1, B1 |

Notas sobre ENT (2026-10-28): 9 variantes de add-in desde un solo fuente + 2 add-ins "antiguos" (net8 con la API 2026 y net48 con la API 2024, vía Nice3point.Revit.Api). **Fallos de entorno propios (D1/B1), compilando con `dotnet build` directo (fuera del registro de `lab build`):** carpetas `obj` de las variantes dentro del proyecto (AssemblyInfo duplicados, 6 builds), `BaseIntermediateOutputPath` global que se propagaba a la `ProjectReference` (1 build) y un `Private` condicional sin efecto. **ENT-10 necesitó 6 arranques**: la sintaxis de `PublicAssemblies`/`Dependencies` está documentada a medias, y la IA fue ajustándola leyendo el **journal de Revit** en cada intento (rutas resueltas contra el directorio de trabajo, `AssemblyName` que no resuelve) y copiando la forma de los manifiestos de Autodesk. **La IA cerró y abrió Revit sola** (`PostCommand(ExitRevit)` + `Start-Process` + espera a `lab ping`), lo que permitió iterar arranques en ~30-40 s sin el usuario, salvo los avisos de seguridad de las DLL nuevas. El journal resultó ser la mejor fuente de verdad de la carga de add-ins: textos exactos de diálogos, conflictos de versiones y resolución de contextos.

| LOTE-UI12 (cerrar Revit) | claude-opus-5-5 | C0 | 1 | 1 (0) | 1 (0 fallidas) | 0 | — | 2.224.182 | resuelto | — |

Notas sobre UI12: funcionó al primer intento. **Error de comunicación propio:** la interfaz marcó la orden como "rechazada" (el usuario la interrumpió) y le dije al usuario que no se había ejecutado; el registro del runner y `results.partial.jsonl` mostraron que sí. Lección: ante un estado dudoso, mirar los registros antes de afirmar nada.

| LOTE-DEV (bloque 3b: Visual Studio y SDK, 4 pruebas) | claude-opus-5-5 | C0 | 3 | 3 (1) | 5 (0 fallidas) | 0 | 14.297 | 12.361.872 | resuelto | B1 |

Notas sobre DEV (2026-10-28): 1 B1 propio (`Replace(char, string)`). El resto fueron pruebas manuales en VS con el usuario. **Lo que la IA aportó:** convertir los síntomas en causas verificables. El mensaje de VS "error inesperado: ." no decía nada; la ventana Salida dio el motivo y la búsqueda en los 89 `runtimeconfig.json` de Revit dio la causa raíz (`MetadataUpdater.IsSupported=false`). La tabla de Módulos que pegó el usuario sirvió para confirmar dos afirmaciones (el runner descarga sus copias; el Add-In Manager las acumula). `lab vigilar` (DEV-09) es código nuevo de la CLI: funcionó a la primera.

| LOTE-INT3 (sesión interactiva, bloque 3a: 7 pruebas) | claude-opus-5-5 | C0 | 4 | 3 (0) | 14 (0 fallidas) | 0 | 11.573 | 8.830.073 | resuelto | — |

Notas sobre INT3 (2026-10-27): ~330 líneas (operaciones largas, bombeo de mensajes, WPF, P/Invoke y AdWindows), todo compiló y funcionó a la primera. Antes de usar AdWindows (fuera del inventario) se amplió `ApiInventory` (el `WindowsBase.dll` fachada de NETCore.App tapaba al real de WPF) para seguir la regla de comprobar la API antes de escribir. **Lección de diseño:** las pruebas que dependen del momento en que actúa el usuario (CTX-05) salieron ambiguas dos veces; la tercera midió la condición automáticamente (`IsWindowEnabled`) y fue concluyente. Generalización: si una prueba manual depende del *cuándo*, medir el estado en lugar de suponerlo.

| LOTE-UI03 (panel acoplable en `OnStartup`) | claude-opus-5-5 | C0 | 2 | 2 (0) | 1 (0 fallidas) | 0 | 4.356 | 1.647.465 | resuelto | — |

Notas sobre UI03: funcionó al primer intento. Dos errores de razonamiento: (1) un botón de prueba dependía de un estado (`Registro.UiApp`) que solo asignan los comandos de la cinta, así que en el panel de arranque daba "prueba no válida"; (2) di por hecho que un add-in cargado por manifiesto recibiría un `AssemblyLoadContext` aislado, y lo afirmé en el checklist de UI-01 antes de comprobarlo. El registro de los comandos lo refutó (también es 'Default'), y la afirmación de UI-01 se corrigió.

Notas sobre INT2 (2026-10-27): proyecto nuevo `RevitLab.Comandos` + cinta, `DirectContext3D` y WPF/XAML, unas 550 líneas. Todo funcionó en Revit **al primer intento de ejecución**; los únicos fallos fueron de compilación por `UseWPF` (B1). **El clasificador automático de `lab build` los marcó como A1/A2** (los CS0103/CS0246 de `Path`/`File` parecen "símbolo inexistente"), y lo corregí a mano en `tareas.jsonl`. Pendiente: que el clasificador no cuente como A1 los símbolos de la BCL. Error de herramienta propio: una sustitución con PowerShell 5.1 (`Get-Content` sin `-Encoding`) estropeó la codificación UTF-8 del XAML; por casualidad sirvió como prueba adicional de que la recarga toma el BAML nuevo. **Medir en vez de interpretar:** la descripción del usuario sobre el `PreviewControl` ("gira la vista 1") era ambigua; una prueba de diagnóstico con `View3D.GetOrientation` antes y después resolvió cuál vista cambiaba.

| LOTE-INT1R (repetición de UI-04, UI-05, VIZ-01) | claude-opus-5-5 | C0 | 2 | 3 (0) | 6 (0 fallidas) | 0 | 4.932 | 3.760.649 | resuelto | — |

Notas sobre INT1R: el usuario pidió repetir las pruebas en las que estaba distraído, porque "los resultados son muy importantes para tomar decisiones". Mejora de método: **una pregunta de control al usuario en cada paso que dependa de su acción** (¿marcaste la casilla?, ¿pulsaste Esc sin clic?). Gracias a ella se descartaron dos falsas rarezas (`WasVerificationChecked` y el Esc en `PickElementsByRectangle`), que eran errores del usuario, y se confirmaron las reales (`PickedBox` sin normalizar). Sin un control, la IA habría registrado como hallazgo de la API lo que era un descuido humano.

Notas sobre INT1 (2026-10-27, sesión interactiva con el usuario delante de Revit):
- **Patrón que funcionó:** pruebas lanzadas por el runner sobre un documento **visible** (`OpenAndActivateDocument` desde `ExternalEvent`) y comprobaciones visuales del usuario con un `TaskDialog` Sí/No, cuya respuesta queda sola en el resultado. Así la verificación manual (M) también queda registrada y es reproducible.
- **Fallos de proceso de la IA (no de código):** (1) lancé UI-04 y UI-05 en el mismo turno en que daba las instrucciones, antes de que el usuario estuviera delante, y ambas agotaron los 600 s de la CLI. Desde entonces: instrucciones → esperar "va" → lanzar con `--timeout 1800`. (2) La primera versión de UI-05 ponía las instrucciones en la barra de estado, que el usuario no vio; un `TaskDialog` antes de cada paso lo resolvió. (3) Mi hipótesis de que Esc no cancela `PickElementsByRectangle` era falsa (eran clics del usuario); se descartó con un control.
- **E1:** EVT-08b leyó 0 eventos porque los campos estáticos no pasan de un run a otro (cada run carga una copia nueva de la DLL). El patrón correcto, con archivos, ya existía en CTX-06 y no lo reutilicé. Le costó al usuario repetir las selecciones.
- **C5 descubiertos:** `TaskDialog` no admite casilla de verificación y casilla extra a la vez; `PickedBox` no normaliza Min/Max; el 2.º `PostCommand` lanza excepción; `ExportImage` sigue al tema del lienzo y no incluye la leyenda del AVF; `SetElementIds` dispara `SelectionChanged` una sola vez y de forma diferida.
- Los 71,7 minutos incluyen pausas largas del usuario (estaba con otras tareas).

Notas sobre RUNNER-2 (2026-10-27):
- Los cambios en el runner, los contratos, la CLI y las pruebas compilaron y funcionaron al primer intento. Antes de compilar, al releer, detecté que olvidaba asignar el campo de la aplicación que usa `Shutdown`.
- **Corrección de un registro anterior (E1 de la sesión del 2026-10-18):** la observación de DEV-07 decía que los eventos retenían el contexto "aunque se den de baja". Era un **factor de confusión**: todas aquellas pruebas usaban `Tx`, cuyo `IFailuresPreprocessor` es lo que de verdad retiene. Se detectó porque el resultado nuevo contradecía al viejo, y se aisló con un control negativo (un evento que nunca se da de baja) y una variante con solo `Tx`. Lección: una afirmación de "retiene" necesita un control que no retenga, y viceversa.
- El sistema no estaba encendido al pedirlo: el usuario dijo que había abierto Revit, pero no había ningún proceso. Se comprobó con `Get-Process` y el registro del runner antes de suponer un fallo del add-in.

Notas sobre SUELTAS (2026-10-27: CTX-13, UI-13, EXP-12, SHL-04):
- Las 3 pruebas en Revit compilaron y funcionaron a la primera. Antes de escribir, la IA consultó el inventario de la API (opciones de `Application`, `SelectionUIOptions`, `UIThemeManager`, `DialogBoxShowing`).
- **B1 propio, fuera del registro:** `ShlProbe` (SHL-04) se compiló con `dotnet build`, no con `lab build`, y el primer intento falló: la función local estática `Run` usaba `args`. Corregí a mano `compilaciones`/`compilaciones_fallidas` en `tareas.jsonl`. Además, el primer lanzamiento ejecutó el `.exe` viejo sin que me diera cuenta de que el build había fallado (filtré la salida y solo vi avisos).
- **Registro corregido:** en `lab fin` puse una causa `D2` que no existe en la taxonomía para el defecto del pipe. No es un fallo de esta tarea, sino un **hallazgo de diseño del runner** (escrito por la IA en la sesión RUNNER): un `PipeServer` de una sola instancia que no responde ni al `eco` mientras atiende una orden. Lo reclasifiqué en `tareas.jsonl`.
- **C5 descubiertos ejecutando:** (1) ningún código de `OverrideResult` acepta un aviso, todos hacen *rollback*; (2) Revit reescribe `Revit.ini` con rutas relativas al guardar las rutas de biblioteca; (3) `BasicFileInfo` mezcla UTF-16 *little-endian* y *big-endian*.
- Los 3,6 minutos no incluyen la lectura previa del runner y del checklist, que se hizo antes de `lab inicio`.

Notas sobre LIM/CTX: las pruebas de estrés y de patrones de hilos compilaron a la primera. Hubo dos errores de la IA: un `ExternalEvent.Create` desde un hilo de fondo (regla de contexto conocida, pero el fallo quedó **silenciado** dentro del `Task`) y borrar un RVT todavía abierto. El aislamiento de las anomalías de rendimiento descartó dos hipótesis propias (la separación entre muros y los muros cruzados) gracias a la medición, en lugar de quedarse con la primera explicación.

Notas sobre DAT/WSH/DIS:
- 18 pruebas, unas 300 líneas, **compilaron a la primera**, incluidos varios miembros escritos de memoria (`StructuralRole`, `NumberOfBarPositions`, `TotalLength`, `GetAnalyticalSurfaces`) que existían.
- **D1:** la IA no puso `CopyLocalLockFileAssemblies` al añadir un NuGet a una biblioteca, un clásico del empaquetado de add-ins que se descubre al ejecutar.
- **C5:** la sintaxis del espaciado personalizado de armaduras (`3@200;2@400`) no está documentada en la firma. Se encontró **probando 11 candidatas** con `IsValidCustomSpacingFormula`: la estrategia exploratoria vuelve a ser la que resuelve.

Notas sobre los benchmarks y ANO/ELM:
- **Cotas automáticas (#9, dificultad 4): primer intento**, con valores exactos. El único defecto fue gráfico: las cotas se cruzan y sus textos se pisan, lo cual solo se ve en la imagen. La IA domina la mecánica (referencias de caras, cadenas) pero no el criterio de composición del dibujo.
- **Escalera en U (#11, dificultad 5): 3 ejecuciones.** Los dos fallos fueron comportamientos de Revit no deducibles de la API: crea barandillas automáticas al confirmar la escalera, y un recorrido de longitud L·huella da L+1 contrahuellas. Ambos se diagnosticaron leyendo los datos devueltos (avisos y conteos por tramo) y se corrigieron sin adivinar.
- **Muro cortina:** la IA tenía **invertido** el significado de las líneas U/V (U = horizontal). Revit no avisa: devuelve null sin excepción. Se resolvió con una prueba diagnóstica que midió la semántica real.
- **El inventario corrigió a la propia investigación:** `ScopeDefiningFilter` no es una clase. Es un ejemplo de alucinación en la **documentación generada por IA**, detectada por la verificación estática contra la DLL.

Notas sobre GEO/EVT:
- **Primera alucinación de API (A1) en ~3.000 líneas:** `ChangePriority.WallsLightWeight` no existe. El script `lab build` la clasificó **automáticamente** como A1 (el símbolo no está en el inventario), y se corrigió en 1 iteración consultando el inventario. Hasta este lote la tasa era de 0 alucinaciones.
- **E1 ×2:** errores de diseño de prueba, no de API. Una tolerancia demasiado estricta para un volumen de revolución, y una altura de prueba igual a la altura por defecto (sin cambio no se dispara el updater). Ambos se detectaron por el resultado y se explicaron con datos.

Notas sobre EXP (el lote más difícil hasta ahora, y **no por el código**):
- El código compiló siempre a la primera. Lo difícil fue **el comportamiento de Revit en ejecución**: diálogos modales en transacciones internas, un `OverrideResult` que aborta en vez de aceptar, un `LinkLoadResult` que falla en silencio, un DGN que devuelve false sin explicación y un **crash nativo reproducible**.
- **Las 5 intervenciones humanas** fueron 3 diálogos modales cerrados a mano y 2 reaperturas de Revit tras un crash. Ninguna fue para corregir código.
- **Errores de la IA:**
  - C1: vincular un modelo abierto en la misma sesión, sin comprobar `LinkLoadResult`.
  - B1: una consulta LINQ diferida evaluada después de borrar los elementos.
  - Un **diagnóstico erróneo**: atribuí el diálogo a la exportación IFC cuando venía de la importación. Se corrigió con los datos del runner (ver qué ejecución tenía el diálogo y en qué fase).
- **Coste:** 15,7 M tokens totales (33 turnos), casi todo relectura de un contexto ya grande. Las esperas por diálogos y crashes alargan la conversación y multiplican el coste, aunque el código no cambie.
- **Conclusión parcial importante:** la "pared" con Revit no está tanto en escribir código contra la API (que la IA hace bien) como en **los efectos secundarios de Revit en ejecución**: diálogos modales, transacciones internas y crashes. Sin un bucle que ejecute y observe, ninguno de estos problemas es visible. Esto apoya la hipótesis H1 y añade una nueva: **H7, la interfaz de Revit interfiere con la automatización**.

Notas sobre FAM:
- **FAM-04 (benchmark #10, dificultad 4) salió al primer intento:** familia paramétrica con cota etiquetada, alineaciones bloqueadas a caras obtenidas con referencias, fórmula y 3 tipos con medidas exactas. Era una de las tareas que se esperaba que fuera difícil.
- **C5:** el perfil de `NewSweep` va en coordenadas locales, algo que no está documentado y que la excepción tampoco explica ("One of the conditions for the inputs was not satisfied"). La IA no adivinó: **escribió una prueba exploratoria con 3 hipótesis** y Revit eligió. Esa estrategia solo es posible con el bucle de ejecución.
- **C1 (error de la IA):** en esa prueba exploratoria creó un `SketchPlane` fuera de una transacción. Se detectó por la excepción explícita y se corrigió en 1 iteración.

Notas sobre PAR/VIS:
- **22 de 23 al primer intento** con unas 500 líneas (parámetros compartidos, globales con fórmula, filtros, 7 tipos de vista, plantillas, sobrescrituras, planos, tablas, recortes no rectangulares, perspectivas).
- **E1 autocorregido:** la caja de la sección de VIS-01 quedó escrita a medias, y lo corregí al releer el código **antes** de compilar. El compilador no lo habría detectado (compilaba) y habría fallado en tiempo de ejecución. Al principio lo registré por error como "intervención humana" y luego lo corregí a mano en `tareas.jsonl`.
- **C5:** las vistas sombreadas se exportan según `ShadowViewsFileType` (JPEG por defecto) aunque se pida PNG en `HLRandWFViewsFileType`. Se diagnosticó mirando los archivos generados.
- **La verificación visual aportó información nueva:** detectó defectos que las aserciones no veían (texto superpuesto en el cajetín, líneas de nivel en el 3D) y que el fondo de las imágenes depende del tema de la interfaz.

Notas sobre los lotes TRX y CTX:
- **TRX: el fallo E1 es interesante.** El código era correcto y compiló a la primera, pero la aserción de TRX-07 partía de un supuesto falso de la IA: que `FailuresAccessor.DeleteWarning` borra el aviso del documento. En realidad solo evita el diálogo. Es un error de conocimiento de la semántica de la API, no de sintaxis, y solo se detecta ejecutando en Revit.
- **TRX: intervención (confirmada por el usuario).** El diálogo modal de advertencia bloqueó la API 146 s hasta que el usuario lo cerró a mano. La IA no lo previó: no sabía que un aviso en una transacción de API lanzada desde un `ExternalEvent` abre un diálogo modal.
- **CTX: cero fallos.** 6 pruebas con async/await, hilos y `ExternalEvent`: código correcto al primer intento y resultados nuevos (por ejemplo, que el hilo de Revit tiene un `WindowsFormsSynchronizationContext`).
- **ELM: 20 de 21 al primer intento.** Unas 400 líneas sobre muros, suelos, cubiertas, puertas, estructura, datums, habitaciones, toposólidos, DirectShape, transformaciones, copias entre documentos, partes, conjuntos, worksets y uniones. La IA consultó el inventario de la API antes de escribir (2 consultas de firmas) y compiló a la primera. El único fallo (ELM-04) fue de tipo **C5**: `NewFootPrintRoof` declara un parámetro `out` que Revit exige que venga inicializado. La firma no lo revela; solo se descubre ejecutando. Se diagnosticó por la traza y se corrigió en 1 iteración.
- **Sesgo de medición detectado:** los tokens solo cuentan las respuestas del modelo **ya escritas** en el historial al ejecutar `lab fin`. La respuesta que llama a `fin` (y la que llamó a `inicio`, si incluía código) puede quedar fuera, así que hay un subconteo de 1 o 2 turnos por tarea. Pendiente: añadir `lab recalcular <id>` para rehacer el cálculo más tarde.

Notas sobre RUNNER (2026-10-18):
- Abarca `RevitLab.Contracts`, `RevitLab.Runner`, las 4 primeras pruebas y la CLI `lab`: unas 700 líneas de C#. Compiló y funcionó en Revit al primer intento.
- La CLI `lab` se compiló directamente con `dotnet` (1 intento, correcto) antes de que existiera el registro, así que no figura en las compilaciones.
- La "ejecución fallida" y la causa `C1?` son un **falso positivo**: DEV-10 lanza una excepción a propósito. Pendiente: añadir `EsperaExcepcion` a `LabTestAttribute` para que el runner no lo cuente como fallo (requiere reiniciar Revit, porque los contratos viven en el runner).
- Los 61 minutos incluyen la espera hasta que se abrió Revit, no son tiempo de trabajo de la IA.
- Turnos: 15 respuestas del modelo. El 98 % de los tokens son lecturas de caché del contexto de la conversación.

**Indicadores agregados:**
- **pass@1**: porcentaje de tareas en verde al primer intento.
- **Iteraciones medianas hasta verde** por nivel de dificultad.
- **Tasa de alucinación**: errores A1 por cada 100 llamadas a la API escritas.
- **Peso de cada causa** (A1…E1) en el total de fallos.
- **Efecto de la retroalimentación**: C0 frente a C2.
- **Efecto de la referencia**: C0 frente a C1.
