Saltar al contenido

El flujo de exportación limpio

Cómo montar el camino desde el fichero de diseño hasta el componente: convenciones en la herramienta, pasos automáticos, verificación, y el punto donde interviene una persona.

El contrato de entrada

Antes de escribir código, el acuerdo con quien exporta. Seis puntos, y cada uno elimina un problema de raíz:

  1. Rejilla fija por familia. Todos los iconos de línea en 24; todos los de relleno en 24; las ilustraciones en la que sea, pero declarada. Mezclar rejillas no es un problema (los symbol lo resuelven), pero declararlo permite verificar.
  2. Margen de seguridad. El dibujo no toca los bordes de la rejilla. Dos unidades de aire en una de 24. Evita el recorte del symbol que se vio en el nivel 19.
  3. Sin marcos con recorte. Elimina los clipPath espurios.
  4. Sin efectos ni filtros. Las sombras van en CSS.
  5. Trazos sin expandir. Conserva la posibilidad de cambiar el grosor.
  6. Paleta cerrada de tokens. Un conjunto acordado de colores, cada uno con su nombre. El pipeline los mapea a variables.

Ese documento es de una página y ahorra más que todo lo demás junto. Lo importante no es que se cumpla al cien por cien, sino que el pipeline pueda verificar cada punto y avisar.

Los pasos, en orden

El orden importa porque algunos pasos dependen del resultado de otros.

JS
// pipeline-iconos.mjsimport { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';import { basename, join } from 'node:path';import { optimize } from 'svgo';import config from './svgo.config.js';const ENTRADA = 'diseno/iconos';const SALIDA  = 'src/iconos';// 1. Normalizar colores a tokensconst PALETA = {  '#1e1e1e': 'currentColor',  '#0d6efd': 'var(--ico-acento, #0d6efd)',  '#6c757d': 'var(--ico-suave, #6c757d)',};function normalizarColores(svg) {  let out = svg;  for (const [hex, token] of Object.entries(PALETA)) {    out = out.replace(new RegExp(hex, 'gi'), token);  }  return out;}// 2. Quitar el fill none del elemento raiz, que bloquea la herenciafunction limpiarRaiz(svg) {  return svg.replace(/(<svg\b[^>]*?)\sfill="none"/i, '$1');}// 3. Verificar el contratofunction verificarContrato(svg, nombre) {  const errores = [];  const vb = svg.match(/viewBox="([^"]+)"/)?.[1];  if (!vb) errores.push('sin viewBox');  else {    const [, , w, h] = vb.split(/[\s,]+/).map(Number);    if (w !== h) errores.push(`rejilla no cuadrada ${w}x${h}`);  }  if (/<filter\b/.test(svg))    errores.push('contiene un filter');  if (/<style\b/.test(svg))     errores.push('contiene un bloque style');  if (/#[0-9a-f]{3,8}\b/i.test(svg)) errores.push('color literal sin mapear');  if (errores.length) throw new Error(`${nombre}: ${errores.join('; ')}`);}await mkdir(SALIDA, { recursive: true });const ficheros = (await readdir(ENTRADA)).filter(f => f.endsWith('.svg'));for (const f of ficheros) {  const bruto = await readFile(join(ENTRADA, f), 'utf8');  const nombre = basename(f, '.svg');  // 4. Optimizar primero: elimina el ruido antes de verificar  const { data } = optimize(bruto, { ...config, path: f });  // 5. Normalizar despues: los colores ya estan en forma canonica  const listo = limpiarRaiz(normalizarColores(data));  // 6. Verificar al final, sobre lo que se va a publicar  verificarContrato(listo, nombre);  await writeFile(join(SALIDA, f), listo);}console.log(`${ficheros.length} iconos procesados`);

Por qué ese orden. Optimizar antes de normalizar porque SVGO canoniza los colores (convierte rgb(30,30,30) y #1E1E1E a la misma forma), lo que hace que el mapeo de la paleta funcione con una sola entrada por color. Verificar al final porque lo que importa es el fichero publicado, no el intermedio.

La verificación que falla el build

El bloque verificarContrato es el corazón del montaje y merece crecer. Cinco comprobaciones más que se pagan solas:

El viewBox existe y tiene la rejilla esperada. Ya está.

No hay identificadores sin prefijar. Si prefixIds está activo, todos los id deben empezar por el nombre del fichero.

No hay colores literales. Ya está, y es la que más avisos genera: cada aviso es un color que el diseño ha usado fuera de la paleta acordada.

El dibujo cabe en la rejilla con margen. Requiere calcular la caja envolvente, lo que exige un entorno con DOM. Con una biblioteca de parseo de rutas se puede aproximar leyendo las coordenadas de las d.

El peso está por debajo de un umbral. Un icono de línea que pesa 4 kB es un icono que alguien ha construido con trescientos puntos. El aviso obliga a mirarlo.

JS
const LIMITE = 2048;if (Buffer.byteLength(listo) > LIMITE) {  console.warn(`${nombre}: ${Buffer.byteLength(listo)} bytes, revisar`);}

Umbral como aviso, no como error: hay iconos legítimamente complejos.

La comparación visual

La única prueba que detecta una optimización que cambia el dibujo. El montaje mínimo: rasterizar original y optimizado con un navegador sin interfaz, al tamaño real de uso, y comparar píxeles con una tolerancia.

Merece la pena decir lo que hay que evitar: comparar a un tamaño grande. Un icono comparado a 512 píxeles esconde los fallos que aparecen a 16, que son los que importan. Rasteriza a los tamaños que usas de verdad.

Y guardar las imágenes de referencia en el repositorio, para que un cambio de versión del optimizador se detecte como una diferencia en el diff.