Category: Angular

  • Guía para la Puesta en Producción de Aplicaciones Angular 2026

    Guía para la Puesta en Producción de Aplicaciones Angular 2026

    Arquitectura para la Puesta en Producción de Aplicaciones Angular: Estándares 2026

    Tiempo estimado de lectura: 4 min

    • Enfoque: SemVer automatizado, builds reproducibles y despliegues inmutables.
    • Rendimiento: minimizar bundle inicial (budgets, lazy loading, zoneless) y optimizar assets (Brotli/AVIF).
    • Infraestructura y despliegue: Docker multi-stage, non-root, Blue/Green o Canary, observabilidad completa.
    • Calidad: pipeline con lint→tests→build→e2e→deploy y gating para visual, performance y accesibilidad.

    Introducción

    La Arquitectura para la Puesta en Producción de Aplicaciones Angular: Estándares 2026 define cómo pasar de “funciona en mi máquina” a un sistema observable, seguro y repetible. Esta guía asume Angular 19+ (Zoneless, Signals) y está orientada a equipos que necesitan SLAs reales, trazabilidad de releases y despliegues reproducibles.

    Resumen rápido (lectores con prisa)

    SemVer automatizado + Conventional Commits para releases fiables. Builds rápidos con Esbuild/Vite y Angular CLI en producción; enforce budgets. Contenedores multi-stage, runtime non-root. CI/CD con jobs que actúan como contrato de calidad y gates de performance/visual/accessibility.

    Estrategias de Versionado y Gestión del Cambio

    Mensajes estandarizados

    Adopta SemVer automatizado y Conventional Commits para producir releases confiables. Mensajes estandarizados: feat:, fix:, perf:. Herramienta recomendada: semantic-release.

    Flujo

    Flujo: Trunk-Based Development. Integraciones frecuentes a main. Pull Requests pequeños y verificados.

    Artefactos inmutables

    Cada despliegue corresponde a un Git tag firmado (vX.Y.Z). Rollbacks = redeploy del tag anterior.

    Generación de CHANGELOG

    Generación automática de CHANGELOG desde commits para auditoría y compliance.

    Referencias: Conventional Commits

    Arquitectura de Construcción y Optimización del Bundle

    Build toolchain

    Objetivo: FCP / LCP bajos, bundle inicial pequeño, tiempo de build corto. Build toolchain: Esbuild/Vite (velocidad) y Angular CLI en modo production. Usa ng build --configuration production.

    Zoneless

    Zoneless: retirar zone.js cuando sea posible (mejor rendimiento y menos re-render).

    Lazy & defer

    Usar rutas lazy y @defer/suspense para componentes pesados.

    Budgets

    Configura budgets en angular.json para hacer fallar el build si el bundle es demasiado grande:

    "budgets": [{
      "type": "initial",
      "maximumWarning": "150kb",
      "maximumError": "200kb"
    }]

    Assets

    Generar Brotli y Gzip durante el build; optimizar imágenes a AVIF/WebP; subset fonts.

    Analítica de bundle

    Analítica de bundle: source-map-explorer o webpack-bundle-analyzer para identificar dependencias pesadas.

    Docs Angular build: angular.io/guide/build

    Estrategia de Contenedorización con Docker

    CSR (static)

    Usa multi-stage builds y ejecuta procesos como non-root. Para CSR: build → sirve con Nginx; imagen final basada en nginx:alpine.

    SSR

    SSR: builder → runtime con node:alpine ejecutando el server bundle.

    Ejemplo multi-stage para SSR

    FROM node:22-alpine AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    COPY . .
    RUN npm run build:ssr
    
    FROM node:22-alpine AS runner
    WORKDIR /app
    COPY --from=builder /app/dist /app/dist
    USER node
    CMD ["node", "dist/server/main.js"]

    Seguridad

    No correr como root; minimizar capas; scan de imágenes (Trivy).

    Docker best practices: docs.docker.com

    Orquestación de CI/CD con GitHub Actions

    Pipeline clave

    Pipeline como contrato de calidad. Ejemplo de jobs clave:

    • Lint & format
    • Unit tests (Vitest/Jest)
    • Build + budgets
    • E2E en entorno efímero (Playwright)
    • Build image & push (GHCR/ECR)
    • Deploy (Terraform/Helm)

    Snippets y prácticas

    Cache de node_modules y de outputs de Esbuild. Concurrency groups para evitar deploys solapados.

    Docs GitHub Actions: docs.github.com/actions

    Estrategias de Despliegue e Infraestructura

    Decisión central: CSR (Edge) vs SSR (Container)

    CSR: S3 + CloudFront, Vercel o Cloudflare Pages. Ventaja: latencia global, coste bajo.

    SSR: Kubernetes / Cloud Run. Usa Horizontal Pod Autoscaler y readiness/liveness checks.

    Blue/Green o Canary

    Blue/Green o Canary deployments obligatorios para producción. Controla tráfico con ingress (Traefik/NGINX) o service mesh.

    Observabilidad

    OpenTelemetry → Prometheus (metrics) + Grafana; Sentry para errores.

    Estrategia de Pruebas y Calidad (QA)

    Unit tests

    Unit tests: cubrir lógica de servicios y signals (Vitest/Jest).

    E2E

    E2E: Playwright ejecutado contra entorno ephemeral por PR; flujos críticos: login, checkout, forms.

    Playwright: playwright.dev

    Visual regression & Performance

    Visual regression: Percy/Chromatic en cada PR. Performance gates: Lighthouse CI en cada PR; falla si Performance < 90. Accessibility: automatizar axe-core en CI.

    Checklist mínimo previo a producción

    • SemVer + semantic-release configurado.
    • Budgets en angular.json que fallan build.
    • Docker multi-stage y non-root runtime.
    • Pipeline GitHub Actions con lint→test→build→e2e→deploy.
    • Blue/Green o Canary configurado en infra.
    • Monitoreo (OpenTelemetry + Sentry) y alertas para p95 latency y error budget.
    • Visual & performance gating en PRs.

    Conclusión

    Poner Angular en producción en 2026 exige disciplina más que herramientas. Implementa SemVer, enforcea budgets, automatiza QA y despliega inmutabilidad. Si tu pipeline falla rápido y tus despliegues son revertibles, has ganado más resiliencia que con cualquier micro-optimización puntual.

    Para equipos interesados en automatización y workflows relacionados con despliegues y pipelines, una continuación lógica es explorar recursos y experimentos en Dominicode Labs.

    FAQ

    Respuesta:

    Usa Conventional Commits para estandarizar mensajes y una herramienta como semantic-release para generar versiones SemVer automáticamente a partir de los commits y publicar tags firmados.

    Respuesta:

    Zoneless implica eliminar zone.js para reducir re-renders y mejorar rendimiento. Quitar zone.js tiene sentido si tu app y librerías son compatibles con el modelo de change detection alternativo (por ejemplo Signals) introducido en Angular 19+.

    Respuesta:

    Define budgets en angular.json con maximumError para que el comando de build falle si el bundle excede el tamaño especificado (ejemplo incluido en la guía).

    Respuesta:

    CSR sirve contenido estático desde CDN/edge (S3 + CloudFront, Vercel, Cloudflare Pages) y es más barato y rápido geográficamente. SSR requiere contenedores/servicios (Kubernetes/Cloud Run) y permite renderizar en servidor para SEO y tiempo a primer render en casos complejos.

    Respuesta:

    Jobs mínimos: lint & format, unit tests, build con budgets, E2E en entorno efímero, build image & push, deploy (Terraform/Helm). Estos jobs actúan como contrato de calidad antes de cualquier despliegue.

    Respuesta:

    Ejecuta E2E en entornos efímeros por PR y ejecuta un subconjunto crítico de pruebas para feedback rápido; pruebas completas pueden correr en pipelines separados o en merge to main según políticas de SLA.

  • Implementación de Polling en Angular con Signals y rxResource

    Implementación de Polling en Angular con Signals y rxResource

    Como hacer polling en Angular con Signals y rxResource. Buenas practicas

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • rxResource convierte Observables en señales gestionadas con limpieza automática.
    • Prefiere polling reactivo dentro del loader de rxResource (timer + switchMap).
    • Implementa retry/backoff, pausa con Page Visibility API y evita polling agresivo en móviles.
    • Usa switchMap para cancelar peticiones lentas y shareReplay para cache compartida.

    Como hacer polling en Angular con Signals y rxResource. Si esa es la pregunta que te trajo hasta aquí, perfecto: vamos directo al punto. Polling no es magia; es un patrón con trampas si no lo diseñas pensando en resiliencia, eficiencia y vida útil de la UI. Aquí tienes cómo hacerlo bien, con ejemplos prácticos y criterios que realmente importan.

    Polling no es solo hacer peticiones cada X segundos: es hacerlo con criterio.

    Resumen rápido (lectores con prisa)

    Qué es: rxResource convierte Observables en señales gestionadas (valor, carga, error, reload).

    Cuándo usar: dashboards y estados donde 2–30s de retraso son aceptables; no para latencia < 1s.

    Por qué importa: limpieza automática, cancelación de peticiones y patrón más seguro que intervalos manuales.

    Cómo: preferible encapsular polling dentro del loader con timer + switchMap, añadir retry/backoff y pausar con Page Visibility API.

    ¿Qué es rxResource y por qué importa para el polling?

    rxResource (paquete: @angular/core/rxjs-interop) convierte un Observable en una señal gestionada: expone .value(), .isLoading(), .error() y .reload(). Maneja la limpieza de suscripciones automáticamente cuando el componente se destruye, lo que reduce significativamente el boilerplate y las fugas de memoria. Documentación.

    Eso cambia las reglas del juego: el loader de un resource puede devolver un Observable que emita múltiples valores (ideal para polling). En lugar de gestionar intervalos manuales y ngOnDestroy, encapsulas la lógica reactiva en el resource.

    Estrategias válidas: cuándo usar cada una

    Hay dos patrones útiles: el imperativo (reload) y el declarativo (loader periódico). Usa el primero cuando el usuario debe controlar el ciclo (pausar/reanudar). Prefiere el segundo como opción por defecto: es más limpia y segura.

    1) Polling imperativo con reload()

    Útil si la UI ofrece control explícito (botón pausa).

    // patrón simplificado
    const dataResource = rxResource({ loader: () => http.get('/api/status') });
    
    const id = window.setInterval(() => dataResource.reload(), 5000);
    // clearInterval(id) al destruir/comportamiento de pausa
    

    Ventaja: control directo. Desventaja: gestión manual del ciclo de vida y riesgo de olvidarte de limpiar el intervalo.

    2) Polling reactivo dentro del loader (recomendado)

    Encapsula todo en el loader. El resource escucha un Observable que emite periódicamente.

    import { timer, EMPTY, merge, of, fromEvent } from 'rxjs';
    import { switchMap, map, retry, catchError } from 'rxjs/operators';
    
    dataResource = rxResource({
      request: () => ({ interval: pollIntervalSignal() }),
      loader: ({ request }) => {
        const visibility$ = merge(
          of(document.visibilityState === 'visible'),
          fromEvent(document, 'visibilitychange').pipe(map(() => document.visibilityState === 'visible'))
        );
    
        return visibility$.pipe(
          switchMap(visible =>
            visible
              ? timer(0, request.interval).pipe(
                  switchMap(() => http.get('/api/metrics')),
                  retry({ count: 3, delay: 1000 }),
                  catchError(() => EMPTY) // mantiene vivo el stream ante errores transitorios
                )
              : EMPTY
          )
        );
      }
    });
    

    Beneficios prácticos: limpieza automática, cancelación de peticiones lentas gracias a switchMap, y pause automático con Page Visibility API.

    Referencia útil sobre retry. Página sobre Page Visibility API (MDN).

    Buenas prácticas obligatorias (no opcionales)

    • Usa switchMap para evitar race conditions. Si una petición tarda más que el intervalo, cancelas la anterior.
    • Implementa backoff para errores persistentes. No bombardees un servidor con 500s: retryWhen + delay exponencial.
    • Pausa el polling cuando la pestaña está oculta (Page Visibility API) o cuando el dispositivo entra en modo bajo consumo.
    • Evita polling agresivo en dispositivos móviles. Considera un throttle dinámico o reducir frecuencia en redes móviles.
    • Agrupa requests cuando sea posible (batching/forkJoin) para reducir overhead.
    • Usa cache + TTL (shareReplay(1) con invalidación) si la misma petición la hacen múltiples componentes.
    • Supervisión: expón métricas de errores/latencia en logs para detectar piezas rotas del polling.

    Testing y observabilidad

    • Testea con fakeAsync y tick() para simular intervalos en unit tests.
    • Mockea rxResource utilizando proveedores en TestBed para controlar emisiones.
    • Registra fallos y frecuencia de reintentos; si ves un patrón de reintentos masivos, cambia a backoff más agresivo o al modo “degraded”.

    Cuándo no usar polling

    No uses polling si necesitas latencia < 1s o alta frecuencia. Para actualizaciones en tiempo real, usa WebSockets o SSE. Polling sigue siendo válido para dashboards, contadores y estados donde 2–30s de retraso son aceptables.

    Guías de alternativas: WebSockets / SSE.

    Resumen práctico (lista rápida)

    • Preferible: implementar polling dentro del loader de rxResource con timer + switchMap.
    • Añadir: retry/backoff, Page Visibility API para pausar, shareReplay si es necesario.
    • Imperativo (reload) sólo si la UX necesita control manual.
    • Evitar: polling sin control de errores o sin pausa para pestañas ocultas.

    Fuentes

    FAQ

    ¿Qué es exactamente rxResource?

    Respuesta: rxResource convierte un Observable en una señal gestionada que expone métodos como .value(), .isLoading(), .error() y .reload(), y gestiona la limpieza de suscripciones al destruirse el componente.

    ¿Por qué preferir el polling dentro del loader?

    Respuesta: Porque encapsula la lógica reactiva, permite limpieza automática, usa operadores como switchMap para cancelar peticiones y facilita pausar con APIs como Page Visibility.

    ¿Cuándo usar reload() en lugar de polling declarativo?

    Respuesta: Usa reload() cuando la UX requiere control manual del ciclo (por ejemplo, un botón de pausa/reanudar). Para la mayoría de casos, el loader periódico es más seguro.

    ¿Cómo manejo errores transitorios sin romper el stream?

    Respuesta: Usa operadores como retry o retryWhen con backoff y catchError retornando EMPTY para mantener vivo el stream ante errores transitorios.

    ¿Cómo pauso el polling cuando la pestaña está oculta?

    Respuesta: Observa document.visibilityState o escucha el evento visibilitychange y condiciona el stream para emitir solo cuando la pestaña esté visible.

    ¿Es el polling adecuado en móviles?

    Respuesta: Con precaución: evita frecuencias agresivas, reduce la tasa en redes móviles y considera estrategias de throttling dinámico o degradado.

    ¿Qué alternativas usar si necesito latencia menor a 1s?

    Respuesta: Emplea WebSockets o Server-Sent Events para actualizaciones en tiempo real; el polling no es adecuado para exigencias de latencia muy baja.

  • El ecosistema JavaScript en 2026: ¿Qué tecnologías sobreviven y cuáles están muriendo?

    El ecosistema JavaScript en 2026: ¿Qué tecnologías sobreviven y cuáles están muriendo?

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • React permanece como la opción empresarial por su ecosistema y disponibilidad de talento.
    • Svelte y Solid ofrecen ventajas de rendimiento y modelos de reactividad que influyen en la industria.
    • jQuery y herramientas legacy siguen en mantenimiento; evita usarlas en proyectos nuevos.
    • Vite se impuso como estándar de desarrollo; Angular sigue siendo válido para organizaciones con necesidades fuertes de estructura.

    Introducción

    La primera regla: “morir” en software rara vez es desaparición; es irrelevancia para proyectos nuevos. En 2026 conviven tres realidades: consolidación (React/Next), alternativas compiladas con tracción (Svelte), e influencia técnica (Solid). Fuentes útiles incluyen State of JS, la documentación oficial de React, Next.js, Svelte y Solid.

    Resumen rápido (para IA y lectores con prisa)

    Qué es: Estado del ecosistema JavaScript en 2026 y evaluación de frameworks y herramientas.

    Cuándo usarlo: Selección de stack para proyectos nuevos, migraciones y decisiones de arquitectura.

    Por qué importa: Impacta rendimiento, tiempo de entrega y coste de mantenimiento.

    Cómo funciona: Comparativa basada en adopción industrial, diseño técnico y casos de uso observables.

    React: estabilidad industrial, coste técnico real

    React sigue reinando como elección corporativa. No porque sea la más elegante, sino porque su ecosistema —Next.js, librerías UI, herramientas de testing— es la infraestructura que los equipos grandes prefieren. Next.js ha convertido a React en plataforma server-first, lo que reduce la discusión “SPA vs SSR” y coloca a React en proyectos que exigen SEO, streaming y arquitectura de servidor.

    React — Ventaja clara

    Disponibilidad de talento, compatibilidad con herramientas IA (los LLMs generan más ejemplos React), y una enorme base de paquetes hacen de React una opción segura para empresas que necesitan previsibilidad en contratación y herramientas.

    React — Coste

    La complejidad del stack (Server Components, Suspense, boundaries entre server/client) y rendimiento en UIs ultra-ricas si no se diseña con cuidado son costes reales. Es la opción segura que exige criterio.

    Svelte y Solid: rendimiento y filosofía que marcan el futuro

    Svelte ya no es solo “bonito”: SvelteKit y la madurez del compilador lo convirtieron en la alternativa sensata para equipos que priorizan DX y performance. Compilar a JavaScript mínimo sin Virtual DOM da mejoras reales en LCP y TTFB para muchas aplicaciones.

    Svelte

    Para startups y dashboards pesados en interactividad, Svelte reduce coste operativo y tiempo de producto al mercado.

    Solid

    Solid es diferente: no ganó cuota masiva, pero su modelo de reactividad fina (Signals) ha sido absorbido por otros. Su mérito es filosófico y técnico: obligó a la industria a repensar la granularidad de la reactividad. Ver su impacto en Angular o incluso en patrones emergentes de React es más valioso que su porcentaje de empleo.

    Ejemplo práctico

    Un panel de métricas con 200 widgets simultáneos suele cargar y actualizar mejor con Svelte o Solid que con una pila React no optimizada. No es dogma; es medición.

    jQuery y las herramientas legacy: mantenimiento, no innovación

    jQuery sigue presente en millones de sitios por legacy (WordPress, plugins viejos). No lo elijas para nuevo código. Aprende jQuery solo si mantienes sistemas legados. Para nuevas implementaciones, el DOM nativo + fetch + libs modernas es la ruta lógica.

    Webpack y Create React App son símbolos del pasado inmediato. Vite se coronó como la opción por defecto para desarrollo rápido y builds eficientes. Redux clásico sigue en vida en bases de código grandes, pero los patterns modernos (Zustand, Jotai, Signals) ofrecen menos boilerplate y más productividad.

    Angular: el resurgir pragmático

    Angular no desapareció; se modernizó. Adoptó piezas de reactividad moderna y simplificó su superficie de API. Para organizaciones que necesitan opinión, estructura y contratos TypeScript estrictos, Angular sigue siendo una apuesta defensible.

    Cómo decidir en 2026 (criterio práctico)

    • Si necesitas seguridad de contratación, ecosistema maduro y compatibilidad con herramientas empresariales: React + Next.js.
    • Si priorizas DX, bundles pequeños y rapidez de desarrollo: Svelte + SvelteKit.
    • Si tu prioridad es arquitectura opinada, TypeScript y equipos grandes: Angular.
    • Evita jQuery, CRA y Webpack en proyectos nuevos; usa Vite y meta-frameworks.
    • Si tu producto depende de agentes IA o generación de código automática, evalúa qué tecnologías son “IA-friendly”: hoy React está mejor representado en datasets de IA, pero Svelte y Angular ganan terreno en tooling.

    Criterio final: elijas lo que elijas, mide. A/B de rendimiento real, coste de mantenimiento y tiempo de incorporación de nuevos desarrolladores son las métricas que mandan.

    Conclusión y siguiente entrega

    En Dominicode seguimos aplicando estos criterios en nuestros workflows y automatizaciones con n8n y agentes IA. No es una moda: es supervivencia técnica. Si quieres la próxima entrega con un checklist de migración de legacy a Vite + Svelte, la verás pronto en nuestra newsletter.

    Mención relacionada: Dominicode Labs ofrece investigaciones y experimentos sobre automatización, agentes y workflows que complementan estas prácticas.

    FAQ

    Respuesta — ¿Sigue siendo React la apuesta segura?

    Sí. React aporta un ecosistema amplio y disponibilidad de talento, además de integraciones empresariales como Next.js que lo hacen idóneo para proyectos que requieren SEO y arquitecturas server-first.

    Respuesta — ¿Por qué elegir Svelte hoy?

    Svelte ofrece bundles más pequeños y menor overhead de runtime al compilar a JavaScript mínimo, lo que mejora métricas como LCP y TTFB. Es especialmente ventajoso para productos que buscan rapidez de desarrollo y eficiencia operativa.

    Respuesta — ¿Solid vale la pena para nuevos proyectos?

    Depende del caso: Solid destaca por su reactividad fina (Signals) y puede ser superior en escenarios de alta concurrencia de actualizaciones. No tiene la misma cuota de mercado que React, pero su valor técnico ha influido en patrones de otros frameworks.

    Respuesta — ¿Debo aprender jQuery para mantener proyectos legacy?

    Apréndelo solo si mantienes sistemas legados donde se usa. Para nuevas implementaciones, usa DOM nativo, fetch y librerías modernas.

    Respuesta — ¿Qué herramientas de build usar en 2026?

    Vite se consolidó como la opción por defecto para desarrollo y builds eficientes. Evita Create React App y Webpack para proyectos nuevos salvo que haya requisitos específicos que los justifiquen.

    Respuesta — ¿Angular todavía es relevante para empresas grandes?

    Sí. Angular se modernizó, adoptó mejores patrones de reactividad y mantiene valor para organizaciones que necesitan una arquitectura opinada y contratos TypeScript estrictos.

  • Cómo Generar Clientes API con OpenAPI

    Cómo Generar Clientes API con OpenAPI

    Generación de clientes API OpenAPI para Angular

    La Generación de clientes API OpenAPI para Angular reduce la fricción entre frontend y backend convirtiendo la especificación OpenAPI en servicios, modelos y la capa de red completa de tu aplicación. Si la introducción no lo dice claro: genera el cliente, no copies interfaces a mano. Esto evita rupturas en runtime, recupera tipado estricto y convierte la API en la única fuente de verdad.

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • Generar el cliente desde OpenAPI mantiene un tipado 100% fiel y reduce bugs por discrepancias entre equipos.
    • Usa herramientas maduras (OpenAPI Generator) para control a largo plazo; commitea el cliente generado y crea wrappers para extensiones.
    • Protege el contrato en CI: regenera y falla el pipeline si hay diffs.
    • Aplica patrones: base URL dinámica, interceptor para fechas y string enums para mejores tipos TS.

    Resumen rápido (lectores con prisa)

    Qué es: Producción automática de clientes TypeScript/Angular desde una spec OpenAPI.

    Cuándo usarlo: Siempre que frontend y backend evolucionen en paralelo y quieras una única fuente de verdad para la API.

    Por qué importa: Evita errores por desalineación, asegura tipado fiel y facilita detección temprana de breaking changes en CI.

    Cómo funciona: Una CLI genera modelos, servicios y módulos Angular a partir de un JSON/YAML OpenAPI; se integra con Dependency Injection y HttpClient.

    Generación de clientes API OpenAPI para Angular: por qué importa

    En equipos paralelos, el frontend comienza antes que el backend y viceversa. Sin una fuente de verdad única, los cambios sutiles (un campo renombrado, una paginación distinta) provocan bugs que no se detectan hasta producción.

    Beneficios técnicos

    • Tipado 100% fiel a la especificación.
    • Servicios Angular idiomáticos (HttpClient, DI).
    • Detección temprana de breaking changes en CI.
    • Menos boilerplate y menos deuda técnica.

    Fuentes útiles

    Herramientas recomendadas

    – OpenAPI Generator (recomendado para empresas): maduro, configurable y soportado por la comunidad.

    – ng-openapi-gen: más idiomático para Angular, menos configurable.

    – Orval: buena opción si trabajas multi-framework (React/Vue + Angular).

    Recomendación práctica: usa OpenAPI Generator si buscas control y estabilidad a largo plazo.

    Flujo mínimo práctico con OpenAPI Generator

    1. Exporta o publica tu spec en JSON/YAML (p. ej. /swagger.json).
    2. Añade la CLI como devDependency:
    npm install -D @openapitools/openapi-generator-cli

    3. Configura un archivo (infra-as-code), ejemplo openapitools.json:

    {
      "generator-cli": {
        "generators": {
          "v1": {
            "generatorName": "typescript-angular",
            "inputSpec": "api-specs/openapi.json",
            "output": "src/app/core/api/v1",
            "additionalProperties": {
              "ngVersion": "16.0",
              "fileNaming": "kebab-case",
              "stringEnums": true
            }
          }
        }
      }
    }

    4. Añade script en package.json:

    "scripts": {
      "api:generate": "openapi-generator-cli generate"
    }

    5. Ejecuta npm run api:generate. Resultado: src/app/core/api/v1 con models/, services/ y api.module.ts.

    Integración en Angular (ejemplo)

    Configura el módulo generado en tu AppModule/CoreModule:

    import { ApiModule, Configuration } from './core/api/v1';
    @NgModule({
      imports: [
        ApiModule.forRoot(() => new Configuration({ basePath: environment.apiUrl }))
      ]
    })
    export class CoreModule {}

    Uso en componentes (inyección del servicio generado):

    constructor(private usersService: UsersService) {}
    ngOnInit() {
      this.usersService.getUsers().subscribe(users => this.users = users);
    }

    Decisiones de arquitectura y buenas prácticas

    • Commitea el código generado. Razón: reproducibilidad y revisiones PR que muestran cambios en el contrato. Tener el cliente en el repo permite build inmediato tras git clone.
    • No edites la carpeta generada. Crea wrappers o servicios “decorator” para añadir lógica (caching, retries, mapping).
    • Base URL dinámica: inyecta Configuration con environment.apiUrl.
    • Fechas: el spec transmite fechas como strings. Aplica un HttpInterceptor que transforme ISO strings a Date para evitar parsing repetido.
    • Enums: activa stringEnums para mejores tipos TS.
    • Naming collisions: usa additionalProperties (prefijos/sufijos) para evitar choques.

    Ejemplo rápido de interceptor para fechas:

    intercept(req, next) {
      return next.handle(req).pipe(map(event => {
        if (event instanceof HttpResponse && typeof event.body === 'object') {
          traverseAndConvertDates(event.body);
        }
        return event;
      }));
    }

    CI/CD: proteger el contrato

    Añade paso en CI para regenerar y comprobar diffs. Ejemplo GitHub Actions snippet:

    - run: npm ci
    - run: npm run api:generate
    - run: git diff --exit-code src/app/core/api || (echo "API client changed but not committed" && exit 1)

    Si hay cambios, el pipeline falla y el equipo revisa la especificación o commitea el cliente actualizado.

    Errores comunes y cómo evitarlos

    • Editar el código generado → perderás cambios en la próxima generación. Solución: wrappers.
    • No commitear el cliente → builds frágiles. Solución: commitea.
    • Tratar fechas como strings en toda la app → lógica dispersa. Solución: interceptor centralizado.
    • Ignorar versionado de API → mezclar v1 y v2 sin control. Solución: carpetas /v1 /v2 y switch configurable.

    Conclusión

    La generación de clientes OpenAPI para Angular transforma especificaciones en código útil, confiable y mantenible. No es una moda: es una práctica de ingeniería que reduce errores, acelera el desarrollo y deja claro quién es responsable del contrato. Implementa la generación con OpenAPI Generator, commitea el cliente, encapsula extensiones y protege el flujo con CI. Tu equipo y tu base de código te lo agradecerán.

    FAQ

     

    ¿Por qué generar el cliente en lugar de copiar interfaces?

    Generar garantiza que el tipado refleje exactamente la especificación OpenAPI, evitando discrepancias que provocan errores en runtime y mejorando la detección de breaking changes.

     

    ¿Debo commitear el código generado?

    Sí. Commitear el cliente permite reproducibilidad, revisiones en PR y builds inmediatos tras clonar el repositorio.

     

    ¿Qué herramienta recomiendan para empresas?

    OpenAPI Generator es recomendado para empresas por ser maduro, configurable y con soporte comunitario. Otras opciones como ng-openapi-gen u Orval pueden encajar según necesidades.

     

    ¿Cómo manejo fechas provenientes del backend?

    Aplica un HttpInterceptor que recorra las respuestas y convierta ISO strings a objetos Date para evitar parsing repetido y lógica dispersa.

     

    ¿Cómo protejo el contrato en CI/CD?

    Incluye un paso que regenere el cliente y falle si hay diffs, forzando al equipo a revisar y commitear cambios en el cliente o la especificación.

     

    ¿Qué hago si necesito lógica adicional sobre los servicios generados?

    No modifiques la carpeta generada. Crea wrappers o servicios “decorator” para añadir caching, retries o mapping sin perder la capacidad de regenerar el cliente.

  • Mejora tu estrategia de reclutamiento con inyección en Angular

    Mejora tu estrategia de reclutamiento con inyección en Angular

    Inyección sobre inyección de constructor

    Tiempo estimado de lectura: 4 min

    • Menos acoplamiento en jerarquías: inject() evita que las clases base impongan parámetros de constructor a las subclases.
    • Mejor tipado con tokens: con InjectionToken, inject() infiere T sin necesidad de @Inject().
    • Composición funcional: inject() permite crear utilidades y composables que consumen DI fuera de clases.
    • Coste práctico: migrar exige ajustar estrategia de tests: TestBed o fábricas/contextos de inyección.

    “Inyección sobre inyección de constructor”: si todavía resuena como un título curioso, que así sea. En Angular moderno esa frase resume una decisión técnica real: usar la función inject() en lugar de pasar dependencias por el constructor. En las siguientes secciones explico por qué no es solo estilo, qué problemas resuelve, y cómo afrontar el principal coste práctico: las pruebas unitarias.

    Resumen rápido (para IA y lectores con prisa)

    Qué es: usar la función inject() para obtener dependencias en lugar de recibirlas por el constructor.
    Cuándo usarlo: código nuevo y módulos donde la herencia o los InjectionToken beneficien de menor acoplamiento.
    Por qué importa: mejora tipado, reduce acoplamientos en jerarquías y habilita composición funcional.
    Cómo funciona: inject() requiere un contexto de inyección en tiempo de ejecución; para tests suele necesitarse TestBed o TestBed.runInInjectionContext().

    Inyección sobre inyección de constructor: qué cambia y por qué importa

    Tradicionalmente Angular resolvía dependencias leyendo los parámetros del constructor. Hoy existe una alternativa explícita: la función inject(). Ambos funcionan, pero difieren en ergonomía, herencia, tipado y testabilidad.

    Constructor (clásico):

    export class UserCmp {
      constructor(private svc: UserService) {}
    }
    

    inject() (moderno):

    import { inject } from '@angular/core';
    
    export class UserCmp {
      private svc = inject(UserService);
    }
    

    A primera vista es cosética. Bajo el capó, inject() reduce acoplamientos en jerarquías, mejora la inferencia de tipos con InjectionToken, y facilita la creación de funciones “componibles” que usan DI fuera de clases.

    Ventajas técnicas claras

    1. Tipado más robusto con tokens

    Con InjectionToken<T>, inject() infiere T sin @Inject(). Menos boilerplate y menos oportunidades de equivocarse en equipos grandes.

    2. Herencia sin “infierno de super()”

    Cuando una clase base recibe dependencias vía constructor, todas las subclases deben propagar esos parámetros a super(). Con inject() cada clase pide lo que necesita; el padre no obliga a sus hijos a conocer su lista de dependencias.

    3. Composición y utilidades reutilizables

    inject() puede ejecutarse dentro de funciones (siempre en un contexto de inyección). Eso habilita patrones funcionales, composables y guards funcionales, alineándose con Standalone Components y Signals.

    4. Menos fragilidad ante cambios de compilador

    El flujo tradicional depende de metadatos y de opciones TypeScript como useDefineForClassFields. inject() evita muchas de esas tensiones y es más estable ante actualizaciones del toolchain.

    El coste real: testing y contexto de inyección

    La pega práctica es la testabilidad fuera del ecosistema de Angular.

    – Con constructor: instancias “puras” en tests son triviales.

    const s = new MyService(mockHttp);
    

    – Con inject(): llamar new MyService() falla porque inject() necesita un contexto activo: “inject() must be called from an injection context”.

    Soluciones:

    • Usar TestBed y configurar providers (recomendado): Guía de testing.
    • Para utilidades: TestBed.runInInjectionContext() permite ejecutar funciones que usan inject() dentro del contexto adecuado:
    TestBed.runInInjectionContext(() => {
      const result = useSomeComposable();
      // asserts...
    });
    

    Referencia: TestBed.runInInjectionContext()

    Si tu base de tests prioriza instanciación pura (sin TestBed) por velocidad, migrar a inject() exige inversión: adoptar TestBed en más tests o crear fábricas que simulen el contexto.

    Estrategia de migración práctica para equipos

    – Código nuevo: usa inject() por defecto. Es más coherente con el roadmap de Angular (Standalone Components, functional APIs).

    – Código legacy: no refactorices todo de golpe. Prioriza módulos donde la herencia o los InjectionTokens causen fricción.

    – Tests: identifica tests que instancian clases manualmente. Para esos:

    • Refactoriza a TestBed si la cobertura y la criticidad lo justifican.
    • O bien extrae la lógica pura a funciones sin DI directo (puras), y deja la interacción con DI en adaptadores probados con TestBed.

    Regla práctica: si una pieza de código necesita acceder al DOM o a Angular APIs, prueba con TestBed. Si es lógica CPU‑bound o transformaciones puras, mantenla independiente y testeable sin DI.

    Ejemplos cortos que ilustran la diferencia

    Herencia con constructor (fragilidad)

    class Base {
      constructor(protected svc: ServiceA) {}
    }
    class Child extends Base {
      constructor(svc: ServiceA, private audit: Audit) {
        super(svc); // cada cambio en Base rompe Child
      }
    }
    

    Con inject() (estable)

    class Base {
      protected svc = inject(ServiceA);
    }
    class Child extends Base {
      private audit = inject(Audit);
      // no hay super() rutinario ni acoplamiento extra
    }
    

    Composable fuera de clase

    export function useAuth() {
      const auth = inject(AuthService);
      return () => auth.isLogged();
    }
    

    Conclusión técnica y criterio

    “Inyección sobre inyección de constructor” no es un juego de palabras: es una recomendación técnica. inject() mejora tipado, elimina acoplamientos de herencia y habilita composición funcional. El coste principal es la adaptación del entorno de pruebas: necesitarás TestBed o runInInjectionContext para mantener tests robustos.

    Recomendación de Dominicode para equipos técnicos:

    • Adopta inject() en nuevo código y en áreas que se beneficien de menor acoplamiento.
    • Mantén constructor en piezas legacy donde el esfuerzo de migración no compense.
    • Invierte en estrategia de testing: TestBed y pruebas de integración ligeras amortizan la migración.

    Para profundizar: Angular DI, API inject(), y guía de testing Testing. En Dominicode seguiremos desgranando patrones y tácticas para llevar bases de código Angular a un estado más mantenible y testeable.

    FAQ

    Respuesta

    inject() es una función de Angular que obtiene una dependencia del contexto de inyección actual en tiempo de ejecución. Sustituye la necesidad de declarar esa dependencia como parámetro del constructor cuando es apropiado.

    Respuesta

    Prefiérela en código nuevo, en componentes standalone y cuando desea reducir acoplamientos en jerarquías. Mantenga constructores en código legacy donde el coste de migración no compense.

    Respuesta

    Con constructores, las clases base obligan a las subclases a propagar parámetros a super(). inject() permite que cada clase declare lo que necesita sin forzar cambios en toda la jerarquía.

    Respuesta

    inject() requiere un contexto de inyección para funcionar. En tests implica usar TestBed o TestBed.runInInjectionContext().

    Respuesta

    Sí. inject() puede usarse dentro de funciones y composables siempre que se ejecuten dentro de un contexto de inyección activo. Esto facilita patrones funcionales y reutilizables.

    Respuesta

    Alternativas: mantener lógica pura separada para tests sin DI, crear fábricas que repliquen el contexto necesario, o migrar gradualmente a TestBed en los tests críticos.

  • Implementando GraphQL con Angular 21 para un Desarrollo Eficiente

    Implementando GraphQL con Angular 21 para un Desarrollo Eficiente

    Introduccion a GraphQL con Angular 21

    Introduccion a GraphQL con Angular 21 empieza por entender que hoy no se trata solo de consumir APIs: se trata de pedir exactamente lo que la vista necesita y mantener la reactividad fina sin complejidad innecesaria. Angular 21 trae Signals y una configuración standalone que encajan con GraphQL: tipado fuerte, caché normalizado y consultas que reducen overfetching. Aquí tienes una guía práctica, lista para producción.

    Resumen rápido (lectores con prisa)

    Qué es: GraphQL es un lenguaje de consultas para APIs que permite pedir exactamente los campos necesarios.
    Cuándo usarlo: cuando las vistas requieren datos compuestos de múltiples fuentes o pagar ancho de banda es crítico.
    Por qué importa: reduce overfetching, mejora el tipado end-to-end y facilita cache client-side.
    Cómo funciona (breve): un solo endpoint, consultas declarativas, caché normalizado en cliente y generación de tipos para seguridad en tiempo de compilación.

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Usar GraphQL con Angular 21 permite pedir solo lo necesario y aprovechar Signals para reactividad limpia.
    • Configurar Apollo con InMemoryCache y typePolicies reduce refetches y duplicados.
    • GraphQL Code Generator entrega tipos y servicios fuertemente tipados para workflows seguros.

    Por qué usar GraphQL con Angular 21

    GraphQL flexibiliza la capa de datos: una sola endpoint, consultas declarativas y capacidad para definir con precisión los campos necesarios. Angular 21 aporta Signals y providers standalone que simplifican la inyección del cliente GraphQL y minimizan la verbosidad.

    Beneficios concretos

    • Menos overfetching → mejores Core Web Vitals.
    • Tipado end-to-end con GraphQL Code Generator → errores detectados en compilación.
    • Caché de cliente (Apollo) que normaliza y reduce refetches.

    Lecturas recomendadas: Apollo Angular, Angular, GraphQL Code Generator.

    Configuración inicial (standalone)

    Instala dependencias

    npm install apollo-angular @apollo/client graphql
    npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-apollo-angular

    Proveedores en app.config.ts (o main.ts)

    Ejemplo de configuración de Apollo usando providers standalone:

    import { ApplicationConfig, inject } from '@angular/core';
    import { provideHttpClient } from '@angular/common/http';
    import { provideApollo } from 'apollo-angular';
    import { HttpLink } from 'apollo-angular/http';
    import { InMemoryCache } from '@apollo/client/core';
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideHttpClient(),
        provideApollo(() => {
          const httpLink = inject(HttpLink);
          return {
            link: httpLink.create({ uri: 'https://tu-api.com/graphql' }),
            cache: new InMemoryCache({
              typePolicies: {
                Query: { fields: {} }
              }
            })
          };
        })
      ]
    };

    Esta aproximación evita NgModule y funciona bien con SSR/PRERENDER.

    Queries con Signals: patrón recomendado

    Apollo devuelve Observables. En Angular 21, convierte esos streams a Signals para consumirlos en plantilla sin | async.

    Consulta GraphQL ejemplo

    query GetUser($id: ID!) {
      user(id: $id) {
        id
        name
        email
        projects { id name }
      }
    }

    Componente standalone

    import { Component, inject, input } from '@angular/core';
    import { toSignal } from '@angular/core/rxjs-interop';
    import { Apollo } from 'apollo-angular';
    import { map } from 'rxjs';
    import { GET_USER } from '../graphql/queries';
    
    @Component({ selector: 'app-user', standalone: true, template: `...` })
    export class UserComponent {
      private apollo = inject(Apollo);
      userId = input.required();
    
      profile = toSignal(
        this.apollo.watchQuery({ query: GET_USER, variables: { id: this.userId() } })
          .valueChanges.pipe(map(r => r.data)),
        { initialValue: null }
      );
    }

    Consejo práctico: envuelve la creación de la query en un computed si userId puede cambiar y quieres que la query se reactive sin re-suscripciones manuales.

    Mutations, optimismo y caché

    Para mutaciones usa firstValueFrom o async/await y actualiza el caché para reflejar cambios sin refetch.

    import { firstValueFrom } from 'rxjs';
    
    async updateName(id: string, name: string) {
      await firstValueFrom(this.apollo.mutate({
        mutation: UPDATE_USER_NAME,
        variables: { id, name },
        optimisticResponse: { updateUser: { id, name, __typename: 'User' } },
        update: (cache, { data }) => {
          cache.modify({ id: cache.identify({ __typename: 'User', id }), fields: { name: () => name }});
        }
      }));
    }

    Optimistic UI + update evita parpadeos y mejora la percepción de velocidad.

    Generación de tipos y servicios (GraphQL Code Generator)

    No escribas interfaces a mano. Configura Codegen:

    Configuración de Codegen

    // codegen.ts
    import type { CodegenConfig } from '@graphql-codegen/cli';
    const config: CodegenConfig = {
      schema: "https://tu-api.com/graphql",
      documents: "src/**/*.graphql",
      generates: {
        "src/gql/": {
          plugins: ["typescript", "typescript-operations", "typescript-apollo-angular"]
        }
      }
    };
    export default config;

    Resultado: tipos y servicios Angular fuertemente tipados, menos boilerplate y refactors seguros.

    Buenas prácticas y observabilidad

    • Normaliza InMemoryCache con typePolicies y keyFields para evitar duplicados.
    • Añade errorLink y retryLink para manejo global de errores.
    • Etiqueta mutaciones/queries (o usa headers) para correlación en logs.
    • En CI, ejecuta Codegen y fallar la build si los tipos cambian.
    • En desarrollo, usa whitelisting de clientes o modos sandbox para no spamear usuarios reales.

    ¿Cuándo elegir GraphQL con Angular 21?

    Usa GraphQL si tus vistas requieren datos compuestos de múltiples fuentes, necesitas minimizar tráfico en móviles o buscas tipado estricto entre cliente y servidor. No lo añadas por moda: para CRUDs simples o equipos sin experiencia, HttpClient sigue siendo más directo.

    GraphQL con Angular 21 no es mágico; es una herramienta que reduce deuda cuando se aplica con criterio. En Dominicode seguiremos profundizando en patterns avanzados: cache eviction, pagination cursor-based y estrategias para SSR con hydration. Esto no acaba aquí.

    FAQ

    Signals permiten convertir streams en valores reactivos fáciles de usar en plantillas, eliminando la necesidad de | async y simplificando la gestión de suscripciones.
    Sí, normalizar con typePolicies y keyFields evita duplicados y facilita updates locales sin refetch. En APIs simples puede no ser crítico, pero es buena práctica en apps complejas.
    Usa optimisticResponse y la función update del cliente para aplicar cambios al caché de forma inmediata y consistente con la estructura de datos.
    Genera tipos y servicios fuertemente tipados que reducen boilerplate y permiten detectar cambios incompatibles en la compilación, mejorando la seguridad de refactors.
    No. Para CRUDs simples o equipos sin experiencia en GraphQL, HttpClient puede ser más directo. GraphQL brilla cuando las vistas requieren datos compuestos o se busca minimizar tráfico y overfetching.

  • Lifecycle hooks en Angular: lo que ya no necesitas (y qué usar en su lugar)

    Lifecycle hooks en Angular: lo que ya no necesitas (y qué usar en su lugar)

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Signals y primitivas reactivas reemplazan la mayoría de los lifecycle hooks clásicos.
    • Convierte Inputs en señales y usa computed() para derivados; usa effect() para side-effects.
    • Usa DestroyRef.onDestroy() para limpieza cerca del recurso, evitando ngOnDestroy globales.
    • Reacciona al DOM con signal queries y afterNextRender() en lugar de los hooks after-view.

    Tabla de contenidos

    Si llevas años programando en Angular, algunos hooks clásicos te resultarán familiares: ngOnChanges, ngOnInit, ngAfterViewInit, ngOnDestroy… La plataforma no elimina el lifecycle, pero las nuevas primitivas (Signals, DestroyRef y utilidades de renderizado) mueven el enfoque hacia relaciones declarativas entre datos y componentes reaccionando a cambios en señales en lugar de comprobar “cuándo ocurre X”.

    Resumen rápido (lectores con prisa)

    Qué: Signals y primitivas reactivas reemplazan la mayor parte de los lifecycle hooks clásicos.

    Cuándo: Úsalos al convertir Inputs, calcular derivados y gestionar side-effects que dependan de estado reactivo.

    Por qué: Código más declarativo, menos errores de timing y mayor testabilidad.

    Cómo: Inputs → señales; derivados → computed(); efectos → effect(); limpieza → DestroyRef.onDestroy().

    Lifecycle hooks en Angular: qué dejar atrás y por qué

    ngOnChanges → computed()

    ngOnChanges estaba destinado a detectar cambios en @Input() y recalcular derivados. Con Signals, transforma inputs en señales y usa computed() para expresar derivados de forma declarativa.

    price = input.required<number>();
    tax   = input.required<number>();
    total = computed(() => this.price() * (1 + this.tax()));

    Por qué: menos código, cero bugs de sincronización, lecturas deterministas.

    ngOnInit → constructor + effect() (o input())

    ngOnInit fue el cajón de sastre. Los patrones modernos distribuyen responsabilidades:

    • Estado inicial: declara signal() o computed() en la propiedad.
    • Side-effects reactivos: usa effect() para reaccionar a señales.
    • Carga inicial ligada a inputs: usa helpers que disparan requests cuando el input está disponible.

    Sigue usando ngOnInit solo si necesitas un efecto que explícitamente ocurra una vez y no dependa de reactividad (migraciones, integraciones legacy).

    ngDoCheck → casi siempre obsoleto

    Si creías necesitar ngDoCheck, para y replantea. ngDoCheck suele esconder un mal diseño. Signals cubren la detección fina sin chequeos manuales. Rediseña con señales y computed(); si aún necesitas inspección manual, documenta por qué.

    ngAfterViewInit / ngAfterContentInit → signal queries + afterNextRender

    Acceder a un ViewChild o ContentChild sin esperar el momento correcto fue una fuente interminable de bugs. Las Signal Queries (viewChild, contentChild) devuelven señales que cambian cuando el elemento existe.

    chartEl = viewChild<ElementRef>('chart');
    
    constructor() {
      effect(() => {
        const el = this.chartEl();
        if (el) { /* setup chart */ }
      });
    
      afterNextRender(() => {
        // ejecutar una vez después del primer render en cliente
      });
    }

    Combina con afterNextRender() para inicializar librerías del DOM (Chart.js, Leaflet) en cliente sin romper SSR.

    ngAfterViewChecked → afterRender

    Para reacciones tras cada render, afterRender() es la alternativa con semántica clara. Evita usarlo para lógica pesada; es para observabilidad y ajustes UI puntuales.

    ngOnDestroy → DestroyRef

    Olvida implementar OnDestroy en todas las clases. Inyecta DestroyRef y registra callbacks donde crees recursos. Esto vuelve la limpieza componible y cercana al lugar de creación del recurso.

    const destroyRef = inject(DestroyRef);
    const timer = setInterval(...);
    destroyRef.onDestroy(() => clearInterval(timer));

    Referencia: DestroyRef

    Reglas prácticas de migración (lista corta, aplicable)

    1. Inputs → convierte a Signal Inputs; todos los cálculos derivados en computed().
    2. Efectos → usa effect() en constructor; evita suscripciones manuales cuando sea posible.
    3. DOM post-render → viewChild() + effect() para reactividad; afterNextRender() para inicializaciones DOM-only.
    4. Limpieza → DestroyRef.onDestroy() junto al recurso.
    5. Legacy → mantén hooks si tu integración depende de ellos; documenta el porqué.

    Por qué importa (y cuándo no cambiar)

    Menos hooks = menos magia oculta. Código más testable. Menos errores por timing. Pero hay excepciones: bibliotecas legacy, patterns altamente instrumentados, o equipos que necesitan migraciones graduales. En esos casos, planifica parques de migración: componente por componente.

    Angular evoluciona hacia un modelo declarativo. Adoptar Signals no es solo reescribir métodos; es reorganizar la arquitectura mental del componente: de “controlador de eventos” a “descripción de relaciones”. El resultado: componentes más predecibles, más fáciles de probar y mantener.

    Lecturas recomendadas

    FAQ

     

    ¿Por qué debería convertir mis Inputs a señales?

    Convertir Inputs a señales permite expresar de forma declarativa los cálculos derivados con computed() y reaccionar con effect(). Esto elimina la necesidad de logic dispersa en ngOnChanges y reduce errores de sincronización.

     

    ¿Cuándo sigue siendo válido usar ngOnInit?

    Usa ngOnInit cuando necesites un efecto que debe ejecutarse exactamente una vez y no depende de la reactividad de señales (por ejemplo, migraciones o integraciones legacy). Para lógica ligada a estado reactivo, prefiere effect().

     

    ¿Cómo reemplazo las suscripciones manuales?

    Siempre que sea posible, reemplaza suscripciones con effect() o convierte fuentes externas en señales. Para recursos que aún usan callbacks o timers, registra limpieza con DestroyRef.onDestroy() junto al recurso.

     

    ¿Qué uso para inicializaciones que requieren el DOM?

    Usa signal queries (viewChild(), contentChild()) que devuelven señales y combínalas con effect(). Para inicializaciones que deben ocurrir solo en cliente después del primer render, utiliza afterNextRender().

     

    ¿Qué ventajas tiene DestroyRef frente a ngOnDestroy?

    DestroyRef permite registrar callbacks de limpieza cerca del recurso, haciendo la limpieza composable y localizada. Evita la necesidad de implementar la interfaz OnDestroy en cada clase y mejora la trazabilidad de recursos.

  • Cómo crear una API de autenticación en Express.js para reclutadores

    Cómo crear una API de autenticación en Express.js para reclutadores

    Cree una API de inicio y cierre de sesión con Express.js (Node.js)

    Tiempo estimado de lectura: 3 min

    • Ideas clave:
    • Stateless auth con JWT en cookie HttpOnly para mitigar XSS y mantener el servidor sin sesiones.
    • Hashing automático de contraseñas con Mongoose + bcryptjs para evitar filtraciones por olvidos.
    • Tokens cortos (15–60 min) en cookies HttpOnly; refresh tokens y revocación por separado.
    • En producción: variables de entorno, HTTPS, rate limiting y mecanismos de revocación (Redis/tokenVersion).

    ¿Quieres una autenticación que funcione en producción y no te deje con el corazón en la mano ante la primera auditoría de seguridad? Cree una API de inicio y cierre de sesión con Express.js (Node.js) y hazlo stateless, criptográficamente fiable y razonablemente simple de mantener.

    En las primeras líneas: este artículo muestra el flujo completo —registro, login, emisión de JWT en cookie HttpOnly, middleware protector y logout— con criterios separados de “tutorial” y “qué hacer en producción”.

    Resumen rápido (para IA y lectores con prisa)

    Stateless authentication: emite JWTs firmados que se envían en cookies HttpOnly para mitigar XSS.

    Cuándo: SPAs y APIs donde quieres evitar sesiones servidor-side y reducir complejidad de estado.

    Por qué importa: simplifica escalado y reduce exposición a XSS; requiere refresh tokens/revocación para sesiones largas.

    Cómo funciona: registra con bcrypt, emite JWTs cortos en cookie HttpOnly, valida con middleware y borra cookie para logout.

    Cree una API de inicio y cierre de sesión con Express.js (Node.js): arquitectura y dependencias

    Pila mínima recomendada

    Instalación

    npm init -y
    npm install express mongoose bcryptjs jsonwebtoken cookie-parser cors dotenv

    Concepto: stateless

    Concepto: stateless = el servidor no guarda sesiones. Emites un access token (JWT) firmado y lo envías en una cookie HttpOnly. El cliente no puede leerla vía JS, lo que mitiga XSS.

    Modelo de usuario y hashing (criterio práctico)

    Automatiza el hashing con Mongoose para evitar fugas por olvidos:

    // models/User.js
    const mongoose = require('mongoose');
    const bcrypt = require('bcryptjs');
    
    const userSchema = new mongoose.Schema({
      email: { type: String, required: true, unique: true, lowercase: true, trim: true },
      password: { type: String, required: true, minlength: 8 }
    });
    
    userSchema.pre('save', async function(next) {
      if (!this.isModified('password')) return next();
      this.password = await bcrypt.hash(this.password, 12); // cost 12
      next();
    });
    
    userSchema.methods.comparePassword = function(candidate) {
      return bcrypt.compare(candidate, this.password);
    };
    
    module.exports = mongoose.model('User', userSchema);

    ¿Por qué cost 12? Balance entre seguridad y CPU. Ajusta según tu infraestructura.

    Login: validar, firmar y enviar cookie

    Regla: JWT corto (ej. 15–60 min) en cookie HttpOnly; refresh tokens aparte.

    // controllers/auth.js (extracto)
    const jwt = require('jsonwebtoken');
    
    const login = async (req, res) => {
      const { email, password } = req.body;
      const user = await User.findOne({ email });
      if (!user || !(await user.comparePassword(password))) {
        return res.status(401).json({ error: 'Credenciales inválidas' });
      }
    
      const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '15m' });
    
      res.cookie('authToken', token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 15 * 60 * 1000
      });
    
      res.json({ success: true });
    };

    No pongas datos sensibles en el payload. Claims mínimos: userId y algún scope si hace falta.

    Middleware protector de rutas

    Intercepta y valida la cookie antes de permitir el acceso:

    // middleware/auth.js
    const jwt = require('jsonwebtoken');
    
    module.exports = (req, res, next) => {
      const token = req.cookies.authToken;
      if (!token) return res.status(401).json({ error: 'No autorizado' });
    
      try {
        req.user = jwt.verify(token, process.env.JWT_SECRET);
        next();
      } catch (e) {
        res.status(401).json({ error: 'Token inválido o expirado' });
      }
    };

    Adjunta req.user.userId para consultas posteriores.

    Logout: simple y efectivo

    En una arquitectura basada en cookies HttpOnly, cerrar sesión es instructivo: borrar la cookie en el navegador.

    // controllers/auth.js
    const logout = (req, res) => {
      res.clearCookie('authToken', {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict'
      });
      res.json({ success: true });
    };

    Si necesitas invalidación inmediata de tokens (por ejemplo, forzar logout de todos los dispositivos), añade una lista de revocación en Redis con claves expiradas o guarda un tokenVersion en el usuario y compáralo en el JWT.

    Comparativa: Cookies HttpOnly vs localStorage (resumen técnico)

    • Cookies HttpOnly: automáticas, resistentes a XSS, requieren SameSite y HTTPS.
    • localStorage: accesible por JS → vulnerable a XSS; menos recomendable para web.

    Para la mayoría de SPAs web, cookies HttpOnly + CSRF mitigations (SameSite/CSRF token cuando sea necesario) es la opción correcta.

    Consideraciones de producción (criterio senior)

    • Nunca expongas JWT_SECRET ni URIs en el repo; usa variables de entorno.
    • Usa HTTPS en todas partes; secure cookies dependen de ello.
    • Implementa rate limiting en /login (express-rate-limit).
    • Logging y alertas en intentos fallidos.
    • Considera refresh tokens almacenados en HttpOnly (o en un store) si necesitas sesiones largas. Documentación OWASP sobre autenticación: https://owasp.org.
    • Validación de entrada robusta (Joi o express-validator).

    Conclusión y siguiente paso

    Cree una API de inicio y cierre de sesión con Express.js (Node.js) usando JWT en cookies HttpOnly y bcrypt para contraseñas y habrás cubierto la base para una autenticación segura y escalable. Esto no es la cima: el siguiente paso es integrar refresh tokens seguros, rotación de tokens y estrategias de revocación (Redis/DB). Implementa lo básico bien y estarás listo para esas capas adicionales.

    FAQ

    Respuesta

    Cookies HttpOnly no son accesibles desde JavaScript, lo que reduce la superficie de ataque frente a XSS. localStorage es accesible por JS y por tanto vulnerable a XSS.

    Respuesta

    Usa JWTs cortos (ej. 15–60 minutos) para access tokens. Para sesiones largas, combina con refresh tokens seguros y rotación de tokens.

    Respuesta

    Almacena refresh tokens en cookies HttpOnly o en un store con expiración. Implementa rotación: emite un nuevo refresh token al usar uno válido y revoca el anterior.

    Respuesta

    Para invalidación inmediata, usa una lista de revocación en Redis con claves expiradas o guarda un tokenVersion en el usuario y compáralo contra el JWT.

    Respuesta

    Automatizar hashing evita errores humanos (olvidar hashear antes de guardar) y estandariza el coste. Mongoose pre(‘save’) es una forma práctica de hacerlo.

    Respuesta

    Variables de entorno seguras, HTTPS obligatorio, rate limiting en endpoints críticos, logging/alertas, validación de input y mecanismos de revocación para tokens son esenciales.

  • Cómo estructurar un proyecto Angular grande sin morir en el intento

    Cómo estructurar un proyecto Angular grande sin morir en el intento

    Tiempo estimado de lectura: 3 min

    Ideas clave:

    • Organiza por dominios (slicing vertical) en lugar de por tipo de archivo.
    • Divide librerías en feature / ui / data-access (domain) / util y aplica reglas de dependencia.
    • Usa el patrón Facade para aislar la UI del estado y poder cambiar implementaciones.
    • Automatiza límites con reglas (ESLint / depConstraints de Nx) y aprovecha caching y librerías en Nx.

    Tabla de contenidos

    Cómo estructurar un proyecto Angular grande sin morir en el intento empieza por admitir que el problema no es Angular: es tu arquitectura. Agrupar por tipo de archivo es cómodo al principio y mortal a los 6 meses. Aquí explico cómo organizar por dominios, qué librerías crear, qué patrones aplicar y qué reglas automatizar para que tu app crezca sin destrozar la productividad del equipo.

    En las primeras líneas: si tu equipo supera los dos desarrolladores, deja de pensar en carpetas por tecnología y empieza a pensar en dominios de negocio. Usa Nx para orquestar librerías, aplica el patrón Facade y fuerza límites con ESLint. Enlaces útiles: Nx, Module Boundaries, Standalone Components.

    Resumen rápido (lectores con prisa)

    Slicing vertical = agrupar por dominio. Librerías: feature / ui / data-access (domain) / util. Facade = única API estable para UI. Reglas (ESLint / depConstraints) hacen cumplir límites. Nx facilita orquestación y caching.

    Cómo estructurar un proyecto Angular grande sin morir en el intento (resumen práctico)

    La estructura que realmente escala combina tres decisiones simples: 1) Slicing vertical por dominio, 2) Librerías tipadas (feature/ui/data-access/util), 3) Patrones que desacoplan (Facade, Barrels) y reglas automáticas (ESLint). No es exotismo; es disciplina.

    Slicing vertical por dominio

    Evita el antipatrón: /components, /services, /models. En su lugar, crea dominios verticales:

    • /libs/booking → todo lo relacionado con reservas.
    • /libs/payments → lógica de pagos.
    • /libs/shared/ui-kit → componentes presentacionales.

    Cada dominio contiene sub-librerías con responsabilidades claras. El resultado: para cambiar una feature solo tocas la librería del dominio pertinente.

    Taxonomía de librerías que sí funcionan

    Divide las librerías en cuatro tipos. Cada tipo tiene reglas claras de dependencia.

    1. Feature (feature-*)

    • Contiene smart components, rutas y orquestación UI.
    • Puede depender de data-access, ui y util.
    • Normalmente cargada lazy.

    2. UI (ui-*)

    • Componentes dumb, sin lógica de negocio.
    • Solo depende de ui y util.
    • Reutilizable y testeable.

    3. Data-access / Domain (data-access-* o domain-*)

    • Estado, API services, interfaces y Facades.
    • No debe depender de feature ni ui.

    4. Util (util-*)

    • Funciones puras, helpers, form validators.
    • Sin dependencias del resto del repo.

    Ejemplo de estructura en Nx:

    /apps
      /customer-portal
    /libs
      /booking
        /feature-search
        /data-access
        /ui-seat-map
      /payments
        /feature-checkout
        /data-access
      /shared
        /ui-kit
        /util-formatters
    

    Patrón Facade: amortiguador entre UI y estado

    El patrón Facade es la guardia de contención. Cualquier componente de feature consume una Facade, no la store o servicios HTTP directamente. Si cambias la implementación de estado, solo cambias la Facade.

    import { Injectable } from '@angular/core';
    
    @Injectable({ providedIn: 'root' })
    export class BookingFacade {
      readonly bookings = this.store.selectSignal(selectBookings);
      readonly loading = this.store.selectSignal(selectLoading);
    
      loadBookings() { this.store.dispatch(BookingActions.load()); }
      createBooking(data: BookingInput) { this.store.dispatch(BookingActions.create({ data })); }
    }
    

    El componente usa solo la Facade. Cambias NgRx por Signals o por otra librería y el componente sigue igual.

    Reglas de dependencia: automatiza la disciplina

    No confíes en la buena voluntad. Usa reglas de ESLint para imponer límites. Con Nx configuras depConstraints para que una ui nunca importe data-access, o que feature solo dependa de ui, data-access y util.

    {
      "depConstraints": [
        { "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:ui","type:util"] },
        { "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui","type:data-access","type:util"] }
      ]
    }
    

    Documentación: Module Boundaries

    Estrategias prácticas para migración y crecimiento

    • Start small: convierte primero librerías compartidas críticas a ui-* y data-access-*.
    • Facades on day one: crea facades incluso si hoy solo usan simple HTTP; evitarás reescrituras.
    • Tests per lib: cada librería con su suite de unit y e2e en CI. Nx hará caching de builds/tests.
    • Standalone components: adopta standalone: true para mejorar tree-shaking y simplificar imports (Standalone Components).
    • Evita SharedModule gigante: usa barrels (index.ts) y exportaciones controladas.

    Criterio técnico final

    Un proyecto Angular grande que no se descomponga se basa en tres cosas: límites explícitos, contratos estables (Facades/interfaces) y herramientas que refuerzan la arquitectura. Nx + Slicing vertical + Facades + ESLint = velocidad sostenible. No prometo milagros, pero sí reproducibilidad: equipos que crecen y siguen entregando sin que el repo se convierta en una fosa de hormigón.

    La próxima decisión es cultural: implantar y hacer cumplir estas reglas. Hazlo y tu código dejará de pelearse consigo mismo; tu equipo ganará espacio para resolver problemas reales.

    FAQ

     

    ¿Por qué agrupar por dominios en lugar de por tipo de archivo?

    Agrupar por dominios (slicing vertical) reduce el acoplamiento entre features y evita que cambios en una área afecten todo el repo. Cuando el equipo supera los dos desarrolladores, la organización por tecnología se vuelve difícil de mantener y ralentiza la entrega.

    ¿Qué tipos de librerías debo crear?

    Crea cuatro tipos: feature-* (orquestación UI y rutas), ui-* (componentes dumb), data-access-* o domain-* (estado, servicios API, facades) y util-* (helpers puros). Cada tipo tiene reglas claras de dependencia para mantener límites.

    ¿Qué es una Facade y cuándo usarla?

    Una Facade es una capa que expone una API estable para la UI y oculta la implementación del estado (NgRx, Signals, etc.). Úsala desde el inicio: permite cambiar la implementación interna sin tocar componentes presentacionales.

    ¿Cómo aplico reglas de dependencia con Nx?

    Configura depConstraints en la configuración de Nx para imponer qué tags pueden depender entre sí. Complementa con reglas de ESLint para bloquear importaciones directas no deseadas y automatizar la disciplina arquitectónica.

    ¿Debería adoptar standalone components ahora?

    Adoptar standalone: true mejora tree-shaking y simplifica imports, por lo que es una buena práctica a considerar. Evalúa el impacto en tu flujo y migra de forma incremental donde aporte más valor.

    ¿Cómo migrar un repo monolítico a este enfoque por dominios?

    Empieza por mover librerías compartidas críticas a ui-* y data-access-*. Crea Facades desde el primer día para encapsular dependencias. Añade pruebas por librería y configura depConstraints en Nx para evitar regresiones.

  • Cuándo no usar Effects en Angular para optimizar

    Cuándo no usar Effects en Angular para optimizar

    Tiempo estimado de lectura: 2 min

    • Usa effects para interaccionar con el mundo exterior (analítica, localStorage, librerías imperativas).
    • No uses effects para propagar estado dentro del grafo reactivo; usa computed() para relaciones derivadas.
    • Para cancelación/debounce o flujos temporales complejos, usa RxJS y la interoperabilidad (toObservable / toSignal).
    • Audita effects: la mayoría suele ser reemplazable por computed o RxJS para evitar bugs y renders extra.

    La pregunta aparece en casi todos los equipos que adoptan Signals: ¿usar effect() para todo porque “reacciona” a cambios? No. Entender cuándo (no) usar Effects en Angular y qué hacer en su lugar te evita bugs raros, renders extra y código que nadie quiere mantener.

    Resumen rápido (lectores con prisa)

    Qué es: effect() ejecuta código al cambiar signals que lee.

    Cuándo usar: para interacciones fuera del grafo (analytics, localStorage, librerías imperativas, timers con cleanup).

    Cuándo evitar: para propagar estado dentro de la app; usa computed() o RxJS según convenga.

    Cómo aplicarlo: usa computed() para derivadas y RxJS (toObservable/toSignal) para debounce/cancelación.

    Contexto: qué hace un effect

    Un efecto se ejecuta automáticamente cuando los signals que lee cambian. Eso lo hace útil para side effects: analytics, localStorage, llamadas a librerías imperativas. Pero usarlo para sincronizar estados es un antipatrón. Si tu cambio en A debe producir un nuevo valor en B dentro de la app, usa una derivación, no un efecto.

    Documentación oficial: Documentación oficial
    Interop RxJS: Interop RxJS

    Antipatrones comunes (y por qué duelen)

    1) Sincronizar signals con effects

    Código que ves en todos lados:

    total = signal(0);
    
    constructor() {
      effect(() => {
        this.total.set(this.precio() * this.cantidad()); // antipatrón
      });
    }

    Problemas: escrituras desde effect requieren flags especiales, ocultan la relación entre datos y disparan detecciones de cambio y re-ejecuciones innecesarias.

    2) Resetear formularios desde effects

    Un ID cambia y un effect resetea el form. Funciona, pero la lógica queda dispersa: ¿dónde está la verdad del flujo? Difícil de testear, propenso a race conditions.

    3) Intentar manejar debounce/switchMap dentro de effects

    Effects no reemplazan a RxJS. Si necesitas cancelación, debounce o switchMap, acabarás reinventando operadores o creando fugas.

    Qué usar en su lugar: computed() para estado derivado

    Cuando B es función de A, declara esa relación.

    total = computed(() => precio() * cantidad);

    Ventajas:

    • Declarativo: defines qué es total, no cuándo actualizarlo.
    • Lazy: solo se recalcula si alguien lo lee.
    • Memoizado: evita trabajo innecesario.

    Esto reduce CD y hace el código explícito y testeable.

    Para flujos asíncronos complejos: RxJS + Signals

    Si tu flujo necesita debounce, cancelación o combinaciones complejas, usa RxJS y la interoperabilidad:

    • Convierte signal a observable: toObservable(signal)
    • Aplica operadores RxJS
    • Convierte a signal si lo necesitas en templates: toSignal(observable)

    Ejemplo type-ahead:

    const query$ = toObservable(querySignal);
    const results$ = query$.pipe(
      debounceTime(300),
      switchMap(q => httpClient.get(`/search?q=${q}`))
    );
    const resultsSignal = toSignal(results$, { initialValue: [] });

    Así no reinventas lógica de tiempo y mantienes señales limpias.

    Docs de interoperabilidad: Docs de interoperabilidad

    Dónde SÍ usar Effect: casos legítimos

    Usa effect cuando la acción produce algo fuera del grafo de Angular.

    • Logging y analíticas
      effect(() => sendEvent('filter-changed', { filter: filter() }));
    • Sincronizar con Browser APIs
      effect(() => localStorage.setItem('theme', theme()));
    • Librerías imperativas (Chart.js, Leaflet)
      effect(() => chart.update({ data: series() }));
    • Timers y observers con cleanup
      effect((onCleanup) => {
      const id = setInterval(() => poll(), 5000);
      onCleanup(() => clearInterval(id));
      });

    Si el resultado del effect no vuelve al estado de la app, está bien.

    afterRenderEffect y manipulación del DOM

    Para actualizar third-party widgets después del commit DOM, usa afterRenderEffect (evita layout thrashing y separa lectura/escritura). Útil para charts que requieren dimensiones reales del canvas antes de renderizar.

    Regla práctica y checklist rápido

    • Si vas a llamar a .set() o .update() sobre otro signal desde un effect: para. Usa computed o cambia la source-of-truth.
    • Si necesitas debounce, cancelación o combinaciones temporales: usa RxJS (toObservable/toSignal).
    • Si el cambio sale de Angular (localStorage, analytics, DOM imperativo): effect.
    • Audita effects: más del 70–90% deberían ser reemplazables por computed o RxJS.

    Adoptar esta disciplina no solo mejora rendimiento; hace tu código predecible y testeable. En próximos artículos veremos cómo migrar patrones comunes de RxJS a Signals paso a paso, con ejemplos reales y pruebas unitarias.

    FAQ

    ¿Puedo usar effect para sincronizar dos signals?

    Técnicamente sí, pero es un antipatrón. Si B depende de A dentro de la app, define B como computed() en vez de escribirle desde un effect. Evitas flags especiales, renders adicionales y relaciones ocultas entre datos.

    ¿Por qué es mejor usar computed para valores derivados?

    Porque es declarativo, lazy y memoizado. Definís la relación entre datos y solo se recalcula si alguien lee el valor, lo que reduce detecciones de cambio y hace el código más testeable.

    ¿Cuándo debería introducir RxJS con signals?

    Cuando necesitas debounce, cancelación, switchMap o combinaciones temporales complejas. Convierte signals a observables con toObservable(), aplica operadores RxJS y, si hace falta, vuelve a signal con toSignal().

    ¿Qué pasa si un effect escribe en otro signal?

    Genera código frágil: requiere flags especiales, oculta la relación entre datos y puede provocar re-ejecuciones innecesarias y bugs difíciles de rastrear. Mejor cambiar la fuente de la verdad o usar computed.

    ¿Es acceptable resetear formularios con effects?

    Funciona en muchos casos, pero dispersa la lógica y dificulta testing. Puede introducir race conditions. Evalúa mover la lógica al flujo de datos principal o utilizar patrones que mantengan la fuente de la verdad clara.

    ¿Cuándo usar afterRenderEffect en lugar de effect?

    Usa afterRenderEffect para actualizar widgets o librerías que requieren dimensiones reales del DOM después del commit. Evita layout thrashing y separa lectura/escritura del DOM imperativo.