Del Error 500 al Control Total: Cómo creé un monitor de WordPress en una tarde gracias a la IA

Herramienta de diagnóstico y monitor de recursos WordPress creado con IA para solucionar el Error 500

Introducción: el problema tras la actualización de WordPress

Todo comenzó como suele empezar cualquier lunes complicado para un webmaster: con una de esas actualizaciones maores en WordPress. Esperas que todo vaya bien, cruzas los dedos, pero de repente, la pantalla se queda en blanco o aparece el temido mensaje:

500 Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.
Please contact the server administrator to inform them of the time this error occurred…

Este tipo de error puede ocurrir tras actualizaciones mayores de WordPress. El Error 500 es la forma que tiene el servidor de decirte: «Algo ha salido mal, pero no te diré exactamente qué». En mi caso, apareció justo después de un aviso que indicaba que era necesaria una actualización de la base de datos de WordPress.

Error 500  Es necesaria una actualización de la base de datos

Generalmente, esto ocurre por tres razones principales:

  • Un plugin o tema incompatible con la nueva versión.
  • Un archivo .htaccess corrupto.
  • Falta de memoria PHP en el servidor.

Para saber qué pasa realmente, no podemos ir a ciegas. Lo primero es «encender la luz». Entré vía FTP, busqué el archivo wp-config.php y cambié esta línea:

define( 'WP_DEBUG', false );

Por esta:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );

Esto genera un archivo de registro. Al abrirlo, pude ver los errores, desactivar los plugins conflictivos y borrar la caché. Sin embargo, me di cuenta de algo frustrante: el log nativo de WordPress es farragoso. Es un muro de texto interminable que te dice el error, pero no te dice cuánta memoria se estaba consumiendo en ese momento, ni si el servidor estaba saturado.

La necesidad de una herramienta propia

Necesitaba un monitor de recursos WordPress que actuara como un monitor de signos vitales para mi web.

Mi objetivo era claro: quería tener una visión precisa del consumo de memoria real, los tiempos de ejecución y la carga de la CPU en cada petición. Pero no quería instalar un plugin pesado de monitorización (como Query Monitor o New Relic) que, irónicamente, consumiera más recursos de los que quería ahorrar.

Decidí desarrollar un monitor de recursos WordPress ligero. El problema es que programar esto desde cero me habría llevado días, no es lo mio…

El papel de la inteligencia artificial generativa

Aquí es donde entraron en juego las versiones gratuitas de ChatGPT, Copilot y Gemini.

En lugar de escribir cada línea de código PHP, actué como «arquitecto» y utilicé a la IA como mi «albañil senior». Mi enfoque fue utilizar la IA para hacer pair programming (programación en pareja) con la máquina:

  • Yo definía la lógica: «Necesito que filtres por tipo de petición», «quiero que el log se limpie solo para no llenar el disco», «ponme colores si pasa de 100MB».
  • La IA escribía la sintaxis: Generaba el código base, las expresiones regulares y las funciones de WordPress.
  • Yo depuraba: «Oye, esto da error si la variable no existe».
  • La IA corregía: Refinando el código al instante.
  • Comparaba y depuraba el código entre los diferentes LLMs

Fue una experiencia súper interesante. Lo que podrían haber sido horas o incluso días de trabajo se convirtió en muchísimo menos tiempo y, además, fue una experiencia muy enriquecedora.

Nace «Memory Logger»: Un monitor de recursos WordPress a medida

El resultado de esta colaboración humano-IA es Memory Logger.

Es una herramienta de monitorización y diagnóstico diseñada específicamente para detectar cuellos de botella y optimizar los recursos del servidor. Su filosofía es simple: ser invisible hasta que la necesitas.

Características Clave:

  • 🧠 Registro Inteligente de Memoria: No registra todo (eso saturaría el disco). Tú configuras un «Umbral» (ej. 70 MB) y el plugin solo guarda los datos si una visita supera ese consumo. Si la web va ligera, el plugin calla. Si la web sufre, el plugin apunta.
  • ⚡ Semáforo de Tiempo: Un sistema visual (Negro/Naranja/Rojo) que te avisa si la página tarda más de lo debido en generarse, ayudándote a distinguir entre falta de caché o procesos bloqueados.
  • 🗄️ Monitor de Consultas SQL: Cuenta cuántas veces consulta WordPress a la base de datos para generar una sola página. Si ves una línea con más de 200 consultas, has encontrado un cuello de botella causado por un plugin ineficiente.
  • 🖥️ Detección de Hardware (CPU): El plugin detecta automáticamente cuántos núcleos tiene tu servidor (en mi caso, descubrí que eran 128). Esto es vital para calcular si la «Carga de CPU» es normal o si el servidor está saturado.
  • 🚦 Clasificación de Solicitudes: Diferencia automáticamente si la carga viene del tráfico web (Frontend), de alguien trabajando en el panel (Backend) o de tareas programadas (Cron).
  • 🪶 Ultraligero (Zero Impact): A diferencia de otros monitores pesados, este plugin tiene apenas 600 líneas de código y no consume recursos. Solo se activa, mide y se apaga.

Funcionamiento del plugin: Invisible y Seguro

El plugin opera de manera automática y silenciosa. Cada vez que alguien visita tu web o WordPress ejecuta una tarea, el plugin mide los signos vitales del servidor justo antes de terminar la ejecución.

Lo mejor es que está diseñado para no requerir mantenimiento. Desde el panel Monitor WP, ofrece:

  • Panel de Control Visual: Una tabla limpia que muestra las últimas 200 entradas relevantes, sin tener que bucear en archivos de texto.
  • Truncamiento Inteligente (Auto-limpieza): Esta es una función de seguridad clave. Si el archivo de log crece demasiado (por defecto 2MB), el plugin borra automáticamente las entradas antiguas. Nunca llenará tu espacio en disco, puedes dejarlo activo meses sin miedo.
  • Filtros Rápidos: Un desplegable para aislar el ruido y ver solo lo que te interesa: «¿Es el Cron el que me tira la web? ¿O es el tráfico del usuario?».
  • Exportación CSV: Un botón para descargar los datos y analizarlos cómodamente en Excel o Google Sheets si necesitas hacer gráficas o enviárselo a tu soporte de hosting.

Cómo se instala (Instalación Rápida)

Para que esta herramienta sea realmente efectiva y pueda medir la memoria desde el inicio, se ha diseñado como un mu-plugin (Must-Use plugin).

  1. Conéctate a tu servidor mediante FTP o gestor de archivos.
  2. Ve a la carpeta wp-content.
  3. Si no existe, crea una carpeta llamada mu-plugins.
  4. Sube el archivo memory-logger.php ahí dentro.

Ventaja: Al estar en mu-plugins, WordPress lo carga automáticamente antes que cualquier otro plugin normal. Esto significa que siempre estará activo, incluso si otros plugins fallan o si cambias de tema.

Opción 2: Fácil y recomendada si no te quieres complicar la vida.

  1. Descarga el archivo .zip que he preparado.
  2. Sube el plugin en Plugins > Añadir nuevo > Subir.
  3. Actívalo y ya está.

Guía de la Interfaz

Simplemente refresca tu panel de administración y busca en el menú lateral izquierdo. Verás una nueva opción llamada «Monitor WP».

(Nota: Aunque el plugin se llama Memory Logger, he etiquetado el menú como Monitor WP para que sea más fácil de identificar y ocupe menos espacio).

Al hacer clic, accederás al panel principal donde podrás:

  • Configuración de Umbrales: Ajusta los valores según tu servidor. Si tienes un hosting modesto con 256MB de RAM, quizás quieras que te avise en rojo si una petición supera los 120MB.
  • Acciones de Mantenimiento: Tienes botones para «Vaciar registro» (recomendado tras instalar actualizaciones) y «Exportar registro» (para descargar un CSV y analizar los datos en Excel).

Entendiendo los datos: Guía de columnas del Memory Logger

Una vez el plugin empieza a registrar datos, verás una tabla con varias columnas. Aquí te explico cómo interpretar cada una para diagnosticar tu web:

  • 🚨 Alerta: Es tu semáforo de emergencia. Si esta columna tiene un icono, atiéndelo primero:
    • 💀 Calavera: Pantalla Blanca (0 KB). La web no cargó.
    • 🔥 Fuego: Consumo de RAM crítico.
    • 🐢 Tortuga: Carga extremadamente lenta (> 5s).
    • Rayo: CPU del servidor saturada.
  • 📅 Fecha: El momento exacto del registro. Útil para correlacionar picos de consumo con campañas de email marketing o ataques de bots.
  • 📂 Tipo: Quién está haciendo la petición:
    • Frontend: Visitas de usuarios a la web pública.
    • Backend: Tú trabajando en el panel de administración.
    • Cron: Tareas de mantenimiento automático.
  • 🤖 Quién (User Agent): Identifica al visitante:
    • 👤 Humano: Navegadores normales (Chrome, Safari, Móvil).
    • 🤖 Bot: Rastreadores como Googlebot, Bing o herramientas SEO (Semrush/Ahrefs).
  • 🚦 Estado (HTTP): El resultado de la visita:
    • 200 (OK): Todo correcto.
    • ⚠️ 404 (No existe): Página no encontrada. Muchos seguidos indican un escaneo de vulnerabilidades.
    • 500 (Error): Fallo crítico del servidor.
  • 🔗 URL: La dirección exacta que causó el consumo. Vital para saber si el problema es la «Portada», la página de «Checkout» o un archivo sospechoso.
  • 🧠 Memoria: Máxima RAM usada por esa visita. El plugin usa estos colores:
    • Negro (< 128 MB): Consumo saludable.
    • Naranja (128 – 200 MB): Consumo alto. Normal en tiendas o admin.
    • Rojo (> 200 MB): Muy alto. Peligro de error «Memory Exhausted».
  • ⚡ Tiempo (s): Cuánto tardó el servidor en «pensar» y entregar la página:
    • < 0.5s: Excelente (generalmente gracias a la caché).
    • 1.0s – 2.0s: Aceptable para zonas sin caché.
    • > 2.0s: Problema de lentitud grave o bloqueo.
  • 🗄️ SQL (Consultas): Número de veces que WordPress preguntó a la Base de Datos.
    • < 100: Optimizado.
    • > 200: Ineficiente. Un plugin está saturando la base de datos (necesitas Redis/Memcached).
  • 📏 Tamaño: El peso del código HTML generado.
    • 0 KB (Gris): Normal en tareas de fondo (Cron/Ajax).
    • 0 KB (Rojo): ¡Error! Si pasa en la web pública, es una Pantalla Blanca.
    • > 150 KB (Naranja): Página muy pesada (exceso de código).
  • 🖥️ CPU (1m): La carga del procesador en el último minuto.
    Nota importante: Este valor depende de los núcleos de tu servidor (el plugin te dice cuántos tienes arriba).
    • Si tienes 4 núcleos, una carga de 4.0 es el 100%.
    • Si tienes 128 núcleos (como en mi hosting), una carga de 30.0 es apenas un 23%.
    • Regla de oro: Si el número es menor que tus núcleos, todo va bien. Si es mayor, hay saturación.

Veamos un ejemplo:

Análisis de la captura: ¿Qué nos dice el Memory Logger?

1. El Servidor (Hardware) está «Dormido» (Excelente) ✅

  • Mira la columna CPU. Los valores oscilan entre 5.48 y 5.61.
  • Como se que este servidor tiene 56 núcleos, una carga de 5 significa que estás usando apenas el ~5-10% de la potencia de la máquina.
  • Conclusión: El servidor va sobrado. No es un problema de falta de potencia.

2. El consumo de RAM «Base» es Alto (Atención) ⚠️

  • Fíjate en las filas de Frontend (las visitas normales). Todas marcan alrededor de 190 MB.
  • Esto es memoria naranja/roja. Una web ligera suele gastar 60-80 MB.
  • Causa: Tienes una carga base de plugins o un tema (probablemente Divi + Events Calendar) que requiere cargar casi 200MB de código en memoria solo para empezar a funcionar.
  • ¿Es grave? No, porque tu servidor tiene 768MB de límite. Tienes margen, pero la web es «pesada».

3. El Cron es el culpable de la lentitud puntual 🐢

  • Mira la cuarta línea: Tipo: Cron.
  • Tiempo: 17.885 segundos (¡Lentísimo!).
  • SQL: 579 consultas.
  • Diagnóstico: Hay una tarea programada (quizás backups, envío de correos masivos o regeneración de datos del calendario) que se ejecuta, tarda casi 20 segundos y martillea la base de datos. Mientras esto ocurre, la web puede sentirse un poco más lenta para ti, pero como hay CPU de sobra, los usuarios apenas lo notarán.

4. El Backend (Ajax) es intenso 🔥

  • Las líneas de admin-ajax.php tienen el icono de Fuego.
  • Consumen 280 MB de RAM.
  • Esto suele ocurrir cuando editas con constructores visuales o cuando plugins como Wordfence o WooCommerce hacen comprobaciones en segundo plano. Es un consumo alto, pero puntual.

Resumiendo: La web está Estable y Segura, pero es Pesada.

¿Qué pasa si la web no carga?

Si el consumo de memoria es tan alto que tira el servidor, no podrás entrar a wp-admin para ver tu bonita tabla.

La buena noticia: Tu plugin ya está diseñado para esto. Como escribe los datos en un archivo físico en el servidor antes de mostrarlos en pantalla, el registro existe independientemente de si puedes ver el backend o no.

Cómo acceder a los datos «a la antigua»: Simplemente entra por FTP a tu carpeta /wp-content/ y encontrarás un archivo llamado memory-usage.log. Ahí verás la última línea registrada justo antes de que la web colapsara, lo cual te dirá qué URL o proceso fue el culpable.

Conclusión: lo que se puede lograr en una tarde con IA

La creación de este monitor de recursos WordPress (Memory Logger): la IA nos permite dar un salto gigante y pasar de ser simples consumidores de software a creadores de nuestras propias soluciones.

Obviamente, soy consciente de que ya existen herramientas fantásticas en el mercado, gratuitas como Query Monitor o WP Crontrol, y soluciones de pago nivel enterprise como New Relic. Pero a veces, esas herramientas son como «matar moscas a cañonazos»: ofrecen demasiados datos o añaden una carga extra al servidor que queríamos evitar.

Memory Logger es diferente. Es un desarrollo extremadamente ligero (apenas 600 líneas de código*) diseñado para no consumir recursos del servidor. Está hecho a medida, es «quirúrgico» y se centra exclusivamente en lo que necesitaba ver: Memoria, CPU, Tiempo y SQL. Y, sinceramente, desarrollarlo mano a mano con la IA ha sido un reto personal mucho más gratificante que simplemente instalar otro plugin más.

Nota: Para ser totalmente transparente, la primera versión funcional sí nació en una tarde. Pero la verdad es que me «piqué» añadiendo funcionalidades extra (como el monitor SQL, gráficos, la detección de CPU, exportación en CSV …) y le estoy dedicando un rato casi cada día a pulirla hasta llegar a esta versión final. Aun así, ¡sigue siendo infinitamente más rápido que escribirlo todo a mano!

Pero esto no acaba aquí. Gracias a la velocidad de desarrollo con IA, ya tengo en la hoja de ruta las próximas funcionalidades que implementaré:

  • 🕵️ Detección de «User Agent»: Para identificar si quien consume la memoria es un usuario real, Googlebot o un bot malicioso.
  • 🚦 Códigos de Estado HTTP: Para ver de un vistazo si la visita terminó en éxito (200), página no encontrada (404) o error (500).
  • 🛡️ Modo Silencioso por IP: Para excluir mi propia IP del registro y no «ensuciar» los datos mientras trabajo en el panel.

Te invito a descargar el código, instalarlo en tu carpeta mu-plugins y descubrir por fin qué es lo que realmente está consumiendo los recursos de tu WordPress.

📥 Descarga el Código

<?php
/**
 * Plugin Name: Memory Logger
 * Plugin URI: https://www.posicionamientowebysem.com
 * Description: Auditor de rendimiento PRO: Gráficos, Memoria, Tiempo, CPU, SQL, HTTP, Disco y Diagnóstico. v10.6.1.
 * Version: 10.6.1
 * Author: Josep Maria Tapia Estaragues
 * Author URI: https://www.posicionamientowebysem.com
 * License: GPLv2 or later
 *
 * == Changelog ==
 * = 10.6.1 =
 * * Improved: Removed bold formatting from normal values in table (better readability)
 * * Improved: Help section background changed to white for better contrast
 * * Added: Detailed explanation of Advanced Options in help section
 * = 10.6 =
 * * Added: OPcache detection and hit rate monitoring
 * * Added: Expired transients count with optimized query
 * * Added: Comprehensive help section with FAQ, troubleshooting, and user guide
 * * Added: CSV export functionality
 * * Added: HTTP status code detection (200, 404, 301, etc.)
 * * Improved: Detailed bot identification (Googlebot, Semrush, Ahrefs, etc.)
 * * Improved: Dashboard reorganized with Cache & Optimization section
 * * Improved: Better color-coded metrics display with alert icons
 * * Improved: Tooltips for all icons (Alert and Status)
 * * Fixed: Spanish (Spain) translations
 * * Fixed: CPU Load suggestion now uses +5 formula
 * * Optimized: Efficient database queries for performance metrics
 */

// Evitar acceso directo
if ( !defined('ABSPATH') ) exit;

if ( !defined('WP_START_TIME') ) define('WP_START_TIME', microtime(true));
if ( !defined('MEMORY_LOG_FILE') ) define('MEMORY_LOG_FILE', WP_CONTENT_DIR . '/memory-usage.log');

// Variable global para capturar el tamaño del buffer de salida
global $ml_page_size;
$ml_page_size = 0;

// BUFFER (Inicio de captura de tamaño)
add_action('init', function() {
    if ( !is_admin() || (defined('DOING_AJAX') && DOING_AJAX) || (defined('DOING_CRON') && DOING_CRON) ) {
        ob_start(function($buffer) {
            global $ml_page_size;
            $ml_page_size = strlen($buffer);
            return $buffer;
        });
    }
}, -999);

function memory_logger_get_options() {
    $defaults = [
        'threshold_mb' => 70,
        'time_warn' => 2.0,
        'time_risk' => 5.0,
        'cpu_warn' => 128,
        'cpu_risk' => 133,
        'debug_mode' => 0,
        'no_charts' => 0
    ];
    $opts = get_option('memory_logger_options', []);
    return wp_parse_args($opts, $defaults);
}

function memory_logger_get_request_type($url) {
    if (strpos($url, 'wp-cron.php') !== false || (defined('DOING_CRON') && DOING_CRON)) return 'Cron';
    if (strpos($url, '/wp-admin') !== false || is_admin() || (defined('WP_ADMIN') && WP_ADMIN)) return 'Backend';
    return 'Frontend';
}

function memory_logger_sanitize_url($url) {
    $url = preg_replace('/[\x00-\x1F\x7F]/', '', $url);
    // Filtrar parámetros sensibles
    $url = preg_replace('/([?&])(token|key|password|pwd|resetpass|auth|api_key|apikey|session|sid|jwt)=[^&]*/i', '$1$2=***', $url);
    if (strlen($url) > 200) $url = substr($url, 0, 197) . '...';
    return $url;
}

function memory_logger_get_cpu_count() {
    $numCpus = 1;
    if ( function_exists('shell_exec') && is_callable('shell_exec') && false === stripos(ini_get('disable_functions'), 'shell_exec') ) {
        $cmd = @shell_exec('nproc');
        if ( $cmd && intval($cmd) > 0 ) return intval($cmd);
    }
    return $numCpus;
}

function memory_logger_identify_ua($ua) {
    if (empty($ua) || $ua === 'N/A') return ['icon' => '❓', 'text' => 'Desconocido'];
    $ua = strtolower($ua);
    
    // Detección específica de bots
    if (strpos($ua, 'googlebot') !== false) return ['icon' => '🤖', 'text' => 'Googlebot'];
    if (strpos($ua, 'bingbot') !== false) return ['icon' => '🤖', 'text' => 'Bingbot'];
    if (strpos($ua, 'ahrefs') !== false) return ['icon' => '🤖', 'text' => 'Ahrefs'];
    if (strpos($ua, 'semrush') !== false) return ['icon' => '🤖', 'text' => 'Semrush'];
    if (strpos($ua, 'mj12bot') !== false) return ['icon' => '🤖', 'text' => 'Majestic'];
    if (strpos($ua, 'dotbot') !== false) return ['icon' => '🤖', 'text' => 'Moz'];
    if (strpos($ua, 'petalbot') !== false) return ['icon' => '🤖', 'text' => 'Petalbot'];
    if (strpos($ua, 'yandex') !== false) return ['icon' => '🤖', 'text' => 'Yandex'];
    if (strpos($ua, 'baiduspider') !== false) return ['icon' => '🤖', 'text' => 'Baidu'];
    
    // Bot genérico
    if (strpos($ua, 'bot') !== false || strpos($ua, 'crawl') !== false || strpos($ua, 'spider') !== false) 
        return ['icon' => '🤖', 'text' => 'Bot Genérico'];
    
    // Navegadores
    if (strpos($ua, 'chrome') !== false) return ['icon' => '👤', 'text' => 'Chrome'];
    if (strpos($ua, 'safari') !== false) return ['icon' => '👤', 'text' => 'Safari'];
    if (strpos($ua, 'firefox') !== false) return ['icon' => '👤', 'text' => 'Firefox'];
    if (strpos($ua, 'edge') !== false) return ['icon' => '👤', 'text' => 'Edge'];
    
    return ['icon' => '👽', 'text' => 'Otro'];
}

function memory_logger_get_alert_info($alert) {
    $alerts = [
        '-' => 'Sin anomalías',
        '💀' => 'Error Fatal: Página en blanco',
        '🔥' => 'Memoria crítica (>200 MB)',
        '🐢' => 'Lentitud extrema (>5 seg)',
        '💾' => 'Sobrecarga SQL (>200 queries)',
        '⚡' => 'CPU saturada'
    ];
    return isset($alerts[$alert]) ? $alerts[$alert] : 'Desconocido';
}

function memory_logger_get_http_status_info($code) {
    $code = (int)$code;
    
    // Icono según código
    if ($code >= 200 && $code < 300) {
        $icon = '✅';
        $color = 'green';
    } elseif ($code >= 300 && $code < 400) {
        $icon = '🔄';
        $color = 'blue';
    } elseif ($code == 404) {
        $icon = '❌';
        $color = 'red';
    } elseif ($code >= 400 && $code < 500) {
        $icon = '⚠️';
        $color = 'orange';
    } elseif ($code >= 500) {
        $icon = '💀';
        $color = 'red';
    } else {
        $icon = '✅';
        $color = 'green';
        $code = 200;
    }
    
    return [
        'icon' => $icon,
        'code' => $code,
        'color' => $color,
        'text' => "HTTP $code"
    ];
}

// DIAGNÓSTICO DEL SISTEMA
function memory_logger_get_system_health() {
    global $wpdb;
   
    // DB Latency
    $db_start = microtime(true);
    $wpdb->query("SELECT 1");
    $db_lat = round((microtime(true) - $db_start) * 1000, 2);
    $db_lat_color = ($db_lat > 20) ? 'red' : (($db_lat > 5) ? 'orange' : 'green');
    
    // Autoload Count
    $autoload_count = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->options WHERE autoload = 'yes'");
    $al_color = ($autoload_count > 800) ? 'red' : (($autoload_count > 400) ? 'orange' : 'green');
   
    // Transients expirados
    $expired_transients = $wpdb->get_var(
        $wpdb->prepare(
            "SELECT COUNT(*) FROM $wpdb->options 
            WHERE option_name LIKE %s 
            AND option_value < %d 
            LIMIT 1000",
            $wpdb->esc_like('_transient_timeout_') . '%',
            time()
        )
    );
    $tr_color = ($expired_transients > 500) ? 'red' : (($expired_transients > 100) ? 'orange' : 'green');
   
    // Object Cache Status
    $oc_status = 'Inactivo (Disco)';
    $oc_latency = '-';
    $oc_class = 'gray';
    $oc_type = '';
    
    if ( wp_using_ext_object_cache() ) {
        global $wp_object_cache;
        $cls = is_object($wp_object_cache) ? get_class($wp_object_cache) : '';
        
        if ( (defined('SG_OPTIMIZER_VERSION') || file_exists(WP_CONTENT_DIR . '/sgo-config.php')) && file_exists(WP_CONTENT_DIR . '/object-cache.php') ) {
            $oc_type = 'SG Memcached';
        } elseif ( defined('WP_REDIS_VERSION') || stripos($cls, 'Redis') !== false ) {
            $oc_type = 'Redis';
        } elseif ( class_exists('MemcachedObjectCache') || stripos($cls, 'Memcache') !== false ) {
            $oc_type = 'Memcached';
        } else {
            $oc_type = 'Activo';
        }
        
        $oc_status = $oc_type;
        $start = microtime(true);
        wp_cache_set('ml_ping', 1, 'ml_test', 1);
        wp_cache_get('ml_ping', 'ml_test');
        $lat = (microtime(true) - $start) * 1000;
        $oc_latency = round($lat, 2);
        if ($lat < 1) $oc_class = 'green'; elseif ($lat < 5) $oc_class = 'orange'; else $oc_class = 'red';
    }
    
    // OPcache
    $opcache_hit = 0;
    $opcache_class = 'gray';
    if (function_exists('opcache_get_status')) {
        $op = @opcache_get_status(false);
        if ($op && isset($op['opcache_statistics']['opcache_hit_rate'])) {
            $opcache_hit = round($op['opcache_statistics']['opcache_hit_rate'], 0);
            $opcache_class = ($opcache_hit > 90) ? 'green' : (($opcache_hit > 70) ? 'orange' : 'red');
        }
    }
    
    // Disk Space
    $disk_free = function_exists('disk_free_space') ? @disk_free_space(WP_CONTENT_DIR) : 0;
    $disk_gb = 0;
    $disk_color = 'green';
    if ($disk_free > 0) {
        $disk_gb = round($disk_free / 1073741824, 2);
        $disk_color = ($disk_gb < 1) ? 'red' : 'green';
    }
    
    return [
        'wp_ver' => get_bloginfo('version'),
        'php_ver' => phpversion(),
        'server' => $_SERVER['SERVER_SOFTWARE'] ?? 'Desconocido',
        'oc_stat' => $oc_status,
        'oc_lat' => $oc_latency,
        'oc_class' => $oc_class,
        'opcache_hit' => $opcache_hit,
        'opcache_class' => $opcache_class,
        'disk_gb' => $disk_gb,
        'disk_color' => $disk_color,
        'autoload' => $autoload_count,
        'autoload_color' => $al_color,
        'transients' => $expired_transients,
        'transients_color' => $tr_color,
        'db_lat' => $db_lat,
        'db_lat_color' => $db_lat_color,
        'theme' => function_exists('wp_get_theme') ? wp_get_theme()->get('Name') : 'N/A',
        'plugins_count' => count(get_option('active_plugins', [])),
    ];
}

add_action('shutdown', function() {
    $opts = memory_logger_get_options();
   
    // Protección del archivo de log (.htaccess)
    $ht_file = WP_CONTENT_DIR . '/.htaccess';
    if ( !file_exists($ht_file) || strpos(@file_get_contents($ht_file), 'memory-usage.log') === false ) {
        $rule = "\n# Memory Logger Protection\n<Files \"memory-usage.log\">\n  <IfModule mod_authz_core.c>\n    Require all denied\n  </IfModule>\n  <IfModule !mod_authz_core.c>\n    Order Allow,Deny\n    Deny from all\n  </IfModule>\n</Files>\n";
        @file_put_contents($ht_file, $rule, FILE_APPEND);
    }
   
    $mem_peak_bytes = memory_get_peak_usage(true);
   
    // Solo registra si supera el umbral o si está en modo debug
    if ( empty($opts['debug_mode']) && $mem_peak_bytes < ($opts['threshold_mb'] * 1024 * 1024) ) return;
   
    $exec_time = round(microtime(true) - WP_START_TIME, 3);
    global $ml_page_size;
    $size_kb = isset($ml_page_size) && $ml_page_size > 0 ? round($ml_page_size / 1024, 1) : 0;
   
    $ua = isset($_SERVER['HTTP_USER_AGENT']) ? substr(str_replace(['|', "\n", "\r"], '', $_SERVER['HTTP_USER_AGENT']), 0, 100) : 'N/A';
    $cpu = '-';
    if ( function_exists('sys_getloadavg') ) { 
        $l = sys_getloadavg(); 
        if(is_array($l)) $cpu = round($l[0], 2);
    }
   
    // Capturar código HTTP
    $http = 200;
    if ( function_exists('http_response_code') ) {
        $http = http_response_code();
    }
    if ( !$http || !is_numeric($http) ) {
        $http = (function_exists('is_404') && is_404()) ? 404 : 200;
    }
   
    $url = memory_logger_sanitize_url($_SERVER['REQUEST_URI'] ?? '/');
    $req_type = memory_logger_get_request_type($url);
   
    $msg = sprintf("DATE:%s | TYPE:%s | URL:%s | MEM:%s | TIME:%s | SQL:%s | HTTP:%s | CPU:%s | SIZE:%s | UA:%s\n",
        gmdate("Y-m-d H:i:s"), $req_type, $url, round($mem_peak_bytes/1024/1024,2), $exec_time, function_exists('get_num_queries')?get_num_queries():0, $http, $cpu, $size_kb, $ua);
   
    @file_put_contents(MEMORY_LOG_FILE, $msg, FILE_APPEND);
});

// EXPORTAR CSV
add_action('admin_post_memory_logger_export_csv', function() {
    if ( !current_user_can('manage_options') ) {
        wp_die('Acceso denegado.');
    }
    
    if ( !isset($_POST['export_csv_nonce']) || !wp_verify_nonce($_POST['export_csv_nonce'], 'memory_logger_export_csv') ) {
        wp_die('Verificación de seguridad fallida.');
    }
    
    if ( !file_exists(MEMORY_LOG_FILE) ) {
        wp_die('El archivo de registro no existe.');
    }
    
    $content = @file_get_contents(MEMORY_LOG_FILE);
    if ( empty($content) ) {
        wp_die('El registro está vacío.');
    }
    
    // Limpiar cualquier salida previa
    if (ob_get_level()) {
        ob_end_clean();
    }
    
    // Configurar headers para descarga CSV
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename="memory-log-' . date('Ymd-His') . '.csv"');
    header('Pragma: no-cache');
    header('Expires: 0');
    
    // BOM UTF-8
    echo "\xEF\xBB\xBF";
    
    // Encabezados CSV
    echo "Fecha,Tipo,URL,Memoria (MB),Tiempo (s),SQL,HTTP,CPU,Tamaño (KB),User Agent\n";
    
    // Procesar líneas
    $lines = explode("\n", trim($content));
    foreach ($lines as $line) {
        if (empty($line) || strpos($line, 'DATE:') === false) continue;
        
        $row = [];
        foreach(explode(' | ', $line) as $part) {
            $kv = explode(':', $part, 2);
            if (count($kv) == 2) {
                $row[trim($kv[0])] = trim($kv[1]);
            }
        }
        
        if (empty($row)) continue;
        
        // Escapar comillas y comas para CSV
        $csv_row = [
            $row['DATE'] ?? '',
            $row['TYPE'] ?? '',
            '"' . str_replace('"', '""', $row['URL'] ?? '') . '"',
            $row['MEM'] ?? '',
            $row['TIME'] ?? '',
            $row['SQL'] ?? '',
            $row['HTTP'] ?? '200',
            $row['CPU'] ?? '',
            $row['SIZE'] ?? '',
            '"' . str_replace('"', '""', $row['UA'] ?? '') . '"'
        ];
        
        echo implode(',', $csv_row) . "\n";
    }
    
    exit;
});

add_action('admin_menu', function() { 
    add_menu_page('Monitor WP', 'Monitor WP', 'manage_options', 'memory-log-viewer', 'memory_logger_admin_page', 'dashicons-chart-area', 80); 
});

function memory_logger_admin_page() {
    $opts = memory_logger_get_options();
    
    if ( isset($_POST['save_ml']) && check_admin_referer('save_ml_action') ) {
        $opts['threshold_mb'] = floatval($_POST['threshold_mb']);
        $opts['time_warn'] = floatval($_POST['time_warn']);
        $opts['time_risk'] = floatval($_POST['time_risk']);
        $opts['cpu_warn'] = floatval($_POST['cpu_warn']);
        $opts['cpu_risk'] = floatval($_POST['cpu_risk']);
        $opts['debug_mode'] = isset($_POST['debug_mode']) ? 1 : 0;
        $opts['no_charts'] = isset($_POST['no_charts']) ? 1 : 0;
        update_option('memory_logger_options', $opts);
        echo '<div class="updated notice"><p>✅ Configuración guardada correctamente.</p></div>';
    }
    
    if ( isset($_POST['clear_log']) && check_admin_referer('clear_log_action') ) {
        @file_put_contents(MEMORY_LOG_FILE, "");
        echo '<div class="updated notice"><p>🗑️ Registro borrado correctamente.</p></div>';
    }
    
    $health = memory_logger_get_system_health();
    $cpu_count = memory_logger_get_cpu_count();
   
    echo '<div class="wrap"><style>
        .ml-card { background:#fff; border:1px solid #c3c4c7; padding:20px; margin-bottom:20px; border-radius:4px; }
        .ml-grid { display:flex; gap:20px; flex-wrap:wrap; }
        .ml-help-box { background:#fff; border-left:4px solid #72aee6; padding:15px; margin-top:10px; }
        .ml-dashboard-col { flex:1; min-width:200px; }
        .ml-dashboard-col strong { display: block; margin-bottom: 8px; font-size:14px; }
        .ml-dashboard-col hr { margin: 8px 0; border:0; border-top:1px solid #ddd; }
        .chart-wrapper { position: relative; width: 100%; height: calc(100% - 40px); }
        .ml-help-toggle { cursor: pointer; color: #2271b1; text-decoration: none; font-weight:bold; display:inline-block; margin:10px 0; }
        .ml-help-toggle:hover { text-decoration: underline; }
        .ml-help-content { display:none; margin-top:15px; }
        .ml-help-content.active { display:block; }
        details { cursor: pointer; margin-bottom:10px; }
        details summary { user-select: none; font-weight:bold; padding:8px; background:#fff; border:1px solid #ddd; border-radius:3px; }
        details[open] summary { margin-bottom: 10px; }
        details > div { padding:15px; background:#fff; border:1px solid #ddd; border-top:none; margin-top:-1px; }
        input[type="number"] { width: 80px; }
        .ml-config-row { display:flex; gap:20px; margin-bottom:15px; align-items:center; }
        .ml-config-label { min-width:150px; font-weight:bold; }
        @media (max-width: 1024px) {
            .ml-grid { flex-direction: column; }
        }
    </style>
    
    <h1 style="margin-bottom:5px;">Memory Logger v10.6.1</h1>
    <p style="margin-top:0; color:#646970; font-size:14px;">
        Auditor de rendimiento PRO: Gráficos, Memoria, Tiempo, CPU, SQL, HTTP, Disco y Diagnóstico. v10.6.1.
    </p>
    <p style="margin:5px 0;">
        <strong>Autor:</strong> <a href="https://www.posicionamientowebysem.com" target="_blank">Josep Maria Tapia Estaragues</a> | 
        <a href="https://www.posicionamientowebysem.com" target="_blank">https://www.posicionamientowebysem.com</a>
    </p>';
    
    // DASHBOARD
    echo '<div class="ml-card ml-grid">
        <div class="ml-dashboard-col">
            <strong>📦 Software</strong>
            <hr>
            WP: '.$health['wp_ver'].'<br>
            PHP: '.$health['php_ver'].'<br>
            Tema: '.esc_html($health['theme']).'<br>
            Plugins Activos: '.$health['plugins_count'].'
        </div>
        <div class="ml-dashboard-col">
            <strong>🖥️ Servidor</strong>
            <hr>
            CPU: '.$cpu_count.' Núcleos<br>
            '.esc_html($health['server']).'<br>
            Disco: <span style="color:'.$health['disk_color'].';">'.$health['disk_gb'].' GB Libres</span><br>
            OPcache: Hit: <span style="color:'.$health['opcache_class'].';">'.$health['opcache_hit'].'%</span>
        </div>
        <div class="ml-dashboard-col">
            <strong>⚡ Caché</strong>
            <hr>
            Obj. Cache: <span style="color:'.$health['oc_class'].';">✅ '.esc_html($health['oc_stat']).'</span><br>
            Latencia: <span style="color:'.$health['oc_class'].';">'.$health['oc_lat'].' ms</span><br>
            Ext. PHP: Memcached
        </div>
        <div class="ml-dashboard-col">
            <strong>🗄️ Base de Datos</strong>
            <hr>
            Autoload: <span style="color:'.$health['autoload_color'].';">'.$health['autoload'].'</span><br>
            Transients Exp: <span style="color:'.$health['transients_color'].';">'.$health['transients'].'</span><br>
            Latencia DB: <span style="color:'.$health['db_lat_color'].';">'.$health['db_lat'].' ms</span>
        </div>
    </div>';
   
    // GRÁFICOS PRO
    if ( empty($opts['no_charts']) && file_exists(MEMORY_LOG_FILE) ) {
        wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true);
        $lines = array_slice(explode("\n", trim(@file_get_contents(MEMORY_LOG_FILE))), -50);
        $labels = []; $mem_data = []; $sql_data = []; $time_data = []; $cpu_data = []; $size_data = [];
        
        foreach ($lines as $l) {
            if (strpos($l, 'DATE:') === false) continue;
            $row = [];
            foreach(explode(' | ', $l) as $x){ 
                $kv=explode(':',$x,2); 
                if(count($kv)==2) $row[trim($kv[0])]=trim($kv[1]); 
            }
            $time = substr($row['DATE'] ?? '', 11, 5);
            $labels[] = $time;
            $mem_data[] = (float)($row['MEM'] ?? 0);
            $sql_data[] = (int)($row['SQL'] ?? 0);
            $time_data[] = (float)($row['TIME'] ?? 0);
            $cpu_data[] = (float)($row['CPU'] ?? 0);
            $size_data[] = (float)($row['SIZE'] ?? 0);
        }
       
        if (!empty($labels)) {
            echo '<div class="ml-grid">
                <div class="ml-card" style="flex:1; min-width:400px; height:250px;">
                    <h3>📊 Recursos (Memoria y SQL)</h3>
                    <div class="chart-wrapper"><canvas id="c_res"></canvas></div>
                </div>
                <div class="ml-card" style="flex:1; min-width:400px; height:250px;">
                    <h3>🐢 Rendimiento (Segundos)</h3>
                    <div class="chart-wrapper"><canvas id="c_perf"></canvas></div>
                </div>
                <div class="ml-card" style="flex:1; min-width:400px; height:250px;">
                    <h3>⚡ CPU y Tamaño</h3>
                    <div class="chart-wrapper"><canvas id="c_cpu_size"></canvas></div>
                </div>
            </div>
            <script>
            document.addEventListener("DOMContentLoaded", function() {
                const chartOptions = {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: { legend: { display: true, position: "top" } },
                    scales: { y: { beginAtZero: true } }
                };
                
                new Chart(document.getElementById("c_res").getContext("2d"), {
                    type: "line",
                    data: {
                        labels: '.json_encode($labels).',
                        datasets: [{
                            label: "Memoria (MB)",
                            data: '.json_encode($mem_data).',
                            borderColor: "#2271b1",
                            backgroundColor: "rgba(34, 113, 177, 0.1)",
                            fill: true,
                            tension: 0.3
                        }, {
                            label: "SQL Queries",
                            data: '.json_encode($sql_data).',
                            borderColor: "#f0b849",
                            backgroundColor: "rgba(240, 184, 73, 0.1)",
                            fill: true,
                            tension: 0.3
                        }]
                    },
                    options: chartOptions
                });
                
                new Chart(document.getElementById("c_perf").getContext("2d"), {
                    type: "line",
                    data: {
                        labels: '.json_encode($labels).',
                        datasets: [{
                            label: "Tiempo (s)",
                            data: '.json_encode($time_data).',
                            borderColor: "#d63638",
                            backgroundColor: "rgba(214, 54, 56, 0.1)",
                            fill: true,
                            tension: 0.3
                        }]
                    },
                    options: chartOptions
                });
                
                new Chart(document.getElementById("c_cpu_size").getContext("2d"), {
                    type: "line",
                    data: {
                        labels: '.json_encode($labels).',
                        datasets: [{
                            label: "CPU Load",
                            data: '.json_encode($cpu_data).',
                            borderColor: "#00a32a",
                            backgroundColor: "rgba(0, 163, 42, 0.1)",
                            fill: true,
                            tension: 0.3
                        }, {
                            label: "Size (KB)",
                            data: '.json_encode($size_data).',
                            borderColor: "#8c8f94",
                            backgroundColor: "rgba(140, 143, 148, 0.1)",
                            fill: true,
                            tension: 0.3
                        }]
                    },
                    options: chartOptions
                });
            });
            </script>';
        }
    }
    
    // CONFIGURACIÓN
    echo '<div class="ml-card">
        <h2>Configuración</h2>
        <form method="post">';
        wp_nonce_field('save_ml_action');
        echo '
        <h3>Opciones Avanzadas</h3>
        <p>
            <label style="display:inline-flex; align-items:center; gap:8px;">
                <input type="checkbox" name="debug_mode" '.checked($opts['debug_mode'], 1, false).'/>
                <span style="color:#d63638;">●</span>
                <strong>Modo Debug (Registrar TODO):</strong> Ignora el umbral de memoria. Útil para ver tráfico normal.
            </label>
        </p>
        <p>
            <label style="display:inline-flex; align-items:center; gap:8px;">
                <input type="checkbox" name="no_charts" '.checked($opts['no_charts'], 1, false).'/>
                <span style="color:#d63638;">⊘</span>
                <strong>Ocultar Gráficos:</strong> No cargar librerías externas (Privacidad / Offline).
            </label>
        </p>
        
        <hr style="margin:20px 0;">
        
        <div class="ml-config-row">
            <span class="ml-config-label">Umbral Memoria (MB)</span>
            <input type="number" name="threshold_mb" value="'.$opts['threshold_mb'].'" step="1" min="1" /> MB
        </div>
        
        <div class="ml-config-row">
            <span class="ml-config-label">Tiempo (s)</span>
            Aviso: <input type="number" name="time_warn" value="'.$opts['time_warn'].'" step="0.1" min="0.1" style="width:60px;" />
            Riesgo: <input type="number" name="time_risk" value="'.$opts['time_risk'].'" step="0.1" min="0.1" style="width:60px;" />
        </div>
        
        <div class="ml-config-row">
            <span class="ml-config-label">CPU Load</span>
            Aviso: <input type="number" name="cpu_warn" value="'.$opts['cpu_warn'].'" step="1" min="1" style="width:60px;" />
            Riesgo: <input type="number" name="cpu_risk" value="'.$opts['cpu_risk'].'" step="1" min="1" style="width:60px;" />
            <span style="color:#646970;">(Sugerido: '.$cpu_count.' / '.($cpu_count + 5).')</span>
        </div>
        
        <p style="margin-top:20px;">
            <button type="submit" name="save_ml" class="button button-primary">Guardar configuración</button>
        </p>
        </form>
        
        <a href="#" class="ml-help-toggle" onclick="event.preventDefault(); document.querySelector(\'.ml-help-content\').classList.toggle(\'active\');">
            ℹ️ Ayuda Completa: Guía de Diagnóstico
        </a>';
        
    // SECCIÓN DE AYUDA (COLAPSADA) - FONDO BLANCO
    echo '<div class="ml-help-content">
        <div class="ml-help-box" style="border-left-color:#2271b1;">
            <h3 style="margin-top:0;">0. Opciones Avanzadas</h3>
            <ul style="line-height:2;">
                <li><strong>● Modo Debug:</strong> Registra TODAS las peticiones, ignorando el umbral. Útil para analizar tráfico normal (1-2 días). Genera muchos datos.</li>
                <li><strong>⊘ Ocultar Gráficos:</strong> No carga Chart.js (CDN externo). Para entornos offline o máxima privacidad. Solo muestra la tabla de datos.</li>
            </ul>
        </div>

        <div class="ml-help-box" style="border-left-color:#2271b1;">
            <h3 style="margin-top:0;">1. Iconos de Alerta (Leyenda)</h3>
            <ul style="line-height:2;">
                <li><strong>-</strong> (Guion): Todo correcto. Sin anomalías graves.</li>
                <li><strong>💀 Calavera (0 KB):</strong> Error Fatal. La página cargó en blanco (Frontend).</li>
                <li><strong>🔥 Fuego (&gt;200 MB):</strong> Memoria crítica. Peligro de agotar RAM.</li>
                <li><strong>🐢 Tortuga (&gt;5 seg):</strong> Lentitud extrema. La página tarda una eternidad.</li>
                <li><strong>💾 Diskette (&gt;200 SQL):</strong> Sobrecarga de base de datos. Necesitas caché de objetos.</li>
                <li><strong>⚡ Rayo (CPU Saturada):</strong> La carga supera los núcleos del servidor.</li>
            </ul>
        </div>

        <div class="ml-help-box" style="border-left-color:#f0b849;">
            <h3 style="margin-top:0;">2. El Semáforo del Tiempo (Tiempo de Ejecución)</h3>
            <ul style="line-height:1.8;">
                <li><strong>Referencia:</strong> Una página en caché debe tardar &lt; 0.5s. Sin caché, &lt; 1s.</li>
                <li><strong>Configuración:</strong> El valor por defecto de Aviso (2s) es conservador.</li>
                <li><strong>⚫ Color Negro (&lt; 0.5s):</strong> Carga rápida.</li>
                <li><strong>🟠 Color Naranja (Aviso):</strong> Atención. La web va lenta.</li>
                <li><strong>🔴 Color Rojo (Riesgo):</strong> Peligro. &gt; 2.0s.</li>
            </ul>
        </div>

        <div class="ml-help-box" style="border-left-color:#00a32a;">
            <h3 style="margin-top:0;">3. El Semáforo de la CPU (Carga del Servidor)</h3>
            <ul style="line-height:1.8;">
                <li><strong>Regla de oro:</strong> Si la Carga es menor que el número de núcleos (que ves arriba), todo va bien.</li>
                <li><strong>Hosting Compartido:</strong> Es normal ver valores altos si el servidor físico es grande. Ajusta el umbral por encima de tu "ruido de fondo".</li>
            </ul>
        </div>

        <div class="ml-help-box" style="border-left-color:#d63638;">
            <h3 style="margin-top:0;">4. El Semáforo de la Memoria (RAM)</h3>
            <ul style="line-height:1.8;">
                <li><strong>Referencia:</strong> Un WordPress moderno consume entre 60MB y 128MB.</li>
                <li><strong>⚫ Negro (&lt; 128 MB):</strong> Consumo saludable.</li>
                <li><strong>🟠 Naranja (128 - 200 MB):</strong> Consumo alto.</li>
                <li><strong>🔴 Rojo (&gt; 200 MB):</strong> Muy alto. Peligro de error.</li>
            </ul>
        </div>

        <div class="ml-help-box" style="border-left-color:#646970;">
            <h3 style="margin-top:0;">5. SQL y Tamaño (Peso HTML)</h3>
            <ul style="line-height:1.8;">
                <li><strong>SQL:</strong> &lt; 100 consultas es óptimo. &gt; 200 (Rojo) indica plugin ineficiente.</li>
                <li><strong>Tamaño:</strong>
                    <ul>
                        <li><strong>0 KB (Gris):</strong> Normal en Backend/Cron/Redirecciones.</li>
                        <li><strong>0 KB (Rojo + 💀):</strong> Error. Pantalla blanca en Frontend.</li>
                    </ul>
                </li>
            </ul>
        </div>

        <div class="ml-help-box" style="border-left-color:#2271b1;">
            <h3 style="margin-top:0;">6. Panel de Salud y Base de Datos</h3>
            <ul style="line-height:1.8;">
                <li><strong>Autoload Options:</strong> Datos que se cargan SIEMPRE. &lt; 800 es sano. &gt; 1000 es basura acumulada.</li>
                <li><strong>Transients Exp:</strong> Datos caducados. Si hay muchos, usa WP-Optimize.</li>
                <li><strong>Latencia DB:</strong> &gt; 20ms indica base de datos lenta.</li>
                <li><strong>OPcache:</strong> Acelerador PHP. Debe estar activo.</li>
            </ul>
        </div>
    </div>';
    
    echo '</div>';
    
    // ACCIONES DE MANTENIMIENTO
    echo '<div class="ml-card">
        <form method="post" style="display:inline;">';
        wp_nonce_field('clear_log_action');
        echo '<button type="submit" name="clear_log" class="button button-secondary" onclick="return confirm(\'¿Seguro que quieres borrar todos los registros?\')">Vaciar registro</button>
        </form>
        
        <form method="post" action="'.admin_url('admin-post.php').'" style="display:inline; margin-left:10px;">';
        wp_nonce_field('memory_logger_export_csv', 'export_csv_nonce');
        echo '<input type="hidden" name="action" value="memory_logger_export_csv">
        <button type="submit" class="button">Exportar CSV</button>
        </form>
    </div>';
    
    // TABLA DE REGISTROS - SIN NEGRITAS EN VALORES NORMALES
    if ( file_exists(MEMORY_LOG_FILE) ) {
        $content = @file_get_contents(MEMORY_LOG_FILE);
        $lines = array_reverse(array_slice(explode("\n", trim($content)), -200));
        
        echo '<div class="ml-card">
            <select id="ml-filter" style="margin-bottom:10px;">
                <option value="">Todo</option>
                <option value="Frontend">Frontend</option>
                <option value="Backend">Backend</option>
                <option value="Cron">Cron</option>
            </select>
            <button class="button" onclick="document.getElementById(\'ml-filter\').value=\'\'; filterTable();">Filtrar</button>
            
            <table class="wp-list-table widefat fixed striped" id="ml-table" style="font-size:12px; margin-top:10px;">
                <thead>
                    <tr>
                        <th style="width:50px;">Alerta</th>
                        <th>Fecha</th>
                        <th>Tipo</th>
                        <th style="width:60px; text-align:center;">Quién</th>
                        <th style="width:60px; text-align:center;">Estado</th>
                        <th>URL</th>
                        <th>Memoria</th>
                        <th>Tiempo</th>
                        <th>SQL</th>
                        <th>Tamaño</th>
                        <th>CPU</th>
                    </tr>
                </thead>
                <tbody>';
        
        foreach ($lines as $l) {
            if (empty($l) || strpos($l, 'DATE:') === false) continue;
            
            $row = [];
            foreach(explode(' | ', $l) as $x){ 
                $kv=explode(':',$x,2); 
                if(count($kv)==2) $row[trim($kv[0])]=trim($kv[1]); 
            }
            
            $date = $row['DATE'] ?? '-';
            $type = $row['TYPE'] ?? '-';
            $url = $row['URL'] ?? '-';
            $mem = (float)($row['MEM'] ?? 0);
            $time = (float)($row['TIME'] ?? 0);
            $sql = (int)($row['SQL'] ?? 0);
            $http = (int)($row['HTTP'] ?? 200);
            $cpu = $row['CPU'] ?? '-';
            $size = (float)($row['SIZE'] ?? 0);
            $ua = $row['UA'] ?? 'N/A';
            
            // Alertas
            $alert = '-';
            if ($type == 'Frontend' && $size == 0) $alert = '💀';
            elseif ($mem > 200) $alert = '🔥';
            elseif ($time > 5) $alert = '🐢';
            elseif ($sql > 200) $alert = '💾';
            
            $cpu_num = is_numeric($cpu) ? (float)$cpu : 0;
            if ($cpu_num > $opts['cpu_risk']) $alert = '⚡';
            
            $alert_title = memory_logger_get_alert_info($alert);
            
            // Estado HTTP
            $http_info = memory_logger_get_http_status_info($http);
            
            // Colores y peso de fuente (solo bold si hay problema)
            $mem_color = ($mem > 256) ? 'red' : (($mem > 128) ? 'orange' : 'black');
            $mem_weight = ($mem > 128) ? 'bold' : 'normal';
            
            $time_color = ($time > $opts['time_risk']) ? 'red' : (($time > $opts['time_warn']) ? 'orange' : 'black');
            $time_weight = ($time > $opts['time_warn']) ? 'bold' : 'normal';
            
            $sql_color = ($sql > 200) ? 'red' : (($sql > 100) ? 'orange' : 'black');
            $sql_weight = ($sql > 100) ? 'bold' : 'normal';
            
            $cpu_color = ($cpu_num > $opts['cpu_risk']) ? 'red' : (($cpu_num > $opts['cpu_warn']) ? 'orange' : 'black');
            $cpu_weight = ($cpu_num > $opts['cpu_warn']) ? 'bold' : 'normal';
            
            $size_color = ($type == 'Frontend' && $size == 0) ? 'red' : 'black';
            $size_weight = ($type == 'Frontend' && $size == 0) ? 'bold' : 'normal';
            
            $ua_info = memory_logger_identify_ua($ua);
            
            echo '<tr data-type="'.$type.'">
                <td style="text-align:center; font-size:18px;" title="'.esc_attr($alert_title).'">'.esc_html($alert).'</td>
                <td>'.esc_html($date).'</td>
                <td><strong>'.esc_html($type).'</strong></td>
                <td style="text-align:center; font-size:18px;" title="'.esc_attr($ua_info['text']).'">'.esc_html($ua_info['icon']).'</td>
                <td style="text-align:center; font-size:18px;" title="'.esc_attr($http_info['text']).'">'.esc_html($http_info['icon']).'</td>
                <td style="max-width:250px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="'.esc_attr($url).'">'.esc_html($url).'</td>
                <td style="color:'.$mem_color.'; font-weight:'.$mem_weight.';">'.number_format($mem, 2).' MB</td>
                <td style="color:'.$time_color.'; font-weight:'.$time_weight.';">'.number_format($time, 3).' s</td>
                <td style="color:'.$sql_color.'; font-weight:'.$sql_weight.';">'.$sql.'</td>
                <td style="color:'.$size_color.'; font-weight:'.$size_weight.';">'.number_format($size, 1).' KB</td>
                <td style="color:'.$cpu_color.'; font-weight:'.$cpu_weight.';">'.esc_html($cpu).'</td>
            </tr>';
        }
        
        echo '</tbody></table>
        <script>
        function filterTable() {
            var filter = document.getElementById("ml-filter").value;
            var rows = document.querySelectorAll("#ml-table tbody tr");
            rows.forEach(function(row) {
                if (filter === "" || row.getAttribute("data-type") === filter) {
                    row.style.display = "";
                } else {
                    row.style.display = "none";
                }
            });
        }
        document.getElementById("ml-filter").addEventListener("change", filterTable);
        </script>
        </div>';
    }
    
    echo '</div>'; // Cierre wrap
}

Comparte si te ha gustado

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *