chore: purgar archivos no utilizados (plan 101 fase 1)

- eliminar carpetas vacias admin/herosection y bootstrapicons
- eliminar 7 scripts legacy con credenciales hardcodeadas
- eliminar formbuilder duplicado en shared/infrastructure/ui
- eliminar 11 archivos .gitkeep en carpetas con contenido

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-12-06 18:09:52 -06:00
parent a33c43a104
commit 1e6a076904
21 changed files with 11 additions and 2466 deletions

View File

@@ -1,94 +0,0 @@
<?php
/**
* Busca casos variados de problemas de listas para validación exhaustiva
*/
$conn = new mysqli("localhost", "preciosunitarios_seo", "ACl%EEFd=V-Yvb??", "preciosunitarios_seo");
$conn->set_charset("utf8mb4");
function detectIssues($html) {
$issues = [];
libxml_use_internal_errors(true);
$doc = new DOMDocument("1.0", "UTF-8");
$wrapped = '<div id="wrapper">' . $html . '</div>';
$doc->loadHTML('<?xml encoding="UTF-8">' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$validChildren = ["li", "script", "template"];
foreach (["ul", "ol"] as $tag) {
foreach ($doc->getElementsByTagName($tag) as $list) {
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$childTag = strtolower($child->nodeName);
if (!in_array($childTag, $validChildren)) {
$issues[] = ["parent" => $tag, "child" => $childTag];
}
}
}
}
}
return $issues;
}
echo "BUSCANDO CASOS VARIADOS...\n\n";
$query = "SELECT id, page, html FROM datos_seo_pagina WHERE html IS NOT NULL AND html != '' ORDER BY id";
$result = $conn->query($query);
if (!$result) {
die("Error en query: " . $conn->error);
}
$cases = [
"many_issues" => [],
"ol_issues" => [],
"mixed_issues" => [],
"few_issues" => []
];
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row["html"]);
if (empty($issues)) continue;
$count = count($issues);
$hasOl = false;
$hasUl = false;
foreach ($issues as $issue) {
if ($issue["parent"] === "ol") $hasOl = true;
if ($issue["parent"] === "ul") $hasUl = true;
}
if ($count > 10 && count($cases["many_issues"]) < 3) {
$cases["many_issues"][] = ["id" => $row["id"], "url" => $row["page"], "count" => $count, "issues" => $issues];
}
if ($hasOl && !$hasUl && count($cases["ol_issues"]) < 3) {
$cases["ol_issues"][] = ["id" => $row["id"], "url" => $row["page"], "count" => $count, "issues" => $issues];
}
if ($hasOl && $hasUl && count($cases["mixed_issues"]) < 3) {
$cases["mixed_issues"][] = ["id" => $row["id"], "url" => $row["page"], "count" => $count, "issues" => $issues];
}
if ($count <= 2 && count($cases["few_issues"]) < 3) {
$cases["few_issues"][] = ["id" => $row["id"], "url" => $row["page"], "count" => $count, "issues" => $issues];
}
}
foreach ($cases as $type => $posts) {
echo "=== " . strtoupper($type) . " ===\n";
if (empty($posts)) {
echo " (ninguno encontrado)\n\n";
continue;
}
foreach ($posts as $post) {
echo "ID: {$post["id"]} - {$post["count"]} problemas\n";
echo "URL: {$post["url"]}\n";
echo "Tipos: ";
$types = [];
foreach ($post["issues"] as $i) {
$types[] = "<{$i["parent"]}> contiene <{$i["child"]}>";
}
echo implode(", ", array_unique($types)) . "\n\n";
}
}
$conn->close();

View File

@@ -1,411 +0,0 @@
<?php
/**
* Corrector de Listas HTML Mal Formadas usando DOMDocument
*
* PROPÓSITO: Detectar y corregir listas con estructura inválida
* - <ul>/<ol> conteniendo elementos no-<li> como hijos directos
* - Listas anidadas que son hermanas en lugar de hijas de <li>
*
* USO:
* php fix-malformed-lists-dom.php --mode=scan # Solo escanear
* php fix-malformed-lists-dom.php --mode=test # Probar corrección (1 post)
* php fix-malformed-lists-dom.php --mode=fix # Aplicar correcciones
*
* @package ROI_Theme
* @since Phase 4.4 Accessibility
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('memory_limit', '512M');
set_time_limit(600);
// Configuración
$db_config = [
'host' => 'localhost',
'database' => 'preciosunitarios_seo',
'username' => 'preciosunitarios_seo',
'password' => 'ACl%EEFd=V-Yvb??',
'charset' => 'utf8mb4'
];
// Parsear argumentos
$mode = 'scan';
foreach ($argv as $arg) {
if (strpos($arg, '--mode=') === 0) {
$mode = substr($arg, 7);
}
}
echo "==============================================\n";
echo " CORRECTOR DE LISTAS - DOMDocument\n";
echo " Modo: $mode\n";
echo " Fecha: " . date('Y-m-d H:i:s') . "\n";
echo "==============================================\n\n";
/**
* Conectar a la base de datos
*/
function connectDatabase(array $config): ?mysqli {
$conn = new mysqli(
$config['host'],
$config['username'],
$config['password'],
$config['database']
);
if ($conn->connect_error) {
echo "Error de conexión: " . $conn->connect_error . "\n";
return null;
}
$conn->set_charset($config['charset']);
return $conn;
}
/**
* Corregir listas mal formadas usando DOMDocument
*/
function fixMalformedLists(string $html): array {
$result = [
'fixed' => false,
'html' => $html,
'changes' => 0,
'details' => []
];
// Suprimir errores de HTML mal formado
libxml_use_internal_errors(true);
$doc = new DOMDocument('1.0', 'UTF-8');
// Envolver en contenedor para preservar estructura
$wrapped = '<div id="temp-wrapper">' . $html . '</div>';
$doc->loadHTML('<?xml encoding="UTF-8">' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
// Procesar todas las listas (ul y ol)
$lists = [];
foreach ($doc->getElementsByTagName('ul') as $ul) {
$lists[] = $ul;
}
foreach ($doc->getElementsByTagName('ol') as $ol) {
$lists[] = $ol;
}
$changes = 0;
foreach ($lists as $list) {
$changes += fixListChildren($list, $result['details']);
}
if ($changes > 0) {
// Extraer HTML corregido
$wrapper = $doc->getElementById('temp-wrapper');
if ($wrapper) {
$innerHTML = '';
foreach ($wrapper->childNodes as $child) {
$innerHTML .= $doc->saveHTML($child);
}
$result['html'] = $innerHTML;
$result['fixed'] = true;
$result['changes'] = $changes;
}
}
return $result;
}
/**
* Corregir hijos de una lista (solo debe contener li, script, template)
*/
function fixListChildren(DOMElement $list, array &$details): int {
$changes = 0;
$validChildren = ['li', 'script', 'template'];
$nodesToProcess = [];
// Recopilar nodos que necesitan corrección
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$nodesToProcess[] = $child;
}
}
}
// Procesar cada nodo inválido
foreach ($nodesToProcess as $node) {
$tagName = strtolower($node->nodeName);
// Si es una lista anidada (ul/ol), envolverla en <li>
if ($tagName === 'ul' || $tagName === 'ol') {
$changes += wrapInLi($list, $node, $details);
}
// Otros elementos inválidos también se envuelven en <li>
else {
$changes += wrapInLi($list, $node, $details);
}
}
return $changes;
}
/**
* Envolver un nodo en <li> o moverlo al <li> anterior
*/
function wrapInLi(DOMElement $list, DOMNode $node, array &$details): int {
$doc = $list->ownerDocument;
$tagName = strtolower($node->nodeName);
// Buscar el <li> hermano anterior
$prevLi = null;
$prev = $node->previousSibling;
while ($prev) {
if ($prev->nodeType === XML_ELEMENT_NODE && strtolower($prev->nodeName) === 'li') {
$prevLi = $prev;
break;
}
$prev = $prev->previousSibling;
}
if ($prevLi) {
// Mover el nodo al final del <li> anterior
$prevLi->appendChild($node);
$details[] = "Movido <$tagName> dentro del <li> anterior";
return 1;
} else {
// No hay <li> anterior, crear uno nuevo
$newLi = $doc->createElement('li');
$list->insertBefore($newLi, $node);
$newLi->appendChild($node);
$details[] = "Envuelto <$tagName> en nuevo <li>";
return 1;
}
}
/**
* Detectar problemas en HTML sin corregir
*/
function detectIssues(string $html): array {
$issues = [];
libxml_use_internal_errors(true);
$doc = new DOMDocument('1.0', 'UTF-8');
$wrapped = '<div id="temp-wrapper">' . $html . '</div>';
$doc->loadHTML('<?xml encoding="UTF-8">' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$validChildren = ['li', 'script', 'template'];
// Revisar ul
foreach ($doc->getElementsByTagName('ul') as $ul) {
foreach ($ul->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$issues[] = [
'list_type' => 'ul',
'invalid_child' => $tagName,
'context' => getNodeContext($child)
];
}
}
}
}
// Revisar ol
foreach ($doc->getElementsByTagName('ol') as $ol) {
foreach ($ol->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$issues[] = [
'list_type' => 'ol',
'invalid_child' => $tagName,
'context' => getNodeContext($child)
];
}
}
}
}
return $issues;
}
/**
* Obtener contexto de un nodo para debug
*/
function getNodeContext(DOMNode $node): string {
$doc = $node->ownerDocument;
$html = $doc->saveHTML($node);
return substr($html, 0, 100) . (strlen($html) > 100 ? '...' : '');
}
// ============================================
// EJECUCIÓN PRINCIPAL
// ============================================
$conn = connectDatabase($db_config);
if (!$conn) {
exit(1);
}
echo "✓ Conexión establecida\n\n";
// Contar registros
$result = $conn->query("SELECT COUNT(*) as total FROM datos_seo_pagina WHERE html IS NOT NULL AND html != ''");
$total = $result->fetch_assoc()['total'];
echo "Total de registros: $total\n\n";
if ($mode === 'scan') {
// MODO SCAN: Solo detectar problemas
echo "MODO: ESCANEO (solo detección)\n";
echo "─────────────────────────────────\n\n";
$batch_size = 100;
$offset = 0;
$affected = 0;
$total_issues = 0;
while ($offset < $total) {
$query = "SELECT id, page, html FROM datos_seo_pagina
WHERE html IS NOT NULL AND html != ''
ORDER BY id LIMIT $batch_size OFFSET $offset";
$result = $conn->query($query);
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row['html']);
if (!empty($issues)) {
$affected++;
$total_issues += count($issues);
if ($affected <= 20) {
echo "[ID: {$row['id']}] " . count($issues) . " problema(s)\n";
echo "URL: {$row['page']}\n";
foreach (array_slice($issues, 0, 2) as $issue) {
echo " - <{$issue['list_type']}> contiene <{$issue['invalid_child']}>\n";
}
echo "\n";
}
}
}
$offset += $batch_size;
if ($offset % 1000 == 0) {
echo "Procesados: $offset/$total...\n";
}
}
echo "─────────────────────────────────\n";
echo "RESUMEN:\n";
echo " Posts afectados: $affected\n";
echo " Total incidencias: $total_issues\n";
} elseif ($mode === 'test') {
// MODO TEST: Probar corrección en 1 post
echo "MODO: PRUEBA (sin guardar)\n";
echo "─────────────────────────────────\n\n";
// Buscar primer post con problemas
$query = "SELECT id, page, html FROM datos_seo_pagina
WHERE html IS NOT NULL AND html != ''
ORDER BY id LIMIT 100";
$result = $conn->query($query);
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row['html']);
if (!empty($issues)) {
echo "POST ID: {$row['id']}\n";
echo "URL: {$row['page']}\n";
echo "Problemas detectados: " . count($issues) . "\n\n";
echo "ANTES (problemas):\n";
foreach (array_slice($issues, 0, 3) as $issue) {
echo " - <{$issue['list_type']}> contiene <{$issue['invalid_child']}>\n";
echo " Contexto: " . htmlspecialchars(substr($issue['context'], 0, 80)) . "\n";
}
// Aplicar corrección
$fixResult = fixMalformedLists($row['html']);
echo "\nDESPUÉS (corrección):\n";
echo " Cambios realizados: {$fixResult['changes']}\n";
foreach ($fixResult['details'] as $detail) {
echo " - $detail\n";
}
// Verificar que no quedan problemas
$issuesAfter = detectIssues($fixResult['html']);
echo "\nVERIFICACIÓN:\n";
echo " Problemas antes: " . count($issues) . "\n";
echo " Problemas después: " . count($issuesAfter) . "\n";
if (count($issuesAfter) < count($issues)) {
echo " ✓ Reducción de problemas\n";
}
// Mostrar fragmento del HTML corregido
if ($fixResult['fixed']) {
echo "\nMUESTRA HTML CORREGIDO (primeros 500 chars):\n";
echo "─────────────────────────────────\n";
echo htmlspecialchars(substr($fixResult['html'], 0, 500)) . "...\n";
}
break;
}
}
} elseif ($mode === 'fix') {
// MODO FIX: Aplicar correcciones
echo "MODO: CORRECCIÓN (GUARDANDO CAMBIOS)\n";
echo "─────────────────────────────────\n\n";
$batch_size = 50;
$offset = 0;
$fixed_count = 0;
$error_count = 0;
while ($offset < $total) {
$query = "SELECT id, page, html FROM datos_seo_pagina
WHERE html IS NOT NULL AND html != ''
ORDER BY id LIMIT $batch_size OFFSET $offset";
$result = $conn->query($query);
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row['html']);
if (!empty($issues)) {
$fixResult = fixMalformedLists($row['html']);
if ($fixResult['fixed']) {
// Guardar HTML corregido
$stmt = $conn->prepare("UPDATE datos_seo_pagina SET html = ? WHERE id = ?");
$stmt->bind_param("si", $fixResult['html'], $row['id']);
if ($stmt->execute()) {
$fixed_count++;
echo "[ID: {$row['id']}] ✓ Corregido ({$fixResult['changes']} cambios)\n";
} else {
$error_count++;
echo "[ID: {$row['id']}] ✗ Error al guardar\n";
}
$stmt->close();
}
}
}
$offset += $batch_size;
if ($offset % 500 == 0) {
echo "Procesados: $offset/$total (corregidos: $fixed_count)\n";
}
}
echo "\n─────────────────────────────────\n";
echo "RESUMEN:\n";
echo " Posts corregidos: $fixed_count\n";
echo " Errores: $error_count\n";
}
$conn->close();
echo "\n✓ Proceso completado.\n";

View File

@@ -1,322 +0,0 @@
<?php
/**
* Corrector de Listas HTML Mal Formadas - WordPress Posts
*
* BASE DE DATOS: preciosunitarios_wp
* TABLA: wp_posts
* CAMPO: post_content
*
* USO:
* php fix-malformed-lists-wp-posts.php --mode=scan
* php fix-malformed-lists-wp-posts.php --mode=test
* php fix-malformed-lists-wp-posts.php --mode=fix
*
* @package ROI_Theme
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('memory_limit', '512M');
set_time_limit(600);
$db_config = [
'host' => 'localhost',
'database' => 'preciosunitarios_wp',
'username' => 'preciosunitarios_wp',
'password' => 'Kq#Gk%yEt+PWpVe&HZ',
'charset' => 'utf8mb4'
];
$mode = 'scan';
foreach ($argv as $arg) {
if (strpos($arg, '--mode=') === 0) {
$mode = substr($arg, 7);
}
}
echo "==============================================\n";
echo " CORRECTOR DE LISTAS - WordPress Posts\n";
echo " Base de datos: {$db_config['database']}\n";
echo " Tabla: wp_posts (post_content)\n";
echo " Modo: $mode\n";
echo " Fecha: " . date('Y-m-d H:i:s') . "\n";
echo "==============================================\n\n";
function connectDatabase(array $config): ?mysqli {
$conn = new mysqli($config['host'], $config['username'], $config['password'], $config['database']);
if ($conn->connect_error) {
echo "Error de conexión: " . $conn->connect_error . "\n";
return null;
}
$conn->set_charset($config['charset']);
return $conn;
}
function detectIssues(string $html): array {
$issues = [];
if (empty(trim($html))) return $issues;
libxml_use_internal_errors(true);
$doc = new DOMDocument('1.0', 'UTF-8');
$wrapped = '<div id="temp-wrapper">' . $html . '</div>';
$doc->loadHTML('<?xml encoding="UTF-8">' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$validChildren = ['li', 'script', 'template'];
foreach (['ul', 'ol'] as $listTag) {
foreach ($doc->getElementsByTagName($listTag) as $list) {
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$issues[] = [
'list_type' => $listTag,
'invalid_child' => $tagName
];
}
}
}
}
}
return $issues;
}
function fixMalformedLists(string $html): array {
$result = ['fixed' => false, 'html' => $html, 'changes' => 0, 'details' => []];
if (empty(trim($html))) return $result;
libxml_use_internal_errors(true);
$doc = new DOMDocument('1.0', 'UTF-8');
$wrapped = '<div id="temp-wrapper">' . $html . '</div>';
$doc->loadHTML('<?xml encoding="UTF-8">' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$lists = [];
foreach ($doc->getElementsByTagName('ul') as $ul) { $lists[] = $ul; }
foreach ($doc->getElementsByTagName('ol') as $ol) { $lists[] = $ol; }
$changes = 0;
$validChildren = ['li', 'script', 'template'];
foreach ($lists as $list) {
$nodesToProcess = [];
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$nodesToProcess[] = $child;
}
}
}
foreach ($nodesToProcess as $node) {
$tagName = strtolower($node->nodeName);
$prevLi = null;
$prev = $node->previousSibling;
while ($prev) {
if ($prev->nodeType === XML_ELEMENT_NODE && strtolower($prev->nodeName) === 'li') {
$prevLi = $prev;
break;
}
$prev = $prev->previousSibling;
}
if ($prevLi) {
$prevLi->appendChild($node);
$result['details'][] = "Movido <$tagName> dentro del <li> anterior";
$changes++;
} else {
$newLi = $doc->createElement('li');
$list->insertBefore($newLi, $node);
$newLi->appendChild($node);
$result['details'][] = "Envuelto <$tagName> en nuevo <li>";
$changes++;
}
}
}
if ($changes > 0) {
$wrapper = $doc->getElementById('temp-wrapper');
if ($wrapper) {
$innerHTML = '';
foreach ($wrapper->childNodes as $child) {
$innerHTML .= $doc->saveHTML($child);
}
$result['html'] = $innerHTML;
$result['fixed'] = true;
$result['changes'] = $changes;
}
}
return $result;
}
// EJECUCIÓN PRINCIPAL
$conn = connectDatabase($db_config);
if (!$conn) {
exit(1);
}
echo "✓ Conexión establecida\n\n";
// Solo posts publicados con contenido
$countQuery = "SELECT COUNT(*) as total FROM wp_posts
WHERE post_status = 'publish'
AND post_type IN ('post', 'page')
AND post_content IS NOT NULL
AND post_content != ''";
$result = $conn->query($countQuery);
$total = $result->fetch_assoc()['total'];
echo "Total de posts/páginas publicados: $total\n\n";
if ($mode === 'scan') {
echo "MODO: ESCANEO (solo detección)\n";
echo "─────────────────────────────────\n\n";
$batch_size = 100;
$offset = 0;
$affected = 0;
$total_issues = 0;
while ($offset < $total) {
$query = "SELECT ID, post_title, post_content, guid FROM wp_posts
WHERE post_status = 'publish'
AND post_type IN ('post', 'page')
AND post_content IS NOT NULL
AND post_content != ''
ORDER BY ID LIMIT $batch_size OFFSET $offset";
$result = $conn->query($query);
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row['post_content']);
if (!empty($issues)) {
$affected++;
$total_issues += count($issues);
if ($affected <= 20) {
echo "[ID: {$row['ID']}] " . count($issues) . " problema(s)\n";
echo "Título: " . substr($row['post_title'], 0, 60) . "\n";
foreach (array_slice($issues, 0, 2) as $issue) {
echo " - <{$issue['list_type']}> contiene <{$issue['invalid_child']}>\n";
}
echo "\n";
}
}
}
$offset += $batch_size;
if ($offset % 1000 == 0) {
echo "Procesados: $offset/$total...\n";
}
}
echo "─────────────────────────────────\n";
echo "RESUMEN:\n";
echo " Posts afectados: $affected\n";
echo " Total incidencias: $total_issues\n";
} elseif ($mode === 'test') {
echo "MODO: PRUEBA (sin guardar)\n";
echo "─────────────────────────────────\n\n";
$query = "SELECT ID, post_title, post_content FROM wp_posts
WHERE post_status = 'publish'
AND post_type IN ('post', 'page')
AND post_content IS NOT NULL
AND post_content != ''
ORDER BY ID LIMIT 200";
$result = $conn->query($query);
$tested = 0;
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row['post_content']);
if (!empty($issues) && $tested < 5) {
$tested++;
echo "POST ID: {$row['ID']}\n";
echo "Título: {$row['post_title']}\n";
echo "Problemas detectados: " . count($issues) . "\n\n";
$fixResult = fixMalformedLists($row['post_content']);
$issuesAfter = detectIssues($fixResult['html']);
echo "ANTES: " . count($issues) . " problemas\n";
echo "DESPUÉS: " . count($issuesAfter) . " problemas\n";
echo "Cambios: {$fixResult['changes']}\n";
// Verificar integridad
$before_ul = substr_count($row['post_content'], '<ul');
$after_ul = substr_count($fixResult['html'], '<ul');
$before_li = substr_count($row['post_content'], '<li');
$after_li = substr_count($fixResult['html'], '<li');
echo "Tags <ul>: $before_ul$after_ul " . ($before_ul === $after_ul ? "" : "⚠️") . "\n";
echo "Tags <li>: $before_li$after_li " . ($before_li === $after_li ? "" : "⚠️") . "\n";
if (count($issuesAfter) === 0) {
echo "✅ CORRECCIÓN EXITOSA\n";
} else {
echo "⚠️ REQUIERE REVISIÓN\n";
}
echo "─────────────────────────────────\n\n";
}
}
} elseif ($mode === 'fix') {
echo "MODO: CORRECCIÓN (GUARDANDO CAMBIOS)\n";
echo "─────────────────────────────────\n\n";
$batch_size = 50;
$offset = 0;
$fixed_count = 0;
$error_count = 0;
while ($offset < $total) {
$query = "SELECT ID, post_content FROM wp_posts
WHERE post_status = 'publish'
AND post_type IN ('post', 'page')
AND post_content IS NOT NULL
AND post_content != ''
ORDER BY ID LIMIT $batch_size OFFSET $offset";
$result = $conn->query($query);
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row['post_content']);
if (!empty($issues)) {
$fixResult = fixMalformedLists($row['post_content']);
if ($fixResult['fixed']) {
$stmt = $conn->prepare("UPDATE wp_posts SET post_content = ? WHERE ID = ?");
$stmt->bind_param("si", $fixResult['html'], $row['ID']);
if ($stmt->execute()) {
$fixed_count++;
echo "[ID: {$row['ID']}] ✓ Corregido ({$fixResult['changes']} cambios)\n";
} else {
$error_count++;
echo "[ID: {$row['ID']}] ✗ Error al guardar\n";
}
$stmt->close();
}
}
}
$offset += $batch_size;
if ($offset % 500 == 0) {
echo "Procesados: $offset/$total (corregidos: $fixed_count)\n";
}
}
echo "\n─────────────────────────────────\n";
echo "RESUMEN:\n";
echo " Posts corregidos: $fixed_count\n";
echo " Errores: $error_count\n";
}
$conn->close();
echo "\n✓ Proceso completado.\n";

View File

@@ -1,307 +0,0 @@
<?php
/**
* Script de Diagnóstico: Listas HTML Mal Formadas
*
* PROPÓSITO: Identificar posts con estructura de listas inválida
* - <ul> conteniendo <ul> como hijo directo (en lugar de dentro de <li>)
* - <ol> conteniendo <ol> como hijo directo
*
* BASE DE DATOS: preciosunitarios_seo
* TABLA: datos_seo_pagina
* CAMPO: html
*
* IMPORTANTE: Este script SOLO LEE, no modifica ningún dato.
*
* @package ROI_Theme
* @since Phase 4.4 Accessibility
*/
// Configuración de errores para debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('memory_limit', '512M');
set_time_limit(300); // 5 minutos máximo
// Credenciales de base de datos (ajustar según servidor)
$db_config = [
'host' => 'localhost',
'database' => 'preciosunitarios_seo',
'username' => 'root', // Cambiar en producción
'password' => '', // Cambiar en producción
'charset' => 'utf8mb4'
];
// Patrones regex para detectar listas mal formadas
$malformed_patterns = [
// <ul> seguido directamente de <ul> (sin estar dentro de <li>)
'ul_direct_ul' => '/<ul[^>]*>\s*(?:<li[^>]*>.*?<\/li>\s*)*<ul/is',
// Patrón más específico: </li> seguido de <ul> (hermanos en lugar de anidados)
'li_sibling_ul' => '/<\/li>\s*<ul[^>]*>/is',
// <ol> seguido directamente de <ol>
'ol_direct_ol' => '/<ol[^>]*>\s*(?:<li[^>]*>.*?<\/li>\s*)*<ol/is',
// </li> seguido de <ol> (hermanos)
'li_sibling_ol' => '/<\/li>\s*<ol[^>]*>/is',
];
/**
* Conectar a la base de datos
*/
function connectDatabase(array $config): ?mysqli {
$conn = new mysqli(
$config['host'],
$config['username'],
$config['password'],
$config['database']
);
if ($conn->connect_error) {
echo "Error de conexión: " . $conn->connect_error . "\n";
return null;
}
$conn->set_charset($config['charset']);
return $conn;
}
/**
* Analizar HTML en busca de listas mal formadas
*/
function analyzeMalformedLists(string $html, array $patterns): array {
$issues = [];
foreach ($patterns as $pattern_name => $pattern) {
if (preg_match_all($pattern, $html, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[0] as $match) {
$position = $match[1];
$context = getContextAroundPosition($html, $position, 100);
$issues[] = [
'type' => $pattern_name,
'position' => $position,
'context' => $context
];
}
}
}
return $issues;
}
/**
* Obtener contexto alrededor de una posición
*/
function getContextAroundPosition(string $html, int $position, int $length = 100): string {
$start = max(0, $position - $length);
$end = min(strlen($html), $position + $length);
$context = substr($html, $start, $end - $start);
// Limpiar para mostrar
$context = preg_replace('/\s+/', ' ', $context);
$context = htmlspecialchars($context);
if ($start > 0) {
$context = '...' . $context;
}
if ($end < strlen($html)) {
$context .= '...';
}
return $context;
}
/**
* Contar total de listas en el HTML
*/
function countListElements(string $html): array {
$ul_count = preg_match_all('/<ul[^>]*>/i', $html);
$ol_count = preg_match_all('/<ol[^>]*>/i', $html);
$li_count = preg_match_all('/<li[^>]*>/i', $html);
return [
'ul' => $ul_count,
'ol' => $ol_count,
'li' => $li_count
];
}
// ============================================
// EJECUCIÓN PRINCIPAL
// ============================================
echo "==============================================\n";
echo " DIAGNÓSTICO: Listas HTML Mal Formadas\n";
echo " Base de datos: {$db_config['database']}\n";
echo " Tabla: datos_seo_pagina\n";
echo " Fecha: " . date('Y-m-d H:i:s') . "\n";
echo "==============================================\n\n";
// Conectar
$conn = connectDatabase($db_config);
if (!$conn) {
exit(1);
}
echo "✓ Conexión establecida\n\n";
// Obtener estructura de la tabla
echo "Verificando estructura de tabla...\n";
$result = $conn->query("DESCRIBE datos_seo_pagina");
if ($result) {
echo "Columnas encontradas:\n";
while ($row = $result->fetch_assoc()) {
echo " - {$row['Field']} ({$row['Type']})\n";
}
echo "\n";
}
// Contar registros totales
$result = $conn->query("SELECT COUNT(*) as total FROM datos_seo_pagina WHERE html IS NOT NULL AND html != ''");
$total = $result->fetch_assoc()['total'];
echo "Total de registros con HTML: {$total}\n\n";
// Procesar en lotes
$batch_size = 100;
$offset = 0;
$affected_posts = [];
$total_issues = 0;
$processed = 0;
echo "Iniciando análisis...\n";
echo "─────────────────────────────────────────────\n";
while ($offset < $total) {
$query = "SELECT id, page, html FROM datos_seo_pagina
WHERE html IS NOT NULL AND html != ''
ORDER BY id
LIMIT {$batch_size} OFFSET {$offset}";
$result = $conn->query($query);
if (!$result) {
echo "Error en consulta: " . $conn->error . "\n";
break;
}
while ($row = $result->fetch_assoc()) {
$processed++;
$id = $row['id'];
$url = $row['page'] ?? 'N/A';
$html = $row['html'];
$issues = analyzeMalformedLists($html, $malformed_patterns);
if (!empty($issues)) {
$list_counts = countListElements($html);
$affected_posts[] = [
'id' => $id,
'url' => $url,
'issues' => $issues,
'list_counts' => $list_counts
];
$total_issues += count($issues);
// Mostrar progreso para posts afectados
echo "\n[ID: {$id}] " . count($issues) . " problema(s) encontrado(s)\n";
echo "URL: {$url}\n";
echo "Listas: UL={$list_counts['ul']}, OL={$list_counts['ol']}, LI={$list_counts['li']}\n";
foreach ($issues as $idx => $issue) {
echo " Problema " . ($idx + 1) . ": {$issue['type']} (pos: {$issue['position']})\n";
}
}
// Mostrar progreso cada 500 registros
if ($processed % 500 == 0) {
echo "\rProcesados: {$processed}/{$total}...";
}
}
$offset += $batch_size;
}
echo "\n\n";
echo "==============================================\n";
echo " RESUMEN DEL ANÁLISIS\n";
echo "==============================================\n\n";
echo "Registros analizados: {$processed}\n";
echo "Posts con problemas: " . count($affected_posts) . "\n";
echo "Total de incidencias: {$total_issues}\n\n";
if (count($affected_posts) > 0) {
echo "─────────────────────────────────────────────\n";
echo "DETALLE DE POSTS AFECTADOS\n";
echo "─────────────────────────────────────────────\n\n";
// Agrupar por tipo de problema
$by_type = [];
foreach ($affected_posts as $post) {
foreach ($post['issues'] as $issue) {
$type = $issue['type'];
if (!isset($by_type[$type])) {
$by_type[$type] = [];
}
$by_type[$type][] = $post['id'];
}
}
echo "Por tipo de problema:\n";
foreach ($by_type as $type => $ids) {
$unique_ids = array_unique($ids);
echo " - {$type}: " . count($unique_ids) . " posts\n";
}
echo "\n─────────────────────────────────────────────\n";
echo "LISTA DE IDs AFECTADOS (para revisión manual)\n";
echo "─────────────────────────────────────────────\n\n";
$ids_list = array_column($affected_posts, 'id');
echo "IDs: " . implode(', ', $ids_list) . "\n";
// Generar archivo de reporte
$report_file = __DIR__ . '/malformed-lists-report-' . date('Ymd-His') . '.json';
$report_data = [
'generated_at' => date('Y-m-d H:i:s'),
'database' => $db_config['database'],
'table' => 'datos_seo_pagina',
'total_analyzed' => $processed,
'total_affected' => count($affected_posts),
'total_issues' => $total_issues,
'by_type' => array_map(function($ids) {
return array_values(array_unique($ids));
}, $by_type),
'affected_posts' => $affected_posts
];
if (file_put_contents($report_file, json_encode($report_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo "\n✓ Reporte JSON guardado en:\n {$report_file}\n";
}
// Muestra de contexto para análisis
echo "\n─────────────────────────────────────────────\n";
echo "MUESTRA DE CONTEXTO (primeros 3 posts)\n";
echo "─────────────────────────────────────────────\n\n";
$sample = array_slice($affected_posts, 0, 3);
foreach ($sample as $post) {
echo "POST ID: {$post['id']}\n";
echo "URL: {$post['url']}\n";
foreach ($post['issues'] as $idx => $issue) {
echo " [{$issue['type']}]\n";
echo " Contexto: {$issue['context']}\n\n";
}
echo "───────────────────────\n";
}
} else {
echo "✓ No se encontraron listas mal formadas.\n";
}
$conn->close();
echo "\n✓ Análisis completado.\n";

View File

@@ -1,91 +0,0 @@
<?php
/**
* Script de PRUEBA - Muestra corrección propuesta sin aplicarla
*
* IMPORTANTE: Este script SOLO MUESTRA, no modifica nada.
*/
$conn = new mysqli("localhost", "preciosunitarios_seo", "ACl%EEFd=V-Yvb??", "preciosunitarios_seo");
$conn->set_charset("utf8mb4");
echo "========================================\n";
echo "ANÁLISIS DE CORRECCIÓN PROPUESTA\n";
echo "========================================\n\n";
// Patrón que encuentra: </li></ul><li>TEXTO</li><ul>
// Este patrón captura:
// - $1: </li> inicial (con espacios)
// - $2: espacios entre </ul> y <li>
// - $3: contenido del <li> (ej: <strong>Texto</strong>)
// - $4: espacios entre </li> y <ul>
$pattern = '#(</li>\s*)</ul>(\s*)<li>(.*?)</li>(\s*)<ul>#is';
$replacement = '$1<li>$3$4<ul>';
echo "PATRÓN A BUSCAR:\n";
echo " </li>\\s*</ul>\\s*<li>CONTENIDO</li>\\s*<ul>\n\n";
echo "REEMPLAZO:\n";
echo " </li><li>CONTENIDO<ul>\n\n";
// Obtener HTML del post ID 3
$result = $conn->query("SELECT id, page, html FROM datos_seo_pagina WHERE id = 3");
$row = $result->fetch_assoc();
$html = $row["html"];
$page = $row["page"];
echo "PROBANDO CON POST ID 3:\n";
echo "URL: $page\n";
echo "────────────────────────────\n\n";
// Encontrar todas las ocurrencias
preg_match_all($pattern, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
echo "Ocurrencias encontradas: " . count($matches) . "\n\n";
// Mostrar cada ocurrencia y su corrección propuesta
foreach (array_slice($matches, 0, 3) as $idx => $match) {
$full_match = $match[0][0];
$position = $match[0][1];
echo "[$idx] Posición: $position\n";
echo "ANTES:\n";
echo htmlspecialchars($full_match) . "\n\n";
$fixed = preg_replace($pattern, $replacement, $full_match);
echo "DESPUÉS:\n";
echo htmlspecialchars($fixed) . "\n";
echo "────────────────────────────\n\n";
}
// Aplicar corrección en memoria y contar diferencia
$html_fixed = preg_replace($pattern, $replacement, $html);
$before = preg_match_all($pattern, $html);
$after = preg_match_all($pattern, $html_fixed);
echo "========================================\n";
echo "RESUMEN DE CORRECCIÓN (sin aplicar):\n";
echo "========================================\n";
echo "Ocurrencias ANTES: $before\n";
echo "Ocurrencias DESPUÉS: $after\n";
echo "Reducción: " . ($before - $after) . "\n\n";
// Verificar que la estructura es válida después de la corrección
$ul_count_before = substr_count($html, '<ul');
$ul_count_after = substr_count($html_fixed, '<ul');
echo "Tags <ul> antes: $ul_count_before\n";
echo "Tags <ul> después: $ul_count_after\n";
$li_count_before = substr_count($html, '<li');
$li_count_after = substr_count($html_fixed, '<li');
echo "Tags <li> antes: $li_count_before\n";
echo "Tags <li> después: $li_count_after\n";
echo "\n========================================\n";
echo "NOTA: Este patrón elimina el </ul> prematuro\n";
echo "pero NO agrega el </li> faltante al final.\n";
echo "Se necesita un segundo paso para balancear.\n";
echo "========================================\n";
$conn->close();

View File

@@ -1,187 +0,0 @@
<?php
/**
* Prueba corrección en casos específicos variados
*/
$conn = new mysqli("localhost", "preciosunitarios_seo", "ACl%EEFd=V-Yvb??", "preciosunitarios_seo");
$conn->set_charset("utf8mb4");
// IDs a probar (casos variados)
$test_ids = [20, 23, 65, 377, 98, 107, 144];
function detectIssues($html) {
$issues = [];
libxml_use_internal_errors(true);
$doc = new DOMDocument("1.0", "UTF-8");
$doc->loadHTML('<?xml encoding="UTF-8"><div id="w">' . $html . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$validChildren = ["li", "script", "template"];
foreach (["ul", "ol"] as $tag) {
foreach ($doc->getElementsByTagName($tag) as $list) {
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$childTag = strtolower($child->nodeName);
if (!in_array($childTag, $validChildren)) {
$issues[] = "<$tag> contiene <$childTag>";
}
}
}
}
}
return $issues;
}
function fixMalformedLists($html) {
$result = ['fixed' => false, 'html' => $html, 'changes' => 0];
libxml_use_internal_errors(true);
$doc = new DOMDocument("1.0", "UTF-8");
$doc->loadHTML('<?xml encoding="UTF-8"><div id="w">' . $html . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$lists = [];
foreach ($doc->getElementsByTagName('ul') as $ul) { $lists[] = $ul; }
foreach ($doc->getElementsByTagName('ol') as $ol) { $lists[] = $ol; }
$changes = 0;
$validChildren = ["li", "script", "template"];
foreach ($lists as $list) {
$nodesToProcess = [];
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$nodesToProcess[] = $child;
}
}
}
foreach ($nodesToProcess as $node) {
$tagName = strtolower($node->nodeName);
$prevLi = null;
$prev = $node->previousSibling;
while ($prev) {
if ($prev->nodeType === XML_ELEMENT_NODE && strtolower($prev->nodeName) === 'li') {
$prevLi = $prev;
break;
}
$prev = $prev->previousSibling;
}
if ($prevLi) {
$prevLi->appendChild($node);
$changes++;
} else {
$newLi = $doc->createElement('li');
$list->insertBefore($newLi, $node);
$newLi->appendChild($node);
$changes++;
}
}
}
if ($changes > 0) {
$wrapper = $doc->getElementById('w');
if ($wrapper) {
$innerHTML = '';
foreach ($wrapper->childNodes as $child) {
$innerHTML .= $doc->saveHTML($child);
}
$result['html'] = $innerHTML;
$result['fixed'] = true;
$result['changes'] = $changes;
}
}
return $result;
}
echo "=====================================================\n";
echo " PRUEBA DE CORRECCIÓN EN CASOS VARIADOS\n";
echo "=====================================================\n\n";
$ids_str = implode(',', $test_ids);
$query = "SELECT id, page, html FROM datos_seo_pagina WHERE id IN ($ids_str)";
$result = $conn->query($query);
$all_passed = true;
while ($row = $result->fetch_assoc()) {
$id = $row['id'];
$url = $row['page'];
$html = $row['html'];
echo "─────────────────────────────────────────────────\n";
echo "POST ID: $id\n";
echo "URL: $url\n\n";
// Detectar problemas antes
$issues_before = detectIssues($html);
echo "ANTES:\n";
echo " Problemas: " . count($issues_before) . "\n";
$unique_types = array_unique($issues_before);
foreach ($unique_types as $type) {
echo " - $type\n";
}
// Aplicar corrección
$fixResult = fixMalformedLists($html);
// Detectar problemas después
$issues_after = detectIssues($fixResult['html']);
echo "\nDESPUÉS:\n";
echo " Cambios aplicados: {$fixResult['changes']}\n";
echo " Problemas restantes: " . count($issues_after) . "\n";
if (count($issues_after) > 0) {
echo " ⚠️ Problemas NO resueltos:\n";
foreach (array_unique($issues_after) as $type) {
echo " - $type\n";
}
$all_passed = false;
}
// Verificar integridad del HTML
$tags_before = [
'ul' => substr_count($html, '<ul'),
'ol' => substr_count($html, '<ol'),
'li' => substr_count($html, '<li'),
];
$tags_after = [
'ul' => substr_count($fixResult['html'], '<ul'),
'ol' => substr_count($fixResult['html'], '<ol'),
'li' => substr_count($fixResult['html'], '<li'),
];
echo "\nINTEGRIDAD DE TAGS:\n";
echo " <ul>: {$tags_before['ul']}{$tags_after['ul']} ";
echo ($tags_before['ul'] === $tags_after['ul'] ? "" : "⚠️ CAMBIÓ") . "\n";
echo " <ol>: {$tags_before['ol']}{$tags_after['ol']} ";
echo ($tags_before['ol'] === $tags_after['ol'] ? "" : "⚠️ CAMBIÓ") . "\n";
echo " <li>: {$tags_before['li']}{$tags_after['li']} ";
echo ($tags_before['li'] === $tags_after['li'] ? "" : "⚠️ CAMBIÓ") . "\n";
// Resultado
if (count($issues_after) === 0 &&
$tags_before['ul'] === $tags_after['ul'] &&
$tags_before['ol'] === $tags_after['ol']) {
echo "\n✅ RESULTADO: CORRECCIÓN EXITOSA\n";
} else {
echo "\n❌ RESULTADO: REQUIERE REVISIÓN\n";
$all_passed = false;
}
}
echo "\n=====================================================\n";
if ($all_passed) {
echo "✅ TODOS LOS CASOS PASARON LA PRUEBA\n";
} else {
echo "⚠️ ALGUNOS CASOS REQUIEREN REVISIÓN\n";
}
echo "=====================================================\n";
$conn->close();

View File

@@ -1,347 +0,0 @@
<?php
/**
* Validador de Correcciones - Genera archivos HTML para revisión visual
*
* PROPÓSITO: Crear archivos comparativos ANTES/DESPUÉS para validar
* que la corrección no rompe el contenido.
*
* USO: php validate-fix-lists.php
*
* GENERA:
* /tmp/list-fix-validation/
* ├── post_ID_before.html
* ├── post_ID_after.html
* └── comparison_report.html
*
* @package ROI_Theme
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('memory_limit', '256M');
$db_config = [
'host' => 'localhost',
'database' => 'preciosunitarios_seo',
'username' => 'preciosunitarios_seo',
'password' => 'ACl%EEFd=V-Yvb??',
'charset' => 'utf8mb4'
];
$output_dir = '/tmp/list-fix-validation';
$sample_size = 5;
echo "==============================================\n";
echo " VALIDADOR DE CORRECCIONES\n";
echo " Fecha: " . date('Y-m-d H:i:s') . "\n";
echo "==============================================\n\n";
// Crear directorio de salida
if (!is_dir($output_dir)) {
mkdir($output_dir, 0755, true);
}
// Limpiar archivos anteriores
array_map('unlink', glob("$output_dir/*.html"));
/**
* Detectar problemas en HTML
*/
function detectIssues(string $html): array {
$issues = [];
libxml_use_internal_errors(true);
$doc = new DOMDocument('1.0', 'UTF-8');
$wrapped = '<div id="temp-wrapper">' . $html . '</div>';
$doc->loadHTML('<?xml encoding="UTF-8">' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$validChildren = ['li', 'script', 'template'];
foreach (['ul', 'ol'] as $listTag) {
foreach ($doc->getElementsByTagName($listTag) as $list) {
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$issues[] = "<$listTag> contiene <$tagName>";
}
}
}
}
}
return $issues;
}
/**
* Corregir listas mal formadas
*/
function fixMalformedLists(string $html): array {
$result = ['fixed' => false, 'html' => $html, 'changes' => 0, 'details' => []];
libxml_use_internal_errors(true);
$doc = new DOMDocument('1.0', 'UTF-8');
$wrapped = '<div id="temp-wrapper">' . $html . '</div>';
$doc->loadHTML('<?xml encoding="UTF-8">' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$lists = [];
foreach ($doc->getElementsByTagName('ul') as $ul) { $lists[] = $ul; }
foreach ($doc->getElementsByTagName('ol') as $ol) { $lists[] = $ol; }
$changes = 0;
$validChildren = ['li', 'script', 'template'];
foreach ($lists as $list) {
$nodesToProcess = [];
foreach ($list->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) {
$tagName = strtolower($child->nodeName);
if (!in_array($tagName, $validChildren)) {
$nodesToProcess[] = $child;
}
}
}
foreach ($nodesToProcess as $node) {
$tagName = strtolower($node->nodeName);
$prevLi = null;
$prev = $node->previousSibling;
while ($prev) {
if ($prev->nodeType === XML_ELEMENT_NODE && strtolower($prev->nodeName) === 'li') {
$prevLi = $prev;
break;
}
$prev = $prev->previousSibling;
}
if ($prevLi) {
$prevLi->appendChild($node);
$result['details'][] = "Movido <$tagName> dentro del <li> anterior";
$changes++;
} else {
$newLi = $doc->createElement('li');
$list->insertBefore($newLi, $node);
$newLi->appendChild($node);
$result['details'][] = "Envuelto <$tagName> en nuevo <li>";
$changes++;
}
}
}
if ($changes > 0) {
$wrapper = $doc->getElementById('temp-wrapper');
if ($wrapper) {
$innerHTML = '';
foreach ($wrapper->childNodes as $child) {
$innerHTML .= $doc->saveHTML($child);
}
$result['html'] = $innerHTML;
$result['fixed'] = true;
$result['changes'] = $changes;
}
}
return $result;
}
/**
* Generar HTML wrapper para visualización
*/
function wrapForVisualization(string $content, string $title, string $status): string {
$statusColor = $status === 'error' ? '#dc3545' : '#28a745';
return <<<HTML
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>$title</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 20px; line-height: 1.6; }
.status { padding: 10px 20px; background: $statusColor; color: white; border-radius: 4px; margin-bottom: 20px; }
.content { border: 1px solid #ddd; padding: 20px; border-radius: 4px; background: #fafafa; }
ul, ol { background: #fff3cd; padding: 15px 15px 15px 35px; border-left: 4px solid #ffc107; margin: 10px 0; }
li { background: #d4edda; padding: 5px 10px; margin: 5px 0; border-left: 3px solid #28a745; }
h1, h2, h3, h4, h5, h6 { color: #333; }
p { color: #555; }
</style>
</head>
<body>
<div class="status">$status</div>
<div class="content">
$content
</div>
</body>
</html>
HTML;
}
// Conectar a DB
$conn = new mysqli($db_config['host'], $db_config['username'], $db_config['password'], $db_config['database']);
$conn->set_charset($db_config['charset']);
if ($conn->connect_error) {
die("Error de conexión: " . $conn->connect_error);
}
echo "✓ Conexión establecida\n\n";
// Buscar posts con problemas
$query = "SELECT id, page, html FROM datos_seo_pagina WHERE html IS NOT NULL AND html != '' ORDER BY id LIMIT 500";
$result = $conn->query($query);
$samples = [];
while ($row = $result->fetch_assoc()) {
$issues = detectIssues($row['html']);
if (!empty($issues) && count($samples) < $sample_size) {
$samples[] = $row;
}
}
echo "Encontrados " . count($samples) . " posts con problemas para validar\n\n";
$comparison_data = [];
foreach ($samples as $idx => $post) {
$id = $post['id'];
$url = $post['page'];
$html_before = $post['html'];
echo "─────────────────────────────────\n";
echo "POST $id: $url\n";
// Detectar problemas antes
$issues_before = detectIssues($html_before);
echo " Problemas ANTES: " . count($issues_before) . "\n";
// Aplicar corrección
$fixResult = fixMalformedLists($html_before);
$html_after = $fixResult['html'];
// Detectar problemas después
$issues_after = detectIssues($html_after);
echo " Problemas DESPUÉS: " . count($issues_after) . "\n";
echo " Cambios aplicados: " . $fixResult['changes'] . "\n";
// Guardar archivos HTML
$file_before = "$output_dir/post_{$id}_BEFORE.html";
$file_after = "$output_dir/post_{$id}_AFTER.html";
file_put_contents($file_before, wrapForVisualization(
$html_before,
"Post $id - ANTES (con errores)",
"ANTES: " . count($issues_before) . " problemas de listas"
));
file_put_contents($file_after, wrapForVisualization(
$html_after,
"Post $id - DESPUÉS (corregido)",
"DESPUÉS: " . count($issues_after) . " problemas - " . $fixResult['changes'] . " correcciones aplicadas"
));
echo " ✓ Archivos generados:\n";
echo " - $file_before\n";
echo " - $file_after\n";
// Guardar datos para reporte
$comparison_data[] = [
'id' => $id,
'url' => $url,
'issues_before' => count($issues_before),
'issues_after' => count($issues_after),
'changes' => $fixResult['changes'],
'file_before' => "post_{$id}_BEFORE.html",
'file_after' => "post_{$id}_AFTER.html"
];
}
// Generar reporte comparativo
$report_html = <<<HTML
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Reporte de Validación - Corrección de Listas</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 20px; }
h1 { color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { padding: 12px; text-align: left; border: 1px solid #ddd; }
th { background: #007bff; color: white; }
tr:nth-child(even) { background: #f8f9fa; }
.success { color: #28a745; font-weight: bold; }
.warning { color: #ffc107; font-weight: bold; }
.error { color: #dc3545; font-weight: bold; }
a { color: #007bff; text-decoration: none; }
a:hover { text-decoration: underline; }
.instructions { background: #e7f3ff; padding: 15px; border-radius: 4px; margin: 20px 0; }
</style>
</head>
<body>
<h1>Reporte de Validación - Corrección de Listas HTML</h1>
<div class="instructions">
<strong>Instrucciones:</strong>
<ol>
<li>Abre cada par de archivos (ANTES/DESPUÉS) en el navegador</li>
<li>Verifica que el contenido se muestre correctamente</li>
<li>Las listas (fondo amarillo) deben contener solo items (fondo verde)</li>
<li>Si todo se ve bien, la corrección es segura</li>
</ol>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>URL</th>
<th>Problemas Antes</th>
<th>Problemas Después</th>
<th>Cambios</th>
<th>Archivos</th>
</tr>
</thead>
<tbody>
HTML;
foreach ($comparison_data as $data) {
$status_class = $data['issues_after'] == 0 ? 'success' : ($data['issues_after'] < $data['issues_before'] ? 'warning' : 'error');
$report_html .= <<<HTML
<tr>
<td>{$data['id']}</td>
<td><a href="{$data['url']}" target="_blank">{$data['url']}</a></td>
<td class="error">{$data['issues_before']}</td>
<td class="$status_class">{$data['issues_after']}</td>
<td>{$data['changes']}</td>
<td>
<a href="{$data['file_before']}" target="_blank">ANTES</a> |
<a href="{$data['file_after']}" target="_blank">DESPUÉS</a>
</td>
</tr>
HTML;
}
$report_html .= <<<HTML
</tbody>
</table>
<p><strong>Generado:</strong> {$_SERVER['REQUEST_TIME_FLOAT']}</p>
</body>
</html>
HTML;
$report_file = "$output_dir/comparison_report.html";
file_put_contents($report_file, $report_html);
echo "\n─────────────────────────────────\n";
echo "REPORTE GENERADO:\n";
echo " $report_file\n\n";
echo "Para revisar, descarga el directorio:\n";
echo " scp -r VPSContabo:$output_dir ./validation/\n\n";
$conn->close();
echo "✓ Validación completada.\n";

View File

@@ -1,697 +0,0 @@
<?php
declare(strict_types=1);
namespace ROITheme\Shared\Infrastructure\Ui;
use ROITheme\Shared\Domain\Entities\Component;
use ROITheme\Shared\Domain\Contracts\FormBuilderInterface;
/**
* TopNotificationBarFormBuilder - Construye formulario de configuración
*
* RESPONSABILIDAD: Generar formulario HTML del admin para Top Notification Bar
*
* CARACTERÍSTICAS:
* - 3 secciones: Visibilidad, Contenido, Estilos
* - 19 campos configurables
* - Lógica condicional (data-conditional-field)
* - WordPress Media Library integration
* - Vista previa en tiempo real
*
* @package ROITheme\Shared\Infrastructure\Ui
*/
final class TopNotificationBarFormBuilder implements FormBuilderInterface
{
public function build(Component $component): string
{
$data = $component->getData();
$componentId = $component->getName();
$html = '<div class="roi-form-builder roi-top-notification-bar-form">';
// Sección de Visibilidad
$html .= $this->buildVisibilitySection($data, $componentId);
// Sección de Contenido
$html .= $this->buildContentSection($data, $componentId);
// Sección de Estilos
$html .= $this->buildStylesSection($data, $componentId);
// Vista previa
$html .= $this->buildPreviewSection($data);
$html .= '</div>';
// Agregar scripts de formulario
$html .= $this->buildFormScripts($componentId);
return $html;
}
private function buildVisibilitySection(array $data, string $componentId): string
{
$html = '<div class="roi-form-section" data-section="visibility">';
$html .= '<h3 class="roi-form-section-title">Visibilidad</h3>';
$html .= '<div class="roi-form-section-content">';
// Is Enabled
$isEnabled = $data['visibility']['is_enabled'] ?? true;
$html .= $this->buildToggle(
'is_enabled',
'Mostrar barra de notificación',
$isEnabled,
$componentId,
'Activa o desactiva la barra de notificación superior'
);
// Show On Pages
$showOn = $data['visibility']['show_on_pages'] ?? 'all';
$html .= $this->buildSelect(
'show_on_pages',
'Mostrar en',
$showOn,
[
'all' => 'Todas las páginas',
'home' => 'Solo página de inicio',
'posts' => 'Solo posts individuales',
'pages' => 'Solo páginas',
'custom' => 'Páginas específicas'
],
$componentId,
'Define en qué páginas se mostrará la barra'
);
// Custom Page IDs
$customPageIds = $data['visibility']['custom_page_ids'] ?? '';
$html .= $this->buildTextField(
'custom_page_ids',
'IDs de páginas específicas',
$customPageIds,
$componentId,
'IDs de páginas separados por comas',
'Ej: 1,5,10',
['data-conditional-field' => 'show_on_pages', 'data-conditional-value' => 'custom']
);
// Hide On Mobile
$hideOnMobile = $data['visibility']['hide_on_mobile'] ?? false;
$html .= $this->buildToggle(
'hide_on_mobile',
'Ocultar en dispositivos móviles',
$hideOnMobile,
$componentId,
'Oculta la barra en pantallas menores a 768px'
);
// Is Dismissible
$isDismissible = $data['visibility']['is_dismissible'] ?? false;
$html .= $this->buildToggle(
'is_dismissible',
'Permitir cerrar',
$isDismissible,
$componentId,
'Agrega botón X para que el usuario pueda cerrar la barra'
);
// Dismissible Cookie Days
$cookieDays = $data['visibility']['dismissible_cookie_days'] ?? 7;
$html .= $this->buildNumberField(
'dismissible_cookie_days',
'Días antes de volver a mostrar',
$cookieDays,
$componentId,
'Días que permanece oculta después de cerrarla',
1,
365,
['data-conditional-field' => 'is_dismissible', 'data-conditional-value' => 'true']
);
$html .= '</div>';
$html .= '</div>';
return $html;
}
private function buildContentSection(array $data, string $componentId): string
{
$html = '<div class="roi-form-section" data-section="content">';
$html .= '<h3 class="roi-form-section-title">Contenido</h3>';
$html .= '<div class="roi-form-section-content">';
// Icon Type
$iconType = $data['content']['icon_type'] ?? 'bootstrap';
$html .= $this->buildSelect(
'icon_type',
'Tipo de ícono',
$iconType,
[
'bootstrap' => 'Bootstrap Icons',
'custom' => 'Imagen personalizada',
'none' => 'Sin ícono'
],
$componentId,
'Selecciona el tipo de ícono a mostrar'
);
// Bootstrap Icon
$bootstrapIcon = $data['content']['bootstrap_icon'] ?? 'bi-megaphone-fill';
$html .= $this->buildTextField(
'bootstrap_icon',
'Clase de ícono Bootstrap',
$bootstrapIcon,
$componentId,
'Nombre de la clase del ícono sin el prefijo \'bi\' (ej: megaphone-fill)',
'Ej: bi-megaphone-fill',
['data-conditional-field' => 'icon_type', 'data-conditional-value' => 'bootstrap']
);
// Custom Icon URL
$customIconUrl = $data['content']['custom_icon_url'] ?? '';
$html .= $this->buildMediaField(
'custom_icon_url',
'Imagen personalizada',
$customIconUrl,
$componentId,
'Sube una imagen personalizada (recomendado: PNG 24x24px)',
['data-conditional-field' => 'icon_type', 'data-conditional-value' => 'custom']
);
// Announcement Label
$announcementLabel = $data['content']['announcement_label'] ?? 'Nuevo:';
$html .= $this->buildTextField(
'announcement_label',
'Etiqueta del anuncio',
$announcementLabel,
$componentId,
'Texto destacado en negrita antes del mensaje',
'Ej: Nuevo:, Importante:, Aviso:'
);
// Announcement Text
$announcementText = $data['content']['announcement_text'] ?? 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.';
$html .= $this->buildTextArea(
'announcement_text',
'Texto del anuncio',
$announcementText,
$componentId,
'Mensaje principal del anuncio (máximo 200 caracteres)',
3
);
// Link Enabled
$linkEnabled = $data['content']['link_enabled'] ?? true;
$html .= $this->buildToggle(
'link_enabled',
'Mostrar enlace',
$linkEnabled,
$componentId,
'Activa o desactiva el enlace de acción'
);
// Link Text
$linkText = $data['content']['link_text'] ?? 'Ver Catálogo';
$html .= $this->buildTextField(
'link_text',
'Texto del enlace',
$linkText,
$componentId,
'Texto del enlace de acción',
'',
['data-conditional-field' => 'link_enabled', 'data-conditional-value' => 'true']
);
// Link URL
$linkUrl = $data['content']['link_url'] ?? '#';
$html .= $this->buildUrlField(
'link_url',
'URL del enlace',
$linkUrl,
$componentId,
'URL de destino del enlace',
'https://',
['data-conditional-field' => 'link_enabled', 'data-conditional-value' => 'true']
);
// Link Target
$linkTarget = $data['content']['link_target'] ?? '_self';
$html .= $this->buildSelect(
'link_target',
'Abrir enlace en',
$linkTarget,
[
'_self' => 'Misma ventana',
'_blank' => 'Nueva ventana'
],
$componentId,
'Define cómo se abrirá el enlace',
['data-conditional-field' => 'link_enabled', 'data-conditional-value' => 'true']
);
$html .= '</div>';
$html .= '</div>';
return $html;
}
private function buildStylesSection(array $data, string $componentId): string
{
$html = '<div class="roi-form-section" data-section="styles">';
$html .= '<h3 class="roi-form-section-title">Estilos</h3>';
$html .= '<div class="roi-form-section-content">';
// Background Color
$bgColor = $data['styles']['background_color'] ?? '#FF8600';
$html .= $this->buildColorField(
'background_color',
'Color de fondo',
$bgColor,
$componentId,
'Color de fondo de la barra (por defecto: orange primary)'
);
// Text Color
$textColor = $data['styles']['text_color'] ?? '#FFFFFF';
$html .= $this->buildColorField(
'text_color',
'Color del texto',
$textColor,
$componentId,
'Color del texto del anuncio'
);
// Link Color
$linkColor = $data['styles']['link_color'] ?? '#FFFFFF';
$html .= $this->buildColorField(
'link_color',
'Color del enlace',
$linkColor,
$componentId,
'Color del enlace de acción'
);
// Font Size
$fontSize = $data['styles']['font_size'] ?? 'small';
$html .= $this->buildSelect(
'font_size',
'Tamaño de fuente',
$fontSize,
[
'extra-small' => 'Muy pequeño (0.75rem)',
'small' => 'Pequeño (0.875rem)',
'normal' => 'Normal (1rem)',
'large' => 'Grande (1.125rem)'
],
$componentId,
'Tamaño del texto del anuncio'
);
// Padding Vertical
$padding = $data['styles']['padding_vertical'] ?? 'normal';
$html .= $this->buildSelect(
'padding_vertical',
'Padding vertical',
$padding,
[
'compact' => 'Compacto (0.5rem)',
'normal' => 'Normal (0.75rem)',
'spacious' => 'Espacioso (1rem)'
],
$componentId,
'Espaciado vertical interno de la barra'
);
// Text Alignment
$alignment = $data['styles']['text_alignment'] ?? 'center';
$html .= $this->buildSelect(
'text_alignment',
'Alineación del texto',
$alignment,
[
'left' => 'Izquierda',
'center' => 'Centro',
'right' => 'Derecha'
],
$componentId,
'Alineación del contenido de la barra'
);
// Animation Enabled
$animationEnabled = $data['styles']['animation_enabled'] ?? false;
$html .= $this->buildToggle(
'animation_enabled',
'Activar animación',
$animationEnabled,
$componentId,
'Activa animación de entrada al cargar la página'
);
// Animation Type
$animationType = $data['styles']['animation_type'] ?? 'slide-down';
$html .= $this->buildSelect(
'animation_type',
'Tipo de animación',
$animationType,
[
'slide-down' => 'Deslizar desde arriba',
'fade-in' => 'Aparecer gradualmente'
],
$componentId,
'Tipo de animación de entrada',
['data-conditional-field' => 'animation_enabled', 'data-conditional-value' => 'true']
);
$html .= '</div>';
$html .= '</div>';
return $html;
}
private function buildPreviewSection(array $data): string
{
$html = '<div class="roi-form-section roi-preview-section">';
$html .= '<h3 class="roi-form-section-title">Vista Previa</h3>';
$html .= '<div class="roi-form-section-content">';
$html .= '<div id="roi-component-preview" class="border rounded p-3 bg-light">';
$html .= '<p class="text-muted">La vista previa se actualizará automáticamente al modificar los campos.</p>';
$html .= '</div>';
$html .= '</div>';
$html .= '</div>';
return $html;
}
private function buildToggle(string $name, string $label, bool $value, string $componentId, string $description = ''): string
{
$fieldId = "roi_{$componentId}_{$name}";
$checked = $value ? 'checked' : '';
$html = '<div class="roi-form-field roi-form-field-toggle mb-3">';
$html .= '<div class="form-check form-switch">';
$html .= sprintf(
'<input type="checkbox" class="form-check-input" id="%s" name="roi_component[%s][%s]" value="1" %s>',
esc_attr($fieldId),
esc_attr($componentId),
esc_attr($name),
$checked
);
$html .= sprintf('<label class="form-check-label" for="%s">%s</label>', esc_attr($fieldId), esc_html($label));
$html .= '</div>';
if (!empty($description)) {
$html .= sprintf('<small class="form-text text-muted">%s</small>', esc_html($description));
}
$html .= '</div>';
return $html;
}
private function buildTextField(string $name, string $label, string $value, string $componentId, string $description = '', string $placeholder = '', array $attrs = []): string
{
$fieldId = "roi_{$componentId}_{$name}";
$html = '<div class="roi-form-field roi-form-field-text mb-3">';
$html .= sprintf('<label for="%s" class="form-label">%s</label>', esc_attr($fieldId), esc_html($label));
$attrString = $this->buildAttributesString($attrs);
$html .= sprintf(
'<input type="text" class="form-control" id="%s" name="roi_component[%s][%s]" value="%s" placeholder="%s"%s>',
esc_attr($fieldId),
esc_attr($componentId),
esc_attr($name),
esc_attr($value),
esc_attr($placeholder),
$attrString
);
if (!empty($description)) {
$html .= sprintf('<small class="form-text text-muted">%s</small>', esc_html($description));
}
$html .= '</div>';
return $html;
}
private function buildTextArea(string $name, string $label, string $value, string $componentId, string $description = '', int $rows = 3, array $attrs = []): string
{
$fieldId = "roi_{$componentId}_{$name}";
$html = '<div class="roi-form-field roi-form-field-textarea mb-3">';
$html .= sprintf('<label for="%s" class="form-label">%s</label>', esc_attr($fieldId), esc_html($label));
$attrString = $this->buildAttributesString($attrs);
$html .= sprintf(
'<textarea class="form-control" id="%s" name="roi_component[%s][%s]" rows="%d"%s>%s</textarea>',
esc_attr($fieldId),
esc_attr($componentId),
esc_attr($name),
$rows,
$attrString,
esc_textarea($value)
);
if (!empty($description)) {
$html .= sprintf('<small class="form-text text-muted">%s</small>', esc_html($description));
}
$html .= '</div>';
return $html;
}
private function buildSelect(string $name, string $label, string $value, array $options, string $componentId, string $description = '', array $attrs = []): string
{
$fieldId = "roi_{$componentId}_{$name}";
$html = '<div class="roi-form-field roi-form-field-select mb-3">';
$html .= sprintf('<label for="%s" class="form-label">%s</label>', esc_attr($fieldId), esc_html($label));
$attrString = $this->buildAttributesString($attrs);
$html .= sprintf(
'<select class="form-select" id="%s" name="roi_component[%s][%s]"%s>',
esc_attr($fieldId),
esc_attr($componentId),
esc_attr($name),
$attrString
);
foreach ($options as $optValue => $optLabel) {
$selected = ($value === $optValue) ? 'selected' : '';
$html .= sprintf(
'<option value="%s" %s>%s</option>',
esc_attr($optValue),
$selected,
esc_html($optLabel)
);
}
$html .= '</select>';
if (!empty($description)) {
$html .= sprintf('<small class="form-text text-muted">%s</small>', esc_html($description));
}
$html .= '</div>';
return $html;
}
private function buildNumberField(string $name, string $label, $value, string $componentId, string $description = '', int $min = null, int $max = null, array $attrs = []): string
{
$fieldId = "roi_{$componentId}_{$name}";
$html = '<div class="roi-form-field roi-form-field-number mb-3">';
$html .= sprintf('<label for="%s" class="form-label">%s</label>', esc_attr($fieldId), esc_html($label));
$attrs['type'] = 'number';
if ($min !== null) {
$attrs['min'] = $min;
}
if ($max !== null) {
$attrs['max'] = $max;
}
$attrString = $this->buildAttributesString($attrs);
$html .= sprintf(
'<input class="form-control" id="%s" name="roi_component[%s][%s]" value="%s"%s>',
esc_attr($fieldId),
esc_attr($componentId),
esc_attr($name),
esc_attr($value),
$attrString
);
if (!empty($description)) {
$html .= sprintf('<small class="form-text text-muted">%s</small>', esc_html($description));
}
$html .= '</div>';
return $html;
}
private function buildUrlField(string $name, string $label, string $value, string $componentId, string $description = '', string $placeholder = '', array $attrs = []): string
{
$attrs['type'] = 'url';
return $this->buildTextField($name, $label, $value, $componentId, $description, $placeholder, $attrs);
}
private function buildColorField(string $name, string $label, string $value, string $componentId, string $description = ''): string
{
$fieldId = "roi_{$componentId}_{$name}";
$html = '<div class="roi-form-field roi-form-field-color mb-3">';
$html .= sprintf('<label for="%s" class="form-label">%s</label>', esc_attr($fieldId), esc_html($label));
$html .= '<div class="input-group">';
$html .= sprintf(
'<input type="color" class="form-control form-control-color" id="%s" name="roi_component[%s][%s]" value="%s">',
esc_attr($fieldId),
esc_attr($componentId),
esc_attr($name),
esc_attr($value)
);
$html .= sprintf(
'<input type="text" class="form-control" value="%s" readonly>',
esc_attr($value)
);
$html .= '</div>';
if (!empty($description)) {
$html .= sprintf('<small class="form-text text-muted">%s</small>', esc_html($description));
}
$html .= '</div>';
return $html;
}
private function buildMediaField(string $name, string $label, string $value, string $componentId, string $description = '', array $attrs = []): string
{
$fieldId = "roi_{$componentId}_{$name}";
$html = '<div class="roi-form-field roi-form-field-media mb-3">';
$html .= sprintf('<label for="%s" class="form-label">%s</label>', esc_attr($fieldId), esc_html($label));
$html .= '<div class="input-group">';
$attrString = $this->buildAttributesString($attrs);
$html .= sprintf(
'<input type="text" class="form-control" id="%s" name="roi_component[%s][%s]" value="%s" readonly%s>',
esc_attr($fieldId),
esc_attr($componentId),
esc_attr($name),
esc_attr($value),
$attrString
);
$html .= sprintf(
'<button type="button" class="btn btn-primary roi-media-upload-btn" data-target="%s">Seleccionar</button>',
esc_attr($fieldId)
);
$html .= '</div>';
if (!empty($value)) {
$html .= sprintf('<div class="mt-2"><img src="%s" alt="Preview" style="max-width: 100px; height: auto;"></div>', esc_url($value));
}
if (!empty($description)) {
$html .= sprintf('<small class="form-text text-muted">%s</small>', esc_html($description));
}
$html .= '</div>';
return $html;
}
private function buildAttributesString(array $attrs): string
{
$attrString = '';
foreach ($attrs as $key => $value) {
$attrString .= sprintf(' %s="%s"', esc_attr($key), esc_attr($value));
}
return $attrString;
}
private function buildFormScripts(string $componentId): string
{
return <<<SCRIPT
<script>
(function($) {
'use strict';
$(document).ready(function() {
// Conditional logic
$('[data-conditional-field]').each(function() {
const field = $(this);
const targetFieldName = field.data('conditional-field');
const targetValue = field.data('conditional-value');
const targetField = $('[name*="[' + targetFieldName + ']"]');
function updateVisibility() {
let currentValue;
if (targetField.is(':checkbox')) {
currentValue = targetField.is(':checked') ? 'true' : 'false';
} else {
currentValue = targetField.val();
}
if (currentValue === targetValue) {
field.closest('.roi-form-field').show();
} else {
field.closest('.roi-form-field').hide();
}
}
targetField.on('change', updateVisibility);
updateVisibility();
});
// Media upload
$('.roi-media-upload-btn').on('click', function(e) {
e.preventDefault();
const button = $(this);
const targetId = button.data('target');
const targetField = $('#' + targetId);
const mediaUploader = wp.media({
title: 'Seleccionar imagen',
button: { text: 'Usar esta imagen' },
multiple: false
});
mediaUploader.on('select', function() {
const attachment = mediaUploader.state().get('selection').first().toJSON();
targetField.val(attachment.url);
const preview = targetField.closest('.roi-form-field-media').find('img');
if (preview.length) {
preview.attr('src', attachment.url);
} else {
targetField.closest('.input-group').after('<div class="mt-2"><img src="' + attachment.url + '" alt="Preview" style="max-width: 100px; height: auto;"></div>');
}
});
mediaUploader.open();
});
// Color picker sync
$('.form-control-color').on('change', function() {
$(this).next('input[type="text"]').val($(this).val());
});
// Auto-update preview
$('.roi-form-field input, .roi-form-field select, .roi-form-field textarea').on('change keyup', function() {
updatePreview();
});
function updatePreview() {
// Aquí iría la lógica para actualizar la vista previa en tiempo real
console.log('Preview updated');
}
});
})(jQuery);
</script>
SCRIPT;
}
public function supports(string $componentType): bool
{
return $componentType === 'top-notification-bar';
}
}