Skip to content

Todo para WordPress, el desarrollo web — y mucho más

🔍 JQuery: cómo encontrar un elemento con texto específico

🔍 JQuery: cómo encontrar un elemento con texto específico

Un desarrollador está creando un diseño de tarjeta de producto en WooCommerce. Todo va según lo planeado hasta que llega una tarea desde el panel de administración: «elimine la insignia Destacado y la coma que le sigue, pero solo para los productos dentro de la categoría contenedora». El procesamiento manual no es una opción, hay cientos de productos. Necesitamos un script que encuentre el bloque con el texto «featured» por sí mismo, alcance el padre, tome el span vecino y elimine toda la construcción.

Y aquí es donde jQuery entra en escena con sus cadenas: .each() para la iteración, .find() para la búsqueda, :contains() para filtrar por texto, .closest() para subir al padre, .next() para moverse al vecino y .remove() para la limpieza. Cinco métodos, y la tarea se resuelve en cuatro líneas. A continuación desgloso cada paso: desde el selector hasta la limpieza final, explicando la lógica y las trampas.

💡 Resumen rápido:

  • Primero encontramos el bloque objetivo con texto mediante el selector contains y subimos a su padre mediante closest
  • Luego nos movemos al elemento vecino mediante next y eliminamos con precisión ambos bloques mediante remove
  • Al final depuramos el script y analizamos errores típicos con pérdida de contexto y next codicioso

Tarea: qué buscamos y qué eliminamos

La estructura HTML original se ve aproximadamente así:

1<span class="tg-cats-holder">
2 <a class="product_visibility" href="...">
3 <span class="tg-item-term">featured</span>
4 </a>
5 <span>, </span>
6 <a class="product_visibility" href="...">
7 <span class="tg-item-term">sale</span>
8 </a>
9 <span>, </span>
10</span>

El objetivo clave es encontrar <span class="tg-item-term"> que contenga el texto featured, subir al padre <a class="product_visibility">, luego moverse al siguiente <span> (con la coma) y eliminarlo. Después, eliminar el <a class="product_visibility"> con la insignia misma. Otras etiquetas (sale, etc.) permanecen intactas.

Script listo

1jQuery(document).ready(function($) {
2 $('span.tg-cats-holder').each(function() {
3 $(this)
4 .find('span.tg-item-term:contains("featured")')
5 .closest('a.product_visibility')
6 .next('span:contains(",")')
7 .remove();
8
9 $(this)
10 .find('span.tg-item-term:contains("featured")')
11 .closest('a.product_visibility')
12 .remove();
13 });
14});

El código está deliberadamente dividido en dos cadenas en lugar de colapsarse en una. La razón es simple: después de .remove() de un elemento, la cadena se rompe, no hay a dónde ir más. Así que primero limpiamos el span vecino con la coma (primera cadena), luego eliminamos el bloque mismo (segunda).

Desglose paso a paso

Paso 1: entrar en cada contenedor

1$('span.tg-cats-holder').each(function() {

El método .each() recorre cada elemento de la colección, en nuestro caso todos los <span class="tg-cats-holder"> de la página. Si hay múltiples contenedores (diferentes categorías de productos), el script funcionará en cada uno de forma independiente. Dentro de la función callback, this apunta al contenedor actual, toda la búsqueda posterior parte de él.

Paso 2: buscar el bloque con texto mediante:contains()

1.find('span.tg-item-term:contains("featured")')

El selector :contains("featured") encuentra un elemento dentro del cual (incluyendo nodos hijos) existe la subcadena «featured». Tres matices importantes:

  • Sensibilidad a mayúsculas. Las cadenas «Featured», «FEATURED» y «featured» son diferentes. Si la insignia está escrita de forma distinta en el panel de administración, añada una comprobación mediante .filter() con .toLowerCase(), ejemplo en la sección de preguntas frecuentes.

  • Búsqueda a través de todos los descendientes. :contains() verifica el contenido de texto tanto del elemento mismo como de todos sus hijos. Si hay otro <strong> dentro de <span class="tg-item-term">, el texto dentro de él también cuenta.

  • Este no es un selector de rendimiento. :contains() no está acelerado por querySelectorAll nativo, jQuery recorre todos los candidatos y lee .textContent. Para páginas con cientos de elementos, es mejor acotar la selección con una clase o atributo adicional.

El método .find() limita la búsqueda a los descendientes del contenedor actual, no a toda la página, esto proporciona tanto un aumento de velocidad como protección contra falsos positivos.

Paso 3: subir al padre mediante.closest()

1.closest('a.product_visibility')

.closest() sube hacia arriba por el árbol desde el elemento encontrado y devuelve el ancestro más cercano que coincida con el selector. A diferencia de .parent(), no está limitado a un nivel, subirá hasta <html> hasta que encuentre una coincidencia o llegue a la raíz.

Si hubiéramos tomado .parent() en lugar de .closest(), el script se rompería con cualquier anidamiento: <span class="tg-item-term"><strong><a class="product_visibility">, .parent() devolvería <strong>, no el enlace necesario. .closest() asegura contra tales sorpresas.

Paso 4: saltar al vecino mediante.next()

1.next('span:contains(",")')

.next() toma el elemento hermano inmediatamente siguiente del mismo padre. El filtro span:contains(",") garantiza que tomemos exactamente el span con la coma, no cualquier elemento siguiente. Sin el filtro, .next() tomaría el primer nodo hermano disponible, por ejemplo, otro <a class="product_visibility"> con la siguiente etiqueta.

La diferencia entre .next() y .nextAll(): el primero toma exactamente un elemento, el segundo toma todos los hermanos subsiguientes. Aquí necesitamos un golpe preciso, así que .next().

Paso 5: eliminar

1.remove();

.remove() corta el elemento del DOM por completo, junto con los manejadores de eventos y los datos de jQuery. Si solo necesita ocultar (con la perspectiva de devolver), use .hide() o .detach(). Pero para la tarea «eliminar la insignia para siempre», solo .remove().

El orden importa: primero eliminamos el span vecino (primera cadena), solo después el bloque mismo (segunda). Si eliminamos el bloque primero, el span vecino perderá contexto, .next() funcionará desde un elemento diferente o devolverá una colección vacía.

Ejemplo ampliado: búsqueda insensible a mayúsculas

En un proyecto real, el texto de la etiqueta puede venir con diferente capitalización: featured, Featured, FEATURED. El :contains() estándar no manejará esto. La solución es un selector personalizado basado en .filter():

1$('span.tg-cats-holder').each(function() {
2 $(this).find('span.tg-item-term').filter(function() {
3 return $(this).text().toLowerCase().indexOf('featured') !== -1;
4 }).closest('a.product_visibility').each(function() {
5 $(this).next('span:contains(",")').remove();
6 $(this).remove();
7 });
8});

Aquí .filter() con una función callback verifica el texto en minúsculas de cada candidato. Hay ligeramente más líneas, pero el script no depende de exactamente cómo se ingresó el texto en el panel de administración.

Alternativa en JavaScript puro

Si jQuery no se usa en absoluto en el proyecto, la misma lógica se escribe en JS vainilla:

1document.querySelectorAll('span.tg-cats-holder').forEach(function(holder) {
2 holder.querySelectorAll('span.tg-item-term').forEach(function(term) {
3 if (term.textContent.includes('featured')) {
4 const link = term.closest('a.product_visibility');
5 const nextSpan = link.nextElementSibling;
6 if (nextSpan && nextSpan.matches('span') && nextSpan.textContent.includes(',')) {
7 nextSpan.remove();
8 }
9 link.remove();
10 }
11 });
12});

El mismo algoritmo: iterar contenedores → buscar por texto mediante .textContent.includes() → subir al padre mediante .closest() nativo → verificar y eliminar vecino → eliminar bloque. La implementación nativa es más larga pero no arrastra una dependencia de jQuery.

Video corto sobre el tema, desglose de :contains() y cadenas de navegación en jQuery:

⁉️🤔 Preguntas frecuentes

¿Por qué :contains() no encuentra texto que veo en la página?

Lo más probable es que el problema esté en las mayúsculas o los espacios. :contains("Featured") no encontrará «featured», este es un selector sensible a mayúsculas. Además, el navegador puede normalizar espacios dentro del HTML no como usted espera. Verifique el contenido exacto mediante console.log($('selector').text()) y copie la cadena tal cual en :contains().

¿Se puede prescindir de .closest() y usar una cadena fija .parent().parent()?

Técnicamente, sí. En la práctica, no se recomienda. Una cadena rígida .parent().parent() se romperá con cualquier cambio en el anidamiento: se añadió un envoltorio, se cambió a un tema diferente, el script se detuvo. .closest('a.product_visibility') es más flexible: busca el elemento coincidente más cercano en cualquier nivel hacia arriba. Se escribe una vez, funciona independientemente de la estructura.

El script elimina el span equivocado. ¿Cómo depurar?

Primer paso, inserte console.log() antes de cada .remove() y verifique qué entra exactamente en la selección. Segundo, refine el selector para .next(). Sin un filtro, .next() toma cualquier elemento siguiente al mismo nivel, incluso si es otro <a> o <div>. Añada span:contains(",") o una clase más específica, y verifique que el vecino sea realmente el correcto.

¿Necesito envolver el script en jQuery(document).ready()?

Sí, si el script está en <head> o se conecta antes de la renderización del DOM. La construcción jQuery(document).ready(function($){ ... }) garantiza que el código se ejecute solo después de la carga completa del árbol DOM. Además, $ se pasa explícitamente al callback, esto asegura contra conflictos con otras librerías que también puedan reclamar $. Si el script está en el pie de página después de todos los elementos, .ready() formalmente no es necesario, pero la disciplina de «envolver siempre» salva de errores difíciles de detectar durante la refactorización.

¿Qué es más rápido: jQuery o JS vainilla para una tarea así?

JavaScript nativo gana en un arranque en frío: no hay necesidad de cargar, analizar y compilar jQuery, y esto ahorra decenas de kilobytes de tráfico y varios milisegundos de análisis. En operaciones DOM, la brecha es más modesta, hablamos de fracciones de milisegundo por llamada. Para un sitio WordPress típico donde jQuery ya está conectado por el tema o los plugins, la diferencia es imperceptible, su script de cuatro líneas no se convertirá en un cuello de botella. Si jQuery ya está en el proyecto, escriba en jQuery, el código es más compacto y legible. Si no, tome JS vainilla, no arrastre una librería para un solo manejador.

¿Vale la pena usar jQuery para búsqueda de texto en 2026?

jQuery no se ha ido a ninguna parte. Según estadísticas de W3Techs de junio de 2026, la librería está instalada en el 87,3% de los sitios con una librería JavaScript conocida (68,1% de todos los sitios), principalmente gracias a WordPress, donde jQuery está en el núcleo y se conecta automáticamente por los temas. Para un desarrollador de WordPress, jQuery es una herramienta nativa que ya está cargada y no crea solicitudes adicionales.

Alternativas directas como Alpine.js o HTMX resuelven tareas diferentes (reactividad, actualizaciones parciales) y son excesivas para un script DOM puntual. querySelectorAll + closest() + remove() nativos es una opción funcional sin dependencias, pero las cadenas de llamadas se vuelven más largas. La elección se reduce a una regla simple: jQuery ya está en el proyecto, tome jQuery; proyecto sin jQuery, tome JS vainilla.

Lo principal es que ahora conoce la mecánica exacta de la combinación :contains() + .closest() + .next() + .remove(), y en qué dialecto escribirla lo decide el proyecto específico.