268 lines
9.5 KiB
SQL
268 lines
9.5 KiB
SQL
-- ============================================
|
|
-- MIGRACIÓN 003: Esquema de Content Items (tes_content)
|
|
-- ============================================
|
|
--
|
|
-- Crea las tablas necesarias para content_items en tes_content
|
|
-- Unifica el schema para que Content Pack Generator funcione correctamente
|
|
--
|
|
-- IMPORTANTE: Este schema es compatible con el Content Pack Generator
|
|
-- que busca tes_content.content_items
|
|
--
|
|
|
|
-- Crear schema si no existe
|
|
CREATE SCHEMA IF NOT EXISTS tes_content;
|
|
|
|
-- Extensión para UUIDs
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- ============================================
|
|
-- ENUM: content_status
|
|
-- ============================================
|
|
CREATE TYPE tes_content.content_status AS ENUM (
|
|
'draft',
|
|
'in_review',
|
|
'approved',
|
|
'published',
|
|
'archived'
|
|
);
|
|
|
|
-- ============================================
|
|
-- ENUM: content_priority
|
|
-- ============================================
|
|
CREATE TYPE tes_content.content_priority AS ENUM (
|
|
'critica',
|
|
'alta',
|
|
'media',
|
|
'baja'
|
|
);
|
|
|
|
-- ============================================
|
|
-- TABLA: content_items
|
|
-- Propósito: Almacena todos los tipos de contenido (protocolos, guías, etc.)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS tes_content.content_items (
|
|
-- Identificación
|
|
id VARCHAR(100) PRIMARY KEY,
|
|
type VARCHAR(50) NOT NULL,
|
|
slug VARCHAR(200) UNIQUE NOT NULL,
|
|
level VARCHAR(50) NOT NULL,
|
|
|
|
-- Contenido
|
|
title VARCHAR(500) NOT NULL,
|
|
short_title VARCHAR(200),
|
|
description TEXT,
|
|
content JSONB NOT NULL,
|
|
content_markdown TEXT,
|
|
|
|
-- Metadatos
|
|
category VARCHAR(100),
|
|
subcategory VARCHAR(100),
|
|
priority tes_content.content_priority,
|
|
age_group VARCHAR(20),
|
|
clinical_context TEXT,
|
|
source_guideline VARCHAR(200),
|
|
|
|
-- Tags y clasificación
|
|
tags TEXT[],
|
|
|
|
-- Versionado
|
|
version INTEGER NOT NULL DEFAULT 1,
|
|
latest_version INTEGER NOT NULL DEFAULT 1,
|
|
|
|
-- Estado y validación
|
|
status tes_content.content_status NOT NULL DEFAULT 'draft',
|
|
validated_by UUID,
|
|
validated_at TIMESTAMPTZ,
|
|
clinical_source VARCHAR(200),
|
|
quality_score INTEGER CHECK (quality_score IS NULL OR (quality_score >= 0 AND quality_score <= 100)),
|
|
|
|
-- Revisión
|
|
reviewed_by UUID,
|
|
reviewed_at TIMESTAMPTZ,
|
|
|
|
-- Auditoría
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
created_by UUID NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_by UUID NOT NULL,
|
|
|
|
-- Constraints
|
|
CONSTRAINT chk_type CHECK (type IN ('protocol', 'guide', 'manual', 'checklist')),
|
|
CONSTRAINT chk_level CHECK (level IN ('operativo', 'formativo', 'referencia'))
|
|
);
|
|
|
|
-- Índices para content_items
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_type ON tes_content.content_items(type);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_level ON tes_content.content_items(level);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_status ON tes_content.content_items(status);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_category ON tes_content.content_items(category);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_slug ON tes_content.content_items(slug);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_validated_at ON tes_content.content_items(validated_at);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_updated_at ON tes_content.content_items(updated_at);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_content_gin ON tes_content.content_items USING GIN (content);
|
|
CREATE INDEX IF NOT EXISTS idx_content_items_title_fts ON tes_content.content_items USING GIN (to_tsvector('spanish', title));
|
|
|
|
-- ============================================
|
|
-- TABLA: content_versions
|
|
-- Propósito: Historial de versiones de contenido
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS tes_content.content_versions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
content_id VARCHAR(100) NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
|
|
-- Snapshot del contenido
|
|
content JSONB NOT NULL,
|
|
content_markdown TEXT,
|
|
title VARCHAR(500) NOT NULL,
|
|
|
|
-- Metadatos de la versión
|
|
status tes_content.content_status NOT NULL,
|
|
validated_by UUID,
|
|
validated_at TIMESTAMPTZ,
|
|
clinical_source VARCHAR(200),
|
|
|
|
-- Cambios
|
|
change_summary TEXT,
|
|
changed_fields TEXT[],
|
|
|
|
-- Auditoría
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
created_by UUID NOT NULL,
|
|
|
|
-- Foreign key
|
|
CONSTRAINT fk_content_versions_content_id
|
|
FOREIGN KEY (content_id)
|
|
REFERENCES tes_content.content_items(id)
|
|
ON DELETE CASCADE,
|
|
|
|
-- Unique constraint
|
|
CONSTRAINT uq_content_versions_content_version
|
|
UNIQUE (content_id, version)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_content_versions_content_id ON tes_content.content_versions(content_id);
|
|
CREATE INDEX IF NOT EXISTS idx_content_versions_version ON tes_content.content_versions(version);
|
|
|
|
-- ============================================
|
|
-- TABLA: media_resources
|
|
-- Propósito: Recursos multimedia (imágenes, videos)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS tes_content.media_resources (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
type VARCHAR(50) NOT NULL,
|
|
path VARCHAR(500) NOT NULL,
|
|
filename VARCHAR(255) NOT NULL,
|
|
file_url VARCHAR(500),
|
|
thumbnail_url VARCHAR(500),
|
|
|
|
-- Metadatos
|
|
title VARCHAR(500),
|
|
description TEXT,
|
|
alt_text VARCHAR(500),
|
|
caption TEXT,
|
|
tags TEXT[],
|
|
|
|
-- Clasificación
|
|
block VARCHAR(100),
|
|
chapter VARCHAR(100),
|
|
priority tes_content.content_priority,
|
|
|
|
-- Propiedades del archivo
|
|
width INTEGER,
|
|
height INTEGER,
|
|
format VARCHAR(50),
|
|
file_size BIGINT,
|
|
duration_seconds INTEGER,
|
|
video_format VARCHAR(50),
|
|
|
|
-- Estado
|
|
status tes_content.content_status NOT NULL DEFAULT 'draft',
|
|
|
|
-- Auditoría
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
created_by UUID NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_by UUID NOT NULL,
|
|
|
|
CONSTRAINT chk_media_type CHECK (type IN ('image', 'video', 'audio', 'document'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_media_resources_type ON tes_content.media_resources(type);
|
|
CREATE INDEX IF NOT EXISTS idx_media_resources_status ON tes_content.media_resources(status);
|
|
CREATE INDEX IF NOT EXISTS idx_media_resources_tags ON tes_content.media_resources USING GIN (tags);
|
|
|
|
-- ============================================
|
|
-- TABLA: content_resource_associations
|
|
-- Propósito: Asociaciones entre contenido y recursos multimedia
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS tes_content.content_resource_associations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
content_item_id VARCHAR(100) NOT NULL,
|
|
media_resource_id UUID NOT NULL,
|
|
|
|
-- Contexto de la asociación
|
|
section VARCHAR(100),
|
|
position INTEGER NOT NULL DEFAULT 0,
|
|
placement VARCHAR(50),
|
|
caption TEXT,
|
|
is_critical BOOLEAN NOT NULL DEFAULT false,
|
|
priority tes_content.content_priority,
|
|
|
|
-- Auditoría
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
created_by UUID NOT NULL,
|
|
|
|
-- Foreign keys
|
|
CONSTRAINT fk_cra_content_item_id
|
|
FOREIGN KEY (content_item_id)
|
|
REFERENCES tes_content.content_items(id)
|
|
ON DELETE CASCADE,
|
|
|
|
CONSTRAINT fk_cra_media_resource_id
|
|
FOREIGN KEY (media_resource_id)
|
|
REFERENCES tes_content.media_resources(id)
|
|
ON DELETE CASCADE,
|
|
|
|
CONSTRAINT chk_placement CHECK (placement IN ('before', 'after', 'inline', 'sidebar'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_cra_content_item_id ON tes_content.content_resource_associations(content_item_id);
|
|
CREATE INDEX IF NOT EXISTS idx_cra_media_resource_id ON tes_content.content_resource_associations(media_resource_id);
|
|
CREATE INDEX IF NOT EXISTS idx_cra_position ON tes_content.content_resource_associations(content_item_id, position);
|
|
|
|
-- ============================================
|
|
-- TABLA: audit_logs
|
|
-- Propósito: Registro de auditoría de cambios
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS tes_content.audit_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL,
|
|
entity_type VARCHAR(50) NOT NULL,
|
|
entity_id VARCHAR(100) NOT NULL,
|
|
action VARCHAR(50) NOT NULL,
|
|
changes JSONB,
|
|
metadata JSONB,
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_action CHECK (action IN ('create', 'update', 'delete', 'submit', 'approve', 'reject', 'publish', 'archive'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON tes_content.audit_logs(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON tes_content.audit_logs(entity_type, entity_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON tes_content.audit_logs(action);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON tes_content.audit_logs(created_at);
|
|
|
|
-- ============================================
|
|
-- COMENTARIOS
|
|
-- ============================================
|
|
COMMENT ON SCHEMA tes_content IS 'Schema principal para contenido de TES';
|
|
COMMENT ON TABLE tes_content.content_items IS 'Tabla principal de contenido (protocolos, guías, etc.)';
|
|
COMMENT ON TABLE tes_content.content_versions IS 'Historial de versiones de contenido';
|
|
COMMENT ON TABLE tes_content.media_resources IS 'Recursos multimedia (imágenes, videos)';
|
|
COMMENT ON TABLE tes_content.content_resource_associations IS 'Asociaciones entre contenido y recursos multimedia';
|
|
COMMENT ON TABLE tes_content.audit_logs IS 'Registro de auditoría de cambios';
|
|
|