diff --git a/scripts/create_buscar_page.php b/scripts/create_buscar_page.php new file mode 100644 index 0000000..44eae16 --- /dev/null +++ b/scripts/create_buscar_page.php @@ -0,0 +1,109 @@ + 'Búsqueda avanzada', + 'en' => 'Advanced Search', + 'fr' => 'Recherche avancée', + 'it' => 'Ricerca avanzata', + 'pt' => 'Pesquisa avançada', +]; +// Contenido mínimo (sin bloques Gutenberg). El formulario se inyecta vía the_content. +$content = '
Utiliza el formulario de búsqueda avanzada para encontrar reflexiones, artículos y más.
'; + +$has_pll = function_exists('pll_set_post_language') && function_exists('pll_save_post_translations'); +$pll_langs = (function_exists('pll_languages_list')) + ? pll_languages_list(['fields' => 'slug']) + : ['es']; + +/** Crea (o devuelve si existe) una página por slug, con idioma Polylang. */ +function fea_buscar_ensure_page(string $slug, string $title, string $content, string $lang, bool $apply, bool $has_pll) { + $existing = get_page_by_path($slug, OBJECT, 'page'); + if ($existing) { + // Asegurar que está publicada + if ($apply && $existing->post_status !== 'publish') { + wp_update_post(['ID' => $existing->ID, 'post_status' => 'publish']); + echo " · ({$lang}) página '{$slug}' existía en estado {$existing->post_status} → publicada (ID {$existing->ID})\n"; + } else { + echo " · ({$lang}) página '{$slug}' ya existe y publicada (ID {$existing->ID})\n"; + } + // Asegurar idioma asignado + if ($apply && $has_pll && function_exists('pll_get_post_language')) { + $cur = pll_get_post_language($existing->ID); + if ($cur !== $lang) { pll_set_post_language($existing->ID, $lang); echo " idioma → {$lang}\n"; } + } + return (int) $existing->ID; + } + + echo ($apply ? " · ({$lang}) creando" : " · ({$lang}) [dry] crearía") . " página '{$slug}'\n"; + if (!$apply) return 0; + + $id = wp_insert_post([ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_name' => $slug, + 'post_title' => $title, + 'post_content' => $content, + 'post_author' => 1, + ], true); + if (is_wp_error($id)) { echo " ERROR: " . $id->get_error_message() . "\n"; return 0; } + if ($has_pll) pll_set_post_language($id, $lang); + echo " creada (ID {$id})\n"; + return (int) $id; +} + +echo ($apply ? "APLICANDO" : "DRY-RUN") . " — crear/reparar /buscar\n"; + +// 1) Página ES (slug 'buscar') +$translations = []; +$es_id = fea_buscar_ensure_page('buscar', $titles['es'], $content, 'es', $apply, $has_pll); +if ($es_id) $translations['es'] = $es_id; + +// 2) Traducciones (en/fr/it/pt) sólo si Polylang activo y el idioma existe +if ($has_pll) { + foreach (['en', 'fr', 'it', 'pt'] as $lang) { + if (!in_array($lang, $pll_langs, true)) { echo " · ({$lang}) idioma no configurado en Polylang, omitido\n"; continue; } + + // Si ya hay traducción vinculada a la ES, reusarla + $linked = ($es_id && function_exists('pll_get_post')) ? (int) pll_get_post($es_id, $lang) : 0; + if ($linked) { + $p = get_post($linked); + if ($p && $p->post_status !== 'publish' && $apply) { + wp_update_post(['ID' => $linked, 'post_status' => 'publish']); + echo " · ({$lang}) traducción vinculada (ID {$linked}) estaba {$p->post_status} → publicada\n"; + } else { + echo " · ({$lang}) traducción ya vinculada (ID {$linked})\n"; + } + $translations[$lang] = $linked; + continue; + } + + // Crear/reparar por slug + $tl_id = fea_buscar_ensure_page('buscar-' . $lang, $titles[$lang], $content, $lang, $apply, $has_pll); + if ($tl_id) $translations[$lang] = $tl_id; + } + + // 3) Revincular todas las traducciones + if ($apply && count($translations) > 1) { + pll_save_post_translations($translations); + echo " · traducciones revinculadas: " . implode(', ', array_map( + fn($l, $id) => "{$l}={$id}", array_keys($translations), $translations)) . "\n"; + } +} + +if (function_exists('wp_cache_flush')) wp_cache_flush(); +echo ($apply ? "APLICADO" : "DRY-RUN") . "\n"; diff --git a/tools/e2e/shot_cat.cjs b/tools/e2e/shot_cat.cjs new file mode 100644 index 0000000..71a331e --- /dev/null +++ b/tools/e2e/shot_cat.cjs @@ -0,0 +1,11 @@ +const { chromium } = require('playwright'); +(async () => { + const b = await chromium.launch(); + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto('http://localhost:8081/buscar/', { waitUntil: 'networkidle' }); + // abrir el select de categoría y listar sus opciones + const opts = await p.$$eval('#fea-cat option', els => els.map(e => e.textContent.trim())); + console.log('opciones categoría ('+opts.length+'):', JSON.stringify(opts)); + await p.screenshot({ path: '/tmp/claude-1000/-mnt-c-Users-Chia/2802dab4-7a28-4ab7-ad16-457debcccc00/scratchpad/cat_curada.png', clip:{x:0,y:120,width:1280,height:520} }); + await b.close(); +})(); diff --git a/tools/e2e/shot_search.cjs b/tools/e2e/shot_search.cjs new file mode 100644 index 0000000..4b66395 --- /dev/null +++ b/tools/e2e/shot_search.cjs @@ -0,0 +1,19 @@ +const { chromium } = require('playwright'); +(async () => { + const b = await chromium.launch(); + // Desktop: cabecera con barra + let p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto('http://localhost:8081/', { waitUntil: 'networkidle' }); + const bar = await p.$('.fea-search-bar'); + console.log('barra presente desktop:', !!bar); + await p.screenshot({ path: '/tmp/claude-1000/search_desktop.png', clip: {x:0,y:0,width:1280,height:320} }); + // Móvil + let m = await b.newPage({ viewport: { width: 390, height: 800 } }); + await m.goto('http://localhost:8081/', { waitUntil: 'networkidle' }); + await m.screenshot({ path: '/tmp/claude-1000/search_mobile.png', clip: {x:0,y:0,width:390,height:360} }); + // Resultados + let r = await b.newPage({ viewport: { width: 1280, height: 1000 } }); + await r.goto('http://localhost:8081/?s=oraci%C3%B3n', { waitUntil: 'networkidle' }); + await r.screenshot({ path: '/tmp/claude-1000/search_results.png', clip: {x:0,y:0,width:1280,height:700} }); + await b.close(); +})(); diff --git a/tools/e2e/shot_search2.cjs b/tools/e2e/shot_search2.cjs new file mode 100644 index 0000000..41caca8 --- /dev/null +++ b/tools/e2e/shot_search2.cjs @@ -0,0 +1,19 @@ +const { chromium } = require('playwright'); +(async () => { + const b = await chromium.launch(); + // Resultados (desktop) + let r = await b.newPage({ viewport: { width: 1280, height: 1200 } }); + await r.goto('http://localhost:8081/?s=oraci%C3%B3n', { waitUntil: 'networkidle' }); + await r.screenshot({ path: '/tmp/claude-1000/res_desktop.png', clip:{x:0,y:180,width:1280,height:880} }); + // Desktop 800 ancho: barra NO debe verse (solo el Buscador del menú) + let d = await b.newPage({ viewport: { width: 900, height: 700 } }); + await d.goto('http://localhost:8081/', { waitUntil: 'networkidle' }); + const visD = await d.$eval('.fea-search-bar', el => getComputedStyle(el).display).catch(()=>'noel'); + console.log('barra display @900px:', visD); + // Móvil: barra SÍ + let m = await b.newPage({ viewport: { width: 390, height: 700 } }); + await m.goto('http://localhost:8081/', { waitUntil: 'networkidle' }); + const visM = await m.$eval('.fea-search-bar', el => getComputedStyle(el).display).catch(()=>'noel'); + console.log('barra display @390px:', visM); + await b.close(); +})(); diff --git a/tools/e2e/shot_search_advanced.cjs b/tools/e2e/shot_search_advanced.cjs new file mode 100644 index 0000000..85a48f5 --- /dev/null +++ b/tools/e2e/shot_search_advanced.cjs @@ -0,0 +1,204 @@ +/** + * shot_search_advanced.cjs — Verificación del buscador avanzado (#8) + * Comprueba: FULLTEXT, filtro autor, filtro tema, filtro cita bíblica, + * filtro fecha, combinado palabra+autor, multiidioma, formulario desktop/móvil. + * + * Uso: NODE_PATH=/home/rafa/joomla-migration/tools/e2e/node_modules node tools/e2e/shot_search_advanced.cjs + */ +const { chromium } = require('playwright'); +const path = require('path'); +const fs = require('fs'); + +const BASE = 'http://localhost:8081'; +const OUT = '/tmp/claude-1000/-mnt-c-Users-Chia/2802dab4-7a28-4ab7-ad16-457debcccc00/scratchpad'; + +// Ensure output dir +fs.mkdirSync(OUT, { recursive: true }); + +function shotPath(name) { + return path.join(OUT, name + '.png'); +} + +async function check(page, label, fn) { + try { + const result = await fn(page); + console.log(`✓ ${label}:`, result); + } catch (e) { + console.error(`✗ ${label}: ERROR`, e.message); + } +} + +(async () => { + const b = await chromium.launch({ headless: true }); + + // ── 1. FULLTEXT por relevancia ────────────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?s=oraci%C3%B3n`, { waitUntil: 'networkidle' }); + // Comprueba que hay resultados + const cards = await p.$$('.fea-archive-card'); + console.log(`✓ FULLTEXT /?s=oración: ${cards.length} tarjetas`); + // Comprueba que el formulario avanzado está presente + const form = await p.$('.fea-adv-form'); + console.log(` formulario avanzado en resultados: ${!!form}`); + const counter = await p.$('.fea-adv-count'); + const countText = counter ? await counter.textContent() : 'no counter'; + console.log(` contador: ${countText}`); + await p.screenshot({ path: shotPath('1_fulltext_oración'), fullPage: false }); + await p.close(); + } + + // ── 2. Filtro autor ───────────────────────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + // Fray Marcos = 382, Florentino Ulibarri = 395 + await p.goto(`${BASE}/?s=&fea_author=382`, { waitUntil: 'networkidle' }); + const cards = await p.$$('.fea-archive-card'); + console.log(`✓ Filtro autor (fea_author=382 Fray Marcos): ${cards.length} tarjetas`); + // Verify selected value in form + const selVal = await p.$eval('#fea-author', el => el.value).catch(() => 'n/a'); + console.log(` select author value: ${selVal}`); + await p.screenshot({ path: shotPath('2_filtro_autor'), fullPage: false }); + await p.close(); + } + + // ── 3. Filtro categoría + verificación del desplegable ─────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?fea_cat=1650`, { waitUntil: 'networkidle' }); + const cards = await p.$$('.fea-archive-card'); + console.log(`✓ Filtro categoría (fea_cat=1650 Artículos): ${cards.length} tarjetas`); + const selVal = await p.$eval('#fea-cat', el => el.value).catch(() => 'n/a'); + console.log(` select cat value: ${selVal}`); + // Label debe decir "Categoría" + const catLabel = await p.$eval('label[for="fea-cat"]', el => el.textContent).catch(() => 'n/a'); + console.log(` label categoría: "${catLabel}"`); + // Contar y listar opciones de categoría (idioma ES) + const catOpts = await p.$$eval('#fea-cat option', els => els.map(e => e.textContent)); + console.log(` nº opciones categoría: ${catOpts.length}`); + console.log(` opciones: ${catOpts.join(' | ')}`); + // No debe contener categorías de carta ni "Testament" + const badCat = catOpts.filter(t => /Testament/i.test(t)); + console.log(` opciones con 'Testament': ${badCat.length}`); + await p.screenshot({ path: shotPath('3_filtro_categoria'), fullPage: false }); + await p.close(); + } + + // ── 3b. Desplegable de AUTOR: sin "Testamento" ─────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?s=fe`, { waitUntil: 'networkidle' }); + const autOpts = await p.$$eval('#fea-author option', els => els.map(e => e.textContent)); + console.log(`✓ Desplegable autor: ${autOpts.length} opciones`); + const badAut = autOpts.filter(t => /Testament/i.test(t)); + console.log(` opciones autor con 'Testament': ${badAut.length} ${badAut.length ? '→ ' + badAut.join(', ') : ''}`); + await p.close(); + } + + // ── 4. Filtro cita bíblica (PREFIJO) ───────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?fea_cita=Jn`, { waitUntil: 'networkidle' }); + const cards = await p.$$('.fea-archive-card'); + console.log(`✓ Filtro cita PREFIJO (fea_cita=Jn): ${cards.length} tarjetas`); + const citaVal = await p.$eval('#fea-cita', el => el.value).catch(() => 'n/a'); + console.log(` input cita value: ${citaVal}`); + const chip = await p.$('.fea-adv-chip'); + const chipText = chip ? await chip.textContent() : 'no chip'; + console.log(` chip activo: ${chipText}`); + // Placeholder corto + foco para ver que NO se corta + const ph = await p.$eval('#fea-cita', el => el.placeholder).catch(() => 'n/a'); + console.log(` placeholder cita: "${ph}"`); + await p.screenshot({ path: shotPath('4_filtro_cita'), fullPage: false }); + await p.close(); + } + + // ── 4b. Cita bíblica con valor largo: comprobar que no desborda ────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?fea_cita=Jn%2020%2C%2019-31`, { waitUntil: 'networkidle' }); + // Medir overflow: scrollWidth vs clientWidth del input y de su fila + const metrics = await p.$eval('#fea-cita', el => ({ + scrollW: el.scrollWidth, clientW: el.clientWidth, + offsetW: el.offsetWidth, parentW: el.parentElement.clientWidth, + })).catch(() => null); + console.log(`✓ Cita valor largo (Jn 20, 19-31): ${JSON.stringify(metrics)}`); + const overflow = metrics ? (metrics.offsetW > metrics.parentW + 1) : 'n/a'; + console.log(` input desborda su celda: ${overflow}`); + await p.screenshot({ path: shotPath('4b_cita_largo_nocorta'), fullPage: false }); + await p.close(); + } + + // ── 5. Filtro fecha ───────────────────────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?fea_date_from=2020-01-01&fea_date_to=2020-12-31`, { waitUntil: 'networkidle' }); + const cards = await p.$$('.fea-archive-card'); + console.log(`✓ Filtro fecha (2020): ${cards.length} tarjetas`); + await p.screenshot({ path: shotPath('5_filtro_fecha'), fullPage: false }); + await p.close(); + } + + // ── 6. Combinado palabra + autor ──────────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?s=amor&fea_author=384`, { waitUntil: 'networkidle' }); // Enrique Martínez Lozano + const cards = await p.$$('.fea-archive-card'); + console.log(`✓ Combinado s=amor + fea_author=384 (Enrique M.L.): ${cards.length} tarjetas`); + await p.screenshot({ path: shotPath('6_combinado_palabra_autor'), fullPage: false }); + await p.close(); + } + + // ── 7. Multiidioma /en/?s=love ────────────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/en/?s=love`, { waitUntil: 'networkidle' }); + const cards = await p.$$('.fea-archive-card'); + // Check form action points to /en/ + const formAction = await p.$eval('.fea-adv-form', el => el.action).catch(() => 'n/a'); + console.log(`✓ Multiidioma /en/?s=love: ${cards.length} tarjetas, form action: ${formAction}`); + // Check if labels are in English + const firstLabel = await p.$eval('.fea-adv-label', el => el.textContent).catch(() => 'n/a'); + console.log(` primer label: ${firstLabel}`); + await p.screenshot({ path: shotPath('7_multiidioma_en'), fullPage: false }); + await p.close(); + } + + // ── 8a. Formulario visible en desktop ─────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/?s=fe`, { waitUntil: 'networkidle' }); + const formVisible = await p.isVisible('.fea-adv-form'); + console.log(`✓ Formulario desktop (1280px): visible=${formVisible}`); + await p.screenshot({ path: shotPath('8a_form_desktop'), fullPage: false }); + await p.close(); + } + + // ── 8b. Formulario visible en móvil ──────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 390, height: 844 } }); + await p.goto(`${BASE}/?s=fe`, { waitUntil: 'networkidle' }); + const formVisible = await p.isVisible('.fea-adv-form'); + console.log(`✓ Formulario móvil (390px): visible=${formVisible}`); + // Check advanced link in mobile header + const advLink = await p.$('.fea-adv-link'); + const advLinkVisible = advLink ? await advLink.isVisible() : false; + console.log(` enlace búsqueda avanzada móvil: visible=${advLinkVisible}`); + await p.screenshot({ path: shotPath('8b_form_mobile'), fullPage: false }); + await p.close(); + } + + // ── 9. Página /buscar ──────────────────────────────────────────────────── + { + const p = await b.newPage({ viewport: { width: 1280, height: 900 } }); + await p.goto(`${BASE}/buscar/`, { waitUntil: 'networkidle' }); + const status = p.url(); + const form = await p.$('.fea-adv-form'); + console.log(`✓ Página /buscar: url=${status}, form=${!!form}`); + await p.screenshot({ path: shotPath('9_pagina_buscar'), fullPage: false }); + await p.close(); + } + + await b.close(); + console.log(`\nCapturas guardadas en: ${OUT}`); +})(); diff --git a/wordpress/wp-content/mu-plugins/fea-search-advanced.php b/wordpress/wp-content/mu-plugins/fea-search-advanced.php new file mode 100644 index 0000000..9d6d5fd --- /dev/null +++ b/wordpress/wp-content/mu-plugins/fea-search-advanced.php @@ -0,0 +1,632 @@ + ['es' => 'Búsqueda avanzada', 'en' => 'Advanced search', + 'fr' => 'Recherche avancée', 'it' => 'Ricerca avanzata', 'pt' => 'Pesquisa avançada'], + 'word' => ['es' => 'Palabra o frase', 'en' => 'Word or phrase', + 'fr' => 'Mot ou phrase', 'it' => 'Parola o frase', 'pt' => 'Palavra ou frase'], + 'author' => ['es' => 'Autor', 'en' => 'Author', + 'fr' => 'Auteur', 'it' => 'Autore', 'pt' => 'Autor'], + 'all_authors' => ['es' => '— Cualquier autor —', 'en' => '— Any author —', + 'fr' => '— Tout auteur —', 'it' => '— Qualsiasi autore —', 'pt' => '— Qualquer autor —'], + 'topic' => ['es' => 'Categoría', 'en' => 'Category', + 'fr' => 'Catégorie', 'it' => 'Categoria', 'pt' => 'Categoria'], + 'all_topics' => ['es' => '— Cualquier categoría —', 'en' => '— Any category —', + 'fr' => '— Toute catégorie —', 'it' => '— Qualsiasi categoria —', 'pt' => '— Qualquer categoria —'], + 'biblical_ref' => ['es' => 'Cita bíblica', 'en' => 'Biblical reference', + 'fr' => 'Référence biblique', 'it' => 'Citazione biblica', 'pt' => 'Referência bíblica'], + 'biblical_ph' => ['es' => 'Ej: Jn 3', 'en' => 'E.g. Jn 3', + 'fr' => 'Ex: Jn 3', 'it' => 'Es: Gv 3', 'pt' => 'Ex: Jo 3'], + 'date_from' => ['es' => 'Desde', 'en' => 'From', + 'fr' => 'Du', 'it' => 'Dal', 'pt' => 'De'], + 'date_to' => ['es' => 'Hasta', 'en' => 'To', + 'fr' => "Jusqu'au", 'it' => 'Al', 'pt' => 'Até'], + 'search_btn' => ['es' => 'Buscar', 'en' => 'Search', + 'fr' => 'Rechercher', 'it' => 'Cerca', 'pt' => 'Pesquisar'], + 'reset_btn' => ['es' => 'Limpiar', 'en' => 'Clear', + 'fr' => 'Effacer', 'it' => 'Cancella', 'pt' => 'Limpar'], + 'results' => ['es' => 'resultado(s)', 'en' => 'result(s)', + 'fr' => 'résultat(s)', 'it' => 'risultato/i', 'pt' => 'resultado(s)'], + 'no_results' => ['es' => 'Sin resultados. Prueba con otros términos.', + 'en' => 'No results. Try other terms.', + 'fr' => 'Aucun résultat. Essayez d\'autres termes.', + 'it' => 'Nessun risultato. Prova con altri termini.', + 'pt' => 'Sem resultados. Tente outros termos.'], + 'active_filters' => ['es' => 'Filtros activos:', 'en' => 'Active filters:', + 'fr' => 'Filtres actifs:', 'it' => 'Filtri attivi:', 'pt' => 'Filtros ativos:'], + 'filter_author' => ['es' => 'Autor', 'en' => 'Author', + 'fr' => 'Auteur', 'it' => 'Autore', 'pt' => 'Autor'], + 'filter_topic' => ['es' => 'Categoría', 'en' => 'Category', + 'fr' => 'Catégorie', 'it' => 'Categoria', 'pt' => 'Categoria'], + 'filter_cita' => ['es' => 'Cita', 'en' => 'Ref.', + 'fr' => 'Réf.', 'it' => 'Cit.', 'pt' => 'Ref.'], + 'filter_date' => ['es' => 'Fecha', 'en' => 'Date', + 'fr' => 'Date', 'it' => 'Data', 'pt' => 'Data'], + 'by' => ['es' => 'por', 'en' => 'by', + 'fr' => 'par', 'it' => 'di', 'pt' => 'por'], + ]; + $row = $strings[$key] ?? []; + return $row[$lang] ?? $row['es'] ?? $key; +} + +// ───────────────────────────────────────────────────────────────── +// Query vars +// ───────────────────────────────────────────────────────────────── + +add_filter('query_vars', function (array $vars): array { + $vars[] = 'fea_author'; + $vars[] = 'fea_cat'; + $vars[] = 'fea_cita'; + $vars[] = 'fea_date_from'; + $vars[] = 'fea_date_to'; + return $vars; +}); + +// ───────────────────────────────────────────────────────────────── +// pre_get_posts — aplica los filtros avanzados +// ───────────────────────────────────────────────────────────────── + +add_action('pre_get_posts', function (WP_Query $q): void { + if (is_admin() || !$q->is_main_query()) return; + + // Leer los parámetros avanzados desde $_GET directamente (más fiable en pre_get_posts) + $fea_author = isset($_GET['fea_author']) ? (int)$_GET['fea_author'] : 0; + $fea_cat = isset($_GET['fea_cat']) ? (int)$_GET['fea_cat'] : 0; + $fea_cita = isset($_GET['fea_cita']) ? sanitize_text_field($_GET['fea_cita']) : ''; + $fea_dfr = isset($_GET['fea_date_from']) ? sanitize_text_field($_GET['fea_date_from']) : ''; + $fea_dto = isset($_GET['fea_date_to']) ? sanitize_text_field($_GET['fea_date_to']) : ''; + + $has_adv = ($fea_author > 0 || $fea_cat > 0 || $fea_cita !== '' || $fea_dfr !== '' || $fea_dto !== ''); + $is_search = $q->is_search(); + + // Activar si: es búsqueda, o si hay vars avanzadas (con o sin ?s=) + if (!$is_search && !$has_adv) return; + + // Si hay filtros avanzados pero no ?s=, convertimos el query en listado de posts + // (evitamos que WP muestre la home o una 404) + if ($has_adv && !$is_search) { + $q->set('post_type', 'post'); + $q->set('post_status', 'publish'); + // Forzamos is_search para que el template search se active + $q->is_home = false; + $q->is_front_page = false; + $q->is_archive = false; + $q->is_search = true; + } + + // Autor + if ($fea_author > 0) $q->set('author', $fea_author); + + // Categoría (tema) + if ($fea_cat > 0) $q->set('cat', $fea_cat); + + // Cita bíblica: coincidencia por PREFIJO (el valor empieza por el término, ej. "Jn"). + // Usamos REGEXP '^