La mayoría de los cursos de arquitectura Angular enseñan teoría. Este no.
Aquí aprendes a detectar problemas reales, tomar decisiones situadas y construir sistemas que aguantan cuando el equipo crece, el producto cambia y el tiempo pasa.
Veinte módulos. Orientado a práctica. Sin relleno.
ÍNDICE — Arquitectura Angular práctica para vida real
01 — Problemas arquitectónicos base
- Lógica de negocio en componentes
- God services
- Estado duplicado o disperso
- Acoplamiento entre features
- Responsabilidades mezcladas
- Carpetas que parecen ordenadas pero no escalan
- Abstracciones prematuras
- Sobreingeniería innecesaria
- Arquitectura pensada para hoy pero no para crecer
- Código difícil de borrar, mover o refactorizar
02 — Estructura real de proyecto
- Feature-based vs layer-based
- Cuándo usar cada enfoque
- Cómo detectar una estructura que no escala
- Cómo dividir por dominios o bounded contexts
- Cómo organizar carpetas para equipos reales
- Naming conventions útiles
- Qué meter y qué NO meter en
shared - Cómo revisar si tu estructura actual aguanta ×10
03 — Arquitectura de componentes
- Componentes demasiado grandes
- Componentes con demasiadas responsabilidades
- Smart vs dumb components
- Container/presentational en Angular moderno
- Composición de componentes
- Cuándo reutilizar y cuándo NO
- Cómo diseñar APIs limpias de componentes
- Inputs/Outputs vs signals vs services
- Problemas típicos en templates
- Cómo detectar componentes difíciles de mantener
04 — Estado real en Angular
- Qué tipos de estado existen
- Cómo detectar mal uso del estado
- Estado local vs compartido vs global vs server state
- Signals vs RxJS
- State colocation
- Cuándo usar service state
- Cuándo usar signal stores
- Cuándo usar NgRx
- Cómo detectar sobrecentralización
- Cómo detectar caos distribuido
- Errores típicos en side effects
- Normalización de estado
- Checklist de revisión de estado
05 — Comunicación y data flow
- Data down / events up
- Comunicación correcta entre componentes
- Comunicación incorrecta entre componentes
- Comunicación entre features
- Señales de acoplamiento oculto
- Uso de services para comunicar
- Uso de signals para comunicar
- Cuándo un event bus es mala idea
- Cómo revisar dirección de dependencias
- Cómo detectar flujo de datos difícil de seguir
06 — Capa de datos y acceso a APIs
- Services vs repositories
- DTOs vs models
- Transformaciones y mapping
- Dónde poner adaptadores
- Errores típicos al consumir APIs
- Cómo desacoplar frontend del backend
- Retry, caching, paginación
- Infinite scroll
- REST vs GraphQL desde arquitectura
- Cómo revisar si tu data layer está limpia o mezclada
07 — Routing architecture
- Diseño de rutas con intención
- UX + SEO en rutas
- Lazy routes
- Guards
- Resolvers
- URL como fuente de estado
- Deep linking
- Rutas acopladas
- Rutas mal pensadas
- Checklist para revisar navegación y escalabilidad
08 — Rendering y estrategia global
- SPA, SSR, SSG, ISR
- Cómo elegir según contexto
- SEO vs complejidad
- Performance vs coste
- Angular SSR architecture
- Hydration e impacto arquitectónico
- Cuándo NO usar SSR
- Cómo revisar si una app necesita una rendering strategy distinta
09 — Performance architecture
- Change detection, OnPush, signals y rendimiento
- Lazy loading real
- Code splitting y bundle strategy
- Caching
- Virtual scrolling y memoization
- Performance budgets
- Cómo detectar cuellos de botella arquitectónicos
- Qué revisar antes de "optimizar"
- Cómo distinguir problema de código vs problema de arquitectura
10 — Testing architecture
- Qué testear y qué no
- Unit vs integration vs e2e
- Testability desde diseño
- Cómo detectar código difícil de testear
- Mocking con sentido
- Fragilidad en tests y overtesting
- Contract testing frontend-backend
- Checklist para revisar si una arquitectura favorece o rompe testing
11 — Nx y monorepo real
- Cuándo merece la pena y cuándo no
- Apps vs libs, boundaries, dependency graph
- Shared libraries bien y mal hechas
- Affected, caching, code ownership
- Cómo escalar a varios equipos
- Anti-patterns de monorepo
- Checklist de revisión
12 — Microfrontends y module federation
- Cuándo sí y cuándo no
- Qué problema real resuelven
- Costes ocultos
- Host vs remotes, versionado, comunicación entre MFEs
- Deployment independiente y complejidad organizativa
- Checklist para decidir si compensa
13 — Design systems útiles
- Qué problema resuelven y cuándo no hace falta uno serio
- Component libraries, tokens, theming, variants
- Consistencia vs flexibilidad
- Storybook, sync diseño-desarrollo
- Errores típicos
- Cómo revisar si tu UI system escala o bloquea
14 — Seguridad frontend
- XSS, CSRF, sanitización
- Arquitectura de auth: JWT, refresh tokens
- Guards y role-based access
- Problemas de seguridad en SSR
- Checklist práctico de revisión
15 — Observabilidad y mantenimiento
- Logging, error tracking, Sentry, metrics
- Tracing conceptual, feature flags, A/B testing
- Cómo detectar puntos ciegos
- Cómo revisar si una app es operable en producción
16 — DevEx y platform thinking
- Developer Experience y tooling
- CI/CD, generators, schematics, automatización
- Cómo detectar fricción innecesaria
- Cómo diseñar arquitectura para equipos y no solo para código
17 — Tradeoffs y toma de decisiones
- Cómo justificar decisiones
- Coste vs beneficio, complejidad vs escalabilidad, velocidad vs mantenibilidad
- Build vs buy
- Cómo escribir ADRs
- Cómo defender una decisión en entrevista o trabajo real
18 — Anti-patterns de arquitecto Angular
- Overengineering, capas inútiles, abstracciones prematuras
sharedmal diseñado, estado global innecesario- Dependencias cruzadas, acoplamiento silencioso
- Mala modularización, ignorar métricas reales
- Checklist de red flags
19 — Auditoría práctica de apps Angular
- Cómo revisar una app existente
- Qué mirar primero
- Qué señales indican deuda seria
- Cómo priorizar problemas
- Qué arreglar antes y qué no tocar aún
- Cómo hacer una revisión arquitectónica útil
20 — Casos reales y entrenamiento
- Auditoría de ecommerce
- Auditoría de dashboard SaaS
- Auditoría de backoffice empresarial
- Auditoría de app pública con SEO
- Diseño de app desde cero
- Rediseño de app caótica
- Preguntas de entrevista
- Ejercicios de detección, decisión y mejora
Módulo 0 — Base del sistema
El punto 0 no es un módulo de contenido. Es el modo de ver que usarás durante todo el roadmap.
Antes de hablar de problemas concretos en Angular, necesitas responder una pregunta:
¿Cómo miras una app que no conoces y decides si está bien o mal hecha?
Hay cuatro habilidades base para eso.
1. Analizar una app sin tocar código
La primera lectura de una app no es en el editor. Es en la estructura. Antes de abrir un solo archivo, ya puedes extraer información:
- ¿Cómo se llaman las carpetas? ¿Los nombres dicen lo que hacen?
- ¿Hay una carpeta
sharedque pesa más que todo lo demás? - ¿Cuántos niveles de anidación tiene la estructura?
- ¿Los módulos o features tienen nombres de dominio o nombres técnicos?
- ¿Dónde están los servicios? ¿Sueltos o dentro de features?
Esto ya te dice mucho sobre si quien hizo la app pensó en términos de negocio o en términos de capas técnicas.
2. Revisar arquitectura de forma práctica
Revisar no es leer código de arriba a abajo. Es hacer preguntas con criterio:
- ¿Dónde vive la lógica de negocio?
- ¿Quién sabe qué en cada capa?
- ¿Cuántos sitios hay que cambiar para añadir una feature nueva?
- ¿Hay dependencias que van en la dirección incorrecta?
Un arquitecto senior no empieza por el componente más complejo. Empieza por los puntos de mayor riesgo: servicios compartidos, estado global y la capa de acceso a datos.
3. Detectar problemas antes de programar
La mayoría del daño arquitectónico ocurre en decisiones tempranas que parecen irrelevantes:
- "Pongo esto en un servicio compartido por ahora"
- "El componente padre maneja este estado, luego lo movemos"
- "Usamos NgRx para todo, así es consistente"
Estas decisiones tienen consecuencia real meses después. El criterio arquitectónico es saber qué está comprando cada decisión y qué deuda está contrayendo.
4. Convertir teoría en checklist real
Cada concepto que veremos en este roadmap — smart/dumb components, state colocation, god services, etc. — tiene que terminar en una pregunta que puedas hacerte mientras revisas código real:
- ¿Este componente sabe de dónde vienen los datos?
- ¿Este servicio tiene más de una razón para cambiar?
- ¿Este estado existe en dos sitios distintos?
Si no puedes convertir un concepto en una pregunta de revisión, no lo has interiorizado todavía.
Módulo 1 — Cómo analizar una app Angular sin tocar código
La primera revisión arquitectónica ocurre antes de abrir el editor. Solo con la estructura de carpetas y los nombres de archivos ya puedes extraer señales claras de calidad. Esto es lo que haría un arquitecto senior los primeros 10 minutos con una app desconocida.
¿Por qué importa?
- Si tardas en detectar problemas arquitectónicos, el coste de corregirlos crece exponencialmente.
- Una estructura mal diseñada desde el día 1 se convierte en deuda técnica que bloquea al equipo meses después.
- Desarrollar criterio visual rápido te permite tomar mejores decisiones antes de escribir una sola línea de código.
Paso 1 — Leer la estructura de carpetas como un mapa
La estructura de carpetas es la primera decisión arquitectónica visible. Te dice cómo el equipo organizó su mundo mental: ¿piensan en términos de tecnología o en términos de negocio?
¿Qué es una feature?
Una feature es una funcionalidad completa de negocio. No un tipo de archivo. No una capa técnica.
Piensa en una app de ecommerce. Las features son:
- El catálogo de productos
- El carrito de compra
- El proceso de checkout
- El perfil de usuario
- La gestión de pedidos
Cada una de esas es un trozo de negocio que tiene sentido por sí solo. Un usuario entra, navega el catálogo, añade al carrito, hace checkout. Eso son features.
Modelo 1 — Organización por capas técnicas (problemático a escala)
Es lo que hace casi todo el mundo al empezar porque parece ordenado:
src/app/
components/
product-card.component.ts
product-list.component.ts
cart-item.component.ts
checkout-form.component.ts
user-profile.component.ts
services/
product.service.ts
cart.service.ts
checkout.service.ts
user.service.ts
models/
product.model.ts
cart.model.ts
user.model.ts
El problema real: tienes que modificar el flujo de checkout. Para entender qué toca, navegas entre tres carpetas distintas — buscas el componente en components/, el servicio en services/, el modelo en models/. Si hay un pipe específico del checkout, está en pipes/ mezclado con pipes de otras features.
Para cambiar una sola feature, navegas por toda la app. Eso es acoplamiento por estructura. Cuando el equipo crece, dos personas trabajando en features distintas tocan las mismas carpetas constantemente → conflictos en git, dificultad para saber quién es dueño de qué.
Modelo 2 — Organización por features (lo que escala)
src/app/
features/
catalog/
components/
product-card.component.ts
product-list.component.ts
services/
product.service.ts
models/
product.model.ts
catalog.routes.ts
cart/
components/
cart-item.component.ts
cart-summary.component.ts
services/
cart.service.ts
cart.routes.ts
checkout/
components/
checkout-form.component.ts
order-confirmation.component.ts
services/
checkout.service.ts
checkout.routes.ts
shared/
core/
Por qué esto funciona: cuando tocas checkout, todo lo de checkout está junto. Un desarrollador nuevo sabe exactamente dónde mirar. Una persona puede ser dueña de una feature completa. Puedes borrar una feature entera borrando solo su carpeta. Los conflictos de git entre features distintas desaparecen casi por completo.
La regla mental clave: si borras una feature, ¿puedes borrar su carpeta entera sin tocar nada más? Si la respuesta es sí, la feature está bien aislada. Si tienes que ir a buscar trozos por toda la app, no lo está.
Paso 2 — Leer los nombres
Los nombres son documentación. Un nombre malo esconde intención y añade carga cognitiva a cada persona que lee el código.
shared/ — qué debe contener y qué no
shared/ es para cosas que múltiples features usan y que NO tienen lógica de negocio propia.
Correcto en shared/:
shared/
components/
button/
modal/
spinner/
avatar/
pipes/
date-format.pipe.ts
truncate.pipe.ts
directives/
click-outside.directive.ts
validators/
email-validator.ts
Son piezas de UI reutilizables o utilidades puras. No saben nada del negocio. Un ButtonComponent no sabe si es un botón de checkout o de perfil de usuario. Solo sabe ser un botón.
Incorrecto en shared/:
shared/
services/
cart.service.ts ← lógica de negocio aquí
user-auth.service.ts ← pertenece a auth/core
product-filter.service.ts ← pertenece a catalog
report-generator.service.ts ← pertenece a reporting
Cuando ves servicios con lógica de negocio dentro de shared/, significa que alguien no supo dónde ponerlos y los metió ahí. Con el tiempo shared/ se convierte en el cajón de sastre de la app.
core/ — qué es y qué no es
core/ es para infraestructura singleton que necesita toda la app, una sola vez.
Correcto en core/:
core/
services/
auth.service.ts
logger.service.ts
error-handler.service.ts
interceptors/
auth.interceptor.ts
error.interceptor.ts
guards/
auth.guard.ts
models/
user-session.model.ts
Incorrecto en core/:
core/
components/
dashboard.component.ts ← eso es una feature
home.component.ts ← igual
services/
product.service.ts ← pertenece a catalog
report-generator.service.ts ← pertenece a reporting
Son cosas que se instancian una vez y afectan a toda la app. El interceptor de autenticación intercepta todas las peticiones HTTP de la app entera, por eso vive en core/.
Nombres vagos — señales de alerta
Un nombre vago es una decisión postergada. Cuando alguien crea data.service.ts, no decidió de qué datos habla ese servicio.
| Lo que ves | Lo que puede significar |
|---|---|
shared/ muy grande |
Todo lo que no encajó en otro sitio. Se convierte en cajón de sastre. |
common/ |
Lo mismo que shared/, pero peor nombrado. Sin criterio de qué entra. |
utils/ con servicios |
Lógica de negocio disfrazada de utilidad. Red flag seria. |
helpers/ |
Igual que utils/. El nombre no dice nada sobre la responsabilidad. |
components/ en raíz |
Sin organización por dominio. Todos los componentes juntos. |
core/ con 40 archivos |
Core mal definido. Se usó como segundo shared/. |
app.service.ts |
Un god service esperando crecer. Señal de alarma máxima. |
data.service.ts |
¿Datos de qué? Responsabilidad indefinida desde el nombre. |
helper.service.ts |
Acaba siendo un service con métodos sin relación entre sí. |
main.component.ts |
¿Qué es "main"? Nombre genérico que oculta responsabilidad real. |
Regla de los nombres: si el nombre del archivo no te dice exactamente qué hace, el archivo probablemente hace demasiado o está mal ubicado.
Ejemplos concretos de nombres vagos y lo que esconden:
utils/format.tscon 800 líneas: mezcla de formateo de fechas + validación de forms + transformación de API.helper.service.ts: acaba siendo un god service con métodos sin relación entre sí.app.service.ts: un servicio llamado como la app entera que tiene responsabilidades de toda la app.
Paso 3 — Contar y medir sin abrir archivos
Antes de leer código, puedes hacer estas observaciones directamente desde la estructura:
Tamaño de componentes como señal
| Señal | Qué indica |
|---|---|
| +300-400 líneas en un componente | Casi siempre está haciendo demasiado. No es regla absoluta pero vale investigar. |
| +10 inputs y outputs | API demasiado compleja. Candidato a ser partido en varios componentes. |
| Llamadas HTTP directas en componente | Mezcla de presentación con acceso a datos. Falta capa de servicio. |
| 1-2 componentes por feature | Probablemente están haciendo demasiado. Falta división interna. |
| +20 providers globales | Demasiado estado y lógica centralizada. No todo necesita ser global. |
Servicios globales — el peligro del providedIn: 'root'
Un servicio global significa que cualquier componente de cualquier feature puede inyectarlo y usarlo. Eso crea dependencias invisibles entre features que deberían ser independientes.
// Servicio que DEBERÍA ser local a la feature de catalog:
@Injectable({ providedIn: 'root' }) // ← señal de problema
export class ProductFilterService { ... }
// Ahora cualquier componente de cualquier feature puede usarlo.
// Si catalog cambia su lógica de filtrado, puede romper
// algo en una feature aparentemente no relacionada.
Paso 4 — app.module.ts o app.config.ts: qué buscar
En apps con módulos (Angular < 17 o legacy)
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
RouterModule,
AuthModule, // ← bien, infraestructura singleton
ProductModule, // ← señal de problema
CartModule, // ← señal de problema
CheckoutModule, // ← señal de problema
UserModule, // ← señal de problema
DashboardModule, // ← señal de problema
// Si hay 15+ módulos de features aquí,
// el lazy loading no está funcionando.
],
providers: [
ProductService, // ← si hay 20+ providers aquí,
CartService, // hay demasiada centralización
UserService,
AuthService,
]
})
Los módulos de features deberían cargarse lazy (bajo demanda), no importarse directamente en el módulo raíz. Si están todos aquí, se cargan todos al inicio aunque el usuario no los necesite nunca.
En apps standalone (Angular 17+)
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withLazyLoading()), // ← correcto
provideHttpClient(withInterceptors([authInterceptor])),
provideAnimations(),
ProductService, // ← esto NO debería estar aquí
CartService, // ← pertenece a la feature de cart
]
}
Regla del config global: el config global solo debe tener infraestructura que afecte a toda la app: router con lazy loading,
HttpClientcon interceptors globales, animaciones, servicios singleton de infraestructura (auth, logger, error handler). Si ves servicios de features en el config global, alguien está registrando cosas globalmente por comodidad, no por necesidad.
Checklist completa — Primera lectura de una app Angular
Estructura
- ¿La estructura está organizada por features (dominio de negocio) o por capas técnicas?
- ¿Puedo borrar una feature entera borrando solo su carpeta sin tocar nada más?
- ¿Las features tienen nombres de dominio de negocio (
checkout,catalog) o nombres técnicos (list,form,detail)? - ¿Existen carpetas con nombres vagos:
utils,helpers,common,misc,data?
shared/ y core/
- ¿
shared/contiene solo componentes UI y utilidades sin lógica de negocio? - ¿
core/contiene solo infraestructura singleton (interceptors, guards, auth service)? - ¿Hay servicios con lógica de negocio dentro de
shared/? - ¿
core/tiene componentes de features o servicios de dominio?
Servicios y providers
- ¿Los servicios de negocio están dentro de sus features o sueltos en global?
- ¿Hay más de 20 providers globales?
- ¿El módulo raíz o
app.config.tsimporta módulos de features directamente (sin lazy)? - ¿Hay servicios con
providedIn: 'root'que deberían ser locales a una feature?
Nombres
- ¿Los nombres de archivos dicen exactamente qué hacen?
- ¿Hay archivos cuyo nombre podría aplicarse a cualquier parte de la app?
- ¿Hay algún archivo llamado
app.service.ts,data.service.tsohelper.service.ts? - ¿
shared/pesa más que cualquier feature individual?
Ejercicio
Busca cualquier proyecto Angular público en GitHub — puede ser uno tuyo o uno open source. Sin abrir ningún archivo, solo mirando la estructura de carpetas y los nombres:
- ¿Qué modelo de organización usa: features o capas técnicas?
- ¿Qué nombres te generan dudas o sospechas?
- ¿Dónde sospechas que está la lógica de negocio?
- ¿Qué carpeta crees que tiene más mezcla de responsabilidades?
- ¿Podrías borrar una feature sin tocar nada más?
Comparte lo que observas en la sesión y lo revisamos juntos como haría un arquitecto senior.
Prompt: analiza tu proyecto con los 4 puntos
Copia este prompt y úsalo con cualquier IA (ChatGPT, Claude, Copilot) pasándole el árbol de carpetas de tu proyecto:
Analiza la arquitectura del proyecto aplicando estos 4 puntos del Pilar 1 y genera un informe.
PUNTO 1 — Estructura de carpetas
Analiza apps/ y libs/ y responde:
- ¿La organización es por features (dominio de negocio) o por capas técnicas?
- ¿Puedes borrar una feature entera borrando solo su carpeta?
- ¿Las features tienen nombres de dominio (
checkout,catalog) o técnicos (list,form,detail)? - ¿Hay carpetas con nombres vagos:
utils,helpers,common,misc,data? - Muestra el árbol real de carpetas que has encontrado.
PUNTO 2 — Nombres de archivos y carpetas
Busca en todo el proyecto y lista:
- Archivos con nombres vagos:
data.service.ts,helper.service.ts,app.service.ts,main.component.ts,utilscon servicios dentro. - Servicios con lógica de negocio dentro de
shared/. - Componentes o servicios de dominio dentro de
core/. - ¿
shared/pesa más que cualquier feature individual? Cuenta archivos.
PUNTO 3 — Servicios globales
Busca en todo el proyecto:
- Todos los servicios con
providedIn: 'root'— lista cuáles son y si deberían ser locales a una feature. - Cuenta cuántos providers globales hay en total.
- Identifica servicios de negocio registrados globalmente cuando deberían estar dentro de su feature.
- Busca servicios en
libs/services/que pertenezcan claramente a un dominio concreto.
PUNTO 4 — Configuración global (app.config.ts o app.module.ts)
Busca el archivo de configuración raíz de cada app y analiza:
- ¿Qué está registrado globalmente que no debería estarlo?
- ¿Los módulos de features se cargan con lazy loading o están importados directamente?
- ¿Hay servicios de negocio en el config global?
- ¿Cuántos providers hay registrados globalmente en total?
FORMATO DEL INFORME
Para cada punto usa esta estructura exacta:
✅ Lo que está bien
(lista concreta con ejemplos reales del código)
⚠️ Señales a revisar
(lista concreta con ruta del archivo y por qué es una señal)
❌ Problemas claros
(lista concreta con ruta del archivo, el problema y el impacto real)
Al final incluye:
Resumen ejecutivo
Una tabla con los 4 puntos, un emoji de estado (✅ ⚠️ ❌) y una línea de conclusión por punto.
Próximos pasos priorizados
Lista de máximo 5 acciones concretas ordenadas por impacto, con la ruta exacta del archivo o carpeta a tocar.
Sé directo. No expliques qué es una feature ni teoría. Solo analiza este proyecto concreto y da hallazgos reales.
