1263 lines
34 KiB
Markdown
1263 lines
34 KiB
Markdown
|
|
# 🏗️ 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.**
|
|||
|
|
|