# MANUAL TÉCNICO

## Metafoliador v2.0.0

### Sistema de Gestión Documental e Indexación Electrónica Integrado con Alfresco ECM

---

**Autor:** MetaBiblioteca © 2026  
**Versión del documento:** 1.0  
**Fecha de elaboración:** Julio 2026  

---

## ÍNDICE GENERAL

1. [Introducción](#1-introducción)
2. [Visión General del Sistema](#2-visión-general-del-sistema)
3. [Arquitectura del Sistema](#3-arquitectura-del-sistema)
4. [Requerimientos del Sistema](#4-requerimientos-del-sistema)
5. [Instalación](#5-instalación)
6. [Configuración](#6-configuración)
7. [Base de Datos](#7-base-de-datos)
8. [Modelos de Datos](#8-modelos-de-datos)
9. [Autenticación y Autorización](#9-autenticación-y-autorización)
10. [API REST — Referencia Completa](#10-api-rest--referencia-completa)
11. [Sistema de Colas y Tareas Asíncronas](#11-sistema-de-colas-y-tareas-asíncronas)
12. [Generación de Índices Electrónicos](#12-generación-de-índices-electrónicos)
13. [Webhook Listener](#13-webhook-listener)
14. [Sistema de Reportes](#14-sistema-de-reportes)
15. [Frontend — Panel Administrativo Web](#15-frontend--panel-administrativo-web)
16. [Procesos y Flujos de Trabajo](#16-procesos-y-flujos-de-trabajo)
17. [Despliegue a Producción](#17-despliegue-a-producción)
18. [Estructura del Proyecto](#18-estructura-del-proyecto)
19. [Mantenimiento y Operación](#19-mantenimiento-y-operación)
20. [Solución de Problemas](#20-solución-de-problemas)
21. [Problemas Conocidos y Limitaciones](#21-problemas-conocidos-y-limitaciones)
22. [Glosario de Términos](#22-glosario-de-términos)
23. [Apéndices](#23-apéndices)

---

## 1. INTRODUCCIÓN

### 1.1 Propósito del Documento

El presente manual técnico tiene como objetivo proporcionar una referencia completa y detallada sobre la arquitectura, componentes, configuración, operación y mantenimiento del sistema **Metafoliador v2**. Está dirigido a administradores de sistemas, desarrolladores, personal de soporte técnico y cualquier profesional encargado de la instalación, configuración y mantenimiento de la plataforma.

### 1.2 Alcance

Este documento cubre la totalidad de los aspectos técnicos del sistema Metafoliador v2, incluyendo:

- Arquitectura de software y componentes
- Requerimientos de infraestructura
- Procedimientos de instalación y configuración
- Modelo de datos y esquema de base de datos
- Documentación completa de la API REST
- Sistema de autenticación y control de acceso basado en roles (RBAC)
- Procesamiento asíncrono de tareas
- Generación de índices electrónicos en formato colombiano
- Integración con Alfresco ECM vía API REST y Webhooks
- Generación de reportes
- Procedimientos de despliegue y mantenimiento

### 1.3 Audiencia Objetivo

| Perfil | Conocimientos esperados |
|--------|------------------------|
| Administrador de sistemas | Linux (Ubuntu/Debian), PostgreSQL, systemd, redes |
| Desarrollador backend | Python 3, FastAPI, SQLAlchemy, Celery |
| Desarrollador frontend | HTML5, CSS3, JavaScript vanilla, Jinja2 |
| Personal de soporte | Operación básica de Linux, consultas SQL |
| Administrador funcional | Gestión documental, Alfresco ECM |

### 1.4 Convenciones del Documento

- Los comandos de terminal se muestran en formato `código`
- Las rutas de archivo se indican en formato `/ruta/al/archivo`
- Los nombres de variables de entorno se escriben en `MAYÚSCULAS`
- Las notas importantes se resaltan con **NOTA:**
- Las advertencias de seguridad se marcan con **⚠️ ADVERTENCIA:**

---

## 2. VISIÓN GENERAL DEL SISTEMA

### 2.1 ¿Qué es Metafoliador v2?

Metafoliador v2 es un sistema de gestión documental e indexación electrónica que se integra con **Alfresco** (Enterprise Content Management) para proporcionar:

1. **Sincronización de contenidos:** Descarga y mantiene actualizada una réplica local de la estructura de sitios, Series, Subseries y Expedientes y archivos alojados en una o múltiples instancias de Alfresco.
2. **Generación de índices electrónicos:** Produce archivos `index.xml` en el formato estandarizado colombiano `TipoDocumentoFoliado`, requerido por entidades gubernamentales para la gestión de expedientes electrónicos.
3. **Cálculo de integridad:** Calcula y almacena hashes SHA-256 de cada archivo para garantizar la integridad documental.
4. **Conteo de folios:** Determina automáticamente el número de páginas de documentos PDF, DOCX, TXT e imágenes.
5. **Panel administrativo web:** Interfaz gráfica con control de acceso basado en roles (RBAC) para administrar conexiones, sitios, sincronizaciones, usuarios y reportes.
6. **Webhook Listener:** Recepción y procesamiento en tiempo real de eventos de Alfresco (creación, modificación, eliminación, movimiento de documentos).
7. **Reportes:** Generación de reportes en formato Excel (TRD de Tipologías) y PDF (Gráficos estadísticos por dependencia, FUID según lineamientos AGN).

### 2.2 Funcionalidades Principales

| Funcionalidad | Descripción |
|---------------|-------------|
| **Conexiones Alfresco** | CRUD de conexiones a instancias Alfresco con soporte para autenticación Basic y OAuth2 |
| **Descubrimiento de Sitios** | Exploración automática de sitios disponibles en una instancia Alfresco |
| **Sincronización de Series, Subseries y Expedientes** | Recorrido recursivo del cuadro de clasificación y archivos con dos modos: superficial (`shallow`) y profundo (`deep`) |
| **Índices Electrónicos** | Generación de `index.xml` en formato `TipoDocumentoFoliado` colombiano con subida automática a Alfresco |
| **Versionado de Índices** | Manejo de versiones del `index.xml` en Alfresco usando `cm:versionable` con incremento de versión menor |
| **Detección de Eliminados** | Identificación de archivos eliminados en Alfresco y marcado como `deleted` en base de datos local |
| **Historial de Movimientos** | Registro de historial de cambios de ubicación de archivos entre Series, Subseries y Expedientes |
| **Webhook Listener** | Recepción de eventos de Alfresco con autorización por IP y logs detallados |
| **Dashboard Interactivo** | Panel con gráficos estadísticos usando Chart.js con 7 tipos de visualización seleccionables |
| **RBAC** | Control de acceso basado en roles con 32 permisos granulares y roles configurables |
| **Reportes Excel** | Reporte TRD de Tipologías con cuadro jerárquico de clasificación y conteo de archivos |
| **Reportes PDF** | Gráficos estadísticos por dependencia con múltiples tipos de gráficos |
| **Reportes FUID** | Formato Único de Inventario Documental (AGN) en Excel y PDF con estructura TRD de 3 niveles |
| **Tema Claro/Oscuro** | Soporte para tema claro y oscuro en la interfaz web |

### 2.3 Formato de Índice Electrónico

El sistema genera archivos `index.xml` que cumplen con el estándar colombiano para índices electrónicos:

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TipoDocumentoFoliado>
  <DocumentoIndizado>
    <Id>uuid-del-documento</Id>
    <Nombre_Documento>nombre-del-archivo.pdf</Nombre_Documento>
    <Tipologia_Documental>Tipología Extraída</Tipologia_Documental>
    <Fecha_Creacion_Documento>2024-01-15</Fecha_Creacion_Documento>
    <Fecha_Incorporacion_Expediente>2024-06-20</Fecha_Incorporacion_Expediente>
    <Valor_Huella>hash-sha256-del-archivo</Valor_Huella>
    <Funcion_Resumen>SHA256</Funcion_Resumen>
    <Orden_Documento_Expediente>1</Orden_Documento_Expediente>
    <Pagina_Inicio>1</Pagina_Inicio>
    <Pagina_Fin>10</Pagina_Fin>
    <Formato>application/pdf</Formato>
    <Estado>Activo</Estado>
    <Tamano>1.5 MB</Tamano>
  </DocumentoIndizado>
</TipoDocumentoFoliado>
```

---

## 3. ARQUITECTURA DEL SISTEMA

### 3.1 Diagrama de Componentes



### 3.2 Stack Tecnológico

| Capa | Tecnología | Versión | Propósito |
|------|-----------|---------|-----------|
| Lenguaje | Python 3 | — | Lenguaje de programación principal |
| Framework Web | FastAPI | 0.115.6 | API REST y vistas HTML |
| Servidor ASGI | Uvicorn | 0.34.0 | Servidor de aplicación HTTP |
| ORM | SQLAlchemy | 2.0.36 | Mapeo objeto-relacional (estilo 2.0 con Mapped) |
| Migraciones | Alembic | 1.14.0 | Control de versiones de esquema de BD |
| Base de Datos | PostgreSQL 16 (psycopg2-binary) | 2.9.10 | Almacenamiento principal |
| Cola de Tareas | Celery | 5.4.0 | Procesamiento asíncrono |
| Message Broker | RabbitMQ (pika) | 1.3.2 | Transporte de mensajes para Celery |
| Result Backend | Redis | 5.2.0 | Almacenamiento de resultados Celery |
| Procesamiento XML | lxml | 5.3.0 | Generación y parsing de XML |
| Cliente HTTP | httpx | 0.28.1 | Llamadas HTTP síncronas y asíncronas |
| Configuración | pydantic-settings | 2.6.1 | Gestión de configuración con .env |
| Templates | Jinja2 | — | Renderizado de vistas HTML |
| Reportes Excel | openpyxl | 3.1.5 | Generación de hojas de cálculo |
| Gráficos | matplotlib | 3.9.3 | Generación de gráficos para PDF |
| PDF | reportlab | 4.2.5 | Generación de documentos PDF |
| Lectura PDF | PyPDF2 | 3.0.1 | Conteo de páginas en archivos PDF |
| Lectura DOCX | python-docx | 1.1.2 | Conteo de páginas en archivos Word |
| Criptografía | cryptography | 44.0.0 | Cifrado simétrico Fernet |
| JWT | PyJWT | 2.10.1 | Generación y validación de tokens |
| Variables Entorno | python-dotenv | 1.0.1 | Carga de archivos .env |
| Validación | pydantic | 2.10.3 | Schemas de validación de datos |
| Frontend Charts | Chart.js | 4.4.7 | Gráficos interactivos en dashboard (CDN) |
| Frontend Labels | chartjs-plugin-datalabels | 2.2.0 | Etiquetas en gráficos (CDN) |

---

## 4. REQUERIMIENTOS DEL SISTEMA

### 4.1 Hardware

| Componente | Mínimo | Recomendado |
|------------|--------|-------------|
| CPU | 2 núcleos | 4+ núcleos |
| RAM | 4 GB | 8+ GB |
| Disco | 20 GB | 50+ GB SSD |
| Red | 100 Mbps | 1 Gbps |

### 4.2 Software Requerido

| Componente | Versión | Notas |
|------------|---------|-------|
| Sistema Operativo | Ubuntu 22.04+ / Debian 12+ | También compatible con derivados |
| Python | 3.10+ | Incluye pip, venv, dev |
| PostgreSQL | 16 | Puerto configurable (default: 5433) |
| RabbitMQ | 3.x | Puerto default: 5672 |
| Redis | 6.x+ | Puerto default: 6379 |
| Git | 2.x | Para clonar el repositorio |
| Nginx | Opcional | Como proxy reverso recomendado |

### 4.3 Dependencias de Sistema (Ubuntu/Debian)

- build-essential, python3-dev, python3-pip, python3-venv
- libpq-dev, libxml2-dev, libxslt1-dev
- libjpeg-dev, zlib1g-dev, libfreetype6-dev
- liblcms2-dev, libwebp-dev, libtiff-dev, libopenjp2-7-dev
- sshpass

### 4.4 Puertos de Red

| Puerto | Servicio | Protocolo |
|--------|----------|-----------|
| 8000 | Uvicorn/FastAPI | HTTP |
| 5433 | PostgreSQL | TCP |
| 5672 | RabbitMQ | AMQP |
| 6379 | Redis | TCP |

---

## 5. INSTALACIÓN

### 5.1 Instalación Automatizada

El proyecto incluye un script de instalación completa para servidores nuevos:


═══════════════════════════════════════════════════════════════
  Metafoliador v2 — Instalación completa
═══════════════════════════════════════════════════════════════
  Directorio:     /var/www/html/metafoliadorv2
  Base de datos:  metafoliadorv2 (usuario: metafoliador)
  PostgreSQL:     localhost:5433
═══════════════════════════════════════════════════════════════

[0;32m[INSTALL][0m Paso 1/8: Instalando dependencias del sistema ...
Selecting previously unselected package libjson-perl.
(Reading database ... 
(Reading database ... 5%
(Reading database ... 10%
(Reading database ... 15%
(Reading database ... 20%
(Reading database ... 25%
(Reading database ... 30%
(Reading database ... 35%
(Reading database ... 40%
(Reading database ... 45%
(Reading database ... 50%
(Reading database ... 55%
(Reading database ... 60%
(Reading database ... 65%
(Reading database ... 70%
(Reading database ... 75%
(Reading database ... 80%
(Reading database ... 85%
(Reading database ... 90%
(Reading database ... 95%
(Reading database ... 100%
(Reading database ... 57196 files and directories currently installed.)
Preparing to unpack .../000-libjson-perl_4.10000-1_all.deb ...
Unpacking libjson-perl (4.10000-1) ...
Selecting previously unselected package postgresql-client-common.
Preparing to unpack .../001-postgresql-client-common_257build1.1_all.deb ...
Unpacking postgresql-client-common (257build1.1) ...
Selecting previously unselected package ssl-cert.
Preparing to unpack .../002-ssl-cert_1.1.2ubuntu1_all.deb ...
Unpacking ssl-cert (1.1.2ubuntu1) ...
Selecting previously unselected package postgresql-common.
Preparing to unpack .../003-postgresql-common_257build1.1_all.deb ...
Adding 'diversion of /usr/bin/pg_config to /usr/bin/pg_config.libpq-dev by postgresql-common'
Unpacking postgresql-common (257build1.1) ...
Selecting previously unselected package libjemalloc2:amd64.
Preparing to unpack .../004-libjemalloc2_5.3.0-2build1_amd64.deb ...
Unpacking libjemalloc2:amd64 (5.3.0-2build1) ...
Selecting previously unselected package liblzf1:amd64.
Preparing to unpack .../005-liblzf1_3.6-4_amd64.deb ...
Unpacking liblzf1:amd64 (3.6-4) ...
Selecting previously unselected package redis-tools.
Preparing to unpack .../006-redis-tools_5%3a7.0.15-1ubuntu0.24.04.4_amd64.deb ...
Unpacking redis-tools (5:7.0.15-1ubuntu0.24.04.4) ...
Selecting previously unselected package redis-server.
Preparing to unpack .../007-redis-server_5%3a7.0.15-1ubuntu0.24.04.4_amd64.deb ...
Unpacking redis-server (5:7.0.15-1ubuntu0.24.04.4) ...
Selecting previously unselected package libfribidi0:amd64.
Preparing to unpack .../008-libfribidi0_1.0.13-3build1_amd64.deb ...
Unpacking libfribidi0:amd64 (1.0.13-3build1) ...
Selecting previously unselected package libpopt0:amd64.
Preparing to unpack .../009-libpopt0_1.19+dfsg-1build1_amd64.deb ...
Unpacking libpopt0:amd64 (1.19+dfsg-1build1) ...
Preparing to unpack .../010-libxml2_2.9.14+dfsg-1.3ubuntu3.8_amd64.deb ...
Unpacking libxml2:amd64 (2.9.14+dfsg-1.3ubuntu3.8) over (2.9.14+dfsg-1.3ubuntu3.7) ...
Selecting previously unselected package locales.
Preparing to unpack .../011-locales_2.39-0ubuntu8.7_all.deb ...
Unpacking locales (2.39-0ubuntu8.7) ...
Selecting previously unselected package logrotate.
Preparing to unpack .../012-logrotate_3.21.0-2build1_amd64.deb ...
Unpacking logrotate (3.21.0-2build1) ...
Selecting previously unselected package sysstat.
Preparing to unpack .../013-sysstat_12.6.1-2_amd64.deb ...
Unpacking sysstat (12.6.1-2) ...
Selecting previously unselected package bzip2-doc.
Preparing to unpack .../014-bzip2-doc_1.0.8-5.1build0.1_all.deb ...
Unpacking bzip2-doc (1.0.8-5.1build0.1) ...
Selecting previously unselected package erlang-base.
Preparing to unpack .../015-erlang-base_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-base (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-asn1.
Preparing to unpack .../016-erlang-asn1_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-asn1 (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-crypto.
Preparing to unpack .../017-erlang-crypto_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-crypto (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-public-key.
Preparing to unpack .../018-erlang-public-key_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-public-key (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-mnesia.
Preparing to unpack .../019-erlang-mnesia_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-mnesia (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-runtime-tools.
Preparing to unpack .../020-erlang-runtime-tools_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-runtime-tools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-ssl.
Preparing to unpack .../021-erlang-ssl_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-ssl (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-eldap.
Preparing to unpack .../022-erlang-eldap_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-eldap (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-ftp.
Preparing to unpack .../023-erlang-ftp_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-ftp (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-tftp.
Preparing to unpack .../024-erlang-tftp_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-tftp (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-inets.
Preparing to unpack .../025-erlang-inets_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-inets (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-snmp.
Preparing to unpack .../026-erlang-snmp_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-snmp (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-os-mon.
Preparing to unpack .../027-erlang-os-mon_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-os-mon (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-parsetools.
Preparing to unpack .../028-erlang-parsetools_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-parsetools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-syntax-tools.
Preparing to unpack .../029-erlang-syntax-tools_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-syntax-tools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-tools.
Preparing to unpack .../030-erlang-tools_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-tools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package erlang-xmerl.
Preparing to unpack .../031-erlang-xmerl_1%3a25.3.2.8+dfsg-1ubuntu4.6_amd64.deb ...
Unpacking erlang-xmerl (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Selecting previously unselected package libpixman-1-0:amd64.
Preparing to unpack .../032-libpixman-1-0_0.42.2-1build1_amd64.deb ...
Unpacking libpixman-1-0:amd64 (0.42.2-1build1) ...
Selecting previously unselected package libxcb-render0:amd64.
Preparing to unpack .../033-libxcb-render0_1.15-1ubuntu2_amd64.deb ...
Unpacking libxcb-render0:amd64 (1.15-1ubuntu2) ...
Selecting previously unselected package libcairo2:amd64.
Preparing to unpack .../034-libcairo2_1.18.0-3build1_amd64.deb ...
Unpacking libcairo2:amd64 (1.18.0-3build1) ...
Selecting previously unselected package libcairo-gobject2:amd64.
Preparing to unpack .../035-libcairo-gobject2_1.18.0-3build1_amd64.deb ...
Unpacking libcairo-gobject2:amd64 (1.18.0-3build1) ...
Selecting previously unselected package gir1.2-freedesktop:amd64.
Preparing to unpack .../036-gir1.2-freedesktop_1.80.1-1_amd64.deb ...
Unpacking gir1.2-freedesktop:amd64 (1.80.1-1) ...
Selecting previously unselected package gir1.2-glib-2.0-dev:amd64.
Preparing to unpack .../037-gir1.2-glib-2.0-dev_2.80.0-6ubuntu3.8_amd64.deb ...
Unpacking gir1.2-glib-2.0-dev:amd64 (2.80.0-6ubuntu3.8) ...
Selecting previously unselected package gir1.2-freedesktop-dev:amd64.
Preparing to unpack .../038-gir1.2-freedesktop-dev_1.80.1-1_amd64.deb ...
Unpacking gir1.2-freedesktop-dev:amd64 (1.80.1-1) ...
Selecting previously unselected package libgraphite2-3:amd64.
Preparing to unpack .../039-libgraphite2-3_1.3.14-2ubuntu0.24.04.1_amd64.deb ...
Unpacking libgraphite2-3:amd64 (1.3.14-2ubuntu0.24.04.1) ...
Selecting previously unselected package libharfbuzz0b:amd64.
Preparing to unpack .../040-libharfbuzz0b_8.3.0-2build2_amd64.deb ...
Unpacking libharfbuzz0b:amd64 (8.3.0-2build2) ...
Selecting previously unselected package libharfbuzz-gobject0:amd64.
Preparing to unpack .../041-libharfbuzz-gobject0_8.3.0-2build2_amd64.deb ...
Unpacking libharfbuzz-gobject0:amd64 (8.3.0-2build2) ...
Selecting previously unselected package gir1.2-harfbuzz-0.0:amd64.
Preparing to unpack .../042-gir1.2-harfbuzz-0.0_8.3.0-2build2_amd64.deb ...
Unpacking gir1.2-harfbuzz-0.0:amd64 (8.3.0-2build2) ...
Selecting previously unselected package icu-devtools.
Preparing to unpack .../043-icu-devtools_74.2-1ubuntu3.1_amd64.deb ...
Unpacking icu-devtools (74.2-1ubuntu3.1) ...
Selecting previously unselected package libbrotli-dev:amd64.
Preparing to unpack .../044-libbrotli-dev_1.1.0-2build2_amd64.deb ...
Unpacking libbrotli-dev:amd64 (1.1.0-2build2) ...
Selecting previously unselected package libbz2-dev:amd64.
Preparing to unpack .../045-libbz2-dev_1.0.8-5.1build0.1_amd64.deb ...
Unpacking libbz2-dev:amd64 (1.0.8-5.1build0.1) ...
Selecting previously unselected package libcommon-sense-perl:amd64.
Preparing to unpack .../046-libcommon-sense-perl_3.75-3build3_amd64.deb ...
Unpacking libcommon-sense-perl:amd64 (3.75-3build3) ...
Selecting previously unselected package libdeflate-dev:amd64.
Preparing to unpack .../047-libdeflate-dev_1.19-1build1.1_amd64.deb ...
Unpacking libdeflate-dev:amd64 (1.19-1build1.1) ...
Selecting previously unselected package libpng-dev:amd64.
Preparing to unpack .../048-libpng-dev_1.6.43-5ubuntu0.6_amd64.deb ...
Unpacking libpng-dev:amd64 (1.6.43-5ubuntu0.6) ...
Selecting previously unselected package libfreetype-dev:amd64.
Preparing to unpack .../049-libfreetype-dev_2.13.2+dfsg-1ubuntu0.1_amd64.deb ...
Unpacking libfreetype-dev:amd64 (2.13.2+dfsg-1ubuntu0.1) ...
Selecting previously unselected package libfribidi-dev:amd64.
Preparing to unpack .../050-libfribidi-dev_1.0.13-3build1_amd64.deb ...
Unpacking libfribidi-dev:amd64 (1.0.13-3build1) ...
Selecting previously unselected package libgirepository-2.0-0:amd64.
Preparing to unpack .../051-libgirepository-2.0-0_2.80.0-6ubuntu3.8_amd64.deb ...
Unpacking libgirepository-2.0-0:amd64 (2.80.0-6ubuntu3.8) ...
Selecting previously unselected package libffi-dev:amd64.
Preparing to unpack .../052-libffi-dev_3.4.6-1build1_amd64.deb ...
Unpacking libffi-dev:amd64 (3.4.6-1build1) ...
Selecting previously unselected package python3-packaging.
Preparing to unpack .../053-python3-packaging_24.0-1_all.deb ...
Unpacking python3-packaging (24.0-1) ...
Selecting previously unselected package libglib2.0-dev-bin.
Preparing to unpack .../054-libglib2.0-dev-bin_2.80.0-6ubuntu3.8_amd64.deb ...
Unpacking libglib2.0-dev-bin (2.80.0-6ubuntu3.8) ...
Selecting previously unselected package uuid-dev:amd64.
Preparing to unpack .../055-uuid-dev_2.39.3-9ubuntu6.5_amd64.deb ...
Unpacking uuid-dev:amd64 (2.39.3-9ubuntu6.5) ...
Selecting previously unselected package libblkid-dev:amd64.
Preparing to unpack .../056-libblkid-dev_2.39.3-9ubuntu6.5_amd64.deb ...
Unpacking libblkid-dev:amd64 (2.39.3-9ubuntu6.5) ...
Selecting previously unselected package libsepol-dev:amd64.
Preparing to unpack .../057-libsepol-dev_3.5-2build1_amd64.deb ...
Unpacking libsepol-dev:amd64 (3.5-2build1) ...
Selecting previously unselected package libpcre2-16-0:amd64.
Preparing to unpack .../058-libpcre2-16-0_10.42-4ubuntu2.1_amd64.deb ...
Unpacking libpcre2-16-0:amd64 (10.42-4ubuntu2.1) ...
Selecting previously unselected package libpcre2-32-0:amd64.
Preparing to unpack .../059-libpcre2-32-0_10.42-4ubuntu2.1_amd64.deb ...
Unpacking libpcre2-32-0:amd64 (10.42-4ubuntu2.1) ...
Selecting previously unselected package libpcre2-posix3:amd64.
Preparing to unpack .../060-libpcre2-posix3_10.42-4ubuntu2.1_amd64.deb ...
Unpacking libpcre2-posix3:amd64 (10.42-4ubuntu2.1) ...
Selecting previously unselected package libpcre2-dev:amd64.
Preparing to unpack .../061-libpcre2-dev_10.42-4ubuntu2.1_amd64.deb ...
Unpacking libpcre2-dev:amd64 (10.42-4ubuntu2.1) ...
Selecting previously unselected package libselinux1-dev:amd64.
Preparing to unpack .../062-libselinux1-dev_3.5-2ubuntu2.1_amd64.deb ...
Unpacking libselinux1-dev:amd64 (3.5-2ubuntu2.1) ...
Selecting previously unselected package libmount-dev:amd64.
Preparing to unpack .../063-libmount-dev_2.39.3-9ubuntu6.5_amd64.deb ...
Unpacking libmount-dev:amd64 (2.39.3-9ubuntu6.5) ...
Selecting previously unselected package libpkgconf3:amd64.
Preparing to unpack .../064-libpkgconf3_1.8.1-2build1_amd64.deb ...
Unpacking libpkgconf3:amd64 (1.8.1-2build1) ...
Selecting previously unselected package pkgconf-bin.
Preparing to unpack .../065-pkgconf-bin_1.8.1-2build1_amd64.deb ...
Unpacking pkgconf-bin (1.8.1-2build1) ...
Selecting previously unselected package pkgconf:amd64.
Preparing to unpack .../066-pkgconf_1.8.1-2build1_amd64.deb ...
Unpacking pkgconf:amd64 (1.8.1-2build1) ...
Selecting previously unselected package libglib2.0-dev:amd64.
Preparing to unpack .../067-libglib2.0-dev_2.80.0-6ubuntu3.8_amd64.deb ...
Unpacking libglib2.0-dev:amd64 (2.80.0-6ubuntu3.8) ...
Selecting previously unselected package libgraphite2-dev:amd64.
Preparing to unpack .../068-libgraphite2-dev_1.3.14-2ubuntu0.24.04.1_amd64.deb ...
Unpacking libgraphite2-dev:amd64 (1.3.14-2ubuntu0.24.04.1) ...
Selecting previously unselected package libharfbuzz-cairo0:amd64.
Preparing to unpack .../069-libharfbuzz-cairo0_8.3.0-2build2_amd64.deb ...
Unpacking libharfbuzz-cairo0:amd64 (8.3.0-2build2) ...
Selecting previously unselected package libharfbuzz-icu0:amd64.
Preparing to unpack .../070-libharfbuzz-icu0_8.3.0-2build2_amd64.deb ...
Unpacking libharfbuzz-icu0:amd64 (8.3.0-2build2) ...
Selecting previously unselected package libharfbuzz-subset0:amd64.
Preparing to unpack .../071-libharfbuzz-subset0_8.3.0-2build2_amd64.deb ...
Unpacking libharfbuzz-subset0:amd64 (8.3.0-2build2) ...
Selecting previously unselected package libicu-dev:amd64.
Preparing to unpack .../072-libicu-dev_74.2-1ubuntu3.1_amd64.deb ...
Unpacking libicu-dev:amd64 (74.2-1ubuntu3.1) ...
Selecting previously unselected package libharfbuzz-dev:amd64.
Preparing to unpack .../073-libharfbuzz-dev_8.3.0-2build2_amd64.deb ...
Unpacking libharfbuzz-dev:amd64 (8.3.0-2build2) ...
Selecting previously unselected package libjpeg-turbo8-dev:amd64.
Preparing to unpack .../074-libjpeg-turbo8-dev_2.1.5-2ubuntu2_amd64.deb ...
Unpacking libjpeg-turbo8-dev:amd64 (2.1.5-2ubuntu2) ...
Selecting previously unselected package libjpeg8-dev:amd64.
Preparing to unpack .../075-libjpeg8-dev_8c-2ubuntu11_amd64.deb ...
Unpacking libjpeg8-dev:amd64 (8c-2ubuntu11) ...
Selecting previously unselected package libjpeg-dev:amd64.
Preparing to unpack .../076-libjpeg-dev_8c-2ubuntu11_amd64.deb ...
Unpacking libjpeg-dev:amd64 (8c-2ubuntu11) ...
Selecting previously unselected package libtypes-serialiser-perl.
Preparing to unpack .../077-libtypes-serialiser-perl_1.01-1_all.deb ...
Unpacking libtypes-serialiser-perl (1.01-1) ...
Selecting previously unselected package libjson-xs-perl.
Preparing to unpack .../078-libjson-xs-perl_4.040-0ubuntu0.24.04.1_amd64.deb ...
Unpacking libjson-xs-perl (4.040-0ubuntu0.24.04.1) ...
Selecting previously unselected package liblcms2-2:amd64.
Preparing to unpack .../079-liblcms2-2_2.14-2ubuntu0.1_amd64.deb ...
Unpacking liblcms2-2:amd64 (2.14-2ubuntu0.1) ...
Selecting previously unselected package liblcms2-dev:amd64.
Preparing to unpack .../080-liblcms2-dev_2.14-2ubuntu0.1_amd64.deb ...
Unpacking liblcms2-dev:amd64 (2.14-2ubuntu0.1) ...
Selecting previously unselected package liblerc-dev:amd64.
Preparing to unpack .../081-liblerc-dev_4.0.0+ds-4ubuntu2_amd64.deb ...
Unpacking liblerc-dev:amd64 (4.0.0+ds-4ubuntu2) ...
Selecting previously unselected package libllvm17t64:amd64.
Preparing to unpack .../082-libllvm17t64_1%3a17.0.6-9ubuntu1_amd64.deb ...
Unpacking libllvm17t64:amd64 (1:17.0.6-9ubuntu1) ...
Selecting previously unselected package libpng-tools.
Preparing to unpack .../083-libpng-tools_1.6.43-5ubuntu0.6_amd64.deb ...
Unpacking libpng-tools (1.6.43-5ubuntu0.6) ...
Selecting previously unselected package libpq5:amd64.
Preparing to unpack .../084-libpq5_16.14-0ubuntu0.24.04.1_amd64.deb ...
Unpacking libpq5:amd64 (16.14-0ubuntu0.24.04.1) ...
Selecting previously unselected package libpq-dev.
Preparing to unpack .../085-libpq-dev_16.14-0ubuntu0.24.04.1_amd64.deb ...
Unpacking libpq-dev (16.14-0ubuntu0.24.04.1) ...
Selecting previously unselected package libsctp1:amd64.
Preparing to unpack .../086-libsctp1_1.0.19+dfsg-2build1_amd64.deb ...
Unpacking libsctp1:amd64 (1.0.19+dfsg-2build1) ...
Selecting previously unselected package libsharpyuv-dev:amd64.
Preparing to unpack .../087-libsharpyuv-dev_1.3.2-0.4build3_amd64.deb ...
Unpacking libsharpyuv-dev:amd64 (1.3.2-0.4build3) ...
Selecting previously unselected package libjbig-dev:amd64.
Preparing to unpack .../088-libjbig-dev_2.1-6.1ubuntu2_amd64.deb ...
Unpacking libjbig-dev:amd64 (2.1-6.1ubuntu2) ...
Selecting previously unselected package liblzma-dev:amd64.
Preparing to unpack .../089-liblzma-dev_5.6.1+really5.4.5-1ubuntu0.3_amd64.deb ...
Unpacking liblzma-dev:amd64 (5.6.1+really5.4.5-1ubuntu0.3) ...
Selecting previously unselected package libzstd-dev:amd64.
Preparing to unpack .../090-libzstd-dev_1.5.5+dfsg2-2build1.1_amd64.deb ...
Unpacking libzstd-dev:amd64 (1.5.5+dfsg2-2build1.1) ...
Selecting previously unselected package libwebpdemux2:amd64.
Preparing to unpack .../091-libwebpdemux2_1.3.2-0.4build3_amd64.deb ...
Unpacking libwebpdemux2:amd64 (1.3.2-0.4build3) ...
Selecting previously unselected package libwebpmux3:amd64.
Preparing to unpack .../092-libwebpmux3_1.3.2-0.4build3_amd64.deb ...
Unpacking libwebpmux3:amd64 (1.3.2-0.4build3) ...
Selecting previously unselected package libwebpdecoder3:amd64.
Preparing to unpack .../093-libwebpdecoder3_1.3.2-0.4build3_amd64.deb ...
Unpacking libwebpdecoder3:amd64 (1.3.2-0.4build3) ...
Selecting previously unselected package libwebp-dev:amd64.
Preparing to unpack .../094-libwebp-dev_1.3.2-0.4build3_amd64.deb ...
Unpacking libwebp-dev:amd64 (1.3.2-0.4build3) ...
Selecting previously unselected package libtiffxx6:amd64.
Preparing to unpack .../095-libtiffxx6_4.5.1+git230720-4ubuntu2.5_amd64.deb ...
Unpacking libtiffxx6:amd64 (4.5.1+git230720-4ubuntu2.5) ...
Selecting previously unselected package libtiff-dev:amd64.
Preparing to unpack .../096-libtiff-dev_4.5.1+git230720-4ubuntu2.5_amd64.deb ...
Unpacking libtiff-dev:amd64 (4.5.1+git230720-4ubuntu2.5) ...
Selecting previously unselected package libtiff5-dev:amd64.
Preparing to unpack .../097-libtiff5-dev_4.5.1+git230720-4ubuntu2.5_amd64.deb ...
Unpacking libtiff5-dev:amd64 (4.5.1+git230720-4ubuntu2.5) ...
Selecting previously unselected package libwrap0:amd64.
Preparing to unpack .../098-libwrap0_7.6.q-33_amd64.deb ...
Unpacking libwrap0:amd64 (7.6.q-33) ...
Selecting previously unselected package libxml2-dev:amd64.
Preparing to unpack .../099-libxml2-dev_2.9.14+dfsg-1.3ubuntu3.8_amd64.deb ...
Unpacking libxml2-dev:amd64 (2.9.14+dfsg-1.3ubuntu3.8) ...
Selecting previously unselected package libxslt1.1:amd64.
Preparing to unpack .../100-libxslt1.1_1.1.39-0exp1ubuntu0.24.04.3_amd64.deb ...
Unpacking libxslt1.1:amd64 (1.1.39-0exp1ubuntu0.24.04.3) ...
Selecting previously unselected package libxslt1-dev:amd64.
Preparing to unpack .../101-libxslt1-dev_1.1.39-0exp1ubuntu0.24.04.3_amd64.deb ...
Unpacking libxslt1-dev:amd64 (1.1.39-0exp1ubuntu0.24.04.3) ...
Selecting previously unselected package postgresql-client-16.
Preparing to unpack .../102-postgresql-client-16_16.14-0ubuntu0.24.04.1_amd64.deb ...
Unpacking postgresql-client-16 (16.14-0ubuntu0.24.04.1) ...
Selecting previously unselected package postgresql-16.
Preparing to unpack .../103-postgresql-16_16.14-0ubuntu0.24.04.1_amd64.deb ...
Unpacking postgresql-16 (16.14-0ubuntu0.24.04.1) ...
Selecting previously unselected package postgresql.
Preparing to unpack .../104-postgresql_16+257build1.1_all.deb ...
Unpacking postgresql (16+257build1.1) ...
Selecting previously unselected package postgresql-client.
Preparing to unpack .../105-postgresql-client_16+257build1.1_all.deb ...
Unpacking postgresql-client (16+257build1.1) ...
Selecting previously unselected package libopenjp2-7:amd64.
Preparing to unpack .../106-libopenjp2-7_2.5.0-2ubuntu0.5_amd64.deb ...
Unpacking libopenjp2-7:amd64 (2.5.0-2ubuntu0.5) ...
Selecting previously unselected package libopenjp2-7-dev:amd64.
Preparing to unpack .../107-libopenjp2-7-dev_2.5.0-2ubuntu0.5_amd64.deb ...
Unpacking libopenjp2-7-dev:amd64 (2.5.0-2ubuntu0.5) ...
Selecting previously unselected package socat.
Preparing to unpack .../108-socat_1.8.0.0-4ubuntu0.1_amd64.deb ...
Unpacking socat (1.8.0.0-4ubuntu0.1) ...
Selecting previously unselected package rabbitmq-server.
Preparing to unpack .../109-rabbitmq-server_3.12.1-1ubuntu1.2_all.deb ...
Unpacking rabbitmq-server (3.12.1-1ubuntu1.2) ...
Setting up postgresql-client-common (257build1.1) ...
Setting up libgraphite2-3:amd64 (1.3.14-2ubuntu0.24.04.1) ...
Setting up liblcms2-2:amd64 (2.14-2ubuntu0.1) ...
Setting up libpixman-1-0:amd64 (0.42.2-1build1) ...
Setting up bzip2-doc (1.0.8-5.1build0.1) ...
Setting up libjpeg-turbo8-dev:amd64 (2.1.5-2ubuntu2) ...
Setting up libzstd-dev:amd64 (1.5.5+dfsg2-2build1.1) ...
Setting up erlang-base (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Created symlink /etc/systemd/system/multi-user.target.wants/epmd.service → /usr/lib/systemd/system/epmd.service.

Created symlink /etc/systemd/system/sockets.target.wants/epmd.socket → /usr/lib/systemd/system/epmd.socket.

Searching for services which depend on erlang and should be started... none found.
Setting up libpng-tools (1.6.43-5ubuntu0.6) ...
Setting up libgirepository-2.0-0:amd64 (2.80.0-6ubuntu3.8) ...
Setting up libxcb-render0:amd64 (1.15-1ubuntu2) ...
Setting up libwebpdemux2:amd64 (1.3.2-0.4build3) ...
Setting up erlang-xmerl (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libpng-dev:amd64 (1.6.43-5ubuntu0.6) ...
Setting up libpq5:amd64 (16.14-0ubuntu0.24.04.1) ...
Setting up libjbig-dev:amd64 (2.1-6.1ubuntu2) ...
Setting up libcommon-sense-perl:amd64 (3.75-3build3) ...
Setting up libwebpdecoder3:amd64 (1.3.2-0.4build3) ...
Setting up erlang-syntax-tools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libpq-dev (16.14-0ubuntu0.24.04.1) ...
Setting up libjemalloc2:amd64 (5.3.0-2build1) ...
Setting up libffi-dev:amd64 (3.4.6-1build1) ...
Setting up locales (2.39-0ubuntu8.7) ...
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
Generating locales (this might take a while)...
Generation complete.
Setting up libcairo2:amd64 (1.18.0-3build1) ...
Setting up libpcre2-16-0:amd64 (10.42-4ubuntu2.1) ...
Setting up libgraphite2-dev:amd64 (1.3.14-2ubuntu0.24.04.1) ...
Setting up libpcre2-32-0:amd64 (10.42-4ubuntu2.1) ...
Setting up python3-packaging (24.0-1) ...
Setting up libpkgconf3:amd64 (1.8.1-2build1) ...
Setting up erlang-parsetools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libwrap0:amd64 (7.6.q-33) ...
Setting up ssl-cert (1.1.2ubuntu1) ...
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
Created symlink /etc/systemd/system/multi-user.target.wants/ssl-cert.service → /usr/lib/systemd/system/ssl-cert.service.

Setting up uuid-dev:amd64 (2.39.3-9ubuntu6.5) ...
Setting up socat (1.8.0.0-4ubuntu0.1) ...
Setting up liblzf1:amd64 (3.6-4) ...
Setting up libfribidi0:amd64 (1.0.13-3build1) ...
Setting up icu-devtools (74.2-1ubuntu3.1) ...
Setting up libsepol-dev:amd64 (3.5-2build1) ...
Setting up pkgconf-bin (1.8.1-2build1) ...
Setting up liblerc-dev:amd64 (4.0.0+ds-4ubuntu2) ...
Setting up erlang-asn1 (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up erlang-tftp (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up liblcms2-dev:amd64 (2.14-2ubuntu0.1) ...
Setting up liblzma-dev:amd64 (5.6.1+really5.4.5-1ubuntu0.3) ...
Setting up libpcre2-posix3:amd64 (10.42-4ubuntu2.1) ...
Setting up libtypes-serialiser-perl (1.01-1) ...
Setting up libsctp1:amd64 (1.0.19+dfsg-2build1) ...
Setting up gir1.2-glib-2.0-dev:amd64 (2.80.0-6ubuntu3.8) ...
Setting up libjson-perl (4.10000-1) ...
Setting up erlang-mnesia (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libopenjp2-7:amd64 (2.5.0-2ubuntu0.5) ...
Setting up libharfbuzz0b:amd64 (8.3.0-2build2) ...
Setting up libcairo-gobject2:amd64 (1.18.0-3build1) ...
Setting up erlang-crypto (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libjpeg8-dev:amd64 (8c-2ubuntu11) ...
Setting up libsharpyuv-dev:amd64 (1.3.2-0.4build3) ...
Setting up libwebpmux3:amd64 (1.3.2-0.4build3) ...
Setting up libtiffxx6:amd64 (4.5.1+git230720-4ubuntu2.5) ...
Setting up libdeflate-dev:amd64 (1.19-1build1.1) ...
Setting up libicu-dev:amd64 (74.2-1ubuntu3.1) ...
Setting up libxml2:amd64 (2.9.14+dfsg-1.3ubuntu3.8) ...
Setting up sysstat (12.6.1-2) ...
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype

Creating config file /etc/default/sysstat with new version
update-alternatives: using /usr/bin/sar.sysstat to provide /usr/bin/sar (sar) in auto mode
update-alternatives: warning: skip creation of /usr/share/man/man1/sar.1.gz because associated file /usr/share/man/man1/sar.sysstat.1.gz (of link group sar) doesn't exist
Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-collect.timer → /usr/lib/systemd/system/sysstat-collect.timer.

Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-summary.timer → /usr/lib/systemd/system/sysstat-summary.timer.

Created symlink /etc/systemd/system/multi-user.target.wants/sysstat.service → /usr/lib/systemd/system/sysstat.service.

Setting up libbrotli-dev:amd64 (1.1.0-2build2) ...
Setting up libpopt0:amd64 (1.19+dfsg-1build1) ...
Setting up libbz2-dev:amd64 (1.0.8-5.1build0.1) ...
Setting up logrotate (3.21.0-2build1) ...
Created symlink /etc/systemd/system/timers.target.wants/logrotate.timer → /usr/lib/systemd/system/logrotate.timer.

Setting up libjson-xs-perl (4.040-0ubuntu0.24.04.1) ...
Setting up libglib2.0-dev-bin (2.80.0-6ubuntu3.8) ...
Setting up libblkid-dev:amd64 (2.39.3-9ubuntu6.5) ...
Setting up gir1.2-freedesktop:amd64 (1.80.1-1) ...
Setting up libharfbuzz-icu0:amd64 (8.3.0-2build2) ...
Setting up libpcre2-dev:amd64 (10.42-4ubuntu2.1) ...
Setting up erlang-runtime-tools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libselinux1-dev:amd64 (3.5-2ubuntu2.1) ...
Setting up libfribidi-dev:amd64 (1.0.13-3build1) ...
Setting up postgresql-client-16 (16.14-0ubuntu0.24.04.1) ...
update-alternatives: using /usr/share/postgresql/16/man/man1/psql.1.gz to provide /usr/share/man/man1/psql.1.gz (psql.1.gz) in auto mode
Setting up libjpeg-dev:amd64 (8c-2ubuntu11) ...
Setting up erlang-tools (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libharfbuzz-gobject0:amd64 (8.3.0-2build2) ...
Setting up gir1.2-harfbuzz-0.0:amd64 (8.3.0-2build2) ...
Setting up pkgconf:amd64 (1.8.1-2build1) ...
Setting up libfreetype-dev:amd64 (2.13.2+dfsg-1ubuntu0.1) ...
Setting up erlang-snmp (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libxml2-dev:amd64 (2.9.14+dfsg-1.3ubuntu3.8) ...
Setting up libwebp-dev:amd64 (1.3.2-0.4build3) ...
Setting up libllvm17t64:amd64 (1:17.0.6-9ubuntu1) ...
Setting up libharfbuzz-cairo0:amd64 (8.3.0-2build2) ...
Setting up libtiff-dev:amd64 (4.5.1+git230720-4ubuntu2.5) ...
Setting up libopenjp2-7-dev:amd64 (2.5.0-2ubuntu0.5) ...
Setting up redis-tools (5:7.0.15-1ubuntu0.24.04.4) ...
Setting up gir1.2-freedesktop-dev:amd64 (1.80.1-1) ...
Setting up erlang-public-key (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up postgresql-common (257build1.1) ...
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype

Creating config file /etc/postgresql-common/createcluster.conf with new version
Building PostgreSQL dictionaries from installed myspell/hunspell packages...
Removing obsolete dictionary files:
invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
Created symlink /etc/systemd/system/multi-user.target.wants/postgresql.service → /usr/lib/systemd/system/postgresql.service.

Setting up libxslt1.1:amd64 (1.1.39-0exp1ubuntu0.24.04.3) ...
Setting up libharfbuzz-subset0:amd64 (8.3.0-2build2) ...
Setting up libmount-dev:amd64 (2.39.3-9ubuntu6.5) ...
Setting up erlang-ssl (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libxslt1-dev:amd64 (1.1.39-0exp1ubuntu0.24.04.3) ...
Setting up postgresql-client (16+257build1.1) ...
Setting up postgresql-16 (16.14-0ubuntu0.24.04.1) ...
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
Creating new PostgreSQL cluster 16/main ...
/usr/lib/postgresql/16/bin/initdb -D /var/lib/postgresql/16/main --auth-local peer --auth-host scram-sha-256 --no-instructions
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "C.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/16/main ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... America/Bogota
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
Setting up erlang-os-mon (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libtiff5-dev:amd64 (4.5.1+git230720-4ubuntu2.5) ...
Setting up redis-server (5:7.0.15-1ubuntu0.24.04.4) ...
invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
Created symlink /etc/systemd/system/redis.service → /usr/lib/systemd/system/redis-server.service.

Created symlink /etc/systemd/system/multi-user.target.wants/redis-server.service → /usr/lib/systemd/system/redis-server.service.

Setting up erlang-eldap (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up libglib2.0-dev:amd64 (2.80.0-6ubuntu3.8) ...
Setting up erlang-ftp (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up postgresql (16+257build1.1) ...
Setting up erlang-inets (1:25.3.2.8+dfsg-1ubuntu4.6) ...
Setting up rabbitmq-server (3.12.1-1ubuntu1.2) ...
info: Selecting GID from range 100 to 999 ...
info: Adding group `rabbitmq' (GID 107) ...
info: Selecting UID from range 100 to 999 ...

info: Adding system user `rabbitmq' (UID 104) ...
info: Adding new user `rabbitmq' (UID 104) with group `rabbitmq' ...
info: Not creating home directory `/var/lib/rabbitmq'.
Created symlink /etc/systemd/system/multi-user.target.wants/rabbitmq-server.service → /usr/lib/systemd/system/rabbitmq-server.service.

invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
Processing triggers for libc-bin (2.39-0ubuntu8.7) ...
Processing triggers for libglib2.0-0t64:amd64 (2.80.0-6ubuntu3.8) ...
No schema files found: doing nothing.
Setting up libharfbuzz-dev:amd64 (8.3.0-2build2) ...
[0;32m[INSTALL][0m Dependencias del sistema instaladas.
[0;32m[INSTALL][0m Paso 2/8: Configurando PostgreSQL ...

Este script ejecuta automáticamente los siguientes pasos:

**Paso 1 — Paquetes del sistema:** Instala Python 3, PostgreSQL, RabbitMQ, Redis, Git y todas las dependencias de compilación necesarias.

**Paso 2 — Configuración de PostgreSQL:** Habilita e inicia el servicio PostgreSQL, configura el puerto 5433, y ejecuta setup_database.sql para crear el usuario y la base de datos.

**Paso 3 — Clonación del repositorio:** Clona desde GitLab rama v2 en /var/www/html/metafoliadorv2.

**Paso 4 — Entorno virtual Python:** Crea el virtualenv, actualiza pip e instala dependencias de requirements.txt.

**Paso 5 — Archivo de configuración:** Crea .env a partir de .env.example.

**Paso 6 — Migraciones:** Ejecuta alembic upgrade head (19 versiones de migración).

**Paso 7 — Directorio de logs:** Crea /var/www/html/metafoliadorv2/logs/.

**Paso 8 — Servicios systemd:** Instala e inicia metafoliadorv2.service, metafoliadorv2-worker.service, y metafoliadorv2-webhook-worker.service.

### 5.2 Instalación Manual

#### 5.2.1 Clonar el Repositorio



#### 5.2.2 Crear Entorno Virtual

Requirement already satisfied: pip in ./venv/lib/python3.12/site-packages (24.0)
Collecting pip
  Downloading pip-26.1.2-py3-none-any.whl.metadata (4.6 kB)
Downloading pip-26.1.2-py3-none-any.whl (1.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 27.7 MB/s eta 0:00:00
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 24.0
    Uninstalling pip-24.0:
      Successfully uninstalled pip-24.0
Successfully installed pip-26.1.2
Collecting fastapi==0.115.6 (from -r requirements.txt (line 1))
  Downloading fastapi-0.115.6-py3-none-any.whl.metadata (27 kB)
Collecting uvicorn==0.34.0 (from uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading uvicorn-0.34.0-py3-none-any.whl.metadata (6.5 kB)
Collecting sqlalchemy==2.0.36 (from -r requirements.txt (line 3))
  Downloading SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (9.7 kB)
Collecting alembic==1.14.0 (from -r requirements.txt (line 4))
  Downloading alembic-1.14.0-py3-none-any.whl.metadata (7.4 kB)
Collecting psycopg2-binary==2.9.10 (from -r requirements.txt (line 5))
  Downloading psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)
Collecting lxml==5.3.0 (from -r requirements.txt (line 6))
  Downloading lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (3.8 kB)
Collecting pika==1.3.2 (from -r requirements.txt (line 7))
  Downloading pika-1.3.2-py3-none-any.whl.metadata (13 kB)
Collecting redis==5.2.0 (from -r requirements.txt (line 8))
  Downloading redis-5.2.0-py3-none-any.whl.metadata (9.1 kB)
Collecting celery==5.4.0 (from -r requirements.txt (line 9))
  Downloading celery-5.4.0-py3-none-any.whl.metadata (21 kB)
Collecting python-dotenv==1.0.1 (from -r requirements.txt (line 10))
  Downloading python_dotenv-1.0.1-py3-none-any.whl.metadata (23 kB)
Collecting pydantic==2.10.3 (from -r requirements.txt (line 11))
  Downloading pydantic-2.10.3-py3-none-any.whl.metadata (172 kB)
Collecting pydantic-settings==2.6.1 (from -r requirements.txt (line 12))
  Downloading pydantic_settings-2.6.1-py3-none-any.whl.metadata (3.5 kB)
Collecting httpx==0.28.1 (from -r requirements.txt (line 13))
  Downloading httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)
Collecting matplotlib==3.9.3 (from -r requirements.txt (line 14))
  Downloading matplotlib-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (11 kB)
Collecting reportlab==4.2.5 (from -r requirements.txt (line 15))
  Downloading reportlab-4.2.5-py3-none-any.whl.metadata (1.5 kB)
Collecting PyPDF2==3.0.1 (from -r requirements.txt (line 16))
  Downloading pypdf2-3.0.1-py3-none-any.whl.metadata (6.8 kB)
Collecting python-docx==1.1.2 (from -r requirements.txt (line 17))
  Downloading python_docx-1.1.2-py3-none-any.whl.metadata (2.0 kB)
Collecting openpyxl==3.1.5 (from -r requirements.txt (line 18))
  Downloading openpyxl-3.1.5-py2.py3-none-any.whl.metadata (2.5 kB)
Collecting cryptography==44.0.0 (from -r requirements.txt (line 19))
  Downloading cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl.metadata (5.7 kB)
Collecting PyJWT==2.10.1 (from -r requirements.txt (line 20))
  Downloading PyJWT-2.10.1-py3-none-any.whl.metadata (4.0 kB)
Collecting starlette<0.42.0,>=0.40.0 (from fastapi==0.115.6->-r requirements.txt (line 1))
  Downloading starlette-0.41.3-py3-none-any.whl.metadata (6.0 kB)
Collecting typing-extensions>=4.8.0 (from fastapi==0.115.6->-r requirements.txt (line 1))
  Downloading typing_extensions-4.16.0-py3-none-any.whl.metadata (3.3 kB)
Collecting annotated-types>=0.6.0 (from pydantic==2.10.3->-r requirements.txt (line 11))
  Downloading annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
Collecting pydantic-core==2.27.1 (from pydantic==2.10.3->-r requirements.txt (line 11))
  Downloading pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)
Collecting click>=7.0 (from uvicorn==0.34.0->uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading click-8.4.2-py3-none-any.whl.metadata (2.6 kB)
Collecting h11>=0.8 (from uvicorn==0.34.0->uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)
Collecting greenlet!=0.4.17 (from sqlalchemy==2.0.36->-r requirements.txt (line 3))
  Downloading greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (3.8 kB)
Collecting Mako (from alembic==1.14.0->-r requirements.txt (line 4))
  Downloading mako-1.3.12-py3-none-any.whl.metadata (2.9 kB)
Collecting billiard<5.0,>=4.2.0 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading billiard-4.2.4-py3-none-any.whl.metadata (4.8 kB)
Collecting kombu<6.0,>=5.3.4 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading kombu-5.6.2-py3-none-any.whl.metadata (3.5 kB)
Collecting vine<6.0,>=5.1.0 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading vine-5.1.0-py3-none-any.whl.metadata (2.7 kB)
Collecting click-didyoumean>=0.3.0 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading click_didyoumean-0.3.1-py3-none-any.whl.metadata (3.9 kB)
Collecting click-repl>=0.2.0 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading click_repl-0.3.0-py3-none-any.whl.metadata (3.6 kB)
Collecting click-plugins>=1.1.1 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading click_plugins-1.1.1.2-py2.py3-none-any.whl.metadata (6.5 kB)
Collecting tzdata>=2022.7 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading tzdata-2026.2-py2.py3-none-any.whl.metadata (1.4 kB)
Collecting python-dateutil>=2.8.2 (from celery==5.4.0->-r requirements.txt (line 9))
  Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl.metadata (8.4 kB)
Collecting anyio (from httpx==0.28.1->-r requirements.txt (line 13))
  Downloading anyio-4.14.1-py3-none-any.whl.metadata (4.6 kB)
Collecting certifi (from httpx==0.28.1->-r requirements.txt (line 13))
  Downloading certifi-2026.6.17-py3-none-any.whl.metadata (2.5 kB)
Collecting httpcore==1.* (from httpx==0.28.1->-r requirements.txt (line 13))
  Downloading httpcore-1.0.9-py3-none-any.whl.metadata (21 kB)
Collecting idna (from httpx==0.28.1->-r requirements.txt (line 13))
  Downloading idna-3.18-py3-none-any.whl.metadata (6.1 kB)
Collecting contourpy>=1.0.1 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (5.5 kB)
Collecting cycler>=0.10 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading cycler-0.12.1-py3-none-any.whl.metadata (3.8 kB)
Collecting fonttools>=4.22.0 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (118 kB)
Collecting kiwisolver>=1.3.1 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (5.1 kB)
Collecting numpy>=1.23 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (6.6 kB)
Collecting packaging>=20.0 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading packaging-26.2-py3-none-any.whl.metadata (3.5 kB)
Collecting pillow>=8 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (9.1 kB)
Collecting pyparsing>=2.3.1 (from matplotlib==3.9.3->-r requirements.txt (line 14))
  Downloading pyparsing-3.3.2-py3-none-any.whl.metadata (5.8 kB)
Collecting chardet (from reportlab==4.2.5->-r requirements.txt (line 15))
  Downloading chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (9.4 kB)
Collecting et-xmlfile (from openpyxl==3.1.5->-r requirements.txt (line 18))
  Downloading et_xmlfile-2.0.0-py3-none-any.whl.metadata (2.7 kB)
Collecting cffi>=1.12 (from cryptography==44.0.0->-r requirements.txt (line 19))
  Downloading cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.5 kB)
Collecting httptools>=0.6.3 (from uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (3.5 kB)
Collecting pyyaml>=5.1 (from uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.4 kB)
Collecting uvloop!=0.15.0,!=0.15.1,>=0.14.0 (from uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (4.9 kB)
Collecting watchfiles>=0.13 (from uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)
Collecting websockets>=10.4 (from uvicorn[standard]==0.34.0->-r requirements.txt (line 2))
  Downloading websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.8 kB)
Collecting amqp<6.0.0,>=5.1.1 (from kombu<6.0,>=5.3.4->celery==5.4.0->-r requirements.txt (line 9))
  Downloading amqp-5.3.1-py3-none-any.whl.metadata (8.9 kB)
Collecting pycparser (from cffi>=1.12->cryptography==44.0.0->-r requirements.txt (line 19))
  Downloading pycparser-3.0-py3-none-any.whl.metadata (8.2 kB)
Collecting prompt-toolkit>=3.0.36 (from click-repl>=0.2.0->celery==5.4.0->-r requirements.txt (line 9))
  Downloading prompt_toolkit-3.0.52-py3-none-any.whl.metadata (6.4 kB)
Collecting wcwidth (from prompt-toolkit>=3.0.36->click-repl>=0.2.0->celery==5.4.0->-r requirements.txt (line 9))
  Downloading wcwidth-0.8.2-py3-none-any.whl.metadata (43 kB)
Collecting six>=1.5 (from python-dateutil>=2.8.2->celery==5.4.0->-r requirements.txt (line 9))
  Downloading six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB)
Collecting MarkupSafe>=0.9.2 (from Mako->alembic==1.14.0->-r requirements.txt (line 4))
  Downloading markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.7 kB)
Downloading fastapi-0.115.6-py3-none-any.whl (94 kB)
Downloading pydantic-2.10.3-py3-none-any.whl (456 kB)
Downloading uvicorn-0.34.0-py3-none-any.whl (62 kB)
Downloading SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.2/3.2 MB 54.9 MB/s  0:00:00
Downloading alembic-1.14.0-py3-none-any.whl (233 kB)
Downloading psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.0 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.0/3.0 MB 55.2 MB/s  0:00:00
Downloading lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl (4.9 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.9/4.9 MB 59.6 MB/s  0:00:00
Downloading pika-1.3.2-py3-none-any.whl (155 kB)
Downloading redis-5.2.0-py3-none-any.whl (261 kB)
Downloading celery-5.4.0-py3-none-any.whl (425 kB)
Downloading python_dotenv-1.0.1-py3-none-any.whl (19 kB)
Downloading pydantic_settings-2.6.1-py3-none-any.whl (28 kB)
Downloading httpx-0.28.1-py3-none-any.whl (73 kB)
Downloading matplotlib-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.3/8.3 MB 72.9 MB/s  0:00:00
Downloading reportlab-4.2.5-py3-none-any.whl (1.9 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.9/1.9 MB 44.0 MB/s  0:00:00
Downloading pypdf2-3.0.1-py3-none-any.whl (232 kB)
Downloading python_docx-1.1.2-py3-none-any.whl (244 kB)
Downloading openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
Downloading cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl (4.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.2/4.2 MB 67.0 MB/s  0:00:00
Downloading PyJWT-2.10.1-py3-none-any.whl (22 kB)
Downloading pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.2/2.2 MB 41.6 MB/s  0:00:00
Downloading billiard-4.2.4-py3-none-any.whl (87 kB)
Downloading click-8.4.2-py3-none-any.whl (119 kB)
Downloading httpcore-1.0.9-py3-none-any.whl (78 kB)
Downloading kombu-5.6.2-py3-none-any.whl (214 kB)
Downloading vine-5.1.0-py3-none-any.whl (9.6 kB)
Downloading amqp-5.3.1-py3-none-any.whl (50 kB)
Downloading starlette-0.41.3-py3-none-any.whl (73 kB)
Downloading anyio-4.14.1-py3-none-any.whl (124 kB)
Downloading annotated_types-0.7.0-py3-none-any.whl (13 kB)
Downloading cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (221 kB)
Downloading click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
Downloading click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
Downloading click_repl-0.3.0-py3-none-any.whl (10 kB)
Downloading contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (362 kB)
Downloading cycler-0.12.1-py3-none-any.whl (8.3 kB)
Downloading fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (5.0 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.0/5.0 MB 65.8 MB/s  0:00:00
Downloading greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (614 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 614.2/614.2 kB 14.9 MB/s  0:00:00
Downloading h11-0.16.0-py3-none-any.whl (37 kB)
Downloading httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (523 kB)
Downloading idna-3.18-py3-none-any.whl (65 kB)
Downloading kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (1.5 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.5/1.5 MB 36.2 MB/s  0:00:00
Downloading numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (16.7 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.7/16.7 MB 94.6 MB/s  0:00:00
Downloading packaging-26.2-py3-none-any.whl (100 kB)
Downloading pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.9/6.9 MB 77.6 MB/s  0:00:00
Downloading prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
Downloading pyparsing-3.3.2-py3-none-any.whl (122 kB)
Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
Downloading pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 807.9/807.9 kB 19.5 MB/s  0:00:00
Downloading six-1.17.0-py2.py3-none-any.whl (11 kB)
Downloading typing_extensions-4.16.0-py3-none-any.whl (45 kB)
Downloading tzdata-2026.2-py2.py3-none-any.whl (349 kB)
Downloading uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (4.4 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.4/4.4 MB 61.6 MB/s  0:00:00
Downloading watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (456 kB)
Downloading websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (184 kB)
Downloading certifi-2026.6.17-py3-none-any.whl (133 kB)
Downloading chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (888 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 888.3/888.3 kB 23.2 MB/s  0:00:00
Downloading et_xmlfile-2.0.0-py3-none-any.whl (18 kB)
Downloading mako-1.3.12-py3-none-any.whl (78 kB)
Downloading markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
Downloading pycparser-3.0-py3-none-any.whl (48 kB)
Downloading wcwidth-0.8.2-py3-none-any.whl (323 kB)
Installing collected packages: websockets, wcwidth, vine, uvloop, tzdata, typing-extensions, six, redis, pyyaml, python-dotenv, PyPDF2, pyparsing, PyJWT, pycparser, psycopg2-binary, pillow, pika, packaging, numpy, MarkupSafe, lxml, kiwisolver, idna, httptools, h11, greenlet, fonttools, et-xmlfile, cycler, click, chardet, certifi, billiard, annotated-types, uvicorn, sqlalchemy, reportlab, python-docx, python-dateutil, pydantic-core, prompt-toolkit, openpyxl, Mako, httpcore, contourpy, click-plugins, click-didyoumean, cffi, anyio, amqp, watchfiles, starlette, pydantic, matplotlib, kombu, httpx, cryptography, click-repl, alembic, pydantic-settings, fastapi, celery

Successfully installed Mako-1.3.12 MarkupSafe-3.0.3 PyJWT-2.10.1 PyPDF2-3.0.1 alembic-1.14.0 amqp-5.3.1 annotated-types-0.7.0 anyio-4.14.1 billiard-4.2.4 celery-5.4.0 certifi-2026.6.17 cffi-2.1.0 chardet-7.4.3 click-8.4.2 click-didyoumean-0.3.1 click-plugins-1.1.1.2 click-repl-0.3.0 contourpy-1.3.3 cryptography-44.0.0 cycler-0.12.1 et-xmlfile-2.0.0 fastapi-0.115.6 fonttools-4.63.0 greenlet-3.5.3 h11-0.16.0 httpcore-1.0.9 httptools-0.8.0 httpx-0.28.1 idna-3.18 kiwisolver-1.5.0 kombu-5.6.2 lxml-5.3.0 matplotlib-3.9.3 numpy-2.5.1 openpyxl-3.1.5 packaging-26.2 pika-1.3.2 pillow-12.3.0 prompt-toolkit-3.0.52 psycopg2-binary-2.9.10 pycparser-3.0 pydantic-2.10.3 pydantic-core-2.27.1 pydantic-settings-2.6.1 pyparsing-3.3.2 python-dateutil-2.9.0.post0 python-docx-1.1.2 python-dotenv-1.0.1 pyyaml-6.0.3 redis-5.2.0 reportlab-4.2.5 six-1.17.0 sqlalchemy-2.0.36 starlette-0.41.3 typing-extensions-4.16.0 tzdata-2026.2 uvicorn-0.34.0 uvloop-0.22.1 vine-5.1.0 watchfiles-1.2.0 wcwidth-0.8.2 websockets-16.0

#### 5.2.3 Configurar Base de Datos



Credenciales por defecto: BD=metafoliadorv2, Usuario=metafoliador, Password=v2_password_here, Puerto=5433.

#### 5.2.4 Crear Archivo .env



#### 5.2.5 Ejecutar Migraciones



#### 5.2.6 Iniciar Servicios



---

## 6. CONFIGURACIÓN

### 6.1 Variables de Entorno

El sistema utiliza pydantic-settings para la gestión de configuración. Todas las variables pueden definirse en el archivo .env o como variables de entorno del sistema.

| Variable | Valor por Defecto | Descripción |
|----------|-------------------|-------------|
| DATABASE_URL | postgresql://metafoliador:v2_password_here@localhost:5433/metafoliadorv2 | Cadena de conexión PostgreSQL |
| RABBITMQ_URL | amqp://guest:guest@localhost:5672/ | URL del broker RabbitMQ |
| REDIS_URL | redis://localhost:6379/0 | URL de Redis para resultados Celery |
| API_HOST | 127.0.0.1 | Dirección de escucha del servidor API |
| API_PORT | 8000 | Puerto del servidor API |
| WEBHOOK_SECRET | change_this_secret | Secreto para validación de webhooks |
| SWAGGER_USERNAME | admin | Usuario HTTP Basic Auth para Swagger UI |
| SWAGGER_PASSWORD | tykUGJ#Nr3u9%DZ!HndX | Contraseña HTTP Basic Auth para Swagger UI |
| LOG_LEVEL | INFO | Nivel de logging (DEBUG, INFO, WARNING, ERROR) |
| TIMEZONE | America/Bogota | Zona horaria del sistema (ZoneInfo). Usada por app/services/timezone.py para fechas de API y frontend |
| MAIN_DOMAIN | metafoliador.local | Dominio del panel principal |
| INDEX_XML_NODE_TYPE | cm:content | Node type para el index.xml |

### 6.2 Clase Settings (app/config.py)



### 6.3 Constantes de Seguridad (app/services/auth_service.py)

| Constante | Valor | Descripción |
|-----------|-------|-------------|
| SECRET_KEY | metafoliador_v2_jwt_secret_change_in_production | Clave de firma JWT (⚠ hardcodeada) |
| ALGORITHM | HS256 | Algoritmo de firma JWT |
| ACCESS_TOKEN_EXPIRE_MINUTES | 480 (8 horas) | Tiempo de expiración del token |
| _fernet_key | Derivada de SHA-256 de SECRET_KEY | Clave de cifrado simétrico Fernet |

⚠ ADVERTENCIA: La SECRET_KEY y otras credenciales se encuentran hardcodeadas en el código fuente. Se recomienda migrarlas a variables de entorno y regenerarlas con valores seguros en producción.

### 6.4 Configuración de Celery (app/worker.py)

Configuración del Celery app:

| Opción | Valor | Descripción |
|--------|-------|-------------|
| broker | settings.rabbitmq_url | Broker RabbitMQ |
| backend | settings.redis_url | Result backend Redis |
| task_serializer | json | Serialización |
| timezone | UTC | Zona horaria interna de Celery (la presentación usa TIMEZONE) |
| task_acks_late | True | El ACK se envía al finalizar la tarea |
| worker_prefetch_multiplier | 1 | Un mensaje a la vez por worker |
| task_time_limit / task_soft_time_limit | None | Sin límites de tiempo |
| task_routes | sync_site_folders, sync_folder_children | Cola `celery` |

> ⚠ **consumer_timeout de RabbitMQ**: con `task_acks_late=True`, RabbitMQ cierra el canal si el worker no reconoce el mensaje antes del `consumer_timeout` (30 minutos por defecto), lo que re-entrega la tarea y la re-ejecuta desde 0. Para jobs de larga duración se debe configurar en `/etc/rabbitmq/rabbitmq.conf`:

```
consumer_timeout = 31536000000
```

(equivalente a 1 año; reiniciar RabbitMQ tras el cambio: `systemctl restart rabbitmq-server`).

### 6.5 Documentación de la API (Swagger UI)

La documentación interactiva está disponible en:

| Ruta | Descripción | Autenticación |
|------|-------------|---------------|
| /docs | Swagger UI interactivo | HTTP Basic Auth |
| /redoc | Documentación ReDoc | HTTP Basic Auth |
| /openapi.json | Esquema OpenAPI JSON | HTTP Basic Auth |

Credenciales por defecto: admin / tykUGJ#Nr3u9%DZ!HndX

La aplicación deshabilita la documentación por defecto de FastAPI (docs_url=None) y sirve endpoints personalizados con Depends(verify_swagger_credentials). El manejador de excepciones 401 tiene un bypass para las rutas de documentación.

---

## 7. BASE DE DATOS

### 7.1 Motor de Base de Datos

- Sistema: PostgreSQL 16
- Puerto: 5433 (configurable vía DATABASE_URL)
- Nombre de BD: metafoliadorv2
- Usuario: metafoliador
- Esquema: public

### 7.2 Conexión desde la Aplicación (app/models/database.py)



### 7.3 Migraciones (Alembic)

El proyecto utiliza Alembic con 19 versiones de migración en alembic/versions/.

Comandos útiles:

| Comando | Descripción |
|---------|-------------|
| alembic upgrade head | Aplicar todas las migraciones pendientes |
| alembic revision --autogenerate -m desc | Crear nueva migración automática |
| alembic downgrade -1 | Revertir última migración |
| alembic history | Ver historial de migraciones |
| alembic current | Ver estado actual |

### 7.4 Script de Creación (scripts/setup_database.sql)



---


## 8. MODELOS DE DATOS

El sistema cuenta con **9 modelos SQLAlchemy** que mapean a **10 tablas** en la base de datos.

### 8.1 Esquema Entidad-Relación

```
alfresco_connections (1) ---- (N) alfresco_sites
alfresco_sites (1) ---- (N) alfresco_folders
alfresco_folders (1) ---- (N) alfresco_folders [self-ref, parent_id]
alfresco_folders (1) ---- (N) alfresco_files
alfresco_files (1) ---- (N) alfresco_file_history

roles (M) ---- role_permissions ---- (N) permissions

Tablas independientes: users, alfresco_listener_logs, sync_progress, reportes
```

### 8.2 Detalle de Cada Modelo

#### 8.2.1 User — Tabla `users`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| email | str(255) | UNIQUE, NOT NULL | Correo electrónico para login |
| password_hash | str(255) | NOT NULL | Hash formato {salt}:{sha256} |
| name | str(255) | default "" | Nombre completo |
| role | str(50) | default "user" | Rol (legacy; RBAC real usa tabla roles) |
| active | bool | default True | Usuario activo/inactivo |
| created_at | datetime | server_default=func.now() | Fecha de creación |

#### 8.2.2 AlfrescoConnection — Tabla `alfresco_connections`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| name | str(255) | NOT NULL | Nombre descriptivo |
| base_url | str(1024) | NOT NULL | URL base de la API Alfresco |
| ip_server | str(45) | nullable | IP del servidor Alfresco (autorización webhooks) |
| username | str(255) | nullable | Usuario de Alfresco |
| password_hash | str(512) | nullable | Contraseña cifrada con Fernet |
| client_id | str(255) | nullable | Client ID para OAuth2 |
| client_secret_hash | str(512) | nullable | Client Secret cifrado con Fernet |
| auth_type | str(50) | default "basic" | Tipo de autenticación (basic/oauth2) |
| active | bool | default True | Conexión activa |
| last_check_at | datetime | nullable | Última verificación |
| last_check_status | str(50) | nullable | Resultado de última verificación |
| notes | Text | nullable | Notas adicionales |
| created_at | datetime | auto | Fecha de creación |
| updated_at | datetime | auto | Fecha de actualización |

Relación: sites → One-to-many hacia AlfrescoSite (cascade all, delete-orphan)

#### 8.2.3 AlfrescoSite — Tabla `alfresco_sites`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador local |
| connection_id | int | FK → alfresco_connections.id, NOT NULL | Conexión asociada |
| name | str(255) | NOT NULL | Nombre del sitio |
| site_id | str(255) | NOT NULL | ID del sitio en Alfresco |
| title | str(512) | nullable | Título descriptivo |
| description | Text | nullable | Descripción |
| visibility | str(50) | default "PUBLIC" | Visibilidad |
| root_folder_id | str(255) | nullable | ID del nodo raíz |
| root_folder_path | str(1024) | nullable | Ruta del nodo raíz |
| document_library_id | str(255) | nullable | ID de biblioteca de documentos |
| site_preset | str(100) | nullable | Plantilla del sitio |
| site_role | str(50) | nullable | Rol del usuario en el sitio |
| metadata_json | JSONB | nullable | Metadatos adicionales |
| active | bool | default True | Sitio activo |
| last_sync_at | datetime | nullable | Última sincronización |
| created_at | datetime | auto | Fecha de creación |
| updated_at | datetime | auto | Fecha de actualización |

Relaciones: connection → Many-to-one a AlfrescoConnection; folders → One-to-many a AlfrescoFolder

#### 8.2.4 AlfrescoFolder — Tabla `alfresco_folders`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | str(255) | PK (UUID Alfresco) | Identificador del nodo en Alfresco |
| site_id | int | FK → alfresco_sites.id, ON DELETE CASCADE, NOT NULL | Sitio propietario |
| parent_id | str(255) | FK → alfresco_folders.id, ON DELETE CASCADE, nullable | Serie, Subserie o Expediente padre (self-referential) |
| name | str(1024) | NOT NULL | Nombre de la Serie, Subserie o Expediente |
| node_path | Text | nullable | Ruta completa del nodo |
| folder_type | str(100) | nullable | Tipo de Serie, Subserie o Expediente |
| child_count | int | default 0 | Cantidad de elementos hijos |
| has_children | bool | default False | Indica si tiene subseries y expedientes |
| has_index_xml | bool | default False | Tiene índice electrónico generado |
| index_xml_node_id | str(255) | nullable | ID del nodo index.xml en Alfresco |
| deleted | bool | default False | Soft delete |
| level | int | default 0 | Nivel de profundidad |
| synced_at | datetime | nullable | Última sincronización |
| created_at | datetime | auto | Fecha de creación |
| updated_at | datetime | auto | Fecha de actualización |

Relaciones: parent → Self-referential many-to-one; children → Self-referential one-to-many (cascade); site → Many-to-one a AlfrescoSite

#### 8.2.5 AlfrescoFile — Tabla `alfresco_files`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | str(255) | PK (UUID Alfresco) | Identificador del nodo en Alfresco |
| folder_id | str(255) | FK → alfresco_folders.id, ON DELETE CASCADE, NOT NULL | Serie, Subserie o Expediente contenedora |
| name | str(1024) | NOT NULL | Nombre del archivo |
| mimetype | str(255) | nullable | Tipo MIME |
| size_bytes | BigInteger | nullable | Tamaño en bytes |
| alfresco_created_at | datetime | nullable | Fecha de creación en Alfresco |
| alfresco_modified_at | datetime | nullable | Fecha de modificación en Alfresco |
| version | str(50) | nullable | Versión del documento |
| properties_json | JSONB | nullable | Propiedades adicionales |
| sort_order | int | default 0 | Orden en el índice (por fecha creación ASC) |
| folios | int | default 1 | Cantidad de páginas/folios |
| deleted | bool | default False | Soft delete |
| hash_sha256 | str(64) | nullable | Hash SHA-256 del contenido |
| synced_at | datetime | nullable | Última sincronización |
| created_at | datetime | auto | Fecha de creación del registro |
| updated_at | datetime | auto | Fecha de actualización del registro |

#### 8.2.6 AlfrescoFileHistory — Tabla `alfresco_file_history`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| file_id | str(255) | FK → alfresco_files.id, ON DELETE CASCADE, NOT NULL | Archivo asociado |
| folder_id | str(255) | FK → alfresco_folders.id, ON DELETE CASCADE, NOT NULL | Serie, Subserie o Expediente de destino |
| status | str(20) | default "Activo" | Activo, Movido, Eliminado |
| created_at | datetime | auto | Fecha del registro |

#### 8.2.7 AlfrescoListenerLog — Tabla `alfresco_listener_logs`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| remote_ip | str(45) | nullable | Dirección IP del remitente |
| origin | str(1024) | nullable | Origen de la solicitud |
| method | str(10) | default "POST" | Método HTTP |
| url | str(1024) | nullable | URL solicitada |
| request_body | JSONB | nullable | Cuerpo de la solicitud |
| response_status | int | nullable | Código HTTP de respuesta |
| response_body | Text | nullable | Cuerpo de la respuesta |
| authorized | bool | default True | Solicitud autorizada |
| error_message | Text | nullable | Mensaje de error |
| duration_ms | int | nullable | Duración en milisegundos |
| created_at | datetime | auto | Fecha de registro |

#### 8.2.8 Role / Permission — Tablas `roles`, `permissions`, `role_permissions`

**Tabla roles:**

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| name | str(100) | UNIQUE, NOT NULL | Nombre del rol |
| all_permissions | bool | default False | Superusuario (acceso total) |
| description | Text | nullable | Descripción del rol |
| created_at | datetime | auto | Fecha de creación |

**Tabla permissions:**

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| code | str(100) | UNIQUE, NOT NULL | Código único (ej: conexiones.create) |
| name | str(255) | NOT NULL | Nombre descriptivo (ej: Crear Conexión) |
| module | str(100) | NOT NULL | Módulo funcional |
| created_at | datetime | auto | Fecha de creación |

**Tabla role_permissions (asociación M:N):** role_id (FK → roles.id) + permission_id (FK → permissions.id), PK compuesta.

#### 8.2.9 SyncProgress — Tabla `sync_progress`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| site_id | int | NOT NULL | Sitio asociado |
| folder_id | str(255) | nullable | Serie, Subserie o Expediente asociada |
| task_id | str(255) | nullable | UUID de tarea Celery |
| status | str(50) | default "pending" | pending, running, completed, failed |
| progress | float | default 0.0 | Porcentaje (0-100) |
| message | str(512) | nullable | Mensaje descriptivo |
| total_folders | int | default 0 | Total de Series, Subseries y Expedientes |
| synced_folders | int | default 0 | Series, Subseries y Expedientes procesadas |
| error_message | Text | nullable | Mensaje de error |
| created_at | datetime | auto | Fecha de creación |
| updated_at | datetime | auto | Fecha de actualización |

#### 8.2.10 Reporte — Tabla `reportes`

| Campo | Tipo | Restricciones | Descripción |
|-------|------|---------------|-------------|
| id | int | PK, autoincrement | Identificador único |
| name | str(255) | NOT NULL | Nombre del reporte |
| code | str(100) | UNIQUE, NOT NULL | Código único |
| description | Text | nullable | Descripción |
| active | bool | default True | Activo/inactivo |
| created_at | datetime | auto | Fecha de creación |
| type_report | int | default 1 | Tipo: 1=interno, 2=externo |
| url_report | str(500) | nullable | URL del reporte externo |

---

## 9. AUTENTICACIÓN Y AUTORIZACIÓN

### 9.1 Sistema de Autenticación

#### 9.1.1 Hashing de Contraseñas

- Algoritmo: SHA-256 con sal aleatoria de 32 caracteres hexadecimales
- Formato de almacenamiento: {salt}:{sha256_hash}
- Funciones: hash_password(password) -> str, verify_password(password, password_hash) -> bool

#### 9.1.2 Tokens JWT

- Algoritmo de firma: HMAC-SHA256 (HS256)
- Clave secreta: hardcodeada en auth_service.py:9
- Payload: {user_id, email, role, exp}
- Duración: 480 minutos (8 horas)
- Transmisión: Cookie HTTP-only access_token (Secure=True, SameSite=Lax, Max-Age=28800)

#### 9.1.3 Cifrado de Credenciales (Fernet)

- Algoritmo: Fernet (cifrado simétrico, cryptography)
- Derivación de clave: SHA-256 de SECRET_KEY, codificado en Base64
- Uso: Cifrar contraseñas de Alfresco y client secrets en la BD

#### 9.1.4 Dependencia get_current_user

Extrae el JWT de la cookie, verifica firma y expiración, retorna el objeto User. Se inyecta como dependencia de FastAPI en los endpoints protegidos.

### 9.2 Sistema de Permisos (RBAC)

#### 9.2.1 Estructura

- 32 permisos predefinidos, sembrados vía POST /api/v1/admin/permissions/seed (idempotente)
- Roles con flag all_permissions=True = superusuario
- Relación M:N roles ↔ permisos vía tabla role_permissions
- Permisos agrupados por módulos funcionales

#### 9.2.2 Lista Completa de Permisos (32)

**Módulo Dashboard:** dashboard.view — Ver Dashboard

**Módulo Conexiones API (5):** conexiones.view, conexiones.create, conexiones.edit, conexiones.delete, conexiones.sync

**Módulo Sitios (5):** sitios.view, sitios.create, sitios.edit, sitios.delete, sitios.sync

**Módulo Series, Subseries y Expedientes (2):** carpetas.view, carpetas.sync

**Módulo Índice Electrónico (2):** index.view, index.generate

**Módulo Usuarios (4):** usuarios.view, usuarios.create, usuarios.edit, usuarios.delete

**Módulo Roles (4):** roles.view, roles.create, roles.edit, roles.delete

**Módulo Trabajos en Cola (3):** jobs.view, jobs.cancel, jobs.purge

**Módulo Logs API (1):** listener-logs.view

**Módulo Reportes (5):** reportes.generate, reportes.toggle, reportes.create, reportes.edit, reportes.delete

#### 9.2.3 Verificación de Permisos

La función require_permission(perm_code) retorna una dependencia FastAPI que verifica el permiso. La función get_user_permissions(user, db) retorna un set de códigos de permisos del usuario. Si el rol tiene all_permissions=True, retorna todos los permisos.

---


## 10. API REST — REFERENCIA COMPLETA

### 10.1 Generalidades

- Prefijo de rutas API: /api/v1/
- Prefijo de rutas web: /app/
- Formato de datos: JSON
- Autenticación: Cookie JWT access_token (para vistas HTML; los endpoints /api/v1/ no tienen Depends(get_current_user) actualmente)
- 13 routers FastAPI, 56 endpoints documentados

### 10.2 Documentación Interactiva

| Ruta | Descripción | Autenticación |
|------|-------------|---------------|
| /docs | Swagger UI | HTTP Basic Auth |
| /redoc | Documentación ReDoc | HTTP Basic Auth |
| /openapi.json | Esquema OpenAPI JSON | HTTP Basic Auth |

Credenciales por defecto: admin / tykUGJ#Nr3u9%DZ!HndX

### 10.3 Autenticación (/auth) — Tag: auth

| Método | Ruta | Descripción |
|--------|------|-------------|
| POST | /auth/login | Login por email+password. JWT en cookie HTTP-only 8h. Body: LoginRequest |
| POST | /auth/logout | Cierra sesión, elimina cookie access_token |
| GET | /auth/me | Perfil del usuario autenticado + permisos |

### 10.4 Vistas HTML (/app) — Tag: views

| Método | Ruta | Descripción | Permiso Requerido |
|--------|------|-------------|-------------------|
| GET | /login | Página de inicio de sesión | — |
| GET | /app | Dashboard con estadísticas y gráficos | — |
| GET | /app/sitios/arbol | Navegador de cuadro de clasificación | carpetas.view |
| GET | /app/conexiones | Gestión de conexiones Alfresco | conexiones.view |
| GET | /app/sitios | Gestión de sitios | sitios.view |
| GET | /app/jobs | Monitor de trabajos en cola | jobs.view |
| GET | /app/usuarios | Gestión de usuarios | usuarios.view |
| GET | /app/roles | Administración de roles y permisos | roles.view |
| GET | /app/listener-logs | Visor de logs del webhook listener | listener-logs.view |
| GET | /app/reportes | Gestión de reportes | — |
| GET | /app/{path} | Fallback SPA: redirige al dashboard | — |

### 10.5 Conexiones (/api/v1/connections) — Tag: connections

| Método | Ruta | Descripción | Parámetros |
|--------|------|-------------|------------|
| GET | /api/v1/connections | Listar conexiones | Query: active_only, search |
| GET | /api/v1/connections/{id} | Detalle de conexión | Path: id |
| POST | /api/v1/connections | Crear conexión | Body: CreateConnectionRequest |
| PUT | /api/v1/connections/{id} | Actualizar conexión | Body: UpdateConnectionRequest |
| DELETE | /api/v1/connections/{id} | Eliminar conexión y sus sitios | Path: id |
| POST | /api/v1/connections/{id}/test | Probar conexión Alfresco | Path: id |
| POST | /api/v1/connections/{id}/fetch-sites | Descubrir y registrar sitios | Path: id |

CreateConnectionRequest: name, base_url, ip_server (requeridos); username, password, client_id, client_secret, auth_type, active, notes (opcionales)

### 10.6 Sitios (/api/v1/sites) — Tag: sites

| Método | Ruta | Descripción | Parámetros |
|--------|------|-------------|------------|
| GET | /api/v1/sites | Listar sitios | Query: connection_id, active_only, search |
| GET | /api/v1/sites/{id} | Detalle de sitio | Path: id |
| POST | /api/v1/sites | Crear sitio | Body: CreateSiteRequest |
| PUT | /api/v1/sites/{id} | Actualizar sitio | Body: UpdateSiteRequest |
| DELETE | /api/v1/sites/{id} | Eliminar sitio y datos | Path: id |
| POST | /api/v1/sites/{id}/sync | Marcar sitio como sincronizado | Path: id |

CreateSiteRequest: name, site_id, connection_id (requeridos); title, description, visibility, root_folder_id, root_folder_path, document_library_id, site_preset, site_role, metadata_json, active (opcionales)

### 10.7 Series, Subseries y Expedientes (/api/v1/folders) — Tag: folders

| Método | Ruta | Descripción | Parámetros |
|--------|------|-------------|------------|
| GET | /api/v1/folders/site/{site_id} | Todas las Series, Subseries y Expedientes de un sitio (lista plana) | Path: site_id |
| GET | /api/v1/folders/{folder_id}/children | Hijos directos de una Serie, Subserie o Expediente | Path: folder_id |
| POST | /api/v1/folders/site/{site_id}/sync | Iniciar sync Celery de todo el sitio | Body: SyncSiteRequest {deep, generate_index} |
| POST | /api/v1/folders/{folder_id}/sync | Iniciar sync Celery de una Serie, Subserie o Expediente | Body: SyncSiteRequest |
| GET | /api/v1/folders/site/{site_id}/status | Estado de sync del sitio | Path: site_id |
| GET | /api/v1/folders/folder/{folder_id}/status | Estado de sync de Serie, Subserie o Expediente | Path: folder_id |
| GET | /api/v1/folders/{node_id}/index-xml | Descargar index.xml desde Alfresco | Path: node_id |
| DELETE | /api/v1/folders/site/{site_id} | Limpiar datos de Series, Subseries y Expedientes del sitio | Path: site_id |

### 10.8 Estadísticas (/api/v1/stats) — Tag: stats

| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | /api/v1/stats | Conteos agregados: sitios, Series, Subseries y Expedientes, archivos, índices, jobs activos |
| GET | /api/v1/stats/charts | Datos para gráficos del dashboard (top 20 sitios). Retorna {folders, files, indices} cada uno con {labels, values} |
| GET | /api/v1/logs/recent | Registros recientes de sync. Query: limit (default 10, max 100) |

### 10.9 Trabajos (/api/v1/jobs) — Tag: jobs

| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | /api/v1/jobs | Listar trabajos. Query: status, limit (default 50, max 200) |
| POST | /api/v1/jobs/{job_id}/cancel | Cancelar trabajo (revoke Celery con terminate) |
| POST | /api/v1/jobs/purge | Purgar cola Celery + eliminar SyncProgress pendientes/running |

### 10.10 Usuarios (/api/v1/users) — Tag: users

| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | /api/v1/users | Listar usuarios. Query: search |
| GET | /api/v1/users/{id} | Detalle de usuario |
| POST | /api/v1/users | Crear usuario. Body: CreateUserRequest |
| PUT | /api/v1/users/{id} | Actualizar usuario. Body: UpdateUserRequest |
| DELETE | /api/v1/users/{id} | Eliminar usuario |

CreateUserRequest: email, password (requeridos); name, role, active (opcionales)

### 10.11 RBAC (/api/v1/admin) — Tag: admin

| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | /api/v1/admin/roles | Listar roles con permisos y conteo de usuarios |
| POST | /api/v1/admin/roles | Crear rol. Body: CreateRoleRequest |
| PUT | /api/v1/admin/roles/{id} | Actualizar rol. Body: UpdateRoleRequest |
| DELETE | /api/v1/admin/roles/{id} | Eliminar rol |
| GET | /api/v1/admin/permissions | Listar 32 permisos agrupados por módulo |
| POST | /api/v1/admin/permissions/seed | Sembrar 32 permisos (idempotente) |

CreateRoleRequest: name (requerido); all_permissions, description, permission_ids (opcionales)

### 10.12 Alfresco Listener (/api/v1/alfresco) — Tag: alfresco-listener

| Método | Ruta | Descripción |
|--------|------|-------------|
| POST | /api/v1/alfresco/listener | Webhook para eventos Alfresco. Body: WebhookListenerRequest |

WebhookListenerRequest: node_id (requerido); project_alfresco_name, is_folder, action (1=create, 2=update, 3=delete, 4=move), folder_parent_id, name (opcionales)

Autorización: Verifica IP del cliente contra alfresco_connections.ip_server

### 10.13 Logs de Listener (/api/v1/listener-logs) — Tag: listener-logs

| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | /api/v1/listener-logs | Listar logs paginados. Query: page (default 1), limit (default 50, max 200) |

### 10.14 Reportes (/api/v1/reportes) — Tags: reportes, reportes-gen

| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | /api/v1/reportes | Listar definiciones de reportes |
| PUT | /api/v1/reportes/{id}/toggle | Activar/inactivar reporte |
| POST | /api/v1/reportes | Crear reporte externo. Body: CreateReporteRequest |
| PUT | /api/v1/reportes/{id} | Actualizar reporte externo. Body: UpdateReporteRequest |
| DELETE | /api/v1/reportes/{id} | Eliminar reporte externo |
| GET | /api/v1/reportes/sites-with-folders | Sitios activos con Series, Subseries y Expedientes (para selectores) |
| GET | /api/v1/reportes/trd-tipologias | Generar Excel TRD (asíncrono). Query: site_id, max_levels (default 3) |
| GET | /api/v1/reportes/trd-tipologias/status | Estado de generación TRD. Query: job_id |
| GET | /api/v1/reportes/trd-tipologias/download | Descargar Excel TRD. Query: job_id |
| GET | /api/v1/reportes/graficos-dependencia | Generar PDF gráficos (asíncrono). Query: site_id, chart_types (default "barras") |
| GET | /api/v1/reportes/graficos-dependencia/status | Estado de gráficos. Query: job_id |
| GET | /api/v1/reportes/graficos-dependencia/download | Descargar PDF. Query: job_id |
| GET | /api/v1/reportes/fuid/dependencias | Listar dependencias de un sitio. Query: site_id |
| GET | /api/v1/reportes/fuid | Generar FUID (asíncrono). Query: site_id, dependency_id (opcional), format |
| GET | /api/v1/reportes/fuid/status | Estado de FUID. Query: job_id |
| GET | /api/v1/reportes/fuid/download | Descargar FUID. Query: job_id, format |

Tipos de gráfico (chart_types, separados por coma): barras, circular, lineal, barras_horizontal, area, dispersion, histograma

### 10.15 Modelos Pydantic (app/schemas/__init__.py)

| Modelo | Uso |
|--------|-----|
| LoginRequest | POST /auth/login |
| CreateConnectionRequest | POST /api/v1/connections |
| UpdateConnectionRequest | PUT /api/v1/connections/{id} |
| CreateSiteRequest | POST /api/v1/sites |
| UpdateSiteRequest | PUT /api/v1/sites/{id} |
| SyncSiteRequest | POST /api/v1/folders/site/{id}/sync y /folder/{id}/sync |
| CreateUserRequest | POST /api/v1/users |
| UpdateUserRequest | PUT /api/v1/users/{id} |
| CreateRoleRequest | POST /api/v1/admin/roles |
| UpdateRoleRequest | PUT /api/v1/admin/roles/{id} |
| CreateReporteRequest | POST /api/v1/reportes |
| UpdateReporteRequest | PUT /api/v1/reportes/{id} |
| WebhookListenerRequest | POST /api/v1/alfresco/listener |

---

## 11. SISTEMA DE COLAS Y TAREAS ASÍNCRONAS

### 11.1 Arquitectura Celery

Componentes: FastAPI (productor) -> RabbitMQ (broker, amqp://guest:guest@localhost:5672/) -> Celery Worker -> Redis (result backend, redis://localhost:6379/0)

Colas definidas: celery (principal), webhooks (listener)
Serialización: JSON
Zona horaria interna: UTC (Celery). La zona horaria de presentación es configurable vía TIMEZONE (default America/Bogota, ver app/services/timezone.py)

Todos los listados de hijos Alfresco usan paginación (maxItems + skipCount + hasMoreItems) para soportar carpetas con más de 500 elementos.

### 11.2 Tareas Definidas

#### 11.2.1 sync_site_folders

Firma: sync_site_folders(self, site_id: int, deep: bool = False, generate_index: bool = True)
Cola: celery

Flujo de ejecución:
1. Validar que el sitio existe y tiene root_folder_id
2. Validar que la conexión asociada está activa y tiene credenciales
3. Obtener los hijos del nivel raíz desde Alfresco con `fetch_children_sync` (paginado: maxItems=500 + skipCount + hasMoreItems)
4. Descubrir documentLibrary y obtener sus hijos
5. Para cada Serie, Subserie o Expediente hija, verificar existencia de index.xml (paginado)
6. Si deep=True: recorrer recursivamente todas las subseries y expedientes usando BFS (cola)
7. Si deep=False: procesar solo Series, Subseries y Expedientes de primer nivel
8. Para cada Serie, Subserie o Expediente, ejecutar _mark_deleted_files para detectar archivos eliminados
9. Si generate_index=True: iterar todas las Series, Subseries y Expedientes y generar index.xml
10. Reportar progreso en SyncProgress en cada etapa

#### 11.2.2 sync_folder_children

Firma: sync_folder_children(self, folder_id: str, deep: bool = False, generate_index: bool = True)
Cola: celery
Flujo: Igual que sync_site_folders pero limitado al alcance de una sola Serie, Subserie o Expediente.

### 11.3 Funciones Auxiliares del Worker

**_upsert_file(db, entry, folder_id, sort_order, site_id_val):** Crea o actualiza un registro AlfrescoFile desde la API Alfresco. Extrae: id, name, mimeType, sizeInBytes, createdAt, modifiedAt, versionLabel, properties, aspects.

**_mark_deleted_files(db, folder_id, alfresco_file_ids, conn_base_url, auth, generate_index=True):** Compara archivos en BD vs conjunto de IDs de Alfresco. Marca deleted=True los que ya no existen. Si se detectan eliminaciones, dispara regeneración del índice.

**_generate_index_for_folder(db, folder_id, conn_base_url, auth):**
1. Verifica que la Serie, Subserie o Expediente tenga archivos activos
2. Calcula SHA-256 faltantes descargando archivos de Alfresco
3. Reordena archivos por alfresco_created_at ASC, actualiza sort_order
4. Construye XML con build_index_xml_content()
5. Sube a Alfresco con upload_index_xml_to_alfresco()
6. Actualiza has_index_xml=True e index_xml_node_id

**_check_index_xml(db, client, auth, base_url, folder_id, site_id):** Verifica si index.xml existe en Alfresco (listado paginado maxItems=500 + skipCount). Actualiza flags.

**fetch_children_sync(client, parent_id, parent_path, level, root_parent=False) / fetch_save(client, parent_id, parent_path, level):** Listan los hijos de un nodo Alfresco con paginación (maxItems=500, skipCount iterativo hasta hasMoreItems=false o página incompleta). Si una página falla, se omite la detección de archivos eliminados para no marcarlos por error.

**_update_progress(db, task_id, status, progress, message, site_id_val=0):** Actualiza registro SyncProgress. No crea nuevos registros.

---

## 12. GENERACIÓN DE ÍNDICES ELECTRÓNICOS

### 12.1 Servicio Principal (app/services/index_generator.py)

Componente central que produce index.xml en formato TipoDocumentoFoliado colombiano.

### 12.2 Funciones

#### extract_tipologia(folder_name, folder_path) -> str

Extrae la tipología documental buscando en la ruta en orden inverso:
1. Formato con punto decimal (ej: "1.5 Contratos")
2. Formato con guiones sin punto (ej: "1-2024 Nombre")
3. Fallback: nombre de la Serie, Subserie o Expediente padre
4. Fallback final: folder_name

#### build_index_xml_content(files, folder_name, folder_path) -> str

Genera XML completo. Para cada archivo genera DocumentoIndizado con:

| Campo XML | Origen |
|-----------|--------|
| Id | file.id (UUID Alfresco) |
| Nombre_Documento | file.name |
| Tipologia_Documental | extract_tipologia() |
| Fecha_Creacion_Documento | file.alfresco_created_at (YYYY-MM-DD) |
| Fecha_Incorporacion_Expediente | datetime.utcnow() |
| Valor_Huella | file.hash_sha256 |
| Funcion_Resumen | Fijo "SHA256" |
| Orden_Documento_Expediente | Secuencial (1-based) |
| Pagina_Inicio / Pagina_Fin | Acumulativo por folios |
| Formato | file.mimetype |
| Estado | "Activo" o "Eliminado" |
| Tamano | Tamaño legible (B/KB/MB) |

Salida: XML UTF-8 con declaración.

#### compute_sha256_from_content(content: bytes) -> str

Retorna hash SHA-256 hexadecimal del contenido binario.

#### count_pages(content: bytes, mimetype: str) -> int

| Tipo MIME | Método |
|-----------|--------|
| application/pdf | PyPDF2.PdfReader (páginas reales) |
| application/msword, vnd.openxmlformats-* | python-docx (párrafos+tablas / 30) |
| image/* (png, jpeg, gif) | Fijo 1 |
| text/* | Líneas / 60 |
| Excel / Otros / Error | Fijo 1 |

#### compute_and_update_hash(db, file_record, conn_base_url, auth, file_node_id) -> str

Descarga contenido desde Alfresco (GET /nodes/{id}/content), calcula SHA-256 y páginas, actualiza BD. Errores manejados silenciosamente.

#### _ensure_versionable(conn_base_url, auth, node_id)

Asegura el aspect cm:versionable en el nodo Alfresco (GET para verificar, PUT para añadir si falta).

#### upload_index_xml_to_alfresco(conn_base_url, auth, folder_id, xml_content) -> tuple[bool, str|None]

1. Lista hijos de la Serie, Subserie o Expediente buscando index.xml existente mediante `_find_index_xml_node` (paginado: maxItems=500 + skipCount + hasMoreItems; soporta carpetas con >500 archivos)
2. Si existe: asegura cm:versionable, actualiza con majorVersion=false (1.0 -> 1.1 -> 1.2)
3. Si no existe: crea nodo con cm:versionable, sube contenido
4. Si el POST falla con 409 Conflict ("index.xml already exists") porque el nodo quedó fuera de la primera página: re-busca con `_find_index_xml_node` y actualiza vía PUT (nunca crea duplicados index-N.xml)

#### _find_index_xml_node(client, conn_base_url, auth, folder_id) -> tuple[bool, str|None]

Busca el nodo index.xml dentro de una Serie, Subserie o Expediente con paginación completa (maxItems=500, skipCount iterativo, corte por hasMoreItems=false o página incompleta). Retorna (existe, node_id).

### 12.3 Versionado de Índices

- Creación inicial: versión 1.0
- Actualizaciones: incremento de versión menor (1.1, 1.2, 1.3...)
- Historial completo de cambios en Alfresco

### 12.4 Ordenamiento de Documentos

Archivos ordenados por alfresco_created_at ASC (más antiguo primero). sort_order se recalcula en cada regeneración.

---

## 13. WEBHOOK LISTENER

### 13.1 Endpoint

POST /api/v1/alfresco/listener — Recibe eventos en tiempo real desde Alfresco.

### 13.2 Autorización

Verifica request.client.host contra alfresco_connections.ip_server (solo conexiones active=True). Si no autorizada: registra en AlfrescoListenerLog con authorized=False, retorna 403.

### 13.3 Registro de Solicitudes

Cada solicitud se registra en alfresco_listener_logs con: remote_ip, origin, method, url, request_body (JSONB), response_status, response_body, authorized, error_message, duration_ms.

### 13.4 Procesamiento de Acciones

#### Acciones sobre Archivos

| Acción | Código | Comportamiento |
|--------|--------|----------------|
| Crear | 1 | Obtiene info de Alfresco, upsert en BD, regenera índice de Serie, Subserie o Expediente padre |
| Actualizar | 2 | Obtiene info actualizada, upsert en BD, regenera índice |
| Eliminar | 3 | Verifica en Alfresco. Si aún existe: actualiza (falso positivo). Si no: marca deleted=True |
| Mover | 4 | Registra historial ("Movido"), actualiza folder_id, regenera índices de origen y destino |

#### Acciones sobre Series, Subseries y Expedientes

| Acción | Código | Comportamiento |
|--------|--------|----------------|
| Crear | 1 | Obtiene info de Alfresco, crea en BD |
| Actualizar | 2 | Actualiza nombre y node_path si cambió; crea si no existe |
| Eliminar | 3 | Marca deleted=True |
| Mover | 4 | Actualiza parent_id, level, node_path. Propaga recursivamente a descendientes y regenera índices |

### 13.5 Flujo del Listener

Alfresco --POST--> /api/v1/alfresco/listener --> Verificar IP --> Registrar log --> Procesar acción (archivo/Serie, Subserie o Expediente) --> Retornar respuesta HTTP

---

## 14. SISTEMA DE REPORTES

### 14.1 Reporte TRD de Tipologías (Excel)

Flujo: Solicitar (GET + site_id) -> SyncProgress (task_id) -> Hilo threading ejecuta _generate_trd() -> Polling de estado -> Descarga Excel (.xlsx)

Contenido:
- Recorre cuadro de clasificación desde documentLibrary
- Identifica Series, Subseries y Expedientes con patrón de tipología (N.N Nombre)
- Columnas: Nivel 1, Nivel 2, ..., Archivos
- Series, Subseries y Expedientes sin tipología: "SIN TIPOLOGIA - N archivos" si profundidad máxima o sin hijos

### 14.2 Reporte de Gráficos por Dependencia (PDF)

Flujo: Igual al TRD pero ejecuta _generate_graficos()

Para cada dependencia (Serie, Subserie o Expediente nivel 1 bajo documentLibrary) genera:
1. Tabla resumen: nombre + cantidad de índices
2. Gráfico por tipología documental (Series, Subseries y Expedientes con patrón N.N)
3. Gráfico por extensión de archivo (omite index.xml; sin extensión -> "SIN EXTENSION")
4. Gráfico por serie documental (subseries y expedientes nivel 2)

Tipos de gráfico: barras, circular, lineal, barras_horizontal, area, dispersion, histograma

### 14.3 Reporte FUID — Formato Único de Inventario Documental (Excel y PDF)

Flujo: Solicitar (GET + site_id + dependency_id opcional + format) -> SyncProgress (task_id) -> Hilo threading ejecuta _generate_fuid() o _generate_fuid_multi() -> Polling de estado -> Descarga Excel (.xlsx) o PDF

**Endpoints:**

| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | /api/v1/reportes/fuid/dependencias | Lista dependencias de un sitio para el selector |
| GET | /api/v1/reportes/fuid | Genera reporte FUID (asíncrono). Parámetros: site_id, dependency_id (opcional), format (excel/pdf) |
| GET | /api/v1/reportes/fuid/status | Consulta estado de generación por job_id |
| GET | /api/v1/reportes/fuid/download | Descarga el archivo generado (job_id + format) |

**Estructura TRD de 3 niveles:**
- Nivel 1: Dependencia (seleccionada por el usuario o todas las del sitio)
- Nivel 2: Serie documental (hijo directo de dependencia)
- Nivel 3: Subserie documental (hijo de serie)
- Si solo hay 2 niveles, la subserie queda vacía
- Los códigos TRD se extraen del nombre de carpeta con formato `X-XXXX-X-XX.N`

**Agrupación y conteo:**
- Cada fila representa una combinación única serie+subserie
- Los archivos `index.xml` se excluyen del conteo
- Folios: suma de folios de archivos directos en todas las carpetas bajo la misma serie+subserie
- Fechas extremas: fecha del primer y último documento en la unidad
- Las carpetas sin archivos aparecen con 0 folios

**Formato de salida:**
- Excel: encabezado AGN, hoja horizontal, auto-filtro, una hoja por dependencia en modo multi
- PDF: orientación horizontal (landscape A4), márgenes estrechos, una página por dependencia, KeepTogether en el pie de página

**Funciones principales:**
- `_build_fuid_rows(db, dependency_id)`: extrae y agrupa datos para una dependencia
- `_extract_trd_code_and_name(name)`: extrae código y nombre de carpeta con formato TRD
- `_generate_fuid_excel(rows, ...)`: genera Excel para una dependencia
- `_generate_fuid_pdf(rows, ...)`: genera PDF para una dependencia
- `_generate_fuid_excel_multi(all_data, ...)`: genera Excel multi-hoja
- `_generate_fuid_pdf_multi(all_data, ...)`: genera PDF multi-página

### 14.4 Procesamiento Asíncrono

Los reportes usan threading.Thread(daemon=True) (no Celery). Estado rastreado vía SyncProgress con job_id como task_id.

---

## 15. FRONTEND — PANEL ADMINISTRATIVO WEB

### 15.1 Tecnologías

| Tecnología | Propósito |
|------------|-----------|
| HTML5 + CSS3 (5 archivos) | Estructura y estilos |
| JavaScript Vanilla (10 archivos) | Lógica del cliente |
| Jinja2 (11 plantillas) | Renderizado server-side |
| Chart.js v4.4.7 | Gráficos del dashboard |
| chartjs-plugin-datalabels v2.2.0 | Etiquetas en gráficos |

### 15.2 Páginas

| Ruta | Página | Descripción |
|------|--------|-------------|
| /login | Inicio de sesión | Formulario de autenticación |
| /app | Dashboard | Panel principal con estadísticas y gráficos interactivos |
| /app/conexiones | Conexiones | CRUD de conexiones Alfresco |
| /app/sitios | Sitios | Gestión de sitios sincronizados |
| /app/sitios/arbol | Cuadro de Clasificación | Navegador jerárquico |
| /app/jobs | Trabajos | Monitor de tareas Celery |
| /app/usuarios | Usuarios | Gestión de usuarios |
| /app/roles | Roles | Administración RBAC |
| /app/listener-logs | Logs | Visor de solicitudes webhook |
| /app/reportes | Reportes | Generación de reportes |

### 15.3 Dashboard Interactivo

- 3 gráficos lado a lado: Series, Subseries y Expedientes, archivos e índices por sitio
- 7 tipos seleccionables: circular, barras, lineal, barras horizontales, radar, dona, área polar
- Tooltips con valor y porcentaje al pasar el mouse
- Datalabels visibles en cada elemento
- Leyenda con formato: site_id (total, porcentaje)
- Escala automática con ~5 niveles redondeados
- Carga por defecto: gráfico circular

### 15.4 Tema Claro/Oscuro

Soporte para cambio de tema desde la barra de navegación.

---

## 16. PROCESOS Y FLUJOS DE TRABAJO

### 16.1 Flujo Completo de Sincronización

1. Usuario crea conexión Alfresco y descubre sitios
2. Selecciona sitio y dispara sync (shallow=1er nivel, deep=recursivo)
3. Se crea SyncProgress y se lanza tarea Celery sync_site_folders()
4. El worker recorre Series, Subseries y Expedientes/archivos vía API Alfresco, crea/actualiza AlfrescoFolder y AlfrescoFile
5. _mark_deleted_files compara IDs de Alfresco vs BD y marca deleted=True
6. Si se detectan eliminados, se regenera index.xml automáticamente
7. Para cada Serie, Subserie o Expediente hoja se genera index.xml con index_generator.py
8. Se sube a Alfresco con cm:versionable y majorVersion=false
9. Se actualiza has_index_xml=True en la Serie, Subserie o Expediente

### 16.2 Flujo de Detección de Eliminados

1. Durante sync, para cada Serie, Subserie o Expediente: obtener lista actual de archivos de Alfresco
2. Extraer conjunto de UUIDs actuales
3. Consultar todos los archivos de la Serie, Subserie o Expediente en BD
4. Identificar UUIDs en BD que no están en Alfresco
5. Marcar como deleted=True
6. Si hubo eliminaciones, regenerar índice (incluye eliminados con estado="Eliminado")

### 16.3 Reactivación de Archivos

Si un archivo deleted=True reaparece en Alfresco: _upsert_file encuentra el UUID, actualiza metadatos, establece deleted=False. El archivo vuelve al índice con estado="Activo".

### 16.4 Flujo de Webhook Listener

1. Alfresco detecta cambio (create/update/delete/move)
2. Envía POST a /api/v1/alfresco/listener
3. Metafoliador: verifica IP, registra log, determina tipo, procesa acción
4. Create/Update: upsert + regenerar índice
5. Delete: soft delete o actualizar si falso positivo
6. Move: historial + actualizar folder_id + regenerar índices origen/destino

---

## 17. DESPLIEGUE A PRODUCCIÓN

### 17.1 Script de Despliegue (deploy.sh)

Sincroniza cambios al servidor productivo vía tar+scp sobre SSH.

Servidor productivo: IP 54.39.196.195, SSH puerto 2828, usuario root, ruta /var/www/html/metafoliadorv2, auth sshpass.

### 17.2 Modos de Despliegue

| Comando | Descripción |
|---------|-------------|
| ./deploy.sh | Incremental: solo archivos modificados desde último deploy |
| ./deploy.sh --all | Completo: todos los archivos |
| ./deploy.sh --force | Forzar reinicio de servicios |

### 17.3 Lógica de Decisión

| Cambio Detectado | Acción Automática |
|------------------|-------------------|
| requirements.txt | pip install + reiniciar API + worker |
| alembic/ o models/ | alembic upgrade head + reiniciar API + worker |
| app/**/*.py | Reiniciar API + worker |
| config.py | Reiniciar API + worker |
| static/ o templates/ | Sin reinicio (estáticos) |

### 17.4 Pasos

1. Detectar archivos modificados (comparar con .deploy_state)
2. Crear tar.gz (excluye .git, __pycache__, .venv, logs, .env)
3. scp al servidor remoto
4. Extraer y limpiar
5. Si necesario: pip install, alembic upgrade head
6. Reiniciar servicios: systemctl restart metafoliadorv2 metafoliadorv2-worker
7. Guardar timestamp en .deploy_state

### 17.5 Servicios systemd

**API (metafoliadorv2.service):** WorkingDirectory=/var/www/html/metafoliadorv2, ExecStart=venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000, Restart=always, logs en logs/api.log

**Worker (metafoliadorv2-worker.service):** ExecStart=venv/bin/celery -A app.worker worker --loglevel=info -Q celery, logs en logs/worker.log

**Webhook Worker (metafoliadorv2-webhook-worker.service):** ExecStart=venv/bin/celery -A app.worker worker --loglevel=info -Q webhooks, logs en logs/webhook-worker.log

Todos con User=root, After=network.target postgresql redis rabbitmq-server, Restart=always, RestartSec=5.

---

## 18. ESTRUCTURA DEL PROYECTO

```
metafoliador/
├── .env.example                    # Template de variables de entorno
├── .gitignore
├── AGENTS.md                       # Documentación para agentes de IA
├── alembic.ini                     # Configuración de Alembic
├── deploy.sh                       # Script de despliegue
├── requirements.txt                # 20 dependencias Python
├── alembic/
│   ├── env.py
│   ├── script.py.mako
│   └── versions/                   # 18 archivos de migración
├── app/                            # Paquete principal
│   ├── main.py                     # Entry point FastAPI (13 routers)
│   ├── config.py                   # Settings (pydantic-settings)
│   ├── worker.py                   # Definición Celery app
│   ├── worker_tasks.py             # 2 tareas Celery + 5 helpers
│   ├── models/                     # 9 modelos SQLAlchemy
│   │   ├── database.py             # Engine, SessionLocal, Base
│   │   ├── user.py
│   │   ├── alfresco_connection.py
│   │   ├── alfresco_site.py
│   │   ├── alfresco_folder.py
│   │   ├── alfresco_file.py
│   │   ├── alfresco_file_history.py
│   │   ├── alfresco_listener_log.py
│   │   ├── role_permission.py      # Role + Permission
│   │   ├── reporte.py
│   │   └── sync_progress.py
│   ├── services/
│   │   ├── auth_service.py         # JWT, hashing, Fernet
│   │   └── index_generator.py      # XML, SHA-256, conteo páginas
│   ├── schemas/
│   │   ├── __init__.py             # 13 modelos Pydantic
│   │   └── webhook.py              # EventType, AlfrescoEvent
│   └── api/routes/                 # 13 routers FastAPI
│       ├── auth.py                 # Login, logout, JWT, permisos
│       ├── views.py                # 11 vistas HTML (Jinja2)
│       ├── stats.py                # Estadísticas dashboard
│       ├── connections.py          # CRUD conexiones Alfresco
│       ├── sites.py                # CRUD sitios
│       ├── folders.py              # Cuadro de clasificación, sync, index.xml
│       ├── jobs.py                 # Listar/cancelar/purgar
│       ├── users_admin.py          # CRUD usuarios
│       ├── admin_rbac.py           # Roles + 32 permisos
│       ├── alfresco_listener.py    # Webhook listener
│       ├── listener_logs_view.py   # Visor logs listener
│       ├── reportes.py             # CRUD reportes
│       └── reportes_gen.py         # Excel TRD + PDF Gráficos + FUID (AGN)
├── scripts/
│   ├── install.sh                  # Instalación completa
│   ├── setup_database.sql          # Creación BD PostgreSQL
│   ├── metafoliadorv2.service      # Systemd API
│   ├── metafoliadorv2-worker.service      # Systemd Worker
│   └── metafoliadorv2-webhook-worker.service  # Systemd Webhook Worker
├── static/
│   ├── css/ (5 archivos)
│   ├── js/ (10 archivos)
│   └── img/ (3 archivos)
└── templates/ (11 archivos Jinja2)
```

---

## 19. MANTENIMIENTO Y OPERACIÓN

### 19.1 Logs del Sistema

| Archivo | Contenido |
|---------|-----------|
| logs/api.log | Salida de uvicorn (API) |
| logs/worker.log | Salida del Celery worker (cola celery) |
| logs/webhook-worker.log | Salida del Celery worker (cola webhooks) |
| alfresco_listener_logs (BD) | Logs de solicitudes webhook |

### 19.2 Comandos de Operación

```
# Ver estado de servicios
systemctl status metafoliadorv2
systemctl status metafoliadorv2-worker

# Reiniciar servicios
systemctl restart metafoliadorv2
systemctl restart metafoliadorv2-worker

# Ver logs en tiempo real
tail -f logs/api.log
tail -f logs/worker.log

# Monitorear Celery
celery -A app.worker inspect active
celery -A app.worker inspect registered

# Purgar cola (desde API)
curl -X POST http://localhost:8000/api/v1/jobs/purge
```

### 19.3 Respaldo de Base de Datos

```
pg_dump -U metafoliador -p 5433 metafoliadorv2 > backup_$(date +%Y%m%d).sql
```

### 19.4 Actualización de Dependencias

```
source venv/bin/activate
pip install --upgrade -r requirements.txt
alembic upgrade head
systemctl restart metafoliadorv2 metafoliadorv2-worker
```

---

## 20. SOLUCIÓN DE PROBLEMAS

### 20.1 La API no inicia

- Verificar que PostgreSQL esté corriendo en puerto 5433
- Verificar credenciales en .env
- Revisar logs/api.log

### 20.2 Las tareas Celery no se ejecutan

- Verificar RabbitMQ: systemctl status rabbitmq-server
- Verificar Redis: systemctl status redis
- Revisar logs/worker.log
- Verificar conectividad: celery -A app.worker inspect ping

### 20.3 La sincronización falla

- Verificar conexión Alfresco con el botón "Probar conexión"
- Revisar credenciales (pueden estar vencidas)
- Revisar registros SyncProgress en BD
- Verificar que el sitio tenga document_library_id configurado

### 20.4 No se generan índices

- Verificar que generate_index=True en la solicitud de sync
- Revisar permisos del usuario Alfresco (necesita escritura para crear/actualizar index.xml)
- Verificar logs del worker

### 20.5 El Webhook Listener no recibe eventos

- Verificar que la IP del servidor Alfresco esté configurada en alfresco_connections.ip_server
- Verificar que la conexión esté activa (active=True)
- Revisar alfresco_listener_logs

### 20.6 El dashboard muestra datos incorrectos

- Ejecutar sincronización del sitio para actualizar datos
- Verificar que el sitio no tenga Series, Subseries y Expedientes/archivos con deleted=True que distorsionen conteos

### 20.7 Un job de sincronización se reinicia desde 0 y no termina

Síntoma: el progreso en SyncProgress baja de golpe (ej: de 30000 a 1000) y el job vuelve a empezar. En logs/worker.log aparecen errores `PreconditionFailed(406)` o `Couldn't ack ... ConnectionResetError`.

Causa: RabbitMQ cierra el canal cuando el worker no reconoce el mensaje antes del `consumer_timeout` (30 min por defecto). Como `task_acks_late=True`, el mensaje se re-entrega y la tarea se re-ejecuta desde 0.

Solución: aumentar `consumer_timeout` en `/etc/rabbitmq/rabbitmq.conf` (ver sección 6.4) y reiniciar RabbitMQ. Verificar con `rabbitmqctl eval 'rabbit_config:get(consumer_timeout).'` que quede aplicado.

---

## 21. PROBLEMAS CONOCIDOS Y LIMITACIONES

### 21.1 Críticos

1. **Paths hardcodeados:** sys.path.insert(0, "/var/www/html/metafoliadorv2") en casi todos los archivos Python. Impide la portabilidad del proyecto a otras ubicaciones.
2. **Secretos hardcodeados:** SECRET_KEY JWT estática en auth_service.py:9. Contraseña SSH en deploy.sh:20. Contraseñas BD en install.sh:35 y setup_database.sql:12.
3. **SSL deshabilitado:** Todos los llamados httpx usan verify=False. Las comunicaciones con Alfresco no validan certificados TLS.
4. **Rutas API sin autenticación:** Los endpoints /api/v1/ no usan Depends(get_current_user). Solo las vistas HTML están protegidas por cookie auth.

### 21.2 Arquitectura

5. **Reportes usan threading en vez de Celery:** reportes_gen.py usa threading.Thread(daemon=True), inconsistente con el resto del procesamiento asíncrono.

### 21.3 Calidad de Código

6. **Sin type checking:** No hay configuración de mypy, pyright ni type hints exhaustivos.
7. **Bare excepts:** except: pass en index_generator.py:136 y alfresco_listener.py:166.
8. **Funciones muy largas:** sync_site_folders (~200 líneas), sync_folder_children (~140 líneas).
9. **Logging con paths hardcodeados:** /var/www/html/metafoliadorv2/logs/api.log en main.py:30.
10. **Sin tests:** No existe ningún archivo de test en el proyecto.

---

## 22. GLOSARIO DE TÉRMINOS

| Término | Definición |
|---------|------------|
| **Alfresco** | Plataforma Enterprise Content Management (ECM) de código abierto para gestión documental |
| **Alembic** | Herramienta de migración de base de datos para SQLAlchemy |
| **ASGI** | Asynchronous Server Gateway Interface — estándar para servidores web asíncronos Python |
| **Celery** | Sistema de colas de tareas distribuidas para procesamiento asíncrono en Python |
| **cm:versionable** | Aspect de Alfresco que habilita el versionado de un nodo |
| **Cookie HTTP-only** | Cookie no accesible desde JavaScript, solo enviada en peticiones HTTP |
| **Deep Sync** | Sincronización recursiva que recorre todo el cuadro de clasificación |
| **ECM** | Enterprise Content Management — gestión de contenido empresarial |
| **Fernet** | Esquema de cifrado simétrico autenticado de la biblioteca cryptography |
| **Folio** | Página de un documento (término usado en gestión documental) |
| **HS256** | HMAC con SHA-256 — algoritmo de firma para JWT |
| **JWT** | JSON Web Token — estándar para transmitir claims de autenticación |
| **JSONB** | Tipo de dato PostgreSQL para almacenar JSON binario con indexación |
| **OAuth2** | Protocolo de autorización estándar para APIs |
| **RBAC** | Role-Based Access Control — control de acceso basado en roles |
| **Redis** | Almacén de datos en memoria usado como backend de resultados Celery |
| **Shallow Sync** | Sincronización superficial (solo primer nivel de Series, Subseries y Expedientes) |
| **Soft Delete** | Marcado lógico de eliminación (deleted=True) sin borrado físico |
| **Tipología Documental** | Clasificación de documentos según su función administrativa |
| **TRD** | Tabla de Retención Documental — instrumento archivístico |
| **UUID** | Universally Unique Identifier — identificador único universal |
| **Uvicorn** | Servidor ASGI de alto rendimiento para Python |
| **Webhook** | Mecanismo de notificación HTTP en tiempo real entre sistemas |

---

## 23. APÉNDICES

### 23.1 Repositorio Git

- URL: https://gitlab.metabiblioteca.com/metabiblioteca/development/metafoliador.git
- Rama principal: v2

### 23.2 Servidor de Producción

- IP: 54.39.196.195
- SSH Puerto: 2828
- Ruta: /var/www/html/metafoliadorv2
- Servicios systemd: metafoliadorv2, metafoliadorv2-worker, metafoliadorv2-webhook-worker

### 23.3 Versiones de Dependencias (requirements.txt)

| Paquete | Versión |
|---------|---------|
| alembic | 1.14.0 |
| celery | 5.4.0 |
| cryptography | 44.0.0 |
| fastapi | 0.115.6 |
| httpx | 0.28.1 |
| Jinja2 | — |
| lxml | 5.3.0 |
| matplotlib | 3.9.3 |
| openpyxl | 3.1.5 |
| pika | 1.3.2 |
| psycopg2-binary | 2.9.10 |
| pydantic | 2.10.3 |
| pydantic-settings | 2.6.1 |
| PyJWT | 2.10.1 |
| PyPDF2 | 3.0.1 |
| python-docx | 1.1.2 |
| python-dotenv | 1.0.1 |
| redis | 5.2.0 |
| reportlab | 4.2.5 |
| SQLAlchemy | 2.0.36 |
| uvicorn | 0.34.0 |

### 23.4 Historial de Versiones del Documento

| Versión | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | Julio 2026 | MetaBiblioteca | Versión inicial del manual técnico |

---

**Fin del Manual Técnico — Metafoliador v2.0.0**

