Logo de unDEAenunaCaja

Documentación técnica · Simulador de DEA / DESA - Aplicación web libre (PWA) · Versión V1.0

Resumen técnico

unDEAenunaCaja es un simulador de Desfibrilador Externo Automático (DEA) y Semiautomático (DESA) para capacitación en reanimación. Una persona instructora conduce la simulación con botones; la aplicación reproduce las indicaciones de voz del equipo, muestra el texto en pantalla y marca los 2 minutos de RCP entre análisis de ritmo.

Característica Detalle
Tipo Aplicación web estática, instalable como PWA y utilizable sin conexión
Stack HTML5, CSS3 y JavaScript (ES6+) sin frameworks, sin dependencias y sin paso de compilación
APIs web HTMLAudioElement, requestAnimationFrame, Service Worker, Cache API, Web App Manifest
Modelo de ejecución Máquina de estados dirigida por datos: 17 estados definidos en el objeto historia, interpretados por un único motor
Salida Audio MP3 pregrabado (13 archivos, unos 1,3 MB; metronomo.mp3 es el 35 %) y texto en pantalla
Persistencia Ninguna de la simulación (el estado vive en memoria). El service worker guarda los recursos en caché
Compatibilidad Chrome, Edge, Firefox y Opera en Windows, Linux y Android; Safari en iOS (limitado)
Licencia GPL v3
Repositorio github.com/Ggaita/undeaenunacaja


  1. Tocá Comenzar y elegí el modo: Automatico o Semiautomatico.
  2. El equipo brinda indicaciones por voz. Quien instruye responde con los botones según lo que hacen los participantes:
    verde cuando lo hacen bien,
    rojo cuando no, iniciando un bucle.
  3. Al tener que iniciar maniobras de RCP aparece la cuenta regresiva de 2 minutos. Al terminar, el equipo vuelve a pedir el análisis de ritmo.
  4. Reiniciar vuelve a la pantalla de inicio en cualquier momento, sin recargar la página.

Flujos de simulación

Cada pantalla tiene una indicación de voz, un texto y hasta dos botones.
El identificador de cada pantalla es la clave que se usa en index.js.

Pantallas comunes

inicio · audio inicio.mp3 (4,8 s)
Dale click a comenzar para iniciar la simulación
Comenzar → inicioOpciones
inicioOpciones · audio opcionesdesimulacion.mp3 (1,8 s)
Opciones de simulación:
Automatico → automatico   Semiautomatico → semiautomatico

Modo automático

El equipo analiza el ritmo y, si corresponde, entrega la descarga por sí solo. Quien instruye solo indica el ritmo y confirma que los participantes siguen las indicaciones.

automatico · modoAutomatico.mp3
Has seleccionado el modo automático…
Comenzar → etapaParchesautomatico
etapaParchesautomatico · comienzo.mp3 (5,1 s)
¿Colocan los parches correctamente?
No → etapaParches2Automatico   Sí → analisisRitmoAutomatico
etapaParches2Automatico · colocarParches2.mp3 (4,6 s)
¿Colocan los parches correctamente?
No → se repite   Sí → analisisRitmoAutomatico
analisisRitmoAutomatico · analizandoRitmo.mp3 (3,7 s)
Seleccione Ritmo:
No Desfibrilable → ritmoNoDesfibrilableAutomatico   Desfibrilable → ritmoDesfibrilableAutomaico
ritmoDesfibrilableAutomaico · ritmoDesfibrilableAutomatico.mp3 (7,8 s)
¿Siguen las indicaciones luego de la descarga?
NO Siguen Indicaciones → se repite   Siguen Indicaciones → metronomoAutomatico
ritmoNoDesfibrilableAutomatico · ritmoNoDesfibrilable.mp3 (7,9 s)
¿Siguen la RCP después de la descarga?
No Siguen la RCP → se repite   Síguen la RCP → metronomoAutomatico
metronomoAutomatico · metronomo.mp3 (2:00)
Espere los 2 minutos para analizar el ritmo nuevamente
Sin botones. Al terminar el audio vuelve solo a analisisRitmoAutomatico.

Modo semiautomático

Igual que el automático, pero con un paso más: cuando el ritmo es desfibrilable, el equipo pide indicar la descarga, y recién después se pasa a la RCP.

semiautomatico · modoSemiautomatico.mp3
Has seleccionado el modo semiautomático…
Comenzar → etapaParches
etapaParches y etapaParches2 · comienzo.mp3 / colocarParches2.mp3
¿Colocan los parches correctamente?
No → etapaParches2 (se repite)   Sí → analisisRitmo
analisisRitmo · analizandoRitmo.mp3
Seleccione Ritmo:
No Desfibrilable → ritmoNoDesfibrilable   Desfibrilable → ritmoDesfibrilable
ritmoDesfibrilable · descargaNecesaria.mp3 (5,0 s)
¿Siguen las indicaciones para la descarga?
NO Siguen Indicaciones → se repite   Siguen Indicaciones → continuaRcp
continuaRcp · descargaRealizada.mp3 (7,5 s)
¿Siguen la RCP después de la descarga?
No Siguen la RCP → se repite   Síguen la RCP → metronomo
ritmoNoDesfibrilable · ritmoNoDesfibrilable.mp3
¿Siguen la RCP después de la descarga?
No Siguen la RCP → se repite   Síguen la RCP → metronomo
metronomo · metronomo.mp3 (2:00)
Espere los 2 minutos para analizar el ritmo nuevamente
Sin botones. Al terminar el audio vuelve solo a analisisRitmo.

Metrónomo y temporizador

En las pantallas metronomo y metronomoAutomatico se reproduce metronomo.mp3, que dura 2 minutos y marca el ritmo de las compresiones. Mientras suena aparece la barra de progreso y el tiempo restante (MM:SS).

La cuenta regresiva sale del propio audio: restante = duración − tiempo actual. Si el navegador todavía no conoce la duración, usa 120 segundos. El color de la barra cambia según el tiempo que queda:


Tiempo restante Color
Más del 50 % Verde #00c853
Entre 50 % y 20 % Amarillo #ffd600
Menos del 20 % Rojo #d50000


Cuando el audio termina (evento audio.onended) se oculta el temporizador y la aplicación pasa sola a la pantalla de análisis de ritmo del modo en curso.

Arquitectura

El diseño separa los datos (qué dice y a dónde va cada pantalla) del motor (cómo se reproduce y se dibuja). Agregar o cambiar pantallas es editar un objeto, sin tocar la lógica; la excepción son los dos metrónomos, que están escritos en el motor (ver Extensión).

Capa Dónde Responsabilidad
Presentación index.html, documentacion.html, styles-documentacion.css, styles.css, reset.css DOM fijo; los botones de respuesta se crean dinámicamente en cada estado
Datos Objeto historia (index.js, líneas 13 a 173) Grafo dirigido de 17 estados: audio, texto y opciones con su destino
Motor cambiarEstado, reproducir, temporizador Interpreta un estado: reproduce el audio, escribe el texto, crea los botones y controla el temporizador
PWA service-worker.js, manifest.json, registro en index.js Instalación, caché sin conexión y aviso de actualización
Recursos src/, icons/ Audios, logos, bandera e íconos

Ciclo de vida de la página

  1. Carga: se muestra el botón Iniciar. Todavía no suena nada porque los navegadores exigen un gesto del usuario antes de reproducir audio.
  2. Se registra el service worker (service-worker.js) y se instala la caché.
  3. Iniciar ejecuta empezar(): oculta ese botón, muestra #opciones y #btnReiniciar, y entra en el estado inicio.
  4. Cada botón de respuesta llama a cambiarEstado(siguiente). Las únicas transiciones sin botón son las de fin de metrónomo, que dispara el evento ended del audio.

Qué hace cambiarEstado(clave)

  1. Guarda estadoActual = clave y obtiene historia[clave]. Si la clave no existe, el nodo es undefined y la función lanza un error: la aplicación deja de responder.
  2. Reproduce nodo.audio desde el segundo 0.
  3. Vacía #opciones y escribe nodo.texto con textContent (cadena vacía si el nodo no tiene texto).
  4. Si la clave es metronomo o metronomoAutomatico: muestra el temporizador y define audio.onended para volver al análisis de ritmo de su modo. En cualquier otro estado oculta el temporizador y pone audio.onended = null.
  5. Crea un <button> por cada opción, con su clase CSS, y le asigna onclick = () => cambiarEstado(op.siguiente).

Referencia del código (index.js)

Los números de línea corresponden a la revisión 3e1b788. El script se carga como archivo clásico (sin módulos) para que los atributos onclick del HTML encuentren las funciones en el ámbito global.

Funciones

Función Líneas Descripción
reproducir(src) 182–186 Asigna audio.src, pone currentTime = 0 y llama a audio.play(). La promesa que devuelve play() no se captura.
iniciarTemporizador() 188–228 Muestra la barra y el tiempo, cancela el cuadro de animación anterior y ejecuta actualizar(), que se reprograma con requestAnimationFrame mientras el audio suene y quede tiempo.
ocultarTemporizador() 230–233 Pone display: none en la barra y en el tiempo restante.
cambiarEstado(estado) 238–295 Motor de la máquina de estados (ver Arquitectura).
empezar() 298–304 Primera entrada: oculta #btnInicio, muestra #opciones y #btnReiniciar, entra en inicio.
reiniciar() 308–310 Vuelve al estado inicio. No recarga la página ni vuelve a mostrar el botón Iniciar.
Registro del service worker 313–339 Registra service-worker.js, escucha updatefound y muestra #updateBanner si hay una versión nueva instalada y ya había un worker activo. En controllerchange recarga la página.
actualizarApp() 342–346 Si hay un worker nuevo en espera, le envía el mensaje "SKIP_WAITING".

Variables globales

undeaenunacaja/
├── index.html                Página única (43 líneas)
├── documentacion.html        Documentación (100 líneas)
├── index.js                  Datos (historia), motor, temporizador, registro del service worker
├── styles-documentacion.css  Estilos de la documentación
├── styles.css                Estilos del simulador
├── reset.css                 Reset y ancho máximo de 1024 px
├── service-worker.js         Precaché, estrategia cache-first, actualización
├── manifest.json             Datos de instalación de la PWA
├── icons/                    icono-192.png, icono-512.png
├── src/                      13 audios .mp3, logos y bandera (src/images/)
├── LICENSE                   GPL v3
└── README.md
  • No hay carpeta de compilación ni package.json: el repositorio es lo que se publica.
  • Para publicar basta un servidor de archivos estáticos con HTTPS (por ejemplo GitHub Pages). Los service workers solo funcionan en HTTPS.

Modelo de datos y extensión

Cada estado es una entrada del objeto historia. La clave es el identificador del estado.

nombreDelEstado: {
  audio: "./src/archivo.mp3",
  texto: "Texto que se muestra",
  opciones: [
    { texto: "Sí", siguiente: "otroEstado", clase: "btn-verde" },
    { texto: "No", siguiente: "nombreDelEstado", clase: "btn-rojo" }
  ]
}
Campo Tipo Obligatorio Regla
audio string sí Ruta relativa a un .mp3. Si el archivo no existe, el estado igual avanza pero no suena.
texto string no Se muestra tal cual (sin HTML). Si falta, se limpia el texto anterior.
opciones array sí Puede ser vacío. Solo los estados de metrónomo terminan por el fin del audio; un estado vacío cualquiera dejaría la simulación sin salida, salvo Reiniciar.
opciones[].texto string sí Rótulo del botón
opciones[].siguiente string sí Clave de un estado existente. Si apunta al mismo estado, la indicación se repite.
opciones[].clase string no btn-opciones, btn-verde o btn-rojo. Sin clase, el botón es azul.

Agregar o modificar una pantalla

  1. Grabar el audio en MP3 y guardarlo en src/.
  2. Agregar el estado en historia y apuntarle un botón desde otro estado.
  3. Agregar la ruta del audio a urlsToCache en service-worker.js.
  4. Subir CACHE_NAME (por ejemplo a dea-simulador-v2) para que los dispositivos descarguen la versión nueva.
  5. Validar el grafo con el script de la sección Verificación.

Agregar un modo nuevo

Hoy exige tres cambios: los estados del modo, un botón eninicioOpciones y un caso en cambiarEstado para el metrónomo. Para no tocar el motor, se podría agregar un campo alTerminar a cada estado de metrónomo, con la clave del estado que sigue.



metronomo: {
  audio: "./src/metronomo.mp3",
  texto: "Espere los 2 minutos para analizar el ritmo nuevamente",
  opciones: [],
  temporizador: true,
  alTerminar: "analisisRitmo"
}

// En cambiarEstado: reemplaza las dos ramas por nombre
if (nodo.temporizador) {
  iniciarTemporizador();
  audio.onended = () => {
    ocultarTemporizador();
    cambiarEstado(nodo.alTerminar);
  };
} else {
  ocultarTemporizador();
  audio.onended = null;
}

PWA y funcionamiento sin conexión

manifest.json

Campo Valor Observación
name / short_name unDEAenunaCAJA
start_url . Relativa a la ubicación del manifest
display standalone Se abre sin barra del navegador
background_color / theme_color #000000 El meta theme-color de index.html es #d32f2f
icons icono-192.png (192×192) e icono-512.png (512×512) Sin purpose: no declara variante maskable

service-worker.js

Evento Comportamiento
install Abre la caché CACHE_NAME y ejecuta cache.addAll(urlsToCache). Es atómico: si un solo archivo falla (404, red), la instalación entera falla y no queda nada guardado. No llama a skipWaiting(): una versión nueva espera hasta que la persona acepte la actualización.
activate Borra toda caché cuyo nombre sea distinto de CACHE_NAME. No llama a clients.claim(): en la primera instalación, la pestaña abierta no queda controlada hasta recargar.
fetch Cache-first para todo: caches.match(request).then(r => r || fetch(request)). No hay caché dinámica: lo que no está en el precaché se pide a la red y no se guarda (por ejemplo /favicon.ico).
message Si el dato es "SKIP_WAITING", ejecuta self.skipWaiting().

Flujo de actualización

  1. Se publica una versión con CACHE_NAME distinto. El navegador detecta que service-worker.js cambió y lo instala en segundo plano (nueva caché).
  2. Cuando el worker nuevo queda installed y ya hay uno controlando la página, index.js muestra el cartel «Nueva versión disponible».
  3. Actualizar envía SKIP_WAITING; el worker nuevo se activa y borra la caché vieja.
  4. El evento controllerchange recarga la página con los recursos nuevos.

Precaché (28 entradas)

GrupoArchivos
Aplicación /, /index.html, /styles.css, /reset.css, /index.js, /manifest.json, /LICENSE, /README.md
Audios (13) Todos los src/*.mp3, incluido bucleComienzo.mp3 que la aplicación no usa
Imágenes cajaWeb.webp, codesLogo.webp, titulounDEAenunacaja.webp, images/Flag_of_Argentina.svg.webp
Íconos icons/icono-192.png, icons/icono-512.png,src/cajaWebIcon.ico (256 KB)
  • Verificado en esta revisión: las 28 rutas corresponden a archivos existentes.service-worker.js no figura en la lista, lo cual es correcto: el navegador lo gestiona aparte.
  • 26 rutas empiezan con/ y 2 no (src/titulounDEAenunacaja.webp ysrc/cajaWebIcon.ico). Con el sitio en la raíz del dominio todas resuelven bien. En una subcarpeta (por ejemplo GitHub Pages en/undeaenunacaja/) las 26 absolutas apuntan a la raíz del dominio, devuelven 404 y la instalación falla; habría que pasarlas a rutas relativas.

Procedimiento para publicar una versión

  1. Modificar los archivos. Si se agregan, renombran o quitan recursos, actualizarurlsToCache.
  2. SubirCACHE_NAME.
  3. Publicar y abrir la aplicación con la versión anterior ya instalada: debe aparecer el cartel de actualización.
  4. Revisar en las herramientas del navegador:Application → Service Workers(estado del worker) yCache Storage (28 entradas en la caché nueva, y sin la vieja).
  5. Probar sin conexión: activar modo avión, recargar y recorrer un modo completo.

Estilos, diseño y accesibilidad

Interfaz oscura de un solo fondo, con botones grandes. Los valores están escritos de forma literal en los CSS (no hay variables). No se declara tipografía: se usa la del sistema, así la aplicación carga sin recursos externos.

Paleta

Fondo#333 · body, main, footer, pista del temporizador
Encabezado#f0e2e20a (translúcido)
Borde#6c7d8f · encabezado y botones
Textorgb(221, 214, 214)
button#2c7ae0 · Iniciar, Reiniciar
.btn-opcionesrgb(39, 172, 132)
.btn-rojored
.btn-verdergb(114, 216, 19) · texto rgb(39, 38, 38)

Colores del temporizador (asignados desde JavaScript): #00c853 verde, #ffd600 amarillo, #d50000 rojo.

Componentes

ComponenteEspecificación
BotónFondo #2c7ae0, texto rgb(221,214,214), borde 1px #6c7d8f, radio 5 px, relleno 10×20 px, margen 20 px, 1.2rem, transición de fondo de 0,3 s
Texto de estado#textoEstado: 1.2rem, centrado, relleno lateral del 10 %, margen inferior 30 px
Barra de tiempoPista de 80 % del ancho (máximo 400 px), alto 22 px, radio 30 px; el relleno cambia de ancho con transición de 0,1 s
Tiempo restante3rem, negrita, blanco, centrado
Cartel de actualizaciónFijo al pie, fondo #222, texto blanco, relleno 10 px. Estilos en línea en el HTML
PieFlex centrado, fondo #333, relleno 20 px; bandera de 70 px con margen izquierdo de 10 px

Disposición y adaptación

  • html y body con ancho 100 %, máximo 1024px y centrados; body es una columna flex. main centra su contenido.
  • No hay media queries: la adaptación depende de unidades relativas y de max-width: 100% en las imágenes.

Glosario

Término Significado
DEA / DESA Desfibrilador Externo Automático / Semiautomático. En el automático el equipo entrega la descarga por sí solo; en el semiautomático la persona debe accionarla.
Ritmo desfibrilable / no desfibrilable Ritmo cardíaco en el que la descarga está indicada / no está indicada.
RCP Reanimación cardiopulmonar. El simulador marca ciclos de 2 minutos entre análisis de ritmo.
PWA Aplicación web progresiva: se instala desde el navegador y puede funcionar sin conexión.
Service worker Script del navegador que intercepta las solicitudes de la página y gestiona la caché.
Precaché / cache-first Guardar archivos en la instalación / responder desde la caché antes de consultar la red.