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
RG
Route groups: sitio de marketing y app protegida, un solo código, un solo despliegue.

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.

TS
Pipeline de tipos: migraciones de Postgres → CLI de Supabase → TS generado → importado en todas partes.

004

El stack

Compañías, frameworks y servicios detrás de la construcción.

Next.js

Next.js

App Router, server actions, auth en middleware

React

React

RSC-first, Suspense, streaming

TypeScript

TypeScript

Strict mode, tipos generados de Supabase

Tailwind CSS

Tailwind CSS

Tokens OKLCH, theming dark-canonical

shadcn/ui

shadcn/ui

Primitivos incluidos, editados en el repo

Supabase

Supabase

Postgres, Auth, RLS, plumbing de cookies SSR

Stripe

Stripe

Hosted Checkout + webhooks idempotentes

Cal.comCal

Cal.com

Webhooks de reservas firmados con HMAC

Vercel

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.

01

Middleware que refresca las sesiones de Supabase en cada request

02

Tres clientes Supabase de un solo propósito (browser, server, admin) con guardas 'server-only'

03

Tipos de Postgres generados, importados en cada consulta y formulario

04

Server actions para cada mutación, revalidadas por tag o path

05

Sistema de diseño shadcn + Tailwind compartido entre marketing y app

06

Route groups que dividen marketing público y CRM protegido en un solo build

07

Streaming con React 19 y boundaries de Suspense en los paths de datos lentos

08

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.