¿Cómo está creado este buscador de artículos?

Este buscador ha sido creado mediante dos componentes principales:

1. Extracción de datos

Se usa un script en Python para recopilar artículos de revistas científicas veterinarias indexadas en PubMed, entre los años 1995 y 2025. Puedes consultar el programa aquí:

https://airsoto.github.io/vet/papers.py

El resultado es un archivo JSON con todos los artículos extraídos:

https://airsoto.github.io/vet/papers.json

2. Interfaz Web de Búsqueda

El siguiente código HTML es el motor del buscador, que permite filtrar artículos por título, autor o revista, y visualizar los resultados junto con los resúmenes:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Buscador de Artículos Veterinarios</title>
  <style>
    body {
      background-color: #121212;
      color: #f0f0f0;
      font-family: 'Georgia', serif;
      margin: 0;
      padding: 2em;
    }

    h1 {
      text-align: center;
      color: #90caf9;
      cursor: pointer;
    }

    .search-section {
      display: flex;
      gap: 1em;
      justify-content: center;
      flex-wrap: wrap;
      margin-bottom: 2em;
    }

    input, select, button {
      padding: 0.5em;
      font-size: 1em;
      border-radius: 5px;
      border: none;
    }

    input, select {
      width: 200px;
    }

    button {
      background-color: #1e88e5;
      color: white;
      cursor: pointer;
    }

    .results {
      margin-top: 2em;
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 1.5em;
    }

    .article {
      background-color: #1e1e1e;
      padding: 1.5em;
      border-left: 4px solid #90caf9;
      border-radius: 8px;
      cursor: pointer;
      transition: transform 0.3s ease, box-shadow 0.3s ease;
      max-height: none;
      overflow: hidden;
    }

    .article:hover {
      transform: scale(1.03);
      box-shadow: 0 0 10px #90caf9;
    }

    .article.expanded {
      grid-column: span 3;
      transform: scale(1.02);
      z-index: 10;
    }

    .latex-style {
      font-family: 'Times New Roman', serif;
      background-color: #222;
      padding: 1em;
      border: 1px solid #555;
      border-radius: 6px;
      margin-top: 1em;
    }

    .abstract-text {
      font-style: italic;
      text-align: justify;
      line-height: 1.6;
      margin-top: 1em;
      font-size: 1.05em;
    }

    .email-section {
      margin-top: 1em;
    }

    .modal {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.8);
      justify-content: center;
      align-items: center;
    }

    .modal-content {
      background-color: #1e1e1e;
      padding: 2em;
      border-radius: 8px;
      max-width: 600px;
      text-align: center;
      color: #fff;
    }

    .close-btn {
      background: #e53935;
      border: none;
      padding: 0.5em 1em;
      color: white;
      margin-top: 1em;
      cursor: pointer;
      border-radius: 5px;
    }
  </style>
</head>
<body>
<h1>
  Buscador de Artículos de una selección de las principales revistas de Medicina Interna Veterinaria
  <br>
  <small>
    <a href="javascript:void(0);" onclick="toggleModal()" style="color: #90caf9; text-decoration: underline;">
      (pulsa aquí para ver las revistas indexadas)
    </a>
  </small>
</h1>

<h2 style="text-align: center;">
  <span style="color: #ff9900;">Desarrollado por: Angel Soto</span>
</h2>

<p style="text-align: center; margin-top: -1em;">
  <a href="https://airsoto.github.io/vet/howpapers.html" target="_blank" style="color: #90caf9; text-decoration: underline;">
    (cómo está creada esta página)
  </a>
</p>

<div class="modal" id="modal">
  <div class="modal-content">
    <h2>Revistas indexadas</h2>
    <p>Journal of Veterinary Internal Medicine<br />Journal of the American Veterinary Medical Association<br />Journal of Veterinary Cardiology<br />Journal of Small Animal Practice</p>
    <p><br />con más de 10600 artículos.</p>
    <button class="close-btn" onclick="toggleModal()">Cerrar</button>
  </div>
</div>

<div class="search-section">
  <select id="search-type">
    <option value="title">Título</option>
    <option value="authors">Autor</option>
    <option value="journal">Revista</option>
  </select>
  <input type="text" id="search-input" placeholder="Buscar...">
  <input type="text" id="keyword-input" placeholder="Palabra clave...">
  <button onclick="search()">Buscar</button>
</div>

<div id="result-count" style="text-align:center; font-size:1.2em; margin-top:1em; color:#90caf9;"></div>
<div id="results" class="results"></div>

<script>
let articles = [];

async function cargarArticulos() {
  try {
    const response = await fetch('papers.json');
    if (!response.ok) throw new Error('No se pudo cargar el archivo JSON');
    articles = await response.json();
  } catch (error) {
    document.getElementById('results').innerHTML = '<p>Error al cargar los artículos.</p>';
    console.error(error);
  }
}

function search() {
  const type = document.getElementById('search-type').value;
  const query = document.getElementById('search-input').value.toLowerCase();
  const keywordQuery = document.getElementById('keyword-input').value.toLowerCase();
  const resultsContainer = document.getElementById('results');
  const countContainer = document.getElementById('result-count');
  resultsContainer.innerHTML = '';
  countContainer.innerHTML = '';

  const filtered = articles.filter(article => {
    const matchType = (type === 'authors')
      ? article.authors.some(a => a.toLowerCase().includes(query))
      : article[type].toLowerCase().includes(query);

    const matchKeyword = keywordQuery === "" || (
      article.keywords &&
      article.keywords.some(k => k.toLowerCase().includes(keywordQuery))
    );

    return matchType && matchKeyword;
  });

  countContainer.innerHTML = `Se encontraron <strong>${filtered.length}</strong> artículo(s).`;

  if (filtered.length === 0) {
    resultsContainer.innerHTML = "<p>No se encontraron resultados.</p>";
    return;
  }

  filtered.forEach(article => {
    const div = document.createElement('div');
    div.className = 'article';
    div.innerHTML = `
      <div class="latex-style">
        <h2 style="text-align: center;">${article.title}</h2>
        <p style="text-align: center; font-style: italic;">${article.authors.join(', ')}</p>
        <p style="text-align: center; font-size: 0.95em;">${article.journal}, Vol. ${article.volume}(${article.issue}), ${article.year}</p>
        <h3 style="margin-top: 1.5em;">Resumen</h3>
        <p class="abstract-text">${article.abstract}</p>
        ${article.keywords ? `<p style="font-size: 0.9em; margin-top: 1em;"><strong>Palabras clave:</strong> ${article.keywords.join(', ')}</p>` : ''}
      </div>
      <div class="email-section">
        <input type="email" placeholder="Tu email..." id="email-${article.title}" style="margin-top: 1em;"/>
        <button onclick="event.stopPropagation(); sendEmail('${article.title.replace(/'/g, "\\'")}')">Enviar por correo</button>
      </div>
    `;
    div.addEventListener('click', () => {
      div.classList.toggle('expanded');
    });
    resultsContainer.appendChild(div);
  });
}

function sendEmail(title) {
  const article = articles.find(a => a.title === title);
  const email = document.getElementById(`email-${title}`).value;

  if (!email || !email.includes('@')) {
    alert("Introduce un email válido.");
    return;
  }

  const subject = encodeURIComponent("Artículo Veterinario: " + article.title);
  const body = encodeURIComponent(`Título: ${article.title}\nAutores: ${article.authors.join(', ')}\nRevista: ${article.journal}, Vol. ${article.volume}(${article.issue}), ${article.year}\n\nResumen:\n${article.abstract}`);
  const mailto = `mailto:${email}?subject=${subject}&body=${body}`;
  window.location.href = mailto;
}

function toggleModal() {
  const modal = document.getElementById('modal');
  modal.style.display = (modal.style.display === 'flex') ? 'none' : 'flex';
}

cargarArticulos();
</script>
</body>
</html>

Nota: El código completo con estilos y scripts detallados puede visualizarse directamente en tu editor favorito o implementarse como base de un visor web en tu servidor.