`; try { mount(root); } catch (err) { console.error(err); root.innerHTML = `
No se pudo cargar el módulo. Detalle: ${String(err && err.message ? err.message : err)}
`; } }); }; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => window.PZ_PYP_INIT_ALL()); else window.PZ_PYP_INIT_ALL(); window.addEventListener('elementor/frontend/init', () => setTimeout(() => window.PZ_PYP_INIT_ALL(), 50)); let tries = 0; const t = setInterval(() => { tries++; window.PZ_PYP_INIT_ALL(); if (tries >= 12) clearInterval(t); }, 300); function getTZ(root){ return (root.getAttribute('data-tz') || 'America/Bogota').trim() || 'America/Bogota'; } function nowPartsInTZ(tz){ const 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 }); const parts = dtf.formatToParts(new Date()); const m = {}; for (const p of parts) if (p.type !== 'literal') m[p.type] = p.value; return { year:+m.year, month:+m.month, day:+m.day, hour:+m.hour, minute:+m.minute, second:+m.second }; } function ymdFromParts(p){ return `${p.year}-${pad2(p.month)}-${pad2(p.day)}`; } function minutesOfDayFromParts(p){ return p.hour*60 + p.minute; } function dateUTCNoonFromYMD(ymd){ const [y,m,d] = ymd.split('-').map(Number); return new Date(Date.UTC(y, m-1, d, 12, 0, 0)); } function addDaysYMD(ymd, delta){ const dt = dateUTCNoonFromYMD(ymd); dt.setUTCDate(dt.getUTCDate() + delta); return `${dt.getUTCFullYear()}-${pad2(dt.getUTCMonth()+1)}-${pad2(dt.getUTCDate())}`; } function prettyDateHeaderES(tz, ymd){ const dt = dateUTCNoonFromYMD(ymd); const s = new Intl.DateTimeFormat('es-CO', { timeZone: tz, weekday:'long', day:'numeric', month:'long' }).format(dt); return s.charAt(0).toUpperCase() + s.slice(1); } function dayChipES(tz, ymd, isToday){ const dt = dateUTCNoonFromYMD(ymd); const name = isToday ? 'HOY' : new Intl.DateTimeFormat('es-CO', { timeZone: tz, weekday:'short' }).format(dt).replace('.','').toUpperCase(); const num = new Intl.DateTimeFormat('es-CO', { timeZone: tz, day:'2-digit' }).format(dt); return { name, num }; } function isHHMM(s){ return /^([01]\d|2[0-3]):([0-5]\d)$/.test(String(s||'').trim()); } function toMinutes(hhmm){ if (!isHHMM(hhmm)) return null; const [h,m] = hhmm.split(':').map(Number); return h*60 + m; } function time24to12ES(hhmm){ if (!isHHMM(hhmm)) return String(hhmm||''); const [h,m] = hhmm.split(':').map(Number); const h12 = ((h + 11) % 12) + 1; const suf = h >= 12 ? 'p. m.' : 'a. m.'; return `${h12}:${pad2(m)} ${suf}`; } function scheduleLabel(schedule){ const slots = Array.isArray(schedule) ? schedule : []; if (!slots.length) return 'Sin restricción'; return slots.map(s => `${time24to12ES(s.start)} a ${time24to12ES(s.end)}`).join(' · '); } function endTimingOnly(schedule, nowMin){ const slots = (Array.isArray(schedule) ? schedule : []) .map(s => ({ s: toMinutes(String(s.start||'')), e: toMinutes(String(s.end||'')) })) .filter(x => x.s!==null && x.e!==null) .sort((a,b)=>a.s-b.s); for (const slot of slots){ if (nowMin >= slot.s && nowMin < slot.e) return { kind:'end', at: slot.e }; } return { kind:'none' }; } function hmFromDelta(mins){ const m = Math.max(0, mins); return { h: Math.floor(m/60), m: m%60 }; } function safeUrl(u){ const s = String(u ?? '').trim(); return /^https?:\/\//i.test(s) ? s : ''; } function iconSvg(type){ const common = `width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"`; const car = `
`;
const bus = `
`;
const taxi = `
`;
const k = String(type||'').toLowerCase();
if (k.includes('bus')) return bus;
if (k.includes('taxi')) return taxi;
return car;
}
function pinSvg(){
return `
`;
}
function chevDownSvg(){
return `
`;
}
function resolveRestriction(cat, ymd){
const rule = cat.rule || {};
if (rule.type === "date_map"){
const v = (rule.map || {})[ymd];
if (!v) return { kind:"unknown", value:"SIN INFORMACIÓN" };
if (String(v).toUpperCase() === "NO_APLICA") return { kind:"na", value:"NO APLICA" };
return { kind:"digits", value:String(v) };
}
return { kind:"unknown", value:"SIN INFORMACIÓN" };
}
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 catsAll = (Array.isArray(city.categories) ? city.categories : [])
.filter(c => c && c.enabled !== false)
.slice()
.sort((a,b)=>Number(a.priority||99)-Number(b.priority||99));
root.innerHTML = `
PICO Y PLACA ${String(city.name||'').toUpperCase()}
${pinSvg()}
Ciudad
${city.name}
${chevDownSvg()}
Desliza para ver más
`;
const $ = (s) => root.querySelector(s);
const elStrip = $('.pz-strip');
const elDateDisplay = $('.pz-date-display');
const elPages = $('.pz-pages');
const elDots = $('.pz-dots');
const elPrev = $('.pz-nav--prev');
const elNext = $('.pz-nav--next');
const elSwipe = $('.pz-swipehint');
const elCredits = $('.pz-credits');
const elCitySel = $('.pz-cityselect');
const opts = city.switch?.options || [];
elCitySel.innerHTML = opts.map(o => ``).join('');
elCitySel.addEventListener('change', (e) => {
const u = safeUrl(e.target.value);
if (u) window.location.href = u;
});
const srcName = String(city.source?.name || 'Fuente oficial').trim();
const srcUrl = safeUrl(city.source?.url || '');
elCredits.innerHTML = srcUrl
? `Fuente: ${srcName}`
: `Fuente: ${srcName}`;
let todayYMD = ymdFromParts(nowPartsInTZ(tz));
let selectedYMD = todayYMD;
function renderStrip(){
const days = [];
for (let i=0;i<9;i++) days.push(addDaysYMD(todayYMD, i));
elStrip.innerHTML = days.map(ymd => {
const isToday = ymd === todayYMD;
const isSel = ymd === selectedYMD;
const chip = dayChipES(tz, ymd, isToday);
return `
${chip.name}
${chip.num}
`;
}).join('');
elStrip.querySelectorAll('.pz-day').forEach(btn=>{
btn.addEventListener('click', ()=>{
selectedYMD = btn.getAttribute('data-ymd');
renderStrip();
renderAll();
});
});
}
let perView = 4;
let pageIndex = 0;
let pages = [];
function computePerView(){
const w = root.getBoundingClientRect().width || window.innerWidth;
if (w < 600) return 1;
if (w < 980) return 2;
return 4;
}
function chunk(arr, size){
const out = [];
for (let i=0;i
${iconSvg(cat.icon || cat.id)}
${String(cat.name||cat.id).toUpperCase()}
${String(cat.plate_label || 'Placas terminadas en:')}
--
`;
}
function renderPages(){
perView = computePerView();
pageIndex = 0;
const single = catsAll.length === 1;
if (single) perView = 1;
if (single) pages = [catsAll];
else if (perView === 4) pages = [catsAll.slice(0,4)];
else pages = chunk(catsAll, perView);
elPages.innerHTML = pages.map((cats, idx) => {
const klass = `pz-page ${idx===0?'is-active':''} pz-per-${perView} ${single?'pz-single':''}`;
return `
${cats.map(cat => cardHtml(cat)).join('')}
`;
}).join('');
if (perView === 4 || single){
elDots.innerHTML = '';
} else {
elDots.innerHTML = pages.map((_,i)=> `
`).join('');
elDots.querySelectorAll('.pz-dot').forEach((d,i)=> d.addEventListener('click', ()=>goTo(i)));
}
const showNav = (!single) && (perView !== 4) && pages.length > 1;
elPrev.style.display = showNav ? 'flex' : 'none';
elNext.style.display = showNav ? 'flex' : 'none';
elSwipe.style.display = showNav ? 'block' : 'none';
elDots.style.display = showNav ? 'flex' : 'none';
updateNavState();
updateCardsDynamic();
}
function goTo(i){
pageIndex = Math.max(0, Math.min(i, pages.length-1));
elPages.querySelectorAll('.pz-page').forEach(p=>p.classList.remove('is-active'));
const active = elPages.querySelector(`.pz-page[data-page="${pageIndex}"]`);
if (active) active.classList.add('is-active');
elDots.querySelectorAll('.pz-dot').forEach((d,idx)=>d.classList.toggle('is-active', idx===pageIndex));
updateNavState();
updateCardsDynamic();
}
function updateNavState(){
const single = catsAll.length === 1;
if (single || perView === 4){
elPrev.disabled = true; elNext.disabled = true;
elPrev.style.opacity = 0; elNext.style.opacity = 0;
return;
}
elPrev.disabled = pageIndex<=0;
elNext.disabled = pageIndex>=pages.length-1;
elPrev.style.opacity = elPrev.disabled ? .35 : 1;
elNext.style.opacity = elNext.disabled ? .35 : 1;
}
function renderAll(){
elDateDisplay.textContent = prettyDateHeaderES(tz, selectedYMD);
renderPages();
}
function updateCardsDynamic(){
const nowP = nowPartsInTZ(tz);
todayYMD = ymdFromParts(nowP);
const nowMin = minutesOfDayFromParts(nowP);
const isToday = selectedYMD === todayYMD;
renderStrip();
elDateDisplay.textContent = prettyDateHeaderES(tz, selectedYMD);
root.querySelectorAll('.pz-card').forEach(card=>{
const id = card.getAttribute('data-cat');
const cat = catsAll.find(c=>c.id===id);
if (!cat) return;
const r = resolveRestriction(cat, selectedYMD);
const elValue = card.querySelector('.pz-value');
const elHours = card.querySelector('.pz-bandhours');
const elTimer = card.querySelector('.pz-timer');
elTimer.style.display = 'none';
elTimer.textContent = '';
const sched = Array.isArray(cat.schedule) ? cat.schedule : [];
if (r.kind === 'na'){
elValue.textContent = 'NO APLICA';
elHours.textContent = 'No aplica';
return;
}
if (r.kind === 'unknown'){
elValue.textContent = 'SIN INFORMACIÓN';
elHours.textContent = scheduleLabel(sched);
return;
}
elValue.textContent = r.value;
elHours.textContent = scheduleLabel(sched);
if (!isToday || !sched.length) return;
const t2 = endTimingOnly(sched, nowMin);
if (t2.kind === 'end'){
const hm = hmFromDelta(t2.at - nowMin);
elTimer.style.display = 'block';
elTimer.textContent = `Termina en ${hm.h}h ${pad2(hm.m)}m`;
}
});
}
elPrev.addEventListener('click', ()=>goTo(pageIndex-1));
elNext.addEventListener('click', ()=>goTo(pageIndex+1));
let startX = null;
elPages.addEventListener('touchstart', (e)=>{
if (catsAll.length === 1) return;
if (perView === 4) return;
startX = e.touches && e.touches[0] ? e.touches[0].clientX : null;
}, {passive:true});
elPages.addEventListener('touchend', (e)=>{
if (catsAll.length === 1) return;
if (perView === 4 || startX === null) return;
const endX = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : null;
if (endX === null) return;
const dx = endX - startX;
if (Math.abs(dx) > 40){
if (dx < 0) goTo(pageIndex+1);
else goTo(pageIndex-1);
}
startX = null;
}, {passive:true});
renderStrip();
renderAll();
updateCardsDynamic();
root._pzTimer && clearInterval(root._pzTimer);
root._pzTimer = setInterval(() => updateCardsDynamic(), 30*1000);
window.addEventListener('resize', ()=>{
const newPV = computePerView();
if (newPV !== perView) renderAll();
});
}
})();