Saltar a contenido

Modelo de Datos del Modulo Expedientes

Conceptos Fundamentales

El flujo de creación de expedientes involucra múltiples entidades que trabajan de forma coordinada para garantizar la integridad, trazabilidad y validez legal del proceso. Cada expediente creado genera automáticamente:

  • Un registro principal que contiene todos los metadatos del expediente
  • Una carátula oficial como documento independiente con numeración propia
  • Relaciones organizacionales que determinan permisos y responsabilidades
  • Un historial de auditoría que registra cada paso del proceso

Flujo de Datos durante la Creacion

[Usuario] → [Validación] → [Expediente] → [Numeración] → [Carátula] → [Vinculación]
↓ ↓ ↓ ↓ ↓ ↓
[Permisos] [Tipos] [Repartición] [Secuencial] [Documento] [Bidireccional]

Estructura de Datos Implementada

Tabla Principal: expedients

Propósito: Registro central de cada expediente con sus metadatos fundamentales.

CREATE TABLE expedients (
    -- Identificación única
    expedient_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    expedient_number VARCHAR UNIQUE NOT NULL, -- EE-AAAA-NNNNNN-SIGLA_ECO-SIGLA_DEPT

    -- Configuración del expediente
    expedient_type_id UUID NOT NULL,
    motive TEXT NOT NULL CHECK (length(trim(motive)) > 0),

    -- Datos del iniciador
    initiator_type initiator_type_enum NOT NULL,
    initiator_details JSONB, -- CUIT, nombre, email para externos

    -- Estructura organizacional
    admin_department_id UUID NOT NULL, -- Repartición administradora
    created_by_user_id UUID NOT NULL,  -- Usuario creador
    assigned_user_id UUID,             -- Usuario asignado (opcional)

    -- Vinculación con carátula
    caratula_document_id UUID,         -- Documento carátula generado

    -- Control de estado
    status expedient_status DEFAULT 'active',

    -- Auditoría temporal
    created_at TIMESTAMP DEFAULT NOW(),
    last_activity_at TIMESTAMP DEFAULT NOW(),

    -- Metadatos adicionales
    audit_data JSONB,
    is_deleted BOOLEAN DEFAULT false,

    -- Constraints
    CONSTRAINT fk_expedient_type FOREIGN KEY (expedient_type_id) 
        REFERENCES expedient_types(expedient_type_id),
    CONSTRAINT fk_admin_department FOREIGN KEY (admin_department_id) 
        REFERENCES departments(department_id),
    CONSTRAINT fk_creator FOREIGN KEY (created_by_user_id) 
        REFERENCES users(user_id),
    CONSTRAINT fk_caratula FOREIGN KEY (caratula_document_id) 
        REFERENCES document_draft(document_id)
);

-- Tipos de datos personalizados
CREATE TYPE initiator_type_enum AS ENUM ('INTERNO', 'EXTERNO');
CREATE TYPE expedient_status AS ENUM ('active', 'transferred', 'archived', 'cancelled');

Sistema de Numeración: expedient_numeration_requests

Propósito: Garantizar numeración secuencial única y trazable.

CREATE TABLE expedient_numeration_requests (
    numeration_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,

    -- Identificación del tipo y contexto
    expedient_type_id UUID NOT NULL,
    department_id UUID NOT NULL,    -- Repartición numeradora
    year SMALLINT NOT NULL,         -- Año de numeración

    -- Control de secuencia
    reserved_number VARCHAR UNIQUE NOT NULL, -- Número reservado (ej: "000123")
    reserved_at TIMESTAMP DEFAULT NOW(),
    is_confirmed BOOLEAN DEFAULT false,

    -- Vinculación final
    expedient_id UUID,             -- Se completa al confirmar

    CONSTRAINT fk_numeration_type FOREIGN KEY (expedient_type_id) 
        REFERENCES expedient_types(expedient_type_id),
    CONSTRAINT fk_numeration_dept FOREIGN KEY (department_id) 
        REFERENCES departments(department_id),
    CONSTRAINT unique_number_per_type_year UNIQUE (expedient_type_id, year, reserved_number)
);

Configuración de Tipos: expedient_types

Propósito: Definir reglas de negocio y permisos por tipo de expediente.

CREATE TABLE expedient_types (
    expedient_type_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,

    -- Identificación
    name VARCHAR NOT NULL,          -- "Licitación Pública"
    acronym VARCHAR UNIQUE NOT NULL, -- "LICPUB"
    description TEXT,

    -- Reglas de creación
    enabled_departments UUID[],     -- Reparticiones que pueden crear
    admin_department_type department_assignment_enum NOT NULL,
    specific_admin_department UUID, -- Si es asignación específica

    -- Control
    is_active BOOLEAN DEFAULT true,
    audit_data JSONB,

    CONSTRAINT fk_specific_dept FOREIGN KEY (specific_admin_department) 
        REFERENCES departments(department_id)
);

CREATE TYPE department_assignment_enum AS ENUM ('CREATOR', 'SPECIFIC');

Funciones de Proceso Automatizado

Generación de Número Oficial

CREATE OR REPLACE FUNCTION generate_expedient_number(
    p_expedient_type_id UUID,
    p_admin_department_id UUID
) RETURNS VARCHAR AS $$
DECLARE
    v_year INTEGER := EXTRACT(YEAR FROM NOW());
    v_next_number INTEGER;
    v_type_acronym VARCHAR;
    v_dept_acronym VARCHAR;
    v_municipality_acronym VARCHAR;
    v_final_number VARCHAR;
BEGIN
    -- Obtener siguiente número secuencial
    SELECT COALESCE(MAX(CAST(reserved_number AS INTEGER)), 0) + 1
    INTO v_next_number
    FROM expedient_numeration_requests
    WHERE expedient_type_id = p_expedient_type_id 
      AND year = v_year;

    -- Obtener componentes del número
    SELECT et.acronym, d.acronym, m.acronym
    INTO v_type_acronym, v_dept_acronym, v_municipality_acronym
    FROM expedient_types et
    JOIN departments d ON d.department_id = p_admin_department_id
    JOIN municipalities m ON d.municipality_id = m.id_municipality
    WHERE et.expedient_type_id = p_expedient_type_id;

    -- Construir número final: EX-AAAA-NNNNNN-SIGLA_ECO-SIGLA_DEPT
    v_final_number := 'EX-' || v_year || '-' || 
                      LPAD(v_next_number::TEXT, 6, '0') || '-' ||
                      v_municipality_acronym || '-' || v_dept_acronym;

    -- Reservar número
    INSERT INTO expedient_numeration_requests (
        expedient_type_id, department_id, year, 
        reserved_number, reserved_at
    ) VALUES (
        p_expedient_type_id, p_admin_department_id, 
        v_year, LPAD(v_next_number::TEXT, 6, '0'), NOW()
    );

    RETURN v_final_number;
END;
$$ LANGUAGE plpgsql;

Validación de Permisos de Creación

CREATE OR REPLACE FUNCTION user_can_create_expedient_type(
    p_user_id UUID,
    p_expedient_type_id UUID
) RETURNS BOOLEAN AS $$
DECLARE
    v_user_department_id UUID;
    v_enabled_departments UUID[];
BEGIN
    -- Obtener department del usuario
    SELECT d.department_id
    INTO v_user_department_id
    FROM users u
    JOIN sectors s ON u.sector_id = s.sector_id
    JOIN departments d ON s.department_id = d.department_id
    WHERE u.user_id = p_user_id;

    -- Obtener departments habilitados para este tipo
    SELECT enabled_departments
    INTO v_enabled_departments
    FROM expedient_types
    WHERE expedient_type_id = p_expedient_type_id
      AND is_active = true;

    -- Verificar si el department del usuario está habilitado
    RETURN v_user_department_id = ANY(v_enabled_departments);
END;
$$ LANGUAGE plpgsql;

Determinación de Repartición Administradora

CREATE OR REPLACE FUNCTION assign_admin_department(
    p_expedient_type_id UUID,
    p_creator_department_id UUID
) RETURNS UUID AS $$
DECLARE
    v_assignment_type department_assignment_enum;
    v_specific_department UUID;
BEGIN
    SELECT admin_department_type, specific_admin_department
    INTO v_assignment_type, v_specific_department
    FROM expedient_types
    WHERE expedient_type_id = p_expedient_type_id;

    CASE v_assignment_type
        WHEN 'CREATOR' THEN
            RETURN p_creator_department_id;
        WHEN 'SPECIFIC' THEN
            RETURN v_specific_department;
        ELSE
            RAISE EXCEPTION 'Tipo de asignación inválido para expedient_type_id: %', p_expedient_type_id;
    END CASE;
END;
$$ LANGUAGE plpgsql;

Triggers y Validaciones Automáticas

-- Trigger de validación pre-inserción
CREATE OR REPLACE FUNCTION validate_expedient_creation()
RETURNS TRIGGER AS $$
BEGIN
    -- Validar permisos de usuario
    IF NOT user_can_create_expedient_type(NEW.created_by_user_id, NEW.expedient_type_id) THEN
        RAISE EXCEPTION 'Usuario no autorizado para crear este tipo de expediente';
    END IF;

    -- Validar datos de iniciador externo
    IF NEW.initiator_type = 'EXTERNO' THEN
        IF NEW.initiator_details IS NULL OR 
           NOT (NEW.initiator_details ? 'cuit') OR
           NOT (NEW.initiator_details ? 'name') THEN
            RAISE EXCEPTION 'Datos de iniciador externo incompletos (CUIT y nombre requeridos)';
        END IF;
    END IF;

    -- Validar motivo
    IF NEW.motive IS NULL OR trim(NEW.motive) = '' THEN
        RAISE EXCEPTION 'Motivo del expediente es obligatorio';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_validate_expedient_creation
    BEFORE INSERT ON expedients
    FOR EACH ROW
    EXECUTE FUNCTION validate_expedient_creation();

-- Trigger post-creación para generar carátula
CREATE OR REPLACE FUNCTION post_expedient_creation()
RETURNS TRIGGER AS $$
DECLARE
    v_caratula_id UUID;
BEGIN
    -- Generar carátula automática
    SELECT generate_expedient_caratula(NEW.expedient_id) INTO v_caratula_id;

    -- Actualizar expediente con referencia a carátula
    UPDATE expedients 
    SET caratula_document_id = v_caratula_id
    WHERE expedient_id = NEW.expedient_id;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_post_expedient_creation
    AFTER INSERT ON expedients
    FOR EACH ROW
    EXECUTE FUNCTION post_expedient_creation();

Índices para Optimización de Performance

-- Índices principales para búsquedas frecuentes
CREATE INDEX idx_expedients_number ON expedients(expedient_number);
CREATE INDEX idx_expedients_type ON expedients(expedient_type_id);
CREATE INDEX idx_expedients_admin_dept ON expedients(admin_department_id);
CREATE INDEX idx_expedients_creator ON expedients(created_by_user_id);
CREATE INDEX idx_expedients_status ON expedients(status) WHERE status != 'archived';
CREATE INDEX idx_expedients_created_date ON expedients(created_at);

-- Índices para numeración
CREATE INDEX idx_numeration_type_year ON expedient_numeration_requests(expedient_type_id, year);
CREATE INDEX idx_numeration_confirmed ON expedient_numeration_requests(is_confirmed);

-- Índices para tipos activos
CREATE INDEX idx_expedient_types_active ON expedient_types(is_active) WHERE is_active = true;