# 🏗️ ARQUITECTURA DE BASE DE DATOS Y GESTIÓN DE CONTENIDO - EMERGES TES **Versión:** 1.0 **Fecha:** 2025-01-XX **Estado:** Especificación Técnica - Sin Implementación **Autor:** Arquitecto Senior de Software Sanitario --- ## 🎯 PROPÓSITO DEL DOCUMENTO Este documento define **LA ARQUITECTURA COMPLETA** del sistema de base de datos y gestión de contenido para EMERGES TES. Especifica: - ✅ Modelo de datos PostgreSQL (tablas, relaciones, índices) - ✅ API REST mínima (endpoints, contratos) - ✅ Flujo de sincronización offline-first - ✅ Panel administrador (alcance, funcionalidades) - ✅ Límites de modificación (qué NO tocar) - ✅ Roadmap de implementación por fases **Este sistema permite modificar contenido clínico sin tocar código React de la app operativa.** --- ## 1️⃣ PRINCIPIOS ARQUITECTÓNICOS ### Principio 1: Separación Estricta ``` ┌─────────────────────────────────────┐ │ APP OPERATIVA (PWA - React) │ │ - Solo LEE contenido (pull-only) │ │ - Funciona 100% offline │ │ - NO modifica contenido │ │ - Service Worker intacto │ └─────────────────────────────────────┘ │ │ GET /api/content/* │ (solo lectura) ▼ ┌─────────────────────────────────────┐ │ BACKEND (Node.js + PostgreSQL) │ │ - Almacena contenido versionado │ │ - API REST simple │ │ - Validación clínica │ │ - Historial completo │ └─────────────────────────────────────┘ ▲ │ PUT/POST /api/content/* │ (escritura con auth) │ ┌─────────────────────────────────────┐ │ PANEL ADMINISTRADOR (Web App) │ │ - Edita contenido │ │ - Valida clínicamente │ │ - Gestiona versiones │ │ - App separada │ └─────────────────────────────────────┘ ``` --- ### Principio 2: Offline-First Garantizado **La app operativa:** - ✅ Cachea contenido localmente (IndexedDB) - ✅ Funciona sin red usando cache - ✅ Sincroniza en background cuando hay red - ✅ Nunca falla por falta de conexión **El backend:** - ✅ Siempre disponible (alta disponibilidad) - ✅ Versionado permite rollback - ✅ API idempotente y estable --- ### Principio 3: Validación Clínica Obligatoria **Todo contenido clínico:** - ✅ Debe pasar validación antes de ser público - ✅ Solo contenido `validated` es servido a la app - ✅ Historial completo de validaciones - ✅ Trazabilidad de quién validó y cuándo --- ## 2️⃣ MODELO DE DATOS POSTGRESQL ### 2.1 Esquema General ```sql -- Esquema principal CREATE SCHEMA IF NOT EXISTS emerges_content; -- Extensión para UUIDs CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Extensión para JSONB (ya incluida en PostgreSQL) ``` --- ### 2.2 Tabla: `content_items` (Contenido Principal) **Propósito:** Almacena todos los tipos de contenido (protocolos, guías, manual, fármacos) ```sql CREATE TABLE emerges_content.content_items ( -- Identificación (inmutable) id VARCHAR(100) PRIMARY KEY, -- "rcp-adulto-svb", "adrenalina", etc. type VARCHAR(50) NOT NULL, -- 'protocol' | 'guide' | 'manual' | 'drug' level VARCHAR(50) NOT NULL, -- 'operativo' | 'formativo' | 'referencia' -- Contenido (editable) title VARCHAR(500) NOT NULL, -- Título del contenido short_title VARCHAR(200), -- Título corto (opcional) content JSONB NOT NULL, -- Contenido estructurado (varía por tipo) content_markdown TEXT, -- Contenido Markdown (para guías/manual) -- Metadatos de contenido category VARCHAR(100), -- Categoría (ej: 'soporte_vital', 'cardiovascular') subcategory VARCHAR(100), -- Subcategoría (ej: 'rcp', 'anafilaxia') priority VARCHAR(20), -- 'critico' | 'alto' | 'medio' | 'bajo' age_group VARCHAR(20), -- 'adulto' | 'pediatrico' | 'neonatal' | 'todos' -- Versionado version INTEGER NOT NULL DEFAULT 1, -- Versión actual latest_version INTEGER NOT NULL DEFAULT 1, -- Última versión (para queries rápidas) -- Estado y validación status VARCHAR(20) NOT NULL DEFAULT 'draft', -- 'draft' | 'pending' | 'validated' | 'rejected' | 'archived' validated_by VARCHAR(100), -- ID del validador clínico validated_at TIMESTAMP WITH TIME ZONE, -- Fecha de validación clinical_source VARCHAR(200), -- 'ERC 2021', 'AHA 2020', 'SEMES 2022', etc. quality_score INTEGER, -- 0-100 (opcional) -- Revisión reviewed_by VARCHAR(100), -- ID del revisor reviewed_at TIMESTAMP WITH TIME ZONE, -- Fecha de revisión -- Auditoría created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_by VARCHAR(100) NOT NULL, -- ID del creador updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_by VARCHAR(100) NOT NULL, -- ID del último editor -- Constraints CONSTRAINT chk_type CHECK (type IN ('protocol', 'guide', 'manual', 'drug')), CONSTRAINT chk_level CHECK (level IN ('operativo', 'formativo', 'referencia')), CONSTRAINT chk_status CHECK (status IN ('draft', 'pending', 'validated', 'rejected', 'archived')), CONSTRAINT chk_priority CHECK (priority IN ('critico', 'alto', 'medio', 'bajo') OR priority IS NULL), CONSTRAINT chk_quality_score CHECK (quality_score IS NULL OR (quality_score >= 0 AND quality_score <= 100)) ); -- Índices para performance CREATE INDEX idx_content_items_type ON emerges_content.content_items(type); CREATE INDEX idx_content_items_level ON emerges_content.content_items(level); CREATE INDEX idx_content_items_status ON emerges_content.content_items(status); CREATE INDEX idx_content_items_category ON emerges_content.content_items(category); CREATE INDEX idx_content_items_validated_at ON emerges_content.content_items(validated_at); CREATE INDEX idx_content_items_updated_at ON emerges_content.content_items(updated_at); -- Índice GIN para búsqueda en JSONB CREATE INDEX idx_content_items_content_gin ON emerges_content.content_items USING GIN (content); -- Índice para búsqueda full-text en título CREATE INDEX idx_content_items_title_fts ON emerges_content.content_items USING GIN (to_tsvector('spanish', title)); ``` --- ### 2.3 Tabla: `content_versions` (Historial de Versiones) **Propósito:** Almacena todas las versiones históricas de cada contenido ```sql CREATE TABLE emerges_content.content_versions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), content_id VARCHAR(100) NOT NULL, -- FK a content_items.id version INTEGER NOT NULL, -- Número de versión -- Snapshot del contenido en esta versión content JSONB NOT NULL, content_markdown TEXT, title VARCHAR(500) NOT NULL, -- Metadatos de la versión status VARCHAR(20) NOT NULL, validated_by VARCHAR(100), validated_at TIMESTAMP WITH TIME ZONE, clinical_source VARCHAR(200), -- Cambios en esta versión change_summary TEXT, -- Resumen de cambios changed_fields TEXT[], -- Array de campos modificados -- Auditoría created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_by VARCHAR(100) NOT NULL, -- Constraints CONSTRAINT fk_content_versions_content_id FOREIGN KEY (content_id) REFERENCES emerges_content.content_items(id) ON DELETE CASCADE, CONSTRAINT chk_version_status CHECK (status IN ('draft', 'pending', 'validated', 'rejected', 'archived')), CONSTRAINT uq_content_versions_content_version UNIQUE (content_id, version) ); -- Índices CREATE INDEX idx_content_versions_content_id ON emerges_content.content_versions(content_id); CREATE INDEX idx_content_versions_version ON emerges_content.content_versions(version); CREATE INDEX idx_content_versions_created_at ON emerges_content.content_versions(created_at); ``` --- ### 2.4 Tabla: `content_relations` (Relaciones entre Contenidos) **Propósito:** Define relaciones entre protocolos, guías y manual ```sql CREATE TABLE emerges_content.content_relations ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), source_id VARCHAR(100) NOT NULL, -- ID del contenido origen target_id VARCHAR(100) NOT NULL, -- ID del contenido destino relation_type VARCHAR(50) NOT NULL, -- 'has_guide' | 'has_manual' | 'references' | 'related_to' -- Metadatos is_primary BOOLEAN DEFAULT false, -- Si es relación principal order_index INTEGER, -- Orden si hay múltiples relaciones -- Auditoría created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_by VARCHAR(100) NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), -- Constraints CONSTRAINT fk_content_relations_source FOREIGN KEY (source_id) REFERENCES emerges_content.content_items(id) ON DELETE CASCADE, CONSTRAINT fk_content_relations_target FOREIGN KEY (target_id) REFERENCES emerges_content.content_items(id) ON DELETE CASCADE, CONSTRAINT chk_relation_type CHECK (relation_type IN ('has_guide', 'has_manual', 'references', 'related_to')), CONSTRAINT uq_content_relations_source_target_type UNIQUE (source_id, target_id, relation_type) ); -- Índices CREATE INDEX idx_content_relations_source ON emerges_content.content_relations(source_id); CREATE INDEX idx_content_relations_target ON emerges_content.content_relations(target_id); CREATE INDEX idx_content_relations_type ON emerges_content.content_relations(relation_type); ``` --- ### 2.5 Tabla: `content_change_log` (Log de Cambios) **Propósito:** Registro detallado de todos los cambios realizados ```sql CREATE TABLE emerges_content.content_change_log ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), content_id VARCHAR(100) NOT NULL, version INTEGER NOT NULL, -- Cambio action VARCHAR(20) NOT NULL, -- 'create' | 'update' | 'delete' | 'validate' | 'reject' | 'archive' field_path VARCHAR(200), -- Ruta del campo (ej: 'steps[0]', 'adultDose') old_value JSONB, -- Valor anterior new_value JSONB, -- Valor nuevo -- Contexto change_reason TEXT, -- Razón del cambio comment TEXT, -- Comentario del editor -- Auditoría created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_by VARCHAR(100) NOT NULL, ip_address INET, -- IP del usuario (para auditoría) user_agent TEXT, -- User agent (para auditoría) -- Constraints CONSTRAINT fk_content_change_log_content FOREIGN KEY (content_id) REFERENCES emerges_content.content_items(id) ON DELETE CASCADE, CONSTRAINT chk_change_log_action CHECK (action IN ('create', 'update', 'delete', 'validate', 'reject', 'archive')) ); -- Índices CREATE INDEX idx_content_change_log_content_id ON emerges_content.content_change_log(content_id); CREATE INDEX idx_content_change_log_version ON emerges_content.content_change_log(version); CREATE INDEX idx_content_change_log_created_at ON emerges_content.content_change_log(created_at); CREATE INDEX idx_content_change_log_created_by ON emerges_content.content_change_log(created_by); ``` --- ### 2.6 Tabla: `users` (Usuarios del Panel) **Propósito:** Gestión de usuarios y roles para el panel administrador ```sql CREATE TABLE emerges_content.users ( id VARCHAR(100) PRIMARY KEY, -- ID único (ej: "dr-juan-perez") email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(200) NOT NULL, role VARCHAR(50) NOT NULL, -- 'admin_clinico' | 'editor_docente' | 'editor_clinico' | 'revisor' -- Autenticación password_hash VARCHAR(255) NOT NULL, -- Hash bcrypt salt VARCHAR(255) NOT NULL, -- Estado is_active BOOLEAN DEFAULT true, last_login TIMESTAMP WITH TIME ZONE, -- Metadatos created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_by VARCHAR(100), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), -- Constraints CONSTRAINT chk_user_role CHECK (role IN ('admin_clinico', 'editor_docente', 'editor_clinico', 'revisor')) ); -- Índices CREATE INDEX idx_users_email ON emerges_content.users(email); CREATE INDEX idx_users_role ON emerges_content.users(role); CREATE INDEX idx_users_active ON emerges_content.users(is_active); ``` --- ### 2.7 Tabla: `content_sync_status` (Estado de Sincronización) **Propósito:** Trackear qué versiones de contenido han sido sincronizadas con las apps ```sql CREATE TABLE emerges_content.content_sync_status ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), content_id VARCHAR(100) NOT NULL, version INTEGER NOT NULL, -- Sincronización synced_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), sync_count INTEGER DEFAULT 1, -- Número de veces sincronizado -- Constraints CONSTRAINT fk_content_sync_status_content FOREIGN KEY (content_id) REFERENCES emerges_content.content_items(id) ON DELETE CASCADE, CONSTRAINT uq_content_sync_status_content_version UNIQUE (content_id, version) ); -- Índices CREATE INDEX idx_content_sync_status_content_id ON emerges_content.content_sync_status(content_id); CREATE INDEX idx_content_sync_status_synced_at ON emerges_content.content_sync_status(synced_at); ``` --- ### 2.8 Vistas Útiles ```sql -- Vista: Contenido validado (solo para lectura pública) CREATE VIEW emerges_content.v_validated_content AS SELECT id, type, level, title, short_title, content, content_markdown, category, subcategory, priority, age_group, version, validated_at, clinical_source, updated_at FROM emerges_content.content_items WHERE status = 'validated' ORDER BY updated_at DESC; -- Vista: Contenido pendiente de validación (para panel admin) CREATE VIEW emerges_content.v_pending_validation AS SELECT ci.*, u.name as created_by_name, u.email as created_by_email FROM emerges_content.content_items ci LEFT JOIN emerges_content.users u ON ci.created_by = u.id WHERE ci.status = 'pending' ORDER BY ci.updated_at ASC; ``` --- ## 3️⃣ API REST MÍNIMA ### 3.1 Principios de Diseño **La API es:** - ✅ RESTful simple - ✅ Solo lectura para la app operativa - ✅ Escritura solo con autenticación - ✅ Versionada (v1) - ✅ Documentada (OpenAPI/Swagger) --- ### 3.2 Endpoints Públicos (App Operativa) #### GET `/api/v1/content/protocols` **Propósito:** Obtener todos los protocolos validados **Query params:** - `level` (opcional): Filtrar por nivel ('operativo') - `category` (opcional): Filtrar por categoría - `status` (opcional): Por defecto 'validated' **Response:** ```json { "data": [ { "id": "rcp-adulto-svb", "type": "protocol", "level": "operativo", "title": "RCP Adulto - Soporte Vital Básico", "shortTitle": "RCP Adulto SVB", "content": { "steps": [...], "warnings": [...], "keyPoints": [...], "equipment": [...], "drugs": [...] }, "version": 3, "updatedAt": "2025-01-15T10:00:00Z" } ], "meta": { "total": 5, "version": "v1" } } ``` --- #### GET `/api/v1/content/protocols/:id` **Propósito:** Obtener un protocolo específico **Response:** ```json { "data": { "id": "rcp-adulto-svb", "type": "protocol", "level": "operativo", "title": "RCP Adulto - Soporte Vital Básico", "content": {...}, "version": 3, "validatedAt": "2025-01-15T10:00:00Z", "clinicalSource": "ERC 2021" } } ``` --- #### GET `/api/v1/content/drugs` **Propósito:** Obtener todos los fármacos validados **Response:** Similar a protocols --- #### GET `/api/v1/content/drugs/:id` **Propósito:** Obtener un fármaco específico --- #### GET `/api/v1/content/guides/:id/sections/:sectionNumber` **Propósito:** Obtener una sección específica de una guía **Response:** ```json { "data": { "guideId": "rcp-adulto-svb", "sectionNumber": 1, "title": "Introducción y Contexto", "contentMarkdown": "# SECCIÓN 1: Introducción...", "version": 2, "updatedAt": "2025-01-10T08:00:00Z" } } ``` --- #### GET `/api/v1/content/manual/blocks/:blockId` **Propósito:** Obtener un bloque del manual --- #### GET `/api/v1/content/relations/:id` **Propósito:** Obtener relaciones de un contenido **Response:** ```json { "data": { "sourceId": "rcp-adulto-svb", "relations": [ { "targetId": "rcp-adulto-svb", "targetType": "guide", "relationType": "has_guide", "isPrimary": true }, { "targetId": "bloque-04-1", "targetType": "manual", "relationType": "has_manual", "isPrimary": true } ] } } ``` --- #### GET `/api/v1/content/versions/:id` **Propósito:** Obtener historial de versiones de un contenido **Query params:** - `limit` (opcional): Número de versiones (default: 10) **Response:** ```json { "data": { "contentId": "rcp-adulto-svb", "currentVersion": 3, "versions": [ { "version": 3, "status": "validated", "validatedAt": "2025-01-15T10:00:00Z", "validatedBy": "dr-juan-perez", "createdAt": "2025-01-15T09:00:00Z" }, { "version": 2, "status": "validated", "validatedAt": "2025-01-10T08:00:00Z", "createdAt": "2025-01-10T07:00:00Z" } ] } } ``` --- #### GET `/api/v1/sync/check` **Propósito:** Verificar si hay actualizaciones disponibles **Query params:** - `lastSync` (requerido): Timestamp ISO 8601 de última sincronización **Response:** ```json { "data": { "hasUpdates": true, "updatedItems": [ { "id": "rcp-adulto-svb", "type": "protocol", "version": 3, "updatedAt": "2025-01-15T10:00:00Z" } ], "serverTime": "2025-01-20T12:00:00Z" } } ``` --- ### 3.3 Endpoints Protegidos (Panel Admin) #### POST `/api/v1/auth/login` **Body:** ```json { "email": "admin@emergestes.es", "password": "***" } ``` **Response:** ```json { "data": { "token": "jwt-token-here", "user": { "id": "admin-001", "name": "Dr. Juan Pérez", "role": "admin_clinico" } } } ``` --- #### PUT `/api/v1/content/protocols/:id` **Headers:** - `Authorization: Bearer {jwt-token}` **Body:** ```json { "title": "RCP Adulto - Soporte Vital Básico (Actualizado)", "content": { "steps": [...], "warnings": [...] }, "changeReason": "Actualización según ERC 2021" } ``` **Response:** ```json { "data": { "id": "rcp-adulto-svb", "version": 4, "status": "draft", "updatedAt": "2025-01-20T12:00:00Z" } } ``` --- #### POST `/api/v1/content/protocols/:id/validate` **Propósito:** Solicitar validación clínica **Body:** ```json { "clinicalSource": "ERC 2021", "comment": "Validado según guías ERC 2021" } ``` --- #### POST `/api/v1/content/protocols/:id/approve` **Propósito:** Aprobar contenido (solo admin_clinico) **Body:** ```json { "qualityScore": 95, "comment": "Contenido validado y aprobado" } ``` --- #### POST `/api/v1/content/protocols/:id/reject` **Propósito:** Rechazar contenido (solo admin_clinico) **Body:** ```json { "reason": "Dosis incorrecta según última guía", "comment": "Revisar dosis de adrenalina" } ``` --- #### GET `/api/v1/admin/content/pending` **Propósito:** Obtener contenido pendiente de validación --- #### GET `/api/v1/admin/audit/log` **Propósito:** Obtener log de auditoría **Query params:** - `contentId` (opcional) - `userId` (opcional) - `action` (opcional) - `from` (opcional): Fecha inicio - `to` (opcional): Fecha fin --- ## 4️⃣ FLUJO DE SINCRONIZACIÓN OFFLINE-FIRST ### 4.1 Arquitectura de Sincronización ``` ┌─────────────────────────────────────────┐ │ APP OPERATIVA (PWA) │ │ │ │ 1. Al iniciar: │ │ - Cargar cache local (IndexedDB) │ │ - Verificar actualizaciones │ │ │ │ 2. Si hay red: │ │ - GET /api/v1/sync/check │ │ - Si hay updates: │ │ * Descargar contenido nuevo │ │ * Actualizar cache local │ │ * Notificar usuario (opcional) │ │ │ │ 3. Si NO hay red: │ │ - Usar cache local │ │ - Funcionar normalmente │ │ │ │ 4. En background (si hay red): │ │ - Verificar actualizaciones │ │ - Sincronizar silenciosamente │ └─────────────────────────────────────────┘ ``` --- ### 4.2 Estrategia de Cache Local **Almacenamiento:** IndexedDB (navegador) **Estructura de cache:** ```typescript interface CachedContent { id: string; type: 'protocol' | 'drug' | 'guide' | 'manual'; version: number; content: any; cachedAt: string; // ISO 8601 expiresAt: string; // ISO 8601 (cachedAt + 7 días) lastSync: string; // ISO 8601 } ``` **Reglas de cache:** 1. **Cache inicial:** Contenido embebido en build (fallback) 2. **Cache dinámico:** Descargado desde API y guardado en IndexedDB 3. **Expiración:** 7 días (configurable) 4. **Invalidación:** Por versión o por tiempo 5. **Prioridad:** Cache local > API > Fallback embebido --- ### 4.3 Flujo Detallado #### Paso 1: Inicio de la App ```typescript // Pseudocódigo conceptual async function initializeApp() { // 1. Cargar cache local const cachedContent = await loadFromIndexedDB(); // 2. Si hay cache válido, usarlo inmediatamente if (cachedContent && !isExpired(cachedContent)) { useContent(cachedContent); } else { // 3. Usar fallback embebido useContent(embeddedContent); } // 4. Intentar sincronizar en background if (navigator.onLine) { syncInBackground(); } } ``` --- #### Paso 2: Sincronización en Background ```typescript async function syncInBackground() { try { // 1. Verificar actualizaciones const lastSync = getLastSyncTimestamp(); const response = await fetch(`/api/v1/sync/check?lastSync=${lastSync}`); const { hasUpdates, updatedItems } = await response.json(); if (hasUpdates) { // 2. Descargar contenido actualizado for (const item of updatedItems) { const content = await fetchContent(item.id, item.type); await saveToIndexedDB(content); } // 3. Actualizar timestamp de sincronización updateLastSyncTimestamp(); // 4. Notificar usuario (opcional, no intrusivo) showUpdateNotification(); } } catch (error) { // Silencioso: no interrumpir uso de la app console.error('Sync failed:', error); } } ``` --- #### Paso 3: Uso Offline ```typescript async function getContent(id: string, type: string) { // 1. Intentar cache local const cached = await getFromIndexedDB(id, type); if (cached && !isExpired(cached)) { return cached.content; } // 2. Si hay red, intentar API if (navigator.onLine) { try { const content = await fetchFromAPI(id, type); await saveToIndexedDB(content); return content; } catch (error) { // Continuar con fallback } } // 3. Fallback embebido (nunca falla) return getEmbeddedContent(id, type); } ``` --- ### 4.4 Versionado de Contenido **Estrategia:** - Cada contenido tiene `version` incremental - App cachea `version` junto con contenido - Al sincronizar, compara versiones - Solo descarga si `version` remota > `version` local **Ventaja:** Minimiza transferencia de datos --- ## 5️⃣ PANEL ADMINISTRADOR ### 5.1 Alcance y Rol **El panel administrador:** - ✅ Es una aplicación web separada (no parte de la PWA) - ✅ Permite editar TODO el contenido clínico - ✅ Gestiona validación clínica - ✅ Proporciona auditoría completa - ✅ NO modifica código React de la app operativa **El panel administrador NO:** - ❌ Modifica componentes React - ❌ Cambia rutas de la app - ❌ Afecta al Service Worker - ❌ Requiere redeploy de la app operativa --- ### 5.2 Funcionalidades Principales #### 5.2.1 Gestión de Contenido **Editor de Protocolos:** - Formulario para editar: título, pasos, advertencias, puntos clave, material, fármacos - Validación en tiempo real - Preview del protocolo - Guardar como borrador o solicitar validación **Editor de Fármacos:** - Formulario para editar: dosis, vías, indicaciones, contraindicaciones, notas - Validación de formato de dosis - Preview del fármaco - Guardar como borrador o solicitar validación **Editor de Guías:** - Editor Markdown con preview - Navegación entre secciones (1-8) - Validación de estructura - Guardar secciones individuales **Editor de Manual:** - Editor Markdown avanzado - Navegación por bloques - Búsqueda y reemplazo - Validación de referencias cruzadas --- #### 5.2.2 Validación Clínica **Panel de Validación:** - Lista de contenido pendiente - Vista previa del contenido - Campos para completar: - Fuente clínica (ERC, AHA, SEMES, etc.) - Puntuación de calidad (0-100) - Comentarios - Botones: Aprobar / Rechazar **Flujo:** 1. Editor crea/modifica contenido → Estado: `draft` 2. Editor solicita validación → Estado: `pending` 3. Validador revisa → Aprobar → Estado: `validated` (público) 4. Validador revisa → Rechazar → Estado: `rejected` (vuelve a `draft`) --- #### 5.2.3 Auditoría **Log de Cambios:** - Historial completo de todos los cambios - Filtros: por contenido, usuario, fecha, acción - Vista de diferencias entre versiones - Exportación de logs **Trazabilidad:** - Quién hizo qué cambio y cuándo - IP y user agent (para seguridad) - Rollback a versiones anteriores --- ### 5.3 Tecnología del Panel **Stack propuesto:** - **Frontend:** React + TypeScript (consistente con app principal) - **UI:** shadcn/ui (consistente con app principal) - **Editor Markdown:** react-markdown-editor-lite - **Validación:** Zod (TypeScript-first) - **Autenticación:** JWT tokens - **Estado:** React Query (para cache de API) **Estructura:** ``` admin-panel/ ├── src/ │ ├── pages/ │ │ ├── ProtocolsEditor.tsx │ │ ├── DrugsEditor.tsx │ │ ├── GuidesEditor.tsx │ │ ├── ManualEditor.tsx │ │ ├── Validation.tsx │ │ └── Audit.tsx │ ├── components/ │ │ ├── ContentEditor/ │ │ ├── Validation/ │ │ └── VersionHistory/ │ └── lib/ │ ├── api.ts │ └── validation.ts ``` --- ## 6️⃣ QUÉ NO TOCAR DE LA APP EXISTENTE ### 6.1 Componentes React **NO modificar:** - ❌ `src/pages/RCP.tsx`, `Ictus.tsx`, `ViaAerea.tsx`, `Shock.tsx` - ❌ `src/components/procedures/` - ❌ `src/components/drugs/` - ❌ Estructura visual y lógica de UI **SÍ permitido:** - ✅ Modificar funciones de carga de datos (de estático a dinámico) - ✅ Añadir lógica de cache local - ✅ Añadir sincronización en background --- ### 6.2 Service Worker **NO modificar:** - ❌ `public/sw.js` - ❌ Estrategia de cache - ❌ Lógica de actualización **Razón:** El Service Worker es crítico para funcionamiento offline. Cualquier cambio puede romper la app en emergencias. --- ### 6.3 Rutas y Navegación **NO modificar:** - ❌ Rutas existentes (`/rcp`, `/ictus`, `/via-aerea`, etc.) - ❌ Estructura de navegación - ❌ Deep links **Razón:** Las rutas pueden estar bookmarked o compartidas. Cambiarlas rompe enlaces. --- ### 6.4 Estructura de Datos TypeScript **NO eliminar:** - ❌ `src/data/procedures.ts` (mantener como fallback) - ❌ `src/data/drugs.ts` (mantener como fallback) - ❌ `src/data/guides-index.ts` (mantener como fallback) **SÍ permitido:** - ✅ Modificar para cargar desde API con fallback - ✅ Añadir lógica de cache --- ### 6.5 Build y Deploy **NO cambiar:** - ❌ Proceso de build (Vite) - ❌ Estructura de archivos públicos - ❌ Configuración de PWA **Razón:** Cambios en build pueden afectar funcionamiento offline. --- ## 7️⃣ ROADMAP DE IMPLEMENTACIÓN ### FASE 1: Infraestructura Base (4-5 semanas) **Objetivos:** - Configurar PostgreSQL - Crear esquema de base de datos - Implementar API REST básica - Migrar contenido inicial **Tareas:** - [ ] Instalar y configurar PostgreSQL - [ ] Ejecutar scripts de creación de esquema - [ ] Crear backend Node.js básico (Express) - [ ] Implementar endpoints GET (solo lectura) - [ ] Script de migración: TypeScript → PostgreSQL - [ ] Validar que contenido migrado es correcto **Criterios de éxito:** - ✅ Base de datos funcionando - ✅ API responde correctamente - ✅ Contenido migrado sin pérdidas - ✅ App operativa sigue funcionando igual --- ### FASE 2: Sincronización Offline (4-5 semanas) **Objetivos:** - Implementar carga dinámica en app - Sistema de cache local (IndexedDB) - Sincronización en background - Fallback robusto **Tareas:** - [ ] Crear `src/lib/content-loader.ts` - [ ] Crear `src/lib/content-cache.ts` (IndexedDB) - [ ] Modificar `src/data/procedures.ts` para cargar dinámicamente - [ ] Modificar `src/data/drugs.ts` para cargar dinámicamente - [ ] Implementar sincronización en background - [ ] Testing exhaustivo offline/online - [ ] Validar que fallback funciona **Criterios de éxito:** - ✅ App carga contenido desde API - ✅ Cache local funciona - ✅ App funciona 100% offline - ✅ Sincronización transparente - ✅ Sin regresiones --- ### FASE 3: Panel Admin MVP (6-8 semanas) **Objetivos:** - Crear panel administrador básico - Editor de protocolos y fármacos - Autenticación y autorización - Guardado en base de datos **Tareas:** - [ ] Crear proyecto `admin-panel/` - [ ] Implementar autenticación JWT - [ ] Crear editor de protocolos - [ ] Crear editor de fármacos - [ ] Implementar endpoints PUT/POST - [ ] Sistema de versionado automático - [ ] Testing del panel **Criterios de éxito:** - ✅ Panel permite editar contenido - ✅ Cambios se guardan en BD - ✅ Versiones se incrementan - ✅ App consume contenido actualizado - ✅ Autenticación funciona --- ### FASE 4: Validación Clínica (4-5 semanas) **Objetivos:** - Sistema de roles y permisos - Flujo de validación clínica - Panel de validación - Metadatos de validación **Tareas:** - [ ] Implementar RBAC - [ ] Crear panel de validación - [ ] Endpoints de aprobación/rechazo - [ ] Campos de metadatos clínicos - [ ] Notificaciones de cambios de estado - [ ] Testing con usuarios reales **Criterios de éxito:** - ✅ Roles funcionan correctamente - ✅ Flujo de validación completo - ✅ Solo contenido validado es público - ✅ Metadatos se guardan correctamente --- ### FASE 5: Editores Completos (6-8 semanas) **Objetivos:** - Editor de guías formativas - Editor de manual completo - Funcionalidades avanzadas - Migración completa **Tareas:** - [ ] Editor de guías (Markdown) - [ ] Editor de manual (Markdown avanzado) - [ ] Comparación de versiones (diff) - [ ] Rollback a versiones anteriores - [ ] Migrar todas las guías - [ ] Migrar todo el manual **Criterios de éxito:** - ✅ Todos los editores funcionan - ✅ Todo el contenido migrado - ✅ Funcionalidades avanzadas disponibles --- ### FASE 6: Auditoría y Optimización (4-5 semanas) **Objetivos:** - Sistema de auditoría completo - Optimización de performance - Documentación - Mejoras de UX **Tareas:** - [ ] Panel de auditoría - [ ] Exportación de logs - [ ] Optimización de queries - [ ] CDN para contenido estático - [ ] Documentación completa - [ ] Testing de carga **Criterios de éxito:** - ✅ Auditoría completa funcional - ✅ Performance optimizada - ✅ Documentación completa --- ## 📊 RESUMEN EJECUTIVO ### Arquitectura - **Base de datos:** PostgreSQL con esquema `emerges_content` - **Backend:** Node.js + Express + API REST - **Panel Admin:** React + TypeScript (app separada) - **Sincronización:** Offline-first con IndexedDB ### Modelo de Datos - **7 tablas principales:** content_items, content_versions, content_relations, content_change_log, users, content_sync_status - **Versionado completo:** Historial de todas las versiones - **Validación clínica:** Estados y metadatos de validación - **Auditoría:** Log completo de cambios ### API REST - **Endpoints públicos:** Solo lectura (app operativa) - **Endpoints protegidos:** Escritura con autenticación (panel admin) - **Versionado:** v1 - **Documentación:** OpenAPI/Swagger ### Sincronización - **Estrategia:** Cache local > API > Fallback embebido - **Offline-first:** App funciona sin red - **Background sync:** Sincronización transparente - **Versionado:** Solo descarga si hay actualizaciones ### Panel Admin - **Alcance:** Edición completa de contenido clínico - **Validación:** Flujo de aprobación/rechazo - **Auditoría:** Historial completo - **Separado:** No modifica app operativa ### Roadmap - **6 fases:** 28-36 semanas total - **Progresivo:** Cada fase independiente - **Validado:** Testing continuo - **Sin disrupciones:** App funciona en todo momento --- **Este sistema permite modificar contenido clínico sin tocar código React, manteniendo funcionamiento offline-first y validación clínica obligatoria.**