feat: implementar sistema híbrido de registry de imágenes (Opción 4)

- Crear image-registry.ts con metadatos centralizados
- Añadir 20+ imágenes al registry con alias, alt, captions y tags
- Modificar MarkdownViewer para resolver alias desde registry
- Añadir soporte para captions automáticos con <figure> y <figcaption>
- Mantener compatibilidad con rutas directas (sistema actual)
- Migrar 12 referencias en Markdown a usar alias:
  - Collarín cervical (4 referencias)
  - Constantes vitales (3 referencias)
  - ABCDE y Triage (2 referencias)
  - Oxigenoterapia y BVM (3 referencias)
- Funciones helper: findImagesByBlock, findImagesByTags, findImageById
- Mejora: referencias más cortas, metadatos centralizados, captions automáticos
This commit is contained in:
planetazuzu 2025-12-21 12:25:33 +01:00
parent 8f54f831e9
commit 4ad67065c1
12 changed files with 365 additions and 29 deletions

View file

@ -12,7 +12,7 @@
Obtener constantes vitales fiables en campo y durante traslado, registrando correctamente los valores obtenidos, evitando lecturas falsas por técnica incorrecta, perfusión, movimiento o mala colocación.
![Registro de constantes vitales](/assets/infografias/bloque-3-material-sanitario/registro-constantes-vitales.png)
![registro-constantes-vitales]
Este capítulo se centra en **técnica operativa de toma de constantes vitales (TA, FC, FR, temperatura) y registro en campo**, no en interpretación clínica avanzada ni tratamiento.
@ -304,7 +304,7 @@ Este capítulo cubre:
### E) Saturación de Oxígeno (SpO₂)
![Uso correcto del pulsioxímetro](/assets/infografias/bloque-3-material-sanitario/uso-correcto-pulsioximetro.png)
![uso-pulsioximetro]
**Procedimiento paso a paso:**
@ -339,7 +339,7 @@ Este capítulo cubre:
## 1.1.6 Registro Operativo en Campo
![Interpretación de constantes vitales (semáforo)](/assets/infografias/bloque-3-material-sanitario/interpretacion-constantes-semaforo.png)
![interpretacion-constantes-semaforo]
### Qué registrar

View file

@ -93,7 +93,7 @@ Este capítulo cubre:
## 1.2.4 Procedimiento Paso a Paso: ABCDE
![Algoritmo operativo del TES](/assets/infografias/bloque-0-fundamentos/ALGORITMO OPERATIVO DEL TES.svg)
![abcde-algoritmo]
### A - Airway (Vía Aérea)

View file

@ -96,7 +96,7 @@ Este capítulo cubre:
## 1.4.4 Sistema Start: Categorías
![Resumen visual del algoritmo START](/assets/infografias/bloque-0-fundamentos/RESUMEN VISUAL DEL ALGORITMO START.svg)
![triage-start]
### Negro - Fallecido / Expectante

View file

@ -276,7 +276,7 @@ No existen contraindicaciones absolutas cuando hay riesgo cervical.
4. Medir distancia entre ambos puntos
5. Seleccionar talla según medida
![Medición anatómica para selección de talla de collarín](/assets/infografias/bloque-2-inmovilizacion/seleccion-talla-collarin-medicion-anatomica.png)
![collarin-medicion]
**Tallas Estándar:**
- **Pediátrico:** <10 cm aproximadamente
@ -284,7 +284,7 @@ No existen contraindicaciones absolutas cuando hay riesgo cervical.
- **Mediano:** 12-14 cm aproximadamente
- **Grande:** >14 cm aproximadamente
![Tabla de tallas de collarín cervical](/assets/infografias/bloque-2-inmovilizacion/seleccion-talla-collarin-tabla-tallas.png)
![collarin-tabla-tallas]
*Nota: Las medidas exactas varían según fabricante. Consultar guía del fabricante.*
@ -508,7 +508,7 @@ No existen contraindicaciones absolutas cuando hay riesgo cervical.
## 2.3.10 Problemas Frecuentes y Resolución Rápida
![Errores frecuentes en la colocación del collarín cervical](/assets/infografias/bloque-2-inmovilizacion/errores-frecuentes-collarin-cervical.png)
![collarin-errores]
### Errores Críticos

View file

@ -243,9 +243,9 @@ Este capítulo se centra en **equipos, uso seguro, montaje, verificación, logí
## 3.0.4 Dispositivos de Administración (Visión Práctica)
![Diagrama de selección de dispositivo de oxigenoterapia](/assets/infografias/bloque-0-fundamentos/diagrama-seleccion-dispositivo-oxigenoterapia.png)
![diagrama-seleccion-oxigenoterapia]
![Tabla de rangos de FiO2 por dispositivo](/assets/infografias/bloque-0-fundamentos/tabla-rangos-fio2-oxigenoterapia.png)
![tabla-rangos-fio2]
### Gafas nasales

View file

@ -95,7 +95,7 @@ Este capítulo cubre:
- Verificar que caudalímetro funciona correctamente.
- Verificar que presión es adecuada.
![Guía de colocación de dispositivos de oxigenoterapia](/assets/infografias/bloque-0-fundamentos/guia-colocacion-dispositivos-oxigenoterapia.png)
![guia-colocacion-oxigenoterapia]
**2. Montar dispositivo y comprobar flujo**
- Montar dispositivo según instrucciones.

View file

@ -12,7 +12,7 @@
## 1. OBJETIVO OPERATIVO
![Cánulas de Guedel y nasofaríngea](/assets/infografias/bloque-3-material-sanitario/canulas-guedel-nasofaringea.png)
![canulas-guedel-nasofaringea]
Insertar cánula orofaríngea (OPA) de forma segura y efectiva en pacientes inconscientes sin reflejo nauseoso, manteniendo vía aérea permeable para facilitar ventilación e integrando con **ventilación con bolsa-mascarilla (3.1) y evaluación primaria ABCDE (1.2)**.

View file

@ -10,9 +10,9 @@
## 3.3.1 Objetivo operativo
![Uso correcto de la bolsa-mascarilla (Ambú)](/assets/infografias/bloque-3-material-sanitario/uso-correcto-ambu.png)
![uso-ambu]
![Configuración máxima de FiO2 con bolsa-mascarilla](/assets/infografias/bloque-3-material-sanitario/configuracion-maxima-fio2-bolsa-mascarilla.png)
![configuracion-fio2-bvm]
Usar la BVM de forma **segura y eficaz** para ventilación asistida básica, integrándola con **oxigenoterapia (3.03.1), aspiración (3.2) y traslado**, minimizando **fugas, insuflación gástrica y pérdida de control**.

View file

@ -52,7 +52,7 @@ Este capítulo se centra en **uso operativo del DESA**, no en desfibrilador manu
## 4.4.3 Principios TES
![Flujo de uso del DESA transtelefónico](/assets/infografias/bloque-0-fundamentos/flujo-desa-telefono.png)
![flujo-desa-telefono]
### El DESA es un instrumento seguro

View file

@ -6,6 +6,7 @@ import rehypeRaw from 'rehype-raw';
import rehypeSanitize from 'rehype-sanitize';
import rehypeHighlight from 'rehype-highlight';
import { Loader2, AlertCircle, FileText } from 'lucide-react';
import { imageRegistry, hasImageId, findImageById } from '@/data/image-registry';
import 'highlight.js/styles/github-dark.css';
interface MarkdownViewerProps {
@ -240,27 +241,70 @@ const MarkdownViewer = ({
img: ({ node, src, alt, ...props }: any) => {
// Normalizar rutas de imágenes
let imageSrc = src || '';
let imageAlt = alt || '';
let imageCaption = '';
// Si es una ruta relativa que empieza con ./assets o ../assets
if (imageSrc.startsWith('./assets/') || imageSrc.startsWith('../assets/')) {
// Convertir a ruta absoluta desde public/
imageSrc = imageSrc.replace(/^\.\.?\/assets\//, '/assets/');
// 1. Intentar resolver desde registry (sistema de alias)
if (imageSrc && hasImageId(imageSrc)) {
const metadata = findImageById(imageSrc);
if (metadata) {
imageSrc = metadata.path;
imageAlt = metadata.alt;
imageCaption = metadata.caption || '';
}
}
// Si es una ruta relativa que empieza con assets/
else if (imageSrc.startsWith('assets/') && !imageSrc.startsWith('/assets/')) {
// Convertir a ruta absoluta
imageSrc = `/${imageSrc}`;
}
// Si no empieza con /, asumir que es relativa desde public/
else if (imageSrc && !imageSrc.startsWith('/') && !imageSrc.startsWith('http')) {
imageSrc = `/${imageSrc}`;
// 2. Si no es alias, normalizar ruta directa (compatibilidad con sistema actual)
else {
// Si es una ruta relativa que empieza con ./assets o ../assets
if (imageSrc.startsWith('./assets/') || imageSrc.startsWith('../assets/')) {
// Convertir a ruta absoluta desde public/
imageSrc = imageSrc.replace(/^\.\.?\/assets\//, '/assets/');
}
// Si es una ruta relativa que empieza con assets/
else if (imageSrc.startsWith('assets/') && !imageSrc.startsWith('/assets/')) {
// Convertir a ruta absoluta
imageSrc = `/${imageSrc}`;
}
// Si no empieza con /, asumir que es relativa desde public/
else if (imageSrc && !imageSrc.startsWith('/') && !imageSrc.startsWith('http')) {
imageSrc = `/${imageSrc}`;
}
// Si no hay alt, usar el src como fallback
if (!imageAlt && imageSrc) {
imageAlt = imageSrc.split('/').pop()?.replace(/\.[^/.]+$/, '') || 'Imagen';
}
}
// Renderizar imagen con caption si está disponible
if (imageCaption) {
return (
<figure className="my-6">
<img
className="rounded-lg max-w-full h-auto border border-border shadow-sm"
src={imageSrc}
alt={imageAlt}
loading="lazy"
onError={(e) => {
// Fallback si la imagen no se carga
console.warn(`No se pudo cargar la imagen: ${imageSrc}`);
(e.target as HTMLImageElement).style.display = 'none';
}}
{...props}
/>
<figcaption className="text-sm text-muted-foreground mt-2 text-center italic px-2">
{imageCaption}
</figcaption>
</figure>
);
}
// Renderizar imagen sin caption
return (
<img
className="rounded-lg my-4 max-w-full h-auto border border-border shadow-sm"
src={imageSrc}
alt={alt || 'Imagen'}
alt={imageAlt || 'Imagen'}
loading="lazy"
onError={(e) => {
// Fallback si la imagen no se carga

View file

@ -144,6 +144,8 @@ const MenuSheet = memo(({ isOpen, onClose }: MenuSheetProps) => {
</div>
</>
);
};
});
MenuSheet.displayName = 'MenuSheet';
export default MenuSheet;

290
src/data/image-registry.ts Normal file
View file

@ -0,0 +1,290 @@
/**
* Registry de imágenes para el sistema de medios visuales
*
* Permite usar alias cortos en Markdown en lugar de rutas completas:
* ![collarin-seleccion] en lugar de ![Descripción](/assets/infografias/bloque-2-inmovilizacion/seleccion-talla-collarin-cervical.png)
*
* Ventajas:
* - Referencias más cortas y legibles
* - Metadatos centralizados (alt, caption, tags)
* - Validación centralizada
* - Fácil mantenimiento (cambiar ruta en un solo lugar)
*/
export interface ImageMetadata {
/** ID único del alias (usado en Markdown) */
id: string;
/** Ruta completa a la imagen */
path: string;
/** Texto alternativo para accesibilidad */
alt: string;
/** Caption opcional que se muestra debajo de la imagen */
caption?: string;
/** Bloque temático al que pertenece */
block: string;
/** Capítulo relacionado (opcional) */
chapter?: string;
/** Tags para búsqueda y filtrado */
tags?: string[];
}
/**
* Registry completo de imágenes
*
* Organizado por bloques temáticos para fácil mantenimiento
*/
export const imageRegistry: Record<string, ImageMetadata> = {
// ============================================
// BLOQUE 0: FUNDAMENTOS
// ============================================
'abcde-algoritmo': {
id: 'abcde-algoritmo',
path: '/assets/infografias/bloque-0-fundamentos/ALGORITMO OPERATIVO DEL TES.svg',
alt: 'Algoritmo operativo del TES - Evaluación ABCDE',
caption: 'Diagrama de flujo del algoritmo ABCDE operativo para TES',
block: 'bloque-0-fundamentos',
tags: ['abcde', 'evaluacion', 'algoritmo', 'fundamentos']
},
'triage-start': {
id: 'triage-start',
path: '/assets/infografias/bloque-0-fundamentos/RESUMEN VISUAL DEL ALGORITMO START.svg',
alt: 'Resumen visual del algoritmo START de triage',
caption: 'Algoritmo START para clasificación rápida de víctimas',
block: 'bloque-0-fundamentos',
tags: ['triage', 'start', 'clasificacion', 'masivo']
},
'diagrama-seleccion-oxigenoterapia': {
id: 'diagrama-seleccion-oxigenoterapia',
path: '/assets/infografias/bloque-0-fundamentos/diagrama-seleccion-dispositivo-oxigenoterapia.png',
alt: 'Diagrama de selección de dispositivo de oxigenoterapia',
caption: 'Guía visual para seleccionar el dispositivo de oxigenoterapia adecuado',
block: 'bloque-0-fundamentos',
tags: ['oxigenoterapia', 'dispositivos', 'seleccion']
},
'tabla-rangos-fio2': {
id: 'tabla-rangos-fio2',
path: '/assets/infografias/bloque-0-fundamentos/tabla-rangos-fio2-oxigenoterapia.png',
alt: 'Tabla de rangos de FiO2 por dispositivo de oxigenoterapia',
caption: 'Rangos de fracción inspiratoria de oxígeno (FiO2) según dispositivo',
block: 'bloque-0-fundamentos',
tags: ['oxigenoterapia', 'fio2', 'tabla', 'rangos']
},
'flujo-rcp-transtelefonica': {
id: 'flujo-rcp-transtelefonica',
path: '/assets/infografias/bloque-0-fundamentos/flujo-rcp-transtelefonica.png',
alt: 'Flujo de RCP transtelefónica',
caption: 'Diagrama de flujo para RCP asistida por teléfono',
block: 'bloque-0-fundamentos',
tags: ['rcp', 'transtelefonica', 'flujo']
},
'flujo-desa-telefono': {
id: 'flujo-desa-telefono',
path: '/assets/infografias/bloque-0-fundamentos/flujo-desa-telefono.png',
alt: 'Flujo de uso de DESA transtelefónico',
caption: 'Diagrama de flujo para uso de DESA con asistencia telefónica',
block: 'bloque-0-fundamentos',
tags: ['desa', 'transtelefonica', 'flujo', 'desfibrilacion']
},
// ============================================
// BLOQUE 1: PROCEDIMIENTOS BÁSICOS
// ============================================
'registro-constantes-vitales': {
id: 'registro-constantes-vitales',
path: '/assets/infografias/bloque-3-material-sanitario/registro-constantes-vitales.png',
alt: 'Registro de constantes vitales',
caption: 'Formato de registro de constantes vitales',
block: 'bloque-1-procedimientos',
tags: ['constantes', 'registro', 'monitorizacion']
},
'interpretacion-constantes-semaforo': {
id: 'interpretacion-constantes-semaforo',
path: '/assets/infografias/bloque-3-material-sanitario/interpretacion-constantes-semaforo.png',
alt: 'Interpretación de constantes vitales - Sistema semáforo',
caption: 'Sistema de interpretación rápida de constantes vitales (verde/amarillo/rojo)',
block: 'bloque-1-procedimientos',
tags: ['constantes', 'interpretacion', 'semaforo', 'alerta']
},
'uso-pulsioximetro': {
id: 'uso-pulsioximetro',
path: '/assets/infografias/bloque-3-material-sanitario/uso-correcto-pulsioximetro.png',
alt: 'Uso correcto del pulsioxímetro',
caption: 'Guía de colocación y uso correcto del pulsioxímetro',
block: 'bloque-1-procedimientos',
tags: ['pulsioximetro', 'spo2', 'oxigenacion', 'monitorizacion']
},
'uso-tensiometro': {
id: 'uso-tensiometro',
path: '/assets/infografias/bloque-3-material-sanitario/uso-correcto-tensiometro.png',
alt: 'Uso correcto del tensiómetro',
caption: 'Guía de colocación y uso correcto del tensiómetro',
block: 'bloque-1-procedimientos',
tags: ['tensiometro', 'presion', 'ta', 'monitorizacion']
},
// ============================================
// BLOQUE 2: INMOVILIZACIÓN
// ============================================
'collarin-seleccion': {
id: 'collarin-seleccion',
path: '/assets/infografias/bloque-2-inmovilizacion/seleccion-talla-collarin-cervical.png',
alt: 'Selección de talla de collarín cervical',
caption: 'Guía visual para seleccionar la talla correcta de collarín cervical',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'inmovilizacion', 'seleccion', 'talla']
},
'collarin-medicion': {
id: 'collarin-medicion',
path: '/assets/infografias/bloque-2-inmovilizacion/seleccion-talla-collarin-medicion-anatomica.png',
alt: 'Medición anatómica para selección de talla de collarín',
caption: 'Técnica de medición anatómica para determinar la talla correcta',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'medicion', 'anatomia', 'talla']
},
'collarin-tabla-tallas': {
id: 'collarin-tabla-tallas',
path: '/assets/infografias/bloque-2-inmovilizacion/seleccion-talla-collarin-tabla-tallas.png',
alt: 'Tabla de tallas de collarín cervical',
caption: 'Tabla de referencia para selección de talla según medidas',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'tabla', 'tallas', 'referencia']
},
'collarin-paso-1': {
id: 'collarin-paso-1',
path: '/assets/infografias/bloque-2-inmovilizacion/colocacion-collarin-paso-1-preparacion.png',
alt: 'Paso 1: Preparación para colocación de collarín',
caption: 'Preparación del paciente y material antes de colocar el collarín',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'colocacion', 'paso-1', 'preparacion']
},
'collarin-paso-2': {
id: 'collarin-paso-2',
path: '/assets/infografias/bloque-2-inmovilizacion/colocacion-collarin-paso-2-parte-posterior.png',
alt: 'Paso 2: Colocación de la parte posterior del collarín',
caption: 'Colocación de la parte posterior del collarín cervical',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'colocacion', 'paso-2', 'posterior']
},
'collarin-paso-3': {
id: 'collarin-paso-3',
path: '/assets/infografias/bloque-2-inmovilizacion/colocacion-collarin-paso-3-parte-anterior.png',
alt: 'Paso 3: Colocación de la parte anterior del collarín',
caption: 'Colocación de la parte anterior del collarín cervical',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'colocacion', 'paso-3', 'anterior']
},
'collarin-paso-4': {
id: 'collarin-paso-4',
path: '/assets/infografias/bloque-2-inmovilizacion/colocacion-collarin-paso-4-ajuste-cierres.png',
alt: 'Paso 4: Ajuste de cierres del collarín',
caption: 'Ajuste correcto de los cierres del collarín cervical',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'colocacion', 'paso-4', 'ajuste']
},
'collarin-paso-5': {
id: 'collarin-paso-5',
path: '/assets/infografias/bloque-2-inmovilizacion/colocacion-collarin-paso-5-verificacion.png',
alt: 'Paso 5: Verificación de la colocación del collarín',
caption: 'Verificación de la correcta colocación del collarín cervical',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'colocacion', 'paso-5', 'verificacion']
},
'collarin-paso-6': {
id: 'collarin-paso-6',
path: '/assets/infografias/bloque-2-inmovilizacion/colocacion-collarin-paso-6-liberacion-controlada.png',
alt: 'Paso 6: Liberación controlada del control manual',
caption: 'Liberación controlada del control manual tras verificar el collarín',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'colocacion', 'paso-6', 'liberacion']
},
'collarin-verificaciones': {
id: 'collarin-verificaciones',
path: '/assets/infografias/bloque-2-inmovilizacion/verificaciones-post-colocacion-collarin.png',
alt: 'Verificaciones post-colocación del collarín cervical',
caption: 'Checklist de verificaciones después de colocar el collarín',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'verificaciones', 'checklist', 'post-colocacion']
},
'collarin-errores': {
id: 'collarin-errores',
path: '/assets/infografias/bloque-2-inmovilizacion/errores-frecuentes-collarin-cervical.png',
alt: 'Errores frecuentes en la colocación del collarín cervical',
caption: 'Errores comunes y cómo evitarlos al colocar el collarín',
block: 'bloque-2-inmovilizacion',
tags: ['collarin', 'errores', 'prevencion', 'comunes']
},
// ============================================
// BLOQUE 3: MATERIAL SANITARIO Y OXIGENOTERAPIA
// ============================================
'canulas-guedel-nasofaringea': {
id: 'canulas-guedel-nasofaringea',
path: '/assets/infografias/bloque-3-material-sanitario/canulas-guedel-nasofaringea.png',
alt: 'Cánulas de Guedel y nasofaríngea',
caption: 'Tipos de cánulas de vía aérea: orofaríngea (Guedel) y nasofaríngea',
block: 'bloque-3-material-sanitario',
tags: ['canulas', 'via-aerea', 'guedel', 'nasofaringea']
},
'uso-ambu': {
id: 'uso-ambu',
path: '/assets/infografias/bloque-3-material-sanitario/uso-correcto-ambu.png',
alt: 'Uso correcto de la bolsa-mascarilla (Ambú)',
caption: 'Técnica correcta de uso de la bolsa-mascarilla para ventilación',
block: 'bloque-3-material-sanitario',
tags: ['ambu', 'bvm', 'ventilacion', 'bolsa-mascarilla']
},
'configuracion-fio2-bvm': {
id: 'configuracion-fio2-bvm',
path: '/assets/infografias/bloque-3-material-sanitario/configuracion-maxima-fio2-bolsa-mascarilla.png',
alt: 'Configuración máxima de FiO2 con bolsa-mascarilla',
caption: 'Configuración para obtener máxima FiO2 con bolsa-mascarilla',
block: 'bloque-3-material-sanitario',
tags: ['bvm', 'fio2', 'configuracion', 'oxigenoterapia']
},
'guia-colocacion-oxigenoterapia': {
id: 'guia-colocacion-oxigenoterapia',
path: '/assets/infografias/bloque-0-fundamentos/guia-colocacion-dispositivos-oxigenoterapia.png',
alt: 'Guía de colocación de dispositivos de oxigenoterapia',
caption: 'Guía visual para la colocación correcta de dispositivos de oxigenoterapia',
block: 'bloque-3-material-sanitario',
tags: ['oxigenoterapia', 'colocacion', 'dispositivos', 'guia']
},
'uso-tensiometro': {
id: 'uso-tensiometro',
path: '/assets/infografias/bloque-3-material-sanitario/uso-correcto-tensiometro.png',
alt: 'Uso correcto del tensiómetro',
caption: 'Guía de colocación y uso correcto del tensiómetro',
block: 'bloque-1-procedimientos',
tags: ['tensiometro', 'presion', 'ta', 'monitorizacion']
},
};
/**
* Buscar imágenes por bloque temático
*/
export const findImagesByBlock = (block: string): ImageMetadata[] => {
return Object.values(imageRegistry).filter(img => img.block === block);
};
/**
* Buscar imágenes por tags
*/
export const findImagesByTags = (tags: string[]): ImageMetadata[] => {
return Object.values(imageRegistry).filter(img =>
img.tags && tags.some(tag => img.tags?.includes(tag))
);
};
/**
* Buscar imagen por ID (alias)
*/
export const findImageById = (id: string): ImageMetadata | undefined => {
return imageRegistry[id];
};
/**
* Verificar si un ID existe en el registry
*/
export const hasImageId = (id: string): boolean => {
return id in imageRegistry;
};