blog/[TypeScript]/typescript-desde-cero-para-quien-sabe-javascript
Developer tipando código TypeScript en un laptop
[TypeScript]6 min de lectura

TypeScript desde cero para quien ya sabe JavaScript

Guía profunda de TypeScript: strict mode, interfaces, unions, genéricos, utility types, tipado de APIs, React/Nest y un plan de 14 días para dejar de usar any.

camilogaon

@camilogaon

27 de julio de 20262 vistas

TypeScript desde cero para quien ya sabe JavaScript

Si ya manejas JavaScript y sigues chocando con bugs tontos en producción (undefined is not a function, props mal tipadas, APIs que “a veces” traen null), TypeScript es la mejora más rentable que puedes hacer a tu stack.

Esta guía te lleva de JS cómodo a TS profesional: tipos, interfaces, genéricos, narrowing, utilidades y cómo usarlo en proyectos reales (Next.js / NestJS).

Prerrequisito: JavaScript intermedio (funciones, arrays, objetos, async/await).
Objetivo: escribir TS con confianza, no solo “poner any para que compile”.


//1. Qué problema resuelve TypeScript

JavaScript es flexible. Esa flexibilidad es poder… y también fuente de errores silenciosos.

js
function getLength(value) {
  return value.length;
}

getLength("hola"); // 4
getLength([1, 2]); // 2
getLength(10);     // 💥 en runtime

Con TypeScript:

ts
function getLength(value: string | unknown[]) {
  return value.length;
}

getLength(10); // ❌ Error en compile-time

TypeScript no reemplaza tests, pero elimina una clase enorme de bugs antes de ejecutar.

TS vs JS (mental model)

JavaScriptTypeScript
DinámicoEstático (en desarrollo)
Errores en runtimeMuchos errores en compile-time
FlexibleFlexible + contratos
Archivos .jsArchivos .ts / .tsx

Al final, TypeScript compila a JavaScript. El navegador/Node sigue ejecutando JS.


//2. Setup mínimo (sin dolor)

Opción A — playground

Entra a typescriptlang.org/play y prueba snippets.

Opción B — proyecto Node rápido

bash
npm init -y
npm i -D typescript tsx @types/node
npx tsc --init

tsconfig.json recomendado (base moderna):

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

Activa strict: true desde el día 1. Aprender TS “relajado” enseña malos hábitos.

Ejecutar:

bash
npx tsx src/index.ts

//3. Tipos básicos y anotaciones

ts
const level: number = 3;
const username: string = "cami";
const active: boolean = true;
const tags: string[] = ["js", "ts"];
const scores: Array<number> = [10, 9, 8];

let maybe: string | null = null;
maybe = "hola";

Inferencia (deja que TS trabaje)

ts
const puerto = 3001; // number inferido
const title = "Blog"; // string inferido

function sum(a: number, b: number) {
  return a + b; // number inferido
}

Anota cuando:

  • exportas APIs públicas
  • el compilador no puede inferir bien
  • quieres documentar intención (contratos)

//4. Objetos: type vs interface

ts
type UserId = string;

interface User {
  id: UserId;
  name: string;
  email: string;
  role?: "USER" | "ADMIN"; // opcional
}

const user: User = {
  id: "u_1",
  name: "Ana",
  email: "ana@programemos.dev",
};

Regla práctica del equipo:

  • interface → objetos/extensibles (modelos de dominio)
  • type → uniones, tuples, utilidades, aliases
ts
type Resultado = { ok: true; data: string } | { ok: false; error: string };
type Point = [number, number];

Readonly y as const

ts
interface Config {
  readonly apiUrl: string;
}

const roles = ["USER", "ADMIN"] as const;
type Role = (typeof roles)[number]; // "USER" | "ADMIN"

//5. Uniones, narrowing y type guards

ts
function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(0));
  }
}

Con objetos discriminados (patrón oro en APIs):

ts
type ApiResponse<T> =
  | { status: "success"; data: T }
  | { status: "error"; message: string };

function handle(res: ApiResponse<User>) {
  if (res.status === "success") {
    console.log(res.data.email);
  } else {
    console.error(res.message);
  }
}

Type guard custom:

ts
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "email" in value
  );
}

//6. Funciones tipadas como adulto

ts
function crearSlug(title: string, max = 80): string {
  return title
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/(^-|-$)/g, "")
    .slice(0, max);
}

type Matcher = (value: string) => boolean;

const startsWithA: Matcher = (value) => value.startsWith("a");

Overloads (cuando hace falta)

ts
function parse(input: string): object;
function parse(input: string, asArray: true): object[];
function parse(input: string, asArray?: boolean) {
  const data = JSON.parse(input);
  return asArray ? (Array.isArray(data) ? data : [data]) : data;
}

//7. Genéricos: reutiliza sin perder tipos

ts
function first<T>(items: T[]): T | undefined {
  return items[0];
}

first([1, 2, 3]);       // number | undefined
first(["a", "b"]);      // string | undefined

En wrappers de fetch:

ts
async function getJson<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json() as Promise<T>;
}

interface Post {
  id: string;
  title: string;
  slug: string;
}

const posts = await getJson<Post[]>("/blog/posts");

Constraints:

ts
function getId<T extends { id: string }>(entity: T): string {
  return entity.id;
}

//8. Utility types que sí se usan en el trabajo

ts
interface BlogPost {
  id: string;
  title: string;
  slug: string;
  content: string;
  publishedAt: Date | null;
}

type CreatePostInput = Omit<BlogPost, "id" | "publishedAt">;
type PostPreview = Pick<BlogPost, "title" | "slug">;
type PartialPost = Partial<BlogPost>;
type ReadonlyPost = Readonly<BlogPost>;
type Status = "DRAFT" | "PUBLISHED";
type StatusMap = Record<Status, number>;

Awaited, ReturnType, Parameters también aparecen mucho en librerías y SDKs.


//9. null, undefined y optional chaining

Con strictNullChecks (incluido en strict), esto falla:

ts
function getName(user: User | null) {
  return user.name; // ❌
}

Corrige con narrowing:

ts
function getName(user: User | null) {
  if (!user) return "Invitado";
  return user.name;
}

O con encadenamiento:

ts
user?.profile?.username ?? "anon";

Evita ! (non-null assertion) salvo casos muy justificados:

ts
const el = document.getElementById("root")!; // peligroso si puede ser null

//10. Tipar datos del mundo real (APIs, forms, JSON)

El borde del sistema es donde vive el caos. Tipar “por fe” no basta.

Estrategia recomendada

  1. Define el tipo esperado
  2. Valida en runtime (Zod / class-validator)
  3. Infiere el tipo desde el schema
ts
import { z } from "zod";

const CreatePostSchema = z.object({
  title: z.string().min(3),
  excerpt: z.string().min(10),
  content: z.string().min(20),
  featured: z.boolean().default(false),
});

type CreatePostDto = z.infer<typeof CreatePostSchema>;

function createPost(input: unknown) {
  const dto: CreatePostDto = CreatePostSchema.parse(input);
  return dto;
}

En NestJS ya usas class-validator + DTOs: es el mismo principio.


//11. TypeScript en frontend (React / Next)

tsx
type ButtonProps = {
  label: string;
  onClick: () => void;
  variant?: "primary" | "ghost";
  disabled?: boolean;
};

export function Button({ label, onClick, variant = "primary", disabled }: ButtonProps) {
  return (
    <button data-variant={variant} disabled={disabled} onClick={onClick}>
      {label}
    </button>
  );
}

Props children:

tsx
type CardProps = {
  title: string;
  children: React.ReactNode;
};

Para estado:

ts
const [posts, setPosts] = useState<Post[]>([]);

//12. TypeScript en backend (NestJS mental model)

En Nest/Prisma sueles combinar:

  • DTOs (entrada HTTP validada)
  • tipos Prisma (Prisma.BlogPostGetPayload)
  • tipos de respuesta para el frontend
ts
type PostListItem = {
  id: string;
  title: string;
  slug: string;
  excerpt: string;
  publishedAt: string | null;
};

No expongas el modelo de DB crudo si el contrato público es distinto (fechas ISO, status en minúsculas, seo anidado, etc.).


//13. Errores típicos (y cómo salir)

1) Abusar de any

ts
// ❌
function load(data: any) {
  return data.items.map((x: any) => x.name);
}

// ✅
type Payload = { items: Array<{ name: string }> };
function load(data: Payload) {
  return data.items.map((x) => x.name);
}

Si no sabes el tipo: usa unknown + narrowing.

2) Pelearte con el compilador en vez de modelar mejor

Si TS “molesta”, casi siempre el modelo de datos es ambiguo. Mejora el tipo, no lo apagues.

3) Enums numéricos confusos

Prefiere unions de strings:

ts
type ContentStatus = "DRAFT" | "PUBLISHED" | "ARCHIVED";

4) Tipar de más demasiado pronto

Empieza por funciones públicas, DTOs y bordes de red. No tipifiques cada variable local trivial.


//14. Mini proyecto: tipar un cliente de Blog

Objetivo: crear un módulo blog-api.ts tipado contra los endpoints de Programemos.

ts
export type BlogSeo = {
  title: string;
  description: string;
  canonical: string;
  robots: string;
};

export type BlogPostDetail = {
  id: string;
  title: string;
  slug: string;
  excerpt: string;
  content: string;
  seo: BlogSeo;
};

export async function getPostBySlug(slug: string): Promise<BlogPostDetail> {
  const res = await fetch(`https://api.example.com/blog/posts/${slug}`);
  if (!res.ok) throw new Error("Post no encontrado");
  return res.json();
}

Extensiones del ejercicio:

  • Tipar listado paginado (items, total, page)
  • Tipar filtros (category, tag, featured)
  • Agregar Zod al response si no confías en el backend (defensa en profundidad)

//15. Plan de 14 días para afianzar TypeScript

DíaTemaPráctica
1–2Tipos básicos + strictMigrar 1 archivo JS → TS
3–4Interfaces / unionsModelar User + ApiResponse
5–6GenéricosgetJson<T> + helpers de array
7–8Utility typesDTOs Create/Update
9–10Narrowing + unknownParsear JSON externo
11–12React o Nest tipado1 componente o 1 endpoint
13–14Mini cliente BlogRepo limpio en GitHub

//16. Checklist “ya sé TypeScript de verdad”

  • Trabajo con strict: true sin miedo
  • Sé cuándo usar type vs interface
  • Domino unions + narrowing
  • Escribo genéricos simples sin copiar Stack Overflow
  • Uso Omit, Pick, Partial, Record
  • Evito any; uso unknown cuando corresponde
  • Tipo DTOs de entrada/salida en mis APIs
  • Puedo migrar un módulo JS existente a TS

//17. Migrar un proyecto JS a TS sin morir en el intento

Estrategia que funciona en equipos reales:

  1. Activa TypeScript en modo permisivo solo temporalmente (allowJs)
  2. Renombra primero los módulos de borde (API, utils, shared)
  3. Enciende strict por carpeta/proyecto cuando compile limpio
  4. Sustituye any por tipos reales en PRs pequeños
  5. Tipa tests al final (o en paralelo si usas Vitest/Jest)
json
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": true,
    "noImplicitAny": true
  }
}

No intentes tipar 20.000 líneas en un fin de semana. Migración = pagar deuda en cuotas.


//18. Patrones avanzados que verás en código senior

Branded types (IDs que no se mezclan)

ts
type UserId = string & { readonly __brand: "UserId" };
type PostId = string & { readonly __brand: "PostId" };

function getPost(id: PostId) {}
// getPost(userId) → error si los brands difieren

Exhaustive checks con never

ts
type Shape = { kind: "circle"; r: number } | { kind: "square"; size: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.r ** 2;
    case "square":
      return shape.size ** 2;
    default: {
      const _exhaustive: never = shape;
      return _exhaustive;
    }
  }
}

Satisfies (TS 4.9+)

ts
const routes = {
  home: "/",
  blog: "/blog",
} satisfies Record<string, `/${string}`>;

//19. 20 ejercicios de TypeScript (con intención)

  1. Tipa una función paginate<T>(items, page, limit).
  2. Crea ApiResult<T> success/error y un handler exhaustive.
  3. Define DeepPartial<T> (recursivo simple).
  4. Tipa un formulario de registro (union de pasos).
  5. Escribe un type guard isNonEmptyString.
  6. Modela un carrito con Readonly e inmutabilidad.
  7. Usa Omit para Create/Update de un Post.
  8. Tipa localStorage get/set con genéricos.
  9. Crea KeyOfType<T, V> (claves cuyo valor es V).
  10. Tipa un event bus simple (on/emit).
  11. Migra un utils.js a utils.ts sin any.
  12. Tipa respuesta paginada del blog.
  13. Usa Zod + z.infer en un DTO.
  14. Tipa props de un Modal accesible.
  15. Crea assertNever(x: never).
  16. Tipa errores custom (class AppError).
  17. Haz fetch tipado con timeout y abort.
  18. Modela permisos: can(user, action, resource).
  19. Tipa un mapper DB → DTO público.
  20. Escribe tests type-level con expectTypeOf (Vitest).

//20. Cómo leer errores del compilador (sin frustrarte)

El mensaje de TS suele ser verboso, pero la clave está arriba:

  1. Lee el tipo esperado vs el tipo recibido
  2. Identifica si falta narrowing (null/undefined)
  3. Pregunta: ¿mi modelo refleja la realidad del dato?
  4. Si el error es enorme, simplifica el ejemplo en el playground

Ejemplo mental:

text
Type 'string | undefined' is not assignable to type 'string'

Solución típica: default (??), early return, o hacer el campo opcional en el destino.


//21. Cuándo NO usar TypeScript

  • Scripts de 20 líneas desechables
  • Prototipos ultra rápidos que vas a tirar hoy
  • Equipos que aún no entienden JS básico (primero JS)

Para producto serio, aprendizaje estructurado y backends/frontends medianos+: sí, TS.


//Conclusión

TypeScript no es burocracia: es diseño de contratos. Cuando tipas bien, el editor se vuelve un compañero de pair programming y tus refactors dejan de romper todo en silencio.

Siguiente paso recomendado en Programemos:

  1. Termina la ruta de TypeScript desde cero
  2. Tipa un proyecto JS que ya tengas
  3. Completa 10 ejercicios de esta guía
  4. Publica un artículo o ejercicio resolviendo un bug real que TS te habría prevenido

Si JavaScript te enseñó a construir, TypeScript te enseña a construir con intención.

FAQ

¿Necesito dominar JavaScript antes de TypeScript?

Sí, al menos a nivel intermedio: funciones, arrays/objetos, módulos y async/await. TypeScript se apoya en esos conceptos.

¿TypeScript se ejecuta en el navegador?

No directamente. TypeScript se compila a JavaScript; el runtime sigue siendo JS en navegador o Node.

¿Debo usar any si estoy empezando?

No. Prefiere unknown + narrowing. any apaga el tipado y elimina la ventaja de TypeScript.

¿type o interface?

Usa interface para objetos de dominio extensibles y type para uniones, tuples y aliases. Ambos son válidos; la consistencia del equipo importa más.

¿TypeScript reemplaza las pruebas?

No. Evita muchos bugs de contratos y formas de datos, pero la lógica de negocio sigue necesitando tests.