
Si hace unos días veíamos cómo dividir entradas largas de WordPress en páginas sin afectar al SEO para mejorar los tiempos de carga, hoy añadiremos una función extra que permita encontrar y categorizar fácilmente esos artículos largos según el tipo de contenido, su extensión e idioma.
Cuando tienes solo unas pocas entradas largas es fácil tenerlas localizadas casi de memoria. La cosa se complica cuando son muchas y además tienen sus respectivas traducciones en otros idiomas. Este código pretende agilizar el proceso de selección a la hora de dividir los post (con el bloque de Gutenberg "Page Break" o "Salto de página) y que sepas rápido los que te faltan por dividir y si tus divisiones actuales son coherentes.
¿Qué hace exactamente el código?
El siguiente código añadido al functions.php de tu plantilla o tema hijo generará un shortcode, usando este shortcode en cualquier página o post mostrará una lista paginada (10 entradas por página) con todos los post que tienen más de 1500 palabras incluyendo los traducidos a otros idiomas. Funciona tanto con Polylang como si no lo usas.
Estos valores pueden cambiarse en // 2. Configuración (Eso sí, no aumentes mucho el número total de páginas a mostrar si no quieres que la consulta ralentice la respuesta). La idea es que uses el shortcode en páginas en borrador o privadas para no arriesgarte a que un golpe de visitas afecte al rendimiento y se coma la CPU de tu server si tienes recursos limitados).
Esta código te permitirá ver no solo cuántos breaks (divisiones) tiene un post (o si no tiene ninguno), sino también cómo están distribuidas las palabras entre las diferentes secciones mostrando cuántas palabras contiene cada división, lo que puede ser muy útil para evaluar si tu paginación actual es equilibrada.
Detección automática de tipos de contenido
Se añade una detección automática para 4 tipos de contenido, donde "break" significa una división cada x palabras. El cálculo está basado en la densidad de información y el límite máximo es configurable.
Técnico (700 palabras/break): Tutoriales, código
Narrativo (1200 palabras/break): Historias, relatos
Periodístico (900 palabras/break): Noticias, artículos
General (1000 palabras/break): Contenido estándar
Criterios de selección
Para los criterios del código he buscado consejos y referencias. Algunos parámetros los he ajustado a mi blog, pero esto siempre es elástico y se puede afinar para cada sitio, siempre se puede añadir alguna señal más para que la detección sea más precisa. Tómalo como un punto de partida y examina qué elementos usas con más frecuencia en tus artículos según su tipo o bien añade marcas propias que el código pueda identificar y alguna etiqueta de las que usas con más frecuencia.
Los criterios actuales para la detección son los siguientes:
A. Contenido Técnico (⚙️ 700 palabras/break)
- Categorías: tutorial, guía, técnico, código, programación
- Etiquetas: código, software, tecnología, dev
- Estructura:
- Bloques de código (``` o <pre> )
- Fórmulas matemáticas (múltiples $)
- Más de 2 fragmentos de código identificados
B. Contenido narrativo (📖 1200 palabras/break)
- Categorías: relato, historia, narrativa, literatura
- Etiquetas: cuento, novela, ficción, poesía
- Estructura:
- Diálogos (líneas que comienzan con — o ")
- Numeración de capítulos (\n1., \n2.)
- Más de 3 diálogos identificados
C. Contenido periodístico (📰 900 palabras/break)
- Categorías: noticia, actualidad, reportaje, artículo
- Etiquetas: prensa, entrevista, opinión
- Estructura:
- Subtítulos frecuentes (###, ####)
- Notas del editor (*Nota:)
- Más de 3 subtítulos identificados
D. Contenido general (✍️ 1000 palabras/break)
- Se aplica cuando no coincide con los criterios anteriores
- Valor por defecto para posts estándar
El código evalúa los artículos en el siguiente orden:
- Categorías del post
- Etiquetas del post
- Estructura del contenido
- Asigna "General" si no hay coincidencias
El número máximo de breaks sugeridos es de 5 por post.
Aspecto
El CSS está incluido en el código por comodidad y devuelve este aspecto a las fichas. Aunque la información que muestra es bastante explícita ahí lo tienes con las notas de cada elemento.
Puedes modificarlo para enlazar el título con la página de edición si te resulta más cómodo para acceder rápido o hacer cualquier otra mejora que se te ocurra.

Después del código puedes encontrar la descarga d otra función o plugin personalizado para mostrar una útil calculadora de párrafos por URL
Código
// ======================================================================
//Muestra una lista paginada de post largos (ordenados de mayor a menor número de palabras) con sugerencias para dividirlos en páginas según tipo de contenido
// Shortcode para análisis de Page Breaks en posts largos. Cortesía de /jrmora.com
// ======================================================================
function analizador_pagebreaks_completo() {
// 1. Protección contra ejecución en admin/AJAX
if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
return '';
}
// 2. Configuración (convertida a constante para mejor performance)
define('PB_UMBRAL_PALABRAS', 1500);
define('PB_POSTS_POR_PAGINA', 10);
$mostrar_todos_idiomas = true;
// 3. Función optimizada para detección de tipo de contenido
function detectar_tipo_contenido($post_id) {
static $cache = [];
if (isset($cache[$post_id])) {
return $cache[$post_id];
}
$post = get_post($post_id);
$content = strip_tags($post->post_content);
$result = ['tipo' => 'general', 'color' => '#f5f5f5', 'icono' => '✍️'];
// Verificar categorías
$categorias = wp_get_post_categories($post_id, ['fields' => 'slugs']);
$cats_lower = array_map('strtolower', $categorias);
if (array_intersect($cats_lower, ['tutorial', 'guia', 'tecnico', 'code', 'programacion'])) {
$result = ['tipo' => 'técnico', 'color' => '#e3f2fd', 'icono' => '⚙️'];
} elseif (array_intersect($cats_lower, ['relato', 'historia', 'narrativa', 'literatura'])) {
$result = ['tipo' => 'narrativo', 'color' => '#f3e5f5', 'icono' => '📖'];
} elseif (array_intersect($cats_lower, ['noticia', 'actualidad', 'reportaje', 'articulo'])) {
$result = ['tipo' => 'periodístico', 'color' => '#e8f5e9', 'icono' => '📰'];
} else {
// Verificar etiquetas solo si no se determinó por categorías
$etiquetas = wp_get_post_tags($post_id, ['fields' => 'slugs']);
$tags_lower = array_map('strtolower', $etiquetas);
if (array_intersect($tags_lower, ['codigo', 'software', 'tecnologia', 'dev'])) {
$result = ['tipo' => 'técnico', 'color' => '#e3f2fd', 'icono' => '⚙️'];
} elseif (array_intersect($tags_lower, ['cuento', 'novela', 'ficcion', 'poesia'])) {
$result = ['tipo' => 'narrativo', 'color' => '#f3e5f5', 'icono' => '📖'];
} else {
// Verificar estructura solo como último recurso
if (preg_match_all('/```|<pre>|<code>/i', $content) > 2) {
$result = ['tipo' => 'técnico', 'color' => '#e3f2fd', 'icono' => '⚙️'];
} elseif (preg_match_all('/\n—|\n"|\n\d+\. /i', $content) > 3) {
$result = ['tipo' => 'narrativo', 'color' => '#f3e5f5', 'icono' => '📖'];
} elseif (preg_match_all('/\n### |\n#### |\*Nota:/i', $content) > 3) {
$result = ['tipo' => 'periodístico', 'color' => '#e8f5e9', 'icono' => '📰'];
}
}
}
$cache[$post_id] = $result;
return $result;
}
// 4. Cálculo de breaks por tipo (optimizado con array estático)
function calcular_breaks_por_tipo($palabras, $tipo) {
static $config = [
'técnico' => 700,
'narrativo' => 1200,
'periodístico' => 900,
'general' => 1000
];
$breaks = max(1, floor($palabras / ($config[$tipo] ?? 1000)) - 1);
return min($breaks, 5);
}
// 5. Función optimizada para calcular distribución de palabras
function calcular_distribucion_palabras($content) {
static $cache = [];
$cache_key = md5($content);
if (isset($cache[$cache_key])) {
return $cache[$cache_key];
}
$secciones = explode('<!--nextpage-->', $content);
$distribucion = [];
foreach ($secciones as $seccion) {
$distribucion[] = str_word_count(strip_tags($seccion));
}
$cache[$cache_key] = $distribucion;
return $distribucion;
}
// 6. Función optimizada para obtener IDs de posts
function obtener_ids_posts_a_procesar($mostrar_todos_idiomas) {
static $cache_key = 'pb_post_ids_cache';
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
$args = [
'post_type' => 'post',
'posts_per_page' => -1,
'fields' => 'ids',
'post_status' => 'publish',
'no_found_rows' => true,
'update_post_term_cache' => false,
'update_post_meta_cache' => false
];
if ($mostrar_todos_idiomas && function_exists('pll_get_post_translations')) {
$query = new WP_Query($args);
$all_post_ids = [];
foreach ($query->posts as $post_id) {
$translations = pll_get_post_translations($post_id);
$all_post_ids = array_merge($all_post_ids, array_values($translations));
}
$result = array_unique($all_post_ids);
} else {
$query = new WP_Query($args);
$result = $query->posts;
}
set_transient($cache_key, $result, HOUR_IN_SECONDS);
return $result;
}
// 7. Función optimizada para procesar posts
function procesar_post_con_tipo($post_id, $umbral, &$posts) {
static $post_cache = [];
if (isset($post_cache[$post_id])) {
if ($post_cache[$post_id]['palabras'] >= $umbral) {
$posts[] = $post_cache[$post_id];
}
return;
}
$post = get_post($post_id);
if (!$post) return;
$content = $post->post_content;
$word_count = str_word_count(strip_tags($content));
if ($word_count >= $umbral) {
$tipo = detectar_tipo_contenido($post_id);
$idioma = function_exists('pll_get_post_language') ? (pll_get_post_language($post_id) ?: 'es') : 'es';
$post_data = [
'ID' => $post_id,
'titulo' => $post->post_title,
'palabras' => $word_count,
'contenido' => $content,
'enlace' => get_permalink($post_id),
'tiene_break' => (strpos($content, '<!--nextpage-->') !== false),
'breaks_actuales' => substr_count($content, '<!--nextpage-->'),
'idioma' => $idioma,
'tipo' => $tipo
];
$posts[] = $post_data;
$post_cache[$post_id] = $post_data;
}
}
// 8. Obtener y procesar posts
$posts_procesados = [];
$post_ids = obtener_ids_posts_a_procesar($mostrar_todos_idiomas);
foreach ($post_ids as $post_id) {
procesar_post_con_tipo($post_id, PB_UMBRAL_PALABRAS, $posts_procesados);
}
// 9. Ordenar por palabras (mayor a menor)
usort($posts_procesados, function($a, $b) {
return $b['palabras'] - $a['palabras'];
});
// 10. Paginación manual
$paged = max(1, get_query_var('paged'));
$total_posts = count($posts_procesados);
$total_paginas = ceil($total_posts / PB_POSTS_POR_PAGINA);
$offset = ($paged - 1) * PB_POSTS_POR_PAGINA;
$posts_paginados = array_slice($posts_procesados, $offset, PB_POSTS_POR_PAGINA);
// CSS (incluido al final para mejor renderizado)
$output = '<style>.pb-ultimate-container{max-width:800px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}.pb-ultimate-item{background:#fff;border-radius:8px;padding:20px;margin-bottom:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08);border:1px solid #e0e0e0}.pb-ultimate-title{font-size:1.25rem;margin:0 0 10px 0;line-height:1.4}.pb-ultimate-meta{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:12px;font-size:0.85rem}.pb-ultimate-meta-item{display:inline-flex;align-items:center;gap:4px}.pb-ultimate-type{padding:2px 10px;border-radius:10px;font-size:0.75rem;font-weight:500}.pb-ultimate-suggestion{background:#f5f5f5;border-left:3px solid #64b5f6;padding:10px 15px;margin-top:15px;border-radius:0 4px 4px 0;font-size:0.9rem}.pb-ultimate-pagination{display:flex;justify-content:center;margin:30px 0;flex-wrap:wrap;gap:8px}.pb-ultimate-page{padding:8px 16px;border-radius:4px;text-decoration:none}.pb-ultimate-page-number{border:1px solid #e0e0e0;color:#1976d2}.pb-ultimate-page-number:hover{background:#e3f2fd}.pb-ultimate-page-current{background:#1976d2;color:white;border:1px solid #1976d2}.pb-ultimate-wordcount{font-weight:600;color:#212121}.pb-ultimate-language{background:#e3f2fd;color:#1565c0;padding:2px 10px;border-radius:10px;font-size:0.75rem}.pb-ultimate-warning{color:#d32f2f;font-weight:500}.pb-ultimate-distribution{margin-left:5px;font-size:0.95em;font-weight:600;color:#444;background:#f8f8f8;padding:2px 6px;border-radius:4px;border-left:2px solid #64b5f6}.pb-ultimate-distribution-separator{color:#999;font-weight:normal;margin:0 3px}</style>';
$output .= '<div class="pb-ultimate-container">';
if (!empty($posts_procesados)) {
foreach ($posts_paginados as $post) {
$breaks_sugeridos = calcular_breaks_por_tipo($post['palabras'], $post['tipo']['tipo']);
$palabras_por_seccion = ceil($post['palabras'] / ($breaks_sugeridos + 1));
$output .= '<div class="pb-ultimate-item">';
$output .= '<h3 class="pb-ultimate-title"><a href="' . esc_url($post['enlace']) . '">' . esc_html($post['titulo']) . '</a></h3>';
$output .= '<div class="pb-ultimate-meta">';
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-wordcount">📝 ' . number_format($post['palabras']) . ' palabras</span>';
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-type" style="background:' . $post['tipo']['color'] . '">';
$output .= $post['tipo']['icono'] . ' ' . ucfirst($post['tipo']['tipo']);
$output .= '</span>';
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-language">' . strtoupper($post['idioma']) . '</span>';
if ($post['tiene_break']) {
$distribucion = calcular_distribucion_palabras($post['contenido']);
$distribucion_text = '';
if (!empty($distribucion)) {
$distribucion_text = '<span class="pb-ultimate-distribution">(';
$distribucion_parts = [];
foreach ($distribucion as $index => $palabras) {
$distribucion_parts[] = ($index + 1) . 'º: ' . number_format($palabras);
}
$distribucion_text .= implode('<span class="pb-ultimate-distribution-separator"> · </span>', $distribucion_parts) . ')</span>';
}
$output .= '<span class="pb-ultimate-meta-item">✅ ' . $post['breaks_actuales'] . ' breaks ';
$output .= $distribucion_text;
$output .= '</span>';
} else {
$output .= '<span class="pb-ultimate-meta-item pb-ultimate-warning">⚠️ SIN PAGINAR</span>';
}
$output .= '</div>';
$output .= '<div class="pb-ultimate-suggestion">';
$output .= '<strong>Sugerencia:</strong> ' . $breaks_sugeridos . ' breaks (1 cada ~' . number_format($palabras_por_seccion) . ' palabras)';
$output .= '</div>';
$output .= '</div>';
}
// Paginación
if ($total_paginas > 1) {
$output .= '<div class="pb-ultimate-pagination">';
$output .= paginate_links([
'base' => str_replace(999999999, '%#%', esc_url(get_pagenum_link(999999999))),
'format' => '?paged=%#%',
'current' => $paged,
'total' => $total_paginas,
'prev_text' => __('«'),
'next_text' => __('»'),
'mid_size' => 1
]);
$output .= '</div>';
}
} else {
$output .= '<div class="pb-ultimate-item">No se encontraron posts que requieran paginación.</div>';
}
return $output . '</div>';
}
add_shortcode('analizador_pagebreaks_ultimate', 'analizador_pagebreaks_completo');
Extra, calculador/analizador de párrafos
Como herramienta complementaria aquí tienes el código para descargar de una calculadora de párrafos que estoy probando y que muestra los párrafos donde puedes dividir por post o página (hasta 5 divisiones) ingresando la URL de cualquier post de tu blog. Aún está por refinar para ajustar algunos contadores.
El margen de error en el conteo de palabras está entre 1-3%
Se puede usar como plugin o añadiendo el código al functions.php de tu plantilla. Se aconseja no usar en sitios en producción sin probarlo antes en un staging y tomárselo como un código en pruebas de usar y tirar.
Descargar 📦calculador_parrafos.zip
Importante, se trata de un código experimental temporal sin depurar. No me responsabilizo de posibles errores en tu entorno. No lo uses en sitios en producción. No pienso actualizarlo porque dejaré de usarlo cuando haya dividido todos mis post largos.
Tiene este aspecto.

Y así muestra los resultados:

🔹 FUNCIONES PRINCIPALES:
- Analiza artículos y sugiere puntos óptimos para dividir contenido
- Excluye automáticamente elementos no relevantes (sidebars, footers, etc.)
- Proporciona métricas detalladas de estructura de contenido
🔹 CARACTERÍSTICAS CLAVE:
- Shortcode:
[calculador_parrafos]
- Interfaz amigable con AJAX
- Detección semántica de párrafos válidos
- Exclusión de 10 tipos de elementos no deseados
- Sistema de análisis con DOMDocument + XPath
- Método alternativo para contenido sin etiquetas
- Estadísticas completas (palabras, longitud, distribución)
🔹 DATOS QUE PROPORCIONA:
✔ Total de párrafos válidos
✔ Sugerencias de división con:
- Posición numérica y porcentual
- Fragmento del párrafo
- Número de palabras y caracteres
- Recomendación de ubicación
✔ Diagnóstico técnico completo: - Tiempos de ejecución
- Uso de memoria
- Elementos excluidos
- Versiones de sistema
🔹 USO TÍPICO:
- Insertar shortcode en página/post
- Ingresar URL del artículo a analizar
- Seleccionar número de divisiones deseadas
- Revisar sugerencias y datos técnicos
🔹 VENTAJAS:
✅ Precisión en detección de párrafos reales
✅ Exclusión inteligente de contenido no relevante
✅ Interfaz profesional con visualización detallada
✅ Recomendaciones basadas en estructura semántica
✅ Compatible con la mayoría de temas WordPress
🔹 NOTAS:
- Requiere jQuery
- Óptimo para contenido narrativo/estructurado
- Incluye sistema de manejo de errores
- Tiempo de análisis típico: 500-1500ms