# 📐 MODELO DE DATOS CANÓNICO DEFINITIVO **Proyecto:** Guía TES - Sistema de Gestión de Contenido **Fuente de Verdad:** FASE B - Matriz Maestra de Contenidos (15 temas críticos) **Fecha:** 2025-01-06 **Arquitecto:** Sistema de Contenido **Estado:** ✅ DEFINITIVO --- ## 🎯 PRINCIPIOS FUNDAMENTALES Este modelo es la **ÚNICA FUENTE DE VERDAD** para el sistema de contenidos. Se basa en: 1. ✅ **FASE B validada:** Los 15 temas críticos confirmados 2. ✅ **Separación de capas:** Operativa, Formativa, Referencia (NUNCA mezclar) 3. ✅ **Versionado completo:** Todo contenido es versionable y reversible 4. ✅ **Trazabilidad total:** Auditoría completa de todas las acciones 5. ✅ **Validación clínica:** Workflow de validación con roles TES/Medico/Formador 6. ✅ **Offline-first:** Todo debe funcionar sin conexión 7. ✅ **SCORM-ready:** Guías formativas exportables a SCORM 1.2 --- ## 📊 ENTIDADES PRINCIPALES ### 1. CONTENIDO (ContentItem) **Definición:** Cualquier pieza de contenido del sistema: protocolos, guías, manual, fármacos, checklists. **Regla de Oro:** Cada ContentItem pertenece a UNA SOLA capa funcional (operativa, formativa, referencia). NUNCA mezclar. #### 1.1 Identificación y Clasificación ``` ContentItem { // ============================================ // IDENTIFICACIÓN ÚNICA // ============================================ id: UUID // ID único e inmutable (generado por BD) slug: String // Identificador legible único (ej: "control-hemorragias-externas") version: String // Versión semántica actual (ej: "1.2.3") latest_version: String // Última versión disponible (ej: "1.2.3") is_latest: Boolean // ¿Es la última versión? (derivado) // ============================================ // CLASIFICACIÓN POR TIPO // ============================================ type: Enum // 'protocol' | 'guide' | 'manual' | 'drug' | 'checklist' // ============================================ // CLASIFICACIÓN CLÍNICA (FASE B) // ============================================ // Basado en los 15 temas críticos de FASE B clinical_topic: Enum // 'hemorragias_externas' | 'torniquetes' | 'convulsiones' | // 'aspiracion_secreciones' | 'ventilacion_ambu' | // 'reevaluacion_abcde' | 'valoracion_secundaria_sample' | // 'shock_hipovolemico' | 'shock_septico' | 'ictus_completo' | // 'trauma_politrauma' | 'tce' | 'alteracion_conciencia' | // 'vademecum_tes' | 'preparacion_intubacion' category: String // Categoría general (ej: "soporte_vital", "trauma", "farmacologia") subcategory: String? // Subcategoría específica (ej: "hemorragia", "shock", "rcp") // ============================================ // CAPA FUNCIONAL (CRÍTICO: NO MEZCLAR) // ============================================ functional_layer: Enum // 'operativa' | 'formativa' | 'referencia' // Regla: Un ContentItem SOLO puede tener contenido de UNA capa // Ejemplo: Un protocolo es SIEMPRE operativa, una guía es SIEMPRE formativa // ============================================ // METADATOS BÁSICOS // ============================================ title: String // Título completo (ej: "Control de Hemorragias Externas") short_title: String? // Título corto para UI (ej: "Hemorragias") description: String // Descripción breve (1-2 líneas) keywords: String[] // Palabras clave para búsqueda // ============================================ // CONTENIDO ESPECÍFICO (JSON flexible) // ============================================ content: JSON // Estructura específica por tipo y capa (ver secciones siguientes) // ============================================ // PRIORIDAD Y CLASIFICACIÓN CLÍNICA // ============================================ priority: Enum // 'critical' | 'high' | 'medium' | 'low' clinical_priority: Enum? // Prioridad clínica específica (si aplica) age_group: Enum? // 'adulto' | 'pediatrico' | 'neonatal' | 'todos' // ============================================ // ESTADO Y VALIDACIÓN // ============================================ status: Enum // 'draft' | 'in_review' | 'approved' | 'published' | 'archived' // Workflow de validación (ver entidad ValidationWorkflow) validation_status: Enum? // 'pending' | 'validated' | 'rejected' validated_by: UUID? // Usuario que validó (TES, Médico, Formador) validated_at: Timestamp? // Fecha de validación validation_expires_at: Timestamp? // Fecha de expiración de validación // ============================================ // RELACIONES CON OTRO CONTENIDO // ============================================ // Relaciones bidireccionales (ver entidad ContentRelation) related_content_ids: UUID[] // IDs de contenido relacionado (derivado de ContentRelation) // Relaciones específicas por tipo (para facilitar queries) related_protocol_ids: UUID[] // Si es guía, protocolos relacionados related_guide_ids: UUID[] // Si es protocolo, guías relacionadas related_manual_ids: UUID[] // Si es protocolo/guía, manual relacionado // ============================================ // RECURSOS MULTIMEDIA ASOCIADOS // ============================================ // Relaciones con recursos (ver entidad ContentResourceRelation) media_resource_ids: UUID[] // IDs de recursos asociados (derivado) // ============================================ // SCORM (Solo para guías formativas) // ============================================ scorm_enabled: Boolean // ¿Tiene versión SCORM disponible? scorm_package_id: UUID? // ID del paquete SCORM generado (ver entidad SCORMPackage) scorm_version: String? // Versión SCORM (ej: "1.2") // ============================================ // FUENTE Y TRAZABILIDAD // ============================================ source_guideline: Enum // 'ERC' | 'SEMES' | 'AHA' | 'INTERNO' | 'MANUAL_TES_DIGITAL' source_reference: String? // Referencia específica (ej: "BLOQUE_03_6_CONTROL_HEMORRAGIAS.md") source_year: Integer? // Año de la fuente source_url: String? // URL de la fuente (si aplica) // ============================================ // METADATOS DE GESTIÓN // ============================================ created_by: UUID // Usuario que creó created_at: Timestamp // Fecha de creación updated_by: UUID // Usuario que actualizó updated_at: Timestamp // Fecha de última actualización published_by: UUID? // Usuario que publicó published_at: Timestamp? // Fecha de publicación (si está published) // ============================================ // METADATOS ADICIONALES (JSON flexible) // ============================================ metadata: JSON // { // "tiempo_lectura": 15, // minutos // "dificultad": "intermedio", // basico | intermedio | avanzado // "checklist_mode": true, // Si tiene modo checklist // "secciones_count": 8, // Para guías // "steps_count": 10, // Para protocolos // ... // } } ``` #### 1.2 Estructura de `content` por Tipo y Capa **PROTOCOLO OPERATIVO (type='protocol', functional_layer='operativa'):** ```json { "steps": [ { "order": 1, "text": "Texto del paso", "critical": false, // ¿Es paso crítico? "verification": "¿Verificación opcional?", "warnings": ["Advertencia 1"], "media_resource_ids": ["uuid-imagen-paso"] } ], "warnings": ["Advertencia global 1"], "key_points": ["Punto clave 1"], "equipment": [ { "name": "Equipo 1", "required": true, "media_resource_id": "uuid-imagen-equipo" } ], "drugs": ["Fármaco 1", "Fármaco 2"], "checklist_mode": true, // ¿Tiene modo checklist? "checklist_steps": [ { "order": 1, "text": "Paso checklist", "critical": true } ], "category": "soporte_vital", "subcategory": "rcp", "age_group": "adulto" } ``` **GUÍA FORMATIVA (type='guide', functional_layer='formativa'):** ```json { "secciones": [ { "numero": 1, "titulo": "Introducción y Contexto", "archivo": "SECCION_01_NOMBRE.md", "ruta": "/docs/consolidado/SECCION_01_NOMBRE.md", "contenido_markdown": "...", // Contenido Markdown completo "tiempo_estimado": 5, // minutos "media_resource_ids": ["uuid-imagen-1", "uuid-video-1"] } ], "protocolo_operativo": { "id": "uuid-protocolo", "slug": "control-hemorragias", "titulo": "Control de Hemorragias Externas", "ruta": "/protocolos/control-hemorragias" }, "scorm_available": true, "tiempo_total_estimado": 40, "objetivos_aprendizaje": [ "Objetivo 1", "Objetivo 2" ] } ``` **MANUAL REFERENCIA (type='manual', functional_layer='referencia'):** ```json { "parte": 1, "parte_nombre": "Fundamentos y Evaluación Inicial", "bloque": 0, "bloque_nombre": "Fundamentos de Emergencias Prehospitalarias", "capitulo": "1.1.1", "ruta_archivo": "/manual/BLOQUE_XX/ARCHIVO.md", "ruta_url": "/manual/parte-i/bloque-0/1.1.1", "contenido_markdown": "...", "nivel_dificultad": "basico", // basico | intermedio | avanzado "importancia": "alta", // alta | media | baja "tiempo_lectura": 15, // minutos "navegacion": { "anterior": "1.0.1", "siguiente": "1.1.2", "relacionados": ["1.1.2", "1.1.3"] } } ``` **FÁRMACO REFERENCIA (type='drug', functional_layer='referencia'):** ```json { "generic_name": "Nombre genérico", "trade_name": "Nombre comercial", "category": "cardiovascular", "presentation": "Presentación", "adult_dose": "Dosis adulto", "pediatric_dose": "Dosis pediátrica", "routes": ["IV", "IM"], "dilution": "Dilución", "indications": ["Indicación 1"], "contraindications": ["Contraindicación 1"], "side_effects": ["Efecto adverso 1"], "antidote": "Antídoto", "notes": ["Nota 1"], "critical_points": ["Punto crítico TES 1"], "source": "Fuente" } ``` **CHECKLIST OPERATIVA (type='checklist', functional_layer='operativa'):** ```json { "phase": "pre_escena", // 'inicio_turno' | 'pre_escena' | 'post_servicio' "sections": [ { "id": "seccion-1", "title": "Título sección", "category": "oxigeno", "items": [ { "id": "item-1", "text": "Texto del item", "critical": false } ], "notes": "Notas opcionales" } ] } ``` --- ### 2. RECURSOS MULTIMEDIA (MediaResource) **Definición:** Imágenes, vídeos y otros recursos visuales asociados al contenido. ``` MediaResource { // ============================================ // IDENTIFICACIÓN // ============================================ id: UUID // ID único filename: String // Nombre del archivo (ej: "hemorragia_presion_directa.jpg") path: String // Ruta relativa en servidor (ej: "/storage/media/images/hemorragia/...") url: String? // URL pública completa (si está en CDN) thumbnail_url: String? // URL del thumbnail (para vídeos) // ============================================ // TIPO Y FORMATO // ============================================ type: Enum // 'image' | 'video' | 'audio' | 'document' format: String // 'jpg' | 'png' | 'svg' | 'webp' | 'mp4' | 'webm' | 'pdf' mime_type: String // 'image/jpeg', 'video/mp4', etc. // ============================================ // METADATOS VISUALES // ============================================ alt: String // Texto alternativo (accesibilidad - OBLIGATORIO) caption: String? // Caption opcional title: String? // Título del recurso // ============================================ // CLASIFICACIÓN // ============================================ functional_layer: Enum[] // ['operativa'] | ['formativa'] | ['referencia'] | // ['operativa', 'formativa'] (puede usarse en múltiples capas) clinical_topic: Enum? // Mismo enum que ContentItem (si aplica a tema específico) category: String? // Categoría (ej: "hemorragia", "rcp") tags: String[] // Tags para búsqueda // ============================================ // DIMENSIONES Y TAMAÑO // ============================================ width: Integer? // Ancho (para imágenes/vídeos) height: Integer? // Alto (para imágenes/vídeos) file_size: Integer // Tamaño en bytes duration: Integer? // Duración en segundos (para vídeos/audio) // ============================================ // PRIORIDAD Y USO // ============================================ priority: Enum // 'critical' | 'high' | 'medium' | 'low' usage_context: Enum[] // ['operativo'] | ['formativo'] | ['referencia'] | // ['operativo', 'formativo'] (dónde se usa) // ============================================ // FUENTE Y ATRIBUCIÓN // ============================================ source: String? // Fuente del recurso attribution: String? // Atribución si es necesario license: String? // Licencia (ej: "CC BY 4.0", "Propietario") // ============================================ // ESTADO // ============================================ status: Enum // 'draft' | 'approved' | 'published' | 'archived' // ============================================ // METADATOS DE GESTIÓN // ============================================ uploaded_by: UUID // Usuario que subió uploaded_at: Timestamp // Fecha de subida updated_at: Timestamp // Fecha de última actualización // ============================================ // METADATOS ADICIONALES // ============================================ metadata: JSON // { // "original_filename": "...", // "compression": "lossy", // "color_space": "RGB", // ... // } } ``` --- ### 3. RELACIONES CONTENIDO ⇄ RECURSOS (ContentResourceRelation) **Definición:** Asociación entre contenido y recursos multimedia con contexto. ``` ContentResourceRelation { id: UUID content_id: UUID // FK a ContentItem resource_id: UUID // FK a MediaResource // ============================================ // CONTEXTO DE LA RELACIÓN // ============================================ context: String? // Dónde se usa: // - Para protocolos: "step-3", "checklist-item-5", "header" // - Para guías: "seccion-2", "introduccion", "caso-clinico-1" // - Para manual: "capitulo-1.1.1", "figura-3" placement: Enum // 'inline' | 'before' | 'after' | 'modal' | 'sidebar' order: Integer? // Orden de aparición (si hay múltiples en mismo contexto) // ============================================ // PRIORIDAD Y CRITICIDAD // ============================================ is_primary: Boolean // ¿Es recurso principal? (para destacar) is_critical: Boolean // ¿Es crítico para comprensión? priority: Enum // 'critical' | 'high' | 'medium' | 'low' // ============================================ // METADATOS // ============================================ caption_override: String? // Caption específico para este contexto (sobrescribe el del recurso) created_at: Timestamp created_by: UUID } ``` --- ### 4. RELACIONES CONTENIDO ⇄ CONTENIDO (ContentRelation) **Definición:** Relaciones bidireccionales entre contenidos (Protocolo ↔ Guía ↔ Manual). ``` ContentRelation { id: UUID source_content_id: UUID // FK a ContentItem (origen) target_content_id: UUID // FK a ContentItem (destino) // ============================================ // TIPO DE RELACIÓN // ============================================ relation_type: Enum // 'protocol_to_guide' | 'guide_to_protocol' | // 'protocol_to_manual' | 'guide_to_manual' | // 'manual_to_protocol' | 'manual_to_guide' | // 'related' (contenido relacionado genérico) // ============================================ // BIDIRECCIONALIDAD // ============================================ is_bidirectional: Boolean // ¿Es bidireccional? // Si true, se crea automáticamente la relación inversa // ============================================ // METADATOS // ============================================ order: Integer? // Orden de aparición (si hay múltiples relaciones) label: String? // Etiqueta opcional (ej: "Guía formativa relacionada") metadata: JSON? // { // "description": "Relación entre protocolo y guía", // "auto_generated": true, // ... // } created_at: Timestamp created_by: UUID } ``` **Reglas:** - Si `is_bidirectional = true`, el sistema crea automáticamente la relación inversa - Las relaciones `protocol_to_guide` y `guide_to_protocol` son siempre bidireccionales - Las relaciones `protocol_to_manual` y `guide_to_manual` pueden ser unidireccionales --- ### 5. VERSIONADO (ContentVersion) **Definición:** Historial de versiones de contenido para versionado semántico y rollback. ``` ContentVersion { id: UUID content_id: UUID // FK a ContentItem (versión actual) version: String // Versión semántica (ej: "1.2.3") previous_version_id: UUID? // FK a ContentVersion (versión anterior) // ============================================ // CONTENIDO DE LA VERSIÓN // ============================================ content: JSON // Snapshot completo del contenido en esta versión content_hash: String // Hash SHA-256 del contenido (para detectar cambios) // ============================================ // CAMBIOS // ============================================ change_summary: String // Resumen de cambios (ej: "Añadido paso de torniquete") change_details: JSON // Detalles de cambios: // { // "added": ["step-5"], // "modified": ["step-3"], // "deleted": [], // "fields_changed": ["title", "content.steps"] // } change_type: Enum // 'major' | 'minor' | 'patch' // - major: Cambios incompatibles (ej: estructura diferente) // - minor: Nuevas funcionalidades compatibles // - patch: Correcciones compatibles is_breaking: Boolean // ¿Es cambio incompatible? (derivado de change_type) // ============================================ // METADATOS // ============================================ created_at: Timestamp created_by: UUID published_at: Timestamp? // Fecha de publicación de esta versión published_by: UUID? // Usuario que publicó esta versión // ============================================ // ESTADO // ============================================ is_active: Boolean // ¿Es la versión activa? (solo una por content_id) } ``` **Reglas de Versionado:** - Versionado semántico: `MAJOR.MINOR.PATCH` - Cada cambio crea una nueva versión - Solo una versión puede estar `is_active = true` por `content_id` - Las versiones anteriores se mantienen para rollback --- ### 6. VALIDACIÓN Y WORKFLOW (ValidationWorkflow) **Definición:** Workflow completo de validación clínica con historial. ``` ValidationWorkflow { id: UUID content_id: UUID // FK a ContentItem // ============================================ // ESTADO ACTUAL // ============================================ current_status: Enum // 'draft' | 'submitted' | 'in_review' | // 'approved' | 'rejected' | 'published' // ============================================ // HISTORIAL COMPLETO // ============================================ history: JSON[] // Array de eventos ordenados: // [ // { // "status": "draft", // "timestamp": "2025-01-06T10:00:00Z", // "user_id": "uuid", // "user_role": "editor_clinico", // "notes": "Creación inicial" // }, // { // "status": "submitted", // "timestamp": "2025-01-06T11:00:00Z", // "user_id": "uuid", // "user_role": "editor_clinico", // "notes": "Enviado para revisión" // }, // { // "status": "in_review", // "timestamp": "2025-01-06T12:00:00Z", // "user_id": "uuid", // "user_role": "tes_validador", // "reviewer_id": "uuid", // "notes": "En revisión por TES" // }, // { // "status": "approved", // "timestamp": "2025-01-06T14:00:00Z", // "user_id": "uuid", // "user_role": "tes_validador", // "reviewer_id": "uuid", // "notes": "Aprobado para publicación" // } // ] // ============================================ // EVENTOS ESPECÍFICOS (para queries rápidas) // ============================================ submitted_at: Timestamp? submitted_by: UUID? reviewed_at: Timestamp? reviewed_by: UUID? reviewer_role: Enum? // 'tes_validador' | 'medico' | 'formador' approved_at: Timestamp? approved_by: UUID? rejected_at: Timestamp? rejected_by: UUID? rejection_reason: String? // ============================================ // VALIDACIÓN CLÍNICA // ============================================ clinical_validation: JSON? // { // "validated_by_tes": true, // "validated_by_medico": false, // "validated_by_formador": true, // "validation_notes": "...", // "compliance_checklist": { // "protocolo_correcto": true, // "recursos_asociados": true, // "relaciones_correctas": true // } // } // ============================================ // METADATOS // ============================================ updated_at: Timestamp } ``` **Workflow de Estados:** ``` draft → submitted → in_review → approved → published ↓ ↓ ↓ rejected rejected rejected ``` --- ### 7. SCORM (SCORMPackage) **Definición:** Paquetes SCORM generados desde guías formativas. ``` SCORMPackage { id: UUID content_id: UUID // FK a ContentItem (type='guide', functional_layer='formativa') // ============================================ // IDENTIFICACIÓN SCORM // ============================================ scorm_version: String // '1.2' (versión SCORM soportada) package_id: String // ID único del paquete SCORM title: String // Título del paquete // ============================================ // ARCHIVO // ============================================ package_path: String // Ruta al archivo ZIP del paquete package_url: String? // URL pública del paquete package_size: Integer // Tamaño en bytes package_hash: String // Hash SHA-256 del paquete // ============================================ // METADATOS SCORM // ============================================ metadata: JSON // { // "organization": "TES Digital", // "identifier": "uuid", // "title": "Guía de Refuerzo: RCP Adulto SVB", // "description": "...", // "mastery_score": 80, // "time_limit_action": "exit,message", // "launch_data": "...", // "max_time_allowed": "PT40M", // "prerequisites": "..." // } // ============================================ // ESTADO // ============================================ status: Enum // 'generating' | 'ready' | 'error' | 'archived' generation_error: String? // Mensaje de error si falló la generación // ============================================ // METADATOS DE GESTIÓN // ============================================ generated_at: Timestamp generated_by: UUID updated_at: Timestamp } ``` **Reglas:** - Solo guías formativas pueden tener paquetes SCORM - Un ContentItem puede tener múltiples versiones SCORM (una por versión del contenido) - El paquete se genera automáticamente cuando `scorm_enabled = true` y el contenido está `published` --- ### 8. USUARIOS Y ROLES (User) **Definición:** Usuarios del panel de administración con roles y permisos granulares. ``` User { id: UUID email: String // Email único username: String // Nombre de usuario password_hash: String // Hash de contraseña (bcrypt) // ============================================ // ROLES // ============================================ role: Enum // 'super_admin' | 'admin' | // 'editor_clinico' | 'editor_formativo' | // 'revisor' | 'viewer' | // 'tes_validador' | 'formador' | 'medico' // ============================================ // PERMISOS GRANULARES (JSON) // ============================================ permissions: JSON // { // "content": { // "create": true, // "edit": true, // "delete": false, // "publish": false, // "validate": true // }, // "media": { // "upload": true, // "delete": false // }, // "scorm": { // "generate": true, // "export": true // }, // "validation": { // "approve": true, // "reject": true // }, // "admin": { // "manage_users": false, // "manage_roles": false // } // } // ============================================ // ESTADO // ============================================ is_active: Boolean last_login: Timestamp? // ============================================ // METADATOS // ============================================ created_at: Timestamp updated_at: Timestamp } ``` **Roles y Permisos por Defecto:** | Rol | Descripción | Permisos Clave | |-----|-------------|----------------| | `super_admin` | Administrador total | Todos los permisos | | `admin` | Administrador | Gestión de contenido y usuarios | | `editor_clinico` | Editor contenido clínico | Crear/editar protocolos, fármacos | | `editor_formativo` | Editor contenido formativo | Crear/editar guías formativas | | `revisor` | Revisor general | Revisar y aprobar contenido | | `tes_validador` | Validador TES | Validar contenido operativo | | `medico` | Médico validador | Validar contenido clínico | | `formador` | Formador | Validar contenido formativo | | `viewer` | Solo lectura | Ver contenido | --- ### 9. AUDITORÍA (AuditLog) **Definición:** Registro completo de todas las acciones del sistema. ``` AuditLog { id: UUID entity_type: String // 'content_item' | 'media_resource' | // 'content_relation' | 'content_version' | // 'scorm_package' | 'user' | 'validation_workflow' entity_id: UUID // ID de la entidad afectada // ============================================ // ACCIÓN // ============================================ action: Enum // 'create' | 'update' | 'delete' | // 'publish' | 'unpublish' | 'archive' | // 'approve' | 'reject' | 'submit' | // 'validate' | 'generate_scorm' | // 'upload_media' | 'delete_media' action_details: JSON? // Detalles específicos de la acción: // { // "fields_changed": ["title", "content"], // "old_value": {...}, // "new_value": {...}, // "reason": "..." // } // ============================================ // USUARIO Y CONTEXTO // ============================================ user_id: UUID // FK a User user_role: String // Rol del usuario (snapshot al momento de la acción) ip_address: String? // IP desde la que se realizó la acción user_agent: String? // User agent del navegador // ============================================ // METADATOS // ============================================ timestamp: Timestamp // Fecha y hora exacta metadata: JSON? // Metadatos adicionales: // { // "session_id": "...", // "request_id": "...", // "duration_ms": 150, // ... // } } ``` **Reglas:** - Todas las acciones importantes se registran - Los logs nunca se eliminan (solo se archivan) - Los logs son inmutables (no se pueden modificar) --- ## 🔗 RELACIONES ENTRE ENTIDADES ``` ContentItem (1) ──< (N) ContentResourceRelation (N) >── (1) MediaResource ContentItem (1) ──< (N) ContentRelation (N) >── (1) ContentItem ContentItem (1) ──< (N) ContentVersion ContentItem (1) ──< (1) ValidationWorkflow ContentItem (1) ──< (N) SCORMPackage User (1) ──< (N) ContentItem (created_by, updated_by, published_by) User (1) ──< (N) MediaResource (uploaded_by) User (1) ──< (N) AuditLog ``` --- ## 📦 EXPORTACIÓN A CONTENT PACK JSON El Content Pack se genera desde estas entidades siguiendo esta estructura: ```json { "metadata": { "version": "1.0.0", "generated_at": "2025-01-06T12:00:00Z", "hash": "sha256:...", "total_items": 150, "total_resources": 200, "source": "server" }, "content": { "protocols": [ // ContentItem donde: // type='protocol' AND functional_layer='operativa' AND status='published' // Incluye: id, slug, title, short_title, description, content, priority, etc. ], "guides": [ // ContentItem donde: // type='guide' AND functional_layer='formativa' AND status='published' ], "drugs": [ // ContentItem donde: // type='drug' AND functional_layer='referencia' AND status='published' ], "checklists": [ // ContentItem donde: // type='checklist' AND functional_layer='operativa' AND status='published' ], "manuals": [ // ContentItem donde: // type='manual' AND functional_layer='referencia' AND status='published' ] }, "media": { "images": [ // MediaResource donde type='image' AND status='published' // Incluye: id, filename, path, url, alt, caption, etc. ], "videos": [ // MediaResource donde type='video' AND status='published' ] }, "relations": { "content_to_content": [ // ContentRelation donde ambos contenidos están published // Incluye: source_content_id, target_content_id, relation_type, is_bidirectional ], "content_to_media": [ // ContentResourceRelation donde ambos están published // Incluye: content_id, resource_id, context, order, is_primary ] } } ``` **Reglas del Content Pack:** - Solo incluye contenido con `status='published'` - Solo incluye la última versión (`is_latest=true`) - Las relaciones están incluidas explícitamente - Los recursos multimedia tienen URLs relativas o absolutas - El hash se calcula sobre `content` + `media` + `relations` --- ## 🎯 PRINCIPIOS DE DISEÑO ### 1. Separación de Capas Funcionales **CRÍTICO:** El campo `functional_layer` garantiza que: - ✅ Contenido operativo NO se mezcle con formativo - ✅ Contenido formativo NO se mezcle con referencia - ✅ Cada capa tiene su propia estructura de `content` - ✅ Las relaciones entre capas están explícitas ### 2. Versionado Semántico - **Major (X.0.0):** Cambios incompatibles (estructura diferente) - **Minor (0.X.0):** Nuevas funcionalidades compatibles - **Patch (0.0.X):** Correcciones compatibles ### 3. Estados y Workflow ``` draft → submitted → in_review → approved → published ↓ ↓ ↓ rejected rejected rejected ``` ### 4. Trazabilidad Completa - Todo cambio queda registrado en `AuditLog` - Historial de versiones en `ContentVersion` - Workflow de validación en `ValidationWorkflow` ### 5. Relaciones Bidireccionales - `ContentRelation` permite relaciones bidireccionales - Campo `is_bidirectional` indica si la relación es simétrica - El Content Pack incluye todas las relaciones ### 6. Offline-First - El Content Pack es autocontenido (incluye todo lo necesario) - Las relaciones están incluidas en el pack - Los recursos multimedia tienen URLs relativas o absolutas - El sistema funciona sin conexión usando cache ### 7. SCORM-Ready - Solo guías formativas pueden tener SCORM - El paquete SCORM se genera automáticamente - Metadatos SCORM completos en `SCORMPackage` --- ## ✅ VALIDACIONES Y CONSTRAINTS ### Validaciones de Contenido 1. **Separación de Capas:** - Un ContentItem SOLO puede tener `functional_layer` de UNA capa - El `content` JSON debe ser compatible con la capa 2. **Tipo y Capa:** - `type='protocol'` → `functional_layer='operativa'` (SIEMPRE) - `type='guide'` → `functional_layer='formativa'` (SIEMPRE) - `type='drug'` → `functional_layer='referencia'` (SIEMPRE) - `type='manual'` → `functional_layer='referencia'` (SIEMPRE) - `type='checklist'` → `functional_layer='operativa'` (SIEMPRE) 3. **Versionado:** - `version` debe seguir formato semántico: `^\d+\.\d+\.\d+$` - Solo una versión puede tener `is_active=true` por `content_id` 4. **SCORM:** - Solo `type='guide'` y `functional_layer='formativa'` pueden tener SCORM - Si `scorm_enabled=true`, debe existir `SCORMPackage` asociado ### Validaciones de Relaciones 1. **ContentRelation:** - `source_content_id != target_content_id` (no auto-relaciones) - Si `is_bidirectional=true`, debe existir relación inversa 2. **ContentResourceRelation:** - `content_id` y `resource_id` deben existir - `context` debe ser válido para el tipo de contenido ### Validaciones de Workflow 1. **ValidationWorkflow:** - `current_status` debe seguir el workflow definido - No se puede pasar de `published` a `draft` directamente - `rejected` requiere `rejection_reason` --- ## 📝 NOTAS TÉCNICAS ### Campos JSON Flexibles Los campos `content` y `metadata` son JSON para permitir: - ✅ Estructuras específicas por tipo de contenido - ✅ Extensibilidad sin cambios de esquema - ✅ Metadatos adicionales sin migraciones ### UUIDs vs IDs Numéricos - **UUIDs** para IDs principales (mejor para distribución, sin colisiones) - **Strings** para slugs (legibles, SEO-friendly) ### Timestamps - Todos los timestamps en formato ISO 8601 - Timezone: UTC - Incluir `created_at` y `updated_at` en todas las entidades principales ### Soft Deletes - No eliminar físicamente contenido publicado - Usar campo `status='archived'` para "eliminación" - Mantener historial completo --- ## 🚀 COMPATIBILIDAD ### Panel de Administración - ✅ CRUD completo para todas las entidades - ✅ Editor visual para cada tipo de contenido - ✅ Gestor de recursos multimedia - ✅ Sistema de validación con workflow - ✅ Generación de SCORM - ✅ Exportación de Content Pack ### Content Pack JSON - ✅ Estructura autocontenida - ✅ Incluye relaciones explícitas - ✅ URLs relativas/absolutas para recursos - ✅ Hash para verificación de integridad ### Uso Offline - ✅ Content Pack descargable y cacheable - ✅ Funciona sin conexión - ✅ Fallback a datos locales garantizado ### SCORM - ✅ Generación automática desde guías formativas - ✅ Metadatos SCORM 1.2 completos - ✅ Exportación como ZIP --- **Última actualización:** 2025-01-06 **Estado:** ✅ MODELO CANÓNICO DEFINITIVO - FUENTE DE VERDAD