@mundoit-lib/histrix-component-vue
| Install | |
|---|---|
npm install @mundoit-lib/histrix-component-vue |
|
| Latest Version: | 0.10.0 |
| Vue: | 3 |
| Older releases: | 2 |
| License: | MIT |
| Last Updated: | Oct 14, 2026 |
| Links: | GitHub · npm |
@mundoit-lib/histrix-component-vue
Componentes Vue 3 + Quasar 2 para construir clientes del backend Histrix (ERP declarativo, schema-driven). El componente raíz <HistrixApp :path="..."/> pide el schema de un XML de Histrix al backend y monta la pantalla completa (tabla, formulario, calendario, dashboard, árbol, gráfico) sin programarla.
v0.1.0+ requiere Vue 3 nativo y Quasar 2. Apps Vue 2: usar la serie
0.0.x.
Instalación
npm install @mundoit-lib/histrix-component-vue
Distribución source-only: el paquete contiene los .vue sin compilar; los compila el bundler de tu app (Vite / Quasar CLI / webpack).
Peers requeridos en tu app:
npm install vue@^3 quasar@^2 @vuelidate/core @vuelidate/validators
Peers opcionales (declarados en peerDependenciesMeta): son los adaptadores por defecto de los contratos de la librería (ver Integración). Si están instalados se usan solos; si no, la app inyecta los suyos.
| Paquete | Contrato | Se toma de |
|---|---|---|
@mundoit-lib/plugin-vue-axios |
http | $axios |
@mundoit-lib/plugin-vue-auth |
auth (1.x/websanova o el AuthService nuevo) |
$auth |
@mundoit-lib/plugin-vue-event |
bus de eventos hacia la app | $events (sin él, bus interno) |
vue-router@^4 || ^5 |
navegación (schema.redirect, menú, volver) |
$router |
Componentes
38 componentes Vue 3. Salvo HistrixUnsupported (aviso interno de HistrixApp para tipos de pantalla sin componente), todos se registran con el plugin y se exportan desde la raíz y por subpath.
| Grupo | Componentes |
|---|---|
| Páginas y diálogos | HistrixPage (página de app: path de la ruta, query y _title), HistrixAppDialog (app en un q-dialog con v-model; se cierra en process-finish/closepopup y emite finish) |
| Pantallas schema-driven | HistrixApp (raíz: monta la pantalla según el schema), HistrixForm, HistrixTable, HistrixTree, HistrixList, HistrixCalendar, HistrixDashboard, HistrixChart |
| Piezas de pantalla | HistrixField, HistrixCell, HistrixFilters, HistrixHelp (picker de ayudas), HistrixPdfViewer (visor de PDF nativo, ver PDF), ExportForm, HistrixUnsupported |
| Auth nativa (sin Quasar) | HistrixLoginSplit, HistrixRegisterSplit, HistrixForgotPasswordSplit, HistrixResetPasswordSplit |
| Auth con Quasar | LoginForm, FormLoginNotStyles, HistrixPasswordChange, InputPassword |
| Menú y shell | HistrixMenu, HistrixExpansionMenu, HistrixSystemMenu (engranaje del menú de sistema), HistrixMenuSearch (buscador con Ctrl/⌘+K), FavoritItems, profileMenu, profileMenuItems, notificationMenu |
| Conexión y utilidades | DatabaseSelector, HistrixConnectionSettings, HistrixFileManager, HistrixLog, HistrixNews, HistrixUsers |
El detalle de props y eventos de cada uno está en docs/03-componentes.md del repo.
Uso
// Como plugin (registra todos los componentes globalmente):
import HistrixPlugin from '@mundoit-lib/histrix-component-vue/plugin';
app.use(HistrixPlugin);
// Named imports:
import { HistrixApp, HistrixTable, config } from '@mundoit-lib/histrix-component-vue';
// Por subpath (tree-shaking manual):
import HistrixForm from '@mundoit-lib/histrix-component-vue/components/HistrixForm';
import useApi from '@mundoit-lib/histrix-component-vue/services/histrixApi';
Configuración runtime (host del backend, base, credenciales OAuth):
import { config } from '@mundoit-lib/histrix-component-vue';
config.apiUrl = 'https://mi-backend-histrix.com'; // `fixApi` sigue andando, pero está deprecado
config.db = 'micliente';
config.clientId = '...';
config.clientSecret = '...';
config es la fuente viva de host y base: se puede cambiar en runtime (multi-tenant, cambio de conexión) y useApi() lo toma en el próximo pedido. Las claves host y database del storage (las escribe HistrixConnectionSettings) son sólo el fallback para cuando config.apiUrl/config.db están vacíos.
Desde 0.2.0 config gana sobre el storage (antes era al revés). Para una app:
- Si seteaba
localStorage.host/localStorage.databasepara que la lib tomara otro host o base (p. ej. unapplyTenant), alcanza conconfig.apiUrl/config.db: sacar esas escrituras. - Si usa
HistrixConnectionSettingsy además seteaconfig.apiUrl/config.dbal arrancar (por.env), lo elegido en el widget vale para la sesión actual, pero al recargar vuelve a mandar la config. Para que persista, la app lee el storage al arrancar y lo vuelca enconfig, o no seteaconfigy deja que la lib caiga al storage.
<!-- Montar cualquier pantalla declarada en un XML de Histrix: -->
<HistrixApp path="ventas/qry/listado.xml" :query="{ id: 123 }" />
Integración
La librería depende de contratos, no de paquetes: bus ({ emit, on, off }), auth ({ login, logout, user, setUser, getToken, check, onUserChange }), http (instancia tipo axios), storage ({ get, set, remove }) y navegación ((to, { replace, back }) => void). Los tipos están en types/integration.d.ts.
(a) App Mundo IT con los tres plugins: no hay que configurar nada más. La librería toma $axios, $auth y $events de la app, y $router si existe. Los eventos login-ok, loaded-user, update-favorit y el eventAfter del login siguen saliendo por $events, así que los events: { 'loaded-user'() {} } de las apps no cambian. Además salen como emits del componente: <HistrixLoginSplit @loaded-user="...">.
(b) App sin plugins: se inyecta lo que haga falta. Lo que no se pase usa el default:
app.use(HistrixPlugin, {
http: miAxios, // o config.http
auth: miAuth, // adaptador parcial; también acepta el AuthService nuevo
bus: miBus, // opcional: sin él hay un bus interno
storage: false, // false = no persistir; sin definir = localStorage
onNavigate: (to, { replace, back }) => (back ? history.back() : miNavegar(to, replace))
});
Para la sesión, useHistrixSession() devuelve { user, isLogged, login(u, p), logout(), refresh() }. Es la única fuente de user y reemplaza a leer localStorage.user. Para las pantallas, HistrixPage reemplaza el pages/Histrix.vue de cada app y HistrixAppDialog, los HistrixAppCard. HistrixList ya no navega a la ruta form, que no existe: emite select con { path, query }. HistrixField.resetField(names) reemplaza al evento global reset-field.
Las pantallas PDF (schema.pdf o la prop pdf de HistrixApp) y el botón de imprimir se ven con HistrixPdfViewer, que usa el visor nativo del navegador en un <iframe>, con Descargar y Abrir en pestaña nueva. Si el navegador no muestra PDFs en línea (navigator.pdfViewerEnabled === false: Chrome Android, algunas PWA de iOS), en lugar del iframe ofrece Compartir (Web Share con el archivo, si el dispositivo lo permite), Abrir y Descargar.
<HistrixPdfViewer v-model="open" :src="blobUrl" :blob="blob" filename="factura.pdf" />
<HistrixPdfViewer inline :src="blobUrl" filename="factura.pdf" />
El blob URL es de quien lo crea (HistrixApp lo revoca al reemplazar el PDF y al desmontarse).
Desde 0.1.4 ya no hace falta @quasar/qpdfviewer ni public/pdfjs (12 MB que la PWA precacheaba). Para sacarlo de una app:
- Actualizar a
@mundoit-lib/histrix-component-vue@^0.1.4. quasar ext remove @quasar/qpdfviewer(saca la extensión depackage.jsonyquasar.extensions.json).- Borrar
public/pdfjs. - Si la app usa
<q-pdfviewer>propio, cambiarlo porHistrixPdfViewer(importado de la lib). - Buildear la PWA y verificar que el precache del service worker ya no incluye
pdfjs/.
Errores y notificaciones
Todos los métodos de useApi() rechazan con un HistrixApiError:
import { isHistrixApiError } from '@mundoit-lib/histrix-component-vue';
try {
await api.insertAppData('ventas/ing/cliente.xml', data);
} catch (e) {
// e.status: 400 | 401 | 404 | 500 | 0 (red)
// e.kind: 'validation' | 'auth' | 'not_found' | 'server' | 'network' | 'unknown'
// e.message: texto legible (sin HTML); e.raw: error original de axios
}
getAppData() resuelve siempre response.data.data como array: un 204 o una consulta vacía dan { data: [] }.
Ante un 401 la librería llama a config.onUnauthorized(error) (no toca el router):
app.use(HistrixPlugin, {
onUnauthorized: () => router.push('/login')
});
Las notificaciones pasan por un notifier inyectable con la interfaz
{ success(msg), error(msg | HistrixApiError), info(msg), confirm(msg) → Promise<boolean> }.
Por defecto el plugin registra uno con Quasar (Notify y Dialog, que tienen que estar habilitados en
quasar.config). Para usar otro, pasalo al instalar el plugin (los métodos que falten caen a la consola):
app.use(HistrixPlugin, {
notify: {
success: (msg) => toast.success(msg),
error: (msg, err) => toast.error(msg),
info: (msg) => toast.info(msg),
confirm: (msg) => miDialogo.confirmar(msg) // Promise<boolean>
}
});
En los componentes:
import { useHistrixNotify } from '@mundoit-lib/histrix-component-vue';
const notify = useHistrixNotify();
notify.error(e); // acepta un HistrixApiError
if (await notify.confirm('¿Eliminar el registro?')) { /* … */ }
Idioma (i18n)
Los textos propios de la librería (botones, avisos, validaciones, paginador…) salen de un diccionario por locale. Sin configurar nada se ven en español, como siempre. Lo que manda el backend (labels, títulos, processButton) no pasa por acá: lo traduce Histrix.
app.use(HistrixPlugin, {
locale: 'en', // default 'es'; acepta un ref para cambiarlo en caliente
messages: { // se mezcla con los diccionarios de la lib: solo las claves a pisar
en: { 'common.search': 'Find' },
pt: { 'common.search': 'Pesquisar', 'common.cancel': 'Cancelar' }
}
});
Las claves son planas, con namespace por componente (table.perPage, form.processFinished, login.title…). La lista completa está en src/locales/es.js (exportado como defaultMessages.es). La librería trae es y en. Si una clave falta en el locale elegido, se usa la de es, y si tampoco está ahí, se muestra la clave. Los parámetros se interpolan con {nombre}: t('export.title', { title }).
Si la app ya usa vue-i18n, puede pasarle su t. Para que funcione, tiene que sumar las claves de la lib a sus mensajes. Las que falten caen al es de la librería:
import { defaultMessages } from '@mundoit-lib/histrix-component-vue';
const i18n = createI18n({
legacy: false,
flatJson: true, // las claves de la lib son planas ('table.perPage')
locale: 'es',
messages: { es: { ...defaultMessages.es, ...misEs }, en: { ...defaultMessages.en, ...misEn } }
});
app.use(HistrixPlugin, { i18n: { t: i18n.global.t, locale: i18n.global.locale } });
Para cambiar el idioma en caliente o usar los textos en la app:
import { useHistrixI18n } from '@mundoit-lib/histrix-component-vue';
const { t, locale } = useHistrixI18n();
locale.value = 'en';
t('common.cancel'); // 'Cancel'
Los props de texto de las pantallas de auth (title, submitLabel, emailPlaceholder…) siguen funcionando: si se pasan, ganan sobre el diccionario.
Personalizar el menú
HistrixExpansionMenu separa la lógica del diseño: el pedido del menú (getMenu(level)), los destacados, los favoritos, el loading, la navegación y las secciones abiertas (guardadas en el storage) las maneja la librería; la app puede cambiar sólo el diseño. Sin slots ni variables se ve igual que antes.
Slots
Todos son scoped y tienen el diseño de la librería como default. Los de ramas y hojas aplican en todos los niveles del árbol.
| Slot | Props | Reemplaza |
|---|---|---|
#loading |
— | los skeletons mientras carga |
#section-header |
section ('featured' | 'favorites'), count, open |
el contenido de la cabecera de Destacados y de Favoritos |
#featured-item |
node, to, label, subtitle, onClick, open |
cada destacado |
#favorite-item |
favorite, to, label, onClick, open, toggleFavorite |
cada favorito |
#branch-header |
node, label, subtitle |
el contenido de la cabecera de una rama (el q-expansion-item y sus hijos los sigue armando el menú) |
#leaf |
node, to, label, subtitle, onClick, open, favoritesEnabled, isFavorite, toggleFavorite |
cada hoja |
label y subtitle vienen con las entidades HTML decodificadas y sin pasar a minúsculas. Para navegar hay dos formas: :to="to" + @click="onClick" (link del router, mantiene la clase del item activo y cierra el drawer) o @click="open" (navega y cierra el drawer, para diseños sin :to).
<HistrixExpansionMenu level="phpmen" :is-favorite="true" @close-drawer="drawer = false">
<template #leaf="{ to, label, onClick, favoritesEnabled, isFavorite, toggleFavorite }">
<q-item :to="to" dense @click="onClick">
<q-item-section>{{ label }}</q-item-section>
<q-item-section v-if="favoritesEnabled" side>
<q-btn flat round dense :icon="isFavorite ? 'favorite' : 'favorite_border'" @click.stop.prevent="toggleFavorite" />
</q-item-section>
</q-item>
</template>
</HistrixExpansionMenu>
Los tipos de las props están en HistrixExpansionMenuSlots. Hay un ejemplo completo en apps/playground/src/AltMenu.vue.
Variables CSS
Se definen en :root o en un ancestro del menú. Sin definir, valen lo del diseño default.
| Variable | Default | Uso |
|---|---|---|
--histrix-menu-accent |
var(--q-primary) |
destacados, item activo |
--histrix-menu-accent-contrast |
#fff |
ícono de los destacados |
--histrix-menu-active-bg |
accent al 11% sobre blanco |
fondo del item activo |
--histrix-menu-favorite |
#f59e0b |
estrella y cabecera de favoritos |
--histrix-menu-star |
#b6c0cf |
estrella sin marcar |
--histrix-menu-muted |
#64748b |
íconos y subtítulos |
--histrix-menu-branch-color |
#1e293b |
texto de las ramas |
--histrix-menu-leaf-color |
#334155 |
texto de las hojas |
--histrix-menu-hover-bg |
rgba(15, 23, 42, 0.045) |
hover de ramas y hojas |
--histrix-menu-dot / --histrix-menu-dot-hover |
#cbd5e1 / #94a3b8 |
punto de las hojas sin ícono |
--histrix-menu-count-color / --histrix-menu-count-bg |
#94a3b8 / rgba(148, 163, 184, 0.16) |
contador de las secciones |
Composable
Para un menú armado desde cero, useHistrixMenu da la misma lógica sin diseño:
import { useHistrixMenu } from '@mundoit-lib/histrix-component-vue';
const menu = useHistrixMenu({ level: 'phpmen', favorites: true, onClose: () => (drawer.value = false) });
// menu.tree, menu.featured, menu.favorites, menu.loading: refs
// menu.featuredOpen, menu.favoritesOpen: refs guardadas en el storage
// menu.isFavorite(menuId), menu.toggleFavorite(node), menu.nodeUri(node), menu.open(node),
// menu.onItemClick(node), menu.label(text), menu.reload()
level y favorites aceptan un valor, una ref o un getter. toggleFavorite llama a la API, notifica y emite update-favorit por el bus, como el menú. Llamado dentro de un componente, comparte su estado con los HistrixExpansionMenu anidados por provide/inject.
Menú de sistema (engranaje)
HistrixSystemMenu es el engranaje de Histrix: un botón con un popup que lista el menú de sistema (GET /menu/phpmen-fsm: usuarios, perfiles, plugins, impresoras…). Se dibuja sólo si el /me trae capabilities.systemMenu: true (administradores) y el menú se pide recién al abrirlo, una vez por usuario. Con systemMenu en false o sin capabilities (Histrix anterior a 2.0) no se dibuja ni se pide nada. Los ítems ocultos y los permisos por perfil los filtra el servidor: se dibuja lo que llega. Cada ítem se abre como cualquier ítem del menú, por su uri.
<HistrixSystemMenu />
<!-- otro ícono, o forzar mostrarlo/ocultarlo sin mirar capabilities -->
<HistrixSystemMenu icon="admin_panel_settings" :enabled="esAdmin" @close="drawer = false" />
Si el menú trae ramas (ítems con children), se dibujan como encabezados con sus ítems debajo.
Slots
Como en HistrixExpansionMenu, son scoped y tienen el diseño de la librería como default.
| Slot | Props | Reemplaza |
|---|---|---|
#button |
loading, open |
el ícono del botón |
#header |
title |
el título del popup |
#loading |
— | los skeletons mientras carga |
#empty |
— | el aviso de menú vacío |
#section |
node, label, depth |
el encabezado de una rama |
#item |
node, to, label, subtitle, depth, onClick, open |
cada ítem |
:to="to" + @click="onClick" o @click="open" navegan y cierran el popup, igual que en el menú lateral. Los tipos están en HistrixSystemMenuSlots.
<HistrixSystemMenu>
<template #item="{ node, label, open }">
<q-item clickable dense @click="open">
<q-item-section avatar><q-icon :name="node.icon || 'build'" /></q-item-section>
<q-item-section>{{ label }}</q-item-section>
</q-item>
</template>
</HistrixSystemMenu>
Composable
Para armarlo desde cero (otro tipo de popup, un drawer, una página), useHistrixSystemMenu da la lógica sin diseño:
import { useHistrixSystemMenu } from '@mundoit-lib/histrix-component-vue';
const system = useHistrixSystemMenu({ immediate: false, onClose: () => (open.value = false) });
// system.enabled: computed, capabilities.systemMenu del usuario de la sesión
// system.tree: ref con el árbol tal como llega; system.entries: computed aplanado ({ node, depth, header })
// system.loading: ref; system.load(): lo pide una vez por usuario; system.reload(): lo vuelve a pedir
// system.nodeUri(node), system.open(node), system.onItemClick(node), system.label(text)
enabled acepta un valor, una ref o un getter y pisa a capabilities.systemMenu. Con immediate: true (default) el menú se pide apenas está habilitado; con false, al llamar a load(). Al cambiar de usuario se descarta y se vuelve a pedir. Si el pedido falla o el menú viene vacío, el próximo load() lo reintenta.
Árboles (arbol)
Las pantallas type: "arbol" (p. ej. histrix/menu/menues.xml, el menú del sistema) las muestra HistrixTree sobre q-tree. Es una tabla autorreferenciada: cada fila cuelga del nodo cuyo schema.childField es igual a su schema.parentField (en el menú, hijo del padre = Id_menu del hijo), y la raíz es schema.values[parentField] (phpmen). El backend, filtrando por la raíz, ya devuelve las filas anidadas en children; una lista plana también se arma.
- Mostrar: el texto del nodo es el primer campo visible de texto que no es clave (
titulo) y al lado van los otros visibles (Orden: 10), con el estilo de celda que manda el backend. Buscador sin mayúsculas ni tildes (texto y valores de la fila, sin el campo padre), un botón que expande o contrae todo, y recargar/exportar/imprimir (en celular, en un menú ⋮ con texto, y sin el detalle al lado del texto); lo abierto se conserva al recargar. - Editar (con
PUTenresources): botón o doble click en el nodo; abreHistrixFormen un diálogo y al grabar recarga. - Agregar (con
POST):Nuevoagrega en la raíz y+en un nodo agrega adentro, con elparentFieldya cargado con elchildFielddel padre. Sólo se puede agregar dentro de un nodo que tienechildField(en el menú, un ítem que llama a un submenú). - Borrar (con
DELETE): pide confirmación (avisa si el nodo tiene elementos adentro) y manda las claves (esClave) del nodo.
can_insert/can_update/can_delete: false o readonly ocultan la acción aunque el recurso exista. La lógica pura está en src/core/tree.js.
Atajos de teclado
HistrixApp replica los atajos del ERP legacy. Atiende las teclas la app más interna que tenga el foco, y si esa no resuelve F9 o Esc, los pasa a la app que la contiene (por ejemplo, del grid de renglones al comprobante).
| Tecla | Acción |
|---|---|
F9 |
Procesa (igual que el botón Procesar: valida antes y sólo si el schema trae can_process). |
Esc |
Cierra el diálogo abierto más reciente. Si la app es inner y está dentro de un popup, emite closepopup. |
F2 |
Abre la ayuda (helpContainer) del campo que tiene el foco. |
F4 |
Limpia los filtros de búsqueda, si el foco está en un filtro. |
Enter |
Pasa al siguiente campo editable. En el último campo no graba, salvo que el form tenga enter-submits. |
Ctrl+K / ⌘K, / |
Abre el buscador de programas (HistrixMenuSearch). |
Al cargar, el form pone el foco en el campo que tenga autofocus en el schema o, si ninguno lo tiene, en el primero editable.
Dentro de un textarea, un contenteditable o un QEditor no se intercepta ninguna tecla.
<!-- Sin atajos ni foco automático (también en las apps y forms que contiene) -->
<HistrixApp path="/ventas/fac.xml" :keyboard="false" />
<!-- Enter en el último campo graba el form -->
<HistrixApp path="/ventas/fac.xml" enter-submits />
<!-- Otro atajo para el buscador, y abrirlo desde código -->
<HistrixMenuSearch ref="search" hotkey="alt+m" />
<!-- this.$refs.search.show() / this.$refs.search.focus() -->
Desarrollo
La librería vive en el monorepo mundoit-lib/monorepo (packages/histrix-component-vue). El playground está en apps/playground (Vite + Quasar 2) y consume esta librería y los tres plugins por workspace:*. Desde la raíz del monorepo:
pnpm install
cp apps/playground/.env.example apps/playground/.env # completar host/db/credenciales
pnpm turbo run build --filter histrix-dev-playground # buildea los plugins
pnpm --filter histrix-dev-playground dev
pnpm --filter @mundoit-lib/histrix-component-vue test
La documentación interna (arquitectura, componentes, servicios, plan de evolución) está en docs/.
Licencia
MIT (c) Luis M. Melgratti luis@mundoit.com.ar
Related Packages
Version History
| Version | Released | License |
|---|---|---|
| 0.10.0 | MIT | |
| 0.9.0 | MIT | |
| 0.8.2 | MIT | |
| 0.8.1 | MIT | |
| 0.8.0 | MIT | |
| 0.7.3 | MIT | |
| 0.7.2 | MIT | |
| 0.7.1 | MIT | |
| 0.7.0 | MIT | |
| 0.6.1 | MIT | |
| 0.6.0 | MIT | |
| 0.5.0 | MIT | |
| 0.4.2 | MIT | |
| 0.4.1 | MIT | |
| 0.4.0 | MIT | |
| 0.3.4 | MIT | |
| 0.3.3 | MIT | |
| 0.3.2 | MIT | |
| 0.3.1 | MIT | |
| 0.3.0 | MIT | |
| 0.2.0 | MIT | |
| 0.1.5 | MIT | |
| 0.1.4 | MIT | |
| 0.1.3 | MIT | |
| 0.1.2 | MIT | |
| 0.1.1 | MIT | |
| 0.1.0 | MIT | |
| 0.0.199 | MIT | |
| 0.0.198 | MIT | |
| 0.0.197 | MIT |
Showing the latest 30 of 34. See every release on npm