Skip to main content

Iframe

Implementación de Iframe: Código, Eventos y Manejo de Datos

Una vez que tengas el token de acceso, debes integrar el iframe en tu aplicación utilizando el siguiente enfoque:

1. HTML - Agregar el iframe

El iframe maneja dos dimensiones estándar iniciales, dependiendo de la vista que desees mostrar:

Dimensión Mediana (MD): width: 320px, height: 220px. (Vista completa).
Dimensión Pequeña (SM): width: 240px, height: 100px. (Vista minimizada).

El siguiente ejemplo utiliza la dimensión mediana (320x220px) como punto de partida.

<iframe
id="speaknosis-iframe"
src="about:blank"
title="Speaknosis App"
width="320px"
height="220px"
allow="microphone; screen-wake-lock"
style="border: none; border-radius: 8px; overflow: hidden"
></iframe>

2. JavaScript - Configuración, carga y manejo de eventos del iframe

Una vez que el elemento <iframe> (del Paso 1) existe en tu página, el siguiente paso es configurarlo dinámicamente. El script a continuación se encarga de dos tareas principales:

  1. Cargar el iframe: Construye la URL de Speaknosis (incluyendo el token de autenticación y otros parámetros) y la asigna al atributo src del iframe para iniciar la aplicación.
  2. Manejar el foco: Añade los listeners de eventos blur y focus a la ventana principal de tu sistema.
(async () => {
// --- Configuración de Ambiente (QA o PROD) ---
const portalUrl = "https://qa-portal.speaknosis.com"; // Ambiente de QA
// const portalUrl = "https://portal.speaknosis.com"; // Ambiente de PROD

try {
// --- Cargar iframe ---

// Selecciona el elemento iframe por su ID.
const iframeElement = document.getElementById("speaknosis-iframe");

const response = await login(); // La función login() simula la función que obtiene el token.

const data = {
view: "iframe",
token: response.access_token, // Se obtiene el token de la sentencia de login.
doctorId: "", // Ingrese el ID del doctor que está generando el informe.
appointmentId: "", // Ingrese el ID de la consulta médica a la cual se le está generando el informe.
reportExampleId: "", // (Opcional) Id de plantilla que quieran preseleccionar.
};

// Crea un objeto URLSearchParams para enviar los datos dentro de la URL.
const urlParams = new URLSearchParams(data);

// Construye la URL para el iframe de Speaknosis usando la variable portalUrl.
const iframeUrl = `${portalUrl}/#/pop-up/?${urlParams}`;

// Asigna la URL final al iframe para cargarlo.
iframeElement.src = iframeUrl;

// --- Eventos blur y focus ---

// Manejo del blur para el iframe
window.addEventListener("blur", () => {
const isClickOutsideIframe = document.activeElement !== iframeElement;
if (isClickOutsideIframe && iframeElement.contentWindow) {
iframeElement.contentWindow.postMessage(
{ focusEvent: "WINDOW_BLUR" },
portalUrl // Se usa la url de ambiente
);
}
});

// Manejo del focus para el iframe
window.addEventListener("focus", () => {
if (iframeElement.contentWindow) {
iframeElement.contentWindow.postMessage(
{ focusEvent: "WINDOW_FOCUS" },
portalUrl // Se usa la url de ambiente
);
}
});
} catch (error) {
console.error("Error al configurar el iframe:", error);
}
})();

Estos eventos blur y focus son esenciales, ya que notificarán a nuestro sitio web (dentro del iframe) cuando se pierda o recupere el foco de la página web de tu sistema. Esto permite que aparezca una ventana flotante y se controle el estado de la grabación.

Imagen de referencia:

Img-Iframe-Window

3. JavaScript: Recibir evento de redimension desde el iframe (REQUEST_RESIZE)

Tu aplicación debe escuchar el evento message para capturar la acción REQUEST_RESIZE enviada por el iframe. Este evento se dispara cuando el contenido interno del iframe cambia de tamaño (por ejemplo, al levantarse una alerta durante la grabación) y necesita que tu elemento <iframe> se ajuste a las nuevas dimensiones.

// --- Recibir eventos del iframe ---
// (Este bloque debe ir DENTRO del 'try' del script anterior)

window.addEventListener("message", (event) => {
// Validar origen usando la variable portalUrl
if (event.origin !== portalUrl) {
return; // Ignora mensajes de orígenes desconocidos
}

// Verifica si es un objeto de acción
if (
typeof event.data === "object" &&
event.data !== null &&
event.data.action
) {
// Maneja la acción 'REQUEST_RESIZE'
if (event.data.action === "REQUEST_RESIZE") {
iframeElement.style.width = "320px"; // Ajusta al ancho necesario
iframeElement.style.height = "220px"; // Ajusta a la altura necesaria
return;
}
}
});

Método Alternativo: Autenticación vía postMessage

Además de enviar el token directamente en la URL, Speaknosis soporta un método alternativo de autenticación mediante postMessage. Este enfoque es útil cuando:

  • Prefieres no exponer el token en la URL.
  • Necesitas enviar datos adicionales junto con el token (como un JSON de configuración).
  • Tu arquitectura requiere generar el token después de cargar el iframe.
Cargar el iframe sin token en la URL

En este flujo, cargas el iframe sin incluir el token en los parámetros de la URL:

const data = {
view: "iframe",
// token: NO se incluye en la URL
doctorId: "", // Ingrese el ID del doctor que está generando el informe.
appointmentId: "", // Ingrese el ID de la consulta médica.
reportExampleId: "", // (Opcional) Id de plantilla que quieran preseleccionar.
};

// Crea un objeto URLSearchParams para enviar los datos dentro de la URL.
const urlParams = new URLSearchParams(data);

// Construye la URL para el iframe de Speaknosis.
const iframeUrl = `https://qa-portal.speaknosis.com/#/pop-up/?${urlParams}`; // Ambiente de QA
// OR
const iframeUrl = `https://portal.speaknosis.com/#/pop-up/?${urlParams}`; // Ambiente de PROD

// Selecciona el elemento iframe por su ID y asigna la URL.
const iframeElement = document.getElementById("speaknosis-iframe");
iframeElement.src = iframeUrl;

// Guarda los datos pendientes de autenticación para enviar posteriormente
const pendingAuthData = {
token: response.access_token, // Token obtenido de tu sistema de login
jsonReport: {}, // Opcional: estructura JSON para plantillas dinámicas
};
Escuchar y responder a la solicitud de autenticación

Speaknosis enviará un mensaje de tipo AUTH_REQUEST cuando esté listo para recibir las credenciales. Tu aplicación debe escuchar este evento y responder con un mensaje AUTH_RESPONSE:

useEffect(() => {
const messageHandler = (event) => {
// Escuchar la solicitud de autenticación desde Speaknosis
if (event.data?.type === "AUTH_REQUEST") {
console.log("[App] Solicitud de autenticación recibida desde Speaknosis");

// Verificar que tenemos los datos y la referencia al iframe
if (pendingAuthData && iframeElement.contentWindow) {
const authResponse = {
type: "AUTH_RESPONSE",
token: pendingAuthData.token,
jsonReport: pendingAuthData.jsonReport, // (Opcional) Datos adicionales en formato JSON
};

// Enviar la respuesta al iframe
iframeElement.contentWindow.postMessage(authResponse, "*");
console.log("[App] Respuesta de autenticación enviada");
} else {
console.warn("[App] No hay datos de autenticación disponibles");
}
return;
}

// Continuar manejando otros mensajes del iframe (ej: datos clínicos)
// ...
};

window.addEventListener("message", messageHandler);
return () => window.removeEventListener("message", messageHandler);
}, [pendingAuthData, iframeElement]);

⚠️ Importante: Si la ventana padre no responde dentro de un tiempo límite (aprox. 5 segundos con 1 reintento automático), Speaknosis redirigirá a una vista de error. Asegúrate de que tu listener esté activo antes de que el iframe complete su carga.

Estructura del mensaje AUTH_RESPONSE
CampoTipoRequeridoDescripción
typestringDebe ser "AUTH_RESPONSE"
tokenstringToken de autenticación JWT válido
jsonReportobject | nullOpcionalDatos adicionales en formato JSON para configuración avanzada. Para definir correctamente la estructura del JSON, contáctate con el equipo de Speaknosis para que te entreguen la documentación.

Uso y Funcionalidad del Iframe

Si el iframe recibe correctamente los datos de la consulta (token, doctorId y appointmentId), el componente del informe debería mostrarse dentro del iframe.

Imagen de referencia:

Img-Iframe

Una vez cargado el iframe, podrás comenzar a grabar audio usando el micrófono de tu computador o un micrófono externo desde tu teléfono. Al comenzar la grabación, aparecerán dos nuevos botones: pausar/reanudar grabación ( / ) y detener grabación ().

Imagen de referencia:

Img-Iframe-Recording

Al finalizar la grabación de audio (presionando el botón de detener), aparecerá una ventana emergente donde deberás seleccionar la plantilla a utilizar para generar el informe médico. Luego, deberás hacer clic en el botón "Generar Informe".

💡 Importante: Autorización de ventanas emergentes.
Para que esta ventana se muestre correctamente, es indispensable que las ventanas emergentes estén habilitadas en el navegador. Si están bloqueadas, la ventana no se abrirá y no será posible continuar con el proceso. Recomendamos permitir las ventanas emergentes para este sitio o incluirlo en la lista de excepciones.

📝 Pre-selección de Plantilla (Opcional) Si incluiste el parámetro reportExampleId en la URL inicial, el sistema utilizará ese ID de reporte de ejemplo para pre-seleccionar la plantilla en esta vista automáticamente. El doctor solo tendrá que revisar y confirmar la plantilla antes de hacer clic en 'Generar informe'. Si no se envía este parámetro, el doctor deberá seleccionar manualmente la plantilla del menú desplegable.

Imagen de referencia:

Img-Iframe-Select


Generación del Informe

Una vez que comienza la generación del informe, una animación indicará que el informe se está generando. Luego, se mostrará el informe médico, el cual podrás editar y aprobar en una ventana flotante o emergente.

Imagen de referencia:

Img-Iframe-Generated

Img-Iframe-Generated

Método para recibir el informe

Para recibir los informes en tu sistema, debes configurar un endpoint personalizado y seguro. La comunicación se autenticará utilizando el método que tu sistema prefiera (por ejemplo, una API Key, tokens de autorización, etc).

Una vez configurado, nuestro sistema enviará la información del informe a la URL que nos proporciones. Este proceso se activa cuando un informe es aprobado en nuestra plataforma, generando los datos en el formato acordado y enviándolos a tu endpoint.

Estructura Base de la Respuesta

La carga útil (payload) que recibirás en tu endpoint tendrá la siguiente estructura fundamental:

{
"response": {
"message": {
"appointmentId": "123123",
"report": { /* ... Contenido del informe ... */ },
"metaData": { /* ... Datos adicionales ... */ }
}
}
}

Detalle de los Campos

  • appointmentId (string): Identificador único de la consulta o cita médica.
  • report (object | string): Contiene el informe médico. Su formato es flexible y depende de la configuración acordada.
  • metaData (object): Contiene datos adicionales y estructurados, como catálogos o códigos.

El campo report es dinámico. Puede contener un objeto JSON estructurado o una cadena de texto (string) con el informe en formato MARKDOWN o HTML.

Contenido del Campo "report"

Opción 1: JSON Estructurado (report como objeto)

Si necesitas los datos del informe de manera segmentada, el campo report contendrá un objeto JSON.

Ejemplo:

"report": {
"reasonForVisit": "Motivo de consulta: Dolor de garganta persistente.",
"anamnesis": "Paciente refiere dolor y dificultad para tragar desde hace 3 días.",
"physicalExam": "Se observan amígdalas enrojecidas e inflamadas."
}
Opción 2: Markdown (report como string)

Para una presentación de texto enriquecido simple y legible, el campo report contendrá un objeto MARKDOWN.

Ejemplo:

"report": "## Motivo de consulta\nDolor de garganta persistente.\n\n## Anamnesis\nPaciente refiere dolor y dificultad para tragar desde hace 3 días."
Opción 3: HTML (report como string)

Si necesitas renderizar el informe directamente en una vista web, el campo report contendrá un objeto HTML.

Ejemplo:

"report": "<h2>Motivo de consulta</h2><p>Dolor de garganta persistente.</p><h2>Anamnesis</h2><p>Paciente refiere dolor y dificultad para tragar desde hace 3 días.</p>"

Contenido del Campo "metaData"

El campo metaData alberga información complementaria y estructurada que enriquece el informe, como códigos de catálogos previamente configurados.

Ejemplo de metaData con catálogos:

"metaData": {
"catalogs": {
"cie-10": [
{ "key": "J03.9", "code": "J03.9", "description": "Amigdalitis aguda, no especificada" }
],
"vademecum": [
{ "key": "IBU600", "code": "IBUPROFENO 600MG", "subcode": "24COMP" }
]
}
}

Una vez que el Pop-Up envíe los datos a tu endpoint, se cerrará automáticamente, permitiéndote utilizar la información recibida en tu aplicación.