- proposal.md: define problem and expected changes - design.md: 9 technical decisions with rationale - spec.md: complete GIVEN/WHEN/THEN scenarios - tasks.md: implementation tasks with dependencies Features specified: - 7 ad insertion locations (H2, H3, p, img, lists, blockquotes, tables) - 5 density modes (legacy, conservative, balanced, aggressive, custom) - 2 selection strategies (position vs priority) - Deterministic probability with daily seed - Backward compatibility with legacy fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
691 lines
27 KiB
Markdown
691 lines
27 KiB
Markdown
# Especificacion: AdSense Placement - In-Content Ads Avanzados
|
|
|
|
## Definiciones Tecnicas
|
|
|
|
### Definicion: Elemento de Bloque Contable
|
|
|
|
Un "elemento" para efectos de espaciado y conteo se define como el tag de cierre de los siguientes elementos de bloque principal:
|
|
|
|
| Tag | Cuenta como elemento | Notas |
|
|
|-----|---------------------|-------|
|
|
| `</p>` | SI | Parrafo |
|
|
| `</h2>` | SI | Encabezado nivel 2 |
|
|
| `</h3>` | SI | Encabezado nivel 3 |
|
|
| `</figure>` | SI | Contenedor de imagen con caption |
|
|
| `</ul>` | SI | Lista desordenada (el contenedor, no los `<li>`) |
|
|
| `</ol>` | SI | Lista ordenada (el contenedor, no los `<li>`) |
|
|
| `</table>` | SI | Tabla (el contenedor, no `<tr>` ni `<td>`) |
|
|
| `</blockquote>` | SI | Cita en bloque |
|
|
| `<img>` | SOLO si no esta dentro de `<figure>` | Imagen standalone |
|
|
| `</li>` | NO | Items de lista no cuentan individualmente |
|
|
| `</tr>`, `</td>` | NO | Elementos internos de tabla no cuentan |
|
|
| `</div>` | NO | Divs genericos no cuentan |
|
|
|
|
### Definicion: Prioridades de Ubicacion (Corregidas)
|
|
|
|
| Tipo de Ubicacion | Prioridad | Justificacion |
|
|
|-------------------|-----------|---------------|
|
|
| Despues de H2 | 10 | Ruptura tematica mayor, alta visibilidad |
|
|
| Despues de parrafos | 8 | Ubicacion tradicional, probada |
|
|
| Despues de H3 | 7 | Ruptura tematica menor |
|
|
| Despues de imagenes/figure | 6 | Pausa visual natural |
|
|
| Despues de listas | 5 | Fin de enumeracion |
|
|
| Despues de blockquotes | 4 | Fin de cita |
|
|
| Despues de tablas | 3 | Fin de datos tabulares |
|
|
|
|
---
|
|
|
|
## ADDED Requirements
|
|
|
|
### Requirement: Algoritmo de Insercion de Anuncios
|
|
|
|
El sistema DEBE seguir un algoritmo determinista para insertar anuncios in-content.
|
|
|
|
#### Scenario: Algoritmo completo de insercion
|
|
- **GIVEN** contenido HTML con multiples elementos de bloque
|
|
- **WHEN** se procesa el contenido para insertar ads
|
|
- **THEN** el sistema DEBE ejecutar los siguientes pasos en orden:
|
|
|
|
```
|
|
PASO 0: PRECONDICION - VALIDAR LONGITUD MINIMA
|
|
- Obtener min_content_length del grupo forms (default: 500)
|
|
- Si strlen(strip_tags($content)) < min_content_length:
|
|
- Retornar contenido sin modificar
|
|
- NO ejecutar pasos siguientes
|
|
- Esta validacion aplica a TODOS los modos (legacy y nuevo)
|
|
|
|
PASO 1: ESCANEO
|
|
- Parsear contenido usando regex: preg_split('/(<\/(?:p|h[2-3]|figure|ul|ol|table|blockquote)>)/i', ...)
|
|
- Para cada tag de cierre, registrar: {posicion, tipo, indice_elemento}
|
|
- Detectar <img> standalone usando logica de dos pasos:
|
|
1. Encontrar todos los <img> con: preg_match_all('/<img[^>]*>/i', $content, $imgs, PREG_OFFSET_CAPTURE)
|
|
2. Para cada <img> encontrado:
|
|
- Buscar si existe <figure> abierto antes sin cerrar
|
|
- Si NO hay <figure> abierto: registrar como ubicacion elegible tipo "image"
|
|
- Si SI hay <figure> abierto: ignorar (se contara con </figure>)
|
|
|
|
PASO 2: FILTRADO POR CONFIGURACION
|
|
- Eliminar ubicaciones cuyo tipo tenga enabled=false
|
|
- Ejemplo: si incontent_after_h3_enabled=false, eliminar todas las ubicaciones </h3>
|
|
|
|
PASO 3: APLICAR PROBABILIDAD DETERMINISTICA
|
|
- Calcular seed: crc32(post_id . date('Y-m-d'))
|
|
- Inicializar: mt_srand(seed)
|
|
- Para cada ubicacion restante:
|
|
- Si mt_rand(1, 100) > probabilidad_del_tipo: eliminar ubicacion
|
|
- Esto garantiza consistencia durante el mismo dia para cache
|
|
|
|
PASO 4-5: FILTRADO Y SELECCION (segun incontent_priority_mode)
|
|
|
|
SI incontent_priority_mode == "position" (default):
|
|
PASO 4: FILTRAR POR ESPACIADO (orden DOM)
|
|
- Ordenar ubicaciones por posicion en DOM
|
|
- Iterar secuencialmente:
|
|
- Si distancia a ubicacion anterior < min_spacing: eliminar ubicacion
|
|
- La distancia se mide en cantidad de elementos de bloque entre ambas
|
|
|
|
PASO 5: APLICAR LIMITE
|
|
- Si cantidad de ubicaciones > max_total_ads:
|
|
- Ordenar por prioridad descendente (H2=10 primero)
|
|
- Tomar las primeras max_total_ads ubicaciones
|
|
- Reordenar por posicion en DOM
|
|
|
|
SI incontent_priority_mode == "priority":
|
|
PASO 4: ORDENAR POR PRIORIDAD
|
|
- Ordenar ubicaciones por prioridad DESC (H2=10 primero, luego p=8, etc.)
|
|
|
|
PASO 5: FILTRAR POR ESPACIADO (orden de prioridad)
|
|
- Iterar en orden de prioridad (no DOM):
|
|
- Si la ubicacion viola min_spacing con alguna ya seleccionada: eliminar
|
|
- Si ya tenemos max_total_ads: parar
|
|
- Reordenar resultado final por posicion DOM
|
|
|
|
PASO 6: INSERCION
|
|
- Para cada ubicacion final, insertar HTML del ad despues del tag de cierre
|
|
- El ad usa el formato configurado (in-article, auto, etc.)
|
|
```
|
|
|
|
#### Scenario: Seed deterministico para cache
|
|
- **GIVEN** un post con ID 12345 visitado multiples veces el mismo dia
|
|
- **WHEN** se calculan las posiciones de ads
|
|
- **THEN** las posiciones DEBEN ser identicas en todas las visitas del mismo dia
|
|
- **AND** al dia siguiente las posiciones pueden cambiar (nuevo seed)
|
|
|
|
#### Scenario: Validacion de longitud minima de contenido
|
|
- **GIVEN** `min_content_length` = 500 (campo del grupo `forms`)
|
|
- **AND** el contenido tiene 300 caracteres (sin tags HTML)
|
|
- **WHEN** se invoca el algoritmo de insercion
|
|
- **THEN** NO se ejecuta ningun paso del algoritmo
|
|
- **AND** se retorna el contenido original sin modificar
|
|
- **AND** esta validacion aplica tanto a modo "legacy" como a modos nuevos
|
|
|
|
#### Scenario: Resolucion de conflictos de posicion
|
|
- **GIVEN** un H2 seguido inmediatamente por un parrafo
|
|
- **WHEN** ambos tipos estan habilitados
|
|
- **AND** el espaciado minimo es 1
|
|
- **THEN** solo se inserta ad en la ubicacion de mayor prioridad (H2)
|
|
- **AND** el parrafo se cuenta para el espaciado pero no recibe ad
|
|
|
|
---
|
|
|
|
### Requirement: Ubicaciones de insercion por tipo de elemento
|
|
|
|
El sistema DEBE permitir insertar anuncios despues de diferentes tipos de elementos HTML estructurales, cada uno con configuracion independiente.
|
|
|
|
#### Scenario: Insercion despues de encabezados H2
|
|
- **WHEN** el campo `incontent_after_h2_enabled` es true
|
|
- **AND** el contenido tiene elementos `</h2>`
|
|
- **THEN** el sistema DEBE registrar cada `</h2>` como ubicacion elegible
|
|
- **AND** la probabilidad de insercion es controlada por `incontent_after_h2_probability`
|
|
|
|
#### Scenario: Insercion despues de parrafos
|
|
- **WHEN** el campo `incontent_after_paragraphs_enabled` es true
|
|
- **AND** el contenido tiene elementos `</p>`
|
|
- **THEN** el sistema DEBE registrar cada `</p>` como ubicacion elegible
|
|
- **AND** la probabilidad de insercion es controlada por `incontent_after_paragraphs_probability`
|
|
- **AND** esta es la ubicacion "tradicional" del sistema legacy
|
|
|
|
#### Scenario: Insercion despues de encabezados H3
|
|
- **WHEN** el campo `incontent_after_h3_enabled` es true
|
|
- **AND** el contenido tiene elementos `</h3>`
|
|
- **THEN** el sistema DEBE registrar cada `</h3>` como ubicacion elegible
|
|
- **AND** la probabilidad de insercion es controlada por `incontent_after_h3_probability`
|
|
|
|
#### Scenario: Insercion despues de imagenes
|
|
- **WHEN** el campo `incontent_after_images_enabled` es true
|
|
- **AND** el contenido tiene elementos `</figure>` o `<img>` standalone
|
|
- **THEN** el sistema DEBE registrar como ubicacion elegible
|
|
- **AND** si `<img>` esta dentro de `<figure>`, solo cuenta `</figure>` (no duplicar)
|
|
- **AND** la probabilidad es controlada por `incontent_after_images_probability`
|
|
|
|
#### Scenario: Insercion despues de listas
|
|
- **WHEN** el campo `incontent_after_lists_enabled` es true
|
|
- **AND** el contenido tiene elementos `</ul>` o `</ol>`
|
|
- **THEN** el sistema DEBE registrar cada cierre de lista como ubicacion elegible
|
|
- **AND** NO se insertara ad si la lista tiene menos de 3 `<li>` directos
|
|
- **AND** la probabilidad es controlada por `incontent_after_lists_probability`
|
|
|
|
#### Scenario: Conteo de items en listas anidadas
|
|
- **GIVEN** el siguiente contenido:
|
|
```html
|
|
<ul>
|
|
<li>Item 1</li>
|
|
<li>Item 2
|
|
<ul>
|
|
<li>Sub-item A</li>
|
|
<li>Sub-item B</li>
|
|
</ul>
|
|
</li>
|
|
<li>Item 3</li>
|
|
</ul>
|
|
```
|
|
- **WHEN** se evalua si la lista externa es elegible para ad
|
|
- **THEN** solo se cuentan los `<li>` directos (hijos inmediatos): 3
|
|
- **AND** los `<li>` de la lista anidada NO se cuentan para la lista padre
|
|
- **AND** la lista anidada se evalua por separado (tiene 2 items, no elegible)
|
|
- **NOTA IMPLEMENTACION**: Usar `substr_count($list_content, '<li')` para contar items. Limitacion conocida: listas anidadas contaran todos los `<li>`. Esto es aceptable para v1 ya que listas anidadas son infrecuentes en el contenido del sitio.
|
|
|
|
#### Scenario: Todos los tipos deshabilitados
|
|
- **GIVEN** todos los campos `*_enabled` son false (H2, H3, paragraphs, images, lists, blockquotes, tables)
|
|
- **WHEN** se ejecuta el algoritmo de insercion
|
|
- **THEN** PASO 2 elimina todas las ubicaciones candidatas
|
|
- **AND** se retorna el contenido sin modificar
|
|
- **AND** no se insertan anuncios
|
|
|
|
#### Scenario: Insercion despues de blockquotes
|
|
- **WHEN** el campo `incontent_after_blockquotes_enabled` es true
|
|
- **AND** el contenido tiene elementos `</blockquote>`
|
|
- **THEN** el sistema DEBE registrar cada `</blockquote>` como ubicacion elegible
|
|
- **AND** la probabilidad es controlada por `incontent_after_blockquotes_probability`
|
|
|
|
#### Scenario: Insercion despues de tablas
|
|
- **WHEN** el campo `incontent_after_tables_enabled` es true
|
|
- **AND** el contenido tiene elementos `</table>`
|
|
- **THEN** el sistema DEBE registrar cada `</table>` como ubicacion elegible
|
|
- **AND** la probabilidad es controlada por `incontent_after_tables_probability`
|
|
|
|
---
|
|
|
|
### Requirement: Modos de densidad de anuncios
|
|
|
|
El sistema DEBE ofrecer modos predefinidos que controlan la cantidad y espaciado de anuncios.
|
|
|
|
#### Scenario: Modo legacy (backward compatibility)
|
|
- **WHEN** `incontent_mode` es "legacy"
|
|
- **THEN** el sistema NO usa el grupo `incontent_advanced` para el algoritmo
|
|
- **AND** usa los campos del grupo `behavior` (post_content_*)
|
|
- **AND** ejecuta la logica de insercion anterior (solo despues de parrafos)
|
|
- **AND** los campos de `incontent_advanced` se muestran deshabilitados en el UI
|
|
- **AND** se muestra banner: "Usando configuracion legacy. Migra al nuevo sistema para mas opciones."
|
|
|
|
#### Scenario: Modo conservador
|
|
- **WHEN** `incontent_mode` es "conservative"
|
|
- **THEN** el maximo de ads in-content es 5
|
|
- **AND** el espaciado minimo entre ads es 5 elementos
|
|
- **AND** solo se activan ubicaciones despues de H2 y parrafos por defecto
|
|
|
|
#### Scenario: Modo balanceado
|
|
- **WHEN** `incontent_mode` es "balanced"
|
|
- **THEN** el maximo de ads in-content es 8
|
|
- **AND** el espaciado minimo entre ads es 3 elementos
|
|
- **AND** se activan H2, H3, imagenes y parrafos por defecto
|
|
|
|
#### Scenario: Modo agresivo
|
|
- **WHEN** `incontent_mode` es "aggressive"
|
|
- **THEN** el maximo de ads in-content es 15
|
|
- **AND** el espaciado minimo entre ads es 2 elementos
|
|
- **AND** se activan todas las ubicaciones por defecto
|
|
|
|
#### Scenario: Modo personalizado
|
|
- **WHEN** `incontent_mode` es "custom"
|
|
- **THEN** el usuario puede configurar cada campo individualmente
|
|
- **AND** los valores de max/spacing no se sobreescriben al cambiar de modo
|
|
|
|
#### Scenario: Modificacion de campos en modo preset (auto-switch a custom)
|
|
- **GIVEN** `incontent_mode` es "balanced" (o cualquier preset excepto "custom" y "legacy")
|
|
- **WHEN** el usuario modifica manualmente cualquiera de estos campos:
|
|
- `incontent_max_total_ads`
|
|
- `incontent_min_spacing`
|
|
- Cualquier campo `*_enabled` o `*_probability`
|
|
- **THEN** el sistema DEBE cambiar automaticamente `incontent_mode` a "custom"
|
|
- **AND** mostrar mensaje informativo: "Modo cambiado a Personalizado"
|
|
- **AND** los valores modificados se preservan
|
|
|
|
#### Scenario: Cambio de modo preset sobreescribe valores
|
|
- **GIVEN** `incontent_mode` es "custom" con valores personalizados
|
|
- **WHEN** el usuario cambia `incontent_mode` a "balanced"
|
|
- **THEN** los valores de max_total_ads, min_spacing y flags enabled se sobreescriben con los del preset
|
|
- **AND** se muestra confirmacion antes de aplicar el cambio
|
|
|
|
---
|
|
|
|
### Requirement: Espaciado minimo entre anuncios
|
|
|
|
El sistema DEBE mantener un espaciado minimo entre cualquier par de anuncios in-content.
|
|
|
|
#### Scenario: Calculo de espaciado
|
|
- **GIVEN** dos ubicaciones candidatas para ads
|
|
- **WHEN** se evalua el espaciado entre ellas
|
|
- **THEN** el espaciado se calcula contando elementos de bloque entre ambas posiciones
|
|
- **AND** solo se cuentan los elementos definidos en "Elemento de Bloque Contable"
|
|
|
|
#### Scenario: Ejemplo de calculo de espaciado
|
|
- **GIVEN** el siguiente contenido:
|
|
```html
|
|
<h2>Titulo</h2> <!-- Ubicacion A (posible ad) -->
|
|
<p>Parrafo 1</p> <!-- Elemento 1 -->
|
|
<p>Parrafo 2</p> <!-- Elemento 2 -->
|
|
<figure><img></figure> <!-- Elemento 3 -->
|
|
<p>Parrafo 3</p> <!-- Ubicacion B (posible ad) - Elemento 4 -->
|
|
```
|
|
- **WHEN** min_spacing es 3
|
|
- **THEN** la distancia entre A y B es 4 elementos
|
|
- **AND** ambas ubicaciones pueden tener ads (4 >= 3)
|
|
|
|
#### Scenario: Espaciado insuficiente
|
|
- **GIVEN** min_spacing es 5
|
|
- **AND** solo hay 3 elementos entre dos ubicaciones candidatas
|
|
- **THEN** la segunda ubicacion se elimina de las candidatas
|
|
- **AND** se conserva la de mayor prioridad
|
|
|
|
---
|
|
|
|
### Requirement: Estrategia de seleccion configurable
|
|
|
|
El sistema DEBE permitir elegir como resolver conflictos cuando dos ubicaciones estan muy cerca.
|
|
|
|
#### Scenario: Modo position (default)
|
|
- **WHEN** `incontent_priority_mode` es "position"
|
|
- **AND** hay un H2 (prioridad 10) en posicion 3 y un parrafo (prioridad 8) en posicion 1
|
|
- **AND** min_spacing es 3
|
|
- **THEN** el parrafo en posicion 1 se selecciona primero (por orden DOM)
|
|
- **AND** el H2 en posicion 3 se elimina por violar espaciado (distancia 2 < 3)
|
|
- **AND** el resultado favorece distribucion uniforme
|
|
|
|
#### Scenario: Modo priority
|
|
- **WHEN** `incontent_priority_mode` es "priority"
|
|
- **AND** hay un H2 (prioridad 10) en posicion 3 y un parrafo (prioridad 8) en posicion 1
|
|
- **AND** min_spacing es 3
|
|
- **THEN** el H2 se selecciona primero (por mayor prioridad)
|
|
- **AND** el parrafo se elimina por violar espaciado con el H2
|
|
- **AND** el resultado maximiza ubicaciones de alto valor
|
|
|
|
---
|
|
|
|
### Requirement: Probabilidad deterministica por ubicacion
|
|
|
|
El sistema DEBE soportar probabilidad configurable que sea consistente durante el dia.
|
|
|
|
#### Scenario: Implementacion de probabilidad deterministica
|
|
- **GIVEN** un post_id y una fecha
|
|
- **WHEN** se calcula si insertar ad en una ubicacion
|
|
- **THEN** el seed es `crc32(post_id . 'YYYY-MM-DD')`
|
|
- **AND** se usa `mt_srand(seed)` antes de evaluar probabilidades
|
|
- **AND** cada ubicacion consume un `mt_rand(1, 100)` en orden de aparicion
|
|
|
|
#### Scenario: Valores de probabilidad disponibles
|
|
- **WHEN** el usuario configura probabilidad para cualquier tipo
|
|
- **THEN** los valores disponibles son: 25, 50, 75, 100
|
|
- **AND** el valor se interpreta como porcentaje
|
|
|
|
---
|
|
|
|
## MODIFIED Requirements
|
|
|
|
### Requirement: Estrategia de campos legacy
|
|
|
|
Los campos existentes del grupo `behavior` relacionados con in-content ads DEBEN coexistir con el nuevo sistema mediante deprecacion suave.
|
|
|
|
#### Scenario: Campos legacy a deprecar
|
|
- **GIVEN** los siguientes campos existentes:
|
|
- `post_content_enabled` (behavior)
|
|
- `post_content_max_ads` (behavior)
|
|
- `post_content_after_paragraphs` (behavior)
|
|
- `post_content_min_paragraphs_between` (behavior)
|
|
- `post_content_random_mode` (behavior)
|
|
- `post_content_format` (behavior)
|
|
- **WHEN** el sistema tiene ambos grupos de campos
|
|
- **THEN** el grupo `incontent_advanced` tiene precedencia si `incontent_mode` != "legacy"
|
|
- **AND** si `incontent_mode` == "legacy", se usan los campos del grupo `behavior`
|
|
|
|
#### Scenario: Migracion automatica en UI
|
|
- **WHEN** el usuario visita el panel de AdSense por primera vez despues de la actualizacion
|
|
- **AND** tiene configuracion legacy activa (`post_content_enabled` = true)
|
|
- **THEN** se muestra un banner informativo sobre el nuevo sistema
|
|
- **AND** se ofrece boton "Migrar a nuevo sistema" que copia valores equivalentes
|
|
|
|
#### Scenario: Mapeo de campos legacy a nuevos
|
|
| Campo Legacy | Campo Nuevo | Logica de Mapeo |
|
|
|--------------|-------------|-----------------|
|
|
| post_content_max_ads | incontent_max_total_ads | Copia directa (ampliar opciones) |
|
|
| post_content_min_paragraphs_between | incontent_min_spacing | Copia directa |
|
|
| post_content_random_mode | N/A | Si true, todas las probabilidades = 75% |
|
|
| post_content_after_paragraphs | N/A | Se usa para primer ad, resto aleatorio |
|
|
|
|
---
|
|
|
|
### Requirement: UI de In-Content Ads reorganizada
|
|
|
|
La interfaz de administracion DEBE organizarse en subsecciones claras usando elementos HTML semanticos.
|
|
|
|
#### Scenario: Estructura HTML de la seccion
|
|
- **WHEN** se renderiza el card de In-Content Ads Avanzado
|
|
- **THEN** DEBE seguir esta estructura:
|
|
|
|
```html
|
|
<div class="card shadow-sm mb-3" style="border-left: 4px solid #0d6efd;">
|
|
<div class="card-body">
|
|
<h5 class="fw-bold mb-3">
|
|
<i class="bi bi-body-text me-2"></i>
|
|
In-Content Ads Avanzado
|
|
<span class="badge bg-success ms-2">Nuevo</span>
|
|
</h5>
|
|
|
|
<!-- Indicador de densidad -->
|
|
<div id="densityIndicator" class="alert alert-info small mb-3">
|
|
<i class="bi bi-speedometer2 me-1"></i>
|
|
Densidad estimada: <strong>Media</strong>
|
|
<span class="badge bg-warning">~6 ads</span>
|
|
</div>
|
|
|
|
<!-- Selector de modo -->
|
|
<div class="mb-4">
|
|
<label class="form-label fw-semibold">Modo de densidad</label>
|
|
<select class="form-select" id="incontentMode">
|
|
<option value="legacy">Legacy (usar config anterior)</option>
|
|
<option value="conservative">Conservador (max 5, espaciado 5)</option>
|
|
<option value="balanced" selected>Balanceado (max 8, espaciado 3)</option>
|
|
<option value="aggressive">Agresivo (max 15, espaciado 2)</option>
|
|
<option value="custom">Personalizado</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Subseccion: Ubicaciones por elemento -->
|
|
<details class="mb-3 border rounded" open>
|
|
<summary class="p-3 bg-light fw-bold cursor-pointer">
|
|
<i class="bi bi-geo-alt me-1"></i>
|
|
Ubicaciones por Elemento
|
|
</summary>
|
|
<div class="p-3">
|
|
<!-- Toggle + probabilidad para cada tipo -->
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<div class="form-check form-switch">
|
|
<input type="checkbox" class="form-check-input" id="afterH2Enabled" checked>
|
|
<label class="form-check-label">Despues de H2</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<select class="form-select form-select-sm" id="afterH2Prob">
|
|
<option value="100" selected>100%</option>
|
|
<option value="75">75%</option>
|
|
<option value="50">50%</option>
|
|
<option value="25">25%</option>
|
|
</select>
|
|
</div>
|
|
<!-- Repetir para H3, images, lists, blockquotes, tables -->
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Subseccion: Limites y espaciado -->
|
|
<details class="mb-3 border rounded">
|
|
<summary class="p-3 bg-light fw-bold cursor-pointer">
|
|
<i class="bi bi-sliders me-1"></i>
|
|
Limites y Espaciado
|
|
</summary>
|
|
<div class="p-3">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Maximo total de ads</label>
|
|
<select class="form-select" id="maxTotalAds">
|
|
<!-- Opciones 1-15 -->
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Espaciado minimo (elementos)</label>
|
|
<select class="form-select" id="minSpacing">
|
|
<option value="2">2 elementos</option>
|
|
<option value="3" selected>3 elementos</option>
|
|
<option value="4">4 elementos</option>
|
|
<option value="5">5 elementos</option>
|
|
<option value="6">6 elementos</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-12 mt-3">
|
|
<label class="form-label">Estrategia de seleccion</label>
|
|
<select class="form-select" id="priorityMode">
|
|
<option value="position" selected>Por posicion (distribucion uniforme)</option>
|
|
<option value="priority">Por prioridad (maximizar H2/H3)</option>
|
|
</select>
|
|
<small class="text-muted">Como resolver conflictos cuando dos ubicaciones estan muy cerca</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Warning para densidad alta -->
|
|
<div id="highDensityWarning" class="alert alert-warning small d-none">
|
|
<i class="bi bi-exclamation-triangle me-1"></i>
|
|
<strong>Atencion:</strong> Densidad alta puede afectar UX y violar politicas de AdSense.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
#### Scenario: Indicador de densidad dinamico
|
|
- **WHEN** el usuario modifica cualquier campo de in-content ads
|
|
- **THEN** el indicador de densidad se actualiza en tiempo real via JavaScript
|
|
- **AND** muestra estimacion basada en: max_ads * promedio_probabilidades / 100
|
|
- **AND** colores: verde (<5), amarillo (5-10), rojo (>10)
|
|
|
|
---
|
|
|
|
## Schema JSON - Campos Completos
|
|
|
|
### Grupo: incontent_advanced (priority 69)
|
|
|
|
```json
|
|
{
|
|
"incontent_advanced": {
|
|
"label": "In-Content Ads Avanzado",
|
|
"priority": 69,
|
|
"fields": {
|
|
"incontent_mode": {
|
|
"type": "select",
|
|
"label": "Modo de densidad",
|
|
"default": "legacy",
|
|
"editable": true,
|
|
"options": {
|
|
"legacy": "Legacy (config anterior)",
|
|
"conservative": "Conservador",
|
|
"balanced": "Balanceado",
|
|
"aggressive": "Agresivo",
|
|
"custom": "Personalizado"
|
|
},
|
|
"description": "Presets que ajustan limites y ubicaciones automaticamente. Default 'legacy' para backward compatibility."
|
|
},
|
|
"incontent_after_h2_enabled": {
|
|
"type": "boolean",
|
|
"label": "Despues de H2",
|
|
"default": true,
|
|
"editable": true,
|
|
"description": "Insertar anuncios despues de encabezados H2"
|
|
},
|
|
"incontent_after_h2_probability": {
|
|
"type": "select",
|
|
"label": "Probabilidad H2",
|
|
"default": "100",
|
|
"editable": true,
|
|
"options": ["25", "50", "75", "100"],
|
|
"description": "Porcentaje de probabilidad de insercion"
|
|
},
|
|
"incontent_after_h3_enabled": {
|
|
"type": "boolean",
|
|
"label": "Despues de H3",
|
|
"default": true,
|
|
"editable": true,
|
|
"description": "Insertar anuncios despues de encabezados H3"
|
|
},
|
|
"incontent_after_h3_probability": {
|
|
"type": "select",
|
|
"label": "Probabilidad H3",
|
|
"default": "50",
|
|
"editable": true,
|
|
"options": ["25", "50", "75", "100"]
|
|
},
|
|
"incontent_after_paragraphs_enabled": {
|
|
"type": "boolean",
|
|
"label": "Despues de parrafos",
|
|
"default": true,
|
|
"editable": true,
|
|
"description": "Insertar anuncios despues de parrafos (ubicacion tradicional)"
|
|
},
|
|
"incontent_after_paragraphs_probability": {
|
|
"type": "select",
|
|
"label": "Probabilidad parrafos",
|
|
"default": "75",
|
|
"editable": true,
|
|
"options": ["25", "50", "75", "100"],
|
|
"description": "Porcentaje de probabilidad de insercion despues de parrafos"
|
|
},
|
|
"incontent_after_images_enabled": {
|
|
"type": "boolean",
|
|
"label": "Despues de imagenes",
|
|
"default": true,
|
|
"editable": true,
|
|
"description": "Insertar anuncios despues de figure o img standalone"
|
|
},
|
|
"incontent_after_images_probability": {
|
|
"type": "select",
|
|
"label": "Probabilidad imagenes",
|
|
"default": "75",
|
|
"editable": true,
|
|
"options": ["25", "50", "75", "100"]
|
|
},
|
|
"incontent_after_lists_enabled": {
|
|
"type": "boolean",
|
|
"label": "Despues de listas",
|
|
"default": false,
|
|
"editable": true,
|
|
"description": "Insertar anuncios despues de ul/ol (minimo 3 items)"
|
|
},
|
|
"incontent_after_lists_probability": {
|
|
"type": "select",
|
|
"label": "Probabilidad listas",
|
|
"default": "50",
|
|
"editable": true,
|
|
"options": ["25", "50", "75", "100"]
|
|
},
|
|
"incontent_after_blockquotes_enabled": {
|
|
"type": "boolean",
|
|
"label": "Despues de blockquotes",
|
|
"default": false,
|
|
"editable": true,
|
|
"description": "Insertar anuncios despues de citas en bloque"
|
|
},
|
|
"incontent_after_blockquotes_probability": {
|
|
"type": "select",
|
|
"label": "Probabilidad blockquotes",
|
|
"default": "50",
|
|
"editable": true,
|
|
"options": ["25", "50", "75", "100"]
|
|
},
|
|
"incontent_after_tables_enabled": {
|
|
"type": "boolean",
|
|
"label": "Despues de tablas",
|
|
"default": false,
|
|
"editable": true,
|
|
"description": "Insertar anuncios despues de tablas"
|
|
},
|
|
"incontent_after_tables_probability": {
|
|
"type": "select",
|
|
"label": "Probabilidad tablas",
|
|
"default": "50",
|
|
"editable": true,
|
|
"options": ["25", "50", "75", "100"]
|
|
},
|
|
"incontent_max_total_ads": {
|
|
"type": "select",
|
|
"label": "Maximo total de ads",
|
|
"default": "8",
|
|
"editable": true,
|
|
"options": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"],
|
|
"description": "Cantidad maxima de anuncios in-content por post"
|
|
},
|
|
"incontent_min_spacing": {
|
|
"type": "select",
|
|
"label": "Espaciado minimo",
|
|
"default": "3",
|
|
"editable": true,
|
|
"options": {
|
|
"2": "2 elementos",
|
|
"3": "3 elementos",
|
|
"4": "4 elementos",
|
|
"5": "5 elementos",
|
|
"6": "6 elementos"
|
|
},
|
|
"description": "Minimo de elementos de bloque entre anuncios"
|
|
},
|
|
"incontent_format": {
|
|
"type": "select",
|
|
"label": "Formato de ads",
|
|
"default": "in-article",
|
|
"editable": true,
|
|
"options": {
|
|
"in-article": "In-Article (fluid)",
|
|
"auto": "Auto (responsive)"
|
|
},
|
|
"description": "Formato de anuncio para todas las ubicaciones in-content"
|
|
},
|
|
"incontent_priority_mode": {
|
|
"type": "select",
|
|
"label": "Estrategia de seleccion",
|
|
"default": "position",
|
|
"editable": true,
|
|
"options": {
|
|
"position": "Por posicion (distribucion uniforme)",
|
|
"priority": "Por prioridad (maximizar H2/H3)"
|
|
},
|
|
"description": "Como resolver conflictos cuando dos ubicaciones estan muy cerca"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Implementacion Tecnica
|
|
|
|
### Opcion de Parsing Recomendada: Regex Multiple
|
|
|
|
```php
|
|
// Regex para detectar todos los elementos de bloque contables (H4 no soportado)
|
|
$pattern = '/(<\/(?:p|h[2-3]|figure|ul|ol|table|blockquote)>)/i';
|
|
$parts = preg_split($pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE);
|
|
|
|
// Para detectar <img> standalone (no dentro de figure)
|
|
// Procesar en segundo paso, verificando contexto
|
|
```
|
|
|
|
**Justificacion:**
|
|
- Mas rapido que DOMDocument
|
|
- No modifica el HTML original
|
|
- Suficiente para el caso de uso (no necesitamos validar anidamiento complejo)
|
|
- El contexto de `<img>` dentro de `<figure>` se resuelve verificando si hay `<figure>` abierto sin cerrar
|
|
|
|
### Diagrama de Dependencias de Tasks
|
|
|
|
```
|
|
1.1 Schema: grupo incontent_advanced ─┐
|
|
1.2 Schema: campos individuales ──────┼──> 1.3 Sync BD ──┬──> 2.x FormBuilder
|
|
│ │
|
|
│ └──> 3.x Renderer
|
|
│ │
|
|
│ v
|
|
│ 4.x Validacion
|
|
│ │
|
|
└──────────────────────> 5.x Docs
|
|
```
|