// 01 — INTRO

Por que TypeScript?

TypeScript = JavaScript + tipos estaticos. Se compila a JS puro. No inventa un lenguaje nuevo — agrega una capa de seguridad que desaparece en runtime.

JavaScript es dinamico: una variable puede ser string ahora y number despues. Esto es flexible pero genera bugs silenciosos que solo aparecen en produccion. TypeScript detecta esos errores antes de ejecutar el codigo, en el editor, con lineas rojas y sugerencias.

🛡️
Errores en compilacion
El compilador detecta tipos incorrectos y propiedades inexistentes antes de que el codigo llegue a produccion.
🧠
Autocompletado
El editor conoce la forma de cada objeto. Sugerencias precisas, documentacion en hover y refactoring automatico seguro.
📖
Codigo autodocumentado
Los tipos son documentacion ejecutable. Una funcion tipada dice exactamente que recibe y que devuelve.
🔄
100% compatible JS
Todo JS valido es TS valido. Se puede migrar archivo por archivo. El output es JS estandar.
JavaScript — el bug aparece en runtime
function calcularTotal(items) {
  return items.reduce((s, i) => s + i.precio, 0);
}

calcularTotal("arrachera");
// TypeError: items.reduce is not a function
// Error visible solo en produccion
TypeScript — error antes de ejecutar
interface Item { precio: number }

function calcularTotal(items: Item[]) {
  return items.reduce((s, i) => s + i.precio, 0);
}

calcularTotal("arrachera");
// Error TS: Argument of type 'string'
// not assignable to type 'Item[]'
💡
Instalar en 30 segundosnpm install -D typescript ts-node && tsc --init
Esto crea el tsconfig.json. Con "strict": true activas todas las verificaciones mas utiles de golpe.

// 02 — TIPOS BASICOS

Tipos Basicos

TS infiere tipos cuando puede. Solo anotas explicitamente cuando el compilador no puede inferir solo, o cuando quieres ser explicito por claridad.

tipos-basicos.tsTS
// Primitivos con anotacion explicita
const nombre:  string  = "Arrachera Premium";
const precio:  number  = 189.50;
const activo:  boolean = true;

// Inferencia — TS deduce el tipo automaticamente
const kilos = 2.5;     // inferido: number
const marca = "VLIM";  // inferido: string
let   stock = 42;      // inferido: number

// Arrays
const precios: number[]       = [189, 145, 98];
const tags:    Array<string> = ["res", "cerdo"];

// Tuplas — longitud y tipos fijos
const coord: [number, number]           = [19.43, -99.13];
const fila:  [string, number, boolean] = ["Bistec", 160, true];

// Enum — conjunto de valores nombrados
enum Estado {
  Activo    = "ACTIVO",
  Inactivo  = "INACTIVO",
  Pendiente = "PENDIENTE",
}
const est: Estado = Estado.Activo;

// unknown — alternativa segura a any
let respuesta: unknown;
if (typeof respuesta === "string") {
  console.log(respuesta.toUpperCase()); // ok — verificado
}

// Literal types — solo valores especificos
type Moneda     = "MXN" | "USD" | "EUR";
type MetodoHTTP = "GET" | "POST" | "PUT" | "DELETE";
const m: Moneda = "MXN";  // ok
// const m2: Moneda = "BTC"; // Error: not assignable

// never — funciones que nunca retornan
function error(msg: string): never {
  throw new Error(msg);
}
TipoEjemploCuando usar
string"hola" `texto ${v}`Texto, nombres, IDs alfanumericos
number42 3.14 NaNPrecios, cantidades, indices
booleantrue falseFlags, estados binarios
null / undefinednull undefinedValores opcionales — null para "sin valor"
unknownJSON.parse(texto)Datos de API externos — requiere narrowing antes de usar
neverthrow new Error()Funciones que siempre lanzan error
any— evitar —Solo como escape hatch temporal en migracion JS→TS
⚠️
Evita anyany desactiva completamente el tipado en esa variable. Es como escribir JS sin TypeScript. Usa unknown cuando no sabes el tipo — te obliga a verificar antes de usar.

// 03 — INTERFACES Y TYPES

Interfaces y Type Aliases

Interface y type alias describen la "forma" de un objeto. Interface es preferida para objetos extensibles. type es mas versatil — puede ser union, interseccion o primitivo con alias.

interfaces.ts — entidades del ERPTS
// Interface — forma de un objeto
interface Producto {
  id:        string;
  nombre:    string;
  precio:    number;
  unidad:    "kg" | "pieza" | "litro";
  activo?:   boolean;        // ? = opcional
  stock:     number;
  readonly creadoEn: string; // no se puede modificar
}

const arrachera: Producto = {
  id: "prod-001", nombre: "Arrachera Premium",
  precio: 189.50, unidad: "kg", stock: 42, creadoEn: "2024-01-15",
};

// Extender interfaces
interface ProductoConProveedor extends Producto {
  proveedorId:  string;
  codigoBarras: string;
  fechaVence?:  Date;
}

// Type alias — mas versatil
type ID          = string | number;
type EstadoVenta  = "pendiente" | "pagada" | "cancelada";

// Interseccion: combina dos tipos
type Auditado = {
  creadoPor:     string;
  creadoEn:      Date;
  actualizadoEn: Date;
};
type VentaAuditada = Venta & Auditado;

// Index signatures — claves dinamicas
interface PreciosPorSucursal {
  [sucursalId: string]: number;
}
const precios: PreciosPorSucursal = {
  "suc-01": 189, "suc-02": 195,
};

// Diferencia clave: interface permite declaration merging
interface Config { host: string }
interface Config { port: number }
// Config ahora tiene { host, port } fusionados
// type no puede fusionarse — genera Duplicate identifier
Demo — interfaces y validacion de tipos en runtime▶ LIVE
// Simulamos tipado TS en runtime JS const producto = { id: "prod-001", nombre: "Arrachera", precio: 189.50, stock: 42, unidad: "kg" }; function validarProducto(p) { const errs = []; if (typeof p.nombre !== "string") errs.push("nombre debe ser string"); if (typeof p.precio !== "number" || p.precio <= 0) errs.push("precio invalido"); if (!["kg","pieza","litro"].includes(p.unidad)) errs.push("unidad invalida"); return errs.length === 0 ? { ok: true, producto: p.nombre } : { ok: false, errs }; } console.log(validarProducto(producto)); console.log(validarProducto({ nombre: 123, precio: -5, unidad: "metro" }));
// outputHaz clic en Ejecutar...

// 04 — FUNCIONES TIPADAS

Funciones Tipadas

Tipar los parametros y el retorno de una funcion es donde TypeScript da mas valor. El compilador verifica cada llamada y el editor autocompleta el resultado.

funciones.tsTS
// Funcion basica con tipos
function conIva(precio: number, iva: number = 0.16): number {
  return precio * (1 + iva);
}

// Arrow function tipada
const formatMXN = (n: number): string =>
  new Intl.NumberFormat("es-MX", { style: "currency", currency: "MXN" }).format(n);

// Parametros opcionales y rest
function crearEtiqueta(nombre: string, peso?: number, ...extras: string[]): string {
  let label = nombre;
  if (peso !== undefined) label += ` ${peso}kg`;
  if (extras.length) label += ` [${extras.join(", ")}]`;
  return label;
}

// Function overloads — misma funcion, firmas distintas
function buscarProducto(id: string):    Producto;
function buscarProducto(codigo: number): Producto;
function buscarProducto(query: string | number): Producto {
  if (typeof query === "string") return db.findById(query);
  return db.findByCodigo(query);
}

// Tipo de funcion como interfaz
type CalculadoraPrecio = (precio: number, cantidad: number) => number;

const calcularSubtotal: CalculadoraPrecio = (precio, cantidad) => precio * cantidad;
const calcularDescuento: CalculadoraPrecio = (precio, pct) => precio * (pct / 100);

// Higher-order functions tipadas
function aplicarDescuento(
  fn: CalculadoraPrecio,
  precio: number,
  arg: number
): number {
  return precio - fn(precio, arg);
}

// Callback con tipos
function procesarVentas(
  ventas:   Venta[],
  onExito:  (venta: Venta) => void,
  onError?: (err: Error, venta: Venta) => void
): void {
  ventas.forEach(v => {
    try   { onExito(v); }
    catch (e) { onError?.(e as Error, v); }
  });
}
Demo — funciones de calculo del ERP▶ LIVE
// Funciones con tipos (simuladas en JS) const IVA = 0.16; function conIva(precio, iva = IVA) { return precio * (1 + iva); } function formatMXN(n) { return new Intl.NumberFormat("es-MX", { style: "currency", currency: "MXN" }).format(n); } function calcularLinea(precio, cantidad, descPct = 0) { const subtotal = precio * cantidad; const descuento = subtotal * (descPct / 100); const base = subtotal - descuento; const iva = base * IVA; return { subtotal, descuento, base, iva, total: base + iva }; } const l = calcularLinea(189, 2, 10); console.log("Subtotal:", formatMXN(l.subtotal)); console.log("Descuento:", formatMXN(l.descuento)); console.log("Total c/IVA:", formatMXN(l.total));
// outputHaz clic en Ejecutar...

// 05 — GENERICS

Generics — Tipos Parametrizados

Los generics permiten escribir codigo que funciona con cualquier tipo, manteniendo la seguridad de tipos. Como templates/plantillas: defines la logica una vez, el tipo se especifica al usar.

generics.tsADV
// Generic basico — <T> es el parametro de tipo
function primero<T>(arr: T[]): T | undefined {
  return arr[0];
}
primero([1, 2, 3]);           // T = number
primero(["a", "b"]);          // T = string

// Respuesta paginada generica — reutilizable para cualquier entidad
interface PaginaRespuesta<T> {
  data:    T[];
  total:   number;
  pagina:  number;
  limite:  number;
  siguiente?: string;
}

// Se usa con cualquier entidad
const resp: PaginaRespuesta<Producto> = {
  data: [arrachera], total: 120, pagina: 1, limite: 20,
};

// Constraints — limitar que tipos acepta el generic
function obtenerNombre<T extends { nombre: string }>(entidad: T): string {
  return entidad.nombre;
}
obtenerNombre(arrachera);     // ok — Producto tiene nombre
obtenerNombre({ nombre: "Juan", rol: "admin" }); // ok

// Generic con multiples parametros
function mapear<T, R>(arr: T[], fn: (item: T) => R): R[] {
  return arr.map(fn);
}
const totales = mapear(ventas, v => v.total);  // T=Venta, R=number

// Clase generica — repositorio base del ERP
class Repositorio<T extends { id: string }> {
  private items: Map<string, T> = new Map();

  guardar(item: T): void           { this.items.set(item.id, item); }
  obtener(id: string): T | undefined { return this.items.get(id); }
  listar(): T[]                      { return [...this.items.values()]; }
  eliminar(id: string): boolean      { return this.items.delete(id); }
}

const productos = new Repositorio<Producto>();
const usuarios  = new Repositorio<Usuario>();

// 06 — UNION Y NARROWING

Union Types y Narrowing

Un union type acepta varios tipos posibles. Narrowing es el proceso de reducir el tipo posible mediante verificaciones, permitiendo acceder a propiedades especificas de forma segura.

union-narrowing.tsTS
// Union type — puede ser cualquiera de los tipos
type EntradaBusqueda = string | number | string[];

function buscar(query: EntradaBusqueda) {
  if (typeof query === "string") {
    return db.buscarPorNombre(query);   // narrowed: string
  }
  if (typeof query === "number") {
    return db.buscarPorId(query);       // narrowed: number
  }
  return db.buscarMultiples(query);    // narrowed: string[]
}

// Discriminated unions — patron muy comun en ERP
type EventoVenta =
  | { tipo: "creada";    venta: Venta;   sucursalId: string }
  | { tipo: "cancelada"; ventaId: string; razon: string  }
  | { tipo: "pagada";    ventaId: string; monto: number  }
  | { tipo: "devuelta";  ventaId: string; items: string[] };

function manejarEvento(ev: EventoVenta): void {
  switch (ev.tipo) {
    case "creada":
      registrarVenta(ev.venta);            // ev.venta disponible
      break;
    case "cancelada":
      cancelarVenta(ev.ventaId, ev.razon); // ev.razon disponible
      break;
    case "pagada":
      procesarPago(ev.ventaId, ev.monto);  // ev.monto disponible
      break;
    default:
      const _exhaustive: never = ev; // Error si falta un caso
  }
}

// Type guards personalizados — is keyword
function esProductoCarne(p: Producto): p is ProductoCarne {
  return ["res", "cerdo", "aves"].includes((p as ProductoCarne).categoria);
}

function procesarProducto(p: Producto) {
  if (esProductoCarne(p)) {
    console.log(p.categoria, p.corte); // narrowed: ProductoCarne
  }
}

// Nullish handling — muy comun con datos de BD
function getNombreCliente(venta: Venta): string {
  return venta.cliente?.nombre ?? "Cliente mostrador";
}

// Assertion functions — asegurar tipos en runtime
function assertDefined<T>(val: T, msg: string): asserts val is NonNullable<T> {
  if (val === null || val === undefined) throw new Error(msg);
}
ℹ️
Discriminated unions en el ERPSon el patron ideal para modelar eventos, estados de documentos (factura pendiente/timbrada/cancelada) o resultados de operaciones. TypeScript verifica en tiempo de compilacion que el switch maneja todos los casos posibles gracias al tipo never en el default.

// 07 — UTILITY TYPES

Utility Types

TypeScript incluye tipos de utilidad predefinidos para transformar tipos existentes. Evitan duplicar definiciones y hacen el codigo mas DRY.

utility-types.ts — los mas usados en el ERPTS5
interface Producto {
  id: string; nombre: string; precio: number; stock: number; activo: boolean;
}

// Partial<T> — todos los campos opcionales (para PATCH/update)
type ActualizarProducto = Partial<Producto>;
const update: ActualizarProducto = { precio: 195 }; // ok, resto opcional

// Required<T> — todos los campos obligatorios
type ProductoCompleto = Required<Producto>;

// Readonly<T> — todo inmutable (respuestas de API)
type ProductoAPI = Readonly<Producto>;
// productoAPI.precio = 200; // Error: cannot assign to readonly

// Pick<T, Keys> — seleccionar solo algunos campos
type ProductoResumen = Pick<Producto, "id" | "nombre" | "precio">;
type CrearProducto   = Pick<Producto, "nombre" | "precio" | "stock">;

// Omit<T, Keys> — excluir campos
type ProductoSinId = Omit<Producto, "id">;
type ProductoPublico = Omit<Producto, "stock" | "activo">;

// Record<Keys, Type> — mapa tipado
type StockPorSucursal = Record<string, number>;
type PermisoPorRol    = Record<"admin"|"cajero"|"bodeguero", string[]>;

// Exclude / Extract — filtrar uniones
type EstadoActivo = Exclude<EstadoVenta, "cancelada" | "devuelta">;
// = "pendiente" | "pagada"

// ReturnType / Parameters — extraer tipos de funciones
type ResultadoCalculo  = ReturnType<typeof calcularLinea>;
type ParamsCalculadora = Parameters<typeof calcularLinea>;

// NonNullable — elimina null y undefined de un tipo
type ClienteDefinido = NonNullable<Venta["cliente"]>;

// Awaited<T> (TS 4.5+) — tipo que devuelve una Promise
type ProductoDB = Awaited<ReturnType<typeof obtenerProducto>>;
Utility TypeTransformacionUso tipico en el ERP
Partial<T>Todos opcionalesBody de PATCH, updates parciales
Required<T>Todos obligatoriosValidacion antes de guardar en BD
Readonly<T>Todos readonlyRespuestas de API, datos en cache
Pick<T, K>Solo los campos KDTOs, vistas de lista
Omit<T, K>Sin los campos KInput de creacion sin id/timestamps
Record<K, V>Mapa K->VStock por sucursal, permisos por rol
ReturnType<F>Tipo de retornoInferir tipo de resultado de servicio

// 08 — CLASES TIPADAS

Clases Tipadas

TypeScript potencia las clases ES6 con modificadores de acceso, campos privados, implementacion de interfaces y tipos de retorno verificados por el compilador.

clases.ts — entidades y servicios del ERPTS
// Clase con modificadores de acceso
class EntidadBase {
  readonly id: string;
  protected creadoEn: Date;
  private _version: number = 1;

  constructor(id?: string) {
    this.id = id ?? crypto.randomUUID();
    this.creadoEn = new Date();
  }

  get version(): number { return this._version; }

  toJSON(): Record<string, unknown> {
    return { id: this.id, creadoEn: this.creadoEn };
  }
}

// Clase hija que implementa una interface
interface IProducto {
  calcularPrecioFinal(cantidad: number): number;
  aplicarDescuento(pct: number): this;
}

class Producto extends EntidadBase implements IProducto {
  private _precio: number;
  private _descuento: number = 0;

  constructor(
    public readonly nombre: string,   // shorthand: crea y asigna
    precio: number,
    public readonly unidad: string = "kg",
  ) {
    super();
    this._precio = precio;
  }

  get precio(): number { return this._precio; }
  set precio(v: number) {
    if (v < 0) throw new Error("Precio no puede ser negativo");
    this._precio = v;
  }

  calcularPrecioFinal(cantidad: number): number {
    const base = this._precio * cantidad * (1 - this._descuento);
    return base * 1.16;  // IVA incluido
  }

  aplicarDescuento(pct: number): this {
    this._descuento = pct / 100;
    return this;  // fluent API
  }

  static desdeBD(row: Record<string, unknown>): Producto {
    const p = new Producto(row.nombre as string, row.precio as number);
    return p;
  }
}

// Uso: fluent API
const p = new Producto("Arrachera", 189)
  .aplicarDescuento(10);

console.log(p.calcularPrecioFinal(2)); // 189*2*0.9*1.16

// 04

Funciones Tipadas

Tipar parametros y retorno de funciones es donde TypeScript da mas valor β€” el compilador verifica cada llamada en todo el proyecto.

funciones.tsTS
// Parametros y retorno tipados
function calcularTotal(precio: number, cantidad: number): number {
  return precio * cantidad;
}

// Parametros opcionales y con valor por defecto
function formatPrecio(
  valor: number,
  moneda: string = "MXN",    // default
  decimales?: number          // opcional
): string {
  return new Intl.NumberFormat("es-MX", {
    style: "currency", currency: moneda,
    minimumFractionDigits: decimales ?? 2,
  }).format(valor);
}

// Arrow functions tipadas
const conIVA = (precio: number): number => precio * 1.16;

// Funcion que retorna void (no retorna valor util)
const logVenta = (ventaId: string): void => {
  console.log(`Venta registrada: ${ventaId}`);
};

// Tipado de funciones como valor (Function type)
type Transformador = (valor: number) => number;
const aplicarDescuento: Transformador = (v) => v * 0.9;

// Sobrecarga de funciones (overloads)
function buscarProducto(id: number): Producto;
function buscarProducto(codigo: string): Producto;
function buscarProducto(param: number | string): Producto {
  if (typeof param === "number") {
    return buscarPorId(param);
  }
  return buscarPorCodigo(param);
}

// Rest parameters tipados
function sumarPrecios(...precios: number[]): number {
  return precios.reduce((a, b) => a + b, 0);
}

// Callbacks tipados
function procesarItems(
  items: Producto[],
  callback: (item: Producto, index: number) => void
): void {
  items.forEach(callback);
}

// 05

Generics β€” Tipos Reutilizables

Los generics permiten escribir codigo que funciona con cualquier tipo sin perder la seguridad. Son el mecanismo principal de reutilizacion en TypeScript.

generics.tsADV
// Generic basico β€” T es un "tipo parametro"
function primerElemento<T>(array: T[]): T | undefined {
  return array[0];
}
const p1 = primerElemento([1, 2, 3]);       // inferido: number | undefined
const p2 = primerElemento(["a", "b"]);      // inferido: string | undefined

// Respuesta API generica β€” patron muy usado en el ERP
interface ApiResponse<T> {
  data:    T;
  total?:  number;
  pagina?: number;
  ok:      boolean;
  error?:  string;
}

type ProductosResponse = ApiResponse<Producto[]>;
type VentaResponse    = ApiResponse<Venta>;

// Restricciones con extends
interface ConId { id: string }

function buscarPorId<T extends ConId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}
// Funciona con cualquier objeto que tenga .id
buscarPorId(productos, "prod-001");
buscarPorId(ventas,    "vta-042");

// Multiples parametros de tipo
function mapeo<K extends string, V>(keys: K[], val: V): Record<K, V> {
  return Object.fromEntries(keys.map(k => [k, val])) as Record<K, V>;
}

// Clase generica β€” repositorio generico del ERP
class Repositorio<T extends ConId> {
  private items: Map<string, T> = new Map();

  guardar(item: T): void {
    this.items.set(item.id, item);
  }
  buscar(id: string): T | undefined {
    return this.items.get(id);
  }
  todos(): T[] {
    return [...this.items.values()];
  }
}

const repoProductos = new Repositorio<Producto>();
const repoVentas    = new Repositorio<Venta>();
🧬
Generics vs anyAmbos aceptan cualquier tipo, pero con any pierdes toda seguridad. Con generics, TypeScript rastrea el tipo real a lo largo del flujo β€” si pasas Producto[], sabe que el retorno tambien es Producto[].

// 06

Union Types y Narrowing

Un union type es "puede ser A o B". El narrowing es como TS detecta cual de los tipos es en cada rama del codigo β€” usando typeof, instanceof, discriminant o type guards.

narrowing.tsTS
// ── Union types ───────────────────────────────────────
type StringOrNumber = string | number;
type NullableString = string | null;

// ── typeof narrowing ──────────────────────────────────
function formatId(id: string | number): string {
  if (typeof id === "number") {
    return id.toString().padStart(6, "0");  // TS sabe: id es number aqui
  }
  return id.toUpperCase();                   // TS sabe: id es string aqui
}

// ── Discriminated unions β€” patron clave del ERP ───────
interface PagoEfectivo {
  tipo:   "efectivo";
  monto:  number;
}
interface PagoTarjeta {
  tipo:        "tarjeta";
  monto:       number;
  ultimos4:    string;
  autorizacion:string;
}
interface PagoTransferencia {
  tipo:    "transferencia";
  monto:   number;
  clabe:   string;
  banco:   string;
}
type Pago = PagoEfectivo | PagoTarjeta | PagoTransferencia;

function procesarPago(pago: Pago): string {
  switch (pago.tipo) {
    case "efectivo":
      return `Efectivo: $${pago.monto}`;
    case "tarjeta":
      return `Tarjeta ***${pago.ultimos4} auth:${pago.autorizacion}`;
    case "transferencia":
      return `SPEI ${pago.banco} CLABE:${pago.clabe}`;
    default:
      const _exhaustive: never = pago; // error si falta un caso
      return _exhaustive;
  }
}

// ── Type guards personalizados ────────────────────────
function esPagoTarjeta(pago: Pago): pago is PagoTarjeta {
  return pago.tipo === "tarjeta";
}

// ── Nullish narrowing ─────────────────────────────────
function obtenerNombre(p: Producto | null): string {
  if (!p) return "Sin producto";
  return p.nombre;                    // TS sabe: p no es null aqui
}

// ── Optional chaining + nullish coalescing ────────────
const ciudad = cliente?.direccion?.ciudad ?? "Sin ciudad";
Demo β€” discriminated unions y type guardsβ–Ά LIVE
// Discriminated unions en JS function procesarPago(pago) { switch (pago.tipo) { case "efectivo": return "Efectivo: $" + pago.monto; case "tarjeta": return "Tarjeta ***" + pago.ultimos4; case "transferencia": return "SPEI CLABE:" + pago.clabe; default: return "Tipo desconocido"; } } const pagos = [ { tipo: "efectivo", monto: 450 }, { tipo: "tarjeta", monto: 320, ultimos4: "4291" }, { tipo: "transferencia", monto: 1800, clabe: "0021..." } ]; pagos.forEach(p => console.log(procesarPago(p)));
// outputHaz clic en Ejecutar...

// 07

Utility Types

TS incluye tipos de utilidad que transforman tipos existentes. Evitan duplicar definiciones y son esenciales en APIs REST β€” DTO de creacion, actualizacion parcial, solo lectura.

utility-types.ts β€” patrones del ERPTS5
interface Producto {
  id:       string;
  nombre:   string;
  precio:   number;
  stock:    number;
  activo:   boolean;
  creadoEn: Date;
}

// Partial β€” todas las propiedades opcionales (PATCH/update)
type ActualizarProducto = Partial<Producto>;
// { id?: string; nombre?: string; precio?: number; ... }

// Required β€” todas obligatorias (inversion de Partial)
type ProductoCompleto = Required<Producto>;

// Readonly β€” ninguna propiedad modificable
type ProductoFijo = Readonly<Producto>;

// Pick β€” seleccionar solo algunas propiedades
type ProductoLista = Pick<Producto, "id" | "nombre" | "precio">;
// { id: string; nombre: string; precio: number }

// Omit β€” excluir algunas propiedades (DTO de creacion)
type CrearProducto = Omit<Producto, "id" | "creadoEn">;
// { nombre: string; precio: number; stock: number; activo: boolean }

// Record β€” objeto con claves y valores tipados
type StockPorSucursal = Record<string, number>;
type CacheProductos   = Record<string, Producto>;

const stock: StockPorSucursal = {
  "suc-01": 42, "suc-02": 18, "suc-03": 5
};

// Exclude / Extract β€” filtrar unions
type EstadoVenta  = "pendiente" | "pagada" | "cancelada" | "devuelta";
type EstadoFinal  = Extract<EstadoVenta, "pagada" | "cancelada">;   // "pagada" | "cancelada"
type EstadoActivo = Exclude<EstadoVenta, "cancelada" | "devuelta">;  // "pendiente" | "pagada"

// NonNullable β€” elimina null y undefined
type ClienteSeguro = NonNullable<Cliente | null | undefined>;  // Cliente

// ReturnType β€” extrae el tipo de retorno de una funcion
function crearVenta() { return { id: "", total: 0, items: [] }; }
type Venta = ReturnType<typeof crearVenta>;

// Parameters β€” extrae parametros de una funcion
type ParamsCalcular = Parameters<typeof calcularTotal>;
// [precio: number, cantidad: number]

// Awaited β€” extrae el tipo resuelto de una Promise (TS 4.5+)
type ResultadoDB = Awaited<ReturnType<typeof buscarProductoAsync>>;
Utility TypeUso tipico en ERP
Partial<T>DTO de actualizacion parcial (PATCH endpoints)
Omit<T, K>DTO de creacion (sin id ni timestamps)
Pick<T, K>Proyecciones para listas (solo campos necesarios)
Record<K, V>Cache de productos, stock por sucursal, mapas
ReturnType<F>Tipar resultados de servicios sin duplicar interfaces
Awaited<T>Extraer el tipo resuelto de promesas de BD

// 08

Clases Tipadas

Las clases TS amplian las clases JS con modificadores de acceso (public/private/protected), campos privados verdaderos con #, propiedades readonly y herencia fuertemente tipada.

clases.ts β€” entidades del dominio ERPTS
abstract class EntidadBase {
  readonly id: string;
  readonly creadoEn: Date;
  actualizadoEn: Date;

  constructor(id?: string) {
    this.id          = id ?? crypto.randomUUID();
    this.creadoEn    = new Date();
    this.actualizadoEn = new Date();
  }

  abstract validar(): boolean;   // subclases deben implementar

  toJSON(): Record<string, unknown> {
    return { id: this.id, creadoEn: this.creadoEn };
  }
}

class Producto extends EntidadBase {
  // Campo privado β€” verdaderamente inaccesible fuera (ES2022)
  #stockInterno: number;

  constructor(
    public readonly nombre: string,     // shorthand: crea y asigna en constructor
    public          precio: number,
    private         codigoBarra: string,
    stockInicial: number = 0
  ) {
    super();
    this.#stockInterno = stockInicial;
  }

  // Getter/setter tipados
  get stock(): number { return this.#stockInterno; }

  set stock(cantidad: number) {
    if (cantidad < 0) throw new Error("Stock no puede ser negativo");
    this.#stockInterno = cantidad;
    this.actualizadoEn = new Date();
  }

  precioConIVA(): number { return this.precio * 1.16; }

  static desdeBD(row: Record<string, unknown>): Producto {
    const p = new Producto(
      row.nombre as string,
      row.precio as number,
      row.codigo_barra as string,
      row.stock as number
    );
    return p;
  }

  validar(): boolean {
    return this.nombre.length > 0 && this.precio > 0;
  }
}

// Interfaces para clases β€” contrato publico
interface IServicio<T> {
  buscarPorId(id: string): Promise<T | null>;
  listar(filtros?: Partial<T>): Promise<T[]>;
  crear(datos: Omit<T, "id">): Promise<T>;
  actualizar(id: string, datos: Partial<T>): Promise<T>;
  eliminar(id: string): Promise<void>;
}

class ProductosService implements IServicio<Producto> {
  // TS verifica que implementa TODOS los metodos de la interfaz
  async buscarPorId(id: string): Promise<Producto | null> { return null; }
  async listar(): Promise<Producto[]> { return []; }
  async crear(datos: Omit<Producto, "id">): Promise<Producto> { return datos as Producto; }
  async actualizar(id: string, datos: Partial<Producto>): Promise<Producto> { return datos as Producto; }
  async eliminar(id: string): Promise<void> { }
}

// 09

Modulos y tsconfig

tsconfig.json controla como TS compila tu codigo. Con strict: true activas 8 verificaciones que previenen la mayoria de bugs comunes. La configuracion correcta es la base del proyecto.

tsconfig.json β€” configuracion recomendada para Node 20 + ESMCONFIG
{
  "compilerOptions": {
    // Target y formato de salida
    "target":       "ES2022",
    "module":       "NodeNext",    // ESM nativo de Node 20
    "moduleResolution": "NodeNext",
    "outDir":       "./dist",
    "rootDir":      "./src",

    // Strict mode β€” SIEMPRE activar en proyectos nuevos
    "strict":                       true,  // activa los 8 de abajo
    "noImplicitAny":                true,  // parametros sin tipo -> error
    "strictNullChecks":             true,  // null/undefined separados
    "strictFunctionTypes":          true,
    "strictPropertyInitialization": true,  // propiedades inicializadas
    "noUncheckedIndexedAccess":     true,  // array[i] puede ser undefined

    // Calidad extra
    "noUnusedLocals":       true,
    "noUnusedParameters":   true,
    "noImplicitReturns":    true,
    "exactOptionalPropertyTypes": true,

    // Utilidades
    "sourceMap":      true,
    "declaration":    true,    // genera .d.ts
    "esModuleInterop":true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,  // import config from './config.json'

    // Para path aliases (@/services/...)
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
package.json β€” scripts de compilacionNPM
{
  "scripts": {
    "build":     "tsc",
    "dev":       "tsx watch src/index.ts",     // tsx: ts-node alternativa mas rapida
    "typecheck": "tsc --noEmit",              // solo verificar, no compilar
    "start":     "node dist/index.js"
  },
  "devDependencies": {
    "typescript": "^5.4.0",
    "tsx":        "^4.0.0",       // ejecuta TS directamente en Node
    "@types/node":"^20.0.0"      // tipos de Node.js
  }
}
πŸ’‘
tsx en lugar de ts-node para desarrollotsx usa esbuild internamente y es 10-20x mas rapido que ts-node para arrancar. Para produccion siempre compila con tsc y ejecuta el JS generado β€” nunca corras TS directamente en prod.

// 10

Async Tipado

Promise y async/await en TypeScript llevan el tipo del valor resuelto. Promise<Producto> garantiza que al hacer await siempre obtienes un Producto β€” no any.

async-tipado.tsTS
// Promise tipada β€” T es el tipo resuelto
async function buscarProducto(id: string): Promise<Producto | null> {
  const row = await db.query('SELECT * FROM productos WHERE id=$1', [id]);
  return row ? Producto.desdeBD(row) : null;
}

async function listarProductos(filtros?: {
  activo?: boolean;
  limite?: number;
  offset?: number;
}): Promise<{ data: Producto[]; total: number }> {
  const { activo = true, limite = 20, offset = 0 } = filtros ?? {};
  const [data, total] = await Promise.all([
    db.queryRows<Producto>('SELECT * FROM productos WHERE activo=$1 LIMIT $2 OFFSET $3', [activo, limite, offset]),
    db.queryOne<{ count: number }>('SELECT COUNT(*) FROM productos WHERE activo=$1', [activo]),
  ]);
  return { data, total: total?.count ?? 0 };
}

// Result type β€” alternativa a try/catch en toda la cadena
type Ok<T>  = { ok: true;  data: T };
type Err<E> = { ok: false; error: E };
type Result<T, E = string> = Ok<T> | Err<E>;

async function registrarVenta(dto: CrearVenta): Promise<Result<Venta>> {
  try {
    const venta = await ventasService.crear(dto);
    return { ok: true, data: venta };
  } catch (e) {
    return { ok: false, error: e instanceof Error ? e.message : "Error desconocido" };
  }
}

// Uso con narrowing automatico
const resultado = await registrarVenta(dto);
if (resultado.ok) {
  console.log(resultado.data.id);    // TS sabe: data es Venta
} else {
  console.error(resultado.error);    // TS sabe: error es string
}

// Tipar respuestas de APIs externas con zod (validacion en runtime)
// import { z } from 'zod';
// const ProductoSchema = z.object({ id: z.string(), precio: z.number() });
// type ProductoAPI = z.infer<typeof ProductoSchema>;
Demo β€” Result type patternβ–Ά LIVE
// Result type en JS puro async function registrarVenta(dto) { try { // Simular validacion if (!dto.clienteId) throw new Error("clienteId requerido"); if (!dto.items?.length) throw new Error("items no puede estar vacio"); const total = dto.items.reduce((s, i) => s + i.precio * i.cantidad, 0); return { ok: true, data: { id: "vta-" + Date.now(), total, ...dto } }; } catch(e) { return { ok: false, error: e.message }; } } async function main() { const r1 = await registrarVenta({ clienteId: "c-01", items: [{ precio: 189, cantidad: 2 }] }); const r2 = await registrarVenta({ items: [{ precio: 100, cantidad: 1 }] }); const r3 = await registrarVenta({ clienteId: "c-02", items: [] }); [r1, r2, r3].forEach(r => { if (r.ok) console.log("OK venta:", r.data.id, "total:", r.data.total); else console.log("ERROR:", r.error); }); } main();
// outputHaz clic en Ejecutar...

// 11

Patrones Avanzados

Mapped types, conditional types y template literal types son las herramientas de metaprogramacion de TypeScript β€” generan tipos a partir de otros tipos automaticamente.

patrones-avanzados.tsADV
// ── Mapped types β€” transformar cada propiedad ─────────
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Getters<T>  = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };

// Template Literal Types β€” generar nombres de eventos
type Entidad  = "venta" | "producto" | "cliente";
type Accion   = "creado" | "actualizado" | "eliminado";
type Evento   = `${Entidad}.${Accion}`;
// "venta.creado" | "venta.actualizado" | ... | "cliente.eliminado"

function onEvento(evento: Evento, handler: () => void): void { /* ... */ }
onEvento("venta.creado", handler);      // ok
// onEvento("sucursal.creado", handler); // Error β€” no es Evento valido

// Conditional types β€” tipo que depende de una condicion
type IsArray<T> = T extends any[] ? true : false;
type Unwrap<T>  = T extends Promise<infer U> ? U : T;
// Unwrap<Promise<Producto>> β†’ Producto
// Unwrap<string>           β†’ string

// keyof y typeof β€” reflexion de tipos
const CONFIG = { host: "localhost", port: 5432, ssl: false } as const;
type ConfigKey   = keyof typeof CONFIG;  // "host" | "port" | "ssl"
type ConfigValue = (typeof CONFIG)[ConfigKey]; // string | number | boolean

// Satisfies β€” verifica tipo sin perder informacion
const roles = {
  admin:   ["ventas.crear", "productos.editar", "reportes.ver"],
  cajero:  ["ventas.crear"],
  gerente: ["reportes.ver", "ventas.crear"],
} satisfies Record<string, string[]>;

roles.admin  // TS sabe que es string[], no solo string[]
roles.cajero // acceso seguro por clave literal

// Decoradores (experimental, Moleculer los usa con reflect-metadata)
// @Service({ name: "productos" })
// class ProductosService { ... }
⚠️
No sobreingenieres los tiposLos tipos avanzados son poderosos pero pueden hacer el codigo difΓ­cil de leer. Prioridad: tipos simples y claros que comunican intencion. Los mapped types y conditional types son para librerias y utilidades compartidas β€” no para cada modelo del negocio.

// 12

TypeScript en el ERP CarnicerΓ­as

El dominio real del ERP tipado de principio a fin: entidades, DTOs, servicios y eventos. Esto es lo que el compilador verifica en cada commit.

src/domain/ β€” tipos del dominio ERPREAL
// ── types/dominio.ts β€” fuente unica de verdad ─────────

export type ID      = string;
export type Dinero  = number;  // siempre en pesos MXN
export type Kilos   = number;

export interface Auditado {
  creadoEn:      Date;
  actualizadoEn: Date;
  creadoPor:     ID;
}

export interface Producto extends Auditado {
  id:          ID;
  nombre:      string;
  codigo:      string;
  precio:      Dinero;
  unidad:      "kg" | "pieza" | "litro";
  categoria:   "res" | "cerdo" | "pollo" | "embutido" | "otro";
  perecedero:  boolean;
  activo:      boolean;
}

export interface LineaVenta {
  productoId: ID;
  nombre:     string;
  cantidad:   number;
  precio:     Dinero;
  descuento:  number;   // 0-1
  subtotal:   Dinero;
  iva:        Dinero;
  total:      Dinero;
}

export type MetodoPago  = "efectivo" | "tarjeta" | "transferencia" | "credito";
export type EstadoVenta = "abierta"  | "pagada"  | "cancelada"     | "devuelta";

export interface Venta extends Auditado {
  id:          ID;
  sucursalId:  ID;
  clienteId?:  ID;
  items:       LineaVenta[];
  subtotal:    Dinero;
  descuentos:  Dinero;
  iva:         Dinero;
  total:       Dinero;
  metodoPago:  MetodoPago;
  estado:      EstadoVenta;
  folio:       string;
  cfdiUUID?:   string;
}

// ── DTOs de entrada ───────────────────────────────────
export type CrearVentaDTO = Pick<Venta,
  "sucursalId" | "clienteId" | "metodoPago"
> & {
  items: Array<{
    productoId: ID;
    cantidad:   number;
    descuento?: number;
  }>;
};

// ── Servicio tipado con Result ─────────────────────────
type Result<T> = { ok: true; data: T } | { ok: false; error: string; code: number };

export interface IVentasService {
  registrar(dto: CrearVentaDTO): Promise<Result<Venta>>;
  cancelar(id: ID, motivo: string): Promise<Result<Venta>>;
  listar(filtros: {
    sucursalId?: ID;
    fechaDesde?: Date;
    fechaHasta?: Date;
    estado?:     EstadoVenta;
    pagina?:     number;
    limite?:     number;
  }): Promise<{ data: Venta[]; total: number }>;
}

// ── Eventos tipados para Moleculer ────────────────────
export type EventoVenta =
  | { tipo: "venta.registrada"; venta: Venta }
  | { tipo: "venta.cancelada";  ventaId: ID; motivo: string }
  | { tipo: "venta.cfdi.emitido"; ventaId: ID; uuid: string };
πŸ₯©
Con esto dominas el stack completoTypeScript es la capa que conecta todo: los servicios Moleculer tienen tipos en sus acciones, BullMQ tiene jobs tipados, las queries de PostgreSQL retornan tipos conocidos. El compilador es tu primer test β€” si pasa tsc, la mayoria de los bugs de integracion ya estan resueltos antes de ejecutar.