Caso de estudio
Un recorrido de proceso y decisiones del CRM con el que operamos el estudio.
CRM de Vetd
Notas del proceso para construir un CRM tipado y server-first de extremo a extremo.
- Año
- 2026
- Duración
- En curso
- Rol
- Diseño, construcción, lanzamiento
- Categoría
- Productos SaaS
001
Resumen
Cómo lo abordamos.
Construimos el estudio sobre Next.js 16, server-first por defecto, tipado de extremo a extremo desde el esquema de Postgres hasta la UI. Este es un recorrido del stack, las decisiones que dieron forma al código y el proceso que usamos para pasar de un repo vacío a un sistema funcionando, sin acumular servicios que no entendíamos.
002
Restricciones
Las cuatro reglas que escribimos antes de que existiera el repo.
Cuatro restricciones moldearon cada decisión técnica. Las escribimos antes de que existiera el repo y las sacábamos cuando una decisión se ponía ruidosa.
01
Type safety de extremo a extremo, sin sincronización manual
Si el esquema de la base de datos cambia, TypeScript debe quejarse antes que el navegador. Eso descartó cualquier capa que aceptara payloads sin tipar y cualquier cliente que escribiera tipos a mano para respuestas del servidor. El esquema es el contrato.
02
Server-first por defecto
La mayoría de las páginas no deberían enviar JavaScript para contenido que no lo necesita. React Server Components tenía que ser el modo de renderizado por defecto, no una optimización opcional que rociaríamos después. Los componentes de cliente existen donde realmente vive la interacción.
03
Un solo código, un solo despliegue
El sitio público de marketing y la app protegida viven lado a lado. Los route groups manejan la división; un solo pipeline de build entrega ambos. Sin microservicios, sin repos divididos, sin paquetes compartidos que mantener sincronizados, hasta que algo se gane la división.
04
Mantenible por un solo desarrollador
Cada abstracción tenía que ganarse su peso. Sin librería de manejo de estado hasta que las server actions tocaran techo. Sin ORM cuando los tipos generados ya lo cubrían. Sin framework que no conociéramos lo suficiente como para depurar a las 2 AM.
“Si el esquema cambia, TypeScript se queja antes que el navegador.”
003
Decisiones
Cinco lugares donde elegimos la opción aburrida y bien tipada y seguimos adelante.
Cinco decisiones que moldearon el código. Cada una un lugar donde elegimos la opción aburrida y bien tipada y seguimos adelante.
01
Next.js 16 App Router, RSC-first
Los Server Components renderizan por defecto; los componentes de cliente son la excepción. Las páginas leen de la base de datos en su propio cuerpo vía funciones async. Sin librería de fetching, sin baile de hidratación para contenido estático. Las mutaciones pasan por server actions, que revalidan por tag o path y dejan que las rutas afectadas se re-rendericen solas.
02
Tres clientes Supabase, distintas puertas
Un cliente de navegador para componentes de cliente, un cliente de servidor enlazado a las cookies de next/headers para RSC y server actions, un cliente de middleware para refrescar cookies por request, y un cliente admin con service role marcado 'server-only' para que no se importe desde un archivo de cliente. Cada uno tiene un solo propósito; elegir el equivocado es un error de tipo, no un bug en runtime.
03
Tipos generados, nunca escritos a mano
El CLI de Supabase genera TypeScript desde el esquema vivo en lib/supabase/types.ts. Cada consulta, cada mutación, cada componente importa desde ese archivo. El cambio de nombre de una columna rompe el build en cada consumidor a la vez. Sin drift, sin fixtures viejos, sin adivinanzas.
04
shadcn + Tailwind v4 como única capa de UI
Incluimos los componentes de shadcn en el repo y los editamos directamente. Los tokens de Tailwind v4 viven en variables CSS (OKLCH), así el sistema de diseño es tematizable sin rebuilds. Sin CSS-in-JS, sin dependencia de librería de componentes, sin resolución de estilos en runtime. Un PR cambia el color primario en toda la app.
05
Vercel Fluid Compute en lugar de serverless
Fluid Compute mantiene instancias calientes entre requests, así que el auth en middleware y las páginas server-rendered no pagan los costos de cold-start como serverless clásico. Clientes Supabase frescos por request (sin singletons a nivel de módulo) significa que el aislamiento entre requests concurrentes se mantiene.
004
El stack
Compañías, frameworks y servicios detrás de la construcción.
Next.js
App Router, server actions, auth en middleware
React
RSC-first, Suspense, streaming
TypeScript
Strict mode, tipos generados de Supabase
Tailwind CSS
Tokens OKLCH, theming dark-canonical
shadcn/ui
Primitivos incluidos, editados en el repo
Supabase
Postgres, Auth, RLS, plumbing de cookies SSR
Stripe
Hosted Checkout + webhooks idempotentes
Cal.com
Webhooks de reservas firmados con HMAC
Vercel
Fluid Compute, hosting en el edge, optimización de imágenes
005
Lo que escribimos
Los artefactos de ingeniería que salieron de la construcción.
Middleware que refresca las sesiones de Supabase en cada request
Tres clientes Supabase de un solo propósito (browser, server, admin) con guardas 'server-only'
Tipos de Postgres generados, importados en cada consulta y formulario
Server actions para cada mutación, revalidadas por tag o path
Sistema de diseño shadcn + Tailwind compartido entre marketing y app
Route groups que dividen marketing público y CRM protegido en un solo build
Streaming con React 19 y boundaries de Suspense en los paths de datos lentos
Tokens OKLCH dark-canonical en variables CSS, tematizables sin rebuilds
006
En números
Una foto rápida de la forma del código.
Tablas principales
10
Migraciones entregadas
35+
Dependencias en runtime
< 40
Herramientas mensuales
$0
¿Tienes un proyecto así?
Agenda una llamada de descubrimiento de 20 minutos. Saldrás con un alcance concreto, un tiempo y un precio.