2DAM — Proyecto Grupal — 2026
SPA Vanilla JS · Vite · Sin frameworks
El frontend que Dolibarr nunca tuvo
El ERP funciona, pero su interfaz tiene 20 años. El objetivo: dar una experiencia moderna sin tocar el backend del ERP.
Antes
Ahora
Una SPA no necesita un framework. Necesita un router, componentes y control del DOM. El proyecto tiene ~7.700 líneas de JS puro sin contar dependencias.
Control total
Cada elemento del DOM lo creamos nosotros. Sin magia, sin diffing invisible.
Dependencias mínimas
Solo 3: Chart.js (gráficas), @huggingface/transformers (voz) y node-forge (cifrado RSA).
Aprendizaje real
Entendemos qué hace el navegador. Un framework encima de esto es trivial; al revés, no tanto.
Una página = una función
render*() devuelve un HTMLElement.
Estado local en closure.
El router la monta y destruye.
Sin estado global
No hay store ni contexto.
Cada página habla con el BFF
de forma independiente.
El router escucha el hash de la URL. Navegar a #invoices desmonta la página actual y monta la nueva.
// router.js — lo esencial
window.addEventListener('hashchange', () => {
const route = location.hash.replace('#', '') || 'dashboard';
const page = getPageByRoute(route);
currentContainer?.cleanup?.(); // cancela timers e intervals
currentContainer = page.render(); // monta la nueva página
appRoot.replaceChildren(currentContainer);
});
cleanup() evita memory leaks. Si una página tiene un setInterval (ej.: countdown del JWT en Configuración), se cancela al navegar.
Con import.meta.glob de Vite, cualquier *Page.js en /pages se registra solo en el sidebar.
// pagesRegistry.js
const modules = import.meta.glob(
'./*Page.js', { eager: true }
);
// filtra las declaradas en pagesConfig
// el resto se auto-registran con ruta
// derivada del nombre del archivo
Páginas explícitas
En pagesConfig.js: ruta, nombre, icono, voice patterns.
Páginas nuevas
Crear MiPaginaPage.js → aparece en el sidebar con ruta #mi-pagina automáticamente.
Dashboard
KPIs, gráfica trimestral, últimos movimientos, tabla con colores.
Facturas
CRUD completo. Plantillas, líneas editables, pagos, cambio de estado.
Fact. Proveedores
Gestión de compras. Total por línea en tiempo real.
Terceros
Clientes y proveedores con rol. Contactos anidados.
Banco
Cuentas bancarias y movimientos. Alta con selector de país.
Configuración
Tema, JWT countdown, webhook Teams/Slack, VeriFactu.
El asistente de voz ejecuta Whisper base (Xenova/whisper-base) directamente en el navegador con @huggingface/transformers. Sin API key, sin servidor de voz.
"Ir a facturas", "abrir banco", "dashboard" → el router navega sin tocar el teclado. Los patrones de voz se definen en pagesConfig.js junto a la ruta.
Flujo de tres pasos que ocurre en el navegador antes de llegar al servidor.
1 — Certificado
El usuario selecciona su .p12. La FileReader API lo convierte a base64 en el navegador. Nunca toca el disco del servidor.
2 — Contraseña cifrada
El BFF expone una clave pública RSA. El frontend cifra la contraseña con ella. Nunca viaja en claro.
3 — Registro
El BFF reenvía al microservicio Go, que valida el .p12, lo almacena y devuelve un token de sesión.
Todo el tráfico HTTP pasa por apiClient.js. JWT automático, 401 redirige al login, errores normalizados.
// services/apiClient.js
export async function apiGet(endpoint) {
const token = localStorage.getItem('token');
const res = await fetch(BASE_URL + endpoint, {
headers: { Authorization: `Bearer ${token}` }
});
if (res.status === 401) { navigate('login'); return; }
if (!res.ok) throw new Error(await res.text());
return res.json();
}
Las páginas nunca manejan tokens ni status HTTP. Solo llaman a apiGet(), apiPost()… y reciben datos o un error.
Hash routing sin servidor
Archivos estáticos. Funciona en cualquier CDN sin configurar rutas.
Cleanup en cada página
container.cleanup() cancela intervals al navegar. Sin memory leaks.
Vite + import.meta.glob
HMR en desarrollo. Cada página es un chunk en producción. Registro dinámico.
FileReader + RSA en cliente
Certificado y contraseña procesados en el navegador. Nunca viajan en claro.
doli-front · Vanilla JS · Vite · 2026