<!DOCTYPE html>

<html lang="es">

<head>

  <meta charset="UTF-8" />

  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <title>Rifa IGSA – 21 Autos (Precargada)</title>

  <style>

    :root { --bg:#0b1020; --card:#121935; --accent:#00e0ff; --accent2:#72ff9f; --text:#e9f0ff; --muted:#8aa1c2; --danger:#ff6b6b; --gold:#ffd166; }

    *{box-sizing:border-box}

    body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,"Helvetica Neue",Noto Sans,Arial,"Apple Color Emoji","Segoe UI Emoji";background:radial-gradient(1200px 800px at 20% 10%,#19234d 0%,#0b1020 60%,#080b17 100%);color:var(--text)}

    .wrap{max-width:1200px;margin:0 auto;padding:24px}

    header{display:flex;align-items:center;gap:16px;flex-wrap:wrap}

    h1{font-size:clamp(22px,3vw,32px);margin:8px 0;letter-spacing:.3px}

    .badge{font-size:12px;padding:6px 10px;border:1px solid #2b386a;border-radius:999px;color:var(--muted)}

    .grid{display:grid;grid-template-columns:1.1fr .9fr;gap:24px}

    @media(max-width:980px){.grid{grid-template-columns:1fr}}

    .card{background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,.02));border:1px solid rgba(255,255,255,.08);border-radius:18px;box-shadow:0 10px 30px rgba(0,0,0,.3)}

    .card-head{padding:16px 20px;border-bottom:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-between}

    .card-body{padding:18px 20px}

    .controls{display:flex;gap:10px;flex-wrap:wrap}

    button,.btn{background:linear-gradient(180deg,rgba(0,224,255,.18),rgba(0,224,255,.08));border:1px solid rgba(0,224,255,.35);color:var(--text);padding:10px 14px;border-radius:12px;cursor:pointer;font-weight:600;letter-spacing:.2px;transition:transform .05s ease,box-shadow .2s ease}

    button:hover{box-shadow:0 0 0 3px rgba(0,224,255,.18) inset}

    button:active{transform:translateY(1px)}

    button.secondary{background:transparent;border-color:#3b4778}

    button.danger{background:linear-gradient(180deg,rgba(255,107,107,.18),rgba(255,107,107,.08));border-color:rgba(255,107,107,.5)}

    button.gold{background:linear-gradient(180deg,rgba(255,209,102,.25),rgba(255,209,102,.08));border-color:rgba(255,209,102,.6);color:#0b0f1e}

    .row{display:flex;gap:14px;align-items:center;flex-wrap:wrap}

    .field{display:flex;flex-direction:column;gap:6px}

    .field label{font-size:12px;color:var(--muted)}

    input[type=number],input[type=text],textarea,select{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:var(--text);padding:10px 12px;border-radius:12px;outline:none;min-width:120px}

    textarea{min-height:120px;width:100%}

    .stage{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:stretch}

    @media(max-width:740px){.stage{grid-template-columns:1fr}}

    .roller{background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.02));border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:14px;text-align:center;position:relative;overflow:hidden}

    .roller h3{margin:0 0 10px;font-weight:700;letter-spacing:.3px;color:var(--muted)}

    .window{height:96px;border-radius:12px;background:radial-gradient(400px 120px at 50% 0%,rgba(255,255,255,.08),rgba(255,255,255,.02));display:flex;align-items:center;justify-content:center;font-size:52px;font-weight:800;letter-spacing:1px;text-shadow:0 8px 24px rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.1)}

    .window.big{height:120px;font-size:64px}

    .blink{animation:blink 1s infinite}

    @keyframes blink{0%,60%{opacity:1}70%{opacity:.5}100%{opacity:1}}

    .result{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}

    .result div{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:10px 12px}

    .muted{color:var(--muted);font-size:13px}

    table{width:100%;border-collapse:collapse}

    th,td{padding:10px 12px;border-bottom:1px solid rgba(255,255,255,.08);font-size:14px}

    th{text-align:left;color:var(--muted);font-weight:600}

    tbody tr:hover{background:rgba(255,255,255,.03)}

    .pill{display:inline-block;padding:4px 8px;border-radius:999px;font-size:11px;border:1px solid rgba(255,255,255,.16)}

    .footer{color:var(--muted);font-size:12px;text-align:center;padding:10px}

    .hidden{display:none!important}

    .confetti{position:fixed;inset:0;pointer-events:none;overflow:hidden}

    .confetti i{position:absolute;width:10px;height:16px;background:var(--accent);top:-20px;opacity:.9;transform:rotate(0deg);animation:fall linear forwards}

    @keyframes fall{to{transform:translateY(110vh) rotate(360deg);opacity:1}}

    #messageBar{margin-top:12px;padding:10px 12px;border-radius:12px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.04);font-size:14px;display:none}

    #messageBar.ok{border-color:rgba(114,255,159,.5)}

    #messageBar.err{border-color:rgba(255,107,107,.5)}

  </style>

</head>

<body>

  <div class="wrap">

    <header>

      <svg width="36" height="36" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">

        <path d="M12 2l2.39 4.84 5.34.78-3.86 3.77.91 5.32L12 14.77 6.22 16.71l.91-5.32L3.27 7.62l5.34-.78L12 2z" fill="url(#g)"/>

        <defs><linearGradient id="g" x1="0" y1="0" x2="24" y2="24"><stop stop-color="#00E0FF"/><stop offset="1" stop-color="#72FF9F"/></linearGradient></defs>

      </svg>

      <div>

        <h1>Rifa IGSA · 21 Autos</h1>

        <div class="badge">Seguro · Transparente · Divertido</div>

      </div>

    </header>


    <div class="grid" id="app">

      <section class="card">

        <div class="card-head">

          <strong>Configuración y sorteo</strong>

          <div class="controls">

            <button id="btnFullscreen" class="secondary" title="Pantalla completa">Pantalla completa</button>

            <button id="btnExport" class="secondary" title="Exportar resultados CSV">Exportar resultados</button>

            <button id="btnReset" class="danger" title="Reiniciar rifa">Reiniciar</button>

          </div>

        </div>

        <div class="card-body">

          <div class="row" style="margin-bottom:12px;">

            <div class="field">

              <label>Total de participantes</label>

              <input type="number" id="totalParticipants" value="900" min="1" />

            </div>

            <div class="field" style="min-width:280px;">

              <label>Participantes válidos (lista y rangos)</label>

              <input type="text" id="validList" placeholder="Ej. 1-900, 12, 34, 100-120" />

              <div class="muted">Si lo dejas vacío, se consideran válidos 1..N. Rangos separados por comas.</div>

            </div>

            <div class="field">

              <label>Lista de autos (CSV) – opcional</label>

              <input type="file" id="carsCsv" accept=".csv" />

              <div class="muted">Ya incluye tus 21 autos precargados. Puedes reemplazarlos cargando un CSV. Formato: <code>id,descripcion,modelo,serie,kilometraje</code></div>

            </div>

            <div class="field">

              <label>Semilla opcional (para reproducir)</label>

              <input type="text" id="seedInput" placeholder="Ej. IGSA-2025" />

            </div>

          </div>


          <div class="stage">

            <div class="roller">

              <h3>Ganador (1…N)</h3>

              <div class="window big" id="winnerWindow">—</div>

            </div>

            <div class="roller">

              <h3>Auto asignado</h3>

              <div class="window big" id="carWindow">—</div>

            </div>

          </div>


          <div class="controls" style="margin-top:14px;">

            <button id="btnDraw" class="gold">🎉 ¡Sortear!</button>

            <button id="btnUndo" class="secondary">↩️ Deshacer último</button>

          </div>


          <div id="messageBar"></div>


          <div class="result" id="resultDetail" style="display:none;">

            <div>

              <div class="muted">Ganador</div>

              <div id="resultWinner" style="font-weight:800; font-size:20px;">—</div>

            </div>

            <div>

              <div class="muted">Auto</div>

              <div id="resultCar" style="font-weight:800; font-size:20px;">—</div>

            </div>

          </div>

        </div>

      </section>


      <aside class="card">

        <div class="card-head"><strong>Estado de la rifa</strong>

          <span class="pill" id="statusCounters">Participantes únicos: 0 · Autos restantes: 0</span>

        </div>

        <div class="card-body" style="max-height:58vh;overflow:auto;">

          <table>

            <thead>

              <tr>

                <th>#</th>

                <th>Ganador</th>

                <th>Auto</th>

                <th>Modelo</th>

                <th>Serie</th>

                <th>Km</th>

                <th>Hora</th>

              </tr>

            </thead>

            <tbody id="resultsBody"></tbody>

          </table>

        </div>

      </aside>

    </div>


    <div id="confetti" class="confetti hidden"></div>


    <div class="footer">Hecho para IGSA · Uso interno de evento · Precargado con tu listado · Imparcialidad: aleatoriedad segura (<code>crypto.getRandomValues</code>) y validación manual (sin perder premios por intentos inválidos).</div>

  </div>


  <script>

    const EMBEDDED_CARS = [{"id": "", "descripcion": "NP300 FRONT", "modelo": "2019", "serie": "", "kilometraje": "171,603"}, {"id": "", "descripcion": "NP300 FR", "modelo": "2019", "serie": "", "kilometraje": ""}, {"id": "", "descripcion": "NP300 FRONTIER", "modelo": "2019", "serie": "", "kilometraje": "126,474"}, {"id": "", "descripcion": "MARCH TM", "modelo": "2019", "serie": "", "kilometraje": "126,831"}, {"id": "", "descripcion": "MARCH TM AC", "modelo": "2019", "serie": "", "kilometraje": "111,425"}, {"id": "", "descripcion": "MARCH TM", "modelo": "2020", "serie": "", "kilometraje": "77,768"}, {"id": "", "descripcion": "MARCH COUPE TM", "modelo": "2021", "serie": "", "kilometraje": "105,106"}, {"id": "", "descripcion": "MARCH TM", "modelo": "2021", "serie": "", "kilometraje": "128,190"}, {"id": "", "descripcion": "MARCH TM", "modelo": "2022", "serie": "", "kilometraje": "85,474"}, {"id": "", "descripcion": "TAOS HIGHLINE", "modelo": "2023", "serie": "", "kilometraje": "8,933"}, {"id": "", "descripcion": "JETTA DSG", "modelo": "2022", "serie": "", "kilometraje": "25,874"}, {"id": "", "descripcion": "VIRTUS COMFORTLINE", "modelo": "2023", "serie": "", "kilometraje": "39,419"}, {"id": "", "descripcion": "TIGUAN COMFORTLINE", "modelo": "2022", "serie": "", "kilometraje": "60,665"}, {"id": "", "descripcion": "JETTA TIPTRONIC", "modelo": "2019", "serie": "", "kilometraje": "84,626"}, {"id": "", "descripcion": "ATLAS TRENDLINE", "modelo": "2021", "serie": "", "kilometraje": "59,662"}, {"id": "", "descripcion": "JETTA TIPTRONIC", "modelo": "2021", "serie": "", "kilometraje": "41,760"}, {"id": "", "descripcion": "KWID INTENSE TM", "modelo": "2022", "serie": "", "kilometraje": "66,225"}, {"id": "", "descripcion": "TRACKER TURBO AT", "modelo": "2022", "serie": "", "kilometraje": "69,997"}, {"id": "", "descripcion": "AVEOLINE LT", "modelo": "2019", "serie": "", "kilometraje": "56,727"}, {"id": "", "descripcion": "ONIX TURBO", "modelo": "2021", "serie": "", "kilometraje": "78,758"}, {"id": "", "descripcion": "TRAILBLAZER", "modelo": "2021", "serie": "", "kilometraje": "52,918"}];

  </script>


  <script>

    const $ = (sel)=>document.querySelector(sel);

    const $$ = (sel)=>document.querySelectorAll(sel);

    function formatTime(d=new Date()){return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'})}

    function csvEscape(v){if(v==null)return'';const s=String(v);return /[",\n]/.test(s)?'"'+s.replace(/"/g,'""')+'"':s}

    function showMsg(txt,type='ok'){const bar=$('#messageBar');bar.className='';bar.classList.add(type==='ok'?'ok':'err');bar.textContent=txt;bar.style.display='block';clearTimeout(showMsg._t);showMsg._t=setTimeout(()=>{bar.style.display='none'},4200)}

    function xmur3(str){let h=1779033703^str.length;for(let i=0;i<str.length;i++){h=Math.imul(h^str.charCodeAt(i),3432918353);h=(h<<13)|(h>>>19)}return function(){h=Math.imul(h^(h>>>16),2246822507);h=Math.imul(h^(h>>>13),3266489909);h^=h>>>16;return h>>>0}}

    function mulberry32(a){return function(){var t=a+=0x6D2B79F5;t=Math.imul(t^t>>>15,t|1);t^=t+Math.imul(t^t>>>7,t|61);return((t^t>>>14)>>>0)/4294967296}}

    function secureRandomInt(min,max){const range=max-min+1;if(window.crypto&&crypto.getRandomValues&&range<=2**32){const maxUnbiased=Math.floor(0xFFFFFFFF/range)*range;let x=new Uint32Array(1);do{crypto.getRandomValues(x)}while(x[0]>=maxUnbiased);return min+(x[0]%range)}else{return Math.floor(Math.random()*range)+min}}

    function seededInt(rand,min,max){return Math.floor(rand()*(max-min+1))+min}


    let participantsTotal=900;

    let participantsDrawn=new Set();

    let validSet=null;

    let cars=[]; let carsRemaining=[]; let results=[]; let drawCount=0; let seeded=false; let seededRand=null;


    function updateCounters(){$('#statusCounters').textContent=`Participantes únicos: ${participantsDrawn.size} · Autos restantes: ${carsRemaining.length}`}

    function renderResults(){const tbody=$('#resultsBody');tbody.innerHTML=results.map((r,idx)=>{const a=r.auto||{};return `<tr><td>${idx+1}</td><td>#${r.ganador}</td><td>${csvEscape(a.descripcion||'-')}</td><td>${csvEscape(a.modelo||'-')}</td><td><span class="pill">${csvEscape(a.serie||'-')}</span></td><td>${csvEscape(a.kilometraje||'-')}</td><td>${r.time}</td></tr>`}).join('')}

    function makeConfetti(){const layer=$('#confetti');layer.innerHTML='';const n=120;const colors=['#00e0ff','#72ff9f','#ffd166','#ffffff'];for(let i=0;i<n;i++){const el=document.createElement('i');el.style.left=Math.random()*100+'vw';el.style.animationDuration=2.5+Math.random()*2+'s';el.style.background=colors[i%colors.length];el.style.transform=`translateY(-20px) rotate(${Math.random()*360}deg)`;layer.appendChild(el)}layer.classList.remove('hidden');setTimeout(()=>layer.classList.add('hidden'),3600)}

    function spinWindow(el,finalText,duration=2200){const start=performance.now();const tick=(now)=>{const t=now-start;if(t<duration){el.textContent=seeded?seededInt(seededRand,1,999):secureRandomInt(1,999);requestAnimationFrame(tick)}else{el.textContent=finalText;el.classList.add('blink');setTimeout(()=>el.classList.remove('blink'),2000)}};requestAnimationFrame(tick)}


    function parseCSV(text){const lines=text.split(/\r?\n/).filter(Boolean);const out=[];const headers=lines[0].split(',').map(h=>h.trim().toLowerCase());const idx={id:headers.indexOf('id'),descripcion:headers.indexOf('descripcion'),modelo:headers.indexOf('modelo'),serie:headers.indexOf('serie'),kilometraje:headers.indexOf('kilometraje')};for(let i=1;i<lines.length;i++){const row=csvSmartSplit(lines[i]);if(!row.length)continue;out.push({id:row[idx.id]||String(i),descripcion:row[idx.descripcion]||'',modelo:row[idx.modelo]||'',serie:row[idx.serie]||'',kilometraje:row[idx.kilometraje]||''})}return out}

    function csvSmartSplit(line){const res=[];let cur='';let inQ=false;for(let i=0;i<line.length;i++){const ch=line[i];if(ch==='"'){if(inQ&&line[i+1]==='"'){cur+='"';i++}else inQ=!inQ}else if(ch===','&&!inQ){res.push(cur);cur=''}else cur+=ch}res.push(cur);return res.map(s=>s.trim())}


    function exportCSV(){const header=['orden','ganador','id_auto','descripcion','modelo','serie','kilometraje','hora'];const lines=[header.join(',')];results.forEach((r,idx)=>{const a=r.auto||{};lines.push([idx+1,r.ganador,csvEscape(a.id),csvEscape(a.descripcion),csvEscape(a.modelo),csvEscape(a.serie),csvEscape(a.kilometraje),r.time].join(','))});const blob=new Blob([lines.join('\n')],{type:'text/csv;charset=utf-8;'});const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download='resultados_rifa_IGSA.csv';a.click();URL.revokeObjectURL(url)}

    function setSeedFromInput(){const seedStr=($('#seedInput').value||'').trim();if(seedStr){const seedFn=xmur3(seedStr);const s1=seedFn();seededRand=mulberry32(s1);seeded=true}else{seeded=false;seededRand=null}}

    function parseValidList(){const txt=($('#validList').value||'').trim();if(!txt){validSet=null;return}const parts=txt.split(',').map(s=>s.trim()).filter(Boolean);const set=new Set();for(const p of parts){const dash=p.indexOf('-');if(dash>-1){let a=parseInt(p.slice(0,dash),10);let b=parseInt(p.slice(dash+1),10);if(Number.isFinite(a)&&Number.isFinite(b)){if(a>b){const t=a;a=b;b=t}for(let n=a;n<=b;n++)set.add(n)}}else{const n=parseInt(p,10);if(Number.isFinite(n))set.add(n)}}validSet=set.size?set:null}

    function isWinnerValid(n){if(n<1||n>participantsTotal)return false;if(participantsDrawn.has(n))return false;if(validSet&&!validSet.has(n))return false;return true}


    function loadEmbeddedCars(){cars=(Array.isArray(EMBEDDED_CARS)?EMBEDDED_CARS:[]).map((x,i)=>({id:String(x.id||''),descripcion:String(x.descripcion||''),modelo:String(x.modelo||''),serie:String(x.serie||''),kilometraje:String(x.kilometraje||''),_idx:i}));carsRemaining=cars.map((_,i)=>i)}


    function drawOnce(){if(carsRemaining.length===0){alert('Todos los autos ya fueron asignados.');return}setSeedFromInput();parseValidList();let winner;const limit=participantsTotal*3;let tries=0;while(true){winner=seeded?seededInt(seededRand,1,participantsTotal):secureRandomInt(1,participantsTotal);if(!participantsDrawn.has(winner))break;tries++;if(tries>limit){alert('No quedan participantes disponibles.');return}}spinWindow($('#winnerWindow'),`#${winner}`);setTimeout(()=>{if(!isWinnerValid(winner)){$('#winnerWindow').style.borderColor='rgba(255,107,107,.8)';setTimeout(()=>$('#winnerWindow').style.borderColor='rgba(255,255,255,.1)',1200);showMsg(`Número #${winner} inválido o no permitido. El premio NO se asignó. Vuelve a sortear.`,'err');return}const idxInRemain=seeded?seededInt(seededRand,0,carsRemaining.length-1):secureRandomInt(0,carsRemaining.length-1);const carIndex=carsRemaining[idxInRemain];const car=cars[carIndex];const displayCarLabel=(car.id&&String(car.id).trim())?`#${car.id}`:`Auto ${carIndex+1}`;spinWindow($('#carWindow'),displayCarLabel);setTimeout(()=>{carsRemaining.splice(idxInRemain,1);participantsDrawn.add(winner);const rec={n:++drawCount,ganador:winner,auto:car,time:formatTime()};results.push(rec);$('#resultWinner').textContent=`Participante #${winner}`;$('#resultCar').textContent=`${car.descripcion} · Modelo ${car.modelo} · Serie ${car.serie}`;$('#resultDetail').style.display='grid';renderResults();updateCounters();makeConfetti();showMsg(`¡Ganador #${winner}! Premio asignado: ${car.descripcion}`,'ok')},2300)},2300)}


    function undoLast(){if(!results.length)return;const last=results.pop();participantsDrawn.delete(last.ganador);const carIdx=cars.findIndex(a=>String(a.id)===String(last.auto.id));if(carIdx>=0)carsRemaining.push(carIdx);drawCount--;if(drawCount<0)drawCount=0;renderResults();updateCounters();$('#winnerWindow').textContent='—';$('#carWindow').textContent='—';$('#resultDetail').style.display=results.length?'grid':'none'}

    function resetAll(){if(!confirm('¿Reiniciar la rifa? Esto borrará los resultados actuales.'))return;participantsTotal=parseInt($('#totalParticipants').value||'900',10);participantsDrawn=new Set();results=[];drawCount=0;setSeedFromInput();parseValidList();if(!cars.length)loadEmbeddedCars();carsRemaining=cars.map((_,i)=>i);renderResults();updateCounters();$('#winnerWindow').textContent='—';$('#carWindow').textContent='—';$('#resultDetail').style.display='none';showMsg('Rifa reiniciada.','ok')}

    function goFullscreen(){const el=document.documentElement;if(!document.fullscreenElement){el.requestFullscreen?.()}else{document.exitFullscreen?.()}}


    $('#btnDraw').addEventListener('click',drawOnce);

    $('#btnUndo').addEventListener('click',undoLast);

    $('#btnReset').addEventListener('click',resetAll);

    $('#btnFullscreen').addEventListener('click',goFullscreen);

    $('#btnExport').addEventListener('click',exportCSV);

    $('#carsCsv').addEventListener('change',e=>{const file=e.target.files?.[0];if(!file)return;const fr=new FileReader();fr.onload=()=>{try{const text=String(fr.result);const parsed=parseCSV(text);if(!parsed.length)throw new Error('CSV vacío o sin encabezados válidos');cars=parsed;carsRemaining=cars.map((_,i)=>i);participantsDrawn=new Set();results=[];drawCount=0;renderResults();updateCounters();alert(`Autos cargados: ${cars.length}`)}catch(err){alert('Error al leer CSV: '+err.message)}};fr.readAsText(file)});


    loadEmbeddedCars();

    parseValidList();

    updateCounters();

  </script>

</body>

</html>