🐾 Animalitos API pública
Backoffice

API pública de animalitos

Resultados de los sorteos de animalitos de Venezuela, scrapeados de tuazar.com, normalizados y servidos como JSON listo para pintar en un sitio web: cada resultado ya trae el nombre del animalito, su número normalizado y la URL absoluta de su imagen.

URL base

Todos los endpoints de esta página son relativos a esa base. Son GET, devuelven application/json y no requieren autenticación. El CORS lo controla la variable CORS_ORIGINS (por defecto *).

Los bloques Probar de cada endpoint ejecutan la petición de verdad contra este mismo servidor, así que lo que ves ahí es el estado real de la base de datos.

Convenciones

Forma de la respuesta

Las colecciones vienen envueltas en { total, data }. Los endpoints paginados añaden page, limit y pages. Los de un solo recurso devuelven { data }. Los errores devuelven { error }.

Fechas y horas

  • fecha y los parámetros date/from/to son YYYY-MM-DD en hora de Venezuela (America/Caracas).
  • hora24 es HH:MM en 24h; horario es la etiqueta tal como la publica la fuente ("1:00 PM").
  • drawAt es el instante exacto del sorteo en ISO 8601 UTC: úsalo para ordenar o comparar entre sorteos, no la fecha suelta.
  • diaSemana y dias van de 1 = lunes a 7 = domingo.

Números y animalitos

  • numero es el valor crudo de la fuente ("7" o "07") y numeroNormalizado el mismo relleno a dos dígitos ("07"). Compara y agrupa siempre por el normalizado.
  • animalSlug es la identidad canónica (aguila) y enlaza con /animals/:slug. animal es el nombre del catálogo y animalOriginal lo que dijo la fuente en ese sorteo.

Imágenes

imagen y logo llegan como URL absoluta. Para un animalito se resuelve en este orden: la imagen mapeada en el backoffice, la del sitio origen guardada en el animalito y, como último recurso, el arte de ese sorteo concreto. Si no hay ninguna, el campo es null — deja un placeholder previsto en el front.

El dominio no es necesariamente el de esta API: las rutas /uploads/… salen por ASSETS_BASE_URL (el CDN) cuando está configurado, y por PUBLIC_BASE_URL si no. No construyas la URL a mano ni asumas el host: usa el valor que trae el campo. Los nombres de archivo llevan versión (aguila-1757380000000.png) y se sirven como inmutables, así que puedes cachearlos sin límite en el front.

Caché

Los catálogos de sorteos y animalitos se cachean 60 s en memoria, así que un cambio de imagen o de nombre hecho en el backoffice puede tardar hasta un minuto en verse aquí. Los resultados no se cachean.

Errores

Siempre el mismo cuerpo, con el mensaje en español:

{ "error": "date debe ser YYYY-MM-DD" }
CódigoCuándo
400Parámetro mal formado — sobre todo fechas que no son YYYY-MM-DD.
404El slug pedido no existe, o la ruta no existe ("No existe GET /api/…").
500Fallo del servidor o de MongoDB. Se registra en los logs del contenedor.
Un filtro válido que no encuentra nada no es un 404: responde 200 con total: 0 y data: [].

Recetas

La tabla de hoy, con los horarios que faltan

const API = '';

const { fecha, data } = await fetch(`${API}/results/today`).then((r) => r.json());

for (const sorteo of data) {
  console.log(sorteo.nombre);
  for (const r of sorteo.resultados) {
    console.log(
      r.pendiente
        ? `  ${r.horario} — pendiente`
        : `  ${r.horario} — ${r.numero} ${r.animal}`,
    );
  }
}

Cintillo con el último resultado de cada sorteo

const { data } = await fetch(`${API}/results/latest`).then((r) => r.json());

document.querySelector('#cintillo').innerHTML = data
  .map((r) => `
    <div class="item">
      <img src="${r.resultado.imagen ?? '/placeholder.png'}" alt="${r.resultado.animal}">
      <strong>${r.resultado.numero}</strong> ${r.resultado.animal}
      <small>${r.sorteo.nombre} · ${r.horario}</small>
    </div>`)
  .join('');

Histórico de un animalito en un sorteo

const params = new URLSearchParams({
  animal: 'aguila',
  lottery: 'ruletaroyal',
  from: '2026-08-01',
  to: '2026-08-31',
  limit: '500',
  sort: 'asc',
});

const { total, data } = await fetch(`${API}/results?${params}`).then((r) => r.json());

Recorrer todas las páginas de un rango grande

async function todos(query) {
  const out = [];
  let page = 1;
  for (;;) {
    const res = await fetch(`${API}/results?${query}&limit=1000&page=${page}`);
    const { data, pages } = await res.json();
    out.push(...data);
    if (page >= pages || !data.length) return out;
    page++;
  }
}

API del backoffice

Además de esta API pública existe /api/admin/*, que es la que usa el backoffice para editar resultados, configurar sorteos, subir imágenes y lanzar el scraper. Requiere la cabecera Authorization: Bearer <ADMIN_TOKEN> y no está pensada para consumirse desde el sitio público. Sus rutas están listadas en el README.md del repositorio.