Files
roi-theme/openspec/changes/add-advanced-incontent-ads/specs/adsense-placement/spec.md
FrankZamora c2fff49961 docs(config): add advanced incontent ads specification
- 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>
2025-12-09 19:58:50 -06:00

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