(function(){ function esc(s){return String(s||'').replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c]));} function moneyText(s){return s || '$ 0,00';} function buildUrl(base, params){ const u = new URL(base, window.location.origin); Object.entries(params||{}).forEach(([k,v])=>{ if(v!==undefined && v!==null && String(v)!=='') u.searchParams.set(k,v); }); return u.toString(); } function tomorrowISO(){ const d = new Date(Date.now()+86400000); return d.toISOString().slice(0,10); } function byId(arr,id){ return (arr||[]).find(x=>String(x.id)===String(id)); } const DEFAULT_STEPS = [ ['mode','Inicio'], ['location','Ubicación'], ['main','Institución / especialidad'], ['place','Sede / profesional'], ['service','Servicio'], ['schedule','Fecha y hora'], ['patient','Tus datos'], ['confirm','Confirmación'] ]; function createBookingWizard(root, cfg, opts){ opts = opts || {}; const content = root.querySelector('#bwContent') || root; const title = root.querySelector('#bwTitle'); const subtitle = root.querySelector('#bwSubtitle'); const badge = root.querySelector('#bwStepBadge'); const prev = root.querySelector('#bwPrev'); const next = root.querySelector('#bwNext'); const fill = root.querySelector('#bwProgressFill'); const label = root.querySelector('#bwProgressLabel'); const stepList = root.querySelector('#bwStepList'); const summary = root.querySelector('#bwSummary'); const state = { step: 0, mode: opts.defaultMode || '', province:'', city:'', institution_id: cfg.initialInstitutionId || '', branch_id:'', service_id: cfg.initialServiceId || '', professional_id:'', booking_type:'first_available', date: tomorrowISO(), time:'', client_name: cfg.linkedPatient?.name || '', client_email: cfg.linkedPatient?.email || '', client_phone: cfg.linkedPatient?.phone || '', notes:'', options:{}, slots:[], source: opts.source || 'website' }; if(opts.prefill) Object.assign(state, opts.prefill); async function loadOptions(extra){ const params = Object.assign({mode: state.mode, province: state.province, city: state.city, institution_id: state.institution_id, branch_id: state.branch_id, service_id: state.service_id}, extra||{}); const resp = await fetch(buildUrl(cfg.optionsUrl, params)); const data = await resp.json(); if(data.ok) state.options = data; return data; } async function loadSlots(){ if(!state.service_id || !state.date) return {ok:false, items:[]}; const params = {service_id:state.service_id, date:state.date, institution_id:state.institution_id, branch_id:state.branch_id, professional_id:state.professional_id, province:state.province, city:state.city, booking_type:state.booking_type}; const resp = await fetch(buildUrl(cfg.slotsUrl, params)); const data = await resp.json(); state.slots = data.items || []; return data; } function setChoice(key,val){ state[key]=val; state.time=''; if(key==='institution_id'){ state.branch_id=''; state.service_id=''; state.professional_id=''; } if(key==='branch_id'){ state.service_id=''; state.professional_id=''; } if(key==='service_id'){ state.professional_id=''; } render(); } function stepMeta(){ const m = { mode:['¿Cómo querés buscar tu turno?','Elegí si ya conocés la institución o si preferís que te mostremos opciones disponibles.'], location:['Elegí tu ubicación','Esto permite filtrar sedes, instituciones y profesionales por provincia y ciudad.'], main:[state.mode==='known'?'Elegí la institución':'Elegí especialidad o servicio', state.mode==='known'?'Buscá la clínica, sanatorio, consultorio o institución donde querés atenderte.':'Te mostramos servicios y especialidades disponibles en tu zona.'], place:[state.mode==='known'?'Elegí la sede':'Elegí profesional o institución sugerida', state.mode==='known'?'Si la institución tiene varias sedes, seleccioná la más conveniente.':'Podés elegir un profesional concreto o dejar que el sistema asigne el primero disponible.'], service:['Elegí el tipo de atención','Seleccioná el servicio, modalidad o tipo de consulta.'], schedule:['Elegí fecha y horario','Seleccioná un día y luego uno de los horarios disponibles.'], patient:['Completá tus datos','Usaremos esta información para confirmar y administrar tu turno.'], confirm:['Revisá y confirmá','Verificá los datos antes de finalizar la solicitud.'] }; return m[DEFAULT_STEPS[state.step][0]]; } function updateChrome(){ const pct = Math.round(((state.step+1)/DEFAULT_STEPS.length)*100); if(fill) fill.style.width = pct+'%'; if(label) label.textContent = `Paso ${state.step+1} de ${DEFAULT_STEPS.length}`; if(badge) badge.textContent = `Paso ${state.step+1}`; const [t,st] = stepMeta(); if(title) title.textContent=t; if(subtitle) subtitle.textContent=st; if(stepList){ stepList.innerHTML = DEFAULT_STEPS.map((s,i)=>`
  • ${i+1}${esc(s[1])}
  • `).join(''); } if(prev) prev.style.visibility = state.step===0?'hidden':'visible'; if(next) next.innerHTML = state.step===DEFAULT_STEPS.length-1 ? 'Confirmar turno ' : 'Siguiente '; renderSummary(); } function renderSummary(){ if(!summary) return; const o=state.options||{}; const inst=byId(o.institutions,state.institution_id); const br=byId(o.branches,state.branch_id); const svc=byId(o.services,state.service_id); const prof=byId(o.professionals,state.professional_id); const rows = [ ['Búsqueda', state.mode==='known'?'Conozco la institución': state.mode==='unknown'?'No conozco la institución':'Pendiente'], ['Provincia', state.province], ['Ciudad', state.city], ['Institución', inst?.name || ''], ['Sede', br?.label || ''], ['Servicio', svc? `${svc.name} · ${moneyText(svc.price)}`:''], ['Profesional', prof? `${prof.name} · ${prof.specialty}` : (state.booking_type==='first_available'?'Primero disponible':'')], ['Fecha', state.date], ['Horario', state.time] ]; summary.innerHTML = rows.map(([k,v])=>`
    ${esc(k)}${esc(v||'—')}
    `).join(''); } function cards(items, selected, click, empty){ if(!items || !items.length) return `
    ${esc(empty||'No hay opciones para la selección actual.')}
    `; return `
    ${items.map(it=>``).join('')}
    `; } async function render(){ updateChrome(); const key = DEFAULT_STEPS[state.step][0]; if(key!=='mode') await loadOptions(); if(key==='mode'){ content.innerHTML = `
    `; content.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{state.mode=b.dataset.mode; render();}); } else if(key==='location'){ const provinces = state.options.provinces || []; const cities = (state.options.cities_by_province||{})[state.province] || []; content.innerHTML = `
    Si no elegís ciudad, el sistema mostrará todas las opciones disponibles de la provincia.
    `; content.querySelector('#bwProvince').onchange=e=>{state.province=e.target.value; state.city=''; render();}; content.querySelector('#bwCity').onchange=e=>{state.city=e.target.value; renderSummary();}; } else if(key==='main'){ if(state.mode==='known'){ content.innerHTML = cards(state.options.institutions, state.institution_id, null, 'No encontramos instituciones activas para esa ubicación.'); content.querySelectorAll('[data-id]').forEach(b=>b.onclick=()=>setChoice('institution_id', b.dataset.id)); } else { content.innerHTML = cards(state.options.services, state.service_id, null, 'No hay servicios cargados para esa ubicación.'); content.querySelectorAll('[data-id]').forEach(b=>b.onclick=()=>setChoice('service_id', b.dataset.id)); } } else if(key==='place'){ if(state.mode==='known'){ content.innerHTML = cards(state.options.branches, state.branch_id, null, 'La institución no tiene sedes activas cargadas para esa ubicación. Podés continuar con sede pendiente.'); content.querySelectorAll('[data-id]').forEach(b=>b.onclick=()=>setChoice('branch_id', b.dataset.id)); } else { const profs = state.options.professionals || []; content.innerHTML = `
    Podés elegir profesional o dejar que el sistema use el primero disponible.
    ${cards(profs.map(p=>({id:p.id,name:p.name,description:[p.specialty,p.institution_name,p.location].filter(Boolean).join(' · ')})), state.professional_id, null, 'No hay profesionales cargados para ese servicio/ubicación.')}`; content.querySelector('#bwFirstAvailable').onclick=()=>{state.professional_id=''; state.booking_type='first_available'; render();}; content.querySelectorAll('[data-id]').forEach(b=>b.onclick=()=>{state.professional_id=b.dataset.id; state.booking_type='specific'; render();}); } } else if(key==='service'){ if(state.mode==='known'){ content.innerHTML = cards(state.options.services, state.service_id, null, 'No hay servicios activos para esta institución/sede.'); content.querySelectorAll('[data-id]').forEach(b=>b.onclick=()=>setChoice('service_id', b.dataset.id)); } else { const svc=byId(state.options.services,state.service_id); content.innerHTML = `

    Servicio seleccionado

    ${esc(svc?.name||'Pendiente')}

    ${esc((svc?.price||'') + (svc?.mode ? ' · '+svc.mode : ''))}

    `; } } else if(key==='schedule'){ content.innerHTML = `
    Elegí fecha y presioná “Buscar horarios”.
    `; content.querySelector('#bwDate').onchange=e=>{state.date=e.target.value; state.time='';}; content.querySelector('#bwBookingType').onchange=e=>{state.booking_type=e.target.value; if(e.target.value!=='specific') state.professional_id='';}; content.querySelector('#bwLoadSlots').onclick=async()=>{ const box=content.querySelector('#bwSlots'); box.innerHTML='Buscando horarios disponibles...'; const data=await loadSlots(); if(!data.ok||!state.slots.length){box.innerHTML='
    No hay horarios disponibles para esa fecha.
    '; return;} box.innerHTML=state.slots.map(item=>`
    ${esc(item.professional_name)} ${esc(item.specialty||'')}
    ${(item.slots||[]).map(slot=>``).join('')}
    `).join(''); box.querySelectorAll('.slot-btn').forEach(btn=>btn.onclick=()=>{box.querySelectorAll('.slot-btn').forEach(x=>x.classList.remove('active')); btn.classList.add('active'); state.professional_id=btn.dataset.prof; state.time=btn.dataset.time; state.booking_type='specific'; renderSummary();}); }; if(state.service_id) content.querySelector('#bwLoadSlots').click(); } else if(key==='patient'){ content.innerHTML = `
    `; ['Name','Email','Phone','Notes'].forEach(k=>{const el=content.querySelector('#bw'+k); if(el) el.oninput=()=>{state['client_'+k.toLowerCase().replace('name','name').replace('phone','phone').replace('email','email').replace('notes','notes')] = el.value; if(k==='Notes') state.notes=el.value;};}); content.querySelector('#bwName').oninput=e=>state.client_name=e.target.value; content.querySelector('#bwEmail').oninput=e=>state.client_email=e.target.value; content.querySelector('#bwPhone').oninput=e=>state.client_phone=e.target.value; content.querySelector('#bwNotes').oninput=e=>state.notes=e.target.value; } else if(key==='confirm'){ const o=state.options||{}; const svc=byId(o.services,state.service_id); const inst=byId(o.institutions,state.institution_id); const br=byId(o.branches,state.branch_id); const prof=byId(o.professionals,state.professional_id); content.innerHTML = `

    Revisá tu solicitud

    Servicio${esc(svc?.name||'—')}
    Institución${esc(inst?.name || prof?.institution_name || '—')}
    Sede${esc(br?.label||'—')}
    Profesional${esc(prof?.name || 'Primero disponible')}
    Fecha${esc(state.date||'—')}
    Horario${esc(state.time||'—')}
    Paciente${esc(state.client_name||'—')}
    Email${esc(state.client_email||'—')}
    `; } updateChrome(); } function canGoNext(){ const key=DEFAULT_STEPS[state.step][0]; if(key==='mode' && !state.mode) return 'Elegí cómo querés buscar el turno.'; if(key==='main' && state.mode==='known' && !state.institution_id) return 'Elegí la institución.'; if(key==='main' && state.mode==='unknown' && !state.service_id) return 'Elegí una especialidad o servicio.'; if(key==='service' && !state.service_id) return 'Elegí el servicio.'; if(key==='schedule' && (!state.date || !state.time)) return 'Elegí fecha y horario.'; if(key==='patient' && (!state.client_name || !state.client_email)) return 'Completá nombre y email.'; return ''; } async function submit(){ const payload = {source:state.source, mode:state.mode, province:state.province, city:state.city, institution_id:state.institution_id, branch_id:state.branch_id, service_id:state.service_id, professional_id:state.professional_id, booking_type:state.booking_type, date:state.date, time:state.time, client_name:state.client_name, client_email:state.client_email, client_phone:state.client_phone, notes:state.notes}; if(next) {next.disabled=true; next.innerHTML='Confirmando...';} try{ const resp=await fetch(cfg.createUrl,{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':cfg.csrfToken||''},body:JSON.stringify(payload)}); const data=await resp.json(); if(!data.ok) throw new Error(data.error||'No se pudo confirmar el turno.'); if(opts.onSuccess){ opts.onSuccess(data); } else { window.location.href = data.redirect_url || data.success_url; } }catch(err){ alert(err.message||'No se pudo confirmar el turno.'); } finally{ if(next){next.disabled=false; next.innerHTML='Confirmar turno '; } } } prev?.addEventListener('click',()=>{ if(state.step>0){state.step--; render();} }); next?.addEventListener('click',async()=>{ const msg=canGoNext(); if(msg){alert(msg); return;} if(state.step===DEFAULT_STEPS.length-1){await submit(); return;} state.step++; render(); }); loadOptions().then(render); return {state, render, submit}; } window.BookingWizard = { create: createBookingWizard }; document.addEventListener('DOMContentLoaded', function(){ const root = document.getElementById('bookingWizardRoot'); if(root && window.BOOKING_WIZARD_CONFIG){ createBookingWizard(root, window.BOOKING_WIZARD_CONFIG, {source:'website'}); } }); })();