Claude Code y TypeScript: consejos prácticos para avanzar rápido y seguro
Mejora TypeScript con Claude Code usando strict, Zod, Union, genéricos, satisfies y pruebas de tipos.
Claude Code acelera mucho el trabajo con TypeScript, sobre todo en formularios, helpers de API y pruebas. El riesgo es igual de claro: si los límites de tipos son vagos, también acelera los errores. Para empezar con buen pie, define las reglas de tipos antes de pedir la funcionalidad.
En este artículo, strict significa que TypeScript rechaza código sospechoso; los tipos de dominio son reglas del negocio escritas como tipos; una Union discriminada modela estados con formas distintas; y la validación en runtime comprueba datos mientras el programa se ejecuta.
La idea no es escribir tipos complicados, sino dar a Claude Code menos margen para inventar atajos inseguros.
Dale primero un mapa de tipos
Antes del prompt largo, prepara un mapa pequeño: reglas del compilador, tipos de dominio, entradas externas, estados y pruebas de tipos. Ese mapa hace que el diff generado sea más fácil de revisar.
flowchart TD
A["Requisito"] --> B["tsconfig: reglas strict"]
B --> C["Tipos de dominio: Plan y Account"]
C --> D["Datos externos: unknown y validación"]
D --> E["Estado: Union discriminadas"]
E --> F["Pruebas de tipos: expectTypeOf / tsd"]
F --> G["Implementación y revisión con Claude Code"]
Usa documentación oficial como base: strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, Narrowing, Generics, Utility Types y la nota de satisfies.
Para validar en runtime, consulta también la documentación de Zod.
Lecturas relacionadas: TypeScript Utility Types, TypeScript Generics y Zod Validation.
Empieza con un tsconfig estricto
No basta con decir “hazlo en TypeScript”. Primero fija el contrato del compilador.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"]
}
Añade las restricciones al prompt.
Este repositorio usa TypeScript strict.
No introduzcas any. Trata las entradas externas como unknown y valídalas con Zod.
Cuando manejes Union en switch, añade una comprobación exhaustiva con never.
Después de implementar, ejecuta npx tsc --noEmit.
noUncheckedIndexedAccess mantiene visible la posibilidad de undefined al leer arrays y objetos.
Es estricto, pero detecta campos ausentes de API, listas vacías y traducciones incompletas antes de producción.
Caso 1: modelar planes SaaS como tipos de dominio
Los tipos de dominio convierten reglas del negocio en TypeScript. Planes, permisos, facturas y estados de publicación deberían existir antes de la UI.
export type Plan = "free" | "pro" | "enterprise";
export type Account = {
id: string;
email: string;
plan: Plan;
seats: number;
trialEndsAt: string | null;
};
export type CreateAccountInput = {
email: string;
plan: Exclude<Plan, "enterprise">;
seats?: number;
};
export type UpdateAccountInput = Partial<
Pick<Account, "email" | "plan" | "seats" | "trialEndsAt">
>;
Exclude elimina miembros de una Union.
Partial vuelve opcionales las propiedades; sirve para actualizaciones, pero puede relajar demasiado una entrada de creación.
Caso 2: validar datos de API desde unknown
Los tipos de TypeScript desaparecen en runtime.
APIs, formularios, cookies, localStorage, CSV y salidas de IA pueden venir rotos.
Recíbelos como unknown, valida y usa el resultado tipado.
npm install zod
import { z } from "zod";
const AccountSchema = z.object({
id: z.string().min(1),
email: z.string().email(),
plan: z.enum(["free", "pro", "enterprise"]),
seats: z.number().int().positive(),
trialEndsAt: z.string().datetime().nullable()
});
type Account = z.infer<typeof AccountSchema>;
export function parseAccountResponse(json: unknown): Account {
return AccountSchema.parse(json);
}
unknown significa que aún no has demostrado qué es el valor.
A diferencia de any, obliga a validar antes de leer propiedades.
Caso 3: cerrar el estado de pago con Union
Pagos, subidas, formularios y jobs son máquinas de estado.
Evita status: string; permite estados imposibles.
type PaymentResult =
| { status: "pending"; invoiceId: string }
| { status: "paid"; invoiceId: string; paidAt: string }
| { status: "failed"; invoiceId: string; reason: string };
export function renderPaymentMessage(result: PaymentResult): string {
switch (result.status) {
case "pending":
return `Invoice ${result.invoiceId} is waiting for payment.`;
case "paid":
return `Invoice ${result.invoiceId} was paid at ${result.paidAt}.`;
case "failed":
return `Invoice ${result.invoiceId} failed: ${result.reason}.`;
default: {
const exhaustive: never = result;
return exhaustive;
}
}
}
La rama never obliga a cubrir todos los estados válidos.
Si más tarde aparece refunded, TypeScript pedirá el nuevo caso.
Caso 4: genéricos y satisfies
Los genéricos permiten reutilizar helpers sin perder el tipo concreto de cada llamada.
export function groupBy<T, K extends PropertyKey>(
items: readonly T[],
getKey: (item: T) => K
): Partial<Record<K, T[]>> {
const grouped: Partial<Record<K, T[]>> = {};
for (const item of items) {
const key = getKey(item);
const bucket = grouped[key] ?? [];
bucket.push(item);
grouped[key] = bucket;
}
return grouped;
}
const accounts = [
{ id: "a1", plan: "free" },
{ id: "a2", plan: "pro" },
{ id: "a3", plan: "pro" }
] as const;
const byPlan = groupBy(accounts, (account) => account.plan);
const proAccounts = byPlan.pro ?? [];
console.log(proAccounts.map((account) => account.id));
Para objetos de configuración, satisfies suele ser mejor que una assertion amplia.
type ApiRoute = {
method: "GET" | "POST" | "PATCH" | "DELETE";
path: `/${string}`;
auth: boolean;
};
const routes = {
listAccounts: { method: "GET", path: "/accounts", auth: true },
createAccount: { method: "POST", path: "/accounts", auth: true },
healthCheck: { method: "GET", path: "/health", auth: false }
} as const satisfies Record<string, ApiRoute>;
type RouteName = keyof typeof routes;
export function getRoute(name: RouteName) {
return routes[name];
}
Añade pruebas de tipos
Los tipos públicos importantes también necesitan pruebas.
npm install -D vitest tsd
import { expectTypeOf, test } from "vitest";
type CreateAccountInput = {
email: string;
plan: "free" | "pro";
seats?: number;
};
test("CreateAccountInput keeps the public API narrow", () => {
expectTypeOf<CreateAccountInput>().toMatchTypeOf<{
email: string;
plan: "free" | "pro";
seats?: number;
}>();
});
Usos reales y errores comunes (Use case / Pitfall checklist)
| Caso de uso | Lo que fijan los tipos | Lo que genera Claude Code |
|---|---|---|
| Facturación SaaS | planes, facturas, permisos | ramas de UI, formularios, mensajes |
| Panel admin con API | schemas Zod, tipos de respuesta | fetch, tablas, estados de carga |
| CMS de artículos | slug, idioma, estado, imagen principal | borradores MDX, listados, validaciones |
| Formulario de contacto | schema de entrada, Union de resultado | UI, envío, cobertura Vitest |
| Error | Consecuencia | Solución |
|---|---|---|
Respuesta API como any | JSON roto compila | unknown y Zod |
status: string | estados imposibles | Union discriminada |
Muchos as User | se ocultan errores | schema, type guards, satisfies |
Partial<T> para crear | campos obligatorios pasan a opcionales | separar create y update |
| Sin pruebas de tipos | los tipos públicos se ensanchan | expectTypeOf o tsd |
En ClaudeCodeLab, Masa vio URLs de locales inválidos cuando lang estaba tipado como string.
Cerrar el tipo a una Union concreta hizo que las ediciones de Claude Code fueran mucho más predecibles.
Reglas y CTA
## TypeScript rules
- Use strict TypeScript.
- Do not introduce `any`. Use `unknown` at external boundaries.
- Prefer discriminated unions for states.
- Prefer `satisfies` over broad type assertions.
- Derive API types from Zod schemas when runtime data is involved.
- Add Vitest or tsd style type checks for exported helper types.
- Run `npx tsc --noEmit` before reporting completion.
Para proyectos personales, el catálogo de productos reúne plantillas y checklists para flujos con Claude Code.
Para equipos, la formación y consultoría de Claude Code puede cubrir migración a strict, límites con Zod, pruebas de tipos y reglas de CLAUDE.md en un repositorio real.
Resultado de la prueba
Probé este flujo en un proyecto TypeScript pequeño: cambié respuestas API de any a unknown con Zod y pedí a Claude Code que añadiera ramas Union y pruebas expectTypeOf.
El resultado útil fue detectar estados no manejados y accesos a propiedades inexistentes antes de la revisión.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Escalera de permisos de Claude Code para ampliar acceso sin perder control
Pasa de read-only a ediciones limitadas, comandos de prueba y checks de deploy con menos riesgo.
Claude Code Small PR Proof Pack: cambios pequeños que sí se pueden revisar
Un paquete de prueba para PRs de Claude Code: diff, checks, URL pública, CTA y rollback.
Gate de revisión antes del commit con Claude Code
Cómo revisar con Claude Code antes del commit: diff, build, URL pública, Gumroad, consultoría, tests y archivos ajenos.