'use strict';
const results = document.querySelector('#results');
const status = document.querySelector('#status');
const widgetRoot = document.querySelector('.widget');
const API_ROOT = '/loteriasyapuestas';
const games = [
['LNAC', 'Lotería Nacional', '#007ac0', 'LN'], ['LAPR', 'La Primitiva', '#008547', 'LP'], ['EMIL', 'Euromillones', '#001367', 'EM'], ['BONO', 'Bonoloto', '#7a8634', 'BL'], ['ELGR', 'El Gordo de la Primitiva', '#dc3f47', 'GP'], ['LAQU', 'La Quiniela', '#cb1517', 'Q'], ['QGOL', 'Quinigol', '#74bacd', 'QG'], ['LOTU', 'Lototurf', '#d16929', 'LT'], ['QUPL', 'Quíntuple Plus', '#eab842', 'QP'], ['EDMS', 'EuroDreams', '#6b3e98', 'ED']
].map(([id, name, color, short]) => ({ id, name, color, short }));
const dateFormatter = new Intl.DateTimeFormat('es-ES', { day: 'numeric', month: 'long', timeZone: 'Europe/Madrid' });
const historyDateFormatter = new Intl.DateTimeFormat('es-ES', { weekday: 'short', day: '2-digit', month: '2-digit', year: '2-digit', timeZone: 'Europe/Madrid' });
function escapeHtml(value) { return String(value ?? '').replace(/[&<>'"]/g, (char) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[char])); }
function decodeEntities(value) { const node = document.createElement('textarea'); node.innerHTML = String(value ?? ''); return node.value; }
function dateOf(value) { const found = String(value || '').match(/^(\d{4}-\d{2}-\d{2})/); return found?.[1] || null; }
function euro(value) {
if (value === null || value === undefined || value === '') return null;
const text = String(value).trim();
const number = Number(text.includes(',') ? text.replace(/\./g, '').replace(',', '.') : text);
return Number.isFinite(number) ? new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'EUR', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(number) : null;
}
function unwrap(data) { return Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []); }
function ymd(days) { const date = new Date(); date.setDate(date.getDate() + days); return date.toISOString().slice(0, 10).replaceAll('-', ''); }
function ymdMonthsAgo(months) { const date = new Date(); date.setMonth(date.getMonth() - months); return date.toISOString().slice(0, 10).replaceAll('-', ''); }
let resizeFrame = 0;
function resize() {
if (parent === window || !widgetRoot) return;
cancelAnimationFrame(resizeFrame);
resizeFrame = requestAnimationFrame(() => {
const height = Math.ceil(widgetRoot.getBoundingClientRect().height);
if (height > 0) parent.postMessage({ type: 'resultados-loterias:resize', height }, '*');
});
}
new ResizeObserver(resize).observe(widgetRoot);
resize();
async function request(route, query) {
const url = new URL(`${API_ROOT}${route}`, location.origin);
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, value));
const response = await fetch(url, { headers: { accept: 'application/json' } });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
async function records(id, date) {
const end = date ? date.replaceAll('-', '') : ymd(0);
const start = date ? end : ymdMonthsAgo(3);
const payload = await request('/servicios/buscadorSorteos', { game_id: id, celebrados: 'true', fechaInicioInclusiva: start, fechaFinInclusiva: end });
return unwrap(payload).filter((row) => {
const rowDate = dateOf(row.fecha_sorteo || row.fechaSorteo || row.fecha);
return rowDate && (date || rowDate.replaceAll('-', '') >= start);
}).sort((a, b) => String(b.fecha_sorteo || b.fechaSorteo || b.fecha).localeCompare(String(a.fecha_sorteo || a.fechaSorteo || a.fecha)));
}
async function provisional(id, next) {
if (!['LAQU', 'QGOL'].includes(id)) return null;
const payload = await request('/servicios/fechav3', { game_id: id, fecha_sorteo: ymd(0) });
return unwrap(payload)[0] || null;
}
function combination(row) {
const value = row?.combinacion ?? row?.combination ?? row?.resultado;
if (typeof value === 'string') return value.split(/[\s,]+/).map((part) => part.trim()).filter(Boolean);
if (Array.isArray(value)) return value.map(String);
if (Array.isArray(row?.partidos)) return row.partidos.map((match) => match.signo || match.resultado || '-');
return [row?.primerPremio?.decimo && `1º ${row.primerPremio.decimo}`, row?.segundoPremio?.decimo && `2º ${row.segundoPremio.decimo}`].filter(Boolean);
}
function nestedResult(row, keys) {
for (const key of keys) {
const value = row?.[key];
const result = typeof value === 'object' && value !== null
? (value.combinacion ?? value.codigo ?? value.resultado ?? value.numero)
: value;
if (result !== null && result !== undefined && String(result).trim() && String(result).trim() !== '-') return String(result).trim();
}
return null;
}
function nationalPrizeRows(row) {
const rows = Array.isArray(row.escrutinio) ? row.escrutinio.map((item) => ({ label: decodeEntities(item.tipo || item.categoria || 'Premio'), value: euro(item.premio) || item.premio || 'Pendiente' })) : [];
const additions = [
['extraccionesDeCincoCifras', 'Extracción de 5 cifras'],
['extraccionesDeCuatroCifras', 'Extracción de 4 cifras'],
['extraccionesDeTresCifras', 'Extracción de 3 cifras'],
['extraccionesDeDosCifras', 'Extracción de 2 cifras']
];
additions.forEach(([key, label]) => {
const first = Array.isArray(row[key]) ? row[key][0] : null;
if (first?.prize !== undefined) rows.push({ label, value: euro(Number(first.prize) / 100) || 'Pendiente' });
});
return rows;
}
function gameLogo(game) {
return `
`;
}
function resultMarkup(game) {
return game.balls.map((item, index) => {
const extra = item.match(/^(C|R)\(([^)]+)\)$/i);
if (extra) {
const label = game.id === 'EDMS' && extra[1] === 'C' ? 'Sueño' : extra[1].toUpperCase();
return `${label}${escapeHtml(extra[2])}`;
}
const isQuiniela = game.id === 'LAQU';
if (isQuiniela && index === game.balls.length - 1) return `${escapeHtml(item)}`;
const isStar = game.id === 'EMIL' && index >= 5 && index <= 6;
const isBonus = /^(M-\d+|JOKER|\d{7,})$/i.test(item);
const colorClass = ['LAPR', 'BONO', 'EMIL', 'ELGR', 'LAQU', 'EDMS'].includes(game.id) && !isQuiniela && !isStar && !isBonus ? ` ball--colored` : '';
const typeClass = game.id === 'LNAC' ? ' ball--rectangular' : (isQuiniela ? ' quiniela-sign' : (isStar ? ' ball--star' : (isBonus ? ' ball--text' : colorClass)));
return `${isStar ? '★ ' : ''}${escapeHtml(item)}`;
}).join('');
}
function card(game, row = {}, next = {}) {
const date = dateOf(row.fecha_sorteo || row.fechaSorteo || row.fecha);
const parts = date ? dateFormatter.format(new Date(`${date}T12:00:00+02:00`)).split(' de ') : ['—', 'SIN RESULTADOS'];
const balls = combination(row).filter((item) => game.id === 'LAQU' || item !== '-');
const prizeRows = game.id === 'LNAC' ? nationalPrizeRows(row) : (Array.isArray(row.escrutinio) ? row.escrutinio.map((item) => ({ label: decodeEntities(item.tipo || item.categoria || 'Premio'), value: euro(item.premio) || item.premio || 'Pendiente' })) : []);
const refunds = game.id === 'LNAC' && Array.isArray(row.reintegros) ? row.reintegros.map((item) => item.decimo).filter(Boolean) : [];
const historicFirstPrize = Number(row.primer_premio?.prize ?? row.primer_premio);
const seriesPrizeValue = game.id === 'LNAC'
? (next.primer_premio ?? (Number.isFinite(historicFirstPrize) ? historicFirstPrize / 10 : null))
: null;
const joker = game.id === 'LAPR' ? nestedResult(row, ['joker']) : null;
const elMillon = game.id === 'EMIL' ? nestedResult(row, ['millon', 'elMillon', 'el_millon', 'codigoMillon', 'codigo_millon']) : null;
// The footer describes the next draw, not the already-played draw's historical jackpot.
const jackpot = game.id === 'LNAC' ? null : (euro(next.premio_bote) ?? euro(0));
return { ...game, date, day: parts[0], month: parts[1].toUpperCase(), weekday: row.dia_semana || '', balls, refunds, seriesPrize: euro(seriesPrizeValue), jackpot, joker, elMillon, prizes: prizeRows, matches: row.partidos || [] };
}
function template(game) {
const balls = game.balls.length ? resultMarkup(game) : 'Resultado pendiente de publicación';
const refunds = game.refunds?.length ? `R${game.refunds.map((item) => `${escapeHtml(item)}`).join('')}` : '';
const extras = `${game.joker ? `JOKER${escapeHtml(game.joker)}` : ''}${game.elMillon ? `EL MILLÓN${escapeHtml(game.elMillon)}` : ''}`;
const footer = game.seriesPrize ? ``
: (game.id === 'EDMS' ? ``
: (game.jackpot !== null ? `` : ''));
return `
| Categoría | Premio |
|---|---|
| ${escapeHtml(item.label)} | ${escapeHtml(item.value)} |
Cargando fechas…
'; try { const dates = await records(current.dataset.game); panel.innerHTML = `No se pudo cargar el histórico.
'; } } resize(); }); (async () => { try { const upcoming = new Map(unwrap(await request('/servicios/proximosv3', { game_id: 'TODOS', num: '1' })).map((item) => [item.game_id, item])); window.widgetGames = await Promise.all(games.map(async (game) => { const next = upcoming.get(game.id); try { const current = await provisional(game.id, next); if (current) return card(game, current, next); return card(game, (await records(game.id))[0], next); } catch { try { return card(game, (await records(game.id))[0], next); } catch { return card(game, {}, next); } } })); renderResults(window.widgetGames); status.hidden = true; resize(); } catch { status.textContent = 'No se pueden mostrar los resultados en este momento. Vuelve a intentarlo en unos minutos.'; } })();