const {useEffect,useState,useRef}=React; const createRoot=ReactDOM.createRoot; const mk=(name)=>(props)=>React.createElement('i',{...props,'data-lucide':name}); const Users=mk('users'),Monitor=mk('monitor'),Ticket=mk('ticket'),Clock=mk('clock'),LogOut=mk('log-out'),Plus=mk('plus'),CheckCircle2=mk('check-circle-2'),BarChart3=mk('bar-chart-3'),UserRound=mk('user-round'),RefreshCw=mk('refresh-cw'); const API='./api/index.php'; async function api(action,data={}){let r=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action,...data})});let j=await r.json();if(!r.ok||j.error)throw Error(j.error||'Error');return j} function Logo(){return UCuenca Jurisprudencia y Ciencias Políticas y Sociales} function Login({setUser}){let[u,su]=useState('admin'),[p,sp]=useState('Admin123!'),[e,se]=useState('');return
Consultorio JurídicoSistema de gestión de turnos
{x.preventDefault();try{setUser((await api('login',{username:u,password:p})).user)}catch(z){se(z.message)}}}>

Bienvenido

Ingresa con tus credenciales institucionales.

{e&&
{e}
}Demo inicial: admin / Admin123!
} function App(){let tv=new URLSearchParams(location.search).get('tv')==='1';let[user,setUser]=useState(null),[view,setView]=useState('dashboard'),[data,setData]=useState({});let load=async()=>{try{setData(await api('state'))}catch{}};useEffect(()=>{if(user||tv)load();let i=setInterval(()=>{if(user||tv)load()},tv?1000:3000);return()=>clearInterval(i)},[user,tv]);if(tv)return ;if(!user)return ;let nav=user.role==='admin'?['dashboard','recepcion','atencion','pantalla','usuarios','reportes']:user.role==='reception'?['recepcion','dashboard','pantalla']:['atencion','dashboard'];return

{({dashboard:'Panel general',recepcion:'Recepción',atencion:'Atención en cubículo',usuarios:'Gestión de usuarios',pantalla:'Pantalla pública de turnos',reportes:'Reportes de atención'})[view]}

{new Date().toLocaleDateString('es-EC',{weekday:'long',year:'numeric',month:'long',day:'numeric'})}

{view==='dashboard'&&} {view==='recepcion'&&} {view==='atencion'&&} {view==='pantalla'&&} {view==='usuarios'&&} {view==='reportes'&&}
} function Dashboard({d}){let active=(d.booths||[]).filter(x=>x.status==='busy').length;return <>
} n={(d.queue||[]).length} t="En espera"/>} n={active} t="Cubículos ocupados"/>} n={(d.today||[]).length} t="Atenciones hoy"/>} n={(d.students||[]).filter(x=>x.active_session).length} t="Estudiantes activos"/>

Estado de cubículos

{(d.booths||[]).map(b=>
{b.name}{b.status==='busy'?'Atendiendo':b.status==='disabled'?'Fuera de servicio':'Disponible'}{b.student&&{b.student}}
)}

Cola actual

} function Queue({q=[]}){return
TicketPersonaHoraEstado
{q.length?q.map(x=>
{x.code}{x.name}{x.created_time}En espera
):
No hay personas esperando.
}
} function Reception({d,load}){let[n,sn]=useState(''),[id,si]=useState(''),[phone,sp]=useState(''),[msg,sm]=useState('');async function add(e){e.preventDefault();try{let r=await api('ticket_create',{name:n,identification:id,phone});sm('Ticket generado: '+r.code);sn('');si('');sp('');load()}catch(e){sm(e.message)}}return

Generar nuevo turno

{msg&&
{msg}
}

Personas en espera

} function Attention({d,user,load}){let session=(d.sessions||[]).find(s=>+s.user_id===+user.id&&s.active),[booth,sb]=useState(''),[msg,sm]=useState(''),[details,sd]=useState(''),[obs,so]=useState('');let current=session&&(d.current||[]).find(x=>+x.session_id===+session.id);async function go(a,p={}){try{let r=await api(a,p);sm(r.message||'Actualizado');if(a==='finish'){sd('');so('')}load()}catch(e){sm(e.message)}}if(!session)return

Iniciar jornada

Selecciona el número del cubículo donde te sentaste hoy. Puedes usar cualquiera que esté disponible.

{msg&&
{msg}
}
;return
Jornada activa · {session.status==='unavailable'?'NO DISPONIBLE':current?'EN ATENCIÓN':'DISPONIBLE'}{session.booth_name}Inicio {session.start_time}
{current?
ATENDIENDO

{current.code}

{current.name}

Inicio: {current.started_time}

Al finalizar, selecciona cómo quedas:

:

{session.status==='unavailable'?'Estás marcado como no disponible':'Cubículo listo para atender'}

{(d.queue||[]).length} persona(s) esperando

{session.status!=='unavailable'&&}{session.status==='unavailable'?:}
}{msg&&
{msg}
}
} function PublicScreen({d,standalone=false}){let calls=d.calls||[],last=calls[0],prev=useRef(null);useEffect(()=>{if(!last||prev.current===last.started_at)return;let first=prev.current===null;prev.current=last.started_at;if(first)return;try{speechSynthesis.cancel();let u=new SpeechSynthesisUtterance(`Turno ${last.code.replace('-', ' ')}, pase al ${last.booth}`);u.lang='es-EC';u.rate=.88;u.volume=1;speechSynthesis.speak(u)}catch(e){}},[last&&last.started_at]);return
CONSULTORIO JURÍDICOTurnos de atención
{last?<>

POR FAVOR DIRÍJASE A

TURNO{last.code}
PASE AL{last.booth}
:
Esperando el próximo turno
}

Últimos llamados

{calls.slice(1,7).map(x=>
{x.code}{x.booth}
)}
{standalone&&
Por favor mantenga visible su turno y acérquese al cubículo cuando sea llamado.
}
} function UsersPage({d,load}){let[name,sn]=useState(''),[username,su]=useState(''),[password,sp]=useState(''),[role,sr]=useState('student');async function add(e){e.preventDefault();await api('user_create',{name,username,password,role});sn('');su('');sp('');load()}return

Crear usuario

Usuarios registrados

{(d.students||[]).map(x=>
{x.name}@{x.username} · {x.role}
)}
} function Reports({d}){let[tab,setTab]=useState('students'),[studentId,setStudentId]=useState(''),[studentSearch,setStudentSearch]=useState(''),[from,setFrom]=useState(''),[to,setTo]=useState(''),[f,sf]=useState('');let students=(d.students||[]).filter(x=>x.role==='student');let filteredStudents=students.filter(x=>!studentSearch||x.name.toLowerCase().includes(studentSearch.toLowerCase())||x.username.toLowerCase().includes(studentSearch.toLowerCase()));let inRange=x=>(!from||x.date>=from)&&(!to||x.date<=to);let base=(d.reports||[]).filter(inRange);let summary=filteredStudents.map(st=>{let r=base.filter(x=>x.student===st.name),j=(d.journeys||[]).filter(x=>+x.user_id===+st.id&&(!from||x.started_at.slice(0,10)>=from)&&(!to||x.started_at.slice(0,10)<=to));return {...st,attentions:r.length,minutes:r.reduce((a,x)=>a+(+x.minutes||0),0),journeys:j.length,last:r[0]?.date||'—'}});let selected=students.find(x=>String(x.id)===String(studentId));let rows=base.filter(x=>(!selected||x.student===selected.name)&&(!f||JSON.stringify(x).toLowerCase().includes(f.toLowerCase())));function dl(name,headers,arr){let esc=v=>'"'+String(v??'').replaceAll('"','""')+'"';let csv='\ufeff'+headers.join(',')+'\n'+arr.map(r=>r.map(esc).join(',')).join('\n');let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv;charset=utf-8'}));a.download=name;a.click()}return <>

Centro de reportes

{tab==='individual'&&}
{tab==='students'?

Reporte general de estudiantes

{summary.length} estudiantes · {summary.reduce((a,x)=>a+x.attentions,0)} atenciones en el período

EstudianteJornadasAtencionesTiempo atención
{summary.map(x=>
{x.name}@{x.username} · Última: {x.last}{x.journeys}{x.attentions}{x.minutes.toFixed(1)} min
)}
:
{!selected?
Busca y selecciona un estudiante para generar su reporte.
:<>

{selected.name}

{rows.length} atenciones · {rows.reduce((a,x)=>a+(+x.minutes||0),0).toFixed(1)} minutos de atención

sf(e.target.value)}/>
TicketPersonaCubículoFecha / horaDuraciónDetalle / observación
{rows.map(x=>
{x.code}{x.client}{x.identification||''}{x.booth}{x.date}{x.start} – {x.end||'en curso'}{x.minutes?x.minutes+' min':'—'}{x.details||'—'}{x.observation||''}
)}
}
}} function Card({icon,n,t}){return
{icon}
{n}{t}
}createRoot(document.getElementById('root')).render(); setInterval(()=>window.lucide&&lucide.createIcons(),500);