# DDL SPECIFICATION: Schema Documents (DMS)

**Version:** 1.0.0
**Fecha:** 2025-12-05
**Schema:** `documents`
**Modulos:** MAE-016 (Gestion Documental y Planos)

---

## Resumen

| Metrica | Valor |
|---------|-------|
| Total Tablas | 10 |
| ENUMs | 5 |
| Funciones | 4 |
| Triggers | 3 |
| Indices | 30+ |

---

## 1. ENUMs

```sql
-- Tipos de documento
CREATE TYPE documents.document_type AS ENUM (
  'plan',              -- Plano (arquitectonico, estructural, etc.)
  'specification',     -- Especificacion tecnica
  'contract',          -- Contrato
  'permit',            -- Permiso/licencia
  'invoice',           -- Factura
  'report',            -- Reporte
  'photo',             -- Fotografia
  'drawing',           -- Dibujo/croquis
  'manual',            -- Manual
  'certificate',       -- Certificado
  'other'              -- Otro
);

-- Estados de documento
CREATE TYPE documents.document_status AS ENUM (
  'draft',             -- Borrador
  'in_review',         -- En revision
  'approved',          -- Aprobado
  'rejected',          -- Rechazado
  'obsolete',          -- Obsoleto
  'archived'           -- Archivado
);

-- Acciones de aprobacion
CREATE TYPE documents.approval_action AS ENUM (
  'submit',            -- Enviar a revision
  'approve',           -- Aprobar
  'reject',            -- Rechazar
  'request_changes',   -- Solicitar cambios
  'withdraw'           -- Retirar
);

-- Tipos de plano
CREATE TYPE documents.plan_type AS ENUM (
  'architectural',     -- Arquitectonico
  'structural',        -- Estructural
  'electrical',        -- Electrico
  'plumbing',          -- Hidraulico
  'hvac',              -- Aire acondicionado
  'landscape',         -- Paisajismo
  'civil',             -- Civil
  'detail',            -- Detalle
  'as_built'           -- As-built
);

-- Niveles de acceso
CREATE TYPE documents.access_level AS ENUM (
  'public',            -- Publico (todos los del proyecto)
  'restricted',        -- Restringido (roles especificos)
  'confidential',      -- Confidencial (solo propietario y admins)
  'private'            -- Privado (solo propietario)
);
```

---

## 2. Tablas de Carpetas y Documentos

### 2.1 folders (Carpetas)

```sql
CREATE TABLE documents.folders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),

  -- Identificacion
  name VARCHAR(200) NOT NULL,
  description TEXT,

  -- Jerarquia
  parent_id UUID REFERENCES documents.folders(id),
  full_path TEXT,
  level INTEGER DEFAULT 1,

  -- Vinculacion
  project_id UUID REFERENCES construction.projects(id),

  -- Tipo de contenido
  folder_type VARCHAR(50),  -- plans, contracts, permits, reports, etc.

  -- Acceso
  access_level documents.access_level DEFAULT 'public',
  allowed_roles TEXT[],

  -- Orden
  sort_order INTEGER DEFAULT 0,

  -- Estado
  is_active BOOLEAN DEFAULT true,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES core.users(id),
  deleted_at TIMESTAMP
);

CREATE INDEX idx_folders_tenant ON documents.folders(tenant_id);
CREATE INDEX idx_folders_parent ON documents.folders(parent_id);
CREATE INDEX idx_folders_project ON documents.folders(project_id);
CREATE INDEX idx_folders_path ON documents.folders(full_path);
```

### 2.2 documents (Documentos)

```sql
CREATE TABLE documents.documents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  folder_id UUID REFERENCES documents.folders(id),

  -- Identificacion
  document_number VARCHAR(50),
  title VARCHAR(300) NOT NULL,
  description TEXT,

  -- Tipo
  document_type documents.document_type NOT NULL,
  subcategory VARCHAR(100),

  -- Vinculacion
  project_id UUID REFERENCES construction.projects(id),
  housing_unit_id UUID REFERENCES construction.housing_units(id),

  -- Version actual
  current_version_id UUID,  -- Referencia a document_versions
  current_version_number VARCHAR(20),
  total_versions INTEGER DEFAULT 1,

  -- Archivo original
  file_url TEXT NOT NULL,
  file_name VARCHAR(255) NOT NULL,
  file_size BIGINT,
  mime_type VARCHAR(100),
  file_extension VARCHAR(20),

  -- Thumbnail y preview
  thumbnail_url TEXT,
  preview_url TEXT,

  -- Metadata
  tags TEXT[],
  custom_fields JSONB,

  -- Estado
  status documents.document_status NOT NULL DEFAULT 'draft',
  access_level documents.access_level DEFAULT 'public',

  -- Fechas
  document_date DATE,
  expiry_date DATE,

  -- OCR y busqueda
  ocr_text TEXT,
  is_ocr_processed BOOLEAN DEFAULT false,
  ocr_processed_at TIMESTAMP,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES core.users(id),
  deleted_at TIMESTAMP,

  CONSTRAINT uq_documents_number UNIQUE (tenant_id, project_id, document_number)
);

CREATE INDEX idx_documents_tenant ON documents.documents(tenant_id);
CREATE INDEX idx_documents_folder ON documents.documents(folder_id);
CREATE INDEX idx_documents_project ON documents.documents(project_id);
CREATE INDEX idx_documents_type ON documents.documents(document_type);
CREATE INDEX idx_documents_status ON documents.documents(status);
CREATE INDEX idx_documents_tags ON documents.documents USING GIN(tags);
CREATE INDEX idx_documents_search ON documents.documents USING GIN(to_tsvector('spanish', coalesce(title, '') || ' ' || coalesce(description, '') || ' ' || coalesce(ocr_text, '')));
```

### 2.3 document_versions (Versiones)

```sql
CREATE TABLE documents.document_versions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  document_id UUID NOT NULL REFERENCES documents.documents(id),

  -- Version
  version_number VARCHAR(20) NOT NULL,  -- 1.0, Rev. A, etc.
  version_sequence INTEGER NOT NULL,

  -- Archivo
  file_url TEXT NOT NULL,
  file_name VARCHAR(255) NOT NULL,
  file_size BIGINT,
  mime_type VARCHAR(100),

  -- Thumbnail y preview
  thumbnail_url TEXT,
  preview_url TEXT,

  -- Cambios
  change_summary TEXT,
  change_type VARCHAR(50),  -- minor, major, revision

  -- Estado
  status documents.document_status NOT NULL DEFAULT 'draft',
  is_current BOOLEAN DEFAULT false,

  -- Aprobacion
  approved_by UUID REFERENCES core.users(id),
  approved_at TIMESTAMP,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),

  CONSTRAINT uq_version_sequence UNIQUE (document_id, version_sequence)
);

CREATE INDEX idx_versions_document ON documents.document_versions(document_id);
CREATE INDEX idx_versions_current ON documents.document_versions(document_id) WHERE is_current = true;
CREATE INDEX idx_versions_status ON documents.document_versions(status);
```

---

## 3. Tablas de Planos

### 3.1 plans (Planos)

```sql
CREATE TABLE documents.plans (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  document_id UUID NOT NULL REFERENCES documents.documents(id),

  -- Tipo de plano
  plan_type documents.plan_type NOT NULL,
  discipline VARCHAR(50),

  -- Identificacion tecnica
  plan_number VARCHAR(50) NOT NULL,
  sheet_number VARCHAR(20),
  total_sheets INTEGER,

  -- Escala
  scale VARCHAR(20),

  -- Dimensiones
  format VARCHAR(20),  -- A0, A1, A2, A3, A4, etc.
  width_mm DECIMAL(10,2),
  height_mm DECIMAL(10,2),

  -- Coordenadas (para planos georeferenciados)
  north_coordinate DECIMAL(18,8),
  east_coordinate DECIMAL(18,8),
  coordinate_system VARCHAR(50),

  -- Software origen
  source_software VARCHAR(100),  -- AutoCAD, Revit, etc.
  source_file_url TEXT,  -- Archivo fuente (.dwg, .rvt)

  -- Revision actual
  current_revision VARCHAR(10),

  -- Estado
  is_for_construction BOOLEAN DEFAULT false,
  is_as_built BOOLEAN DEFAULT false,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES core.users(id),

  CONSTRAINT uq_plans_number UNIQUE (tenant_id, project_id, plan_number)
);

CREATE INDEX idx_plans_document ON documents.plans(document_id);
CREATE INDEX idx_plans_type ON documents.plans(plan_type);
CREATE INDEX idx_plans_number ON documents.plans(plan_number);
```

### 3.2 plan_annotations (Anotaciones en Planos)

```sql
CREATE TABLE documents.plan_annotations (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  plan_id UUID NOT NULL REFERENCES documents.plans(id),
  version_id UUID REFERENCES documents.document_versions(id),

  -- Tipo de anotacion
  annotation_type VARCHAR(50) NOT NULL,  -- comment, markup, measurement, symbol

  -- Posicion
  x_position DECIMAL(10,4),
  y_position DECIMAL(10,4),
  width DECIMAL(10,4),
  height DECIMAL(10,4),
  rotation DECIMAL(6,2),

  -- Contenido
  content TEXT,
  color VARCHAR(20),
  style JSONB,

  -- Forma (para markups)
  shape_type VARCHAR(30),  -- rectangle, circle, arrow, freehand, etc.
  shape_points JSONB,  -- Array de puntos

  -- Estado
  is_resolved BOOLEAN DEFAULT false,
  resolved_by UUID REFERENCES core.users(id),
  resolved_at TIMESTAMP,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES core.users(id),
  deleted_at TIMESTAMP
);

CREATE INDEX idx_annotations_plan ON documents.plan_annotations(plan_id);
CREATE INDEX idx_annotations_version ON documents.plan_annotations(version_id);
CREATE INDEX idx_annotations_unresolved ON documents.plan_annotations(plan_id) WHERE is_resolved = false;
```

---

## 4. Tablas de Flujo de Aprobacion

### 4.1 approval_workflows (Flujos de Aprobacion)

```sql
CREATE TABLE documents.approval_workflows (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),

  -- Identificacion
  name VARCHAR(200) NOT NULL,
  description TEXT,

  -- Configuracion
  document_types documents.document_type[],
  total_levels INTEGER NOT NULL DEFAULT 2,

  -- Estado
  is_active BOOLEAN DEFAULT true,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES core.users(id)
);

CREATE INDEX idx_workflows_tenant ON documents.approval_workflows(tenant_id);
CREATE INDEX idx_workflows_active ON documents.approval_workflows(tenant_id) WHERE is_active = true;
```

### 4.2 workflow_levels (Niveles de Aprobacion)

```sql
CREATE TABLE documents.workflow_levels (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  workflow_id UUID NOT NULL REFERENCES documents.approval_workflows(id),

  -- Nivel
  level_number INTEGER NOT NULL,
  level_name VARCHAR(100) NOT NULL,

  -- Aprobadores
  approver_type VARCHAR(30) NOT NULL,  -- user, role, department
  approver_ids UUID[],
  approver_roles TEXT[],

  -- Configuracion
  requires_all BOOLEAN DEFAULT false,  -- true = todos deben aprobar
  can_skip BOOLEAN DEFAULT false,

  -- Orden
  sort_order INTEGER DEFAULT 0,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),

  CONSTRAINT uq_workflow_level UNIQUE (workflow_id, level_number)
);

CREATE INDEX idx_workflow_levels_workflow ON documents.workflow_levels(workflow_id);
```

### 4.3 document_approvals (Aprobaciones)

```sql
CREATE TABLE documents.document_approvals (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  document_id UUID NOT NULL REFERENCES documents.documents(id),
  version_id UUID REFERENCES documents.document_versions(id),

  -- Workflow
  workflow_id UUID REFERENCES documents.approval_workflows(id),
  current_level INTEGER DEFAULT 1,

  -- Estado general
  status VARCHAR(30) DEFAULT 'pending',  -- pending, in_progress, approved, rejected

  -- Fechas
  submitted_at TIMESTAMP,
  submitted_by UUID REFERENCES core.users(id),
  completed_at TIMESTAMP,

  -- Resultado
  final_action documents.approval_action,
  final_comments TEXT,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES core.users(id)
);

CREATE INDEX idx_approvals_document ON documents.document_approvals(document_id);
CREATE INDEX idx_approvals_status ON documents.document_approvals(status);
CREATE INDEX idx_approvals_pending ON documents.document_approvals(tenant_id) WHERE status = 'in_progress';
```

### 4.4 approval_actions (Acciones de Aprobacion)

```sql
CREATE TABLE documents.approval_actions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  approval_id UUID NOT NULL REFERENCES documents.document_approvals(id),

  -- Nivel
  level_number INTEGER NOT NULL,

  -- Accion
  action documents.approval_action NOT NULL,
  action_by UUID NOT NULL REFERENCES core.users(id),
  action_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  -- Comentarios
  comments TEXT,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_approval_actions_approval ON documents.approval_actions(approval_id);
CREATE INDEX idx_approval_actions_user ON documents.approval_actions(action_by);
```

---

## 5. Tablas de Acceso y Compartir

### 5.1 document_shares (Compartidos)

```sql
CREATE TABLE documents.document_shares (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  document_id UUID NOT NULL REFERENCES documents.documents(id),

  -- Destinatario
  share_type VARCHAR(30) NOT NULL,  -- user, role, external
  shared_with_user_id UUID REFERENCES core.users(id),
  shared_with_role VARCHAR(100),
  shared_with_email VARCHAR(255),

  -- Permisos
  can_view BOOLEAN DEFAULT true,
  can_download BOOLEAN DEFAULT false,
  can_annotate BOOLEAN DEFAULT false,
  can_share BOOLEAN DEFAULT false,

  -- Vigencia
  expires_at TIMESTAMP,
  access_token VARCHAR(100),  -- Para links externos

  -- Estado
  is_active BOOLEAN DEFAULT true,

  -- Auditoria
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES core.users(id)
);

CREATE INDEX idx_shares_document ON documents.document_shares(document_id);
CREATE INDEX idx_shares_user ON documents.document_shares(shared_with_user_id);
CREATE INDEX idx_shares_token ON documents.document_shares(access_token);
```

### 5.2 document_access_logs (Registro de Accesos)

```sql
CREATE TABLE documents.document_access_logs (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES core.tenants(id),
  document_id UUID NOT NULL REFERENCES documents.documents(id),

  -- Accion
  action VARCHAR(50) NOT NULL,  -- view, download, print, annotate, share

  -- Usuario
  user_id UUID REFERENCES core.users(id),
  user_ip VARCHAR(45),
  user_agent TEXT,

  -- Detalles
  version_id UUID REFERENCES documents.document_versions(id),
  details JSONB,

  -- Timestamp
  accessed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_access_logs_document ON documents.document_access_logs(document_id);
CREATE INDEX idx_access_logs_user ON documents.document_access_logs(user_id);
CREATE INDEX idx_access_logs_date ON documents.document_access_logs(accessed_at);
```

---

## 6. Funciones

### 6.1 Actualizar Version Actual

```sql
CREATE OR REPLACE FUNCTION documents.update_current_version()
RETURNS TRIGGER AS $$
BEGIN
  -- Si es version actual, quitar el flag de otras versiones
  IF NEW.is_current = true THEN
    UPDATE documents.document_versions
    SET is_current = false
    WHERE document_id = NEW.document_id
      AND id != NEW.id
      AND is_current = true;

    -- Actualizar documento
    UPDATE documents.documents
    SET
      current_version_id = NEW.id,
      current_version_number = NEW.version_number,
      updated_at = CURRENT_TIMESTAMP
    WHERE id = NEW.document_id;
  END IF;

  -- Actualizar contador de versiones
  UPDATE documents.documents
  SET total_versions = (
    SELECT COUNT(*) FROM documents.document_versions
    WHERE document_id = NEW.document_id
  )
  WHERE id = NEW.document_id;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_update_current_version
AFTER INSERT OR UPDATE ON documents.document_versions
FOR EACH ROW
EXECUTE FUNCTION documents.update_current_version();
```

### 6.2 Actualizar Path de Carpeta

```sql
CREATE OR REPLACE FUNCTION documents.update_folder_path()
RETURNS TRIGGER AS $$
DECLARE
  v_parent_path TEXT;
BEGIN
  IF NEW.parent_id IS NULL THEN
    NEW.full_path := '/' || NEW.name;
    NEW.level := 1;
  ELSE
    SELECT full_path, level + 1 INTO v_parent_path, NEW.level
    FROM documents.folders
    WHERE id = NEW.parent_id;

    NEW.full_path := v_parent_path || '/' || NEW.name;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_update_folder_path
BEFORE INSERT OR UPDATE OF parent_id, name ON documents.folders
FOR EACH ROW
EXECUTE FUNCTION documents.update_folder_path();
```

### 6.3 Procesar Flujo de Aprobacion

```sql
CREATE OR REPLACE FUNCTION documents.process_approval_action(
  p_approval_id UUID,
  p_action documents.approval_action,
  p_user_id UUID,
  p_comments TEXT DEFAULT NULL
)
RETURNS BOOLEAN AS $$
DECLARE
  v_approval RECORD;
  v_workflow RECORD;
  v_next_level INTEGER;
  v_max_level INTEGER;
BEGIN
  SELECT * INTO v_approval FROM documents.document_approvals WHERE id = p_approval_id;

  -- Registrar accion
  INSERT INTO documents.approval_actions (
    tenant_id, approval_id, level_number, action, action_by, comments
  )
  VALUES (
    v_approval.tenant_id, p_approval_id, v_approval.current_level, p_action, p_user_id, p_comments
  );

  -- Procesar segun accion
  CASE p_action
    WHEN 'approve' THEN
      -- Verificar si hay mas niveles
      SELECT total_levels INTO v_max_level
      FROM documents.approval_workflows
      WHERE id = v_approval.workflow_id;

      IF v_approval.current_level >= v_max_level THEN
        -- Aprobacion completa
        UPDATE documents.document_approvals
        SET
          status = 'approved',
          final_action = 'approve',
          final_comments = p_comments,
          completed_at = CURRENT_TIMESTAMP,
          updated_at = CURRENT_TIMESTAMP
        WHERE id = p_approval_id;

        -- Actualizar documento y version
        UPDATE documents.documents SET status = 'approved', updated_at = CURRENT_TIMESTAMP
        WHERE id = v_approval.document_id;

        UPDATE documents.document_versions SET status = 'approved', approved_by = p_user_id, approved_at = CURRENT_TIMESTAMP
        WHERE id = v_approval.version_id;
      ELSE
        -- Avanzar al siguiente nivel
        UPDATE documents.document_approvals
        SET
          current_level = current_level + 1,
          updated_at = CURRENT_TIMESTAMP
        WHERE id = p_approval_id;
      END IF;

    WHEN 'reject' THEN
      UPDATE documents.document_approvals
      SET
        status = 'rejected',
        final_action = 'reject',
        final_comments = p_comments,
        completed_at = CURRENT_TIMESTAMP,
        updated_at = CURRENT_TIMESTAMP
      WHERE id = p_approval_id;

      UPDATE documents.documents SET status = 'rejected', updated_at = CURRENT_TIMESTAMP
      WHERE id = v_approval.document_id;

      UPDATE documents.document_versions SET status = 'rejected'
      WHERE id = v_approval.version_id;

    WHEN 'request_changes' THEN
      UPDATE documents.document_approvals
      SET
        status = 'pending',
        final_comments = p_comments,
        updated_at = CURRENT_TIMESTAMP
      WHERE id = p_approval_id;

      UPDATE documents.documents SET status = 'in_review', updated_at = CURRENT_TIMESTAMP
      WHERE id = v_approval.document_id;
  END CASE;

  RETURN true;
END;
$$ LANGUAGE plpgsql;
```

### 6.4 Busqueda Full-Text

```sql
CREATE OR REPLACE FUNCTION documents.search_documents(
  p_tenant_id UUID,
  p_query TEXT,
  p_project_id UUID DEFAULT NULL,
  p_document_type documents.document_type DEFAULT NULL,
  p_limit INTEGER DEFAULT 50
)
RETURNS TABLE (
  document_id UUID,
  title VARCHAR,
  document_type documents.document_type,
  relevance REAL
) AS $$
BEGIN
  RETURN QUERY
  SELECT
    d.id,
    d.title,
    d.document_type,
    ts_rank(
      to_tsvector('spanish', coalesce(d.title, '') || ' ' || coalesce(d.description, '') || ' ' || coalesce(d.ocr_text, '')),
      plainto_tsquery('spanish', p_query)
    ) as relevance
  FROM documents.documents d
  WHERE d.tenant_id = p_tenant_id
    AND d.deleted_at IS NULL
    AND (p_project_id IS NULL OR d.project_id = p_project_id)
    AND (p_document_type IS NULL OR d.document_type = p_document_type)
    AND to_tsvector('spanish', coalesce(d.title, '') || ' ' || coalesce(d.description, '') || ' ' || coalesce(d.ocr_text, ''))
        @@ plainto_tsquery('spanish', p_query)
  ORDER BY relevance DESC
  LIMIT p_limit;
END;
$$ LANGUAGE plpgsql;
```

---

## 7. Row Level Security

```sql
-- Habilitar RLS en todas las tablas
ALTER TABLE documents.folders ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.document_versions ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.plans ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.plan_annotations ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.approval_workflows ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.workflow_levels ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.document_approvals ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.approval_actions ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.document_shares ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents.document_access_logs ENABLE ROW LEVEL SECURITY;

-- Crear politicas de aislamiento por tenant
DO $$
DECLARE
  t TEXT;
BEGIN
  FOR t IN SELECT tablename FROM pg_tables WHERE schemaname = 'documents'
  LOOP
    EXECUTE format('
      CREATE POLICY tenant_isolation ON documents.%I
      USING (tenant_id = current_setting(''app.current_tenant_id'')::uuid)
    ', t);
  END LOOP;
END $$;
```

---

## 8. Seeds Iniciales

```sql
-- Carpetas base por defecto
INSERT INTO documents.folders (tenant_id, name, folder_type, sort_order)
VALUES
  ('{{TENANT_ID}}', 'Planos', 'plans', 1),
  ('{{TENANT_ID}}', 'Contratos', 'contracts', 2),
  ('{{TENANT_ID}}', 'Permisos y Licencias', 'permits', 3),
  ('{{TENANT_ID}}', 'Especificaciones', 'specifications', 4),
  ('{{TENANT_ID}}', 'Reportes', 'reports', 5),
  ('{{TENANT_ID}}', 'Fotografias', 'photos', 6);

-- Workflow de aprobacion por defecto
INSERT INTO documents.approval_workflows (tenant_id, name, document_types, total_levels)
VALUES
  ('{{TENANT_ID}}', 'Aprobacion de Planos', ARRAY['plan']::documents.document_type[], 3);

-- Niveles del workflow
INSERT INTO documents.workflow_levels (tenant_id, workflow_id, level_number, level_name, approver_type, approver_roles)
SELECT
  '{{TENANT_ID}}',
  w.id,
  level_number,
  level_name,
  'role',
  approver_roles
FROM documents.approval_workflows w
CROSS JOIN (VALUES
  (1, 'Revision Tecnica', ARRAY['supervisor', 'residente']),
  (2, 'Revision Gerencia', ARRAY['gerente_proyecto']),
  (3, 'Aprobacion Final', ARRAY['director'])
) AS levels(level_number, level_name, approver_roles)
WHERE w.name = 'Aprobacion de Planos';
```

---

## Referencias

- [MAE-016: Gestion Documental](../../02-definicion-modulos/MAE-016-gestion-documental/)
- [ADR-007: Database Design](../../97-adr/ADR-007-database-design.md)

---

*Ultima actualizacion: 2025-12-05*
