# Metafoliador v2 — AGENTS.md

## Resumen del Proyecto

**Metafoliador v2** es un sistema de gestión documental e indexación electrónica que se integra con **Alfresco** (ECM). Permite sincronizar sitios/Series, Subseries y Expedientes/archivos de Alfresco, generar índices electrónicos (`index.xml`) en formato colombiano (`TipoDocumentoFoliado`), calcular hashes SHA-256 y conteo de páginas, y subir los índices de vuelta a Alfresco. Incluye un panel web administrativo con RBAC.

- **Versión:** 2.0.0
- **Autor:** MetaBiblioteca (© 2026)
- **Licencia:** No especificada

---

## Stack Tecnológico

| Capa | Tecnología | Versión |
|------|-----------|---------|
| Lenguaje | Python 3 | — |
| Framework Web | FastAPI | 0.115.6 |
| Servidor ASGI | Uvicorn | 0.34.0 |
| ORM | SQLAlchemy | 2.0.36 |
| Migraciones | Alembic | 1.14.0 |
| Base de datos | PostgreSQL 16 (psycopg2-binary) | 2.9.10 |
| Cola de tareas | Celery | 5.4.0 |
| Message Broker | RabbitMQ (pika) | 1.3.2 |
| Result Backend | Redis | 5.2.0 |
| Procesamiento XML | lxml | 5.3.0 |
| Cliente HTTP | httpx | 0.28.1 |
| Configuración | pydantic-settings | 2.6.1 |
| Frontend | HTML/CSS/JS vanilla + Jinja2 | — |
| Reportes Excel | openpyxl | 3.1.5 |
| Gráficos | matplotlib | 3.9.3 |
| PDF | reportlab | 4.2.5 |
| Conteo páginas PDF | PyPDF2 | 3.0.1 |
| Criptografía | cryptography (Fernet) | 44.0.0 |
| JWT | PyJWT | 2.10.1 |

---

## Estructura del Proyecto

```
metafoliador/
├── .env.example              # Template de variables de entorno
├── .gitignore
├── alembic.ini               # Configuración de Alembic (tenant)
├── alembic_main.ini          # Configuración de Alembic (BD principal)
├── requirements.txt          # Dependencias Python
├── deploy.sh                 # Script de despliegue a producción
├── docs/                      # Documentación del proyecto
│   ├── manual-tecnico.md      # Manual técnico (23 capítulos)
│   └── manual-usuario.md      # Manual de usuario (14 capítulos)
├── scripts/                  # Scripts de instalación y configuración
│   ├── install.sh            # Instalación completa en servidor nuevo
│   ├── setup_database.sql    # Creación de BD y usuario PostgreSQL
│   ├── metafoliadorv2.service
│   ├── metafoliadorv2-worker.service
│   └── metafoliadorv2-webhook-worker.service
├── alembic/                  # Migraciones de BD tenant (24 versiones)
│   ├── env.py
│   ├── script.py.mako
│   └── versions/
├── alembic_main/             # Migraciones de BD principal
│   ├── env.py
│   ├── script.py.mako
│   └── versions/
├── app/                      # Paquete principal
│   ├── main.py               # Entry point FastAPI
│   ├── config.py             # Settings con pydantic-settings
│   ├── worker.py             # Definición de Celery app
│   ├── worker_tasks.py       # Tareas Celery
│   ├── middleware/            # Middleware de tenant
│   │   ├── __init__.py        # TenantMiddleware (bloqueo, resolución)
│   │   └── tenant.py          # Resolución de tenant por subdominio
│   ├── models/               # Modelos SQLAlchemy (12 modelos)
│   │   ├── database.py       # Engine tenant, SessionLocal, Base
│   │   ├── main_database.py  # Engine principal, MainSessionLocal, MainBase
│   │   ├── user.py           # Usuario del tenant
│   │   ├── main_user.py      # Usuario del panel principal
│   │   ├── main_customer.py  # Cliente/tenant (subdominio, db_url)
│   │   ├── alfresco_connection.py
│   │   ├── alfresco_site.py
│   │   ├── alfresco_folder.py
│   │   ├── alfresco_file.py
│   │   ├── alfresco_file_history.py
│   │   ├── alfresco_listener_log.py
│   │   ├── alfresco_tipology.py
│   │   ├── role_permission.py
│   │   ├── reporte.py
│   │   └── sync_progress.py
│   ├── services/
│   │   ├── auth_service.py        # Hashing, JWT, Fernet (tenant)
│   │   ├── main_auth_service.py   # Hashing, JWT para panel principal
│   │   ├── index_generator.py     # XML colombiano (TipoDocumentoFoliado)
│   │   └── timezone.py            # Zona horaria configurable (ZoneInfo)
│   ├── schemas/                # Modelos Pydantic para Swagger/OpenAPI
│   │   ├── __init__.py         # 15 modelos de request body documentados
│   │   └── webhook.py          # Dataclasses para eventos Alfresco
│   └── api/
│       ├── routes/           # 14 routers tenant (59 endpoints)
│       └── main/              # Routers del panel principal (17 endpoints)
│           ├── auth.py        # Login/logout del panel principal
│           ├── customers.py   # CRUD de clientes (tenants)
│           ├── users.py       # CRUD de usuarios del panel principal
│           └── views.py       # Vistas HTML del panel principal
├── static/
│   ├── css/ (6 archivos)      # +main.css para panel principal
│   ├── js/
│   │   ├── main/ (4 archivos) # JS del panel principal
│   │   └── (11 archivos tenant)
│   └── img/ (3 archivos)
└── templates/
    ├── main/ (5 archivos)      # Templates del panel principal
    └── (12 archivos tenant)
```

---

## Comandos Principales

```bash
# Instalación completa en servidor nuevo
sudo ./scripts/install.sh

# Iniciar servidor API
uvicorn app.main:app --host 127.0.0.1 --port 8001

# Iniciar worker Celery
celery -A app.worker worker --loglevel=info -Q celery

# Ejecutar migraciones
alembic upgrade head

# Crear nueva migración
alembic revision --autogenerate -m "descripcion"

# Desplegar a productivo
./deploy.sh              # Solo archivos modificados desde el último deploy
./deploy.sh --all        # Despliegue completo
./deploy.sh --force      # Fuerza reinicio de todos los servicios
```

---

## Funcionalidades Principales

### 1. Conexiones Alfresco
- CRUD de conexiones a instancias Alfresco (URL, usuario, contraseña, tipo de auth)
- **Credenciales separadas**: usuario/contraseña para sync (workers) + usuario/contraseña para API/listener (webhooks), con fallback automático
- **Tamaño del repositorio**: columna visual que muestra la suma de size_bytes de todos los archivos por conexión (formato legible B/KB/MB/GB/TB)
- Test de conexión
- Descubrimiento de sitios desde Alfresco

### 2. Sincronización de Sitios
- Sincronización recursiva de Series, Subseries y Expedientes y archivos desde un sitio Alfresco
- Dos modos: shallow (solo primer nivel) y deep (recursivo completo)
- Tracking de progreso en tiempo real (SyncProgress)
- Soft delete para elementos eliminados en Alfresco
- Detección de archivos eliminados durante el sync: compara los IDs de Alfresco contra la DB y marca `deleted = True` los que ya no existen
- Reactivación automática si un archivo eliminado reaparece en Alfresco
- Historial de movimientos de archivos entre Series, Subseries y Expedientes
- **sync_folder_children genera índices recursivamente** en todas las subcarpetas

### 3. Generación de Índices Electrónicos
- Formato colombiano `TipoDocumentoFoliado` con `DocumentoIndizado`
- **Tipología_Documental**: se resuelve dinámicamente desde `alfresco_tipologys.value` contra las propiedades de cada archivo. Sin match → vacío
- **Autor**: formateado como `displayName (id)` desde `entry.createdByUser`
- **Version**: desde `cm:versionLabel` en las propiedades del archivo
- Cálculo de hash SHA-256 por archivo (solo recalcula si el hash está vacío o la versión cambió)
- **Comparación de versión**: si `cm:versionLabel` cambia, limpia `hash_sha256` para forzar recálculo
- Conteo de páginas solo para PDFs (PyPDF2). Todo lo demás → 1 página
- Subida automática del `index.xml` con `autoRename: false` (evita duplicados `index-N.xml`)
- **Manejo de 409 Conflict**: si el POST falla porque ya existe `index.xml`, re-busca y actualiza vía PUT
- Flag `has_index_xml` en Series, Subseries y Expedientes
- **Versionado**: el `index.xml` con aspect `cm:versionable`, `majorVersion=false`

### 4. Webhook Listener
- Recepción de eventos de Alfresco (create/update/delete/move)
- Autorización por IP
- Logging de requests entrantes
- **Procesamiento vía ThreadPoolExecutor (max=4)**, ya no usa Celery. El índice se regenera en un hilo sin colas
- **Credenciales API separadas**: el listener usa `api_username`/`api_password_hash` si están configurados, fallback a credenciales sync
- **Action=3 (delete)**: marca `deleted=True` directamente sin consultar Alfresco (evita falsos positivos con archivos en papelera)

### 5. Módulo Tipologías - Modelos de Datos
- CRUD completo en `/app/tipologias`
- Tabla `alfresco_tipologys` (name, value, soft-delete)
- Permisos: `tipologias.view`, `.create`, `.edit`, `.delete`
- Sidebar: sección "Alfresco" → "Tipologías - Modelos de Datos"

### 6. Panel Administrativo Web
- Login con JWT (cookie-based)
- Dashboard con estadísticas y gráficos interactivos (Chart.js)
- **Tamaño de sitio**: columna visual en tabla de sitios (suma size_bytes, formato legible)
- Navegador de cuadro de clasificación
- Gestión de conexiones y sitios
- Visor de jobs/cola
- Gestión de usuarios
- RBAC: roles y 35 permisos granulares
- Tema claro/oscuro con persistencia

### 7. Reportes (Optimizados con CTE)
- **TRD de Tipologías**: Excel con árbol jerárquico. Usa `alfresco_tipologys` para detectar tipologías (misma lógica del índice)
- **Gráficos por Dependencia**: PDF con gráficos estadísticos
- **FUID**: Excel y PDF según lineamientos AGN
- **Optimización**: todas las funciones recursivas de BD reemplazadas por CTE + agregados en memoria (~5000 queries → ~3)

### 8. Sistema Multitenant
- Panel Principal y Panel Tenant con dominios separados
- Wildcard SSL para subdominios
- Auto-login entre paneles

---

## Modelos de Datos (12 tablas)

| Modelo | Tabla | Propósito |
|--------|-------|-----------|
| User | users | Usuarios del tenant |
| MainUser | main_users | Usuarios del panel principal |
| Customer | customers | Clientes/tenants |
| AlfrescoConnection | alfresco_connections | Conexión Alfresco (incluye api_username, api_password_hash) |
| AlfrescoSite | alfresco_sites | Sitios Alfresco |
| AlfrescoFolder | alfresco_folders | Cuadro jerárquico |
| AlfrescoFile | alfresco_files | Metadatos de archivos (incluye properties_json con createdByUser) |
| AlfrescoFileHistory | alfresco_file_history | Historial de movimientos |
| AlfrescoListenerLog | alfresco_listener_logs | Logs del listener |
| AlfrescoTipology | alfresco_tipologys | Tipologías documentales (CRUD, soft-delete) |
| Role / Permission | roles, permissions, role_permissions | RBAC (35 permisos) |
| SyncProgress | sync_progress | Progreso de jobs |
| Reporte | reportes | Definiciones de reportes |

---

## Esquema de Servicios (systemd)

| Servicio | Cola/Mecanismo | Ejecuta |
|----------|---------------|---------|
| `metafoliadorv2` | Threading directo (max 4) | API + Listener (regenera índices en hilo) + Reportes |
| `metafoliadorv2-worker` | `celery` | Sync manual: `sync_site_folders`, `sync_folder_children` |
| `metafoliadorv2-webhook-worker` | `webhooks` | **Deshabilitado** — ya no se usa |

---

## Flujos de Proceso

### Flujo de Sincronización
1. Usuario dispara sync (shallow o deep) desde UI
2. Se crea `SyncProgress` y se lanza tarea Celery en cola `celery`
3. La tarea recorre carpetas vía API Alfresco, crea/actualiza `AlfrescoFolder` y `AlfrescoFile`
4. `_upsert_file` compara `cm:versionLabel`: si cambió, limpia `hash_sha256`
5. `_mark_deleted_files` detecta eliminados y regenera índice
6. Si `generate_index=True`, genera `index.xml` para todas las carpetas (recursivo en sync_folder_children)
7. `build_index_xml_content`: resuelve tipología desde `alfresco_tipologys`, agrega Autor y Version

### Flujo de Webhook Listener
1. Alfresco envía POST a `/api/v1/alfresco/listener`
2. Verifica IP → usa credenciales API (o fallback sync)
3. `process_file_action` / `process_folder_action` según `is_folder`
4. Para action=3: marca `deleted=True` sin consultar Alfresco
5. `_regenerate_index` → ThreadPoolExecutor.submit() → `_generate_index_for_folder`
6. El hilo crea su propia sesión BD, descarga archivos, calcula hash, sube index.xml

---

## Variables de Entorno (.env)

| Variable | Default | Descripción |
|----------|---------|-------------|
| `database_url` | `postgresql://.../metafoliadorv2` | URL PostgreSQL (tenant) |
| `main_database_url` | `postgresql://.../metafoliador_main` | URL PostgreSQL (BD principal) |
| `main_domain` | `metafoliador.local` | Dominio del panel principal |
| `rabbitmq_url` | `amqp://guest:guest@localhost:5672/` | URL RabbitMQ |
| `redis_url` | `redis://localhost:6379/0` | URL Redis |
| `log_level` | `INFO` | Nivel de logging |
| `swagger_username` | `admin` | Basic Auth Swagger |
| `swagger_password` | `tykUGJ#Nr3u9%DZ!HndX` | Basic Auth Swagger |
| `index_xml_node_type` | `cm:content` | Node type para index.xml |
| `timezone` | `America/Bogota` | Zona horaria del sistema (ZoneInfo) |

---

## Cambios Recientes (v2)

- **Credenciales separadas API/Sync**: `alfresco_connections` con `api_username` y `api_password_hash`. Listener usa credenciales API, workers usan credenciales sync
- **Listener con ThreadPoolExecutor**: regeneración de índices sin Celery, sin colas, sin mensajes huérfanos
- **Webhook worker deshabilitado**: `metafoliadorv2-webhook-worker` ya no se necesita
- **Tipología documental dinámica**: `Tipologia_Documental` en index.xml se resuelve desde `alfresco_tipologys.value` contra propiedades del archivo
- **Campos Autor y Version en index.xml**: `Autor` desde `createdByUser`, `Version` desde `cm:versionLabel`
- **Comparación de versiones**: `_upsert_file` limpia `hash_sha256` si `cm:versionLabel` cambió
- **Módulo Tipologías**: CRUD completo en `/app/tipologias`, tabla `alfresco_tipologys`, 4 permisos
- **Reportes optimizados**: CTE recursivo + agregados en memoria (FUID, TRD, Gráficos)
- **TRD usa alfresco_tipologys**: misma lógica de detección que el índice
- **autoRename=false + manejo 409**: evita duplicados `index-N.xml`
- **count_pages solo PDF**: PyPDF2 para contar páginas, resto → 1
- **Action=3 simplificado**: marca deleted=True sin consultar Alfresco
- **Tamaño repositorio**: columna visual en conexiones y sitios (B/KB/MB/GB/TB)
- **Task time limits removidos**: `task_time_limit=None`, `task_soft_time_limit=None`
- **sync_folder_children**: genera índices recursivamente en todas las subcarpetas
- **Permisos ampliados**: 35 permisos (3 nuevos para tipologías)
- **Migraciones**: +5 nuevas (alfresco_models → alfresco_tipologys, api_credentials, fix listener)
- **Paginación Alfresco**: listados de hijos con `maxItems`+`skipCount`+`hasMoreItems` en `fetch_children_sync`, `fetch_save`, `_check_index_xml` y `_find_index_xml_node`. Soporta carpetas con >500 hijos (fix `index.xml` 409 "already exists", sync parcial y detección de eliminados)
- **Zona horaria configurable**: `TIMEZONE` en .env (default `America/Bogota`), servicio `app/services/timezone.py` con `now()`, `to_local()`, `iso()`, `strftime()`
- **consumer_timeout de RabbitMQ**: configurado en `/etc/rabbitmq/rabbitmq.conf` (1 año). Evita que `consumer_timeout` (30 min por defecto) + `task_acks_late` maten el canal y re-ejecuten jobs de larga duración desde 0
