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>
This commit is contained in:
309
_openspec/changes/adsense-cache-unified-visibility/spec.md
Normal file
309
_openspec/changes/adsense-cache-unified-visibility/spec.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# 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
|
||||
|
||||
```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
|
||||
|
||||
```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
|
||||
|
||||
```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
|
||||
|
||||
```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
|
||||
|
||||
```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 |
|
||||
Reference in New Issue
Block a user