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

` | SI | Parrafo | | `` | SI | Encabezado nivel 2 | | `` | SI | Encabezado nivel 3 | | `` | SI | Contenedor de imagen con caption | | `` | SI | Lista desordenada (el contenedor, no los `
  • `) | | `` | SI | Lista ordenada (el contenedor, no los `
  • `) | | `` | SI | Tabla (el contenedor, no `` ni ``) | | `` | SI | Cita en bloque | | `` | SOLO si no esta dentro de `
    ` | Imagen standalone | | `
  • ` | NO | Items de lista no cuentan individualmente | | ``, `` | NO | Elementos internos de tabla no cuentan | | `` | 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 standalone usando logica de dos pasos: 1. Encontrar todos los con: preg_match_all('/]*>/i', $content, $imgs, PREG_OFFSET_CAPTURE) 2. Para cada encontrado: - Buscar si existe
    abierto antes sin cerrar - Si NO hay
    abierto: registrar como ubicacion elegible tipo "image" - Si SI hay
    abierto: ignorar (se contara con
    ) PASO 2: FILTRADO POR CONFIGURACION - Eliminar ubicaciones cuyo tipo tenga enabled=false - Ejemplo: si incontent_after_h3_enabled=false, eliminar todas las ubicaciones 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 `` - **THEN** el sistema DEBE registrar cada `` 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 `

    ` - **THEN** el sistema DEBE registrar cada `

    ` 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 `` - **THEN** el sistema DEBE registrar cada `` 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 `
    ` o `` standalone - **THEN** el sistema DEBE registrar como ubicacion elegible - **AND** si `` esta dentro de `
    `, solo cuenta `
    ` (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 `` o `` - **THEN** el sistema DEBE registrar cada cierre de lista como ubicacion elegible - **AND** NO se insertara ad si la lista tiene menos de 3 `
  • ` directos - **AND** la probabilidad es controlada por `incontent_after_lists_probability` #### Scenario: Conteo de items en listas anidadas - **GIVEN** el siguiente contenido: ```html
    • Item 1
    • Item 2
      • Sub-item A
      • Sub-item B
    • Item 3
    ``` - **WHEN** se evalua si la lista externa es elegible para ad - **THEN** solo se cuentan los `
  • ` directos (hijos inmediatos): 3 - **AND** los `
  • ` 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, '`. 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 `` - **THEN** el sistema DEBE registrar cada `` 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 `` - **THEN** el sistema DEBE registrar cada `` 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

    Titulo

    Parrafo 1

    Parrafo 2

    Parrafo 3

    ``` - **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
    In-Content Ads Avanzado Nuevo
    Densidad estimada: Media ~6 ads
    Ubicaciones por Elemento
    Limites y Espaciado
    Como resolver conflictos cuando dos ubicaciones estan muy cerca
    Atencion: Densidad alta puede afectar UX y violar politicas de AdSense.
    ``` #### 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 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 `` dentro de `
    ` se resuelve verificando si hay `
    ` 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 ```