Frontend¶
SPA en React 19 + TypeScript construida con Vite 7, estilos con TailwindCSS v3 y rutas con React Router v7. Paquetes con pnpm.
cd frontend
pnpm install
pnpm dev # Vite con HMR en :5173
pnpm build # tsc -b && vite build
pnpm lint # ESLint
pnpm test # Vitest
Estructura de src/¶
frontend/src/
├── App.tsx # proveedores + rutas
├── main.tsx # punto de entrada + Sentry
├── index.css # directivas Tailwind + @layer components
├── pages/ # una página por ruta
├── components/ # UI reutilizable (+ admin/, annotations/, mfa/, subscription/)
├── contexts/ # AuthContext, ThemeContext, PresentationContext
├── services/ # api.ts (Axios + ApiService), streamConsumer.ts (SSE del chat)
├── hooks/ # useIsMobile, useTouchZoomPan, useDialogA11y, …
├── types/ # tipos por dominio (ai, auth, chat, project, sharing, …)
├── utils/ # render, exportación, pizarrón, sketch → Mermaid, … (+ __tests__/)
└── i18n/ # config.ts + locales/es.json, en.json
Rutas¶
Definidas en frontend/src/App.tsx.
flowchart TD
T["ThemeProvider"] --> SH["/shared/:token<br/>sin AuthProvider"]
T --> AP["AuthProvider"]
AP --> IG["InstallationGuard"]
IG --> PUB["Rutas públicas<br/>login, registro, setup…"]
IG --> PR["PrivateRoute"]
PR --> SL["SidebarLayout<br/>dashboard, editor, ajustes…"]
SL --> ADM["Páginas admin<br/>comprueban role = admin"]
| Ruta | Página | Acceso |
|---|---|---|
/shared/:token |
SharedDiagramPage |
Pública, fuera de AuthProvider |
/setup |
InstallationWizardPage |
Pública (asistente de instalación) |
/login, /register |
LoginPage, RegisterPage |
Pública |
/forgot-password, /reset-password |
ForgotPasswordPage, ResetPasswordPage |
Pública |
/mfa-verify |
MfaVerifyPage |
Pública (segundo paso del login) |
/oauth/callback |
OAuthCallbackPage |
Pública (retorno de Google) |
/onboarding |
OnboardingWizardPage |
Sesión iniciada |
/dashboard |
DashboardPage |
Sesión iniciada |
/projects-list |
ProjectsPage |
Sesión iniciada |
/projects/:projectId |
DiagramEditorPage |
Sesión iniciada |
/projects/:projectId/diagrams/:diagramId |
DiagramEditorPage |
Sesión iniciada |
/profile, /settings |
ProfilePage, SettingsPage |
Sesión iniciada |
/subscription |
SubscriptionPage |
Sesión iniciada |
/about |
AboutPage |
Sesión iniciada |
/admin, /admin/users, /admin/plans, /integrations |
AdminPage, UserManagementPage, PlansPage, IntegrationsPage |
Administrador |
/ |
redirige a /dashboard |
— |
* |
NotFoundPage |
— |
InstallationGuardenvía a/setupmientras no exista un administrador.PrivateRouteredirige a/loginsin sesión.- Las páginas de administración redirigen a
/dashboardsiuser.role !== 'admin'. Es solo navegación: la autorización real la hace el backend. - Las rutas del editor se envuelven además en
PresentationProvider(modo presentación).
Estado global¶
| Contexto | Qué guarda |
|---|---|
AuthContext |
Usuario y JWT (en localStorage, clave token); cierre de sesión automático ante un 401 |
ThemeContext |
Tema claro/oscuro, persistido en localStorage bajo theme |
PresentationContext |
Si el editor está en modo presentación |
Capa de API¶
services/api.ts centraliza todas las llamadas en la clase ApiService (exportada como apiService):
- Instancia de Axios con
baseURL = API_URL. - Interceptor de petición: añade
Authorization: Bearer <token>yAccept-Languagecon el idioma de la interfaz (los errores y la IA responden en ese idioma). - Interceptor de respuesta: ante
401borratokenyusery vuelve al login. - El streaming del chat se consume con
fetch+ SSE enservices/streamConsumer.ts.
API_URL, SENTRY_DSN y APP_ENV salen de utils/runtimeConfig.ts con esta precedencia: window.__DH_CONFIG__ (inyectado en /config.js al arrancar la imagen de producción), luego las variables VITE_* de build y por último valores de localhost. Así la misma imagen sirve para cualquier dominio.
Internacionalización¶
- i18next + react-i18next, configurado en
src/i18n/config.ts. Español por defecto (fallbackLng: 'es'), inglés disponible; el idioma se guarda enlocalStoragebajolanguage. - Todo texto visible se escribe con
t('clave')y la clave existe enes.jsonyen.json. El testi18nParity.test.tsfalla si los archivos no tienen exactamente las mismas claves.
Design system (resumen)¶
La referencia obligatoria es DESIGN.md. Lo esencial:
- Sin librerías de UI ni de íconos: solo TailwindCSS y componentes propios; íconos SVG inline estilo Heroicons.
- Identidad morada:
bg-purple-600/hover:bg-purple-700; botón principal.btn-glass. Peligro enred-600, éxito engreen-600. - Modo oscuro en todo:
darkMode: 'class'y variantesdark:en cada color. - Sin colores en
style: solo clases de Tailwind; CSS propio únicamente enindex.cssbajo@layer components. - Accesibilidad mínima: elementos
<button>/<a>semánticos,aria-labelen controles sin texto, foco visiblefocus:ring-2 focus:ring-purple-500, contraste AA. - Páginas nuevas en
pages/+ ruta enApp.tsx; UI reutilizable encomponents/.