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:
FrankZamora
2026-01-08 15:30:45 -06:00
parent 0d6b6db108
commit 0f6387ab46
58 changed files with 15364 additions and 1171 deletions

View 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 |