No es este un artículo sobre cómo una IA construyó un plugin para mí. En realidad, es justo al revés: se trata de cómo un problema aparentemente simple en Figma me obligó a sentar reglas, tomar decisiones de producto y entender el entorno real donde una herramienta tiene que funcionar.
Son años ya los que llevo usando Figma. Y durante años he visto el mismo patrón repetirse en archivos grandes: decenas, a veces cientos de frames, versiones duplicadas, pantallas que nacieron como experimentos y terminaron mezcladas con el flujo principal.
No es sólo el desorden el problema. Es la pérdida de la confianza. Cuando un equipo no sabe qué pantalla es la actual, el archivo deja de ser una fuente de verdad y se convierte en una especie de arqueología visual. Los frames repetidos afectan el handoff, la consistencia visual, el seguimiento de decisiones y la confianza del equipo en sus entregas. Detectarlos no es solo limpiar Figma, es reducir ruido operativo.
La solución manual es laboriosa: ir frame a frame, comparar a ojo, esperar no perderse nada. Sabía que debía haber una forma mejor. No la había construido, simplemente.
Primera pregunta: ¿qué significa en realidad "duplicado"?
Antes de escribir una línea de código me topé con la pregunta que resultó ser el meollo del proyecto: ¿qué quiere decir que dos artboards sean iguales?
¿Es una duplicación visual o estructural? ¿Deben ser incluidos los nombres de capas — ¿o se considera que un artboard está duplicado incluso si alguien renombró sus capas internas? ¿Y los componentes? Si dos artboards usan el mismo componente pero con diferentes overrides de texto, ¿son duplicados? ¿Y un frame con la misma estructura pero en otra posición del canvas?
No son preguntas técnicas estas. Son decisiones de diseño. Y yo tenía que responderlas antes de poder especificar qué estaba construyendo.
Definí dos modos: comparación estricta, que incluye los nombres de las capas internas, y visual, que los ignora y se centra en la estructura y las propiedades visuales. Y la posición del artboard en el canvas —algo sin importancia para su contenido— debía eliminarse de la comparación por completo.
Esa definición fue la base de todo lo subsiguiente.
El brief como herramienta del diseñador
He decidido apoyarme en Claude Code como copiloto técnico para acelerar la implementación. Pero el punto de partida no fue un código, sino un brief.
No un one-liner rápido, sino un documento de producto de verdad: qué debía hacer el plugin, cómo funcionar la lógica de comparación, qué propiedades incluir, cómo ver el output, qué casos borde considerar. El mismo tipo de documento que escribiría antes de hacer handoff a un equipo de desarrollo.
En este caso, el brief no era un prompt para hacer código. Se trataba de una especificación de producto: alcance, reglas, casos de borde y criterios de calidad. Usando este documento como base, pude generar una primera versión funcional del plugin. No era perfecta — era un punto de partida. Luego vino la parte interesante.
Los errores fueron lecciones, no trabas
Tenía la esperanza de que el código funcionara a la primera. No sirvió. Pero los errores fueron más instructivos que una ejecución sin fricciones.
El primer encontronazo fue con el runtime de plugins de Figma: algunas sintaxis modernas de JavaScript sencillamente no están soportadas. No era un error de lógica sino de contexto. El código tenía que adaptarse al entorno real donde iba a correr, no al ideal JavaScript que uno asumiría desde un navegador moderno.
A continuación: `t.map no es una función`. La API de Figma devuelve colecciones tipo array, que no son arrays reales de JavaScript (no tienen `.map()`, `.filter()` ni `.forEach()`). La solución fue convertir todo a arrays reales antes de operar con ellos.
Luego: `no se puede convertir símbolo a número`. Esto es `figma.mixed` — un valor Symbol que Figma retorna cuando una propiedad tiene valores diferentes entre hijos (como corner radii que varían por esquina). Se necesitó un wrapper de seguridad para cada propiedad numérica.
Cada error reveló algo real acerca de cómo funciona Figma internamente. Cuando el plugin se ejecutó sin errores, entendí la API mucho mejor de lo que lo habría entendido leyendo la documentación solo.
La comparación que no hacía comparaciones
El plugin se estaba ejecutando. Mostraba resultados. Cero duplicados, incluso en frames que yo había duplicado a mano para probarlo.
Este fue el problema más interesante para depurar. La lógica daba resultado, pero comparaba la cosa errónea.
Agregué un modo debug: para cualquier par de artboards con el mismo nombre pero diferente hash, encontrar la primera propiedad que difiere y mostrarla. El output no se hizo esperar: `root.x (4925 vs 4941)`.
La posición del artboard X en el lienzo. Ya la había metido en el hash.
Por supuesto que dos artboards con posiciones diferentes en el canvas generarían hashes diferentes —aunque fueran idénticos en contenido. No importa dónde se encuentre un frame en el canvas para saber si es duplicado. No forma parte del contenido. Es solo donde lo pusiste. Uno de esas decisiones que en retrospectiva parece obvia y totalmente invisible hasta que los datos te la muestran.
Una línea de código: poner a cero la X e Y del artboard raíz antes de hashear. Después de aquello, funcionó.
Publicar no es lo mismo que correr localmente
Creí que publicar sería fácil. Enviar, esperar, preparado.
Figma rechazó la primera versión.
El motivo: había incluido `"documentAccess": "dynamic-page"` en el manifest —lo que necesita Figma para los plugins que requieren acceder a páginas fuera de la activa—. Pero con esa flag, no puedes acceder a `page.children` de forma síncrona. Primero tienes que hacer `await page.loadAsync()`. Y no puedes hacer `node.mainComponent`, tienes que usar `await node.getMainComponentAsync()`.
Todas las APIs síncronas que yo utilizaba se volvieron asíncronas. Así que la función de serialización, que recorre recursivamente cada nodo en cada artboard, tuvo que hacerse async, y cada llamada que la toca tuvo que ser `await`eada. Aproximadamente una hora de refactoring. La segunda entrega fue aprobada.
Existe una brecha real entre un plugin que corre en desarrollo y uno que funciona en producción bajo las restricciones reales de Figma. Esa brecha sólo se deja ver cuando se intenta cruzar.
Lo que de verdad aprendí
Los problemas técnicos tenían solución. Me sorprendió todo lo que los rodeaba.
La lección más importante vino antes de cualquier código: antes de pedirle a una IA que construya algo, hay que saber qué se está construyendo. Y eso no es un problema de Inteligencia Artificial. Es cuestión de diseño.
El brief me obligó a tomar decisiones de producto que hubiese saltado de haber ido directo al código. El modo debug me obligó a entender qué estaba haciendo realmente la lógica de comparación, no lo que yo asumía que hacía. El rechazo de Figma me hizo entender la diferencia entre un entorno de desarrollo y uno de producción.
Ninguna de esas lecciones provino de la lectura anticipada de documentación. Siempre se termina encontrando problemas concretos en contexto, lo cual, más o menos, es como funciona la investigación de diseño.
El plugin ha sido publicado
Artboard list to Excel está publicado en la Figma Community. Detecta *frames duplicados* usando comparación estructural profunda — tipos de nodo, posiciones relativas al artboard, fills, strokes, tipografía, propiedades de auto layout, referencias de componentes — y exporta los resultados como archivo Excel. También encuentra frames dentro de Sections, que resultó ser un caso de uso común que no había considerado hasta que un archivo de prueba tenía todos sus frames agrupados en una sección.
¿Es perfecto? No. Las imágenes se comparan por su hash interno, no por su contenido visual. Las variables se comparan por el valor obtenido, no por el id de la variable. Hay casos límite que aún no me he encontrado.
Pero resuelve un problema real, en un contexto real, para gente que trabaja con archivos reales. Y para un primer plugin —también un recordatorio de que los diseñadores podemos construir herramientas, no solo especificarlas— ese es un buen punto de partida.

