codigo0/backend/database/migrations/003_create_content_items_schema.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';