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 new y 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.json y los tres tsconfig
  • Desarrollar con ng serve, usar la recarga en caliente y conectar con la API EMPRESA mediante un proxy
  • Generar código con ng generate y cambiar sus valores por defecto
  • Compilar para producción con ng build, entender presupuestos, hashes, base href y entornos
  • Ejecutar pruebas con ng test y Vitest, e interpretar un fallo
  • Actualizar un proyecto con ng update sin saltarse versiones mayores
  • Preparar VS Code, usar Angular DevTools y las utilidades ng de 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.

El proyecto entra en la orden ng, que lee angular.json y delega en schematics, esbuild, Vite y Vitest para producir ficheros, dist, un servidor local e informes

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.json qué 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.0 o >=26.0.0; la LTS (Angular 21) admite ^20.19.0, ^22.12.0 o ^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 -v qué versión responde realmente. En Windows es habitual tener dos instalaciones y que la antigua aparezca antes en el PATH.

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-windows o fnm. 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, o ng dejará 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 con Set-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/cli de cada proyecto. Cuando ejecutas ng dentro 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.

Página de bienvenida generada por Angular con el texto Hello, empresa y enlaces a la documentación

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-run existe también en ng 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, .css y .spec.ts.
  • public/ contiene lo que debe llegar sin transformar: imágenes, iconos, ficheros de datos. public/logo.png estará disponible en /logo.png. Nada de esa carpeta pasa por el compilador.
  • src/styles.css se aplica a toda la página. Los estilos de app.css solo afectan a su componente. El motivo se explica en el módulo 03.
  • node_modules/ puede pesar cientos de megas y se regenera con npm install. Nunca lo copies ni lo subas a Git. Si envías el proyecto a alguien, envía el resto y que ejecute npm 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.

Seis pasos del arranque: index.html con app-root vacío, main.ts llama a bootstrapApplication, app.config.ts aporta los proveedores, app.ts declara el selector, se pinta la plantilla y queda la página visible

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 changeDetection usa la estrategia OnPush. En la LTS (Angular 21) el valor por defecto es Eager, y el CLI tampoco escribe esa propiedad. Si usas la LTS: añade changeDetection: ChangeDetectionStrategy.OnPush a 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.0 admite cualquier versión 22.x.y igual o posterior a la 22.2.0.
  • ~6.0.2 solo admite 6.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.json impide ejecutar todas las órdenes del proyecto. Si ng serve deja 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.

tsconfig.app.json y tsconfig.spec.json extienden tsconfig.json, que reúne las reglas comunes

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 strict está activado por defecto, y por eso ng new ya no escribe esa línea en tsconfig.json. La LTS (Angular 21) usa TypeScript 5.9, cuyo valor por defecto es false. Si usas la LTS: mantén "strict": true, que ng new escribe en su tsconfig.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.json no lo diga, y ng new ya no escribe strictTemplates. En la LTS (Angular 21) el valor por defecto sigue siendo false. Si usas la LTS: conserva "strictTemplates": true en angularCompilerOptions, que ng new escribe 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:

Mensaje de error superpuesto en el navegador: TS2551, la propiedad estadoo no existe en el tipo Inicio, con sugerencia de estado

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.

El navegador pide /api/departamentos a ng serve en el puerto 4200, que reenvía la petición a la API en el puerto 3000

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 /api hacia la API: normalmente, Nginx u otro servidor web. Lo verás en el módulo 27.

💡 Consejo: Si target usa localhost y recibes errores de conexión, prueba con http://127.0.0.1:3000. Algunos sistemas resuelven localhost a 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 service genera 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:

Aplicación EMPRESA con cabecera azul y una tarjeta que dice: API operativa, 7 departamentos

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 eso index.html no 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 a ng. Al probar ng 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ón baseHref de angular.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 test empieza compilando la aplicación, igual que ng build. En una máquina muy cargada, el arranque del proceso de Vitest puede tardar más de lo permitido y verás Timeout 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 test que apunta a http://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 usa ng 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 --tool son claude-code, cursor, gemini-cli, open-ai-codex y vscode, y además de las instrucciones se crea la configuración MCP. En la LTS (Angular 21) los valores son claude, copilot, cursor, gemini, jetbrains, windsurf y agents. Si usas la LTS: usa esos nombres en --tool y 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_target y las herramientas devserver_* funcionan sin activar nada desde las correcciones de la 22.0. En la LTS (Angular 21) existe además find_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 --root del servidor MCP no existe en la LTS (Angular 21). Si usas la LTS: no hay opción equivalente; usa --read-only para 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:

  1. La API debe estar en marcha en el puerto de target.
  2. proxy.conf.json debe estar registrado, con proxyConfig en angular.json o con --proxy-config.
  3. ng serve debe 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 como numberAttribute, 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