feat: implementar set de calculadoras clínicas, unificar marca codigo0 y preparar infraestructura MongoDB

This commit is contained in:
Javier 2026-03-22 23:36:00 +01:00
parent 20ae8fb2aa
commit b901b138c5
266 changed files with 30703 additions and 2 deletions

33
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Deploy Código 0
on:
push:
branches: [ main, master ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
cache-dependency-path: './frontend/package-lock.json'
- name: Deploy to VPS via Docker SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }} # Opcional si la clave tiene contraseña
script: |
cd /home/${{ secrets.SSH_USER }}/Proyectos/Proyectos\ en\ Desarrollo/codigo0-nuevo
git pull origin main
docker compose build
docker compose up -d
docker system prune -f # Limpieza opcional de imágenes huérfanas

69
.gitignore vendored Executable file
View file

@ -0,0 +1,69 @@
# Dependencies
node_modules
admin-panel/node_modules/
backend/node_modules/
# Environment variables
.env
.env.local
.env.*.local
backend/.env
# Build outputs
dist
build
.admin-panel/dist
backend/dist
# Runtime data
*.log
logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Coverage directory
coverage
.nyc_output
# Editor directories and files
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Contexto de IA
.ai/
*.ai.md
.ai-assistant.md
# Documentación interna
docs/internal/
# Archivos MD internos específicos
docs/BACKLOG_MICRO_TICKETS.md
docs/QUE_FALTA.md
docs/CONTENIDO_FALTANTE.md
SPEC.md
README_TODO.md
auditoria-assets-completa.md
MEDIOS_REALES_NECESARIOS_FILTRADO.md
# Temporary files
tmp
temp

View file

@ -0,0 +1,175 @@
# INVENTARIO COMPLETO - Proyecto Original vs Nuevo
## Resumen
**Total Bloques:** 18 directorios
**Total Archivos MD:** ~100+
---
## 1. PROTOCOLOS TRANSTELEFÓNICOS (BLOQUE 5)
| # | Archivo MD | YAML | Estado Nuevo |
|---|------------|------|--------------|
| 1 | BLOQUE_05_1_PCR_TRANSTELEFONICA.md | ❌ | Falta |
| 2 | BLOQUE_05_2_OVACE_TRANSTELEFONICA.md | ❌ | Falta |
| 3 | BLOQUE_05_3_SCA_TRANSTELEFONICO.md | ❌ | Falta |
| 4 | BLOQUE_05_4_ICTUS_TRANSTELEFONICO.md | ❌ | Falta |
| 5 | BLOQUE_05_5_ANAFILAXIA_TRANSTELEFONICA.md | ❌ | Falta |
| 6 | BLOQUE_05_6_CRISIS_ASMATICA_TRANSTELEFONICA.md | ❌ | Falta |
| 7 | BLOQUE_05_7_HIPOGLUCEMIA_TRANSTELEFONICA.md | ❌ | Falta |
| 8 | BLOQUE_05_8_COMUNICACION_COORDINADOR.md | ❌ | Falta |
| 9 | BLOQUE_05_9_PROTOCOLOS_EMERGENCIAS_ESPECIFICAS.md | ❌ | Falta |
**→ Total Transtelefónicos: 9 protocolos**
---
## 2. SOPORTE VITAL Y RCP (BLOQUE 4)
| # | Archivo MD | Página Nueva | Estado |
|---|------------|--------------|--------|
| 1 | BLOQUE_04_1_RCP_ADULTOS.md | RCP.tsx | ⚠️ Mockeado |
| 2 | BLOQUE_04_2_RCP_PEDIATRIA.md | RCP.tsx | ❌ Falta |
| 3 | BLOQUE_04_3_RCP_LACTANTES.md | RCP.tsx | ❌ Falta |
| 4 | BLOQUE_04_4_USO_DESA.md | - | ❌ Falta |
| 5 | BLOQUE_04_5_RCP_DOS_INTERVINIENTES.md | - | ❌ Falta |
| 6 | BLOQUE_04_6_OVACE_ADULTOS.md | - | ❌ Falta |
| 7 | BLOQUE_04_7_OVACE_PEDIATRIA.md | - | ❌ Falta |
| 8 | BLOQUE_04_8_OVACE_LACTANTES.md | - | ❌ Falta |
| 9 | BLOQUE_04_9_POSICION_LATERAL_SEGURIDAD.md | - | ❌ Falta |
| 10 | BLOQUE_04_0_RECONOCIMIENTO_PCR.md | - | ❌ Falta |
| 11 | BLOQUE_04_10_ACCESO_VASCULAR_BASICO.md | - | ❌ Falta |
**→ Total Soporte Vital: 11 guías/protocolos**
---
## 3. PROCEDIMIENTOS BÁSICOS (BLOQUE 1)
| # | Archivo MD | Página Nueva | Estado |
|---|------------|--------------|--------|
| 1 | BLOQUE_01_1_CONSTANTES_VITALES.md | Escena.tsx | ❌ Falta |
| 2 | BLOQUE_01_2_ABCDE_OPERATIVO.md | Escena.tsx | ❌ Falta |
| 3 | BLOQUE_01_3_GLASGOW_OPERATIVO.md | Herramientas.tsx | ✅ Calculadora |
| 4 | BLOQUE_01_4_TRIAGE_START.md | - | ❌ Falta |
**→ Total Procedimientos: 4**
---
## 4. MATERIAL E INMOVILIZACIÓN (BLOQUE 2)
| # | Archivo MD | Página Nueva | Estado |
|---|------------|--------------|--------|
| 1 | BLOQUE_02_0_ANATOMIA_OPERATIVA.md | - | ❌ Falta |
| 2 | BLOQUE_02_2_INMOVILIZACION_MANUAL.md | Material.tsx | ❌ Falta |
| 3 | BLOQUE_02_3_COLLARIN_CERVICAL.md | ViaAerea.tsx | ❌ Falta |
| 4 | BLOQUE_02_4_CAMILLA_CUCHARA.md | Material.tsx | ❌ Falta |
| 5 | BLOQUE_02_5_TABLERO_ESPINAL.md | Material.tsx | ❌ Falta |
| 6 | BLOQUE_02_6_COLCHON_VACIO.md | Material.tsx | ❌ Falta |
| 7 | BLOQUE_02_7_EXTRICACION_MOVIMIENTOS_BLOQUE.md | - | ❌ Falta |
| 8 | BLOQUE_02_8_TRANSFERENCIAS_MOVILIZACION.md | - | ❌ Falta |
| 9 | BLOQUE_02_9_ERRORES_CRITICOS.md | - | ❌ Falta |
| 10 | BLOQUE_02_10_FERULAS.md | - | ❌ Falta |
| 11 | BLOQUE_02_11_CINTURON_PELVICO.md | - | ❌ Falta |
| 12 | BLOQUE_02_12_FERULA_TRACCION.md | - | ❌ Falta |
| 13 | BLOQUE_02_13_CAMILLAS_SILLAS_EVACUACION.md | - | ❌ Falta |
| 14 | BLOQUE_02_14_INVENTARIO_MATERIAL.md | - | ❌ Falta |
**→ Total Material: 14**
---
## 5. MATERIAL SANITARIO Y OXIGENOTERAPIA (BLOQUE 3)
| # | Archivo MD | Estado |
|---|------------|--------|
| 1-4 | Oxigenoterapia | ❌ Falta |
| 5-9 | Dispositivos, Aspiración, BVM | ❌ Falta |
| 10-13 | Monitorización, Glucómetro, Termometría | ❌ Falta |
| 14-18 | Bioseguridad, Gestión, Documentación | ❌ Falta |
| 19-24 | Maletines, Inventarios | ❌ Falta |
**→ Total Material Sanitario: ~24**
---
## 6. FARMACOLOGÍA (BLOQUE 6)
| # | Archivo MD | Página Nueva | Estado |
|---|------------|--------------|--------|
| 1 | BLOQUE_06_0_PRINCIPIOS_ADMINISTRACION_FARMACOS.md | - | ❌ Falta |
| 2 | BLOQUE_06_1_VADEMECUM_OPERATIVO.md | Farmacos.tsx | ✅ Existe |
| 3 | BLOQUE_06_2_OXIGENO_ADMINISTRACION_Y_SEGURIDAD.md | - | ❌ Falta |
| 4 | BLOQUE_06_3_ADRENALINA_USO_ANAFILAXIA_Y_RCP.md | - | ❌ Falta |
| 5 | BLOQUE_06_4_ASPIRINA_USO_SCA.md | - | ❌ Falta |
| 6 | BLOQUE_06_5_GLUCAGON_USO_HIPOGLUCEMIA.md | - | ❌ Falta |
| 7 | BLOQUE_06_6_SALBUTAMOL_USO_CRISIS_ASMATICA.md | - | ❌ Falta |
| 8 | BLOQUE_06_7_ABREVIATURAS_TERMINOLOGIA_FARMACOLOGICA.md | - | ❌ Falta |
**→ Total Farmacología: 8**
---
## 7. OTRAS GUÍAS
| Bloque | Área | Archivos |
|--------|------|----------|
| BLOQUE 0 | Fundamentos | 1 |
| BLOQUE 7 | Conducción y Seguridad Vial | 5 |
| BLOQUE 8 | Gestión Operativa | 4 |
| BLOQUE 9 | Medicina Emergencias | 1 |
| BLOQUE 10 | Situaciones Especiales | 1 |
| BLOQUE 11 | Protocolos Trauma | 1 |
| BLOQUE 12 | Marco Legal | 1 |
| BLOQUE 13 | Comunicación | 1 |
| BLOQUE 14 | Seguridad Personal | 1 |
| BLOQUE 15 | Alteraciones Psiquiátricas | 6 |
---
## RESUMEN TOTAL
| Categoría | Total Archivos | En Proyecto Nuevo |
|-----------|----------------|-------------------|
| Transtelefónicos | 9 | 1 (solo rcp-adulto) |
| Soporte Vital/RCP | 11 | 1 (mockeado) |
| Procedimientos Básicos | 4 | 1 parcial |
| Material/Inmovilización | 14 | 1 parcial |
| Material Sanitario | ~24 | 1 parcial |
| Farmacología | 8 | 1 |
| Otros Bloques | ~22 | 0 |
**TOTAL: ~100+ protocolos/guías → En nuevo: ~15 páginas**
---
## RECURSOS VISUALES - IMÁGENES/INFOGRAFÍAS
**Ubicación en original:** `/public/assets/infografias/`
### Por Bloque:
| Bloque | Carpetas | Contenido |
|--------|----------|-----------|
| bloque-0-fundamentos | ✅ | Fundamentos |
| bloque-2-inmovilizacion | ✅ | Collarines, tablas, férulas |
| bloque-3-material-sanitario | ✅ | Material sanitario |
| bloque-4-rcp | ✅ | Algoritmo RCP (svg + png) |
| bloque-7-conduccion | ✅ | Conducción |
| bloque-12-marco-legal | ✅ | Legal |
**Total carpetas con infografías: 6**
### Imágenes específicas bloque-4-rcp:
- algoritmo_rcp_comentado.png
- algoritmo_rcp_comentado.svg
- introduccion_rcp_adulto_svb.png
---
## RECURSOS VISUALES - VIDEOS
**Buscar en archivos MD:** patrones `<video>`, `youtube`, `embed`
*(Pendiente de análisis)*

128
.planning/PHASE-PLAN.md Normal file
View file

@ -0,0 +1,128 @@
# Código0 Nuevo - Plan de Fases para Guías y Protocolos
## Estado Actual (15/03/2026)
### ✅ Completado
- Sistema Visual Protocol Renderer (YAML + componentes React)
- 69 archivos YAML de protocolos creados
- Hook `useProtocol.ts` funcionando
- VisualProtocolRenderer integrado en página Protocolo
- Infografías copiadas (6 carpetas)
---
## 📋 INVENTARIO COMPLETO
### 1. PROTOCOLOS TRANSTELEFÓNICOS (BLOQUE 5) - 13 total
| # | Archivo | YAML | Estado |
|---|---------|------|--------|
| 1 | PCR Adulto | ✅ | Completado |
| 2 | PCR Pediatría | ✅ | Completado |
| 3 | PCR Lactantes | ✅ | Completado |
| 4 | OVACE Adulto | ✅ | Completado |
| 5 | OVACE Lactantes | ✅ | Completado |
| 6 | DESA Teléfono | ✅ | Completado |
| 7 | SCA | ✅ | Completado |
| 8 | Ictus | ✅ | Completado |
| 9 | Anafilaxia | ✅ | Completado |
| 10 | Crisis Asmática | ✅ | Completado |
| 11 | Hipoglucemia | ✅ | Completado |
| 12 | Comunicación Coordinador | ✅ | Completado |
| 13 | RCP Clínica (no telefónico) | ✅ | Ya existía |
### 2. SOPORTE VITAL Y RCP (BLOQUE 4) - 11 total
| # | Archivo | Estado |
|---|---------|--------|
| 1 | RCP Adultos | ✅ Completado |
| 2 | RCP Pediatría | ✅ Completado |
| 3 | RCP Lactantes | ✅ Completado |
| 4 | Uso DESA | ✅ Completado |
| 5 | RCP 2 Intervinientes | ✅ Completado |
| 6 | OVACE Adultos | ✅ Completado |
| 7 | OVACE Pediatría | ✅ Completado |
| 8 | OVACE Lactantes | ✅ Completado |
| 9 | Posición Lateral Seguridad | ✅ Completado |
| 10 | Reconocimiento PCR | ✅ Completado |
| 11 | Acceso Vascular Básico | ✅ Completado |
### 3. PROCEDIMIENTOS BÁSICOS (BLOQUE 1) - 4 total
- Constantes Vitales - ✅ Completado
- ABCDE Operativo - ✅ Completado
- Glasgow - ✅ En calculadoras
- Triage START - ✅ Completado
### 4. MATERIAL E INMOVILIZACIÓN (BLOQUE 2) - 14 total
-Todos faltantes
### 5. MATERIAL SANITARIO (BLOQUE 3) - ~24 total
- Todos faltantes
### 6. FARMACOLOGÍA (BLOQUE 6) - 8 total
- Vademécum operativo - ✅ Existe
### 7. OTROS BLOQUES - ~22 total
- Todos faltantes
---
## RECURSOS VISUALES
### Imágenes/Infografías
**Origen:** `/Proyectos Originales/codigo0/public/assets/infografias/`
| Carpeta | Contenido |
|---------|-----------|
| bloque-0-fundamentos | Fundamentos |
| bloque-2-inmovilizacion | Collarines, tablas, férulas |
| bloque-3-material-sanitario | Material sanitario |
| bloque-4-rcp | Algoritmo RCP (svg + png) |
| bloque-7-conduccion | Conducción |
| bloque-12-marco-legal | Legal |
**Acción:** Copiar a `/frontend/public/assets/infografias/`
### Videos
*Pendiente análisis*
---
## PLAN DE FASES PROPUESTO
### Phase 1: Transtelefónicos (13 protocolos YAML)
**Objetivo:** Crear los protocolos transtelefónicos en YAML
**Estado:** ✅ COMPLETADO (15/03/2026)
### Phase 2: Soporte Vital RCP (11 guías clínicas)
**Objetivo:** Convertir todas las guías de soporte vital a YAML
**Estado:** ✅ COMPLETADO (15/03/2026)
### Phase 3: Procedimientos Básicos (4 guías)
**Objetivo:** ABCDE, Constantes, Glasgow, Triage
**Estado:** ✅ COMPLETADO (15/03/2026)
### Phase 4: Farmacología (8 fármacos)
**Objetivo:** Vademécum completo en YAML
**Estado:** ✅ COMPLETADO (6 fármacos)
### Phase 5: Material e Inmovilización (14 guías)
**Estado:** ✅ COMPLETADO (12 guías)
### Phase 6: Material Sanitario (~24 guías)
**Objetivo:**Oxigenoterapia, dispositivos, monitorización
### Phase 7: Otros Bloques (~22 guías)
**Objetivo:** Conducción, gestión, legal, psiquiatría
### Phase 8: Recursos Visuales - Imágenes
**Objetivo:** Copiar infografías del original
**Estado:** ✅ COMPLETADO (15/03/2026)
### Phase 9: Recursos Visuales - Videos (Optional)
**Objetivo:**Evaluar e implementar videos
---
*Plan creado: 2026-03-15*
*Proyecto: Código0 Nuevo*

160
.planning/ROADMAP.md Normal file
View file

@ -0,0 +1,160 @@
# Código 0 - Roadmap GSD
## Milestone v1.0: Minimum Viable Product
### Phase 01: Infraestructura Core
**Goal:** Sistema de rendering de protocolos y base del frontend
| # | Plan | Status |
|---|------|--------|
| 01-01 | Setup proyecto React+Vite con routing | ✅ |
| 01-02 | Crear VisualProtocolRenderer | ✅ |
| 01-03 | Implementar useProtocol hook | ✅ |
**Goal:** Backend API básica
| # | Plan | Status |
|---|------|--------|
| 01-04 | Setup Express server | ✅ |
| 01-05 | Crear API de protocolos transtelefónicos | ⚠️ partial |
---
### Phase 02: Contenido Clínico - Transtelefónicos
**Goal:** 13 protocolos transtelefónicos en YAML
| # | Plan | Status |
|---|------|--------|
| 02-01 | PCR Adulto/Pediatria/Lactantes | ✅ |
| 02-02 | OVACE Adulto/Pediatria/Lactantes | ✅ |
| 02-03 | SCA, Ictus, Anafilaxia, Crisis Asmática | ✅ |
| 02-04 | Hipoglucemia, DESA, Comunicación Coordinador | ✅ |
---
### Phase 03: Soporte Vital y RCP
**Goal:** 11 guías de soporte vital en YAML
| # | Plan | Status |
|---|------|--------|
| 03-01 | RCP Adultos/Pediatria/Lactantes | ✅ |
| 03-02 | OVACE y uso DESA | ✅ |
| 03-03 | RCP 2 intervinientes, Posición Lateral | ✅ |
| 03-04 | Reconocimiento PCR, Acceso Vascular | ✅ |
---
### Phase 04: Procedimientos Básicos
**Goal:** ABCDE, Constantes, Glasgow, Triage
| # | Plan | Status |
|---|------|--------|
| 04-01 | Constantes Vitales y ABCDE Operativo | ✅ |
| 04-02 | Glasgow (integrado en calculadoras) | ✅ |
| 04-03 | Triage START | ✅ |
---
### Phase 05: Farmacología
**Goal:** Vademécum operativo en YAML
| # | Plan | Status |
|---|------|--------|
| 05-01 | Adrenalina, Aspirina, Salbutamol | ✅ |
| 05-02 | Oxígeno, Glucagon, Principios administración | ✅ |
| 05-03 | Integración en Farmacos.tsx | ✅ |
---
### Phase 06: Material e Inmovilización
**Goal:** 14 guías de inmovilización
| # | Plan | Status |
|---|------|--------|
| 06-01 | Collarines, Tabla Espinal | ✅ |
| 06-02 | Férulas, Inmovilización completa | ✅ |
---
### Phase 07: Material Sanitario
**Goal:** ~24 guías de material sanitario
| # | Plan | Status |
|---|------|--------|
| 07-01 | Oxigenoterapia | ✅ |
| 07-02 | Termometría, Bioseguridad | ✅ |
| 07-03 | Otros dispositivos | 🔄 in progress |
---
### Phase 08: Páginas Esenciales
**Goal:** Páginas que faltan del frontend
| # | Plan | Status |
|---|------|--------|
| 08-01 | Favoritos.tsx + useFavorites | 🔄 pending |
| 08-02 | Historial.tsx + useHistory | 🔄 pending |
| 08-03 | Urgencias.tsx | 🔄 pending |
| 08-04 | Parto.tsx + protocolo YAML | 🔄 pending |
| 08-05 | AvisoLegal + DescargoResponsabilidad | 🔄 pending |
---
### Phase 09: Features Offline y Búsqueda
**Goal:** Funcionalidad offline y búsqueda
| # | Plan | Status |
|---|------|--------|
| 09-01 | useSearch - búsqueda full-text | 🔄 pending |
| 09-02 | useOfflineMode - PWA + caché | 🔄 pending |
| 09-03 | Service Worker | 🔄 pending |
---
### Phase 10: Componentes UI Avanzados
**Goal:** Componentes mejorados
| # | Plan | Status |
|---|------|--------|
| 10-01 | MarkdownViewer | 🔄 pending |
| 10-02 | DecisionTreeViewer | 🔄 pending |
| 10-03 | InfografiaViewer | 🔄 pending |
---
## Milestone v2.0: Contenido Completo
### Phase 11: Bloques 7-15 del Manual
**Goal:** 75+ capítulos restantes
| # | Plan | Status |
|---|------|--------|
| 11-01 | Bloque 7: Conducción y Seguridad Vial | 🔄 pending |
| 11-02 | Bloque 8: Gestión Operativa | 🔄 pending |
| 11-03 | Bloque 9: Medicina de Emergencias | 🔄 pending |
| 11-04 | Bloque 10-15: Restantes | 🔄 pending |
---
### Phase 12: Guías de Refuerzo JSON
**Goal:** 10 guías de refuerzo
| # | Plan | Status |
|---|------|--------|
| 12-01 | Migrar 10 guías JSON desde original | 🔄 pending |
---
### Phase 13: Integración Backend Completa
**Goal:** Conectar frontend completamente al backend
| # | Plan | Status |
|---|------|--------|
| 13-01 | Completar 3 protocolos transtelefónicos faltantes | 🔄 pending |
| 13-02 | API de favoritos e historial | 🔄 pending |
| 13-03 | API de búsqueda | 🔄 pending |
---
*Generado desde PHASE-PLAN.md y codigo0-roadmap-maestro.md*
*Proyecto: Código 0 - codigo0-nuevo*

42
.planning/STATE.md Normal file
View file

@ -0,0 +1,42 @@
# Estado - Código 0
## Project State
**status:** in_progress
**last_updated:** 2026-03-17
## Resumen Ejecutivo
El nuevo codigo0-nuevo tiene la **infraestructura técnica lista** pero le falta contenido clínico y páginas esenciales.
## Métricas
| Métrica | Total | Completado | % |
|---------|-------|------------|---|
| YAMLs de protocolos | ~90 | 69 | 77% |
| Páginas frontend | 26 | 22 | 85% |
| Fases GSD | 13 | 6 | 46% |
## Decisiones
| Decisión | rationale |
|----------|-----------|
| Formato YAML para protocolos | Renderer ya implementado, fácil de mantener |
| Frontend-only con PWA | Sin app nativa, offline-capable |
| No Firebase | VPS con PostgreSQL disponible |
| Backend Express separado | Arquitectura modular |
## Blockers
| Bloqueador | severidad |
|------------|-----------|
| Falta conectar frontend a backend | high |
| No hay persistencia de favoritos/historial | high |
| PWA no implementado | medium |
## Próxima Fase
**Phase 08: Páginas Esenciales** - Las páginas que faltan (Favoritos, Historial, Urgencias, Parto)
---
*Generado automáticamente*

View file

@ -0,0 +1,239 @@
# Architecture
**Analysis Date:** 2026-03-13
## Pattern Overview
**Overall:** Modular Monorepo with Frontend-Backend Separation
**Key Characteristics:**
- **Separation of Concerns**: Frontend and backend are completely separate applications with no shared runtime
- **API-First Design**: Backend exposes RESTful API endpoints consumed by the frontend
- **Component-Based UI**: React application structured by feature domains (clinical, protocols, tools)
- **Lazy Loading**: Heavy pages are lazy-loaded for performance optimization
## Layers
### Backend Layer
**Purpose:** API server providing medical protocol data and authentication
**Location:** `backend/`
**Contains:**
- Express server setup (`src/index.ts`, `src/app.ts`)
- Route handlers (`src/routes/`)
- Business logic/services (`src/services/`)
- Configuration (`src/config.ts`)
- TypeScript types/interfaces
**Depends on:**
- Express framework for HTTP handling
- Mongoose for MongoDB connectivity (configured but placeholder)
- JWT for authentication
- Zod for validation (not yet implemented in routes)
**Used by:**
- Frontend application via HTTP API calls
### Frontend Layer
**Purpose:** React single-page application for medical protocol reference
**Location:** `frontend/src/`
**Contains:**
- Application entry point (`main.tsx`)
- Root component with routing (`App.tsx`)
- Page components (`pages/`)
- UI components (`components/`)
- Layout components (`components/layout/`)
- Services layer (`services/`)
- Types definitions (`types/`)
- Utilities (`utils/`, `hooks/`)
**Depends on:**
- React 18 with hooks
- React Router DOM for navigation
- TailwindCSS for styling
- Next-themes for dark mode
- Lucide React for icons
**Used by:**
- End users via web browser
### Static Site Layer
**Purpose:** Marketing/promotional landing page
**Location:** `promo-site/`
**Contains:**
- Static HTML with inline CSS
- Marketing content and calls-to-action
## Data Flow
### API Request Flow
1. **Frontend Component** triggers data fetch (e.g., `pages/Index.tsx`)
2. **Service Layer** (`frontend/src/services/`) makes HTTP request to backend
3. **Backend Route** (`backend/src/routes/`) receives request
4. **Service Layer** (`backend/src/services/`) processes business logic
5. **Response** returns JSON data to frontend
6. **Component** renders data using React state
### Protocol Data Flow (Example: RCP Protocol)
```
Frontend: Protocol Page
↓ HTTP GET /api/content/:id
Backend: content.ts route
↓ calls telephone-protocols.ts service
Backend: telephone-protocols.ts service
↓ returns TelephoneProtocol object
Backend: JSON response
Frontend: Service layer
↓ state update
Frontend: Component renders protocol steps
```
## State Management
**Frontend State:**
- **Local State**: Component-level state using `useState` for UI interactions (search modal, menu state)
- **URL State**: React Router for navigation and route parameters (`/protocolo/:id`)
- **Theme State**: `next-themes` for dark/light mode persistence
**Backend State:**
- **Configuration**: Environment variables loaded via `dotenv`
- **Session State**: JWT tokens (authentication flow placeholder)
- **Database State**: MongoDB connection (configured but not fully implemented)
## Key Abstractions
### Telephone Protocol Model
**Purpose:** Standardized structure for medical emergency protocols
**Location:** `backend/src/services/telephone-protocols.ts`
**Examples:**
- `rcpTelephoneAdult` - Adult CPR protocol
- `ictusTelephone` - Stroke assessment protocol
- `desaTelephone` - AED usage protocol
**Pattern:**
```typescript
interface TelephoneProtocol {
id: string;
title: string;
category: ProtocolCategory;
steps: ProtocolStep[];
initialAssessment: string[];
importantNotes?: string[];
}
```
### API Route Pattern
**Purpose:** Consistent Express route structure
**Location:** `backend/src/routes/`
**Pattern:**
```typescript
import { Router } from 'express';
const router = Router();
router.get('/', (req, res) => { /* ... */ });
router.get('/:id', (req, res) => { /* ... */ });
router.post('/', (req, res) => { /* ... */ });
export default router;
```
### React Page Pattern
**Purpose:** Consistent page component structure
**Location:** `frontend/src/pages/`
**Pattern:**
```typescript
import { useState } from 'react';
import { Link } from 'react-router-dom';
// ... imports
const PageName = () => {
// State and logic
return (
<div>
{/* JSX */}
</div>
);
};
export default PageName;
```
## Entry Points
### Backend Entry Point
**Location:** `backend/src/index.ts`
**Triggers:** Node.js process execution
**Responsibilities:**
- Create Express app instance
- Read configuration
- Start HTTP server on configured port
- Handle graceful shutdown signals (SIGINT, SIGTERM)
### Frontend Entry Point
**Location:** `frontend/src/main.tsx`
**Triggers:** Browser loads HTML page
**Responsibilities:**
- Initialize React root
- Register Service Worker (production only)
- Render main App component with error boundaries
- Apply console error filtering for browser extensions
### Frontend App Entry
**Location:** `frontend/src/App.tsx`
**Triggers:** React root render
**Responsibilities:**
- Set up ThemeProvider for dark mode
- Configure BrowserRouter with future flags
- Define all application routes with lazy loading
- Layout structure (Header, Main, Footer, BottomNav)
- Modal management (Search, Menu)
## Error Handling
**Strategy:** Layered error handling with graceful degradation
**Patterns:**
**Frontend Error Boundaries:**
- Root-level error display in `main.tsx`
- Suspense fallbacks for lazy-loaded pages
- Console error filtering for non-critical browser extension errors
**Backend Error Handling:**
- Global error handler in `app.ts` (line 51-57)
- 404 handler for undefined routes
- Development mode shows detailed error messages
- Production mode shows generic error messages
## Cross-Cutting Concerns
**Logging:**
- **Backend:** Console logging for server startup, errors, and graceful shutdown
- **Frontend:** Console logging for Service Worker registration, React rendering errors
**Validation:**
- **Backend:** Zod dependency available but not yet integrated into routes
- **Frontend:** No explicit validation layer detected
**Authentication:**
- **Backend:** JWT configuration in `config.ts` with placeholder secret
- **Frontend:** No authentication UI implemented (routes are placeholder)
**Security:**
- **Backend:** Helmet.js for security headers, CORS configuration
- **Frontend:** Service Worker for PWA capabilities (production only)
---
*Architecture analysis: 2026-03-13*

View file

@ -0,0 +1,90 @@
# Technology Stack
**Analysis Date:** 2026-03-13
## Languages
**Primary:**
- TypeScript 5.2.2 - Both backend (`backend/`) and frontend (`frontend/`)
- JavaScript (via transpilation) - Runtime execution
**Secondary:**
- HTML/CSS - Static promo site (`promo-site/`)
## Runtime
**Environment:**
- Node.js (runtime for backend)
- Browser (runtime for frontend)
**Package Manager:**
- npm (lockfiles: `package-lock.json` in both `backend/` and `frontend/`)
- Lockfile: Present
## Frameworks
**Core:**
- Express 4.18.2 - Backend API server (`backend/package.json`)
- React 18.2.0 - Frontend UI library (`frontend/package.json`)
**Testing:**
- Jest 29.6.2 - Unit testing for backend (`backend/package.json`)
- ts-jest - TypeScript support for Jest
**Build/Dev:**
- Vite 4.4.9 - Frontend build tool and dev server (`frontend/package.json`)
- TypeScript 5.2.2 - Type checking and compilation for both layers
- ts-node-dev - Development server for backend (`backend/package.json`)
## Key Dependencies
**Critical (Backend):**
- `express` 4.18.2 - HTTP server framework
- `mongoose` 7.5.0 - MongoDB ODM (configured, partially implemented)
- `jsonwebtoken` 9.0.2 - Authentication tokens
- `bcryptjs` 2.4.3 - Password hashing
- `zod` 3.22.2 - Schema validation (available, not fully utilized)
- `socket.io` 4.7.2 - Real-time communication (installed, usage unclear)
**Critical (Frontend):**
- `react` 18.2.0 - UI library
- `react-router-dom` 6.15.0 - Client-side routing
- `tailwindcss` 3.3.3 - Utility-first CSS framework
- `next-themes` 0.2.1 - Dark mode handling
- `lucide-react` 0.263.1 - Icon library
- `vite` 4.4.9 - Build tool
**Infrastructure:**
- `cors` 2.8.5 - Backend CORS handling
- `helmet` 7.0.0 - Security headers
- `multer` 1.4.5-lts.1 - File upload handling
## Configuration
**Environment:**
- **Backend:** `.env` file in root (not shown in structure, referenced in code)
- Configured in `backend/src/config.ts`
- Key configs: `PORT`, `NODE_ENV`, `CORS_ORIGIN`, `MONGODB_URI`, `JWT_SECRET`, `UPLOADS_DIR`
- **Frontend:** `.env` file in `frontend/` (not read due to security rules, likely Vite env vars)
**Build:**
- **Backend:** `backend/tsconfig.json` - TypeScript configuration
- **Frontend:**
- `frontend/vite.config.ts` - Vite build configuration with path aliases
- `frontend/tailwind.config.ts` - TailwindCSS theme configuration
- `frontend/postcss.config.js` - PostCSS processing
## Platform Requirements
**Development:**
- Node.js environment (backend)
- Modern browser with ES6+ support (frontend)
**Production:**
- Node.js server for backend API
- Static file hosting for frontend (Vite build output)
- Static file hosting for promo site
---
*Stack analysis: 2026-03-13*

View file

@ -9,6 +9,7 @@
"plan_check": true,
"verifier": true,
"nyquist_validation": true,
"auto_advance": true
"auto_advance": true,
"_auto_chain_active": true
}
}
}

View file

@ -0,0 +1,18 @@
# 08-01: Favoritos + useFavorites
## Objective
Crear página Favoritos.tsx y hook useFavorites para persistir y leer favoritos del usuario.
## Requirements
- Hook useFavorites con localStorage/IndexedDB
- Página Favoritos.tsx con listado de protocolos/guías guardados
- Poder agregar/quitar favoritos desde cualquier página
## Deliverable
- `/frontend/src/hooks/useFavorites.ts`
- `/frontend/src/pages/Favoritos.tsx`
## Test Criteria
- [ ] Puedo guardar un protocolo en favoritos
- [ ] Puedo ver la lista de favoritos
- [ ] Persisten al recargar la página

View file

@ -0,0 +1,18 @@
# 08-02: Historial + useHistory
## Objective
Crear página Historial.tsx y hook useHistory para registrar y mostrar últimas consultas.
## Requirements
- Hook useHistory con localStorage/IndexedDB
- Página Historial.tsx con listado de últimos protocolos vistos
- Timestamps para cada entrada
## Deliverable
- `/frontend/src/hooks/useHistory.ts`
- `/frontend/src/pages/Historial.tsx`
## Test Criteria
- [ ] Al ver un protocolo, se registra en historial
- [ ] Muestra los últimos 20 accesos
- [ ] Persisten al recargar la página

View file

@ -0,0 +1,22 @@
# 08-03: Urgencias.tsx
## Objective
Crear página de acceso rápido a protocolos críticos para situaciones de urgencia.
## Requirements
- Página con accesos directos a protocolos más críticos:
- PCR
- OVACE
- Anafilaxia
- SCA
- Ictus
- Shock
- Diseño optimizado para acceso rápido en emergencias
## Deliverable
- `/frontend/src/pages/Urgencias.tsx`
## Test Criteria
- [ ] Muestra los 6 protocolos críticos
- [ ] Acceso rápido con un tap
- [ ] Diseño visible en condiciones de estrés

View file

@ -0,0 +1,18 @@
# 08-04: Parto.tsx + protocolo YAML
## Objective
Crear página Parto.tsx con el protocolo de parto extrahospitalario.
## Requirements
- Protocolo YAML de parto emergencia
- Página Parto.tsx que renderice el protocolo
- Pasos para asistir parto en entorno prehospitalario
## Deliverable
- `/frontend/public/protocols/emergencias/parto-emergencia.yaml` (ya existe)
- `/frontend/src/pages/Parto.tsx`
## Test Criteria
- [ ] Protocolo se renderiza correctamente
- [ ] Todos los pasos visibles
- [ ] Compatible con VisualProtocolRenderer

View file

@ -0,0 +1,17 @@
# 08-05: Avisos Legales
## Objective
Crear páginas de Aviso Legal y Descargo de Responsabilidad médicas.
## Requirements
- AvisoLegal.tsx con texto legal estándar
- DescargoResponsabilidad.tsx con aviso de uso responsable
- Links en pie de página
## Deliverable
- `/frontend/src/pages/AvisoLegal.tsx`
- `/frontend/src/pages/DescargoResponsabilidad.tsx`
## Test Criteria
- [ ] Ambas páginas accesibles desde el menú
- [ ] Contenido apropiado para app médica

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# codigo0
Asistente avanzado de referencia médica para Técnicos de Emergencias Sanitarias (TES).
## 🚀 Características
- **Protocolos Clínicos**: Visualización interactiva de guías de soporte vital.
- **Herramientas Operativas**: Glasgow, Triage START, Superficie Quemada y Dosis Pediátricas.
- **Manual del TES**: Guía técnica completa integrada para consulta rápida.
- **PWA**: Soporte offline total.
## 🛠️ Stack Tecnológico
- **Frontend**: React 18 + Vite + TailwindCSS
- **Backend**: Node.js + Express + TypeScript (Clean Architecture)
- **Base de Datos**: MongoDB (Dockerized)
## 📄 Documentación y Memoria
Para mantener la rama principal limpia y centrada en el código, el seguimiento del proyecto se gestiona en dos ubicaciones:
1. **Git (Rama `docs-memoria`)**: Contiene `PROGRESS.md` y `CONTEXT.md` actualizados con el historial técnico.
2. **Obsidian (Bóveda `99-agentes`)**: Contiene el Plan Maestro, Auditoría de Medios y Contexto Global.
---
*codigo0 — 0 Errores. 0 Dudas.*

30
backend/Dockerfile Normal file
View file

@ -0,0 +1,30 @@
# Etapa 1: Construcción del backend
FROM node:18-alpine AS build-stage
WORKDIR /app
# Instalar dependencias
COPY package*.json ./
RUN npm install
# Copiar el resto de archivos y construir
COPY . .
RUN npm run build
# Etapa 2: Imagen para ejecución
FROM node:18-alpine
WORKDIR /app
# Copiar solo dependencias de producción y archivos construidos
COPY package*.json ./
RUN npm install --production
COPY --from=build-stage /app/dist/ ./dist/
# Exponer el puerto por defecto (3000)
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "dist/index.js"]

6075
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

40
backend/package.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "codigo0-backend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "jest",
"migrate": "ts-node src/migrateYamlToMongo.ts"
},
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"helmet": "^7.0.0",
"js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.2",
"mongoose": "^7.5.0",
"multer": "^1.4.5-lts.1",
"socket.io": "^4.7.2",
"zod": "^3.22.2"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.5",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.2",
"@types/multer": "^1.4.7",
"@types/node": "^20.6.3",
"@types/socket.io": "^3.0.2",
"jest": "^29.6.2",
"ts-jest": "^29.1.1",
"ts-node-dev": "^2.0.0",
"typescript": "^5.2.2"
}
}

60
backend/src/app.ts Normal file
View file

@ -0,0 +1,60 @@
import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import dotenv from 'dotenv';
import authRoutes from './routes/auth';
import protocolRoutes from './infrastructure/http/routes/protocol.routes';
// Load environment variables
dotenv.config();
export function createApp() {
const app = express();
// Security middleware
app.use(helmet());
// CORS configuration
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Health check endpoint
app.get('/health', (_req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
service: 'codigo0-backend',
version: '0.1.0'
});
});
// API routes
app.use('/api/auth', authRoutes);
app.use('/api/content', protocolRoutes);
// 404 handler
app.use((_req, res) => {
res.status(404).json({
error: 'Not Found',
message: 'The requested resource was not found'
});
});
// Error handler
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
console.error(err.stack);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong'
});
});
return app;
}

View file

@ -0,0 +1,10 @@
import { IProtocolRepository } from '../../domain/repositories/IProtocolRepository';
import { TelephoneProtocol } from '../../domain/entities/TelephoneProtocol';
export class GetAllProtocolsUseCase {
constructor(private protocolRepository: IProtocolRepository) {}
async execute(): Promise<TelephoneProtocol[]> {
return this.protocolRepository.findAll();
}
}

View file

@ -0,0 +1,10 @@
import { IProtocolRepository } from '../../domain/repositories/IProtocolRepository';
import { TelephoneProtocol } from '../../domain/entities/TelephoneProtocol';
export class GetProtocolByIdUseCase {
constructor(private protocolRepository: IProtocolRepository) {}
async execute(id: string): Promise<TelephoneProtocol | null> {
return this.protocolRepository.findById(id);
}
}

17
backend/src/config.ts Normal file
View file

@ -0,0 +1,17 @@
import dotenv from 'dotenv';
dotenv.config();
export const config = {
port: parseInt(process.env.PORT || '3000', 10),
nodeEnv: process.env.NODE_ENV || 'development',
corsOrigin: process.env.CORS_ORIGIN || '*',
// Database configuration (to be implemented)
dbUri: process.env.MONGODB_URI || 'mongodb://localhost:27017/codigo0',
// JWT configuration
jwtSecret: process.env.JWT_SECRET || 'your-secret-key-change-in-production',
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
// File upload configuration
uploadsDir: process.env.UPLOADS_DIR || './uploads',
maxFileSize: parseInt(process.env.MAX_FILE_SIZE || '10485760', 10), // 10MB
};

View file

@ -0,0 +1,46 @@
export interface Drug {
nombre: string;
filas: { label: string; valor: string }[];
formula?: string;
}
export interface ProtocolStep {
tipo: 'checklist' | 'decision' | 'tecnica' | 'info';
titulo: string;
descripcion?: string;
items?: string[];
urgencia?: 'critica';
badge?: string;
parametros?: { label: string; valor: string; destacado?: boolean }[];
si?: string;
no?: string;
imagen?: string;
video?: string;
}
export interface ProtocolPhase extends Omit<ProtocolStep, 'titulo'> {
id: string | number;
nombre: string;
icono?: string;
pregunta?: string;
ramas?: { condicion: string; destino: string | number }[];
}
export interface ClinicalProtocol {
id: string; // Slug único
titulo: string;
subtitulo?: string;
categoria: string;
urgencia: 'critica' | 'alta' | 'media' | 'baja';
version: string;
descripcion?: string;
fuente?: string;
actualizado?: string;
alertas?: { tipo: 'warning' | 'danger' | 'info'; texto: string }[];
grupos_edad?: { id: string; label: string; sublabel?: string }[];
parametros_por_grupo?: Record<string, Record<string, string>>;
pasos?: ProtocolStep[];
fases?: ProtocolPhase[];
farmacos?: Drug[];
equipamiento?: string[];
}

View file

@ -0,0 +1,24 @@
export type ProtocolCategory = 'rcp' | 'desa' | 'ovace' | 'sca' | 'ictus' | 'comunicacion';
export type AgeGroup = 'adulto' | 'pediatrico' | 'lactante' | 'todos';
export interface ProtocolStep {
id: string;
order: number;
instruction: string;
verification?: string;
notes?: string;
critical?: boolean;
}
export interface TelephoneProtocol {
id: string;
title: string;
shortTitle: string;
category: ProtocolCategory;
ageGroup: AgeGroup;
description: string;
initialAssessment: string[];
steps: ProtocolStep[];
importantNotes?: string[];
source?: string;
}

View file

@ -0,0 +1,8 @@
import { TelephoneProtocol, ProtocolCategory, AgeGroup } from '../entities/TelephoneProtocol';
export interface IProtocolRepository {
findAll(): Promise<TelephoneProtocol[]>;
findById(id: string): Promise<TelephoneProtocol | null>;
findByCategory(category: ProtocolCategory): Promise<TelephoneProtocol[]>;
findByAgeGroup(ageGroup: AgeGroup): Promise<TelephoneProtocol[]>;
}

29
backend/src/index.ts Normal file
View file

@ -0,0 +1,29 @@
import { createApp } from './app';
import { config } from './config';
const app = createApp();
const PORT = config.port || 3000;
// Start the server
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('SIGINT received: shutting down gracefully');
server.close(() => {
console.log('Closed out remaining connections.');
process.exit(0);
});
});
process.on('SIGTERM', () => {
console.log('SIGTERM received: shutting down gracefully');
server.close(() => {
console.log('Closed out remaining connections.');
process.exit(0);
});
});
export default server;

View file

@ -0,0 +1,79 @@
import mongoose, { Schema, Document } from 'mongoose';
import { ClinicalProtocol } from '../../domain/entities/ClinicalProtocol';
const ProtocolStepSchema = new Schema({
tipo: { type: String, enum: ['checklist', 'decision', 'tecnica', 'info'], required: true },
titulo: { type: String, required: true },
descripcion: String,
items: [String],
urgencia: { type: String, enum: ['critica'] },
badge: String,
parametros: [{
label: String,
valor: String,
destacado: Boolean
}],
si: String,
no: String,
imagen: String,
video: String
}, { _id: false });
const ProtocolPhaseSchema = new Schema({
id: Schema.Types.Mixed,
nombre: { type: String, required: true },
icono: String,
pregunta: String,
ramas: [{
condicion: String,
destino: Schema.Types.Mixed
}],
// Reutilizamos campos de step por consistencia
tipo: String,
descripcion: String,
items: [String],
urgencia: String,
badge: String,
parametros: [{ label: String, valor: String, destacado: Boolean }],
imagen: String,
video: String
}, { _id: false });
const DrugSchema = new Schema({
nombre: { type: String, required: true },
filas: [{ label: String, valor: String }],
formula: String
}, { _id: false });
const ClinicalProtocolSchema = new Schema({
id: { type: String, required: true, unique: true },
titulo: { type: String, required: true },
subtitulo: String,
categoria: { type: String, required: true },
urgencia: { type: String, enum: ['critica', 'alta', 'media', 'baja'], required: true },
version: { type: String, required: true },
descripcion: String,
fuente: String,
actualizado: String,
alertas: [{
tipo: { type: String, enum: ['warning', 'danger', 'info'] },
texto: String
}],
grupos_edad: [{
id: String,
label: String,
sublabel: String
}],
parametros_por_grupo: Schema.Types.Map,
pasos: [ProtocolStepSchema],
fases: [ProtocolPhaseSchema],
farmacos: [DrugSchema],
equipamiento: [String]
}, {
timestamps: true,
collection: 'protocols'
});
export interface ProtocolDocument extends ClinicalProtocol, Document {}
export const ProtocolModel = mongoose.model<ProtocolDocument>('Protocol', ClinicalProtocolSchema);

View file

@ -0,0 +1,41 @@
import { Request, Response } from 'express';
import { GetAllProtocolsUseCase } from '../../../application/usecases/GetAllProtocolsUseCase';
import { GetProtocolByIdUseCase } from '../../../application/usecases/GetProtocolByIdUseCase';
export class ProtocolController {
constructor(
private getAllProtocolsUseCase: GetAllProtocolsUseCase,
private getProtocolByIdUseCase: GetProtocolByIdUseCase
) {}
getAll = async (_req: Request, res: Response): Promise<void> => {
try {
const protocols = await this.getAllProtocolsUseCase.execute();
res.json({
message: 'Telephone protocols list',
data: protocols
});
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
}
};
getById = async (req: Request, res: Response): Promise<void> => {
try {
const protocol = await this.getProtocolByIdUseCase.execute(req.params.id);
if (protocol) {
res.json({
message: 'Protocol found',
data: protocol
});
} else {
res.status(404).json({
message: 'Protocol not found',
id: req.params.id
});
}
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
}
};
}

View file

@ -0,0 +1,34 @@
import { Router } from 'express';
import { ProtocolController } from '../controllers/ProtocolController';
import { GetAllProtocolsUseCase } from '../../../application/usecases/GetAllProtocolsUseCase';
import { GetProtocolByIdUseCase } from '../../../application/usecases/GetProtocolByIdUseCase';
import { StaticProtocolRepository } from '../../persistence/StaticProtocolRepository';
// manual dependency injection for routes
const repository = new StaticProtocolRepository();
const getAllUseCase = new GetAllProtocolsUseCase(repository);
const getByIdUseCase = new GetProtocolByIdUseCase(repository);
const controller = new ProtocolController(getAllUseCase, getByIdUseCase);
const router = Router();
router.get('/', controller.getAll);
router.get('/:id', controller.getById);
// Placeholders for future endpoints matching the original content.ts routes
router.post('/', (_req, res) => {
res.status(201).json({
message: 'Create content endpoint - to be implemented',
id: 'new-content-id'
});
});
router.put('/:id', (req, res) => {
res.json({ message: `Update content ${req.params.id} - to be implemented` });
});
router.delete('/:id', (req, res) => {
res.json({ message: `Delete content ${req.params.id} - to be implemented` });
});
export default router;

View file

@ -0,0 +1,22 @@
import { IProtocolRepository } from '../../domain/repositories/IProtocolRepository';
import { TelephoneProtocol, ProtocolCategory, AgeGroup } from '../../domain/entities/TelephoneProtocol';
import { telephoneProtocols } from '../../services/telephone-protocols';
export class StaticProtocolRepository implements IProtocolRepository {
async findAll(): Promise<TelephoneProtocol[]> {
return telephoneProtocols;
}
async findById(id: string): Promise<TelephoneProtocol | null> {
const protocol = telephoneProtocols.find(p => p.id === id);
return protocol || null;
}
async findByCategory(category: ProtocolCategory): Promise<TelephoneProtocol[]> {
return telephoneProtocols.filter(p => p.category === category);
}
async findByAgeGroup(ageGroup: AgeGroup): Promise<TelephoneProtocol[]> {
return telephoneProtocols.filter(p => p.ageGroup === ageGroup || p.ageGroup === 'todos');
}
}

View file

@ -0,0 +1,59 @@
import mongoose from 'mongoose';
import fs from 'fs';
import path from 'path';
import yaml from 'js-yaml';
import dotenv from 'dotenv';
import { ProtocolModel } from './infrastructure/database/models/ProtocolModel';
import { ClinicalProtocol } from './domain/entities/ClinicalProtocol';
dotenv.config();
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/codigo0';
const PROTOCOLS_DIR = path.join(__dirname, '../../frontend/public/protocols');
async function migrate() {
try {
console.log('🚀 Iniciando migración de protocolos YAML a MongoDB...');
await mongoose.connect(MONGODB_URI);
console.log('✅ Conectado a MongoDB');
const folders = fs.readdirSync(PROTOCOLS_DIR);
let totalMigrated = 0;
for (const folder of folders) {
const folderPath = path.join(PROTOCOLS_DIR, folder);
if (!fs.statSync(folderPath).isDirectory()) continue;
const files = fs.readdirSync(folderPath).filter(f => f.endsWith('.yaml'));
for (const file of files) {
const filePath = path.join(folderPath, file);
const content = fs.readFileSync(filePath, 'utf8');
const data = yaml.load(content) as ClinicalProtocol;
if (!data.id) {
data.id = file.replace('.yaml', '');
}
// Upsert
await ProtocolModel.findOneAndUpdate(
{ id: data.id },
data,
{ upsert: true, new: true, setDefaultsOnInsert: true }
);
console.log(` 📄 Migrado: [${folder}] ${data.titulo} (${data.id})`);
totalMigrated++;
}
}
console.log(`\n🎉 Migración completada. Total: ${totalMigrated} protocolos.`);
} catch (error) {
console.error('❌ Error durante la migración:', error);
} finally {
await mongoose.disconnect();
process.exit();
}
}
migrate();

View file

@ -0,0 +1,36 @@
import { Router } from 'express';
const router = Router();
/**
* POST /api/auth/login
* Login endpoint (placeholder)
*/
router.post('/login', (_req, res) => {
res.json({
message: 'Login endpoint - to be implemented',
// In a real app, this would validate credentials and return a JWT
});
});
/**
* POST /api/auth/logout
* Logout endpoint (placeholder)
*/
router.post('/logout', (_req, res) => {
res.json({
message: 'Logout endpoint - to be implemented'
});
});
/**
* POST /api/auth/refresh
* Refresh token endpoint (placeholder)
*/
router.post('/refresh', (_req, res) => {
res.json({
message: 'Refresh token endpoint - to be implemented'
});
});
export default router;

View file

@ -0,0 +1,67 @@
import { Router } from 'express';
import { telephoneProtocols, getProtocolById } from '../services/telephone-protocols';
const router = Router();
/**
* GET /api/content
* Get all telephone protocols
*/
router.get('/', (_req, res) => {
res.json({
message: 'Telephone protocols list',
data: telephoneProtocols
});
});
/**
* GET /api/content/:id
* Get telephone protocol by ID
*/
router.get('/:id', (req, res) => {
const protocol = getProtocolById(req.params.id);
if (protocol) {
res.json({
message: 'Protocol found',
data: protocol
});
} else {
res.status(404).json({
message: 'Protocol not found',
id: req.params.id
});
}
});
/**
* POST /api/content
* Create new content (placeholder)
*/
router.post('/', (_req, res) => {
res.status(201).json({
message: 'Create content endpoint - to be implemented',
id: 'new-content-id'
});
});
/**
* PUT /api/content/:id
* Update content (placeholder)
*/
router.put('/:id', (_req, res) => {
res.json({
message: `Update content ${_req.params.id} - to be implemented`
});
});
/**
* DELETE /api/content/:id
* Delete content (placeholder)
*/
router.delete('/:id', (_req, res) => {
res.json({
message: `Delete content ${_req.params.id} - to be implemented`
});
});
export default router;

View file

@ -0,0 +1,18 @@
import { Router } from 'express';
const router = Router();
/**
* GET /api/health
* Health check básico
*/
router.get('/', (_req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
service: 'codigo0-backend',
version: '0.1.0'
});
});
export default router;

View file

@ -0,0 +1,671 @@
/**
* Protocolos Transtelefónicos - Manual TES Digital
*
* Este módulo contiene protocolos para guiar a testigos por teléfono
* en situaciones de emergencia (RCP, DESA, OVACE, SCA, Ictus).
*
* Fuente: Manual TES Digital (Bloque 5)
*/
export type ProtocolCategory = 'rcp' | 'desa' | 'ovace' | 'sca' | 'ictus' | 'comunicacion';
export type AgeGroup = 'adulto' | 'pediatrico' | 'lactante' | 'todos';
export interface ProtocolStep {
id: string;
order: number;
instruction: string; // Instrucción para leer al testigo
verification?: string; // Pregunta para verificar que se está haciendo
notes?: string; // Notas para el operador (no se leen al testigo)
critical?: boolean; // Si es crítico, debe verificarse antes de continuar
}
export interface TelephoneProtocol {
id: string;
title: string;
shortTitle: string;
category: ProtocolCategory;
ageGroup: AgeGroup;
description: string;
initialAssessment: string[]; // Preguntas de evaluación inicial
steps: ProtocolStep[];
importantNotes?: string[]; // Notas importantes para el operador
source?: string;
}
/**
* Protocolo: RCP Transtelefónica - Adultos
* Fuente: BLOQUE_04_1_RCP_ADULTOS.md (adaptado para guía telefónica)
*/
export const rcpTelephoneAdult: TelephoneProtocol = {
id: 'rcp-telephone-adult',
title: 'RCP Transtelefónica - Adultos',
shortTitle: 'RCP Adulto',
category: 'rcp',
ageGroup: 'adulto',
description: 'Guía paso a paso para que un testigo realice RCP en un adulto mientras llega la ambulancia.',
initialAssessment: [
'¿Está la persona consciente? ¿Responde si le habla o le toca?',
'¿Está respirando normalmente? (No cuente boqueadas o respiraciones muy lentas)',
'¿Es seguro acercarse? (No hay riesgo de tráfico, electricidad, fuego, violencia)',
],
steps: [
{
id: 'activar-112',
order: 1,
instruction: 'Primero, active el servicio de emergencias. ¿Ya ha llamado al 112?',
verification: 'Confirme que ha llamado al 112 o que alguien más lo está haciendo',
critical: true,
notes: 'Si no ha llamado, guíe para que llame primero o que otra persona llame',
},
{
id: 'colocar-superficie',
order: 2,
instruction: 'Coloque a la persona boca arriba sobre una superficie firme y plana, como el suelo.',
verification: '¿Está la persona boca arriba sobre una superficie firme?',
critical: true,
},
{
id: 'abrir-via-aerea',
order: 3,
instruction: 'Incline suavemente la cabeza hacia atrás y levante la barbilla. Esto abre la vía aérea.',
verification: '¿Puede ver que la cabeza está inclinada hacia atrás y la barbilla levantada?',
notes: 'Si sospecha trauma cervical, indique que no mueva la cabeza',
},
{
id: 'verificar-respiracion',
order: 4,
instruction: 'Acérquese y mire, escuche y sienta si respira normalmente. Hágalo durante 10 segundos máximo.',
verification: '¿Respira normalmente? (No cuente boqueadas o respiraciones muy lentas)',
critical: true,
},
{
id: 'iniciar-compresiones',
order: 5,
instruction: 'Si NO respira normalmente, coloque el talón de una mano en el centro del pecho, entre los pezones. Coloque la otra mano encima y entrelace los dedos.',
verification: '¿Tiene las manos colocadas correctamente en el centro del pecho?',
critical: true,
},
{
id: 'compresiones-tecnica',
order: 6,
instruction: 'Con los brazos rectos y los hombros sobre las manos, comprima el pecho hacia abajo unos 5-6 centímetros. Deje que el pecho vuelva completamente arriba después de cada compresión.',
verification: '¿Está comprimiendo hacia abajo y dejando que el pecho vuelva completamente arriba?',
critical: true,
notes: 'La profundidad y el recoil completo son críticos',
},
{
id: 'ritmo-compresiones',
order: 7,
instruction: 'Haga las compresiones a un ritmo rápido y constante, aproximadamente 100-120 por minuto. Puede contar en voz alta: "uno, dos, tres..." hasta "treinta".',
verification: '¿Está haciendo las compresiones a un ritmo rápido y constante?',
critical: true,
notes: 'El ritmo es crítico: demasiado lento o rápido reduce la efectividad',
},
{
id: 'ventilaciones',
order: 8,
instruction: 'Después de 30 compresiones, incline la cabeza hacia atrás, levante la barbilla, pellizque la nariz y dé 2 respiraciones de rescate. Cada respiración debe durar 1 segundo y ver que el pecho se eleva.',
verification: '¿Está dando las respiraciones y ve que el pecho se eleva?',
notes: 'Si no puede o no quiere dar respiraciones, continúe solo con compresiones',
},
{
id: 'continuar-ciclos',
order: 9,
instruction: 'Continúe con ciclos de 30 compresiones seguidas de 2 respiraciones. No pare hasta que llegue la ambulancia o la persona empiece a respirar normalmente.',
verification: '¿Está continuando con los ciclos sin parar?',
critical: true,
},
{
id: 'cambio-reanimador',
order: 10,
instruction: 'Si hay otra persona disponible, pueden turnarse cada 2 minutos para evitar el cansancio. El cambio debe ser rápido, en menos de 5 segundos.',
verification: '¿Hay alguien más que pueda ayudar?',
notes: 'Solo si hay más personas disponibles',
},
],
importantNotes: [
'Si el testigo no puede o no quiere dar respiraciones, es mejor que continúe solo con compresiones que parar',
'Las compresiones continuas sin respiraciones son mejores que no hacer nada',
'No pare para buscar pulso: continúe hasta que llegue la ambulancia o la persona respire normalmente',
'Si la persona empieza a respirar normalmente o se mueve, pare las compresiones y colóquela de lado',
],
source: 'BLOQUE_04_1_RCP_ADULTOS.md',
};
/**
* Protocolo: RCP Transtelefónica - Pediatría
* Fuente: BLOQUE_04_2_RCP_PEDIATRIA.md (adaptado para guía telefónica)
*/
export const rcpTelephonePediatric: TelephoneProtocol = {
id: 'rcp-telephone-pediatric',
title: 'RCP Transtelefónica - Pediatría',
shortTitle: 'RCP Pediátrico',
category: 'rcp',
ageGroup: 'pediatrico',
description: 'Guía paso a paso para que un testigo realice RCP en un niño (1 año hasta pubertad) mientras llega la ambulancia.',
initialAssessment: [
'¿Está el niño consciente? ¿Responde si le habla o le toca?',
'¿Está respirando normalmente? (No cuente boqueadas o respiraciones muy lentas)',
'¿Es seguro acercarse?',
'¿Cuántos años tiene el niño aproximadamente?',
],
steps: [
{
id: 'activar-112-ped',
order: 1,
instruction: 'Primero, active el servicio de emergencias. ¿Ya ha llamado al 112?',
verification: 'Confirme que ha llamado al 112',
critical: true,
notes: 'Si está solo y el colapso no fue presenciado, puede hacer 2 minutos de RCP primero',
},
{
id: 'colocar-superficie-ped',
order: 2,
instruction: 'Coloque al niño boca arriba sobre una superficie firme y plana. Si es pequeño, puede usar una mesa si es más firme que el suelo.',
verification: '¿Está el niño boca arriba sobre una superficie firme?',
critical: true,
},
{
id: 'abrir-via-aerea-ped',
order: 3,
instruction: 'Incline suavemente la cabeza hacia atrás en posición neutra y levante la barbilla. Esto abre la vía aérea.',
verification: '¿Puede ver que la cabeza está en posición neutra y la barbilla levantada?',
},
{
id: 'verificar-respiracion-ped',
order: 4,
instruction: 'Acérquese y mire, escuche y sienta si respira normalmente. Hágalo durante 10 segundos máximo.',
verification: '¿Respira normalmente?',
critical: true,
},
{
id: 'iniciar-compresiones-ped',
order: 5,
instruction: 'Si NO respira normalmente, coloque el talón de una mano (o dos manos si el niño es grande) en el centro del pecho, entre los pezones.',
verification: '¿Tiene la(s) mano(s) colocada(s) correctamente en el centro del pecho?',
critical: true,
notes: 'Una mano para niños pequeños, dos manos para niños grandes/púberes',
},
{
id: 'compresiones-tecnica-ped',
order: 6,
instruction: 'Comprima el pecho hacia abajo aproximadamente un tercio de la profundidad del pecho (unos 5 cm en la mayoría de niños). Deje que el pecho vuelva completamente arriba después de cada compresión.',
verification: '¿Está comprimiendo hacia abajo y dejando que el pecho vuelva completamente arriba?',
critical: true,
},
{
id: 'ritmo-compresiones-ped',
order: 7,
instruction: 'Haga las compresiones a un ritmo rápido y constante, aproximadamente 100-120 por minuto. Cuente en voz alta: "uno, dos, tres..." hasta "quince" (si hay dos personas) o "treinta" (si está solo).',
verification: '¿Está haciendo las compresiones a un ritmo rápido y constante?',
critical: true,
},
{
id: 'ventilaciones-ped',
order: 8,
instruction: 'Después de las compresiones (15 si hay dos personas, 30 si está solo), incline la cabeza, levante la barbilla, y dé 2 respiraciones de rescate cubriendo boca Y nariz del niño. Cada respiración debe durar 1 segundo y ver que el pecho se eleva.',
verification: '¿Está dando las respiraciones cubriendo boca y nariz y ve que el pecho se eleva?',
notes: 'En pediatría, la ventilación es tan importante como las compresiones',
},
{
id: 'continuar-ciclos-ped',
order: 9,
instruction: 'Continúe con ciclos de compresiones y respiraciones. No pare hasta que llegue la ambulancia o el niño empiece a respirar normalmente.',
verification: '¿Está continuando con los ciclos sin parar?',
critical: true,
},
],
importantNotes: [
'En pediatría, la ventilación es tan crítica como las compresiones',
'Si está solo y el colapso no fue presenciado, haga 2 minutos de RCP primero, luego active el 112',
'Si el colapso fue presenciado súbito, active el 112 inmediatamente',
'Las compresiones deben ser proporcionales al tamaño del niño',
],
source: 'BLOQUE_04_2_RCP_PEDIATRIA.md',
};
/**
* Protocolo: DESA Guiado por Teléfono
* Fuente: BLOQUE_04_4_USO_DESA.md (adaptado para guía telefónica)
*/
export const desaTelephone: TelephoneProtocol = {
id: 'desa-telephone',
title: 'DESA Guiado por Teléfono',
shortTitle: 'DESA Teléfono',
category: 'desa',
ageGroup: 'todos',
description: 'Guía paso a paso para que un testigo use un DESA mientras se realiza RCP.',
initialAssessment: [
'¿Hay un DESA disponible cerca?',
'¿Se está realizando RCP en este momento?',
'¿La persona está en una superficie seca y segura?',
],
steps: [
{
id: 'continuar-rcp',
order: 1,
instruction: 'Mientras alguien trae el DESA, continúe con las compresiones torácicas sin parar.',
verification: '¿Se están haciendo compresiones continuamente?',
critical: true,
notes: 'La RCP no debe parar mientras se prepara el DESA',
},
{
id: 'encender-desa',
order: 2,
instruction: 'Cuando tenga el DESA, ábralo o pulse el botón de encendido. El dispositivo le dará instrucciones verbales. Siga sus indicaciones.',
verification: '¿Está encendido el DESA y puede oír las instrucciones?',
critical: true,
},
{
id: 'preparar-torax',
order: 3,
instruction: 'Exponga el pecho de la persona. Si está mojado, séquelo rápidamente con una toalla. Si hay mucho vello, rasure la zona rápidamente si es posible.',
verification: '¿Está el pecho expuesto, seco y sin vello que interfiera?',
notes: 'Solo pare RCP por menos de 10 segundos para esta preparación',
},
{
id: 'colocar-parches',
order: 4,
instruction: 'Retire los parches del DESA. Coloque un parche en el lado derecho del pecho, debajo de la clavícula, junto al esternón. Coloque el otro parche en el lado izquierdo, debajo y a la izquierda del pezón. Presione firmemente cada parche durante 5 segundos.',
verification: '¿Están los parches colocados y bien pegados?',
critical: true,
notes: 'Colocación estándar: esternal (derecho) y apical (izquierdo)',
},
{
id: 'conectar-cable',
order: 5,
instruction: 'Conecte el cable al DESA si no está pre-conectado. Debería encajarse con un clic.',
verification: '¿Está el cable conectado correctamente?',
},
{
id: 'analizar-ritmo',
order: 6,
instruction: 'El DESA le dirá "Alto, analizando ritmo" o similar. En ese momento, pare las compresiones y asegúrese de que NADIE toca a la persona. Grite "¡Todos fuera!" para asegurarse.',
verification: '¿Está todo el mundo alejado de la persona?',
critical: true,
notes: 'Nadie debe tocar durante el análisis',
},
{
id: 'seguir-ordenes',
order: 7,
instruction: 'El DESA analizará el ritmo. Siga exactamente las instrucciones que le dé.',
verification: '¿Está escuchando las instrucciones del DESA?',
critical: true,
},
{
id: 'descarga-si-indicada',
order: 8,
instruction: 'Si el DESA dice "Se recomienda descarga" o similar, confirme en voz alta "DESCARGA RECOMENDADA". Asegúrese de que NADIE toca a la persona. Pulse el botón de descarga (parpadeante) y manténgalo pulsado hasta que se administre.',
verification: '¿Está todo el mundo alejado antes de pulsar el botón de descarga?',
critical: true,
notes: 'Verificación final de seguridad antes de descargar',
},
{
id: 'reanudar-rcp',
order: 9,
instruction: 'Inmediatamente después de la descarga (o si el DESA dice "No se recomienda descarga"), reanude las compresiones torácicas sin demora. Continúe con ciclos de 30 compresiones y 2 respiraciones.',
verification: '¿Ha reanudado las compresiones inmediatamente?',
critical: true,
},
{
id: 'continuar-ciclos-desa',
order: 10,
instruction: 'El DESA le pedirá que pare cada 2 minutos para analizar de nuevo. Siga sus instrucciones. Continúe hasta que llegue la ambulancia o la persona empiece a respirar normalmente.',
verification: '¿Está siguiendo las instrucciones del DESA?',
critical: true,
},
],
importantNotes: [
'NUNCA toque a la persona durante el análisis o la descarga',
'La RCP solo se para para analizar y descargar, no para colocar parches',
'Si el DESA no recomienda descarga, reanude RCP inmediatamente',
'El DESA guiará los ciclos completos de 2 minutos',
],
source: 'BLOQUE_04_4_USO_DESA.md',
};
/**
* Protocolo: OVACE Transtelefónica - Adultos
* Fuente: BLOQUE_04_6_OVACE_ADULTOS.md (adaptado para guía telefónica)
*/
export const ovaceTelephoneAdult: TelephoneProtocol = {
id: 'ovace-telephone-adult',
title: 'OVACE Transtelefónica - Adultos',
shortTitle: 'OVACE Adulto',
category: 'ovace',
ageGroup: 'adulto',
description: 'Guía paso a paso para que un testigo resuelva una obstrucción de vía aérea en un adulto.',
initialAssessment: [
'¿La persona puede hablar o toser?',
'¿Se está llevando las manos al cuello? (signo universal de atragantamiento)',
'¿Está consciente o inconsciente?',
'¿Es una persona obesa o embarazada?',
],
steps: [
{
id: 'evaluar-gravedad',
order: 1,
instruction: 'Primero, evalúe la gravedad. ¿Puede la persona hablar, toser o respirar?',
verification: '¿Puede hablar/toser efectivamente o no puede?',
critical: true,
notes: 'Si puede toser efectivamente: animar a toser, no intervenir',
},
{
id: 'ovace-leve',
order: 2,
instruction: 'Si puede toser con fuerza, anímela a seguir tosiendo. No haga nada más. La tos es el mejor mecanismo para desobstruir.',
verification: '¿Está tosiendo con fuerza?',
notes: 'Solo si la tos es efectiva',
},
{
id: 'ovace-grave-consciente',
order: 3,
instruction: 'Si NO puede hablar, toser o respirar, y está consciente, colóquese de pie detrás de la persona. Rodee su cintura con sus brazos.',
verification: '¿Está detrás de la persona con los brazos alrededor de su cintura?',
critical: true,
notes: 'Solo si está consciente y la obstrucción es grave',
},
{
id: 'heimlich',
order: 4,
instruction: 'Coloque el puño (con el pulgar hacia dentro) en la línea media del abdomen, entre el ombligo y el final del esternón. Agarre su puño con la otra mano.',
verification: '¿Tiene el puño en la posición correcta?',
critical: true,
},
{
id: 'compresiones-abdominales',
order: 5,
instruction: 'Aplique compresiones rápidas hacia dentro y hacia arriba, como si intentara levantar a la persona. Haga 5 compresiones separadas y distintas.',
verification: '¿Está haciendo compresiones hacia dentro y hacia arriba?',
critical: true,
},
{
id: 'reevaluar',
order: 6,
instruction: 'Después de 5 compresiones, pregunte: "¿Ha salido? ¿Puede respirar?" Si puede hablar/toser efectivamente, pare. Si no, repita las 5 compresiones.',
verification: '¿Puede ahora hablar o toser efectivamente?',
critical: true,
},
{
id: 'si-inconsciente',
order: 7,
instruction: 'Si la persona pierde el conocimiento, guíela al suelo con control. Active el 112 si no lo ha hecho. Inicie RCP inmediatamente (ver protocolo RCP).',
verification: '¿Está inconsciente? ¿Ha activado el 112?',
critical: true,
notes: 'Si pierde consciencia, tratar como PCR y seguir protocolo RCP',
},
{
id: 'embarazada-obeso',
order: 8,
instruction: 'Si la persona está embarazada o es obesa, haga compresiones TORÁCICAS (sobre el esternón) en lugar de abdominales. Use la misma técnica pero sobre el pecho.',
verification: '¿Está haciendo compresiones sobre el pecho en lugar del abdomen?',
notes: 'Solo si está embarazada o es obesa',
},
],
importantNotes: [
'Si puede toser efectivamente: NO intervenir, animar a toser',
'Si está inconsciente: iniciar RCP inmediatamente',
'En embarazadas/obesos: usar compresiones torácicas, no abdominales',
'Después de desobstruir, siempre evaluar y considerar traslado',
],
source: 'BLOQUE_04_6_OVACE_ADULTOS.md',
};
/**
* Protocolo: SCA Transtelefónico
* Fuente: Manual TES Digital (Bloque 5.6)
*/
export const scaTelephone: TelephoneProtocol = {
id: 'sca-telephone',
title: 'Sospecha de Síndrome Coronario Agudo (SCA)',
shortTitle: 'SCA',
category: 'sca',
ageGroup: 'adulto',
description: 'Reconocimiento de síntomas de SCA y primeros auxilios mientras llega la ambulancia.',
initialAssessment: [
'¿Tiene dolor en el pecho? ¿Cómo lo describe? (presión, opresión, ardor)',
'¿El dolor se extiende a brazos, cuello, mandíbula o espalda?',
'¿Tiene dificultad para respirar?',
'¿Tiene náuseas, sudoración o mareo?',
'¿Tiene antecedentes de problemas cardíacos?',
],
steps: [
{
id: 'activar-112-sca',
order: 1,
instruction: 'Active el servicio de emergencias inmediatamente. Llame al 112 y explique que sospecha un problema cardíaco.',
verification: '¿Ha llamado al 112?',
critical: true,
},
{
id: 'posicion-comoda',
order: 2,
instruction: 'Coloque a la persona en una posición cómoda, preferiblemente semisentada o recostada con la cabeza elevada. Evite que se acueste completamente plano.',
verification: '¿Está en una posición cómoda?',
},
{
id: 'aflojar-ropa',
order: 3,
instruction: 'Afloje cualquier ropa ajustada, especialmente alrededor del cuello y el pecho.',
verification: '¿Está la ropa aflojada?',
},
{
id: 'aspirina-si-protocolo',
order: 4,
instruction: 'Si la persona está consciente, no es alérgica a la aspirina, y su protocolo local lo contempla, puede darle una aspirina (300 mg) para masticar. NO la trague entera.',
verification: '¿Puede tomar aspirina? (Consulte si es alérgica)',
notes: 'Solo si protocolo local contempla y no es alérgica',
},
{
id: 'monitorizar',
order: 5,
instruction: 'Mantenga a la persona tranquila y monitorice su estado. Si pierde el conocimiento o deja de respirar, inicie RCP (ver protocolo RCP).',
verification: '¿Está consciente y respirando?',
critical: true,
},
{
id: 'preparar-para-traslado',
order: 6,
instruction: 'Prepare a la persona para el traslado. No le dé de comer ni beber. Mantenga la calma y espere a la ambulancia.',
verification: '¿Está preparado para cuando llegue la ambulancia?',
},
],
importantNotes: [
'El tiempo es crítico: activar 112 inmediatamente',
'No dar aspirina si es alérgica o si protocolo local no lo contempla',
'Si pierde consciencia o deja de respirar, iniciar RCP',
'Mantener a la persona tranquila y en posición cómoda',
],
source: 'BLOQUE_05_6_SCA_TRANSTELEFONICO.md',
};
/**
* Protocolo: Ictus Transtelefónico - COMPLETO
* Fuente: Capítulo 5.4 - Protocolo de Actuación Transtelefónica para Sospecha de Ictus Agudo
* Versión: Expandida y detallada
*/
export const ictusTelephone: TelephoneProtocol = {
id: 'ictus-telephone',
title: 'Protocolo de Actuación Transtelefónica para Sospecha de Ictus Agudo',
shortTitle: 'Ictus Transtelefónico',
category: 'ictus',
ageGroup: 'adulto',
description: 'Protocolo completo para evaluación telefónica de sospecha de ictus agudo usando FAST/BE-FAST, determinación de urgencia, medidas de seguridad y preparación para traslado.',
initialAssessment: [
'¿Cuándo empezaron los síntomas? ¿A qué hora fue la ÚLTIMA VEZ que lo vio completamente normal?',
'¿Puede sonreír normalmente? ¿Tiene la cara caída de un lado? (F - Face)',
'¿Puede levantar ambos brazos? ¿Tiene un brazo débil o caído? (A - Arms)',
'¿Puede hablar con claridad? ¿Tiene dificultad para hablar o habla arrastrado? (S - Speech)',
'¿Tiene antecedentes: hipertensión, diabetes, fibrilación auricular o ictus previo?',
'¿Es diabético? ¿Tiene glucómetro disponible? (para descartar hipoglucemia)',
],
steps: [
{
id: 'paso-0-control-inicial',
order: 0,
instruction: 'PASO 0: CONTROL INICIAL Y ACTIVACIÓN (primeros 30 segundos). Pregunte la dirección COMPLETA: calle, número, piso, puerta y localidad. Confirme puntos de referencia. Pregunte edad aproximada y antecedentes (HTA, diabetes, FA, ictus previo). Mientras evalúa, active el Código Ictus o equivalente. NO cuelgue, manténgase en línea.',
verification: '¿Tiene la ubicación exacta? ¿Ha activado el Código Ictus?',
critical: true,
notes: 'Activación inmediata mientras se evalúa. Mantener línea abierta.',
},
{
id: 'paso-1-fast-face',
order: 1,
instruction: 'PASO 1: EVALUACIÓN FAST - F (FACE/CARA). Instruya: "Pídale que SONRÍA o que enseñe los dientes." Pregunte: "¿Se le cae un lado de la cara? ¿La sonrisa es asimétrica o torcida?"',
verification: '¿Hay asimetría facial clara? (F+)',
critical: true,
notes: 'Positivo si: un lado de la cara no se mueve igual que el otro',
},
{
id: 'paso-1-fast-arms',
order: 2,
instruction: 'PASO 1: EVALUACIÓN FAST - A (ARMS/BRAZOS). Instruya: "Que levante los DOS brazos al frente, con las palmas hacia arriba y los ojos cerrados." Pregunte: "¿Se le cae uno de los brazos? ¿O no puede levantarlo?"',
verification: '¿Un brazo cae, se desvía hacia abajo, o no puede levantarse? (A+)',
critical: true,
notes: 'Positivo si: un brazo cae, se desvía, o no puede levantarse',
},
{
id: 'paso-1-fast-speech',
order: 3,
instruction: 'PASO 1: EVALUACIÓN FAST - S (SPEECH/HABLA). Instruya: "Que repita una frase simple: \'Hoy hace buen día\'." Pregunte: "¿Habla raro, arrastra las palabras, dice palabras incorrectas o no puede hablar?"',
verification: '¿Hay disartria, afasia o mutismo? (S+)',
critical: true,
notes: 'Positivo si: disartria (arrastra palabras), afasia (palabras incorrectas), o mutismo',
},
{
id: 'paso-1-fast-time',
order: 4,
instruction: 'PASO 1: EVALUACIÓN FAST - T (TIME/TIEMPO) - EL DATO MÁS CRÍTICO. Pregunta OBLIGATORIA: "¿A qué hora fue la ÚLTIMA VEZ QUE LO VIO COMPLETAMENTE NORMAL, sin ningún síntoma?" Si no hay testigo: "¿Cuándo comenzaron exactamente los síntomas?" Si se despertó con síntomas: "¿A qué hora se acostó estando normal?"',
verification: '¿Sabe la hora exacta de última vez visto normal?',
critical: true,
notes: 'INFORMACIÓN MÁS CRÍTICA. Determina ventana terapéutica. Si no lo preguntas, puedes cerrar la puerta al tratamiento.',
},
{
id: 'paso-2-be-fast-balance',
order: 5,
instruction: 'PASO 2: EVALUACIÓN BE-FAST (si FAST es negativo pero persiste sospecha). B (BALANCE/EQUILIBRIO): "¿Está muy mareado o tiene dificultad para mantenerse en pie o caminar?" Clave: Mareo AGUDO + otros signos neurológicos. No mareo aislado.',
verification: '¿Mareo agudo con otros signos neurológicos?',
notes: 'Solo si FAST negativo pero persiste sospecha clínica',
},
{
id: 'paso-2-be-fast-eyes',
order: 6,
instruction: 'PASO 2: EVALUACIÓN BE-FAST - E (EYES/OJOS). "¿Ha perdido visión de repente en un ojo o en parte del campo visual? ¿Ve doble?" Clave: Pérdida visual SÚBITA, no progresiva.',
verification: '¿Pérdida visual súbita?',
notes: 'Solo si FAST negativo pero persiste sospecha clínica',
},
{
id: 'paso-3-determinar-urgencia',
order: 7,
instruction: 'PASO 3: DETERMINACIÓN DE URGENCIA. Si FAST positivo O BE-FAST positivo con inicio <6h: ACTIVAR CÓDIGO ICTUS (Prioridad 1). Objetivos: Puerta-TC <25 min, Puerta-aguja <60 min. Transporte directo a Hospital con Unidad de Ictus.',
verification: '¿Se ha activado Código Ictus? ¿Hospital con Unidad de Ictus notificado?',
critical: true,
notes: 'Criterios: FAST positivo, inicio <6h, déficit neurológico grave, cefalea explosiva + déficit, crisis convulsiva al inicio, anticoagulante con síntomas agudos',
},
{
id: 'paso-4-posicionamiento',
order: 8,
instruction: 'PASO 4: POSICIONAMIENTO SEGURO. Si consciente, estable y sin vómitos: "Siéntelo o reclínelo semisentado, con la cabeza elevada a unos 30°." Si somnoliento o riesgo aspiración: "Póngalo de LADO (posición lateral de seguridad). Si hay un lado afectado, que quede ARRIBA." Si vómitos activos: "Siempre de lado, con la cabeza ligeramente baja." PROHIBICIÓN: NO sentar si somnoliento. NO acostar boca arriba si riesgo aspiración.',
verification: '¿Está en posición segura según su estado?',
critical: true,
notes: 'La posición boca arriba mata por aspiración. La posición lateral salva vías aéreas.',
},
{
id: 'paso-4-no-administracion',
order: 9,
instruction: 'PASO 4: PROHIBICIÓN ABSOLUTA. "NO coma ni beba NADA." (Alto riesgo disfagia/aspiración). "NO tome ninguna medicación por boca, incluida la aspirina." (Hasta determinar si es isquémico o hemorrágico). "NO administre insulina aunque sea diabético." (Solo si glucemia <70 mg/dL confirmada). "NO intente bajarle la tensión arterial." (Salvo TA >220/120 según protocolo).',
verification: '¿Ha indicado claramente NADA por boca?',
critical: true,
notes: 'Nada por boca es absoluto. No aspirina hasta determinar tipo de ictus.',
},
{
id: 'verificar-hipoglucemia',
order: 10,
instruction: 'DIFERENCIAL: HIPOGLUCEMIA (puede simular ictus). Preguntas obligatorias: "¿Es diabético? ¿Tiene un glucómetro disponible? ¿Tiene sudoración fría, temblor, sensación de hambre?" Si hay glucómetro: "Mida la glucemia ahora mismo." Si glucemia <70 mg/dL: Si consciente y traga seguro: "3 terrones de azúcar o zumo pequeño." Si no traga seguro o inconsciente: "NO le dé nada por la boca." NOTA: Corregir hipoglucemia no excluye ictus concurrente. Mantener sospecha.',
verification: '¿Se ha descartado hipoglucemia?',
notes: 'Hipoglucemia puede simular ictus. Verificar pero mantener sospecha si persiste',
},
{
id: 'preparar-entorno',
order: 11,
instruction: 'PREPARACIÓN DEL ENTORNO. "Abra la puerta o deje el acceso libre para los equipos." "Prepare DNI, tarjeta sanitaria y lista de medicación HABITUAL." Especial énfasis: Anticoagulantes (Sintrom, Xarelto, Eliquis, Pradaxa). Antiagregantes (Adiro, Aspirina, Clopidogrel). "Si tiene informes médicos recientes, téngalos a mano." "Un acompañante debe PREPARARSE para ir al hospital."',
verification: '¿Está preparado el entorno y la documentación?',
},
{
id: 'monitorizacion-continua',
order: 12,
instruction: 'MONITORIZACIÓN CONTINUA (cada 2-3 minutos). Preguntas estandarizadas: "¿Sigue CONSCIENTE? ¿Responde a mi voz o a estímulos?" "¿RESPIRA con normalidad? ¿Escucha algún ruido raro?" "¿HA EMPEORADO la debilidad, el adormecimiento o el habla?" "¿TIENE VÓMITOS o el dolor de cabeza se ha hecho más fuerte?"',
verification: '¿Está monitorizando cada 2-3 minutos?',
critical: true,
notes: 'Vigilancia activa hasta llegada de recursos. Detectar deterioro inmediatamente',
},
{
id: 'criterios-deterioro-pcr',
order: 13,
instruction: 'CRITERIOS DE DETERIORO AGUDO (posible transición a PCR). Activar si: Pérdida brusca de consciencia (no responde). Respiración anormal (apnea, boqueos, estridor). Convulsiones activas prolongadas. Cianosis reportada ("se ha puesto morado"). CONDUCTA: "¿Responde si le toca o le habla fuerte? ¿Respira con normalidad?" Si NO responde o NO respira normal: "Póngalo en el suelo, boca arriba, sobre superficie dura. Empiece compresiones en el centro del pecho, fuerte y rápido." Transicionar inmediatamente al Protocolo RCP.',
verification: '¿Está vigilando signos de deterioro?',
critical: true,
notes: 'Si deteriora a PCR, transicionar inmediatamente a protocolo RCP',
},
{
id: 'traspaso-informacion',
order: 14,
instruction: 'INFORMACIÓN CRÍTICA PARA TRASPASO. Estructurar mensaje: HORA ÚLTIMA VEZ NORMAL: "[HH:MM]" o "desconocida". FAST/BE-FAST: Componentes positivos (Ej: "F y S positivos, A negativo"). GRAVEDAD: Breve descripción (Ej: "Paciente consciente pero afásico y con hemiparesia derecha"). ANTECEDENTES: "HTA, FA anticoagulada con Sintrom, diabetes". SÍNTOMAS ASOCIADOS: "Cefalea explosiva, vómitos x2". GLUCEMIA: "120 mg/dL" o "no medida". MEDIDAS TOMADAS: "Posicionado en lateral izquierda, nada por boca, documentación preparada".',
verification: '¿Tiene toda la información crítica para el traspaso?',
critical: true,
notes: 'Mensaje estándar: "Código Ictus activado. Paciente [Edad/Sexo]: Última vez normal: [HH:MM] (hace X horas/minutos). FAST: [F+/A+/S+]. Déficit principal: [descripción]. Antecedentes clave: [lista]. TA actual: [valor] si se conoce. Llegada estimada: [HH:MM]."',
},
],
importantNotes: [
'🔴 TIEMPO ES CEREBRO: Cada minuto sin tratamiento destruye ~1,9 millones de neuronas. La evaluación telefónica debe ser más rápida que la progresión del infarto.',
'🔴 NUNCA olvidar preguntar "¿Última vez visto normal?" - Es la información más crítica.',
'🔴 NUNCA permitir que coma o beba - Riesgo de aspiración muy alto.',
'🔴 NUNCA dejar somnoliento en decúbito supino - Siempre posición lateral.',
'🔴 NUNCA minimizar síntomas atípicos (mareo, visión) especialmente en mayores, diabéticos o hipertensos.',
'🔴 NUNCA retrasar activación del Código Ictus para "confirmar más" - TIEMPO = CEREBRO.',
'🔴 NUNCA administrar o sugerir aspirina u otra medicación por teléfono - No se sabe si es isquémico o hemorrágico.',
'🟢 SIEMPRE activar Código Ictus/prioridad máxima si FAST positivo y inicio <6h.',
'🟢 SIEMPRE preguntar específicamente por medicación anticoagulante - Crítica para manejo hospitalario.',
'🟢 SIEMPRE recomendar posición lateral de seguridad si hay cualquier disminución del nivel de conciencia.',
'🟢 SIEMPRE indicar "Nada por boca" de forma absoluta y clara.',
'🟢 SIEMPRE documentar con precisión: Hora última vez normal, componentes FAST, antecedentes clave.',
'🟢 SIEMPRE preparar traspaso de información estructurado al equipo de ictus o al SVB.',
'🟢 SIEMPRE vigilar continuamente nivel de consciencia y respiración hasta llegada de recursos.',
'FAST negativo NO descarta ictus: Síntomas atípicos en pacientes de riesgo son ictus hasta demostrar lo contrario.',
'El dato del tiempo es vital: La "última vez visto normal" determina la ventana terapéutica.',
],
source: 'Capítulo 5.4 - Protocolo de Actuación Transtelefónica para Sospecha de Ictus Agudo',
};
/**
* Lista de todos los protocolos transtelefónicos disponibles
*/
export const telephoneProtocols: TelephoneProtocol[] = [
rcpTelephoneAdult,
rcpTelephonePediatric,
desaTelephone,
ovaceTelephoneAdult,
scaTelephone,
ictusTelephone,
];
/**
* Obtener un protocolo por ID
*/
export function getProtocolById(id: string): TelephoneProtocol | undefined {
return telephoneProtocols.find((protocol) => protocol.id === id);
}
/**
* Obtener protocolos por categoría
*/
export function getProtocolsByCategory(category: ProtocolCategory): TelephoneProtocol[] {
return telephoneProtocols.filter((protocol) => protocol.category === category);
}
/**
* Obtener protocolos por grupo etario
*/
export function getProtocolsByAgeGroup(ageGroup: AgeGroup): TelephoneProtocol[] {
return telephoneProtocols.filter((protocol) => protocol.ageGroup === ageGroup || protocol.ageGroup === 'todos');
}

17
backend/tsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

50
docker-compose.yml Normal file
View file

@ -0,0 +1,50 @@
version: "3.8"
services:
# Frontend - Puerto 9112
codigo0-frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: codigo0-frontend
restart: always
ports:
- "9112:9112"
networks:
- proxy
environment:
- VITE_API_URL=http://codigo0-backend:3000/api
# Backend - Puerto 3000
codigo0-backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: codigo0-backend
restart: always
ports:
- "3000:3000"
networks:
- proxy
environment:
- PORT=3000
- NODE_ENV=production
- MONGODB_URI=mongodb://mongodb:27017/codigo0
- CORS_ORIGIN=*
# MongoDB
mongodb:
image: mongo:latest
container_name: codigo0-mongodb
restart: always
volumes:
- mongodb_data:/data/db
networks:
- proxy
networks:
proxy:
external: true
volumes:
mongodb_data:

1
frontend/.env.production Normal file
View file

@ -0,0 +1 @@
VITE_API_URL=/api

26
frontend/Dockerfile Normal file
View file

@ -0,0 +1,26 @@
# Etapa 1: Construcción del frontend
FROM node:18-alpine AS build-stage
WORKDIR /app
# Instalar dependencias
COPY package*.json ./
RUN npm install
# Copiar el resto de archivos y construir
COPY . .
RUN npm run build
# Etapa 2: Servidor Nginx para producción
FROM nginx:alpine
# Copiar archivos construidos desde la etapa anterior
COPY --from=build-stage /app/dist/ /usr/share/nginx/html/
# Copiar configuración personalizada de Nginx
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Exponer el puerto configurado en nginx.conf
EXPOSE 9112
CMD ["nginx", "-g", "daemon off;"]

View file

@ -0,0 +1,15 @@
version: "3.8"
services:
codigo0:
build: .
container_name: codigo0
restart: always
ports:
- "9112:9112"
networks:
- proxy
networks:
proxy:
external: true

23
frontend/index.html Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" sizes="192x192" href="/icon-192.png" />
<link rel="apple-touch-icon" href="/icon-512.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#0f172a" />
<meta name="description" content="Referencia rápida de protocolos de emergencias para Técnicos de Emergencias Sanitarias (TES). RCP, OVACE, Ictus, SCA, Shock y más." />
<meta name="keywords" content="emergencias, TES, protocolos, RCP, OVACE, ambulancia, sanitario" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Código 0" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="mobile-web-app-capable" content="yes" />
<link rel="manifest" href="/manifest.json" />
<title>codigo0 — 0 Errores. 0 Dudas.</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

44
frontend/nginx.conf Normal file
View file

@ -0,0 +1,44 @@
server {
listen 9112;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
# Cache estatic assets (JS/CSS/fonts)
location ~* \.(js|css|woff2?|ttf|otf|eot|svg|ico|png|jpg|jpeg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Cache YAMLs de protocolos (corta duración para poder actualizarlos)
location /protocols/ {
expires 1d;
add_header Cache-Control "public, must-revalidate";
try_files $uri =404;
}
# PWA: manifest y service worker sin caché
location ~* (manifest\.json|sw\.js)$ {
expires off;
add_header Cache-Control "no-cache, no-store, must-revalidate";
try_files $uri =404;
}
# SPA fallback todas las rutas sirven index.html
location / {
try_files $uri $uri/ /index.html;
}
# Seguridad básica
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
}

4959
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

35
frontend/package.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "codigo0-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"clsx": "^2.0.0",
"hotkeys-js": "^3.13.4",
"js-yaml": "^4.1.1",
"lucide-react": "^0.263.1",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^6.15.0",
"sonner": "^1.3.1",
"tailwind-merge": "^1.14.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.4",
"autoprefixer": "^10.4.15",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-svgr": "^3.2.0"
}
}

View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" viewBox="0 0 800 600">
<rect width="800" height="600" fill="#f8f9fa"/>
<rect x="20" y="20" width="760" height="560" fill="white" stroke="#dee2e6" stroke-width="2" rx="8"/>
<rect x="40" y="40" width="720" height="80" fill="#dc3545" rx="4"/>
<text x="400" y="85" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="white" text-anchor="middle">ERROR COMUN ABCDE #1</text>
<text x="400" y="160" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#212529" text-anchor="middle">SALTARSE LETRAS</text>
<text x="400" y="220" font-family="Arial, sans-serif" font-size="18" fill="#495057" text-anchor="middle">Pasar de A directamente a C o D sin evaluar B</text>
<circle cx="400" cy="340" r="80" fill="#fee" stroke="#dc3545" stroke-width="3"/>
<text x="400" y="360" font-family="Arial, sans-serif" font-size="60" fill="#dc3545" text-anchor="middle" font-weight="bold">A D</text>
<line x1="370" y1="320" x2="430" y2="380" stroke="#dc3545" stroke-width="4"/>
<line x1="430" y1="320" x2="370" y2="380" stroke="#dc3545" stroke-width="4"/>
<rect x="100" y="450" width="600" height="100" fill="#fff3cd" stroke="#ffc107" stroke-width="2" rx="4"/>
<text x="400" y="485" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#856404" text-anchor="middle">CONSECUENCIA</text>
<text x="400" y="515" font-family="Arial, sans-serif" font-size="15" fill="#856404" text-anchor="middle">Puedes perder una hemorragia masiva (B)</text>
<text x="400" y="540" font-family="Arial, sans-serif" font-size="15" fill="#856404" text-anchor="middle">o una obstruccion de via aerea (A)</text>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View file

@ -0,0 +1,157 @@
<svg width="1200" height="1600" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradientA" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#dc2626;stop-opacity:1" />
<stop offset="100%" style="stop-color:#ea580c;stop-opacity:1" />
</linearGradient>
<linearGradient id="gradientB" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#f59e0b;stop-opacity:1" />
<stop offset="100%" style="stop-color:#eab308;stop-opacity:1" />
</linearGradient>
<linearGradient id="gradientC" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#eab308;stop-opacity:1" />
<stop offset="100%" style="stop-color:#84cc16;stop-opacity:1" />
</linearGradient>
<linearGradient id="gradientD" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#22c55e;stop-opacity:1" />
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:1" />
</linearGradient>
<linearGradient id="gradientE" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#6366f1;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Fondo -->
<rect width="1200" height="1600" fill="#1e293b"/>
<!-- Badge Modo Formación -->
<rect x="50" y="30" width="300" height="50" rx="25" fill="#3b82f6" opacity="0.9"/>
<text x="200" y="60" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="white" text-anchor="middle">Modo Formación / Refuerzo</text>
<!-- Título Principal -->
<text x="600" y="120" font-family="Arial, sans-serif" font-size="36" font-weight="bold" fill="#f1f5f9" text-anchor="middle">ABCDE: Estructura Mental de Priorización</text>
<text x="600" y="160" font-family="Arial, sans-serif" font-size="24" fill="#94a3b8" text-anchor="middle">Lo que mata antes va primero</text>
<!-- Comparación: Lado Izquierdo (Desordenado) -->
<g transform="translate(100, 220)">
<rect x="0" y="0" width="450" height="600" rx="15" fill="#1e293b" stroke="#475569" stroke-width="2"/>
<text x="225" y="40" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#ef4444" text-anchor="middle">Sin Estructura</text>
<text x="225" y="70" font-family="Arial, sans-serif" font-size="18" fill="#f87171" text-anchor="middle">→ Errores de Prioridad</text>
<!-- Elementos caóticos -->
<circle cx="100" cy="150" r="30" fill="#ef4444" opacity="0.6"/>
<text x="100" y="160" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white" text-anchor="middle">?</text>
<circle cx="250" cy="120" r="25" fill="#f59e0b" opacity="0.6"/>
<text x="250" y="130" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="white" text-anchor="middle">!</text>
<circle cx="350" cy="180" r="28" fill="#eab308" opacity="0.6"/>
<text x="350" y="190" font-family="Arial, sans-serif" font-size="15" font-weight="bold" fill="white" text-anchor="middle">?</text>
<circle cx="150" cy="250" r="22" fill="#ef4444" opacity="0.5"/>
<text x="150" y="260" font-family="Arial, sans-serif" font-size="13" font-weight="bold" fill="white" text-anchor="middle">!</text>
<circle cx="300" cy="280" r="26" fill="#f59e0b" opacity="0.5"/>
<text x="300" y="290" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="white" text-anchor="middle">?</text>
<!-- Líneas caóticas -->
<line x1="100" y1="150" x2="250" y2="120" stroke="#64748b" stroke-width="2" opacity="0.4"/>
<line x1="250" y1="120" x2="350" y2="180" stroke="#64748b" stroke-width="2" opacity="0.4"/>
<line x1="350" y1="180" x2="150" y2="250" stroke="#64748b" stroke-width="2" opacity="0.4"/>
<line x1="150" y1="250" x2="300" y2="280" stroke="#64748b" stroke-width="2" opacity="0.4"/>
<!-- Icono de confusión -->
<text x="225" y="450" font-family="Arial, sans-serif" font-size="80" fill="#64748b" opacity="0.5" text-anchor="middle"></text>
</g>
<!-- Comparación: Lado Derecho (Estructurado ABCDE) -->
<g transform="translate(650, 220)">
<rect x="0" y="0" width="450" height="600" rx="15" fill="#1e293b" stroke="#22c55e" stroke-width="3"/>
<text x="225" y="40" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#22c55e" text-anchor="middle">Con Estructura ABCDE</text>
<text x="225" y="70" font-family="Arial, sans-serif" font-size="18" fill="#4ade80" text-anchor="middle">→ Priorización Correcta</text>
<!-- A - Aire/Respiración (Más crítico) -->
<rect x="125" y="100" width="200" height="80" rx="10" fill="url(#gradientA)"/>
<text x="225" y="140" font-family="Arial, sans-serif" font-size="48" font-weight="bold" fill="white" text-anchor="middle">A</text>
<text x="225" y="170" font-family="Arial, sans-serif" font-size="16" fill="white" text-anchor="middle">Aire / Respiración</text>
<!-- Icono pulmones -->
<circle cx="180" cy="130" r="15" fill="white" opacity="0.3"/>
<circle cx="270" cy="130" r="15" fill="white" opacity="0.3"/>
<!-- Flecha descendente -->
<polygon points="225,190 235,210 215,210" fill="#f1f5f9"/>
<!-- B - Circulación -->
<rect x="135" y="220" width="180" height="70" rx="10" fill="url(#gradientB)"/>
<text x="225" y="255" font-family="Arial, sans-serif" font-size="40" font-weight="bold" fill="white" text-anchor="middle">B</text>
<text x="225" y="280" font-family="Arial, sans-serif" font-size="14" fill="white" text-anchor="middle">Circulación</text>
<!-- Icono corazón -->
<path d="M 200 240 Q 200 230, 210 240 Q 220 230, 240 240 Q 240 250, 225 260 Q 210 250, 200 240" fill="white" opacity="0.3"/>
<!-- Flecha descendente -->
<polygon points="225,300 232,315 218,315" fill="#f1f5f9"/>
<!-- C - Cerebro/Consciencia -->
<rect x="145" y="335" width="160" height="65" rx="10" fill="url(#gradientC)"/>
<text x="225" y="370" font-family="Arial, sans-serif" font-size="36" font-weight="bold" fill="white" text-anchor="middle">C</text>
<text x="225" y="390" font-family="Arial, sans-serif" font-size="13" fill="white" text-anchor="middle">Cerebro / Consciencia</text>
<!-- Icono cerebro -->
<ellipse cx="200" cy="360" rx="12" ry="8" fill="white" opacity="0.3"/>
<ellipse cx="250" cy="360" rx="12" ry="8" fill="white" opacity="0.3"/>
<!-- Flecha descendente -->
<polygon points="225,410 230,420 220,420" fill="#f1f5f9"/>
<!-- D - Exposición/Entorno -->
<rect x="155" y="440" width="140" height="60" rx="10" fill="url(#gradientD)"/>
<text x="225" y="470" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white" text-anchor="middle">D</text>
<text x="225" y="490" font-family="Arial, sans-serif" font-size="12" fill="white" text-anchor="middle">Exposición / Entorno</text>
<!-- Icono exposición -->
<rect x="200" y="455" width="50" height="20" rx="3" fill="white" opacity="0.3"/>
<!-- Flecha descendente -->
<polygon points="225,510 228,518 222,518" fill="#f1f5f9"/>
<!-- E - Evaluación Continua -->
<rect x="165" y="535" width="120" height="55" rx="10" fill="url(#gradientE)"/>
<text x="225" y="565" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="white" text-anchor="middle">E</text>
<text x="225" y="580" font-family="Arial, sans-serif" font-size="11" fill="white" text-anchor="middle">Evaluación Continua</text>
<!-- Icono ciclo -->
<circle cx="225" cy="560" r="8" fill="none" stroke="white" stroke-width="2" opacity="0.3"/>
<path d="M 225 552 A 8 8 0 0 1 225 568" fill="none" stroke="white" stroke-width="2" opacity="0.3"/>
</g>
<!-- Texto Explicativo Inferior -->
<rect x="100" y="860" width="1000" height="200" rx="15" fill="#0f172a" stroke="#334155" stroke-width="2"/>
<text x="600" y="900" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#f1f5f9" text-anchor="middle">Estructura Mental de Priorización</text>
<text x="600" y="930" font-family="Arial, sans-serif" font-size="16" fill="#cbd5e1" text-anchor="middle" style="max-width: 900px;">
El ABCDE no es un checklist, sino una estructura mental que organiza tu pensamiento
</text>
<text x="600" y="960" font-family="Arial, sans-serif" font-size="16" fill="#cbd5e1" text-anchor="middle" style="max-width: 900px;">
bajo estrés. Cada nivel debe evaluarse antes de pasar al siguiente.
</text>
<text x="600" y="1000" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#22c55e" text-anchor="middle">Aplicable a TODAS las emergencias prehospitalarias</text>
<!-- Advertencia -->
<rect x="100" y="1100" width="1000" height="120" rx="15" fill="#1e293b" stroke="#f59e0b" stroke-width="2"/>
<text x="600" y="1135" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#fbbf24" text-anchor="middle">⚠ Esta guía es para formación y comprensión</text>
<text x="600" y="1165" font-family="Arial, sans-serif" font-size="16" fill="#fcd34d" text-anchor="middle">Para acción inmediata, consulta el Protocolo Operativo ABCDE</text>
<!-- Badge Modo Operativo (inferior) -->
<rect x="400" y="1250" width="400" height="50" rx="25" fill="#dc2626" opacity="0.9"/>
<text x="600" y="1280" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="white" text-anchor="middle">Modo Operativo</text>
<!-- Contexto visual: Escena de emergencia (sutil) -->
<g opacity="0.2">
<rect x="50" y="1350" width="1100" height="200" rx="10" fill="#334155"/>
<circle cx="200" cy="1450" r="30" fill="#475569"/>
<circle cx="1000" cy="1450" r="25" fill="#475569"/>
<rect x="500" y="1420" width="200" height="60" rx="5" fill="#475569"/>
</g>
<!-- Texto final -->
<text x="600" y="1520" font-family="Arial, sans-serif" font-size="14" fill="#64748b" text-anchor="middle">Guía de Refuerzo - ABCDE Operativo</text>
<text x="600" y="1550" font-family="Arial, sans-serif" font-size="12" fill="#475569" text-anchor="middle">Estructura mental de priorización para emergencias prehospitalarias</text>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1700" viewBox="0 0 1000 1700" role="img" aria-label="Diagrama de flujo START para triaje en emergencias (español)">
<defs>
<style>
:root{
--bg:#f7f9fb;
--ink:#0f172a;
--muted:#334155;
--line:#94a3b8;
--box:#ffffff;
--red:#dc2626;
--yellow:#f59e0b;
--green:#16a34a;
--black:#111827;
--accent:#2563eb;
}
.title{font:800 34px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; fill:var(--ink);}
.subtitle{font:500 16px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; fill:var(--muted);}
.hdr{font:800 20px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; fill:var(--ink);}
.txt{font:500 16px/1.5 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; fill:var(--muted);}
.small{font:500 13px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; fill:var(--muted);}
.box{fill:var(--box); stroke:var(--line); stroke-width:2;}
.boxStrong{fill:var(--box); stroke:var(--accent); stroke-width:2.5;}
.diamond{fill:var(--box); stroke:var(--line); stroke-width:2.5;}
.diamondRed{fill:var(--box); stroke:var(--red); stroke-width:2.8;}
.arrow{stroke:var(--line); stroke-width:3; fill:none; marker-end:url(#arrowHead);}
.arrowAccent{stroke:var(--accent); stroke-width:3.5; fill:none; marker-end:url(#arrowHeadAccent);}
.pillText{font:800 15px/1 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; fill:#fff;}
.legendText{font:700 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; fill:var(--ink);}
.tagRed{fill:var(--red);}
.tagYellow{fill:var(--yellow);}
.tagGreen{fill:var(--green);}
.tagBlack{fill:var(--black);}
.iconStroke{stroke:var(--muted); stroke-width:2.2; fill:none; stroke-linecap:round; stroke-linejoin:round;}
</style>
<marker id="arrowHead" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
<path d="M0,0 L10,5 L0,10 Z" fill="#94a3b8"/>
</marker>
<marker id="arrowHeadAccent" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
<path d="M0,0 L10,5 L0,10 Z" fill="#2563eb"/>
</marker>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#0f172a" flood-opacity="0.12"/>
</filter>
</defs>
<!-- Background -->
<rect x="0" y="0" width="1000" height="1600" fill="var(--bg)"/>
<!-- Header -->
<text x="500" y="70" text-anchor="middle" class="title">Triage START (Simple Triage and Rapid Treatment)</text>
<text x="500" y="105" text-anchor="middle" class="subtitle">Algoritmo de clasificación rápida en incidentes con múltiples víctimas</text>
<!-- Legend -->
<g filter="url(#shadow)">
<rect x="140" y="130" rx="16" ry="16" width="720" height="70" class="box"/>
</g>
<g transform="translate(170,152)">
<rect x="0" y="0" rx="12" ry="12" width="140" height="36" class="tagRed"/>
<text x="70" y="24" text-anchor="middle" class="pillText">ROJO</text>
<text x="160" y="24" class="legendText">Inmediato</text>
<rect x="290" y="0" rx="12" ry="12" width="140" height="36" class="tagYellow"/>
<text x="360" y="24" text-anchor="middle" class="pillText">AMARILLO</text>
<text x="450" y="24" class="legendText">Urgente</text>
<rect x="560" y="0" rx="12" ry="12" width="140" height="36" class="tagGreen"/>
<text x="630" y="24" text-anchor="middle" class="pillText">VERDE</text>
<text x="720" y="24" class="legendText">Leve</text>
</g>
<g transform="translate(170,188)">
<rect x="0" y="0" rx="12" ry="12" width="140" height="30" class="tagBlack"/>
<text x="70" y="20" text-anchor="middle" class="pillText" style="font-size:14px;">NEGRO</text>
<text x="160" y="20" class="legendText">Fallecido / Expectante</text>
</g>
<!-- Start box -->
<g filter="url(#shadow)">
<rect x="220" y="240" rx="18" ry="18" width="560" height="90" class="boxStrong"/>
</g>
<text x="500" y="275" text-anchor="middle" class="hdr">INICIO</text>
<text x="500" y="305" text-anchor="middle" class="txt">Evaluación rápida por víctima (≈ 3060 s). Aplicar medidas básicas si procede.</text>
<path class="arrowAccent" d="M500,330 L500,385"/>
<!-- Decision 1: ¿Camina? -->
<g filter="url(#shadow)">
<polygon class="diamond" points="500,390 645,505 500,620 355,505"/>
</g>
<text x="500" y="495" text-anchor="middle" class="hdr">¿Camina?</text>
<text x="500" y="525" text-anchor="middle" class="small">(deambula por sí mismo)</text>
<!-- YES -> Verde -->
<text x="700" y="500" class="txt" style="fill:var(--green); font-weight:700;"></text>
<path class="arrow" d="M645,505 C735,505 785,505 860,505"/>
<g filter="url(#shadow)">
<rect x="865" y="460" rx="16" ry="16" width="120" height="90" class="box"/>
</g>
<rect x="875" y="472" rx="12" ry="12" width="100" height="30" class="tagGreen"/>
<text x="925" y="493" text-anchor="middle" class="pillText">VERDE</text>
<text x="925" y="525" text-anchor="middle" class="small">Leve</text>
<!-- NO -> next -->
<text x="300" y="500" class="txt" style="font-weight:700;">No</text>
<path class="arrowAccent" d="M500,620 L500,675"/>
<!-- Decision 2: Respiración presente -->
<g filter="url(#shadow)">
<polygon class="diamondRed" points="500,680 660,810 500,940 340,810"/>
</g>
<text x="500" y="800" text-anchor="middle" class="hdr">¿Respira?</text>
<text x="500" y="830" text-anchor="middle" class="small">Si NO: abrir vía aérea</text>
<!-- No breathing -> Negro -->
<text x="695" y="805" class="txt" style="font-weight:700;">No</text>
<path class="arrow" d="M660,810 C745,810 805,810 860,810"/>
<g filter="url(#shadow)">
<rect x="865" y="765" rx="16" ry="16" width="120" height="105" class="box"/>
</g>
<rect x="875" y="777" rx="12" ry="12" width="100" height="30" class="tagBlack"/>
<text x="925" y="798" text-anchor="middle" class="pillText">NEGRO</text>
<text x="925" y="830" text-anchor="middle" class="small">Fallecido</text>
<text x="925" y="852" text-anchor="middle" class="small">/ expectante</text>
<!-- If starts breathing after opening airway -> Rojo -->
<text x="300" y="805" class="txt" style="font-weight:700;"></text>
<path class="arrowAccent" d="M500,940 L500,990"/>
<!-- Decision 3: FR > 30 -->
<g filter="url(#shadow)">
<polygon class="diamond" points="500,995 660,1125 500,1255 340,1125"/>
</g>
<text x="500" y="1115" text-anchor="middle" class="hdr">¿FR &gt; 30 rpm?</text>
<text x="500" y="1145" text-anchor="middle" class="small">(frecuencia respiratoria)</text>
<!-- Yes -> Rojo -->
<text x="695" y="1120" class="txt" style="fill:var(--red); font-weight:700;"></text>
<path class="arrow" d="M660,1125 C745,1125 805,1125 860,1125"/>
<g filter="url(#shadow)">
<rect x="865" y="1080" rx="16" ry="16" width="120" height="90" class="box"/>
</g>
<rect x="875" y="1092" rx="12" ry="12" width="100" height="30" class="tagRed"/>
<text x="925" y="1113" text-anchor="middle" class="pillText">ROJO</text>
<text x="925" y="1145" text-anchor="middle" class="small">Inmediato</text>
<!-- No -> Perfusion -->
<text x="300" y="1120" class="txt" style="font-weight:700;">No</text>
<path class="arrowAccent" d="M500,1255 L500,1310"/>
<!-- Decision 4: Perfusión -->
<g filter="url(#shadow)">
<polygon class="diamond" points="500,1315 660,1445 500,1575 340,1445"/>
</g>
<text x="500" y="1420" text-anchor="middle" class="hdr">Perfusión adecuada?</text>
<text x="500" y="1448" text-anchor="middle" class="small">Relleno capilar ≤ 2 s y pulso radial presente</text>
<!-- If inadequate -> Rojo -->
<text x="695" y="1440" class="txt" style="fill:var(--red); font-weight:700;">No</text>
<path class="arrow" d="M660,1445 C745,1445 805,1445 860,1445"/>
<g filter="url(#shadow)">
<rect x="865" y="1400" rx="16" ry="16" width="120" height="90" class="box"/>
</g>
<rect x="875" y="1412" rx="12" ry="12" width="100" height="30" class="tagRed"/>
<text x="925" y="1433" text-anchor="middle" class="pillText">ROJO</text>
<text x="925" y="1465" text-anchor="middle" class="small">Inmediato</text>
<!-- If adequate -> Mental -->
<text x="300" y="1440" class="txt" style="fill:var(--green); font-weight:700;"></text>
<!-- Mental status box (placed below with connector) -->
<!-- Arrow down from perfusion diamond to mental box -->
<path class="arrowAccent" d="M500,1575 L500,1605"/>
<!-- expand viewBox height already fixed; so place mental box a bit higher within area by shifting diamond up? Not possible.
We'll place mental status box above perfusion diamond and connect from its bottom-left? Instead: add side branch to left below diamond (fits).
-->
<!-- Replace: Side branch to left for mental status -->
<path class="arrowAccent" d="M340,1445 C250,1445 215,1445 170,1445"/>
<g filter="url(#shadow)">
<rect x="20" y="1370" rx="18" ry="18" width="300" height="150" class="box"/>
</g>
<text x="170" y="1405" text-anchor="middle" class="hdr">Estado mental</text>
<text x="170" y="1436" text-anchor="middle" class="small">¿Obedece órdenes?</text>
<text x="170" y="1465" text-anchor="middle" class="small">• No obedece → ROJO</text>
<text x="170" y="1492" text-anchor="middle" class="small">• Obedece → AMARILLO</text>
<!-- From mental box to outcomes -->
<path class="arrow" d="M320,1415 C375,1415 400,1415 430,1415"/>
<text x="345" y="1395" class="small" style="fill:var(--red); font-weight:700;">No</text>
<g filter="url(#shadow)">
<rect x="435" y="1368" rx="16" ry="16" width="120" height="85" class="box"/>
</g>
<rect x="445" y="1380" rx="12" ry="12" width="100" height="30" class="tagRed"/>
<text x="495" y="1401" text-anchor="middle" class="pillText">ROJO</text>
<text x="495" y="1432" text-anchor="middle" class="small">Inmediato</text>
<path class="arrow" d="M320,1478 C380,1478 420,1478 480,1478"/>
<text x="350" y="1502" class="small" style="fill:var(--green); font-weight:700;"></text>
<g filter="url(#shadow)">
<rect x="485" y="1435" rx="16" ry="16" width="140" height="90" class="box"/>
</g>
<rect x="495" y="1447" rx="12" ry="12" width="120" height="30" class="tagYellow"/>
<text x="555" y="1468" text-anchor="middle" class="pillText">AMARILLO</text>
<text x="555" y="1500" text-anchor="middle" class="small">Urgente</text>
<!-- Footer note -->
<text x="500" y="1660" text-anchor="middle" class="small">
Nota: En ausencia de respiración, abrir vía aérea. Si no respira tras apertura → NEGRO; si inicia respiración → ROJO.
</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Some files were not shown because too many files have changed in this diff Show more