Apariencia
🍳 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
- Nuevo proyecto
app-semana02— mismo patrón que el proyecto anterior. - Repaso de TypeScript: variables tipadas, funciones tipadas e interfaces.
- El restaurante "TecyLab": código real de
app.ts+app.html. - Data Binding: interpolación, Event Binding y Two-way Binding.
- Código final de la clase, ya sin errores.
- 3 errores reales que salieron al armar el ejercicio, y su arreglo.
- Control flow en el template:
@if/@else if/@elsey@for. - Pipes básicos:
uppercaseycurrency. - Ejercicio "hoja de pedidos": junta todo lo anterior (
@for+@if/@else+currencycon formato) sobre un array de platillos tipado.
🆕 El proyecto
Carpeta/proyecto:
app-semana02(creado conng new app-semana02 --style=css --routing --package-manager=npm, ya instalado en02-Ejercicios/app-semana02/).Signal
title:signal('app-semana02')→ sigue la misma convención que el proyecto anterior (app-semana01poníasignal('app-semana01')): eltitleguarda el nombre/slug del proyecto. → ver05-Estructura-proyecto.md.app.htmlinicial:html<h2>BIENVENIDO AL CURSO</h2>Mismo patrón que el
HOLA MUNDOdel primer proyecto: primer cambio visible para confirmar que el live reload funciona. → ver05-Estructura-proyecto.md(secciónapp.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 claseApp): 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. → ver02-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 aplicados al código real de esta clase (
cocineroPrincipal,llamarMesero()/llamarSeguridad(),comentarioPlato). → ver también04-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.saltarydibujarson 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 propiedadcomentarioPlatoy un<input>. → ver02-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():
› 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í):
| Error | Causa | Arreglo | Nota 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 element | Al 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/@elseterminó 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 en02-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.mda cada rato.
📌 Pendiente:
IPlatillotiene la propiedadcategoria(además deid,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 IPlatilloahora tiene 2 propiedades nuevas:disponible: booleanycategoria: string. Cualquier objeto del arraymenudebe tener las 5 propiedades (id,nombre,precio,disponible,categoria).menu: IPlatillo[]— un array tipado:IPlatillo[]significa "un array donde cada elemento es unIPlatillo". 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,itemes el platillo de esa vuelta (item.nombre,item.precio,item.disponible).{{ item.precio | currency : 'PEN' : 'symbol' : '1.2-2' }}— el pipecurrencyacepta más argumentos aparte del código de moneda:'PEN'→ la moneda (soles).'symbol'→ mostrar el símbolo (S/) en vez del código (PEN).'1.2-2'→ cuántos decimales mostrar (mínimo 2, máximo 2 — siempre 2 decimales exactos). → ver02-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 poridconthis.menu.find(p => p.id === idPlato)(método de array de JavaScript: recorre y devuelve el primer elemento que cumpla la condición, oundefinedsi ninguno cumple). Si lo encuentra, invierte su disponibilidad (!plato.disponible): disponible pasa a agotado y viceversa. → nota nueva en02-Conceptos.mdsobreArray.find().enviarACocina(nombrePlato: String)— de momento solo haceconsole.log; aún no está conectado a ningún botón en el HTML (nivalidarStock). 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 invocavalidarStocktodavía.
📝 Detalle de tipos:
enviarACocina(nombrePlato: String)usaStringcon mayúscula (el objeto envoltorio de JS/TS), pero lo normal y recomendado en TypeScript esstringen minúscula (el tipo primitivo). Funciona igual porque TS es permisivo aquí, pero la convención/buenas prácticas pidenstringminú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
@elsefinal (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?
trackle 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@fores 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 defectocurrencymostrarí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 enimports: [...]del componente. Al agregarFormsModulese reemplazóRouterOutleten vez de sumarlo — la lista quedó incompleta, así que Angular ya no encontraba ese elemento.