Le pedí a Claude Code que "parseara la respuesta de la API" y recibí esto:
const data = response.data as any
const user = data.user as any
const name = (data.user as any).name as string
Tres casteos as any. Toda la información de tipos perdida. La verificación de TypeScript anulada por completo.
Este patrón es una fábrica de bugs en proyectos reales. Un campo que la API renombra de userName a displayName, pero el casteo as any silencia la discrepancia. Sin error del compilador, sin error en runtime — hasta que un usuario reporta un nombre en blanco en su perfil.
Por defecto, Claude Code prioriza "código que funciona" sobre "código type-safe." Cambiar eso requiere configuración explícita — del tipo que se establece una vez en CLAUDE.md en lugar de pelear en cada prompt.
Esto es lo que aprenderás en este artículo:
- Por qué se genera
as anyy cómo detenerlo - Cómo aplicar la configuración strict de
tsconfig.jsona través de CLAUDE.md - El flujo de trabajo para que Claude Code diseñe esquemas Zod
- Un bucle de retroalimentación que hace que Claude auto-corrija errores de tipo
- Una plantilla de CLAUDE.md para generación de código consistentemente type-safe
¿Por Qué Claude Code Abusa de any?
Hay tres razones principales por las que Claude Code recurre a any:
1. Intenta completar código cuando falta información de tipos
Sin tipo de respuesta de API definido, tipados de librería incompletos — cuando Claude no conoce el tipo, lo rellena con any para que el código compile. Terminar la funcionalidad toma prioridad sobre la corrección de tipos.
2. Tu prompt no menciona requisitos de tipos
"Parsea esto" y "transforma esto" no dicen nada sobre tipos. Claude se enfoca en entregar la funcionalidad solicitada. La rigurosidad de tipos queda como una idea secundaria.
3. La configuración de tipos de tu proyecto no se está comunicando
Si Claude Code no sabe sobre tu tsconfig.json, no sabe que estás ejecutando strict mode. Genera código que pasaría una verificación de tipos permisiva.
La solución tiene tres partes: comunicar la configuración del proyecto por adelantado, hacer que Claude defina tipos antes de escribir lógica, y requerir verificación de tipos como paso obligatorio. Una vez configurado, el conteo de as any en la salida de Claude Code se reduce drásticamente.
¿Cómo Aplicas la Configuración Strict de tsconfig.json en CLAUDE.md?
Añadir lo siguiente a CLAUDE.md hace que Claude Code haga referencia a tus requisitos de seguridad de tipos antes de generar cualquier código.
Primero, la configuración de tsconfig.json (a partir de TypeScript 5.9, tsc --init incluye noUncheckedIndexedAccess y exactOptionalPropertyTypes por defecto):
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
Luego las reglas de CLAUDE.md:
## TypeScript Configuration
### Code generation rules
1. `as any` is banned. No exceptions.
2. `as Type` casting is only allowed after a type guard or Zod validation
3. All function parameters and return types must have explicit type annotations
4. Use `unknown` instead of `any`, then narrow with type guards
5. Rely on type inference where possible (excessive explicit annotations are also bad)
### Required verification
After generating any code, run `npx tsc --noEmit`.
Fix all type errors before submitting. Do not return code with type errors.
Copiar tu tsconfig.json en CLAUDE.md es el cambio individual de mayor impacto que puedes hacer. Le dice a Claude exactamente bajo qué restricciones opera — sin necesidad de repetir "no uses any" en cada prompt.
¿Cómo Haces que Claude Code Diseñe Tus Esquemas Zod?
Para respuestas de APIs externas, el orden correcto es: definir el esquema Zod primero, luego generar el código de parsing. Así es cómo funciona.
Paso 1: Alimenta a Claude con un ejemplo de respuesta de API y obtén un esquema Zod
Genera un esquema Zod a partir del siguiente ejemplo de respuesta de API.
Ejemplo de respuesta:
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"role": "admin" | "user" | "guest",
"createdAt": "2026-01-01T00:00:00Z",
"profile": {
"bio": "Developer",
"avatarUrl": "https://example.com/avatar.jpg" | null
}
}
Requisitos:
- Tipar cada campo explícitamente
- Definir role como tipo unión
- Convertir createdAt a Date usando z.coerce.date()
- Marcar campos nullable con .nullable()
- Exportar tipos inferidos usando z.infer<typeof Schema>
Esquema generado:
import { z } from 'zod'
const ProfileSchema = z.object({
bio: z.string(),
avatarUrl: z.string().url().nullable(),
})
const UserRoleSchema = z.enum(['admin', 'user', 'guest'])
export const UserSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(1),
email: z.string().email(),
role: UserRoleSchema,
createdAt: z.coerce.date(),
profile: ProfileSchema,
})
export type User = z.infer<typeof UserSchema>
export type UserRole = z.infer<typeof UserRoleSchema>
Paso 2: Genera la función de parsing usando el esquema
Escribe una función que haga fetch de datos de usuario y los parsee con el UserSchema anterior.
Requisitos:
- Recibir la respuesta como unknown
- Lanzar ZodError si falla el parseo
- Sin casteos `as any`
import { UserSchema, type User } from './schemas/user'
export async function fetchUser(userId: number): Promise<User> {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`)
}
const json: unknown = await response.json()
return UserSchema.parse(json)
}
Recibir response.json() como unknown, validar con Zod y devolver un valor tipado — este es el patrón correcto para manejar datos externos sin as any.
¿Cómo Haces de tsc --noEmit un Requisito Obligatorio en CLAUDE.md?
Añade esto a CLAUDE.md para evitar que Claude devuelva código con errores de tipo persistentes:
## Mandatory Check Before Submitting Code
After generating or modifying any code, always run `npx tsc --noEmit`.
Fix all type errors until the output shows zero errors.
Never return code that still has type errors.
La restricción de "cero errores antes de enviar" es lo que hace que esto funcione. Sin ella, Claude a veces devuelve código con errores de tipo argumentando que "funciona correctamente." El flag --noEmit ejecuta la verificación de tipos sin emitir archivos JavaScript — perfecto para este tipo de control.
Añádelo también a package.json:
{
"scripts": {
"type-check": "tsc --noEmit",
"type-check:watch": "tsc --noEmit --watch"
}
}
¿Cómo Ejecutas un Bucle que Hace que Claude Auto-Corrija Errores de Tipo?
Ejecuta tsc --noEmit, captura la salida y aliméntala directamente a Claude para corrección automática.
npx tsc --noEmit 2>&1
Ejemplo de salida:
src/utils/parseResponse.ts:12:5 - error TS2322: Type 'any' is not assignable to type 'User'.
src/utils/parseResponse.ts:18:3 - error TS7006: Parameter 'data' implicitly has an 'any' type.
Found 2 errors.
Pásalo a Claude:
Corrige los siguientes errores de TypeScript.
Errores:
src/utils/parseResponse.ts:12:5 - error TS2322: Type 'any' is not assignable to type 'User'.
src/utils/parseResponse.ts:18:3 - error TS7006: Parameter 'data' implicitly has an 'any' type.
Enfoque de corrección:
- No usar `as any`
- Usar tipo unknown + type guards, o validación con Zod
- Confirmar que `tsc --noEmit` muestra cero errores antes de responder
Ejecuta este bucle hasta que el verificador de tipos esté limpio. Cuando hay múltiples errores, pásalos todos a la vez — Claude los abordará juntos.
¿Cómo Haces que Claude Code Tipe Respuestas de APIs Externas?
Cuando tienes una especificación Swagger u OpenAPI, pasarla a Claude para conversión a esquemas Zod es el enfoque más rápido.
Genera esquemas TypeScript + Zod a partir de la siguiente spec OpenAPI.
Spec (extracto):
---
components:
schemas:
Product:
type: object
required:
- id
- name
- price
properties:
id:
type: integer
name:
type: string
maxLength: 100
price:
type: number
minimum: 0
category:
type: string
enum: [electronics, clothing, food]
stock:
type: integer
nullable: true
---
Requisitos:
- Exportar tipos usando z.infer<typeof Schema>
- Reflejar restricciones de validación (maxLength, minimum) en Zod
- Expresar nullable con .nullable()
Herramientas como openapi-zod-client también existen para conversión automatizada, pero dejar que Claude lo haga te da más control sobre casos límite y convenciones de nomenclatura.
¿Cómo Maximizas la Inferencia de Tipos al Usar Prisma?
Prisma auto-genera tipos TypeScript desde tu esquema de base de datos. Dile a Claude Code que use esos tipos en lugar de inventar los suyos. Para más detalles, consulta Claude Code × Prisma: Auto-Genera APIs.
## Prisma Type Rules (add to CLAUDE.md)
- Actively use Prisma-generated types (PrismaClient, Prisma.UserGetPayload, etc.)
- Import the User type from '@prisma/client', not custom definitions
- Use Prisma.UserGetPayload<{include: {posts: true}}> for query return types
- Do not override Prisma types with custom type definitions
Cómo se ve en la práctica:
import type { Prisma } from '@prisma/client'
import { prisma } from '@/lib/prisma'
type UserWithPosts = Prisma.UserGetPayload<{
include: { posts: { include: { tags: true } } }
}>
export async function getUserWithPosts(userId: string): Promise<UserWithPosts | null> {
return prisma.user.findUnique({
where: { id: userId },
include: {
posts: {
include: { tags: true },
},
},
})
}
Cuando usas los tipos generados por Prisma, los cambios en el esquema de la base de datos aparecen instantáneamente como errores de TypeScript. Claude Code solo necesita "usa los tipos de Prisma" en el prompt o CLAUDE.md para aprovechar esto.
¿Cómo Se Ve una Plantilla de CLAUDE.md para Generación de Código Type-Safe?
Aquí tienes la plantilla completa de CLAUDE.md basada en todo lo que cubre este artículo. Cópiala en tu proyecto tal cual.
## TypeScript Type Safety Policy
### Absolute prohibitions
- Using `as any` (banned regardless of circumstances)
- Using `@ts-ignore` (`@ts-expect-error` is allowed with a comment explaining why)
- Function definitions without type annotations (parameters and return types are required)
### Typing external data
- Receive external API/JSON data as `unknown` and validate with Zod
- `response.json() as SomeType` casting is banned
- Export types from Zod schemas using `z.infer<typeof Schema>`
### tsconfig.json compliance
strict: true / noImplicitAny: true / strictNullChecks: true / noUncheckedIndexedAccess: true
### Required check (mandatory)
After generating or modifying code, run `npx tsc --noEmit` and confirm zero errors before responding.
### Zod conventions
- Centralize schema definitions in `src/schemas/`
- `.parse()` throws on validation failure (intentional behavior)
- Use `.safeParse()` when you need to handle errors gracefully
- Always export the inferred type alongside the schema
¿Cómo generar genéricos y tipos de utilidad con precisión?
Cuando Claude Code escribe tipos genéricos, tiende a abusar de <T> sin restricciones. Agregar reglas de genéricos a CLAUDE.md mejora la precisión.
Reglas de genéricos para CLAUDE.md:
# Reglas de Genéricos
- Siempre restringir parámetros de tipo (`<T extends Base>`, no `<T>` suelto)
- Usar tipos de utilidad (Pick, Omit, Partial) activamente para reducir definiciones
- Tipos condicionales máximo 2 niveles. Dividir 3+ niveles en sobrecargas de función
Ejemplo de prompt para tipos condicionales:
Crea un tipo de respuesta API con estas condiciones:
- GET /users → User[]
- GET /users/:id → User
- POST /users → User
- Usa un tipo condicional que varíe el tipo de retorno por endpoint
Ejemplo de tipo generado:
type ApiEndpoint = "GET /users" | "GET /users/:id" | "POST /users";
type ApiResponse<E extends ApiEndpoint> = E extends "GET /users"
? User[]
: E extends "GET /users/:id"
? User
: E extends "POST /users"
? User
: never;
Preguntas Frecuentes
¿Es seguro usar as unknown as Type?
Es más seguro que as any porque pasas explícitamente por unknown, pero sigue evitando el verificador de tipos. El enfoque correcto es validar con Zod o un type guard antes de hacer el casteo. Reserva as unknown as Type para casos donde ya verificaste la forma en runtime y la inferencia de TypeScript no puede seguir tu lógica — y siempre agrega un comentario explicando por qué.
¿Qué pasa si un paquete @types de terceros usa any?
Envuelve la llamada a la librería en una función adaptadora delgada que recibe unknown y valida la salida con Zod o un type guard. Esto aísla el any en el límite en lugar de dejarlo filtrarse en tu código. Por ejemplo, si someLib.parse() retorna any, escribe function safeParse(input: string): MyType { return MySchema.parse(someLib.parse(input)) }.
¿Debo usar Zod para datos internos también, o solo para APIs externas?
Para datos internos que se mantienen dentro de tu boundary de TypeScript (ej. llamadas función a función), el sistema de tipos estático es suficiente. Zod es más valioso en límites de confianza — respuestas de API, input de usuario, variables de entorno, salida de JSON.parse(), y cualquier cosa que venga de fuera de tu código verificado por tipos.
¿Cómo tipar JSON.parse() de forma segura?
JSON.parse() retorna any por defecto. El patrón type-safe es: const raw: unknown = JSON.parse(jsonString) seguido de MySchema.parse(raw). Esto asegura validación en runtime antes de que tu código toque los datos. Nunca hagas JSON.parse(str) as MyType.
¿Activar strictNullChecks rompe el código existente?
Casi con certeza sí, si tu proyecto no fue escrito con esto activado. Empieza activándolo en una rama separada, ejecuta tsc --noEmit y corrige errores incrementalmente. Activarlo típicamente saca a la luz numerosos problemas de null-safety que son bugs genuinos esperando a ocurrir.
¿Puedo usar @ts-expect-error en lugar de @ts-ignore?
Sí, y deberías. @ts-expect-error falla cuando el error que suprime se corrige, así que no se convierte silenciosamente en código muerto. Siempre agrega un comentario: // @ts-expect-error — los tipos de la librería no exponen internalMethod. Incluye @ts-expect-error en tus reglas de CLAUDE.md como la única supresión aceptada.
¿El modo strict ralentiza tsc?
De forma insignificante. Los flags strict agregan más pasadas de verificación, pero la diferencia es típicamente menor al 5% en proyectos de hasta 100k líneas. El flag noUncheckedIndexedAccess agrega | undefined a cada acceso por índice, lo que puede aumentar el número de errores de tipo a manejar — pero eso es un beneficio de corrección, no un costo de rendimiento.
¿Cómo manejar tipos de eventos React con strict mode?
Usa los tipos de eventos integrados de React: React.ChangeEvent<HTMLInputElement>, React.FormEvent<HTMLFormElement>, etc. Si Claude Code genera (e: any) =>, agrega una regla en CLAUDE.md: "Los event handlers de React deben usar eventos tipados de React.*Event. Nunca tipar un parámetro de evento como any."
Artículos Relacionados
- Generación Automatizada de Tests con Claude Code: Guía Práctica — Patrones de testing para código type-safe
- Claude Code × Prisma: Auto-Genera APIs desde Tu Esquema — Construcción de APIs type-safe con inferencia de Prisma
- Gestión de Contexto en Claude Code: Patrones de Diseño para CLAUDE.md — Cómo integrar políticas de tipos en CLAUDE.md
Conclusión
La clave para obtener TypeScript type-safe de Claude Code es hacer que la seguridad de tipos sea explícita — no una expectativa implícita.
- Copia la configuración strict de tsconfig.json en CLAUDE.md — política de tipos a nivel de proyecto que se mantiene
- Requiere
tsc --noEmitcomo verificación obligatoria — evita que los errores de tipo se cuelen - Empieza con esquemas Zod para datos externos — la única forma de evitar
as anyen respuestas de API - Alimenta los logs de errores de tipo directamente a Claude — el bucle de retroalimentación maneja la auto-corrección
- Dile a Claude que use los tipos generados por Prisma — el esquema de BD y los tipos TypeScript se mantienen sincronizados automáticamente
Deja de decir "no uses any" en cada prompt. Escribe la política de tipos una vez en CLAUDE.md y deja que haga el trabajo.
Artículos relacionados: