MVP de una aplicación web que convierte el derecho de protección de datos personales en algo accionable: el ciudadano describe un caso (crédito negado, seguro encarecido, CV filtrado, beneficio rechazado), adjunta evidencia, y un agente especializado en la Ley 19.628 + Ley 21.719 genera un reporte con sus derechos, fundamentos legales, plantillas de cartas y un PDF descargable.
- Next.js 15 (App Router) + TypeScript strict
- Prisma ORM + PostgreSQL (Supabase)
- TailwindCSS + componentes custom (estética SaaS oscura)
- JWT (HS256, jose) en cookie HttpOnly + verificación por correo
- Resend para correos transaccionales
- Anthropic SDK (
claude-opus-4-7) con prompt caching de la base de conocimiento - @react-pdf/renderer para PDFs sin headless browser
- Supabase Storage para adjuntos
- Crear un proyecto.
- Copiar las connection strings (Settings → Database):
- DATABASE_URL (pooler, puerto 6543)
- DIRECT_URL (direct, puerto 5432, para migrations)
- En Storage, crear un bucket privado llamado
cdt-files. - Copiar el
URLy laservice_rolekey (Settings → API).
- Crear API key.
- Para producción, verificar dominio. Para desarrollo, usar
onboarding@resend.devcomo sender.
- Crear API key en
console.anthropic.com.
git clone <repo> cdt
cd cdt
cp .env.example .env
# Edita .env con tus valores reales
npm install
npx prisma migrate dev --name init
npm run devAbrir http://localhost:3000.
| Comando | Descripción |
|---|---|
npm run dev |
Servidor de desarrollo |
npm run build |
Build de producción |
npm run start |
Servidor de producción |
npm run typecheck |
TypeScript sin emit |
npm run lint |
ESLint |
npm run db:migrate |
prisma migrate dev |
npm run db:deploy |
prisma migrate deploy (CI/prod) |
npm run db:studio |
UI visual de la BD |
app/
(auth)/ # login, register, verify
(dashboard)/ # protegido por middleware
dashboard/ # listado de solicitudes
solicitudes/ # nueva + detalle
api/
auth/{register,login,verify,logout,me}/
solicitudes/ # POST crea + dispara agente; GET lista
solicitudes/[id]/ # detalle
solicitudes/[id]/pdf # streaming del reporte
page.tsx # landing pública
lib/
prisma.ts # PrismaClient singleton
auth.ts # JWT (jose), bcrypt, cookies
rut.ts # validador chileno
validators.ts # zod schemas
email.ts # cliente Resend
storage.ts # Supabase Storage helpers
kb.ts # carga lazy de /knowledge-base/*.md
agent.ts # Anthropic SDK + prompt caching
pdf/ReporteDocument.tsx
components/
ui/ # Spinner, Alert
forms/ # LoginForm, RegisterForm, SolicitudForm
dashboard/ # EstadoBadge, LogoutButton, ReporteView
middleware.ts # JWT en cookie → x-user-id header
prisma/schema.prisma
knowledge-base/*.md # base legal — versionada en git
- Registro:
/register→POST /api/auth/register→ valida RUT chileno (DV) + Zod → crea usuario sin verificar → envía correo Resend con?token=…. - Verificación: link →
/verify→POST /api/auth/verify→ marcaemailVerifiedy limpia el token. - Login:
/login→POST /api/auth/login→ bcrypt compare → firma JWT (7d) → cookiecdt_sessionHttpOnly+Secure+SameSite=Lax. Bloquea login siemailVerifiedes null. - Middleware: protege
/dashboard/*,/solicitudes/*,/api/solicitudes/*. Inyectax-user-iden headers para handlers downstream. - Nueva solicitud:
/solicitudes/nueva→POST /api/solicitudes(multipart/form-data):- Valida glosa, MIME types, tamaño (10MB/archivo, 25MB total, 5 archivos máx).
- Crea
Solicitud { ACTIVA }. - Sube cada adjunto a Supabase Storage (
solicitudes/{userId}/{solicitudId}/{uuid}-{filename}) y registra en BD. - Marca
EN_PROCESO. - Llama
analyzeSolicitud(): Anthropic con system de dos bloques — preámbulo + KB completa concache_control: ephemeral(90% cache hit esperado en cargas reales). - Persiste reporte y marca
CERRADA. En error, vuelve aACTIVA.
- Detalle:
/solicitudes/[id]muestra glosa, adjuntos, reporte renderizado conreact-markdown. - PDF: botón "Descargar PDF" →
GET /api/solicitudes/[id]/pdf→renderToBuffercon@react-pdf/renderer→ stream al navegador.
El middleware corre en Edge runtime; jsonwebtoken no es compatible. jose sí.
Supabase expone un pooler PgBouncer (puerto 6543) optimizado para queries rápidas, pero prisma migrate necesita conexión directa con transacciones largas (puerto 5432). Por eso DATABASE_URL apunta al pooler y DIRECT_URL al direct.
SYSTEM_PREAMBLE y la concatenación de los .md van como dos bloques en system, con cache_control: { type: "ephemeral" } en el bloque grande. La primera request paga el costo de escritura (~1.25×); las siguientes leen al 0.1× durante 5 minutos. La KB es estable entre solicitudes → cache hit ~90%.
Los adjuntos se referencian por nombre/tipo en el prompt; el agente puede pedir más contexto en "Información que falta". v2: integrar pdf-parse o la Files API de Anthropic.
El usuario pidió "blob"; al escalar la decisión, se prefirió Storage para no inflar la BD ni los backups. Las columnas storagePath/pdfPath guardan rutas, no bytes.
Inmune a XSS para robo de sesión. SameSite=Lax mitiga CSRF en navegación normal.
Modelo más capaz disponible (1M context). Adaptive thinking deja que el modelo decida cuánto razonar; sin tuning manual de budget_tokens. Streaming con finalMessage() evita timeouts en respuestas largas.
- A01 Broken Access: middleware verifica JWT y
x-user-id; cada query usauserIddel header. Ownership verificado en GET/PDF antes de devolver. - A02 Crypto: bcrypt cost 12; JWT HS256 con secret ≥ 32 bytes; cookies
HttpOnly; Secure; SameSite=Lax. - A03 Injection: Prisma parametriza; Zod valida y sanitiza;
sanitizeFilenameelimina caracteres peligrosos antes del path en Storage. - A04 Insecure Design: timing-safe login (bcrypt dummy compare cuando el usuario no existe → no filtra existencia); token de verificación de 32 bytes con expiración de 24h.
- A05 Misconfig:
SUPABASE_SERVICE_ROLE_KEYyANTHROPIC_API_KEYsolo en archivos server. - A07 Auth Failures: login bloqueado sin
emailVerified; mensaje genérico en credenciales incorrectas. - Uploads: tamaño máximo, whitelist de MIME, sanitización de filename, paths bajo
solicitudes/{userId}/...(sin path traversal).
Pendiente para producción: rate limiting (Upstash o middleware con Redis) en
/api/auth/*y/api/solicitudes.
- Push del repo a GitHub.
- Vercel → Import Project → seleccionar repo.
- Pegar todas las variables de
.enven Environment Variables. - Build command:
next build(default). Elpostinstallcorreprisma generate. - Antes del primer deploy productivo:
DATABASE_URL=<DIRECT_URL_de_prod> npx prisma migrate deploy
- Resend: verificar dominio antes de prod (sin verificación, los correos solo van al owner de la API key).
- Supabase Storage: el bucket
cdt-filesdebe ser privado. El backend usaservice_role; el frontend nunca obtiene URLs públicas — siempre signed URLs de corta duración (helpergetSignedUrl()conexpiresIn: 300). - Verificar
APP_URLapunte al dominio productivo (los correos de verificación lo usan).
npm run dev/registercon RUT inválido → muestra error de DV./registercon datos válidos → "Revisa tu correo".- Click link de Resend →
/verify?token=…→ "Cuenta verificada". /login→ redirect a/dashboard./solicitudes/nuevacon glosa + 1 PDF → estado pasa porACTIVA → EN_PROCESO → CERRADA.- Detalle muestra reporte renderizado.
- "Descargar PDF" → archivo con título, glosa y reporte formateado.
- Logout →
/dashboardredirige a/login. - Registrar segundo usuario →
GET /api/solicitudes/{id-de-otro}→ 403.
npm run typecheck # TS strict, sin errores
npm run lint # ESLint
npx prisma validate # esquema válido
npm run build # build productivo- OAuth social / Clave Única
- Streaming SSE de la respuesta del agente (en lugar de bloqueo síncrono)
- Files API de Anthropic para enviar PDFs nativos al modelo
- Panel admin para editar la KB desde UI
- Rate limiting con Upstash
- Notificaciones push cuando termine un análisis largo
- i18n (por ahora todo en español ES-CL)