'; } }); } function pad2(n){ return String(n).padStart(2,'0'); } function getTZ(root){ return (root.getAttribute('data-tz') || 'America/Bogota').trim() || 'America/Bogota'; } function nowPartsInTZ(tz){ var dtf = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false }); var parts = dtf.formatToParts(new Date()); var m = {}; for (var i=0;i= 12 ? 'p. m.' : 'a. m.'; return h12 + ':' + pad2(m) + ' ' + suf; } function scheduleLabel(schedule){ var slots = Array.isArray(schedule) ? schedule : []; if (slots.length === 1 && String(slots[0].start) === "00:00" && String(slots[0].end) === "24:00") return "Todo el día"; var parts = []; for (var i=0;i= slot.s && nowMin < slot.e) return { kind:'active', endMin: slot.e }; } return { kind:'inactive', endMin:null }; } function countdownHM(endMin, nowMin){ if (endMin === null) return null; var rem = Math.max(0, endMin - nowMin); return { h: Math.floor(rem/60), m: rem % 60 }; } function safeUrl(u){ var s = String(u ?? '').trim(); return /^https?:\/\//i.test(s) ? s : ''; } function dayKeyFromYMD(ymd){ var dt = dateUTCNoonFromYMD(ymd); return ['sun','mon','tue','wed','thu','fri','sat'][dt.getUTCDay()]; } function resolveRestriction(city, cat, ymd){ var rule = cat.rule || {}; var dk = dayKeyFromYMD(ymd); if (rule.type === "weekday_pairs"){ if (rule.weekend_no_aplica && (dk === "sat" || dk === "sun")){ return { value:"NO APLICA", kind:"na", note:"Fin de semana: no aplica.", schedule: [] }; } var v = rule.weekdays && rule.weekdays[dk] ? String(rule.weekdays[dk]) : ""; if (!v) return { value:"SIN INFORMACIÓN", kind:"unknown", note:"No hay dato cargado para esta fecha.", schedule: [] }; return { value:v, kind:"digits", note:"", schedule: cat.schedule || [] }; } if (rule.type === "weekday_calendar_list"){ var v2 = rule.days && rule.days[ymd] ? String(rule.days[ymd]) : ""; if (!v2) return { value:"SIN INFORMACIÓN", kind:"unknown", note: String(rule.default_note||"").trim(), schedule: [] }; if (v2.toUpperCase() === "NO APLICA"){ return { value:"NO APLICA", kind:"na", note:"No aplica este día.", schedule: [] }; } return { value:v2, kind:"digits", note:"", schedule: cat.schedule || [] }; } return { value:"SIN INFORMACIÓN", kind:"unknown", note:"", schedule: [] }; } function mount(root){ var tz = getTZ(root); var key = String(root.getAttribute("data-key") || "").trim(); var 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.'); var city = cfg.city; var options = (city.switch && Array.isArray(city.switch.options)) ? city.switch.options : []; root.innerHTML = '
' + '' + '' + '
Hora: --:--
' + '
' + '
' + '' + '
' + '
' + '
' + '
' + 'Si vas a viajar, revisa el pico y placa para la fecha de tu viaje.
' + '' + '' + ''; function $(sel){ return root.querySelector(sel); } var elTitle = $('.pz-title'); var elTime = $('.pz-time'); var elCity = $('.pz-cityselect'); var elWeek = $('.pz-weekday'); var elDate = $('.pz-date'); var elDateInput = $('.pz-dateinput'); var elDateCenter = $('.pz-datecenter'); var elPrevDay = $('.pz-daybtn--left'); var elNextDay = $('.pz-daybtn--right'); var elGrid = $('.pz-grid'); var elFoot = $('.pz-foot'); var elPrevPage = $('.pz-page--prev'); var elNextPage = $('.pz-page--next'); elCity.innerHTML = options .slice() .sort(function(a,b){ return String(a.name||'').localeCompare(String(b.name||''), 'es'); }) .map(function(o){ return ''; }) .join(''); elCity.value = city.id; var todayTZ = ymdFromParts(nowPartsInTZ(tz)); var selectedYMD = todayTZ; var pageIndex = 0; var pageSize = window.matchMedia('(min-width: 820px)').matches ? 4 : 2; var visibleNodes = new Map(); function getSortedCats(){ return (Array.isArray(city.categories) ? city.categories : []) .slice() .sort(function(a,b){ return (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(){ var srcName = String(city.source && city.source.name ? city.source.name : '').trim(); var srcUrl = safeUrl(city.source && city.source.url ? city.source.url : ''); elFoot.innerHTML = srcUrl ? 'Fuente: '+(srcName || 'Fuente oficial')+'' : (srcName ? ('Fuente: ' + srcName) : ''); } function calcPageSize(){ return window.matchMedia('(min-width: 820px)').matches ? 4 : 2; } function renderPage(){ var cats = getSortedCats(); var newSize = calcPageSize(); if (newSize !== pageSize) { pageSize = newSize; pageIndex = 0; } var totalPages = Math.max(1, Math.ceil(cats.length / pageSize)); pageIndex = Math.min(pageIndex, totalPages - 1); var start = pageIndex * pageSize; var pageCats = cats.slice(start, start + pageSize); var showPager = cats.length > pageSize; elPrevPage.style.display = showPager ? 'flex' : 'none'; elNextPage.style.display = showPager ? 'flex' : 'none'; elGrid.innerHTML = pageCats.map(function(cat){ var emoji = String(cat.emoji || "🚗"); return '' + '
' + '' + '
'+emoji+'
' + '
'+String(cat.name || cat.id).toUpperCase()+'
' + '
' + '' + '
--
' + '
--
' + '
' + '⏱' + 'Termina en: --' + '
' + '
' + '' + '⏱' + 'Horario: --' + '
' + '' + ''; }).join(''); visibleNodes = new Map(); root.querySelectorAll('.pz-card').forEach(function(card){ var 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(){ var nowP = nowPartsInTZ(tz); todayTZ = ymdFromParts(nowP); var nowMin = minutesOfDayFromParts(nowP); var isTodaySelected = (selectedYMD === todayTZ); var cats = getSortedCats(); for (var i=0;i 0; if (!canCount){ refs.cd.style.display = 'none'; refs.cdText.textContent = ''; continue; } var st = getHorarioEstado(sched, nowMin); if (st.kind !== 'active'){ refs.cd.style.display = 'none'; refs.cdText.textContent = ''; continue; } var 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); } elCity.addEventListener('change', function(){ var nextId = elCity.value; var target = options.find(function(o){ return String(o.id) === String(nextId); }); var url = target && target.url ? String(target.url).trim() : ''; if (safeUrl(url)) window.location.href = url; }); elPrevDay.addEventListener('click', function(){ selectedYMD = addDaysYMD(selectedYMD, -1); pageIndex = 0; updateHeader(); renderPage(); }); elNextDay.addEventListener('click', function(){ 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', function(e){ if (e.key === 'Enter' || e.key === ' '){ e.preventDefault(); openPicker(); } }); elDateInput.addEventListener('change', function(){ if (elDateInput.value){ selectedYMD = elDateInput.value; pageIndex = 0; updateHeader(); renderPage(); } }); elPrevPage.addEventListener('click', function(){ var cats = getSortedCats(); var totalPages = Math.max(1, Math.ceil(cats.length / pageSize)); pageIndex = (pageIndex - 1 + totalPages) % totalPages; renderPage(); }); elNextPage.addEventListener('click', function(){ var cats = getSortedCats(); var 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(function(){ updateHeader(); updateCardsDynamic(); }, 30 * 1000); } })();