]*?)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_content . ''; // Envolver con div.analisis return '
' . $table . '
'; }, $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 dentro de // Patrón: capturar con sus atributos y contenido $content = preg_replace_callback( '/]*)>(.*?)<\/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>\s*]*>(.*?)<\/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 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_content . ''; }, $content ); return $content; } /** * Shortcode: [apu_table] * Permite envolver tablas manualmente con la clase .analisis * * Uso: * [apu_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 '
' . $content . '
'; } 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"] * * Material * * * * * [/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 '' . $content . ''; } else { return '' . $content . ''; } } 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(); ?>
$cell): ?> >
Suma de
Costo Directo
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);