600 lines
17 KiB
Markdown
600 lines
17 KiB
Markdown
|
|
# 🅲 FASE C - MODELO DE DATOS CANÓNICO
|
||
|
|
|
||
|
|
**Proyecto:** Guía TES - Sistema de Gestión de Contenido
|
||
|
|
**Fecha:** 2025-01-06
|
||
|
|
**Arquitecto:** Sistema de Contenido
|
||
|
|
**Estado:** 📋 PLANIFICACIÓN
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 OBJETIVO
|
||
|
|
|
||
|
|
Definir el modelo de datos lógico (conceptual) que soporte:
|
||
|
|
- ✅ Base de datos en nuestro servidor (PostgreSQL/SQLite)
|
||
|
|
- ✅ Panel de administración completo
|
||
|
|
- ✅ Exportación a JSON (Content Pack)
|
||
|
|
- ✅ Consumo offline por la app PWA
|
||
|
|
- ✅ Versionado y reversibilidad
|
||
|
|
- ✅ Estados de validación (draft → review → approved → published)
|
||
|
|
|
||
|
|
**⚠️ IMPORTANTE:** Este es un modelo **CONCEPTUAL**, no tecnológico. Define QUÉ datos necesitamos, no CÓMO implementarlos.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 ENTIDADES PRINCIPALES
|
||
|
|
|
||
|
|
### 1. CONTENIDO (Content Items)
|
||
|
|
|
||
|
|
Representa cualquier pieza de contenido: protocolos, guías, manual, fármacos, checklists.
|
||
|
|
|
||
|
|
#### 1.1 Identificación y Clasificación
|
||
|
|
|
||
|
|
```
|
||
|
|
ContentItem {
|
||
|
|
// IDENTIFICACIÓN ÚNICA
|
||
|
|
id: UUID // ID único e inmutable
|
||
|
|
slug: String // Identificador legible (ej: "control-hemorragias")
|
||
|
|
version: String // Versión semántica (ej: "1.2.3")
|
||
|
|
latest_version: Boolean // ¿Es la última versión?
|
||
|
|
|
||
|
|
// CLASIFICACIÓN
|
||
|
|
type: Enum // 'protocol' | 'guide' | 'manual' | 'drug' | 'checklist'
|
||
|
|
category: String // Categoría clínica (ej: "soporte_vital", "trauma")
|
||
|
|
subcategory: String? // Subcategoría opcional (ej: "hemorragia", "shock")
|
||
|
|
|
||
|
|
// CAPA FUNCIONAL (CRÍTICO: NO MEZCLAR)
|
||
|
|
functional_layer: Enum // 'operativa' | 'formativa' | 'referencia'
|
||
|
|
|
||
|
|
// METADATOS BÁSICOS
|
||
|
|
title: String // Título completo
|
||
|
|
short_title: String? // Título corto (para UI)
|
||
|
|
description: String // Descripción breve
|
||
|
|
keywords: String[] // Palabras clave para búsqueda
|
||
|
|
|
||
|
|
// CONTENIDO ESPECÍFICO
|
||
|
|
content: JSON // Estructura específica por tipo (ver abajo)
|
||
|
|
|
||
|
|
// 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'
|
||
|
|
validation_workflow: JSON? // Historial de validación (ver abajo)
|
||
|
|
|
||
|
|
// RELACIONES
|
||
|
|
related_content: UUID[] // IDs de contenido relacionado (bidireccional)
|
||
|
|
parent_content: UUID? // Si es versión de otro contenido
|
||
|
|
replaces_content: UUID? // Si reemplaza a otro contenido
|
||
|
|
|
||
|
|
// RECURSOS ASOCIADOS
|
||
|
|
media_resources: UUID[] // IDs de recursos multimedia asociados
|
||
|
|
|
||
|
|
// 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_at: Timestamp? // Fecha de publicación (si está published)
|
||
|
|
published_by: UUID? // Usuario que publicó
|
||
|
|
|
||
|
|
// FUENTE Y TRAZABILIDAD
|
||
|
|
source_guideline: String? // Fuente clínica (ej: "Manual TES Digital", "ERC 2021")
|
||
|
|
source_reference: String? // Referencia específica (ej: "BLOQUE_03_6_CONTROL_HEMORRAGIAS.md")
|
||
|
|
|
||
|
|
// METADATOS ADICIONALES
|
||
|
|
metadata: JSON // Metadatos flexibles (tiempo_lectura, dificultad, etc.)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.2 Estructura de `content` por Tipo
|
||
|
|
|
||
|
|
**PROTOCOLO (Operativa):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"steps": [
|
||
|
|
{
|
||
|
|
"order": 1,
|
||
|
|
"text": "Texto del paso",
|
||
|
|
"critical": false,
|
||
|
|
"verification": "¿Verificación opcional?",
|
||
|
|
"warnings": ["Advertencia 1", "Advertencia 2"]
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"warnings": ["Advertencia global 1"],
|
||
|
|
"key_points": ["Punto clave 1"],
|
||
|
|
"equipment": ["Equipo 1", "Equipo 2"],
|
||
|
|
"drugs": ["Fármaco 1", "Fármaco 2"],
|
||
|
|
"checklist_mode": true,
|
||
|
|
"checklist_steps": ["Paso checklist 1"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**GUÍA (Formativa):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"secciones": [
|
||
|
|
{
|
||
|
|
"numero": 1,
|
||
|
|
"titulo": "Título de sección",
|
||
|
|
"archivo": "SECCION_01_NOMBRE.md",
|
||
|
|
"ruta": "/docs/consolidado/SECCION_01_NOMBRE.md",
|
||
|
|
"contenido_markdown": "...",
|
||
|
|
"tiempo_estimado": 5
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"protocolo_operativo": {
|
||
|
|
"id": "uuid-protocolo",
|
||
|
|
"titulo": "Título del protocolo",
|
||
|
|
"ruta": "/ruta-protocolo"
|
||
|
|
},
|
||
|
|
"scorm_available": false,
|
||
|
|
"tiempo_total_estimado": 40
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**MANUAL (Referencia):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"parte": 1,
|
||
|
|
"parte_nombre": "Nombre de parte",
|
||
|
|
"bloque": 0,
|
||
|
|
"bloque_nombre": "Nombre de bloque",
|
||
|
|
"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",
|
||
|
|
"importancia": "alta",
|
||
|
|
"tiempo_lectura": 15,
|
||
|
|
"navegacion": {
|
||
|
|
"anterior": "1.0.1",
|
||
|
|
"siguiente": "1.1.2",
|
||
|
|
"relacionados": ["1.1.2", "1.1.3"]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**FÁRMACO (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):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"phase": "pre_escena",
|
||
|
|
"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 (Media Resources)
|
||
|
|
|
||
|
|
Representa imágenes, vídeos y otros recursos visuales.
|
||
|
|
|
||
|
|
```
|
||
|
|
MediaResource {
|
||
|
|
// IDENTIFICACIÓN
|
||
|
|
id: UUID // ID único
|
||
|
|
filename: String // Nombre del archivo (ej: "hemorragia_presion_directa.jpg")
|
||
|
|
path: String // Ruta relativa (ej: "/assets/infografias/hemorragia/...")
|
||
|
|
url: String? // URL completa (si está en CDN)
|
||
|
|
|
||
|
|
// TIPO Y FORMATO
|
||
|
|
type: Enum // 'image' | 'video' | 'audio' | 'document'
|
||
|
|
format: String // 'jpg' | 'png' | 'svg' | 'mp4' | 'webm' | 'pdf'
|
||
|
|
mime_type: String // 'image/jpeg', 'video/mp4', etc.
|
||
|
|
|
||
|
|
// METADATOS VISUALES
|
||
|
|
alt: String // Texto alternativo (accesibilidad)
|
||
|
|
caption: String? // Caption opcional
|
||
|
|
title: String? // Título del recurso
|
||
|
|
|
||
|
|
// CLASIFICACIÓN
|
||
|
|
functional_layer: Enum // 'operativa' | 'formativa' | 'referencia'
|
||
|
|
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_type: Enum[] // ['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")
|
||
|
|
|
||
|
|
// ESTADO
|
||
|
|
status: Enum // 'draft' | 'approved' | 'published' | 'archived'
|
||
|
|
|
||
|
|
// METADATOS DE GESTIÓN
|
||
|
|
created_by: UUID
|
||
|
|
created_at: Timestamp
|
||
|
|
updated_by: UUID
|
||
|
|
updated_at: Timestamp
|
||
|
|
uploaded_at: Timestamp // Fecha de subida
|
||
|
|
|
||
|
|
// METADATOS ADICIONALES
|
||
|
|
metadata: JSON // Metadatos flexibles
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. RELACIONES CONTENIDO ⇄ RECURSOS
|
||
|
|
|
||
|
|
Tabla de relación muchos-a-muchos entre contenido y recursos multimedia.
|
||
|
|
|
||
|
|
```
|
||
|
|
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 (ej: "step-3", "seccion-2", "header")
|
||
|
|
order: Integer? // Orden de aparición
|
||
|
|
is_primary: Boolean // ¿Es recurso principal?
|
||
|
|
|
||
|
|
// METADATOS
|
||
|
|
created_at: Timestamp
|
||
|
|
created_by: UUID
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. RELACIONES CONTENIDO ⇄ CONTENIDO
|
||
|
|
|
||
|
|
Tabla de relación bidireccional 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'
|
||
|
|
|
||
|
|
// METADATOS
|
||
|
|
is_bidirectional: Boolean // ¿Es bidireccional?
|
||
|
|
order: Integer? // Orden de aparición
|
||
|
|
metadata: JSON? // Metadatos adicionales
|
||
|
|
|
||
|
|
created_at: Timestamp
|
||
|
|
created_by: UUID
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 5. VERSIONADO
|
||
|
|
|
||
|
|
Historial de versiones de contenido.
|
||
|
|
|
||
|
|
```
|
||
|
|
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)
|
||
|
|
|
||
|
|
// CAMBIOS
|
||
|
|
change_summary: String // Resumen de cambios
|
||
|
|
change_details: JSON // Detalles de cambios (qué cambió)
|
||
|
|
change_type: Enum // 'major' | 'minor' | 'patch'
|
||
|
|
|
||
|
|
// METADATOS
|
||
|
|
created_at: Timestamp
|
||
|
|
created_by: UUID
|
||
|
|
published_at: Timestamp?
|
||
|
|
published_by: UUID?
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 6. VALIDACIÓN Y WORKFLOW
|
||
|
|
|
||
|
|
Historial de validación y estados.
|
||
|
|
|
||
|
|
```
|
||
|
|
ValidationWorkflow {
|
||
|
|
id: UUID
|
||
|
|
content_id: UUID // FK a ContentItem
|
||
|
|
|
||
|
|
// ESTADO ACTUAL
|
||
|
|
current_status: Enum // 'draft' | 'in_review' | 'approved' | 'rejected' | 'published'
|
||
|
|
|
||
|
|
// HISTORIAL
|
||
|
|
history: JSON[] // Array de eventos:
|
||
|
|
// [
|
||
|
|
// {
|
||
|
|
// "status": "draft",
|
||
|
|
// "timestamp": "...",
|
||
|
|
// "user_id": "...",
|
||
|
|
// "notes": "..."
|
||
|
|
// },
|
||
|
|
// {
|
||
|
|
// "status": "in_review",
|
||
|
|
// "timestamp": "...",
|
||
|
|
// "user_id": "...",
|
||
|
|
// "reviewer_id": "...",
|
||
|
|
// "notes": "..."
|
||
|
|
// }
|
||
|
|
// ]
|
||
|
|
|
||
|
|
// METADATOS
|
||
|
|
submitted_at: Timestamp?
|
||
|
|
submitted_by: UUID?
|
||
|
|
reviewed_at: Timestamp?
|
||
|
|
reviewed_by: UUID?
|
||
|
|
approved_at: Timestamp?
|
||
|
|
approved_by: UUID?
|
||
|
|
rejected_at: Timestamp?
|
||
|
|
rejected_by: UUID?
|
||
|
|
rejection_reason: String?
|
||
|
|
|
||
|
|
updated_at: Timestamp
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 7. USUARIOS Y ROLES
|
||
|
|
|
||
|
|
Gestión de usuarios del panel de administración.
|
||
|
|
|
||
|
|
```
|
||
|
|
User {
|
||
|
|
id: UUID
|
||
|
|
email: String // Email único
|
||
|
|
username: String // Nombre de usuario
|
||
|
|
password_hash: String // Hash de contraseña
|
||
|
|
role: Enum // 'super_admin' | 'admin' | 'editor_clinico' |
|
||
|
|
// 'editor_formativo' | 'revisor' | 'viewer' |
|
||
|
|
// 'tes_validador' | 'formador' | 'medico'
|
||
|
|
|
||
|
|
// PERMISOS GRANULARES
|
||
|
|
permissions: JSON // {
|
||
|
|
// "content:create": true,
|
||
|
|
// "content:edit": true,
|
||
|
|
// "content:delete": false,
|
||
|
|
// "content:publish": false,
|
||
|
|
// "content:validate": true,
|
||
|
|
// "media:upload": true,
|
||
|
|
// ...
|
||
|
|
// }
|
||
|
|
|
||
|
|
// ESTADO
|
||
|
|
is_active: Boolean
|
||
|
|
last_login: Timestamp?
|
||
|
|
|
||
|
|
// METADATOS
|
||
|
|
created_at: Timestamp
|
||
|
|
updated_at: Timestamp
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 8. AUDITORÍA
|
||
|
|
|
||
|
|
Registro de todas las acciones importantes.
|
||
|
|
|
||
|
|
```
|
||
|
|
AuditLog {
|
||
|
|
id: UUID
|
||
|
|
entity_type: String // 'content_item' | 'media_resource' | 'user' | ...
|
||
|
|
entity_id: UUID // ID de la entidad
|
||
|
|
|
||
|
|
// ACCIÓN
|
||
|
|
action: Enum // 'create' | 'update' | 'delete' | 'publish' |
|
||
|
|
// 'approve' | 'reject' | 'submit' | ...
|
||
|
|
action_details: JSON? // Detalles de la acción
|
||
|
|
|
||
|
|
// USUARIO Y CONTEXTO
|
||
|
|
user_id: UUID // FK a User
|
||
|
|
user_role: String // Rol del usuario (snapshot)
|
||
|
|
ip_address: String?
|
||
|
|
user_agent: String?
|
||
|
|
|
||
|
|
// METADATOS
|
||
|
|
timestamp: Timestamp
|
||
|
|
metadata: JSON? // Metadatos adicionales
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔗 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
|
||
|
|
User (1) ──< (N) ContentItem (created_by, updated_by, published_by)
|
||
|
|
User (1) ──< (N) MediaResource (created_by, updated_by)
|
||
|
|
User (1) ──< (N) AuditLog
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📦 EXPORTACIÓN A CONTENT PACK
|
||
|
|
|
||
|
|
El Content Pack JSON se genera desde estas entidades:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"version": "1.0.0",
|
||
|
|
"generatedAt": "2025-01-06T12:00:00Z",
|
||
|
|
"hash": "sha256:...",
|
||
|
|
"content": {
|
||
|
|
"protocols": [
|
||
|
|
// ContentItem donde type='protocol' AND status='published' AND functional_layer='operativa'
|
||
|
|
// Incluye: id, slug, title, short_title, description, content, priority, etc.
|
||
|
|
],
|
||
|
|
"guides": [
|
||
|
|
// ContentItem donde type='guide' AND status='published' AND functional_layer='formativa'
|
||
|
|
],
|
||
|
|
"drugs": [
|
||
|
|
// ContentItem donde type='drug' AND status='published' AND functional_layer='referencia'
|
||
|
|
],
|
||
|
|
"checklists": [
|
||
|
|
// ContentItem donde type='checklist' AND status='published' AND functional_layer='operativa'
|
||
|
|
],
|
||
|
|
"manuals": [
|
||
|
|
// ContentItem donde type='manual' AND status='published' AND functional_layer='referencia'
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"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": [
|
||
|
|
// ContentRelation donde ambos contenidos están published
|
||
|
|
// Incluye: source_content_id, target_content_id, relation_type
|
||
|
|
],
|
||
|
|
"content_resource_relations": [
|
||
|
|
// ContentResourceRelation donde ambos están published
|
||
|
|
// Incluye: content_id, resource_id, context, order
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 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`
|
||
|
|
|
||
|
|
### 2. Versionado Semántico
|
||
|
|
|
||
|
|
- **Major (X.0.0):** Cambios incompatibles
|
||
|
|
- **Minor (0.X.0):** Nuevas funcionalidades compatibles
|
||
|
|
- **Patch (0.0.X):** Correcciones compatibles
|
||
|
|
|
||
|
|
### 3. Estados y Workflow
|
||
|
|
|
||
|
|
```
|
||
|
|
draft → in_review → approved → published
|
||
|
|
↓ ↓ ↓
|
||
|
|
rejected rejected archived
|
||
|
|
```
|
||
|
|
|
||
|
|
### 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
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 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
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 PRÓXIMOS PASOS
|
||
|
|
|
||
|
|
1. **Validar modelo** con stakeholders clínicos
|
||
|
|
2. **Diseñar esquema de base de datos** (PostgreSQL recomendado)
|
||
|
|
3. **Implementar migraciones** de base de datos
|
||
|
|
4. **Crear API REST** para panel de administración
|
||
|
|
5. **Implementar generación de Content Pack** desde base de datos
|
||
|
|
6. **Crear panel de administración** con CRUD completo
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Última actualización:** 2025-01-06
|
||
|
|
**Estado:** ✅ Modelo conceptual completo
|
||
|
|
|