Architecture Decision Records
El "por qué" detrás de cada decisión técnica crítica
ADR-001
PostgreSQL + Prisma como base de datos primaria
Contexto
NutriTrack AI gestiona datos mixtos: un núcleo relacional fuerte (clínicas → nutricionistas → pacientes → planes de dieta) y datos semi-estructurados (logs nutricionales diarios con campos variables por tipo de alimento). El equipo es de 4 personas con capacidad de ops limitada. La regulación sanitaria exige auditoría e integridad transaccional de los cambios de estado de los planes.
Decisión
Usar PostgreSQL (Neon como managed hosting) con Prisma ORM para acceso tipado y gestión de migraciones. Los logs nutricionales se almacenan como columnas
jsonb en PostgreSQL, eliminando la necesidad de una base de datos documental separada.
Alternativas evaluadas
| Opción | Pros | Contras | Veredicto |
|---|---|---|---|
PostgreSQL + Prisma |
ACID, jsonb nativo, full-text search, ecosistema maduro, Neon serverless | Requiere schema migrations cuidadosas | ✓ Elegida |
MongoDB Atlas |
Schema flexible, fácil onboarding inicial | Datos relacionales complejos sin JOINs nativos; transacciones multi-documento costosas | ✗ Rechazada |
DynamoDB |
Escala automática, sin servidor | Queries relacionales imposibles sin GSIs complejos; vendor lock-in AWS; coste impredecible | ✗ Rechazada |
Consecuencias
Prisma genera tipos TypeScript desde el schema → cero errores de tipo en queries
Full-text search nativo para buscar alimentos en logs sin Elasticsearch adicional
Neon ofrece branching de base de datos para PRs de staging gratuitos
Migraciones Prisma deben ejecutarse en CI/CD antes de desplegar pods nuevos
ADR-002
SDK Anthropic nativo para agentes IA (sin frameworks)
Contexto
Los agentes de NutriTrack analizan historiales nutricionales de 7 a 90 días y generan recomendaciones personalizadas. La regulación sanitaria exige que cada decisión del agente quede auditada con: prompt exacto enviado, respuesta recibida, herramientas invocadas y timestamp. Necesitamos control total sobre el flujo de llamadas para cumplir con GDPR y normativa de software médico clase IIa (MDR).
Decisión
Usar el SDK de Anthropic directamente (claude-sonnet-4-5) sin frameworks de agentes intermedios. El loop agentico se implementa manualmente con un interceptor de auditoría que persiste cada llamada en la tabla
agent_audit_logs antes y después de ejecutar herramientas. Esto nos da trazabilidad completa y evita cajas negras.
Alternativas evaluadas
| Opción | Pros | Contras | Veredicto |
|---|---|---|---|
SDK Anthropic nativo |
Control total, auditoría precisa, sin abstracciones opacas, menor dependencia externa | Más código de plumbing; loop agentico manual | ✓ Elegida |
LangChain / LangGraph |
Abstracciones listas, comunidad grande | Versiones inestables, breaking changes frecuentes; difícil auditar el flujo interno; overhead de abstracción innecesario | ✗ Rechazada |
CrewAI |
Orquestación multi-agente declarativa | Demasiada "magia" para cumplimiento regulatorio; dificulta auditoría de herramientas; sobre-ingeniería para nuestro caso | ✗ Rechazada |
Consecuencias
Cada invocación de herramienta se persiste antes y después → trazabilidad completa para auditorías
Sin dependencia de frameworks de terceros inestables → upgrades controlados
El loop agentico (~200 líneas) debe mantenerse en equipo; documentar con gotchas inline
Ver ADR-001: los logs de auditoría van en jsonb en PostgreSQL sin tabla aparte
ADR-003
Supabase Auth + RBAC multi-tenant para clínicas
Contexto
El modelo de datos es multi-tenant: cada clínica es un tenant aislado, con nutricionistas y pacientes como sub-usuarios. Los roles son:
admin_clinica (gestiona usuarios, ve todos los planes), nutricionista (crea/edita planes propios), paciente (solo lectura de su plan). La separación de datos entre clínicas es crítica por GDPR — una fuga entre tenants sería devastadora.
Decisión
Usar Supabase Auth para autenticación (ya tenemos Supabase en stack como alternativa a Neon para algunas queries). RBAC implementado con
clinic_id en JWT custom claims y Row Level Security (RLS) de PostgreSQL como capa de enforcement — ni siquiera el código de aplicación puede acceder a datos de otro tenant aunque tenga un bug. Los roles se comprueban a nivel de RLS, no solo en el middleware.
Alternativas evaluadas
| Opción | Pros | Contras | Veredicto |
|---|---|---|---|
Supabase Auth + RLS |
RLS nativo en PostgreSQL, ya en stack, tenant isolation en base de datos, open source | RLS puede ser compleja de debuggear en queries anidadas | ✓ Elegida |
Auth0 |
Enterprise-grade, UI de gestión excelente, muchos integraciones | Coste elevado a escala; los roles/permisos a nivel de datos requieren lógica adicional; vendor externo para datos sensibles de salud | ✗ Rechazada |
JWT propio |
Control total, sin dependencias | Reinventar la rueda en criptografía/sesiones es un riesgo de seguridad inaceptable; coste de desarrollo + audit | ✗ Rechazada |
Consecuencias
Tenant isolation garantizado a nivel de base de datos, no solo middleware → defensa en profundidad
custom claims JWT incluyen
clinic_id y role → propagación automática a RLS policiesLas políticas RLS deben testearse con usuarios de diferentes tenants en cada PR que toque tablas compartidas
Supabase ofrece Audit Logs integrado para cambios de auth → cumple requisito MDR
Gotchas documentados inline
Trampas conocidas que deben quedar en el código para que agentes IA y nuevos devs no repitan errores
/**
* IMPORTANT: initializeAgentAuditLog() DEBE llamarse ANTES de ejecutar cualquier herramienta.
* Si se llama después, la entrada de auditoría queda sin tool_calls y viola el requisito MDR.
* El agente puede parecer que funciona correctamente pero los logs serán incompletos.
*
* Correcto: await audit.start(callId); → executeTools(); → await audit.end(callId, result);
* Incorrecto: executeTools(); → await audit.log(callId, everything); ← NO
*
* Ver ADR-002 para el razonamiento completo.
*/
export async function runAgentLoop(
messages: MessageParam[],
auditCtx: AuditContext
): Promise<AgentResult> {
const callId = generateCallId();
// SIEMPRE inicializar audit antes de la llamada al modelo
await audit.start(callId, { messages, auditCtx });
const response = await anthropic.messages.create({ ... });
// ...
}
/**
* GOTCHA — RLS y service_role key:
* Las queries que usan supabaseAdmin (service_role) BYPASSAN las políticas RLS.
* Usar supabaseAdmin SOLO para operaciones de sistema (cron jobs, webhooks internos).
* Para todas las queries de usuario, usar supabaseClient con el JWT del usuario.
*
* Si usas supabaseAdmin en un endpoint de usuario, los datos de TODOS los tenants
* serán visibles → fuga de datos entre clínicas.
*
* Ver ADR-003 para el diseño de tenant isolation.
*/
// ✅ CORRECTO — usuario autenticado
const { data } = await supabaseClient(userJwt)
.from('meal_plans')
.select('*'); // RLS filtra por clinic_id automáticamente
// ❌ INCORRECTO para endpoints de usuario
const { data } = await supabaseAdmin
.from('meal_plans')
.select('*'); // Devuelve datos de TODAS las clínicas
README — Sección Arquitectura
Fragmento listo para copiar en el README del repo
NutriTrack AI
Plataforma SaaS B2B para clínicas de nutrición. Analiza historiales nutricionales con agentes IA y genera planes de dieta personalizados con trazabilidad completa para cumplimiento sanitario (MDR clase IIa).
Quick Start
git clone https://github.com/nutritrack-ai/app
npm install
cp .env.example .env # rellenar DATABASE_URL, ANTHROPIC_API_KEY, SUPABASE_*
npx prisma migrate dev
npm run dev # http://localhost:3000
Comandos
| Comando | Descripción |
|---|---|
npm run dev | Servidor de desarrollo (Next.js 15 + Turbopack) |
npm test | Tests unitarios + integración (Vitest) |
npm run test:e2e | Tests Playwright (requiere DB de test) |
npm run build | Build de producción |
npx prisma migrate dev | Aplicar migraciones de base de datos |
npm run lint | ESLint + TypeScript type-check |
Arquitectura
┌──────────────────────────────────────────────────────┐
│ NUTRITRACK AI │
└──────────────────────────────────────────────────────┘
Frontend Next.js 15 (App Router) · TypeScript · Tailwind
Auth Supabase Auth + JWT custom claims + RLS (→ ADR-003)
API Next.js Route Handlers · tRPC v11 para tipos E2E
Agentes IA SDK Anthropic nativo · claude-sonnet-4-5 (→ ADR-002)
Base de datos PostgreSQL (Neon) · Prisma ORM · jsonb para logs (→ ADR-001)
Almacenamiento Supabase Storage · PDFs de planes de dieta
Infra Vercel (frontend + API) · cron jobs en Vercel Functions
ADRs en docs/decisions/ — leer antes de cambios arquitectónicos
Changelog
Historial de cambios user-facing siguiendo Keep a Changelog
v1.0.0 — Launch
2026-06-20 (planned)
Added
- Análisis nutricional con agente IA: historial de 7, 30 y 90 días con recomendaciones personalizadas #88
- Panel multi-tenant para clínicas: gestión de nutricionistas y pacientes con RBAC completo #91
- Generación automática de planes de dieta PDF firmados digitalmente #95
- Dashboard de logs de auditoría del agente IA (cumplimiento MDR) exportables a CSV #98
- Integración con bases de datos de alimentos: BEDCA (España), Open Food Facts #101
Fixed
- Duplicación de registros de peso al hacer sync rápido desde app móvil #103
- El agente fallaba silenciosamente cuando el historial tenía gaps de más de 14 días #107
Security
- RLS policies auditadas por tercero — confirmada separación completa entre tenants #112
- Headers HIPAA-aligned añadidos a todos los endpoints de datos de pacientes #114
v0.9.2 — Beta
2026-05-28
Changed
- El agente ahora usa claude-sonnet-4-5 (era claude-3-haiku) → recomendaciones +40% más específicas según feedback beta #79
- Paginación de historial nutricional: 90 días por defecto (era 30) para contexto completo del agente #82
Checklist de verificación
Estado actual de la documentación antes del lanzamiento v1.0
ADRs para decisiones críticas — PostgreSQL, agentes IA y auth documentados con contexto, alternativas y consecuencias
ADR
README cubre Quick Start y comandos — Instrucciones completas desde clone hasta dev
README
Sección de arquitectura en README — Stack completo y links a ADRs
README
Gotchas críticos documentados inline — Loop agentico y RLS con service_role key
Código
APIs internas con JSDoc —
runAgentLoop, createMealPlan, getPatientHistory documentadas con @param, @returns, @throwsAPI
Sin código comentado en main — Git history para referencias históricas
Código
CLAUDE.md actualizado — Convenciones del proyecto para agentes IA (RLS, audit log, tenant context)
Agentes
Changelog mantenido — v0.9.x y v1.0.0 con PR refs y categorías semánticas
Changelog
OpenAPI spec para REST hooks externos — Webhooks de eventos (plan_updated, goal_reached) pendientes de documentar antes de abrir acceso a integradores
Pendiente