Skip to content

study guide

Janio Isacura edited this page Oct 4, 2026 · 44 revisions

Guía de estudio: Angular UFH

Guía para aprender TypeScript y Angular desde cero, siguiendo los proyectos del repositorio. No reemplaza la documentación oficial: explica qué problema resuelve cada concepto, cómo funciona por dentro y cómo se ve en el código del curso.

Regla de lectura: primero ejecuta el ejemplo del repositorio, después lee la teoría del módulo y al final intenta responder las preguntas de entrevista sin mirar la respuesta.

Cómo usar esta guía

  1. Cada proyecto del repositorio (01-typescript-intro/, …) es una parte de esta guía.
  2. Cada archivo de src/topics/ es un módulo. El número del módulo coincide con el número del archivo.
  3. Cada módulo termina con preguntas de entrevista de nivel senior, cada una con su respuesta debajo. Intenta responder antes de leerla.
  4. Si algo no te queda claro, cámbialo en el archivo del tópico, ejecuta npx tsc y mira qué error aparece. El compilador es el mejor profesor.

Mapa general

angular-ufh/
└── 01-typescript-intro/        Parte 1: TypeScript sin framework
    └── src/topics/
        ├── 01-basic-types.ts       Módulo 1: tipos básicos
        └── 02-object-interface.ts  Módulo 2: arrays e interfaces

Ruta recomendada

Etapa Tema Resultado esperado
0 Qué es TypeScript y cómo corre el proyecto Saber qué hace tsc y qué hace Vite
1 Tipos básicos, literales y uniones Leer y escribir anotaciones de tipo sin dudar
2 Arrays tipados e interfaces Describir la forma de un objeto y entender los errores

Parte 1 · 0. Qué es TypeScript

El problema que resuelve

JavaScript no revisa tipos hasta que el código se ejecuta. Un error como llamar .toUpperCase() sobre un número sólo aparece cuando esa línea corre, quizás en producción.

// JavaScript: no avisa nada al escribirlo
const hp = 39
hp.toUpperCase() // TypeError en runtime

TypeScript agrega un sistema de tipos que se revisa antes de ejecutar. El mismo error aparece en el editor, mientras escribes.

Definición simple

TypeScript es JavaScript con anotaciones de tipo. Todo JavaScript válido es casi siempre TypeScript válido; TypeScript sólo agrega información para que el compilador pueda revisar el código.

Cómo funciona por dentro

  archivo.ts
      |
      +--> tsc ............ revisa tipos, informa errores (no genera archivos: noEmit)
      |
      +--> Vite ........... quita los tipos y entrega JavaScript al navegador
                                 |
                                 v
                           el navegador ejecuta JavaScript

Idea clave: los tipos se borran (type erasure). En el navegador no queda ningún string, interface ni type. Por eso TypeScript no puede validar datos que llegan en runtime (una API, un formulario): eso requiere código real (type guards o librerías de validación).

Cómo se aplica en este repositorio

  • npm run dev levanta Vite. Vite no revisa tipos: si hay un error de tipos, la página carga igual.
  • npx tsc es quien revisa tipos. Ejecútalo seguido.
  • TypeScript 6.0 activa el modo strict por defecto, aunque tsconfig.json no lo diga. Eso incluye noImplicitAny y strictNullChecks.
  • noUnusedLocals está activo: una variable de ejemplo que no se usa rompe el build. Por eso cada tópico termina con export { ... }.

Comparación con Angular

Angular usa el mismo compilador de TypeScript, pero envuelto en su propio compilador (ngc), que además revisa los templates HTML. Lo que aprendas aquí aplica directamente a componentes, servicios y formularios.

Errores comunes

  • Creer que si la página carga, el código no tiene errores de tipos. Vite no revisa tipos: usa npx tsc.
  • Creer que una anotación valida datos en runtime. const user: User = await res.json() no comprueba nada; sólo le dice al compilador que confíe.

Resumen

TypeScript revisa antes de ejecutar y desaparece al ejecutar. tsc revisa, Vite compila.

Preguntas de entrevista (senior)

1. Si los tipos se borran en runtime, ¿cómo garantizas que la respuesta de una API cumple una interfaz?

Respuesta:

No se puede con tipos solos. Hay que validar en runtime en el borde del sistema: con un type guard escrito a mano (function isUser(x: unknown): x is User) o con una librería de esquemas (Zod, Valibot) que genere el tipo a partir del esquema, así el tipo y la validación no se desincronizan. Dentro del sistema, una vez validado, el tipo ya es confiable.

2. ¿Qué diferencia hay entre revisar tipos y transpilar? ¿Por qué Vite no revisa tipos?

Respuesta:

Transpilar es quitar la sintaxis de TypeScript y producir JavaScript; se puede hacer archivo por archivo, muy rápido (esbuild, SWC, Oxc). Revisar tipos requiere entender el programa completo, porque un tipo puede venir de otro archivo. Vite separa las dos tareas para que el servidor de desarrollo sea rápido; la revisión se hace con tsc (en el editor, en CI o en npm run build).

3. ¿Qué activa strict y por qué conviene en un proyecto nuevo?

Respuesta:

Agrupa varias revisiones, entre ellas noImplicitAny (prohíbe parámetros sin tipo que caen en any), strictNullChecks (null y undefined dejan de ser asignables a cualquier tipo) y strictPropertyInitialization. En un proyecto nuevo cuesta poco activarlo; en uno existente se activa de a una opción para no tener miles de errores juntos. Angular genera proyectos con strict activo.

4. ¿Qué es isolatedModules / verbatimModuleSyntax y por qué existen?

Respuesta:

Las herramientas que transpilan archivo por archivo no pueden saber si un import es un tipo o un valor que vive en otro archivo. verbatimModuleSyntax obliga a escribir import type { X } para los tipos, así el transpilador puede borrar ese import sin mirar otros archivos. Este repositorio lo tiene activo.


Parte 1 · Módulo 1. Tipos básicos, literales y uniones

Archivo: 01-typescript-intro/src/topics/01-basic-types.ts

El problema que resuelve

Una variable que guarda la vida de un personaje debería aceptar números y quizás algunos estados fijos ('FULL', 'DEAD'), pero no cualquier texto. Sin tipos, hpPoints = 'FUL' (con un error de tipeo) pasaría sin aviso.

Definición simple

  • Anotación de tipo: decirle al compilador qué tipo tiene algo (const name: string).
  • Tipos primitivos: string, number, boolean, bigint, symbol, null, undefined.
  • Tipo literal: un tipo que acepta un único valor ('FULL').
  • Unión (|): un valor puede ser de uno de varios tipos.
  • Alias de tipo (type): un nombre para un tipo.

El código del repositorio

const name: string = 'Titus'
const isAlive: boolean = true
type LifeStatus = 'FULL' | 'DEAD' | 'INJURED'

let hpPoints: number | LifeStatus = 39

hpPoints = 'FULL'
  • LifeStatus es una unión de tres tipos literales. Sólo esos tres textos son válidos.
  • hpPoints acepta cualquier number o uno de esos tres textos.
  • hpPoints = 'FUL' daría error en tsc.

Cómo funciona por dentro

Inferencia. Si no anotas, TypeScript deduce el tipo del valor inicial:

const a = 'Titus' // tipo: 'Titus'  (literal: un const no puede cambiar)
let b = 'Titus'   // tipo: string   (se "ensancha": un let puede cambiar)

A esto se le llama widening. En el archivo, const name: string pierde el tipo literal porque la anotación lo ensancha a string a propósito.

Narrowing (estrechamiento). El tipo declarado de hpPoints es number | LifeStatus, pero después de hpPoints = 'FULL' el compilador sabe que en ese punto vale exactamente 'FULL'. TypeScript sigue el flujo del código y estrecha el tipo:

function describe(hp: number | LifeStatus) {
  if (typeof hp === 'number') {
    return hp.toFixed(0) // aquí hp es number
  }
  return hp.toLowerCase() // aquí hp es LifeStatus
}

any, unknown y never:

unknown  "puede ser cualquier cosa; demuéstrame qué es antes de usarlo"  (seguro)
any      "puede ser cualquier cosa; no reviso nada"                     (apaga el sistema)
never    "esto no puede ocurrir"                                         (unión vacía)

Comparación con JavaScript

typeof existe en JavaScript y devuelve un texto en runtime. TypeScript lo reutiliza para estrechar tipos dentro de un if. Lo que TypeScript agrega (el tipo LifeStatus) desaparece al compilar; el typeof queda.

Detalle de este proyecto: name

En el navegador ya existe una variable global name (window.name). Si este archivo no fuera un módulo, const name chocaría con ella. moduleDetection: "force" hace que cada archivo sea un módulo con su propio alcance, y por eso no hay conflicto.

Errores comunes

  • Usar any para "salir del paso". Usa unknown y estrecha.
  • Anotar todo. Si el valor inicial deja claro el tipo, la inferencia basta; anota en los bordes (parámetros, retornos públicos, datos externos).
  • Creer que number distingue enteros de decimales. Es un único tipo de punto flotante (0.1 + 0.2 !== 0.3). Para enteros grandes existe bigint.

Resumen

Anota lo necesario, deja que la inferencia haga el resto, y usa uniones de literales para limitar los valores posibles.

Preguntas de entrevista (senior)

1. Diferencia entre any, unknown y never. ¿Cuándo usarías cada uno?

Respuesta:

any desactiva la revisión: se puede asignar a todo y usar como sea; se contagia a lo que toca. unknown acepta cualquier valor pero no deja usarlo hasta estrecharlo (typeof, instanceof, un type guard); es el tipo correcto para datos externos y para catch (e). never es el tipo sin valores: el retorno de una función que siempre lanza, o la rama imposible en un chequeo exhaustivo. any sólo se justifica en migraciones o con librerías mal tipadas, y aislado.

2. ¿Qué tipo infiere TypeScript para const x = 'a' y para let x = 'a'? ¿Por qué?

Respuesta:

const infiere el literal 'a' porque el valor no puede cambiar. let infiere string (widening) porque se espera reasignarlo. Lo mismo pasa con las propiedades de un objeto: { status: 'FULL' } infiere status: string, salvo que se use as const o se anote el tipo.

3. ¿Cómo garantizas que un switch sobre una unión cubre todos los casos?

Respuesta:

Con un chequeo exhaustivo usando never:

function label(s: LifeStatus): string {
  switch (s) {
    case 'FULL': return 'Sano'
    case 'DEAD': return 'Muerto'
    case 'INJURED': return 'Herido'
    default: {
      const unreachable: never = s
      return unreachable
    }
  }
}

Si mañana se agrega 'POISONED' a LifeStatus, s deja de ser never en el default y el compilador marca el error en todos los switch que faltan.

4. Unión de literales vs enum vs objeto as const: ¿qué eliges y por qué?

Respuesta:

  • Unión de literales: cero código en runtime, se lee bien, buen autocompletado. No da una lista de valores para iterar.
  • enum: genera un objeto real en runtime (se puede iterar), pero es sintaxis propia de TypeScript que no se puede borrar sin más; herramientas como erasableSyntaxOnly o el modo de "sólo quitar tipos" de Node la rechazan. Los enum numéricos además aceptan cualquier número.
  • const STATUS = { FULL: 'FULL', ... } as const + type Status = typeof STATUS[keyof typeof STATUS]: da valores en runtime y un tipo derivado, sin sintaxis especial.

En código nuevo suele preferirse la unión o el objeto as const; enum sigue siendo común en código Angular existente. En este repositorio enum está permitido a propósito.

5. ¿Qué cambia strictNullChecks?

Respuesta:

Sin él, null y undefined se pueden asignar a cualquier tipo, y user.name.length compila aunque user pueda ser null. Con él, hay que declararlo (string | null) y estrecharlo antes de usarlo (if (user), user?.name, ??). Es la opción que más errores reales evita.


Parte 1 · Módulo 2. Arrays tipados e interfaces

Archivo: 01-typescript-intro/src/topics/02-object-interface.ts

El problema que resuelve

Un personaje tiene varias propiedades (nombre, vida, habilidades). Sin una descripción de esa forma, nada impide escribir geralt.hp = 'cien', olvidar hometown o equivocarse en el nombre de una propiedad.

Definición simple

  • Array tipado: string[] es una lista donde todos los elementos son string.
  • Interfaz (interface): describe la forma de un objeto, es decir, qué propiedades tiene y de qué tipo.
  • Propiedad opcional (?): puede estar o no estar.

El código del repositorio

const skills: string[] = ['Bash', 'Counter', 'Healing', 'Magic', 'Stealth']

interface Character {
  name: string
  hp: number
  skills: string[]
  hometown: string
  age?: number
}

const geralt: Character = {
  name: 'Geralt of Rivia',
  hp: 100,
  skills: ['Bash', 'Counter'],
  hometown: 'Rivia'
}

geralt.age = 100
  • Sin la anotación, ['Bash', true, 42] se infiere como (string | number | boolean)[]. La anotación string[] obliga a que todos sean texto.
  • geralt no tiene age al crearse, y es válido porque age es opcional.
  • geralt.age = 100 funciona aunque geralt sea const: const impide reasignar la variable, no modificar el objeto.

Cómo funciona por dentro

Tipado estructural. TypeScript compara formas, no nombres. Cualquier objeto con las propiedades de Character es un Character, aunque nunca se haya declarado así.

  Nominal (Java, C#)          Estructural (TypeScript)
  "¿se declaró Character?"    "¿tiene name, hp, skills, hometown?"

Revisión de propiedades sobrantes. Si escribes un objeto literal directamente donde se espera un Character, TypeScript rechaza propiedades que no existen en la interfaz:

const c: Character = { ..., extra: 1 }  // error: 'extra' no existe en Character

const raw = { ..., extra: 1 }
const c2: Character = raw               // sin error: raw ya no es un literal "fresco"

La revisión sólo aplica a literales recién escritos, porque ahí casi siempre indica un error de tipeo. Con una variable intermedia, el tipado estructural acepta el objeto porque tiene todo lo que pide la interfaz.

Opcional no es lo mismo que undefined:

interface A { age?: number }            // la propiedad puede faltar
interface B { age: number | undefined } // la propiedad debe existir, aunque valga undefined

Comparación con Angular

En Angular las interfaces se usan para describir los datos: la respuesta de una API (http.get<Character[]>(...)), las entradas de un componente (@Input() character!: Character o input<Character>()) y los modelos de formularios. Recuerda que en http.get<Character[]> la interfaz no valida nada en runtime (ver la sección 0).

interface o type

interface Character { name: string }
type Character2 = { name: string }

Para objetos, las dos sirven casi igual. Diferencias que importan:

interface type
Se puede declarar dos veces y se fusiona Un nombre, una declaración
Se extiende con extends Se combina con & (intersección)
Sólo describe objetos (y funciones, clases) Describe cualquier tipo: uniones, tuplas, etc.

Errores comunes

  • Pensar que const hace el objeto inmutable. Para eso existe readonly (readonly name: string, readonly string[]) o Object.freeze en runtime.
  • Usar any[] porque "el array tiene de todo". Si los elementos son de tipos distintos con posiciones fijas, es una tupla: [string, number].
  • Dejar todo opcional para evitar errores. Cada ? obliga a revisar undefined en todos los lugares donde se usa.

Resumen

Las interfaces describen formas; TypeScript compara formas, no nombres. Los literales escritos directamente reciben una revisión extra de propiedades.

Preguntas de entrevista (senior)

1. interface vs type: ¿cuándo usas cada uno?

Respuesta:

type es obligatorio para uniones, tuplas, tipos mapeados y condicionales. Para formas de objetos ambos sirven; muchas guías prefieren interface porque los errores son más legibles, extends falla temprano cuando hay propiedades incompatibles (una intersección & puede producir never en silencio) y el compilador cachea mejor las relaciones entre interfaces. interface además permite declaration merging, útil para extender tipos de librerías (por ejemplo, agregar propiedades a Window). Lo importante es ser consistente en el proyecto.

2. ¿Por qué const c: Character = { ..., extra: 1 } falla pero pasando por una variable intermedia no?

Respuesta:

Por la revisión de propiedades sobrantes (excess property checking), que sólo aplica a literales de objeto "frescos" asignados directamente a un tipo. Con una variable intermedia rige el tipado estructural normal: el objeto tiene todo lo que Character exige, así que es asignable. Es una heurística para detectar errores de tipeo, no una garantía de que el objeto no tenga propiedades extra; en runtime puede tenerlas.

3. ¿Qué es el tipado estructural y cómo simularías tipado nominal?

Respuesta:

Dos tipos son compatibles si sus formas lo son, sin importar el nombre. Esto causa problemas cuando dos conceptos tienen la misma forma (UserId y OrderId, ambos string). Se simula tipado nominal con branded types:

type UserId = string & { readonly __brand: 'UserId' }
const toUserId = (s: string) => s as UserId

Así un OrderId no se puede pasar donde se espera un UserId.

4. ¿Diferencia entre age?: number y age: number | undefined?

Respuesta:

Con ? la propiedad puede faltar del todo; con | undefined debe estar presente, aunque sea con valor undefined. Por defecto TypeScript también permite asignar undefined explícito a una propiedad ?; con exactOptionalPropertyTypes eso se prohíbe, y la diferencia importa para 'age' in obj, Object.keys o al serializar a JSON.

5. const, readonly y Object.freeze: ¿qué protege cada uno?

Respuesta:

const impide reasignar la variable, no cambiar el objeto. readonly (y ReadonlyArray<T> / readonly T[]) impide modificar en tiempo de compilación, pero desaparece en runtime y es superficial (las propiedades anidadas siguen siendo mutables). Object.freeze protege en runtime y también es superficial. En Angular, tratar los datos como inmutables importa para la detección de cambios con OnPush y con signals: si mutas un objeto en lugar de crear uno nuevo, la referencia no cambia y la vista puede no actualizarse.


Referencias del repositorio

  • 01-typescript-intro/README.md: cómo ejecutar el proyecto y agregar lecciones.
  • 01-typescript-intro/tsconfig.json: reglas del compilador.
  • 01-typescript-intro/src/topics/: un archivo por módulo de esta guía.

Clone this wiki locally