PROBLEM: - apu-tables-auto-class.js was adding CSS classes to table rows after DOMContentLoaded - This DOM manipulation caused CLS of 0.692 in Lab Data (PageSpeed Insights) - body.wp-singular was the main culprit with 0.692 CLS contribution SOLUTION: - Added roi_add_apu_row_classes() function in Inc/apu-tables.php - This applies the same logic server-side during the_content filter - Classes (section-header, subtotal-row, total-row) are now in HTML from first render - Disabled the JS script in Inc/enqueue-scripts.php This should significantly reduce Lab Data CLS (target: < 0.1) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
427 lines
14 KiB
PHP
427 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* APU Tables Processing
|
|
* Funciones helper para tablas de Análisis de Precios Unitarios
|
|
*
|
|
* @package Apus_Theme
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Procesa automáticamente tablas APU en el contenido
|
|
*
|
|
* Detecta tablas con el atributo data-apu y las envuelve
|
|
* con la clase .analisis para aplicar estilos específicos.
|
|
*
|
|
* TAMBIÉN agrega clases a filas especiales (section-header, subtotal-row, total-row)
|
|
* del lado del servidor para PREVENIR CLS. Anteriormente esto se hacía con JS
|
|
* en apu-tables-auto-class.js, lo cual causaba CLS de 0.692 en Lab Data.
|
|
*
|
|
* @param string $content El contenido del post
|
|
* @return string El contenido procesado
|
|
*/
|
|
function roi_process_apu_tables($content) {
|
|
// Verificar que haya contenido
|
|
if (empty($content)) {
|
|
return $content;
|
|
}
|
|
|
|
// Patrón para detectar tablas con atributo data-apu
|
|
$pattern = '/<table([^>]*?)data-apu([^>]*?)>(.*?)<\/table>/is';
|
|
|
|
// Reemplazar cada tabla encontrada
|
|
$content = preg_replace_callback($pattern, function($matches) {
|
|
$before_attrs = $matches[1];
|
|
$after_attrs = $matches[2];
|
|
$table_content = $matches[3];
|
|
|
|
// Reconstruir la tabla sin el atributo data-apu
|
|
$table = '<table' . $before_attrs . $after_attrs . '>' . $table_content . '</table>';
|
|
|
|
// Envolver con div.analisis
|
|
return '<div class="analisis">' . $table . '</div>';
|
|
}, $content);
|
|
|
|
// Procesar TODAS las tablas APU (dentro de .analisis o .desglose) para agregar clases a filas
|
|
// Esto reemplaza la lógica de apu-tables-auto-class.js para prevenir CLS
|
|
$content = roi_add_apu_row_classes($content);
|
|
|
|
return $content;
|
|
}
|
|
add_filter('the_content', 'roi_process_apu_tables', 20);
|
|
|
|
/**
|
|
* Agrega clases CSS a filas especiales de tablas APU del lado del servidor
|
|
*
|
|
* IMPORTANTE: Esta función reemplaza apu-tables-auto-class.js para PREVENIR CLS.
|
|
* La manipulación del DOM con JS después de DOMContentLoaded causa Layout Shift.
|
|
*
|
|
* Detecta y clasifica:
|
|
* - section-header: Material, Mano de Obra, Herramienta, Equipo
|
|
* - subtotal-row: Filas que empiezan con "Suma de" o "Subtotal"
|
|
* - total-row: Costo Directo, Total
|
|
*
|
|
* @param string $content HTML del contenido
|
|
* @return string HTML con clases agregadas a filas
|
|
*/
|
|
function roi_add_apu_row_classes($content) {
|
|
// Solo procesar si hay tablas APU
|
|
if (strpos($content, 'class="analisis"') === false && strpos($content, 'class="desglose"') === false) {
|
|
return $content;
|
|
}
|
|
|
|
// Patrones para detectar tipos de fila (basado en contenido de segunda celda)
|
|
$section_headers = [
|
|
'Material',
|
|
'Mano de Obra',
|
|
'Herramienta',
|
|
'Equipo',
|
|
'MATERIAL',
|
|
'MANO DE OBRA',
|
|
'HERRAMIENTA',
|
|
'EQUIPO',
|
|
];
|
|
|
|
$total_patterns = [
|
|
'Costo Directo',
|
|
'COSTO DIRECTO',
|
|
'Costo directo',
|
|
'Total',
|
|
'TOTAL',
|
|
];
|
|
|
|
// Procesar filas <tr> dentro de <tbody>
|
|
// Patrón: capturar <tr> con sus atributos y contenido
|
|
$content = preg_replace_callback(
|
|
'/<tr([^>]*)>(.*?)<\/tr>/is',
|
|
function($matches) use ($section_headers, $total_patterns) {
|
|
$tr_attrs = $matches[1];
|
|
$tr_content = $matches[2];
|
|
|
|
// Si ya tiene clase especial, no modificar
|
|
if (preg_match('/class\s*=\s*["\'][^"\']*(?:section-header|subtotal-row|total-row)/', $tr_attrs)) {
|
|
return $matches[0];
|
|
}
|
|
|
|
// Extraer texto de la segunda celda
|
|
if (!preg_match('/<td[^>]*>.*?<\/td>\s*<td[^>]*>(.*?)<\/td>/is', $tr_content, $cell_match)) {
|
|
return $matches[0]; // No hay segunda celda
|
|
}
|
|
|
|
$cell_text = trim(strip_tags($cell_match[1]));
|
|
|
|
// Determinar clase a agregar
|
|
$class_to_add = '';
|
|
|
|
// Verificar section-header
|
|
if (in_array($cell_text, $section_headers, true)) {
|
|
$class_to_add = 'section-header';
|
|
}
|
|
// Verificar subtotal-row
|
|
elseif (stripos($cell_text, 'suma de ') === 0 || stripos($cell_text, 'subtotal ') === 0) {
|
|
$class_to_add = 'subtotal-row';
|
|
}
|
|
// Verificar total-row
|
|
elseif (in_array($cell_text, $total_patterns, true)) {
|
|
$class_to_add = 'total-row';
|
|
}
|
|
|
|
// Si no hay clase que agregar, retornar sin modificar
|
|
if (empty($class_to_add)) {
|
|
return $matches[0];
|
|
}
|
|
|
|
// Agregar clase al <tr>
|
|
if (preg_match('/class\s*=\s*["\']([^"\']*)["\']/', $tr_attrs)) {
|
|
// Ya tiene atributo class, agregar a las clases existentes
|
|
$tr_attrs = preg_replace(
|
|
'/class\s*=\s*["\']([^"\']*)["\']/',
|
|
'class="$1 ' . $class_to_add . '"',
|
|
$tr_attrs
|
|
);
|
|
} else {
|
|
// No tiene class, agregar el atributo
|
|
$tr_attrs = ' class="' . $class_to_add . '"' . $tr_attrs;
|
|
}
|
|
|
|
return '<tr' . $tr_attrs . '>' . $tr_content . '</tr>';
|
|
},
|
|
$content
|
|
);
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Shortcode: [apu_table]
|
|
* Permite envolver tablas manualmente con la clase .analisis
|
|
*
|
|
* Uso:
|
|
* [apu_table]
|
|
* <table>
|
|
* <thead>...</thead>
|
|
* <tbody>...</tbody>
|
|
* </table>
|
|
* [/apu_table]
|
|
*
|
|
* @param array $atts Atributos del shortcode
|
|
* @param string $content Contenido del shortcode
|
|
* @return string HTML procesado
|
|
*/
|
|
function roi_apu_table_shortcode($atts, $content = null) {
|
|
// Verificar que haya contenido
|
|
if (empty($content)) {
|
|
return '';
|
|
}
|
|
|
|
// Procesar shortcodes anidados si los hay
|
|
$content = do_shortcode($content);
|
|
|
|
// Envolver con la clase .analisis
|
|
return '<div class="analisis">' . $content . '</div>';
|
|
}
|
|
add_shortcode('apu_table', 'roi_apu_table_shortcode');
|
|
|
|
/**
|
|
* Shortcode: [apu_row type="tipo"]
|
|
* Facilita la creación de filas especiales en tablas APU
|
|
*
|
|
* Tipos disponibles:
|
|
* - section: Encabezado de sección (Material, Mano de Obra, etc)
|
|
* - subtotal: Fila de subtotal
|
|
* - total: Fila de total final
|
|
*
|
|
* Uso:
|
|
* [apu_row type="section"]
|
|
* <td></td>
|
|
* <td>Material</td>
|
|
* <td class="c3"></td>
|
|
* <td class="c4"></td>
|
|
* <td class="c5"></td>
|
|
* <td class="c6"></td>
|
|
* [/apu_row]
|
|
*
|
|
* @param array $atts Atributos del shortcode
|
|
* @param string $content Contenido del shortcode
|
|
* @return string HTML procesado
|
|
*/
|
|
function roi_apu_row_shortcode($atts, $content = null) {
|
|
// Atributos por defecto
|
|
$atts = shortcode_atts(
|
|
array(
|
|
'type' => 'normal',
|
|
),
|
|
$atts,
|
|
'apu_row'
|
|
);
|
|
|
|
// Verificar que haya contenido
|
|
if (empty($content)) {
|
|
return '';
|
|
}
|
|
|
|
// Determinar la clase según el tipo
|
|
$class = '';
|
|
switch ($atts['type']) {
|
|
case 'section':
|
|
$class = 'section-header';
|
|
break;
|
|
case 'subtotal':
|
|
$class = 'subtotal-row';
|
|
break;
|
|
case 'total':
|
|
$class = 'total-row';
|
|
break;
|
|
default:
|
|
$class = '';
|
|
}
|
|
|
|
// Procesar shortcodes anidados
|
|
$content = do_shortcode($content);
|
|
|
|
// Construir la fila con la clase apropiada
|
|
if (!empty($class)) {
|
|
return '<tr class="' . esc_attr($class) . '">' . $content . '</tr>';
|
|
} else {
|
|
return '<tr>' . $content . '</tr>';
|
|
}
|
|
}
|
|
add_shortcode('apu_row', 'roi_apu_row_shortcode');
|
|
|
|
/**
|
|
* Función helper para generar una tabla APU completa
|
|
*
|
|
* Esta función puede ser llamada desde templates para generar
|
|
* tablas APU programáticamente.
|
|
*
|
|
* @param array $data Array con la estructura de la tabla
|
|
* @return string HTML de la tabla completa
|
|
*
|
|
* Ejemplo de estructura de datos:
|
|
* array(
|
|
* 'headers' => array('Clave', 'Descripción', 'Unidad', 'Cantidad', 'Costo', 'Importe'),
|
|
* 'sections' => array(
|
|
* array(
|
|
* 'title' => 'Material',
|
|
* 'rows' => array(
|
|
* array('AGRE-016', 'Agua potable', 'm3', '0.237500', '$19.14', '$4.55'),
|
|
* array('AGRE-001', 'Arena en camión de 6 m3', 'm3', '0.541500', '$1,750.00', '$947.63'),
|
|
* ),
|
|
* 'subtotal' => '$2,956.51'
|
|
* ),
|
|
* array(
|
|
* 'title' => 'Mano de Obra',
|
|
* 'rows' => array(
|
|
* array('MOCU-027', 'Cuadrilla No 27', 'jor', '0.100000', '$2,257.04', '$225.70'),
|
|
* ),
|
|
* 'subtotal' => '$225.70'
|
|
* ),
|
|
* ),
|
|
* 'total' => '$3,283.52'
|
|
* )
|
|
*/
|
|
function roi_generate_apu_table($data) {
|
|
// Validar datos mínimos
|
|
if (empty($data) || !isset($data['sections'])) {
|
|
return '';
|
|
}
|
|
|
|
// Iniciar buffer de salida
|
|
ob_start();
|
|
?>
|
|
<div class="analisis">
|
|
<table>
|
|
<?php if (isset($data['headers']) && is_array($data['headers'])): ?>
|
|
<thead>
|
|
<tr>
|
|
<?php foreach ($data['headers'] as $header): ?>
|
|
<th scope="col"><?php echo esc_html($header); ?></th>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
</thead>
|
|
<?php endif; ?>
|
|
|
|
<tbody>
|
|
<?php foreach ($data['sections'] as $section): ?>
|
|
<?php if (isset($section['title'])): ?>
|
|
<!-- Encabezado de sección -->
|
|
<tr class="section-header">
|
|
<td></td>
|
|
<td><?php echo esc_html($section['title']); ?></td>
|
|
<td class="c3"></td>
|
|
<td class="c4"></td>
|
|
<td class="c5"></td>
|
|
<td class="c6"></td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
|
|
<?php if (isset($section['rows']) && is_array($section['rows'])): ?>
|
|
<?php foreach ($section['rows'] as $row): ?>
|
|
<tr>
|
|
<?php foreach ($row as $index => $cell): ?>
|
|
<?php
|
|
// Aplicar clases especiales a columnas específicas
|
|
$class = '';
|
|
if ($index === 2) $class = ' class="c3"';
|
|
elseif ($index === 3) $class = ' class="c4"';
|
|
elseif ($index === 4) $class = ' class="c5"';
|
|
elseif ($index === 5) $class = ' class="c6"';
|
|
?>
|
|
<td<?php echo $class; ?>><?php echo esc_html($cell); ?></td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if (isset($section['subtotal'])): ?>
|
|
<!-- Subtotal de sección -->
|
|
<tr class="subtotal-row">
|
|
<td></td>
|
|
<td>Suma de <?php echo esc_html($section['title']); ?></td>
|
|
<td class="c3"></td>
|
|
<td class="c4"></td>
|
|
<td class="c5"></td>
|
|
<td class="c6"><?php echo esc_html($section['subtotal']); ?></td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
|
|
<?php if (isset($data['total'])): ?>
|
|
<!-- Total final -->
|
|
<tr class="total-row">
|
|
<td></td>
|
|
<td>Costo Directo</td>
|
|
<td class="c3"></td>
|
|
<td class="c4"></td>
|
|
<td class="c5"></td>
|
|
<td class="c6"><?php echo esc_html($data['total']); ?></td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Agregar clases CSS al body cuando hay tablas APU
|
|
*
|
|
* Esto permite aplicar estilos adicionales a nivel de página
|
|
* cuando se detectan tablas APU.
|
|
*
|
|
* @param array $classes Array de clases del body
|
|
* @return array Array modificado de clases
|
|
*/
|
|
function roi_add_apu_body_class($classes) {
|
|
// Solo en posts individuales
|
|
if (is_single()) {
|
|
global $post;
|
|
|
|
// Verificar si el contenido tiene tablas APU
|
|
if (has_shortcode($post->post_content, 'apu_table') ||
|
|
strpos($post->post_content, 'data-apu') !== false ||
|
|
strpos($post->post_content, 'class="analisis"') !== false) {
|
|
$classes[] = 'has-apu-tables';
|
|
}
|
|
}
|
|
|
|
return $classes;
|
|
}
|
|
add_filter('body_class', 'roi_add_apu_body_class');
|
|
|
|
/**
|
|
* Permitir ciertos atributos HTML en tablas para el editor
|
|
*
|
|
* Esto asegura que los atributos data-apu y las clases especiales
|
|
* no sean eliminados por el sanitizador de WordPress.
|
|
*
|
|
* @param array $allowed_tags Array de tags HTML permitidos
|
|
* @param string $context Contexto de uso
|
|
* @return array Array modificado de tags permitidos
|
|
*/
|
|
function roi_allow_apu_table_attributes($allowed_tags, $context) {
|
|
if ($context === 'post') {
|
|
// Permitir atributo data-apu en tablas
|
|
if (isset($allowed_tags['table'])) {
|
|
$allowed_tags['table']['data-apu'] = true;
|
|
}
|
|
|
|
// Asegurar que las clases específicas estén permitidas
|
|
if (isset($allowed_tags['tr'])) {
|
|
$allowed_tags['tr']['class'] = true;
|
|
}
|
|
if (isset($allowed_tags['td'])) {
|
|
$allowed_tags['td']['class'] = true;
|
|
}
|
|
}
|
|
|
|
return $allowed_tags;
|
|
}
|
|
add_filter('wp_kses_allowed_html', 'roi_allow_apu_table_attributes', 10, 2);
|