`; } }); function mount(root){ const tz = getTZ(root); const key = String(root.getAttribute("data-key") || "").trim(); const cfg = window.PZ_PYP_DATA && window.PZ_PYP_DATA[key] ? window.PZ_PYP_DATA[key] : null; if (!cfg) throw new Error(`No existe window.PZ_PYP_DATA["${key}"]. Revisa data-key.`); const city = cfg.city; const sw = city.switch || {}; const options = Array.isArray(sw.options) ? sw.options : []; const baseUrl = safeUrl(sw.base_url || ""); root.innerHTML = `
Hora: --:--
Si vas a viajar, revisa el pico y placa para la fecha de tu viaje.
`; const $ = (sel) => root.querySelector(sel); const elTitle = $('.pz-title'); const elTime = $('.pz-time'); const elCity = $('.pz-cityselect'); const elWeek = $('.pz-weekday'); const elDate = $('.pz-date'); const elDateInput = $('.pz-dateinput'); const elDateCenter = $('.pz-datecenter'); const elPrevDay = $('.pz-daybtn--left'); const elNextDay = $('.pz-daybtn--right'); const elGrid = $('.pz-grid'); const elFoot = $('.pz-foot'); const elPrevPage = $('.pz-page--prev'); const elNextPage = $('.pz-page--next'); elCity.innerHTML = options .slice() .sort((a,b) => String(a.name||'').localeCompare(String(b.name||''), 'es')) .map(o => ``) .join(''); elCity.value = city.id; let todayTZ = ymdFromParts(nowPartsInTZ(tz)); let selectedYMD = todayTZ; let pageIndex = 0; let pageSize = window.matchMedia('(min-width: 820px)').matches ? 4 : 2; let visibleNodes = new Map(); function getSortedCats(){ return (Array.isArray(city.categories) ? city.categories : []) .slice() .sort((a,b) => (Number(a.priority||99) - Number(b.priority||99)) || String(a.name||'').localeCompare(String(b.name||''), 'es')); } function updateHeader(){ elTitle.textContent = `PICO Y PLACA ${String(city.name || city.id).toUpperCase()}`.trim(); elTime.textContent = `Hora: ${fmtTimeNoSeconds(tz)}`; elWeek.textContent = weekdayUpperES(tz, selectedYMD); elDate.textContent = prettyDateES(selectedYMD); elDateInput.value = selectedYMD; } function updateFooter(){ const src = city.source || {}; const items = Array.isArray(src.items) ? src.items : []; const verified = String(city.last_verified || '').trim(); const links = items .map(it => { const name = String(it && it.name ? it.name : '').trim(); const url = safeUrl(it && it.url ? it.url : ''); if (!url) return ''; return `${name || 'Fuente oficial'}`; }) .filter(Boolean); const parts = []; if (links.length) parts.push(`Fuentes: ${links.join(' · ')}`); if (verified) parts.push(`Verificado: ${verified}`); elFoot.innerHTML = parts.join(' · '); } function calcPageSize(){ return window.matchMedia('(min-width: 820px)').matches ? 4 : 2; } function renderPage(){ const cats = getSortedCats(); const newSize = calcPageSize(); if (newSize !== pageSize) { pageSize = newSize; pageIndex = 0; } const totalPages = Math.max(1, Math.ceil(cats.length / pageSize)); pageIndex = Math.min(pageIndex, totalPages - 1); const start = pageIndex * pageSize; const pageCats = cats.slice(start, start + pageSize); const showPager = cats.length > pageSize; elPrevPage.style.display = showPager ? 'flex' : 'none'; elNextPage.style.display = showPager ? 'flex' : 'none'; elGrid.innerHTML = pageCats.map(cat => `
${iconSvg(cat.icon || cat.id)}
${String(cat.name || cat.id).toUpperCase()}
Placas terminadas en:
--
⏱ Termina en: --
⏱ Horario: --
`).join(''); visibleNodes = new Map(); elGrid.querySelectorAll('.pz-card').forEach(card => { const catId = card.getAttribute('data-cat'); visibleNodes.set(catId, { label: card.querySelector('.pz-label'), value: card.querySelector('.pz-value'), cd: card.querySelector('.pz-countdown'), cdText: card.querySelector('.pz-counttext'), band: card.querySelector('.pz-band'), bandText: card.querySelector('.pz-bandtext'), note: card.querySelector('.pz-note') }); }); updateCardsDynamic(); } function updateCardsDynamic(){ const nowP = nowPartsInTZ(tz); todayTZ = ymdFromParts(nowP); const nowMin = minutesOfDayFromParts(nowP); const isTodaySelected = (selectedYMD === todayTZ); const cats = getSortedCats(); for (const cat of cats){ const refs = visibleNodes.get(String(cat.id)); if (!refs) continue; const r = resolveRestriction(city, cat, selectedYMD); const sched = Array.isArray(r.schedule) && r.schedule.length ? r.schedule : (Array.isArray(cat.schedule) ? cat.schedule : []); const schedLabel = scheduleLabel(sched); refs.value.classList.remove('pz-value--na'); /* Label: por defecto placas, pero taxis usa número interno */ const labelOverride = String(r.labelOverride || '').trim(); const baseLabel = labelOverride || 'Placas terminadas en:'; if (r.kind === 'na'){ refs.label.textContent = 'Estado hoy:'; refs.value.textContent = 'NO APLICA'; refs.value.classList.add('pz-value--na'); } else if (r.kind === 'unknown'){ refs.label.textContent = 'Estado hoy:'; refs.value.textContent = 'SIN INFORMACIÓN'; } else { refs.label.textContent = baseLabel; refs.value.textContent = String(r.value); } if (r.kind === 'na' || r.kind === 'unknown'){ refs.band.classList.remove('pz-band--yellow'); refs.band.classList.add('pz-band--gray'); refs.bandText.textContent = 'Sin restricción'; } else { refs.band.classList.remove('pz-band--gray'); refs.band.classList.add('pz-band--yellow'); refs.bandText.textContent = `Horario: ${schedLabel}`; } const note = String(r.note || '').trim(); if (note){ refs.note.style.display = 'block'; refs.note.textContent = note; } else { refs.note.style.display = 'none'; refs.note.textContent = ''; } const canCount = isTodaySelected && r.kind !== 'na' && r.kind !== 'unknown' && Array.isArray(sched) && sched.length > 0; if (!canCount){ refs.cd.style.display = 'none'; refs.cdText.textContent = ''; continue; } const st = getHorarioEstado(sched, nowMin); if (st.kind !== 'active'){ refs.cd.style.display = 'none'; refs.cdText.textContent = ''; continue; } const hm = countdownHM(st.endMin, nowMin); refs.cd.style.display = 'flex'; refs.cdText.textContent = `Termina en: ${hm.h}h ${pad2(hm.m)}m`; } elTime.textContent = `Hora: ${fmtTimeNoSeconds(tz)}`; } /* Redirección por ciudad */ elCity.addEventListener('change', () => { const nextId = elCity.value; const target = options.find(o => String(o.id) === String(nextId)); const slug = target && target.slug ? String(target.slug).trim() : ''; const url = baseUrl && slug ? `${baseUrl}${slug.replace(/^\/+|\/+$/g,'')}/` : ''; if (safeUrl(url)) window.location.href = url; }); elPrevDay.addEventListener('click', () => { selectedYMD = addDaysYMD(selectedYMD, -1); pageIndex = 0; updateHeader(); renderPage(); }); elNextDay.addEventListener('click', () => { selectedYMD = addDaysYMD(selectedYMD, +1); pageIndex = 0; updateHeader(); renderPage(); }); function openPicker(){ if (typeof elDateInput.showPicker === 'function') elDateInput.showPicker(); else elDateInput.focus(); } elDateCenter.addEventListener('click', openPicker); elDateCenter.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' '){ e.preventDefault(); openPicker(); } }); elDateInput.addEventListener('change', () => { if (elDateInput.value){ selectedYMD = elDateInput.value; pageIndex = 0; updateHeader(); renderPage(); } }); elPrevPage.addEventListener('click', () => { const cats = getSortedCats(); const totalPages = Math.max(1, Math.ceil(cats.length / pageSize)); pageIndex = (pageIndex - 1 + totalPages) % totalPages; renderPage(); }); elNextPage.addEventListener('click', () => { const cats = getSortedCats(); const totalPages = Math.max(1, Math.ceil(cats.length / pageSize)); pageIndex = (pageIndex + 1) % totalPages; renderPage(); }); window.addEventListener('resize', renderPage); updateHeader(); updateFooter(); renderPage(); root._pzTimer && clearInterval(root._pzTimer); root._pzTimer = setInterval(() => { updateHeader(); updateCardsDynamic(); }, 30 * 1000); } })();