// vita-telemed2.jsx — Live call (patient POV) + Vita copilot + AI-generated closing note // The scripted consult — drives live captions + Vita's copilot. (Live insights scripted; closing note = real AI.) function buildConsultScript(profile) { const n = (profile.nombre || 'Daniela').split(' ')[0]; const med = (Array.isArray(profile.medicamentos) ? profile.medicamentos[0] : profile.medicamentos) || 'Losartán 50mg'; return [ { t: 0, speaker: 'doctor', text: `Hola ${n}, qué gusto verte. ¿Cómo te has sentido estas semanas?` }, { t: 3.4, vita: { tipo: 'prompt', text: 'Cuéntale del dolor de cabeza en las mañanas 💛' } }, { t: 5.2, speaker: 'paciente', text: 'Bien doctora, aunque he amanecido con dolor de cabeza varios días.' }, { t: 8.6, vita: { tipo: 'data', text: 'Compartí con la Dra.: PA promedio 7 días — 138/88 mmHg' } }, { t: 10.4, speaker: 'doctor', text: '¿Has tomado el ' + med + ' todos los días?' }, { t: 13, vita: { tipo: 'data', text: 'Adherencia confirmada: 12 días consecutivos ✓' } }, { t: 14.6, speaker: 'paciente', text: 'Sí, sin falta, todas las mañanas.' }, { t: 17.4, vita: { tipo: 'alert', text: 'Bandera: sueño ~5 h/noche puede elevar la presión y dar cefalea' } }, { t: 20, speaker: 'doctor', text: 'Tu presión está casi en meta. La cefalea puede venir del poco sueño. Vamos a cuidarlo.' }, { t: 24, vita: { tipo: 'prompt', text: 'Pregúntale si sigues con la misma dosis' } }, { t: 26, speaker: 'paciente', text: '¿Sigo con la misma dosis entonces?' }, { t: 28.6, speaker: 'doctor', text: 'Sí, mantenemos la dosis. Pediré un perfil lipídico de control y te veo en tres meses.' }, { t: 32.6, vita: { tipo: 'order', text: 'Orden creada: Perfil lipídico (control 1 mes)' } }, { t: 34.4, vita: { tipo: 'order', text: 'Control agendado: Cardiología en 3 meses' } }, { t: 36.2, speaker: 'doctor', text: 'Cuídate mucho, ' + n + '. Vita te deja todo anotado.' }, { t: 39, end: true }, ]; } function scriptToTranscript(script) { return script.filter(s => s.speaker).map(s => (s.speaker === 'doctor' ? 'Dra. Gómez' : 'Paciente') + ': ' + s.text).join('\n'); } const VITA_EVENT = { prompt: { label: 'Para ti', color: '#3DD9D6', icon: 'heart' }, data: { label: 'Vita → Dra.', color: '#5BC0EB', icon: 'pulse' }, alert: { label: 'Atención', color: '#E6A93C', icon: 'alert' }, order: { label: 'Orden', color: '#73C58E', icon: 'check' }, }; // ── Live call ─────────────────────────────────────────────────── function CallScreen({ t, persona, profile, cam, onEnd }) { const accent = persona.accent; const script = React.useMemo(() => buildConsultScript(profile), [profile]); const [elapsed, setElapsed] = React.useState(0); const [caption, setCaption] = React.useState(null); const [feed, setFeed] = React.useState([]); // vita events const [docSpeaking, setDocSpeaking] = React.useState(true); const feedRef = React.useRef(null); const firedRef = React.useRef(new Set()); const startRef = React.useRef(Date.now()); React.useEffect(() => { const iv = setInterval(() => { const sec = (Date.now() - startRef.current) / 1000; setElapsed(sec); script.forEach((ev, i) => { if (ev.t <= sec && !firedRef.current.has(i)) { firedRef.current.add(i); if (ev.end) { setTimeout(() => onEnd(scriptToTranscript(script)), 600); return; } if (ev.speaker) { setCaption({ speaker: ev.speaker, text: ev.text }); setDocSpeaking(ev.speaker === 'doctor'); } if (ev.vita) setFeed(f => [...f, { ...ev.vita, id: i }]); } }); }, 200); return () => clearInterval(iv); }, [script]); React.useEffect(() => { if (feedRef.current) feedRef.current.scrollTop = feedRef.current.scrollHeight; }, [feed]); const mm = String(Math.floor(elapsed / 60)).padStart(2, '0'); const ss = String(Math.floor(elapsed % 60)).padStart(2, '0'); return (
{/* doctor "video" presence */}
{docSpeaking && }
LG
Dra. Liliana Gómez
Cardiología · Hablando contigo
{/* top bar */}
{mm}:{ss}
{/* patient self-view PiP */}
{/* spacer */}
{/* live caption */} {caption && (
{caption.speaker === 'doctor' ? 'Dra. Gómez' : 'Tú'}
{caption.text}
)} {/* Vita copilot panel */}
Vita te acompaña en vivo · sin que escribas nada
{feed.length === 0 &&
Estoy escuchando. Te voy ayudando con lo importante…
} {feed.map(ev => { const m = VITA_EVENT[ev.tipo] || VITA_EVENT.data; return (
{Icon[m.icon]({ s: 13, c: m.color })}
{m.label}
{ev.text}
); })}
{/* controls */}
{}} icon="volume" /> {}} icon="mic" hidden />
); } function CallBtn({ onClick, off, icon, hidden }) { if (hidden) return
; return ( ); } // ── Closing summary (Vita generated note + orders via real AI) ── function ClosingSummary({ t, persona, profile, nota, loading, onDone }) { const accent = persona.accent; return (
{Icon.check({ s: 14, c: t.green, w: 3 })} Consulta terminada

Yo me encargo del resto

No tuviste que escribir nada. Tomé nota de todo y dejé lo importante listo en tu historia.

{loading || !nota ? (
Vita está escribiendo tu nota clínica…
) : (
{/* resumen */}
Resumen de tu consulta

{nota.resumen}

{/* indicaciones */} {nota.indicaciones && nota.indicaciones.length > 0 && (
Lo que te indicó la doctora
{nota.indicaciones.map((ind, i) => (
{Icon.check({ s: 13, c: accent, w: 3 })} {ind}
))}
)} {/* ordenes → seguimiento */} {nota.ordenes && nota.ordenes.length > 0 && (
{Icon.sparkle({ s: 16, c: accent })}Vita ya gestionó por ti
{nota.ordenes.map((o, i) => (
{Icon[o.tipo === 'laboratorio' ? 'lab' : o.tipo === 'cita' ? 'calendar' : o.tipo === 'medicamento' ? 'pill' : 'scan']({ s: 18, c: accent })}
{o.titulo}
{o.detalle}
{Icon.check({ s: 16, c: t.green, w: 3 })}
))}
)}
)}
); } // ── Orchestrator: waiting → call → closing ────────────────────── function TelemedExperience({ t, persona, profile, asis, cita, onExit, onAddOrders }) { const [phase, setPhase] = React.useState('waiting'); // waiting | call | closing const [prep, setPrep] = React.useState(null); const [prepLoading, setPrepLoading] = React.useState(true); const [nota, setNota] = React.useState(null); const [notaLoading, setNotaLoading] = React.useState(false); const cam = useCamera(phase === 'waiting' || phase === 'call'); const ordersAddedRef = React.useRef(false); React.useEffect(() => { let alive = true; prepConsulta(profile, asis, cita && cita.motivo).then(p => { if (alive) { setPrep(p); setPrepLoading(false); } }); return () => { alive = false; }; }, []); const endCall = (transcript) => { setPhase('closing'); setNotaLoading(true); generateConsultaNota(profile, asis, transcript, cita && cita.motivo).then(nt => { setNota(nt); setNotaLoading(false); // push orders to seguimiento once if (!ordersAddedRef.current && onAddOrders) { ordersAddedRef.current = true; const items = (nt.ordenes || []).map((o, i) => ({ id: 'tm-' + Date.now() + '-' + i, tipo: o.tipo === 'laboratorio' ? 'laboratorio' : o.tipo === 'cita' ? 'cita' : o.tipo === 'medicamento' ? 'medicamento' : 'examen', titulo: o.titulo, detalle: (o.detalle || '') + ' · de tu teleconsulta', estado: 'agendado', })); onAddOrders(items); } }); }; if (phase === 'waiting') return setPhase('call')} onCancel={() => onExit()} />; if (phase === 'call') return ; return onExit(nota)} />; } Object.assign(window, { buildConsultScript, scriptToTranscript, CallScreen, ClosingSummary, TelemedExperience });