TuBrief
구독 채널
비디오
커뮤니티

Un pipeline de compilación para aislar habilidades de diseño externas de Claude en el código de producción

TuBrief 편집팀
2026년 9월 11일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

Español한국어English中文العربيةहिन्दीDeutschFrançaisPortuguêsРусскийBahasa Indonesia日本語

관련 영상

Increíbles habilidades de diseño de Claude que necesitas para crear sitios hermosos12:55

Increíbles habilidades de diseño de Claude que necesitas para crear sitios hermosos

AI LABS

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

Un pipeline de compilación para aislar habilidades de diseño externas de Claude en el código de producción

Para un desarrollador que crea un servicio full-stack solo y sin diseñador, los documentos de habilidades de diseño externas son atractivos. Copiar un prompt de sistema de diseño de 5,000 líneas encontrado en Twitter o GitHub y colocarlo en las instrucciones del sistema de Claude da la impresión de que la interfaz quedará impecable.

En la realidad, ocurre exactamente lo contrario. Tan pronto como se envía el primer mensaje, salta una advertencia de límite de tokens y el tiempo de espera de la respuesta de la API aumenta indefinidamente. El código generado por el modelo inserta estilos en línea desconocidos en los componentes o importa un módulo de Framer Motion que jamás se ha instalado en el proyecto. Al final, después de corregir manualmente la UI rota hasta altas horas de la madrugada, se regresa a la plantilla básica original de shadcn/ui.

El problema no es la sensibilidad estética del modelo. La causa principal es el método de verter texto Markdown no estructurado directamente en el prompt del sistema. Es necesario convertir las descripciones de estilo en lenguaje natural a especificaciones de tokens legibles por máquina y construir un pipeline que realice pruebas de aislamiento del código generado por el modelo antes de que este toque los archivos de producción.

Desechar el Markdown emocional y reemplazarlo por la especificación JSON W3C DTCG

Pegar por completo el Markdown de un sistema de diseño en el prompt genera un desperdicio severo de tokens. Los modificadores estéticos como "un azul marino profundo que inspira confianza al usuario" no sirven para absolutamente nada en la generación de diseños. Estas oraciones consumen recursos de cálculo dentro del modelo de lenguaje que deberían destinarse a las interfaces de TypeScript o a la lógica de validación que realmente se deben cumplir.

Se eliminan las descripciones en lenguaje natural y se reconstruye el prompt del sistema utilizando objetos JSON que cumplen con la especificación W3C Design Tokens Community Group (DTCG). Solo se conservan los colores, espaciados y radios de curvatura.

`json
{
"color": {
"background": {
"surface": { "value":"oklch(0.980.005250)","value": "oklch(0.98 0.005 250)", "value":"oklch(0.980.005250)","type": "color" },
"canvas": { "value":"oklch(1.000)","value": "oklch(1.0 0 0)", "value":"oklch(1.000)","type": "color" }
},
"action": {
"primary": { "value":"oklch(0.550.20250)","value": "oklch(0.55 0.20 250)", "value":"oklch(0.550.20250)","type": "color" },
"primary-hover": { "value":"oklch(0.480.22250)","value": "oklch(0.48 0.22 250)", "value":"oklch(0.480.22250)","type": "color" }
}
},
"spacing": {
"compact": { "value":"0.5rem","value": "0.5rem", "value":"0.5rem","type": "dimension" },
"comfortable": { "value":"1rem","value": "1rem", "value":"1rem","type": "dimension" }
},
"radius": {
"md": { "value":"0.5rem","value": "0.5rem", "value":"0.5rem","type": "dimension" }
}
}

`

Se combina la especificación de tokens estandarizada con Anthropic Prompt Caching. Los tokens básicos y el rol del sistema se fijan en un bloque estático aplicando cache_control: { type: "ephemeral" }.

`typescript
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic();

export async function requestComponentGeneration(componentBrief: string) {
return await anthropic.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 4096,
system: [
{
type: "text",
text: Generador de componentes de Next.js y Tailwind CSS. Utiliza estrictamente solo las clases definidas en la especificación de tokens DTCG a continuación y no emitas estilos en línea (atributo style). [DTCG_TOKENS_JSON_DATA],
cache_control: { type: "ephemeral" },
},
],
messages: [
{
role: "user",
content: componentBrief,
},
],
});
}

`

Al eliminar los documentos de texto plano, el tamaño del prompt se reduce de alrededor de 10,000 tokens a un nivel de 1,200 tokens. Dado que, según la documentación de Anthropic, la lectura de caché de prompts cobra solo el 10% de la tarifa de tokens de entrada básica, los costos de la API se reducen en más del 70% durante las llamadas repetidas.

Reglas de linter para prevenir estilos en línea arbitrarios y conflictos de clases

Al inyectar habilidades externas, Claude suele introducir sigilosamente valores arbitrarios como style={{ marginTop: '13px' }}. También genera clases arbitrarias entre corchetes (w-[342px]) de manera indiscriminada. Este tipo de código destruye poco a poco el sistema de estilos global.

Se configuran eslint-plugin-react y eslint-plugin-tailwindcss en el Flat Config de eslint.config.mjs para filtrarlos en la fase de compilación.

`javascript
import reactPlugin from "eslint-plugin-react";
import tailwindPlugin from "eslint-plugin-tailwindcss";

export default [
{
files: ["**/.{ts,tsx}"],
plugins: {
react: reactPlugin,
tailwindcss: tailwindPlugin,
},
settings: {
tailwindcss: {
callees: ["cn", "cva"],
config: "./tailwind.config.ts",
},
},
rules: {
"react/forbid-dom-props": [
"error",
{
forbid: [
{
propName: "style",
message: "No se permiten estilos en línea. Utiliza las utilidades de Tailwind especificadas.",
},
],
},
],
"tailwindcss/no-arbitrary-value": "error",
"tailwindcss/no-custom-classname": [
"error",
{
whitelist: ["animate-.
"],
},
],
},
},
];

`

Los tokens extraídos se mapean en theme.extend dentro de tailwind.config.ts. Si se sobrescribe la paleta predeterminada, los colores a los que hacen referencia las primitivas internas de shadcn/ui se corrompen.

`typescript
import type { Config } from "tailwindcss";
import themeTokens from "./build/tailwind/theme.json";

const config: Config = {
content: ["./src/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
surface: themeTokens.color.background.surface,
canvas: themeTokens.color.background.canvas,
action: {
primary: themeTokens.color.action.primary,
"primary-hover": themeTokens.color.action["primary-hover"],
},
},
borderRadius: {
token: themeTokens.radius.md,
},
},
},
plugins: [require("tailwindcss-animate")],
};

export default config;

`

Las interacciones se aíslan mediante variantes de Class Variance Authority (CVA). Alterar directamente las primitivas de Radix UI destruye las propiedades del árbol de accesibilidad (ARIA).

`typescript
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-token text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-action-primary text-white hover:bg-action-hover active:scale-[0.98]",
secondary: "bg-surface text-foreground hover:bg-surface/80",
ghost: "hover:bg-surface text-foreground",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-8 px-3 text-xs",
lg: "h-12 px-8 text-base",
},
motion: {
subtle: "transition-all duration-150 ease-out",
expressive: "transition-all duration-300 cubic-bezier(0.16, 1, 0.3, 1)",
},
},
defaultVariants: {
variant: "default",
size: "default",
motion: "subtle",
},
}
);

export interface ActionButtonProps
extends React.ButtonHTMLAttributes,
VariantProps {
asChild?: boolean;
}

export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, variant, size, motion, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, motion, className }))}
ref={ref}
{...props}
/>
);
}
);
ActionButton.displayName = "ActionButton";

`

Limitación de animaciones para proteger el tamaño del bundle

Cuando se le piden movimientos vistosos, Claude recurre habitualmente a Framer Motion. Según las mediciones de Bundlephobia, Framer Motion ocupa aproximadamente 60 KB incluso después de la compresión Gzip. En el momento en que se añaden 60 KB solo para animar un par de botones, el indicador de carga inicial LCP se ve afectado.

Se prohíbe en el prompt del sistema la importación de bibliotecas de terceros y se restringe el uso exclusivo a propiedades orientadas al hilo del sintetizador del navegador (Compositor Thread).

`typescript
// Forma no recomendada que provoca reflujo de diseño en el hilo principal
//

// Forma recomendada procesada en el hilo del sintetizador de la GPU

`

Las propiedades geométricas como top, left, width y height recalculan el árbol de diseño en cada fotograma. Por el contrario, la utilidad transform-gpu y la opacidad opacity se procesan en una capa independiente de la GPU sin pasar por el hilo principal, lo que permite mantener fluidos los 60 FPS.

Asimismo, el efecto de desenfoque de fondo (backdrop-filter: blur()) se omite en las áreas de desplazamiento. En navegadores móviles de bajas prestaciones, un filtro de desenfoque dentro del viewport de desplazamiento recalcula las texturas en cada fotograma, haciendo que la frecuencia de actualización de la pantalla caiga por debajo de los 20 FPS. Usar un canal alfa semitransparente de color sólido (bg-background/80 y un borde de 1px) en lugar de desenfoque evita problemas de ocupación de memoria de video.

Pipeline de verificación automática a través de Storybook y Playwright

Si el código generado por Claude se inserta directamente en el directorio app/ de Next.js, el diseño se rompe y se producen errores de hidratación. Los componentes se renderizan primero en un entorno independiente de Storybook y solo aquellos que superan las pruebas se fusionan en producción.

Se integra axe-playwright en .storybook/test-runner.js para comprobar la accesibilidad según los estándares WCAG 2.1 AA.

`javascript
const { injectAxe, checkA11y } = require("axe-playwright");

module.exports = {
async preVisit(page) {
await injectAxe(page);
},
async postVisit(page) {
await page.waitForSelector("#storybook-root", { state: "attached" });

await checkA11y(page, "#storybook-root", {
  detailedReport: true,
  axeOptions: {
    runOnly: {
      type: "tag",
      values: ["wcag2a", "wcag2aa"],
    },
  },
});

},
};

`

Si una prueba falla, el registro de errores se devuelve directamente a Claude.

`text
[Bucle de retroalimentación de IA: Prompt de calibración de componentes]
El código generado ha fallado en la prueba de accesibilidad de axe-core.
Analice los registros y emita un código que conserve la estructura original corrigiendo únicamente los defectos.

[Detalles de la falla]
Elemento violado: color-contrast
Nodo fallido:
Motivo: La relación de contraste de color es 2.8:1, lo cual no alcanza el estándar WCAG AA de 4.5:1.
Directriz de solución: Reemplace la utilidad de texto por text-foreground o text-action-primary-hover.

`

Se configura un script de shell que toma instantáneas de regresión visual (Visual Regression) con Playwright y crea una rama únicamente cuando se superan todas las comprobaciones.

`typescript
import { test, expect } from "@playwright/test";
import storybookManifest from "../../storybook-static/index.json";

const stories = Object.values(storybookManifest.entries).filter(
(entry) => entry.type === "story"
);

for (const story of stories) {
test(Verificación de instantánea: ${story.title} - ${story.name}, async ({ page }) => {
await page.goto(/iframe.html?id=${story.id}&viewMode=story);
await page.waitForSelector("#storybook-root");
await page.waitForLoadState("networkidle");

await expect(page).toHaveScreenshot(`${story.id}.png`, {
  animations: "disabled",
  maxDiffPixelRatio: 0.01,
  threshold: 0.2,
});

});
}

`

`bash
#!/usr/bin/env bash
set -e

COMPONENT_NAME=$1

if [ -z "$COMPONENT_NAME" ]; then
echo "Error: Ingrese el nombre del componente a verificar."
exit 1
fi

echo "1. Comprobación de guardarraíles de linter"
pnpm eslint "src/components/ui/${COMPONENT_NAME}.tsx" --max-warnings=0

echo "2. Compilación estática de Storybook"
pnpm build-storybook --quiet

echo "3. Pruebas CLI de accesibilidad en Storybook"
pnpm test-storybook

echo "4. Comparación de instantáneas de regresión visual con Playwright"
pnpm playwright test tests/visual/component-regression.spec.ts

echo "5. Aprobado: Creación de rama y envío de PR"
git checkout -b "feature/ui-COMPONENTNAME"gitadd"src/components/ui/{COMPONENT_NAME}" git add "src/components/ui/COMPONENTN​AME"gitadd"src/components/ui/{COMPONENT_NAME}.tsx"
git commit -m "feat(ui): COMPONENTNAMEverificacioˊndeaislamientocompletada"gitpushorigin"feature/ui−{COMPONENT_NAME} verificación de aislamiento completada" git push origin "feature/ui-COMPONENTN​AMEverificacioˊndeaislamientocompletada"gitpushorigin"feature/ui−{COMPONENT_NAME}"
gh pr create --title "feat(ui): Adición de ${COMPONENT_NAME}" --body "Componente que ha superado las pruebas de accesibilidad A11y en Storybook y de regresión en Playwright."

`

Ya no es necesario abrir las herramientas de desarrollo del navegador cada vez que la interfaz se distorsiona para ajustar los márgenes píxel a píxel. Al delegar las reglas de validación a un script de terminal, es posible diseñar UIs de forma segura incluso estando a solas.