codigo0/docs/ARQUITECTURA_BBDD_Y_GESTION_CONTENIDO.md

1263 lines
34 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🏗️ 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.**