[Javascript] Búsqueda dinámica usando jQuery

Hola.

Hoy vamos a ver cómo podemos mostrar resultados de una búsqueda según un criterio determinado y, además, informar al usuario cuando no existen resultados que coincidan con su búsqueda.

Puede parecer algo sencillo, y realmente lo es, pero este patrón aparece constantemente en aplicaciones web: buscadores de clientes, productos, artículos, usuarios, pedidos, documentos, registros, etc.

La idea es muy sencilla:

  • El usuario introduce un criterio de búsqueda.
  • La aplicación recibe ese criterio.
  • Consultamos la base de datos.
  • Mostramos los registros encontrados.
  • Si no existe ninguna coincidencia, mostramos un mensaje informativo.

El resultado que buscamos es algo parecido a esto:

Usuario introduce: "Pedro"

        ↓

Aplicación procesa la búsqueda

        ↓

Base de datos

        ↓

¿Existen resultados?

   ┌───────────────┴───────────────┐
   ↓                               ↓
 SÍ                               NO
   ↓                               ↓
Mostrar resultados        "No se encontraron resultados"

Vamos a verlo paso a paso.

La búsqueda como parte de la experiencia de usuario

Cuando hablamos de un buscador, no basta simplemente con ejecutar una consulta SQL y devolver información.

También tenemos que pensar en qué ocurre cuando el usuario no encuentra nada.

Un buscador debería poder responder, como mínimo, a estas dos situaciones:

Hay resultados.
Perfecto. Los mostramos de forma clara.

No hay resultados.
No dejamos una pantalla vacía. Informamos al usuario de lo que ha ocurrido.

Esto último es especialmente importante desde el punto de vista de la experiencia de usuario. Una página completamente vacía puede hacer pensar que la aplicación ha fallado.

Un mensaje como:

No se encontraron resultados para "Pedro".

deja mucho más claro lo que está ocurriendo.

El formulario de búsqueda

Lo primero que necesitamos es un formulario donde el usuario pueda introducir el criterio que quiere buscar.

<form method="get" action="">

    <label for="buscar">Buscar</label>

    <input
        type="search"
        id="buscar"
        name="buscar"
        placeholder="Introduce un nombre..."
    >

    <button type="submit">
        Buscar
    </button>

</form>

En este ejemplo utilizamos el método GET porque estamos realizando una consulta y no modificando información.

Por ejemplo, si el usuario escribe:

Pedro

el navegador podría generar una URL similar a:

https://ejemplo.com/busqueda.php?buscar=Pedro

Esto tiene además una ventaja interesante: la búsqueda puede ser compartida mediante URL y podemos utilizar los botones de navegación del navegador para volver atrás o repetirla.

Recibiendo el criterio de búsqueda

En PHP podemos recuperar el valor enviado mediante $_GET:

$buscar = $_GET['buscar'] ?? '';

El operador ?? nos permite proporcionar un valor por defecto cuando el parámetro no existe.

De esta manera evitamos intentar acceder directamente a una variable que todavía no ha sido enviada.

Preparando la consulta SQL

Supongamos que tenemos una tabla llamada clientes con una columna nombre.

Podríamos realizar una búsqueda parcial utilizando LIKE:

SELECT id, nombre, telefono
FROM clientes
WHERE nombre LIKE :buscar
ORDER BY nombre ASC;

El operador LIKE nos permite buscar coincidencias parciales.

Por ejemplo, si buscamos:

Pedro

podríamos encontrar:

  • Pedro García
  • Pedro Sánchez
  • Pedro López
  • Juan Pedro Martínez

Esto se consigue utilizando el comodín %:

$sql = "SELECT id, nombre, telefono
        FROM clientes
        WHERE nombre LIKE :buscar
        ORDER BY nombre ASC";

$stmt = $dbconn->prepare($sql);

$stmt->execute([
    ':buscar' => '%' . $buscar . '%'
]);

¿Por qué utilizar consultas preparadas?

Aquí tenemos un detalle que conviene destacar.

No deberíamos construir consultas SQL concatenando directamente lo que escribe el usuario:

$sql = "SELECT * FROM clientes WHERE nombre LIKE '%" . $_GET['buscar'] . "%'";

Además de ser una mala práctica, este patrón puede abrir la puerta a problemas de inyección SQL.

Las consultas preparadas permiten separar los datos proporcionados por el usuario de la propia sentencia SQL:

$stmt = $dbconn->prepare($sql);

$stmt->execute([
    ':buscar' => '%' . $buscar . '%'
]);

Es una pequeña diferencia en el código, pero una diferencia enorme cuando hablamos de seguridad.

Comprobando si existen resultados

Ahora viene la parte que realmente nos interesa.

No debemos asumir que la consulta siempre devolverá información.

Podemos recorrer los resultados y comprobar si hemos encontrado al menos un registro:

$encontrados = false;

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

    $encontrados = true;

    echo '<p>';
    echo htmlspecialchars($row['nombre'], ENT_QUOTES, 'UTF-8');
    echo '</p>';
}

if (!$encontrados) {
    echo '<p>No se encontraron resultados.</p>';
}

La variable $encontrados funciona como una pequeña bandera.

Inicialmente suponemos que no hemos encontrado nada:

$encontrados = false;

En cuanto aparece un registro, la cambiamos:

$encontrados = true;

Y al finalizar el recorrido, si continúa siendo false, sabemos que la consulta no ha devuelto ningún resultado.

Mostrando los resultados

En una aplicación real probablemente querremos mostrar los resultados dentro de una tabla:

<?php if ($encontrados): ?>

<table>

    <thead>
        <tr>
            <th>ID</th>
            <th>Nombre</th>
            <th>Teléfono</th>
        </tr>
    </thead>

    <tbody>

        <?php foreach ($resultados as $cliente): ?>

            <tr>
                <td>
                    <?= htmlspecialchars($cliente['id']) ?>
                </td>

                <td>
                    <?= htmlspecialchars($cliente['nombre']) ?>
                </td>

                <td>
                    <?= htmlspecialchars($cliente['telefono']) ?>
                </td>
            </tr>

        <?php endforeach; ?>

    </tbody>

</table>

<?php else: ?>

    <p class="sin-resultados">
        No se encontraron resultados.
    </p>

<?php endif; ?>

De esta forma conseguimos separar claramente las dos situaciones posibles.

Una implementación algo más limpia

También podemos obtener primero todos los resultados y posteriormente decidir qué vamos a pintar:

$sql = "SELECT id, nombre, telefono
        FROM clientes
        WHERE nombre LIKE :buscar
        ORDER BY nombre ASC";

$stmt = $dbconn->prepare($sql);

$stmt->execute([
    ':buscar' => '%' . $buscar . '%'
]);

$resultados = $stmt->fetchAll(PDO::FETCH_ASSOC);

Ahora podemos comprobar directamente si el array contiene elementos:

<?php if (count($resultados) > 0): ?>

    <!-- Pintamos los resultados -->

<?php else: ?>

    <p>No se encontraron resultados.</p>

<?php endif; ?>

Incluso podemos utilizar empty():

<?php if (!empty($resultados)): ?>

    <!-- Hay resultados -->

<?php else: ?>

    <!-- No hay resultados -->

<?php endif; ?>

Personalmente, para este tipo de ejemplo, esta última aproximación resulta bastante cómoda porque tenemos todos los resultados disponibles y podemos reutilizarlos posteriormente.

¿Y si el usuario no ha introducido nada?

Hay otro caso que debemos controlar.

¿Qué ocurre si alguien entra directamente en:

busqueda.php

sin enviar ningún criterio?

No tiene demasiado sentido ejecutar una búsqueda con una cadena vacía.

Podemos controlarlo:

$buscar = trim($_GET['buscar'] ?? '');

if ($buscar === '') {

    echo '<p>Introduce un criterio de búsqueda.</p>';

    exit;
}

De esta manera distinguimos tres situaciones diferentes:

  • No se ha introducido ningún criterio.
  • Se ha introducido un criterio, pero no existen coincidencias.
  • Se ha introducido un criterio y existen resultados.

Una búsqueda puede crecer bastante

Este ejemplo es deliberadamente sencillo, pero el mismo patrón puede utilizarse para construir buscadores bastante más completos.

Por ejemplo, podríamos buscar simultáneamente por nombre, correo electrónico y teléfono:

SELECT id, nombre, email, telefono
FROM clientes
WHERE nombre LIKE :buscar
   OR email LIKE :buscar
   OR telefono LIKE :buscar
ORDER BY nombre ASC;

También podemos añadir filtros adicionales:

  • Estado del cliente.
  • Fecha de alta.
  • Ciudad.
  • Categoría.
  • Rango de precios.
  • Ordenación.

Y cuando el volumen de datos comienza a crecer, aparece otro concepto fundamental: la paginación.

No tiene demasiado sentido devolver 100.000 registros al navegador cuando el usuario solamente necesita ver los primeros 20.

Un detalle importante: rendimiento

Una búsqueda aparentemente inocente puede convertirse en una consulta bastante pesada cuando trabajamos con millones de registros.

Por ejemplo:

WHERE nombre LIKE '%pedro%'

puede ser costosa dependiendo del tamaño de la tabla y de los índices disponibles, porque el comodín situado al principio dificulta el aprovechamiento de un índice B-Tree tradicional sobre esa columna.

Por el contrario, una búsqueda como:

WHERE nombre LIKE 'Pedro%'

puede ser mucho más favorable para un índice convencional.

Cuando la aplicación crece, este tipo de detalles empiezan a importar muchísimo.

La experiencia de usuario también cuenta

Un buen buscador no debería limitarse a devolver datos.

También debería proporcionar información al usuario durante todo el proceso:

Antes de buscar: “Introduce un criterio de búsqueda.”

Buscando: podemos mostrar un indicador de carga si la consulta es asíncrona.

Con resultados: mostramos los registros encontrados.

Sin resultados: informamos claramente de que no existen coincidencias.

Incluso podemos mostrar el número de resultados:

<p>
    Se han encontrado
    <strong><?= count($resultados) ?></strong>
    resultados.
</p>

Así, una búsqueda que inicialmente parecía simplemente una consulta SQL se convierte en un pequeño flujo completo de interacción entre usuario, aplicación y base de datos.

Conclusión

Mostrar resultados según un criterio es uno de esos patrones que probablemente terminaremos utilizando una y otra vez durante nuestra vida como desarrolladores.

La mecánica fundamental es muy sencilla:

Entrada del usuario
       ↓
Validación
       ↓
Consulta preparada
       ↓
Base de datos
       ↓
¿Hay resultados?
   ↙          ↘
 SÍ            NO
 ↓              ↓
Mostrar       Informar
datos         al usuario

Pero detrás de algo tan aparentemente sencillo hay varios conceptos importantes: consultas preparadas, seguridad, validación, experiencia de usuario, rendimiento e índices.

Y precisamente ahí está la gracia: empezar con un simple buscador y acabar entendiendo buena parte de cómo se comunica una aplicación con su base de datos.

Un saludo y nos vemos en el próximo artículo.

Visitas: 441

Website |  + posts

Automation & Data Specialist | Web Development | Data Processing & Integration

Deja un comentario