# Design: AdSense Lazy Loading con Intersection Observer ## Context ### Problema Actual El `adsense-loader.js` actual implementa un modelo "todo o nada": 1. Usuario interactua (scroll/click) O timeout 5s 2. Se carga `adsbygoogle.js` (biblioteca principal) 3. Se ejecutan TODOS los `push({})` simultaneamente 4. Google intenta llenar TODOS los slots de una vez **Consecuencias:** - Fill rate bajo: Google tiene limite de ads por pagina/sesion - Slots vacios visibles: No hay inventario para todos - Impresiones desperdiciadas: Ads below-the-fold nunca vistos - Impacto en Core Web Vitals: Carga masiva de recursos ### Solucion Propuesta Cambiar a modelo "por demanda con visibilidad": 1. La biblioteca `adsbygoogle.js` se carga UNA vez (primer ad visible) 2. Cada slot individual se activa al entrar en viewport 3. Slots permanecen ocultos hasta que tengan contenido 4. No hay timeout global, cada ad tiene su propio trigger ## Goals / Non-Goals ### Goals - Mejorar fill rate cargando ads secuencialmente - Eliminar espacios en blanco de slots vacios - Reducir tiempo de carga inicial (menos JS ejecutado) - Mejorar Core Web Vitals (menor TBT, mejor LCP) - Cumplir politicas de Google AdSense ### Non-Goals - Reciclar o eliminar ads ya cargados (viola politicas) - Implementar "infinite scroll" de ads - Cache de contenido de ads - Prefetch de ads futuros ## Decisions ### Decision 1: Extension del Modulo Existente AdsensePlacement **Razon:** Mantener Clean Architecture del proyecto. No crear modulo nuevo. **Ubicacion de archivos:** - Schema: `Schemas/adsense-placement.json` (nuevos campos en grupo `forms`) - Renderer: `Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php` - FormBuilder: `Admin/AdsensePlacement/Infrastructure/Ui/AdsensePlacementFormBuilder.php` - FieldMapper: `Admin/AdsensePlacement/Infrastructure/FieldMapping/AdsensePlacementFieldMapper.php` - Asset Enqueuer: `Public/AdsensePlacement/Infrastructure/Services/AdsenseAssetEnqueuer.php` - JavaScript: `Assets/Js/adsense-loader.js` **Alternativas descartadas:** - Crear modulo nuevo `AdsenseLazyLoading`: Viola principio de cohesion, duplica logica ### Decision 2: Usar Intersection Observer API **Razon:** API nativa del navegador, alto rendimiento, soporte >95% global. **Alternativas consideradas:** - Scroll listener + getBoundingClientRect(): Mayor consumo de CPU - requestAnimationFrame loop: Complejo, mismo resultado - Third-party library (lozad.js): Dependencia innecesaria ### Decision 3: Ocultar slots por defecto con CSS Dinamico **Razon:** Evitar layout shift (CLS) cuando un slot no recibe ad. **Implementacion via CSSGeneratorService** (NO CSS estatico): ```php // En AdsensePlacementRenderer.php $this->cssGenerator->generate([ '.roi-ad-slot' => [ 'display' => $lazyEnabled ? 'none' : 'block', ], '.roi-ad-slot.roi-ad-filled' => [ 'display' => 'block', ], ]); ``` **Alternativas descartadas:** - CSS estatico en archivo: Viola arquitectura del tema - `visibility: hidden`: Ocupa espacio, causa CLS - `height: 0; overflow: hidden`: Hack, problemas con responsive - Remover del DOM: Viola politicas de AdSense ### Decision 4: Criterios Concretos de Fill Detection **Razon:** Evitar ambiguedad sobre cuando un ad "tiene contenido". **Criterios para marcar como `roi-ad-filled`:** 1. El elemento `` contiene al menos un hijo 2. **Y** ese hijo es un `