CommunityProgramación y desarrollogithub.com

figma-build

Construcción de contenido de Figma y FigJam desde un plugin: las reglas de tamaño y fuente que fallan en silencio, los límites que evitan que un valor incorrecto arruine una compilación, la lectura de comentarios a través de la API REST y el bucle que te dice si tu código realmente se ejecutó. Útil al escribir o depurar un plugin de Figma, generar marcos o wireframes programáticamente, o cuando una compilación se aborta a medio dibujar, un diseño colapsa, el texto se ajusta un carácter por línea, los marcos se superponen a algo que ya está en la página, o 'se ve igual'.

¿Qué es figma-build?

Esta habilidad técnica cubre las fallas silenciosas y los problemas de compilación más comunes al desarrollar plugins de Figma o FigJam. Explica cómo evitar que un solo valor rechazado por la API arruine toda la compilación, cómo manejar correctamente el tamaño del texto (el orden exacto de las operaciones para evitar alturas fijas incorrectas), y cómo solucionar restricciones circulares que provocan que las tarjetas se muestren con solo 45px de ancho o el texto se ajuste a un carácter por línea. Incluye un bucle de construcción para verificar si el código se ejecutó realmente (sellando la compilación y limpiando la salida anterior), y un flujo de revisión completo para leer comentarios a través de la API REST de Figma, aplicar cambios in situ sin reemplazar nodos comentados, y mantener un registro de comentarios manejados. También cubre la simulación local del plugin con un stub estricto, la carga y resolución de fuentes, y la toma de capturas de pantalla a escala. La habilidad es aplicable a Claude Code, Cursor, Codex, y otros agentes de IA que trabajen con la API de Figma.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ABarracu87/figma-build-skill/tree/HEAD

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Construcción en Figma

Aquí todo falló silenciosamente primero. Nada lanza excepciones, nada advierte, y el lienzo simplemente se ve mal.

El bucle, antes que nada

Sella la compilación y limpia tu propia salida. De lo contrario, puedes depurar "se ve igual" durante mucho tiempo cuando Figma está sirviendo un paquete en caché.

const BUILD = "15:03";                 // aumentar en cada paquete
function clearPrevious() {
  const mine = figma.currentPage.children.filter(n => /^\d\d · /.test(n.name));
  mine.forEach(n => n.remove());
  return mine.length;
}
figma.closePlugin("Build " + BUILD + " — replaced " + removed + " frames.");

Dos números en el mensaje, y ambos son de diagnóstico. Un sello desactualizado significa que el paquete está en caché: elimina el plugin, vuelve a importar el manifiesto, vuelve a abrir la pestaña. Replaced 0 con marcos visibles en el lienzo significa lo mismo.

Sin esto, un plugin que recrea marcos apila un segundo conjunto encima del primero y nada te dice cuál estás viendo. Contar copias de un nombre de marco es cómo demuestras si una ejecución ocurrió realmente.

Limpiar y reconstruir es solo para las primeras compilaciones. Una vez que un archivo está en revisión, los comentarios están fijados a los ID de nodo. Eliminar un marco y dibujar uno nuevo deja huérfanos todos los comentarios que tenía. Durante una ronda de revisión, edita los nodos existentes en su lugar (ver El bucle de revisión).


Un valor incorrecto arruina toda la compilación

Cada fallo en este archivo tiene la misma forma: un solo valor que la API rechaza, lanzado a medio dibujo, dejando una página medio renderizada que se lee como un problema de diseño. Culpables típicos: un ancho negativo, una altura de texto fija de 10px, un peso de fuente no cargado.

Aplica límites en el límite en lugar de confiar en la aritmética.

function W1(v, min){ const n = Number(v); return (isFinite(n) && n > 0) ? n : (min || 1); }

Haz pasar cada ancho, altura y ancho de texto a través de él. Un límite cuesta una caja con aspecto incorrecto; un lanzamiento cuesta la página.

La aritmética que muerde es una resta fija que solo se cumple en un ancho:

const FEED = DW - PAD*2 - 400;   // 840 a 1440.  MENOS 50 a 390.

Cualquier constante restada del ancho del lienzo necesita un guarda de punto de interrupción, o se vuelve negativa en el pequeño.

Síntoma a reconocer: los marcos anchos se ven terminados y los estrechos están ausentes o medio dibujados, superponiéndose a algo más. Eso no es un error de posicionamiento. Lee el mensaje de error: nombra la propiedad y el nodo.


Nunca dibujes en el origen

Un plugin que comienza en (0, 0) cae encima de lo que ya está allí, y un limpiador que solo elimina sus propios marcos no te salvará: dos plugins compartiendo una página ambos creen que el lienzo está vacío.

Mide primero, luego comienza limpio:

let startY = 0;
figma.currentPage.children.forEach(n => { startY = Math.max(startY, n.y + n.height); });
if (figma.currentPage.children.length) startY += 600;

Etiqueta cada fila en el lienzo, y haz que el limpiador coincida con todo lo que la compilación emite (marcos y etiquetas), o al volver a ejecutar quedarán textos huérfanos atrás.


Tamaño del texto, en este orden exacto

El error más costoso. Figma recalcula la altura de un nodo de texto cuando su contenido cambia, no cuando lo redimensionas.

const str = String(s);
const t = figma.createText();
t.fontName = { family: "Inter", style: weight };
t.characters = str;                    // 1. PRIMERO
t.fontSize = size;                     // 2. estilos después
t.lineHeight = { unit: "PERCENT", value: 145 };
if (width) {
  t.textAutoResize = "HEIGHT";         // 3. NO se mantiene en un nodo vacío
  t.resize(width, t.height);           //    nunca una altura literal
  t.characters = str;                  // 4. forzar una nueva medición con el nuevo ancho
} else {
  t.textAutoResize = "WIDTH_AND_HEIGHT";
}
  • textAutoResize = "HEIGHT" establecido antes de que el nodo tenga caracteres se descarta. Reordenar para poner el contenido al final sin mover la propiedad lo empeora.
  • resize(w, 10) fija la altura en 10 para siempre. Pasa t.height.
  • Reasignar la misma cadena es la doble garantía de remedición.

Síntoma: cada texto con un ancho explícito mide ~10px de alto, cada texto sin una es correcto, y cada marco cuya altura proviene solo del texto colapsa a su relleno.


width significa ancho fijo — no es obvio qué eje es

if (opts.horizontal) {
  f.primaryAxisSizingMode = width ? "FIXED" : "AUTO";   // width ES el eje principal
  f.counterAxisSizingMode = "AUTO";
} else {
  f.counterAxisSizingMode = width ? "FIXED" : "AUTO";   // width es el eje contra
  f.primaryAxisSizingMode = "AUTO";
}

Establecer counterAxisSizingMode en un marco horizontal fija su altura y deja el ancho ajustándose al contenido. Cada fila construida de esa manera no tiene ancho libre, por lo que ningún hijo puede crecer dentro de ella.

Redimensiona antes de establecer los modos de tamaño, o el tamaño explícito lucha contra el ajuste al contenido.


Una columna que llena una fila

function growCol(n) { n.layoutGrow = 1; n.counterAxisSizingMode = "FIXED"; return n; }

Un marco vertical creado con width: null se ajusta al contenido. Dale layoutGrow = 1 y hijos con ESTIRAR y tienes una restricción circular: el padre se ajusta a los hijos, los hijos se ajustan al padre. Figma lo resuelve al mínimo.

Síntoma: tarjetas de ~45px de ancho, texto del cuerpo ajustándose un carácter por línea, nodos de texto informando 1 x 988. Marcos de página tres veces más altos de lo que deberían, enteramente por espacio vertical falso.

Prefiere pasar a las filas un ancho explícito en lugar de confiar en ESTIRAR para resolverlo.


Mide, no mires

La geometría responde la pregunta más rápido que una captura de pantalla, y expone todos los errores anteriores. Obtén get_metadata, luego:

  • alturas ≤ 12px → texto o marcos colapsados
  • anchos de 1 → la restricción circular anterior
  • una página mucho más alta que su equivalente HTML → ajuste estrecho, no contenido
  • una copia de cada marco cuando esperabas dos → la ejecución nunca ocurrió

Verifica los totales: los rangos y de un bloque deberían encajar en su página sin espacios, y el último bloque debería terminar exactamente en la altura de la página.


Simulando el plugin localmente

tools/figma_stub.js en esta habilidad es un stub estricto listo para usar; node tools/test_figma_stub.js demuestra que aún lanza donde Figma lanza.

Un stub del objeto global figma detecta lanzamientos, agregados nulos y errores estructurales antes de un viaje de ida y vuelta. No puede emular métricas de texto, por lo que demuestra que el código se ejecuta, nunca que el diseño sea correcto.

Haz el stub tan estricto como la API real, o pasará compilaciones que mueren en Figma. Cada uno de estos es un error que llegó al lienzo porque el stub era tolerante:

resize(w, h){
  if (!(w >= 0)) throw new Error('in resize: Property "width" failed validation');
  if (!(h >= 0)) throw new Error('in resize: Property "height" failed validation');
  ...
}
set fontName(v){
  if (!loaded.has(v.family + "|" + v.style))
    throw new Error('Cannot use unloaded font "' + v.family + " " + v.style + '"');
  ...
}
appendChild(c){ if (!c) throw new Error("null child"); ... }

Dale al stub currentPage.children, y siembra con los marcos que la página real ya contiene — así es como una colisión de origen se muestra localmente.

Para verificar el posicionamiento, el stub también necesita calcular alturas de diseño automático: suma los hijos más itemSpacing y el relleno para un marco VERTICAL, toma el máximo para uno HORIZONTAL, y devuelve el valor fijo cuando primaryAxisSizingMode === "FIXED". Aproximado es suficiente para detectar superposición de filas.

Ejecuta cada ruta de envío, cada punto de interrupción y cada configuración de fuente, no solo la feliz:

Inter solo · Inter + mono · los tres · Inter Regular solo
escritorio · móvil     oscuro · claro

Fuentes: carga lo que pedirás y resuelve lo que obtienes

Asignar una fuente no cargada lanza un error. Dos trampas detrás de eso:

  • Debes precargar cada peso que el código solicite. Cargar Regular, Medium y Bold y luego solicitar Semi Bold es un fallo esperando a la única tarjeta que lo usa.
  • El nombre del estilo difiere según la familia. Inter dice "Semi Bold"; JetBrains Mono dice "SemiBold". Pedir el peso correcto con la ortografía incorrecta aún lanza.

Prueba cada peso por separado, registra lo que realmente se cargó, y resuelve cada solicitud a través de una cadena de respaldo en lugar de asumir:

const ALIAS = { "Semi Bold": ["Semi Bold","SemiBold","Medium","Bold","Regular"], … };
function font(family, style){
  for (const s of ALIAS[style]) if (LOADED[family+"|"+s]) return { family, style:s };
  for (const s of ALIAS[style]) if (LOADED["Inter|"+s]) return { family:"Inter", style:s };
  return { family:"Inter", style:"Regular" };
}

Nunca precargues estrictamente en el punto de entrada con Promise.all — un peso faltante allí mata una compilación que el resolvedor habría manejado. Informa las familias a las que resolviste en el mensaje de cierre.


Un plugin, un trabajo

Resiste un switch de envío dentro de un solo plugin — según el tipo de editor, nombre de página o selección. Un switch por nombre de página significa que el usuario debe realizar un ritual antes de que funcione, y cuando silenciosamente hace lo incorrecto no hay nada que leer.

Dos manifiestos con diferentes valores de id y name no cuestan nada y eliminan toda la clase de problema. Figma también reutiliza un registro en caché cuando un manifiesto reimportado lleva un id que ya conoce, por lo que cambiar el id es cómo se fuerza una carga limpia.


Leyendo comentarios de vuelta

El MCP expone get_metadata, get_design_context, get_screenshot, get_figjam, use_figma — ninguno de ellos lee comentarios, y la API de Plugins tampoco tiene acceso a comentarios. Los comentarios viven en la API REST.

Usa tools/figma_comments.py en esta carpeta de habilidad (Python 3, biblioteca estándar):

T=~/.claude/skills/figma-build/tools/figma_comments.py
python3 $T check <file-url>            # OK: token lee comentarios (N en file KEY)
python3 $T fetch <file-url>            # hilos abiertos como JSON, ledger y resueltos filtrados
python3 $T mark  <file-url> ID... --change "lo que cambió"
python3 $T reply <file-url> ROOT_ID "Hecho: logo 20% más grande"   # necesita el alcance de escritura

Lee el token de ~/.figma-token y nunca lo imprime. Ejecútalo desde la carpeta de trabajo para que el ledger aterrice allí, o establece FIGMA_LEDGER. No hay comando de eliminación. Si informa un error de certificado SSL, el Python no tiene certificados raíz: ejecuta /usr/bin/python3 en su lugar, o pip install certifi.

Alcance: Comentarios de archivo → Leer. Almacena el token en un archivo (~/.figma-token, chmod 600), nunca en la conversación. Si la persona no puede ejecutar un comando de shell, escribe el archivo con un marcador de posición y open -a TextEdit para que lo peguen.

/v1/me devuelve 403 en un token con alcance de comentarios. Eso es esperado, no un token roto. /v1/files/:key/nodes también da 403 sin file_content:read.

Cada comentario lleva client_meta:

{ "node_id": "2:95", "node_offset": { "x": 279, "y": 1257 } }

Mapea a un bloque obteniendo get_metadata y encontrando el marco hijo cuyo rango y contiene el desplazamiento. Los hilos vienen planos: agrupa por parent_id, y las respuestas a menudo llevan la instrucción real mientras que la raíz es solo un puntero.

Los comentarios son por archivo. Mover contenido a un nuevo archivo los deja atrás.

El bucle de revisión, cuando te pidan "lee mis comentarios"

  1. Obtener con figma_comments.py fetch, que agrupa comentarios en hilos. Salta los resueltos (con resolved_at establecido) y cada ID ya en el ledger manejado (abajo). Los comentarios antiguos permanecen en el archivo hasta que se resuelven, por lo que sin estos dos filtros cada revisión, y cada conversación posterior, reaplica los comentarios de la ronda anterior.
  2. Mapear cada hilo a su marco a través de client_meta, como arriba.
  3. Confirmar antes de dibujar: lista cada comentario, el marco al que se mapea y el cambio planeado, en una tabla corta. Un comentario fijado en lienzo vacío, o uno que es ambiguo, recibe una pregunta en lugar de una suposición.
  4. Aplicar los cambios en el lugar: cambia las propiedades de los nodos existentes, manteniendo sus IDs. Nunca elimines y redibujes un marco comentado, porque eso deja huérfanos sus comentarios. A través del MCP (use_figma) el lienzo se actualiza directamente. A través de un plugin de desarrollo, edita su código en el disco, aumenta el sello de compilación y pide a la persona que ejecute el plugin nuevamente.
  5. Verificar con get_metadata y una captura de pantalla (ver Mide, no mires). En la ruta del plugin, verifica que el mensaje de cierre muestre el nuevo sello antes de confiar en el lienzo.
  6. Informar comentario por comentario qué cambió. Si el token tiene el alcance de escritura y la persona lo desea, responde en cada hilo.
  7. Registrar cada ID de comentario manejado (raíces de hilos y respuestas) con figma_comments.py mark antes de que termine el turno, incluyendo aquellos a los que solo respondiste con una pregunta.
  8. Nunca elimines comentarios, incluso cuando te pidan limpiar. Son el registro de revisión. La retroalimentación antigua se mantiene fuera de rondas posteriores a través del ledger, no eliminándola. Si una ronda necesita limpieza en Figma, la persona resuelve los comentarios.

El ledger manejado. Un archivo JSON junto al trabajo: figma/comments-handled.json en una carpeta de campaña, de lo contrario figma-comments-handled.json en el directorio de trabajo. Léelo al comienzo de cada revisión, incluso en una conversación nueva.

{ "<file_key>": { "<comment_id>": { "handled_at": "2026-11-03T14:05:00Z", "change": "logo 20% más grande en 1080x1920" } } }

No hay un endpoint de API para resolver un comentario, por lo que resolver queda en manos de la persona.

Cuándo pedir una reimportación (solo ruta de plugin): cuando el mensaje muestra un sello desactualizado, o "replaced 0" mientras hay marcos visibles. Pídeles que eliminen el plugin, importen su manifest.json nuevamente y vuelvan a abrir la pestaña. Si eso falla, cambia el id del manifiesto (ver Un plugin, un trabajo).


Capturas de pantalla a escala

Una captura por página más recortes locales es mejor que una llamada por bloque. La altura y/ del bloque proviene de get_metadata, maxDimension igual o por encima de la altura del marco da 1:1, y los recortes se alinean exactamente.

Skills relacionados