Arquitectura del sistema
Visión de cómo está organizado Metabolics.app a nivel de sistema. Para setup local, ver DEVELOPMENT.md. Para deploy, ver DEPLOY.md.
Diagrama de alto nivel
┌──────────────────────────────────────────────────────────────┐
│ Browser / PWA / Mobile │
│ (Nutricionista o Paciente) │
└──────────────────────────┬───────────────────────────────────┘
│ HTTPS
▼
┌──────────────────────────────────────────────────────────────┐
│ Cloudflare (proxy + WAF) │
│ (DDoS, headers de seguridad, caché) │
└──────────────────────────┬───────────────────────────────────┘
│ HTTPS
▼
┌──────────────────────────────────────────────────────────────┐
│ Traefik (Coolify) │
│ Routing por Host + Let's Encrypt │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ SvelteKit (adapter-node) │
│ ┌─────────────────┬─────────────────┬──────────────────┐ │
│ │ (marketing) │ (app) │ (patient) │ │
│ │ landing/pricing│ nutricionista │ app paciente │ │
│ │ público │ role=nutritionist│ role=patient │ │
│ └────────┬────────┴────────┬────────┴────────┬─────────┘ │
│ │ │ │ │
│ ┌────────▼─────────────────▼──────────────────▼──────────┐ │
│ │ Capas compartidas │ │
│ │ • Hooks SSR (Supabase Auth) │ │
│ │ • Modules feature-based (9 módulos) │ │
│ │ • Services cross-cutting (auth, email) │ │
│ │ • UI (shadcn-svelte + Tailwind 4) │ │
│ │ • Utils (cn, format) │ │
│ └────────┬───────────────────────────────────────────────┘ │
└───────────┼─────────────────────────────────────────────────┘
│ drizzle-orm
▼
┌──────────────────────────────────────────────────────────────┐
│ PostgreSQL (Supabase local / managed) │
│ • 9 módulos mapeados 1:1 a tablas │
│ • auth.users (Supabase) + profiles (extensión) │
│ • RLS policies (Supabase, separadas de Drizzle) │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Servicios externos │
│ • Resend (email transaccional) │
│ • Umami (analytics self-hosted en subdomain) │
│ • Paddle (v2.0, no MVP) │
└──────────────────────────────────────────────────────────────┘
Las 3 interfaces (route groups)
SvelteKit route groups ((marketing), (app), (patient)) viven en la misma app, comparten Drizzle schema, pero tienen layouts y auth separados.
| Group | URL base | Auth | Layout |
|---|---|---|---|
(marketing) |
/, /pricing, /about |
No | Header + footer simple |
(app) |
/dashboard, /patients, /meal-plans, /agenda, /payments, /chat, /gamification, /public-page, /settings |
Sí, role=nutritionist o admin |
Sidebar desktop + topbar |
(patient) |
/home, /plan, /progress, /messages |
Sí, role=patient |
Topbar + bottom nav mobile-first |
El gating se hace en cada +layout.server.ts del group, no en routes individuales.
Los 9 módulos
Ver MODULES.md para el detalle. Resumen:
| # | Módulo | Carpeta | Tablas clave |
|---|---|---|---|
| 1 | Gestión de pacientes | modules/patients/ |
profiles, nutritionist_profiles, patients, anthropometric_measurements, consultations |
| 2 | Planificación alimentaria | modules/meal-plans/ |
foods, food_categories, recipes, recipe_ingredients, meal_plan_templates, meal_plans, meal_plan_items |
| 3 | Agenda y gestión de citas | modules/appointments/ |
appointments |
| 4 | Pagos y facturación | modules/payments/ |
payments |
| 5 | Comunicación profesional–paciente | modules/communication/ |
chat_messages |
| 6 | App del paciente (diario) | modules/patient-app/ |
patient_meal_logs |
| 7 | Gamificación | modules/gamification/ |
patient_streaks, patient_points, achievements, patient_achievements |
| 8 | Página web pública | modules/public-pages/ |
professional_pages |
| 9 | Planes y acceso | modules/subscriptions/ |
subscriptions |
Cada módulo es feature-based y autocontenido: components/, services/, schema.ts (Zod), types.ts, index.ts.
Flujo de datos
Lectura (queries)
Componente .svelte
→ $modules/<x>/services/<x>.service.ts
→ $server/db (cliente Drizzle)
→ PostgreSQL
← row data tipado
← DTO (mapeado a types.ts del módulo)
Los componentes nunca importan $server/db directamente. Siempre van por el service del módulo.
Escritura (mutations)
Form (sveltekit-superforms)
→ action en +page.server.ts
→ schema Zod (mismo del módulo) valida
→ service mutation
→ Drizzle INSERT/UPDATE/DELETE
→ redirect o return fail(...)
Auth
Request llega
→ hooks.server.ts (sequence: supabase, sessionHydrate)
→ locals.session, locals.user, locals.supabase
→ (app)/+layout.server.ts o (patient)/+layout.server.ts chequean rol
→ si OK: locals.user extendido a AppUser (con profile row)
Decisiones de arquitectura clave
Drizzle > Supabase client para queries
- Type-safety end-to-end: el schema es TypeScript, las queries heredan tipos.
- Menos acoplamiento: si migramos de Supabase, la lógica de queries no cambia.
- RLS no bloquea: como usamos Drizzle con conexión directa a Postgres, no necesitamos que Supabase haga cumplir RLS para queries — pero sí lo mantenemos para defensa en profundidad y para queries vía PostgREST si las necesitamos.
- Trade-off: perdemos algunas features del Supabase client (realtime subscriptions) — agregar
@supabase/supabase-jsdirecto si hace falta.
Supabase solo para auth + storage + admin
@supabase/ssrmaneja la sesión en cookies.- El service-role client (
supabase-admin.ts) solo se usa para bypass de RLS en admin tasks (ej. signup cleanup, admin operations). - Storage de archivos (PDFs generados, fotos) usa Supabase Storage en el futuro.
Single schema file
Decisión deliberada para el MVP. Con ~25 tablas es manejable en un solo archivo. Si crece a >50 tablas, evaluar split por dominio (no por tabla).
Modular feature-based
Cada módulo tiene su propio services/ y components/. Esto previene que src/lib/components/ se vuelva un cajón de sastre. Los componentes que se comparten entre módulos viven en src/lib/components/ui/ (shadcn-svelte).
Tailwind 4 CSS-based config
Sin tailwind.config.ts con JS — todo el theme va en CSS con @theme. Más simple, mejor integración con CSS variables que shadcn-svelte necesita para los colores.
adapter-node para Coolify
Mismo patrón que finanple.app. Output a build/, Traefik + Coolify manejan el routing, Let's Encrypt automático.
Lo que NO está en la arquitectura (todavía)
- No hay backend separado. Todo vive en SvelteKit endpoints (
+server.tsfiles o actions). Si crece mucho, considerar extraer a Hono o similar. - No hay queue system. Tareas async (envío de emails, generación de PDFs) se hacen sincrónicamente por ahora. Migrar a BullMQ/Cron cuando sea necesario.
- No hay caching layer. Si el dashboard se vuelve lento, agregar Redis (que ya viene con Coolify).
- No hay CDN para assets. Cloudflare proxy cachea el HTML, los assets se sirven desde Coolify. Si crecen, migrar a R2.
- No hay service worker / PWA manifest todavía. Para "app del paciente" en MVP, el responsive + "Add to home screen" es suficiente. Service worker viene cuando agreguemos offline.