1061 lines
39 KiB
Markdown
1061 lines
39 KiB
Markdown
# 📐 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
|
|
|