Skip to content

💡 Conceptos — TypeScript

Conceptos clave explicados con mis propias palabras.

🅰️ Variables y tipos base

En TypeScript declaro una variable con let y le anoto de qué tipo es con : tipo:

typescript
let nombre: string = "Styp";        // texto
let edad: number = 30;              // números (enteros o decimales)
let esDesarrollador: boolean = true; // verdadero / falso

El : tipo es una promesa al compilador: "esta variable solo guardará ese tipo de dato". Si intento meter otra cosa (edad = "treinta"), TypeScript me avisa antes de ejecutar. Eso es lo que JavaScript normal NO hace.

TipoQué guardaEjemplo
stringTexto"hola", "Styp"
numberNúmeros (enteros y decimales)30, 3.14
booleanVerdadero o falsotrue, false

🧪 Entrevista: ¿Qué aporta TypeScript sobre JavaScript? Los tipos: detecta errores en tiempo de desarrollo (antes de correr), da autocompletado y documenta el código. Al compilar, los tipos desaparecen y queda JS normal.

🅰️ El tipo any (Clase 2)

any = "cualquier cosa". Una variable any acepta cualquier tipo y TypeScript deja de revisarla (es como apagar TS para esa variable).

typescript
let variableAny: any;
variableAny = "Hola, mundo!";   // ✅ string
variableAny = 42;               // ✅ number  (también válido)
variableAny = true;             // ✅ boolean (también válido)
Tipo¿TS lo revisa?Acepta
string / number / boolean✅ SíSolo ese tipo
any❌ NoCualquier cosa

⚠️ Evita any: anula la ventaja de TS (detectar errores de tipo). Úsalo solo en casos puntuales. Alternativa segura: unknown (acepta todo pero obliga a comprobar el tipo antes de usarlo). → ver Clase 2

🔒 unknown, void y never (Clase 2)

Otros tipos "diferentes" (detalle en Clase 2):

TipoQué esEjemplo
unknownComo any, pero obliga a comprobar el tipo antes de usarlo (con typeof). Es el any seguro.let x: unknown
voidTipo de retorno de una función que no devuelve nadafunction f(): void {}
neverTipo de retorno de una función que nunca termina (siempre lanza error)function f(): never { throw ... }
typescript
let x: unknown = 42;
if (typeof x === "number") {   // typeof = compruebo el tipo en ejecución
    console.log(x + 1);        // aquí TS ya sabe que es number
}

🧪 Entrevista: any vs unknown → los dos aceptan todo, pero unknown es seguro (obliga a verificar el tipo). void vs nevervoid termina sin devolver valor; never no llega a terminar (rompe con error).

🕳️ undefined vs null

Ambos representan "vacío", pero no son lo mismo:

typescript
let mes: string | undefined;   // declarada SIN valor → vale undefined solo
let variableNull: null = null; // vacío puesto A PROPÓSITO por mí
ValorQué significaQuién lo pone
undefined"Sin definir": declaré la variable pero no le di valor.Lo pone JavaScript solo
null"Vacío intencional": aquí no hay nada, y lo decidí yo.Lo pongo yo a mano

⚠️ Una variable sin valor sí se puede imprimir: sale la palabra undefined, no queda en blanco. console.log(mes)undefined. Eso NO es un error.

🧩 Union types (string | undefined)

El símbolo | significa "o". string | undefined = "esta variable puede ser un texto o estar sin definir".

typescript
let mes: string | undefined;  // puede ser "Julio" o undefined
mes = "Julio";                // ✅ válido
mes = undefined;              // ✅ válido
mes = 30;                     // ❌ error: number no está en la unión

🔤 Concatenación vs Template strings

Dos formas de armar un texto con variables dentro:

typescript
// 1) Concatenación con + (la vieja escuela)
console.log("Hola, " + nombre + "!");

// 2) Template string con backticks ` ` y ${...}  (más limpia)
console.log(`Hola, ${nombre}!`);

Las dos imprimen Hola, Styp!. La segunda usa backticks (`, no comillas normales) y ${variable} para insertar valores. Es más legible, sobre todo con varias variables.

💡 El backtick está en el teclado normalmente arriba a la izquierda (tecla junto al 1). ${...} solo funciona dentro de backticks, no dentro de "..." ni '...'.

📚 Arrays, tuplas, interfaces y enums (Clase 3)

Resumen (detalle en Clase 3):

ConceptoCómo se escribeQué es
Arraylet x: number[] = [1,2,3]Lista de UN mismo tipo, tamaño variable
Array mixto(string | number)[]Lista donde cada elemento puede ser varios tipos
Tupla[string, number]Lista de tamaño fijo, un tipo por posición
Array de tuplas[string, number][]Lista de tuplas
Interfaceinterface P { nombre: string }Molde que define la forma de un objeto
Array de objetoslet ps: P[] = [...]Lista de objetos que cumplen la interface
Enumenum Dia { Lunes, Martes }Nombres para un conjunto de valores (numéricos por defecto, desde 0)
typescript
interface Persona { nombre: string; edad: number; }
let personas: Persona[] = [{ nombre: "Juan", edad: 25 }];

enum Dia { Lunes, Martes, Miercoles }   // 0, 1, 2
let d: Dia = Dia.Miercoles;
console.log(d);          // 2         (nombre → número)
console.log(Dia[d]);     // Miercoles (número → nombre, "reverse mapping")

⚠️ Una tupla NO es 100% inmutable (TS permite reasignar y push); lo fijo es su estructura. Para inmutabilidad real: readonly [string, number].

🧪 Entrevista: interface describe la forma de un objeto (contrato). enum reemplaza "números mágicos" por nombres legibles. Un enum numérico tiene mapeo inverso (número → nombre).

🧩 Funciones / métodos (Clase 4)

Se tipan los parámetros y el valor de retorno (detalle en Clase 4):

typescript
function sumar(a: number, b: number): number {   // params tipados : tipo de retorno
  return a + b;
}

function imprimir(msg: string): void {   // void = no devuelve nada
  console.log(msg);
}
EscrituraQué es
(a: number, b: number)Parámetros obligatorios con su tipo
: number / : voidTipo que devuelve (void = nada)
saludo?: stringParámetro opcional (puede faltar → undefined)
saludo: string = "Hola"Parámetro por defecto (usa ese valor si falta)
...numeros: number[]Parámetro rest: junta N argumentos en un array (va al final)
const f = (a: number) => a * 2Arrow function (forma corta)
function f(a: string): string;
function f(a: number): number;
Sobrecarga: varias firmas + una implementación (con typeof distingue casos)

📝 ? vs !: ? hace un parámetro opcional. ! NO es marcador de parámetro (da error de sintaxis); es el operador non-null assertion sobre un valor (x!.length = "confía, no es null/undefined"). Los parámetros ya son obligatorios por defecto.

⚠️ Los parámetros opcionales van al final, después de los obligatorios.

🧱 Interfaces avanzadas (Clase 5)

Además del uso básico (ver tabla de Clase 3), una interface admite (detalle en Clase 5):

typescript
interface Persona {
  readonly nombre: string;       // solo asignable una vez
  edad: number;
  lenguajeFavorito?: string;     // opcional
}

interface Empleado extends Persona {   // hereda todo Persona + agrega lo suyo
  readonly puesto: string;
}

interface Sumar {                       // interface para la FIRMA de una función
  (a: number, b: number): number;
}
EscrituraQué es
lenguajeFavorito?: stringPropiedad opcional (puede faltar)
readonly nombre: stringPropiedad de solo lectura: reasignarla es error TS2540
interface B extends A { ... }B hereda las propiedades de A y suma las suyas
interface F { (a: T): R }Interface que describe la firma de una función

📝 readonlyconst: const bloquea reasignar la variable; readonly bloquea reasignar una propiedad del objeto. Ambos son chequeos de TS que desaparecen al compilar.

🔐 Modificadores de acceso (Clase 7)

Los modificadores de acceso controlan desde dónde se puede usar una propiedad o método de una clase. Son clave para el encapsulamiento.

typescript
class Persona {
  public name: string;
  protected age: number;
  private isDeveloper: boolean;

  constructor(name: string, age: number, isDeveloper: boolean) {
    this.name = name;
    this.age = age;
    this.isDeveloper = isDeveloper;
  }

  public saludar(): string {
    return `Hola, me llamo ${this.name}`;
  }

  protected obtenerEdad(): number {
    return this.age;
  }

  private esDesarrollador(): boolean {
    return this.isDeveloper;
  }
}
ModificadorDesde fuera de la claseUso típico
public✅ SíAPI visible de la clase
protected❌ NoMecánicas internas no visibles externamente
private❌ NoDatos y lógica totalmente interna
typescript
const persona = new Persona("Juan", 30, true);

console.log(persona.name);      // ✅
console.log(persona.saludar()); // ✅

// console.log(persona.age);           // ❌ protected
// console.log(persona.obtenerEdad()); // ❌ protected
// console.log(persona.isDeveloper);   // ❌ private

💡 Regla útil: empieza con lo más restrictivo posible y solo haz público lo que de verdad necesites exponer.

🧬 Herencia (extends) (Clase 8)

La herencia permite crear una clase nueva a partir de una clase base, reutilizando lo que ya existe y agregando solo lo nuevo.

typescript
class Persona {
  public nombre: string;

  constructor(nombre: string) {
    this.nombre = nombre;
  }

  public saludar(): string {
    return `Hola, soy ${this.nombre}`;
  }
}

class Empleado extends Persona {
  public puesto: string;

  constructor(nombre: string, puesto: string) {
    super(nombre);
    this.puesto = puesto;
  }
}
ConceptoQué hace
extendsHace que una clase herede de otra
super(...)Llama al constructor de la clase padre
Clase baseLa clase original que aporta propiedades y métodos
Clase hijaLa clase que hereda y además agrega o redefine comportamiento
typescript
class Empleado extends Persona {
  public saludar(): string {
    return `Hola, trabajo como ${this.puesto}`;
  }
}

💡 La herencia evita duplicar código. Tomas una base que ya funciona y construyes una versión más específica encima.

🧬 Genéricos (<T>) (Clase 9)

Un genérico es un parámetro de tipo: permite escribir funciones y clases reutilizables para cualquier tipo, sin perder el chequeo de tipos (a diferencia de any, que lo apaga).

typescript
function identity<T>(arg: T): T {
  return arg;
}

let a = identity<string>("Hola"); // tipo explícito
let b = identity(42);             // TS infiere T = number

class Caja<T> {
  private contenido: T;

  constructor(contenido: T) {
    this.contenido = contenido;
  }

  public obtenerContenido(): T {
    return this.contenido;
  }
}

const caja = new Caja<string>("Libros"); // queda fija a T = string

// Interfaz genérica: describe un CONTRATO, sin fijar el tipo todavía
interface Contenedor<T> {
  contenido: T;
  obtenerContenido(): T;
}

class CajaGenerica<T> implements Contenedor<T> {
  contenido: T;
  constructor(valor: T) { this.contenido = valor; }
  obtenerContenido(): T { return this.contenido; }
}

// La función solo pide "algo que cumpla Contenedor<T>", no una clase concreta
function mostrarContenido<T>(c: Contenedor<T>): T {
  return c.obtenerContenido();
}
ConceptoQué es
<T>Parámetro de tipo: "lo que sea que reciba, eso mismo relaciono"
Tipo explícitoidentity<string>(...) — fijas T a mano
Inferenciaidentity(42) — TS deduce T del argumento
class Caja<T>Genérico aplicado a una clase entera (propiedades, constructor, métodos)
interface Contenedor<T>Genérico aplicado a un contrato: cualquier clase que lo implemente puede tener un T distinto
T extends FormaRestringe qué tipos puede tomar T (debe cumplir esa forma)
Necesito...Uso
Utilidad reutilizable sin estadoFunción genérica
Entidad con estado (guarda datos)Clase genérica
Contrato que varias clases pueden cumplirInterfaz genérica

🧪 Entrevista: any vs genérico → any acepta todo y deja de verificar tipos; un genérico acepta cualquier tipo pero mantiene la relación de tipos entre entrada y salida. La T es solo convención (de "Type"), no palabra reservada.

⚠️ Una instancia genérica queda fija al tipo con que se creó: new Caja<string>(...) no puede luego guardar un number (TypeScript lo marca con TS2322).

🧩 Extender métodos nativos (Clase 10)

Dos técnicas distintas, aunque ambas se llaman "extender":

typescript
// 1) Restringir un genérico a una FORMA (lo que enseña esta clase)
function obtenerLongitud<T extends {length: number}>(obj: T): number {
  return obj.length; // T puede ser string, array, o cualquier cosa con .length
}

// 2) Declaration merging + prototype: agregar un método NUEVO de verdad (bonus)
interface String {
  invertir(): string;
}
String.prototype.invertir = function (): string {
  return this.split("").reverse().join("");
};
TécnicaQué haceRiesgo si te saltas un paso
T extends {propiedad}Restringe qué tipos acepta un genérico, para poder usar esa propiedad con seguridadSi el tipo no cumple, error de compilación (TS2345) — no llega a ejecutarse
Declaration merging + .prototypeLe agrega un método real y nuevo a un tipo nativo (String, Array, etc.)Si solo declaras la interface y olvidas el .prototype, compila bien pero truena en ejecución

⚠️ typeof sobre un array da "object", no "array" — para distinguir arrays de verdad se usa Array.isArray(valor).

📦 Módulos: export / import (Clase 11)

Dividir el código en archivos pequeños (módulos) en vez de un solo archivo gigante (monolito). export expone algo hacia afuera; import { } lo trae a otro archivo.

typescript
// calculator.ts
export function suma(a: number, b: number): number { return a + b; }
export const Pi = 3.1416;

// main.ts
import { suma, Pi } from "./calculator.js"; // .js al final, no .ts
console.log(suma(Pi, 1));
ConceptoQué hace
export function / export constMarca algo como visible fuera de su archivo
import { X } from "./ruta.js"Trae elementos específicos de otro archivo (llaves = puntual)
import X from "./ruta.js" (sin llaves)Importa un export default (uno solo por archivo)
tsc main.tsCompila main.ts y, en cascada, todo lo que main.ts importe

⚠️ Gotcha de proyectos con "type": "module" en package.json (como este): el import necesita la extensión .js completa (from "./calculator.js", no from "./calculator"). TypeScript compila igual sin ella, pero Node truena al ejecutar (ERR_MODULE_NOT_FOUND) porque ESM no adivina la ruta como CommonJS.

🧪 Entrevista: ¿Qué es un monolito? Un archivo que concentra toda la lógica — difícil de mantener, escalar y depurar. Los módulos son la solución: piezas pequeñas, reutilizables, con una responsabilidad clara cada una.

🌐 TypeScript compilado y ejecutado en el navegador (Clase 12)

El navegador solo entiende JavaScript. Un archivo .ts necesita compilarse con tsc a .js antes de que un <script src="..."> pueda invocarlo — nunca se apunta al .ts directamente.

typescript
// main.ts
console.log("Hola desde mi explorador");
html
<!-- index.html -->
<script src="main.js"></script>  <!-- .js, NO .ts -->
bash
tsc main.ts   # genera main.js
ConceptoQué es
tsc archivo.tsCompila a .js — es lo único que el navegador puede ejecutar
Live Server / Live PreviewExtensión de VS Code que sirve el HTML en localhost con recarga automática
DevTools Console (F12)Donde se confirma que el console.log llegó al navegador
document.getElementById(...) as HTMLInputElementCast necesario porque el DOM no sabe de antemano qué tipo exacto de elemento devolvió
<script type="module">Obligatorio para que el navegador entienda import/export (Clase 11) directamente — sin él: SyntaxError: Cannot use import statement outside a module

⚠️ Gotcha #1: editar main.ts sin volver a correr tsc deja el sitio mostrando la versión vieja — el <script> sigue apuntando al .js que no cambió.

⚠️ Gotcha #2: sin módulos, varios <script> comparten un solo scope global y se ejecutan en el orden físico en que aparecen en el HTML — invertir el orden entre quien declara una variable y quien la usa lanza ReferenceError.

🗂️ Organizar un proyecto con src/public y outDir (Clase 13)

Separar código fuente (src/, no se despliega) de archivos públicos (public/, sí se despliega). outDir en tsconfig.json le dice a tsc dónde dejar el .js compilado — resuelto relativo al tsconfig.json, no a tu terminal.

json
// src/tsconfig.json
{ "compilerOptions": { "outDir": "../public/scripts", "target": "ES6", "module": "CommonJS", "strict": true } }
ComandoDesde dónde funciona
tsc (sin nada)Solo parado en la carpeta del tsconfig.json
tsc -p ruta/tsconfig.jsonDesde cualquier carpeta
tsc archivo.tsIgnora el tsconfig.json — o truena con TS5112 si hay uno presente

⚠️ outDir clona la estructura de src/: una subcarpeta src/helpers/x.ts termina en outDir/helpers/x.js, no todo aplanado.

⚠️ module: "CommonJS" + import/export no corre en el navegador, ni con type="module" — se compila a require()/exports (pensado para Node), y el navegador no tiene require(). La solución: module: "ES6" + type="module" en el <script>, o un bundler.

🖱️ Manipular el DOM: querySelector + tipos (Clase 14)

document.querySelector(selector) siempre devuelve Element | null, sin importar el selector — dos problemas en un solo tipo: puede ser null (se resuelve con ?.) y es demasiado genérico (se resuelve con as TipoEspecífico). Son soluciones distintas para problemas distintos; hace falta ambas para acceder a propiedades como .value o .placeholder sin error de compilación.

typescript
const message = document.querySelector('#message') as HTMLInputElement;
console.log(message?.placeholder);
SituaciónErrores de tsc
Sin ?. ni asTS18047 (posible null) y TS2339 (propiedad no existe en Element)
Solo ?.Solo TS2339 (el tipo sigue siendo genérico)
?. + as TipoCompila limpio

⚠️ HTMLHeadElementHTMLHeadingElement. El primero es para <head>; el segundo es el correcto para <h1><h6>. tsc no avisa si los confundes (ambos derivan de HTMLElement) — hay que revisar el nombre con cuidado.

💡 tsc archivo.ts --watch (o -w) recompila solo cada vez que guardas, sin correr tsc a mano en cada cambio — deja el proceso corriendo hasta Ctrl+C.

⚛️ Proyecto React con Vite (Clase 15)

npm create vite@latest genera un proyecto React + TypeScript completo (JSX en archivos .tsx) en un solo comando. El tsconfig.json ya no configura nada directamente — solo referencia a tsconfig.app.json (código de src/, corre en el navegador) y tsconfig.node.json (vite.config.ts, corre en Node).

bash
npm create vite@latest mi-app -- --template react-ts
cd mi-app && npm install && npm run dev

⚠️ npm run dev NO revisa tipos — solo transpila (Babel/SWC quitan los tipos sin validarlos). La verificación de tipos real ocurre en npm run build ("build": "tsc -b && vite build") — un error de tipos no te detiene mientras desarrollas, pero sí al compilar para producción.

💡 HMR preserva el estado: a diferencia de una recarga completa (Live Server, Clase 12), el Hot Module Replacement de Vite reemplaza solo el módulo cambiado — un contador de React sigue en su valor tras guardar.

📝 El tooling cambia con el tiempo: la opción "TypeScript + SWC" del asistente ya no existe en versiones recientes de create-vite — hoy la lista muestra "TypeScript" y "TypeScript + React Compiler" en su lugar. El paquete @vitejs/plugin-react-swc sigue en npm, pero hay que configurarlo a mano.

⚠️ Bug real verificado: vite ^8.2.0 (Rolldown-Vite, experimental — depende de rolldown) combinado con React Compiler rompe el HMR con RefreshRuntime.register is not a function (pantalla en negro). Solución: usar vite ^7.x (clásico, esbuild + rollup) sin el plugin de React Compiler. Historia completa, diagnóstico y fix en la Clase 15.

🅰️ Angular con TypeScript: decoradores y standalone components (Clase 16)

ng newng serve (puerto 4200) genera un proyecto Angular + TypeScript ya integrado — sin configurar nada extra. El decorador @Component({...}) marca una clase como componente visual; hoy Angular usa standalone components (cada uno declara sus propios imports, sin NgModule) y main.ts arranca con bootstrapApplication, no con un AppModule.

typescript
@Component({ selector: 'app-root', imports: [RouterOutlet], templateUrl: './app.html', styleUrl: './app.css' })
export class App {
  protected readonly title = signal('proyectoAngular'); // signal reactiva
}

⚠️ experimentalDecorators: true ya NO es estrictamente obligatorio — verificado: TypeScript 6.0 compila @Component con el nuevo estándar de decoradores de ECMAScript (helpers __esDecorate/Symbol.metadata) sin esa bandera. Angular la sigue incluyendo por defecto (compatibilidad con su sistema de inyección de dependencias), pero no es un requisito duro para que compile.

💡 ng serve SÍ revisa tipos en cada guardado (compilación AOT real) — al revés que npm run dev de Vite (Clase 15), que solo transpila sin validar. Un error de tipos hace fallar el bundle de Angular al instante, no solo en el build de producción.

🐛 TypeScript 6.0 exige rootDir explícito en cuanto hay outDir — antes lo inferia solo. Angular CLI 22 genera tsconfig.app.json/tsconfig.spec.json sin rootDir, lo que produce error TS5011. Fix: agregar "rootDir": "./src" a ambos archivos.

🌐 API con TypeScript y Express (Clase 17)

npm init -y + npm install express cors dotenv + typescript/ts-node/@types/* como dev — un middleware (app.use(...)) es una función que corre entre la petición y la respuesta (cors() agrega headers, express.json() parsea el body a req.body).

typescript
import express from 'express';   // requiere esModuleInterop: true (paquete CommonJS)
const app = express();
app.use(express.json());
app.get('/hello', (req, res) => res.send('Hola'));
app.listen(3000);

⚠️ npm install con varios paquetes es todo-o-nada: un solo typo en el nombre de un paquete (@type/node en vez de @types/node) aborta el comando completo con E404 — ninguno de los paquetes bien escritos queda instalado.

🐛 npm install typescript sin fijar versión es impredecible: una vez resolvió 7.0.2 (rompió ts-node con TypeError: Cannot read properties of undefined) y, al reinstalar el mismo proyecto, resolvió 5.9.3 (todo funcionó sin tocar nada). Fija la versión (typescript@5.9.3) para reproducibilidad — tsx es además un reemplazo moderno de ts-node si te toca una combinación incompatible.

🐛 El mismo TS5011 de rootDir (ver Clase 16) también aparece aquí: sin rootDir explícito junto a outDir, el build falla y además el archivo compilado queda en la ruta equivocada (dist/src/server.js en vez de dist/server.js).

CRUD real + SQLite (node:sqlite): Node.js 22.5+ trae un cliente de SQLite integrado — sin instalar ningún paquete. Un prepared statement (? como placeholder) separa la consulta de los datos, y por eso previene inyección SQL:

typescript
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync('./data/nexus.db');
db.prepare('SELECT * FROM empleados WHERE id = ?').get(req.params.id);

⚠️ Rutas fijas antes que dinámicas: app.get('/empleados/:id', ...) registrada ANTES que app.get('/empleados/estadisticas', ...) hace que /empleados/estadisticas se interprete como id = "estadisticas" — Express prueba las rutas en el orden en que las registraste, no por especificidad.


📎 Relacionado: Comandos · Clase 3 · Clase 4 · Clase 5 · Clase 7 · Clase 8 · Clase 9 · Clase 10 · Clase 11 · Clase 12 · Clase 13 · Clase 14 · Clase 15 · Clase 16 · Clase 17 · Errores