Files
roi-theme/_openspec/changes/adsense-cache-unified-visibility/spec.md
FrankZamora 0f6387ab46 refactor: reorganizar openspec y planificacion con spec recaptcha
- renombrar openspec/ a _openspec/ (carpeta auxiliar)
- mover specs de features a changes/
- crear specs base: arquitectura-limpia, estandares-codigo, nomenclatura
- migrar _planificacion/ con design-system y roi-theme-template
- agregar especificacion recaptcha anti-spam (proposal, tasks, spec)
- corregir rutas y referencias en todas las specs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 15:30:45 -06:00

9.8 KiB

Especificacion: Unificacion de Visibilidad AdSense para Compatibilidad con Cache

Purpose

Unificar la logica de visibilidad de AdSense para que TODA la evaluacion dependiente de usuario (hide_for_logged_in) se realice en el cliente via JavaScript-First, eliminando llamadas a is_user_logged_in() durante el render PHP.

Problema actual: El AdsensePlacementRenderer usa PageVisibilityHelper::shouldShow() que internamente llama is_user_logged_in() en PHP. Esto causa que el HTML generado varie segun el estado de autenticacion, rompiendo la compatibilidad con page cache.

Solucion: Cuando javascript_first_mode esta activo, el Renderer debe generar SIEMPRE el HTML de los slots, delegando la decision de hide_for_logged_in al endpoint REST y al JavaScript del cliente.


Background

Arquitectura JavaScript-First existente (spec: adsense-javascript-first)

El sistema JavaScript-First ya implementa:

  1. Endpoint REST /wp-json/roi-theme/v1/adsense-placement/visibility

    • Evalua is_user_logged_in() en tiempo real (no cacheado)
    • Retorna decision show_ads: true/false con razones
    • Headers anti-cache (no-store, no-cache)
  2. Cliente JavaScript adsense-visibility.js

    • Consulta endpoint via AJAX con cookies (credentials: same-origin)
    • Cachea decision en localStorage
    • Aplica clases CSS para mostrar/ocultar slots

El gap actual

FLUJO ACTUAL (PROBLEMATICO):

Request → PHP Render → PageVisibilityHelper::shouldShow()
                            ↓
                       is_user_logged_in()  ← ROMPE CACHE
                            ↓
                   HTML con/sin slots (depende de login)
                            ↓
                       Page Cache  ← HTML incorrecto para otros usuarios
FLUJO DESEADO (CON ESTA SPEC):

Request → PHP Render → [javascript_first_mode=true?]
                            ↓ SI
                   SIEMPRE genera HTML con slots
                            ↓
                       Page Cache  ← HTML identico para todos
                            ↓
                   JS consulta endpoint REST
                            ↓
                   Muestra/oculta segun respuesta

Requirements

Requirement 1: Bypass de hide_for_logged_in en modo JavaScript-First

The AdsensePlacementRenderer MUST skip the is_user_logged_in() evaluation during PHP render when javascript_first_mode is enabled.

Scenario: JavaScript-First activo, usuario anonimo

  • GIVEN javascript_first_mode esta habilitado en settings
  • AND hide_for_logged_in esta habilitado
  • AND el visitante NO esta logueado
  • WHEN se renderiza un slot de AdSense
  • THEN el HTML del slot DEBE generarse (placeholders visibles)
  • AND el JavaScript DEBE consultar el endpoint
  • AND el endpoint retorna show_ads: true
  • AND los anuncios se muestran

Scenario: JavaScript-First activo, usuario logueado

  • GIVEN javascript_first_mode esta habilitado
  • AND hide_for_logged_in esta habilitado
  • AND el visitante ESTA logueado
  • WHEN se renderiza un slot de AdSense
  • THEN el HTML del slot DEBE generarse (en cache seria identico a anonimo)
  • AND el JavaScript DEBE consultar el endpoint
  • AND el endpoint retorna show_ads: false con razon hide_for_logged_in
  • AND los anuncios se OCULTAN via CSS/JS

Scenario: JavaScript-First deshabilitado (modo legacy)

  • GIVEN javascript_first_mode esta DESHABILITADO
  • AND hide_for_logged_in esta habilitado
  • AND el visitante ESTA logueado
  • WHEN se renderiza un slot de AdSense
  • THEN el HTML del slot NO se genera (comportamiento legacy)
  • AND is_user_logged_in() SE evalua en PHP
  • BECAUSE sin JS-First, el modo legacy es la unica opcion

Requirement 2: PageVisibilityHelper debe respetar modo JavaScript-First

The PageVisibilityHelper MUST provide a method that excludes user-dependent checks when JavaScript-First mode is active for a component.

Scenario: Nuevo metodo shouldShowForCache

  • GIVEN un componente con javascript_first_mode habilitado
  • WHEN se llama PageVisibilityHelper::shouldShowForCache('adsense-placement')
  • THEN DEBE evaluar visibilidad por tipo de pagina (home, posts, pages, etc.)
  • AND DEBE evaluar exclusiones por categoria, ID, URL pattern
  • AND NO DEBE evaluar hide_for_logged_in
  • BECAUSE esa evaluacion la hace el cliente

Scenario: Componente sin JavaScript-First usa metodo existente

  • GIVEN un componente sin javascript_first_mode
  • WHEN se llama PageVisibilityHelper::shouldShow('otro-componente')
  • THEN DEBE usar flujo existente incluyendo hide_for_logged_in
  • BECAUSE sin JS-First, PHP es la unica fuente de verdad

Requirement 3: WordPressComponentVisibilityRepository debe soportar modo bypass

The repository MUST support skipping is_user_logged_in() check when requested.

Scenario: isNotExcluded con bypass de login check

  • GIVEN se llama isNotExcluded('adsense-placement', skipLoginCheck: true)
  • WHEN el metodo evalua exclusiones
  • THEN NO DEBE llamar is_user_logged_in()
  • AND DEBE evaluar otras exclusiones normalmente

Requirement 4: El endpoint REST DEBE evaluar hide_for_logged_in

The REST endpoint /adsense-placement/visibility MUST evaluate hide_for_logged_in and include it in the decision.

NOTA: Esto YA esta implementado en AdsenseVisibilityController.php. Solo documentamos para claridad.

Scenario: Endpoint evalua usuario logueado

  • GIVEN peticion al endpoint con cookies de sesion
  • AND hide_for_logged_in esta habilitado
  • AND el usuario ESTA logueado
  • WHEN el endpoint procesa la peticion
  • THEN retorna show_ads: false
  • AND reasons incluye hide_for_logged_in

Requirement 5: El CSS de slots ocultos NO debe afectar el layout

When JavaScript hides ad slots, they MUST collapse completely without affecting page layout.

NOTA: Esto YA esta implementado con :has([data-ad-status='unfilled']). Documentamos para verificar que se mantiene.


Implementation

Archivos a modificar

1. AdsensePlacementRenderer.php

// ANTES (linea 38-43):
public function renderSlot(array $settings, string $location): string
{
    // 0. Verificar visibilidad por tipo de pagina y exclusiones
    if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
        return '';
    }
    // ...
}

// DESPUES:
public function renderSlot(array $settings, string $location): string
{
    // 0. Verificar visibilidad (respetando modo JS-First para cache)
    $jsFirstMode = ($settings['behavior']['javascript_first_mode'] ?? false) === true;

    if ($jsFirstMode) {
        // En modo JS-First, usar evaluacion cache-friendly (sin is_user_logged_in)
        if (!PageVisibilityHelper::shouldShowForCache('adsense-placement')) {
            return '';
        }
    } else {
        // Modo legacy: usar evaluacion completa
        if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
            return '';
        }
    }
    // ...
}

2. PageVisibilityHelper.php

// AGREGAR nuevo metodo:
/**
 * Evalua visibilidad SIN checks dependientes de usuario
 *
 * Para uso cuando JavaScript manejara los checks de usuario.
 * Evalua: tipos de pagina, exclusiones por categoria/ID/URL
 * NO evalua: hide_for_logged_in
 *
 * @param string $componentName
 * @return bool
 */
public static function shouldShowForCache(string $componentName): bool
{
    $container = DIContainer::getInstance();
    $useCase = $container->getEvaluateComponentVisibilityUseCase();

    return $useCase->executeForCache($componentName);
}

3. EvaluateComponentVisibilityUseCase.php

// AGREGAR nuevo metodo:
/**
 * Evalua visibilidad SIN checks dependientes de usuario
 */
public function executeForCache(string $componentName): bool
{
    // Paso 1: Verificar visibilidad por tipo de pagina (sin cambios)
    $visibleByPageType = $this->pageVisibilityUseCase->execute($componentName);

    if (!$visibleByPageType) {
        return false;
    }

    // Paso 2: Verificar exclusiones SIN hide_for_logged_in
    $isExcluded = $this->exclusionsUseCase->executeForCache($componentName);

    return !$isExcluded;
}

4. EvaluateExclusionsUseCase.php

// AGREGAR nuevo metodo:
/**
 * Evalua exclusiones SIN hide_for_logged_in
 */
public function executeForCache(string $componentName): bool
{
    // Evaluar exclusiones por categoria, ID, URL
    // NO evaluar hide_for_logged_in
    return $this->repository->isExcludedForCache($componentName);
}

5. WordPressComponentVisibilityRepository.php

// MODIFICAR isNotExcluded o agregar nuevo metodo:
/**
 * Verifica exclusiones SIN evaluar hide_for_logged_in
 */
public function isNotExcludedForCache(string $componentName): bool
{
    // OMITE: shouldHideForLoggedIn()
    // MANTIENE: PageVisibilityHelper::shouldShow() para otras exclusiones

    return PageVisibilityHelper::shouldShowByPageType($componentName);
}

Acceptance Criteria

  1. Con javascript_first_mode=true y hide_for_logged_in=true:

    • Usuario anonimo: HTML generado, JS muestra ads
    • Usuario logueado: HTML generado, JS oculta ads
    • Page cache sirve mismo HTML a ambos
  2. Con javascript_first_mode=false:

    • Comportamiento legacy sin cambios
    • is_user_logged_in() se evalua en PHP
  3. Otras exclusiones (categoria, ID, URL) funcionan igual en ambos modos

  4. No hay regresion en visibilidad por tipo de pagina

  5. Los slots ocultos por JS colapsan completamente (height: 0)


Migration Notes

  • NO hay breaking changes: shouldShow() mantiene comportamiento actual
  • Nuevo metodo opcional: shouldShowForCache() para modo JS-First
  • Backward compatible: Si javascript_first_mode=false, todo funciona igual

Version History

Version Date Changes
1.0 2025-12-11 Initial spec