codigo0/docs/ARQUITECTURA_BBDD_Y_GESTION_CONTENIDO.md

1263 lines
34 KiB
Markdown
Raw Normal View History

2026-01-19 08:10:16 +00:00
# 🏗️ 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.**