Guía del UI kit — Expreso Brasilia
@aspsols/brasilia-ui · Design System de Expreso Brasilia

Guía técnica y de uso

La librería de componentes que unifica la identidad visual de Brasilia.

Un paquete Angular privado con design tokens, tema, iconos y componentes de marca. Se desarrolla una vez, se publica en el registro privado de GitLab y se consume desde cada proyecto del cliente — con una identidad visual idéntica y garantizada.

v0.1.0 publicada en el GitLab Package Registry · ya consumida por Relevo Tarjetón
Angular 21 · signals PrimeNG 21 Tailwind v4 Storybook 10 Vitest ng-packagr GitLab Registry
Para dirección: qué resuelve y por qué así Para el equipo: arquitectura, API y cómo consumirla

En una frase: es la fuente única de verdad de la interfaz del cliente. Cada botón, campo o color vive en un solo sitio versionado; los proyectos lo instalan como dependencia. Un cambio se hace una vez y se propaga a todos con una nueva versión.

Qué es y qué problema resuelve

De copiar-pegar estilos a una dependencia versionada

El objetivo de negocio: que todos los proyectos del cliente se vean iguales, sin reinventar ni mantener la interfaz por separado en cada repositorio.

Antes
  • Cada proyecto copiaba y ajustaba sus propios botones, inputs y estilos.
  • Los mismos componentes se veían distintos entre aplicaciones.
  • Un cambio de marca obligaba a tocar cada repositorio a mano.
  • Los mismos bugs de UI se arreglaban varias veces.
Con la librería
  • Los componentes viven en un repositorio y se instalan como paquete.
  • Identidad visual idéntica y garantizada por el mismo tema compartido.
  • Un cambio se hace una vez, se publica una versión, y todos la reciben.
  • Un bug se corrige una vez; todos los proyectos se benefician.

Arquitectura

Tres capas, de lo abstracto a lo concreto

La librería no es solo componentes: es un sistema en capas donde cada nivel se apoya en el anterior. Esta es la clave de que todo se vea coherente.

1

Tokens de diseño y tema

La base: la paleta, tipografía, espaciados, radios y sombras como design tokens. Un módulo TypeScript es la fuente de verdad de los valores; de ahí se derivan el preset de PrimeNG (que tinta todos sus componentes de azul de marca) y las utilidades de Tailwind. Ningún componente hardcodea un color.

tokens.tsbrasilia-preset.ts_tokens.scss_tailwind-theme.scss
2

Sistema de iconos

La familia de iconos de marca (SVG propios, optimizados) se sirve inline con <bui-icon>. El trazo usa currentColor, así que cada icono hereda el color del texto que lo rodea. Un registro central los carga al arrancar; un proyecto puede añadir iconos propios sin tocar la librería.

bui-iconIconRegistry36 iconos
3

Componentes

Componentes de presentación (standalone, OnPush, signals) que envuelven PrimeNG tras una API de marca estable — o migran componentes propios. El equipo consume <bui-button> en vez de repetir la configuración cruda de PrimeNG por todos lados.

bui-buttonbui-inputbui-select

El motor del tema

Cómo se aplica la marca a todo, sin hardcodear

Este es el corazón técnico: cómo un solo azul de marca (#1c59ba) llega de forma consistente a PrimeNG, a Tailwind y a los componentes.

PiezaRol
tokens.tsFuente de verdad en TypeScript de los valores crudos (rampa de azul, neutros, estado). Con tests que verifican contraste AA.
BrasiliaPresetdefinePreset(Aura, …): sobrescribe el primitivo blue con la rampa de marca, de modo que la variable --p-primary-* de PrimeNG resuelve al azul Expreso. Ajusta botón y select.
_tokens.scssReplica los tokens como variables CSS (--color-*, --space-*, --radius-*) que consumen los componentes.
@theme (Tailwind)Puente que genera las utilidades oficiales (bg-primary, rounded-sm, text-body) desde esos mismos valores.
Orden de capas CSS@layer theme, base, primeng, components, utilities: garantiza que los estilos del kit ganen a los de PrimeNG sin usar !important.

Registrar el tema en una app es una sola llamada:

import { providePrimeNG } from 'primeng/config';
import { BrasiliaPreset, PRIMENG_ES } from '@aspsols/brasilia-ui';

providePrimeNG({
  translation: PRIMENG_ES,
  theme: { preset: BrasiliaPreset },
});

Catálogo

Componentes y su API

Todos son standalone: se importan y se usan. Cada uno trae su documentación viva en Storybook (con código copiable) y sus pruebas. Despliega cada uno para ver su API.

<bui-button>Botón de marca

Envuelve p-button + BrasiliaPreset tras una API de variant, en vez de combinar severity/outlined/text a mano.

Uso

<!-- variantes, icono del catálogo, estados -->
<bui-button label="Guardar" variant="primary" />
<bui-button label="Cancelar" variant="secondary" />
<bui-button label="Eliminar" variant="danger" [loading]="saving()" />
<bui-button label="Nueva solicitud" icon="plus" />

Inputs principales

variantprimary · secondary · tertiary · danger
sizesmall · normal · large
icon / iconPosIcono del catálogo (izquierda/derecha)
loading · disabled · fullWidth · typeEstados y tipo (submit)
<bui-input>Campo de formulario

Campo completo: label + input + error/ayuda, con los id de accesibilidad enlazados. Implementa ControlValueAccessor, así que funciona con formControlName.

Uso

<bui-input
  label="Email"
  type="email"
  formControlName="requesterEmail"
  [error]="errorFor('requesterEmail')"
  required />

Inputs principales

label · requiredEtiqueta y asterisco de requerido
typetext · email · password · number · tel · url · search
error · hintMensaje de error (marca inválido) o ayuda
readonly · disabledEstados
<bui-select>Desplegable

Sobre p-select / p-multiSelect. Las variantes se controlan por input(): básico, filtrable, panel de tabla y multiselección. ControlValueAccessor + proyección de plantillas para filas a medida.

Uso

<!-- panel de tabla: basta con optionMeta + rótulos -->
<bui-select
  [options]="vehiculos()"
  optionLabel="placa" optionMeta="numeroBus"
  headerLabel="Placa" metaLabel="Bus"
  [filter]="true" formControlName="placa" />

Capacidades

multipleSimple o multiselección
optionMeta + headerLabel/metaLabelPanel de tabla de dos columnas, sin plantillas
filter · filterByBuscador interno
invalid · errorText · loadingEstados
plantillas #item/#selectedItemFilas personalizadas proyectadas
<bui-status-pill>Chip de estado

Píldora de estado con 5 tonos semánticos (neutro, info, éxito, aviso, peligro). Cada proyecto la envuelve para mapear sus propios estados de dominio al tono correcto, sin duplicar el estilo.

Uso

<bui-status-pill label="Aprobado" tone="success" />
<bui-status-pill label="Pendiente" tone="warning" />
<bui-icon>Icono de marca

Renderiza inline un SVG del catálogo por su name. Hereda el color del texto (currentColor) y admite tamaños sm/md/lg o un valor CSS.

<bui-icon name="bus" size="lg" />

// icono propio de un proyecto, sin tocar la librería
iconRegistry.register('mi-icono', '<svg>…</svg>');

El catálogo incluye además bui-card (superficie), bui-datepicker (fecha en español) y bui-page-subheader (cabecera de página). 7 componentes, 55 pruebas en verde.

Para el equipo

Cómo consumir la librería en un proyecto

  1. 1 · Instalar

    Tras autenticar el scope @aspsols contra el registro de GitLab (una vez por máquina — ver Primera vez: configurar el acceso más abajo):

    pnpm add @aspsols/brasilia-ui
  2. 2 · Un solo import de estilos

    En el styles.scss de la app. Incluye Tailwind, los tokens, el puente @theme y el chrome de los overlays:

    @use '@aspsols/brasilia-ui/styles';
  3. 3 · Registrar el tema

    En app.config.ts (ver el bloque de «El motor del tema» arriba).

  4. 4 · Usar los componentes

    Se importan como cualquier standalone y se usan en la plantilla:

    import { ButtonComponent } from '@aspsols/brasilia-ui';
    // …
    <bui-button label="Enviar" type="submit" [loading]="saving()" />

Para el equipo · primera vez

Configurar el acceso al registro privado

El paquete no vive en el npm público, sino en el registro privado de GitLab (proyecto 1666). Si al instalar te sale ERR_PNPM_FETCH_404 · registry.npmjs.org/@aspsols…: Not Found o No authorization header was set, es porque todavía no configuraste el acceso. Sigue estos 5 pasos en orden — se hacen una sola vez por máquina.

La idea, en dos archivos. El acceso se reparte en dos archivos .npmrc distintos, y esto es la clave para que funcione:

① El del proyecto (en la carpeta del frontend) → solo dice dónde buscar el paquete. Se puede subir al repo.

② El de tu usuario (en tu carpeta personal) → guarda tu token. Es personal y nunca se sube.

⚠️ Importante (pnpm v11): el token no puede ir en el .npmrc del proyecto — las versiones nuevas de pnpm lo ignoran por seguridad (para que no se filtre al subirlo al repo). Por eso va en el archivo de tu usuario. Si lo pones en el del proyecto verás el aviso Ignored project-level auth setting… environment variables are not expanded y no autenticará.

  1. Paso 1 · Crea tu token de acceso en GitLab

    Cada persona genera su propio token — es personal y no se comparte. Abre este enlace (te lleva directo a la página de tokens):

    https://git.aspsols.com/-/user_settings/personal_access_tokens
    • Inicia sesión si te lo pide y clic en Add new token.
    • Token name: npm-brasilia-ui (o el nombre que quieras).
    • Expiration date: una fecha futura, ej. 1 año (GitLab exige caducidad).
    • Scopes: marca read_api. Con eso basta para instalar. (Si aparece read_package_registry aparte, márcalo también.)
    • Clic en Create personal access token.

    ⚠️ GitLab te muestra el token una sola vez. Cópialo apenas aparezca (empieza por glpat-…). Si cierras la página sin copiarlo, no lo vuelves a ver y toca crear otro. Guárdalo un momento en el bloc de notas: lo usarás en el paso 3.

  2. Paso 2 · Crea el .npmrc del proyecto (solo el registro)

    Este archivo dice «el scope @aspsols se descarga de GitLab». Abre la terminal en la raíz de tu frontend (donde está el package.json) y corre una línea según tu sistema. Crea un archivo .npmrc con una sola línea:

    Windows · PowerShell

    '@aspsols:registry=https://git.aspsols.com/api/v4/projects/1666/packages/npm/' | Out-File -FilePath .npmrc -Encoding utf8

    WSL / Linux / Mac

    echo '@aspsols:registry=https://git.aspsols.com/api/v4/projects/1666/packages/npm/' > .npmrc

    Este archivo es seguro de subir al repositorio (no contiene tu token). De hecho, lo ideal es que quede versionado para que todo el equipo lo tenga sin repetir este paso.

  3. Paso 3 · Guarda tu token en el .npmrc de tu usuario

    Aquí va tu token, en el .npmrc de tu carpeta personal (no la del proyecto). Reemplaza glpat-TU_TOKEN_AQUI por el token real del paso 1 y corre la línea de tu sistema:

    Windows · PowerShell

    Add-Content -Path "$HOME\.npmrc" -Value "//git.aspsols.com/api/v4/projects/1666/packages/npm/:_authToken=glpat-TU_TOKEN_AQUI"

    WSL / Linux / Mac

    echo '//git.aspsols.com/api/v4/projects/1666/packages/npm/:_authToken=glpat-TU_TOKEN_AQUI' >> ~/.npmrc

    WSL tiene su propia carpeta personal, separada de Windows. Si vas a correr pnpm dentro de WSL, el token debe estar en el ~/.npmrc de WSL (con el comando de Linux de arriba), no en el de Windows. Si trabajas en los dos, ponlo en ambos.

  4. Paso 4 · Verifica que el token quedó guardado

    No uses pnpm config get con el token: da el error option is protected a propósito (los tokens no se pueden leer así). En su lugar, abre el archivo y comprueba que aparece la línea con tu glpat-…:

    Windows · PowerShell

    Get-Content "$HOME\.npmrc"

    WSL / Linux / Mac

    cat ~/.npmrc

    Si ves la línea //git.aspsols.com/...:_authToken=glpat-…, el token está bien guardado. Si no aparece o el archivo está vacío, repite el paso 3.

  5. Paso 5 · Instala

    Parado en la raíz del proyecto:

    pnpm install

    Ahora pnpm encuentra @aspsols/brasilia-ui en GitLab (paso 2) y se autentica con tu token (paso 3). Debe terminar en Done, sin 404 ni 401. ✅

    Usa pnpm install, no npm install — este proyecto usa pnpm. Si necesitas fijar la versión, es pnpm add @aspsols/brasilia-ui@0.1.1, que la escribe en el package.json como "^0.1.1".

Si algo falla

Mensaje que vesQué pasaSolución
404 Not Found · registry.npmjs.org/@aspsols…Falta el .npmrc del proyecto, o pnpm no lo está leyendo.Repite el paso 2 parado en la raíz del proyecto (donde está el package.json).
Ignored project-level auth setting · environment variables are not expandedPusiste el token en el .npmrc del proyecto; pnpm v11 lo ignora por seguridad.Borra la línea del _authToken del .npmrc del proyecto (deja solo la del registro) y ponlo en el de tu usuario: paso 3.
401 · No authorization header was setFalta el token en el .npmrc de tu usuario, o está mal escrito.Revisa con el paso 4 que la línea del token esté; si no, repite el paso 3.
403 ForbiddenEl token no tiene permiso de lectura del registro.Crea uno nuevo con el scope read_api marcado (paso 1).
option is protected, can not be retrievedEs normal: los tokens no se leen con config get.No es un error. Verifica con Get-Content / cat (paso 4).
No aparece «Access Tokens» en GitLabEl admin deshabilitó los tokens personales.Pide un Deploy Token del proyecto 1666 como alternativa.
Remove-Item: no se puede quitar… el directorio no está vacío / ruta muy largaEl proyecto está en OneDrive: rutas de node_modules que superan el límite de 260 caracteres de Windows.Mueve el proyecto fuera de OneDrive (ej. C:\Projects\…). Para borrar node_modules: mkdir vacia; robocopy vacia node_modules /MIR; rmdir vacia,node_modules.

No pongas el proyecto dentro de OneDrive. Node genera miles de archivos en node_modules; OneDrive los sincroniza (lento, con conflictos) y alarga las rutas hasta romper el límite de Windows. Trabaja desde una ruta corta como C:\Projects\mi-proyecto.

Decisiones técnicas

Por qué está construida así

Las preguntas que un revisor técnico hará, respondidas.

¿Por qué GitLab Registry y no npm público?

GitLab Registry
  • Privado: solo quien tiene acceso al GitLab del cliente puede instalarla.
  • Sin costo extra: incluido en el mismo GitLab del código.
  • Mismos permisos y tokens que ya usa el equipo.
  • Estándar para librerías internas de empresa.
npm público — descartado
  • Abierto al mundo: expondría el código del cliente.
  • La privacidad exige un plan de pago aparte.
  • Gestión de organización y tokens en un tercero.

Componentes envoltura (wrappers), no forks de PrimeNG. Exponemos variant="primary" en vez de la mezcla cruda de PrimeNG. Ventaja: una API de marca estable que aísla a las apps de los cambios internos de PrimeNG, y un punto único donde encapsular variantes, estados y accesibilidad.

Estilos encapsulados salvo los overlays. Los estilos de cada componente van encapsulados (no se escapan). La excepción son los paneles que PrimeNG monta en <body> (appendTo="body", para que no los recorte un contenedor): esos deben ser globales y viajan en la hoja de estilos del kit. Es un híbrido deliberado, no una fuga.

Angular y PrimeNG como peerDependencies. La librería no empaqueta Angular ni PrimeNG: los declara como «pares». La app consumidora manda la versión; así no hay duplicados ni conflictos de versión.

Ingeniería y calidad

Cómo está construida y cómo se mantiene sana

HerramientaRol
Angular 21Framework de las apps del cliente; componentes standalone, OnPush y signals.
PrimeNG 21 + @primeuix/themesComponentes ricos ya resueltos y el motor de tema (preset).
Tailwind v4Utilidades de layout y espaciado, atadas a los tokens.
Storybook 10Documentación viva: cada componente con sus estados y código copiable.
VitestPruebas unitarias; un componente no se da por terminado sin ellas en verde.
ng-packagrEmpaqueta la librería en el formato estándar (FESM + tipos) que se publica.
pnpm · Node 22Gestor y runtime, fijados para builds reproducibles.

Definición de hecho por componente: API con input()/output(), standalone + OnPush, cero valores hardcodeados (todo por token), story con estados y spec en verde.

Distribución

Publicación y versionado

1

Desarrollo

El componente se construye y aprueba en Storybook, con pruebas.

2

Empaquetado

ng-packagr genera el paquete en dist/.

3

Versión

Se sube el número (SemVer) y se publica al GitLab Registry.

4

Consumo

Cada proyecto actualiza a la nueva versión cuando le conviene.

El versionado semántico (mayor.menor.parche) le dice a cada proyecto si una actualización trae cambios que rompen (mayor), funciones nuevas (menor) o solo correcciones (parche). Nadie se actualiza a la fuerza.

Adopción en producción

Relevo Tarjetón ya consume la librería

La versión 0.1.0 está publicada en el GitLab Registry y es el primer proyecto que la instala como dependencia real — no un enlace local, sino el paquete descargado del registro.

0.1.0
publicada en el Registry (proyecto 1664)
^0.1.0
declarada como dependencia en Relevo
2
componentes del kit ya en uso: bui-button y bui-status-pill
60+
usos de bui-button en la app, en una veintena de pantallas
Ya migrado en Relevo
  • bui-button reemplaza a p-button en botones de acción y diálogos de toda la app.
  • bui-status-pill: el chip de estado propio ahora envuelve el del kit, con los 5 tonos semánticos unificados.
  • El paquete se instala desde el Registry privado vía .npmrc con token de GitLab.
Pendiente en Relevo
  • Adoptar el tema/preset y la hoja de estilos del kit (hoy usa su PRIMENG_ES local).
  • Sustituir los p-button crudos que aún quedan en algunas plantillas.
  • Migrar el resto: input, select, card, datepicker y subheader.

Estado del proyecto

En qué vamos

Hecho

Fase 0 · Base del proyecto y tema

Workspace, librería, azul de marca y tokens, Storybook funcionando.

Hecho

Fase 1 · Sistema de iconos

36 iconos de marca como bui-icon, con galería y pruebas.

Hecho

Fase 2 · Componentes base

7 componentes migrados (botón, input, select, chip, tarjeta, datepicker, subheader), 55 pruebas en verde.

En curso

Fase 3 · Publicación y adopción

v0.1.0 publicada en el Registry. Relevo Tarjetón ya consume el paquete: botón y chip de estado adoptados; falta el tema y el resto de componentes.

Fase 4 · Resto de componentes

Buscador, estado vacío, diálogo de confirmación y más.