// vita-telemed.jsx — Telemedicine (patient POV): orchestrator + waiting room + camera hook // HealthSings modeled as the underlying RTC/API layer. Vita is the patient's copilot. // ── Camera hook (real getUserMedia, graceful fallback) ────────── function useCamera(active) { const videoRef = React.useRef(null); const streamRef = React.useRef(null); const [status, setStatus] = React.useState('idle'); // idle | granted | denied | unavailable const [camOn, setCamOn] = React.useState(true); const [micOn, setMicOn] = React.useState(true); React.useEffect(() => { let cancelled = false; if (!active) return; async function start() { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { setStatus('unavailable'); return; } try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } }, audio: true }); if (cancelled) { stream.getTracks().forEach(t => t.stop()); return; } streamRef.current = stream; setStatus('granted'); if (videoRef.current) { videoRef.current.srcObject = stream; videoRef.current.play().catch(() => {}); } } catch (e) { setStatus('denied'); } } start(); return () => { cancelled = true; if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; } }; }, [active]); const toggleCam = () => { const s = streamRef.current; if (!s) { setCamOn(c => !c); return; } s.getVideoTracks().forEach(t => { t.enabled = !t.enabled; }); setCamOn(s.getVideoTracks()[0] ? s.getVideoTracks()[0].enabled : false); }; const toggleMic = () => { const s = streamRef.current; if (!s) { setMicOn(m => !m); return; } s.getAudioTracks().forEach(t => { t.enabled = !t.enabled; }); setMicOn(s.getAudioTracks()[0] ? s.getAudioTracks()[0].enabled : false); }; return { videoRef, status, camOn, micOn, toggleCam, toggleMic }; } // ── HealthSings connection chip (technical layer) ─────────────── function HealthSingsChip({ t, state = 'secure', compact }) { const map = { connecting: { label: 'Conectando · HealthSings', color: '#E6A93C', bars: 1 }, secure: { label: 'Sala segura · HealthSings RTC', color: '#73C58E', bars: 3 }, e2e: { label: 'Cifrado E2E · HealthSings', color: '#73C58E', bars: 3 }, }; const s = map[state] || map.secure; return (
{[5, 8, 11].map((h, i) => )} {s.label}
); } // ── Waiting room ──────────────────────────────────────────────── function WaitingRoom({ t, persona, profile, prep, prepLoading, cam, cita, onEnter, onCancel }) { const accent = persona.accent; const [phase, setPhase] = React.useState('prep'); // prep -> ready const steps = ['Estableciendo sala segura con HealthSings', 'Enviando tu resumen clínico al médico', 'Preparando tu acompañamiento con Vita']; const [stepN, setStepN] = React.useState(0); React.useEffect(() => { const iv = setInterval(() => setStepN(x => { if (x >= steps.length) { clearInterval(iv); return x; } return x + 1; }), 850); const tm = setTimeout(() => setPhase('ready'), 3100); return () => { clearInterval(iv); clearTimeout(tm); }; }, []); return (
{/* camera preview as background */}
); } function PreToggle({ on, onClick, icon, off, label }) { return ( ); } Object.assign(window, { useCamera, HealthSingsChip, WaitingRoom, PreToggle });