# Plan de pruebas: alcances y límites de la programación y la automatización en AutoCAD 2027

**Objetivo:** llevar al límite **todas las vías** de programación y automatización de AutoCAD 2027 (.NET, AutoLISP/Visual LISP, COM/ActiveX, `accoreconsole`, macros y CUIx, JavaScript, VBA, ObjectARX, Design Automation y lectura de DWG sin AutoCAD) y registrar, capacidad por capacidad, qué **se puede**, qué **no se puede** y bajo qué **condiciones**, además de las **diferencias entre vías**. Es la continuación de [RevitLab 2027](../../revitlab-2027/pruebas/Plan%20de%20pruebas%20API%20NET%20Revit%202027.md), que hizo 206 pruebas de la API .NET de Revit.

**Entorno fijo** (no actualizar a mitad del laboratorio): AutoCAD 2027 **R26.0, build 26.0.118**, interfaz en inglés (ENU), .NET 10 (SDK 10.0.403), Visual Studio 2026 y VS Code. Instalados: .NET, AutoLISP/Visual LISP (con `AutoLispDebugAdapter.exe`), COM/ActiveX (`acax26enu.tlb`, `axdb26enu.tlb`, `Autodesk.AutoCAD.Interop*.dll`), `accoreconsole.exe`, Express Tools y la carpeta local `Design Automation/`. **No instalados:** el módulo VBA y el SDK de ObjectARX (instalarlos requiere permiso del usuario).

Además de las vías, se mide **qué tan difícil le resulta a la IA** programar contra cada una (tokens, iteraciones, causas de fallo): ver la sección 2 y las métricas de dificultad IA del laboratorio.

Contexto: [Recomendaciones desde RevitLab](../Recomendaciones%20desde%20RevitLab.md) (equivalencias Revit→AutoCAD por verificar) y [estado y pendientes](../docs/estado-y-pendientes.md).

---

## 1. Cómo se verifica (el "Playwright" de AutoCAD)

Como en RevitLab, la API .NET solo vive **dentro** de un `acad.exe` abierto, con licencia y en su hilo principal. Un ejecutor de pruebas dentro de AutoCAD recibe órdenes desde fuera y devuelve resultados en JSON. A diferencia de Revit, AutoCAD tiene además vías **fuera del proceso** (COM desde otro programa, `accoreconsole`), que se verifican con scripts propios que leen la salida estándar, el código de salida y los archivos generados.

```
Claude escribe la prueba (C#, LISP, script COM o .scr) → lab build → el runner (o la consola) la ejecuta
→ resultados JSON + imágenes + registros → Claude los lee y actualiza este checklist
```

### 1.1 Niveles de verificación

| Nivel | Código | Quién verifica | Cómo | Ejemplos |
|---|---|---|---|---|
| **Estático** | `S` | Claude, sin AutoCAD | Reflexión sobre `acdbmgd`/`acmgd`/`accoremgd` (`pruebas/resultados/api-surface-autocad-2027.tsv`), bibliotecas de tipos COM, inventario de funciones LISP, consolas .NET que usan la API fuera de AutoCAD | "¿Hay API .NET para crear un bloque dinámico?", lectura de la cabecera de un DWG |
| **Automático** | `A` | Claude | Una prueba con aserciones se ejecuta dentro de AutoCAD (runner) o en `accoreconsole` y devuelve OK, FALLO o EXCEPCIÓN (tipo y mensaje) en JSON | Crear una polilínea y comprobar su área |
| **Visual** | `V` | Claude | La prueba exporta una imagen (`DocumentExtension.CapturePreviewImage`) o un PDF trazado; Claude la abre y la juzga. Si hace falta, una captura de la ventana con PowerShell | Que una ventana gráfica muestra lo esperado; que una cota se dibuja bien |
| **Manual** | `M` | Tú | Lo que necesita interacción humana o percepción de la interfaz. Claude prepara la prueba y tú anotas el resultado | Cinta, paletas, jigs, selección con el ratón |
| **Medición** | `P` | Claude | `Stopwatch`, memoria del proceso, número de entidades, tamaño de archivos; resultados en JSON | Crear 100.000 líneas: tiempo y memoria |
| **Registro** | `J` | Claude | El equivalente al journal de Revit, **por verificar**: `LOGFILEON` (archivo `.log` en `LOGFILEPATH`), el historial de la línea de comandos (`Autodesk.AutoCAD.Internal.Utils.GetLastCommandLines`), la salida estándar de `accoreconsole`, los informes CER (`ErrorReports`) y el Visor de eventos de Windows | Diagnosticar un crash o un diálogo inesperado |

> En RevitLab casi todos los diagnósticos salieron del journal. Aquí hay que averiguar desde el principio qué fuente registra los errores de la API, los avisos y los cuelgues (DEV-02).

### 1.2 El ejecutor de pruebas: `AutoCADLab.Runner`

Add-in mínimo (`addins/AutoCADLab.{Contracts,Runner,Tests}`), verificado el 2026-10-28:

1. Se instala solo al compilar como **bundle** en `%APPDATA%\Autodesk\ApplicationPlugins\AutoCADLab.bundle` (`PackageContents.xml`). Comando `AUTOCADLAB` para comprobar la carga.
2. Abre un *named pipe* local, **`\\.\pipe\autocadlab`** (y `autocadlab-<pid>` para varias instancias), restringido al usuario.
3. Las órdenes del pipe pasan al hilo principal mediante el **`Dispatcher` de WPF** capturado en `IExtensionApplication.Initialize`; ahí `IsApplicationContext=true` y `IsQuiescent=true`. Para modificar el documento activo desde ese contexto hace falta `DocumentLock` (TRX-01, TRX-02).
4. La DLL de pruebas se carga desde una **copia sombra** en un `AssemblyLoadContext` descargable (falso hot reload): ~2,2 s por iteración (DEV-01). Resultados parciales en `results.partial.jsonl`, que sobreviven a un crash.

Del lado de Claude, la CLI `lab` (`lab inicio/build/run/fin`, `lab vigilar`, `--pid`) habla con el pipe y registra las métricas de la IA.

### 1.3 Cómo se verifica cada vía que no es .NET

| Vía | Cómo se lanza | Cómo se lee el resultado |
|---|---|---|
| AutoLISP | Desde el runner (`SendStringToExecute` o `Application.Invoke`), desde un `.scr` o desde VS Code | Archivo de resultados escrito por la rutina, historial de comandos, `LOGFILEON` |
| COM dentro del proceso | Prueba .NET con `Application.AcadApplication` | JSON del runner |
| COM desde fuera | Script C#, Python o PowerShell lanzado por Claude | Salida estándar y archivos |
| `accoreconsole` | `accoreconsole.exe /i <dwg> /s <scr>` lanzado por Claude | Salida estándar, código de salida y archivos generados |
| Macros, CUIx, JS, VBA, ARX | Según la prueba; casi siempre con la persona delante | Comprobación manual + archivo testigo |

### 1.4 Plantilla de prueba .NET

```csharp
[LabTest("ELM-01", "Crear una línea en un dibujo en memoria")]
public static LabResult CrearLinea(LabContext ctx)
{
    var db = ctx.NuevaBase();
    using (var t = db.TransactionManager.StartTransaction())
    {
        var ms = (BlockTableRecord)t.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);
        var linea = new Line(Point3d.Origin, new Point3d(10, 0, 0));
        ms.AppendEntity(linea);
        t.AddNewlyCreatedDBObject(linea, true);
        t.Commit();
    }
    // … releer y comprobar
    return LabResult.Assert(Math.Abs(largo - 10) < 1e-9, $"longitud {largo:F9}");
}
```

El JSON de salida por prueba incluye `id`, `estado` (`ok | fallo | excepcion | crash | timeout`), `excepcion` (tipo y mensaje), `ms`, `memMB`, `obs` y `evidencias`.

### 1.5 Dibujos de prueba

- **Nunca** se trabaja sobre dibujos reales. Todo dibujo se crea desde plantilla (`acadiso.dwt`) o se genera por código.
- `D0-vacio.dwg`: desde la plantilla métrica. `D1-basico.dwg`: generado por una prueba (capas, bloques con atributos, cotas, presentaciones). `D2-grande.dwg`: generado, con 1.000.000 de entidades o más, para las pruebas de estrés.
- **Archivos de lote fuera del repositorio** (`%LOCALAPPDATA%\AutoCADLab`), salvo que la prueba estudie precisamente eso (EXP-09).
- La sesión de AutoCAD abierta puede usarse para otras cosas: las pruebas trabajan en bases laterales o en documentos nuevos y los cierran sin guardar.

---

## 2. Relación con las métricas de dificultad para la IA

Cada prueba que exige escribir código es también una **tarea de IA** que se mide con `lab inicio`/`lab fin` (tokens, compilaciones, ejecuciones, intervenciones humanas, causa de cada fallo según la taxonomía A1–E1 de las métricas de dificultad IA del laboratorio). Novedades frente a RevitLab:

- **Una vía, una referencia.** La regla de consultar el inventario antes de escribir se aplica a cada vía: `api-surface-autocad-2027.tsv` para .NET, el inventario de COM (SHL-05) y el de LISP (SHL-06). Así se puede medir la alucinación (A1) también en LISP y COM, donde no hay compilador que la detecte.
- **La familia CMP** repite la misma tarea por varias vías y compara tiempo de ejecución **y** dificultad para la IA (iteraciones, errores, tokens). Es el dato nuevo del laboratorio.
- Las hipótesis H1–H7 se mantienen; H7 (la interfaz interfiere) se contrasta con `accoreconsole`, que no tiene interfaz.

---

## 3. Convenciones del checklist

- `- [ ]` pendiente. `- [x]` probado (con el resultado ya anotado).
- **Resultado:** `✅ se puede` · `❌ no se puede` (no hay API o lanza excepción) · `⚠️ se puede con condiciones` · `💥 tumba o cuelga AutoCAD` · `⏭ omitido`.
- **Nivel:** `S A V M P J` (ver 1.1), entre paréntesis al final de la capacidad.
- **Obs:** condiciones, excepción exacta, workaround, tiempo medido, enlace a evidencia.
- Lo marcado como «por verificar» viene de la documentación o de versiones anteriores, no de esta instalación.
- Las pruebas que **instalan software, usan la nube o gastan dinero** empiezan por pedir permiso al usuario.

Formato de cada línea (lo lee `tools/checklist.py`):

```
- [ ] **FAM-NN** Capacidad que se prueba (A) · R: — · Obs:
```

> Las suposiciones son puntos de partida, no hechos. Si un resultado contradice lo esperado, ese es precisamente un hallazgo que vale la pena explicar en Obs.

---

## 4. Checklist

### DEV · Ciclo de desarrollo y falso hot reload

- [x] **DEV-00** Hot Reload y Editar y continuar de Visual Studio 2026 adjuntado a `acad.exe` (se espera que no, por `MetadataUpdater.IsSupported=false` en los `runtimeconfig.json`) (M) · R: ❌ · Obs: Hot Reload de C# NO funciona en AutoCAD 2027 lanzado desde Visual Studio 2026. El runtime sí lo admitiría: dentro de acad.exe MetadataUpdater.IsSupported es False de serie (acdbmgd.runtimeconfig.json lo fija en false), pero True con DOTNET_MODIFIABLE_ASSEMBLIES=debug en el entorno y True al lanzar AutoCAD con F5 desde VS. Lo rechaza Visual Studio: cambiar un literal «v1» → «v2» en un complemento normal (addins/AutoCADLab.Hot, NETLOAD, perfil Executable) y pulsar Recarga activa da «Recarga activa: error inesperado» y en la salida «Hot Reload is not supported in this session due to the lack of a managed language service. An unexpected error has occurred, any pending updates have been discarded»; LABHOT sigue devolviendo «v1». Igual en 4 intentos automáticos por COM (solución creada al vuelo o .slnx real esperando al CodeModel; VS invisible o visible) y en 1 a mano por la persona con F5 y el botón de la llama: no es el montaje por COM. La Recarga activa de XAML sí se conecta a acad.exe. Hipótesis sin verificar: acad.exe es un proceso nativo que carga CoreCLR él mismo y VS no asocia la sesión al servicio de lenguaje C#. Con el runner sería imposible de todos modos (payload collectible). Alternativa práctica: el ciclo con runner y recarga del payload (DEV-03/05, ~2,2 s). Dato aparte: el mismo runtimeconfig pone ItemsControlDoesNotSupportAutomation=true
- [x] **DEV-01** Iteración compilar + ejecutar con el runner (P) · R: ✅ · Obs: ~2,2 s (RevitLab 3,8 s)
- [x] **DEV-02** Fuente de verdad equivalente al journal: qué registran `LOGFILEON`, el historial de comandos (`Utils.GetLastCommandLines`) y CER de una excepción de API, un aviso y un cuelgue provocados (J) · R: ⚠️ · Obs: Internal.Utils.GetLastCommandLines(40, true) funciona, pero tras las ráfagas de SendStringToExecute solo trae prompts «Command:» vacíos: no sirve como registro sin filtrar. LOGFILEMODE=0 (desactivado); LOGFILENAME apunta a …\R26.0\enu\Drawing1_….log. Falta activar LOGFILEON y provocar excepción, aviso y cuelgue; CER sin mirar
- [x] **DEV-03** Línea base: cerrar AutoCAD, compilar, arrancar y ejecutar una prueba sin intervención (tiempo del ciclo con reinicio) (P) · R: ✅ · Obs: ciclo completo sin intervención con una instancia propia (tools/instancia.py arrancar + lab run con --pid + matar), 3 veces: compilar 1,2–2,2 s; arrancar AutoCAD hasta que el runner escucha 6,4–8,9 s; primera ejecución en frío 3,5 s; total 11–14 s (frente a ~2,2 s del runner sin reiniciar, DEV-01). Cerrar es inmediato porque se termina el proceso; con QUIT y un dibujo modificado habría diálogo
- [x] **DEV-04** Recarga automática con `lab vigilar` + `dotnet watch build`: tiempo de guardar a resultado (P/M) · R: ✅ · Obs: dotnet watch build --non-interactive sobre addins/AutoCADLab.UIPal (payload sin comandos) + lab vigilar UI-04n --dll … --pid 57412 --veces 3 (FileSystemWatcher sobre la DLL, espera 1,5 s sin cambios y ejecuta por el pipe): tres guardados del .cs → resultado de la prueba en AutoCAD a los 3,77, 3,62 y 3,67 s. Reparto: dotnet watch recompila en ~1,7 s (de guardar a DLL escrita; compilación 0,8 s), la espera antirrebote de lab vigilar 1,5 s y la carga + ejecución en AutoCAD 0,43–0,49 s. Sin reiniciar AutoCAD. Acortar la espera de 1,5 s bajaría el ciclo a ~2,2 s
- [x] **DEV-05** 50 recargas seguidas: memoria de `acad.exe` y contextos anteriores vivos (P) · R: ✅ · Obs: REPETIDA el 2026-10-28 06:14 con el runner que quita el payload de ExtensionLoader.m_extensions (DEV-07): 20 recargas seguidas con descargado=True y sin acumular contextos (vivos se quedó en 1, el de DEV-06); memoria 552 MB estable. La medida anterior (❌) usaba una métrica defectuosa y el runner sin ese arreglo: 81 copias
- [x] **DEV-06** Dependencias NuGet administradas y nativas (`Microsoft.Data.Sqlite`) dentro del payload recargable (A) · R: ⚠️ · Obs: Microsoft.Data.Sqlite 10.0.13 (SQLite 3.53.3) funciona en el contexto descargable tras dos arreglos del RUNNER: LoadUnmanagedDll con AssemblyDependencyResolver y copia sombra con subcarpetas (runtimes\win-x64\native). Pero cada ejecución que usa SQLite deja su contexto vivo (2 ejecuciones = 2 copias de Microsoft.Data.Sqlite y de AutoCADLab.Tests), aunque el runner lo quite de ExtensionLoader: SQLitePCL retiene algo global (causa sin demostrar)
- [x] **DEV-07** Descarga del AssemblyLoadContext de pruebas (A) · R: ⚠️ · Obs: CAUSA DEMOSTRADA (2026-10-28): el contexto descargable no se descarga porque Autodesk.AutoCAD.Runtime.ExtensionLoader guarda cada ensamblado cargado en su Hashtable estática m_extensions (DEV-07c: 7 entradas de pruebas = 7 ejecuciones). Control (DEV-07d): quitar por reflexión esas entradas libera los contextos en el acto (7 → 1 vivo); sin el control vuelven a acumularse. La primera conclusión («se descarga después») venía de una métrica defectuosa: AssemblyLoadContext.All deja de listar un contexto al llamar a Unload(). El runner mide ahora con WeakReference y con el recuento de copias
- [x] **DEV-08** Breakpoints de Visual Studio 2026 en la copia sombra del payload, adjuntando a `acad.exe` (M) · R: ✅ · Obs: sin la persona, con pruebas/dev/dev08_dte.py: Visual Studio 2026 controlado por COM (VisualStudio.DTE.18.0, ventana invisible) pone un breakpoint en Bloqueos.cs:31, se adjunta a acad.exe (Process.Attach() automático; Attach2 y Debugger.Transports no se ven con pywin32 dinámico) en 0,8 s, y al lanzar CTX-SONDA SE DETIENE en el código del payload aunque el runner lo cargue desde una copia sombra (el .pdb va al lado y las rutas de fuente coinciden). Lee locales y la pila completa (Sonda → MethodBaseInvoker → Ordenes.Ejecutar → RunnerApp), y evalúa con el hilo de AutoCAD parado llamadas reales a la API: DocumentManager.Count=6, GetSystemVariable("CLAYER")="0", ctx.DocActivo.Name. Go, Breakpoints.Delete, DetachAll y Quit: 8 s en total. Repetido en otra instancia con el mismo resultado
- [x] **DEV-09** Un `CommandMethodAttribute` dentro del payload descargable: si AutoCAD registra el comando al cargar el contexto y qué pasa al llamarlo después de descargarlo (A/M) · R: ⚠️ · Obs: un [CommandMethod] en el payload descargable se registra al cargar el contexto (IsCommandNameInUse = ARXCmd) y sigue registrado: el comando ejecuta SIEMPRE el código de la PRIMERA copia cargada (…061420-961), aunque después se carguen otras; AutoCAD no reemplaza un comando ya registrado. Sin crash. El runner conserva en ExtensionLoader los payloads con CommandMethod/LispFunction/CommandClass (los demás los quita). Ojo: si no hay documento abierto, MdiActiveDocument es null y SendStringToExecute no se puede usar
- [x] **DEV-10** Excepción no controlada en una prueba: AutoCAD sobrevive (A) · R: ✅ · Obs:
- [x] **DEV-11** Recursión infinita (`StackOverflowException`) en el payload: AutoCAD muere y qué queda en CER y en el Visor de eventos (M/J) · R: 💥 · Obs: recursión infinita dentro de try/catch (instancia propia): el catch no sirve, AutoCAD muere (0xc00000fd). Queda: diálogo de CER «AutoCAD Error Report» (cer_dialog.exe, página web; cerrado sin enviar: dialog.log «user consent not granted, skip sending report extra files», aunque registra «autosend is enabled, sending graphic info»), volcado en %LOCALAPPDATA%\Autodesk\CER\<hash>\<id>\minidump.dmp.zip (guarda 5 informes), y en el Visor de eventos «Application Error» 1000 y WER 1001 APPCRASH. No se escribe acad.err
- [x] **DEV-12** Excepción no controlada dentro de un manejador de evento o de un comando propio, fuera del `try/catch` del runner: diálogo de error fatal o no (M/J) · R: ⚠️ · Obs: instancia propia. Excepción no controlada en un manejador de Application.Idle (DEV-12a) o en un CommandMethod propio (DEV-12b): AutoCAD NO muere; diálogo de WinForms «Unhandled exception has occurred in a component in your application» con Details y Continue (idioma del hilo: inglés en el hilo de AutoCAD, español en el finalizador); tras Continue sigue funcionando. Excepción en Database.ObjectAppended (DEV-12c): salta en tr.Commit() y el llamante la captura, pero deja la transacción rota: Dispose lanza eInvalidContext y, más tarde, el finalizador de la Transaction vuelve a lanzar en el hilo del GC → el mismo diálogo; tras Continue el hilo finalizador queda retenido para siempre (el runner se cuelga en GC.WaitForPendingFinalizers) y el Visor anota .NET Runtime 1026 «terminated due to an unhandled exception». Moraleja: no lanzar nunca desde un manejador de eventos de la base
- [x] **DEV-13** `NETLOAD` dos veces de una DLL recompilada con el mismo nombre: ¿se carga la nueva, se ignora o da error? (un ensamblado del contexto por defecto no se puede descargar) (A/M) · R: ✅ · Obs: NETLOAD de una versión recompilada con el mismo nombre de ensamblado (otra carpeta): falla con «Cannot load assembly… FileLoadException: Assembly with same name is already loaded» (ExtensionLoader.Load → Assembly.LoadFrom en el contexto por defecto). LABVERSION sigue ejecutando la v1. Además, la DLL cargada queda bloqueada y no se puede sobrescribir. Para recargar sin reiniciar hay que usar un contexto propio (como el runner)
- [x] **DEV-14** Visual Studio lanzando `acad.exe` con F5 (perfil `Executable` con `/nologo` y un script de arranque) (M) · R: ✅ · Obs: sin la persona, con pruebas/dev/dev14_hot.py: Visual Studio 2026 por COM crea una solución temporal (fuera del repo), añade addins/AutoCADLab.Hot y ejecuta Debug.Start (F5) con el perfil Executable de Properties/launchSettings.json (acad.exe /nologo /product ACAD /language en-US /b hot.scr, que hace NETLOAD desde la carpeta de confianza). AutoCAD arranca con el depurador adjunto en ~4 s, el runner responde a los ~10 s y LABHOT funciona (Debugger.IsAttached=True). Repetido 3 veces. TRAMPAS: el AutoCAD lanzado con F5 muere al cerrar VS (y un VS arrancado por COM se cierra solo al soltar la última referencia, aunque esté depurando); la salida de OutDir debe ir directamente a la carpeta de confianza, porque TRUSTEDPATHS no incluye subcarpetas si no acaba en \...

### ENT · Carga de add-ins y entorno .NET 10

- [x] **ENT-01** Add-in cargado como bundle desde %APPDATA%\Autodesk\ApplicationPlugins: el runner responde (A) · R: ✅ · Obs: carga, pero SECURELOAD avisa (carpeta no de confianza); ver ENT de SECURELOAD
- [x] **ENT-02** SECURELOAD: si «Always Load» se recuerda en el siguiente arranque con la DLL sin cambios, si se recuerda por archivo o por ruta, y por qué salió un segundo aviso para la misma DLL tras pulsar Always Load (M/J) · R: ❌ · Obs: «Always Load» NO se recuerda entre arranques: tras pulsarlo en el arranque de las 04:42 (DLL recompilada a las 04:32), el siguiente arranque (tras el crash de TRX-08), con la DLL sin cambios, volvió a mostrar el aviso de SECURELOAD (esta vez una sola vez; en el arranque de las 04:42 salió dos veces seguidas para la misma DLL). Tampoco se encontró en HKCU\…\R26.0 dónde se guardaría la decisión. Para no ver el aviso: ruta en TRUSTEDPATHS o DLL firmada
- [x] **ENT-03** Añadir la carpeta del bundle a `TRUSTEDPATHS` (con `\...` para subcarpetas) y comprobar que el aviso desaparece; si se puede cambiar `TRUSTEDPATHS` por código y si exige reiniciar (A/M) · R: ✅ · Obs: por código desde el runner: SetSystemVariable("TRUSTEDPATHS", …) llega al registro en el acto, sin reiniciar; accoreconsole lo ve en su siguiente arranque. Rutas autorizadas por el usuario el 2026-10-28: %LOCALAPPDATA%\AutoCADLab\confianza y %APPDATA%\Autodesk\ApplicationPlugins\AutoCADLab.bundle\... (ENT-03b). VERIFICADO en el arranque de las 06:13: con la carpeta del bundle en TRUSTEDPATHS el aviso de SECURELOAD ya no sale
- [x] **ENT-04** `SECURELOAD` 0, 1 y 2: qué se carga y qué no desde una carpeta que no es de confianza (restaurar el valor al terminar) (M) · R: ✅ · Obs: con permiso de la persona, en una instancia propia, un .lsp en %LOCALAPPDATA%\AutoCADLab
ofiable (no de confianza) cargado con (load) dentro de vl-catch-all-apply: SECURELOAD=2 → rechazo SIN aviso, error capturable «File load canceled: …» y no se ejecuta (control: el mismo archivo en la carpeta de confianza sí carga con 2); SECURELOAD=1 (valor de partida) → aviso modal Win32 «Security - Unsigned Executable File» (Name, Publisher «Unknown Publisher», Location, Always Load / Load Once / Do Not Load): Do Not Load da el mismo «File load canceled», Load Once lo ejecuta; SECURELOAD=0 → carga sin aviso. SECURELOAD restaurado a 1 y comprobado. Sin probar con DLL (NETLOAD) en este ciclo (en CON-04 la consola rechazaba una DLL fuera de confianza)
- [x] **ENT-05** `NETLOAD` desde la línea de comandos y desde un script con `FILEDIA=0`: misma DLL, mismo aviso (A) · R: ✅ · Obs: DLL fuera de TRUSTEDPATHS, cada vía en una instancia nueva: NETLOAD tecleado con FILEDIA=0, (command "_.NETLOAD" ruta) desde LISP y NETLOAD dentro de un .scr lanzado con SCRIPT dan el mismo diálogo «Security - Unsigned Executable File» (Always Load / Load Once / Do Not Load); con Load Once se carga y corre Initialize. Tras Do Not Load, los NETLOAD siguientes del mismo archivo fallan en silencio en esa sesión («Unable to load … assembly», sin diálogo). Una vez, con copias de la misma DLL en varias carpetas, una copia se cargó tras pulsar Do Not Load por programa: no reproducido (mismo nombre de ensamblado en las tres copias)
- [x] **ENT-06** Carga a demanda por registro (`HKCU\…\Applications`, `LOADCTRLS`) frente a bundle: cuándo se ejecuta `Initialize` (A/J) · R: ✅ · Obs: clave temporal HKCU\…\R26.0\ACAD-A101:409\Applications\AutoCADLabEnt (LOADER, MANAGED=1) con la DLL en la carpeta de confianza: LOADCTRLS=2 carga al arrancar (Initialize 2 ms después de que el runner abre su pipe); LOADCTRLS=12 con la subclave Commands\LABENT no carga nada al arrancar y Initialize corre justo antes de ejecutar LABENT (1 ms). Hallazgo: el cargador de bundles copia cada componente a esa misma clave (AutoCADLab.Runner con LOADCTRLS=2; LoadOnCommandInvocation → LOADCTRLS=4 + Commands + Groups). Clave borrada al terminar
- [x] **ENT-07** `PackageContents.xml`: `SeriesMin`/`SeriesMax`, `Platform` y carga diferida al invocar un comando (`LoadOnCommandInvocation`) (A/M) · R: ✅ · Obs: componentes temporales en el PackageContents.xml del bundle del laboratorio (restaurado después): LoadOnCommandInvocation con <Commands> no carga al arrancar y carga al invocar LABENT; los componentes con SeriesMin/SeriesMax R27.0, R24.0–R25.0 o Platform="ACADM" se ignoran sin mensaje y ni siquiera se registran en Applications. Control: sin la entrada, LABENT es «Unknown command». Al quitar el componente del bundle, su clave Applications\LabEntDemanda SOBREVIVE al siguiente arranque (inofensiva: «Unknown command»); hubo que borrarla a mano
- [x] **ENT-08** Bundle en `%ProgramData%\Autodesk\ApplicationPlugins` (todos los usuarios; la carpeta no existe): si requiere administrador y si se carga (M/J) · R: ⚠️ · Obs: crear %ProgramData%\Autodesk\ApplicationPlugins\<x>.bundle NO exige administrador (Usuarios tiene WD/AD en %ProgramData%\Autodesk), pero AutoCAD 2027 ignora el bundle: ni aviso, ni Initialize, ni clave en Applications. Control: el mismo bundle en %APPDATA%\Autodesk\ApplicationPlugins se detecta (aviso de seguridad y clave LabEntTodos). AcAutoLoader.crx contiene el valor «LoadFromProgramData» junto a «\Applications\AcadAutoLoader»; esa clave de HKLM no lo tiene. Hipótesis sin verificar (crearlo exige administrador): activar ese valor haría que se recorra ProgramData. Todo limpiado
- [x] **ENT-09** DLL compilada para `net8.0-windows` contra la API de AutoCAD 2025/2026 cargada en 2027 (A/J) · R: ✅ · Obs: DLL net8.0-windows compilada contra AutoCAD.NET 25.0.1 (API de AutoCAD 2025, lib/net8.0) cargada con NETLOAD en 2027: Initialize corre en .NET 10.0.14 y el comando crea y borra una línea sin problemas. Ojo con NuGet: AutoCAD.NET 25.0.2 y 25.1.1 traen solo lib/net10.0 (ensamblados 25.0.0.0 recompilados), así que «compilar para net8 contra 2025» exige fijar 25.0.1. Compilar net8 contra las DLL de 2027 es imposible: CS1705 (acdbmgd 26.0 usa System.Runtime 10.0)
- [x] **ENT-10** DLL compilada para .NET Framework 4.8 contra la API de AutoCAD 2024 (A/J) · R: ⚠️ · Obs: DLL net48 compilada contra AutoCAD.NET 24.3.0 (API de AutoCAD 2024, .NET Framework) cargada con NETLOAD en 2027: se carga, Initialize corre en .NET 10.0.14 y el comando crea y borra una línea. Lo que se quitó de la API falla de forma perezosa: solo el método que usa KernelDescriptor.Drawing3D lanza MissingFieldException al compilarse (JIT), capturable en el llamante; el resto de la DLL funciona. Inventario de 2024 con ApiInventory y diferencias en pruebas/resultados/api-cambios-2024-2027.tsv: 24 tipos quitados (casi todos Internal), 63 miembros quitados de tipos que siguen y 305 añadidos. Error mío durante la prueba: inserté un método entre un [CommandMethod] y el suyo y el atributo pasó al método equivocado
- [x] **ENT-11** Excepción en `IExtensionApplication.Initialize`: qué ve el usuario y si los comandos de la DLL quedan registrados (M/J) · R: ⚠️ · Obs: excepción en IExtensionApplication.Initialize, cargada por bundle con LoadOnCommandInvocation o por NETLOAD desde la ruta de confianza: el usuario no ve NADA (ni diálogo ni mensaje) y los comandos de la DLL no quedan registrados («Unknown command "LABENT"»); invocarlo otra vez no reintenta Initialize. Fallo silencioso: conviene envolver Initialize en try/catch y escribir el error en la línea de comandos
- [x] **ENT-12** Dos add-ins con Newtonsoft.Json 12 y 13: qué versión trae AutoCAD y quién gana en el contexto por defecto (A) · R: ✅ · Obs: dos complementos mínimos compilados contra Newtonsoft.Json 12.0.3 y 13.0.3 (pruebas/ent/Nj12 y Nj13, cada uno con su DLL en su carpeta de %LOCALAPPDATA%\AutoCADLab\ent12) cargados con Assembly.LoadFrom en el contexto por defecto (el de NETLOAD): antes no había ningún Newtonsoft cargado; los DOS reciben el Newtonsoft.Json 13.0.0.0 (archivo 13.0.3.27908) de la carpeta de AutoCAD, no su copia: gana siempre el de AutoCAD (la carpeta de la aplicación se sondea antes que la del complemento). Un complemento no puede usar otra versión de Newtonsoft en el contexto por defecto; su copia se ignora sin aviso
- [x] **ENT-13** Aislamiento de dependencias por add-in (equivalente a `UseRevitContext=false`): buscarlo en `PackageContents.xml` y en los bundles de Autodesk (S/A) · R: ❌ · Obs: Verificado (ent13.py), con la salvedad de que la muestra es pequeña. Solo hay 3 PackageContents.xml en las 4 raíces revisadas: AutoCAD-MCP-Server-2027.bundle (en Program Files y en Roaming) y el AutoCADLab.bundle del laboratorio. Ninguno de Autodesk, salvo el MCP, y ninguno dentro de la carpeta de AutoCAD 2027. Atributos que aparecen: ApplicationPackage (SchemaVersion, AppVersion, ProductCode, UpgradeCode, Name, Author, Description, ProductType, PreferNewestAcross), ComponentEntry (AppName, ModuleName, AppDescription, LoadOnAutoCADStartup, LoadOnRequest) y RuntimeRequirements (OS, Platform, SeriesMin, SeriesMax, SupportPath). Ninguna línea contiene context, isolat, AssemblyLoadContext, AppDomain ni sandbox. Hay 10 *.runtimeconfig.json (todos net10.0; acdbmgd.runtimeconfig.json es el del proceso): sus configProperties son solo MetadataUpdater.IsSupported, EnableUnsafeBinaryFormatterSerialization, CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS, UseNls y un Switch de WPF, sin nada de carga ni aislamiento. Solo hay un deps.json (Das.Local). Los inventarios del laboratorio (api-surface y internal) no tienen tipos ni miembros con LoadContext o Isolat. Por cadenas, acmgd.dll y acdbmgd.dll usan LoadFrom/LoadFile/AssemblyResolve, y ningún ensamblado propio de AutoCAD contiene 'AssemblyLoadContext' (solo Roslyn y TraceEvent). Conclusión: no hay equivalente documentado a UseRevitContext=false; todos los complementos comparten el contexto de carga por defecto. Esto último es una inferencia estática; comprobarlo en ejecución (AssemblyLoadContext.GetLoadContext) queda pendiente con AutoCAD.
- [x] **ENT-14** `System.Text.Json`, `HttpClient` y `System.Threading.Channels` dentro del add-in (A) · R: ✅ · Obs: dentro del add-in (contexto de aplicación): System.Text.Json serializa y deserializa con Unicode; HttpClient GET https://example.com/ → HTTP 200 (8,3 s la primera vez, con .GetAwaiter().GetResult() en el hilo de AutoCAD sin bloquearse); Channel acotado productor/consumidor en Task.Run: suma 1..1000 = 500500
- [x] **ENT-15** `BinaryFormatter`: confirmar que lanza `PlatformNotSupportedException` en .NET 10 (A) · R: ✅ · Obs: BinaryFormatter.Serialize lanza System.PlatformNotSupportedException «BinaryFormatter serialization and deserialization have been removed» (interruptor EnableUnsafeBinaryFormatterSerialization=False, fijado también en los runtimeconfig de AutoCAD). Para compilar hace falta #pragma warning disable SYSLIB0011
- [x] **ENT-16** `LOCALE` devuelve `ES` con el producto ENU: de dónde sale (registro, idioma de Windows, perfil) y si afecta a los nombres de comando o a los mensajes (A/S) · R: ✅ · Obs: LOCALE=ES sale de la configuración regional de Windows (es-PE: CurrentCulture=es-PE, InstalledUICulture=es-PE, Control Panel\International LocaleName=es-PE), no del producto: CurrentUICulture=en-US (ENU) y HKCU R26.0\ACAD-A101:409 no tiene LocaleId/Language. SYSCODEPAGE y DWGCODEPAGE = ANSI_1252. LOCALEROOTPREFIX ya no existe en 2027 (eInvalidInput). En es-PE el separador decimal es el punto, así que los números no cambian; los comandos y mensajes siguen en inglés

### SHL · Fuera de AutoCAD (sin interfaz)

- [x] **SHL-01** Consola .NET 10 que referencia `acdbmgd.dll` y opera con `Point3d`, `Vector3d` y `Matrix3d` (S) · R: ✅ · Obs: consola .NET 10 (pruebas/shl/Shl01) FUERA de AutoCAD que referencia C:\Program Files\Autodesk\AutoCAD 2027cdbmgd.dll sin copiarla (resolución desde la instalación con AssemblyLoadContext.Default.Resolving y la carpeta en PATH): Point3d, Vector3d y Matrix3d funcionan: (1,2,3)+(10,0,0) girado 90° en Z = (-2, 11, 3); |v|=10; v·Y=0
- [x] **SHL-02** Consola .NET 10 que llama a `new Database(true, true)` fuera de AutoCAD: qué excepción da (solo observar; si funcionara, detenerse y consultar por el licenciamiento) (S) · R: 💥 · Obs: Ejecutada el 2026-10-29 con la autorización explícita de la persona (antes la había bloqueado el clasificador de permisos). pruebas/shl/Shl01 con el argumento «db»: consola .NET 10 que carga acdbmgd.dll desde la instalación. SHL-01 (geometría) funciona en el mismo proceso. new Database(true, true) → «Fatal error. 0xC0000005» en <Module>.AcDbDatabase.{ctor}(AcDbDatabase*, Boolean, Boolean): violación de acceso nativa dentro del constructor de AcDbDatabase. No es una excepción .NET (el try/catch no la ve): el proceso muere en 207 ms con exit 139. Lo más probable es que falten los servicios de la aplicación anfitriona (acdbHostApplicationServices), que solo crea AutoCAD o accoreconsole; no lo he verificado. Conclusión: fuera de AutoCAD, acdbmgd sirve para la geometría (AcGe) pero no para la base de datos; no hay nada que eluda el licenciamiento y no se intentó nada más
- [x] **SHL-03** Inventario .NET por reflexión de `acdbmgd`, `acmgd` y `accoremgd` (S) · R: ✅ · Obs: 2.093 tipos y 19.679 miembros (232 obsoletos), 0 errores de reflexión; acdbmgd 1.135, acmgd 624, accoremgd 334. pruebas/resultados/api-surface-autocad-2027.tsv
- [x] **SHL-04** Inventario de `AdWindows.dll` (cinta) y `AcCui.dll` (CUIx), que no están en el inventario actual (S) · R: ✅ · Obs: pruebas/resultados/api-surface-ui-autocad-2027.tsv: AdWindows 5.4.5.2 (355 tipos, 3.196 miembros), AcCui (203 tipos, 1.721) y AcWindows (318 tipos, 1.965), 0 errores, 10 obsoletos. Existen ComponentManager.Ribbon, RibbonTab, RibbonPanel, RibbonButton y CustomizationSection(fileName[, readOnly[, showMessageBox]])
- [x] **SHL-05** Inventario de COM desde `acax26enu.tlb` y `axdb26enu.tlb`: interfaces, métodos y eventos (S) · R: ✅ · Obs: pruebas/resultados/api-surface-com-autocad-2027.tsv, por reflexión de los ensamblados de interop generados desde las .tlb (Autodesk.AutoCAD.Interop y .Interop.Common 26.0): 665 tipos, 14.294 miembros, 311 interfaces, 208 eventos, 0 errores. IAcadApplication tiene ZoomExtents/ZoomAll/ZoomWindow…, LoadArx, RunMacro y GetInterfaceObject
- [x] **SHL-06** Inventario de LISP: funciones `vl-*`, `vla-*`, `vlax-*` y `vlr-*` con `atoms-family`, guardado en un TSV (A) · R: ✅ · Obs: pruebas/resultados/api-surface-lisp-autocad-2027.tsv (símbolo, tipo en la interfaz, tipo en la consola), con pruebas/lisp/inv-lisp.lsp (atoms-family 1). Interfaz 3.849 símbolos, 2.759 funciones; consola 3.698, 2.608; todas las de la consola están en la interfaz. vl- 77, vla- 2.042, vlax- 69 y vlr- 46 en los dos: la consola tiene las 2.042 vla-* aunque vlax-get-acad-object devuelve nil. Las 151 solo de la interfaz son Express Tools (acet-*, C:BURST, C:FLATTEN…). En la interfaz se lanzó con SendStringToExecute y se recogió en una segunda prueba (SHL-06a/b)
- [x] **SHL-07** Los 232 miembros `[Obsolete]` del inventario .NET clasificados por área, con su mensaje de sustitución (S) · R: ✅ · Obs: pruebas/resultados/obsoletos-autocad-2027.tsv: 231 (no 232: el grep anterior contaba texto, no atributos). 213 en DatabaseServices, 10 en EditorInput; 130 mensajes distintos y solo 1 sin mensaje. Los más repetidos: «Use overload taking IntPtr instead» (13) y los de Table, que remiten a Cells[row, column]
- [x] **SHL-08** Tipos públicos de `Autodesk.AutoCAD.Internal` (sin documentar): cuántos hay, cuáles serían útiles y el riesgo de usarlos (S) · R: ⚠️ · Obs: pruebas/resultados/internal-autocad-2027.tsv: 356 tipos y 3.388 miembros en espacios *.Internal* (213 en Autodesk.AutoCAD.Internal, 45 en Reactors). Internal.Utils tiene 222 miembros; útiles a priori: GetLastCommandLines (historial de comandos: ¿fuente de verdad?), AddCommand/RemoveCommand (comandos dinámicos), SetUndoMark, PostCommandPrompt, FlushGraphics. Riesgo de usarlos sin documentación: por medir en pruebas A

### TRX · Transacciones, bloqueos y deshacer

- [x] **TRX-01** Modificar el documento activo sin DocumentLock desde el contexto de aplicación (A) · R: ✅ · Obs: rechazado con eLockViolation, como se esperaba
- [x] **TRX-02** Modificar el documento activo con DocumentLock y deshacer con Abort (A) · R: ✅ · Obs: funciona; tras Abort, IsErased=True
- [x] **TRX-03** Transacciones anidadas: `Abort` de la interna conserva lo hecho en la externa (A) · R: ⚠️ · Obs: en una base lateral Database(true,true) el Abort de la interna NO deshace: quedan 2 entidades. Causa verificada con control (TRX-06c/d/e): la base lateral nace con UndoRecording=False y sin grabación de deshacer no hay rollback. En el documento activo, o con db.DisableUndoRecording(false), sí funciona
- [x] **TRX-04** `StartOpenCloseTransaction` frente a `StartTransaction`: mismo resultado y tiempo leyendo 100.000 entidades (A/P) · R: ✅ · Obs: 100.000 líneas: StartTransaction 38 ms (repetida 32), StartOpenCloseTransaction 51 ms; misma suma de longitudes. En lectura pura, OpenClose no es más rápida aquí
- [x] **TRX-05** Olvidar `AddNewlyCreatedDBObject`: qué pasa con la entidad (excepción, fuga o nada) (A) · R: ✅ · Obs: sin AddNewlyCreatedDBObject la entidad queda en el espacio modelo y se lee bien después (longitud 5); sin excepción ni aviso. El envoltorio .NET no se libera (IsDisposed=False): posible fuga del objeto gestionado
- [x] **TRX-06** Salir del `using` sin `Commit`: la transacción se aborta en silencio (A) · R: ⚠️ · Obs: en una base lateral, salir del using sin Commit NO aborta: la entidad queda. Misma causa que TRX-03 (UndoRecording=False en bases laterales). Controles: documento activo 0 de más (TRX-06c); base lateral con DisableUndoRecording(false) 0 de más (TRX-06d); base lateral tal cual 1 de más (TRX-06e)
- [x] **TRX-07** `UpgradeOpen` de un objeto abierto para lectura y abrir para escritura un objeto de una capa bloqueada (`ErrorStatus` exacto) (A) · R: ✅ · Obs: UpgradeOpen funciona (IsWriteEnabled=True). ForWrite de una entidad en una capa bloqueada: ErrorStatus OnLockedLayer; con GetObject(id, ForWrite, false, true) se abre y se modifica
- [x] **TRX-08** Usar un `DBObject` después de liberar su transacción: excepción capturable o crash (A) · R: 💥 · Obs: tras salir del using de la transacción: IsDisposed=False y LEER Length funciona (100.000); ESCRIBIR EndPoint muestra «AutoCAD Error Aborting — INTERNAL ERROR: !dbobji.cpp@8753: eNotOpenForWrite» y AutoCAD se cierra. No hay excepción .NET que capturar (2026-10-28 05:53, base lateral)
- [x] **TRX-09** Cuántas entradas de deshacer deja una transacción del runner, y si `UNDO` lanzado después por `SendStringToExecute` la deshace entera (A/M) · R: ✅ · Obs: una transacción del runner (3 líneas en el documento activo) = un solo paso de deshacer: UNDO 1 por SendStringToExecute borró las 3. El número de entradas de la pila no se midió
- [x] **TRX-10** `Database.DisableUndoRecording(true)` en una edición masiva: ahorro de tiempo y memoria y si después se puede deshacer (P) · R: ⚠️ · Obs: 20.000 líneas en el documento activo: con deshacer 81 ms (+13 MB), repetida 81 ms (+12 MB), sin deshacer 74 ms (+12 MB). Ahorro pequeño (~9 %) a esta escala. No se probó si después se puede deshacer
- [x] **TRX-11** Excepción a mitad de una transacción con 1.000 cambios: el dibujo queda intacto (huella de handles y coordenadas) (A) · R: ⚠️ · Obs: base lateral: una excepción a mitad (500 de 1.000 entidades movidas) deja el dibujo CAMBIADO (huella distinta), porque sin UndoRecording no hay rollback. En el documento activo, o con DisableUndoRecording(false), la huella queda igual (TRX-06c/d). Riesgo real en el procesamiento por lotes con bases laterales
- [x] **TRX-12** `LockDocument` sobre un documento abierto que no es el activo, desde el contexto de aplicación (A) · R: ✅ · Obs: DocumentCollectionExtension.Add abre Drawing2 y lo ACTIVA; se reactivó Drawing1 con MdiActiveDocument. Sobre el documento no activo: sin bloqueo LockViolation; con LockDocument se modifica. CloseAndDiscard lo cierra
- [x] **TRX-13** Transacción gigante: 100.000 entidades en un `Commit` (tiempo de crear frente a confirmar, y memoria) (P) · R: ✅ · Obs: 100.000 líneas en una base lateral: crear 68 ms (+62 MB), Commit 39 ms (+32 MB más)

### CTX · Contexto de ejecución e hilos (el límite estructural)

- [x] **CTX-01** Leer y escribir la base de datos desde `Task.Run`: qué protege AutoCAD y qué no (riesgo de 💥) (A) · R: ⚠️ · Obs: AutoCAD NO comprueba el hilo de las llamadas a la base de datos, pero el acceso concurrente lo rompe. (a) Task.Run con el hilo principal esperando (Wait): leer con OpenCloseTransaction y con StartTransaction funciona; escribir sin LockDocument da eLockViolation (igual que en el hilo principal); escribir con LockDocument tomado desde el hilo de fondo funciona; una base lateral propia del hilo (new Database + ReadDwgFile) funciona. (b) CONCURRENCIA REAL (CTX-01b): el hilo principal y un Task.Run añaden 500 círculos cada uno a la vez, cada uno con su LockDocument: el principal termina sus 500; el de fondo falla en el primero con InvalidOperationException «The calling thread must be STA» y se queda BLOQUEADO más de 60 s (0/500); además AutoCAD muestra el diálogo de excepción no controlada «The calling thread cannot access this object because a different thread owns it» con pila Application.OnIdle → RibbonPaletteSet.AcadApplication_Idle → Dispatcher.VerifyAccess: liberar un LockDocument en un hilo de fondo hace que AutoCAD dispare su evento Idle EN ESE HILO, y los manejadores de interfaz (la cinta) revientan. No hubo 💥, pero la instancia quedó con un hilo atascado y se cerró. Conclusión: base del documento solo desde el hilo principal; en hilos de fondo, solo bases laterales propias
- [x] **CTX-02** `async/await` dentro de un comando: en qué hilo y en qué contexto sigue el código después del `await` (A) · R: ⚠️ · Obs: dentro de ExecuteInCommandContextAsync, tras await Task.Delay o Task.Yield, el código sigue en el hilo principal (hilo 2) pero FUERA del contexto de comando (appCtx=True, CMDACTIVE=0); entonces CommandAsync lanza eInvalidInput y, sin try, la excepción se pierde (nadie espera la Task). Solo los await de CommandAsync conservan el contexto (CMDACTIVE=33)
- [x] **CTX-03** `DocumentCollection.ExecuteInApplicationContext` llamado desde un comando: cuándo se ejecuta el callback (A) · R: ✅ · Obs: ExecuteInApplicationContext llamado en contexto de comando: el callback corre EN EL ACTO (antes de volver), con appCtx=True y el comando aún activo (CMDACTIVE=33). Llamado ya en contexto de aplicación: también en el acto
- [x] **CTX-04** `ExecuteInCommandContextAsync` desde el runner: `Editor.CommandAsync` dentro funciona y espera a que termine el comando (A) · R: ✅ · Obs: ExecuteInCommandContextAsync desde el runner devuelve en el acto (ExecutionResult) y el callback corre después, cuando AutoCAD queda libre, con CMDNAMES='EXECUTEFUNCTION'. Dentro, await ed.CommandAsync("_.REGEN") funciona dos veces seguidas. Ver CTX-02 para el límite
- [x] **CTX-05** `Editor.Command` llamado directamente desde el contexto de aplicación (el runner): excepción esperada y su mensaje (A) · R: ✅ · Obs: Editor.Command desde el contexto de aplicación lanza Autodesk.AutoCAD.Runtime.Exception eInvalidInput
- [x] **CTX-06** `Document.SendStringToExecute` desde el runner: es asíncrono; cuándo se ejecuta y cómo saber que terminó (archivo testigo, `CommandEnded`) (A/P) · R: ✅ · Obs: SendStringToExecute es asíncrono: no se ejecuta dentro de la llamada, ni esperando 500 ms con Thread.Sleep; corre cuando AutoCAD queda libre (~1,3 s después, contando el final del lab run). Para saber que terminó: un archivo testigo escrito desde LISP
- [x] **CTX-07** 1.000 órdenes seguidas al `Dispatcher` y 1.000 `SendStringToExecute`: cuántas se ejecutan y en qué orden (P) · R: ✅ · Obs: 1.000 BeginInvoke al Dispatcher y 1.000 SendStringToExecute: todas ejecutadas, en orden y sin duplicados (encolar las 1.000 SendStringToExecute: 109 ms). En Revit, 1.000 Raise() daban 1 ejecución
- [x] **CTX-08** `Application.Idle`: frecuencia con AutoCAD en primer y en segundo plano, y consumo de CPU (P) · R: ⚠️ · Obs: con AutoCAD en segundo plano y 3 s de bombeo con un DispatcherFrame anidado, Application.Idle se disparó 0 veces (CPU 219 ms). Falta medirlo fuera de un marco anidado y en primer plano (con el usuario)
- [x] **CTX-09** Orden del pipe con un diálogo modal abierto (por ejemplo OPTIONS): si el `Dispatcher` la ejecuta con el modal abierto, medido con `IsWindowEnabled` y `Application.IsQuiescent` (M) · R: ✅ · Obs: con OPTIONS abierto por la persona: la orden del pipe (CTX-SONDA, vía Dispatcher) SE EJECUTA en ~2 s y crea un círculo con LockDocument; estado durante la orden: ventana principal deshabilitada (IsWindowEnabled=False), CMDNAMES=OPTIONS, CMDACTIVE=1, IsQuiescent=False, IsApplicationContext=False (!) y documento en Write. Comprobado después en el dibujo: 3 sondas = 3 círculos. El runner no detecta solo que hay un modal abierto: habría que mirar IsWindowEnabled o CMDNAMES
- [x] **CTX-10** Orden del pipe mientras un comando interactivo espera un punto (LINE): `IsQuiescent`, `DocumentLock` y resultado (M/A) · R: ✅ · Obs: con LINE esperando el segundo punto (y antes, con COPYCLIP a medias lanzado sin querer por Ctrl+C en la ventana web): la orden del pipe se ejecuta y crea el círculo (appCtx=False, quiescente=False, CMDNAMES=LINE, ventana habilitada), y el LINE de la persona sigue vivo y termina bien. Es decir: el runner modifica el dibujo en mitad de un comando interactivo sin que AutoCAD lo impida (ver la nota de docs/estado-y-pendientes.md sobre rechazar órdenes si IsQuiescent es falso)
- [x] **CTX-11** Operación de 60 s en el hilo principal: si la interfaz responde, y con `ProgressMeter` (M) · R: ✅ · Obs: 30 s de trabajo puro en el hilo principal (contexto de aplicación, desde el runner) con ProgressMeter.SetLimit/Start/MeterProgress: la barra de progreso aparece en la barra de estado con su texto y avanza. AutoCAD no procesa mensajes: SendMessageTimeout(WM_NULL) agota 1 s; IsHungAppWindow pasa a True a los ~11 s tras un clic real en la cinta (a los 4 s aún False) y vuelve a False al terminar. El clic se pierde o se procesa al final. TRAMPA para automatizar: con AutoCAD ocupado, PrintWindow y ShowWindow/SetForegroundWindow BLOQUEAN al que llama (tools/entrada.py quedó 25 s parado en «frente»): usar ImageGrab, IsHungAppWindow y SendInput sin traer la ventana al frente
- [x] **CTX-12** Cancelar un bucle largo con Esc sondeando `HostApplicationServices.UserBreak` y deshacer lo hecho (M) · R: ✅ · Obs: UserBreak() SOLO detecta Esc dentro de un comando registrado. [CommandMethod] LABBRK5, lanzado y cancelado por la persona: Esc físico (GetAsyncKeyState) a los 5.444 ms y UserBreak true a los 5.445 ms; el bucle se corta (272 vueltas). Control sin Esc: 20 s y fin normal. En cambio, con Esc real (SendInput) y el estado físico de la tecla confirmado, UserBreak NO lo ve nunca en ExecuteInCommandContextAsync (CTX-12d) ni en contexto de aplicación (CTX-12c, el del runner, las paletas y los botones), ni con DoEvents en cada vuelta (CTX-12b), ni con Esc por PostMessage. Consecuencia: el código lanzado desde una paleta, la cinta o ExecuteInCommandContextAsync no se puede cancelar con UserBreak; usar GetAsyncKeyState o lanzar un comando registrado. Trampas del montaje: un comando duplicado (eDuplicateKey) corta el registro del resto de comandos de su clase al recargar el payload (LABBRK2 y LABBRK3 nunca existieron); el Alt usado para traer AutoCAD al frente encendía los KeyTips de la cinta y desviaba la entrada (un LABBRK4 acabó en VARS2SCR); COM SendCommand es síncrono y bloquea hasta que termina el comando. Sin verificar si un Esc por SendInput también corta el [CommandMethod] (los intentos automáticos fallaron por el montaje)
- [x] **CTX-13** Pipe con varias instancias: `eco` responde mientras AutoCAD está ocupado 10 s (A) · R: ✅ · Obs: con AutoCAD ocupado 10 s en el hilo principal, lab eco respondió 4 veces en ~65 ms; lab ping esperó a que terminara (5 s)
- [x] **CTX-14** Dos `acad.exe` a la vez, cada uno con su runner (`autocadlab-<pid>`): licencia y rendimiento conjunto (P) · R: ✅ · Obs: dos instancias propias (42076 y 49568) a la vez, cada una con su runner (pipe autocadlab-<pid>), más una tercera abierta por la persona desde VS (51584): sin avisos de licencia (EDUCATION). EXP-13 (leer 100 DWG con ReadDwgFile) solo: 508–569 ms; las dos a la vez: 612–708 ms cada una, es decir el doble de trabajo en ~0,7 s frente a ~1,04 s seguido (×1,5)

### ELM · Entidades, bloques, capas y referencias

- [x] **ELM-01** Crear una línea en un dibujo en memoria (Database lateral) (A) · R: ✅ · Obs: longitud 10 exacta
- [x] **ELM-02** Polilínea con segmentos de arco (bulge): área y longitud exactas frente al cálculo manual (A) · R: ✅ · Obs: polilínea cerrada 10×10 con un lado en semicírculo: área 139,269908170 y longitud 45,707963268, exactas. Error propio en el 1.er intento: con bulge -1 el arco quedó hacia dentro (área 60,73); en un recorrido antihorario, el arco hacia fuera es bulge +1
- [x] **ELM-03** Círculo, arco, elipse y spline: longitudes con `GetDistAtPoint` y puntos con `GetClosestPointTo` (A) · R: ✅ · Obs: longitudes con GetDistanceAtParameter: círculo r=5 → 10π exacto; arco de 90° → 2,5π; elipse a=10, b=5 → 48,442241 (Ramanujan 48,442105); spline por 3 puntos 14,572; GetClosestPointTo y GetDistAtPoint exactos
- [x] **ELM-04** Sombreado (`Hatch`) con isla y contorno asociativo: área, y si se actualiza al mover el contorno por código (A/V) · R: ⚠️ · Obs: el sombreado SOLID con isla y contorno asociativo se crea sin error (SetHatchPattern, Associative, AppendLoop ×2, EvaluateHatch, Commit), pero leer Hatch.Area lanza eNotApplicable. En ELM-04b, 7 variantes (asociativo o no, con o sin isla, contorno por ids o por vértices, base lateral como WorkingDatabase, documento activo, patrón ANSI31) dan siempre eNotApplicable. Alternativa que funciona: calcular el área desde GetLoopAt(i).Polyline (400,0). No se pudo medir si se actualiza al mover el contorno. Pendiente: control con un sombreado creado por el comando HATCH
- [x] **ELM-05** `DBText` con alineación distinta de la izquierda en una base lateral: `AlignmentPoint` y `AdjustAlignment` (A) · R: ⚠️ · Obs: DBText centrado (TextCenter, AlignmentPoint (10,10)) en una base lateral: AdjustAlignment(db) NO cambia Position (sigue en (0,0)) aunque se le pasa la base. Solo funciona con HostApplicationServices.WorkingDatabase = la base lateral: Position (0,4167, 10). Trampa C5: el parámetro no basta
- [x] **ELM-06** `MText` con códigos de formato (`\P`, `\C`, `\f`) y acentos: `Contents` y `ActualWidth` (A) · R: ✅ · Obs: MText con \P, \C1; y \fArial|b1; y acentos/ñ: Contents se conserva tal cual; Text da el texto sin formato («Línea uno\r\nÁrea roja y negrita ñandú»); ActualWidth 55,394 y ActualHeight 7,5 en una base lateral
- [x] **ELM-07** Bloque con atributos: definir, insertar y rellenar con `AttributeReference.SetAttributeFromBlock` (A) · R: ✅ · Obs: bloque PUERTA con AttributeDefinition NUM; inserción girada 90°; AttributeReference con SetAttributeFromBlock(def, BlockTransform) y TextString P-101: se lee NUM=P-101 en (4,5,5,0), con giro 1,5708 (sigue al bloque)
- [x] **ELM-08** Bloque dinámico: leer y cambiar `DynamicBlockReferenceProperty` (distancia y visibilidad) en un bloque de muestra (A) · R: ✅ · Obs: muestra Sample\en-us\Dynamic Blocks\Architectural - Metric.dwg: 9 bloques dinámicos. Insertados y modificados: Door - Metric (Door Size 750 → 1000, de 6 valores permitidos; visibilidad Opening Angle «Open 30º» → «Open 45º»), Aluminum Window (Window Width 900 → 1500) y Stud (Stud Height 89 → 286; Stud Size «50 x 300» → «50 x 100»). Cada referencia pasa a un bloque anónimo *U13…*U15 y DynamicBlockTableRecord sigue apuntando a la definición. Error propio en el 1.er intento: ReadDwgFile sobre Database(true, true) (con dibujo por defecto) dio IsDynamicBlock=False en todos; con Database(false, true) sale bien
- [x] **ELM-09** Crear un bloque dinámico desde cero (parámetros y acciones) por alguna vía (por verificar; se espera que no haya API) (S/A) · R: ❌ · Obs: no hay API .NET para crear un bloque dinámico: 0 clases públicas de parámetros o acciones de bloque (Block*Parameter/Block*Action) en acdbmgd/acmgd/accoremgd. Solo se pueden leer y cambiar las propiedades de bloques dinámicos ya hechos (ELM-08). Vías alternativas sin probar: copiar el bloque de un DWG plantilla con WblockCloneObjects, o BEDIT a mano
- [x] **ELM-10** Capas: crear, color, tipo de línea, transparencia, inutilizar y bloquear; borrar una capa en uso (A) · R: ⚠️ · Obs: capa con color 1, transparencia 50 % (alfa 127), inutilizada y bloqueada: se crea y se lee bien, PERO asignar Transparency antes de añadirla a la tabla lanza eNoDatabase (Color, IsFrozen e IsLocked sí se pueden antes). Borrar una capa EN USO con Erase() FUNCIONA sin error: la línea queda en una capa borrada (el comando LAYDEL o PURGE no lo permitirían). Borrar la capa 0: CannotBeErasedByCaller
- [x] **ELM-11** Cargar un tipo de línea con `LoadLineTypeFile` desde `acadiso.lin` y asignarlo (A) · R: ✅ · Obs: LoadLineTypeFile("DASHED", "acadiso.lin") en una base lateral: carga (2 trazos, patrón 19,05) y se asigna a una capa. Cargarlo otra vez lanza DuplicateRecordName: hay que comprobar antes con LinetypeTable.Has
- [x] **ELM-12** Xrefs: adjuntar, superponer, recargar, desenlazar y ligar (`AttachXref`, `OverlayXref`, `ReloadXrefs`, `DetachXref`, `BindXrefs`) (A) · R: ✅ · Obs: en una base lateral: AttachXref y OverlayXref de un DWG de 4 líneas → Resolved, 4 entidades (IsFromOverlayReference distingue los dos); ReloadXrefs ok; BindXrefs(insertBind=true) lo convierte en bloque normal (NotAnXref, 4 entidades); DetachXref lo borra
- [x] **ELM-13** Copiar entidades entre dibujos con `WblockCloneObjects` y `DuplicateRecordCloning.Replace`: conflictos de capas y bloques (A) · R: ✅ · Obs: WblockCloneObjects de una línea en la capa COMPARTIDA (color 1) a un dibujo que ya tiene COMPARTIDA (color 5): con Ignore la capa del destino se queda con color 5; con Replace pasa a color 1. La línea copiada queda en COMPARTIDA en los dos casos
- [x] **ELM-14** Transformaciones con `TransformBy` (`Matrix3d.Rotation`, `Displacement`, simetría) y matrices asociativas (ARRAY): ¿hay API .NET para crearlas? (A/S) · R: ⚠️ · Obs: TransformBy con Matrix3d.Rotation y Mirroring(Line3d): resultados correctos con errores de redondeo del orden de 1e-16. SÍ hay API .NET de matriz asociativa (AssocArray.CreateArray con AssocArrayRectangularParameters y VertexRef), pero solo en un documento: en una base lateral devuelve null sin excepción (también con WorkingDatabase); en el documento activo crea un BlockReference
- [x] **ELM-15** Sólidos 3D: `CreateBox`, `BooleanOperation`, `Slice` y `MassProperties` con volumen exacto (A) · R: ✅ · Obs: CreateBox(10,10,10) centra la caja en el origen; unión con otra desplazada (5,5,5): volumen 1.875,000000 exacto; Slice por el plano z=0: 1.375,000000 exacto. Tras BoolUnite, el sólido b no queda borrado (IsErased=False): hay que borrarlo a mano
- [x] **ELM-16** Regiones con `Region.CreateFromCurves` y área de una región con hueco (A) · R: ✅ · Obs: Region.CreateFromCurves de un cuadrado 10×10 menos un círculo r=2 (BoolSubtract): área 87,433629386 exacta; perímetro 52,566
- [x] **ELM-17** Datos propios: XData (`RegAppTableRecord`), diccionario de extensión y `Xrecord`; si viajan al copiar, al hacer `Wblock` y al pasar por DXF (A) · R: ⚠️ · Obs: XData (RegApp AUTOCADLAB, 1000 y 1040) y diccionario de extensión con Xrecord: se conservan con DeepCloneObjects y con Database.Wblock(). Falta la ida y vuelta por DXF (EXP-03)
- [x] **ELM-18** Restricciones geométricas y paramétricas por código (`AssocManager`, `AssocVariable`): crear una y que se resuelva (S/A) · R: ⚠️ · Obs: variables paramétricas SÍ: AssocNetwork.GetInstanceFromObject(espacio modelo) + AssocVariable (AddDBObject, AddAction, SetName, SetExpression) en una base lateral: ancho=12, area=ancho*3 → EvaluateTopLevelNetwork True, area 36; al cambiar ancho a 20, area 60. Restricciones GEOMÉTRICAS no: no hay Assoc2dConstraintAPI ni equivalente público en .NET (0 tipos en el inventario); habría que usar los comandos GEOMCONSTRAINT/DIMCONSTRAINT
- [x] **ELM-19** Purgar con `Database.Purge` en bucle hasta que no quede nada que purgar (A) · R: ✅ · Obs: Database.Purge en bucle: ronda 1 purga SIN_USO_1, SIN_USO_2 y el bloque SIN_USO; ronda 2, la capa SOLO_EN_BLOQUE (solo se libera al borrar el bloque); ronda 3, nada. Las dependencias anidadas exigen repetir
- [x] **ELM-20** Imagen raster (`RasterImageDef`) y PDF subyacente (`PdfDefinition`/`PdfReference`) adjuntados por código (A/V) · R: ✅ · Obs: raster: RasterImageDef (PNG de VIS-06, Load → IsLoaded, 800×435 px) + RasterImage con Orientation y AssociateRasterDef en una base lateral. PDF (ELM-20b): diccionario ACAD_PDFDEFINITIONS (UnderlayDefinition.GetDictionaryKey), PdfDefinition con SourceFileName = PDF trazado en accoreconsole (CON-10) e ItemName «1», Load(null) → Loaded True, PdfReference de 2.999,5×2.119,6 unidades
- [x] **ELM-21** Identidad persistente: el `Handle` de una entidad tras guardar, cerrar, reabrir y `Database.GetObjectId` (A) · R: ✅ · Obs: el Handle de una línea se conserva al guardar (SaveAs) y reabrir con ReadDwgFile: TryGetObjectId lo encuentra con las mismas coordenadas

### ANO · Anotación

- [x] **ANO-01** Cotas alineada y girada: `Measurement` exacto y texto sustituido (A/V) · R: ✅ · Obs: AlignedDimension (0,0)-(3,4): Measurement 5 exacto, ya antes de RecomputeDimensionBlock; RotatedDimension a 0°: 3; texto sustituido «<> m (aprox.)» con medida 10
- [x] **ANO-02** Cota asociativa creada por código: si se actualiza al mover la geometría (por verificar si .NET crea la asociación) (A) · R: ❌ · Obs: una AlignedDimension creada por código sobre los extremos de una línea NO es asociativa: al alargar la línea de 10 a 15, la cota sigue midiendo 10. La API .NET no crea la asociación directamente (habría que pasar por el comando DIMREASSOCIATE o por AssocManager, por verificar)
- [x] **ANO-03** Estilo de cota nuevo (`DimStyleTableRecord`) y sobrescrituras por cota (A) · R: ✅ · Obs: DimStyleTableRecord LAB (Dimtxt 3,5, Dimdec 3) y sobrescritura por cota (cota.Dimtxt = 5) sin tocar el estilo
- [x] **ANO-04** Directriz múltiple (`MLeader`) con contenido MText y con bloque (A/V) · R: ✅ · Obs: MLeader con SetDatabaseDefaults(db), ContentType MTextContent y AddLeaderLine: 1 línea de directriz, contenido conservado, extensión calculada en una base lateral. Falta la variante con bloque
- [x] **ANO-05** Tabla (`Table`): tamaño, combinar celdas, formato y fórmula en una celda (A/V) · R: ✅ · Obs: Table 4×3 en el documento activo con MergeCells(CellRange.Create(t,0,0,0,2)): la captura muestra la fila 0 combinada sobre las 3 columnas con el título. La duda de la primera prueba se explica: Cells[r,c].IsMerged solo es True en la celda de ANCLAJE (0,0) y False en (0,1) y (0,2); para saber si una celda está dentro de una combinación usar Table.IsMergedCell(r,c,out rango) o GetMergeRange (las tres dan 0,0-0,2; la (1,1), False). Fórmula «=A2+A3» evaluada (prueba anterior). Ojo con la escala: con el estilo de tabla de acad.dwt (imperial) la tabla mide 3×2,3 unidades
- [x] **ANO-06** Campo en un MText con el área de una polilínea: `Database.EvaluateFields` y si se actualiza al cambiar la polilínea (A) · R: ✅ · Obs: campo %<\AcObjProp Object(%<\_ObjId OldIdPtr>%).Area>% en un MText: antes de evaluar «####»; Database.EvaluateFields() → «100.00»; al agrandar la polilínea el texto NO se actualiza solo («100.00») hasta volver a llamar a EvaluateFields («200.00»)
- [x] **ANO-07** Tabla vinculada a datos externos (`DataLink`, `Table.SetDataLink`) desde un CSV o un XLSX (A) · R: ✅ · Obs: DataLink con DataAdapterId «AcExcel» a un XLSX creado con openpyxl, en el documento activo: con ConnectionString = ruta sola, Update y GetUpdateStatus dicen OK pero las celdas quedan VACÍAS (es el fallo silencioso de la primera prueba con un CSV); con «ruta.xlsx!Hoja1» o «ruta.xlsx!Hoja1!A1:B3» la tabla se rellena (Pieza, Tornillo, 30). Los valores llegan con el formato de Excel como códigos de MText («{Calibri|b0|i0|c0;Pieza»): para el texto limpio, GetTextString(FormatOption.ForEditing) o quitar los códigos. Sin comprobar si hace falta Excel instalado (no se ha mirado si lo está) ni CSV con «!hoja»
- [x] **ANO-08** Objetos anotativos: `Annotative`, `AddContext` con dos escalas y visibilidad en ventanas con escalas distintas (A/V) · R: ✅ · Obs: MText con Annotative = True y AddContext de las escalas 1:50 y 1:100 de ObjectContextManager.GetContextCollection("ACDB_ANNOTATIONSCALES") (17 escalas en una base nueva): HasContext True en las dos. La visibilidad en ventanas con escalas distintas no se probó (V)
- [x] **ANO-09** Cotas de arco, radio, coordenada y angular de 3 puntos (A) · R: ✅ · Obs: ArcDimension 2,5π, RadialDimension 5, OrdinateDimension X 7 y Point3AngularDimension π/2: Measurement exacto en las cuatro
- [x] **ANO-10** 1.000 cotas y 1.000 `MLeader` en lote: tiempo por elemento (P) · R: ✅ · Obs: base lateral: 1.000 AlignedDimension en 1 ms (sin calcular el bloque de la cota hasta el Commit), 1.000 MLeader en 66 ms, Commit 91 ms

### VIS · Espacio modelo y papel, presentaciones y vistas

- [x] **VIS-01** Presentaciones: crear, renombrar, clonar y borrar con `LayoutManager` (A) · R: ✅ · Obs: LayoutManager.Current sobre una base lateral (con WorkingDatabase = la base): CreateLayout, RenameLayout, CloneLayout y DeleteLayout funcionan; un nombre repetido da DuplicateKey; borrar Model da DelIsModelSpace
- [x] **VIS-02** Ventana gráfica en espacio papel con escala estándar exacta (`StandardScale`) y vista centrada en una zona del modelo (A/V) · R: ⚠️ · Obs: Viewport en una presentación nueva de una base lateral: StandardScale 1:50 → CustomScale 0,02 exacto, ViewCenter y ViewHeight bien. Pero On = true da eNotInPaperspace si la presentación no es la actual, y hacerla actual con LayoutManager.Current.CurrentLayout sobre una base lateral (WorkingDatabase) 💥 CERRÓ AutoCAD sin aviso (2026-10-28 06:35). Sin On, el resto se configura y se guarda en el DWG. Falta la verificación visual
- [x] **VIS-03** Inutilizar capas en una sola ventana (`FreezeLayersInViewport`) (A/V) · R: ✅ · Obs: FreezeLayersInViewport con una capa: IsLayerFrozenInViewport True en esa ventana y la capa sigue sin inutilizar en el resto del dibujo (IsFrozen False). Funciona aunque la ventana no esté encendida. Falta la verificación visual
- [x] **VIS-04** Cambiar entre espacio modelo y papel por código (`SwitchToModelSpace`, `TileMode`) desde el contexto de aplicación (A) · R: ✅ · Obs: Database.TileMode = false/true en el documento activo desde el contexto de aplicación (con LockDocument): cambia a Layout1 y vuelve al modelo sin error
- [x] **VIS-05** Vistas guardadas (`ViewTableRecord`) y `Editor.SetCurrentView`; zoom extensión sin método .NET directo (por COM o comando) (A/V) · R: ✅ · Obs: ViewTableRecord «LAB» guardada desde Editor.GetCurrentView; zoom extensión por COM dentro de AutoCAD con dynamic Application.AcadApplication.ZoomExtents() (en accoreconsole ese acceso tumba el proceso, CON-03): la captura muestra la línea en (-500,-300) y el círculo en (800,600) en los bordes (control visual); Editor.SetCurrentView restaura la vista guardada
- [x] **VIS-06** Captura del dibujo con `DocumentExtension.CapturePreviewImage` como evidencia visual: tamaño máximo y si refleja el estado tras la transacción (V) · R: ✅ · Obs: DocumentExtension.CapturePreviewImage(doc, w, h) devuelve un Bitmap que CONSERVA LA PROPORCIÓN DE LA VENTANA e ignora la pedida: 800×600 → 800×435, 2000×1500 → 2753×1500, 4096² → 7519×4096, 10000² → 18357×10000 (sin un límite pequeño). Refleja en el acto lo creado en la misma llamada, sin Regen (VIS-06d, control visual: círculo nuevo visible en la captura). La 1.ª captura salió vacía, probablemente porque la vista no abarcaba el origen (no medido). Sirve como evidencia visual: Claude puede abrir el PNG y juzgarlo
- [x] **VIS-07** Estilos visuales (`VisualStyle`) en una vista 3D y captura (V) · R: ✅ · Obs: 18+ estilos visuales en el diccionario (2dWireframe, Conceptual, Realistic, Shaded…). ViewportTableRecord actual: VisualStyleId = Realistic y ViewDirection (1,-1,1) + Editor.UpdateTiledViewportsFromDatabase: la captura muestra la caja 3D sombreada en isométrico (control visual); la altura de vista pedida (250) no se aplicó y la caja sale pequeña. Restaurado el estilo anterior. Error propio en el 1.er intento: recorrer DBDictionary con LINQ Cast<DBDictionaryEntry> da InvalidCastException (el enumerador no tipado devuelve DictionaryEntry); con foreach funciona
- [x] **VIS-08** Estados de capa (`LayerStateManager`): guardar, cambiar capas y restaurar (A) · R: ✅ · Obs: LayerStateManager: SaveLayerState(Frozen|Locked|Color); se inutiliza, bloquea y cambia el color de la capa; RestoreLayerState la deja como estaba (no inutilizada, no bloqueada, color 3)
- [x] **VIS-09** SCP (`UcsTableRecord`): coordenadas que devuelve `Editor.GetPoint` con un SCP girado (A/M) · R: ✅ · Obs: SCP girado 30° con origen (10,10) puesto con Editor.CurrentUserCoordinateSystem y Editor.GetPoint en contexto de comando, con un clic REAL (SendInput) en el sitio de la pantalla del punto (100,40,0) del SCU: Value=(93,05; −18,94), es decir, en COORDENADAS DEL SCP (el (100,40) del SCU en el SCP es (92,94; −19,02); la diferencia es la precisión del clic); con Value.TransformBy(CurrentUserCoordinateSystem) se obtiene el SCU (100,06; 40,12). UCSORG=(10,10,0). El SCP se restaura a Identity al terminar
- [x] **VIS-10** 500 presentaciones con una ventana cada una: tiempo (P) · R: ✅ · Obs: LÍMITE: un DWG admite 256 presentaciones contando el modelo (Model + Layout1 + Layout2 + 253 creadas); LayoutManager.CreateLayout de la 254.ª lanza eMaxLayouts, así que las 500 del plan son imposibles. En una base lateral puesta como WorkingDatabase (sin cambiar CurrentLayout, que en VIS-02 tumbaba AutoCAD): 253 presentaciones con una Viewport cada una en 7 ms; SaveAs 8 ms, 39 KB

### EXP · Exportación, versiones y lotes dentro de AutoCAD

- [x] **EXP-01** Trazar una presentación a PDF con `PlotEngine` y `DWG To PDF.pc3` (`BACKGROUNDPLOT=0`) (A/V) · R: ✅ · Obs: PlotEngine (PlotFactory.CreatePublishEngine + PlotProgressDialog oculto) con DWG To PDF.pc3, BACKGROUNDPLOT=0 (restaurado), medio ISO_A4 elegido de GetCanonicalMediaNameList, ScaleToFit, girado 90°: Model (Extents) → PDF vectorial de 2 KB en 110 ms y Layout1 (Layout) → 2,3 KB en 156 ms; renderizados con PyMuPDF muestran los 5 círculos y las 5 líneas (Layout1 con el marco de su ventana). TRAMPAS: PlotEngine solo traza la presentación ACTIVA (eLayoutNotCurrent en PlotInfoValidator.Validate): hay que hacerla actual (LayoutManager.Current.CurrentLayout) y restaurarla; SetPlotCentered lanza eInvalidInput con PlotType.Layout (con Extents funciona)
- [x] **EXP-02** PDF multipágina con `Publisher.PublishExecute` y `DsdData` (A/V) · R: ✅ · Obs: Publisher.PublishExecute(DsdData MultiPdf, PlotConfigManager.SetCurrentConfig("DWG To PDF.pc3")) de un DWG CERRADO creado en base lateral, entradas Model + Layout1 + Layout2: PDF de 3 páginas A4 con el contenido de cada una en ~0,8 s (verificado renderizando). TRAMPA GRAVE: si las presentaciones no tienen impresora asignada (las de una base lateral están en «None»), PublishExecute no avisa de nada y produce un PDF de UNA página carta en blanco: hay que asignar DWG To PDF y el medio a cada presentación (PlotSettingsValidator sobre la WorkingDatabase) y comprobar siempre el PDF. Base lateral imperial: el contenido de una presentación en (20,20) altura 10 cae fuera de un A4; con PlotType.Extents se ve. DsdData.WriteDsd deja el .dsd legible (formato INI DWF6)
- [x] **EXP-03** Ida y vuelta por DXF (`DxfOut`/`DxfIn`): conserva entidades, XData y handles (A) · R: ✅ · Obs: DxfOut(Current) → DXF de 77 KB; DxfIn en una base nueva: 3 entidades, se conservan el handle y el XData. Curiosidad: GetSupportedDxfOutVersions devuelve solo AC1024, AC1021, AC1800, AC1015 y AC1012 (hasta 2010), aunque DxfOut con Current funciona
- [x] **EXP-04** Guardar en versiones anteriores (`SaveAs` con `DwgVersion.AC1027` y `AC1032`) y reabrir (A) · R: ✅ · Obs: SaveAs con DwgVersion.AC1027 (2013) y AC1032 (2018) y reapertura con ReadDwgFile: OriginalFileVersion correcta, handle y XData conservados (14 KB cada uno)
- [x] **EXP-05** Guardar con contraseña (`SecurityParameters`): si AutoCAD 2027 aún lo admite (A) · R: ❌ · Obs: SaveAs(ruta, SecurityParameters) con contraseña, RC4 de 40 bits y el proveedor base de Microsoft: eInvalidInput. El constructor vacío de SecurityParameters es [Obsolete] con error; SecurityAlgorithm solo tiene RC4. Parece que AutoCAD ya no guarda DWG con contraseña (por verificar en la documentación). Error propio en el 1.er intento: escribí SecurityAlgorithm.Rc4 sin consultar el inventario (A1)
- [x] **EXP-06** `Wblock` de una selección a un DWG nuevo (A) · R: ✅ · Obs: Database.Wblock(ObjectIdCollection, Point3d) de una línea: DWG nuevo con 1 entidad, con XData y la RegApp copiada; el handle cambia (es una base nueva)
- [x] **EXP-07** Lote de 100 DWG como base lateral (`ReadDwgFile`, modificar, `SaveAs`): tiempo por archivo y memoria (P) · R: ✅ · Obs: 100 DWG de 50 líneas como base lateral (ReadDwgFile, CloseInput, añadir una línea, SaveAs) en 1.223 ms dentro de AutoCAD: mediana 11 ms por archivo, máximo 15 ms; memoria +7 MB
- [x] **EXP-08** El mismo lote abriendo documentos visibles (`DocumentCollectionExtension.Open` + `CloseAndSave`) frente a la base lateral (P) · R: ✅ · Obs: 20 DWG abiertos de forma visible (DocumentCollectionExtension.Open + LockDocument + añadir una línea + DocumentExtension.CloseAndSave) desde el contexto de aplicación: 12,0 s, mediana 602 ms y máximo 639 ms por archivo, +70 MB; queda solo el documento inicial. Frente a la base lateral (EXP-07, 11 ms por archivo), unas 55 veces más lento. No se comprobó el contenido guardado de cada archivo
- [x] **EXP-09** Guardar en bucle dentro del repositorio frente a `%LOCALAPPDATA%` (la sensibilidad que tumbaba Revit en EXP-10 de RevitLab) (P/J) · R: ✅ · Obs: 200 SaveAs seguidos del mismo DWG (2.000 líneas, base lateral): en %LOCALAPPDATA% 1.457 ms (7,3 ms por guardado) y dentro del repositorio (pruebas/resultados/runs/…) 1.334 ms (6,7 ms), 0 fallos en los dos; solo deja el .dwg (SaveAs de base lateral no crea .bak). Sin la sensibilidad de Revit (EXP-10 de RevitLab)
- [x] **EXP-10** Abrir un DWG dañado a propósito: `NeedsRecovery`, `AppContextRecoverDocument` y qué diálogos salen (A/J) · R: ✅ · Obs: DWG sano con 7 entidades dañado de tres formas. ReadDwgFile da al instante (0–1 ms), sin diálogos, una excepción distinta por cada daño: 4 KB sobrescritos en el centro → eFilerError; cabecera → eBadDwgHeader; truncado a 2/3 → eEndOfFile (control: el sano se lee, NeedsRecovery=False). En la interfaz, DocumentManager.Open del dañado lanza COMException «Error Decrypting Data» en 355 ms, sin diálogo (mensaje engañoso: no está cifrado). DocumentCollection.AppContextRecoverDocument lo recupera y lo abre en 442 ms SIN diálogos (en LIM-05 RECOVER no se pudo automatizar: esta es la vía), con el informe de AUDIT en la línea de comandos («Salvaged database from drawing», «Missing AcDbDimStyleTable/ViewportTable/UCSTable/ViewTable»), pero el modelo queda vacío: las 7 entidades estaban en la zona dañada
- [x] **EXP-11** Formatos con API .NET y formatos solo por comando: DWF, FBX, STL, DGN, importar PDF (S/A) · R: ⚠️ · Obs: API .NET de exportación: ninguna para FBX, STL, DGN ni para importar PDF (solo las definiciones de calco DgnDefinition/PdfDefinition/DwfDefinition para VINCULARLOS). DWF: por API funciona con Publisher.PublishExecute(DsdData SheetType.MultiDwf, PlotConfigManager.SetCurrentConfig("DWF6 ePlot.pc3")): 15 KB en 1,1 s, ZIP con 13 entradas y 3 hojas .w2d. Por comando, con FILEDIA=0/CMDDIA=0/EXPERT=5 y (command …) desde LISP: STLOUT de un BOX → STL de 3,4 KB ✅; -DGNEXPORT V8 → DGN de 48 KB ✅ («Translation successful», aunque el comando se quedó pidiendo algo más al final); -PDFIMPORT de la página 1 de un PDF de AutoCAD → 23 LWPOLYLINE (círculos y textos SHX convertidos en geometría) ✅; FBXEXPORT con una ruta no generó archivo ni diálogo visible ❌ y -EXPORT DWF se quedó esperando avisos que el guion no contestó ❌ (no depurado: SendStringToExecute sin eco no deja ver los avisos en el historial)
- [x] **EXP-12** Diálogos que bloquean un lote (proxys, fuentes que faltan, xrefs rotas) y cuáles se evitan con `FILEDIA`, `CMDDIA`, `EXPERT` y `PROXYNOTICE` (A/J) · R: ⚠️ · Obs: dos DWG creados en base lateral: uno con un estilo de texto cuya fuente es noexiste_lab.shx y otro con una xref (XREF_ROTA) adjuntada con Database.AttachXref y luego borrada. Abiertos en la interfaz con DocumentManager.Open, con FILEDIA=1, CMDDIA=1, EXPERT=0, PROXYNOTICE=1, XREFNOTIFY=2: la fuente que falta NO saca diálogo (301 ms; FONTALT=simplex.shx la sustituye en silencio) y tampoco con FONTALT="." (346 ms); la xref rota NO bloquea (266 ms) pero AutoCAD abre por su cuenta la paleta no modal «External References». Ninguno de los dos detiene un lote abierto por API. Proxys (ARX-05, 2026-10-29): con PROXYNOTICE=1 el diálogo modal «Proxy Information» SÍ bloquea, incluso el arranque con el DWG pasado como argumento (180 s sin pipe); accoreconsole no avisa. Sin probar: abrir por comando (OPEN) en lugar de por API. Variables restauradas (FILEDIA=1, CMDDIA=1, EXPERT=0, FONTALT=simplex.shx)
- [x] **EXP-13** Extraer los atributos de 100 DWG a CSV con un recorrido propio frente a DATAEXTRACTION (A/P) · R: ⚠️ · Obs: recorrido propio con ReadDwgFile de los 100 DWG de cmp\lote: 100 atributos (bloque PLACA, etiqueta CODIGO) a %LOCALAPPDATA%\AutoCADLab\exp\exp13.csv (UTF-8 con BOM, separador ;) en 559 ms (5,6 ms por DWG). Falta la comparación con DATAEXTRACTION (necesita una plantilla .dxe; -DATAEXTRACTION por línea de comandos sin probar)

### EVT · Eventos, reactores y overrules

- [x] **EVT-01** `Database.ObjectAppended`, `ObjectModified` y `ObjectErased`: un evento por entidad y su orden (A) · R: ✅ · Obs: cada línea creada dispara ObjectAppended y DOS ObjectModified (más 1 Modified del BlockTableRecord al empezar). Modificar y borrar dentro de una transacción disparan Modified y Erased AL CONFIRMAR, no en el momento del cambio
- [x] **EVT-02** Modificar la base de datos dentro de `ObjectModified`: rechazo, recursión o crash (A) · R: ✅ · Obs: dentro de ObjectModified: abrir OTRA entidad para escritura con TopTransaction funciona y el cambio se guarda; UpgradeOpen de la propia entidad funciona; el evento no se vuelve a disparar en bucle (1 llamada para esa línea)
- [x] **EVT-03** `CommandWillStart`, `CommandEnded` y `CommandCancelled`: nombres globales y comandos transparentes (A/M) · R: ✅ · Obs: CommandWillStart/Ended/Cancelled con comandos encolados con SendStringToExecute: REGEN (WillStart y Ended); LINE con un ZOOM transparente dentro → WillStart LINE, WillStart ZOOM, Ended ZOOM, Ended LINE (anidados); LINE cancelada con Ctrl+C (\x03) → Cancelled LINE. GlobalCommandName sin prefijos. Los manejadores sobreviven a la ejecución: se guardan en AppDomain.SetData y se dan de baja en una prueba posterior
- [x] **EVT-04** Vetar un comando nativo con `DocumentLockModeChanged` + `Veto` (A/M) · R: ✅ · Obs: DocumentLockModeChanged + e.Veto() cuando GlobalCommandName es ERASE: el ERASE lanzado desde LISP no borra la línea (sigue viva); control: sin el manejador, el mismo ERASE desde reposo la borra. El evento ve también el texto LISP completo como GlobalCommandName, «ERASE», «» y «#». TRAMPA: vetar un comando lanzado con (command …) deja el intérprete LISP a medias (CMDACTIVE=32, IsQuiescent=False) y se traga lo que se envíe después (el primer control falló por eso); hace falta Esc
- [x] **EVT-05** Sustituir un comando nativo: `UNDEFINE` + comando propio con el mismo nombre; `.LINE` sigue llegando al nativo (A/M) · R: ✅ · Obs: comando .NET [CommandMethod("LABEVT05", "LINE")] con el nombre global del nativo: antes de UNDEFINE, LINE ejecuta el nativo (el .NET no lo tapa); tras UNDEFINE LINE: LINE → el propio, _LINE → el propio (el guion bajo NO salta el UNDEFINE), (command "LINE") desde LISP → el propio, .LINE → el nativo (el punto sí lo salta); tras REDEFINE LINE vuelve el nativo. El propio corrió exactamente 3 veces (las esperadas). Montaje: el comando va solo en su clase (el eDuplicateKey de comandos ya registrados corta el registro del resto)
- [x] **EVT-06** `DrawableOverrule` con `SetXDataFilter` que cambia cómo se dibujan ciertas entidades (V/M) · R: ✅ · Obs: DrawableOverrule sobre Circle con SetXDataFilter(LABUI_EVT06): los círculos con esa XData (1.º, 3.º y 5.º) se dibujan con dos círculos rojos concéntricos extra y los demás quedan normales (verificado con captura); WorldDraw llamado 3 veces tras Regen; al quitarlo vuelven a dibujarse normales. TRAMPA (error propio, C5): guardar el overrule en datos del AppDomain y recuperarlo con su clase propia (Previo<Diana>) devuelve null tras recargar el payload, porque cada copia de la DLL define otro tipo: el overrule seguía activo; recuperarlo como Overrule (tipo de AutoCAD)
- [x] **EVT-07** `TransformOverrule` o `GripOverrule` que impide mover ciertas entidades (M) · R: ✅ · Obs: TransformOverrule sobre Circle con SetXDataFilter(LABUI_FIJO) y TransformBy vacío: MOVE del círculo marcado no lo mueve (sigue en 55,60; 1 TransformBy bloqueado) y MOVE de otro círculo (control) sí (135,60 → 135,10); confirmado con captura. Sin probar: arrastre de pinzamientos (GripOverrule)
- [x] **EVT-08** Un overrule o un evento registrado desde el payload descargable y no dado de baja en `[LabShutdown]`: si retiene el contexto (control positivo y negativo) (A/P) · R: ✅ · Obs: con el runner que descarga de verdad: suscribir Application.SystemVariableChanged y darlo de baja en [LabShutdown] no retiene el contexto (vivos 4 → 4); sin darlo de baja lo retiene (4 → 5, y se queda en 5 tras otra ejecución). Un evento huérfano = un contexto que no se descarga
- [x] **EVT-09** `Database.BeginSave`/`SaveComplete` y `BeginDocumentClose` con `Veto` (A) · R: ✅ · Obs: Database.BeginSave y SaveComplete se disparan en SaveAs del documento y también en una base lateral (e.FileName con el nombre). DocumentBeginCloseEventArgs.Veto() detiene incluso doc.CloseAndDiscard(), que lanza una COMException sin mensaje, y el documento sigue abierto (1 veto); quitado el manejador, CloseAndDiscard lo cierra
- [x] **EVT-10** `Application.SystemVariableChanged` al cambiar variables por código y por la interfaz (A/M) · R: ✅ · Obs: Application.SystemVariableChanged llega SÍNCRONO al llamar a SetSystemVariable (1 evento antes de volver) y también al cambiar la variable por la línea de comandos (OSMODE tecleado, setvar de LISP): 6 eventos en total para 6 cambios (OSMODE ×2 y USERI5 por cada vía), todos con Changed=True
- [x] **EVT-11** `Editor.PointMonitor` con un tooltip propio al pasar el ratón (M) · R: ✅ · Obs: Editor.PointMonitor con AppendToolTipText: al pasar el cursor real sobre una línea aparece el tooltip «EVT-11: X=15.0 Y=15.1 sobre LINE» encima de la ficha nativa de la línea (Color/Layer/Linetype), en una ventana WPF propia (HwndWrapper) capturable con PrintWindow; 21 eventos durante el recorrido; GetPickedEntities da la entidad bajo el cursor
- [x] **EVT-12** Coste de un manejador `ObjectModified` pesado durante una edición de 10.000 entidades (P) · R: ✅ · Obs: 10.000 líneas del documento: cambiar el color en una transacción 44 ms sin manejador; con un ObjectModified que gasta ~50 µs, 564 ms (×13) con exactamente 10.000 llamadas (1 por entidad, dentro de la transacción); quitado el manejador, 80 ms. Un manejador pesado se paga entero por cada objeto modificado

### UI · Interfaz de usuario

- [x] **UI-01** Pestaña y panel propios en la cinta, en caliente, con `Autodesk.Windows.ComponentManager.Ribbon` (AdWindows no está en el inventario: por verificar tras SHL-04) (M) · R: ✅ · Obs: con la persona delante (addins/AutoCADLab.UI, payload del runner): pestaña AutoCADLab con panel y 3 RibbonButton (CommandHandler = ICommand propio) añadida en caliente con ComponentManager.Ribbon.Tabs.Add en 3 ms (61 pestañas) y activada con ActiveTab. Los clics llegan en contexto de aplicación (appCtx=True, documento sin bloquear): para modificar hace falta LockDocument o pasar a ExecuteInCommandContextAsync (el botón Seleccionar lanza UI-06 así y funciona). TRAMPA: la pestaña desaparece sin aviso en cuanto se carga un CUIx parcial (UI-02), porque la cinta se reconstruye: un complemento que añade pestañas por código debe volver a añadirlas (p. ej. en el evento WorkspaceLoaded de RibbonPaletteSet, sin probar)
- [x] **UI-02** CUIx parcial cargado por código (`Application.LoadPartialMenu`) con un botón que lanza un comando propio (M) · R: ✅ · Obs: Application.LoadPartialMenu del CUIx de MAC-06: True en 126–178 ms (UnloadPartialMenu 119–155 ms). La pestaña aparece en la cinta, pero sus paneles se rellenan de forma perezosa: en memoria el panel tiene 0 elementos hasta que la persona hace clic en la pestaña (luego 6). Efectos secundarios: escribe en el acad.cuix principal de la persona (lista de parciales y una WSRibbonTabSourceReference por espacio de trabajo, que UnloadPartialMenu no quita) y borra las pestañas añadidas por código (UI-01). Junto al CUIx deja autocadlab.mnr y autocadlab_light.mnr
- [x] **UI-03** `PaletteSet` con contenido WPF (`AddVisual`), acoplado y persistente entre sesiones por GUID (M) · R: ⚠️ · Obs: PaletteSet con GUID fijo y AddVisual(StackPanel WPF): se crea flotante (Dock=None, 450x450). Los clics llegan en contexto de aplicación: con LockDocument crea el círculo; sin él, eLockViolation capturable (control). TRAMPA: el contenido WPF no hereda el tema de AutoCAD; con COLORTHEME=0 (oscuro) el texto negro por defecto es invisible sobre el fondo oscuro de la paleta (la persona no veía nada): hay que leer COLORTHEME y fijar Foreground y Background. Sin probar: persistencia de posición entre sesiones por GUID (necesita reiniciar)
- [x] **UI-04** `PaletteSet` creado desde el payload descargable: qué pasa con la paleta al descargar el contexto (M/A) · R: ✅ · Obs: payload mínimo sin [CommandMethod] (addins/AutoCADLab.UIPal), así el runner lo quita de ExtensionLoader y su contexto collectible podría descargarse. Las ejecuciones que no crean paleta se descargan (no se acumulan); la que crea una PaletteSet con un botón WPF queda VIVA (contextos anteriores: AutoCADLab.UIPal 1) y su botón sigue funcionando atendido por ESA copia vieja (sombra …141610-425, contexto AutoCADLab.Pruebas.20261029-141610-425) aunque después se carguen copias nuevas. Ni Visible=false, ni PaletteSet.Dispose(), ni soltar la referencia del AppDomain liberan el contexto (sigue vivo tras dos recargas más): una paleta creada desde un payload descargable lo retiene para siempre y sigue ejecutando el código viejo. Para paletas, usar el complemento fijo (no el payload)
- [x] **UI-05** Menú contextual propio con `ContextMenuExtension` (`AddDefaultContextMenuExtension` y `AddObjectContextMenuExtension`) (M) · R: ✅ · Obs: AddDefaultContextMenuExtension (submenú AutoCADLab al hacer clic derecho en vacío, evento Popup y Click: contó 24 entidades) y AddObjectContextMenuExtension(RXClass de Circle) (solo aparece con un círculo seleccionado, no con una línea: control); en el Click, Editor.SelectImplied devuelve la selección (radios=[10]). Clics en contexto de aplicación. Con SHORTCUTMENU=11
- [x] **UI-06** Selección con filtro: `GetEntity` con `AddAllowedClass`/`SetRejectMessage` y `GetSelection` con `SelectionFilter` de `TypedValue`; Esc devuelve `PromptStatus.Cancel` (M) · R: ✅ · Obs: dentro de ExecuteInCommandContextAsync (appCtx=False, lock=Write): GetEntity con SetRejectMessage + AddAllowedClass(Circle) rechaza la línea con el mensaje propio y acepta el círculo (OK CIRCLE); GetSelection con SelectionFilter(DxfCode.Start=LINE) sobre una ventana que lo abarca todo devuelve 5 objetos, solo las líneas. Esc devuelve PromptStatus.Cancel en los dos (control hecho)
- [x] **UI-07** Jig (`EntityJig` o `DrawJig`) para colocar un bloque con vista previa (M) · R: ✅ · Obs: EntityJig sobre una BlockReference de LABUI_MARCA, con Editor.Drag en contexto de comando: vista previa fluida según la persona, 3.760 llamadas a Sampler en 98 s (~38 por segundo), Drag → OK y el bloque se inserta en el sitio elegido
- [x] **UI-08** Gráficos transitorios con `TransientManager` que no son entidades (M/V) · R: ✅ · Obs: TransientManager.AddTransient de dos círculos (DirectShortTerm rojo, Main verde): las entidades del dibujo no cambian (23 → 23), no se pueden seleccionar, sobreviven a REGEN y siguen al zoom como geometría normal; EraseTransient los quita. Hay que guardar la referencia al Drawable para poder borrarlo (aquí en los datos del AppDomain, porque el payload se recarga)
- [x] **UI-09** Ventana WPF con `Application.ShowModalWindow` y `ShowModelessWindow`; la no modal modifica el dibujo con `DocumentLock` (M) · R: ✅ · Obs: ShowModelessWindow(Window WPF): AutoCAD sigue usable y los botones con LockDocument modifican el dibujo. ShowModalWindow: bloquea toda la interfaz (ni zoom ni clics) y devuelve False al cerrar con la X (135 s), pero el código de sus botones sigue en contexto de aplicación y SIN bloqueo: sin LockDocument da eLockViolation también dentro de la modal. TRAMPAS: la ventana sale en una esquina de la pantalla (177,177), no centrada en AutoCAD, y sin Owner WPF; con tamaño fijo los botones quedaban cortados (usar SizeToContent); en una Window suelta el fondo es el blanco de WPF, no el del tema, así que el arreglo de colores de la paleta dejaba texto blanco sobre blanco: fijar siempre Foreground y Background
- [x] **UI-10** Barra de estado: `Pane` con texto propio y `TrayItem` con `ShowBubbleWindow` (M) · R: ✅ · Obs: Application.StatusBar: Pane con texto «AutoCADLab UI» y TrayItem con icono; los dos reciben MouseDown (botón y doble clic) en contexto de aplicación; TrayItem.ShowBubbleWindow muestra el globo, y Closed informa ClosedByUser. Hace falta StatusBar.Update() tras añadir
- [x] **UI-11** Qué ve la persona: `Editor.WriteMessage` frente a `ShowAlertDialog` frente a una ventana propia (M) · R: ✅ · Obs: en contexto de comando: Editor.WriteMessage sale en la línea de comandos sin bloquear; ShowAlertDialog bloquea hasta OK (20–24 s con la persona); una Window WPF con ShowModalWindow bloquea hasta cerrarla. En el primer intento la persona no vio la ventana WPF aunque estuvo abierta 5,2 s y se cerró (sin explicar; en el segundo, con registro de Loaded/Activated/Closing, apareció en 177,177 y la cerró ella). Para la persona, lo más visible es el cuadro modal; el mensaje de la línea de comandos se pierde con facilidad
- [x] **UI-12** Cerrar AutoCAD con `Application.Quit` desde el add-in con un dibujo modificado: diálogo de guardar y cierre limpio (M) · R: ✅ · Obs: con un dibujo modificado (DBMOD≠0): el comando _.QUIT encolado (SendStringToExecute) muestra el cuadro de Windows «Save changes to Drawing2.dwg?» (texto en inglés, botones «Sí/No/Cancelar» en el idioma de Windows); con «No» se disparan BeginQuit y QuitWillStart y AutoCAD termina limpio en ~3 s, sin CER ni eventos de error. TRAMPA GRAVE: Application.Quit() llamado desde el contexto de aplicación (el del runner/Dispatcher, igual que desde una paleta o un botón) CUELGA AutoCAD sin mostrar ningún diálogo: dentro de ExecuteInApplicationContext, colgado al instante (IsHungAppWindow=True, sin WM_NULL) más de 1 min; llamado directamente, vuelve en el acto y se cuelga a los pocos segundos (2 instancias matadas). No se dispara BeginQuit. Para cerrar AutoCAD desde un complemento: SendStringToExecute("_.QUIT ")
- [x] **UI-13** Opciones y perfiles: `SetSystemVariable`, `Application.Preferences` (COM) y `UserConfigurationManager`; cuáles se guardan en el perfil al instante (A) · R: ✅ · Obs: volcados del registro del perfil (HKCU\…\R26.0\ACAD-A101:409\Profiles\<<Unnamed Profile>>) antes y después de cada paso: SetSystemVariable("CURSORSIZE", 5→8) NO cambia el registro al instante (AutoCAD ya usa el valor); Preferences.Selection.PickBoxSize por COM dentro del proceso (3→5, PICKBOX=5) tampoco; UserConfigurationManager.OpenCurrentProfile().CreateSubsection("AutoCADLab").WriteProperty escribe la subclave y el valor AL INSTANTE, incluso antes de Close. Restaurados CURSORSIZE=5, PickBoxSize=3 y borrada la sección: el registro final es idéntico al inicial. Sin verificar: que las variables de perfil se escriban al cerrar AutoCAD (lo esperable)

### LSP · AutoLISP y Visual LISP

- [x] **LSP-01** Cargar un `.lsp` con `(load)` desde el runner y devolver el resultado por archivo (A) · R: ✅ · Obs: (load) de pruebas/lisp/lab-lineas.lsp desde la ruta de confianza, encolado desde el runner con SendStringToExecute; la función escribe sus resultados en un archivo que lee la prueba siguiente (CMP-02a/b)
- [x] **LSP-02** Carga automática (`acaddoc.lsp`, `ACADLSPASDOC`) y SECURELOAD con `.lsp`, `.fas` y `.vlx` fuera de las rutas de confianza (A/M) · R: ✅ · Obs: con permiso: un acaddoc.lsp temporal en la carpeta de soporte del perfil (%APPDATA%\Autodesk\AutoCAD 2027\R26.0\enu\support, que NO es de confianza) se intenta cargar al abrir cada dibujo nuevo y con SECURELOAD=1 sale el aviso «Security - Unsigned Executable File» por acaddoc.LSP; con Load Once se carga (testigo «acaddoc.lsp cargado en Drawing8.dwg») y en el siguiente dibujo se carga SIN preguntar (Load Once vale para el resto de la sesión). Un .fas compilado con vlisp-compile en la carpeta no fiable se comporta igual que el .lsp (aviso; Load Once lo ejecuta). acaddoc.lsp temporal borrado. Sin probar: .vlx y ACADLSPASDOC con acad.lsp
- [x] **LSP-03** `(vl-load-com)` y `vla-AddLine` frente a `entmake`: mismas 1.000 líneas, mismo resultado (A) · R: ✅ · Obs: 10.000 líneas con entmake en 109 ms y 10.000 con vla-AddLine en 360 ms (MILLISECS); las 20.000 contadas con ssget filtrando por coordenada y borradas con entdel en 140 ms
- [x] **LSP-04** Reactores (`vlr-object-reactor`, `vlr-command-reactor`): disparo, y si `vlr-pers` sobrevive a guardar y reabrir (A/M) · R: ⚠️ · Obs: vlr-object-reactor sobre una línea: se dispara al cambiar su EndPoint (longitud 25); vlr-command-reactor :vlr-commandEnded: se dispara al terminar REGEN. vlr-remove los quita. No se probó vlr-pers tras guardar y reabrir
- [x] **LSP-05** Errores en LISP: `*error*` y `vl-catch-all-apply`; qué queda en la línea de comandos y en `LOGFILEON` (A/J) · R: ⚠️ · Obs: vl-catch-all-apply captura (/ 1 0) («divide by zero») y (car 5) («bad argument type: consp 5»), pero NO la llamada a una función inexistente: «bad function: LAB-FUNCION-QUE-NO-EXISTE» sale a la línea de comandos y aborta el resto de la expresión (la siguiente llamada encolada sí se ejecuta). Un *error* local recibe «divide by zero». Los errores quedan en el historial (Internal.Utils.GetLastCommandLines); LOGFILEON sin probar
- [x] **LSP-06** Depurar desde VS Code con `AutoLispDebugAdapter.exe`: breakpoint, variables y paso a paso (M) · R: ✅ · Obs: SIN VS Code ni su extensión AutoLISP (no instalada): pruebas/lisp/dap_lsp06.py habla Debug Adapter Protocol directamente con C:\Program Files\Autodesk\AutoCAD 2027\AutoLispDebugAdapter.exe por stdin/stdout (Content-Length). Con LISPSYS=1: initialize (anuncia supportsConfigurationDoneRequest y supportsEvaluateForHovers), attach {type:attachlisp, attributes:{process:acad}, processId} en 0,4 s, setBreakpoints en lsp06.lsp:6 (verified), configurationDone; al ejecutar (lsp06-suma 5) en AutoCAD llega stopped/breakpoint; stackTrace (seudomarcos :BREAK-POINT, :CALLBACK-ENTRY, :ARQ-SUBR-CALLBACK y el marco LSP06-SUMA con archivo y línea), scopes Locals y Last Value, variables con tipo (N=5, I=0, TOTAL=0), evaluate en el marco ((* total 10) → 0, (getvar "CLAYER") → "0"), next (:AFTER-STEP), continue y disconnect: 3 s en total. El adaptador escribe también texto suelto por stdout, fuera del protocolo. TRAMPA GRAVE: si el cliente muere con AutoCAD parado en un breakpoint, AutoCAD queda colgado (IsHungAppWindow=True, el runner no responde) y rechaza una sesión nueva con «This instance of AutoCAD is currently being used to debug another file»; ni Esc por mensaje lo libera: hubo que matar la instancia. El cliente envía ahora siempre continue y disconnect en un finally
- [x] **LSP-07** Compilar a `.fas`/`.vlx` y cargar el compilado (A) · R: ✅ · Obs: (vlisp-compile 'st "lab-lsp.lsp" "lab-lsp.fas") en el AutoCAD con interfaz → T y el .fas existe. Cargarlo desde la ruta de confianza: las funciones quedan definidas como SUBR (compiladas) en vez de USUBR. Falta .vlx
- [x] **LSP-08** Función LISP definida en .NET con `LispFunctionAttribute`: argumentos en `ResultBuffer` y valor de retorno (A) · R: ✅ · Obs: [LispFunction("lab-net-suma")] en un payload .NET: (lab-net-suma 40 2) → (42.0). Los enteros llegan como Int16 en el ResultBuffer y un ResultBuffer de un valor vuelve a LISP como lista
- [x] **LSP-09** Llamar a LISP desde .NET con `Application.Invoke(ResultBuffer)` y leer o escribir símbolos con `GetLispSymbol`/`SetLispSymbol` (A) · R: ⚠️ · Obs: Document.SetLispSymbol/GetLispSymbol: funcionan (42). Application.Invoke(ResultBuffer) SOLO llama funciones registradas con (vl-acad-defun): lab-suma registrada → 42; lab-resta definida con defun pero sin registrar → eInvalidInput; strcat (nativa) → eInvalidInput
- [x] **LSP-10** `LispFunction` definida en el payload descargable: si sigue respondiendo tras descargar el contexto, o si cuelga (A) · R: ⚠️ · Obs: la LispFunction del payload descargable sigue respondiendo después de la ejecución, porque el runner conserva en ExtensionLoader los payloads con LispFunction/CommandMethod (DEV-07/09): el contexto no se descarga y la función ejecuta el código de esa copia (AutoCADLab.Pruebas.…071116-928). Sin esa salvaguarda, por verificar si cuelga
- [x] **LSP-11** `(command)` frente a `(command-s)` frente a `vla-*` dentro de un reactor: cuál está prohibido (A) · R: ✅ · Obs: dentro de un reactor de comando: (command) → «bad order function: COMMAND», error que vl-catch-all-apply NO captura y que aborta el reactor; (command-s) → «Unknown (command-s) failure.» (sí se captura); vla-ZoomExtents → funciona. En reactores, solo vla-*
- [x] **LSP-12** Codificación de los `.lsp` (UTF-8 con y sin BOM, ANSI): textos con acentos en entidades (A) · R: ✅ · Obs: el mismo .lsp en UTF-8 con BOM, UTF-8 sin BOM y ANSI (cp1252), cargado desde la carpeta de confianza, crea con entmake un TEXT «Acción ñandú €»: los tres quedan correctos en el DWG (U+00F3 U+00F1 U+00FA U+20AC leídos desde .NET). AutoCAD 2027 detecta el UTF-8 incluso sin BOM y lee ANSI como SYSCODEPAGE (ANSI_1252). Lo que sigue en ANSI es la ESCRITURA: write-line escribe en cp1252 (pista de 2026-10-28)
- [x] **LSP-13** Recursión profunda y listas muy grandes en LISP: error controlado o 💥 (A) · R: ⚠️ · Obs: recursión no terminal (1+ (r (1- n))): 1.000, 10.000 y 15.000 niveles bien (<1 ms); con 20.000 o más la evaluación MUERE EN SILENCIO: ni vl-catch-all-apply ni un *error* propio se enteran, no sale ningún mensaje y AutoCAD sigue vivo (límite entre 15.000 y 20.000 niveles). Listas: 1 millón de elementos con cons en 172 ms y 5 millones en 1.015 ms, sin problemas
- [x] **LSP-14** `vlax-invoke` y `vlax-get-property` sobre objetos ActiveX de Express Tools o de un servidor COM propio (A) · R: ✅ · Obs: servidor COM ajeno a AutoCAD (Scripting.FileSystemObject) desde LISP: vlax-create-object, vlax-invoke-method FileExists → :vlax-true, vlax-invoke GetTempName → «radA1413.tmp» (vlax-invoke también vale para objetos que no son de AutoCAD), vlax-get-property Name → cadena; propiedad inexistente → error capturable «ActiveX Server returned the error: unknown name: NOEXISTE»; vlax-typeinfo-available-p T. TRAMPA: la conversión de variants es irregular: DateLastModified llega ya convertida a real (46324,6, fecha OLE; vlax-variant-value sobre ella da «bad argument type: variantp»), pero Size llega como #<variant 3 14435> y hay que desenvolverlo. No probado: objetos ActiveX de Express Tools (en la consola no existen; ver MAC-04)
- [x] **LSP-15** Alucinación de la IA en LISP: 5 rutinas medianas comprobadas contra el inventario de SHL-06 (funciones inexistentes por cada 100 líneas) (S/P) · R: ✅ · Obs: cinco rutinas medianas (pruebas/lisp/lsp15/lsp15.lsp: 91 líneas de código, 60 funciones distintas: entidades por capa a CSV, prefijo en nombres de bloque con vla-*, área de LWPOLYLINE cerradas por capa con rótulo, atributo en todas las inserciones con vla-GetAttributes, estilo de cota con -DIMSTYLE y vla-put-StyleName) escritas por la IA DE MEMORIA, sin consultar el inventario, y comprobadas con pruebas/lisp/lsp15/analizar.py contra api-surface-lisp-autocad-2027.tsv: 0 funciones inexistentes (0,0 por cada 100 líneas) y ninguna solo de interfaz. Ejecutadas en AutoCAD sobre el DXF de DWG-04 + una polilínea 10×5 y una cota: las 5 funcionan a la primera (CSV con 2 capas; 4 bloques renombrados, entre ellos auxiliares de ezdxf; área 50,0; 1 atributo cambiado; estilo aplicado a 1 cota). Matices: la muestra es pequeña, la IA medida es la que escribe, y las rutinas usan funciones muy comunes; la primera versión del analizador daba 5 falsos positivos (listas de argumentos de defun/lambda) y se corrigió. En .NET, en cambio, los errores A1 del laboratorio fueron símbolos inventados (ver Metricas de dificultad IA)

### COM · ActiveX dentro del proceso y desde fuera

- [x] **COM-01** Desde fuera en C# .NET 10: obtener la instancia en marcha sin `Marshal.GetActiveObject` (no existe en .NET Core), por la ROT o `oleaut32` (A) · R: ✅ · Obs: cliente .NET 10 (pruebas/com/ComCliente): Marshal.GetActiveObject no existe; con P/Invoke a ole32 CLSIDFromProgID + oleaut32 GetActiveObject se conecta en 4 ms (System.__ComObject). 1.000 AddLine con dynamic: 812 ms
- [x] **COM-02** Desde PowerShell 5.1: `[Runtime.InteropServices.Marshal]::GetActiveObject("AutoCAD.Application")` y crear una línea (A) · R: ✅ · Obs: PowerShell 5.1: [Runtime.InteropServices.Marshal]::GetActiveObject("AutoCAD.Application") conecta en 3 ms; 1.000 AddLine con [double[]] en 690 ms; borrar 629 ms; Version «26.0s (LMS Tech)»
- [x] **COM-03** Desde Python (`pywin32` o `comtypes`, con permiso si no están instalados): conectar y crear una línea (A) · R: ✅ · Obs: Python 3.13 + pywin32 (ya instalado; hay que ejecutar SIN python -I o no lo encuentra): GetActiveObject en 9 ms; 1.000 AddLine en 1.013 ms (puntos como VARIANT VT_ARRAY|VT_R8). Con gencache.EnsureDispatch, Item() devuelve IAcadEntity y hay que usar CastTo(e, "IAcadLine") para leer StartPoint. Mientras AutoCAD rechazaba las llamadas, pywin32 lo disfrazó como AttributeError «<unknown>.Name» (ver COM-06)
- [x] **COM-04** Arrancar una instancia nueva oculta por COM (`AutoCAD.Application.26`, `Visible=false`) y cerrarla sin tocar la sesión abierta (A/P) · R: ✅ · Obs: Python DispatchEx("AutoCAD.Application.26") arranca un acad.exe nuevo en 11,1 s, ya OCULTO (Visible=False al crearlo) y con 1 documento; Documents.Add + AddLine funcionan; Quit lo cierra en 2,2 s. El AutoCAD del usuario no se toca (se identifica cada instancia por el PID de su HWND). La instancia nueva carga también el bundle del runner y escucha en su propio pipe (autocadlab-<pid>), con la instancia del usuario en autocadlab
- [x] **COM-05** Con dos `acad.exe` abiertos, a cuál se conecta `GetActiveObject` (A) · R: ✅ · Obs: con dos acad.exe (el del usuario y uno arrancado por COM), GetActiveObject("AutoCAD.Application") devuelve el del usuario, el primero que se registró en la ROT
- [x] **COM-06** `RPC_E_CALL_REJECTED` con AutoCAD ocupado y un `IMessageFilter` que reintenta (A/P) · R: ⚠️ · Obs: observado sin provocarlo (2026-10-28 ~06:55): durante unos minutos AutoCAD rechazó TODAS las llamadas COM externas con RPC_E_CALL_REJECTED (0x80010001), también tras 200 reintentos en 10 s, mientras Application.IsQuiescent = False aunque CMDACTIVE = 0 y Editor.IsQuiescent = True. Se desbloqueó tras un clic en el dibujo + Esc y un autoguardado (no se sabe cuál de los dos). Con pywin32, el rechazo en GetIDsOfNames aparece como AttributeError. Falta provocarlo a propósito (operación larga) y medir un IMessageFilter
- [x] **COM-07** Llamada COM desde fuera con un diálogo modal abierto en AutoCAD: bloqueo, rechazo o ejecución (M) · R: ✅ · Obs: COM desde fuera (Python, AddLine en el AutoCAD de la persona): con OPTIONS abierto, 120 rechazos RPC_E_CALL_REJECTED en 60 s y nada creado; con LINE a medias, 40 rechazos en 20 s y nada creado; control con AutoCAD en reposo: 0 rechazos, línea creada al instante. Contraste con el pipe del runner (CTX-09/10), que sí se ejecuta en esas dos situaciones
- [x] **COM-08** COM dentro del proceso desde .NET: `Application.AcadApplication` y `DocumentExtension.GetAcadDocument` con `dynamic` frente a la interop tipada (A) · R: ✅ · Obs: COM dentro del proceso desde el runner: Application.AcadApplication y DocumentExtension.GetAcadDocument funcionan en el AutoCAD con interfaz (en accoreconsole tumban el proceso, CON-03). 10.000 AddLine: dynamic 163 ms (16 µs/línea), interop tipada incrustada 221 ms (22 µs/línea)
- [x] **COM-09** Interop en .NET 10: referenciar `Autodesk.AutoCAD.Interop.dll` con `EmbedInteropTypes` y compilar (S/A) · R: ✅ · Obs: Autodesk.AutoCAD.Interop(.Common).dll con EmbedInteropTypes=true compila en net10.0-windows (cliente externo y payload del runner); cast de __ComObject a AcadApplication funciona. Desde fuera, 1.000 AddLine tipadas: 713 ms (dynamic: 812 ms)
- [x] **COM-10** Lo que solo está en COM y no en .NET (por ejemplo `ZoomExtents`, `Preferences`): lista a partir de SHL-05 frente al inventario .NET (S/A) · R: ⚠️ · Obs: comparación por NOMBRE de los miembros de 11 interfaces IAcad* (aplicación, documento, utilidad, preferencias, espacio modelo, presentación, trazado, ventana…) frente a todos los miembros .NET: 144 sin un nombre equivalente (pruebas/resultados/solo-com-autocad-2027.tsv). Muchos son falsos positivos (AddCircle… = new Circle + AppendEntity; GetPlotDeviceNames ≈ PlotSettingsValidator). Parecen exclusivos de COM: Zoom* (ZoomExtents, ZoomAll, ZoomPrevious…), gestión de perfiles (Preferences.Profiles: CopyProfile, ImportProfile…), AuditInfo, PurgeAll, Eval y la carga de VBA (LoadDVB, RunMacro). Verificación uno a uno pendiente
- [x] **COM-11** Eventos COM recibidos desde fuera del proceso (`BeginCommand`, `EndSave`) (A/M) · R: ✅ · Obs: Python con win32com.client.WithEvents recibe desde otro proceso BeginCommand/EndCommand del documento (REGEN) y SysVarChanged de la aplicación (USERI1 cambiado por LISP). Llegan durante el propio SendCommand (síncrono), antes de que vuelva. Una expresión LISP no dispara BeginCommand
- [x] **COM-12** `ObjectDBX.AxDbDocument.26`: leer y modificar DWG por COM sin abrirlos en la interfaz (A/P) · R: ✅ · Obs: ObjectDBX desde Python: acad.GetInterfaceObject("ObjectDBX.AxDbDocument.26"), Open de una copia de un DWG de 50 líneas, AddCircle, SaveAs: 72 ms; 50 → 51 entidades; el DWG no aparece en Documents de la interfaz
- [x] **COM-13** `SendCommand` por COM frente a métodos directos: sincronía y cómo saber que terminó (A) · R: ✅ · Obs: SendCommand por COM desde fuera es SÍNCRONO: volvió en 14 ms con la línea ya creada (+1 entidad al volver, igual tras 1 s). A diferencia de SendStringToExecute desde .NET (CTX-06), que es asíncrono
- [x] **COM-14** Liberar las referencias (`ReleaseComObject`) y si un `acad.exe` arrancado por COM se cierra al terminar el cliente (A) · R: ❌ · Obs: un acad.exe arrancado por COM (DispatchEx) y soltado sin Quit (referencias a None, gc, CoUninitialize) SIGUE VIVO pasados 30 s: queda un AutoCAD huérfano y oculto, que hubo que terminar con taskkill. Hay que llamar siempre a Quit (cierra en 2,2 s, COM-04)

### CON · `accoreconsole` y scripts `.scr`

- [x] **CON-01** `accoreconsole.exe /i <dwg> /s <scr>` en vacío: tiempo de arranque, código de salida y codificación de la salida estándar (P) · R: ⚠️ · Obs: con /i base.dwg (copia de acadiso.dwt): LISP+LINE+SAVEAS+QUIT en 2,85 s, exit 0, FILEDIA=0. Sin /i: exit 53 en 1,2 s porque no carga acadiso.dwt (¿bloqueada por el AutoCAD abierto? .dwl con la hora de apertura). /? no da ayuda: espera en el prompt. Salida estándar UTF-16 mezclada con ASCII (tools/con.py quita los NUL)
- [x] **CON-02** `NETLOAD` de una DLL propia en el `.scr` y ejecución de un `CommandMethod` (A) · R: ✅ · Obs: NETLOAD desde la ruta de confianza + CommandMethod: funciona. En la consola, IsApplicationContext=False, WorkingDatabase = la del documento (comparando UnmanagedObject; ReferenceEquals da False porque son envoltorios distintos). 10.000 líneas en 24 ms dentro de una transacción. Script completo con SAVEAS en 1,98 s
- [x] **CON-03** Qué API .NET falta sin interfaz: `acmgd.dll` no está disponible; lista de clases de `accoremgd` que lanzan excepción (A/S) · R: 💥 · Obs: en la consola hay 3 accesos que tumban el proceso con 0xC0000005, sin excepción .NET que capturar: acmgd Application.AcadApplication, Application.StatusBar y DocumentExtension.GetAcadDocument (COM). Sin caída: Core.Application.MainWindow devuelve null; acmgd Application.DocumentManager funciona (acmgd sí carga). Cada acceso se probó en su propio proceso (addins/AutoCADLab.Consola, comandos LABP1-5)
- [x] **CON-04** SECURELOAD en `accoreconsole`: DLL fuera de rutas de confianza (se bloquea, se salta o se rechaza) (A/P) · R: ✅ · Obs: confirmado con control: con SECURELOAD=1 y TRUSTEDPATHS vacío, NETLOAD falla sin preguntar («Unable to load … assembly») y el script se abandona en el siguiente comando desconocido; la misma DLL en una ruta de TRUSTEDPATHS carga (CON-02). SECURELOAD sigue en 1
- [x] **CON-05** LISP en `accoreconsole`: `(load)`, `vl-load-com` y qué funciones `vla-*`/`vlax-*` funcionan sin interfaz (A) · R: ⚠️ · Obs: (load) solo desde la ruta de confianza: con05.lsp en confianza carga y se ejecuta; el mismo archivo desde pruebas/con da «File load canceled» como error, no el valor onfailure (control: un archivo inexistente sí devuelve onfailure). vl-load-com da un error vacío y vlax-get-acad-object devuelve nil. Sin objeto ActiveX, vlax-ename->vla-object falla («bad argument type: VLA-OBJECT nil»), así que no hay vla-object de ninguna entidad: vla-get-Length/Radius/Area, vla-put-Color (control: el 62 de entget no cambia), vla-Move/Copy/Offset/GetBoundingBox/Delete, vla-get-Document, vlr-object-reactor y vlax-vla-object->ename fallan los 18. Funcionan entmakex, entget, ssget, vlax-curve-* con ename (getDistAtParam=50.0), vlax-3d-point y safearrays, vlax-ldata-put/get y vlax-create-object de COM externo (Scripting.FileSystemObject: FolderExists=-1). Se trabaja con entget/entmod y vlax-curve-*, no con vla-* (subagente, 2026-10-28)
- [x] **CON-06** Un `.scr` con un comando que falla o espera entrada: si la consola se cuelga, y cómo detectarlo con un tiempo límite (A/P) · R: ⚠️ · Obs: No se cuelga: comando desconocido (abandona el resto del script con exit 0, de modo que el código de salida no avisa), LINE a medias al acabar el script (exit 1, 5 s), script sin QUIT aunque el dibujo esté modificado (sale con exit 0 en 2 s y descarta los cambios) y error LISP (/ 1 0) (sigue con la línea siguiente). Sí se cuelga: QUIT con el dibujo modificado, porque espera «Really want to discard all changes? <N>». Con stdin cerrado gira al 100 % de un núcleo y con stdin abierto queda al 0 %; matado a los 20 s en ambos casos. Control: el mismo QUIT seguido de _Y sale en 4 s, y QUIT sin cambios también. getpoint que recibe una línea LISP da «Can't reenter LISP», «Invalid point» y luego 0xC0000005 dentro de LISP, pero exit 0. Detección: tiempo límite + última línea «Command: _.QUIT» + CPU (100 % con stdin cerrado), y buscar «Unknown command»/«Invalid option keyword» en el log
- [x] **CON-07** Lote de 100 DWG, un proceso por archivo: tiempo por archivo y memoria (P) · R: ✅ · Obs: 100 DWG (copias de un DWG con 2.101 entidades, 62 KB), un accoreconsole por archivo: contar, añadir un círculo, QSAVE y QUIT. 100/100 correctos en 343,5 s (17,5 DWG/min): media 3,43 s, mediana 3,03 s, mínimo 1,62 s, máximo 5,24 s. Memoria pico 117–118 MB por proceso. Control: al reabrir uno se cuentan 2.102 entidades (QSAVE persiste). Cada arranque añade una sección a ErrorReports/<hash>/cer.log en la carpeta de trabajo (100 arranques, sin excepciones)
- [x] **CON-08** Paralelismo: 1, 2, 4 y 8 consolas a la vez; rendimiento y si la licencia lo permite (P) · R: ✅ · Obs: 48 DWG por nivel, el mismo script que CON-07, en un equipo de 24 núcleos y 63 GB. 1 consola: 17,5 DWG/min; 2: 34,3 (83,9 s); 4: 63,0 (45,7 s); 8: 97,2 (29,6 s), 5,6× con 8. El tiempo por archivo sube de 3,4 a 4,8 s de media con 8. 0 fallos, los 48 guardados (2.102 entidades en el log) y 0 logs con «licen/adlm/entitlement». Memoria pico 117 MB por proceso en todos los niveles. La licencia no limita hasta 8 a la vez, con el AutoCAD de la interfaz abierto
- [x] **CON-09** Un solo proceso que abre los 100 DWG con `ReadDwgFile` desde la DLL frente a un proceso por DWG (P) · R: ✅ · Obs: Comando LABLOTE (addins/AutoCADLab.Consola), carpeta en AUTOCADLAB_LOTE: un solo proceso abre 100 DWG con new Database(false,true), ReadDwgFile(OpenForReadAndAllShare), CloseInput(true), añade un círculo y hace SaveAs encima. 100/100 en 755 ms (7 ms por archivo, máximo 18 ms); proceso completo con NETLOAD en 7,0 s; pico 154 MB y 99 MB privados. Un proceso por DWG (CON-07) tardó 3,43 s por archivo: unas 450 veces más lento por archivo, unas 49 veces más lento en total. Control: d077 reabierto en la consola tiene 2.102 entidades. Ojo: las bases laterales nacen con UndoRecording=False y no hacen rollback (TRX-03); aquí no se usa Abort
- [x] **CON-10** Trazar a PDF en `accoreconsole` (A/V) · R: ✅ · Obs: -PLOT detallado (Model, DWG To PDF.pc3, ISO_full_bleed_A4_(297.00_x_210.00_MM), Extents, Fit, Center, sin estilo) genera plot.pdf de 91 KB, %PDF-1.7, 1 página, MediaBox 842x595 (A4 apaisado), con %%EOF. -EXPORT _PDF _E _N <archivo> genera export.pdf de 14,8 KB, 1 página, 595x842. BACKGROUNDPLOT=2, pero el PDF ya existe en la línea siguiente del script (findfile). Trazar deja el dibujo modificado: QUIT pide confirmación (_Y). Una palabra clave inválida (_C en -EXPORT) da «Invalid option keyword» y el script se abandona. Aviso «Missing font. Text may not plot»
- [x] **CON-11** `SaveAs` a versiones anteriores y DXF en `accoreconsole` (A) · R: ✅ · Obs: SAVEAS en cadena: 2013 da AC1027 (63 KB), 2018 da AC1032 (62 KB), 2000 da AC1015 (274 KB) y DXF da ASCII, $ACADVER AC1032, precisión 16, 406 KB. 2018 es el formato más nuevo que ofrece (no hay 2027). Al elegir DXF pide «decimal places … <16>» (basta una línea vacía). Control: los 4 archivos reabiertos con /i tienen 2.102 entidades; /i acepta también un .dxf. Script completo en 3,9 s
- [x] **CON-12** Opciones de la línea de comandos (`/l`, `/isolate`, sin `/i`): qué hace cada una en 2027 (A/S) · R: ⚠️ · Obs: /l en-US, /l es-ES y /l xx-XX: sin efecto visible (LOCALE=ES, rutas …\enu\, mensajes en inglés, cabeceras idénticas) y sin error: solo está instalado en-US (es-ES tiene 338 KB de .qm de Qt). /isolate <id> <carpeta>: ROAMABLEROOTPREFIX y LOCALROOTPREFIX pasan a <carpeta>\Roaming y \Local, y copia allí los datos locales, incluidos acadiso.dwl/.dwl2 y los PlotandPublishLog del perfil normal. TRUSTEDPATHS y SECURELOAD siguen igual y NETLOAD desde confianza sigue funcionando. /p LABNOEXISTE se ignora sin aviso (CPROFILE=<<Unnamed Profile>>, el único perfil del registro). /product ACAD y /product NOEXISTE: sin efecto (PRODUCT=AutoCAD). Argumentos desconocidos se aceptan sin error (ver CON-13). Sin /i: exit 53 en 1,1 s al no cargar …\enu\Template\acadiso.dwt; con /isolate falla igual y sigue buscando la plantilla en la ruta normal, no en la aislada. El .dwt se puede leer desde otro proceso, así que no hay bloqueo de lectura; la hipótesis de CON-01 sigue sin verificar
- [x] **CON-13** Pasar parámetros al script (variables de entorno, `getenv`, archivo de entrada) (A) · R: ✅ · Obs: getenv lee variables de entorno del proceso: AUTOCADLAB_PARAM=«hola ñandú; con espacios» llega íntegra a LISP y a .NET (Environment.GetEnvironmentVariable). Control: sin definirla, getenv da nil. Archivo de entrada UTF-8 (ruta por variable): open/read-line lee bien «ñandú café» con y sin "utf8" (ascii ñ=241), y radio=7.5 dibuja un círculo de 7.5. Los argumentos extra tras /s (PARAMX=42 /miparam valor) los acepta sin error y llegan a .NET por Environment.GetCommandLineArgs (comando LABARGS); LISP no tiene forma de leerlos. Ojo: tools/con.py perdía los caracteres no ASCII de la salida estándar (corregido el 2026-10-28: decodifica como latin-1 tras quitar los NUL)
- [x] **CON-14** Depurar la DLL dentro de `accoreconsole` adjuntando Visual Studio (M) · R: ✅ · Obs: sin la persona, con pruebas/dev/con14_dte.py: accoreconsole lanzado con un .scr que hace NETLOAD de AutoCADLab.Consola.dll (carpeta de confianza) y LABESPERA (espera hasta 60 s a Debugger.IsAttached); Visual Studio 2026 por COM pone un breakpoint en Comandos.cs:72, se adjunta (Process.Attach) y la consola SE DETIENE en EsperarDepurador; lee locales (adjuntado=true, dibujo=…\conase.dwg, entidades=0) y evalúa con el proceso parado Application.DocumentManager.MdiActiveDocument.Name; Go y la consola termina (exit 0), unos 7 s en total. TRAMPA: en el primer intento se adjuntó a los 1,2 s, antes de que la consola cargara CoreCLR, y Attach() eligió solo el motor nativo: Debugger.IsAttached siguió en False 60 s; hay que adjuntarse cuando .NET ya está cargado (aquí, al ver en la traza que LABESPERA espera)

### MAC · Macros, CUIx, DIESEL, Action Recorder y Express Tools

- [x] **MAC-01** Macro de botón en CUIx (`^C^C_LINE;0,0;10,0;;`): caracteres especiales, pausas (`\`) y comandos transparentes (M) · R: ✅ · Obs: botones de cinta de un CUIx parcial con la persona: ^C^C_LINE;0,0;100,0;; dibuja la línea y termina; ^C^C_LINE;\; (2 pausas) pide 2 puntos y termina solo; '_ZOOM;_E dentro de LINE es transparente («Resuming LINE command»); ^C^C_-LAYER;_M;LAB_CUIX;; crea y activa la capa (el último aviso queda escrito y no se reescribe Command:, lo que confunde a la persona); varios comandos en una macro funcionan; el atajo CTRL+ALT+L (MenuAccelerator) ejecuta la macro. TRAMPA: «\» es una pausa también dentro de una cadena LISP de la macro: (princ "
…") corta la macro; usar (chr 10). TRAMPA GRAVE (explicada en MAC-04, 2026-10-29): con OSNAPCOORD=2 (por defecto) y OSMODE activo, en macros, scripts y (command) de LISP la referencia a objetos se impone a las coordenadas escritas: los centros de ^C^C_CIRCLE;50,50;… saltaron al extremo de una línea cercana, y (command "_.LINE" "0,0,5" "10,0,10" "") dio (0,0,0)-(10,0,0) pegada a extremos existentes, frente a z 5/10 con "_non" delante de cada punto (control). Depende del zoom (la distancia se mide en píxeles). Usar _non o OSMODE=0 mientras corre el código
- [x] **MAC-02** DIESEL en `MODEMACRO` y en etiquetas de menú (`$(getvar, …)`) (A/M) · R: ✅ · Obs: DIESEL en MODEMACRO («MAC-02 capa=$(getvar,clayer) | hora=$(edtime,$(getvar,date),HH:MM:SS)», puesto por COM): aparece en la barra de estado ya evaluado y se recalcula al cambiar CLAYER y con la actividad. En cambio, en el texto de un botón de cinta de CUIx («Capa: $(getvar,clayer)») se evalúa solo al construir el panel: sigue diciendo «Capa: 0» tras cambiar la capa y tras cambiar de pestaña y volver. MODEMACRO restaurado a vacío. Sin probar: etiquetas de menú desplegable (MENUBAR=0)
- [x] **MAC-03** Action Recorder: grabar una acción y ver si el `.actm` es legible o generable por la IA (M/S) · R: ✅ · Obs: sin la persona (UI Automation para el diálogo): ACTRECORD + comandos + ACTSTOP abre el diálogo WPF «Action Macro» (m_tbName, m_btOk con ValuePattern/InvokePattern) y guarda en ACTRECPATH (%APPDATA%\…\support\Actions) un archivo .actmx (no .actm): JSON en texto, 9,8 KB, con $type de Autodesk.AutoCAD.MacroRecorder. Estado inicial (versión, OSMODE, DYNMODE, TILEMODE, INSUNITS, CANNOSCALE, vista, SCP) y un CommandNode por comando con sus nodos de entrada (GetPointNode, GetDistanceNode) con m_prompt, palabras clave y el valor grabado en m_data. LEGIBLE Y GENERABLE: una copia editada con Python (radio 2 → 7) se reproduce y crea el círculo de radio 7.0 en 0,0. TRAMPAS: las macros de la carpeta no existen como comandos al arrancar AutoCAD (getcname nil) hasta que se inicializa el Action Recorder (p. ej. un ACTRECORD; activar la pestaña Manage no basta); al reproducir sale el diálogo modal «Action Macro – Playback Complete» y, si cambió algo del estado (aquí INSUNITS: grabada en acad.dwt, reproducida en acadiso.dwt), «Action Macro – Inconsistency» (Use the current settings / Stop playback): diálogos Win32 que hay que cerrar (tools/pulsar.py). Las macros de prueba se movieron a %LOCALAPPDATA%\AutoCADLabctions
- [x] **MAC-04** Express Tools: qué comandos se pueden usar desde .NET, LISP, `.scr` y `accoreconsole` (A) · R: ✅ · Obs: Express Tools: en la interfaz, sus comandos (FLATTEN, BURST, TXTEXP, TCOUNT) son C: de LISP de tipo USUBR (cargadores) y las funciones acet-* no existen hasta que se usa el primer comando de Express, que carga acetutil.arx (luego acet-str-replace es EXRXSUBR y funciona). Funcionan escritos como texto en la línea de comandos: tecleados, desde .scr o desde .NET con SendStringToExecute («FLATTEN L␣␣N␣» pasó una LINE de z 5/10 a z 0, con control). NO funcionan con (command "FLATTEN" …) desde LISP ni con Editor.Command("FLATTEN", …) desde .NET (eInvalidInput): no llaman comandos definidos en LISP. En accoreconsole no existen: c:flatten nil, «Unknown command "FLATTEN"» (y el script se corta), arxload de acetutil.arx da «ARXLOAD failed» (un .arx necesita acad.exe; Express solo trae RText.crx) y acetauto.lsp falla con «no function definition: ACET-AUTOLOAD2»
- [x] **MAC-05** Generar un CUIx parcial desde cero (ZIP con XML) sin `AcCui.dll` y cargarlo (A/M) · R: ✅ · Obs: CUIx parcial escrito desde cero con Python (pruebas/cuix/mac05_generar.py), sin AcCui.dll: paquete OPC (ZIP) con [Content_Types].xml, _rels/.rels (una Relationship Type="CUI" por parte), Menu_Package_Info.xml y SOLO 3 partes CUI: Header.cui, MenuGroup.cui (una MenuMacro) y RibbonRoot.cui (pestaña, panel y RibbonCommandButton con Id="AcRibbonCommandButton"), 2,2 KB frente a las 20 partes y 13 KB de AcCui. AcCui lo lee sin errores (macros=1 tabs=1 panels=1), LoadPartialMenu=True en 223 ms, la pestaña «AutoCADLab a mano» aparece con su botón y el botón ejecuta la macro (^C^C_CIRCLE;_non;0,0;5; → círculo en 0,0 r 5). Para pulsarlo sin ratón: UI Automation solo ofrece TogglePattern en los botones de CUIx (Toggle no ejecuta nada, a diferencia del InvokePattern de los botones creados por código en UI-01); funciona desde dentro con ComponentManager.Ribbon.FindItem(id) y RibbonCommandItem.CommandHandler.Execute (el propio RibbonCommandButton de Autodesk.AutoCAD.Ribbon). UnloadPartialMenu al terminar
- [x] **MAC-06** Editar un CUIx con la API de `AcCui.dll` (`CustomizationSection`, fuera del inventario: por verificar) (S/A) · R: ✅ · Obs: CUIx parcial creado desde cero con AcCui.dll (CustomizationSection, MacroGroup.CreateMenuMacro, RibbonPanelSource/RibbonRow/RibbonCommandButton, RibbonTabSource con DefaultDisplay.AddToWorkspace, MenuAccelerator) y SaveAs en 1–102 ms, 13 KB; se relee bien. Trampas de la API: MacroGroup y MenuAccelerator se añaden solos a su colección al construirlos, pero RibbonPanelSource y RibbonTabSource NO (hay que hacer Add). TRAMPA GRAVE: RibbonItem.Id no es un nombre libre: con Id propio (LAB_BTN1…) la cinta construyó el panel con 0 botones sin ningún error; dejarlo en su valor por defecto (AcRibbonCommandButton, como en Express Tools)
- [x] **MAC-07** Alias en `acad.pgp` añadidos por código y recargados con `REINIT` (A) · R: ✅ · Obs: alias añadido por código al final de %APPDATA%\Autodesk\AutoCAD 2027\R26.0\enu\supportcad.pgp (findfile "acad.pgp"), tras copiarlo a %LOCALAPPDATA%\AutoCADLab\instcad.pgp.respaldo: «LABC, *CIRCLE». Antes de recargar no existe (control: ningún círculo); tras (setvar "RE-INIT" 16), que recarga el PGP sin el diálogo de REINIT, «LABC _non 30,30 3» crea el círculo. Restaurado el original (mismo SHA-1) y recargado: el alias desaparece. El archivo usa LF y la línea añadida CRLF: AutoCAD lo acepta. El PGP es del perfil, compartido por todas las instancias de AutoCAD de la persona
- [x] **MAC-08** Scripts `.scr` con interfaz (SCRIPT): espacios, líneas en blanco, `DELAY` y `RSCRIPT` (A) · R: ✅ · Obs: scripts .scr lanzados en la interfaz con (command "_.SCRIPT" ruta) (con FILEDIA=1 el SCRIPT tecleado abriría el diálogo). Cada espacio y cada salto de línea es un Enter: «_LINE 0,0 10,0␣↵» crea la línea y sigue; «_LINE 0,0 10,0␣␣↵» son tres Enter y el último, en Command: vacío, REPITE LINE, que se traga el resto del script (CMDNAMES=LINE). Una línea en blanco repite el comando anterior (un _CIRCLE dio 2 círculos). DELAY 1500 detiene el script 1.500 ms exactos (MILLISECS). RSCRIPT repite sin fin (~450 vueltas por segundo) y un error de LISP NO lo detiene («; error: bad argument type» en cada vuelta); se para con ^C enviado como texto (SendStringToExecute de ). Trampa del montaje: el editor de archivos de la IA quita los espacios finales de línea; los .scr se escriben con Python
- [x] **MAC-09** Comandos .NET con `CommandFlags.NoActionRecording` y `ActionMacro`: efecto en el Action Recorder (A/M) · R: ✅ · Obs: tres comandos .NET grabados con el Action Recorder y reproducidos: LABREC1 (Modal) queda como CommandNode y se reproduce; LABREC2 (Modal | NoActionRecording) no aparece en la macro (control de la marca: funciona); LABREC3 (Modal | ActionMacro) queda como MacroCommandNode «Copy of LABREC3» con la lista de hijos vacía y al reproducir NO ejecuta nada (AutoCAD lo trata como una macro de acciones anidada). Solo LABREC1 creó su círculo al reproducir. Montaje: los comandos van en una clase sin otros [CommandMethod] ya registrados, porque un eDuplicateKey corta el registro del resto de la clase

### JSA · JavaScript y HTML

- [x] **JSA-01** Si sigue habiendo API JavaScript: `Application.LoadJSScript`, `IJavaScriptCallable` y `Application.ShowModalWindow(Uri)` existen en el inventario; cargar un HTML local y mostrarlo (S/M) · R: ✅ · Obs: la API sigue en 2027: Application.ShowModelessWindow(Uri) abre una página HTML local (%LOCALAPPDATA%\AutoCADLab\jsa\lab.html); execAsync y window.chrome.webview existen sin incluir nada (AutoCAD inyecta AcWebViewInterop.js). Llamada JS → .NET con execAsync(JSON{functionName, invokeAsCommand:false, functionParams}) a un método de instancia con [JavaScriptCallback]: .NET recibe solo functionParams como texto (con un salto de línea final). TRAMPA: la respuesta debe ser un JSON {"retCode":0,"retValue":…}; con un JSON libre la página recibe retCode 1 «'retCode' is not present or not int in CallResult::deserialize» (aunque .NET sí se ejecutó). A diferencia de [CommandMethod] (DEV-09), al recargar el payload la función nueva sustituye a la vieja (respondió js-v3 y js-v4). Ctrl+C no copia texto dentro de la ventana web (la persona pegaba una y otra vez el portapapeles viejo): para recoger resultados, la página los reenvía a .NET
- [x] **JSA-02** Llamar desde JavaScript a una función .NET (`IJavaScriptCallable`) y de .NET a JavaScript (A/M) · R: ⚠️ · Obs: JS → .NET funciona (JSA-01): el callback corre en el hilo principal (ManagedThreadId 2), con IsApplicationContext=True, IsQuiescent=False y el documento ya en LockMode Write (LockDocument anidado funciona). .NET → JS: no hay API pública (ni en acdbmgd/acmgd/accoremgd ni en AdWindows); AcWebViewInterop.js tiene registerCallback/onCallbackReceive, que llama el host (uso interno). Sin probar invokeAsCommand:true
- [x] **JSA-03** Motor del navegador embebido (CEF o WebView2): versión y si permite `fetch` a internet (M) · R: ✅ · Obs: motor WebView2: userAgent «Chrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0 Autodesk/ACAD/26.0/en-US/A101». fetch("https://example.com/") desde la página local funciona: HTTP 200 en 557 ms, y 1 ms la segunda vez (caché). La página tiene red sin restricciones aparentes
- [x] **JSA-04** WebView2 propio dentro de un `PaletteSet` frente a la API JS de Autodesk: qué se puede hacer con cada uno (M) · R: ✅ · Obs: comparación completa. (a) API de Autodesk (JSA-01, JSA-04a): página local con PaletteSet.Add(nombre, Uri) o ShowModelessWindow(Uri); el puente execAsync existe sin incluir nada; solo JS → .NET ([JavaScriptCallback] con respuesta {retCode, retValue}); no hay API pública de .NET → JS; el userAgent lleva «Autodesk/ACAD/26.0». (b) WebView2 PROPIO (paquete Microsoft.Web.WebView2 1.0.4191.47 descargado con permiso, en un payload sin comandos, addins/AutoCADLab.WebView): control WPF en una PaletteSet con CoreWebView2Environment.CreateAsync y carpeta de datos propia (%LOCALAPPDATA%\AutoCADLab\wv2; la de por defecto estaría junto a acad.exe, en Program Files): se inicializa en 0,5 s con el MISMO runtime evergreen que AutoCAD (156.0.4314.50); página → .NET con chrome.webview.postMessage y WebMessageReceived en el hilo principal; .NET → página con ExecuteScriptAsync (devuelve {"suma":4,"ancho":274}) y PostWebMessageAsString (la página responde «eco: hola desde .NET»), comprobado también con captura; execAsync NO existe (undefined) y el userAgent no lleva la marca de Autodesk. Resumen: el propio da comunicación en las dos direcciones y control total, pero sin el puente de Autodesk; los callbacks del propio no están sujetos a [JavaScriptCallback] ni al formato retCode
- [x] **JSA-05** Crear 1.000 entidades desde JavaScript: tiempo (P) · R: ✅ · Obs: 1.000 líneas desde JavaScript: 1.000 llamadas execAsync (una línea cada una, con su transacción) 406 ms en total (~0,4 ms por llamada, más rápido que COM desde fuera, 0,7–1 ms); una sola llamada que crea las 1.000: 5 ms en JS (3,6 ms en .NET)

### VBA · Visual Basic for Applications (no instalado)

- [x] **VBA-01** Instalar el módulo VBA de AutoCAD 2027 con permiso del usuario: origen, tamaño y si exige reiniciar (M) · R: ✅ · Obs: Lo instaló el usuario (permiso de la tanda 16): Autodesk_AutoCAD_2027_VBA_Enabler.exe, 59 MB, de autodesk.com y con la firma de Autodesk válida; la instalación funcionó al 2.º intento. Instala «AutoCAD 2027 VBA Enabler 26.0.60.0» como bundle (C:\Program Files\Autodesk\ApplicationPlugins\AcVBA2027.Bundle\Contents\AcVba.arx, 436 KB) más VBA 7.1 en Common Files. acvba.arx carga en AutoCAD sin reiniciar Windows; los comandos VBA aparecen al abrir una instancia nueva
- [x] **VBA-02** Macro VBA que crea líneas, ejecutada con `-VBARUN` desde un `.scr` (A/M) · R: ⚠️ · Obs: El proyecto se crea por código: _VBANEW (en (command) no crea nada) y AcadApplication.VBE → VBComponents.Add(1) + CodeModule.AddFromString. RunMacro("LabModulo.Lineas") crea 10 líneas en 2-7 ms. Guardar: VBProject.SaveAs da «not valid in this type of project» y el comando VBASAVE NO EXISTE en 2027 ((getcname "VBASAVE") → nil; control: _VBAMAN sí). Solo se guarda por la interfaz: VBA Manager → Save as (con mensajes Win32, pruebas/vba/vbaman_guardar.py; .dvb de 16 KB). -VBARUN desde un .scr: 💥 caída (Access Violation 0xffffffff) con el proyecto creado en memoria y sin guardar; ✅ funciona con el .dvb cargado desde disco ((vl-vbaload) + _-VBARUN ruta!Modulo.Macro: +10 líneas, repetido). -VBARUN por SendStringToExecute/(vl-vbarun)/SendCommand con el proyecto en memoria: «Can't run macro, AutoCAD is busy» y CMDNAMES=-VBARUN colgado
- [x] **VBA-03** VBA en `accoreconsole` (A) · R: ❌ · Obs: No hay VBA en accoreconsole: (arx) solo lista .crx; (arxload "acvba.arx") y con la ruta completa del bundle → «ARXLOAD failed»; -VBARUN y -VBALOAD no existen (getcname → nil); (vl-vbaload) → «bad argument type: VLA-OBJECT nil» (la consola no tiene objeto Application). (vl-vbarun) devuelve T, pero no se ha medido si hace algo (sin VBA cargado, no puede). Control: un script trivial en la misma consola, exit 0
- [x] **VBA-04** Cargar un `.dvb` por código y su relación con SECURELOAD (A/M) · R: ✅ · Obs: AcadApplication.LoadDVB(ruta) + RunMacro("<ruta completa>!LabModulo.Lineas") (solo con el nombre del archivo: «Failed to run VBA macro») + UnloadDVB. SECURELOAD=1: en una ruta de confianza carga sin aviso (24 ms); fuera de ella, diálogo «Security - Unsigned Executable File» que bloquea la llamada COM. «Do Not Load» → COMException 0x8021004A «Problem in loading DVB file», y el mismo archivo vuelve a fallar en 1 ms sin diálogo durante la sesión (control: otro archivo sí muestra el diálogo). «Load Once» → carga, y ese archivo queda admitido en la sesión aunque luego se pase a SECURELOAD=2. SECURELOAD=2 con un archivo nuevo fuera de confianza → 0x8021004A en 7 ms, sin diálogo; en una ruta de confianza, bien. SECURELOAD=0 no se probó (rebaja la seguridad)
- [x] **VBA-05** Generar y depurar VBA con la IA sin herramientas modernas: iteraciones y errores frente a .NET (S/P) · R: ⚠️ · Obs: El código VBA (vba.bas, 11 líneas) funcionó al primer intento. La dificultad está en el entorno: 3 suposiciones falsas de la IA (A1/C5): que existe VBASAVE, que VBProject.SaveAs sirve y que RunMacro acepta solo el nombre del .dvb; un 💥 con -VBARUN desde un .scr y el aviso «busy». Sin compilador ni inventario de comandos: los errores solo salen ejecutando, y los mensajes son genéricos (0x8021004A, «Failed to run VBA macro»). La depuración (VBAIDE) es solo interactiva; no hay DAP ni DTE. Frente a .NET: más intentos por cada resultado y ninguna detección automática. Ver Metricas de dificultad IA.md

### ARX · ObjectARX en C++ (SDK no instalado)

- [x] **ARX-01** Descargar e instalar el SDK de ObjectARX 2027 con permiso del usuario: toolset de Visual Studio exigido y tamaño (M) · R: ⚠️ · Obs: objectarx-for-autocad-2027-win-64bit.exe (121 MB, firmado por Autodesk; lo descargó y extrajo el usuario en Downloads\CDROM1): 179 MB con inc, inc-x64, lib-x64 (acdb26.lib…), samples, docs, utils. Los ejemplos piden PlatformToolset v143 (VS 2022) y C++20; VS 2026 trae v145 (MSVC 14.51.36231), que instalé con elevación (error mío: --passive sin elevación daba 5007). No reinstalé v143: compilando con cl y v145 contra el SDK funciona (ARX-02); los .vcxproj de ejemplo no se han reorientado ni probado
- [x] **ARX-02** Compilar y cargar con `ARXLOAD` un `.arx` mínimo con un comando (A) · R: ✅ · Obs: pruebas/arx/arx02/lab_arx.cpp (≈80 líneas, sin MFC ni asistente) + compilar.cmd (cl /LD + rxapi/accore/acdb26/acge26; acrxGetApiVersion exportado con #pragma). Compiló al 2.º intento (faltaba adscodes.h para RTSTR); 15,8 KB, ~1,6 s. (arxload) desde la ruta de confianza sin aviso; LABARX01 crea 10 líneas y escribe USERS1=«ARX v1: 10 lineas»
- [x] **ARX-03** Descargar y recargar un `.arx` sin reiniciar (`ARXUNLOAD`) frente al bloqueo de las DLL .NET (DEV-13) (A/P) · R: ✅ · Obs: Con la v1 cargada, sobrescribir el .arx da «Device or resource busy» (bloqueado, como las DLL .NET). (arxunload "lab_arx.arx") lo descarga (gracias a unlockApplication en kInitAppMsg) y libera el archivo; se copia la v2, (arxload) y LABARX01 → «ARX v2: 10 lineas» en la misma sesión, sin reiniciar. Frente a .NET (DEV-13: un ensamblado no se puede descargar), ARX sí permite el ciclo editar → compilar (1,6 s) → descargar → recargar
- [x] **ARX-04** Excepción nativa en un `.arx`: AutoCAD muere y qué queda en CER (M/J) · R: 💥 · Obs: LABARXCRASH (lee un puntero nulo): diálogo «AutoCAD Error Aborting: FATAL ERROR: Unhandled Access Violation Reading 0x0000 Exception at 9CD1074h» → cer_dialog «AutoCAD Error Report» (cerrado SIN enviar con WM_CLOSE) → «AutoCAD cannot continue… Should AutoCAD try to save your changes?» → Sí guardó arx05_recover.dwg (31 KB) → el proceso termina. CER deja en %LOCALAPPDATA%\Autodesk\CER\<id>\<n>\ un minidump.dmp.zip (734 KB) y los .pb; cer.log registra exception/code 0xC0000005, module_name=lab_arx.arx, address 0x00007FFE09CD1074: identifica el módulo culpable. No hay forma de capturarlo desde .NET ni de que AutoCAD siga vivo
- [x] **ARX-05** Entidad personalizada y objeto proxy al abrir el DWG sin el `.dbx` (A/V) · R: ✅ · Obs: LabMarca (AcDbEntity: dwgIn/OutFields, subWorldDraw, subTransformBy, subGetGeomExtents) en lab_ent.dbx + comando LABENT en lab_ent_cmd.crx; compiló al primer intento. Creada y guardada en arx05.dwg (LABMARCA=1). Al abrirlo en una instancia nueva sin el .dbx: diálogo modal «Proxy Information» («Missing Application: AutoCADLab / Product Desc: AutoCADLab ARX-05… Total Number of Proxies: 1, entities with real graphics metafile: 1») que BLOQUEÓ el arranque (el pipe del runner no respondió en 180 s hasta pulsar OK). Después: ACAD_PROXY_ENTITY=1, se ve el gráfico guardado (círculo con cruz) y (entdel) no lo borra (proxy flags kNoOperation). Completa EXP-12: un proxy SÍ detiene un lote abierto como argumento de acad.exe (con PROXYNOTICE=1)
- [x] **ARX-06** `.dbx` propio cargado en `accoreconsole` (A) · R: ✅ · Obs: En accoreconsole (/i base.dwg): (arxload) del .dbx y del .crx bien, LABENT crea la LabMarca (es=0), (ssget) → (LABMARCA) y SAVEAS 2018 → arx06.dwg (32 KB), exit 0 en 2,4 s. La consola abre arx05.dwg sin el .dbx sin ningún aviso: la entidad sale como ACAD_PROXY_ENTITY (exit 0, 2,2 s). Contraste con VBA-03: el .arx de VBA no carga en la consola, pero un .dbx/.crx propio sí

### APS · Design Automation (nube, por verificar)

- [x] **APS-01** Qué contiene la carpeta local `Design Automation/` de la instalación, sin ejecutar nada en la nube (S) · R: ✅ · Obs: Verificado listando sin ejecutar nada. 'C:\Program Files\Autodesk\AutoCAD 2027\Design Automation' tiene 27 archivos (1,44 MB) y ninguna documentación ni ejemplo. En Bin\ están Das.Local.exe/.dll 6.1.0.0 (.NET 10 + ASP.NET Core 10, según runtimeconfig) y sus dependencias: Newtonsoft.Json 13.0.3, Json.Bson, AspNetCore.JsonPatch/Mvc.NewtonsoftJson 8.0.13 y System.IO.Abstractions 22.0.11. Por sus cadenas es un servidor HTTP local ('http://localhost', PostWorkItem/CancelWorkItem, reportUrl, debugInfoUrl, x-ads-das-synthetic-workitem): un clon local del servicio de WorkItems de Design Automation. Bin\Activities\ tiene 15 definiciones de actividad *+prod.json con el mismo formato que APS (commandLine, parameters con verb get/post/read, engine 'Autodesk.AutoCAD+23_1', appbundles, settings): BlockSuggestion, EncodeBlock, ExportBlockMetadataCmd, ExtractAllBlockPreviews, ExtractBlockFileMetadata, ExtractBlockNameList, ExtractBlockPreview, IndexBlock, TakeBlockSnapshot, PlotToPdf ('AcCoreConsole.exe /i HostDwg /s _scr' con '_tilemode 0 _-export _pdf _all'), PublishDsd, Isolate ('AcCoreConsole.exe /isolate'), Delay, LongDelay e IsolateDelay. Quién lo usa: DasClient.dll (raíz de AutoCAD) contiene la cadena 'Das.Local', y BlockIndexer\ trae AcDasLocalLauncherLib.dll, AcBlockIndexPipeline, SuggestionEngine y BlockEncoder. Parece la infraestructura local de las funciones de bloques (sugerencias, sustitución e índice) y de PDF, no un SDK para el usuario. Que lo lance AutoCAD queda como interpretación, sin verificar en ejecución.
- [x] **APS-02** AppBundle (`PackageContents.xml` + DLL) que funciona en `accoreconsole` local como ensayo previo (A) · R: ✅ · Obs: CORREGIDO el 2026-10-29 (antes ❌). El AppBundle SÍ funciona en accoreconsole local si se cumplen dos condiciones, y faltaban las dos: (1) /al recibe la carpeta que CONTIENE LabAps.bundle, no el .bundle (así lo hace Design Automation: /al "…\LabAps[1].package", visto en el informe de APS-04); (2) esa carpeta debe estar en TRUSTEDPATHS. Matriz medida con el mismo bundle (PackageContents.xml de APS-04 + AutoCADLab.Aps.dll) y el script LABAPS: carpeta .bundle fuera de confianza → «Unknown command»; carpeta .bundle dentro de confianza → «Unknown command»; carpeta padre fuera de confianza → «Unknown command», sin ningún mensaje de SECURELOAD en el log; carpeta padre dentro de confianza (bajo …\ApplicationPlugins\AutoCADLab.bundle\..., ya en TRUSTEDPATHS) → LABAPS crea 10 líneas e info.txt. El bundle de prueba se borró después. El fallo por SECURELOAD es silencioso: el comando simplemente no existe
- [x] **APS-03** Crear cuenta y aplicación en APS, con permiso del usuario por el coste (M) · R: ✅ · Obs: La persona creó la cuenta y una app «Server-to-Server» en APS (2026-10-29). Token de dos patas (client_credentials, scope code:all) en 1,1 s, 3.599 s de vida. Design Automation responde: forgeapps/me devuelve el client id (sin apodo) y hay 32 motores, 7 de AutoCAD, el más nuevo Autodesk.AutoCAD+26_0 (= AutoCAD 2027). Hasta aquí sin coste: ni bucket, ni AppBundle, ni WorkItem
- [x] **APS-04** Ejecutar un WorkItem con ese AppBundle en la nube: tiempo y créditos gastados, con permiso del usuario (P) · R: ✅ · Obs: pruebas/aps/aps04.py + addins/AutoCADLab.Aps (comando LABAPS solo con accoremgd/acdbmgd, sin acmgd). Bucket OSS transient, entrada base.dwg (31,8 KB) como URN de OSS con cabecera Authorization, AppBundle LabAps v1 (zip de 3,8 KB), Activity LabAct v1 (accoreconsole /i /al /s con el script en settings) y UN WorkItem: success al primer intento. Tiempos de stats: en cola → descarga 0,06 s, descarga 0,5 s, instrucciones (accoreconsole) 2,2 s, subida 0,65 s, total 3,7 s en el servidor y 8,3 s de pared con sondeo cada 3 s; el ciclo entero (token, bucket, subidas, alias, WorkItem, bajadas) 18 s. El salida.dwg bajado tiene las 10 líneas (verificado en accoreconsole local; ACADVER=26.0). Entorno de la nube según info.txt: .NET 10.0.13 (local: 10.0.14), Windows Server 10.0.20348, 2 CPU, 99 MB, máquina EC2AMAZ-*, carpeta C:\DARoot\Jobs\<id>, limitProcessingTimeSec=100. Ningún aviso de pago ni de créditos en las respuestas, y el informe no dice cuánto costó: el consumo hay que mirarlo en el portal (cuenta de estudiante, sin tarjeta). Contraste con APS-02 ❌: en la nube /al SÍ carga el AppBundle .NET; en local no (hipótesis sin verificar: el bundle local estaba fuera de TRUSTEDPATHS)

### DWG · Leer DWG y DXF sin AutoCAD

- [x] **DWG-01** Versión del DWG desde los 6 primeros bytes (`AC1032`…) sin AutoCAD (S) · R: ✅ · Obs: Verificado sin AutoCAD (Python, solo bytes; scan_preview.py y dwg_offline.py en %LOCALAPPDATA%\AutoCADLab\offline). Tabla: AC1009=R11/12, AC1012=R13, AC1014=R14, AC1015=2000, AC1018=2004, AC1021=2007, AC1024=2010, AC1027=2013, AC1032=2018 (2018-2027). Aplicada a 1223 DWG: 1119 del laboratorio (1115 AC1032; con11\v2000.dwg=AC1015 y v2013.dwg=AC1027, guardados así a propósito; 2 plantillas copiadas de Autodesk en con\iso = AC1021) y 104 de la instalación (91 AC1021, 11 AC1024, 2 AC1032). Todos los guardados por AutoCAD 2027 dan AC1032, como se esperaba. El DXF con11\actual.dxf da AC1032 por $ACADVER (en un DXF no está en los 6 primeros bytes). Los dañados siguen dando AC1032: exp10-truncado (9623 bytes) y exp10-cabecera, que tiene los bytes 0x0B-0x14 cambiados por 0x55 (maint=85, codepage=21845, dirección de vista previa inválida). Por tanto los 6 bytes no detectan daños. Límite: AC1032 no distingue 2018 de 2027; eso sale de la cadena UTF-16 <ProductInformation ... registry_version="26.0"> (o de "25.0" en Sample\Mechanical - Metric.dwg, guardado por AutoCAD 2025 y también AC1032).
- [x] **DWG-02** Miniatura incrustada del DWG extraída sin AutoCAD a partir de la especificación pública (S) · R: ⚠️ · Obs: Verificado: el índice de la vista previa se lee sin AutoCAD según la especificación ODS. Se usa el puntero de la cabecera (0x0D), que coincide con el sentinel 1F256D07D43628289D57CA3F9D44102B en 1218 de 1223 archivos (0x120 en placa-000/exp02, 0x1C0 en los de 2013+). Tras el sentinel van el tamaño, el número de entradas y, en cada una, el tipo (1=cabecera de 80 bytes en ceros, 2=BMP DIB, 6=PNG), el inicio y el tamaño. Se extrajo un PNG de 'Sample\en-us\Dynamic Blocks\Mechanical - Metric.dwg' (256x138, 1051 bytes, 2 colores): muestra en blanco, sobre fondo gris oscuro (33,40,48), una columna de unos 10 símbolos de bloques mecánicos (válvulas, interruptores y etiquetas de equipo). De v2000.dwg (R2000) se extrajo un DIB de 184x102 a 8 bits, convertido a PNG con PIL. Matiz importante: placa-000.dwg y exp02.dwg NO llevan imagen (solo la entrada de cabecera), igual que 726 archivos del laboratorio. Otros 384 llevan un PNG de 896 bytes, y los 3 revisados (con02, cmp05-base, v2013) son de un solo color de fondo (vacíos). 5 archivos no tienen vista previa: exp10-cabecera (puntero 0x55555555, porque tiene la cabecera dañada) y los cuatro lim05_* recuperados (puntero 0). Control: la miniatura que da el Explorador (IShellItemImageFactory) es idéntica píxel a píxel a la extraída en Mechanical-Metric y en con02. Archivos: offline\mech-metric-miniatura.png y offline\mech-metric-x4.png.
- [x] **DWG-03** Metadatos que Windows muestra de un DWG (propiedades y miniatura del Explorador) y de dónde los saca (S) · R: ⚠️ · Obs: Verificado en el registro (solo lectura): HKCR\.dwg = AutoCAD.Drawing.26. Las claves shellex {E357FCCD...} (IThumbnailProvider) y {BB2E617C...} (IExtractImage) apuntan a {AC1DB655-...} ACTHUMBNAIL = Common Files\Autodesk Shared\Thumbnail\AcThumbnail16.dll (26.0.118). El PropertyHandler de .dwg (HKLM ...PropertySystem\PropertyHandlers) es {8A0BC933-...} AcColumnHandler, en AcShellEx\AcShellExtension.dll. También hay un PersistentHandler/IFilter, AcDwgFilter16.dll (para el indexador de Windows Search), un InfoTip AcInfoTipHandler, una hoja de propiedades acadficn.dll y AcSignExt.dll (firmas digitales). La apertura desde el Explorador pasa por AcLauncher.exe. Propiedades leídas con Shell.Application: del propio DWG solo sale System.Document.LastAuthor ('User' en con02/v2013, 'Autodesk' en Mechanical-Metric, ninguno en exp02/placa-000, que no lo guardan). Título, autor, asunto y comentarios salen vacíos porque esos DWG no tienen DWGPROPS rellenas: no lo pude probar con valores, porque crearlos exige AutoCAD. En v2000.dwg (R2000), LastAuthor no aparece aunque 'User' está en DWGPROPS; supongo, sin verificarlo, que el handler solo lee el prop_set XML de R2004+. Miniatura del Explorador: es la vista previa incrustada (control de DWG-02, idéntica píxel a píxel). Para placa-000 (sin imagen), GetImage(THUMBNAILONLY) devuelve 0x80004005. Trampa medida: Shell.Namespace() devuelve null para la carpeta %LOCALAPPDATA%\AutoCADLab\con, porque CON es un nombre de dispositivo reservado; hubo que copiar los archivos a offline\shell.
- [x] **DWG-04** Leer y escribir DXF con una librería abierta (por ejemplo `ezdxf`, MIT, con permiso para instalarla) y abrir el resultado en AutoCAD (S/A) · R: ✅ · Obs: ezdxf 1.4.4 (MIT) YA ESTABA INSTALADO (no hubo que instalar nada). Escribir: ezdxf.new('R2018') con capa, LINE, CIRCLE, TEXT «DWG-04 ñandú €» y un bloque con atributo → DXF de 69 KB que AutoCAD abre con DocumentManager.Open en 303 ms sin diálogos: CIRCLE=1, INSERT=1, LINE=1, TEXT=1 y el texto intacto (U+00F1 U+00FA U+20AC). Leer: los DXF guardados por AutoCAD 2027 (EXP-03 y CON-11) se leen como AC1032/R2018 con todas sus entidades (hasta 2.000 LINE + 101 CIRCLE + TEXT) y audit() con 0 errores; ezdxf detecta las XData (has_xdata). Sin comprobar a fondo: atributos y XData campo a campo
- [x] **DWG-05** Leer DWG con una librería de licencia compatible (por ejemplo ACadSharp, MIT; revisar la licencia antes) y comparar con lo que ve AutoCAD (S) · R: ✅ · Obs: con permiso de la persona para descargar ACadSharp 3.8.0 (MIT, autor DomCr; se eligió la de 2026-09-21 en lugar de la 3.8.35 del día anterior). Consola pruebas/dwg/Dwg05 SIN AutoCAD: lo que ve coincide con AutoCAD en todos los DWG guardados por AutoCAD 2027 (AC1032): placa-000 → INSERT PLACA con CODIGO=VIEJO-000; exp02 → 6 Circle + 1 TextEntity «EXP-02 modelo»; vis10 → 256 presentaciones (el límite de VIS-10); D2-grande (24 MB) → exactamente 600.000 Line, 200.000 Circle, 100.000 LwPolyline y 100.000 TextEntity y 21 capas, en 8,3 s (frente a 0,6 s con ReadDwgFile dentro de AutoCAD); el DWG dañado (exp10-centro) → ArgumentOutOfRangeException. LÍMITE: la cabecera no es fiable: LastSavedBy sale «ACadSharp» (su valor por defecto) y CreateDateTime sale en el futuro (2026-10-30) en todos: no usar sus metadatos. Sin comprobar: escribir DWG con ACadSharp y abrirlo en AutoCAD
- [x] **DWG-06** Privacidad: rutas, usuario y otros datos personales que viajan dentro de un DWG (S) · R: ⚠️ · Obs: Verificado en los bytes (ASCII/cp1252 y UTF-16LE) de 10 DWG y 1 DXF (privacidad.py, privacidad.json). En claro, en AC1032 (AcDb:SummaryInfo/AppInfo, sin comprimir): el prop_set XML con fmtid f29f85e0 (SummaryInformation) incluye prop 8 = último autor = nombre de usuario de Windows ('User', presente en con02 y v2013; los guardados por accoreconsole/.NET, como exp02 y placa-000, no lo llevan), fechas (prop 10/12: 2026-10-28T10:12:50 y 2013-01-16, esta heredada de acad.dwt), el producto ('AutoCAD 2027', build X.118.0.0) y <ProductInformation> con install_id_string='ACAD-A101:409', registry_version 26.0, idioma 1033 y git_commit_id. NO aparecen en claro, en ningún DWG 2013/2018, ni el nombre del equipo (EDER3232-I9), ni 'C:\Users', ni rutas del laboratorio. Control de compresión, con el mismo dibujo en tres formatos: el texto 'AutoCADLab CON-07' se ve en v2000.dwg (R2000, sin comprimir) y en actual.dxf, pero no en v2013 ni en v2018 (secciones comprimidas con LZ77 en R2004+). Por tanto, el contenido, las variables de cabecera y las rutas de AC1032 no se pueden auditar sin descomprimir: no verificado. El DXF (equivale a descomprimir) muestra $LASTSAVEDBY=User, $TDCREATE/$TDUPDATE, $TDINDWG (tiempo de edición, 0.000129 días), $FINGERPRINTGUID y $VERSIONGUID, y una ruta heredada de la plantilla: 'C:\Documents and Settings\basas\Application Data\Autodesk\AutoCAD 2005\...\Default Windows System Printer.pc3' (usuario ajeno 'basas'). La muestra de Autodesk 'Assembly Sample.dwg' (AC1021) lleva en claro 'C:\Documents and Settings\Administrator\...\Plot Styles\' y rutas de fuentes. La entropía media por bloques de 4 KiB (5.8-6.9) no sirve para detectar la compresión, porque el LZ77 de DWG deja la entropía baja.
- [x] **DWG-07** ODA File Converter y Teigha: solo documentar licencia y condiciones, sin instalar sin permiso (S) · R: ⚠️ · Obs: Solo documentación web, sin descargar nada (2026-10-29). ODA File Converter y ODA Viewer: opendesign.com/node/3218 los describe como 'free downloads' para conocer los SDK de ODA, y dice que 'If you are not an ODA member, you can use them for non-commercial applications only'. También dice 'We do not distribute ODA File Converter' (fuera de su web) y que solo ofrece la última versión: las anteriores son para miembros. No encontré el texto exacto de la licencia ni una definición de 'non-commercial', ni si la descarga exige cuenta. ODA SDK (antes Teigha), según la página y el PDF de precios de 2026: suscripción anual. Commercial: 3.000 USD el primer año y 2.250 USD la renovación; menos de 100 copias al año, sin web/SaaS. Sustaining: 7.500/4.500 USD, distribución ilimitada y SaaS. Founding: 37.500/18.000 USD, con el código fuente. Non-Commercial: 375/150 USD, uso interno, 2 años como máximo y sin comercializar. Si se deja la suscripción, se pierde el derecho a distribuir. Prueba gratuita: 60 días, cuenta con correo confirmado, 1 equipo, solo DLL y sin depuración. Fuentes: https://www.opendesign.com/node/3218 ; https://www.opendesign.com/agreements/2026/en/ODA%20Membership%20&%20Extension%20pricing.pdf ; https://www.opendesign.com/pricing/individual.md ; https://opendesign.com/free-trial . El resumen de precios viene del buscador, no lo leí en el PDF original: conviene confirmarlo antes de citarlo.

### CMP · Comparación entre vías

Cada prueba mide el **tiempo de ejecución** y la **dificultad para la IA** (iteraciones, errores, tokens; ver la sección 2) en: .NET, LISP, COM dentro del proceso, COM desde fuera y `accoreconsole`.

- [x] **CMP-01** Crear 10.000 líneas por .NET en una transacción (P) · R: ✅ · Obs: 10.000 líneas por .NET en una transacción del documento activo: 60 ms (6 µs/línea); borrarlas 62 ms
- [x] **CMP-02** Crear 10.000 líneas por LISP con `entmake` y con `vla-AddLine` (P) · R: ✅ · Obs: 10.000 líneas por LISP en el documento activo: entmake 109 ms (11 µs/línea), vla-AddLine 360 ms (36 µs/línea)
- [x] **CMP-03** Crear 10.000 líneas por COM dentro del proceso (`dynamic` desde .NET) (P) · R: ✅ · Obs: 10.000 líneas por COM dentro del proceso: dynamic 163 ms (16 µs/línea), interop tipada 221 ms (22 µs/línea)
- [x] **CMP-04** Crear 10.000 líneas por COM desde fuera: C#, Python y PowerShell (P) · R: ⚠️ · Obs: COM desde fuera, medido con 1.000 líneas (no 10.000): PowerShell 690 ms, C# tipada 713 ms, C# dynamic 812 ms, Python 1.013 ms (0,7–1 ms por llamada entre procesos, ~100–170 veces más lento por línea que .NET dentro del proceso)
- [x] **CMP-05** Crear 10.000 líneas en `accoreconsole` con `.scr` puro, con LISP y con DLL (P) · R: ✅ · Obs: accoreconsole, 10.000 líneas, 3 ejecuciones por vía (subagente): .scr puro (10.000 _.LINE, OSMODE=0) 7,0–7,8 s de trabajo (0,7 ms/línea) y 10,3 s de proceso; LISP entmake 31–47 ms de trabajo y 2,5 s de proceso; DLL (LABLINEAS) 24–25 ms y 3,2 s. El .scr es ~250 veces más lento que LISP o .NET; con LISP o .NET manda el arranque de la consola (1,5–5 s), no el trabajo
- [x] **CMP-06** Recorrer 100.000 entidades y contarlas por capa, por cada vía (P) · R: ✅ · Obs: contar por capa 100.000 entidades (cmp100k.dwg: 10 capas × 10.000). Interfaz, documento abierto: .NET 326 ms; COM interno dynamic 469 ms; LISP ssget por capa 625 ms y entget de todo 891 ms; COM externo Python: SelectionSet filtrado 623 ms, recorrer entidad a entidad 176 s (1,8 ms por entidad). .NET sobre base lateral: ReadDwgFile 50 ms + contar 32 ms (el documento abierto es ~10 veces más lento que la base lateral). Consola: DLL 89–92 ms, LISP 453–485 ms (ssget filtrado ≈ entget de todo). Control: 10.000 por capa en todas las vías
- [x] **CMP-07** Cambiar de capa 10.000 entidades, por cada vía (P) · R: ✅ · Obs: 10.000 entidades de C3 a DESTINO. Interfaz: .NET 106 ms; LISP entmod 312 ms; COM interno dynamic 446 ms; COM externo Python (SelectionSet + Layer=) 17,7 s. Consola: DLL 113–119 ms + QSAVE 62 ms; LISP entmod 156 ms + QSAVE 47 ms. Control: DESTINO 10.000 tras cada vía (en la interfaz se devolvió a C3 con .NET entre una vía y otra)
- [x] **CMP-08** Procesar 100 DWG (abrir, cambiar un atributo, guardar): .NET en AutoCAD, `accoreconsole`, ObjectDBX por COM (P) · R: ✅ · Obs: 100 DWG, atributo CODIGO VIEJO-nnn → NUEVO-nnn y guardar. .NET en AutoCAD con base lateral: 1,14 s (11 ms/archivo). ObjectDBX por COM desde Python: 3,3 s (33 ms/archivo). Consola, un proceso con DLL (ReadDwgFile): 544 ms los 100 (5 ms/archivo) + 7,75 s de proceso. Consola, un proceso por archivo con LISP: 277 s (2,8 s/archivo). Abrir documentos visibles (EXP-08): ~600 ms/archivo. Controles: relectura de 100 NUEVO y 0 VIEJO; muestras NUEVO-042 por varias vías
- [x] **CMP-09** Misma tarea mediana escrita por la IA en .NET, LISP, COM con Python y `.scr` (renombrar bloques y actualizar sus atributos): iteraciones, errores y tokens (P) · R: ⚠️ · Obs: tarea «renombrar el bloque PLACA a PLACA_V2 y añadir -R al atributo CODIGO» escrita por la IA (Opus 5.5, C0) en 4 vías, sobre copias de placa-000.dwg, con control al reabrir: .NET (base lateral, GetBlockReferenceIds) 1 compilación y 1 ejecución, a la primera; LISP en accoreconsole (-RENAME + entmod de los ATTRIB, sin vla-*) 1 ejecución, a la primera, ~1,7 k tokens de salida; Python COM con ObjectDBX (Blocks.Item().Name, GetAttributes) 1 ejecución, a la primera, ~1 k tokens; .scr puro (-RENAME + -ATTEDIT global) 1 ejecución, a la primera, pero SOLO porque el valor era conocido: un .scr sin LISP no puede añadir un sufijo a un valor desconocido. ⚠️ por el procedimiento: en .NET la tarea de métricas se abrió después de escribir el código (sin minutos ni tokens), y lab no cuenta las ejecuciones de la consola ni de Python. Tarea demasiado fácil para separar las vías: hace falta una más larga
- [x] **CMP-10** Tiempo de iteración editar → ver resultado por vía (P) · R: ✅ · Obs: tiempo de máquina editar → ver (sin el tiempo de pensar), media de 3: Python COM (lanzar un script que conecta, crea y borra) ~80 ms; LISP en la interfaz (editar el .lsp, cargarlo por el runner y leer el resultado) ~820 ms; .NET con el runner (compilar + ejecutar) ~2,2 s (DEV-01); consola LISP 2,96 s; consola DLL 3,79 s (build incremental 0,9 s). En la consola, la DLL itera casi como LISP porque cada proceso es nuevo
- [x] **CMP-11** Matriz final capacidad × vía con lo que cada vía no puede hacer (S) · R: ⚠️ · Obs: primera versión de la matriz capacidad × vía en reports/Matriz de vias AutoCAD 2027.md (rendimiento, capacidades y límites, trampas por vía, dificultad para la IA). Provisional: con 132/244 pruebas hechas; quedan por verificar varias celdas (bloques dinámicos por LISP o COM, trazado desde la interfaz, matriz asociativa por COM) y la medida de dificultad con una tarea más larga

### LIM · Estrés y límites duros

- [x] **LIM-01** 1.000.000 de entidades en un dibujo: tiempo, memoria y tamaño del DWG (P) · R: ✅ · Obs: instancia propia, D2-grande.dwg en %LOCALAPPDATA%\AutoCADLab\estres (600.000 LINE, 200.000 CIRCLE, 100.000 TEXT, 100.000 LWPOLYLINE de 10 vértices, 20 capas): crear en base lateral 1,9 s (10 transacciones de 100.000), SaveAs 1,1 s, 24 MB; memoria privada +510 MB. ReadDwgFile 0,6 s. Abrir como documento 3,9 s, ZoomExtents 2,4 s, Regen 2,3 s, CloseAndDiscard 0,9 s (+670 MB al abrir, se liberan al cerrar). accoreconsole: abrir y ssget de las polilíneas 6,9 s en total. HALLAZGO (LIM-01d): recorrer el millón abriendo cada entidad con UNA StartOpenCloseTransaction cuesta 32 s y crece con cada tramo (0,35 s el primer tramo de 100.000 → 6,7 s el décimo: cuadrático); con StartTransaction 0,33 s (constante, ~30 ms por tramo); OpenClose con una transacción por tramo 2,9 s (10 veces más lenta que StartTransaction). Contradice la recomendación habitual de usar OpenCloseTransaction para leer
- [x] **LIM-02** Sesión larga: 8 horas de ejecución continua del runner (fugas de memoria y de identificadores) (P/J) · R: ⚠️ · Obs: versión comprimida, no 8 horas: 600 lab run seguidos (ELM-01 + DEV-10) en una instancia nueva, 505 s (0,84 s por ejecución), 0 fallos. Memoria privada 1.983 → 1.994 MB, WS 703 → 836 MB (sube en las primeras 100 y luego se estabiliza), handles 2.035 → 2.345 (estables desde la ejecución 100), hilos 108 → 117, contextos de pruebas vivos 0 en todo momento. Sin fugas apreciables del runner con payloads que no registran comandos. Falta la sesión de horas real y con payloads que registran comandos (esos se conservan a propósito: DEV-07/DEV-09)
- [x] **LIM-03** DWG de más de 500 MB: abrir, recorrer y guardar (P) · R: ✅ · Obs: D3-enorme.dwg: 50.000 polilíneas de 1.000 vértices con coordenadas pseudoaleatorias (calibración LIM-03a: 12,8 bytes por vértice), 610 MB. Base lateral: crear 1,7 s, SaveAs 3,0 s; ReadDwgFile 1,0 s (+850 MB), recorrer sumando 50 millones de vértices 19 ms, mover todo (TransformBy en lotes de 5.000) 1,2 s, SaveAs a otro archivo 2,9 s (604 MB). Como documento visible: Open 4,8 s (+2,5 GB de memoria privada), Regen 1,2 s, CloseAndDiscard 0,5 s. accoreconsole: abrir y contar las 50.000 polilíneas 5,7 s en total (ssget 47 ms). Ningún límite a la vista en este equipo (63 GB)
- [x] **LIM-04** Excepciones capturables (`Autodesk.AutoCAD.Runtime.Exception` con `ErrorStatus`) frente a fallos nativos que matan el proceso (A/J) · R: ✅ · Obs: instancia propia. Capturables: AcException (NullObjectId, KeyNotFound, InvalidInput), OverflowException, AccessViolationException de Marshal.ReadInt32(IntPtr.Zero) (Marshal la convierte a propósito). Excepción no controlada en un hilo secundario: NO mata AutoCAD ni muestra nada; AutoCAD tiene un manejador en AppContext.UnhandledException (Autodesk.AutoCAD.Runtime.UnhandledExceptionFilter) y el hilo que lanzó queda retenido (IsAlive=True para siempre). Task no observada: inofensiva. Matan el proceso sin remedio: puntero a memoria no mapeada (*(int*)0x7FFF00000000, muerte instantánea, sin CER, solo .NET Runtime 1026 en el Visor), StackOverflow (DEV-11, con CER), Environment.FailFast (con CER), errores fatales internos (TRX-08, VIS-02: «AutoCAD Error Aborting» + CER)
- [x] **LIM-05** Recuperación tras un crash: CER (hay una carpeta `ErrorReports` dentro del repositorio: averiguar de qué), `.sv$`, `.bak` y DWG a medio guardar (J) · R: ⚠️ · Obs: instancia propia, lim05.dwg abierto: LINE + QSAVE (crea .bak) + CIRCLE sin guardar, SAVETIME=1 (restaurado a 10 al terminar). El .sv$ aparece en SAVEFILEPATH (%TEMP%) como lim05_1_29790_142b107c.sv$. Error fatal interno (escribir con la transacción cerrada): «AutoCAD Error Aborting» → «AutoCAD cannot continue. You can attempt to save changes into …\lim05_recover.dwg up to the start of the last command» (Sí/No) → con Sí escribe lim05_recover.dwg y acad.err (en la carpeta de trabajo del proceso, se sobrescribe) y aparece el CER. Contenido: .bak = original; .dwg = con la línea; .sv$ = línea + círculo (lo más reciente, legible con ReadDwgFile tras renombrarlo a .dwg); _recover.dwg = ReadDwgFile da eDwgNeedsRecovery. Recuperarlo por programa NO salió: no hay API .NET (solo Database.NeedsRecovery); RECOVER en accoreconsole rechaza el archivo («Invalid file name»); en la interfaz, RECOVER por SendStringToExecute o (command) pierde el argumento al cambiar de documento, y DocumentCollection.Open deja un documento sin Database. Quedan .dwl/.dwl2 huérfanos. La carpeta ErrorReports del repositorio NO es de una caída: accoreconsole crea ErrorReports\<hash>\cer.log en su carpeta de trabajo en cada arranque
- [x] **LIM-06** Memoria tras abrir y cerrar 100 documentos visibles (fugas) (P) · R: ⚠️ · Obs: instancia nueva, 3 rondas de abrir (solo lectura) y cerrar (CloseAndDiscard) los 100 DWG pequeños de cmp\lote: 2,61 / 2,64 / 2,65 s por documento (Open ~2–2,7 s, Close ~0,35 s; EXP-08 midió ~0,6 s en otra sesión, diferencia sin explicar). FUGA DE HANDLES: 2.251 → 7.169 → 11.816 → 16.418 (~47 handles del kernel por documento abierto y cerrado), y no se recuperan tras 1 minuto en reposo (16.536). Memoria privada tras cada ronda 1.060 → 1.270 → 1.387 MB (~+150 MB por ronda de 100). Un primer intento agotó los 25 min de timeout con la CPU casi parada y sin testigo, sin causa identificada; la repetición con testigo cada 10 documentos (estres\lim06.txt) terminó en 13 min. Para lotes de miles de documentos visibles: reiniciar AutoCAD cada cierto número o usar bases laterales (EXP-07)
- [x] **LIM-07** Mismo documento manipulado a la vez por el runner, por COM desde fuera y por un reactor LISP (A) · R: ✅ · Obs: pruebas/com/lim07_concurrencia.py en una instancia arrancada por COM (DispatchEx; GetActiveObject y la ROT devuelven siempre la primera instancia, la del usuario): a la vez, el runner (LIM-07a) añade 2.000 círculos con una transacción por círculo, Python por COM añade 2.000 líneas y un reactor LISP vlr-acdb-reactor :vlr-objectAppended (lim07.lsp, cargado desde la ruta de confianza) cuenta. Resultado: 2.000 líneas, 2.000 círculos, reactor 4.000; 0 errores. Todo se serializa en el hilo de AutoCAD: mientras corre el código .NET (126–151 ms), COM recibe RPC_E_CALL_REJECTED (7 reintentos) y su llamada más lenta espera 0,7–1,1 s. Con DoEvents cada 50 círculos, igual (la parte .NET es demasiado corta para ver intercalado). Conclusión: la concurrencia es segura porque no existe; quien no reintente RPC_E_CALL_REJECTED falla
- [x] **LIM-08** Rutas de más de 260 caracteres y Unicode en nombres de DWG, por cada vía (A) · R: ⚠️ · Obs: ruta Unicode (ñandú_日本語_Ελληνικά\plano_ü€😀.dwg, 79–85 caracteres) y ruta de 379–385 caracteres (LongPathsEnabled=1 y acad.exe/accoreconsole.exe con longPathAware=true). .NET: SaveAs, ReadDwgFile y abrir como documento funcionan con las dos. COM desde fuera: ObjectDBX y Documents.Open funcionan con las dos (FullName exacto). accoreconsole: /i, ssget y SAVEAS funcionan con las dos. LISP: con Unicode todo funciona; con la ruta larga findfile la encuentra pero open "w", vl-file-copy y vl-file-size FALLAN (nil), en la interfaz y en la consola. Nota: con pywin32 dinámico, RPC_E_CALL_REJECTED aparece como AttributeError (COM-06): usar gencache.EnsureDispatch y reintentar
- [x] **LIM-09** Inventario final: lo que la interfaz hace y ninguna vía expone, con el ID de cada hallazgo (S) · R: ⚠️ · Obs: inventario en reports/Inventario final LIM-09.md: solo por la interfaz o su comando (bloques dinámicos, restricciones geométricas, cotas asociativas, RECOVER, contraseña), solo por COM (zoom, perfiles, AuditInfo, PurgeAll), API rota o incompleta (Hatch.Area, AssocArray en base lateral, DataLink con CSV, IsMerged, rutas largas en LISP, bundles en ProgramData) y lo que sí existe pese a lo esperado. Provisional: faltan las familias UI, MAC y JSA (con la persona delante), el trazado a PDF desde la interfaz, VBA y ObjectARX

---

## 5. Registro de sesiones

| Fecha | Build de AutoCAD | IDs probados | ✅ | ❌ | ⚠️ | 💥 | Notas o registro |
|---|---|---|---|---|---|---|---|
| 2026-10-28 | 26.0.118 | ENT-01, ELM-01, TRX-01, TRX-02, DEV-07, DEV-10, DEV-01 | 6 | 0 | 1 | 0 | Runner mínimo como bundle con pipe y `Dispatcher`. SECURELOAD avisa (dos veces para la misma DLL). Iteración ~2,2 s. El contexto descargable se libera después de la llamada, no durante |

## 6. Orden sugerido

Primero lo que **no necesita a la persona delante**; las pruebas manuales se agrupan en sesiones con instrucciones y preguntas de control.

1. **Sin AutoCAD (S):** SHL-03 a SHL-08 (inventarios de .NET, AdWindows, AcCui y COM), DWG-01 a DWG-03, DWG-06, APS-01, CON-12. Dan las referencias para todo lo demás.
2. **Fuente de verdad y base del runner (A/J), con la sesión de AutoCAD libre:** DEV-02, SHL-06 (inventario LISP), CTX-02 a CTX-08, CTX-13, TRX-03 a TRX-13, DEV-05, DEV-06, DEV-09, DEV-13.
3. **`accoreconsole` (A/P), sin tocar la sesión abierta:** CON-01 a CON-13. Es la mayor ventaja de AutoCAD para los lotes.
4. **API .NET en bases laterales (A):** ELM, ANO, VIS-01 a VIS-08, EXP-03 a EXP-08, EVT-01 a EVT-03 y EVT-08.
5. **COM y LISP desde el runner y desde fuera (A/P):** COM-01 a COM-06, COM-08 a COM-14, LSP-01 a LSP-05, LSP-07 a LSP-11; después la familia **CMP**.
6. **Con la persona delante (M), en bloques:** ENT-02 a ENT-04 en el próximo arranque (Always Load con la DLL sin cambios), DEV-00, DEV-08, DEV-14, CTX-09 a CTX-12, UI, MAC, JSA, LSP-06, EVT-04 a EVT-07 y EVT-11.
7. **Reinicios y crashes:** DEV-03, DEV-11, DEV-12, ENT-05 a ENT-11, LIM-04, LIM-05.
8. **Con permiso del usuario (instalación, nube o coste):** VBA, ARX, APS-03 y APS-04, COM-03, DWG-04, DWG-05.
9. **Estrés (P), al final, con el dibujo D2 generado:** LIM-01 a LIM-03, LIM-06 a LIM-08, y LIM-09 y CMP-11 como entregables finales.
