codigo0/docs/PLAN_TECNICO_SISTEMA_CONTENIDO.md

39 KiB

🏗️ 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
  2. FASE 2 — INTEGRACIÓN ESTRUCTURAL
  3. FASE 3 — VALIDACIÓN Y ESCALADO
  4. PANEL DE ADMINISTRACIÓN
  5. 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

// ============================================
// 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:

-- 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

// ============================================
// 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

// ============================================
// 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

// ============================================
// 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:

// 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

// ============================================
// 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

// ============================================
// 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

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

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

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

// ============================================
// 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

// ============================================
// 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