mbcj-ui-styles
v1.6.0
Published
Librería de estilos compartidos para el ecosistema TDF MBCyJ
Maintainers
Readme
mbcj-ui-styles
Librería de estilos compartidos y componentes de UI para el ecosistema de aplicaciones del Ministerio de Bienestar Ciudadano y Justicia (TDF).
Proporciona una identidad visual unificada (paleta, tipografía, layouts, formularios, tablas) para los sistemas React del ministerio.
Requisitos
Peer dependencies del proyecto consumidor:
- React
^19.0.0 - Wouter
^3.0.0(para la navegación del Header) - @mui/material
>=7.3.10 - @mui/icons-material
>=7.3.10 - @emotion/react
^11.14.0 - @emotion/styled
^11.14.1 - react-toastify
^11.0.5
Instalación
npm install mbcj-ui-stylesEn el archivo raíz (típicamente main.jsx o App.jsx):
import 'mbcj-ui-styles/src/index.css';Tema y tipografía
El SDK exporta un theme de MUI que centraliza la tipografía institucional (Public Sans). Envolvé la app con ThemeProvider + CssBaseline para que todos los componentes MUI la hereden sin forzarla por CSS:
import { ThemeProvider, CssBaseline } from '@mui/material';
import { theme } from 'mbcj-ui-styles';
import 'mbcj-ui-styles/src/index.css';
export default function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* ...resto de la app... */}
</ThemeProvider>
);
}Componentes exportados
| Componente | Propósito |
| ---------------- | ------------------------------------------------------------ |
| Header | Barra de navegación institucional con menú responsive. |
| Footer | Pie de página institucional. |
| Tables | Tabla paginada con filas expandibles y server-side support. |
| GenerarMessage | Diálogo de confirmación inline para usar en notificaciones. |
| FormCard | Tarjeta contenedora para una sección de formulario. |
| FormRow | Fila de campos en grilla responsive (1 a 4 columnas). |
| CampoConLabel | Input con etiqueta visible arriba (variant standard). |
| CampoSinLabel | Input estilo cápsula (pill) sin etiqueta, fondo gris suave. |
| Boton | Botón institucional unificado; el estilo se elige con la prop variante. |
| Header | (layout) Barra de navegación institucional. |
| Footer | (layout) Pie de página institucional (sin props). |
| Inicio | (layout) Pantalla de inicio: saludo + accesos agrupados por rol. |
| theme | Tema MUI con la tipografía institucional (Public Sans). |
Nota de migración (1.4+): los antiguos
BotonPrimarioyBotonNaranjase unificaron en un únicoBotoncon propvariante. Ver sección 2.
Reglas de uso
1. CampoConLabel vs CampoSinLabel — cuándo usar cada uno
Ambos cumplen la misma función técnica (un campo de texto controlado), pero responden a contextos visuales distintos. Elegir el correcto es importante para mantener consistencia entre sistemas.
| Criterio | CampoConLabel | CampoSinLabel |
| ------------------------------- | ------------------------------------------------ | -------------------------------------------------------- |
| Densidad de la pantalla | Alta (muchos campos) | Baja (1 a 3 campos visibles) |
| Etiqueta | Siempre visible, en negrita, arriba del campo | Solo placeholder (gris dentro del campo) |
| Borde / fondo | Línea inferior delgada | Cápsula con fondo gris suave |
| Adornos (íconos) | Opcionales; el label sigue siendo la guía | Recomendados; el ícono refuerza la semántica del campo |
| Estados disabled/error | Mensajes vía helperText y error | Solo opacidad reducida (sin texto de error) |
| Tipo de pantalla | Edición de datos, formularios completos | Login, búsqueda rápida, autenticación en kiosco, modales |
Usar CampoConLabel cuando:
- El formulario tiene 3 o más campos en pantalla.
- El usuario necesita ver claramente qué dato pide cada campo, aún cuando el campo ya esté completado.
- Se requiere validación con
helperText(ej. "DNI inválido", "Las contraseñas no coinciden"). - El campo va dentro de un
FormCardcon título y otros campos relacionados.
Placeholder por defecto: si no se pasa la prop
placeholder, toma el mismo texto quelabel. Esto da una "ancla visual" dentro del input cuando está vacío. Para suprimirlo o personalizarlo, pasarplaceholder=""oplaceholder="Ej: ..."explícitamente.
Texto largo (
multiline): para comentarios, motivos o descripciones, pasámultiline(y opcionalmenterows/minRows). El campo se presenta automáticamente como una caja con borde (textarea) en vez de la línea inferior, conservando el label arriba. El límite de caracteres se pasa coninputProps={{ maxLength: 300 }}.<CampoConLabel label="Motivo" value={motivo} onChange={(e) => setMotivo(e.target.value.toUpperCase())} multiline minRows={5} inputProps={{ maxLength: 300 }} />
<FormCard titulo="Datos personales" onSubmit={guardar} acciones={<Boton variante="celeste" type="submit">Guardar</Boton>}>
<FormRow>
{/* placeholder automático "Apellidos" */}
<CampoConLabel label="Apellidos" required value={apellidos} onChange={(e) => setApellidos(e.target.value.toUpperCase())} />
{/* placeholder personalizado */}
<CampoConLabel label="DNI" placeholder="Ej: 00.000.000" required value={dni} onChange={(e) => setDni(e.target.value)} />
</FormRow>
</FormCard>Usar CampoSinLabel cuando:
- La pantalla tiene muy pocos campos y el contexto (título de la pantalla, ícono interno) ya transmite qué se pide.
- Se busca una estética liviana y minimalista (login, splash de bienvenida, barra de búsqueda).
- Se quiere reforzar el campo con íconos que actúan como guía visual.
<CampoSinLabel
placeholder="00.000.000"
type="number"
required
value={documento}
onChange={(e) => setDocumento(e.target.value)}
startAdornment={<Person />}
/>NO usar CampoSinLabel cuando:
- Hay más de 3 campos en pantalla (genera carga cognitiva: el usuario no sabe qué pide cada uno una vez completado).
- Se requiere mostrar texto de error o helper específico.
- El campo está dentro de un
FormCardcon otrosCampoConLabel(mezclar estilos confunde).
2. Boton — variantes y cuándo usar cada una
Boton es el botón institucional unificado. La apariencia se elige con la prop variante (nombrada por color). Reemplaza a los antiguos BotonPrimario/BotonNaranja.
| variante | Estilo | Usar para |
| ----------- | ------------------------------- | ------------------------------------------------------------ |
| celeste | Outline azul (#0073E6) | Acción principal repetible: Guardar, Actualizar, Buscar, Nuevo, Continuar (valor por defecto). |
| naranja | Relleno naranja (#F57600) | Acción final/crítica del flujo: Enviar, Validar, Registrar, Confirmar. Idealmente una por flujo. |
| verde | Relleno verde | Acción positiva final: Aprobar, Activar, Finalizar, Terminar. |
| rojo | Relleno rojo | Acción destructiva: Eliminar, Anular, Desactivar, Quitar. |
| negro | Outline gris neutro | Acción neutra: Cancelar, Volver, Salir, Corregir. |
| azul | Relleno azul institucional | Sesión / navegación: Iniciar sesión, Cerrar sesión. |
Props: variante (default 'celeste'), children (etiqueta), onClick, type ('button'|'submit'|'reset'), disabled, startIcon, endIcon, fullWidth, sx.
import { Boton } from 'mbcj-ui-styles';
// Botonera típica de un formulario (salir + guardar)
<Boton variante="negro" onClick={salir}>Salir</Boton>
<Boton variante="celeste" type="submit">Guardar</Boton>
// Acción final / destructiva
<Boton variante="naranja" onClick={enviar}>Enviar</Boton>
<Boton variante="rojo" startIcon={<DeleteIcon/>} onClick={eliminar}>Eliminar</Boton>3. FormCard + FormRow — estructura de formularios
FormCard es el contenedor de una sección de formulario. FormRow distribuye los campos dentro en una grilla responsive.
<FormCard
titulo="Datos de Contacto"
onSubmit={guardar}
acciones={<Boton variante="celeste" type="submit">Guardar</Boton>}
>
<FormRow columns={1}>
<Autocomplete ... renderInput={(p) => <TextField {...p} variant="standard" InputLabelProps={{shrink:true}} />} />
</FormRow>
<FormRow>
<CampoConLabel label="Localidad" ... />
<CampoConLabel label="Código Postal" ... />
</FormRow>
<FormRow columns={3}>
<CampoConLabel label="Número" ... />
<CampoConLabel label="Puerta" ... />
<CampoConLabel label="Piso" ... />
</FormRow>
</FormCard>columns(1, 2, 3 o 4) define el máximo en desktop; colapsa automáticamente a 1 columna en mobile (auto-fit minmax(240px, 1fr)).- Para anidar (por ejemplo, calle ocupa media fila y a la derecha tres campitos), usar un
FormRowdentro de otro. - El botón submit va en
acciones, no como hijo delFormCard. - Si el formulario no es un
<form>HTML, omitironSubmity disparar la lógica desde los botones cononClick.
4. Tokens CSS (en variables.css)
Disponibles globalmente; preferir tokens antes que hardcodear colores:
| Token | Uso |
| ---------------------------------- | -------------------------------------------------------- |
| --color-primary | Azul institucional general (#1976d2). |
| --color-outline-primary | Azul outline (#0073E6) — botones primarios. |
| --color-outline-primary-hover | Hover de outline primario. |
| --color-accent / --color-accent-hover | Naranja crítico (#F57600) — Boton variante="naranja". |
| --color-success / --color-success-hover | Verde — Boton variante="verde". |
| --color-danger / --color-danger-hover | Rojo — Boton variante="rojo". |
| --color-neutral / --color-neutral-hover | Gris neutro — Boton variante="negro". |
| --color-azul / --color-azul-hover | Azul institucional — Boton variante="azul". |
| --bg-white / --bg-subtle | Fondos claros (cards, filas de tabla, inputs pill). |
| --bg-inactive | Fondo gris para filas/inputs deshabilitados. |
| --border-input / --border-light| Bordes de inputs y separadores suaves. |
| --radius-button / --radius-card| Radios consistentes (10px / 12px). |
| --label-weight / --label-color | Negrita y color del label en CampoConLabel. |
| --font-family-base | Tipografía base (Public Sans, system-ui). |
| --z-modal / --z-toast / etc. | Escalera de z-index institucional. |
5. Tables — paginación server-side
Ver JSDoc del componente en src/layout/Tables.jsx. Resumen:
- Acepta
cabeceras(definición de columnas conid,titulo,align,render),resultados,total,page(0-indexed),rowsPerPage, callbackshandleChangePageyhandleChangeRowsPerPage. - Para filas expandibles, pasar
renderExpandableRow={(row) => <Detalle ... />}. - Las filas se colorean según
row.activo(booleano). - El backend debe aceptar
limit,offsety opcionalmentebusquedacomo query params.
6. Patrones recomendados
- No mezclar
CampoConLabelyCampoSinLabelen la misma pantalla. - Un solo
Boton variante="naranja"visible a la vez (acción final principal). - Validaciones por sección: cada
FormCardcon su propioonSubmity su propio botón, para mantener la sintaxis de endpoints separados (perfil, contacto, password, etc.). - Mobile-first: el SDK ya colapsa filas y secciones; evitar overrides de ancho que rompan ese comportamiento.
- Iconos: usar
@mui/icons-materialpara mantener consistencia. Pasar comostartAdornment/endAdornment.
7. Layout — Header, Footer, Inicio
Header — barra de navegación institucional. Las opciones deben llegar ya filtradas por rol (el Header no filtra internamente).
| Prop | Tipo | Descripción |
| --------------- | ------------------ | ------------------------------------------------------------------ |
| logged | boolean | Si hay usuario logueado. Si es false, oculta el menú y muestra "Ingresar". |
| opciones | {titulo, url}[] | Ítems de menú (ya filtrados por rol). |
| onLogout | function | Handler de "Cerrar Sesión". |
| login | string | Ruta de login (default "/ingresar"). |
| tituloSistema | string | Texto alt del logo institucional. |
<Header logged={logged} opciones={opcionesDelRol} onLogout={logout} />Footer — sin props; el contenido se gestiona en el SDK y se distribuye uniforme a todos los sistemas.
<Footer />Inicio — pantalla de inicio con accesos agrupados por rol. No navega internamente: cada item.onClick y loginCta.onClick los provee el consumidor (sirve con cualquier router).
| Prop | Tipo | Descripción |
| --------------- | -------------------------------------- | ----------------------------------------------------------------- |
| usuario | object | null | Usuario logueado, o null para la vista pública (con loginCta). |
| nombreSistema | string | Nombre del sistema. |
| roles | { [rolId]: {nombre, color} } | Mapeo rol_id → etiqueta y color del chip. |
| secciones | {titulo, descripcion?, roles, items}[] | items: {label, icon?, onClick, roles, variante?}. |
| loginCta | {texto, icon?, mensaje?, onClick} | CTA mostrado cuando usuario === null. |
| getRolId | function | Resuelve el rol_id (default parseInt(u.tipo_usuario_id, 10)). |
<Inicio
usuario={usuario}
nombreSistema="Sistema de Comedores"
roles={{ 4: { nombre: 'Administrador', color: 'error' } }}
secciones={[{
titulo: 'Gestión', roles: [4],
items: [{ label: 'Comedores', icon: <RestaurantIcon/>, onClick: () => navigate('/comedores'), roles: [4] }]
}]}
loginCta={{ texto: 'INICIAR SESIÓN', onClick: () => navigate('/ingresar') }}
/>Desarrollo local
Si se hace npm link desde el SDK a un proyecto consumidor:
Asegurar que el proyecto consumidor tenga en
vite.config.js:resolve: { dedupe: ['react', 'react-dom', 'wouter', '@mui/material', '@emotion/react', '@emotion/styled'] }Esto evita instancias duplicadas que rompen Context (ej.
<Router base>de wouter).No usar
preserveSymlinks: true; Vite necesita resolver tras el symlink para deduplicar.Borrar
node_modules/.vitesi los cambios de CSS del SDK no se reflejan.
Versiones
- 1.6.0 —
CampoConLabelconmultilineahora se presenta como caja con borde (textarea), no como línea inferior. Aplica automáticamente, dentro o fuera deFormCard.Header: el ítem activo se resuelve por sección (primer segmento de la ruta), por lo que las sub-rutas (ej./visitas/cargas) resaltan su ítem padre. - 1.4.0 – 1.5.0 —
BotonPrimario/BotonNaranjaunificados enBotoncon propvariante(celeste/naranja/verde/rojo/negro/azul). NuevothemeMUI (tipografía Public Sans) y componenteInicio. Paleta semántica de tokens. - 1.3.2 —
CampoConLabelahora hereda automáticamenteplaceholder = labelcuando no se especifica uno. - 1.3.0 —
CampoConLabelyCampoSinLabel(renombrado deCampoTextoyCampoPill). - 1.2.0 — Nuevos componentes de formulario:
FormCard,FormRow,CampoTexto,CampoPill,BotonPrimario,BotonNaranja. Tokens de paleta institucional. - 1.1.0 —
Tablescon paginación server-side y filas expandibles. - 1.0.x — Header, Footer, GenerarMessage.
