Skip to main content
Los callbacks handleReport, onReportApply y handleExtras se disparan al final de un flujo. No te dicen nada de lo que pasa mientras tanto: si el médico está grabando, si está escribiendo, si SofIA está generando la nota. onEvent cubre ese hueco con un flujo de eventos tipados y sin datos clínicos. El caso de uso que lo motivó: un HIS que cierra la sesión por inactividad no puede distinguir “el médico lleva veinte minutos grabando una consulta sin tocar el teclado” de “el médico se fue hace veinte minutos”. Con el micrófono activo y sin otra actividad, deslogueaba al médico a mitad de consulta.
Los eventos nunca llevan texto clínico, contenido del informe, lo que se escribió ni identificadores de paciente o médico. Cada campo del payload es un enumerado, un booleano o un número. Si necesitas el contenido clínico, ya tienes canales dedicados para ello (handleReport, onReportApply, handleExtras).

Las dos props

La suscripción es explícita: sin eventSubscriptions (o con un array vacío) no se entrega ningún evento, y el SDK registra un aviso en consola si pasas onEvent sin ella. Admite nombres exactos ("recording.started"), comodines de familia ("recording.*") o todo ("*").

Forma de un evento

sessionId es el mismo identificador aleatorio que el SDK usa para su analítica interna. Cambia con cada paciente, así que te sirve para correlacionar una ráfaga de eventos con una consulta y para descartar los que lleguen tras un cambio de paciente.

Catálogo de eventos

recording — grabación

activity — actividad del médico

Ambos se limitan a un evento cada 5 segundos por tipo/superficie, en el flanco de subida: la primera pulsación o tecla se emite al instante y las siguientes se silencian durante la ventana. Para detectar inactividad, emitir de más es inocuo (tu temporizador se reinicia); emitir de menos cerraría la app con el médico a mitad de frase.

report — generación del informe

Suprime tu temporizador de inactividad entre report.generation_started y report.settled. La generación tarda decenas de segundos sin que el médico toque nada —se parece exactamente a la inactividad— y cerrar el widget ahí pierde la nota. Los dos eventos van siempre en pareja: el SDK garantiza el settled desde todas las salidas del código, no desde nombres de eventos.

lifecycle — ciclo de vida

Mantener viva una sesión externa

Si tu sistema cierra la sesión por inactividad, lo que necesita son pings periódicos de actividad, no los flancos started/stopped. Para eso existe recording.heartbeat.
Con eso tu integración no guarda ningún estado. Y falla en cerrado: si el audio deja de fluir por la razón que sea —micrófono desconectado, red caída, conexión zombi, pestaña congelada— los latidos cesan y tu propio timeout actúa.
¿Por qué no basarlo en recording.started / recording.stopped? Porque entre los dos puede haber veinte minutos sin ningún otro evento, así que tendrías que mantener un setInterval propio entre ambos. Y si te pierdes el stopped (recarga a mitad de grabación, pestaña colgada, un throw en tu handler), ese intervalo mantiene la sesión viva para siempre: justo el fallo que un cierre por inactividad existe para evitar.
El latido sale del mismo punto del código que el SDK usa internamente para saber que una grabación está sana: el envío de cada paquete de audio, que solo ocurre con el transcriptor listo, la conexión abierta y el micrófono captando. Sigue latiendo aunque el médico esté en silencio, porque los paquetes de audio se envían igualmente.

Receta completa para un temporizador de inactividad

Con eventSubscriptions={['recording.*', 'activity.*', 'report.*']}. Un recording.audio_lost o recording.microphone_disconnected también reinicia el temporizador aquí —es razonable: el médico sigue delante de la pantalla viendo el aviso—, pero si prefieres tratarlos como el fin de la actividad, compáralos por event.name.

Garantías de entrega

  • Asíncrona. Cada evento se entrega en un microtask posterior, nunca dentro del render de React ni en la ruta de audio. Tu handler puede hacer setState sin problema, y un handler lento no retrasa la grabación.
  • Ordenada. seq es monotónico y los eventos llegan en ese orden.
  • Aislada. Si tu handler lanza una excepción, el SDK la captura y sigue funcionando; registra un único aviso en consola por carga de página.
  • Estable entre pacientes. Cambiar patientid o userid remonta el widget por dentro, pero tu suscripción y tu handler sobreviven sin que tengas que volver a asignarlos. Pasar una función inline en cada render tampoco re-suscribe.
  • Un widget por página. El SDK admite una sola instancia de <Omniscribe>/<sofia-sdk>. Si se registrara un segundo onEvent, gana el último y se avisa en consola.
  • Nombres abiertos. Pueden añadirse eventos nuevos en versiones menores. Compara por event.name o event.family y trata los nombres desconocidos como “algo pasó”; no asumas un conjunto cerrado.
Si en TypeScript quieres un switch exhaustivo, estrecha event.name a KnownSdkEventName. Los tipos SdkEvent, SdkEventName, KnownSdkEventName, SdkEventFamily, SdkEventHandler, SdkEventPayloadMap y SdkEventSubscription se exportan desde @omniloy/sofia-sdk.

Privacidad

El diseño parte de una invariante que el propio compilador del SDK hace cumplir: ningún campo de ningún payload es una cadena libre. Los errores viajan como códigos, los informes como booleanos, la escritura como el nombre de la superficie. El SDK tiene un test que lee la definición de tipos y falla la build si aparece un string sin acotar. Por eso onEvent no es un volcado del log interno del SDK: los mensajes de log son prosa escrita a mano que interpola identificadores y objetos de error completos, y eso no se puede hacer seguro con un filtro. Si necesitas depurar, usa la prop debug y la consola; si necesitas contenido clínico, usa los callbacks dedicados.

Próximos pasos

Propiedades opcionales

Todas las props y callbacks del componente

Vista previa de inserción

De dónde sale activity.typing con surface: 'note'