Construir un backoffice es mucho más que armar una tabla y algunos formularios. Implica decisiones sobre autenticación, gestión de estado, mocking para desarrollo, aislamiento de componentes, sistema de temas, soporte offline y una arquitectura que escale sin volverse un laberinto. Cuando todo eso se hace bien, el resultado es una herramienta que el equipo quiere usar y que los desarrolladores futuros pueden entender.
Este artículo documenta las decisiones técnicas detrás de Quasar Converse Backoffice, el panel de administración del sistema API Converse. Es un admin PWA construido con Quasar Framework v2, Vue 3 y TypeScript estricto, desarrollado progresivamente con la asistencia de Claude Code.
1. El Proyecto: Qué es y Para Qué Sirve
El backoffice gestiona las identidades de los bots conectados a API Converse: las configuraciones de cada canal de WhatsApp o Telegram que tiene activo el sistema. Desde este panel se pueden crear, editar y desactivar identidades, monitorear el estado de las conversaciones activas, y visualizar errores de webhooks en tiempo real.
El dashboard principal muestra cuatro métricas clave: identidades registradas, conversaciones activas, mensajes procesados hoy y webhooks con error. Un carrusel de recordatorios alerta sobre situaciones que requieren atención, y un componente de AI Insights ofrece sugerencias basadas en los patrones del sistema.
Sin el backoffice
Con el backoffice
2. Arquitectura "Screaming" y Separación de Capas
La estructura de carpetas sigue el patrón Screaming Architecture: cada feature "grita" su nombre desde el sistema de archivos. Cada página y componente vive en su propia carpeta nombrada por dominio, y esa carpeta contiene exactamente tres archivos: el template (index.vue), la lógica (setup.ts) y los estilos (styles.sass).
src/
pages/
home/ → index.vue + setup.ts + styles.sass
identities/ → index.vue + setup.ts + styles.sass + types.ts
settings/ → index.vue + setup.ts + styles.sass
components/
home/
statsCards/ → index.vue + setup.ts + styles.sass
reminders/ → index.vue + setup.ts + styles.sass
aiInsights/ → index.vue + setup.ts + styles.sass
identities/
table/ → index.vue + setup.ts + styles.sass
stores/
authentication/ → index.ts
theme/ → index.ts + colors.ts
layout/ → index.ts
services/
identities/ → index.ts
authentication/ → index.ts
mocks/
handlers/ → identities.ts
browser.ts → configuración MSW para browser El patrón setup.ts es clave. Este archivo exporta una función de composición que es el único dueño del estado reactivo y la lógica de negocio del componente. El index.vue solo llama a esa función y expone el resultado al template. Esto mantiene el template completamente declarativo y hace que la lógica sea testeable en aislamiento.
pages/
index.vue + setup.ts + styles.sass por feature
components/
Componentes reutilizables aislados con Storybook
stores/
Pinia stores: auth, theme, layout
services/
Capa HTTP con HttpClient de quasar-lib-common
La capa de services/ aísla todas las llamadas HTTP. Los servicios extienden una clase HttpClient de quasar-lib-common, una librería interna que provee un cliente Axios configurado con retry automático e interceptores de autenticación. Esto significa que ningún componente o store importa Axios directamente.
3. 7 Temas de Color + Dark/Light Mode
El sistema de temas es una de las funcionalidades más visibles del backoffice. El usuario puede elegir entre 7 paletas de color y alternar entre modo claro y oscuro, y ambas preferencias persisten entre sesiones. El cambio es instantáneo y no requiere recargar la página.
La implementación evita cargar todos los archivos CSS de temas al inicio. Solo el tema por defecto (Purple) está incluido en el bundle. Los demás se inyectan dinámicamente con un elemento <link> cuando el usuario los selecciona. Si el usuario no cambia el tema, nunca descarga ese CSS.
// stores/theme/index.ts
function setTheme(themeName: string) {
// 1. Inyectar CSS del tema si no está cargado
if (!document.getElementById(`theme-${themeName}`)) {
const link = document.createElement('link')
link.id = `theme-${themeName}`
link.rel = 'stylesheet'
link.href = `/themes/${themeName}.css`
document.head.appendChild(link)
}
// 2. Actualizar variable de Quasar
setCssVar('primary', THEME_COLORS[themeName].primary)
// 3. Persistir en localStorage
localStorage.setItem('app-theme', themeName)
currentTheme.value = themeName
} El modo dark/light detecta la preferencia del sistema operativo al iniciar la aplicación vía window.matchMedia('(prefers-color-scheme: dark)'). Un listener activo responde si el usuario cambia la preferencia del OS mientras la app está abierta. Para evitar el "flash" de tema incorrecto en la carga inicial, hay un script inline en el index.html que aplica el tema antes de que Vue hidrate el DOM.
4. Autenticación y Refresh de Token Automático
El sistema de autenticación utiliza un par de tokens: un access token de vida corta y un refresh token de vida larga. El access token se envía en cada request. Cuando expira y el servidor responde con 401, el interceptor de Axios entra en acción de forma transparente para el resto de la aplicación.
Request → Interceptor de salida (agrega Bearer token)
↓
API Server
↓
[200 OK] → Response al caller original
[401 Unauthorized]
↓
renewAccessToken() ← llama al endpoint de refresh
├── [OK] → Re-encola todos los requests fallidos → Replay
└── [FAIL] → Limpia el estado de auth → redirectToLogin() El detalle crítico está en el manejo de requests concurrentes. Si diez llamadas a la API fallan simultáneamente con 401, el interceptor solo ejecuta renewAccessToken() una vez. Las nueve restantes esperan en una cola y se reproducen con el nuevo token cuando el refresh termina. Esto evita race conditions y múltiples llamadas de refresh innecesarias.
Los guards de Vue Router verifican el estado de autenticación antes de cada transición de ruta. Si el access token expiró y el refresh también falla, el usuario es redirigido a login sin pasar por la ruta protegida.
5. Gestión de Identidades: Tabla Paginada
La página de identidades es la superficie CRUD principal del backoffice. Usa el componente q-table de Quasar con paginación server-side: cada cambio de página o tamaño de filas dispara un nuevo request a la API. El usuario puede elegir entre 15, 30 o 50 filas por página.
// components/identities/table/setup.ts
const columns = [
{ name: 'display', label: 'Identidad', field: 'display', align: 'left' },
{ name: 'status', label: 'Estado', field: 'status', align: 'left' },
{ name: 'active', label: 'Activa', field: 'active', align: 'center' },
{ name: 'created_at', label: 'Creada', field: 'created_at', align: 'left' },
{ name: 'actions', label: '', field: 'actions', align: 'center' }
]
// Paginación: el evento @request de q-table
// dispara fetchIdentities() con los nuevos parámetros
async function onRequest({ pagination }) {
currentPage.value = pagination.page
rowsPerPage.value = pagination.rowsPerPage
await fetchIdentities()
} La columna status se renderiza como un q-chip con variante de color: active es verde (positive), pending es amarillo (warning) e inactive es gris. La columna de acciones tiene dos botones por fila: editar y eliminar. El estado de carga se muestra con q-inner-loading superpuesto a la tabla, y el estado vacío tiene su propio slot con ícono y mensaje.
6. MSW + Storybook: Desarrollo Sin Backend
Una de las inversiones en DX que más retorno da es poder desarrollar sin depender de que el backend esté corriendo. Para eso, el proyecto integra Mock Service Worker (MSW) como capa de mocking a nivel de service worker: las llamadas HTTP salen del browser, MSW las intercepta y devuelve respuestas realistas.
Sin MSW
Con MSW
El handler de identidades genera 25 registros con datos realistas (nombre, status random entre active/pending/inactive, timestamps). La paginación está implementada correctamente: el handler calcula el slice correcto y devuelve el total real, por lo que la tabla pagina exactamente igual que con el backend real. Todo esto se activa con la variable de entorno MOCK_ENABLED=true.
Storybook v10 complementa MSW permitiendo desarrollar y revisar cada componente en aislamiento completo. Los componentes del dashboard —StatsCards, Reminders, AiInsights— tienen sus propias stories que se pueden parametrizar con distintos temas. Esto es especialmente útil para verificar que el sistema de temas se ve correcto en todos los componentes antes de tocar la aplicación completa.
7. PWA: Service Worker, Offline e Install Prompt
El backoffice es una Progressive Web App completa. Quasar integra Workbox en modo InjectManifest, lo que da control total sobre el service worker mientras el framework maneja el precaching automático de assets estáticos.
Hay una sutileza importante en la configuración: el service worker de Workbox y el service worker de MSW necesitan coexistir en desarrollo sin bloquearse mutuamente. La solución es configurar clientsClaim: false en el build de Workbox durante development, para que el SW de Workbox no reclame el control de los tabs existentes y MSW pueda seguir funcionando. En producción, clientsClaim: true garantiza que las actualizaciones del SW se apliquen de inmediato.
8. Flujo de Trabajo Asistido por IA
El proyecto fue construido progresivamente con la asistencia de Claude Code. La IA no reemplazó el juicio técnico: lo amplificó. Las decisiones de arquitectura, los patrones de diseño y la resolución de casos borde siguieron siendo responsabilidad del desarrollador. La IA aceleró la ejecución de lo que ya estaba decidido.
smart_toy Claude Code hizo
psychology Decisiones humanas
La ventaja más clara de trabajar con IA en un proyecto con patrones bien definidos —como el trío index.vue + setup.ts + styles.sass— es la consistencia. Una vez que el patrón está establecido, Claude Code lo aplica de forma idéntica en cada nuevo componente, eliminando la variación que inevitablemente aparece cuando un humano escribe el mismo boilerplate docena de veces.
9. Reflexiones y Próximos Pasos
Quasar hace trivial lo que en otros frameworks requiere semanas. El sistema de componentes es completo, la documentación es excelente, el modo PWA funciona desde el primer día y la integración con Vite hace que el DX sea moderno. Con la capa de mocking de MSW, el equipo puede construir y probar el frontend completo sin tocar el backend. Con Storybook, cada componente tiene su entorno de revisión independiente.
El backoffice está actualmente funcional con datos mockeados. Los próximos pasos son conectar la tabla de identidades al endpoint real de API Converse, agregar el formulario de creación, implementar el visor de logs de webhooks y la página de detalle de conversaciones. El sistema de temas y la autenticación ya están listos para producción.
La conclusión más importante: cuando se tiene una arquitectura clara, un framework con buen DX y asistencia de IA para la ejecución repetitiva, el tiempo de construcción de un backoffice completo se reduce drásticamente. Lo que antes era semanas de setup, hoy es días.
¿Necesitas un backoffice?
Construimos dashboards admin con Quasar, Vue 3 y TypeScript estricto. Con autenticación, temas personalizados, PWA y arquitectura escalable desde el día uno.
sendConversemos