Módulo 01: Entorno y Angular CLI
Objetivos del módulo
- Entender qué hace Angular CLI y qué herramientas coordina por debajo
- Instalar una versión de Node compatible y Angular CLI, y elegir entre la versión activa y la LTS
- Crear proyectos con
ng newy escoger sus opciones con criterio - Reconocer cada fichero de un proyecto recién generado y seguir el arranque de la aplicación desde
index.html - Leer y modificar
package.json,angular.jsony los trestsconfig - Desarrollar con
ng serve, usar la recarga en caliente y conectar con la API EMPRESA mediante un proxy - Generar código con
ng generatey cambiar sus valores por defecto - Compilar para producción con
ng build, entender presupuestos, hashes,base hrefy entornos - Ejecutar pruebas con
ng testy Vitest, e interpretar un fallo - Actualizar un proyecto con
ng updatesin saltarse versiones mayores - Preparar VS Code, usar Angular DevTools y las utilidades
ngde la consola - Conectar un asistente de IA con el servidor MCP de Angular CLI
1. Qué es Angular CLI y por qué se usa siempre
En el Curso de JavaScript podías abrir un .html con doble clic y ver el resultado. Una aplicación Angular no funciona así. Tu código está en TypeScript, que el navegador no entiende; tus plantillas usan una sintaxis propia (@if, {{ }}, [propiedad]) que hay que traducir a JavaScript; y cientos de módulos deben unirse en unos pocos ficheros optimizados. Hacerlo a mano exigiría configurar un compilador, un empaquetador, un servidor de desarrollo y un ejecutor de pruebas, y mantenerlos sincronizados en cada versión.
Angular CLI es el programa de línea de órdenes que hace todo eso por ti. Se invoca con ng y es la forma oficial de trabajar con Angular. No es un añadido opcional: en la práctica, todo proyecto Angular profesional lo usa.
Figura 1. Cada orden de ng delega en una herramienta especializada.
| Orden | Qué hace | Herramienta real |
|---|---|---|
ng new | Crea un proyecto completo | Schematics (plantillas de código) y npm |
ng generate | Crea componentes, servicios, rutas… | Schematics |
ng serve | Compila y sirve la aplicación mientras programas | Compilador de Angular, esbuild y Vite |
ng build | Prepara la aplicación para publicarla | Compilador de Angular y esbuild |
ng test | Ejecuta las pruebas unitarias | Vitest y jsdom |
ng update | Sube de versión y adapta tu código | Migraciones automáticas |
ng mcp | Da acceso al proyecto a asistentes de IA | Model Context Protocol |
📘 Concepto: Un schematic es un programa que genera o modifica ficheros de código. Un builder es un programa que realiza una tarea sobre el proyecto, como compilar o probar. El CLI no sabe compilar por sí mismo: mira en
angular.jsonqué builder corresponde a cada orden y lo ejecuta. Por eso casi todo su comportamiento se puede configurar.
El resultado es que cambiar de herramienta interna no te afecta. Hace unos años ng build usaba Webpack y ng test usaba Karma. Hoy usan esbuild y Vitest, y las órdenes que escribes son las mismas.
2. Node.js: el motor de las herramientas
Tu aplicación final se ejecutará en el navegador y no necesita Node: es un conjunto de ficheros estáticos. Node hace falta durante el desarrollo, porque el CLI, el compilador y Vitest son programas JavaScript que se ejecutan en tu máquina. También aporta npm, el gestor de paquetes con el que se instalan Angular y sus dependencias.
Versiones admitidas 🔄 v22.0
🔄 Cambia en Angular 22.0. Angular 22 exige Node
^22.22.3,^24.15.0o>=26.0.0; la LTS (Angular 21) admite^20.19.0,^22.12.0o^24.0.0. Si usas la LTS: Node 24 sirve en ambas, así que instala una versión 24.15 o posterior y podrás trabajar con las dos sin cambiar nada.
| Angular 22.2 | Angular 21.2 (LTS) | |
|---|---|---|
| Node | ^22.22.3 \|\| ^24.15.0 \|\| >=26.0.0 | ^20.19.0 \|\| ^22.12.0 \|\| ^24.0.0 |
| TypeScript | >=6.0 <6.1 | >=5.9 <6.0 |
| RxJS | ^6.5.3 \|\| ^7.4.0 | ^6.5.3 \|\| ^7.4.0 |
| Pruebas por defecto | Vitest 5 y jsdom 30 | Vitest 4 y jsdom 28 |
La sintaxis es la de las versiones semánticas de npm: ^24.15.0 significa “24.15.0 o superior, sin llegar a 25”. Fíjate en que Node 22 no vale en cualquier versión: tiene que ser al menos la 22.22.3. Este curso se ha comprobado con Node 24.19.
Conviene usar versiones pares de Node, que son las que reciben soporte largo. El propio CLI lo vigila: al arrancar comprueba tu versión y, si no es compatible, termina con un mensaje parecido a este antes de hacer nada.
Node.js version v20.11.0 detected.
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.
Please update your Node.js version or visit https://nodejs.org/ for additional instructions.
💡 Consejo: Si ves ese mensaje después de “actualizar” Node, comprueba con
node -vqué versión responde realmente. En Windows es habitual tener dos instalaciones y que la antigua aparezca antes en elPATH.
Instalar Node
Tienes dos caminos:
- Instalador oficial de nodejs.org. Es lo más sencillo si solo trabajas en un proyecto.
- Un gestor de versiones, como
nvm(Linux y macOS),nvm-windowsofnm. Es lo recomendable si tienes varios proyectos, porque cada uno puede necesitar una versión distinta de Node. Un proyecto antiguo con Angular 20 y uno nuevo con Angular 22 no comparten requisitos.
Con nvm el proceso es este:
nvm install 24 # descarga la última 24.x
nvm use 24 # la activa
node -v # comprueba: v24.19.0
npm -v # comprueba: 11.6.2
Los dos últimos comandos son la prueba de que todo está bien. Las versiones exactas en tu máquina pueden ser posteriores.
⚠️ Importante: Los paquetes globales se instalan por versión de Node. Si cambias de versión con
nvm, tendrás que reinstalar Angular CLI en la nueva, ongdejará de encontrarse.
3. Instalar Angular CLI
Instalación global
npm install -g @angular/cli
ng version
La opción -g lo instala de forma global, para que ng esté disponible en cualquier carpeta. ng version debe mostrar algo así:
Angular CLI : 22.2.5
Angular : 22.2.4
Node.js : 24.19.0
Package Manager : npm 11.6.2
Operating System : win32 x64
Dentro de un proyecto añade también una tabla con cada paquete, la versión instalada y la que pide package.json. Es lo primero que conviene pegar cuando pides ayuda en un foro.
La primera vez que lo ejecutes puede preguntarte si quieres compartir estadísticas de uso anónimas con el equipo de Angular. Elige lo que prefieras. Puedes cambiarlo después con ng analytics disable.
⚠️ Importante: En PowerShell de Windows puede aparecer un error que dice que la ejecución de scripts está deshabilitada. Windows bloquea
ng.ps1, el lanzador que crea npm. Se resuelve una sola vez conSet-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned.
CLI global y CLI local
Hay dos copias del CLI, y entender esto evita muchas confusiones:
- La global, que acabas de instalar, solo se usa de verdad para crear proyectos con
ng new. - La local, en
node_modules/@angular/clide cada proyecto. Cuando ejecutasngdentro de un proyecto, la global detecta la local y le cede el control.
Gracias a eso, un proyecto siempre se compila con la versión de CLI que indica su package.json, aunque tu CLI global sea más nuevo. Dos compañeros con distintos CLI globales obtienen el mismo resultado.
Si no quieres instalar nada de forma global, npx descarga y ejecuta una versión concreta:
npx @angular/cli@22 new empresa # sin instalación global
cd empresa
npx ng serve # usa el CLI local del proyecto
npm start # equivale a ng serve (lo verás en la sección 6)
Elegir versión: activa o LTS
El curso usa Angular 22.2. Si en tu centro o empresa se trabaja con la LTS, indica la versión al crear el proyecto. Como Angular 21 necesita su propio CLI, npx es lo más cómodo:
npx @angular/cli@21 new empresa
El módulo 00 explica cómo decidir. A lo largo del curso, cada diferencia aparece marcada con 🆕 o 🔄 y una caja que dice qué hacer en la LTS.
Autocompletado en la terminal
ng completion añade autocompletado con la tecla Tab a tu terminal (Bash o Zsh). Después podrás escribir ng g <Tab> y ver los generadores disponibles.
4. Crear un proyecto con ng new
Vamos a crear el proyecto que acompañará al resto del módulo:
ng new empresa
Si no indicas el nombre, te lo pide. Después hace unas preguntas: el sistema de estilos (CSS, Tailwind, Sass o Less), si quieres renderizado en el servidor (SSR) y para qué herramientas de IA quieres generar configuración. Para este curso responde CSS, No a SSR y ninguna herramienta de IA. Tailwind y SSR tienen sus propios módulos.
El CLI muestra lo que crea:
CREATE empresa/.prettierrc (173 bytes)
CREATE empresa/angular.json (1979 bytes)
CREATE empresa/package.json (782 bytes)
CREATE empresa/README.md (1519 bytes)
CREATE empresa/tsconfig.json (939 bytes)
CREATE empresa/.editorconfig (331 bytes)
CREATE empresa/.gitignore (666 bytes)
CREATE empresa/tsconfig.app.json (412 bytes)
CREATE empresa/tsconfig.spec.json (423 bytes)
CREATE empresa/.vscode/extensions.json (134 bytes)
CREATE empresa/.vscode/launch.json (490 bytes)
CREATE empresa/.vscode/tasks.json (1020 bytes)
CREATE empresa/src/main.ts (228 bytes)
CREATE empresa/src/index.html (306 bytes)
CREATE empresa/src/styles.css (81 bytes)
CREATE empresa/src/app/app.spec.ts (705 bytes)
CREATE empresa/src/app/app.ts (302 bytes)
CREATE empresa/src/app/app.css (0 bytes)
CREATE empresa/src/app/app.html (20488 bytes)
CREATE empresa/src/app/app.config.ts (322 bytes)
CREATE empresa/src/app/app.routes.ts (80 bytes)
CREATE empresa/public/favicon.ico (15086 bytes)
Después instala las dependencias con npm install, inicializa un repositorio Git y hace el primer commit. Con un proyecto recién generado ya puedes arrancar:
cd empresa
ng serve
Abre http://localhost:4200 en el navegador.

Figura 2. La aplicación recién creada. Todo este contenido está en src/app/app.html y lo borrarás enseguida.
Opciones de ng new
Las preguntas se pueden responder en la orden, lo que resulta útil en guiones y para repetir una configuración.
| Opción | Efecto | Valor por defecto |
|---|---|---|
--style | css, scss, sass, less o tailwind | Pregunta |
--ssr | Configura renderizado en el servidor (módulo 24) | Pregunta |
--ai-config | Herramientas de IA para las que generar instrucciones | Pregunta |
--routing | Incluye el router | Activado |
-p, --prefix | Prefijo de los selectores (app-...) | app |
--skip-tests, -S | No genera ficheros .spec.ts | false |
--skip-install | No ejecuta npm install | false |
--skip-git, -g | No inicializa Git | false |
--package-manager | npm, pnpm, yarn o bun | El detectado |
--test-runner | vitest o karma | vitest |
--minimal | Sin pruebas, para experimentar | false |
--inline-template, -t | Plantilla dentro de app.ts | false |
--inline-style, -s | Estilos dentro de app.ts | false |
--create-application | Con false, crea un espacio de trabajo vacío | true |
--file-name-style-guide | 2025 (app.ts) o 2016 (app.component.ts) | 2025 |
--dry-run, -d | Muestra qué crearía, sin escribir nada | false |
--defaults | No pregunta nada que tenga valor por defecto | false |
Ejemplos habituales:
# Prueba sin crear nada: solo lista los ficheros
ng new empresa --dry-run
# Sin preguntas, para un guion
ng new empresa --defaults --skip-git
# Con Sass y prefijo propio
ng new empresa --style=scss --prefix=emp
# Con Tailwind ya configurado (lo verás en el módulo 22)
ng new empresa --style=tailwind
# Con nombres de fichero al estilo clásico
ng new empresa --file-name-style-guide=2016
Esto es lo que cambia cada opción respecto a los 22 ficheros de la lista anterior:
| Opción | Efecto en los ficheros |
|---|---|
--minimal | 17 ficheros: sin .editorconfig, tsconfig.spec.json, app.spec.ts, app.html ni app.css |
--style=scss | styles.scss y app.scss en lugar de los .css |
--style=tailwind | Añade .postcssrc.json, y styles.css importa Tailwind |
--ssr | Añade main.server.ts, server.ts, app.config.server.ts y app.routes.server.ts |
--ai-config=claude-code | Añade CLAUDE.md y .mcp.json |
--file-name-style-guide=2016 | app.component.ts, app.component.html, app.component.css… |
Las opciones de ng new son las mismas en Angular 21 y 22. Los proyectos solo se diferencian en lo que señalan las marcas de las secciones siguientes y en las versiones de package.json.
💡 Consejo:
--dry-runexiste también enng generate. Acostúmbrate a usarlo cuando no tengas claro qué hará una orden: no escribe nada en disco.
5. Estructura de un proyecto
Quitando lo que no debes tocar, esto es lo que hay:
empresa/
├── .vscode/ Recomendaciones y depuración para VS Code
├── node_modules/ Dependencias instaladas (no se sube a Git)
├── public/ Ficheros que se copian tal cual a la salida
│ └── favicon.ico
├── src/
│ ├── app/
│ │ ├── app.config.ts Configuración global de la aplicación
│ │ ├── app.css Estilos del componente raíz
│ │ ├── app.html Plantilla del componente raíz
│ │ ├── app.routes.ts Tabla de rutas
│ │ ├── app.spec.ts Pruebas del componente raíz
│ │ └── app.ts Clase del componente raíz
│ ├── index.html Página HTML única de la aplicación
│ ├── main.ts Punto de entrada: arranca Angular
│ └── styles.css Estilos globales
├── .editorconfig Sangrado y codificación para los editores
├── .gitignore Qué no se sube a Git
├── .prettierrc Reglas de formato de Prettier
├── angular.json Configuración del CLI y de los builders
├── package.json Dependencias y scripts
├── package-lock.json Versiones exactas instaladas
├── README.md Instrucciones básicas
├── tsconfig.json Opciones comunes de TypeScript
├── tsconfig.app.json TypeScript para la aplicación
└── tsconfig.spec.json TypeScript para las pruebas
Dónde va cada cosa
src/app/es donde pasarás casi todo el tiempo. Cada componente se compone de hasta cuatro ficheros con el mismo nombre:.ts,.html,.cssy.spec.ts.public/contiene lo que debe llegar sin transformar: imágenes, iconos, ficheros de datos.public/logo.pngestará disponible en/logo.png. Nada de esa carpeta pasa por el compilador.src/styles.cssse aplica a toda la página. Los estilos deapp.csssolo afectan a su componente. El motivo se explica en el módulo 03.node_modules/puede pesar cientos de megas y se regenera connpm install. Nunca lo copies ni lo subas a Git. Si envías el proyecto a alguien, envía el resto y que ejecutenpm install.
Nombres sin sufijo
Si has visto código Angular antiguo, recordarás ficheros como app.component.ts y clases como AppComponent. La guía de estilo actual los elimina: el fichero es app.ts y la clase, App. La idea es que el nombre diga para qué sirve algo, no a qué categoría técnica pertenece: ListaEmpleados en lugar de ListaEmpleadosComponent.
Vas a ver los dos estilos en documentación y tutoriales. Funcionan igual: el sufijo era una convención, nunca una exigencia del compilador.
De index.html al primer componente
Cuando abres la aplicación, ocurre esta secuencia.
Figura 3. La secuencia de arranque de una aplicación Angular.
Paso 1. El servidor entrega src/index.html. Es sorprendentemente corto:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Empresa</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
Dentro de <body> solo hay un elemento desconocido para el navegador, y vacío. Tampoco hay ninguna etiqueta <script>: el CLI las añade al compilar. Te conviene cambiar ya lang="en" por lang="es", porque es información que usan los lectores de pantalla y los traductores del navegador.
La etiqueta <base href="/"> indica la ruta base de todas las direcciones relativas. La necesitan el router y la carga de ficheros. Tiene consecuencias al publicar, como verás en la sección 11.
Paso 2. Se carga el JavaScript y se ejecuta src/main.ts, el punto de entrada:
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));
bootstrapApplication recibe dos cosas: el componente raíz y la configuración. Devuelve una promesa, y el catch muestra cualquier error del arranque.
Paso 3. src/app/app.config.ts reúne la configuración global en forma de proveedores:
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes)
]
};
Las funciones provide... añaden capacidades a la aplicación. provideRouter activa la navegación con la tabla de app.routes.ts, que al principio está vacía. provideBrowserGlobalErrorListeners hace que los errores que nadie captura en el navegador lleguen al gestor de errores de Angular. Aquí añadirás provideHttpClient en el módulo 12. Los proveedores se explican a fondo en el módulo 08.
Paso 4. Angular crea el componente raíz, definido en src/app/app.ts:
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.html',
styleUrl: './app.css'
})
export class App {
protected readonly title = signal('empresa');
}
El decorador @Component convierte la clase en componente, y selector: 'app-root' es la pieza que conecta con index.html.
Paso 5. Angular busca <app-root> en la página y pinta dentro el contenido de app.html con los estilos de app.css. Si cambias el selector en app.ts sin cambiar index.html, la página se queda en blanco y la consola muestra este error:
RuntimeError: NG05104: The selector "app-raiz" did not match any elements.
Los errores de Angular llevan un código NG y un enlace a una explicación en angular.dev. Acostúmbrate a abrir la consola del navegador (F12) siempre que algo no se pinte.
Paso 6. Al final de app.html está <router-outlet />, el hueco donde el router coloca el componente de la ruta actual. La página ya está viva.
📘 Concepto: Una aplicación Angular es una aplicación de página única (Single Page Application, SPA). El servidor entrega un solo HTML, y el JavaScript construye y cambia la pantalla sin volver a pedir páginas. Al navegar no se recarga el documento: Angular cambia los componentes.
Lo que no hay en este proyecto
Si comparas con un tutorial de hace unos años, echarás en falta varias cosas, y es buena señal:
| Antes | Ahora | Se explica en |
|---|---|---|
app.module.ts y NgModule | Componentes standalone con su propio imports | Módulo 28 |
standalone: true | Es el valor por defecto; no se escribe | Módulo 03 |
zone.js | Aplicación zoneless, guiada por señales | Módulo 09 |
karma.conf.js | Vitest, sin fichero de configuración | Sección 12 |
environments/ | Se crea solo si lo pides | Sección 11 |
Todo esto vale también para la LTS. Zoneless y Vitest no son novedades de la 22.
Componentes sin changeDetection 🔄 v22.0
🔄 Cambia en Angular 22.0. Un componente sin
changeDetectionusa la estrategiaOnPush. En la LTS (Angular 21) el valor por defecto esEager, y el CLI tampoco escribe esa propiedad. Si usas la LTS: añadechangeDetection: ChangeDetectionStrategy.OnPusha cada componente para tener el mismo comportamiento.
El App generado no declara changeDetection, pero su comportamiento depende de la versión. Por ahora basta con saber que existe esta diferencia. Si usas señales para el estado, como hace este curso desde el principio, no notarás nada. El módulo 09 lo explica con detalle.
6. package.json: dependencias y scripts
{
"name": "empresa",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"packageManager": "npm@11.6.2",
"dependencies": {
"@angular/common": "^22.2.0",
"@angular/compiler": "^22.2.0",
"@angular/core": "^22.2.0",
"@angular/forms": "^22.2.0",
"@angular/platform-browser": "^22.2.0",
"@angular/router": "^22.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular/build": "^22.2.5",
"@angular/cli": "^22.2.5",
"@angular/compiler-cli": "^22.2.0",
"jsdom": "^30.0.0",
"prettier": "^3.8.1",
"typescript": "~6.0.2",
"vitest": "^5.0.0"
}
}
Scripts
npm start ejecuta ng serve usando el CLI local, aunque no tengas el global. Es útil en servidores de integración continua. Para pasar opciones a través de npm hay que separarlas con --:
npm start -- --port 4300
npm test -- --watch=false
Dependencias
Las de dependencies forman parte de la aplicación y acaban, al menos en parte, en el navegador. Las de devDependencies solo se usan para construirla.
| Paquete | Para qué sirve |
|---|---|
@angular/core | El núcleo: componentes, señales, inyección |
@angular/common | Utilidades comunes, pipes y HttpClient |
@angular/compiler | Compilador en tiempo de ejecución, usado en desarrollo y pruebas |
@angular/forms | Formularios |
@angular/platform-browser | Arranque en el navegador |
@angular/router | Navegación |
rxjs | Programación reactiva con observables (módulo 14) |
tslib | Funciones auxiliares que comparte el código compilado por TypeScript |
@angular/build | Los builders: esbuild, Vite y unión con Vitest |
@angular/cli | La orden ng local |
@angular/compiler-cli | Compilador de plantillas y TypeScript (ngc) |
typescript | El lenguaje |
vitest, jsdom | Pruebas y un DOM simulado para ejecutarlas en Node |
prettier | Formato de código |
Una instalación de Angular no incluye todo. @angular/material, @angular/ssr o @angular/animations se añaden cuando hacen falta.
Los símbolos ^ y ~
^22.2.0admite cualquier versión22.x.yigual o posterior a la 22.2.0.~6.0.2solo admite6.0.y, igual o posterior a la 6.0.2.
¿Por qué TypeScript y RxJS van con ~, más estricto? Porque Angular solo admite ciertas versiones de TypeScript: >=6.0 <6.1 en este caso. Con ^, un npm install podría traer una 6.1 no compatible.
package-lock.json y npm ci
package.json dice qué rango aceptas. package-lock.json guarda las versiones exactas que se instalaron, incluidas las de las dependencias de tus dependencias. Súbelo siempre a Git: es lo que garantiza que todos tengáis lo mismo.
| Orden | Qué hace | Cuándo usarla |
|---|---|---|
npm install | Instala y puede actualizar el package-lock.json | En tu máquina, al añadir paquetes |
npm ci | Borra node_modules e instala exactamente lo que dice el lock | En integración continua |
La propiedad packageManager declara qué gestor y versión usa el proyecto. Herramientas como Corepack la leen para usar el mismo gestor en todas las máquinas.
7. angular.json: la configuración del CLI
Este fichero describe el espacio de trabajo (workspace): uno o varios proyectos y cómo se construye cada uno. Este es el generado, con la parte de presupuestos resumida:
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": { "packageManager": "npm" },
"newProjectRoot": "projects",
"projects": {
"empresa": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [{ "glob": "**/*", "input": "public" }],
"styles": ["src/styles.css"]
},
"configurations": {
"production": {
"budgets": [ "..." ],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": { "buildTarget": "empresa:build:production" },
"development": { "buildTarget": "empresa:build:development" }
},
"defaultConfiguration": "development"
},
"test": {
"builder": "@angular/build:unit-test"
}
}
}
}
}
La línea $schema tiene un efecto práctico: VS Code valida el fichero y autocompleta las opciones mientras escribes.
Las piezas
| Clave | Significado |
|---|---|
projects | Los proyectos del espacio de trabajo. Normalmente uno |
projectType | application o library |
root, sourceRoot | Raíz del proyecto y carpeta del código |
prefix | Prefijo de los selectores que genera ng generate |
schematics | Valores por defecto de los generadores (sección 10) |
architect | Los objetivos (targets) del proyecto: build, serve, test |
builder | Programa que ejecuta ese objetivo |
options | Opciones del objetivo |
configurations | Conjuntos de opciones con nombre, que se suman a options |
defaultConfiguration | La configuración si no indicas otra |
Ahora se entiende de dónde sale cada comportamiento. ng build ejecuta el objetivo build con la configuración production, que es su valor por defecto. ng serve ejecuta serve con la configuración development, y esta a su vez usa build:development. Por eso un servidor local no minimiza el código.
Cada orden es un atajo de la forma general ng run proyecto:objetivo:configuración:
ng build # build con production
ng build --configuration development # build con development
ng run empresa:build:development # lo mismo, forma larga
Las opciones de línea de órdenes tienen prioridad sobre angular.json. La precedencia es: valores por defecto del builder, luego options, luego configurations y, por último, la línea de órdenes.
Cambios habituales
Añadir una hoja de estilos global de una biblioteca:
"styles": [
"node_modules/normalize.css/normalize.css",
"src/styles.css"
]
Copiar otra carpeta a la salida, además de public:
"assets": [
{ "glob": "**/*", "input": "public" },
{ "glob": "**/*", "input": "datos", "output": "datos" }
]
Consultar y cambiar valores desde la terminal con ng config, sin abrir el fichero:
ng config projects.empresa.prefix # muestra: "app"
ng config cli.packageManager # muestra: npm
ng config projects.empresa.architect.serve.options.proxyConfig proxy.conf.json
⚠️ Importante: Un error de sintaxis en
angular.jsonimpide ejecutar todas las órdenes del proyecto. Sing servedeja de funcionar nada más editarlo, busca primero una coma de más.
8. Los tres tsconfig
TypeScript se configura en tres ficheros porque la aplicación y las pruebas necesitan cosas distintas.
Figura 4. Los ficheros de aplicación y de pruebas heredan las reglas comunes de tsconfig.json.
{
"compileOnSave": false,
"compilerOptions": {
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true
},
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.spec.json" }
]
}
compilerOptions
| Opción | Qué significa |
|---|---|
noImplicitOverride | Sobrescribir un método exige la palabra override |
noPropertyAccessFromIndexSignature | Las claves dinámicas se leen con obj['clave'], no con obj.clave |
noImplicitReturns | Todos los caminos de una función deben devolver valor |
noFallthroughCasesInSwitch | Un case no puede caer al siguiente sin break |
skipLibCheck | No revisa los .d.ts de las bibliotecas; acelera la compilación |
isolatedModules | Cada fichero debe poder compilarse solo, como hace esbuild |
experimentalDecorators | Activa la sintaxis de decoradores que usa Angular |
importHelpers | Las funciones auxiliares se importan de tslib en lugar de repetirse |
target | Versión de JavaScript que genera TypeScript |
module | preserve deja los import tal cual para el empaquetador |
angularCompilerOptions
Son opciones del compilador de Angular, no de TypeScript.
| Opción | Qué significa |
|---|---|
strictInjectionParameters | Error si un parámetro no se puede inyectar |
strictInputAccessModifiers | Respeta private, protected y readonly al enlazar inputs en plantillas |
enableI18nLegacyMessageIdFormat | Con false, usa el formato moderno de identificadores de traducción |
strict y TypeScript 6 🔄 v22.0
🔄 Cambia en Angular 22.0. Angular 22 usa TypeScript 6, donde
strictestá activado por defecto, y por esong newya no escribe esa línea entsconfig.json. La LTS (Angular 21) usa TypeScript 5.9, cuyo valor por defecto esfalse. Si usas la LTS: mantén"strict": true, queng newescribe en sutsconfig.json, y tendrás las mismas comprobaciones.
Que la opción no aparezca no significa que esté desactivada. Este código, sin tocar el tsconfig.json generado por Angular 22, no compila:
export const edad: number = null;
X [ERROR] TS2322: Type 'null' is not assignable to type 'number'. [plugin angular-compiler]
⚠️ Importante: TypeScript 7 no está soportado por Angular 22. Si un día ves un error de versión de TypeScript al compilar, comprueba que no se ha instalado por accidente una versión mayor: Angular 22 exige
>=6.0 <6.1.
El módulo 02 explica qué activa strict y cómo te ayuda.
strictTemplates 🔄 v22.0
🔄 Cambia en Angular 22.0. Las plantillas se comprueban en modo estricto aunque
tsconfig.jsonno lo diga, yng newya no escribestrictTemplates. En la LTS (Angular 21) el valor por defecto sigue siendofalse. Si usas la LTS: conserva"strictTemplates": trueenangularCompilerOptions, queng newescribe por ti; si lo quitas, los errores de tipos en plantillas dejan de detectarse.
El modo estricto hace que el compilador compruebe los tipos también dentro de las plantillas. Es de lo más útil de Angular: un error en el HTML se detecta antes de ejecutar nada. En este ejemplo se pasa un texto a una entrada numérica.
// tarjeta-dato.ts
export class TarjetaDato {
readonly total = input.required<number>();
}
<!-- inicio.html: estado() devuelve un string -->
<app-tarjeta-dato [total]="estado()" />
Este es el resultado real, compilando el mismo código con cuatro configuraciones:
| Versión | tsconfig.json | Resultado |
|---|---|---|
| Angular 22.2 | Sin la línea, como lo deja ng new | TS2322: Type 'string' is not assignable to type 'number' |
| Angular 22.2 | Con "strictTemplates": false | Compila, y el error llegará al usuario |
| Angular 21.2 | Con "strictTemplates": true, como lo deja ng new | TS2322: Type 'string' is not assignable to type 'number' |
| Angular 21.2 | Sin la línea | Compila, y el error llegará al usuario |
Las entradas con input() se explican en el módulo 06. Lo importante aquí es la consecuencia práctica. En la 22, quien quiera perder esta comprobación tiene que pedirlo con "strictTemplates": false, y no conviene hacerlo salvo para migrar un proyecto antiguo por etapas. En la 21, en cambio, basta con borrar la línea sin querer para perderla sin aviso.
tsconfig.app.json y tsconfig.spec.json
{
"extends": "./tsconfig.json",
"compilerOptions": { "types": [] },
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.spec.ts"]
}
{
"extends": "./tsconfig.json",
"compilerOptions": { "types": ["vitest/globals"] },
"include": ["src/**/*.d.ts", "src/**/*.spec.ts"]
}
La diferencia está en types. En la aplicación, la lista vacía impide que aparezcan tipos globales de paquetes de node_modules. En las pruebas, vitest/globals declara describe, it, expect y vi, por eso los .spec.ts los usan sin importarlos. Si escribieras describe en app.ts, el compilador daría error, y es justo lo que quieres.
9. ng serve: el servidor de desarrollo
ng serve
√ Building...
Initial chunk files | Names | Raw size
main.js | main | 6.74 kB |
styles.css | styles | 95 bytes |
| Initial total | 6.84 kB
Application bundle generation complete. [15.415 seconds]
Watch mode enabled. Watching for file changes...
NOTE: Raw file sizes do not reflect development server per-request transformations.
➜ Local: http://localhost:4200/
ng serve compila la aplicación en memoria, sin escribir nada en dist/, levanta un servidor Vite y vigila tus ficheros. Te extrañará que main.js pese solo 6,74 kB. No incluye Angular: en desarrollo, las dependencias se empaquetan aparte y se sirven por separado, de modo que al cambiar tu código solo se recompila lo tuyo. Los tiempos de compilación se reducen, además, gracias a la caché de .angular/cache, y las siguientes veces arrancará mucho más rápido.
Mientras se ejecuta, la terminal queda ocupada. Para detenerlo, pulsa Ctrl+C.
Opciones
| Opción | Efecto | Valor por defecto |
|---|---|---|
--port | Puerto. La variable de entorno PORT tiene prioridad | 4200 |
--host | Dirección en la que escucha. 0.0.0.0 te permite probar desde el móvil en tu red | localhost |
-o, --open | Abre el navegador | false |
--hmr | Reemplazo en caliente de módulos | Según --live-reload |
--live-reload | Recarga la página al cambiar algo | true |
--proxy-config | Fichero de proxy para la API | Sin proxy |
--ssl | Sirve por HTTPS | false |
--poll | Vigila los ficheros por sondeo, para carpetas de red o contenedores | Desactivado |
-c, --configuration | Configuración de angular.json | development |
Si el puerto ya está ocupado, el CLI se detiene:
An unhandled exception occurred: Port 4200 is already in use. Use '--port' to specify a different port.
Normalmente significa que dejaste otro ng serve abierto en otra terminal.
Recarga en caliente
Con ng serve en marcha, cambia el texto de app.html y guarda. El navegador se actualiza sin que hagas nada. Con HMR (Hot Module Replacement), en la medida de lo posible, Angular sustituye solo lo que ha cambiado, como plantillas o estilos, sin recargar la página ni perder el estado. Cuando no puede, recarga la página completa.
Los errores de compilación
Si cometes un error, la terminal lo muestra y el navegador lo superpone sobre la página, indicando fichero, línea y columna. Aquí se ha escrito estadoo() en una plantilla:

Figura 5. Error de plantilla detectado al compilar. El compilador incluso sugiere el nombre correcto.
Al corregirlo y guardar, el aviso desaparece solo. Fíjate en lo que significa: un error tipográfico en HTML se ha convertido en un error de compilación con el mismo formato que uno de TypeScript. Esto es lo que aporta strictTemplates.
Conectar con la API: el proxy
La aplicación EMPRESA se apoya en una API. Descarga recursos/empresa-api.zip, descomprímelo y arráncalo en otra terminal:
node server.mjs
La API queda en http://localhost:3000/api. Ahora tienes dos servidores, cada uno en su puerto, y aparece un problema: para el navegador, localhost:4200 y localhost:3000 son orígenes distintos. La política del mismo origen impide leer respuestas de otro origen, salvo que ese servidor lo autorice con cabeceras CORS.
La API EMPRESA envía esas cabeceras, así que funcionaría directamente. Muchas APIs reales no lo hacen, y además escribir http://localhost:3000 en el código te obligaría a cambiarlo al publicar. La solución habitual es el proxy del servidor de desarrollo.
Figura 6. El navegador solo habla con ng serve, que reenvía a la API lo que empieza por /api.
Crea proxy.conf.json en la raíz del proyecto:
{
"/api": {
"target": "http://localhost:3000",
"secure": false
}
}
Y regístralo en angular.json, para no tener que escribir --proxy-config cada vez:
ng config projects.empresa.architect.serve.options.proxyConfig proxy.conf.json
Esa orden añade esto dentro de serve:
"options": {
"proxyConfig": "proxy.conf.json"
}
Reinicia ng serve, porque esta configuración solo se lee al arrancar. Comprueba que la petición atraviesa el proxy:
curl http://localhost:4200/api/departamentos
{"contenido":[{"deptId":10,"nombre":"DIRECCIÓN","localidad":"Madrid","presupuesto":500000},{"deptId":20,"nombre":"VENTAS",...
⚠️ Importante: El proxy es parte de
ng serve. No existe en la aplicación compilada. En producción, algo tiene que dirigir/apihacia la API: normalmente, Nginx u otro servidor web. Lo verás en el módulo 27.
💡 Consejo: Si
targetusalocalhosty recibes errores de conexión, prueba conhttp://127.0.0.1:3000. Algunos sistemas resuelvenlocalhosta la dirección IPv6, y el servidor puede estar escuchando solo en IPv4.
10. ng generate: crear código sin escribirlo
ng generate, abreviado ng g, crea ficheros a partir de plantillas. Evita errores tontos y hace que todo el proyecto tenga la misma forma.
ng generate component empleados/ficha
ng g c empleados/ficha # lo mismo
CREATE src/app/empleados/ficha/ficha.spec.ts (547 bytes)
CREATE src/app/empleados/ficha/ficha.ts (191 bytes)
CREATE src/app/empleados/ficha/ficha.css (0 bytes)
CREATE src/app/empleados/ficha/ficha.html (21 bytes)
import { Component } from '@angular/core';
@Component({
imports: [],
selector: 'app-ficha',
styleUrl: './ficha.css',
templateUrl: './ficha.html',
})
export class Ficha {}
El nombre incluye la ruta relativa a src/app, y el CLI crea la carpeta. El selector se forma con el prefijo y el nombre (app-ficha), y la clase se escribe en PascalCase. Si escribes lista-empleados, obtienes la clase ListaEmpleados.
Qué se puede generar
| Orden | Alias | Genera | Se ve en el módulo |
|---|---|---|---|
ng g component | c | Componente | 03 |
ng g directive | d | Directiva | 07 |
ng g pipe | p | Pipe | 07 |
ng g service | s | Servicio | 08 |
ng g guard | g | Guard de ruta | 11 |
ng g resolver | r | Resolver de ruta | 11 |
ng g interceptor | Interceptor HTTP | 12 | |
ng g class | cl | Clase | 02 |
ng g interface | i | Interfaz | 02 |
ng g enum | e | Enumerado | 02 |
ng g environments | Ficheros de entorno | Sección 11 | |
ng g library | lib | Biblioteca | |
ng g application | app | Otra aplicación en el espacio de trabajo | |
ng g web-worker | Web worker | ||
ng g service-worker | Service worker | 25 | |
ng g ai-config | Instrucciones para asistentes de IA | Sección 17 | |
ng g config | Ficheros de configuración de Vitest, Karma o browserslist |
Opciones de ng g component
| Opción | Efecto |
|---|---|
--dry-run, -d | Muestra lo que haría, sin escribir |
--flat | No crea carpeta propia |
--inline-template, -t | Plantilla en el .ts |
--inline-style, -s | Estilos en el .ts |
--style=none | Sin fichero de estilos |
--skip-tests | Sin .spec.ts |
--type=component | Añade el tipo al nombre de fichero: ficha.component.ts |
--add-type-to-class-name | Añade también el tipo a la clase |
--change-detection, -c | Eager u OnPush |
--display-block, -b | Añade :host { display: block; } |
--prefix, -p | Otro prefijo |
--selector | Otro selector |
--skip-selector | Sin selector, para componentes de ruta |
--force | Sobrescribe ficheros existentes |
Servicios: ng g service genera @Service 🆕 v22.0
🆕 Desde Angular 22.0.
ng g servicegenera una clase con el decorador@Service(), que no existe en la LTS (Angular 21). Si usas la LTS: la misma orden genera@Injectable({ providedIn: 'root' }), que hace lo mismo; no tienes que cambiar nada.
ng g service servicios/empleados
import { Service } from '@angular/core';
@Service()
export class Empleados {}
Si necesitas el decorador clásico en Angular 22, añade --injectable. Esa opción no existe en la 21.
ng g service servicios/empleados --injectable
Fíjate en que ng g s empleados crea la clase Empleados, sin sufijo. Si ya tienes un componente con ese nombre, usa carpetas distintas o un nombre más descriptivo, como ApiEmpleados. El módulo 08 cuenta todo lo relativo a servicios.
Práctica: la primera página de EMPRESA
Vamos a poner en orden lo aprendido. Primero crea una página:
ng g c paginas/inicio
Sustituye el contenido de bienvenida de src/app/app.html por esto:
<header class="cabecera">
<h1>{{ title() }}</h1>
<p>Gestión de departamentos y empleados</p>
</header>
<main>
<router-outlet />
</main>
Añade sus estilos en src/app/app.css:
.cabecera {
background: #1a237e;
color: #ffffff;
padding: 1rem 1.5rem;
}
.cabecera h1 {
margin: 0;
font-size: 1.6rem;
}
.cabecera p {
margin: 0.25rem 0 0;
opacity: 0.85;
}
main {
padding: 1.5rem;
}
En src/app/app.ts, cambia el título:
protected readonly title = signal('EMPRESA');
Registra la ruta en src/app/app.routes.ts:
import { Routes } from '@angular/router';
import { Inicio } from './paginas/inicio/inicio';
export const routes: Routes = [{ path: '', component: Inicio }];
Ahora el componente src/app/paginas/inicio/inicio.ts, que comprueba si la API responde:
import { Component, signal } from '@angular/core';
@Component({
imports: [],
selector: 'app-inicio',
styleUrl: './inicio.css',
templateUrl: './inicio.html',
})
export class Inicio {
protected readonly estado = signal('Comprobando la API...');
constructor() {
this.comprobarApi();
}
private async comprobarApi() {
try {
const respuesta = await fetch('/api/departamentos');
const datos = await respuesta.json();
this.estado.set(`API operativa: ${datos.total} departamentos`);
} catch {
this.estado.set('No se puede contactar con la API');
}
}
}
Su plantilla, inicio.html:
<section class="tarjeta">
<h2>Panel de inicio</h2>
<p>{{ estado() }}</p>
</section>
Y sus estilos, inicio.css:
.tarjeta {
background: #e8eaf6;
border-left: 4px solid #3f51b5;
border-radius: 6px;
padding: 1rem 1.25rem;
max-width: 32rem;
}
.tarjeta h2 {
margin-top: 0;
color: #1a237e;
}
Con la API y ng serve en marcha, este es el resultado:

Figura 7. La primera pantalla de EMPRESA, con datos que llegan a través del proxy.
No te preocupes si no entiendes aún cada línea. signal guarda un valor reactivo: al llamar a set, Angular actualiza la parte de la pantalla que lo muestra. Se explica en el módulo 05. Tampoco es la forma definitiva de pedir datos: fetch en el constructor sirve para comprobar el entorno, pero lo correcto será HttpClient (módulo 12) y httpResource (módulo 13).
Observa lo que no has tenido que hacer: no has añadido ningún <script>, no has configurado ningún compilador y no has reiniciado el servidor al cambiar el código.
Cambiar los valores por defecto de los generadores
Si un proyecto siempre usa las mismas opciones, escríbelas en la clave schematics de angular.json, dentro de tu proyecto:
"schematics": {
"@schematics/angular:component": {
"skipTests": true,
"style": "none",
"changeDetection": "Eager"
}
}
A partir de ahí, ng g c paginas/ayuda crea solo el .ts y el .html, y escribe changeDetection: ChangeDetectionStrategy.Eager en el decorador. Las opciones de la línea de órdenes siguen mandando: ng g c paginas/ayuda --skip-tests=false vuelve a crear el .spec.ts.
Edita esta clave a mano. ng config no admite bien las rutas con @, / y :, y falla con un error de validación del esquema.
11. ng build: preparar la publicación
ng build
√ Building...
Initial chunk files | Names | Raw size | Estimated transfer size
main-CEP2SBLF.js | main | 192.19 kB | 52.58 kB
styles-5INURTSO.css | styles | 0 bytes | 0 bytes
| Initial total | 192.19 kB | 52.58 kB
Application bundle generation complete. [27.911 seconds]
Output location: .../empresa/dist/empresa
La salida queda así:
dist/empresa/
├── 3rdpartylicenses.txt Licencias de las bibliotecas incluidas
└── browser/
├── favicon.ico Copiado de public/
├── index.html Con <script> y <link> ya añadidos
├── main-CEP2SBLF.js Tu código y Angular, minimizados
└── styles-5INURTSO.css Estilos globales
Lo que debes subir a un servidor es el contenido de browser/. Son ficheros estáticos: cualquier servidor web los sirve, sin Node.
Producción y desarrollo
La misma aplicación, compilada de las dos formas:
ng build (production) | ng build --configuration development | |
|---|---|---|
main | 192,19 kB | 1,35 MB |
| Transferencia estimada | 52,58 kB | No se calcula |
| Nombre de fichero | Con hash: main-CEP2SBLF.js | Fijo: main.js |
| Minimizado | Sí | No |
| Source maps | No | Sí (main.js.map) |
| Modo de desarrollo de Angular | No | Sí |
La reducción a menos de una séptima parte viene de tres procesos: la minimización acorta nombres y elimina espacios; el tree shaking descarta el código que nadie usa; y las comprobaciones de desarrollo de Angular desaparecen. La columna “Estimated transfer size” es lo que viajaría comprimido por la red.
📘 Concepto: El hash del nombre (
CEP2SBLF) se calcula a partir del contenido. Si el fichero no cambia, el nombre tampoco. Así un servidor puede pedir al navegador que guarde el fichero en caché por mucho tiempo: cuando publiques una versión nueva, tendrá otro nombre y el navegador lo descargará. Por esoindex.htmlno debe guardarse en caché mucho tiempo: es el que apunta a los ficheros nuevos.
Por qué no puedes abrir index.html con doble clic
Es tentador abrir dist/empresa/browser/index.html directamente. El resultado es una página en blanco, y la consola explica por qué:
Failed to load resource: net::ERR_FILE_NOT_FOUND
Access to script at 'file:///C:/main-CEP2SBLF.js' from origin 'null' has been blocked by CORS policy
Hay dos problemas. <base href="/"> hace que el navegador busque main-CEP2SBLF.js en la raíz del disco (C:/), y los módulos de JavaScript (type="module") no se pueden cargar desde file://. La aplicación necesita un servidor HTTP, aunque sea sencillo:
npx serve -s dist/empresa/browser
Al abrir la dirección que indica, verás la aplicación, pero la tarjeta dirá “No se puede contactar con la API”, porque el proxy de la sección 9 pertenece a ng serve y aquí no existe. La opción -s hace que el servidor responda con index.html a las rutas desconocidas. Es lo que necesita una SPA, como verás enseguida.
Publicar en una subcarpeta: --base-href
Si tu aplicación no va en la raíz del dominio, sino en https://usuario.github.io/empresa/, debes indicarlo:
ng build --base-href /empresa/
Eso cambia <base href="/"> en el index.html final. Si no lo haces, el navegador pedirá /main-CEP2SBLF.js en la raíz del dominio y recibirá un 404, con la página en blanco.
Si siempre publicas en el mismo sitio, es mejor fijarlo en la configuración production de angular.json, y así no se te olvidará:
"production": {
"baseHref": "/empresa/",
"outputHashing": "all"
}
⚠️ Importante: En Git Bash para Windows, un argumento que empieza por
/se interpreta como una ruta del sistema y se traduce antes de llegar ang. Al probarng build --base-href /CursosGithubIO/empresa/desde Git Bash se generó<base href="C:/Program Files/Git/CursosGithubIO/empresa/">, sin ningún error ni aviso. Lanza esta orden desde PowerShell o CMD, o usa la opciónbaseHrefdeangular.json. Las dos formas dan el resultado correcto.
También necesitas que el servidor devuelva index.html para cualquier ruta desconocida. Si un usuario recarga /empleados/100, el servidor buscará ese fichero, y solo Angular sabe qué hacer con esa ruta. Se configura en el módulo 27.
Presupuestos
Los presupuestos (budgets) son límites de tamaño que hacen fallar la compilación si la aplicación engorda sin que te des cuenta. Están en la configuración production:
"budgets": [
{ "type": "initial", "maximumWarning": "500kB", "maximumError": "1MB" },
{ "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" }
]
initial: JavaScript y CSS necesarios para arrancar la aplicación. Es el “Initial total” de la tabla de salida.anyComponentStyle: tamaño de la hoja de estilos de cualquier componente.
Si se supera el límite de aviso, la compilación termina con una advertencia. Si se supera el de error, falla. Esto es lo que ocurre al bajar los límites a 100kB y 150kB para probarlo:
Application bundle generation failed.
▲ [WARNING] bundle initial exceeded maximum budget. Budget 100.00 kB was not met by 92.52 kB with a total of 192.52 kB.
X [ERROR] bundle initial exceeded maximum budget. Budget 150.00 kB was not met by 42.52 kB with a total of 192.52 kB.
El mensaje dice el límite, cuánto se ha superado y el total. La orden termina con código de salida 1, y por eso un error de presupuesto detiene un sistema de integración continua.
Otros tipos son bundle (uno concreto, con name), allScript, all, anyScript y any. Un presupuesto no se sube porque moleste. Antes conviene averiguar qué ha crecido, y ng build --stats-json ayuda: genera el fichero dist/empresa/browser-stats.json, que se puede analizar en esbuild.github.io/analyze. Lo practicarás en el módulo 23.
Entornos
A veces necesitas valores distintos en desarrollo y en producción. Los proyectos ya no incluyen ficheros de entorno, pero se crean con una orden:
ng g environments
CREATE src/environments/environment.ts (31 bytes)
CREATE src/environments/environment.development.ts (31 bytes)
UPDATE angular.json (2222 bytes)
Los dos ficheros contienen export const environment = {};. El generador añade a la configuración development de angular.json una sustitución de ficheros:
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
Es decir: tu código siempre importa environment.ts, y al compilar en development el CLI lo cambia por el otro. Rellena los dos con las mismas propiedades:
// src/environments/environment.ts (producción)
export const environment = {
etiqueta: '',
apiUrl: '/api',
};
// src/environments/environment.development.ts
export const environment = {
etiqueta: 'DESARROLLO',
apiUrl: '/api',
};
Y úsalo en cualquier fichero:
import { environment } from '../environments/environment';
No se decide al ejecutar, sino al compilar. El otro fichero ni siquiera entra en la salida: en esta aplicación, la palabra DESARROLLO aparece en main.js de la compilación de desarrollo, pero en ningún fichero de la de producción.
⚠️ Importante: Lo que pones en un entorno acaba en el JavaScript que descarga cualquier usuario. Nunca guardes ahí contraseñas ni claves privadas. Además, el valor se fija al compilar: para una misma compilación desplegada en varios sitios necesitarás configuración en tiempo de ejecución (módulo 27).
12. ng test: pruebas con Vitest
Angular usa Vitest como ejecutor de pruebas, y ng new lo deja listo sin ningún fichero de configuración. Por defecto, las pruebas se ejecutan en Node sobre jsdom, una simulación del DOM en JavaScript. No se abre ningún navegador.
ng test # vigila los ficheros y repite las pruebas
ng test --watch=false # una sola pasada, como en integración continua
Un fichero de pruebas
El CLI crea un .spec.ts junto a cada componente. Este es app.spec.ts:
import { TestBed } from '@angular/core/testing';
import { App } from './app';
describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [App],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(App);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it('should render title', async () => {
const fixture = TestBed.createComponent(App);
await fixture.whenStable();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, empresa');
});
});
describe agrupa pruebas, it define una, y expect comprueba un resultado. TestBed es el banco de pruebas de Angular: crea el componente en un entorno controlado. fixture.whenStable() espera a que Angular termine de pintar. Se ve a fondo en el módulo 26.
Leer un fallo
Has cambiado el título, así que la segunda prueba ya no es cierta. Ejecútala:
ng test --watch=false
❯ |empresa| src/app/app.spec.ts (2 tests | 1 failed)
❯ App (2)
× should render title
FAIL |empresa| src/app/app.spec.ts > App > should render title
AssertionError: expected 'EMPRESA' to contain 'Hello, empresa'
Expected: "Hello, empresa"
Received: "EMPRESA"
❯ src/app/app.spec.ts:21:55
19| await fixture.whenStable();
20| const compiled = fixture.nativeElement as HTMLElement;
21| expect(compiled.querySelector('h1')?.textContent).toContain('Hello…
| ^
22| });
23| });
Test Files 1 failed | 1 skipped (2)
Tests 1 failed | 1 passed | 2 skipped (4)
La salida indica qué prueba ha fallado, qué esperaba y qué ha recibido. No siempre significa que tu código esté mal: aquí la prueba se ha quedado anticuada. Corrige la expectativa:
expect(compiled.querySelector('h1')?.textContent).toContain('EMPRESA');
Probar el componente Inicio
El .spec.ts que generó el CLI haría una petición real. Una prueba unitaria no debe depender de que la API esté en marcha, así que simulamos fetch con vi.spyOn. Sustituye src/app/paginas/inicio/inicio.spec.ts:
import { TestBed } from '@angular/core/testing';
import { Inicio } from './inicio';
describe('Inicio', () => {
afterEach(() => vi.restoreAllMocks());
it('muestra el total de departamentos cuando la API responde', async () => {
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ total: 7 })),
);
const fixture = TestBed.createComponent(Inicio);
await vi.waitFor(() => {
expect(fixture.nativeElement.textContent).toContain('API operativa: 7 departamentos');
});
});
it('avisa si la API no está disponible', async () => {
vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('sin conexión'));
const fixture = TestBed.createComponent(Inicio);
await vi.waitFor(() => {
expect(fixture.nativeElement.textContent).toContain('No se puede contactar');
});
});
});
vi.waitFor repite la comprobación hasta que se cumple o se agota el tiempo. Hace falta porque fetch es asíncrono, y el texto tarda un instante en cambiar.
Test Files 2 passed (2)
Tests 4 passed (4)
Opciones de ng test
| Opción | Efecto |
|---|---|
--watch=false | Una pasada y termina |
--filter=Inicio | Solo las pruebas cuyo nombre encaja con la expresión regular |
--include=src/app/paginas | Solo los ficheros de esa ruta |
--list-tests | Muestra los ficheros de prueba y termina, sin compilar |
--ui | Interfaz web de Vitest |
--browsers=chromium | Ejecuta en un navegador real, con el proveedor correspondiente instalado |
--coverage | Informe de cobertura |
--reporters=verbose | Otro formato de salida |
--runner-config | Usa un fichero de configuración de Vitest propio |
--list-tests muestra:
Discovered test files:
src\app\app.spec.ts
src\app\paginas\inicio\inicio.spec.ts
La cobertura necesita un paquete que no se instala de serie. Si no lo tienes, el CLI te avisa:
The following packages are required but were not found:
- Code coverage requires either "@vitest/coverage-v8" or "@vitest/coverage-istanbul" to be installed.
Please install the missing packages and rerun the test command.
💡 Consejo:
ng testempieza compilando la aplicación, igual queng build. En una máquina muy cargada, el arranque del proceso de Vitest puede tardar más de lo permitido y verásTimeout waiting for worker to respond, sin que se llegue a ejecutar ninguna prueba. No es un fallo de tus pruebas: cierra otros procesos y repite la orden.
13. ng update: mantener el proyecto al día
Angular publica una versión mayor al año desde la 22, y varias menores y correcciones entre medias. ng update hace algo más que npm update: además de cambiar versiones, ejecuta migraciones, programas que modifican tu código para adaptarlo a los cambios de la nueva versión.
Ejecutado sin argumentos, ng update no cambia nada: lista los paquetes de tu proyecto que tienen una versión más reciente y la orden exacta para actualizar cada uno. Necesita un node_modules completo. Si faltan paquetes, termina con un error missing: ..., y se resuelve con npm install.
El proceso
# 1. Parte de un repositorio limpio: ng update se niega si hay cambios sin confirmar
git status
# 2. Actualiza el núcleo y el CLI juntos
ng update @angular/core@22 @angular/cli@22
# 3. Actualiza las bibliotecas de Angular que uses
ng update @angular/material@22
# 4. Revisa los cambios, compila y prueba
git diff
ng build
ng test --watch=false
Partir de un repositorio limpio no es un capricho: las migraciones modifican tu código, y quieres poder ver y deshacer lo que han hecho.
⚠️ Importante: No se pueden saltar versiones mayores. Para ir de la 20 a la 22 hay que pasar por la 21. La documentación oficial lo exige: el origen debe estar a una versión mayor del destino como mucho, y las migraciones de cada versión deben ejecutarse en orden. El asistente de actualización genera la lista de pasos para tu caso.
Opciones
| Opción | Efecto |
|---|---|
--force | Ignora incompatibilidades de dependencias entre pares. Úsala con cuidado |
--allow-dirty | Permite actualizar con cambios sin confirmar |
--create-commits, -C | Crea un commit por cada migración |
--next | Incluye versiones preliminares |
--migrate-only | Solo ejecuta migraciones, sin cambiar versiones |
--name | Ejecuta una migración concreta |
--from, --to | Rango de versiones de las migraciones, con --migrate-only |
Quedarse en la LTS no te libra de actualizar: dentro de la misma versión mayor sigues recibiendo correcciones de seguridad. ng update @angular/core@21 @angular/cli@21 las aplica. El paso de 21 a 22 se recorre paso a paso en el módulo 28.
14. Otras órdenes útiles
| Orden | Para qué sirve |
|---|---|
ng add <paquete> | Instala un paquete y lo configura. Por ejemplo, ng add @angular/material añade el tema y las dependencias |
ng lint | Analiza el código con ESLint. Hay que añadirlo antes |
ng cache | Consulta o limpia la caché de disco: ng cache info, ng cache clean |
ng config | Lee y escribe angular.json |
ng analytics | Activa o desactiva las estadísticas de uso |
ng completion | Autocompletado en la terminal |
ng deploy | Publica, si has añadido un paquete de despliegue |
ng extract-i18n | Extrae textos para traducir (módulo 25) |
ng e2e | Pruebas de extremo a extremo, si has configurado un builder |
ng run | Ejecuta cualquier objetivo de angular.json |
La diferencia entre npm install paquete y ng add paquete es importante. El primero solo descarga. El segundo ejecuta además el schematic del paquete, que puede modificar angular.json, añadir proveedores o crear ficheros.
ng lint no funciona en un proyecto recién creado, y el propio mensaje dice cómo empezar:
Cannot find "lint" target for the specified project.
You can add a package that implements these capabilities.
For example:
ESLint: ng add angular-eslint
ng cache info muestra si la caché está activa y cuánto ocupa. Su ruta es .angular/cache, ya excluida de Git. Si compilaciones que antes funcionaban fallan sin motivo tras cambiar de versión, ng cache clean es una buena primera prueba.
15. Preparar VS Code
Cualquier editor sirve, pero VS Code es el más extendido y el proyecto incluye configuración para él.
Angular Language Service
Al abrir el proyecto, VS Code te sugiere instalar la extensión recomendada, porque .vscode/extensions.json la declara:
{
"recommendations": ["angular.ng-template"]
}
Es la extensión Angular Language Service, mantenida por el equipo de Angular. Instálala: lleva a las plantillas lo que TypeScript ya te da en los .ts.
- Autocompletado en plantillas: propiedades del componente, selectores, inputs.
- Errores en el editor, mientras escribes, sin esperar a compilar.
- Información al pasar el ratón: de dónde viene cada símbolo y qué tipo tiene.
- Ir a la definición con F12, desde el HTML a la clase.
También funciona en editores derivados de VS Code, como Cursor. Sin ella, app.html es solo un fichero HTML más para el editor.
Formato: Prettier y .editorconfig
.editorconfig fija sangrado y codificación, y los editores lo respetan sin más. .prettierrc define las reglas de formato del proyecto:
{
"printWidth": 100,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": { "parser": "angular" }
}
]
}
La parte importante es "parser": "angular", porque el formateador HTML normal no entiende @if ni @for. Prettier ya viene en devDependencies, así que puedes formatear todo con npx prettier --write src. Si instalas la extensión de Prettier en VS Code, puedes activar editor.formatOnSave.
Depurar
.vscode/launch.json trae la configuración ng serve. Pulsa F5: VS Code ejecuta npm start, abre Chrome y se conecta a él. Puedes poner puntos de ruptura en tus .ts, gracias a los source maps.
⚠️ Importante: El mismo fichero incluye una configuración
ng testque apunta ahttp://localhost:9876/debug.html, la dirección de Karma. Con Vitest sobre jsdom no se abre ningún navegador, así que esa entrada no te servirá. Para depurar pruebas usang test --debug.
16. Angular DevTools y la consola del navegador
La extensión Angular DevTools
Angular DevTools es una extensión para Chrome y Firefox, disponible en sus tiendas. Tras instalarla, abre las herramientas de desarrollo con F12 y busca la pestaña Angular.
| Pestaña | Para qué sirve |
|---|---|
| Components | Árbol de componentes y directivas; muestra y permite editar su estado |
| Profiler | Graba la actividad para averiguar qué ralentiza la detección de cambios |
| Injector Tree | Jerarquía de inyectores de entorno y de elemento (módulo 08) |
| Router Tree | Árbol de rutas de la aplicación |
Solo funciona con aplicaciones compiladas sin optimización, como las que sirve ng serve. Con una compilación de producción normal no podrás usarla. La pestaña nueva del navegador no ejecuta extensiones, así que abre siempre tu aplicación primero.
Las utilidades ng de la consola
Aunque no tengas la extensión, en modo de desarrollo Angular publica un objeto global ng. Selecciona <app-inicio> en el panel Elementos y escribe en la consola:
const inicio = ng.getComponent($0); // $0 es el elemento seleccionado
inicio.estado(); // 'API operativa: 7 departamentos'
inicio.estado.set('Modificado a mano');
La última línea cambia la pantalla al momento, lo que resulta muy útil para probar estados difíciles de reproducir. Observa que estado es protected: esa protección solo existe para el compilador de TypeScript, no en el JavaScript que se ejecuta.
| Función | Devuelve |
|---|---|
ng.getComponent(el) | El componente de ese elemento |
ng.getOwningComponent(el) | El componente en cuya plantilla está el elemento |
ng.getRootComponents(el) | Los componentes raíz |
ng.getDirectives(el) | Las directivas aplicadas |
ng.getContext(el) | El contexto de una plantilla, como el de un @for |
ng.getListeners(el) | Los manejadores de eventos |
ng.getInjector(el) | El inyector asociado |
ng.applyChanges(componente) | Fuerza a actualizar la vista de un componente |
ng.isSignal(valor) | Si un valor es una señal |
Al inspeccionar verás a veces nombres como _Inicio en lugar de Inicio: es una transformación del compilador en modo de desarrollo y no afecta a nada.
En la compilación de producción, ng no existe:
> typeof ng
'undefined'
Esa es una forma rápida de saber si una página publicada se ha compilado en modo de producción.
17. El servidor MCP de Angular CLI
Los asistentes de programación con IA cometen errores frecuentes con Angular: conocen sobre todo la sintaxis antigua (*ngIf, NgModule) y no siempre saben qué versión usas. El Model Context Protocol (MCP) es un estándar abierto para que un asistente use herramientas externas. Angular CLI incluye un servidor MCP, que da al asistente acceso a la documentación oficial, las buenas prácticas de la versión de tu proyecto y a tus propios proyectos.
📘 Concepto: El servidor MCP no es una IA. Es un programa local que ofrece al asistente una lista de herramientas, y es el asistente quien decide cuándo llamarlas. Lo arranca el propio editor, con la orden
ng mcp. Tú no lo ejecutas a mano.
Configurarlo
En VS Code, crea .vscode/mcp.json. Fíjate en que .gitignore ya lo deja fuera de la exclusión de .vscode, para que puedas compartirlo con el equipo:
{
"servers": {
"angular-cli": {
"command": "npx",
"args": ["-y", "@angular/cli", "mcp"]
}
}
}
Cursor usa .cursor/mcp.json, y cambia la clave servers por mcpServers:
{
"mcpServers": {
"angular-cli": {
"command": "npx",
"args": ["-y", "@angular/cli", "mcp"]
}
}
}
Generar la configuración con ng g ai-config 🔄 v22.0
🔄 Cambia en Angular 22.0. Los valores de
--toolsonclaude-code,cursor,gemini-cli,open-ai-codexyvscode, y además de las instrucciones se crea la configuración MCP. En la LTS (Angular 21) los valores sonclaude,copilot,cursor,gemini,jetbrains,windsurfyagents. Si usas la LTS: usa esos nombres en--tooly crea a mano el fichero MCP de tu editor, como se ha mostrado arriba.
El CLI puede crear esos ficheros por ti, junto con un documento de instrucciones que explica al asistente las buenas prácticas de Angular moderno:
ng g ai-config --tool=claude-code --tool=vscode
CREATE CLAUDE.md (3029 bytes)
CREATE .mcp.json (129 bytes)
CREATE AGENTS.md (3029 bytes)
CREATE .vscode/mcp.json (126 bytes)
Lo mismo se consigue al crear el proyecto, con ng new empresa --ai-config=claude-code.
Herramientas que ofrece 🔄 v22.0
🔄 Cambia en Angular 22.0.
run_targety las herramientasdevserver_*funcionan sin activar nada desde las correcciones de la 22.0. En la LTS (Angular 21) existe ademásfind_examples, y ejecutar órdenes es experimental, con otros nombres (build,test,devserver.start…). Si usas la LTS: actívalas con"args": ["-y", "@angular/cli@21", "mcp", "--experimental-tool", "all"]; sin esa opción, el servidor solo consulta información y no ejecuta nada.
Esta es la lista que devuelve el servidor de la versión 22.2.5 a un cliente real:
| Herramienta | Qué hace | Solo lectura | Usa Internet |
|---|---|---|---|
list_projects | Lista espacios de trabajo, proyectos y objetivos | Sí | No |
get_best_practices | Devuelve la guía de buenas prácticas de Angular | Sí | No |
search_documentation | Busca en angular.dev | Sí | Sí |
ai_tutor | Convierte al asistente en un tutor de Angular | Sí | No |
onpush_zoneless_migration | Analiza código y planifica la migración a OnPush y zoneless | Sí | No |
run_target | Ejecuta un objetivo: build, test, lint… | No | No |
devserver_start | Arranca ng serve en segundo plano | No | No |
devserver_stop | Lo detiene | No | No |
devserver_wait_for_build | Espera a la compilación y devuelve el registro | Sí | No |
ai_tutor es interesante para estudiar: el asistente te guía para construir una aplicación paso a paso, en lugar de escribirla por ti.
Limitar lo que puede hacer
| Opción | Efecto |
|---|---|
--read-only | Solo registra herramientas que no modifican nada |
--local-only | Solo registra herramientas que no necesitan Internet |
Se añaden a args:
"args": ["-y", "@angular/cli", "mcp", "--read-only"]
Con --read-only --local-only quedan cinco herramientas: ai_tutor, get_best_practices, list_projects, onpush_zoneless_migration y devserver_wait_for_build.
Limitar las carpetas con --root 🆕 v22.2
🆕 Desde Angular 22.2. La opción
--rootdel servidor MCP no existe en la LTS (Angular 21). Si usas la LTS: no hay opción equivalente; usa--read-onlypara impedir que el asistente modifique nada y abre en el editor solo la carpeta del proyecto.
--root indica las carpetas a las que el servidor puede acceder y en las que busca espacios de trabajo. Se puede repetir para indicar varias. Es útil si tu cliente no comunica bien la carpeta abierta, o si quieres limitar el alcance.
"args": ["-y", "@angular/cli", "mcp", "--root", "/home/ana/proyectos/empresa"]
⚠️ Importante: Un asistente conectado por MCP puede ejecutar órdenes y modificar ficheros en tu proyecto. Trabaja con Git, revisa lo que cambia y no des por bueno un código solo porque compile. Si estudias, intenta primero resolver los ejercicios por tu cuenta: el objetivo es que aprendas tú.
El módulo 27 retoma estas herramientas con ejemplos de uso y Agent Skills.
18. Errores frecuentes
| Síntoma | Causa | Solución |
|---|---|---|
ng: command not found o “no se reconoce” | CLI sin instalar en esta versión de Node, o PATH sin actualizar | npm i -g @angular/cli, abre otra terminal o usa npx ng |
| PowerShell: “la ejecución de scripts está deshabilitada” | Política de ejecución de Windows | Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned |
The Angular CLI requires a minimum Node.js version... | Node demasiado antiguo | Actualiza Node a una versión admitida |
This command is not available when running the Angular CLI inside a workspace. | Has lanzado ng new dentro de un proyecto | Sal de esa carpeta |
Port 4200 is already in use | Otro ng serve abierto | Ciérralo o usa --port |
Cannot find module '@angular/...' | No se han instalado las dependencias | npm install |
NG05104: The selector "..." did not match any elements | index.html y selector no coinciden | Hazlos coincidir |
/api/... devuelve el HTML de la aplicación | Proxy no aplicado | Revisa proxyConfig y reinicia ng serve |
/api/... devuelve 500 o error de conexión | La API no está arrancada, o localhost se resuelve a otra dirección | Arranca la API o usa 127.0.0.1 en target |
Página en blanco tras publicar, con 404 en main-*.js | base href incorrecto | Compila con --base-href |
| 404 al recargar una ruta publicada | El servidor no devuelve index.html | Configura el servidor (módulo 27) |
bundle initial exceeded maximum budget | Se ha superado un presupuesto | Investiga con --stats-json antes de subir el límite |
Timeout waiting for worker to respond en ng test | Máquina saturada | Cierra procesos y repite |
ng update rechaza actualizar | Hay cambios sin confirmar | Haz commit o usa --allow-dirty |
| Comportamiento extraño tras cambiar de versión | Caché antigua | ng cache clean |
19. Ejercicios
Ejercicio 1: Proyecto a medida sin tocar el disco
Averigua qué ficheros generaría un proyecto mínimo llamado demo, sin pruebas y con la plantilla y los estilos del componente raíz dentro de app.ts. No debe crearse ningún fichero ni instalarse nada. Indica cuántos crearía y qué falta respecto a uno normal.
Ver solución
La opción --minimal hace justo eso, y --dry-run evita escribir:
ng new demo --minimal --defaults --skip-git --skip-install --dry-run
CREATE demo/.prettierrc (173 bytes)
CREATE demo/angular.json
CREATE demo/package.json
CREATE demo/README.md (1516 bytes)
CREATE demo/tsconfig.json (886 bytes)
CREATE demo/.gitignore (666 bytes)
CREATE demo/tsconfig.app.json (412 bytes)
CREATE demo/.vscode/extensions.json (134 bytes)
CREATE demo/.vscode/launch.json (310 bytes)
CREATE demo/.vscode/tasks.json (575 bytes)
CREATE demo/src/main.ts (228 bytes)
CREATE demo/src/index.html (303 bytes)
CREATE demo/src/styles.css (81 bytes)
CREATE demo/src/app/app.ts (339 bytes)
CREATE demo/src/app/app.config.ts (322 bytes)
CREATE demo/src/app/app.routes.ts (80 bytes)
CREATE demo/public/favicon.ico (15086 bytes)
NOTE: The "--dry-run" option means no changes were made.
Son 17 ficheros frente a los 22 de un proyecto normal. Faltan .editorconfig, tsconfig.spec.json, app.spec.ts, app.html y app.css: no hay pruebas, y la plantilla y los estilos están dentro de app.ts. El tsconfig.json también es más corto, porque omite opciones. Compruébalo con ls: no se ha creado ninguna carpeta demo.
Este modo es solo para experimentar. Sin pruebas no es un punto de partida para un proyecto real.
Ejercicio 2: Componente en una ruta
Crea en EMPRESA un componente paginas/acerca sin fichero de estilos ni pruebas, con un título y un párrafo, y hazlo accesible en /acerca. Comprueba con --dry-run qué creará antes de generarlo.
Ver solución
ng g c paginas/acerca --style=none --skip-tests --dry-run
ng g c paginas/acerca --style=none --skip-tests
src/app/paginas/acerca/acerca.html:
<h2>Acerca de EMPRESA</h2>
<p>Aplicación de gestión de departamentos y empleados.</p>
src/app/app.routes.ts:
import { Routes } from '@angular/router';
import { Inicio } from './paginas/inicio/inicio';
import { Acerca } from './paginas/acerca/acerca';
export const routes: Routes = [
{ path: '', component: Inicio },
{ path: 'acerca', component: Acerca },
];
Sin --style=none, el generador habría creado un .css vacío y añadido styleUrl. Con esta opción, el decorador no lleva styleUrl. Visita http://localhost:4200/acerca: la cabecera sigue ahí, porque está en App, y lo que cambia es lo que hay dentro de <router-outlet />.
Ejercicio 3: El proxy
Sin proxy, ¿qué respuesta recibes de curl http://localhost:4200/api/departamentos? ¿Por qué? Configúralo y explica qué tres cosas deben cumplirse para que funcione.
Ver solución
Sin proxy, ng serve responde con el HTML de la aplicación y estado 200. No hay error: el servidor de desarrollo devuelve index.html para las rutas que no conoce, porque en una SPA es lo correcto. Por eso fetch no falla por la red, sino en respuesta.json(), al no poder interpretar HTML como JSON. El componente Inicio captura ese error y muestra “No se puede contactar con la API”.
Es un síntoma muy reconocible: si ves Unexpected token '<' en una petición a la API, estás recibiendo HTML.
Para que funcione:
- La API debe estar en marcha en el puerto de
target. proxy.conf.jsondebe estar registrado, conproxyConfigenangular.jsono con--proxy-config.ng servedebe haberse reiniciado después de cambiar la configuración.
Ejercicio 4: Una etiqueta de entorno
Haz que la cabecera muestre DESARROLLO junto al título en desarrollo, y nada en producción. Comprueba en los ficheros compilados que la palabra no llega a producción.
Ver solución
ng g environments
Rellena los dos ficheros como en la sección 11, y usa el valor en src/app/app.ts:
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { environment } from '../environments/environment';
@Component({
imports: [RouterOutlet],
selector: 'app-root',
styleUrl: './app.css',
templateUrl: './app.html',
})
export class App {
protected readonly title = signal('EMPRESA');
protected readonly etiqueta = environment.etiqueta;
}
En app.html:
<h1>{{ title() }} <small>{{ etiqueta }}</small></h1>
Para comprobarlo, compila de las dos formas y busca el texto:
ng build
grep -c DESARROLLO dist/empresa/browser/main-*.js # 0
ng build --configuration development
grep -l DESARROLLO dist/empresa/browser/*.js # main.js
La compilación de producción no contiene la palabra: environment.development.ts no se ha incluido. ng serve usa la configuración development, y por eso muestra la etiqueta.
Ejercicio 5: Presupuesto ajustado
Configura el presupuesto initial para que avise a partir de 100 kB y falle a partir de 1 MB. Compila y explica la diferencia entre aviso y error para un sistema de integración continua.
Ver solución
En angular.json, dentro de la configuración production:
"budgets": [
{ "type": "initial", "maximumWarning": "100kB", "maximumError": "1MB" },
{ "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" }
]
ng build
▲ [WARNING] bundle initial exceeded maximum budget. Budget 100.00 kB was not met by 92.19 kB with a total of 192.19 kB.
La aplicación ocupa 192 kB, más que el aviso y menos que el error. Se genera dist/ y la orden termina con código 0, de modo que el aviso no detiene nada: solo lo verás si lees la salida. Un error, en cambio, termina con código 1, y un sistema de integración continua lo trata como un fallo.
Por eso los dos límites se complementan: el aviso avisa pronto al equipo, y el error marca lo que no se debe publicar. No olvides devolver el valor a 500kB, porque en una aplicación real de este tamaño 100 kB no tiene sentido: solo Angular ya ocupa más.
Ejercicio 6: Una función con prueba
Crea src/app/utilidades/formato.ts con una función iniciales(nombre) que devuelva las iniciales en mayúsculas: 'ana garcia' da 'AG'. Debe ignorar los espacios sobrantes. Escribe primero su prueba y ejecuta solo esa.
Ver solución
src/app/utilidades/formato.spec.ts:
import { iniciales } from './formato';
describe('iniciales', () => {
it('toma la primera letra de cada palabra', () => {
expect(iniciales('ana garcia')).toBe('AG');
});
it('ignora los espacios sobrantes', () => {
expect(iniciales(' luis martinez lopez ')).toBe('LML');
});
});
src/app/utilidades/formato.ts:
export function iniciales(nombre: string): string {
return nombre
.trim()
.split(/\s+/)
.map((palabra) => palabra[0].toUpperCase())
.join('');
}
ng test --watch=false --filter=iniciales
Test Files 1 passed | 2 skipped (3)
Tests 2 passed | 4 skipped (6)
trim elimina los espacios de los extremos, y la expresión \s+ trata cualquier grupo de espacios como un solo separador. Con split(' ') aparecerían palabras vacías y palabra[0] sería undefined. Una función pura como esta no necesita TestBed, y por eso la prueba es tan corta.
Ejercicio 7: Los valores por defecto del equipo
En tu equipo, los componentes no llevan prueba automática ni fichero de estilos. Configúralo en angular.json y comprueba que sigues pudiendo pedir la prueba en un caso concreto.
Ver solución
En angular.json, dentro de projects.empresa, sustituye "schematics": {} por esto:
"schematics": {
"@schematics/angular:component": {
"skipTests": true,
"style": "none"
}
}
Compruébalo:
ng g c paginas/ayuda --dry-run
CREATE src/app/paginas/ayuda/ayuda.ts
CREATE src/app/paginas/ayuda/ayuda.html
Y en el caso especial, con la opción de la línea de órdenes, que tiene prioridad:
ng g c paginas/ayuda --skip-tests=false --dry-run
CREATE src/app/paginas/ayuda/ayuda.spec.ts
CREATE src/app/paginas/ayuda/ayuda.ts
CREATE src/app/paginas/ayuda/ayuda.html
Como angular.json está en Git, todo el equipo obtiene el mismo comportamiento. Los valores se aplican a un solo proyecto; también pueden ponerse en la clave schematics de la raíz para todo el espacio de trabajo.
Ejercicio 8: Diagnóstico de plantilla
Un compañero añade "strictTemplates": false a angularCompilerOptions “para que deje de dar errores”. Sin esa línea, el proyecto da este error:
TS2322: Type 'string' is not assignable to type 'number'.
Explica qué ha conseguido y cuál es la solución correcta.
Ver solución
Ha ocultado el aviso, no ha resuelto el problema. El componente sigue recibiendo un texto donde espera un número, y el fallo pasará a la ejecución, delante del usuario. Puede que total() + 1 produzca '121' en lugar de 13, o que un cálculo dé NaN.
La solución es quitar esa línea y corregir el tipo, de una de estas formas:
- Pasar un número desde la plantilla:
[total]="totalDepartamentos()". - Convertir el dato en el componente padre.
- Si la entrada debe aceptar texto, declararla con
input.required<string>(), o aplicar una transformación comonumberAttribute, que verás en el módulo 06.
Ejercicio 9 (propuesto): Sirve la compilación
Compila EMPRESA para producción y sírvela con un servidor estático: npx serve -s dist/empresa/browser. Explica por qué la tarjeta dice ahora que no puede contactar con la API, y qué significa la opción -s.
Ejercicio 10 (propuesto): Qué hay pendiente
Crea un proyecto con npx @angular/cli@21 new prueba21, ejecuta ng update dentro y anota qué paquetes propone. Lee la guía de update-guide para pasar de 21 a 22, y haz la actualización en una rama de Git. ¿Qué ha cambiado en tsconfig.json?
Ejercicio 11 (propuesto): Explora con DevTools
Instala Angular DevTools, selecciona Inicio en Components y cambia el valor de estado. Después repite el cambio desde la consola con ng.getComponent($0). Graba una sesión en Profiler mientras recargas la página.
Ejercicio 12 (propuesto): Conecta un asistente
Si usas un asistente compatible con MCP, añade el servidor con --read-only. Pídele que liste los proyectos del espacio de trabajo y que busque en la documentación cómo funciona bootstrapApplication. Comprueba en sus respuestas qué herramienta ha usado.
Resumen
| Concepto | API | Idea clave |
|---|---|---|
| CLI | ng | Una orden por tarea; delega en schematics y builders |
| Node | node -v | Solo para desarrollar; Angular 22 exige ^22.22.3 \|\| ^24.15.0 \|\| >=26.0.0 |
| CLI global y local | npm i -g @angular/cli, npx | El proyecto usa siempre su CLI local |
| Crear proyecto | ng new, --dry-run | Usa npx @angular/cli@21 para la LTS |
| Arranque | bootstrapApplication(App, appConfig) | index.html tiene <app-root>, y selector lo conecta |
| Configuración global | app.config.ts, provide... | Proveedores para toda la aplicación |
| Dependencias | package.json, package-lock.json, npm ci | Sube el lock a Git; node_modules, nunca |
| Espacio de trabajo | angular.json, architect, configurations | Cada orden es un objetivo con su builder |
| TypeScript | tsconfig.json y sus dos derivados | strict activo por defecto en TS 6; TS 7 no se admite |
| Plantillas | strictTemplates | Implícito en 22: los errores del HTML se detectan al compilar |
| Desarrollo | ng serve, --port, --open | Compila en memoria, vigila ficheros y recarga |
| API | proxy.conf.json, proxyConfig | Solo en desarrollo; reinicia al cambiarlo |
| Generar | ng g c, ng g s, --dry-run | Sin sufijos; @Service() en 22 |
| Valores del equipo | schematics en angular.json | Evitan repetir opciones |
| Producción | ng build, dist/empresa/browser | Ficheros estáticos con hash |
| Subcarpeta | --base-href | Sin él, página en blanco. Cuidado con Git Bash |
| Presupuestos | budgets | Avisan o rompen si la aplicación engorda |
| Entornos | ng g environments | Se eligen al compilar; nada de secretos |
| Pruebas | ng test, --watch=false, --filter | Vitest sobre jsdom, sin navegador |
| Actualizar | ng update, --create-commits | Con repositorio limpio y de una versión mayor en una |
| Editor | Angular Language Service | Autocompletado y errores en plantillas |
| Inspección | Angular DevTools, ng.getComponent($0) | Solo en compilaciones de desarrollo |
| IA | ng mcp, --read-only, --local-only | El asistente usa documentación actual y tu proyecto |