Skip to content

🍳 Clase 2

📅 2026-07-04 · 🗂️ Proyecto: app-semana02 · 🍽️ Nuevo proyecto: restaurante "TecyLab"

Arranca un ejercicio nuevo (tema restaurante) y el profe repasa fundamentos de TypeScript (tipos, funciones, interfaces) antes de usarlos en el componente real.

🗂️ Temas de esta clase

  1. Nuevo proyecto app-semana02 — mismo patrón que el proyecto anterior.
  2. Repaso de TypeScript: variables tipadas, funciones tipadas e interfaces.
  3. El restaurante "TecyLab": código real de app.ts + app.html.
  4. Data Binding: interpolación, Event Binding y Two-way Binding.
  5. Código final de la clase, ya sin errores.
  6. 3 errores reales que salieron al armar el ejercicio, y su arreglo.
  7. Control flow en el template: @if / @else if / @else y @for.
  8. Pipes básicos: uppercase y currency.
  9. Ejercicio "hoja de pedidos": junta todo lo anterior (@for + @if/@else + currency con formato) sobre un array de platillos tipado.

🆕 El proyecto

  • Carpeta/proyecto: app-semana02 (creado con ng new app-semana02 --style=css --routing --package-manager=npm, ya instalado en 02-Ejercicios/app-semana02/).

  • Signal title: signal('app-semana02') → sigue la misma convención que el proyecto anterior (app-semana01 ponía signal('app-semana01')): el title guarda el nombre/slug del proyecto. → ver 05-Estructura-proyecto.md.

  • app.html inicial:

    html
    <h2>BIENVENIDO AL CURSO</h2>

    Mismo patrón que el HOLA MUNDO del primer proyecto: primer cambio visible para confirmar que el live reload funciona. → ver 05-Estructura-proyecto.md (sección app.html).

📖 Repaso de TypeScript (dejado comentado en app.ts como referencia)

Antes de escribir la lógica real, el profe dejó un bloque comentado en app.ts a modo de acordeón/repaso de TypeScript básico:

ts
// variables con tipo
let nombreRestaurante: string = "Sabores Modernos";
let aforoMaximo: number = 60;
let estaAbierto: boolean = true;

// función con parámetros y retorno tipados
function calcularPrecioDeJugoConDescuento(precioBase: number, descuento: number): number {
  return precioBase - descuento;
}

// interfaz: define la "forma" de un objeto
interface IPlatillo {
  id: number;
  nombre: string;
  precio: number;
}

Esto amplía lo ya visto en 02-Conceptos.md (tipos de propiedades y métodos) con el concepto de interfaz. → ver 02-Conceptos.md (sección de interfaces).

💡 Que este código esté comentado (/* ... */) es intencional: es solo un recordatorio en pantalla, no código que se ejecute todavía. Es normal verlo así antes de que el profe lo "descomente" y lo use de verdad en la clase de hoy.

  • [ ] 📌 Queda un comentario cortado en la captura: //este metodo es usad... (se perdió el resto de la frase). Completar cuando se vea el resto de la clase.

🍽️ El restaurante "TecyLab": código real de app.ts + app.html

Con el código ya visible, se confirma cómo arma la pantalla del navegador ("BIENVENIDO AL RESTAURANTE TECYLAB" / "Bienvenido a su mesa, Señor tecylab"):

  • cocineroPrincipal (no es el proyecto, es una propiedad de la clase App): guarda el texto "Señor tecylab". Se muestra en el <h3> con interpolación ({{cocineroPrincipal}}) — de ahí sale el "Señor tecylab" que se ve en pantalla.
  • servirPlatillo(plato: IPlatillo) usa un template literal (comillas invertidas ` con ${ } adentro) para armar el mensaje de consola combinando texto fijo + variables. → ver 02-Conceptos.md (sección de template literals).
  • llamarMesero() / llamarSeguridad() son los métodos reales que disparan los botones con Event Binding: (click)="llamarMesero()" y (click)="llamarSeguridad()". Cierran el círculo del tema "Data Binding" de más abajo: interpolación (mostrar) + evento (reaccionar).

💡 El texto de los botones está en mayúsculas ("LLAMAR AL MESERO") solo por estilo del profe, no es una regla de Angular — es HTML/texto normal.

🔀 Data Binding: los "canales de comunicación"

El profe introdujo el tema con este comentario en el código, y luego completó el punto 2 con ejemplos concretos de eventos:

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

🗺️ Diagrama de los 3 canales aplicados al código real de esta clase (cocineroPrincipal, llamarMesero() / llamarSeguridad(), comentarioPlato). → ver también 04-Recursos/imagenes/clase2-data-binding.png.

ts
//Data Binding (los canales de comunicacion)
/*
1.- Interpolacion  {{}}
2.- EventBinding  (evento)  (saltar) (dibujar)
3.- two way binding  [(ngModel)]
*/
  • 1. Interpolación {{ }} — ya la veníamos usando (mostrar un dato de la clase en la vista).
  • 2. Event Binding (evento) — la vista avisa a la lógica que pasó algo. saltar y dibujar son ejemplos de nombres de acciones/métodos que se podrían disparar con un evento (p. ej. <button (click)="saltar()"> o (click)="dibujar()").
  • 3. Two-way Binding [(ngModel)] — combina interpolación + evento en un solo canal, en ambas direcciones. Ya se usó de verdad con la propiedad comentarioPlato y un <input>. → ver 02-Conceptos.md (sección Two-way Binding, con el ejemplo completo).

Detalle completo → ver 02-Conceptos.md (sección Data Binding).

✅ Código final de la clase (app.ts + app.html, ya sin errores)

ts
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';   // habilita [(ngModel)]
import { RouterOutlet } from '@angular/router'; // habilita <router-outlet/>

// la interfaz va FUERA de la clase (no es un miembro válido de clase)
interface IPlatillo {
  id: number;
  nombre: string;
  precio: number;
}

@Component({
  selector: 'app-root',
  imports: [FormsModule, RouterOutlet], // los dos, uno no reemplaza al otro
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {
  cocineroPrincipal: string = 'Señor tecylab';
  comentarioPlato: string = '';

  servirPlatillo(plato: IPlatillo): void {
    console.log(`El ${this.cocineroPrincipal} esta preparando: ${plato.nombre}`);
  }

  llamarMesero() {
    console.log('alerta mesero la mesa de sandivel necesita asistencia');
  }

  llamarSeguridad() {
    console.log('alerta mesero la mesa de sandivel necesita a seguridad');
  }
}
html
<h2>BIENVENIDO AL RESTAURANTE TECYLAB</h2>

<h3>Bienvenido a su mesa, {{cocineroPrincipal}}</h3>

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

<input type="text" [(ngModel)]="comentarioPlato" placeholder="Aqui puede agregar notas">
<router-outlet/>

Al hacer clic en cada botón, esto es lo que se ve en la consola del navegador (F12 → pestaña Console) — el console.log() de llamarMesero()/llamarSeguridad():

DevTools · Console
 clic en "LLAMAR AL MESERO"
alerta mesero la mesa de sandivel necesita asistencia
 clic en "LLAMAR SEGURIDAD"
alerta mesero la mesa de sandivel necesita a seguridad

🐛 Errores que salieron en esta clase (y su arreglo)

Se juntaron 3 errores reales al ir armando este ejercicio, todos por descuidos al escribir/mover código (no por Angular en sí):

ErrorCausaArregloNota completa
Cannot find name 'Iplatillo' (TS2304)El parámetro usaba Iplatillo (p minúscula) pero la interfaz se llamaba distinto/estaba comentada.Usar el nombre exacto IPlatillo en los dos lugares (TS distingue mayúsculas/minúsculas).../06-Errores/Angular-TypeScript/2026-07-04-nombre-de-tipo-no-coincide.md
A constructor, method, accessor... was expected (TS1068)Se escribió interface IPlatillo {...} dentro del cuerpo de la clase App.Mover la interfaz fuera de la clase (junto a los import, antes de @Component).../06-Errores/Angular-TypeScript/2026-07-04-interfaz-dentro-de-clase.md
NG8001: 'router-outlet' is not a known elementAl agregar FormsModule a imports, se reemplazó RouterOutlet en vez de dejar los dos.imports: [FormsModule, RouterOutlet] — es una lista, se suma, no se reemplaza.../06-Errores/Angular-TypeScript/2026-07-04-router-outlet-no-reconocido.md

💡 Patrón que se repite: los 3 errores son de "algo no coincide exactamente" (nombre mal escrito, elemento en el lugar equivocado, lista incompleta). TypeScript y Angular son muy estrictos con la sintaxis exacta — ahí está el 90% de los errores de un principiante. → ver 00-Notas/06-Lecciones-aprendidas.md (resumen acumulado de estos patrones, útil para no repetirlos en la próxima clase).

🔀 Nuevo tema: control flow @if / @else if / @else

El profe pasó a explicar los condicionales dentro del template, con un ejemplo de notas de un alumno (promedio → normal / bueno / excelente). Sintaxis correcta (ya documentada con detalle en 02-Conceptos.md):

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>
}

Reemplaza a la vieja directiva *ngIf. → ver 02-Conceptos.md (sección Control flow en el template) para el detalle completo y el tip de entrevista.

También se vieron un segundo ejemplo de @if/@else (acceso permitido/no permitido) y el bloque @for (repetir por cada elemento de una lista, con track y @empty para el caso de lista vacía) — ejemplo del listado de PCs seleccionadas. Todo con detalle completo, incluida una corrección sobre track, en 02-Conceptos.md (secciones Control flow: @if/@else y Control flow: @for).

🧴 Pipes básicos: uppercase y currency

El profe siguió con pipes: transformaciones que se aplican a un dato directo en el template, con el símbolo |. Ejemplo visto:

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

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

Detalle completo (con la corrección de sintaxis) → ver 02-Conceptos.md (sección Pipes básicos).

🧾 Ejercicio "Hoja de pedidos" (práctica propia, junta todo lo visto hoy)

📝 Este ejercicio lo armé por mi cuenta como práctica después de la clase, combinando los temas de hoy — no es algo que el profe haya mostrado en vivo.

Ejercicio que combina, en un solo componente, casi todos los temas de la clase: @for, @if/@else, el pipe currency con formato, un array tipado con una interfaz, y un método que busca y modifica un elemento del array.

Enunciado: arma un componente que liste los platillos de un menú con @for, marcando con @if/@else si cada uno está disponible o agotado, mostrando el precio formateado con el pipe currency (en soles, 2 decimales). El array de platillos debe estar tipado con una interfaz (id, nombre, precio, disponible, categoria). Intenta escribirlo antes de abrir la solución.

Ver solución
ts
import { CurrencyPipe } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

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

@Component({
  selector: 'app-root',
  imports: [FormsModule, CurrencyPipe],
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {
  nombreRestaurante: string = 'Java - Angular 21';
  meseroAsignado: string = 'Ronald Front Developer';
  notaEspecial: string = '';

  menu: IPlatillo[] = [
    { id: 1, nombre: 'lomo saltado', precio: 20.0, disponible: true, categoria: 'segundos' },
    { id: 2, nombre: 'ceviche', precio: 24.0, disponible: true, categoria: 'entradas' },
    { id: 3, nombre: 'limonada', precio: 5.0, disponible: true, categoria: 'bebidas' },
  ];

  enviarACocina(nombrePlato: String) {
    console.log(`enviando a cosina ${nombrePlato}`);
  }

  validarStock(idPlato: number) {
    const plato = this.menu.find((p) => p.id === idPlato);
    if (plato) {
      plato.disponible = !plato.disponible;
    }
  }
}
html
<div>
  <header>
    <h1>{{ nombreRestaurante }}</h1>
    <p>Mesero atendiendo: <strong>{{ meseroAsignado }}</strong></p>
  </header>

  <section>
    <h4>Notas</h4>
    <input type="text" [(ngModel)]="notaEspecial" placeholder="Mensaje" />
    <p>Nota especial: <strong>{{ notaEspecial }}</strong></p>
  </section>

  <section>
    <h4>hoja de pedidos</h4>

    @for (item of menu; track item.id) {
      <h3>{{ item.nombre }}</h3>
      <p>{{ item.precio | currency : 'PEN' : 'symbol' : '1.2-2' }}</p>

      @if (item.disponible) {
        <div>✅ STOCK DISPONIBLE</div>
      } @else {
        <div>❌ AGOTADO TEMPORALMENTE</div>
      }
    }
  </section>
</div>

💡 Versión final del archivo (revisada el mismo día): el @if/@else terminó con emojis (✅ / ❌) en vez de texto plano — mismo condicional, solo cambia lo que se muestra dentro de cada <div>.

🗒️ El HTML terminó con comentarios explicando cada bloque

En la versión final de app.html, cada técnica (interpolación, two-way binding, @for, el pipe currency, @if/@else) quedó comentada directamente en el código, a modo de apunte dentro del propio archivo:

html
<!-- ========================================= -->
<!-- ENCABEZADO DE LA PÁGINA -->
<!-- ========================================= -->
<header>
  <!--
  INTERPOLACIÓN
  Símbolo: {{ }}
  Permite mostrar el contenido de una variable del componente TypeScript dentro del HTML.
  -->
  <h1>{{ nombreRestaurante }}</h1>
  ...
</header>
  • Los bloques <!-- ===== TÍTULO ===== --> son comentarios HTML normales usados como separadores visuales de sección (ENCABEZADO, SECCIÓN DE NOTAS, LISTA DE PLATILLOS). No es sintaxis especial de Angular, es una convención de estilo para que el archivo sea más fácil de leer al crecer. → nota nueva en 02-Conceptos.md (sección Comentarios como separadores de sección).
  • Que el propio código explique "qué es cada símbolo" (en vez de solo el nombre de la técnica) es justo el mismo objetivo de estas notas: sirve para repasar sin tener que volver a 02-Conceptos.md a cada rato.

📌 Pendiente: IPlatillo tiene la propiedad categoria (además de id, nombre, precio, disponible), pero el HTML todavía no la muestra en ningún lado (ni con interpolación ni con @if). Es de esperar que se use más adelante, quizás para agrupar o filtrar el menú por categoría ('entradas', 'segundos', 'bebidas').

🔍 Cómo funciona, pieza por pieza

  • interface IPlatillo ahora tiene 2 propiedades nuevas: disponible: boolean y categoria: string. Cualquier objeto del array menu debe tener las 5 propiedades (id, nombre, precio, disponible, categoria).
  • menu: IPlatillo[] — un array tipado: IPlatillo[] significa "un array donde cada elemento es un IPlatillo". Si a un objeto del array le falta una propiedad o le sobra, TypeScript avisa al escribirlo.
  • @for (item of menu; track item.id) { ... } — repite el bloque una vez por cada platillo del array. Dentro, item es el platillo de esa vuelta (item.nombre, item.precio, item.disponible).
  • {{ item.precio | currency : 'PEN' : 'symbol' : '1.2-2' }} — el pipe currency acepta más argumentos aparte del código de moneda:
    1. 'PEN' → la moneda (soles).
    2. 'symbol' → mostrar el símbolo (S/) en vez del código (PEN).
    3. '1.2-2' → cuántos decimales mostrar (mínimo 2, máximo 2 — siempre 2 decimales exactos). → ver 02-Conceptos.md (sección Pipes básicos, ya actualizada con esto).
  • @if (item.disponible) { ... } @else { ... }dentro de cada vuelta del @for, decide si mostrar "STOCK DISPONIBLE" o "AGOTADO TEMPORALMENTE" según ese platillo puntual. Los bloques de control flow se pueden anidar sin problema.
  • validarStock(idPlato: number) — busca el platillo por id con this.menu.find(p => p.id === idPlato) (método de array de JavaScript: recorre y devuelve el primer elemento que cumpla la condición, o undefined si ninguno cumple). Si lo encuentra, invierte su disponibilidad (!plato.disponible): disponible pasa a agotado y viceversa. → nota nueva en 02-Conceptos.md sobre Array.find().
  • enviarACocina(nombrePlato: String) — de momento solo hace console.log; aún no está conectado a ningún botón en el HTML (ni validarStock). Como es práctica propia, queda pendiente agregar yo mismo los botones para dispararlos, típico Event Binding ((click)="enviarACocina(item.nombre)" / (click)="validarStock(item.id)") — por eso todos los platillos se ven siempre como "STOCK DISPONIBLE": nada invoca validarStock todavía.

📝 Detalle de tipos: enviarACocina(nombrePlato: String) usa String con mayúscula (el objeto envoltorio de JS/TS), pero lo normal y recomendado en TypeScript es string en minúscula (el tipo primitivo). Funciona igual porque TS es permisivo aquí, pero la convención/buenas prácticas piden string minúscula siempre que sea un valor de texto simple.

📌 Quedan 2 comentarios del profe anunciando lo próximo: "Formatear fecha con pipe" y "Validaciones con pipe" — pendiente de documentar cuando se vea el código.

❓ Preguntas y respuestas

1. ¿Qué pasa si el promedio no cae en ninguna condición de un @if/@else if/@else?

No puede pasar: el @else final (sin condición) es el "resto de los casos", siempre se cumple si ninguno de los anteriores lo hizo. Por eso en el ejemplo de notas, un promedio mayor a 17 siempre cae en "Eres un alumno excelente".

2. ¿Para qué sirve track en un @for, y qué pasa si se te olvida ponerlo?

track le dice a Angular por qué propiedad identificar cada elemento (aquí, item.id) para no perder de vista cuál es cuál si la lista cambia. A diferencia del viejo *ngFor, en @for es obligatorio — si lo omites, Angular directamente marca error de compilación, no lo deja pasar en silencio.

3. ¿Por qué {{ item.precio | currency : 'PEN' : 'symbol' : '1.2-2' }} necesita 3 argumentos y no solo 'PEN'?

El primero es la moneda (PEN), pero por defecto currency mostraría el código (PEN 20.00). El segundo argumento ('symbol') pide el símbolo (S/) y el tercero ('1.2-2') fija los decimales (siempre 2). Sin esos dos extra, el pipe funciona pero no se ve igual de prolijo.

4. En el error NG8001: 'router-outlet' is not a known element, ¿por qué el problema no era el HTML sino el arreglo imports?

Porque <router-outlet/> sí estaba bien escrito en el template; lo que faltaba era que Angular supiera reconocerlo, y eso se declara en imports: [...] del componente. Al agregar FormsModule se reemplazó RouterOutlet en vez de sumarlo — la lista quedó incompleta, así que Angular ya no encontraba ese elemento.