Volver al blog
T3 Stack + Claude Code: prototipar un dashboard en una tarde

T3 Stack + Claude Code: prototipar un dashboard en una tarde

Usamos create-t3-app y Claude Code para levantar un dashboard con autenticacion, API type-safe y componentes estilizados. Esto funciono, esto no, y esto aprendimos.

20 de abril de 2026 @devlabscl 8 min de lectura
t3-stack claude-code next-js trpc prisma tailwind prototyping

El experimento

Queriamos saber cuanto tardabamos en tener un dashboard funcional desde cero. No un mockup, no un wireframe en Figma — un dashboard real con autenticacion, datos desde una base de datos, rutas protegidas y componentes decentes.

Las herramientas: create-t3-app para el scaffold y Claude Code como asistente de desarrollo en terminal. El resultado fue un prototipo operativo en una tarde. Pero no todo salio limpio.

Por que T3 Stack

El T3 Stack combina Next.js, TypeScript, tRPC, Prisma, Tailwind CSS y NextAuth.js. La gracia no es que sean tecnologias nuevas — es que vienen integradas con type safety de punta a punta. Cambias un campo en el schema de Prisma y TypeScript te grita en el frontend si no actualizas el componente que lo consume.

Para prototipar rapido, eso es clave. No pierdes tiempo debuggeando tipos que no calzan entre backend y frontend porque tRPC los comparte automaticamente. El contrato entre cliente y servidor es el mismo tipo de TypeScript.

pnpm create t3-app@latest mi-dashboard --CI --trpc --tailwind --prisma --nextAuth

Con ese comando tienes un proyecto con autenticacion (NextAuth.js con Discord como provider por defecto), un ORM configurado (Prisma), una API type-safe (tRPC), y estilos listos (Tailwind). En menos de un minuto.

Claude Code como copiloto

Aca es donde el flujo cambia. En vez de armar todo a mano, le delegamos a Claude Code las tareas repetitivas y de scaffolding.

El schema de Prisma

Le pedimos que extendiera el schema para un dashboard de gestion de proyectos. Le dimos contexto minimo: “necesitamos proyectos, tareas y usuarios asignados”.

// Lo que genero Claude Code en ~30 segundos
model Project {
  id          String   @id @default(cuid())
  name        String
  description String?
  status      ProjectStatus @default(ACTIVE)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  tasks       Task[]
  createdBy   User     @relation(fields: [createdById], references: [id])
  createdById String
}

model Task {
  id          String     @id @default(cuid())
  title       String
  description String?
  status      TaskStatus @default(TODO)
  priority    Priority   @default(MEDIUM)
  project     Project    @relation(fields: [projectId], references: [id], onDelete: Cascade)
  projectId   String
  assignee    User?      @relation("assignedTasks", fields: [assigneeId], references: [id])
  assigneeId  String?
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt
}

enum ProjectStatus {
  ACTIVE
  ARCHIVED
  COMPLETED
}

enum TaskStatus {
  TODO
  IN_PROGRESS
  DONE
}

enum Priority {
  LOW
  MEDIUM
  HIGH
}

Bien hecho? Si, en general. Pero tuvimos que ajustar las relaciones con User porque el schema base de NextAuth ya tiene campos en el modelo User y Claude Code no siempre maneja bien la integracion con modelos preexistentes. Tocamos dos relaciones a mano. Tiempo perdido: 5 minutos. Tiempo ahorrado: 15.

Los routers de tRPC

Aca es donde Claude Code brilla. Le dijimos: “crea un router de tRPC para proyectos con CRUD completo, protegido con sesion”.

// src/server/api/routers/project.ts
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";

export const projectRouter = createTRPCRouter({
  getAll: protectedProcedure.query(async ({ ctx }) => {
    return ctx.db.project.findMany({
      where: { createdById: ctx.session.user.id },
      include: { tasks: true },
      orderBy: { createdAt: "desc" },
    });
  }),

  create: protectedProcedure
    .input(z.object({
      name: z.string().min(1),
      description: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.project.create({
        data: {
          ...input,
          createdById: ctx.session.user.id,
        },
      });
    }),

  // ... update, delete, getById
});

El patron protectedProcedure ya viene en el scaffold de T3. Claude Code lo uso correctamente porque leyo el contexto del proyecto. No invento un middleware de autenticacion nuevo — uso el que ya existia. Eso es lo que lo diferencia de copiar snippets de un chat.

Componentes del dashboard

Para el frontend le pedimos componentes con Tailwind y Lucide para iconos. El flujo fue:

  1. “Crea un componente ProjectCard que muestre nombre, estado, cantidad de tareas y un badge de prioridad”
  2. Claude Code lee los tipos generados por tRPC, usa los mismos tipos en el componente
  3. Genera el componente con Tailwind, importa los iconos de Lucide
// BIEN -- usa los tipos inferidos de tRPC
import type { RouterOutputs } from "~/utils/api";
type Project = RouterOutputs["project"]["getAll"][number];

// MAL -- define tipos a mano que se desincronicen
interface Project {
  id: string;
  name: string;
  // ... se desactualiza cuando cambias el schema
}

Ese detalle importa. Si el componente usa los tipos inferidos de tRPC, cuando cambias el schema de Prisma el compilador te avisa en el frontend. Si defines tipos a mano, pierdes la cadena de type safety que es la razon de usar T3 en primer lugar.

Lo que no funciono

Estilos inconsistentes

Claude Code genero componentes que funcionaban, pero cada uno tenia su propio criterio de spacing, colores y border radius. Sin un sistema de diseno definido o un archivo de configuracion de Tailwind claro, cada componente parecia de un proyecto diferente.

La leccion: antes de pedirle componentes, define tu tailwind.config.ts con los colores, espaciados y radios que vas a usar. O mejor, dale un componente de referencia y dile “sigue este estilo”. Sin esa guia, cada componente es una improvisacion.

Logica de negocio compleja

Para el CRUD basico, impecable. Pero cuando le pedimos logica mas compleja — “cuando una tarea se marca como DONE, actualiza el porcentaje de completitud del proyecto y notifica al creador” — el resultado necesito reescritura. No porque el codigo fallara, sino porque las decisiones de donde poner esa logica (router, service layer, event) las tiene que tomar un humano con contexto de negocio.

Migraciones de Prisma

Claude Code puede generar schemas, pero no siempre entiende el estado actual de las migraciones. Nos genero un cambio que requeria una migracion destructiva en un campo que ya tenia datos. En un prototipo da igual. En produccion habria sido un problema.

# Siempre revisar antes de aplicar
npx prisma migrate dev --name add-tasks --create-only
# Leer el SQL generado antes de confirmar

El flujo que nos funciono

Despues de la primera iteracion, ajustamos el proceso:

  1. Scaffold con create-t3-app — el boilerplate inicial, sin tocar nada
  2. Definir el schema de Prisma a mano — los modelos de datos son decisiones de arquitectura, no scaffolding
  3. Claude Code para los routers de tRPC — CRUD, validaciones con Zod, queries. Esto es repetitivo y lo hace bien
  4. Claude Code para componentes — pero con un componente de referencia como guia de estilo
  5. Logica de negocio a mano — las reglas del dominio las escribe el equipo
  6. Claude Code para tests — generar tests unitarios de los routers funciona sorprendentemente bien

El patron es claro: lo repetitivo y estructurado se delega. Lo que requiere criterio se hace a mano. Es la misma logica de nuestro post sobre Claude Code — excelente para implementar, no para decidir.

Los numeros

No medimos con cronometro, pero la estimacion honesta del tiempo es esta:

TareaSin Claude CodeCon Claude Code
Scaffold inicial5 min5 min
Schema Prisma (5 modelos)30 min10 min + 5 min ajustes
Routers tRPC (CRUD x3)90 min15 min + 10 min review
Componentes UI (8)120 min30 min + 20 min ajustes
Auth y rutas protegidas45 minIncluido en scaffold
Total~5 horas~1.5 horas

El asterisco: la version “con Claude Code” todavia necesita revision humana en cada paso. No es automatico. Pero la reduccion de tiempo es real, especialmente en el codigo estructurado y repetitivo.

Cuando tiene sentido este approach

Prototipar con T3 + Claude Code tiene sentido cuando:

  • Necesitas validar una idea rapido con un prototipo funcional, no un mockup
  • Tu equipo ya conoce TypeScript y el ecosistema React/Next.js
  • El dominio del proyecto se puede modelar con CRUD basico al principio
  • Quieres type safety desde el dia uno para no pagar deuda tecnica despues

No tiene sentido cuando:

  • El proyecto tiene logica de negocio compleja desde el inicio
  • Necesitas un stack distinto (Go, Python, etc.)
  • Tu equipo no esta comodo revisando y ajustando codigo generado por IA
  • El “prototipo” va directo a produccion sin pasar por refactoring

La velocidad del prototipo no reemplaza la calidad del producto. Pero tener algo funcional en una tarde cambia la conversacion con el equipo y con los stakeholders. Discutir sobre algo que se puede tocar es mas productivo que discutir sobre un documento.

T3 te da la estructura type-safe. Claude Code te da las manos extra. El criterio de que construir y como lo sigues poniendo tu.

Fuentes: