-- ============================================ -- 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';