Buscador de información genética: creación de un cuadro de búsqueda mediante API y JSON
Al finalizar este tema
Podrás crear un cuadro de búsqueda que, al ingresar el nombre de un gen, obtenga información sobre su función, ubicación y alias de una base de datos pública y la muestre en forma de tarjeta, combinando las API y el JSON que aprendiste en clase. Así, podrás familiarizarte con el código que permite "utilizar una base de datos masiva creada por otros en tu propia aplicación".
Este artículo es un ejemplo didáctico. Utiliza una API de anotación de genes gratuita y pública como material de referencia para aprender el patrón universal de obtención de datos externos desde la web.
"¿Qué gen era este?" — El problema de tener que buscar en varios sitios cada vez
Cuando se realizan experimentos, a menudo se conoce el nombre de un gen, pero se olvidan los detalles. ¿En qué cromosoma se encuentra TP53? ¿Cuál es el alias oficial de BRCA1?
En esos casos, abrimos el navegador, vamos al sitio web de la base de datos pública, escribimos en el cuadro de búsqueda y revisamos la página de resultados. No es un problema si se hace una o dos veces, pero ¿qué pasa si hay que verificar 50 genes? Se abre y cierra 50 pestañas, lo cual es un infierno.
En ese momento, surge el pensamiento del desarrollador: "Ese sitio web también muestra datos de una base de datos. ¿No podría consultar directamente esa base de datos?" La respuesta es sí. Esa es la API.
Primero, veamos el producto final (haciendo funcionar la caja negra)
El cuadro de búsqueda que vamos a crear funcionará de la siguiente manera: al ingresar el nombre del gen en el cuadro de búsqueda...
┌──────────────────────────────────────┐
│ 🔍 [ TP53 ] [Buscar] │
├──────────────────────────────────────┤
│ TP53 (tumor protein p53) │
│ ───────────────────────────── │
│ 📍 Ubicación: 17p13.1 │
│ 🏷️ Alias : p53, LFS1, BCC7 │
│ 🧬 Resumen : ciclo celular, supresión │
└──────────────────────────────────────┘No es necesario abrir un sitio web; la información aparece directamente en la barra de búsqueda. Para lograr esto, solo se necesitan dos conceptos: dónde buscar la información (API) y cómo leer la respuesta (JSON).
¿De qué componentes está compuesto este programa? (Desglose de componentes)
Widget de búsqueda de genes
┌────────────────────────────────────────┐
│ [Consulta] Construir URL ── pieza: API │ ← La construyes tú ★
│ │ │
│ ▼ │
│ [Envío] Llamada con fetch ─ pieza: fetch │ ← Se proporciona completa
│ │ + asincronía │
│ ▼ │
│ [Análisis] Parsear respuesta ─ pieza: JSON│ ← La construyes tú ★
│ │ │
│ ▼ │
│ [Salida] Renderizar tarjeta │
└────────────────────────────────────────┘| Componente | ¿Dónde se aprendió? | ¿Qué hace en esta herramienta? |
|---|---|---|
| API | api-basics | Crear la URL para indicar "qué y dónde preguntar" |
| fetch/asíncrono | ajax-fetch, sync-vs-async | Realizar la solicitud y esperar |
| JSON | json-data-format | Extraer los valores necesarios de la respuesta recibida |
📌 Si estos conceptos son nuevos para ti (enlace en la parte superior)
Los dos conceptos nuevos que construiremos son API y JSON. fetch, que realiza la comunicación real, se proporciona como un producto terminado porque es una herramienta.
🔎 API en una frase (cajón — api-basics) Una API es un menú de restaurante. No puedes entrar directamente en la cocina (la base de datos), pero si haces un pedido en el menú (la API) de acuerdo con las reglas, obtendrás un plato (datos). Aprenderemos a hacer un pedido de "dame esta información genética" de acuerdo con las reglas del menú.
Paso 1: Construir la URL para indicar qué preguntar ★ (API)
✍️ Sección para completar. Componente = API. Objetivo: convertir "dame esta información genética" en una URL que la API pueda entender.
La forma de hacer un pedido a la API suele ser a través de una URL. Se puede construir "dirección + lo que se desea" siguiendo las reglas. Por ejemplo, supongamos que una API de genes pública tiene estas reglas.
https://api-ejemplo.org/query?q={nombre_gen}&fields={campos_deseados}- Después de
q=, el término de búsqueda (nombre del gen). - Después de
fields=, los elementos que desea recibir (separados por comas).
Si se crea manualmente, puede haber errores tipográficos y de codificación. Por lo tanto, se crea una función para ensamblar la URL de forma segura. Lo importante aquí es encodeURIComponent: envuelve el término de búsqueda para que la URL no se dañe, incluso si el término de búsqueda contiene espacios o caracteres especiales.
const API_BASE = "https://example-gene-api.org/query";
function buildQueryUrl(geneName, fields = ["symbol", "name", "genomic_pos", "alias", "summary"]) {
const q = encodeURIComponent(geneName.trim());
const f = encodeURIComponent(fields.join(","));
return `${API_BASE}?q=${q}&fields=${f}`;
}
// Verificación
console.assert(
buildQueryUrl("TP53") === "https://example-gene-api.org/query?q=TP53&fields=symbol%2Cname%2Cgenomic_pos%2Calias%2Csummary",
"Falló la construcción de la URL básica"
);
// También debe codificar de forma segura consultas con espacios
console.assert(
buildQueryUrl(" TP 53 ").includes("q=TP%2053"),
"Falló la codificación de espacios"
);El segundo assert es clave. Aunque el usuario introduzca un espacio, como en TP 53, este se codificará de forma segura como TP%2053. Si no se utiliza encodeURIComponent, este espacio dividirá la URL y la solicitud fallará. La mitad de los errores en las llamadas a la API se deben a que se omite esta codificación.
🤔 Pregunta para la reflexión ¿Por qué se incluyó
geneName.trim()? Piense en cómo la API interpretaría esto si un usuario introduce"TP53 "(con un espacio al final) en la barra de búsqueda y no se utiliza la funcióntrim. (Es la misma idea que la normalización de secuencias en el primer módulo: asegúrese de que todo tenga el formato correcto antes de enviarlo).
Paso 2: realizar la solicitud (fetch, código completo proporcionado)
La parte de la realización de la solicitud y la espera de la respuesta se proporciona como código completo, ya que es una herramienta. Es igual que lo que aprendimos en ajax-fetch y sync-vs-async: fetch + async/await.
async function callGeneApi(geneName) {
const url = buildQueryUrl(geneName);
const response = await fetch(url); // ← Viaje de red; await espera la respuesta
if (!response.ok) {
throw new Error(`Error de API: ${response.status}`);
}
return await response.json(); // ← Convierte el cuerpo en un objeto JSON
}🔎 ¿Por qué usar async/await? (Diagrama — sincrónico vs. asíncrono) Las solicitudes de red tardan tiempo (ida y vuelta al servidor). Durante este tiempo, el navegador no debería bloquearse.
awaitpermite "esperar a que esto termine, pero sin bloquear la interfaz". Es como pedir comida en un restaurante y recibir un dispositivo de vibración para sentarse a esperar: no tienes que quedarte de pie hasta que llegue la comida.
La última línea de esta función, response.json(), es el puente hacia el siguiente paso. Convierte el bloque de texto enviado por el servidor en un objeto JavaScript que podemos manejar. El procesamiento de ese objeto es el paso 3.
Paso 3: extraer solo lo necesario de la respuesta ★ (JSON)
✍️ Sección para completar manualmente. Componente = JSON. Objetivo: extraer de forma segura los valores que necesitamos de una respuesta compleja.
El JSON que devuelve una API suele ser mucho más grande y complejo de lo que necesitamos. Supongamos que la respuesta real tiene este aspecto.
{
"hits": [
{
"symbol": "TP53",
"name": "tumor protein p53",
"genomic_pos": { "chr": "17", "start": 7668402, "end": 7687550 },
"alias": ["p53", "LFS1", "BCC7"],
"summary": "Gen implicado en la regulación del ciclo celular y la supresión tumoral."
}
]
}Aquí, usaremos el nombre, la ubicación, el alias y el resumen en la tarjeta. Creamos una función que busca en el JSON, extrae solo los valores necesarios y los organiza en un objeto limpio. En este caso, es importante tener en cuenta — manejar de forma segura los valores que podrían no estar presentes. Algunos genes no tienen alias y algunas respuestas no tienen resultados.
function parseGeneResponse(data) {
const hit = data.hits && data.hits[0];
if (!hit) {
return null; // Sin resultados; lo gestiona el llamador
}
const pos = hit.genomic_pos || {};
return {
symbol: hit.symbol || "?",
name: hit.name || "",
location: pos.chr ? `chr${pos.chr}:${pos.start}-${pos.end}` : "Sin información",
aliases: Array.isArray(hit.alias) ? hit.alias : [], // Array vacío si no hay alias
summary: hit.summary || "No hay información de resumen.",
};
}Ahora, lo validaremos con una respuesta simulada. De esta manera, podemos verificar si la lógica de análisis es correcta sin necesidad de una red real. Este es un buen diseño: separar el procesamiento de datos (análisis) puro de la red facilita las pruebas.
const mockResponse = {
hits: [{
symbol: "TP53",
name: "tumor protein p53",
genomic_pos: { chr: "17", start: 7668402, end: 7687550 },
alias: ["p53", "LFS1", "BCC7"],
summary: "Gen implicado en la regulación del ciclo celular y la supresión tumoral.",
}],
};
const parsed = parseGeneResponse(mockResponse);
console.assert(parsed.symbol === "TP53", "Falló el análisis del símbolo");
console.assert(parsed.location === "chr17:7668402-7687550", "Falló la construcción de la ubicación");
console.assert(parsed.aliases.length === 3, "Falló el análisis de los alias");
// También debe gestionar de forma segura una respuesta sin resultados
console.assert(parseGeneResponse({ hits: [] }) === null, "Falló el manejo de resultados vacíos");
console.assert(parseGeneResponse({}) === null, "Falló el manejo de un objeto vacío");
// Un gen sin alias tampoco debe provocar un error
const noAlias = parseGeneResponse({ hits: [{ symbol: "X", genomic_pos: { chr: "1", start: 1, end: 2 } }] });
console.assert(Array.isArray(noAlias.aliases) && noAlias.aliases.length === 0, "Falló el manejo de un gen sin alias");Los últimos tres assert separan a los principiantes de los expertos. Si solo se manejan los casos que funcionan, eres un principiante; si se manejan también los casos inexistentes, vacíos o incorrectos, eres un experto. Si hit.alias no está presente y se usa directamente, la aplicación fallará con undefined.length. Una sola línea de Array.isArray(...) ? ... : [] evita ese error.
🤔 Indicación de autoexplicación En
data.hits && data.hits[0], ¿por qué se usó&&? Si se recibe una respuesta dondedata.hitsno está presente (undefined), explica qué error ocurriría si no se aplicara esta protección. (Este es un tema similar al de "valores que pueden no estar presentes" en el primer artículo).
Uniendo las piezas: un widget completo
Ahora, unimos la construcción de la URL (API) → la llamada (fetch) → el análisis (JSON) → y el renderizado.
function renderGeneCard(gene) {
if (!gene) return "No hay resultados de búsqueda.";
return [
`${gene.symbol} (${gene.name})`,
`📍 Ubicación: ${gene.location}`,
`🏷️ Alias: ${gene.aliases.join(", ") || "ninguno"}`,
`🧬 Resumen: ${gene.summary}`,
].join("\n");
}
async function searchGene(geneName) {
try {
const raw = await callGeneApi(geneName);
const gene = parseGeneResponse(raw);
return renderGeneCard(gene);
} catch (e) {
return `Se produjo un error: ${e.message}`;
}
}
// Comprueba también el renderizado con un mock, sin necesidad de red
const card = renderGeneCard(parseGeneResponse(mockResponse));
console.assert(card.includes("TP53"), "Falló el renderizado de la tarjeta");
console.assert(card.includes("chr17:7668402-7687550"), "Falló el renderizado de la ubicación");
console.assert(renderGeneCard(null) === "No hay resultados de búsqueda.", "Falló el renderizado de un resultado vacío");searchGene Mira cómo try/catch lo envuelve todo. Las redes pueden fallar en cualquier momento (el servidor se cae, la conexión a Internet se interrumpe). En lugar de que la aplicación se bloquee por completo, mostrar "Se produjo un error" es la diferencia entre una herramienta de uso real y un juguete.
Otras opciones (reflexión multipaso)
- Búsqueda simultánea de varios genes: Buscar 50 genes uno por uno con
awaites lento (secuencial). Lanzarlos simultáneamente conPromise.all([...])es mucho más rápido. Compromiso: Puede sobrecargar el servidor, por lo que las API públicas tienen un límite de frecuencia de solicitudes por segundo. - Caché: Si se vuelve a buscar el mismo gen, en lugar de llamar a la API de nuevo, se utiliza el resultado almacenado. La misma idea que el diccionario (secuencia → valor) de la sección anterior, pero aquí con
nombre del gen → resultado. - Proxy de backend: Si un servicio requiere una clave de API, exponer la clave en el navegador es un riesgo. En la práctica, se utiliza nuestro propio servidor como intermediario. → Continúa en la sección de aplicación Backend de búsqueda de genes.
Punto clave: Llamar directamente a una API externa desde el frontend es bueno para obtener resultados rápidos, pero hay mejores opciones en términos de velocidad (solicitudes simultáneas), reutilización (caché) y seguridad (clave).
Próximos pasos (enlaces de salida en la parte inferior)
- Para almacenar los resultados de la búsqueda en nuestra base de datos y encontrarlos rápidamente, consulta la sección de aplicación Indexación de bases de datos de secuencias que utiliza índices de bases de datos.
- Si quieres enviar los resultados en tiempo real, consulta WebSocket.
- El origen de la reutilización de resultados (caché) se encuentra en Listas y diccionarios.
Ponlo en práctica (problema independiente)
- Estado de carga: Muestra "Buscando..." mientras se realiza la búsqueda y, una vez finalizada, muestra los resultados. (Representa visualmente el "tiempo de espera" de la operación asíncrona).
- Añade caché: Guarda los genes que ya se han buscado en un objeto (
{}) para reutilizarlos y evitar llamar a la API de nuevo. Utilizaconsole.assertpara comprobar si la segunda llamada utiliza la caché. - Varios genes: Recibe varios nombres de genes separados por comas y búscalos simultáneamente con
Promise.all. - Desafío: El formato de respuesta de la API puede cambiar, y
genomic_pospuede devolver una matriz (múltiples posiciones). Modifica el código para queparseGeneResponsegestione correctamente tanto las matrices como los objetos.
Resumen
Hemos transformado la "molestia de buscar la información de los genes en el sitio web cada vez" en un widget de búsqueda que trae toda la información a mi aplicación con una sola API.
- La API representa "qué y dónde preguntar" mediante una URL. (Consulta)
- Fetch/asíncrono envía la solicitud y espera sin bloquear la pantalla. (Envío)
- JSON extrae de forma segura solo los valores necesarios de una respuesta compleja, incluso si faltan algunos. (Interpretación)
La API y JSON pueden parecer abstractos cuando se aprenden por separado, pero al combinarlos, todos los conjuntos de datos públicos del mundo se convierten en los materiales de mi aplicación. Ya sea una base de datos de genes, una base de datos de artículos o una base de datos de estructuras de proteínas, el patrón siempre es el mismo: crear una URL de solicitud, enviarla y analizar la respuesta.
Este artículo es un ejemplo educativo general. En un servicio real, se agregarían autenticación, límites de solicitud, reintentos de errores y paginación. Puede agregar la versión detallada a esta estructura básica.