Ingeniería Frontend

Dashboard Admin Backoffice con Quasar Framework y IA: De Cero a PWA en Producción

Cómo construimos un admin backoffice PWA con Quasar v2, Vue 3, TypeScript estricto, 7 temas visuales, MSW para mocking, Storybook para aislamiento de componentes y guards de autenticación — todo asistido con Claude Code

9 min de lectura
Marzo 2026
Backoffice
Desarrollado con Claude Code
Quasar v2.16
Vue 3.5
TypeScript 5.9
Pinia 3
Axios
MSW 2
Storybook 10
PWA
Vue i18n v11
SASS

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

Inspección manual de la API vía curl o Postman
Sin visibilidad de identidades activas ni su estado
Errores de webhook sin notificación ni historial
Configuración de canal requiere acceso directo al servidor

Con el backoffice

Tabla paginada de identidades con búsqueda, edición y eliminación
Stats en tiempo real: mensajes, conversaciones, webhooks
Carrusel de recordatorios y AI Insights en el dashboard
Sin acceso al servidor para gestionar configuraciones

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).

Estructura de carpetas del proyecto
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.

Purple
Pink
Crimson
Sand
Teal
Blue
Steel

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.

Fragmento del Pinia store de temas
// 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.

Flujo del interceptor de autenticació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.

Definición de columnas de la tabla
// 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

Backend corriendo localmente es obligatorio
Datos de prueba inconsistentes entre sesiones
Riesgo de mutar datos en la BD compartida de dev
Dependencia de red para cada cambio visual

Con MSW

Backend 100% opcional durante el desarrollo
25 identidades seeded con paginación real
Fixtures versionados junto al código fuente
Sin estado compartido con otros desarrolladores

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.

Características PWA del backoffice
Instalable en iOS y Android (Add to Home Screen)
Soporte offline: shell precacheada con Workbox InjectManifest
Service worker con control total sobre estrategias de caché
skipWaiting() + clientsClaim() para actualizaciones inmediatas en producción
manifest.json con íconos y display: standalone
Notificación de actualización disponible con opción de recargar

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.

Claude Code hizo

Scaffolding de la estructura screaming folder para cada feature
Tipos TypeScript inferidos desde shapes de respuesta de la API
Handlers MSW con seed data y lógica de paginación realista
Sistema de temas con lazy CSS injection y prevención de FOUC
Boilerplate de stories de Storybook para todos los componentes

Decisiones humanas

Arquitectura screaming folders vs feature-based convencional
Patrón Options API con setup.ts como función de composición pura
Diseño del interceptor de refresh token con cola de requests concurrentes
Estrategia de coexistencia PWA (Workbox) + MSW en desarrollo
Naming de CSS custom properties para los tokens de color del tema

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.

Conversemos