feat(analytics): Dashboard v2 con recomendaciones accionables y UX mejorada

- Agregar KPIs con tendencias vs período anterior (↑↓% comparativo)
- Implementar secciones de recomendaciones: Contenido a Crear, CTR 0%,
  Quick Wins, Contenido Estrella, Contenido en Decadencia
- Convertir listados a tablas con columnas separadas para mejor legibilidad
- Agregar botones Editar + Ver en todas las tablas de posts
- Ocultar secciones vacías dinámicamente (Búsquedas Sin Resultados)
- Relajar criterios Quick Wins: pos 2-15, CTR ≥2%, búsquedas ≥2
- Incluir distribución de clicks por posición con barras de progreso
- Agregar exportación a Markdown para análisis con IA

Archivos nuevos:
- admin/class-analytics-dashboard.php (UI del dashboard)
- admin/class-metrics-repository.php (queries de métricas)
- admin/assets/dashboard.css (estilos Bootstrap 5)
- admin/assets/dashboard.js (interactividad y export)
- sql/create-indices.sql (índices para optimización)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-12-03 20:15:56 -06:00
parent 255d720db6
commit 41fb658ca7
6 changed files with 1821 additions and 2 deletions

233
admin/assets/dashboard.js Normal file
View File

@@ -0,0 +1,233 @@
/**
* ROI APU Search - Analytics Dashboard Scripts
*
* @package ROI_APU_Search
* @since 1.2.0
*/
(function() {
'use strict';
/**
* Initialize Bootstrap tooltips
*/
function initTooltips() {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
if (tooltipTriggerList.length > 0 && typeof bootstrap !== 'undefined') {
[...tooltipTriggerList].map(el => new bootstrap.Tooltip(el));
}
}
/**
* Copy markdown to clipboard
* Implementation in FASE 9
*/
function copyMarkdown() {
const markdown = generateMarkdown();
navigator.clipboard.writeText(markdown).then(function() {
showSuccessMessage('Copiado: El resumen está en tu portapapeles.');
}).catch(function(err) {
console.error('Error al copiar:', err);
showErrorMessage('Error al copiar al portapapeles.');
});
}
/**
* Generate comprehensive markdown report from dashboard data (v2)
*/
function generateMarkdown() {
const data = window.roiApuDashboardData;
if (!data) {
return '# Error: No hay datos disponibles';
}
let md = '';
// Header
md += '# Reporte Analytics - Buscador APUs\n\n';
md += `**Período**: Últimos ${data.period} días\n`;
md += `**Generado**: ${data.generated}\n`;
md += `**Sitio**: ${data.siteUrl}\n\n`;
// KPIs Table
md += '## Métricas Clave\n\n';
md += '| Métrica | Valor |\n';
md += '|---------|-------|\n';
md += `| Búsquedas totales | ${data.kpis.totalBusquedas} |\n`;
md += `| CTR (Click-Through Rate) | ${data.kpis.ctr} |\n`;
md += `| Búsquedas sin resultados | ${data.kpis.sinResultados} |\n`;
md += `| Posición promedio clicks | ${data.kpis.posProm} |\n\n`;
// Click Distribution
if (data.clickDistribution && data.clickDistribution.length > 0) {
md += '## Distribución de Clicks por Posición\n\n';
md += '| Posición | Clicks | Porcentaje |\n';
md += '|----------|--------|------------|\n';
data.clickDistribution.forEach(function(dist) {
md += `| ${dist.posicion} | ${dist.clicks} | ${dist.porcentaje}% |\n`;
});
md += '\n';
}
md += '---\n\n';
md += '## RECOMENDACIONES ACCIONABLES\n\n';
// 🔴 Urgent: Zero Results
if (data.zeroResults && data.zeroResults.length > 0) {
md += '### 🔴 ACCIÓN URGENTE: Contenido a Crear\n\n';
md += 'Los usuarios buscan esto pero NO encuentran resultados:\n\n';
md += '| Término | Frecuencia |\n';
md += '|---------|------------|\n';
data.zeroResults.forEach(function(term) {
md += `| ${term.term} | ${term.frecuencia} veces |\n`;
});
md += '\n**Acción**: Crear APUs o contenido para estos términos.\n\n';
}
// 🟡 CTR 0% Section
if (data.ctrZero && data.ctrZero.length > 0) {
md += '### 🟡 REVISAR: Títulos con CTR 0%\n\n';
md += 'Estos términos tienen resultados pero nadie hace click:\n\n';
md += '| Término | Búsquedas | Resultados |\n';
md += '|---------|-----------|------------|\n';
data.ctrZero.forEach(function(term) {
md += `| ${term.term} | ${term.busquedas} | ${term.resultados} |\n`;
});
md += '\n**Acción**: Mejorar títulos y descripciones de los APUs mostrados.\n\n';
}
// 🎯 Quick Wins
if (data.quickWins && data.quickWins.length > 0) {
md += '### 🎯 QUICK WINS: Oportunidades Fáciles\n\n';
md += 'Términos en posición 4-10 con buen CTR (una pequeña mejora = top 3):\n\n';
md += '| Término | Búsquedas | CTR | Pos. Actual |\n';
md += '|---------|-----------|-----|-------------|\n';
data.quickWins.forEach(function(term) {
md += `| ${term.term} | ${term.busquedas} | ${term.ctr}% | ${term.posProm} |\n`;
});
md += '\n**Acción**: Optimizar estos APUs para subir al top 3.\n\n';
}
// 📉 Decay Content
if (data.decayContent && data.decayContent.length > 0) {
md += '### 📉 ATENCIÓN: Contenido en Decadencia\n\n';
md += 'Posts que perdieron >20% clicks vs período anterior:\n\n';
md += '| Título | Cambio | Clicks (antes → ahora) |\n';
md += '|--------|--------|------------------------|\n';
data.decayContent.forEach(function(post) {
md += `| [${post.title}](${post.url}) | ${post.cambioPct}% | ${post.clicksAnterior}${post.clicksActual} |\n`;
});
md += '\n**Acción**: Revisar si el contenido está desactualizado.\n\n';
}
// 🟢 Star Content
if (data.contenidoEstrella && data.contenidoEstrella.length > 0) {
md += '### 🟢 MANTENER: Tu Contenido Estrella\n\n';
md += 'Posts con más clicks - mantén este contenido actualizado:\n\n';
md += '| Título | Clicks | Pos. Prom. |\n';
md += '|--------|--------|------------|\n';
data.contenidoEstrella.forEach(function(post) {
md += `| [${post.title}](${post.url}) | ${post.clicks} | ${post.posProm} |\n`;
});
md += '\n**Acción**: Mantener actualizado y considerar contenido relacionado.\n\n';
}
// Infraposicionados Section
if (data.infraposicionados && data.infraposicionados.length > 0) {
md += '### 🔼 OPORTUNIDAD: Posts Infraposicionados\n\n';
md += 'Estos posts reciben clicks pero aparecen muy abajo:\n\n';
md += '| Título | Clicks | Pos. Prom. |\n';
md += '|--------|--------|------------|\n';
data.infraposicionados.forEach(function(post) {
md += `| ${post.title} | ${post.clicks} | ${post.posProm} |\n`;
});
md += '\n**Acción**: Mejorar scoring o relevancia de estos APUs.\n\n';
}
md += '---\n\n';
// Summary stats
md += '## Resumen de Datos\n\n';
md += `- **Términos únicos buscados**: ${data.totals.searches}\n`;
md += `- **Posts con clicks**: ${data.totals.clicks}\n`;
md += `- **Términos sin resultados**: ${data.totals.zeroResults}\n\n`;
// Questions for analysis
md += '## Preguntas para Análisis con IA\n\n';
md += '1. ¿Qué contenido debería crear primero basándome en las búsquedas sin resultados?\n';
md += '2. ¿Por qué algunos términos tienen resultados pero CTR 0%? ¿Qué puedo mejorar?\n';
md += '3. ¿Cómo optimizo los Quick Wins para llegar al top 3?\n';
md += '4. ¿Qué patrones veo en mi contenido estrella que debería replicar?\n';
md += '5. ¿Hay contenido en decadencia que debería actualizar urgentemente?\n\n';
// Footer
md += '---\n';
md += '*Generado por ROI APU Search Dashboard v2*\n';
md += '*Comparte este reporte con Claude para obtener recomendaciones detalladas.*\n';
return md;
}
/**
* Show success message
*/
function showSuccessMessage(message) {
const container = document.getElementById('alertContainer');
if (!container) return;
container.innerHTML = `
<div class="admin-notice notice-success mb-3" role="alert">
<i class="bi bi-check-circle me-2"></i>
<strong>${message}</strong>
</div>
`;
// Auto-hide after 3 seconds
setTimeout(function() {
container.innerHTML = '';
}, 3000);
}
/**
* Show error message
*/
function showErrorMessage(message) {
const container = document.getElementById('alertContainer');
if (!container) return;
container.innerHTML = `
<div class="admin-notice notice-error mb-3" role="alert">
<i class="bi bi-exclamation-circle me-2"></i>
<strong>${message}</strong>
</div>
`;
}
/**
* Initialize dashboard
*/
function init() {
initTooltips();
// Bind export button
const exportBtn = document.getElementById('btn-export-md');
if (exportBtn) {
exportBtn.addEventListener('click', copyMarkdown);
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Expose functions globally for debugging
window.roiApuDashboard = {
copyMarkdown: copyMarkdown,
initTooltips: initTooltips
};
})();