/* AMIIN · Ranking Halal/Muslim-Friendly (JS externo v1.2) - CSV principal: https://amiin.es/wp-content/uploads/2025/09/Ranking.csv - Columnas flexibles (case-insensitive): Estrellas: "Estrellas_%" | "Estrellas" Lunas: "Lunas_%" | "Lunas" Nota final: "notafinal" | "Nota_final" | "Nota final" | "Nota_final_/10" - Fallbacks de datos: CSV local -> Google CSV -> Google GViz JSON */ (function(){ "use strict"; var SOURCES = { localCSV: "https://amiin.es/wp-content/uploads/2025/09/Ranking.csv", googleCSV:"https://docs.google.com/spreadsheets/d/e/2PACX-1vReAdUJ-QqL81u8NoOFmoSGMN0MBTbOjmUW2qL3z3OGeDvlb_PlxTeV5Fntj7XAmg/pub?output=csv", googleGViz:"https://docs.google.com/spreadsheets/d/e/2PACX-1vReAdUJ-QqL81u8NoOFmoSGMN0MBTbOjmUW2qL3z3OGeDvlb_PlxTeV5Fntj7XAmg/gviz/tq?tqx=out:json" }; var WEIGHT_STAR=0.45, WEIGHT_MOON=0.55, FLOOR=3.0; var MACRO_COLS=["Renta_disponible","PIB_per_capita","Tasa_empleo","Densidad_empresarial","Accesibilidad_turismo","Asequibilidad_vivienda","Nivel_educativo","Coste_vida_relativo","Seguridad_general"]; var HALAL_NONBIN=["Poblacion_musulmana","Porcentaje_musulmana","Oratorios_asociaciones","Restaurantes_tiendas_halal","Empresas_certificadas_halal","Eventos_halal","Plan_municipal_diversidad","Delitos_odio_invertido"]; var HALAL_BIN=["Cementerio_islamico","Docentes_religion_islamica","Reconocimiento_Eid"]; var ROOT = document.getElementById("amiin-sheet-ranking"); if(!ROOT){ return; } // ===== CSS inyectado (desde script externo) ===== var CSS = ` :root{--blue:#0e00ff;--ink:#0b1220;--muted:#5b657a;--bg:#f5f7fb;--card:#fff;--line:#e7eaf1;--chip:#f2f4fb;--star-empty:#cfd6e6;--star-full:#ffd54f;--moon-empty:#cfd6e6;--moon-full:#3aa6ff} .amiin-wrap{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Arial,sans-serif;color:var(--ink);background:var(--bg);border:1px solid var(--line);border-radius:16px;padding:18px} .amiin-header h2{margin:0 0 4px 0;font-size:22px;font-weight:800} .sub{color:var(--muted);font-size:12px;margin:0 0 8px 0} .status{margin:8px 0;font-size:12px;color:var(--muted)} .amiin-controls{display:grid;gap:10px;margin:10px 0 14px} .amiin-controls .row{display:flex;flex-wrap:wrap;gap:10px} .amiin-controls label{display:grid;gap:4px;font-size:12px} .amiin-controls input,.amiin-controls select{padding:8px;border:1px solid var(--line);border-radius:10px;background:#fff} .btn{background:var(--blue);color:#fff;border:1px solid var(--blue);border-radius:999px;padding:8px 12px;cursor:pointer;font-size:12px} .btn.secondary{background:#eef2ff;color:#1f2a5a;border:1px solid #d7dcff} .view{display:none}.view.is-active{display:block} .pager{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px} .pager button{background:#eef2ff;color:#1f2a5a;border:1px solid #d7dcff;border-radius:999px;padding:6px 10px;cursor:pointer;font-size:12px} .pager button.is-active{background:var(--blue);border-color:var(--blue);color:#fff} .rank-list{list-style:none;margin:0;padding:0;display:grid;gap:10px} .card{display:grid;grid-template-columns:56px 1fr auto;gap:12px;align-items:center;background:var(--card);border:1px solid var(--line);border-radius:14px;padding:12px} .rank{width:48px;height:48px;border-radius:12px;background:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;color:var(--blue);border:2px solid var(--blue)} .meta{display:grid;gap:2px}.city{font-weight:700}.ccaa{font-size:12px;color:var(--muted)}.notes{font-size:12px;color:#30384a} .scores{display:grid;gap:6px;text-align:right} .row{display:flex;align-items:center;gap:8px;justify-content:flex-end} .label{font-size:11px;color:var(--muted)} .chip{display:inline-flex;align-items:center;gap:8px;background:var(--chip);border:1px solid var(--line);border-radius:999px;padding:6px 10px} .stars,.moons{position:relative;display:inline-block;line-height:1;font-size:14px} .stars>span:first-child{color:var(--star-empty)} .stars>span:last-child{color:var(--star-full);position:absolute;inset:0;width:0%;overflow:hidden;white-space:nowrap} .moons>span:first-child{color:var(--moon-empty)} .moons>span:last-child{color:var(--moon-full);position:absolute;inset:0;width:0%;overflow:hidden;white-space:nowrap} .total{margin-top:2px} .total-chip{display:inline-flex;align-items:center;gap:6px;background:#eef2ff;color:#1f2a5a;border:1px solid #d7dcff;border-radius:999px;padding:6px 10px;font-variant-numeric:tabular-nums} .table-wrap{background:#fff;border:1px solid var(--line);border-radius:14px;padding:10px} table{width:100%;border-collapse:collapse;font-size:14px} thead th{background:#f0f3ff;border-bottom:1px solid var(--line);padding:8px;text-align:left;cursor:pointer} tbody td{padding:8px;border-bottom:1px solid var(--line)} .table-pager{display:flex;justify-content:space-between;align-items:center;margin-top:8px} @media (max-width:640px){.card{grid-template-columns:44px 1fr}.scores{text-align:left}} `; var style=document.createElement("style"); style.textContent=CSS; document.head.appendChild(style); // ===== HTML mínimo dentro del contenedor ===== ROOT.innerHTML = '' + '

Ranking Halal/Muslim-Friendly · 50 ciudades (España)

' + '

Estrellas (macro/micro) · Medias lunas (halal/muslim-friendly) · Nota final (0–10, suelo 3.0)

' + '
Cargando datos…
' + '
' + '' + '' + '' + '' + '
' + '
' + '' + '
' + '
    ' + '
    ' + '' + '
    #CiudadCCAAEstrellas %Lunas %Nota /10
    '; // ===== Refs & estado ===== var statusBox=ROOT.querySelector("#amiin-status"); var rankList=ROOT.querySelector("#rank-list"); var pager=ROOT.querySelector(".pager"); var fSearch=ROOT.querySelector("#f-search"); var fCCAA=ROOT.querySelector("#f-ccaa"); var fMin=ROOT.querySelector("#f-min"); var fMax=ROOT.querySelector("#f-max"); var btnClear=ROOT.querySelector("#btn-clear"); var btnCSV=ROOT.querySelector("#btn-csv"); var btnViewRanking=ROOT.querySelector("#btn-view-ranking"); var btnViewTable=ROOT.querySelector("#btn-view-table"); var viewRanking=ROOT.querySelector("#view-ranking"); var viewTable=ROOT.querySelector("#view-table"); var tBody=ROOT.querySelector("#summary-table tbody"); var tPrev=ROOT.querySelector("#t-prev"); var tNext=ROOT.querySelector("#t-next"); var tInfo=ROOT.querySelector("#t-info"); var DATA=[], CURRENT=[]; var page=1, PER_PAGE=10, tPage=1, T_PER=10, tSortKey="rank", tSortDir="asc"; // ===== Utils ===== var clamp=function(n,min,max){ return Math.max(min,Math.min(max,n)); }; var num=function(v){ if(v===null||v===undefined) return null; var x=parseFloat(String(v).replace(",",".").trim()); return isFinite(x)?x:null; }; function detectDelimiter(line){ var commas=(line.match(/,/g)||[]).length; var semis=(line.match(/;/g)||[]).length; return semis>commas?';':','; } function parseCSV(text){ var first = text.split(/\r?\n/,1)[0] || ''; var DELIM = detectDelimiter(first); var rows=[], i=0, f="", r=[], inQ=false; while(i0 || r.length>0){ r.push(f); rows.push(r); } return rows; } // header-map: genera un objeto {lowercasedHeader: originalHeader} function mapHeaders(headers){ var m={}; headers.forEach(function(h){ var key=String(h||'').trim(); if(key) m[key.toLowerCase()]=key; }); return m; } // toma por alias (case-insensitive) function pickByAliases(obj, headerMap, aliases){ for(var i=0;i'+c+'';}).join(''); } function renderRanking(){ var pages=Math.ceil(CURRENT.length/10)||1; page=Math.min(page,pages); var start=(page-1)*10, end=start+10; var slice=CURRENT.slice(start,end); rankList.innerHTML=''; slice.forEach(function(d){ var li=document.createElement('li'); li.className='card'; var r=document.createElement('div'); r.className='rank'; r.textContent=d.rank; var meta=document.createElement('div'); meta.className='meta'; meta.innerHTML='
    '+d.city+'
    '+d.ccaa+'
    '+(d.notes||'')+'
    '; var scores=document.createElement('div'); scores.className='scores'; var s=document.createElement('span'); s.className='stars'; s.innerHTML='★★★★★★★★★★'; s.querySelector('span:last-child').style.width=Math.max(0,Math.min(100,d.star))+'%'; var m=document.createElement('span'); m.className='moons'; m.innerHTML='☾☾☾☾☾☾☾☾☾☾'; m.querySelector('span:last-child').style.width=Math.max(0,Math.min(100,d.moon))+'%'; var row1=document.createElement('div'); row1.className='row'; row1.innerHTML='Estrellas'; var chip1=document.createElement('span'); chip1.className='chip'; chip1.appendChild(s); row1.appendChild(chip1); var row2=document.createElement('div'); row2.className='row'; row2.innerHTML='Medias lunas'; var chip2=document.createElement('span'); chip2.className='chip'; chip2.appendChild(m); row2.appendChild(chip2); var total=document.createElement('div'); total.className='total'; var pill=document.createElement('span'); pill.className='total-chip'; pill.innerHTML=''+d.score.toFixed(1)+' / 10'; total.appendChild(pill); scores.appendChild(row1); scores.appendChild(row2); scores.appendChild(total); li.append(r,meta,scores); rankList.appendChild(li); }); pager.innerHTML=''; for(var i=1;i<=pages;i++){ var b=document.createElement('button'); b.textContent=((i-1)*10+1)+'–'+Math.min(i*10,CURRENT.length); if(i===page) b.classList.add('is-active'); (function(pi){ b.addEventListener('click', function(){ page=pi; renderRanking(); window.scrollTo({top:ROOT.offsetTop,behavior:'smooth'}); }); })(i); pager.appendChild(b); } } function renderTable(){ var rows=CURRENT.slice().sort(function(a,b){ var k=tSortKey, m=(tSortDir==='asc')?1:-1; if(k==='city'||k==='ccaa') return m*String(a[k]).localeCompare(String(b[k])); return m*((a[k]||0)-(b[k]||0)); }); var pages=Math.ceil(CURRENT.length/T_PER)||1; tPage=Math.min(tPage,pages); var start=(tPage-1)*T_PER, end=start+T_PER; var slice=rows.slice(start,end); tBody.innerHTML=slice.map(function(d){ return ''+d.rank+''+d.city+''+d.ccaa+''+d.star.toFixed(0)+'%'+d.moon.toFixed(0)+'%'+d.score.toFixed(1)+''; }).join(''); tInfo.textContent='Mostrando '+(slice.length?start+1:0)+'–'+Math.min(end,CURRENT.length)+' de '+CURRENT.length; tPrev.disabled=(tPage<=1); tNext.disabled=(tPage>=pages); } function applyFilters(){ var q=(fSearch.value||'').trim().toLowerCase(); var ccaa=fCCAA.value||''; var min=clamp(parseFloat((fMin.value||'0').replace(',','.'))||0,0,10); var max=clamp(parseFloat((fMax.value||'10').replace(',','.'))||10,0,10); CURRENT = DATA.filter(function(d){ var okQ=!q || d.city.toLowerCase().includes(q); var okC=!ccaa || d.ccaa===ccaa; var okS=d.score>=min && d.score<=max; return okQ && okC && okS; }); page=1; tPage=1; } function renderAll(){ renderRanking(); if(viewTable.classList.contains('is-active')) renderTable(); } [fSearch,fCCAA,fMin,fMax].forEach(function(inp){ inp.addEventListener('input', function(){ applyFilters(); renderAll(); }); }); btnClear.addEventListener('click', function(){ fSearch.value=''; fCCAA.value=''; fMin.value='0'; fMax.value='10'; applyFilters(); renderAll(); }); btnCSV.addEventListener('click', function(){ var head=['rank','city','ccaa','Estrellas_%','Lunas_%','Nota_/10'], lines=[head.join(',')]; CURRENT.forEach(function(d){ lines.push([d.rank,'"'+d.city.replace(/"/g,'""')+'"','"'+d.ccaa.replace(/"/g,'""')+'"',d.star.toFixed(0),d.moon.toFixed(0),d.score.toFixed(1)].join(',')); }); var blob=new Blob([lines.join('\n')],{type:'text/csv;charset=utf-8;'}); var url=URL.createObjectURL(blob); var a=document.createElement('a'); a.href=url; a.download='amiin_ranking_vista.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }); ROOT.querySelectorAll('#summary-table thead th').forEach(function(th){ th.addEventListener('click', function(){ var k=th.getAttribute('data-k'); if(!k) return; if(tSortKey===k){ tSortDir=(tSortDir==='asc'?'desc':'asc'); } else { tSortKey=k; tSortDir='asc'; } renderTable(); }); }); tPrev.addEventListener('click', function(){ tPage=Math.max(1,tPage-1); renderTable(); }); tNext.addEventListener('click', function(){ tPage=tPage+1; renderTable(); }); btnViewRanking.addEventListener('click', function(){ viewRanking.classList.add('is-active'); viewTable.classList.remove('is-active'); renderRanking(); }); btnViewTable.addEventListener('click', function(){ viewTable.classList.add('is-active'); viewRanking.classList.remove('is-active'); renderTable(); }); // ===== Carga con fallbacks ===== function tryLoad(){ setStatus("Cargando datos…"); // 1) CSV local fetchWithTimeout(SOURCES.localCSV+"?nocache="+Date.now(),15000) .then(function(res){ if(!res.ok) throw new Error("HTTP "+res.status); return res.text(); }) .then(function(txt){ var rows=parseCSV(txt); if(!rows.length || rows[0].length<2) throw new Error("CSV local vacío/dañado"); processRows(rows); setStatus(""); renderAll(); }) .catch(function(err1){ // 2) CSV Google fetchWithTimeout(SOURCES.googleCSV+"&_="+Date.now(),15000) .then(function(res){ if(!res.ok) throw new Error("HTTP "+res.status); return res.text(); }) .then(function(txt){ var rows=parseCSV(txt); if(!rows.length || rows[0].length<2) throw new Error("CSV Google vacío/dañado"); processRows(rows); setStatus(""); renderAll(); }) .catch(function(err2){ // 3) GViz JSON fetchWithTimeout(SOURCES.googleGViz+"&_="+Date.now(),15000) .then(function(res){ if(!res.ok) throw new Error("HTTP "+res.status); return res.text(); }) .then(function(text){ var rows=(function(t){var m=t.match(/setResponse\(([\s\S]*?)\)\s*;?$/);if(!m) throw new Error("GViz: formato inesperado");var j=JSON.parse(m[1]);if(!j.table||!j.table.cols||!j.table.rows) throw new Error("GViz: tabla vacía");var h=j.table.cols.map(function(c){return (c.label||c.id||'').trim();});var rw=j.table.rows.map(function(r){return r.c.map(function(cell){return (cell&&cell.v!=null)?String(cell.v):'';});});return [h].concat(rw);})(text); processRows(rows); setStatus(""); renderAll(); }) .catch(function(err3){ setStatus("Error al cargar datos. Detalles:
    " + "Local: "+(err1.message||err1)+"
    " + "Google CSV: "+(err2.message||err2)+"
    " + "GViz: "+(err3.message||err3)+"
    "); }); }); }); } tryLoad(); })();