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

27 KiB

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:
<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:
<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:
<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)

{
  "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

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