codigo0/docs/PLAN_TECNICO_SISTEMA_CONTENIDO.md

1359 lines
39 KiB
Markdown
Raw Normal View History

2026-01-19 08:10:16 +00:00
# 🏗️ PLAN TÉCNICO EJECUTABLE - SISTEMA DE GESTIÓN DE CONTENIDO
**Fecha:** 2025-01-06
**Arquitecto:** Tech Lead + Arquitecto de Sistema
**Versión:** 1.0
**Estado:** Diseño - Sin Implementación
---
## 📋 ÍNDICE
1. [FASE 4 — BASE DE CONTENIDO](#fase-4--base-de-contenido)
2. [FASE 2 — INTEGRACIÓN ESTRUCTURAL](#fase-2--integración-estructural)
3. [FASE 3 — VALIDACIÓN Y ESCALADO](#fase-3--validación-y-escalado)
4. [PANEL DE ADMINISTRACIÓN](#panel-de-administración)
5. [ROADMAP DE IMPLEMENTACIÓN](#roadmap-de-implementación)
---
# FASE 4 — BASE DE CONTENIDO
## 4.1 Modelo de Datos Canónico
### A. Principios de Diseño
1. **Desacoplamiento Total**
- Modelo independiente de `procedures.ts` y `drugs.ts`
- Compatible con contenido local (fallback)
- Extensible sin romper producción
2. **Neutralidad de Almacenamiento**
- Modelo JSON-serializable
- Compatible con SQL, NoSQL, JSON local
- Versionado integrado
3. **Asociación de Recursos**
- Imágenes y vídeos como entidades separadas
- Relaciones many-to-many
- Metadatos completos
---
### B. Modelo de Datos Base
```typescript
// ============================================
// CORE: Base Content Item
// ============================================
interface BaseContentItem {
// Identificación
id: string; // ID estable (inmutable, ej: "rcp-adulto-svb")
slug: string; // Slug para URLs (ej: "rcp-adulto-svb")
type: ContentType; // 'protocol' | 'guide' | 'manual' | 'drug' | 'checklist'
level: ContentLevel; // 'operativo' | 'formativo' | 'referencia'
// Metadatos básicos
title: string;
shortTitle?: string;
description?: string;
icon?: string;
// Prioridad y clasificación
priority: Priority; // 'critica' | 'alta' | 'media' | 'baja'
category?: string; // Categoría temática
tags: string[]; // Tags para búsqueda
// Estado y validación
status: ContentStatus; // 'draft' | 'in_review' | 'approved' | 'published' | 'archived'
validatedBy?: string; // ID del validador clínico
validatedAt?: string; // ISO timestamp
clinicalSource?: string; // Fuente clínica (ERC, SEMES, etc.)
// Versionado
version: number; // Versión actual
latestVersion: number; // Última versión disponible
versionHistory?: string[]; // IDs de versiones anteriores
// Auditoría
createdBy?: string;
createdAt: string; // ISO timestamp
updatedBy?: string;
updatedAt: string; // ISO timestamp
// Relaciones
relations?: ContentRelations;
}
// ============================================
// RECURSOS VISUALES (Imágenes y Vídeos)
// ============================================
interface MediaResource {
id: string; // ID único del recurso
type: 'image' | 'video';
// Archivo
filename: string; // Nombre del archivo (ej: "rcp_posicion_manos_adulto.png")
path: string; // Ruta relativa (ej: "/assets/infografias/rcp/...")
url?: string; // URL completa (si está en CDN)
// Metadatos
alt: string; // Texto alternativo (accesibilidad)
caption?: string; // Caption opcional
title?: string; // Título del recurso
// Clasificación
block?: string; // Bloque temático (ej: "bloque-0-fundamentos")
chapter?: string; // Capítulo relacionado
tags: string[]; // Tags para búsqueda
// Prioridad y uso
priority: Priority; // Prioridad del recurso
usageType: ContentLevel[]; // ['operativo', 'formativo'] - dónde se usa
// Dimensiones (para imágenes)
width?: number;
height?: number;
format?: string; // 'png' | 'svg' | 'jpg' | 'webp'
// Duración (para vídeos)
duration?: number; // Duración en segundos
format?: string; // 'mp4' | 'webm'
// Fuente
source?: string; // Fuente del recurso
attribution?: string; // Atribución si es necesario
// Estado
status: 'draft' | 'approved' | 'published';
// Auditoría
uploadedBy?: string;
uploadedAt: string;
updatedAt: string;
}
// ============================================
// ASOCIACIÓN RECURSOS ⇄ CONTENIDO
// ============================================
interface ContentResourceAssociation {
id: string;
contentId: string; // ID del contenido (protocolo, guía, etc.)
resourceId: string; // ID del recurso (imagen/vídeo)
// Contexto de asociación
context: {
section?: string; // Sección específica (ej: "paso-3", "seccion-4")
position?: number; // Posición en el contenido (orden)
placement?: 'inline' | 'before' | 'after' | 'modal'; // Dónde se muestra
caption?: string; // Caption específico para este contexto
};
// Metadatos
isRequired: boolean; // Si es obligatorio para el contenido
priority: Priority; // Prioridad de esta asociación
createdAt: string;
}
// ============================================
// PROTOCOLO (Extendido)
// ============================================
interface ProtocolContent {
// Pasos operativos
steps: ProtocolStep[];
// Checklist integrado
checklist?: {
enabled: boolean;
title?: string;
items: ChecklistItem[];
};
// Dosis inline
inlineDoses?: InlineDose[];
// Herramientas de contexto
contextTools?: ContextTool[];
// Fuentes clínicas
clinicalSources?: ClinicalSource[];
// Campos existentes (compatibilidad)
warnings: string[];
keyPoints?: string[];
equipment?: string[];
drugs?: string[];
}
interface Protocol extends BaseContentItem {
type: 'protocol';
level: 'operativo';
content: ProtocolContent;
// Campos específicos
category: Category;
subcategory?: string;
ageGroup: AgeGroup;
// Recursos asociados
resources?: {
images?: string[]; // IDs de MediaResource
videos?: string[]; // IDs de MediaResource
};
}
// ============================================
// GUÍA FORMATIVA (Extendido)
// ============================================
interface GuideSection {
numero: number; // 1-8
titulo: string;
markdown: string; // Contenido Markdown
estimatedTime?: string; // Tiempo estimado de lectura
// Recursos por sección
resources?: {
images?: string[]; // IDs de MediaResource
videos?: string[]; // IDs de MediaResource
links?: Array<{ title: string; url: string }>;
};
}
interface GuideContent {
sections: GuideSection[]; // Siempre 8 secciones
relations: {
protocolId?: string; // Protocolo operativo relacionado
manualChapterIds?: string[]; // Capítulos de manual relacionados
};
metadata?: {
learningObjectives?: string[];
prerequisites?: string[];
targetAudience?: string[];
};
}
interface Guide extends BaseContentItem {
type: 'guide';
level: 'formativo';
content: GuideContent;
// Recursos globales de la guía
resources?: {
images?: string[]; // IDs de MediaResource
videos?: string[]; // IDs de MediaResource
};
}
// ============================================
// CAPÍTULO DE MANUAL (Extendido)
// ============================================
interface ManualChapterContent {
markdown: string;
tags: string[];
relatedProtocols?: string[]; // IDs de protocolos relacionados
relatedGuides?: string[]; // IDs de guías relacionadas
references?: ClinicalSource[];
}
interface ManualChapter extends BaseContentItem {
type: 'manual';
level: 'referencia';
content: ManualChapterContent;
// Estructura jerárquica
block: string; // Bloque del manual (ej: "BLOQUE_01")
section?: string; // Sección dentro del bloque
order: number; // Orden dentro del bloque
// Recursos asociados
resources?: {
images?: string[]; // IDs de MediaResource
videos?: string[]; // IDs de MediaResource
};
}
// ============================================
// FÁRMACO (Extendido)
// ============================================
interface DrugContent {
genericName: string;
tradeName: string;
category: string;
presentation: string;
adultDose: string;
pediatricDose?: string;
routes: AdministrationRoute[];
dilution?: string;
indications: string[];
contraindications: string[];
sideEffects?: string[];
antidote?: string;
notes?: string[];
criticalPoints?: string[];
source?: string;
}
interface Drug extends BaseContentItem {
type: 'drug';
level: 'referencia';
content: DrugContent;
// Recursos asociados (infografías de administración)
resources?: {
images?: string[]; // IDs de MediaResource (esquemas de administración)
};
}
// ============================================
// RELACIONES ENTRE CONTENIDO
// ============================================
interface ContentRelations {
// Relaciones bidireccionales
protocols?: string[]; // IDs de protocolos relacionados
guides?: string[]; // IDs de guías relacionadas
manuals?: string[]; // IDs de capítulos de manual relacionados
drugs?: string[]; // IDs de fármacos relacionados
checklists?: string[]; // IDs de checklists relacionadas
// Relaciones específicas
parentContent?: string; // ID del contenido padre (si aplica)
childContent?: string[]; // IDs de contenidos hijos (si aplica)
}
// ============================================
// CONTENT PACK (Para distribución)
// ============================================
interface ContentPack {
version: string; // Versión semántica (ej: "1.2.3")
timestamp: string; // ISO timestamp
hash: string; // Hash SHA-256 del contenido
// Contenido
protocols?: Protocol[];
guides?: Guide[];
manuals?: ManualChapter[];
drugs?: Drug[];
checklists?: ChecklistReusable[];
// Recursos (opcional, puede estar en CDN)
resources?: MediaResource[];
// Metadatos
publishedBy?: string;
publishedAt?: string;
clinicalSource?: string;
notes?: string;
// Compatibilidad
minAppVersion?: string; // Versión mínima de app requerida
maxAppVersion?: string; // Versión máxima de app compatible
}
// ============================================
// TIPOS AUXILIARES
// ============================================
type ContentType = 'protocol' | 'guide' | 'manual' | 'drug' | 'checklist';
type ContentLevel = 'operativo' | 'formativo' | 'referencia';
type ContentStatus = 'draft' | 'in_review' | 'approved' | 'published' | 'archived';
type Priority = 'critica' | 'alta' | 'media' | 'baja';
type Category = 'soporte_vital' | 'patologias' | 'escena';
type AgeGroup = 'adulto' | 'pediatrico' | 'neonatal' | 'todos';
type AdministrationRoute = 'IV' | 'IM' | 'SC' | 'IO' | 'Nebulizado' | 'SL' | 'Rectal' | 'Nasal';
interface ProtocolStep {
order: number;
text: string;
critical?: boolean;
equipment?: string[];
timeEstimate?: string;
}
interface ChecklistItem {
id: string;
text: string;
order: number;
critical?: boolean;
}
interface InlineDose {
drugId: string;
drugName: string;
adultDose: string;
pediatricDose?: string;
route: AdministrationRoute;
timing?: string;
}
interface ContextTool {
id: string;
name: string;
type: 'calculator' | 'algorithm' | 'reference';
url?: string;
}
interface ClinicalSource {
organization: string; // ERC, SEMES, AHA, etc.
guideline: string;
year: number;
url?: string;
section?: string;
}
```
---
## 4.2 Base de Datos Recomendada
### A. Opción Principal: **Supabase**
#### Justificación:
1. **Offline-First Compatible**
- ✅ PostgreSQL con replicación local
- ✅ Supabase Realtime para sincronización
- ✅ Supabase Storage para recursos multimedia
- ✅ Cache local con IndexedDB
2. **Editable desde Panel Admin**
- ✅ API REST automática
- ✅ Dashboard web integrado
- ✅ Autenticación y RBAC incluidos
- ✅ Editor visual de datos (opcional)
3. **Sin Tocar App en Producción**
- ✅ Content Pack como JSON descargable
- ✅ App consume pack, no DB directamente
- ✅ Actualizaciones sin deploy de app
- ✅ Versionado automático
4. **Fácil para Sanitarios No Técnicos**
- ✅ Interfaz web intuitiva
- ✅ Editor de Markdown integrado
- ✅ Subida de imágenes/vídeos drag-and-drop
- ✅ Validación de datos en UI
5. **Control de Versiones**
- ✅ Historial de cambios automático
- ✅ Rollback de versiones
- ✅ Auditoría completa
- ✅ Git-like para contenido
#### Arquitectura Propuesta:
```
┌─────────────────────────────────────────┐
│ SUPABASE (Cloud) │
├─────────────────────────────────────────┤
│ PostgreSQL Database │
│ ├── content_items │
│ ├── media_resources │
│ ├── content_resource_associations │
│ ├── content_versions │
│ └── audit_logs │
│ │
│ Storage (S3-compatible) │
│ ├── images/ │
│ └── videos/ │
│ │
│ Realtime (WebSockets) │
│ └── content_updates │
└─────────────────────────────────────────┘
│ (Content Pack JSON)
┌─────────────────────────────────────────┐
│ ADMIN PANEL (React) │
│ └── Edita contenido → Supabase │
└─────────────────────────────────────────┘
│ (Publica Content Pack)
┌─────────────────────────────────────────┐
│ APP TES (PWA) │
│ └── Descarga pack → Cache local │
│ └── Funciona 100% offline │
└─────────────────────────────────────────┘
```
#### Esquema de Base de Datos:
```sql
-- Tabla principal de contenido
CREATE TABLE content_items (
id TEXT PRIMARY KEY,
slug TEXT UNIQUE NOT NULL,
type TEXT NOT NULL, -- 'protocol' | 'guide' | 'manual' | 'drug' | 'checklist'
level TEXT NOT NULL, -- 'operativo' | 'formativo' | 'referencia'
title TEXT NOT NULL,
short_title TEXT,
description TEXT,
icon TEXT,
priority TEXT NOT NULL, -- 'critica' | 'alta' | 'media' | 'baja'
category TEXT,
tags TEXT[],
status TEXT NOT NULL, -- 'draft' | 'in_review' | 'approved' | 'published' | 'archived'
validated_by TEXT,
validated_at TIMESTAMPTZ,
clinical_source TEXT,
version INTEGER NOT NULL DEFAULT 1,
latest_version INTEGER NOT NULL DEFAULT 1,
created_by TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_by TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
content JSONB NOT NULL, -- Contenido específico del tipo
relations JSONB, -- Relaciones con otros contenidos
resources JSONB -- IDs de recursos asociados
);
-- Tabla de recursos multimedia
CREATE TABLE media_resources (
id TEXT PRIMARY KEY,
type TEXT NOT NULL, -- 'image' | 'video'
filename TEXT NOT NULL,
path TEXT NOT NULL,
url TEXT,
alt TEXT NOT NULL,
caption TEXT,
title TEXT,
block TEXT,
chapter TEXT,
tags TEXT[],
priority TEXT NOT NULL,
usage_type TEXT[], -- ['operativo', 'formativo']
width INTEGER,
height INTEGER,
format TEXT,
duration INTEGER, -- Para vídeos (segundos)
source TEXT,
attribution TEXT,
status TEXT NOT NULL,
uploaded_by TEXT,
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Tabla de asociaciones contenido ⇄ recursos
CREATE TABLE content_resource_associations (
id TEXT PRIMARY KEY,
content_id TEXT NOT NULL REFERENCES content_items(id),
resource_id TEXT NOT NULL REFERENCES media_resources(id),
context JSONB NOT NULL, -- { section, position, placement, caption }
is_required BOOLEAN NOT NULL DEFAULT false,
priority TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(content_id, resource_id, context->>'section')
);
-- Tabla de versiones
CREATE TABLE content_versions (
version_id TEXT PRIMARY KEY,
content_id TEXT NOT NULL REFERENCES content_items(id),
version INTEGER NOT NULL,
content JSONB NOT NULL,
status TEXT NOT NULL,
change_summary TEXT,
created_by TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
validated_by TEXT,
validated_at TIMESTAMPTZ,
UNIQUE(content_id, version)
);
-- Tabla de auditoría
CREATE TABLE audit_logs (
log_id TEXT PRIMARY KEY,
content_item_id TEXT REFERENCES content_items(id),
version_id TEXT REFERENCES content_versions(version_id),
user_id TEXT NOT NULL,
action TEXT NOT NULL, -- 'create' | 'update' | 'delete' | 'validate' | 'approve' | 'publish' | 'revert'
details JSONB,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Índices para búsqueda
CREATE INDEX idx_content_items_type ON content_items(type);
CREATE INDEX idx_content_items_level ON content_items(level);
CREATE INDEX idx_content_items_status ON content_items(status);
CREATE INDEX idx_content_items_tags ON content_items USING GIN(tags);
CREATE INDEX idx_media_resources_type ON media_resources(type);
CREATE INDEX idx_media_resources_usage_type ON media_resources USING GIN(usage_type);
CREATE INDEX idx_content_resource_associations_content ON content_resource_associations(content_id);
CREATE INDEX idx_content_resource_associations_resource ON content_resource_associations(resource_id);
```
#### Ventajas de Supabase:
-**Gratis hasta 500MB** (suficiente para MVP)
-**PostgreSQL completo** (relaciones, transacciones, triggers)
-**Storage integrado** (imágenes/vídeos)
-**Realtime opcional** (notificaciones de actualizaciones)
-**API REST automática** (sin código backend)
-**Autenticación incluida** (JWT, RBAC)
-**Dashboard web** (edición visual)
-**Migraciones** (control de versiones de schema)
---
### B. Opción Alternativa: **PocketBase**
#### Justificación:
1. **Offline-First Compatible**
- ✅ SQLite embebido (archivo local)
- ✅ Sincronización opcional
- ✅ API REST automática
2. **Editable desde Panel Admin**
- ✅ Panel admin integrado
- ✅ Editor de datos visual
- ✅ Autenticación incluida
3. **Sin Tocar App en Producción**
- ✅ Export a JSON
- ✅ Content Pack como archivo
4. **Fácil para Sanitarios**
- ✅ Interfaz simple
- ✅ Subida de archivos
5. **Control de Versiones**
- ⚠️ Manual (export/import JSON)
- ⚠️ Sin historial automático
#### Ventajas de PocketBase:
-**Self-hosted** (control total)
-**SQLite** (sin servidor de BD)
-**Ligero** (single binary)
-**Gratis y open-source**
#### Desventajas vs Supabase:
- ❌ Sin historial automático
- ❌ Sin realtime
- ❌ Storage menos robusto
- ❌ Requiere hosting propio
---
## 4.3 Estructura de Archivos (JSON Local - Fallback)
Si se opta por JSON local versionado (alternativa simple):
```
content/
├── packs/
│ ├── pack-v1.0.0.json
│ ├── pack-v1.1.0.json
│ └── pack-v1.2.0.json
├── resources/
│ ├── images/
│ │ ├── rcp_posicion_manos_adulto.png
│ │ └── ...
│ └── videos/
│ ├── rcp_adulto_svb.mp4
│ └── ...
└── metadata/
├── content-index.json
└── resources-index.json
```
---
# FASE 2 — INTEGRACIÓN ESTRUCTURAL
## 2.1 Capa de Lectura (Content Adapter)
### A. Arquitectura de Adaptadores
```typescript
// ============================================
// INTERFACE: Content Adapter
// ============================================
interface ContentAdapter {
// Lectura de contenido
getProtocol(id: string): Promise<Protocol | null>;
getGuide(id: string): Promise<Guide | null>;
getManualChapter(id: string): Promise<ManualChapter | null>;
getDrug(id: string): Promise<Drug | null>;
// Lectura de recursos
getResource(id: string): Promise<MediaResource | null>;
getResourcesForContent(contentId: string, type?: 'image' | 'video'): Promise<MediaResource[]>;
// Búsqueda
searchContent(query: string, filters?: ContentFilters): Promise<BaseContentItem[]>;
// Content Pack
getLatestPack(): Promise<ContentPack | null>;
getPackVersion(version: string): Promise<ContentPack | null>;
}
// ============================================
// IMPLEMENTACIÓN: Local Adapter (Fallback)
// ============================================
class LocalContentAdapter implements ContentAdapter {
// Lee de src/data/ (procedures.ts, drugs.ts, etc.)
// Compatible con código actual
// Sin cambios en producción
async getProtocol(id: string): Promise<Protocol | null> {
// 1. Intentar desde content pack (si existe)
const pack = await this.getLatestPack();
if (pack?.protocols) {
const protocol = pack.protocols.find(p => p.id === id);
if (protocol) return protocol;
}
// 2. Fallback a local
const { getProcedureById } = await import('@/data/procedures');
const localProtocol = getProcedureById(id);
if (!localProtocol) return null;
// 3. Convertir formato local → formato canónico
return this.convertLocalProtocolToCanonical(localProtocol);
}
// ... otros métodos
}
// ============================================
// IMPLEMENTACIÓN: Supabase Adapter (Futuro)
// ============================================
class SupabaseContentAdapter implements ContentAdapter {
private supabase: SupabaseClient;
async getProtocol(id: string): Promise<Protocol | null> {
// 1. Intentar desde cache local (IndexedDB)
const cached = await this.getFromCache('protocol', id);
if (cached) return cached;
// 2. Obtener desde Supabase
const { data, error } = await this.supabase
.from('content_items')
.select('*')
.eq('id', id)
.eq('type', 'protocol')
.eq('status', 'published')
.single();
if (error || !data) {
// 3. Fallback a local
return await this.fallbackToLocal(id);
}
// 4. Cachear localmente
await this.cacheContent('protocol', id, data);
return data as Protocol;
}
// ... otros métodos
}
// ============================================
// FACTORY: Selección de Adapter
// ============================================
class ContentAdapterFactory {
static create(): ContentAdapter {
// Detectar si hay content pack disponible
const hasPack = this.checkForContentPack();
if (hasPack) {
// Usar adapter con soporte de pack
return new PackContentAdapter();
}
// Fallback a local
return new LocalContentAdapter();
}
}
```
---
### B. Modo Fallback
```typescript
// ============================================
// ESTRATEGIA DE FALLBACK
// ============================================
class FallbackStrategy {
// 1. Intentar Content Pack (si existe)
// 2. Si falla, usar contenido local (procedures.ts, drugs.ts)
// 3. Si falla, mostrar error controlado
async getContentWithFallback<T>(
type: ContentType,
id: string,
localGetter: () => T | null
): Promise<T | null> {
try {
// Intentar desde pack
const packContent = await this.getFromPack(type, id);
if (packContent) return packContent;
} catch (error) {
console.warn(`Error obteniendo ${type} ${id} desde pack:`, error);
}
// Fallback a local
try {
const localContent = localGetter();
if (localContent) {
// Convertir formato local → canónico
return this.convertLocalToCanonical(type, localContent);
}
} catch (error) {
console.error(`Error obteniendo ${type} ${id} desde local:`, error);
}
return null;
}
}
```
---
## 2.2 Enlaces Bidireccionales
### A. Sistema de Navegación
```typescript
// ============================================
// NAVEGACIÓN BIDIRECCIONAL
// ============================================
interface ContentNavigation {
// Desde Protocolo
protocolToGuide?: string; // ID de guía formativa relacionada
protocolToManual?: string[]; // IDs de capítulos de manual relacionados
// Desde Guía
guideToProtocol?: string; // ID de protocolo operativo relacionado
guideToManual?: string[]; // IDs de capítulos de manual relacionados
// Desde Manual
manualToProtocols?: string[]; // IDs de protocolos relacionados
manualToGuides?: string[]; // IDs de guías relacionadas
}
// ============================================
// COMPONENTE: Navigation Links
// ============================================
function ContentNavigationLinks({ contentId, type }: { contentId: string; type: ContentType }) {
const navigation = useContentNavigation(contentId, type);
return (
<div className="content-navigation">
{navigation.protocolToGuide && (
<Link to={`/guia-refuerzo/${navigation.protocolToGuide}`}>
📚 Ver Guía de Refuerzo
</Link>
)}
{navigation.guideToProtocol && (
<Link to={`/escena?tab=${navigation.guideToProtocol}`}>
🔵 Ver Protocolo Operativo
</Link>
)}
{navigation.manualToProtocols && navigation.manualToProtocols.map(protocolId => (
<Link key={protocolId} to={`/escena?tab=${protocolId}`}>
🔵 Ver Protocolo {protocolId}
</Link>
))}
</div>
);
}
```
### B. Mapeo Existente (Sin Cambios)
El archivo `src/data/protocol-guide-manual-mapping.ts` ya existe y funciona. Se extiende:
```typescript
// Extender sin romper
interface ExtendedMapping extends ProtocolGuideManualMapping {
// Añadir recursos
resources?: {
images?: string[];
videos?: string[];
};
// Añadir prioridades
priority?: Priority;
}
```
---
# FASE 3 — VALIDACIÓN Y ESCALADO
## 3.1 Validación con TES Reales
### A. Sistema de Validación
```typescript
// ============================================
// VALIDACIÓN DE CONTENIDO
// ============================================
interface ContentValidation {
contentId: string;
validatorId: string; // ID del TES validador
validatorName: string;
validatorRole: string; // 'tes' | 'medico' | 'formador'
validatorOrganization?: string;
status: 'pending' | 'approved' | 'rejected' | 'needs_revision';
comments?: string;
suggestions?: string[];
validatedAt?: string;
expiresAt?: string; // Fecha de expiración de validación
}
// ============================================
// FLUJO DE VALIDACIÓN
// ============================================
enum ValidationFlow {
// 1. Editor crea contenido → status: 'draft'
DRAFT = 'draft',
// 2. Editor solicita validación → status: 'in_review'
IN_REVIEW = 'in_review',
// 3. Validador TES revisa → status: 'approved' | 'needs_revision'
APPROVED = 'approved',
NEEDS_REVISION = 'needs_revision',
// 4. Publicación → status: 'published'
PUBLISHED = 'published',
// 5. Obsoleto → status: 'archived'
ARCHIVED = 'archived'
}
```
### B. Feedback desde la App
```typescript
// ============================================
// FEEDBACK DESDE APP
// ============================================
interface UserFeedback {
id: string;
userId?: string; // Opcional (si hay usuarios)
contentId: string;
contentType: ContentType;
type: 'error' | 'suggestion' | 'question' | 'praise';
message: string;
screenshot?: string; // URL de screenshot (opcional)
context?: {
section?: string;
step?: number;
resourceId?: string;
};
createdAt: string;
status: 'pending' | 'reviewed' | 'resolved';
}
// Componente en app
function FeedbackButton({ contentId, contentType }: Props) {
const [showFeedback, setShowFeedback] = useState(false);
return (
<>
<Button onClick={() => setShowFeedback(true)}>
💬 Enviar Feedback
</Button>
{showFeedback && (
<FeedbackModal
contentId={contentId}
contentType={contentType}
onClose={() => setShowFeedback(false)}
/>
)}
</>
);
}
```
---
## 3.2 Estados de Contenido
### A. Estados y Transiciones
```
┌─────────┐
│ DRAFT │ ← Creado por editor
└────┬────┘
│ Solicita validación
┌─────────────┐
│ IN_REVIEW │ ← En revisión por TES
└────┬────────┘
├─→ APPROVED ──→ PUBLISHED
└─→ NEEDS_REVISION ──→ DRAFT
PUBLISHED ──→ ARCHIVED (obsoleto)
```
### B. Marcado de Contenido
```typescript
interface ContentStatusBadge {
status: ContentStatus;
validatedBy?: string;
validatedAt?: string;
expiresAt?: string;
}
// Componente visual
function ContentStatusBadge({ status, validatedBy }: Props) {
const badgeConfig = {
draft: { label: 'Borrador', color: 'gray' },
in_review: { label: 'En Revisión', color: 'yellow' },
approved: { label: 'Validado', color: 'green' },
published: { label: 'Publicado', color: 'blue' },
archived: { label: 'Obsoleto', color: 'red' }
};
const config = badgeConfig[status];
return (
<Badge color={config.color}>
{config.label}
{validatedBy && ` por ${validatedBy}`}
</Badge>
);
}
```
---
## 3.3 Mejoras sin Romper Producción
### A. Versionado Semántico
```typescript
interface ContentVersion {
version: string; // Semántica: "1.2.3"
breaking: boolean; // Si rompe compatibilidad
changelog: string[]; // Lista de cambios
minAppVersion?: string; // Versión mínima de app requerida
}
// Estrategia:
// - Versión mayor (1.x.x): Cambios breaking
// - Versión menor (x.2.x): Nuevas features
// - Versión patch (x.x.3): Correcciones
```
### B. Feature Flags
```typescript
interface ContentFeatureFlag {
id: string;
enabled: boolean;
targetAudience?: string[]; // ['tes', 'formadores']
rolloutPercentage?: number; // 0-100
}
// Ejemplo:
// - Nuevo protocolo en beta: enabled=false para producción
// - Nuevo protocolo validado: enabled=true, rolloutPercentage=100
```
---
# PANEL DE ADMINISTRACIÓN
## 4.1 Funcionalidades Requeridas
### A. Gestión de Contenido
```typescript
// ============================================
// EDITOR DE PROTOCOLO
// ============================================
interface ProtocolEditor {
// Edición de texto
- Editar pasos (arrastrar para reordenar)
- Editar warnings, keyPoints
- Editar checklist integrado
// Gestión de recursos
- Asociar imágenes (drag-and-drop)
- Asociar vídeos (upload o URL)
- Previsualizar recursos
// Prioridades
- Cambiar prioridad (crítica/alta/media/baja)
- Marcar como crítico/opcional
// Versionado
- Ver historial de versiones
- Revertir a versión anterior
- Crear nueva versión
// Validación
- Solicitar validación
- Ver estado de validación
- Ver comentarios de validadores
}
// ============================================
// GESTOR DE RECURSOS
// ============================================
interface ResourceManager {
// Subida
- Upload imágenes (drag-and-drop)
- Upload vídeos (con progreso)
- Subida múltiple
// Metadatos
- Editar alt text, caption
- Asignar tags, bloque, capítulo
- Cambiar prioridad
// Asociación
- Ver qué contenidos usan el recurso
- Asociar a contenido específico
- Desasociar recurso
// Organización
- Filtrar por tipo, bloque, prioridad
- Buscar recursos
- Ver recursos sin asociar
}
// ============================================
// DASHBOARD DE ESTADO
// ============================================
interface ContentDashboard {
// Estado general
- Total de protocolos: X completos, Y incompletos
- Total de recursos: X imágenes, Y vídeos
- Protocolos sin recursos críticos (lista)
// Validación
- Contenido pendiente de validación
- Contenido validado recientemente
- Contenido obsoleto
// Actividad
- Últimos cambios
- Contenido más consultado (si hay analytics)
}
```
### B. Restricciones del Panel
```typescript
// ============================================
// LO QUE EL PANEL NO DEBE HACER
// ============================================
const PANEL_RESTRICTIONS = {
// ❌ NO modificar rutas de navegación
// ❌ NO cambiar estructura de componentes
// ❌ NO afectar Service Worker
// ❌ NO modificar código de la app
// ✅ SÍ puede:
// - Editar contenido (texto, pasos, etc.)
// - Subir recursos (imágenes, vídeos)
// - Asociar recursos a contenido
// - Cambiar prioridades
// - Activar/desactivar contenido
// - Versionar cambios
};
```
---
## 4.2 Arquitectura del Panel
```
┌─────────────────────────────────────────┐
│ ADMIN PANEL (React + Vite) │
├─────────────────────────────────────────┤
│ ┌───────────────────────────────────┐ │
│ │ Auth (JWT) │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Dashboard │ │
│ │ - Estado de contenido │ │
│ │ - Protocolos incompletos │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Editor de Protocolo │ │
│ │ - Tabs: Básico, Pasos, Checklist, │ │
│ │ Dosis, Fuentes, Recursos │ │
│ │ - Preview "Modo TES" │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Editor de Guía │ │
│ │ - 8 secciones Markdown │ │
│ │ - Preview con recursos │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Gestor de Recursos │ │
│ │ - Upload imágenes/vídeos │ │
│ │ - Asociar a contenido │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Biblioteca de Contenido │ │
│ │ - Lista con filtros │ │
│ │ - Búsqueda │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Auditoría │ │
│ │ - Historial de cambios │ │
│ │ - Comparar versiones │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
│ (API REST)
┌─────────────────────────────────────────┐
│ SUPABASE (Backend) │
│ - PostgreSQL Database │
│ - Storage (S3) │
│ - Auth │
└─────────────────────────────────────────┘
```
---
# ROADMAP DE IMPLEMENTACIÓN
## Fase 4.1: Modelo de Datos (Semana 1-2)
- [ ] Definir tipos TypeScript completos
- [ ] Crear esquema de base de datos (Supabase)
- [ ] Migrar contenido existente a formato canónico
- [ ] Crear scripts de migración
## Fase 4.2: Base de Datos (Semana 3-4)
- [ ] Configurar Supabase
- [ ] Crear tablas y relaciones
- [ ] Configurar Storage para recursos
- [ ] Configurar autenticación y RBAC
- [ ] Migrar contenido inicial
## Fase 2.1: Capa de Lectura (Semana 5-6)
- [ ] Implementar ContentAdapter interface
- [ ] Implementar LocalContentAdapter (fallback)
- [ ] Implementar PackContentAdapter
- [ ] Crear ContentAdapterFactory
- [ ] Integrar en hooks existentes (useProtocol, useDrug)
## Fase 2.2: Enlaces Bidireccionales (Semana 7)
- [ ] Extender protocol-guide-manual-mapping
- [ ] Crear componente ContentNavigationLinks
- [ ] Integrar en páginas de protocolos
- [ ] Integrar en guías formativas
- [ ] Integrar en manual
## Fase 3.1: Validación (Semana 8-9)
- [ ] Implementar sistema de validación
- [ ] Crear flujo de validación en panel
- [ ] Implementar feedback desde app
- [ ] Crear dashboard de validación
## Fase 3.2: Versionado (Semana 10)
- [ ] Implementar versionado semántico
- [ ] Crear sistema de versiones en BD
- [ ] Implementar rollback
- [ ] Crear historial de cambios
## Panel Admin: Fase 1 (Semana 11-12)
- [ ] Editor de protocolo (básico)
- [ ] Gestor de recursos (upload)
- [ ] Asociación recursos ⇄ contenido
- [ ] Dashboard de estado
## Panel Admin: Fase 2 (Semana 13-14)
- [ ] Editor de guías (Markdown)
- [ ] Editor de manual
- [ ] Biblioteca de contenido
- [ ] Auditoría y versiones
## Testing y Validación (Semana 15-16)
- [ ] Testing de adaptadores
- [ ] Testing de fallback
- [ ] Validación con TES reales
- [ ] Optimización de performance
---
## Métricas de Éxito
### Fase 4 (Base de Contenido):
- ✅ Modelo de datos completo y documentado
- ✅ Base de datos configurada y funcionando
- ✅ Contenido migrado sin pérdida de datos
### Fase 2 (Integración):
- ✅ App consume contenido externo sin romper
- ✅ Fallback a local funciona 100%
- ✅ Enlaces bidireccionales operativos
### Fase 3 (Validación):
- ✅ Sistema de validación funcional
- ✅ Feedback desde app operativo
- ✅ Versionado completo
### Panel Admin:
- ✅ Edición de contenido sin tocar código
- ✅ Subida de recursos funcional
- ✅ Asociación recursos ⇄ contenido operativa
---
**Fin del Plan Técnico**