Skip to content

💡 Conceptos — Angular

Conceptos clave explicados con mis propias palabras.

🌐 Frontend = HTML + CSS + JavaScript

El frontend es la parte de la web que el usuario ve y con la que interactúa (lo que se ejecuta en el navegador). Se sostiene sobre 3 pilares:

PilarPara qué sirveAnalogía
HTMLLa estructura y el contenido (textos, imágenes, botones).El esqueleto
CSSEl estilo: colores, tamaños, posición, diseño.La ropa / apariencia
JavaScriptEl comportamiento: interactividad, lógica, peticiones.El cerebro / músculos

💡 Angular es un framework de frontend basado en estos tres + TypeScript (JavaScript con tipos).

🎨 Pre-procesadores CSS

Un pre-procesador es un lenguaje que extiende el CSS con funciones que el CSS puro no tenía (o tardó en tener): variables, anidación, mixins, funciones… Escribes en ese lenguaje y una herramienta lo compila a CSS normal (el navegador solo entiende CSS).

Escribes SCSS  ──(compilación)──►  CSS puro  ──►  el navegador lo entiende
Pre-procesadorNota
Sass / SCSSEl más popular. SCSS usa {} y ; (parecido a CSS); Sass usa indentación.
LessSimilar a SCSS, más antiguo, hoy poco usado.

💡 Conecta con la pregunta de ng new sobre estilos: ahí eliges si tu proyecto usará CSS puro o un pre-procesador. → ver Comandos/04-ng-new.md.

⚠️ Hoy el CSS moderno ya tiene variables nativas (--mi-color) y anidación, así que los pre-procesadores son menos imprescindibles que antes, pero siguen muy usados.

🧩 Pre-procesador vs Framework CSS (¡no es lo mismo!)

El profe separó el mundo del CSS en dos ramas. Ojo con la diferencia:

Qué esEjemplos
Pre-procesador CSSUn lenguaje que extiende el CSS (variables, anidación…) y se compila a CSS.Sass / SCSS, Less
Framework CSSUna librería de estilos ya hechos que usas en tu HTML para maquetar más rápido.Tailwind, Bootstrap

🌬️ Tailwind = framework utility-first

Utility-first ("primero utilidades") significa que en vez de escribir CSS aparte, maquetas combinando clases pequeñas directamente en el HTML, donde cada clase hace una sola cosa:

html
<!-- Tailwind: clases utilitarias en el propio HTML -->
<div class="flex p-4 text-center bg-blue-500 rounded">Hola</div>
<!--          │    │      │           │           └ bordes redondeados
              │    │      │           └ fondo azul
              │    │      └ texto centrado
              │    └ padding (espaciado interno)
              └ display: flex -->

💡 El profe escribió "first utility"; el término correcto es "utility-first".

💡 Resumen: Sass/Less te ayudan a escribir CSS; Tailwind te da clases para no escribir CSS propio casi nunca. Por eso en ng new aparecen juntos como opciones de estilo, aunque sean conceptos distintos.

🏷️ Semántica web (HTML semántico)

HTML semántico = usar etiquetas que describen el significado del contenido, no solo cómo se ve. Ayuda al SEO, a la accesibilidad (lectores de pantalla) y a que el código sea más legible.

EtiquetaQué representa
<head>Metadatos del documento (no visible): título, enlaces a CSS, etc.
<body>Todo el contenido visible de la página.
<header>Cabecera de la página o de una sección (logo, menú).
<section>Una sección temática del contenido.
<footer>Pie de página (créditos, enlaces, contacto).

⚠️ Ojo (apunte de entrevista): <div> NO es semántica — es una caja neutra sin significado. Se usa solo para agrupar/maquetar cuando ninguna etiqueta semántica encaja. Lo semántico es preferir <header>, <section>, <footer>, <nav>, <article>, <main>, <aside> antes que llenar todo de <div>.

🧩 Anatomía de un componente (lo más importante de Angular)

Un componente es la pieza básica con la que construyes una app Angular: un trozo de interfaz reutilizable. La app es, en el fondo, un árbol de componentes.

🧱 ¿Qué puede ser un componente? (casi todo)

Un componente puede representar muchísimas cosas de la interfaz, desde algo pequeño hasta una página entera:

PequeñosMedianosGrandes
🔘 un botón🪪 una card (tarjeta)📋 un formulario
🏷️ una etiqueta🍔 un menú🗔 un modal / ventana
🔍 un input🧭 una barra de navegación📄 una página completa

💡 La idea: divides la interfaz en piezas (componentes) y las combinas y reutilizas. Un mismo componente boton puede usarse 50 veces; una card se repite por cada producto; un modal se reutiliza en toda la app. Eso es componetizar.

❓ ¿Por qué Angular usa componentes?

Porque permiten:

  • ✂️ Dividir la aplicación en piezas pequeñas y manejables.
  • ♻️ Reutilizar código (escribes una vez, usas muchas).
  • 🗂️ Organizar responsabilidades (cada componente hace una cosa).
  • 🔧 Facilitar el mantenimiento (arreglas/cambias una pieza sin tocar el resto).
  • 👥 Trabajar en equipo (cada quien desarrolla componentes distintos a la vez).

🎬 Ejemplo concreto: MovieCardComponent + reutilización

Un componente MovieCard (tarjeta de película) representa: 🖼️ imagen, 📝 título, ⏱️ duración, 🔞 clasificación y 🛒 botón comprar. Todo eso empaquetado en una sola pieza.

Lo potente es la reutilización: defines la MovieCard una vez y la repites por cada película, cambiando solo sus datos:

html
<MovieCard />   <!-- Aída y vuelta -->
<MovieCard />   <!-- Hokum, La maldición de la Bruja -->
<MovieCard />   <!-- Obsesión -->
<MovieCard />   <!-- ...y así por cada película -->

💡 Misma estructura, distintos datos: todas las tarjetas comparten el mismo diseño y comportamiento (el componente), pero muestran una película diferente. Esos datos se le pasan al componente con inputs (lo verás más adelante: @Input / input()).

Cada componente se reparte normalmente en 3 archivos (separa responsabilidades):

ArchivoCapaQué contiene
app.ts🧠 LógicaLa clase TypeScript: datos, métodos, comportamiento.
app.html🦴 Estructura/vistaEl HTML (la "semántica") que se ve en pantalla.
app.scss🎨 EstilosEl CSS/SCSS que aplica solo a este componente.

💡 Los estilos de un componente están encapsulados: por defecto no afectan a otros componentes (a diferencia de styles.scss, que es global).

🔌 El decorador @Component (el "pegamento")

El archivo de lógica (app.ts) tiene un decorador @Component({...}) que le dice a Angular cómo es ese componente y conecta las 3 capas:

ts
@Component({
  selector: 'app-root',          // 🏷️ nombre de etiqueta para usarlo: <app-root>
  imports: [RouterOutlet],       // 📦 qué otros componentes/directivas usa este
  templateUrl: './app.html',     // 🦴 enlaza su HTML (la vista)
  styleUrl: './app.scss',        // 🎨 enlaza sus estilos
})
export class App { }             // 🧠 aquí va la lógica
PropiedadPara qué sirve
selectorEl nombre de etiqueta con el que insertas el componente en otro HTML (<app-root>).
importsLo que este componente necesita usar (otros componentes, directivas, pipes). Propio de los componentes standalone.
templateUrlRuta al archivo HTML del componente. (Alternativa: template: con el HTML en línea.)
styleUrlRuta al archivo de estilos. (Alternativa: styles: en línea.)

💡 Decorador = etiqueta que añade info. @Component "marca" una clase normal de TS como un componente de Angular y le adjunta su configuración (vista, estilos, selector).

🔗 Así se cierra el ecosistema: @Component en app.ts apunta con templateUrl al app.html y con styleUrl al app.scss. Los 3 archivos forman un solo componente.

        app.ts  (@Component)
        ┌─────────────────────────┐
        │ selector  → <app-root>  │
        │ templateUrl → app.html ─┼──► 🦴 vista (HTML)
        │ styleUrl    → app.scss ─┼──► 🎨 estilos (SCSS)
        │ class App { ... }       │──► 🧠 lógica (TS)
        └─────────────────────────┘

🧪 Tip de entrevista: "¿Qué es @Component?" → un decorador que convierte una clase en un componente Angular y enlaza su plantilla (templateUrl), estilos (styleUrl) y nombre de uso (selector).

🏷️ El selector en detalle

ts
selector: 'app-root',

El selector es el nombre de etiqueta HTML con el que usas el componente en otra plantilla. Es como crear tu propia etiqueta de HTML:

html
<!-- usar el componente = escribir su selector como etiqueta -->
<app-root></app-root>
  • Donde escribas <app-root></app-root>, Angular inserta ahí todo el HTML de ese componente. (Por eso funciona el <app-root> del index.html.)
  • Puedes reutilizarlo las veces que quieras: si tienes un componente selector: 'app-tarjeta', pones <app-tarjeta> muchas veces y aparece repetido.

🔤 Convención de nombres del selector

  • Suele llevar el prefijo app- (app-root, app-header, app-tarjeta). Ese prefijo evita choques con etiquetas HTML reales o de otras librerías.
  • Se escribe en kebab-case (minúsculas con guiones), igual que las etiquetas HTML.
  • El prefijo se puede cambiar al crear el proyecto/componente (--prefix).

💡 Regla de oro: el selector que pongas en @Component es exactamente la etiqueta que debes escribir en el HTML. Si no coinciden, el componente no aparece.

⚠️ El selector del componente raíz (app-root) es especial porque coincide con la etiqueta del index.html. Los demás componentes los insertas dentro de otros componentes, no en el index.html.

📝 Nombres antiguos: en proyectos viejos verás app.component.ts, app.component.html, app.component.scss y styleUrls: [...] (en plural/array). Mismo concepto, nomenclatura previa.

🔀 Data Binding (los "canales de comunicación" vista ↔ lógica)

Data Binding es el nombre general para las distintas formas en que se comunican la vista (app.html) y la lógica (app.ts) de un componente. Cada tipo es un "canal" distinto, según qué dirección viaja la información:

#TipoSintaxisDirección
1Interpolación{{ }}Lógica vista (mostrar un dato).
2Event Binding(evento)Vista Lógica (la vista avisa que pasó algo, p. ej. un clic).
3Two-way Binding[(ngModel)]Lógica vista (las dos direcciones a la vez).

Diagrama de los 3 canales de Data Binding aplicados al restaurante TecyLab: interpolación, event binding y two-way binding

🗺️ Mismo diagrama que en 01-Clases/cocinero-principal.md, aplicado al código real de esa clase (cocineroPrincipal, llamarMesero() / llamarSeguridad(), comentarioPlato).

🖱️ Event Binding (evento) en detalle

Es la forma de reaccionar desde la vista a algo que hace el usuario (clic, tecla, envío de formulario…) y ejecutar un método de la clase. Se escribe el nombre del evento entre paréntesis:

html
<button (click)="saltar()">Saltar</button>
<button (click)="dibujar()">Dibujar</button>
  • (click) = el evento que escuchas (entre paréntesis).
  • "saltar()" / "dibujar()" = el método de la clase que se ejecuta cuando ocurre ese evento. El profe usó saltar y dibujar como ejemplos de acciones.

🍽️ Ejemplo real de la clase (restaurante TecyLab)

ts
// app.ts
cocineroPrincipal: string = "Señor tecylab";

llamarMesero() {
  console.log("alerta mesero la mesa de sandivel necesita asistencia");
}
html
<!-- app.html -->
<h3>Bienvenido a su mesa, {{cocineroPrincipal}}</h3>

<button (click)="llamarMesero()">LLAMAR AL MESERO</button>

Aquí se ven los dos canales juntos: {{cocineroPrincipal}} muestra el dato (interpolación) y (click)="llamarMesero()" reacciona al clic (Event Binding), ejecutando el método que imprime la alerta en consola. → ver 01-Clases/2026-07-04-cocinero-principal.md.

💡 Regla mental: interpolación {{ }} saca datos de la clase hacia la vista; Event Binding ( ) manda avisos de la vista hacia la clase. Son las dos caras del Data Binding, en direcciones opuestas.

📚 Categorías de eventos en Angular

Teoría que compartió el profe: todo Event Binding usa la misma sintaxis — (evento)="metodo()" —, pero los eventos que puedes escuchar se agrupan en 3 categorías según de dónde vienen:

CategoríaDe dónde vieneEjemplo
Eventos del DOM (nativos)El navegador (clics, teclado, formularios)(click), (input), (keyup.enter)
Eventos personalizadosOtro componente (comunicación hijo → padre)@Output() cambio = new EventEmitter()
Eventos del ciclo de vidaEl propio AngularngOnInit(), ngOnChanges(), ngOnDestroy() → ver más arriba (sección Ciclo de vida de un componente)

1️⃣ Eventos del DOM (nativos)

Permiten que el template reaccione a lo que hace el usuario:

GrupoEventosEjemplo
Ratón(click), (dblclick), (mouseenter), (mouseleave), (mousemove)<button (click)="saltar()">
Teclado(keydown), (keyup), (keypress) — se pueden filtrar por tecla(keyup.enter)="guardar()" (solo dispara con Enter)
Formularios/Inputs(input), (change), (blur), (focus) — el envío del formulario usa (ngSubmit)(ngSubmit)="procesarFormulario()"
  • El objeto $event — Angular le pasa al método la información nativa del evento del navegador. Con inputs de texto, lo normal es leer $event.target.value (o castear evento.target as HTMLInputElement en TypeScript, como en el ejemplo real de abajo):

    html
    <input (input)="onInput($event)">
    ts
    onInput(evento: Event): void {
      const input = evento.target as HTMLInputElement;
      console.log(input.value);
    }

    → Ejemplo real de esto en 01-Clases/ciclo-de-vida.md (componente informacion, captura el texto de un <input> con (input) + evento.target as HTMLInputElement).

📌 Las otras 2 categorías (Eventos personalizados con @Output()/EventEmitter para que un hijo le avise algo a su padre, y el detalle de los Eventos del ciclo de vida) quedan pendientes de completar cuando se vean con más profundidad en clase.

🔄 Two-way Binding [(ngModel)]

El profe lo agregó como punto 3 de la lista, con su sintaxis: [(ngModel)]. Combina los corchetes [ ] (property binding, dato hacia el HTML) con los paréntesis ( ) (event binding, dato desde el HTML) — por eso se le dice "banana in a box" 🍌📦 (el ( ) envuelto en [ ]).

🍽️ Ejemplo real de la clase (restaurante TecyLab)

ts
// app.ts
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  imports: [FormsModule, RouterOutlet],   // 👈 FormsModule habilita ngModel
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {
  comentarioPlato: string = '';
}
html
<!-- app.html -->
<input type="text" [(ngModel)]="comentarioPlato" placeholder="Aqui puede agregar notas">
  • FormsModule es obligatorio en imports para poder usar ngModel (viene de @angular/forms, no de @angular/core).
  • Escribes en el <input>comentarioPlato se actualiza sola en la clase (vista → lógica). Si cambiaras comentarioPlato desde el código, el <input> mostraría el nuevo valor (lógica → vista). Las dos direcciones a la vez, de ahí el nombre.

⚠️ Error real que dio este ejemplo: al agregar FormsModule a imports, se reemplazó RouterOutlet en vez de dejar los dos (imports: [FormsModule] en vez de imports: [FormsModule, RouterOutlet]). Como el HTML seguía usando <router-outlet/>, Angular tiró NG8001: 'router-outlet' is not a known element. imports es una lista: agregar uno no debe borrar los que ya estaban. → ver ../06-Errores/Angular-TypeScript/2026-07-04-router-outlet-no-reconocido.md.

💡 La idea general: todos son formas de conectar el HTML con la clase TS del componente, para que se mantengan sincronizados sin que tengas que tocar el DOM a mano.

🔗 Interpolación {{ }} (mostrar datos en la vista)

La interpolación es la forma de mostrar en el HTML un dato que vive en la clase del componente. Se usan dobles llaves {{ }} y dentro pones el nombre de la propiedad.

ts
// mi-componente.ts (la clase = lógica/datos)
export class MiComponente {
  nombre: string = 'Leonardo';
  edad: number = 18;
}
html
<!-- mi-componente.html (la vista) -->
soy {{ nombre }} <br>
tengo {{ edad }} años.

➡️ En pantalla se ve: soy Leonardo / tengo 18 años. Angular reemplaza{{ nombre }} por el valor de la propiedad nombre.

  • Es one-way (un solo sentido): de la clase → la vista. Si cambia el dato, la vista se actualiza sola.
  • Dentro de {{ }} va una expresión simple: una propiedad ({{ edad }}), una operación ({{ edad + 1 }}) o llamar un método ({{ saludar() }}).
  • No se ponen llaves para atributos HTML (eso es property binding con [ ], lo verás después). La interpolación es para texto/contenido.

💡 Regla mental: "lo que pongas entre {{ }} en el HTML se rellena con el valor que tenga esa variable en el .ts". Es el puente lógica → vista.

🔤 Tipos de las propiedades (TypeScript)

Como Angular usa TypeScript, a cada propiedad le pones su tipo:

ts
nombre: string = 'Leonardo';        // texto
edad: number = 18;                  // número
haceEjercicio: boolean = true;      // verdadero/falso
random: number | string = '';       // "union": puede ser número O texto
TipoQué guardaEjemplo
stringTexto'Leonardo'
numberNúmeros18
booleantrue / falsetrue
number | stringUnión: uno u otro tipo5 o 'hola'

💡 El | (barra) crea un tipo unión: number | string significa "puede ser número o string". TypeScript te avisará si intentas meter otro tipo.

⚙️ Métodos en un componente

Además de propiedades (datos), una clase de componente puede tener métodos (acciones/funciones). Anatomía de un método en TypeScript:

ts
metodo(parame: string): string {     // nombre(parámetro: tipo): tipo-que-devuelve
  console.log('hola' + parame);      // hace algo (aquí, imprime en la consola)
  return 'hola' + parame;            // devuelve un valor
}
ParteQué es
metodoEl nombre del método (cómo lo llamas).
(parame: string)El parámetro que recibe y su tipo (aquí, un string).
: string (tras el ))El tipo de lo que devuelve (lo del return).
console.log(...)Imprime en la consola del navegador (útil para depurar).
return ...Devuelve un valor a quien llamó el método.
  • Para llamarlo desde la vista: {{ metodo('Leonardo') }} (interpolación) o, más común, desde un evento: <button (click)="metodo('hola')"> (lo verás más adelante).
  • Si un método no devuelve nada, su tipo de retorno es void.

💡 Propiedades vs métodos: las propiedades guardan datos (nombre, edad); los métodos ejecutan acciones (calcular, llamar una API, responder a un clic). Juntos forman la lógica del componente (el .ts).

📝 Template literals (comillas invertidas ` + ${ })

Un template literal es una forma de escribir strings que permite incrustar variables directamente adentro del texto, en vez de concatenar con +. Se escribe con comillas invertidas (backticks, `) y cada variable va dentro de ${ }.

ts
servirPlatillo(plato: IPlatillo) {
  console.log(`El ${this.cocineroPrincipal} esta preparando: ${plato.nombre}`);
}
ParteQué es
` `Las comillas del template literal (ni ' simples ni " dobles).
${this.cocineroPrincipal}Inserta el valor de esa propiedad ahí mismo, en medio del texto.
${plato.nombre}Puede ir cualquier expresión, no solo una propiedad simple.

Comparación con concatenar (+), la forma antigua:

ts
// forma antigua (concatenar con +)
console.log('El ' + this.cocineroPrincipal + ' esta preparando: ' + plato.nombre);

// template literal (más legible)
console.log(`El ${this.cocineroPrincipal} esta preparando: ${plato.nombre}`);

💡 Por qué se usa: es más legible que sumar strings con +, y permite saltos de línea dentro del texto sin trucos raros (\n).

🧪 Tip de entrevista: "¿Qué es un template literal?" → un string con comillas invertidas que permite interpolar expresiones con ${ }, sin concatenar con +.

🧾 Interfaces en TypeScript (el "contrato" de forma)

Una interfaz describe qué propiedades debe tener un objeto (nombre y tipo de cada una), sin implementar nada: es solo un molde/contrato. TypeScript la usa para avisarte si un objeto le falta o le sobra algo.

ts
interface IPlatillo {
  id: number;
  nombre: string;
  precio: number;
}
ts
// un objeto que "cumple" la interfaz IPlatillo
const sopa: IPlatillo = {
  id: 1,
  nombre: 'Sopa del día',
  precio: 8.5,
};
ParteQué es
interface IPlatilloEl nombre del contrato (convención: prefijo I + PascalCase).
id: number / nombre: string / precio: numberCada propiedad y su tipo obligatorio.
const sopa: IPlatilloUn objeto tipado con esa interfaz: TS exige que tenga esas 3 propiedades con esos tipos.

💡 ¿Para qué sirve si no "hace" nada? Para que TypeScript te avise en tiempo de escritura si te falta una propiedad, si pusiste el tipo equivocado, o si escribiste mal un nombre (precio vs Precio). Es documentación verificada por el compilador.

💡 Dónde se usa en Angular: las interfaces son ideales para tipar datos que vienen de una API o que defines tú (un IPlatillo, un IUsuario…), y luego usarlos como tipo de una propiedad, un array (platillos: IPlatillo[]) o la respuesta de un servicio HTTP.

🧪 Tip de entrevista: "¿Para qué sirve una interfaz en TypeScript?" → para definir la forma que debe tener un objeto (propiedades y tipos), sin implementación. Ayuda a detectar errores en tiempo de compilación, no en producción.

🔀 Control flow en el template: @if / @else if / @else

Es la forma moderna (desde Angular v17) de escribir condicionales dentro del HTML de un componente, para mostrar un bloque u otro según una condición. Reemplaza a la directiva antigua *ngIf.

html
@if (promedio <= 13) {
  <p>Eres un alumno normal</p>
} @else if (promedio <= 17) {
  <p>Eres un alumno bueno</p>
} @else {
  <p>Eres un alumno excelente</p>
}
ParteQué hace
@if (condición) { ... }Si la condición es verdadera, muestra ese bloque.
@else if (condición) { ... }Se evalúa solo si el @if de arriba fue falso. Puedes encadenar varios.
@else { ... }El "de lo contrario": si ninguna condición anterior se cumplió. Va sin condición y siempre al final.

📝 Ojo con la sintaxis: cada rama intermedia necesita el if después del @else (@else if (...)). Un @else solo (sin if) no lleva condición — es el catch-all final. Si escribes @else con una condición pero sin la palabra if, no es válido.

💡 Por qué reemplaza a *ngIf: esta sintaxis con @ es nativa del template de Angular (no necesita importar ninguna directiva en imports), se lee más parecido a un if normal de JavaScript, y Angular la optimiza mejor por dentro.

🧪 Tip de entrevista: "¿Cómo se hace un condicional en un template de Angular moderno?" → con los bloques @if (cond) {…} @else if (cond) {…} @else {…}, disponibles desde Angular v17, en reemplazo de *ngIf.

🔐 Otro ejemplo: @if / @else simple (sin @else if)

Cuando solo hay dos casos (sí/no), no hace falta @else if, alcanza con @if + @else:

html
@if (tieneAcceso) {
  <div>Acceso permitido</div>
} @else {
  <div>No tiene acceso permitido</div>
}

tieneAcceso sería una propiedad boolean de la clase (tieneAcceso: boolean = true;). Si es true → se muestra el primer <div>; si es false → el del @else.

🔁 Control flow en el template: @for (repetir por cada elemento de una lista)

Es el reemplazo moderno de *ngFor: repite un bloque de HTML una vez por cada elemento de un array.

html
<h3>Lista de PCs seleccionadas</h3>
<ul>
  @for (producto of listaDePcs; track producto.id) {
    <li>Producto seleccionado {{producto.id}}: {{producto.nombre}}</li>
  } @empty {
    <li>No has seleccionado nada</li>
  }
</ul>
ParteQué es
producto of listaDePcsPor cada vuelta, producto es un elemento del array listaDePcs.
track producto.idObligatorio. Le dice a Angular cómo identificar de forma única cada elemento (normalmente su id), para que sepa qué reciclar cuando la lista cambia (más rápido que redibujar todo).
@empty { ... }Bloque opcional: se muestra solo si el array está vacío (listaDePcs.length === 0). Reemplaza el viejo truco de un *ngIf aparte para el caso vacío.

📝 Corrección sobre track: en la pizarra se vio track id a secas, pero id por sí solo no existe en ese contexto — tiene que ser una propiedad del elemento actual del @for, o sea track producto.id (usando el nombre que le diste después del of, aquí producto). Sin el producto. delante, TypeScript no sabe de dónde sacar id y marca error.

💡 track es obligatorio (a diferencia del viejo *ngFor donde trackBy era opcional). Si tus elementos no tienen un id único, puedes usar track $index (la posición en el array), aunque no es lo ideal si la lista se reordena.

🧪 Tip de entrevista: "¿Para qué sirve track en @for?" → para que Angular identifique cada elemento de forma única y solo actualice/mueva lo que cambió en el DOM, en vez de volver a dibujar toda la lista.

🔗 Angular tiene un bloque más de este estilo: @switch (reemplaza a *ngSwitch). Se documenta aquí en cuanto se vea en clase.

🧴 Pipes básicos (transformar datos directo en el template)

Un pipe (|) es una función que transforma un dato justo antes de mostrarlo, sin tener que cambiar la propiedad original en la clase. Se escribe dentro de la interpolación, después del dato, con el símbolo |:

html
<p>Mesero: {{ 'pepito' | uppercase }}</p>
<p>Precio total: {{ 225.50 | currency:'PEN' }}</p>

➡️ En pantalla: Mesero: PEPITO / Precio total: S/ 225.50.

PipeQué haceEjemplo
uppercasePasa el texto a mayúsculas.`'pepito'
currencyDa formato de moneda (símbolo + 2 decimales).`225.50
  • dato | pipe = "toma dato y pásalo por el pipe pipe antes de mostrarlo".
  • dato | pipe:argumento — algunos pipes reciben un argumento después de : (aquí, currency:'PEN' le dice en qué moneda formatear: soles peruanos).
  • El dato original de la clase no cambia — el pipe solo transforma lo que se muestra.

📝 2 correcciones sobre el ejemplo visto en clase:

  • El pipe se llama currency (en inglés), no currendcy — nombre mal escrito en la pizarra.
  • El código de moneda va entre comillas, como string: currency:'PEN'. Escribir currency: PEN (sin comillas) haría que Angular busque una propiedad llamada PEN en la clase (que no existe) en vez de tratarlo como texto.

💡 Por qué usar un pipe y no cambiarlo en el .ts: mantienes el dato puro en la clase (precio: number = 225.50) y solo lo formateas al mostrarlo. Si en otro lugar necesitas el número sin formato (para sumarlo, por ejemplo), sigue intacto.

🧪 Tip de entrevista: "¿Qué es un pipe en Angular?" → una función que transforma un valor en el template, con sintaxis valor | nombrePipe:argumento, sin alterar el dato original de la clase.

⚠️ Los pipes también van en imports (componentes standalone)

En un componente standalone (como los de este curso, sin NgModule), los pipes no vienen "gratis" — hay que importarlos en @Component({ imports: [...] }), igual que FormsModule o RouterOutlet:

ts
import { UpperCasePipe, CurrencyPipe } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [UpperCasePipe, CurrencyPipe],  // sin esto, Angular no reconoce el pipe
  templateUrl: './app.html',
})
export class App { }

⚠️ Si usas {{ dato | uppercase }} en el HTML pero olvidas importar UpperCasePipe, Angular tira un error de compilación tipo "The pipe 'uppercase' could not be found". Cada pipe viene de @angular/common con su propio nombre de exportación (UpperCasePipe, LowerCasePipe, CurrencyPipe, DatePipe, DecimalPipe…).

💰 currency con más argumentos (moneda, símbolo, decimales)

El pipe currency acepta hasta 4 argumentos, separados por :, no solo el código de moneda:

html
<p>{{ item.precio | currency : 'PEN' : 'symbol' : '1.2-2' }}</p>
Argumento (en orden)Qué controlaEjemplo
1. código de monedaQué moneda usar.'PEN' (soles), 'USD' (dólares)
2. formato de visualizaciónCómo se ve el prefijo: 'symbol' (S/), 'code' (PEN) o 'symbol-narrow'.'symbol'
3. digitsInfo (decimales)'minEnteros.minDecimales-maxDecimales'.'1.2-2' → siempre 2 decimales exactos

➡️ 225.50 | currency:'PEN':'symbol':'1.2-2' se ve como S/ 225.50.

💡 '1.2-2' desglosado: 1 = mínimo 1 dígito entero; 2-2 = mínimo y máximo 2 decimales (fuerza siempre 2, ni más ni menos — así 20 se ve 20.00, no 20).

📅 El pipe date (formatear fechas)

Igual que currency formatea números como dinero, date formatea una fecha (un Date, un texto ISO, o un timestamp) en un texto legible:

ts
import { DatePipe } from '@angular/common';

@Component({
  imports: [DatePipe], // igual que CurrencyPipe: hay que importarlo
  ...
})
html
<p>Última actualización: {{ item.actualizado_en | date:'short' }}</p>
FormatoSe ve como
'short'7/4/26, 9:56 PM (fecha + hora, corto)
'medium'Jul 4, 2026, 9:56:35 PM
'shortDate'7/4/26 (solo fecha)
'longDate'July 4, 2026

⚠️ Requisito importante: el pipe date solo convierte bien la zona horaria si el texto de fecha indica explícitamente que es UTC (termina en Z, ej. "2026-07-05T02:56:16Z"). Si el backend guarda la fecha sin esa marca (ej. "2026-07-05 02:56:16", como da datetime('now') en SQLite), el pipe la muestra sin convertir — mostrando la hora de Greenwich como si fuera la tuya. → ver sección "El pipe date + SQLite: cuidado con la zona horaria" más abajo para el caso real y la solución (strftime(...'Z')).

🗒️ Comentarios como separadores de sección (estilo de código, no de Angular)

En el HTML de un componente que ya tiene varias secciones (encabezado, formulario, listado…), ayuda marcarlas con un comentario HTML "banner" antes de cada una:

html
<!-- ========================================= -->
<!-- ENCABEZADO DE LA PÁGINA -->
<!-- ========================================= -->
<header>
  ...
</header>

<!-- ========================================= -->
<!-- SECCIÓN DE NOTAS -->
<!-- ========================================= -->
<section>
  ...
</section>
  • Es un comentario HTML normal (<!-- -->), sin ningún significado especial para Angular — es pura convención de legibilidad, igual que separar capítulos en un documento.
  • Sirve para ubicarse rápido en un archivo app.html que va creciendo con varias secciones, sin tener que leer todo el árbol de etiquetas.
  • Es opcional y es gusto del que escribe el código (aquí, un hábito del profe/curso), no una regla de Angular ni de HTML.

💡 Mismo truco funciona en TypeScript: un comentario // ===== NOMBRE ===== antes de un grupo de métodos relacionados cumple la misma función de "separador visual".

🔡 Objeto de JavaScript vs JSON (no es lo mismo)

Surgió practicando el backend (node:sqlite + Express) del ejercicio app-semana02-practica. Al correr esto en la terminal:

bash
node -e "const db = require('./db'); console.log(db.prepare('SELECT * FROM platillos').all());"

Se ve algo así en pantalla:

[
  [Object: null prototype] {
    id: 1,
    nombre: 'lomo saltado',
    precio: 20,
    disponible: 1,
    categoria: 'segundos'
  },
  ...
]

Se parece a JSON, pero no lo es:

Qué esDónde vive
Objeto/array de JavaScriptDatos en memoria: puedes hacer resultado[0].nombre directo.Solo mientras el programa Node está corriendo.
JSONUn string (texto) con ese mismo contenido, en un formato específico.Se usa para enviar datos por la red (HTTP) o guardarlos en un archivo .json.
  • console.log(...) imprime los objetos de JS usando util.inspect por dentro — por eso se ve "bonito" y parecido a JSON, pero sigue siendo JavaScript, no texto JSON.
  • Para convertir un objeto JS a JSON de verdad (un string), se usa JSON.stringify(objeto). Para el camino inverso (string JSON → objeto JS), JSON.parse(string).
  • [Object: null prototype]: detalle de node:sqlite — las filas que devuelve .all() no heredan de Object.prototype (optimización interna). No afecta en nada al convertirlas a JSON; JSON.stringify y res.json() de Express funcionan igual.
Node en memoria:        { nombre: 'lomo saltado', precio: 20 }   ← objeto JS
        │  JSON.stringify(...)  /  res.json(...) en Express

Por la red (HTTP):       '{"nombre":"lomo saltado","precio":20}'  ← JSON (string)
        │  JSON.parse(...)  /  Angular HttpClient lo hace solo

Angular en memoria:      { nombre: 'lomo saltado', precio: 20 }   ← objeto JS de nuevo

💡 Por qué importa: cuando el backend (Express) responde con res.json(datos), Express hace el JSON.stringify por ti. Cuando Angular pide esos datos con HttpClient, hace el JSON.parse por ti también — por eso, en la práctica, casi nunca escribes esas conversiones a mano, pero es clave saber que por el medio viaja texto, no el objeto original.

🧪 Tip de entrevista: "¿Qué diferencia hay entre un objeto de JS y JSON?" → un objeto de JS vive en memoria dentro del programa; JSON es su representación en texto, pensada para transportar o guardar esos datos. Se convierten entre sí con JSON.stringify() / JSON.parse().

🧰 Servicios, Inyección de Dependencias y HttpClient

Surgió al conectar Angular (app-semana02-practica) a un backend propio (Node + Express + SQLite, → ver 02-Ejercicios/app-semana02-practica/README.md). Hasta ahora los datos (menu) vivían fijos en app.ts; para traerlos de un servidor real hacen falta 3 piezas nuevas.

1️⃣ app.config.ts — dónde se "activan" las piezas globales de la app

ts
// src/app/app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
    provideHttpClient(), // activa HttpClient en TODA la app
  ],
};
ParteQué es
appConfigLa configuración raíz de la app. main.ts la usa para arrancarla (bootstrapApplication(App, appConfig)).
providersLa lista de servicios globales disponibles para inyectar en cualquier componente o servicio de la app.
provideHttpClient()Activa el servicio HttpClient. Sin esto, cualquier clase que lo pida por su constructor falla en tiempo de ejecución con "no provider for HttpClient".

⚠️ Mismo patrón que el error de RouterOutlet de la clase original:providers es una lista — agregar provideHttpClient() debe sumarse, nunca reemplazar los que ya estaban.

2️⃣ Un servicio — la clase que sabe hablar con el backend

ts
// src/app/platillo.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface IPlatillo {
  id: number;
  nombre: string;
  precio: number;
  disponible: boolean;
  categoria: string;
}

@Injectable({
  providedIn: 'root', // un único servicio compartido en toda la app
})
export class Platillo {
  private readonly apiUrl = 'http://localhost:3000/api/platillos';

  constructor(private http: HttpClient) {}

  obtenerTodos(): Observable<IPlatillo[]> {
    return this.http.get<IPlatillo[]>(this.apiUrl);
  }
}
  • Componente vs servicio: un componente (app.ts) muestra cosas en pantalla; un servicio (platillo.ts) hace un trabajo (hablar con el backend) que cualquier componente puede pedir, sin duplicar código.
  • @Injectable({ providedIn: 'root' }): marca la clase como servicio e indica que Angular debe crear una sola instancia, compartida por toda la app (no una por componente).

3️⃣ Inyección de dependencias — constructor(private http: HttpClient)

En vez de que la clase cree su propio HttpClient a mano (new HttpClient()), Angular se lo entrega ya listo por el constructor:

provideHttpClient() en app.config.ts
        │  "aquí hay un HttpClient para repartir"

constructor(private http: HttpClient) en el servicio
        │  Angular se lo inyecta automáticamente al crear el servicio

this.http.get(...) ya se puede usar dentro de la clase

💡 Inyección de dependencias (DI): un patrón donde una clase recibe las herramientas que necesita (por su constructor) en vez de crearlas ella misma. Angular arma y entrega esas dependencias automáticamente.

4️⃣ Observable — un dato que llega después

this.http.get<IPlatillo[]>(url) no devuelve el array directo — devuelve un Observable<IPlatillo[]> (de la librería rxjs), algo parecido a una promesa: representa un dato que todavía no existe, hasta que alguien se suscribe (.subscribe(...)) y dispara la petición HTTP real.

🧪 Tip de entrevista: "¿Qué diferencia hay entre un servicio inyectado por constructor y crear el objeto a mano (new)?" → con inyección de dependencias, Angular controla el ciclo de vida (por ejemplo, una sola instancia compartida con providedIn: 'root') y facilita testear el componente reemplazando esa dependencia por una falsa (mock). Con new, quedas atado a esa implementación exacta.

5️⃣ ngOnInit() — cuándo pedir los datos (el ciclo de vida del componente)

Un componente Angular no nace y muere de golpe: pasa por momentos distintos ("ciclo de vida"), y Angular te avisa de cada uno llamando un método con nombre fijo, si lo declaras. El primero y más usado es ngOnInit:

ts
import { Component, OnInit } from '@angular/core';

export class App implements OnInit {
  menu: IPlatillo[] = []; // empieza vacío

  constructor(private platilloService: Platillo) {}

  // Angular lo llama automáticamente UNA SOLA VEZ, apenas termina de
  // crear el componente (después del constructor).
  ngOnInit(): void {
    this.platilloService.obtenerTodos().subscribe((datos) => {
      this.menu = datos;
    });
  }
}
ParteQué es
implements OnInitLe avisa a TypeScript "esta clase promete tener un método ngOnInit()" (si lo olvidas, TS marca error).
constructor(...)Solo para recibir dependencias (inyección). No es el lugar para hacer trabajo pesado ni pedir datos.
ngOnInit()El lugar recomendado para cargar datos iniciales (llamadas HTTP, etc.), justo cuando el componente ya está listo.

⚠️ ¿Por qué no pedir los datos directo en el constructor? El constructor existe para recibir lo que Angular inyecta (platilloService), no para hacer cosas. Separar "recibir dependencias" de "usarlas" es una convención que evita bugs sutiles (por ejemplo, si el componente todavía no terminó de inicializar sus propiedades).

💡 Hay más "momentos" del ciclo de vida (ngOnDestroy, ngOnChanges, etc.) — se documentan aquí a medida que aparezcan en el curso. Por ahora, ngOnInit es el único que necesitas para cargar datos al arrancar un componente.

🧪 Tip de entrevista: "¿Por qué no hacer la petición HTTP en el constructor?" → el constructor es para inyección de dependencias, no para lógica; Angular garantiza que ngOnInit se ejecuta cuando el componente ya está listo para trabajar, por eso es el lugar correcto para el "arranque".

6️⃣ .subscribe(...) en detalle — "desenvolver" el Observable

ts
this.platilloService.obtenerTodos().subscribe((datos) => {
  this.menu = datos;
});
  • obtenerTodos() devuelve un Observable — en ese instante todavía no se disparó ninguna petición HTTP. Un Observable sin .subscribe() no hace nada (a diferencia de una promesa, que arranca sola).
  • .subscribe(callback) es lo que dispara la petición real y le dice a RxJS "avísame cuando tengas el dato, ejecutando este callback".
  • El callback ((datos) => { this.menu = datos; }) recibe como argumento el array de platillos ya convertido de JSON a objetos JS (Angular lo hace solo, con HttpClient).
obtenerTodos()                    ←  crea el Observable (aún no pasa nada)
      │  .subscribe(datos => ...) ←  esto SÍ dispara la petición HTTP

   (tiempo de espera: el backend procesa la consulta a SQLite)

   el callback se ejecuta con los datos ya listos

💡 Diferencia con una Promise: una Promise arranca apenas se crea; un Observable es "perezoso" (lazy) — no hace nada hasta que alguien se suscribe. Por eso, si nunca llamas .subscribe(), la petición HTTP nunca se envía.

7️⃣ CORS — por qué el navegador bloquea la respuesta del backend

Al conectar Angular (localhost:4200) con el backend propio (localhost:3000), la primera petición falló con un HttpErrorResponse en consola y la lista de platillos quedó vacía, aunque el backend sí respondía bien (ya lo habíamos probado con curl). La causa: CORS.

CORS (Cross-Origin Resource Sharing) es una regla de seguridad que aplican los navegadores (no Angular, no Node): JavaScript de una página no puede leer la respuesta de un servidor en otro origen, a menos que ese servidor lo permita explícitamente con un header.

📌 ¿Qué cuenta como "otro origen"? La combinación protocolo + host + puerto. http://localhost:4200 y http://localhost:3000 son orígenes distintos aunque el host (localhost) sea el mismo — el puerto ya alcanza para que cuenten como diferentes.

Angular (localhost:4200)  ──HTTP──►  Backend (localhost:3000)
        │                                    │
        │        el backend SÍ responde,     │
        │        pero el NAVEGADOR bloquea   │
        │◄── que Angular lea la respuesta ───┘
              (si el backend no manda el header
               Access-Control-Allow-Origin)
  • El backend sí recibe la petición y sí procesa la consulta a SQLite — el bloqueo pasa después, cuando el navegador decide si le entrega esa respuesta al código de Angular o no.
  • Solución: el backend debe mandar el header Access-Control-Allow-Origin indicando qué origen(es) tienen permiso de leer sus respuestas:
js
// backend/index.js
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:4200');
  next();
});
  • Es un middleware de Express: código que se ejecuta antes de cada ruta (app.get('/api/platillos', ...)), y con next() deja seguir la petición hacia la ruta correspondiente.

💡 CORS no es un bug de tu código ni de Angular — es una protección del navegador para que una página cualquiera no pueda leer en silencio datos de otro sitio/servidor sin que ese servidor lo autorice.

🧪 Tip de entrevista: "¿Qué es CORS y quién lo aplica?" → una política de seguridad que aplican los navegadores: bloquean que JS de un origen lea respuestas de otro origen (protocolo+host+puerto), salvo que el servidor lo permita con el header Access-Control-Allow-Origin. El servidor procesa la petición — el navegador es quien decide si entrega la respuesta.

🚦 Segunda vuelta: PATCH/POST necesitan un permiso EXTRA (preflight)

El GET de arriba funcionó con un solo header. Pero al agregar un botón que hace PATCH (para guardar validarStock de verdad en SQLite), volvió a fallar — aunque el header Access-Control-Allow-Origin ya estaba puesto.

Motivo: el navegador distingue entre peticiones "simples" (un GET normal) y "no simples" (PATCH/POST/PUT/DELETE, o cualquiera con un body JSON y el header Content-Type: application/json). Para las no-simples, el navegador manda primero una petición de "permiso" — el preflight — con el método OPTIONS, preguntando "¿me dejas mandar un PATCH con este header?". Si el servidor no responde ese OPTIONS correctamente, el navegador nunca llega a enviar el PATCH real.

Angular hace PATCH ──► el navegador manda antes un OPTIONS (preflight)

                    ¿el backend responde con los headers correctos?
                         │                        │
                        SÍ                       NO
                         │                        │
                 ahora sí manda el PATCH    bloquea todo, ni siquiera
                 real                       intenta el PATCH

Solución — el backend debe responder el OPTIONS con 3 headers, y devolver 204 (sin contenido) para ese preflight:

js
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:4200');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');

  if (req.method === 'OPTIONS') {
    res.sendStatus(204); // solo confirma el permiso, sin lógica de negocio
    return;
  }

  next();
});
HeaderPara qué
Access-Control-Allow-OriginQué origen puede leer la respuesta (ya lo teníamos, para el GET).
Access-Control-Allow-MethodsQué métodos HTTP están permitidos (PATCH, POST, etc. — no solo GET).
Access-Control-Allow-HeadersQué headers custom puede mandar el cliente (aquí, Content-Type, porque mandamos JSON).

💡 Por qué GET no lo necesitó: un GET sin headers especiales cuenta como petición "simple" para el navegador — no dispara preflight. En cuanto agregas un método distinto o un Content-Type: application/json, sí lo dispara.

🧪 Tip de entrevista: "¿Qué es un preflight en CORS?" → una petición OPTIONS que el navegador manda antes de un PATCH/POST/PUT/DELETE (o cualquier petición "no simple"), preguntando si el servidor lo permite. Si el servidor no responde con los headers Access-Control-Allow-Methods / -Headers correctos, el navegador bloquea la petición real sin siquiera enviarla.

🧬 inject() — la forma funcional de pedir dependencias

Hasta ahora, la única forma vista de inyección de dependencias fue el constructor (constructor(private http: HttpClient) {}, ver sección "Servicios, Inyección de Dependencias..." de arriba). inject() es una función que hace lo mismo — pedirle a Angular una dependencia ya lista — pero sin necesitar un constructor.

ts
// Antes (constructor) — ya visto con HttpClient
export class Platillo {
  constructor(private http: HttpClient) {}
}

// Con inject() — mismo resultado, sin constructor
export class Platillo {
  private http = inject(HttpClient);
}
  • inject(HttpClient) — le pide a Angular la instancia de HttpClient que ya está registrada (vía provideHttpClient() en app.config.ts) y la asigna directo a la propiedad http. Es el mismo HttpClient que llegaría por el constructor, solo que pedido de otra forma.
  • Funciona porque Angular ejecuta el código de la clase dentro de un contexto de inyección válido — inject() "sabe" en qué contexto está corriendo y de ahí saca la dependencia. Por eso no se puede llamar inject() en cualquier lado (por ejemplo, dentro de un setTimeout o de un método normal llamado después) — solo funciona en el momento en que Angular está construyendo la clase o ejecutando la función (ver caso de guards abajo).

⚠️ Cuidado con la frase "inject() se puede usar en cualquier lugar" — es una simplificación que se dice mucho en clase, pero no es exacta. Lo correcto: inject() funciona en más lugares que el constructor (no solo en clases — también en guards, resolvers e interceptors funcionales), pero igual solo dentro de un contexto de inyección válido. Fuera de ese contexto (un setTimeout, un callback de addEventListener, un método llamado más tarde por el usuario) inject() falla con un error en tiempo de ejecución ("inject() must be called from an injection context"). No es libertad total, es una restricción distinta a la del constructor, no la ausencia de restricción.

🆕 ¿Por qué existe si el constructor ya funcionaba?

Porque hay lugares donde no hay una clase con constructor: los guards funcionales de rutas (CanActivateFn, ver routing-avanzado.md), los resolvers, los interceptors funcionales, y en general cualquier función suelta que Angular ejecuta dentro de un contexto de inyección:

ts
// auth-guard.ts — una FUNCIÓN, no una clase, no tiene constructor
export const authGuard: CanActivateFn = () => {
  const authService = inject(Auth);   // 👈 única forma de pedir el servicio aquí
  const router = inject(Router);
  // ...
};
  • Antes de inject() (Angular 14), este tipo de guards se escribía como una clase que implementaba CanActivate con el servicio en el constructor — más código, y una clase entera solo para una función de sí/no.

📌 inject() se introdujo en Angular 14. Desde la actualización de la guía de estilo oficial de Angular (alrededor de la v17-v18), se recomienda usar inject() en vez del constructor para pedir dependencias en general — no solo en guards/resolvers, sino en componentes y servicios normales también. El profe lo mencionó como "desde Angular 17 se puede usar un servicio directamente [sin constructor]" — la función existe desde antes (v14), pero es correcto que la recomendación oficial de usarla por defecto es más reciente (v17+).

Constructor (clásico)inject() (moderno)
Dónde funcionaSolo en clases (constructor(...))Clases y funciones sueltas (guards, resolvers, interceptors funcionales)
Sintaxisconstructor(private http: HttpClient) {}private http = inject(HttpClient);
Desde qué versiónAngular 2+ (siempre existió)Angular 14+ (recomendado como default desde ~v17)
VentajaExplícito, familiarMás corto con muchas dependencias; único modo posible fuera de clases

🧪 Tip de entrevista: ¿inject() reemplaza por completo al constructor? → No es obligatorio — ambos siguen funcionando y hacen lo mismo dentro de una clase. La guía de estilo oficial de Angular ahora recomienda inject() como default, pero la razón por la que existe es que hay contextos (guards funcionales, resolvers, interceptors) donde no hay clase ni constructor, y ahí inject() es la única opción.

📎 Ver routing-avanzado.md para el caso real de inject() usado en un CanActivateFn (Route Guard).

🔂 Patrón Singleton (y cómo Angular ya lo aplica solo)

Singleton es un patrón de diseño (no específico de Angular, existe en cualquier lenguaje orientado a objetos): garantiza que una clase tenga una sola instancia en toda la aplicación, y da un punto de acceso único a esa instancia — nadie más puede crear otra copia por su cuenta.

ts
// Singleton "a mano" (fuera de Angular, solo para ver la idea)
class Configuracion {
  private static instancia: Configuracion;

  private constructor() {}   // 👈 privado: nadie puede hacer "new Configuracion()" desde afuera

  static obtenerInstancia(): Configuracion {
    if (!Configuracion.instancia) {
      Configuracion.instancia = new Configuracion();
    }
    return Configuracion.instancia;
  }
}

const a = Configuracion.obtenerInstancia();
const b = Configuracion.obtenerInstancia();
// a === b → true, siempre es LA MISMA instancia
  • constructor privado — es la pieza clave: impide que cualquier otra parte del código haga new Configuracion() directamente. La única forma de conseguir el objeto es a través de obtenerInstancia().
  • static instancia — se guarda una vez (la primera llamada la crea); todas las llamadas siguientes devuelven esa misma instancia guardada, nunca una nueva.

🅰️ Angular ya te da esto gratis con providedIn: 'root'

Todos los servicios que se vieron en el curso (Platillo, Auth, Productoservice, …) usan este patrón sin escribirlo a mano:

ts
@Injectable({
  providedIn: 'root',   // 👈 esto ES el patrón Singleton, manejado por Angular
})
export class Auth { /* ... */ }
  • Angular actúa como la "fábrica" que ya tiene el Configuracion.obtenerInstancia() de arriba, pero automático: la primera vez que alguien pide Auth (por constructor o inject(Auth)), Angular la crea; todas las veces siguientes, en cualquier componente/servicio/guard de la app, entrega esa misma instancia.
  • Por eso el estado de un servicio (como el signal logueado en Auth) es compartido: si un componente llama auth.iniciarSesion(), y otro componente distinto (en otra parte de la app) hace auth.estaLogueado(), ve el cambio — porque es literalmente el mismo objeto en memoria, no una copia.
  • No hace falta escribir el constructor privado ni el static obtenerInstancia() — el Injector de Angular (el sistema de Inyección de Dependencias) hace ese trabajo por detrás. providedIn: 'root' le dice "esta clase es singleton, a nivel de toda la app".

💡 Por qué importa para depurar bugs: si dos componentes muestran datos "desincronizados" de un mismo servicio, casi nunca es porque haya "dos instancias" (con providedIn: 'root' eso no puede pasar) — el problema suele ser change detection (la vista no se refrescó) o estado mutado sin que nadie se entere (ver sección de Signals/reactividad).

🧪 Tip de entrevista: ¿Cómo implementa Angular el patrón Singleton? → Con el Injector (sistema de Inyección de Dependencias) y providedIn: 'root': la primera vez que se pide una dependencia, el injector raíz la crea y la guarda; cualquier inyección posterior (por constructor o inject()) devuelve esa misma instancia, en cualquier parte de la app.

🧪 Tip de entrevista: ¿Un servicio con providedIn: 'root' es SIEMPRE un singleton global único? → Casi siempre sí, pero hay una excepción: si además se declara ese mismo servicio en el array providers de un componente específico, ese componente (y sus hijos) reciben su propia instancia nueva, distinta de la del root — el patrón Singleton en Angular es "por injector", y cada componente con sus propios providers crea un injector hijo nuevo.

🛡️ Route Guards — proteger o interceptar la navegación

Un guard es una función (o clase, en la forma clásica) que Angular Router consulta antes de completar una navegación, para decidir si la deja pasar. Se registran en la ruta (app.routes.ts), no en el componente.

TipoSe ejecuta...Para qué
CanActivateAntes de entrar a una rutaBloquear acceso (p. ej. sin login)
CanActivateChildAntes de entrar a una ruta hijaProteger toda una sección de una vez
CanDeactivateAntes de salir de una rutaConfirmar "¿salir sin guardar?"
CanMatchAntes de que el router decida si la ruta aplicaComo CanActivate, pero puede dejar que se pruebe otra ruta con el mismo path
ts
// forma moderna: una función (CanActivateFn), con inject()
export const authGuard: CanActivateFn = () => {
  const auth = inject(Auth);
  return auth.estaLogueado();   // true = deja pasar, false = bloquea
};

// en app.routes.ts
{ path: 'dashboard', component: Dashboard, canActivate: [authGuard] }

🧪 Tip de entrevista: ¿CanActivate vs CanDeactivate? → CanActivate decide si se puede entrar; CanDeactivate decide si se puede salir (y por eso recibe la instancia del componente como parámetro, para poder preguntarle su estado — p. ej. "¿tienes cambios sin guardar?").

📎 Código real, con CanActivate y CanDeactivate funcionando (más el error de beforeunload vs CanDeactivate) → ver routing-avanzado.md.

⚡ Lazy Loading — cargar código de una ruta solo cuando hace falta

Por defecto, todos los componentes de las rutas (component: Producto, etc.) se importan arriba de app.routes.ts con un import normal — viajan juntos en el mismo bundle inicial (main.js), aunque el usuario nunca visite esa ruta. Lazy Loading carga ese código bajo demanda, solo cuando el usuario navega ahí.

ts
// Eager (de siempre): viaja en main.js
import { Contacto } from './contacto/contacto';
{ path: 'contacto', component: Contacto }

// Lazy: import() dinámico, queda en su propio chunk aparte
{ path: 'contacto', loadComponent: () => import('./contacto/contacto').then(m => m.Contacto) }
  • loadComponent — carga un componente de forma perezosa.
  • loadChildren — carga un array de rutas completo (útil para una sección con varias sub-rutas, agrupadas en un solo chunk).
  • Se verifica con ng build: las rutas lazy aparecen como "Lazy chunk files" separados de main.js; las eager quedan dentro del bundle inicial.

🧪 Tip de entrevista: ¿Cómo confirmas que el lazy loading realmente funciona? → Con ng build, viendo que la ruta genera un chunk separado; la pestaña Network del navegador en modo desarrollo (ng serve) NO sirve para esto, porque el dev server (Vite/esbuild) sirve cada componente bajo demanda sea eager o lazy.

📎 Código real y evidencia del ng build → ver routing-avanzado.md.

📝 Formularios: Template-driven vs Reactivos

Angular tiene dos formas de construir formularios:

Template-driven (ngModel)Reactivos (FormGroup/FormBuilder)
MóduloFormsModuleReactiveFormsModule
Dónde vive la estructuraRepartida: clase + validadores en el HTMLCentralizada: un FormGroup armado en la clase
ValidadoresAtributos HTML (required, minlength...)Funciones TS (Validators.required...)
Bueno paraFormularios simples y pequeñosFormularios grandes/dinámicos, testeo sin DOM
ts
// Reactivo, con FormBuilder (la forma más corta)
formulario = inject(FormBuilder).group({
  nombre: ['', [Validators.required, Validators.minLength(3)]],
  email: ['', [Validators.required, Validators.email]],
});

⚠️ Con FormBuilder, el array de cada campo es [valorInicial, validadorSíncrono, validadorAsíncrono] (hasta 3 posiciones) — varios validadores síncronos van juntos en un array en la 2ª posición ([Validators.required, Validators.email]), nunca sueltos como elementos separados del array principal.

🧪 Tip de entrevista: ¿Cuándo usar cada uno? → Template-driven para formularios simples; Reactivos cuando se necesita testear la validación sin depender del DOM, o armar/cambiar controles desde código.

📎 Código completo de los dos enfoques (y el error real del array de FormBuilder) → ver formularios.md.

📅 El pipe date + SQLite: cuidado con la zona horaria (UTC vs hora local)

Al agregar una columna actualizado_en (para mostrar "última actualización" con el pipe date), la hora se veía mal — mostraba 2:56 AM cuando en Perú eran las 9:57 PM.

Causa: datetime('now') en SQLite devuelve la hora en UTC (Perú está en UTC-5), pero como texto sin ninguna marca de que es UTC:

"2026-07-05 02:56:16"    ← ¿es UTC? ¿es hora local? el texto no lo dice

El navegador, al no ver una Z (UTC) ni un +/- (offset) al final, asume que ya es tu hora local y la muestra tal cual, sin convertir nada. Por eso salía la hora de Greenwich disfrazada de hora peruana.

Solución: guardar la fecha en formato ISO 8601 completo, con T (en vez de espacio) y Z al final (marca explícita de UTC):

sql
-- en vez de: datetime('now')                    -> "2026-07-05 02:56:16"
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')          -- -> "2026-07-05T02:56:16Z"

Con la Z presente, el navegador (y el pipe date de Angular) sabe que debe convertir de UTC a tu zona horaria antes de mostrarlo:

SQLite guarda (UTC):        2026-07-05T02:56:16Z
        │  el navegador ve la "Z" → SÍ convierte a tu zona horaria

Pantalla (Perú, UTC-5):     4/7/26, 9:56 PM   ✅ correcto

💡 Regla mental: cualquier fecha/hora que guardes para mostrar después debe llevar su zona horaria explícita en el texto (Z para UTC, o un offset +05:00). Sin eso, cada quien la interpreta como quiera — normalmente "como si ya fuera la hora local de quien la lee", que casi nunca es correcto.

🧪 Tip de entrevista: "¿Por qué una fecha se ve con la hora equivocada en el navegador?" → probablemente el string de fecha no indica su zona horaria (falta la Z o un offset). Sin esa marca, Date de JavaScript la interpreta como hora local en vez de convertirla desde UTC.

❌ Manejar errores de HttpClient: subscribe({ next, error })

Hasta ahora .subscribe((datos) => { ... }) solo tenía un callback: el que se ejecuta cuando todo sale bien. Pero una petición HTTP también puede fallar (sin internet, el servidor cae, o el backend la rechaza a propósito). Para manejar los dos casos, .subscribe(...) acepta un objeto con dos funciones:

ts
enviarACocina(idPlato: number) {
  this.platilloService.hacerPedido(idPlato).subscribe({
    next: (actualizado) => {
      // Se ejecuta si el backend respondió bien (200 OK).
      this.menu.update((items) => items.map((p) => (p.id === idPlato ? actualizado : p)));
    },
    error: () => {
      // Se ejecuta si el backend respondió con un error (ej. 400, 404, 500).
      this.errorPedido.set('⚠️ No se pudo enviar el pedido: sin stock disponible.');
    },
  });
}
ParteCuándo se ejecuta
nextLa petición funcionó: llegó una respuesta exitosa (200 OK).
errorLa petición falló: el servidor respondió con un código de error, o ni siquiera hubo respuesta (sin conexión).

Del lado del backend, para que el error llegue con sentido, el servidor responde con un código de estado de error y un mensaje (no un 200):

js
// backend/index.js
if (fila.stock <= 0) {
  // VALIDACIÓN: 400 = "Bad Request", la petición no se puede cumplir tal como está.
  res.status(400).json({ error: 'Sin stock disponible' });
  return;
}

HttpClient en Angular detecta automáticamente que 400 es un código de error, y por eso ejecuta el callback error del subscribe(...) en vez del next — no hay que revisar el código de estado a mano.

💡 .subscribe((datos) => ...) de un solo callback (como en ngOnInit) sigue siendo válido — es solo una forma más corta cuando no te interesa manejar el caso de error explícitamente. Se usa la forma { next, error } cuando sí quieres reaccionar distinto ante un fallo.

🧪 Tip de entrevista: "¿Cómo maneja Angular los errores de una petición HTTP?" → HttpClient convierte cualquier respuesta con código de error (4xx/5xx) o fallo de red en el canal error del Observable; se captura con subscribe({ next, error }) en vez de una función sola.

🔘 [disabled] — deshabilitar un botón según una condición (property binding)

html
<button [disabled]="item.stock === 0" (click)="enviarACocina(item.id)">
  Enviar a cocina
</button>
  • [disabled]="condición" son corchetes: eso es property binding — le pasa el valor de una expresión de TypeScript (aquí, un boolean) a una propiedad del elemento HTML.
  • Si item.stock === 0 da true, el botón queda deshabilitado: ni se puede clicar, ni dispara (click).
  • Es validación en la interfaz: evita que el usuario intente pedir algo sin stock, aunque el backend también lo valide (por si alguien llama a la API directo, sin pasar por el botón — nunca confíes solo en la validación del frontend).

💡 Regla mental: paréntesis ( ) = evento (la vista avisa "pasó algo"); corchetes [ ] = property binding (la clase le manda un valor a una propiedad del HTML). [(ngModel)] combina los dos — por eso se le dice "banana in a box" (ya lo viste en la sección de Two-way Binding).

🔎 Array.find() — buscar un elemento en un array

find() es un método de los arrays de JavaScript/TypeScript: recorre el array y devuelve el primer elemento que cumpla una condición (o undefined si ninguno la cumple).

ts
menu: IPlatillo[] = [
  { id: 1, nombre: 'lomo saltado', precio: 20, disponible: true, categoria: 'segundos' },
  { id: 2, nombre: 'ceviche', precio: 24, disponible: true, categoria: 'entradas' },
];

validarStock(idPlato: number) {
  const plato = this.menu.find(p => p.id === idPlato);  // busca el platillo con ese id

  if (plato) {                       // si lo encontró (no es undefined)...
    plato.disponible = !plato.disponible;   // invierte su disponibilidad
  }
}
ParteQué es
this.menu.find(...)Recorre menu buscando el primer elemento que cumpla la condición.
p => p.id === idPlatoLa condición: una función que recibe cada elemento (p) y devuelve true/false.
if (plato)find() puede devolver undefined (si no encontró nada) — este if evita un error si intentas usar plato.disponible sobre algo que no existe.
!plato.disponibleEl operador ! (negación) invierte un boolean: si era true pasa a false y viceversa.

💡 Por qué el if (plato): como find() puede no encontrar nada, TypeScript tipa el resultado como IPlatillo | undefined. Si intentaras hacer plato.disponiblesin comprobar antes que existe, TypeScript marcaría error (no te deja usar propiedades de algo que podría ser undefined).

🧪 Tip de entrevista: "¿Qué devuelve Array.find()?" → el primer elemento que cumple la condición, o undefined si ninguno la cumple (a diferencia de filter(), que devuelve todos los que cumplen, en un array nuevo).

🖥️ Estrategias de renderizado (CSR · SSR · SSG)

"Renderizar" = construir el HTML que ve el usuario. La estrategia define dónde y cuándo se arma ese HTML. Angular te lo pregunta al crear el proyecto (ng new).

SiglaNombre¿Dónde se arma el HTML?¿Cuándo?
CSRClient Side RenderingEn el navegador (cliente), con JavaScript.Cada vez que el usuario entra.
SSRServer Side RenderingEn el servidor, antes de enviarlo.En cada petición.
SSGStatic Site GenerationSe genera una sola vez al compilar (build).Antes de desplegar (páginas ya hechas).

🔍 Cada una en una frase

  • CSR (cliente): el servidor manda un HTML casi vacío + un JS grande; el navegador arma la página. → App rápida al navegar, pero primera carga más lenta y peor SEO.
  • SSR (servidor): el servidor manda el HTML ya construido. → Mejor SEO y se ve contenido antes, pero más carga para el servidor.
  • SSG (estático): las páginas se generan al hacer el build y se sirven como archivos fijos. → Muy rápido y barato, ideal para contenido que no cambia mucho (blogs, documentación, landing).

💡 Regla rápida para elegir:

  • Contenido fijo (blog, landing) → SSG.
  • Necesitas SEO + datos que cambian → SSR.
  • App privada tipo dashboard (detrás de login, SEO no importa) → CSR.

🧪 Tip de entrevista: "¿Diferencia entre CSR y SSR?" → en CSR el HTML lo arma el navegador; en SSR lo arma el servidor. SSR mejora el SEO y el tiempo hasta ver contenido.

📝 El profe escribió statis; lo correcto es static (Static Site Generation).

⚡ Reactividad, Zone.js y el problema de rendimiento

Reactividad = que la vista se actualice sola cuando cambian los datos. Tú cambias una variable y la pantalla se refresca sin que tengas que tocar el DOM a mano.

Para saber cuándo refrescar, Angular usa un proceso llamado detección de cambios (change detection).

🌀 Zone.js (el mecanismo "clásico")

Zone.js es una librería que vigila TODO lo que pasa en la app (clics, peticiones HTTP, temporizadores…). Cada vez que ocurre algo, le avisa a Angular: "pasó algo, revisa si hay que actualizar la vista".

⚠️ El problema (lo del dibujo del profe): cuando ocurre un solo evento, Zone.js hace que Angular revise el árbol ENTERO de componentes para ver cuál cambió. Si tu app tiene ~1.000 componentes (1k c), revisa los 1.000 aunque solo cambió uno. Eso es trabajo desperdiciado → menos rendimiento.

Un clic ──► Zone.js avisa ──► Angular revisa los 1000 componentes 😵
                                (aunque solo cambió 1)

✨ La solución moderna: Signals (desde Angular v17)

Los Signals llegaron a Angular a partir de la versión 17. Son variables "inteligentes" que avisan exactamente qué cambió. Así Angular actualiza solo el componente afectado, no todo el árbol.

📌 Línea de tiempo: Signals = v17. Es la base de la nueva reactividad de Angular y lo que permite el modo zoneless (también v17).

Un clic ──► el signal avisa ──► Angular actualiza SOLO ese componente 🎯
  • Con Signals, Angular puede funcionar sin Zone.js (modo zoneless) → más rápido.
  • Es la dirección hacia la que va Angular (17+).

⚖️ zoneJS vs zoneless (comparativo del profe)

El profe dibujó dos apps iguales con muchos componentes (10k c = 10.000 componentes):

zoneJS (clásico)zoneless (moderno, desde V17)
¿Cómo detecta cambios?Zone.js vigila todo y dispara la revisión.Los Signals avisan qué cambió.
¿Qué revisa ante un evento?Todo el árbol de componentes.Solo el componente que cambió.
Rendimiento con muchos componentesPeor (revisa los 10.000).Mejor (revisa lo justo).
Librería Zone.jsNecesaria.No se necesita → bundle más pequeño.

📌 zoneless se introdujo en Angular V17. Es el modo sin Zone.js que se apoya en Signals. Cuantos más componentes tenga la app, más se nota la mejora.

🧪 Tip de entrevista: "¿Qué es zoneless?" → ejecutar Angular sin Zone.js, usando Signals para la detección de cambios. Disponible desde v17.

🧪 Tip de entrevista: "¿Por qué Signals?" → porque Zone.js revisa todo el árbol en cada evento; los Signals permiten actualizar solo lo que cambió (reactividad granular) y prescindir de Zone.js.

⚠️ Caso real (app-semana02-practica): un ng new reciente (Angular 22) crea el proyecto zoneless por defecto (sin zone.js en package.json). Al conectar HttpClient y guardar la respuesta con this.menu = datos (asignación normal), la petición respondía 200 OK pero la pantalla no se actualizaba — sin signal, nada le avisa a Angular que debe repintar. Solución: menu = signal([]) + this.menu.set(datos). → ver detalle completo en 06-Errores/Angular-TypeScript/2026-07-04-zoneless-menu-no-se-actualiza.md.

🔣 Símbolos de versión ^ y ~ (rangos SemVer)

En el package.json, antes de cada versión hay un símbolo que indica hasta qué actualizaciones automáticas se permiten al hacer npm install. Se basan en SemVer (MAJOR.MINOR.PATCH). → ver detalle práctico en 05-Estructura-proyecto.md.

^ Acento circunflejo (caret)

Permite actualizaciones a versiones menores y de parche (los dos últimos números), siempre que NO cambie el número principal (MAJOR). Sirve para mantener el proyecto actualizado automáticamente con nuevas funciones compatibles.

  • Regla: actualiza mientras el primer dígito que no sea cero se mantenga igual.
  • Ejemplo: ^1.2.3 permite instalar cualquier versión superior a 1.2.3 pero inferior a 2.0.0 (p. ej. 1.3.0 o 1.4.5).

~ Tilde

Permite únicamente actualizaciones de parche (el último número) para la versión menor especificada. Se prefiere en proyectos críticos donde la estabilidad es la máxima prioridad y quieres evitar cualquier cambio en las funcionalidades.

  • Regla: actualiza solo el último dígito; la versión menor (MINOR) queda bloqueada.
  • Ejemplo: ~1.2.3 permite de 1.2.3 hasta < 1.3.0 (p. ej. 1.2.4, 1.2.9).

📊 Resumen visual

                       ^  caret  →  deja subir MINOR + PATCH  (más permisivo)
   MAJOR . MINOR . PATCH
     │       │       │   ~  tilde  →  deja subir SOLO PATCH    (más estricto)
     │       │       │
   nunca   ^ sí     ^ y ~ sí
   sube    ~ no     (ambos)

   ^1.2.3  → de 1.2.3 hasta < 2.0.0   (1.3.0, 1.4.5… ✅   2.0.0 ❌)
   ~1.2.3  → de 1.2.3 hasta < 1.3.0   (1.2.4, 1.2.9… ✅   1.3.0 ❌)

💡 Truco: el caret ^ "apunta hacia arriba" → deja subir más (MINOR); la tilde ~ es más "plana" → casi no deja subir (solo PATCH). Ninguno cruza al siguiente MAJOR.

⚠️ Detalle fino del ^ con versiones 0.x: la regla real es "el primer dígito distinto de cero no cambia". Por eso ^0.2.3 solo permite parches (< 0.3.0), porque ahí el dígito que manda es el MINOR, no el MAJOR.

🧪 Tip de entrevista: "¿^ o ~ para un proyecto crítico?" → ~, porque solo acepta parches (bugfixes) y evita cambios de funcionalidad inesperados.

🎯 Ejercicio test: "¿qué versión instala?"

Pregunta típica de examen: dada la definición en el package.json y la última versión disponible ("que hay ahora"), ¿qué versión instala npm?

Con ^ (caret) — sube MINOR y PATCH, nunca el MAJOR:

DefiniciónQue hay ahoraQue instalaPor qué
^20.3.020.3.1520.3.15Mismo MAJOR (20) → toma la más alta disponible.
^20.3.020.5.020.5.0Mismo MAJOR (20), MINOR puede subir → la más alta.
^20.3.021.0.020.x más alta21 es otro MAJOR → ❌ no salta; se queda en 20.x.

Con ~ (tilde) — sube SOLO PATCH:

DefiniciónQue hay ahoraQue instalaPor qué
~20.3.020.3.1520.3.15Mismo MINOR (20.3) → toma el PATCH más alto.
~20.3.020.5.020.3.x más alta20.5 cambia el MINOR → ❌ no sube; se queda en 20.3.x.

💡 Regla para resolverlo rápido:

  1. Mira el símbolo: ^ deja moverse dentro del MAJOR; ~ dentro del MINOR.
  2. Dentro de ese rango, npm instala siempre la versión más alta disponible.

⚠️ Caso real: cómo el ^ rompe un despliegue (anécdota del profe)

Historia real que contó el profe y que explica por qué esto importa tanto:

  • En el package.json tenían una librería con ^13.0.1.
  • En el servidor de despliegue (el pipeline) se ejecutó npm install.
  • Como ^ permite subir el MINOR, npm instaló 13.1.0 (más nueva).
  • Pero 13.1.0 traía cambios incompatibles → el proyecto se rompió y hubo que cambiar mucha sintaxis.
  • Lo peor: en local seguía funcionando (con la versión vieja ya instalada), así que perdieron muchísimo tiempo descubriendo qué se había roto.
local:        ^13.0.1  →  ya tenía 13.0.1 instalada  → todo OK ✅
despliegue:   ^13.0.1  →  npm install baja 13.1.0     → ¡se rompe! ❌
              (13.1.0 está dentro del rango ^, pero no es compatible)

📌 Moraleja: el ^ puede meter una versión nueva sin que tú cambies nada, y romperte producción. Por eso es clave entender estos símbolos (y, mejor aún, fijar versiones con un lock file → ver abajo).

🔒 package-lock.json y npm ci (la solución al caso anterior)

El package-lock.json es un archivo que npm genera automáticamente y que guarda las versiones EXACTAS que se instalaron (no el rango ^/~, sino el número exacto: 13.0.1). Así todos —tu máquina, la de un compañero, el servidor— instalan exactamente lo mismo.

🆚 package.json vs package-lock.json (la diferencia clave)

En una frase: package.json = lo que quieres (rango, lo escribes tú); package-lock.json = lo que realmente se instaló (versión exacta, lo genera npm).

package.jsonpackage-lock.json
¿Quién lo escribe? (a mano o con npm install).npm, automáticamente.
¿Qué versión guarda?El rango permitido: ^20.3.0.La versión exacta: 20.3.25.
¿Para qué sirve?Declarar qué necesita el proyecto (intención).Reproducir el mismo node_modules en cualquier máquina.
¿Qué paquetes lista?Solo los directos (los que tú instalaste).Todos: los tuyos + los de tus dependencias (árbol completo).
resolved / integrityNo.Sí (URL de descarga + hash de seguridad).
¿Se edita a mano?Sí.No, lo maneja npm.

🍪 Analogía: package.json es la receta ("necesito harina marca X, v20 o superior"); package-lock.json es el ticket de compra ("compré exactamente harina marca X, lote 20.3.25"). La receta dice qué sirve; el ticket garantiza que todos compren lo idéntico.

🧪 Tip de entrevista: "¿Diferencia entre package.json y package-lock.json?" → el primero guarda rangos (intención); el segundo, las versiones exactas instaladas (reproducibilidad). El lock incluye todo el árbol de dependencias + integrity.

Lo que dijo el profe: "si quieres que otra persona instale lo mismo que tú, debe usar el package-lock.json". Ese archivo es el que reproduce el entorno exacto. (Ojo: es el lock, no el package.json a secas — el package.json solo tiene el rango.)

🔬 Qué hay dentro del package-lock.json

Por cada paquete instalado (en node_modules), el lock guarda una ficha con su info exacta. Ejemplo real de @angular/compiler:

jsonc
"node_modules/@angular/compiler": {
  "version": "20.3.25",                          // versión EXACTA instalada
  "resolved": "https://registry.npmjs.org/...",  // de DÓNDE se descargó
  "integrity": "sha512-TSh6gVoQ...",             // huella para verificar que no se alteró
  "license": "MIT",                              // licencia del paquete
  "peer": true,                                  // es una "peer dependency"
  "dependencies": { "tslib": "^2.3.0" },         // de qué depende a su vez
  "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }  // qué Node necesita
}
CampoPara qué sirve
versionLa versión exacta que se instaló (lo que fija el lock).
resolvedLa URL del registro npm de donde se bajó el paquete.
integrityUn hash de seguridad: npm verifica que el paquete descargado es idéntico al original (no manipulado).
licenseLa licencia legal del paquete (MIT, Apache…).
dependenciesLas dependencias propias de ese paquete.
enginesLas versiones de Node con las que ese paquete funciona.

💡 El campo integrity es por seguridad: si alguien alterara el paquete en el registro, el hash no coincidiría y la instalación fallaría (te protege de paquetes manipulados).

npm install vs npm ci

ComandoQué haceCuándo usarlo
npm installResuelve los rangos y puede actualizar dentro de ^/~. Actualiza el lock.Cuando desarrollas y quieres añadir/actualizar paquetes.
npm ciInstala exactamente lo que dice el package-lock.json, sin actualizar nada.En despliegue / CI (servidores, pipelines). Reproducible y seguro.

💡 Lo que debió pasar en la anécdota: si el pipeline hubiera usado npm ci (en vez de npm install), habría instalado 13.0.1 exacta (la del lock) y no se habría roto. Por eso en despliegue se usa npm ci.

⚠️ Sube el package-lock.json al repositorio (no lo ignores en .gitignore). Es lo que garantiza que todos instalen las mismas versiones.

🧪 Tip de entrevista: "¿Diferencia entre npm install y npm ci?" → install resuelve rangos y puede actualizar; ci instala las versiones exactas del lock file, ideal para builds reproducibles en CI/CD.

🧰 Qué trae Angular ya instalado (vía npm)

Cuando creas un proyecto con ng new, npm instala automáticamente un montón de cosas para que puedas trabajar desde el primer minuto. Dos importantes que mencionó el profe:

nodeJs
 └─ npm
     ├─ angular: TypeScript          ← Angular se programa en TypeScript (no JS puro)
     └─ servidor web de desarrollo   ← el que arranca `ng serve` / `npm start`
  • TypeScript: Angular no se escribe en JavaScript puro, sino en TypeScript (JS + tipos). Viene incluido; por eso no tienes que instalarlo en cada proyecto.
  • Servidor web de desarrollo: un mini-servidor local solo para programar. Es el que levanta ng serve en http://localhost:4200 con recarga automática. No es un servidor de producción.

💡 Por eso un proyecto Angular ya funciona apenas lo creas: trae el lenguaje (TypeScript) y el servidor de pruebas listos. Todo eso queda anotado en las dependencias del package.json. → ver 05-Estructura-proyecto.md.

🤖 MCP en Angular (Model Context Protocol)

MCP (Model Context Protocol) es un estándar que permite que un asistente de IA (Cursor, Antigravity, Claude, Copilot…) se conecte a herramientas externas y trabaje con ellas. Piensa en MCP como un "enchufe universal" entre la IA y las herramientas.

🔌 Analogía: MCP es al asistente de IA lo que un puerto USB-C es a tus dispositivos: un conector estándar para que cualquier IA "hable" con cualquier herramienta.

🅰️ El servidor MCP de Angular

El Angular CLI trae su propio servidor MCP. Al conectarlo, tu asistente de IA deja de "adivinar" sobre Angular y trabaja con información oficial y actualizada del CLI.

CapacidadQué hace
get_best_practicesLe da a la IA las buenas prácticas oficiales de Angular antes de escribir código.
search_documentationBusca en angular.dev en tiempo real (docs actualizadas, no de memoria).
Generar códigoLa IA puede pedir al CLI generar componentes, rutas, guards… (ng generate).
Analizar el workspaceLista los proyectos y entiende la estructura para razonar mejor.

⚙️ Cómo se activa (idea general)

bash
ng mcp          # comando del CLI para preparar/configurar el servidor MCP

Luego se registra el servidor en el IDE. En VS Code va en .vscode/mcp.json, dentro de una clave servers (así lo confirma la doc oficial de Angular):

json
{
  "servers": {
    "angular-cli": {
      "command": "npx",
      "args": ["-y", "@angular/cli", "mcp"]
    }
  }
}

💡 Este archivo no lo genera ng new automáticamente: lo agregas tú a mano (o VS Code lo crea cuando usas su UI de "Add MCP Server"). Por eso un proyecto recién creado con ng new no trae .vscode/mcp.json, aunque sí trae extensions.json, launch.json y tasks.json.

📌 Versiones: el servidor MCP de Angular apareció como experimental (~v20) y es estable desde Angular v21 — justo la versión del CLI que tienes instalada.

💡 Por qué importa: las IAs entrenan con datos hasta cierta fecha y Angular cambia rápido (Signals, zoneless…). Con el MCP, la IA consulta la documentación viva y sigue las buenas prácticas actuales en vez de sugerir código antiguo.

⚠️ No confundir: "construir una app que use MCP" (tu app es cliente/servidor MCP) es distinto de "usar el servidor MCP del Angular CLI" (para que la IA te ayude a programar en Angular). Aquí hablamos de lo segundo.

📦 Gestores de dependencias de Node

Un gestor de dependencias es la herramienta que descarga, instala y actualiza las librerías (paquetes) que tu proyecto necesita, y las guarda en node_modules/. Lleva la cuenta de qué versión usas en el package.json para que el proyecto sea reproducible en otra máquina.

Vienen con Node: al instalar Node ya tienes npm. Los otros se instalan aparte.

GestorQué es
npmEl gestor oficial de Node (viene incluido). El más usado por defecto.
pnpmAlternativa más rápida y que ahorra disco: guarda los paquetes una sola vez y los enlaza entre proyectos.
yarnAlternativa creada por Facebook; popular por su velocidad e instalaciones reproducibles.

💡 Los tres hacen lo mismo (instalar dependencias); cambian en velocidad, uso de disco y algunos comandos. En este curso lo normal es usar npm.

⚠️ Recuerda: tu Angular CLI mostró Package Manager: yarn, así que ng new intentará usar yarn. Si quieres npm: ng new app --package-manager npm. → ver 01-Comandos.md.

📝 En la pizarra el profe escribió npmn; lo más probable es que se refiera a pnpm (el segundo gestor más conocido junto a npm y yarn). Confirmar con el profe.

🔄 Ciclo de vida de un componente (hooks)

Un componente Angular no nace y muere de golpe: pasa por momentos concretos (crearse, recibir datos, pintarse, destruirse), y Angular avisa de cada uno llamando un método con nombre exacto. A esos métodos se les llama hooks del ciclo de vida.

MomentoMétodoCuándo se dispara
0. Nace el objetoconstructor()Al crear la instancia de la clase — no es un hook de Angular, es JS/TS puro, se ejecuta antes que cualquier otro.
1. Cambia un @InputngOnChanges(changes: SimpleChanges)Solo si el componente tiene al menos un @Input(). Se dispara antes que ngOnInit (incluida la primera vez que llega el valor inicial) y cada vez que el padre le pasa un valor nuevo. changes['prop'].currentValue/.previousValue dan el valor nuevo/anterior.
2. Listo para usarsengOnInit()Una sola vez, justo después de que Angular termina de armar el componente (ya puede leer sus @Input). Aquí se inicializan datos, no en el constructor.
3. Se destruyengOnDestroy()Justo antes de quitar el componente de la pantalla (p. ej. al salir con @if/@else o cambiar de ruta). Sirve para "limpiar" (cancelar suscripciones, timers, etc.).
ts
import { Component, OnInit, OnDestroy } from '@angular/core';

export class Saludo implements OnInit, OnDestroy {

  constructor() {
    console.log('nace (constructor)');
  }

  ngOnInit(): void {
    console.log('listo (ngOnInit)');
  }

  ngOnDestroy(): void {
    console.log('se destruye (ngOnDestroy)');
  }
}
  • implements OnInit, OnDestroy: son interfaces de @angular/core que obligan (por TypeScript) a escribir el método con el nombre exacto si la clase dice que lo implementa. Angular igual llamaría al método por convención de nombre aunque no se declare implements, pero declarar la interfaz le pide a TypeScript que verifique en compilación que el método existe.
  • Caso de uso real de ngOnInit: inicializar una propiedad con un valor (p. ej. this.mensaje = 'Bienvenido al curso') en vez de hacerlo en el constructor, para no mezclar la creación del objeto con la lógica de arranque del componente.

🧪 Tip de entrevista: ¿Por qué usar ngOnInit en vez de poner todo en el constructor? → El constructor es de JavaScript/TypeScript (crea el objeto); ngOnInit es de Angular y se garantiza que corre después de que los @Input ya tienen valor. Poner lógica que depende de esos @Input en el constructor puede fallar porque todavía no llegaron.

🧪 Tip de entrevista: ¿En qué orden se ejecutan ngOnChanges y ngOnInit? → ngOnChanges siempre antes, y solo si el componente declara al menos un @Input(). ngOnInit corre una sola vez; ngOnChanges se repite cada vez que cambia algún @Input (confirmado en consola: nacientoy en ngOnChangesentre al ngOnInit).

→ ver 01-Clases/ciclo-de-vida.md (clase completa, con el código real de saludo y bienvenida y los errores/erratas que salieron).