'; } }); } 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 : []; // Caso “Todo el día” 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 iconSvg(key){ var common = 'width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true"'; var car = '
';
var taxi = '
';
var k = String(key||'').toLowerCase();
if (k.includes('taxi')) return taxi;
return car;
}
function resolveRestriction(city, cat, ymd){
var rule = cat.rule || {};
var dk = dayKeyFromYMD(ymd);
if (rule.type === "no_aplica"){
return { value:"NO APLICA", kind:"na", note: String(rule.note||"").trim(), schedule: [] };
}
if (rule.type === "weekday_calendar_list"){
var v = rule.days && rule.days[ymd] ? String(rule.days[ymd]) : "";
if (!v) return { value:"SIN INFORMACIÓN", kind:"unknown", note: String(rule.default_note||"").trim(), schedule: [] };
return { value:v, 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: --:--
' +
'
' +
'📍 ' +
(city.switch && city.switch.label ? city.switch.label : "Cambiar ciudad") +
' – ' + String(city.name || city.id) +
' ' +
'' +
' ' +
' ' +
'
' +
'' +
'
◀ AYER ' +
'
' +
'
MAÑANA ▶ ' +
'
' +
'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){
return '' +
'
' +
'' +
'
'+iconSvg(cat.icon || cat.id)+'
' +
'
'+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);
}
})();