Plantilla de un proyecto nuevo¶
Receta para dejar un proyecto de Angular nuevo igual que los demás de
angular-ufh: mismo formato de código, Tailwind CSS con daisyUI, la clave de
la API detrás de un proxy y el despliegue en Coolify con su subdominio. Son
pasos mecánicos; el porqué de cada uno está en el módulo que se enlaza.
Para seguirla necesitas un proyecto recién creado con ng new (el
Módulo A2 explica las
preguntas que hace). En este repositorio los proyectos se crean en la raíz,
al mismo nivel que 01-typescript-intro/; ng new detecta el repositorio
padre y no crea otro. La carpeta lleva número (05-pipes-app/) y el proyecto
de Angular no (pipes-app, en angular.json y package.json).
En esta página, <nombre> es el nombre del proyecto sin número (pipes-app)
y <carpeta> la carpeta con número (05-pipes-app).
Atajo: la skill ng-setup. Toda la parte mecánica de esta receta la hace
una skill compartida de Claude Code y Codex, ng-setup (/ng-setup <carpeta>;
vive en ~/.codex/skills/ng-setup/). Pregunta el kit de UI (Tailwind con
daisyUI por defecto, Tailwind solo, Bootstrap o ninguno), el tema de daisyUI,
si el proyecto llama a una API con clave y el subdominio, y después ejecuta su
script:
py ~/.codex/skills/ng-setup/scripts/setup.py <carpeta> --ui daisyui --theme sunset --domain angular-<nombre>
# con una API con clave, además:
# --api-name NOMBRE_API_KEY --api-target https://api.ejemplo.com # --api-prefix api/<nombre> --api-path v1 --api-key-mode header # o query
Cubre los pasos 1 a 4 y 6.1 a 6.3: borra el .prettierrc del proyecto, agrega
los scripts de formato y lint, el kit de UI, rootDir y los alias @shared/* y
@environments/* en los tsconfig, los environments, .env con su
plantilla y el proxy, Dockerfile y Nginx, .dockerignore, .env en
.gitignore, la carpeta src/app/shared/ (components, interfaces,
services, utils, pages, constants), el borrado de los archivos de
contexto que deja ng new, las secciones del README y la fila y la sección del
AGENTS.md de la raíz. Se
puede repetir sin miedo: lo que ya está hecho lo deja igual. Quedan a mano el
paso 5 (Coolify y Cloudflare, los imprime al final) y el 6.4 (la página de la
wiki). Los prefijos de la API van sin la barra inicial (api/<nombre>): en Git
Bash un argumento que empieza por / se convierte en una ruta de Windows. Si la
clave de la API llega cuando el proyecto ya está hecho, la skill add-proxy-env
(/add-proxy-env <carpeta>, mismas opciones --api-*) agrega sólo el paso 3 y
la parte del proxy del paso 4: .env, proxy.conf.mjs, la URL en los
environments, nginx.conf.template, y actualiza el README y el AGENTS.md.
Lista de control¶
| Paso | Qué queda hecho | Se puede saltar si... |
|---|---|---|
| 1 | Prettier y ESLint con los scripts format y lint |
Nunca |
| 2 | Tailwind CSS y daisyUI con un tema elegido | El proyecto no lleva estilos de daisyUI |
| 3 | La clave de la API en .env y un proxy en ng serve |
El proyecto no llama a una API con clave |
| 4 | Dockerfile, Nginx y .dockerignore |
El proyecto no se publica |
| 5 | Coolify y el subdominio en Cloudflare | El proyecto no se publica |
| 6 | README, AGENTS.md y la página de la wiki | Nunca |
1. Formato del código: Prettier y ESLint¶
La convención del repositorio es comillas simples y sin punto y coma. Prettier reescribe los archivos y ESLint avisa cuando algo no cumple. La explicación completa está en Formato del código; aquí, sólo los pasos.
1.1. No crees un .prettierrc en el proyecto. Ya existe uno en la raíz
del repositorio y Prettier usa el más cercano sin mezclarlos: si el proyecto
tuviera el suyo, reemplazaría al de la raíz por completo. ng new 22 crea uno
(sin semi: false): bórralo, o deja que la skill ng-setup lo haga. Compruébalo
desde la carpeta del proyecto:
npx prettier --find-config-path src/app/app.ts
# ../.prettierrc
1.2. Instala las herramientas (Prettier para formatear, ESLint con el parser de TypeScript y el plugin de estilo para la regla del punto y coma):
npm install -D prettier eslint typescript-eslint @stylistic/eslint-plugin
Versiones que usan los proyectos actuales, por si una nueva rompe algo:
prettier ^3.8, eslint ^10, typescript-eslint ^8.71,
@stylistic/eslint-plugin ^5.10.
1.3. Agrega los scripts a package.json, junto a start y build:
"format": "prettier --write \"src/**/*.{ts,html,scss}\"",
"format:check": "prettier --check \"src/**/*.{ts,html,scss}\"",
"lint": "eslint \"src/**/*.ts\"",
"lint:fix": "eslint \"src/**/*.ts\" --fix"
Sólo .ts, .html y .scss: el .css de Tailwind se deja fuera a propósito
(Prettier cambiaría sus comillas y reordenaría la lista de temas).
1.4. Crea eslint.config.mjs en la carpeta del proyecto:
import { defineConfig, globalIgnores } from 'eslint/config'
import stylistic from '@stylistic/eslint-plugin'
import tseslint from 'typescript-eslint'
// Only code style rules live here. Prettier formats the files; ESLint makes the "no
// semicolons" convention visible (editor underline, `npm run lint`) even if a file was
// saved without formatting.
export default defineConfig([
globalIgnores(['dist/', '.angular/', 'node_modules/', 'coverage/']),
{
files: ['**/*.ts'],
languageOptions: { parser: tseslint.parser },
plugins: { '@stylistic': stylistic },
rules: {
'@stylistic/semi': ['error', 'never'],
},
},
])
1.5. Primera pasada. El código que genera ng new trae punto y coma, así
que la primera vez cambia muchos archivos. Hazlo en un commit aparte, para no
mezclarlo con cambios de verdad:
npm run format
npm run lint
Si formateas al guardar en VS Code, la receta está en Formato del código; es configuración local de cada computadora.
1.6. Sufijos de los archivos generados. Desde Angular 20,
ng generate component demo crea demo.ts (sin .component). En este
repositorio se conservan los nombres con sufijo (demo.component.ts,
data.service.ts, upper.pipe.ts), y los archivos se generan con la extensión
de VS Code Angular Schematics (clic derecho sobre una carpeta → Angular
Schematics: Generate a file). Esa extensión no usa el CLI ni lee
angular.json: tiene su propia configuración, que se fija una vez por
repositorio con Ctrl+Shift+P → Angular Schematics: Configuration helper
(Use legacy naming conventions, Enable SCSS...). El resultado queda en
.vscode/settings.json de la raíz, bajo
angular-schematics.schematicsDefaultOptions. Esta es la configuración del
repositorio:
"angular-schematics.schematicsDefaultOptions": {
"angular-*": { "externalTemplate": true, "skipStyle": true, "style": "scss" },
"angular-component": { "ngOnInit": false, "suffix": "component", "fileNameSuffix": ".component" },
"angular-page": { "ngOnInit": false, "fileNameSuffix": ".page" },
"angular-directive": { "suffix": "directive", "fileNameSuffix": ".directive" },
"angular-service": { "suffix": "service", "fileNameSuffix": ".service" },
"angular-pipe": { "fileNameSuffix": ".pipe" },
"angular-guard": { "fileNameSuffix": ".guard" },
"angular-interceptor": { "fileNameSuffix": ".interceptor" },
"angular-resolver": { "fileNameSuffix": ".resolver" },
"angular-async-reactive-service": { "fileNameSuffix": ".service" },
"angular-sync-reactive-service": { "fileNameSuffix": ".service" },
"angular-material-dialog": { "fileNameSuffix": ".dialog" },
"angular-material-table": { "suffix": "component", "fileNameSuffix": ".component" }
}
Qué hace cada clave:
suffix: el sufijo de la clase (DemoComponent,DataService);fileNameSuffix, el del archivo (demo.component.ts). Los pipes, guards, interceptors y resolvers ya llevan el sufijo en la clase, así que sólo cambian el del archivo.externalTemplate: true: la plantilla va endemo.component.html, no en línea.skipStyle: true: no crea el archivo de estilos; cuando un componente lo necesita se agrega a mano (styleUrl: './demo.component.scss').style: "scss"es la extensión que usaría si se crea.ngOnInit: false: la clase sale vacía, sinconstructorningOnInit.- No hay
skipChangeDetectionOnPush: con esa clave la extensión escribíachangeDetectionen el decorador; sin ella no agrega nada.
.vscode/ está ignorado en todo el repositorio, así que esta configuración es
local de cada computadora: en una máquina nueva se copia este bloque o se
repite el configuration helper. ng generate en la terminal no la ve y sigue
creando demo.ts.
2. Tailwind CSS y daisyUI¶
Tailwind da las clases pequeñas (text-3xl, flex) y daisyUI agrega encima
las clases de componentes (btn, card, navbar). El porqué de cada archivo
está en el Módulo C1
y la elección del tema en el
Módulo C2.
2.1. Instala los paquetes:
npm install tailwindcss @tailwindcss/postcss postcss
npm install -D daisyui
2.2. Crea .postcssrc.json en la carpeta del proyecto, para que Angular
procese el CSS con Tailwind:
{
"plugins": {
"@tailwindcss/postcss": {}
}
}
2.3. Crea src/tailwind.css con Tailwind, daisyUI y los temas que vas a
generar. Sólo se puede activar un tema que esté en esta lista:
@import "tailwindcss";
@plugin "daisyui" {
themes: light --default, dark --prefersdark, sunset;
}
--default: el tema cuando no se indica ninguno.--prefersdark: el que se usa si el sistema está en modo oscuro.sunset(o el que elijas): el que vas a activar a mano en el paso 2.5. La lista completa está en la documentación de daisyUI.
2.4. Carga el archivo en angular.json, antes de los estilos propios, en
projects.<nombre>.architect.build.options.styles:
"styles": [
"src/tailwind.css",
"src/styles.scss"
]
No pongas @import "tailwindcss" dentro de styles.scss: Sass intenta
resolver ese @import él mismo y avisa de que está obsoleto. Por eso Tailwind
vive en su propio .css.
2.5. Activa el tema en src/index.html, en la etiqueta <html>:
<html lang="en" data-theme="sunset">
Con data-theme en <html> toda la aplicación usa ese tema. También se puede
poner en cualquier elemento para cambiar sólo esa parte.
2.6. Reinicia ng serve (los cambios en .postcssrc.json y
angular.json no se recargan solos) y prueba una clase de cada uno en una
plantilla: class="btn btn-primary" tiene que verse como un botón del tema.
VS Code marca @plugin como regla CSS desconocida: es un aviso del editor, no
del build.
2.7. Opcional: cursores propios. La flecha del sistema a veces casi no se ve sobre un tema de fondo claro, o simplemente quieres darle tu toque. CSS no puede cambiarle el color a esa flecha, pero sí reemplazarla por una imagen. El repositorio usa un juego de 12 SVG de 32×32 px, uno por tipo de cursor:
Archivo en public/ |
Cursor de respaldo | Cuándo sale |
|---|---|---|
cursor.svg |
auto |
La flecha normal (todo el body) |
cursor-pointer.svg |
pointer |
Enlaces y botones |
cursor-text.svg |
text |
Texto e inputs |
cursor-not-allowed.svg |
not-allowed |
Elementos deshabilitados |
cursor-wait.svg |
wait |
Algo cargando (aria-busy="true") |
cursor-grab.svg |
grab |
Elementos arrastrables (draggable="true") |
cursor-grabbing.svg |
grabbing |
Mientras se arrastra (:active) |
cursor-move.svg |
move |
Clase .cursor-move |
cursor-cross.svg |
crosshair |
Clase .cursor-cross |
cursor-zoom.svg |
zoom-in |
Clase .cursor-zoom |
cursor-help.svg |
help |
Clase .cursor-help |
cursor-resize.svg |
nwse-resize |
Clase .cursor-resize |
- Copia los 12 archivos a
public/. Si dibujas los tuyos, ponles un color explícito, comofill="#ef4444": concurrentColorse ven negros, porque dentro de un cursor la imagen no hereda el color del texto. Y deja siemprewidth="32" height="32"en la etiqueta<svg>: sin tamaño, algunos navegadores ignoran la imagen. - Aplícalos en
src/styles.scss:
body {
cursor:
url('/cursor.svg') 4 2,
auto;
}
a,
button,
[role='button'] {
cursor:
url('/cursor-pointer.svg') 13 2,
pointer;
}
p,
h1,
h2,
h3,
input,
textarea {
cursor:
url('/cursor-text.svg') 16 16,
text;
}
:disabled,
[aria-disabled='true'] {
cursor:
url('/cursor-not-allowed.svg') 16 16,
not-allowed;
}
[aria-busy='true'] {
cursor:
url('/cursor-wait.svg') 16 16,
wait;
}
[draggable='true'] {
cursor:
url('/cursor-grab.svg') 16 16,
grab;
&:active {
cursor:
url('/cursor-grabbing.svg') 16 16,
grabbing;
}
}
.cursor-move {
cursor:
url('/cursor-move.svg') 16 16,
move;
}
/* .cursor-cross, .cursor-zoom, .cursor-help y .cursor-resize siguen el mismo patrón */
- Cada
url(...)lleva dos números, el punto activo (hotspot): el lugar de la imagen que hace clic, en píxeles desde su esquina superior izquierda. En la flecha es la punta (4 2), en la manita la punta del dedo (13 2) y en los demás el centro de la imagen (16 16). Si el clic se siente desplazado, es lo primero que hay que ajustar. - El último valor (
auto,pointer,text…) es el cursor de respaldo si la imagen no carga. - Los navegadores limitan la imagen a 128×128 px; 32×32 es lo seguro.
- En un proyecto con daisyUI no hace falta nada más: las clases de daisyUI
(
.btn) viven en una capa de CSS (@layer) y los estilos destyles.scss, que no tienen capa, les ganan. Bootstrap no usa capas y pone su propiocursor: pointeren.btnybutton:not(:disabled); ahí hay que sumar esos dos selectores a la regla de la manita (el ejemplo está en06-reactive-forms-app/). - En un proyecto con
styles.css(sin Sass) no se puede anidar&:active: se escribe como una regla aparte,[draggable='true']:active { ... }.
Un ejemplo real está en 05-pipes-app/ (public/ y styles.scss).
3. La clave de la API: .env y un proxy¶
La aplicación nunca conoce la clave. Llama a una URL relativa de su propio
servidor (/api/<nombre>/...) y un proxy agrega la clave y reenvía la
petición a la API: en desarrollo lo hace ng serve, en producción Nginx
(paso 4). La explicación completa y qué protege cada pieza están en el
Módulo P5;
los dos casos concretos, en el
Módulo G15
(Giphy, clave en la URL) y el
Módulo C10
(REST Countries, clave en un encabezado).
3.1. .env y .env.template. En la carpeta del proyecto, .env guarda
la clave y está ignorado por git; .env.template va en git con el mismo
nombre de variable y un marcador en lugar del valor:
# .env.template (documentación: copia este archivo a .env y pon el valor)
NOMBRE_API_KEY=<NOMBRE_API_KEY>
cp .env.template .env # y escribe la clave real
ng new no lo trae: agrega .env a .gitignore antes de crear el archivo,
para que no entre en git ni por error. En el paso 4 lo agregas también a
.dockerignore.
3.2. La aplicación usa una URL relativa, sin clave, en
src/environments/environment.ts y environment.development.ts:
nombreApiUrl: '/api/<nombre>/v1'
El servicio pide ${nombreApiUrl}/... con sus parámetros normales. Como la
URL es relativa, la petición va al servidor que sirvió la página, en local y
en producción.
3.3. Crea proxy.conf.mjs en la carpeta del proyecto. Hay dos variantes
según cómo pida la clave la API. Si la pide en la URL (?api_key=...), como
Giphy:
import { existsSync, readFileSync } from 'node:fs'
import { parseEnv } from 'node:util'
const env = existsSync('.env') ? parseEnv(readFileSync('.env', 'utf8')) : {}
const apiKey = process.env.NOMBRE_API_KEY ?? env.NOMBRE_API_KEY
if (!apiKey) {
console.error('[proxy] NOMBRE_API_KEY is missing. Copy .env.template to .env and fill it in.')
process.exit(1)
}
export default {
'/api/<nombre>': {
target: 'https://api.ejemplo.com',
changeOrigin: true,
// Strips the /api/<nombre> prefix and appends the key as a query parameter.
rewrite: (path) => {
const url = new URL(path.replace(/^\/api\/<nombre>/, ''), 'https://api.ejemplo.com')
url.searchParams.set('api_key', apiKey)
return url.pathname + url.search
},
},
}
Si la pide en un encabezado (Authorization: Bearer ...), como REST
Countries, cambia el bloque exportado por:
export default {
'/api/<nombre>': {
target: 'https://api.ejemplo.com',
changeOrigin: true,
// Replaces the /api/<nombre> prefix with the real path of the API.
rewrite: (path) => path.replace(/^\/api\/<nombre>/, '/v1'),
headers: { Authorization: `Bearer ${apiKey}` },
},
}
Cambia https://api.ejemplo.com, el prefijo /api/<nombre> y la ruta real de
la API por los de tu caso. Los dos archivos reales están en
03-gifs-app/proxy.conf.mjs y 04-country-app/proxy.conf.mjs.
3.4. Regístralo en angular.json, en projects.<nombre>.architect.serve:
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"proxyConfig": "proxy.conf.mjs"
},
...
}
3.5. Prueba con npm start y una petición directa al proxy desde el
navegador o con curl: http://localhost:4200/api/<nombre>/v1/... tiene que
devolver el JSON de la API. Si falta la clave en .env, ng serve se detiene
con el mensaje del proxy.
4. Despliegue: Dockerfile, Nginx y .dockerignore¶
Una aplicación de Angular compilada son archivos estáticos. El Dockerfile
los construye con Node en una etapa y los sirve con Nginx en otra; Nginx
además hace de proxy de la API con la clave que recibe del contenedor. El
porqué está en el Módulo A5 y en
el Módulo P3.
4.1. Crea Dockerfile en la carpeta del proyecto:
# Build: Angular 22 requires Node >= 22.22.3 or >= 24.15.0.
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime: the static files served by Nginx, plus a proxy for the API.
FROM nginx:alpine
# The image renders /etc/nginx/templates/*.template into /etc/nginx/conf.d/ at
# startup, replacing ${NOMBRE_API_KEY} with the container's environment variable.
COPY nginx.conf.template /etc/nginx/templates/default.conf.template
COPY --from=build /app/dist/<nombre>/browser /usr/share/nginx/html
EXPOSE 80
La carpeta de salida lleva el nombre del proyecto de Angular
(dist/<nombre>/browser), no el de la carpeta con número. Si no coincide, el
build se corta en ese COPY. No uses Nixpacks en Coolify: instala un Node más
viejo que el mínimo de Angular 22.
4.2. Crea nginx.conf.template. Es una plantilla porque la imagen de
Nginx reemplaza ${NOMBRE_API_KEY} al arrancar con la variable del
contenedor; sus propias variables ($uri, $args) no se tocan. Pon el
dominio real en server_name:
limit_req_zone $binary_remote_addr zone=api:1m rate=5r/s;
server {
listen 80;
server_name angular-<nombre>.reskyon.com localhost;
root /usr/share/nginx/html;
index index.html;
# Proxy for the API. The browser calls /api/<nombre>/... and Nginx forwards the
# request adding the key, which only exists in this container.
location ^~ /api/<nombre> {
# Only requests that come from our own pages (the browser sets the Referer
# header itself). A script can fake it, which is why the rate limit exists.
valid_referers server_names;
if ($invalid_referer) {
return 403;
}
limit_req zone=api burst=20 nodelay;
# Key in the URL (Giphy): add it to the query string.
set $args $args&api_key=${NOMBRE_API_KEY};
proxy_pass https://api.ejemplo.com/;
proxy_set_header Host api.ejemplo.com;
proxy_ssl_server_name on;
}
# Files with a hash in their name: long-lived cache.
location ~* \.(?:js|css|woff2?|ttf|svg|png|jpg|jpeg|gif|ico|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# SPA: any Angular router path falls back to index.html.
location / {
add_header Cache-Control "no-cache";
try_files $uri $uri/ /index.html;
}
}
Para una clave en encabezado (REST Countries), reemplaza las cuatro líneas
desde set $args por:
rewrite ^/api/<nombre>(.*)$ /v1$1 break;
proxy_pass https://api.ejemplo.com;
proxy_set_header Host api.ejemplo.com;
proxy_set_header Authorization "Bearer ${NOMBRE_API_KEY}";
proxy_ssl_server_name on;
Sin ese bloque location, una petición a /api/<nombre> cae en el fallback
de la SPA y el navegador recibe index.html en vez de JSON. Si el proyecto no
usa ninguna API con clave, quita el bloque del proxy y la línea
limit_req_zone, y copia el archivo como nginx.conf a
/etc/nginx/conf.d/default.conf (así está 02-bases).
4.3. Crea .dockerignore, para que ni los paquetes de tu computadora ni
la clave local entren a la imagen:
node_modules
dist
.angular
.vscode
.claude
.git
.env
4.4. Prueba la imagen en local antes de tocar Coolify. Con Docker Desktop abierto, desde la carpeta del proyecto:
docker build -t angular-<nombre> .
docker run --rm -p 8080:80 -e NOMBRE_API_KEY=<NOMBRE_API_KEY> angular-<nombre>
Tres comprobaciones: http://localhost:8080/ muestra la app; recargar una
ruta interna (/alguna-pagina) no da 404; /api/<nombre>/... responde 403
sin Referer y con JSON si le pasas Referer: http://localhost:8080/.
5. Coolify y el subdominio¶
Con el push a main, Coolify descarga el repositorio, construye la imagen y
la publica en un dominio. Los nombres siguen un patrón: carpeta
<carpeta>/ → proyecto de Coolify angular-<nombre> → dominio
angular-<nombre>.reskyon.com. El camino completo del navegador al contenedor
está en el Módulo P3.
5.1. Crea el recurso en Coolify (New resource → Public/Private repository) con estos valores:
| Campo | Valor |
|---|---|
| Repositorio | Reskyon/angular-ufh |
| Rama | main |
| Build pack | Dockerfile |
| Base directory | /<carpeta> (por ejemplo /05-pipes-app) |
| Dockerfile location | /Dockerfile (relativo a la base directory) |
| Ports exposes | 80 |
| Domain | https://angular-<nombre>.reskyon.com |
| Watch paths | vacío (cualquier push a main redespliega) |
5.2. La variable de la clave, en Environment Variables: crea
NOMBRE_API_KEY con el mismo nombre que en .env y sin marcar
Available at Buildtime. La lee Nginx cuando el contenedor arranca, no el
build. Si el proyecto no tiene clave, no hay nada que crear.
5.3. El subdominio en Cloudflare. En el DNS de reskyon.com, agrega un
registro A con nombre angular-<nombre>, la IP del servidor (la misma que
usan los otros registros angular-*) y el proxy de Cloudflare activado (nube
naranja). El modo SSL de la zona ya está en Full (strict); no hay que
tocarlo. El certificado del servidor lo pide Traefik a Let's Encrypt cuando
el contenedor ya corre.
5.4. Despliega y comprueba. El primer despliegue se lanza a mano desde Coolify (Deploy); los siguientes, con cada push. Cuando esté en verde, abre el dominio. Un error 526 de Cloudflare casi siempre significa que el despliegue falló y no hay contenedor: se mira el log de Coolify, no Cloudflare. Si la app carga pero la API responde 401, falta la variable del paso 5.2.
Ojo con el orden cuando cambies variables más adelante: crear la variable
nueva, hacer push, y recién después borrar la vieja. Cambiar variables en
Coolify dispara un redespliegue por sí solo, con el código que haya en main
en ese momento.
6. Documentar el proyecto¶
6.1. README del proyecto. Sección Deployment con la tabla de Coolify
del paso 5.1, la variable y el comando para probar la imagen en local
(03-gifs-app/README.md sirve de modelo).
6.2. AGENTS.md de la raíz: una fila en la tabla de carpetas y una sección
## <carpeta> con lo que no se deduce del código (nombre del proyecto en
Coolify, dominio, variable, decisiones). Es el único archivo de contexto para
los agentes de IA: Codex lo lee directamente y Claude Code lo lee a través de
CLAUDE.md, que sólo contiene la línea @AGENTS.md (una importación). Por
eso se edita siempre AGENTS.md, nunca CLAUDE.md.
6.3. Los archivos de contexto del proyecto: no se dejan. Si al crear el
proyecto se marcó la opción de IA, ng new escribe tres copias iguales de sus
reglas de buenas prácticas para asistentes dentro de la carpeta: AGENTS.md,
.claude/CLAUDE.md y .github/copilot-instructions.md. Ningún proyecto las
conserva: esas reglas ya están una sola vez en el AGENTS.md de la raíz
(sección «Reglas de Angular»), y Claude y Codex lo leen desde cualquier carpeta
del repositorio. Tener copias en cada proyecto sólo las desactualiza y suma
tamaño, y Codex lee como máximo 32 KiB. Se borran esos tres archivos del
proyecto; los de la raíz del repositorio nunca se tocan.
La skill ng-setup los borra sola, y sólo si todavía tienen el texto que
escribió ng new (si alguno tiene contenido propio, lo avisa y no lo borra). La
skill agent-context (/agent-context, en Claude y en Codex) lo comprueba en
todo el repositorio:
py ~/.codex/skills/agent-context/scripts/check_context.py --fix
6.4. La wiki. Cada proyecto tiene su página con el nombre de la carpeta
(05-pipes-app), que abre con una introducción propia (qué construye, carpeta,
enlace al sitio publicado, lista «Qué vas a aprender» con un enlace por módulo
y enlaces a la parte anterior y siguiente). Además se agrega la parte al
índice (study-guide: tabla de páginas, mapa general y ruta
recomendada) y a la navegación de la portada (Home). La página se
mantiene con /wiki-study-guide-update.
6.5. Publicar la wiki. No hay nada que configurar: la Action
mirror-wiki copia la wiki privada a la pública y hace que el sitio se
reconstruya (se lanza a mano desde la pestaña Actions o sola una vez al
día). Una página con nombre NN-nombre entra al menú del sitio por sí sola;
sólo una página sin número (como esta) hay que declararla en
site/scripts/prepare.py del repositorio github.com/Reskyon/angular-wiki.
Cómo funciona todo eso está en el
Módulo P2.