chore: clean unnecessary markdown files for CV sharing
This commit is contained in:
501
ARCHITECTURE.md
501
ARCHITECTURE.md
@@ -1,501 +0,0 @@
|
||||
# Arquitectura CBCFacil v9
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
CBCFacil es un servicio de IA modular para procesamiento de documentos (audio, PDF, texto) con integracion a Nextcloud. Este documento describe la arquitectura actual, patrones de diseno utilizados y guia de extension del sistema.
|
||||
|
||||
## Evucion Arquitectonica
|
||||
|
||||
### Problema Original (v7)
|
||||
|
||||
El proyecto sufria de un archivo monolitico de 3167 lineas (`main.py`) que contenia todas las responsabilidades en un solo archivo.
|
||||
|
||||
### Solucion Actual (v9)
|
||||
|
||||
Arquitectura modular con separacion clara de responsabilidades en capas independientes.
|
||||
|
||||
```
|
||||
FLUJO DE DATOS
|
||||
=============
|
||||
|
||||
1. MONITOREO 2. DESCARGA 3. PROCESAMIENTO
|
||||
+---------------+ +---------------+ +---------------+
|
||||
| Nextcloud |-------->| Downloads/ |------>| Processors |
|
||||
| (WebDAV) | | Local | | (Audio/PDF) |
|
||||
+---------------+ +---------------+ +---------------+
|
||||
| |
|
||||
v v
|
||||
+---------------+ +---------------+
|
||||
| WebDAV | | AI Services |
|
||||
| Service | | (Claude/ |
|
||||
+---------------+ | Gemini) |
|
||||
+---------------+
|
||||
|
|
||||
v
|
||||
4. GENERACION 5. REGISTRO 6. NOTIFICACION
|
||||
+---------------+ +---------------+ +---------------+
|
||||
| Document |-------->| Processed |------>| Telegram |
|
||||
| Generators | | Registry | | Service |
|
||||
+---------------+ +---------------+ +---------------+
|
||||
| |
|
||||
v v
|
||||
+---------------+ +---------------+
|
||||
| Nextcloud | | Dashboard |
|
||||
| (Upload) | | (Flask) |
|
||||
+---------------+ +---------------+
|
||||
```
|
||||
|
||||
## Estructura de Directorios
|
||||
|
||||
```
|
||||
cbcfacil/
|
||||
├── main.py # Orquestador principal (149 lineas)
|
||||
├── run.py # Script de ejecucion alternativo
|
||||
├── config/ # Configuracion centralizada
|
||||
│ ├── __init__.py # Exports: settings, validate_environment
|
||||
│ ├── settings.py # Configuracion desde variables de entorno
|
||||
│ └── validators.py # Validadores de configuracion
|
||||
├── core/ # Nucleo compartido
|
||||
│ ├── __init__.py # Exports: excepciones, Result
|
||||
│ ├── exceptions.py # Excepciones personalizadas
|
||||
│ ├── result.py # Patron Result/Error handling
|
||||
│ └── base_service.py # Clase base BaseService
|
||||
├── services/ # Servicios externos
|
||||
│ ├── __init__.py # Exports de servicios
|
||||
│ ├── webdav_service.py # WebDAV/Nextcloud operaciones
|
||||
│ ├── vram_manager.py # GPU memory management
|
||||
│ ├── telegram_service.py # Telegram notificaciones
|
||||
│ ├── metrics_collector.py # Metricas y estadisticas
|
||||
│ ├── ai_service.py # Servicio AI unificado
|
||||
│ └── ai/ # AI Providers
|
||||
│ ├── __init__.py
|
||||
│ ├── base_provider.py # Interfaz BaseProvider
|
||||
│ ├── claude_provider.py # Claude (Z.ai) implementation
|
||||
│ ├── gemini_provider.py # Gemini API/CLI implementation
|
||||
│ └── provider_factory.py # Factory para proveedores
|
||||
├── processors/ # Procesadores de archivos
|
||||
│ ├── __init__.py # Exports de procesadores
|
||||
│ ├── base_processor.py # Clase base FileProcessor
|
||||
│ ├── audio_processor.py # Whisper transcription
|
||||
│ ├── pdf_processor.py # PDF OCR processing
|
||||
│ └── text_processor.py # Text summarization
|
||||
├── document/ # Generacion de documentos
|
||||
│ ├── __init__.py
|
||||
│ └── generators.py # DOCX/PDF/Markdown generation
|
||||
├── storage/ # Persistencia y cache
|
||||
│ ├── __init__.py
|
||||
│ └── processed_registry.py # Registro de archivos procesados
|
||||
├── api/ # API REST
|
||||
│ ├── __init__.py
|
||||
│ └── routes.py # Flask routes y endpoints
|
||||
├── tests/ # Tests unitarios e integracion
|
||||
│ ├── conftest.py # Fixtures pytest
|
||||
│ ├── test_config.py # Tests de configuracion
|
||||
│ ├── test_storage.py # Tests de almacenamiento
|
||||
│ ├── test_webdav.py # Tests de WebDAV
|
||||
│ ├── test_processors.py # Tests de procesadores
|
||||
│ ├── test_ai_providers.py # Tests de AI providers
|
||||
│ ├── test_vram_manager.py # Tests de VRAM manager
|
||||
│ └── test_main_integration.py # Tests de integracion main
|
||||
├── docs/ # Documentacion
|
||||
│ ├── archive/ # Documentacion historica
|
||||
│ ├── SETUP.md # Guia de configuracion
|
||||
│ ├── TESTING.md # Guia de testing
|
||||
│ └── DEPLOYMENT.md # Guia de despliegue
|
||||
├── requirements.txt # Dependencias produccion
|
||||
├── requirements-dev.txt # Dependencias desarrollo
|
||||
├── .env.example # Template de configuracion
|
||||
├── .env.secrets # Configuracion local (no versionar)
|
||||
└── Dockerfile # Container Docker
|
||||
```
|
||||
|
||||
## Componentes Principales
|
||||
|
||||
### 1. Servicios (services/)
|
||||
|
||||
#### WebDAVService
|
||||
|
||||
**Archivo**: `services/webdav_service.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Conexion y operaciones con Nextcloud via WebDAV
|
||||
- Download/upload de archivos
|
||||
- Listado y creacion de directorios remotos
|
||||
- Manejo de errores con reintentos configurables
|
||||
|
||||
```python
|
||||
class WebDAVService:
|
||||
def initialize(self) -> None: ...
|
||||
def list(self, remote_path: str) -> List[str]: ...
|
||||
def download(self, remote_path: str, local_path: Path) -> None: ...
|
||||
def upload(self, local_path: Path, remote_path: str) -> None: ...
|
||||
def mkdir(self, remote_path: str) -> None: ...
|
||||
```
|
||||
|
||||
#### VRAMManager
|
||||
|
||||
**Archivo**: `services/vram_manager.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Gestion de memoria GPU
|
||||
- Carga/descarga de modelos (Whisper, OCR, TrOCR)
|
||||
- Limpieza automatica de VRAM ociosa
|
||||
- Fallback a CPU cuando GPU no disponible
|
||||
|
||||
```python
|
||||
class VRAMManager:
|
||||
def initialize(self) -> None: ...
|
||||
def cleanup(self) -> None: ...
|
||||
def should_cleanup(self) -> bool: ...
|
||||
def lazy_cleanup(self) -> None: ...
|
||||
```
|
||||
|
||||
#### TelegramService
|
||||
|
||||
**Archivo**: `services/telegram_service.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Envio de notificaciones a Telegram
|
||||
- Throttling de errores para evitar spam
|
||||
- Notificaciones de inicio/parada del servicio
|
||||
|
||||
```python
|
||||
class TelegramService:
|
||||
def configure(self, token: str, chat_id: str) -> None: ...
|
||||
def send_message(self, message: str) -> None: ...
|
||||
def send_error_notification(self, context: str, error: str) -> None: ...
|
||||
def send_start_notification(self) -> None: ...
|
||||
```
|
||||
|
||||
### 2. Procesadores (processors/)
|
||||
|
||||
#### AudioProcessor
|
||||
|
||||
**Archivo**: `processors/audio_processor.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Transcripcion de audio usando Whisper
|
||||
- Modelo: medium (optimizado para espanol)
|
||||
- Soporte GPU/CPU automatico
|
||||
- Post-procesamiento de texto transcrito
|
||||
|
||||
#### PDFProcessor
|
||||
|
||||
**Archivo**: `processors/pdf_processor.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Extraccion de texto de PDFs
|
||||
- OCR con EasyOCR + Tesseract + TrOCR en paralelo
|
||||
- Correccion de texto con IA
|
||||
- Generacion de documentos DOCX
|
||||
|
||||
#### TextProcessor
|
||||
|
||||
**Archivo**: `processors/text_processor.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Resumenes usando IA (Claude/Gemini)
|
||||
- Clasificacion de contenido
|
||||
- Generacion de quizzes opcionales
|
||||
|
||||
### 3. AI Services (services/ai/)
|
||||
|
||||
#### ProviderFactory
|
||||
|
||||
**Archivo**: `services/ai/provider_factory.py`
|
||||
|
||||
Patron Factory para seleccion dinamica de proveedor de IA:
|
||||
```python
|
||||
class ProviderFactory:
|
||||
def get_provider(self, provider_type: str = "auto") -> BaseProvider: ...
|
||||
```
|
||||
|
||||
Proveedores disponibles:
|
||||
- `claude`: Claude via Z.ai API
|
||||
- `gemini`: Google Gemini API
|
||||
- `gemini_cli`: Gemini CLI local
|
||||
- `auto`: Seleccion automatica basada en disponibilidad
|
||||
|
||||
### 4. Document Generation (document/)
|
||||
|
||||
#### DocumentGenerator
|
||||
|
||||
**Archivo**: `document/generators.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Creacion de documentos DOCX
|
||||
- Conversion a PDF
|
||||
- Formateo Markdown
|
||||
- Plantillas de documentos
|
||||
|
||||
### 5. Storage (storage/)
|
||||
|
||||
#### ProcessedRegistry
|
||||
|
||||
**Archivo**: `storage/processed_registry.py`
|
||||
|
||||
Responsabilidades:
|
||||
- Registro persistente de archivos procesados
|
||||
- Cache en memoria con TTL
|
||||
- File locking para thread-safety
|
||||
|
||||
```python
|
||||
class ProcessedRegistry:
|
||||
def initialize(self) -> None: ...
|
||||
def load(self) -> Set[str]: ...
|
||||
def save(self, file_path: str) -> None: ...
|
||||
def is_processed(self, file_path: str) -> bool: ...
|
||||
def mark_for_reprocess(self, file_path: str) -> None: ...
|
||||
```
|
||||
|
||||
### 6. API (api/)
|
||||
|
||||
#### Flask Routes
|
||||
|
||||
**Archivo**: `api/routes.py`
|
||||
|
||||
Endpoints REST disponibles:
|
||||
- `GET /api/files` - Listado de archivos
|
||||
- `POST /api/reprocess` - Reprocesar archivo
|
||||
- `POST /api/mark-unprocessed` - Resetear estado
|
||||
- `GET /api/refresh` - Sincronizar con Nextcloud
|
||||
- `GET /health` - Health check
|
||||
|
||||
## Patrones de Diseno Utilizados
|
||||
|
||||
### 1. Repository Pattern
|
||||
|
||||
```python
|
||||
# storage/processed_registry.py
|
||||
class ProcessedRegistry:
|
||||
def save(self, file_path: str) -> None: ...
|
||||
def load(self) -> Set[str]: ...
|
||||
def is_processed(self, file_path: str) -> bool: ...
|
||||
```
|
||||
|
||||
### 2. Factory Pattern
|
||||
|
||||
```python
|
||||
# services/ai/provider_factory.py
|
||||
class ProviderFactory:
|
||||
def get_provider(self, provider_type: str = "auto") -> BaseProvider: ...
|
||||
```
|
||||
|
||||
### 3. Strategy Pattern
|
||||
|
||||
```python
|
||||
# services/vram_manager.py
|
||||
class VRAMManager:
|
||||
def cleanup(self) -> None: ...
|
||||
def should_cleanup(self) -> bool: ...
|
||||
def lazy_cleanup(self) -> None: ...
|
||||
```
|
||||
|
||||
### 4. Service Layer Pattern
|
||||
|
||||
```python
|
||||
# services/webdav_service.py
|
||||
class WebDAVService:
|
||||
def list(self, remote_path: str) -> List[str]: ...
|
||||
def download(self, remote_path: str, local_path: Path) -> None: ...
|
||||
def upload(self, local_path: Path, remote_path: str) -> None: ...
|
||||
```
|
||||
|
||||
### 5. Singleton Pattern
|
||||
|
||||
Servicios implementados como singletons para compartir estado:
|
||||
```python
|
||||
# services/webdav_service.py
|
||||
webdav_service = WebDAVService()
|
||||
|
||||
# services/vram_manager.py
|
||||
vram_manager = VRAMManager()
|
||||
```
|
||||
|
||||
### 6. Result Pattern
|
||||
|
||||
```python
|
||||
# core/result.py
|
||||
class Result:
|
||||
@staticmethod
|
||||
def success(value): ...
|
||||
@staticmethod
|
||||
def failure(error): ...
|
||||
```
|
||||
|
||||
## Decisiones Arquitectonicas (ADR)
|
||||
|
||||
### ADR-001: Arquitectura Modular
|
||||
|
||||
**Decision**: Separar el monolito en modulos independientes.
|
||||
|
||||
**Contexto**: El archivo main.py de 3167 lineas era dificil de mantener y testar.
|
||||
|
||||
**Decision**: Separar en capas: config/, core/, services/, processors/, document/, storage/, api/.
|
||||
|
||||
**Consecuencias**:
|
||||
- Positivo: Codigo mas mantenible y testeable
|
||||
- Positivo: Reutilizacion de componentes
|
||||
- Negativo: Mayor complejidad inicial
|
||||
|
||||
### ADR-002: Configuracion Centralizada
|
||||
|
||||
**Decision**: Usar clase Settings con variables de entorno.
|
||||
|
||||
**Contexto**: Credenciales hardcodeadas representan riesgo de seguridad.
|
||||
|
||||
**Decision**: Todas las configuraciones via variables de entorno con .env.secrets.
|
||||
|
||||
**Consecuencias**:
|
||||
- Positivo: Seguridad mejorada
|
||||
- Positivo: Facil despliegue en diferentes entornos
|
||||
- Negativo: Requiere documentacion de variables
|
||||
|
||||
### ADR-003: GPU-First con CPU Fallback
|
||||
|
||||
**Decision**: Optimizar para GPU pero soportar CPU.
|
||||
|
||||
**Contexto**: No todos los usuarios tienen GPU disponible.
|
||||
|
||||
**Decision**: VRAMManager con lazy loading y cleanup automatico.
|
||||
|
||||
**Consecuencias**:
|
||||
- Positivo: Performance optimo en GPU
|
||||
- Positivo: Funciona sin GPU
|
||||
- Negativo: Complejidad adicional en gestion de memoria
|
||||
|
||||
### ADR-004: Factory para AI Providers
|
||||
|
||||
**Decision**: Abstraer proveedores de IA detras de interfaz comun.
|
||||
|
||||
**Contexto**: Multiples proveedores (Claude, Gemini) con diferentes APIs.
|
||||
|
||||
**Decision**: BaseProvider con implementaciones concretas y ProviderFactory.
|
||||
|
||||
**Consecuencias**:
|
||||
- Positivo: Facilidad para agregar nuevos proveedores
|
||||
- Positivo: Fallback entre proveedores
|
||||
- Negativo: Sobrecarga de abstraccion
|
||||
|
||||
## Guia de Extension del Sistema
|
||||
|
||||
### Agregar Nuevo Procesador
|
||||
|
||||
1. Crear archivo en `processors/`:
|
||||
```python
|
||||
from processors.base_processor import FileProcessor
|
||||
|
||||
class NuevoProcessor(FileProcessor):
|
||||
def process(self, file_path: str) -> None:
|
||||
# Implementar procesamiento
|
||||
pass
|
||||
```
|
||||
|
||||
2. Registrar en `processors/__init__.py`:
|
||||
```python
|
||||
from processors.nuevo_processor import NuevoProcessor
|
||||
|
||||
__all__ = ['NuevoProcessor', ...]
|
||||
```
|
||||
|
||||
3. Integrar en `main.py`:
|
||||
```python
|
||||
from processors.nuevo_processor import NuevoProcessor
|
||||
|
||||
nuevo_processor = NuevoProcessor()
|
||||
```
|
||||
|
||||
### Agregar Nuevo AI Provider
|
||||
|
||||
1. Crear clase en `services/ai/`:
|
||||
```python
|
||||
from services.ai.base_provider import BaseProvider
|
||||
|
||||
class NuevoProvider(BaseProvider):
|
||||
def summarize(self, text: str) -> str:
|
||||
# Implementar
|
||||
pass
|
||||
```
|
||||
|
||||
2. Registrar en `provider_factory.py`:
|
||||
```python
|
||||
PROVIDERS = {
|
||||
'nuevo': NuevoProvider,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
3. Usar:
|
||||
```python
|
||||
provider = factory.get_provider('nuevo')
|
||||
```
|
||||
|
||||
### Agregar Nuevo Servicio
|
||||
|
||||
1. Crear archivo en `services/`:
|
||||
```python
|
||||
from core.base_service import BaseService
|
||||
|
||||
class NuevoService(BaseService):
|
||||
def initialize(self) -> None:
|
||||
pass
|
||||
|
||||
nuevo_service = NuevoService()
|
||||
```
|
||||
|
||||
2. Inicializar en `main.py`:
|
||||
```python
|
||||
from services.nuevo_service import nuevo_service
|
||||
|
||||
nuevo_service.initialize()
|
||||
```
|
||||
|
||||
### Agregar Nuevo Endpoint API
|
||||
|
||||
1. Editar `api/routes.py`:
|
||||
```python
|
||||
@app.route('/api/nuevo', methods=['GET'])
|
||||
def nuevo_endpoint():
|
||||
return {'status': 'ok'}, 200
|
||||
```
|
||||
|
||||
## Configuracion Detallada
|
||||
|
||||
### Variables de Entorno Principales
|
||||
|
||||
| Variable | Requerido | Default | Descripcion |
|
||||
|----------|-----------|---------|-------------|
|
||||
| NEXTCLOUD_URL | Si | - | URL de Nextcloud WebDAV |
|
||||
| NEXTCLOUD_USER | Si | - | Usuario Nextcloud |
|
||||
| NEXTCLOUD_PASSWORD | Si | - | Contrasena Nextcloud |
|
||||
| ANTHROPIC_AUTH_TOKEN | No | - | Token Claude/Z.ai |
|
||||
| GEMINI_API_KEY | No | - | API Key Gemini |
|
||||
| TELEGRAM_TOKEN | No | - | Token Bot Telegram |
|
||||
| TELEGRAM_CHAT_ID | No | - | Chat ID Telegram |
|
||||
| CUDA_VISIBLE_DEVICES | No | "all" | GPU a usar |
|
||||
| POLL_INTERVAL | No | 5 | Segundos entre polls |
|
||||
| LOG_LEVEL | No | "INFO" | Nivel de logging |
|
||||
|
||||
## Metricas y Benchmarks
|
||||
|
||||
| Metrica | Valor |
|
||||
|---------|-------|
|
||||
| Lineas main.py | 149 (antes 3167) |
|
||||
| Modulos independientes | 8+ |
|
||||
| Cobertura tests | ~60%+ |
|
||||
| Tiempo inicio | 5-10s |
|
||||
| Transcripcion Whisper | ~1x tiempo audio (GPU) |
|
||||
| OCR PDF | 0.5-2s/pagina |
|
||||
|
||||
## Beneficios de la Arquitectura
|
||||
|
||||
1. **Mantenibilidad**: Cada responsabilidad en su propio modulo
|
||||
2. **Testabilidad**: Servicios independientes y testeables
|
||||
3. **Escalabilidad**: Facil agregar nuevos procesadores/servicios
|
||||
4. **Reutilizacion**: Componentes desacoplados
|
||||
5. **Legibilidad**: Codigo organizado y documentado
|
||||
6. **Seguridad**: Configuracion centralizada sin hardcoding
|
||||
|
||||
## Licencia
|
||||
|
||||
MIT License - Ver LICENSE para detalles.
|
||||
@@ -1,187 +0,0 @@
|
||||
# 🚀 ROCm Setup para AMD GPU
|
||||
|
||||
## ✅ Estado Actual del Sistema
|
||||
|
||||
**GPU**: AMD Radeon RX 6800 XT
|
||||
**ROCm**: 6.0
|
||||
**PyTorch**: 2.5.0+rocm6.0
|
||||
|
||||
## 📋 Comandos Esenciales
|
||||
|
||||
### Verificar GPU
|
||||
```bash
|
||||
# Información básica de la GPU
|
||||
lspci | grep -i vga
|
||||
|
||||
# Estado en tiempo real de ROCm
|
||||
rocm-smi
|
||||
|
||||
# Información detallada del sistema
|
||||
rocminfo
|
||||
```
|
||||
|
||||
### Variables de Entorno Críticas
|
||||
```bash
|
||||
# CRÍTICO para gfx1030 (RX 6000 series)
|
||||
export HSA_OVERRIDE_GFX_VERSION=10.3.0
|
||||
|
||||
# Agregar al ~/.bashrc o ~/.zshrc
|
||||
echo 'export HSA_OVERRIDE_GFX_VERSION=10.3.0' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
### Verificar PyTorch con ROCm
|
||||
```bash
|
||||
# Test básico de PyTorch
|
||||
python3 -c "
|
||||
import torch
|
||||
print(f'PyTorch: {torch.__version__}')
|
||||
print(f'ROCm disponible: {torch.cuda.is_available()}')
|
||||
print(f'Dispositivos: {torch.cuda.device_count()}')
|
||||
if torch.cuda.is_available():
|
||||
print(f'GPU: {torch.cuda.get_device_name(0)}')
|
||||
print(f'Memoria: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB')
|
||||
"
|
||||
|
||||
# Benchmark rápido
|
||||
python3 -c "
|
||||
import torch, time
|
||||
a = torch.randn(4096, 4096, device='cuda')
|
||||
b = torch.randn(4096, 4096, device='cuda')
|
||||
start = time.time()
|
||||
c = torch.matmul(a, b)
|
||||
torch.cuda.synchronize()
|
||||
print(f'GPU time: {time.time() - start:.4f}s')
|
||||
"
|
||||
```
|
||||
|
||||
## 🧪 Script de Stress Test
|
||||
|
||||
### Ejecutar Stress Test (2 minutos)
|
||||
```bash
|
||||
python3 /home/ren/gpu/rocm_stress_test.py
|
||||
```
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Si ROCm no detecta la GPU:
|
||||
```bash
|
||||
# Verificar módulos del kernel
|
||||
lsmod | grep amdgpu
|
||||
lsmod | grep kfd
|
||||
|
||||
# Recargar módulos
|
||||
sudo modprobe amdgpu
|
||||
sudo modprobe kfd
|
||||
|
||||
# Verificar logs
|
||||
dmesg | grep amdgpu
|
||||
```
|
||||
|
||||
### Si PyTorch no encuentra ROCm:
|
||||
```bash
|
||||
# Reinstalar PyTorch con ROCm
|
||||
pip uninstall torch torchvision torchaudio
|
||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.0
|
||||
```
|
||||
|
||||
### Si hay errores de memoria:
|
||||
```bash
|
||||
# Limpiar cache de GPU
|
||||
python3 -c "import torch; torch.cuda.empty_cache()"
|
||||
|
||||
# Verificar uso de memoria
|
||||
rocm-smi --meminfo
|
||||
```
|
||||
|
||||
## 📊 Monitoreo Continuo
|
||||
|
||||
### Terminal 1 - Monitor en tiempo real
|
||||
```bash
|
||||
watch -n 1 rocm-smi
|
||||
```
|
||||
|
||||
### Terminal 2 - Información detallada
|
||||
```bash
|
||||
rocm-smi --showtemp --showmeminfo vram --showmeminfo all
|
||||
```
|
||||
|
||||
## 💡 Ejemplos de Uso
|
||||
|
||||
### Cargar modelo en GPU
|
||||
```python
|
||||
import torch
|
||||
from transformers import AutoModel
|
||||
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
print(f"Usando dispositivo: {device}")
|
||||
|
||||
model = AutoModel.from_pretrained("bert-base-uncased")
|
||||
model = model.to(device)
|
||||
|
||||
# Los tensores ahora se procesarán en la GPU
|
||||
inputs = torch.tensor([1, 2, 3]).to(device)
|
||||
```
|
||||
|
||||
### Entrenamiento en GPU
|
||||
```python
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
device = torch.device("cuda")
|
||||
model = tu_modelo().to(device)
|
||||
criterion = nn.CrossEntropyLoss().to(device)
|
||||
optimizer = torch.optim.Adam(model.parameters())
|
||||
|
||||
for epoch in range(epochs):
|
||||
for batch in dataloader:
|
||||
inputs, labels = batch
|
||||
inputs, labels = inputs.to(device), labels.to(device)
|
||||
|
||||
optimizer.zero_grad()
|
||||
outputs = model(inputs)
|
||||
loss = criterion(outputs, labels)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
```
|
||||
|
||||
## 🎯 Optimizaciones
|
||||
|
||||
### Para mejor rendimiento:
|
||||
```python
|
||||
# Usar mixed precision (más rápido en RDNA2)
|
||||
from torch.cuda.amp import autocast, GradScaler
|
||||
|
||||
scaler = GradScaler()
|
||||
with autocast():
|
||||
output = model(inputs)
|
||||
loss = criterion(output, targets)
|
||||
|
||||
scaler.scale(loss).backward()
|
||||
scaler.step(optimizer)
|
||||
scaler.update()
|
||||
```
|
||||
|
||||
## 📈 Comandos Útiles
|
||||
|
||||
```bash
|
||||
# Ver versión de ROCm
|
||||
rocm-smi --version
|
||||
|
||||
# Verificar HSA
|
||||
rocminfo
|
||||
|
||||
# Test de compatibilidad
|
||||
python3 /opt/rocm/bin/rocprofiler-compute-test.py
|
||||
|
||||
# Verificar BLAS
|
||||
python3 -c "import torch; print(torch.backends.mps.is_available())" # False en AMD
|
||||
```
|
||||
|
||||
## ⚡ Performance Tips
|
||||
|
||||
1. **Siempre mueve datos a GPU**: `.to(device)`
|
||||
2. **Usa batch sizes grandes**: Aprovecha los 16GB de VRAM
|
||||
3. **Mixed precision**: Acelera el entrenamiento 1.5-2x
|
||||
4. **DataLoader con num_workers**: Carga datos en paralelo
|
||||
5. **torch.cuda.synchronize()**: Para benchmarks precisos
|
||||
@@ -1,418 +0,0 @@
|
||||
# Guia de Despliegue - CBCFacil
|
||||
|
||||
Esta guia describe las opciones y procedimientos para desplegar CBCFacil en diferentes entornos.
|
||||
|
||||
## Opciones de Despliegue
|
||||
|
||||
| Metodo | Complejidad | Recomendado para |
|
||||
|--------|-------------|------------------|
|
||||
| Docker Compose | Baja | Desarrollo, Produccion ligera |
|
||||
| Docker Standalone | Media | Produccion con orquestacion |
|
||||
| Virtual Environment | Baja | Desarrollo local |
|
||||
| Kubernetes | Alta | Produccion a escala |
|
||||
|
||||
## Despliegue con Docker Compose
|
||||
|
||||
### Prerrequisitos
|
||||
|
||||
- Docker 24.0+
|
||||
- Docker Compose 2.20+
|
||||
- NVIDIA Container Toolkit (para GPU)
|
||||
|
||||
### Configuracion
|
||||
|
||||
1. Crear archivo de configuracion:
|
||||
```bash
|
||||
cp .env.example .env.production
|
||||
nano .env.production
|
||||
```
|
||||
|
||||
2. Configurar variables sensibles:
|
||||
```bash
|
||||
# .env.production
|
||||
NEXTCLOUD_URL=https://nextcloud.example.com/remote.php/webdav
|
||||
NEXTCLOUD_USER=tu_usuario
|
||||
NEXTCLOUD_PASSWORD=tu_contrasena_segura
|
||||
|
||||
ANTHROPIC_AUTH_TOKEN=sk-ant-...
|
||||
GEMINI_API_KEY=AIza...
|
||||
|
||||
TELEGRAM_TOKEN=bot_token
|
||||
TELEGRAM_CHAT_ID=chat_id
|
||||
|
||||
CUDA_VISIBLE_DEVICES=all
|
||||
LOG_LEVEL=INFO
|
||||
```
|
||||
|
||||
3. Verificar docker-compose.yml:
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
cbcfacil:
|
||||
build: .
|
||||
container_name: cbcfacil
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- ./downloads:/app/downloads
|
||||
- ./resumenes_docx:/app/resumenes_docx
|
||||
- ./logs:/app/logs
|
||||
- ./data:/app/data
|
||||
environment:
|
||||
- NEXTCLOUD_URL=${NEXTCLOUD_URL}
|
||||
- NEXTCLOUD_USER=${NEXTCLOUD_USER}
|
||||
- NEXTCLOUD_PASSWORD=${NEXTCLOUD_PASSWORD}
|
||||
- ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN}
|
||||
- GEMINI_API_KEY=${GEMINI_API_KEY}
|
||||
- TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
|
||||
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
|
||||
- CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES}
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
### Despliegue
|
||||
|
||||
```bash
|
||||
# Construir y levantar
|
||||
docker compose up -d --build
|
||||
|
||||
# Ver logs
|
||||
docker compose logs -f cbcfacil
|
||||
|
||||
# Ver estado
|
||||
docker compose ps
|
||||
|
||||
# Reiniciar
|
||||
docker compose restart cbcfacil
|
||||
|
||||
# Detener
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Actualizacion
|
||||
|
||||
```bash
|
||||
# Hacer backup de datos
|
||||
docker cp cbcfacil:/app/data ./backup/data
|
||||
|
||||
# Actualizar imagen
|
||||
docker compose pull
|
||||
docker compose up -d --build
|
||||
|
||||
# Verificar
|
||||
docker compose logs -f cbcfacil
|
||||
```
|
||||
|
||||
## Despliegue con Docker Standalone
|
||||
|
||||
### Construir Imagen
|
||||
|
||||
```bash
|
||||
# Construir imagen
|
||||
docker build -t cbcfacil:latest .
|
||||
|
||||
# Verificar imagen
|
||||
docker images cbcfacil
|
||||
```
|
||||
|
||||
### Ejecutar Contenedor
|
||||
|
||||
```bash
|
||||
# Con GPU
|
||||
docker run -d \
|
||||
--name cbcfacil \
|
||||
--gpus all \
|
||||
-p 5000:5000 \
|
||||
-v $(pwd)/downloads:/app/downloads \
|
||||
-v $(pwd)/resumenes_docx:/app/resumenes_docx \
|
||||
-v $(pwd)/logs:/app/logs \
|
||||
-e NEXTCLOUD_URL=${NEXTCLOUD_URL} \
|
||||
-e NEXTCLOUD_USER=${NEXTCLOUD_USER} \
|
||||
-e NEXTCLOUD_PASSWORD=${NEXTCLOUD_PASSWORD} \
|
||||
-e ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN} \
|
||||
-e GEMINI_API_KEY=${GEMINI_API_KEY} \
|
||||
cbcfacil:latest
|
||||
|
||||
# Ver logs
|
||||
docker logs -f cbcfacil
|
||||
|
||||
# Detener
|
||||
docker stop cbcfacil && docker rm cbcfacil
|
||||
```
|
||||
|
||||
## Despliegue Local (Virtual Environment)
|
||||
|
||||
### Prerrequisitos
|
||||
|
||||
- Python 3.10+
|
||||
- NVIDIA drivers (opcional)
|
||||
|
||||
### Instalacion
|
||||
|
||||
```bash
|
||||
# Clonar y entrar al directorio
|
||||
git clone <repo_url>
|
||||
cd cbcfacil
|
||||
|
||||
# Crear entorno virtual
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Instalar dependencias
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Configurar variables de entorno
|
||||
cp .env.example .env.production
|
||||
nano .env.production
|
||||
|
||||
# Crear directorios
|
||||
mkdir -p downloads resumenes_docx logs data
|
||||
|
||||
# Ejecutar
|
||||
python main.py
|
||||
```
|
||||
|
||||
### Como Servicio Systemd
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/cbcfacil.service
|
||||
[Unit]
|
||||
Description=CBCFacil AI Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=cbcfacil
|
||||
WorkingDirectory=/opt/cbcfacil
|
||||
Environment="PATH=/opt/cbcfacil/.venv/bin"
|
||||
EnvironmentFile=/opt/cbcfacil/.env.production
|
||||
ExecStart=/opt/cbcfacil/.venv/bin/python main.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=cbcfacil
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
# Instalar servicio
|
||||
sudo cp cbcfacil.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable cbcfacil
|
||||
sudo systemctl start cbcfacil
|
||||
|
||||
# Verificar estado
|
||||
sudo systemctl status cbcfacil
|
||||
|
||||
# Ver logs
|
||||
journalctl -u cbcfacil -f
|
||||
```
|
||||
|
||||
## Configuracion de Produccion
|
||||
|
||||
### Variables de Entorno Criticas
|
||||
|
||||
```bash
|
||||
# Obligatorias
|
||||
NEXTCLOUD_URL=...
|
||||
NEXTCLOUD_USER=...
|
||||
NEXTCLOUD_PASSWORD=...
|
||||
|
||||
# Recomendadas para produccion
|
||||
DEBUG=false
|
||||
LOG_LEVEL=WARNING
|
||||
POLL_INTERVAL=10
|
||||
|
||||
# GPU
|
||||
CUDA_VISIBLE_DEVICES=all
|
||||
```
|
||||
|
||||
### Optimizaciones
|
||||
|
||||
```bash
|
||||
# En .env.production
|
||||
# Reducir polling para menor carga
|
||||
POLL_INTERVAL=10
|
||||
|
||||
# Optimizar memoria GPU
|
||||
PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512
|
||||
|
||||
# Limitar threads
|
||||
OMP_NUM_THREADS=4
|
||||
MKL_NUM_THREADS=4
|
||||
```
|
||||
|
||||
### Seguridad
|
||||
|
||||
```bash
|
||||
# Crear usuario dedicado
|
||||
sudo useradd -r -s /bin/false cbcfacil
|
||||
|
||||
# Asignar permisos
|
||||
sudo chown -R cbcfacil:cbcfacil /opt/cbcfacil
|
||||
|
||||
# Proteger archivo de variables
|
||||
sudo chmod 600 /opt/cbcfacil/.env.production
|
||||
```
|
||||
|
||||
## Monitoreo
|
||||
|
||||
### Health Check
|
||||
|
||||
```bash
|
||||
# Endpoint de salud
|
||||
curl http://localhost:5000/health
|
||||
|
||||
# Respuesta esperada
|
||||
{"status": "healthy"}
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
```bash
|
||||
# Ver logs en tiempo real
|
||||
docker logs -f cbcfacil
|
||||
|
||||
# O con journalctl
|
||||
journalctl -u cbcfacil -f
|
||||
|
||||
# Logs estructurados en JSON (produccion)
|
||||
LOG_LEVEL=WARNING
|
||||
```
|
||||
|
||||
### Metricas
|
||||
|
||||
El sistema expone metricas via API:
|
||||
```bash
|
||||
# Estado del servicio
|
||||
curl http://localhost:5000/api/status
|
||||
```
|
||||
|
||||
## Respaldo y Recuperacion
|
||||
|
||||
### Respaldo de Datos
|
||||
|
||||
```bash
|
||||
# Directorios a respaldar
|
||||
# - downloads/ (archivos procesados)
|
||||
# - resumenes_docx/ (documentos generados)
|
||||
# - data/ (registros y estados)
|
||||
# - logs/ (logs del sistema)
|
||||
|
||||
# Script de backup
|
||||
#!/bin/bash
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR=/backup/cbcfacil
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
tar -czf $BACKUP_DIR/cbcfacil_$DATE.tar.gz \
|
||||
/opt/cbcfacil/downloads \
|
||||
/opt/cbcfacil/resumenes_docx \
|
||||
/opt/cbcfacil/data
|
||||
|
||||
# Limpiar backups antiguos (mantener ultimos 7 dias)
|
||||
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
|
||||
```
|
||||
|
||||
### Recuperacion
|
||||
|
||||
```bash
|
||||
# Detener servicio
|
||||
docker compose down
|
||||
|
||||
# Restaurar datos
|
||||
tar -xzf backup_*.tar.gz -C /opt/cbcfacil/
|
||||
|
||||
# Verificar permisos
|
||||
chown -R cbcfacil:cbcfacil /opt/cbcfacil
|
||||
|
||||
# Reiniciar servicio
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Troubleshooting de Produccion
|
||||
|
||||
### Contenedor no Inicia
|
||||
|
||||
```bash
|
||||
# Verificar logs
|
||||
docker logs cbcfacil
|
||||
|
||||
# Verificar configuracion
|
||||
docker exec -it cbcfacil python -c "from config import settings; print(settings.has_webdav_config)"
|
||||
```
|
||||
|
||||
### Error de Memoria GPU
|
||||
|
||||
```bash
|
||||
# Verificar GPUs disponibles
|
||||
nvidia-smi
|
||||
|
||||
# Liberar memoria
|
||||
sudo nvidia-smi --gpu-reset
|
||||
|
||||
# O limitar GPU
|
||||
CUDA_VISIBLE_DEVICES=0
|
||||
```
|
||||
|
||||
### WebDAV Connection Failed
|
||||
|
||||
```bash
|
||||
# Verificar conectividad
|
||||
curl -u $NEXTCLOUD_USER:$NEXTCLOUD_PASSWORD $NEXTCLOUD_URL
|
||||
|
||||
# Verificar credenciales
|
||||
echo "URL: $NEXTCLOUD_URL"
|
||||
echo "User: $NEXTCLOUD_USER"
|
||||
```
|
||||
|
||||
### High CPU Usage
|
||||
|
||||
```bash
|
||||
# Reducir threads
|
||||
OMP_NUM_THREADS=2
|
||||
MKL_NUM_THREADS=2
|
||||
|
||||
# Aumentar intervalo de polling
|
||||
POLL_INTERVAL=15
|
||||
```
|
||||
|
||||
## Checklist de Produccion
|
||||
|
||||
- [ ] Variables de entorno configuradas
|
||||
- [ ] Credenciales seguras (.env.production)
|
||||
- [ ] Usuario dedicado creado
|
||||
- [ ] Permisos correctos asignados
|
||||
- [ ] Logs configurados
|
||||
- [ ] Health check funcionando
|
||||
- [ ] Backup automatizado configurado
|
||||
- [ ] Monitoreo activo
|
||||
- [ ] SSL/TLS configurado (si aplica)
|
||||
- [ ] Firewall configurado
|
||||
|
||||
## Recursos Adicionales
|
||||
|
||||
- `docs/SETUP.md` - Guia de configuracion inicial
|
||||
- `docs/TESTING.md` - Guia de testing
|
||||
- `ARCHITECTURE.md` - Documentacion arquitectonica
|
||||
337
docs/SETUP.md
337
docs/SETUP.md
@@ -1,337 +0,0 @@
|
||||
# Guia de Configuracion - CBCFacil
|
||||
|
||||
Esta guia describe los pasos para configurar el entorno de desarrollo de CBCFacil.
|
||||
|
||||
## Requisitos Previos
|
||||
|
||||
### Software Requerido
|
||||
|
||||
| Componente | Version Minima | Recomendada |
|
||||
|------------|----------------|-------------|
|
||||
| Python | 3.10 | 3.11+ |
|
||||
| Git | 2.0 | Latest |
|
||||
| NVIDIA Driver | 535+ | 550+ (para CUDA 12.1) |
|
||||
| Docker | 24.0 | 25.0+ (opcional) |
|
||||
| Docker Compose | 2.20 | Latest (opcional) |
|
||||
|
||||
### Hardware Recomendado
|
||||
|
||||
- **CPU**: 4+ nucleos
|
||||
- **RAM**: 8GB minimum (16GB+ recomendado)
|
||||
- **GPU**: NVIDIA con 4GB+ VRAM (opcional, soporta CPU)
|
||||
- **Almacenamiento**: 10GB+ libres
|
||||
|
||||
## Instalacion Paso a Paso
|
||||
|
||||
### 1. Clonar el Repositorio
|
||||
|
||||
```bash
|
||||
git clone <repo_url>
|
||||
cd cbcfacil
|
||||
```
|
||||
|
||||
### 2. Crear Entorno Virtual
|
||||
|
||||
```bash
|
||||
# Crear venv
|
||||
python3 -m venv .venv
|
||||
|
||||
# Activar (Linux/macOS)
|
||||
source .venv/bin/activate
|
||||
|
||||
# Activar (Windows)
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
|
||||
### 3. Instalar Dependencias
|
||||
|
||||
```bash
|
||||
# Actualizar pip
|
||||
pip install --upgrade pip
|
||||
|
||||
# Instalar dependencias de produccion
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Instalar dependencias de desarrollo (opcional)
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
### 4. Configurar Variables de Entorno
|
||||
|
||||
```bash
|
||||
# Copiar template de configuracion
|
||||
cp .env.example .env.secrets
|
||||
|
||||
# Editar con tus credenciales
|
||||
nano .env.secrets
|
||||
```
|
||||
|
||||
### 5. Estructura de Archivos Necesaria
|
||||
|
||||
```bash
|
||||
# Crear directorios requeridos
|
||||
mkdir -p downloads resumenes_docx logs
|
||||
|
||||
# Verificar estructura
|
||||
ls -la
|
||||
```
|
||||
|
||||
## Configuracion de Credenciales
|
||||
|
||||
### Nextcloud/WebDAV
|
||||
|
||||
```bash
|
||||
# Obligatorio para sincronizacion de archivos
|
||||
NEXTCLOUD_URL=https://tu-nextcloud.com/remote.php/webdav
|
||||
NEXTCLOUD_USER=tu_usuario
|
||||
NEXTCLOUD_PASSWORD=tu_contrasena
|
||||
```
|
||||
|
||||
### AI Providers (Opcional)
|
||||
|
||||
```bash
|
||||
# Claude via Z.ai
|
||||
ANTHROPIC_AUTH_TOKEN=sk-ant-...
|
||||
|
||||
# Google Gemini
|
||||
GEMINI_API_KEY=AIza...
|
||||
|
||||
# Gemini CLI (opcional)
|
||||
GEMINI_CLI_PATH=/usr/local/bin/gemini
|
||||
```
|
||||
|
||||
### Telegram (Opcional)
|
||||
|
||||
```bash
|
||||
TELEGRAM_TOKEN=bot_token
|
||||
TELEGRAM_CHAT_ID=chat_id
|
||||
```
|
||||
|
||||
### GPU Configuration (Opcional)
|
||||
|
||||
```bash
|
||||
# Usar GPU especifica
|
||||
CUDA_VISIBLE_DEVICES=0
|
||||
|
||||
# Usar todas las GPUs
|
||||
CUDA_VISIBLE_DEVICES=all
|
||||
|
||||
# Forzar CPU
|
||||
CUDA_VISIBLE_DEVICES=
|
||||
```
|
||||
|
||||
## Verificacion de Instalacion
|
||||
|
||||
### 1. Verificar Python
|
||||
|
||||
```bash
|
||||
python --version
|
||||
# Debe mostrar Python 3.10+
|
||||
```
|
||||
|
||||
### 2. Verificar Dependencias
|
||||
|
||||
```bash
|
||||
python -c "import torch; print(f'PyTorch: {torch.__version__}')"
|
||||
python -c "import flask; print(f'Flask: {flask.__version__}')"
|
||||
python -c "import whisper; print('Whisper instalado correctamente')"
|
||||
```
|
||||
|
||||
### 3. Verificar Configuracion
|
||||
|
||||
```bash
|
||||
python -c "from config import settings; print(f'WebDAV configurado: {settings.has_webdav_config}')"
|
||||
python -c "from config import settings; print(f'AI configurado: {settings.has_ai_config}')"
|
||||
```
|
||||
|
||||
### 4. Test Rapido
|
||||
|
||||
```bash
|
||||
# Ejecutar tests basicos
|
||||
pytest tests/test_config.py -v
|
||||
```
|
||||
|
||||
## Configuracion de Desarrollo
|
||||
|
||||
### IDE Recomendado
|
||||
|
||||
#### VS Code
|
||||
|
||||
```json
|
||||
// .vscode/settings.json
|
||||
{
|
||||
"python.defaultInterpreterPath": ".venv/bin/python",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"editor.formatOnSave": true,
|
||||
"python.formatting.provider": "black"
|
||||
}
|
||||
```
|
||||
|
||||
#### PyCharm
|
||||
|
||||
1. Open Settings > Project > Python Interpreter
|
||||
2. Add Interpreter > Existing Environment
|
||||
3. Select `.venv/bin/python`
|
||||
|
||||
### Git Hooks (Opcional)
|
||||
|
||||
```bash
|
||||
# Instalar pre-commit
|
||||
pip install pre-commit
|
||||
pre-commit install
|
||||
|
||||
# Verificar hooks
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
### Formateo de Codigo
|
||||
|
||||
```bash
|
||||
# Instalar formateadores
|
||||
pip install black isort
|
||||
|
||||
# Formatear codigo
|
||||
black .
|
||||
isort .
|
||||
|
||||
# Verificar estilo
|
||||
black --check .
|
||||
isort --check-only .
|
||||
```
|
||||
|
||||
## Ejecucion del Servicio
|
||||
|
||||
### Modo Desarrollo
|
||||
|
||||
```bash
|
||||
# Activar entorno virtual
|
||||
source .venv/bin/activate
|
||||
|
||||
# Ejecutar servicio completo
|
||||
python main.py
|
||||
|
||||
# Con logging verbose
|
||||
LOG_LEVEL=DEBUG python main.py
|
||||
```
|
||||
|
||||
### Comandos CLI Disponibles
|
||||
|
||||
```bash
|
||||
# Transcribir audio
|
||||
python main.py whisper <archivo_audio> <directorio_output>
|
||||
|
||||
# Procesar PDF
|
||||
python main.py pdf <archivo_pdf> <directorio_output>
|
||||
```
|
||||
|
||||
### Dashboard
|
||||
|
||||
El dashboard estara disponible en:
|
||||
- URL: http://localhost:5000
|
||||
- API: http://localhost:5000/api/
|
||||
|
||||
## Solucion de Problemas Comunes
|
||||
|
||||
### Error: "Module not found"
|
||||
|
||||
```bash
|
||||
# Verificar que el venv esta activado
|
||||
which python
|
||||
# Debe mostrar path hacia .venv/bin/python
|
||||
|
||||
# Reinstalar dependencias
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Error: "CUDA out of memory"
|
||||
|
||||
```bash
|
||||
# Reducir uso de GPU
|
||||
export CUDA_VISIBLE_DEVICES=
|
||||
|
||||
# O usar solo una GPU
|
||||
export CUDA_VISIBLE_DEVICES=0
|
||||
```
|
||||
|
||||
### Error: "WebDAV connection failed"
|
||||
|
||||
```bash
|
||||
# Verificar credenciales
|
||||
echo $NEXTCLOUD_URL
|
||||
echo $NEXTCLOUD_USER
|
||||
|
||||
# Probar conexion manualmente
|
||||
curl -u $NEXTCLOUD_USER:$NEXTCLOUD_PASSWORD $NEXTCLOUD_URL
|
||||
```
|
||||
|
||||
### Error: "Telegram token invalid"
|
||||
|
||||
```bash
|
||||
# Verificar token con BotFather
|
||||
# https://t.me/BotFather
|
||||
|
||||
# Verificar variables de entorno
|
||||
echo $TELEGRAM_TOKEN
|
||||
echo $TELEGRAM_CHAT_ID
|
||||
```
|
||||
|
||||
### Error: "Whisper model not found"
|
||||
|
||||
```bash
|
||||
# El modelo se descarga automaticamente la primera vez
|
||||
# Para forzar recarga:
|
||||
rm -rf ~/.cache/whisper
|
||||
```
|
||||
|
||||
## Configuracion de GPU (Opcional)
|
||||
|
||||
### Verificar Instalacion de CUDA
|
||||
|
||||
```bash
|
||||
# Verificar drivers NVIDIA
|
||||
nvidia-smi
|
||||
|
||||
# Verificar CUDA Toolkit
|
||||
nvcc --version
|
||||
|
||||
# Verificar PyTorch CUDA
|
||||
python -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}')"
|
||||
```
|
||||
|
||||
### Configurar Memoria GPU
|
||||
|
||||
```bash
|
||||
# En .env.secrets
|
||||
PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512
|
||||
```
|
||||
|
||||
## Variables de Entorno Completa
|
||||
|
||||
| Variable | Requerido | Default | Descripcion |
|
||||
|----------|-----------|---------|-------------|
|
||||
| NEXTCLOUD_URL | Si | - | URL WebDAV de Nextcloud |
|
||||
| NEXTCLOUD_USER | Si | - | Usuario Nextcloud |
|
||||
| NEXTCLOUD_PASSWORD | Si | - | Contrasena Nextcloud |
|
||||
| ANTHROPIC_AUTH_TOKEN | No | - | Token Claude/Z.ai |
|
||||
| GEMINI_API_KEY | No | - | API Key Gemini |
|
||||
| TELEGRAM_TOKEN | No | - | Token Bot Telegram |
|
||||
| TELEGRAM_CHAT_ID | No | - | Chat ID Telegram |
|
||||
| CUDA_VISIBLE_DEVICES | No | "all" | GPUs a usar |
|
||||
| POLL_INTERVAL | No | 5 | Segundos entre polls |
|
||||
| LOG_LEVEL | No | "INFO" | Nivel de logging |
|
||||
| DEBUG | No | false | Modo debug |
|
||||
| DASHBOARD_PORT | No | 5000 | Puerto del dashboard |
|
||||
| DASHBOARD_HOST | No | "0.0.0.0" | Host del dashboard |
|
||||
|
||||
## Siguientes Pasos
|
||||
|
||||
1. Verificar instalacion con tests
|
||||
2. Configurar integracion con Nextcloud
|
||||
3. Probar procesamiento de archivos
|
||||
4. Configurar notificaciones Telegram (opcional)
|
||||
|
||||
Ver juga:
|
||||
- `docs/TESTING.md` - Guia de testing
|
||||
- `docs/DEPLOYMENT.md` - Guia de despliegue
|
||||
- `ARCHITECTURE.md` - Documentacion arquitectonica
|
||||
482
docs/TESTING.md
482
docs/TESTING.md
@@ -1,482 +0,0 @@
|
||||
# Guia de Testing - CBCFacil
|
||||
|
||||
Esta guia describe como ejecutar y escribir tests para CBCFacil.
|
||||
|
||||
## Estructura de Tests
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Fixtures compartidos
|
||||
├── test_config.py # Tests de configuracion
|
||||
├── test_storage.py # Tests de almacenamiento
|
||||
├── test_webdav.py # Tests de WebDAV
|
||||
├── test_processors.py # Tests de procesadores
|
||||
├── test_ai_providers.py # Tests de AI providers
|
||||
├── test_vram_manager.py # Tests de VRAM manager
|
||||
└── test_main_integration.py # Tests de integracion
|
||||
```
|
||||
|
||||
## Instalacion de Dependencias de Test
|
||||
|
||||
```bash
|
||||
# Activar entorno virtual
|
||||
source .venv/bin/activate
|
||||
|
||||
# Instalar dependencias de desarrollo
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
# Verificar instalacion
|
||||
pytest --version
|
||||
```
|
||||
|
||||
## Ejecutar Tests
|
||||
|
||||
### Todos los Tests
|
||||
|
||||
```bash
|
||||
# Ejecutar todos los tests
|
||||
pytest tests/
|
||||
|
||||
# Con output detallado
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
### Tests Especificos
|
||||
|
||||
```bash
|
||||
# Tests de configuracion
|
||||
pytest tests/test_config.py -v
|
||||
|
||||
# Tests de almacenamiento
|
||||
pytest tests/test_storage.py -v
|
||||
|
||||
# Tests de WebDAV
|
||||
pytest tests/test_webdav.py -v
|
||||
|
||||
# Tests de procesadores
|
||||
pytest tests/test_processors.py -v
|
||||
|
||||
# Tests de AI providers
|
||||
pytest tests/test_ai_providers.py -v
|
||||
|
||||
# Tests de VRAM manager
|
||||
pytest tests/test_vram_manager.py -v
|
||||
|
||||
# Tests de integracion
|
||||
pytest tests/test_main_integration.py -v
|
||||
```
|
||||
|
||||
### Tests con Coverage
|
||||
|
||||
```bash
|
||||
# Coverage basico
|
||||
pytest tests/ --cov=cbcfacil
|
||||
|
||||
# Coverage con reporte HTML
|
||||
pytest tests/ --cov=cbcfacil --cov-report=html
|
||||
|
||||
# Coverage con reporte term-missing
|
||||
pytest tests/ --cov=cbcfacil --cov-report=term-missing
|
||||
|
||||
# Coverage por modulo
|
||||
pytest tests/ --cov=cbcfacil --cov-report=term-missing --cov-report=annotate
|
||||
```
|
||||
|
||||
### Tests en Modo Watch
|
||||
|
||||
```bash
|
||||
# Recargar automaticamente al detectar cambios
|
||||
pytest-watch tests/
|
||||
```
|
||||
|
||||
### Tests Parallelos
|
||||
|
||||
```bash
|
||||
# Ejecutar tests en paralelo
|
||||
pytest tests/ -n auto
|
||||
|
||||
# Con numero fijo de workers
|
||||
pytest tests/ -n 4
|
||||
```
|
||||
|
||||
## Escribir Nuevos Tests
|
||||
|
||||
### Estructura Basica
|
||||
|
||||
```python
|
||||
# tests/test_ejemplo.py
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
class TestEjemplo:
|
||||
"""Clase de tests para un modulo"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup antes de cada test"""
|
||||
pass
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup despues de cada test"""
|
||||
pass
|
||||
|
||||
def test_funcion_basica(self):
|
||||
"""Test de una funcion basica"""
|
||||
# Arrange
|
||||
input_value = "test"
|
||||
|
||||
# Act
|
||||
result = mi_funcion(input_value)
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
assert result == "expected"
|
||||
```
|
||||
|
||||
### Usar Fixtures
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
@pytest.fixture
|
||||
def temp_directory(tmp_path):
|
||||
"""Fixture para directorio temporal"""
|
||||
dir_path = tmp_path / "test_files"
|
||||
dir_path.mkdir()
|
||||
return dir_path
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings():
|
||||
"""Fixture con settings de prueba"""
|
||||
class MockSettings:
|
||||
NEXTCLOUD_URL = "https://test.example.com"
|
||||
NEXTCLOUD_USER = "test_user"
|
||||
NEXTCLOUD_PASSWORD = "test_pass"
|
||||
return MockSettings()
|
||||
|
||||
# En tu test
|
||||
def test_con_fixture(temp_directory, mock_settings):
|
||||
"""Test usando fixtures"""
|
||||
assert temp_directory.exists()
|
||||
assert mock_settings.NEXTCLOUD_URL == "https://test.example.com"
|
||||
```
|
||||
|
||||
### Tests de Configuracion
|
||||
|
||||
```python
|
||||
# tests/test_config.py
|
||||
import pytest
|
||||
from config import settings
|
||||
|
||||
class TestSettings:
|
||||
"""Tests para configuracion"""
|
||||
|
||||
def test_has_webdav_config_true(self):
|
||||
"""Test con WebDAV configurado"""
|
||||
# Verificar que las properties funcionan
|
||||
assert hasattr(settings, 'has_webdav_config')
|
||||
assert hasattr(settings, 'has_ai_config')
|
||||
|
||||
def test_processed_files_path(self):
|
||||
"""Test del path de archivos procesados"""
|
||||
path = settings.processed_files_path
|
||||
assert isinstance(path, Path)
|
||||
assert path.suffix == ".txt"
|
||||
```
|
||||
|
||||
### Tests de WebDAV
|
||||
|
||||
```python
|
||||
# tests/test_webdav.py
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
class TestWebDAVService:
|
||||
"""Tests para WebDAV Service"""
|
||||
|
||||
@pytest.fixture
|
||||
def webdav_service(self):
|
||||
"""Crear instancia del servicio"""
|
||||
from services.webdav_service import webdav_service
|
||||
return webdav_service
|
||||
|
||||
def test_list_remote_path(self, webdav_service):
|
||||
"""Test de listado de archivos remotos"""
|
||||
# Mock del cliente WebDAV
|
||||
with patch('services.webdav_service.WebDAVClient') as mock_client:
|
||||
mock_instance = Mock()
|
||||
mock_instance.list.return_value = ['file1.pdf', 'file2.mp3']
|
||||
mock_client.return_value = mock_instance
|
||||
|
||||
# Inicializar servicio
|
||||
webdav_service.initialize()
|
||||
|
||||
# Test
|
||||
files = webdav_service.list("TestFolder")
|
||||
assert len(files) == 2
|
||||
assert "file1.pdf" in files
|
||||
```
|
||||
|
||||
### Tests de Procesadores
|
||||
|
||||
```python
|
||||
# tests/test_processors.py
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
class TestAudioProcessor:
|
||||
"""Tests para Audio Processor"""
|
||||
|
||||
@pytest.fixture
|
||||
def processor(self):
|
||||
"""Crear procesador"""
|
||||
from processors.audio_processor import AudioProcessor
|
||||
return AudioProcessor()
|
||||
|
||||
def test_process_audio_file(self, processor, tmp_path):
|
||||
"""Test de procesamiento de audio"""
|
||||
# Crear archivo de prueba
|
||||
audio_file = tmp_path / "test.mp3"
|
||||
audio_file.write_bytes(b"fake audio content")
|
||||
|
||||
# Mock de Whisper
|
||||
with patch('processors.audio_processor.whisper') as mock_whisper:
|
||||
mock_whisper.load_model.return_value.transcribe.return_value = {
|
||||
"text": "Texto transcrito de prueba"
|
||||
}
|
||||
|
||||
# Ejecutar
|
||||
result = processor.process(str(audio_file))
|
||||
|
||||
# Verificar
|
||||
assert result is not None
|
||||
```
|
||||
|
||||
### Tests de AI Providers
|
||||
|
||||
```python
|
||||
# tests/test_ai_providers.py
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
class TestClaudeProvider:
|
||||
"""Tests para Claude Provider"""
|
||||
|
||||
def test_summarize_text(self):
|
||||
"""Test de resumen con Claude"""
|
||||
from services.ai.claude_provider import ClaudeProvider
|
||||
|
||||
provider = ClaudeProvider()
|
||||
test_text = "Texto largo para resumir..."
|
||||
|
||||
# Mock de la llamada API
|
||||
with patch.object(provider, '_call_api') as mock_call:
|
||||
mock_call.return_value = "Texto resumido"
|
||||
|
||||
result = provider.summarize(test_text)
|
||||
|
||||
assert result == "Texto resumido"
|
||||
mock_call.assert_called_once()
|
||||
```
|
||||
|
||||
### Tests de Integracion
|
||||
|
||||
```python
|
||||
# tests/test_main_integration.py
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
class TestMainIntegration:
|
||||
"""Tests de integracion del main"""
|
||||
|
||||
def test_main_loop_no_files(self):
|
||||
"""Test del loop principal sin archivos nuevos"""
|
||||
with patch('main.webdav_service') as mock_webdav:
|
||||
with patch('main.processed_registry') as mock_registry:
|
||||
mock_webdav.list.return_value = []
|
||||
mock_registry.is_processed.return_value = True
|
||||
|
||||
# El loop no debe procesar nada
|
||||
# Verificar que no se llama a procesadores
|
||||
```
|
||||
|
||||
## Configuracion de pytest
|
||||
|
||||
```ini
|
||||
# pytest.ini o pyproject.toml
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = [
|
||||
"-v",
|
||||
"--tb=short",
|
||||
"--strict-markers",
|
||||
]
|
||||
filterwarnings = [
|
||||
"ignore::DeprecationWarning",
|
||||
]
|
||||
```
|
||||
|
||||
## Mejores Practicas
|
||||
|
||||
### 1. Nombrado de Tests
|
||||
|
||||
```python
|
||||
# BIEN
|
||||
def test_webdav_service_list_returns_files():
|
||||
...
|
||||
|
||||
def test_processed_registry_is_processed_true_for_processed_file():
|
||||
...
|
||||
|
||||
# MAL
|
||||
def test_list():
|
||||
...
|
||||
|
||||
def test_check():
|
||||
...
|
||||
```
|
||||
|
||||
### 2. Estructura AAA
|
||||
|
||||
```python
|
||||
def test_ejemplo_aaa():
|
||||
# Arrange
|
||||
input_data = {"key": "value"}
|
||||
expected = "result"
|
||||
|
||||
# Act
|
||||
actual = function_under_test(input_data)
|
||||
|
||||
# Assert
|
||||
assert actual == expected
|
||||
```
|
||||
|
||||
### 3. Tests Aislados
|
||||
|
||||
```python
|
||||
# Cada test debe ser independiente
|
||||
def test_independent():
|
||||
# No depender de estado de otros tests
|
||||
# Usar fixtures para setup/cleanup
|
||||
pass
|
||||
```
|
||||
|
||||
### 4. Evitar TestLego
|
||||
|
||||
```python
|
||||
# BIEN - Test del comportamiento, no la implementacion
|
||||
def test_registry_returns_true_for_processed_file():
|
||||
registry = ProcessedRegistry()
|
||||
registry.save("file.txt")
|
||||
assert registry.is_processed("file.txt") is True
|
||||
|
||||
# MAL - Test de implementacion
|
||||
def test_registry_uses_set_internally():
|
||||
# No testar detalles de implementacion
|
||||
registry = ProcessedRegistry()
|
||||
assert hasattr(registry, '_processed_files')
|
||||
```
|
||||
|
||||
### 5. Mocks Appropriados
|
||||
|
||||
```python
|
||||
# Usar mocks para dependencias externas
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
|
||||
def test_with_mocked_api():
|
||||
with patch('requests.get') as mock_get:
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {"key": "value"}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
result = my_api_function()
|
||||
|
||||
assert result == {"key": "value"}
|
||||
```
|
||||
|
||||
## Coverage Objetivo
|
||||
|
||||
| Componente | Coverage Minimo |
|
||||
|------------|-----------------|
|
||||
| config/ | 90% |
|
||||
| core/ | 90% |
|
||||
| services/ | 70% |
|
||||
| processors/ | 60% |
|
||||
| storage/ | 90% |
|
||||
| api/ | 80% |
|
||||
|
||||
## Integracion con CI/CD
|
||||
|
||||
```yaml
|
||||
# .github/workflows/tests.yml
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
pytest tests/ --cov=cbcfacil --cov-report=xml
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tests Fallan por Imports
|
||||
|
||||
```bash
|
||||
# Verificar que el venv esta activado
|
||||
source .venv/bin/activate
|
||||
|
||||
# Reinstalar el paquete en modo desarrollo
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### Tests Muy Lentos
|
||||
|
||||
```bash
|
||||
# Ejecutar en paralelo
|
||||
pytest tests/ -n auto
|
||||
|
||||
# O ejecutar solo tests rapidos
|
||||
pytest tests/ -m "not slow"
|
||||
```
|
||||
|
||||
### Memory Errors
|
||||
|
||||
```bash
|
||||
# Reducir workers
|
||||
pytest tests/ -n 2
|
||||
|
||||
# O ejecutar secuencial
|
||||
pytest tests/ -n 0
|
||||
```
|
||||
|
||||
## Recursos Adicionales
|
||||
|
||||
- [Documentacion de pytest](https://docs.pytest.org/)
|
||||
- [Documentacion de unittest.mock](https://docs.python.org/3/library/unittest.mock.html)
|
||||
- [pytest-cov](https://pytest-cov.readthedocs.io/)
|
||||
@@ -1,207 +0,0 @@
|
||||
# Pipeline de Generación de Resúmenes Matemáticos (LaTeX -> PDF)
|
||||
|
||||
Este documento contiene un script genérico en Python diseñado para integrarse en pipelines de automatización (GitHub Actions, Jenkins, GitLab CI). El script toma un archivo de texto plano, genera un resumen académico con fórmulas matemáticas usando LLMs (MiniMax, GLM, Gemini) y lo compila a PDF preservando la notación LaTeX.
|
||||
|
||||
## 1. Requisitos del Sistema
|
||||
|
||||
El entorno donde se ejecute este script debe tener instalado:
|
||||
|
||||
- **Python 3.8+**
|
||||
- **Pandoc** (para conversión de documentos)
|
||||
- **PDFLaTeX** (generalmente parte de TexLive, para renderizar fórmulas)
|
||||
|
||||
### Instalación en Debian/Ubuntu (Docker o CI)
|
||||
```bash
|
||||
apt-get update && apt-get install -y pandoc texlive-latex-base texlive-fonts-recommended python3-pip
|
||||
pip install requests
|
||||
```
|
||||
|
||||
## 2. Script Genérico (`math_summary.py`)
|
||||
|
||||
Guarda el siguiente código como `math_summary.py`. Este script es agnóstico al proveedor y se configura mediante argumentos o variables de entorno.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Configuración de Modelos
|
||||
PROVIDERS = {
|
||||
"minimax": {
|
||||
"url": "https://api.minimax.io/anthropic/v1/messages",
|
||||
"model": "MiniMax-M2",
|
||||
"header_key": "x-api-key",
|
||||
"version_header": {"anthropic-version": "2023-06-01"},
|
||||
"env_var": "MINIMAX_API_KEY"
|
||||
},
|
||||
"glm": {
|
||||
"url": "https://api.z.ai/api/anthropic/v1/messages",
|
||||
"model": "glm-4.7",
|
||||
"header_key": "x-api-key",
|
||||
"version_header": {"anthropic-version": "2023-06-01"},
|
||||
"env_var": "GLM_API_KEY"
|
||||
}
|
||||
}
|
||||
|
||||
PROMPT_SYSTEM = """
|
||||
Eres un asistente académico experto en matemáticas y economía.
|
||||
Tu tarea es resumir el texto proporcionado manteniendo el rigor científico.
|
||||
|
||||
REGLAS DE FORMATO (CRÍTICO):
|
||||
1. La salida debe ser Markdown válido.
|
||||
2. TODAS las fórmulas matemáticas deben estar en formato LaTeX.
|
||||
3. Usa bloques $$ ... $$ para ecuaciones centradas importantes.
|
||||
4. Usa $ ... $ para ecuaciones en línea.
|
||||
5. NO uses bloques de código (```latex) para las fórmulas, úsalas directamente en el texto para que Pandoc las renderice.
|
||||
6. Incluye una sección de 'Conceptos Matemáticos' con las fórmulas desglosadas.
|
||||
"""
|
||||
|
||||
def get_api_key(provider):
|
||||
env_var = PROVIDERS[provider]["env_var"]
|
||||
key = os.getenv(env_var)
|
||||
if not key:
|
||||
print(f"Error: La variable de entorno {env_var} no está definida.")
|
||||
sys.exit(1)
|
||||
return key
|
||||
|
||||
def call_llm(provider, text, api_key):
|
||||
print(f"--- Contactando API: {provider.upper()} ---")
|
||||
|
||||
config = PROVIDERS[provider]
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
config["header_key"]: api_key,
|
||||
}
|
||||
if "version_header" in config:
|
||||
headers.update(config["version_header"])
|
||||
|
||||
payload = {
|
||||
"model": config["model"],
|
||||
"max_tokens": 4096,
|
||||
"messages": [
|
||||
{"role": "user", "content": f"{PROMPT_SYSTEM}\n\nTEXTO A RESUMIR:\n{text}"}
|
||||
]
|
||||
}
|
||||
|
||||
try:
|
||||
resp = requests.post(config["url"], json=payload, headers=headers, timeout=120)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
# Manejo específico para MiniMax que puede devolver bloques de "thinking"
|
||||
content = ""
|
||||
for part in data.get("content", []):
|
||||
if part.get("type") == "text":
|
||||
content += part.get("text", "")
|
||||
|
||||
# Fallback si no hay tipo explícito (GLM estándar)
|
||||
if not content and data.get("content"):
|
||||
if isinstance(data["content"], list):
|
||||
content = data["content"][0].get("text", "")
|
||||
|
||||
return content
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error llamando a {provider}: {e}")
|
||||
return None
|
||||
|
||||
def convert_to_pdf(markdown_content, output_file):
|
||||
base_name = os.path.splitext(output_file)[0]
|
||||
md_file = f"{base_name}.md"
|
||||
|
||||
with open(md_file, "w", encoding="utf-8") as f:
|
||||
f.write(markdown_content)
|
||||
|
||||
print(f"--- Generando PDF: {output_file} ---")
|
||||
cmd = [
|
||||
"pandoc", md_file,
|
||||
"-o", output_file,
|
||||
"--pdf-engine=pdflatex",
|
||||
"-V", "geometry:margin=2.5cm",
|
||||
"-V", "fontsize=12pt",
|
||||
"--highlight-style=tango"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print("Éxito: PDF generado correctamente.")
|
||||
return True
|
||||
else:
|
||||
print("Error en Pandoc:")
|
||||
print(result.stderr)
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generador de Resúmenes Matemáticos PDF")
|
||||
parser.add_argument("input_file", help="Ruta al archivo de texto (.txt) fuente")
|
||||
parser.add_argument("--provider", choices=["minimax", "glm"], default="glm", help="Proveedor de IA a usar")
|
||||
parser.add_argument("--output", default="resumen_output.pdf", help="Nombre del archivo PDF de salida")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.input_file):
|
||||
print(f"Error: No se encuentra el archivo {args.input_file}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(args.input_file, "r", encoding="utf-8") as f:
|
||||
text_content = f.read()
|
||||
|
||||
api_key = get_api_key(args.provider)
|
||||
summary_md = call_llm(args.provider, text_content, api_key)
|
||||
|
||||
if summary_md:
|
||||
convert_to_pdf(summary_md, args.output)
|
||||
else:
|
||||
print("Fallo en la generación del resumen.")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
## 3. Ejemplo de Uso en Pipeline
|
||||
|
||||
### Ejecución Local
|
||||
```bash
|
||||
export GLM_API_KEY="tu_api_key_aqui"
|
||||
python3 math_summary.py entrada.txt --provider glm --output reporte_final.pdf
|
||||
```
|
||||
|
||||
### GitHub Actions (Ejemplo .yaml)
|
||||
Este paso automatizaría la creación del PDF cada vez que se sube un .txt a la carpeta `docs/`.
|
||||
|
||||
```yaml
|
||||
name: Generar PDF Matemático
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/*.txt'
|
||||
|
||||
jobs:
|
||||
build-pdf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Instalar dependencias
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pandoc texlive-latex-base texlive-fonts-recommended
|
||||
pip install requests
|
||||
|
||||
- name: Generar Resumen
|
||||
env:
|
||||
GLM_API_KEY: ${{ secrets.GLM_API_KEY }}
|
||||
run: |
|
||||
python3 math_summary.py docs/archivo.txt --provider glm --output docs/resumen.pdf
|
||||
|
||||
- name: Subir Artefacto
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: PDF-Resumen
|
||||
path: docs/resumen.pdf
|
||||
```
|
||||
221
latex/resumen.md
221
latex/resumen.md
@@ -1,221 +0,0 @@
|
||||
# Prompt para Generar Resúmenes Académicos en LaTeX
|
||||
|
||||
## Instrucciones de Uso
|
||||
|
||||
1. Transcribir la clase (audio a texto) usando Whisper o similar
|
||||
2. Tener el material bibliográfico en formato digital (PDF escaneado con OCR o texto)
|
||||
3. Copiar el prompt de abajo y completar los campos entre `[corchetes]`
|
||||
|
||||
---
|
||||
|
||||
## Prompt Template
|
||||
|
||||
```
|
||||
Sos un asistente académico experto en [MATERIA]. Tu tarea es crear un resumen extenso y detallado en LaTeX basado en la transcripción de clase y el material bibliográfico que te proporciono.
|
||||
|
||||
## Material de entrada
|
||||
|
||||
### Transcripción de clase:
|
||||
[PEGAR TRANSCRIPCIÓN AQUÍ]
|
||||
|
||||
### Material bibliográfico de apoyo:
|
||||
[PEGAR TEXTO DEL LIBRO/APUNTE O INDICAR QUE LO SUBISTE COMO ARCHIVO]
|
||||
|
||||
## Requisitos del resumen
|
||||
|
||||
### Extensión y profundidad:
|
||||
- Mínimo 10 páginas
|
||||
- Cubrir TODOS los temas mencionados en clase
|
||||
- Expandir cada concepto con definiciones formales del material bibliográfico
|
||||
- No resumir demasiado: preferir explicaciones completas
|
||||
|
||||
### Estructura obligatoria:
|
||||
1. Portada con título, materia, fecha y tema
|
||||
2. Índice (table of contents)
|
||||
3. Introducción contextualizando el tema
|
||||
4. Desarrollo organizado en secciones y subsecciones
|
||||
5. Tablas comparativas cuando haya clasificaciones o tipos
|
||||
6. Diagramas con TikZ cuando haya procesos, flujos o relaciones
|
||||
7. Cajas destacadas para definiciones, ejemplos y conceptos importantes
|
||||
8. Fórmulas matemáticas cuando corresponda
|
||||
9. Glosario de términos técnicos al final
|
||||
10. Referencias al material bibliográfico
|
||||
|
||||
### Formato LaTeX requerido:
|
||||
|
||||
```latex
|
||||
\documentclass[11pt,a4paper]{article}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[spanish,provide=*]{babel}
|
||||
\usepackage{amsmath,amssymb}
|
||||
\usepackage{geometry}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{tikz}
|
||||
\usetikzlibrary{arrows.meta,positioning,shapes.geometric,calc}
|
||||
\usepackage{booktabs}
|
||||
\usepackage{enumitem}
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{titlesec}
|
||||
\usepackage{tcolorbox}
|
||||
\usepackage{array}
|
||||
\usepackage{multirow}
|
||||
|
||||
\geometry{margin=2.5cm}
|
||||
\pagestyle{fancy}
|
||||
\fancyhf{}
|
||||
\fancyhead[L]{[MATERIA] - CBC}
|
||||
\fancyhead[R]{Clase [N]}
|
||||
\fancyfoot[C]{\thepage}
|
||||
|
||||
% Cajas para destacar contenido
|
||||
\newtcolorbox{definicion}[1][]{
|
||||
colback=blue!5!white,
|
||||
colframe=blue!75!black,
|
||||
fonttitle=\bfseries,
|
||||
title=#1
|
||||
}
|
||||
|
||||
\newtcolorbox{importante}[1][]{
|
||||
colback=red!5!white,
|
||||
colframe=red!75!black,
|
||||
fonttitle=\bfseries,
|
||||
title=#1
|
||||
}
|
||||
|
||||
\newtcolorbox{ejemplo}[1][]{
|
||||
colback=green!5!white,
|
||||
colframe=green!50!black,
|
||||
fonttitle=\bfseries,
|
||||
title=#1
|
||||
}
|
||||
```
|
||||
|
||||
### Estilo de contenido:
|
||||
- Usar \textbf{} para términos clave en su primera aparición
|
||||
- Usar \textit{} para énfasis y palabras en otros idiomas
|
||||
- Incluir ejemplos concretos mencionados en clase
|
||||
- Relacionar teoría con casos prácticos
|
||||
- Mantener el tono académico pero accesible
|
||||
- Si el profesor hizo énfasis en algo ("esto es importante", "esto entra en el parcial"), destacarlo en caja roja
|
||||
|
||||
### Elementos visuales:
|
||||
- Tablas con booktabs para comparaciones (usar \toprule, \midrule, \bottomrule)
|
||||
- Diagramas TikZ para flujos, ciclos o relaciones entre conceptos
|
||||
- Listas itemize/enumerate para secuencias o características
|
||||
- Fórmulas centradas con equation o align para expresiones matemáticas
|
||||
|
||||
## Ejemplo de calidad esperada
|
||||
|
||||
Para cada concepto principal:
|
||||
1. Definición formal (del libro)
|
||||
2. Explicación en palabras simples (como lo explicó el profesor)
|
||||
3. Ejemplo concreto
|
||||
4. Relación con otros conceptos
|
||||
5. Por qué es importante / para qué sirve
|
||||
|
||||
## Output
|
||||
|
||||
Generá el archivo .tex completo, listo para compilar con pdflatex (dos pasadas para el índice).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comandos para compilar
|
||||
|
||||
```bash
|
||||
# Compilar (dos veces para índice)
|
||||
pdflatex resumen_clase_X.tex
|
||||
pdflatex resumen_clase_X.tex
|
||||
|
||||
# Abrir PDF
|
||||
xdg-open resumen_clase_X.pdf # Linux
|
||||
open resumen_clase_X.pdf # macOS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pipeline completo
|
||||
|
||||
### 1. Transcripción de audio (con Whisper)
|
||||
|
||||
```bash
|
||||
# Instalar whisper
|
||||
pip install openai-whisper
|
||||
|
||||
# Transcribir audio de clase
|
||||
whisper "clase_X.mp3" --language Spanish --output_format txt
|
||||
```
|
||||
|
||||
### 2. OCR de PDFs escaneados (con marker-pdf)
|
||||
|
||||
```bash
|
||||
# Crear entorno virtual
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Instalar marker
|
||||
pip install marker-pdf
|
||||
|
||||
# Procesar PDF (usa GPU si está disponible)
|
||||
marker_single "libro_capitulo_X.pdf" --output_dir output/
|
||||
```
|
||||
|
||||
### 3. Generar resumen
|
||||
|
||||
Usar el prompt de arriba con:
|
||||
- Claude (Anthropic)
|
||||
- GPT-4 (OpenAI)
|
||||
- Gemini (Google)
|
||||
|
||||
### 4. Compilar LaTeX
|
||||
|
||||
```bash
|
||||
pdflatex resumen.tex && pdflatex resumen.tex
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tips para mejores resultados
|
||||
|
||||
1. **Transcripción completa**: No cortar la transcripción, la IA necesita todo el contexto
|
||||
2. **Material bibliográfico**: Incluir los capítulos específicos, no todo el libro
|
||||
3. **Ser específico**: Indicar la materia, el nivel (CBC, carrera, posgrado) y el enfoque del profesor
|
||||
4. **Iterar**: Si el primer resultado es corto, pedir "expandí la sección X con más detalle"
|
||||
5. **Diagramas**: Si hay un diagrama importante, describirlo y pedir que lo haga en TikZ
|
||||
6. **Revisar**: La IA puede cometer errores conceptuales, siempre verificar con el material
|
||||
|
||||
---
|
||||
|
||||
## Materias donde funciona bien
|
||||
|
||||
- Economía (micro/macro)
|
||||
- Física
|
||||
- Química
|
||||
- Matemática (álgebra, análisis)
|
||||
- Biología
|
||||
- Sociología
|
||||
- Historia
|
||||
- Derecho (con adaptaciones)
|
||||
- Cualquier materia con contenido teórico estructurado
|
||||
|
||||
---
|
||||
|
||||
## Ejemplo de uso rápido
|
||||
|
||||
```
|
||||
Sos un asistente académico experto en Física. Creá un resumen extenso en LaTeX sobre "Cinemática" basado en esta transcripción de clase del CBC:
|
||||
|
||||
[pegar transcripción]
|
||||
|
||||
Material de apoyo: Capítulo 2 de Serway "Movimiento en una dimensión":
|
||||
|
||||
[pegar texto del capítulo]
|
||||
|
||||
Incluí:
|
||||
- Definiciones de posición, velocidad, aceleración
|
||||
- Fórmulas del MRU y MRUV
|
||||
- Diagramas de movimiento con TikZ
|
||||
- Gráficos posición-tiempo y velocidad-tiempo
|
||||
- Ejemplos resueltos paso a paso
|
||||
- Glosario de términos
|
||||
```
|
||||
799
plus.md
799
plus.md
@@ -1,799 +0,0 @@
|
||||
# 🚀 CBCFacil - Mejoras y Extensiones Recomendadas
|
||||
|
||||
Documento con recomendaciones para hacer el proyecto más complejo, robusto y profesional.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Resumen Ejecutivo
|
||||
|
||||
Después de analizar todo el proyecto, identifiqué las siguientes áreas principales de mejora:
|
||||
|
||||
| Área | Prioridad | Complejidad | Estado Actual |
|
||||
|------|-----------|-------------|---------------|
|
||||
| Testing | 🔴 Alta | Media | Solo `conftest.py` existe |
|
||||
| Frontend Dashboard | 🔴 Alta | Alta | Template básico sin JS |
|
||||
| Sistema de Colas | 🟡 Media | Alta | Loop síncrono simple |
|
||||
| Autenticación API | 🔴 Alta | Media | Sin autenticación |
|
||||
| Base de Datos | 🟡 Media | Media | Solo archivo TXT |
|
||||
| Métricas/Observabilidad | 🟡 Media | Media | Básico |
|
||||
| Video Processor | 🟢 Baja | Alta | No existe |
|
||||
| WebSockets | 🟢 Baja | Media | No existe |
|
||||
| Internacionalización | 🟢 Baja | Baja | Solo español |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 1. Testing Completo (CRÍTICO)
|
||||
|
||||
### Estado Actual
|
||||
- Solo existe `tests/conftest.py` y `tests/__init__.py`
|
||||
- No hay tests unitarios ni de integración implementados
|
||||
- Arquitectura mencionada en `ARCHITECTURE.md` indica ~60% cobertura (falso)
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 1.1 Tests Unitarios
|
||||
```
|
||||
tests/
|
||||
├── unit/
|
||||
│ ├── test_settings.py # Validar configuración
|
||||
│ ├── test_validators.py # Validar validators.py
|
||||
│ ├── test_result.py # Patrón Result
|
||||
│ ├── test_exceptions.py # Excepciones personalizadas
|
||||
│ ├── test_bloom_filter.py # BloomFilter en registry
|
||||
│ ├── test_token_bucket.py # Rate limiter
|
||||
│ └── test_circuit_breaker.py # Circuit breaker
|
||||
```
|
||||
|
||||
#### 1.2 Tests de Integración
|
||||
```
|
||||
tests/
|
||||
├── integration/
|
||||
│ ├── test_webdav_service.py # Mock de Nextcloud
|
||||
│ ├── test_telegram_service.py # Mock de Telegram API
|
||||
│ ├── test_ai_providers.py # Mock de APIs AI
|
||||
│ ├── test_audio_processor.py # Con audio de prueba
|
||||
│ ├── test_pdf_processor.py # Con PDF de prueba
|
||||
│ └── test_document_generator.py
|
||||
```
|
||||
|
||||
#### 1.3 Tests E2E
|
||||
```
|
||||
tests/
|
||||
├── e2e/
|
||||
│ ├── test_full_audio_workflow.py
|
||||
│ ├── test_full_pdf_workflow.py
|
||||
│ └── test_api_endpoints.py
|
||||
```
|
||||
|
||||
#### 1.4 Fixtures de Prueba
|
||||
```python
|
||||
# tests/fixtures/
|
||||
# - sample_audio.mp3 (5 segundos de audio en español)
|
||||
# - sample_pdf.pdf (2 páginas con texto)
|
||||
# - expected_transcription.txt
|
||||
# - expected_summary.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ 2. Dashboard Frontend Completo
|
||||
|
||||
### Estado Actual
|
||||
- Solo existe `templates/` con un archivo básico
|
||||
- API REST sin interfaz visual
|
||||
- Sin JavaScript interactivo
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 2.1 Estructura Frontend
|
||||
```
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── FileList.js # Lista de archivos
|
||||
│ │ ├── FileCard.js # Tarjeta individual
|
||||
│ │ ├── ProcessingStatus.js # Estado en tiempo real
|
||||
│ │ ├── GPUMonitor.js # Monitor VRAM
|
||||
│ │ ├── QueueViewer.js # Cola de procesamiento
|
||||
│ │ └── NotificationBell.js # Notificaciones
|
||||
│ ├── pages/
|
||||
│ │ ├── Dashboard.js # Vista principal
|
||||
│ │ ├── Files.js # Gestión de archivos
|
||||
│ │ ├── Settings.js # Configuración
|
||||
│ │ └── Logs.js # Visor de logs
|
||||
│ └── services/
|
||||
│ ├── api.js # Cliente API
|
||||
│ └── websocket.js # Conexión WS
|
||||
├── public/
|
||||
│ └── index.html
|
||||
└── package.json
|
||||
```
|
||||
|
||||
#### 2.2 Funcionalidades
|
||||
- [ ] Drag & drop para subir archivos
|
||||
- [ ] Preview de PDFs y audio
|
||||
- [ ] Visor de transcripciones lado a lado
|
||||
- [ ] Editor de resúmenes con Markdown preview
|
||||
- [ ] Gráficas de uso de GPU/CPU
|
||||
- [ ] Historial de procesamiento
|
||||
- [ ] Búsqueda en contenido
|
||||
- [ ] Dark mode / Light mode
|
||||
|
||||
---
|
||||
|
||||
## 📬 3. Sistema de Colas (Celery/RQ)
|
||||
|
||||
### Estado Actual
|
||||
- Loop infinito síncrono en `main.py`
|
||||
- Sin priorización de tareas
|
||||
- Sin reintentos configurables
|
||||
- Sin distribución de carga
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 3.1 Implementar Celery
|
||||
```python
|
||||
# services/queue/
|
||||
├── __init__.py
|
||||
├── celery_app.py # Configuración Celery
|
||||
├── tasks/
|
||||
│ ├── __init__.py
|
||||
│ ├── audio_tasks.py # Tareas de audio
|
||||
│ ├── pdf_tasks.py # Tareas de PDF
|
||||
│ └── notification_tasks.py
|
||||
└── workers/
|
||||
└── worker_config.py
|
||||
```
|
||||
|
||||
#### 3.2 Estructura de Tareas
|
||||
```python
|
||||
# tasks/audio_tasks.py
|
||||
from celery import shared_task
|
||||
|
||||
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
|
||||
def process_audio(self, file_path: str, options: dict) -> dict:
|
||||
"""Procesar audio con reintentos automáticos"""
|
||||
...
|
||||
|
||||
@shared_task
|
||||
def transcribe_audio(file_path: str) -> str:
|
||||
"""Transcribir audio con Whisper"""
|
||||
...
|
||||
|
||||
@shared_task
|
||||
def generate_summary(transcription: str, base_name: str) -> dict:
|
||||
"""Generar resumen con IA"""
|
||||
...
|
||||
```
|
||||
|
||||
#### 3.3 Prioridades de Cola
|
||||
- `high`: Archivos pequeños (<10MB)
|
||||
- `default`: Archivos normales
|
||||
- `low`: Archivos grandes (>100MB)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 4. Autenticación y Autorización
|
||||
|
||||
### Estado Actual
|
||||
- API completamente abierta
|
||||
- Sin manejo de sesiones
|
||||
- Sin roles de usuario
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 4.1 Implementar JWT
|
||||
```python
|
||||
# api/auth/
|
||||
├── __init__.py
|
||||
├── jwt_handler.py # Generación/validación JWT
|
||||
├── middleware.py # Middleware de autenticación
|
||||
├── decorators.py # @require_auth, @require_admin
|
||||
└── models.py # User, Role, Permission
|
||||
```
|
||||
|
||||
#### 4.2 Endpoints de Auth
|
||||
```python
|
||||
# api/routes_auth.py
|
||||
POST /api/auth/login # Login con usuario/password
|
||||
POST /api/auth/refresh # Refrescar token
|
||||
POST /api/auth/logout # Invalidar token
|
||||
GET /api/auth/me # Perfil del usuario
|
||||
```
|
||||
|
||||
#### 4.3 Roles Sugeridos
|
||||
- `admin`: Acceso completo
|
||||
- `processor`: Puede procesar archivos
|
||||
- `viewer`: Solo lectura
|
||||
- `api`: Acceso solo API (para integraciones)
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ 5. Base de Datos (SQLite/PostgreSQL)
|
||||
|
||||
### Estado Actual
|
||||
- Solo `processed_files.txt` como registro
|
||||
- Sin historial de procesamiento
|
||||
- Sin metadatos de archivos
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 5.1 Modelos de Base de Datos
|
||||
```python
|
||||
# storage/models/
|
||||
├── __init__.py
|
||||
├── base.py # SQLAlchemy base
|
||||
├── file.py # Modelo File
|
||||
├── processing_job.py # Modelo ProcessingJob
|
||||
├── user.py # Modelo User
|
||||
└── audit_log.py # Modelo AuditLog
|
||||
```
|
||||
|
||||
#### 5.2 Esquema Propuesto
|
||||
```sql
|
||||
-- files
|
||||
CREATE TABLE files (
|
||||
id SERIAL PRIMARY KEY,
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
original_path TEXT,
|
||||
file_type VARCHAR(20),
|
||||
file_size BIGINT,
|
||||
checksum VARCHAR(64),
|
||||
status VARCHAR(20) DEFAULT 'pending',
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- processing_jobs
|
||||
CREATE TABLE processing_jobs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
file_id INTEGER REFERENCES files(id),
|
||||
job_type VARCHAR(50),
|
||||
status VARCHAR(20),
|
||||
started_at TIMESTAMP,
|
||||
completed_at TIMESTAMP,
|
||||
error_message TEXT,
|
||||
result_path TEXT,
|
||||
metadata JSONB
|
||||
);
|
||||
|
||||
-- audit_logs
|
||||
CREATE TABLE audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
action VARCHAR(50),
|
||||
resource_type VARCHAR(50),
|
||||
resource_id INTEGER,
|
||||
details JSONB,
|
||||
timestamp TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 6. Métricas y Observabilidad
|
||||
|
||||
### Estado Actual
|
||||
- `services/metrics_collector.py` básico
|
||||
- Sin exportación a sistemas externos
|
||||
- Sin dashboards de monitoreo
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 6.1 Prometheus Metrics
|
||||
```python
|
||||
# services/observability/
|
||||
├── __init__.py
|
||||
├── prometheus_exporter.py # Endpoint /metrics
|
||||
├── metrics.py # Definición de métricas
|
||||
└── tracing.py # Tracing distribuido
|
||||
```
|
||||
|
||||
#### 6.2 Métricas a Implementar
|
||||
```python
|
||||
from prometheus_client import Counter, Histogram, Gauge
|
||||
|
||||
# Contadores
|
||||
files_processed_total = Counter('files_processed_total', 'Total files processed', ['type', 'status'])
|
||||
ai_requests_total = Counter('ai_requests_total', 'AI API requests', ['provider', 'operation'])
|
||||
|
||||
# Histogramas
|
||||
processing_duration = Histogram('processing_duration_seconds', 'Processing time', ['type'])
|
||||
ai_response_time = Histogram('ai_response_time_seconds', 'AI response time', ['provider'])
|
||||
|
||||
# Gauges
|
||||
active_jobs = Gauge('active_jobs', 'Currently processing jobs')
|
||||
vram_usage = Gauge('vram_usage_bytes', 'GPU memory usage')
|
||||
queue_size = Gauge('queue_size', 'Jobs in queue', ['priority'])
|
||||
```
|
||||
|
||||
#### 6.3 Integración
|
||||
- [ ] Grafana dashboard preconfigurado
|
||||
- [ ] Alertas con AlertManager
|
||||
- [ ] Logs estructurados con Loki
|
||||
- [ ] Tracing con Jaeger/Zipkin
|
||||
|
||||
---
|
||||
|
||||
## 🎬 7. Video Processor (NUEVO)
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 7.1 Estructura
|
||||
```python
|
||||
# processors/video_processor.py
|
||||
class VideoProcessor(FileProcessor):
|
||||
"""Processor for video files"""
|
||||
|
||||
def extract_audio(self, video_path: str) -> str:
|
||||
"""Extraer audio de video con ffmpeg"""
|
||||
...
|
||||
|
||||
def extract_frames(self, video_path: str, interval: int = 60) -> List[str]:
|
||||
"""Extraer frames cada N segundos para análisis"""
|
||||
...
|
||||
|
||||
def analyze_frames(self, frames: List[str]) -> Dict[str, Any]:
|
||||
"""Analizar frames con visión AI (Gemini Vision)"""
|
||||
...
|
||||
|
||||
def process(self, file_path: str) -> Dict[str, Any]:
|
||||
"""Pipeline completo: audio + frames + análisis"""
|
||||
...
|
||||
```
|
||||
|
||||
#### 7.2 Extensiones de Video
|
||||
```python
|
||||
VIDEO_EXTENSIONS = {".mp4", ".avi", ".mkv", ".mov", ".webm"}
|
||||
```
|
||||
|
||||
#### 7.3 Funcionalidades
|
||||
- [ ] Transcripción de audio del video
|
||||
- [ ] Extracción de frames clave
|
||||
- [ ] Análisis visual con IA (slides, pizarra)
|
||||
- [ ] Generación de índice por escenas
|
||||
- [ ] Subtítulos automáticos (SRT/VTT)
|
||||
|
||||
---
|
||||
|
||||
## 🔌 8. WebSockets para Tiempo Real
|
||||
|
||||
### Estado Actual
|
||||
- Solo API REST
|
||||
- Sin actualizaciones en tiempo real
|
||||
- Polling pesado para estado
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 8.1 Implementación
|
||||
```python
|
||||
# api/websocket/
|
||||
├── __init__.py
|
||||
├── manager.py # ConnectionManager
|
||||
├── events.py # Tipos de eventos
|
||||
└── handlers.py # Event handlers
|
||||
```
|
||||
|
||||
#### 8.2 Eventos a Implementar
|
||||
```python
|
||||
# Eventos del servidor -> cliente
|
||||
{
|
||||
"type": "file.processing_started",
|
||||
"data": {"file_id": 1, "filename": "audio.mp3"}
|
||||
}
|
||||
{
|
||||
"type": "file.processing_progress",
|
||||
"data": {"file_id": 1, "progress": 45, "stage": "transcribing"}
|
||||
}
|
||||
{
|
||||
"type": "file.processing_completed",
|
||||
"data": {"file_id": 1, "result_path": "/path/to/result.docx"}
|
||||
}
|
||||
{
|
||||
"type": "system.gpu_usage",
|
||||
"data": {"vram_used": 4.5, "vram_total": 8.0}
|
||||
}
|
||||
```
|
||||
|
||||
#### 8.3 Integración con Flask
|
||||
```python
|
||||
from flask_socketio import SocketIO, emit
|
||||
|
||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
emit('connected', {'status': 'ok'})
|
||||
|
||||
@socketio.on('subscribe')
|
||||
def handle_subscribe(data):
|
||||
join_room(data['file_id'])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌐 9. API versioning y OpenAPI
|
||||
|
||||
### Estado Actual
|
||||
- API sin versionado
|
||||
- Sin documentación OpenAPI/Swagger
|
||||
- Endpoints inconsistentes
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 9.1 Versionado de API
|
||||
```
|
||||
/api/v1/files
|
||||
/api/v1/process
|
||||
/api/v1/health
|
||||
/api/v2/files (futura versión)
|
||||
```
|
||||
|
||||
#### 9.2 OpenAPI Spec
|
||||
```python
|
||||
# api/openapi/
|
||||
├── spec.yaml # Especificación OpenAPI 3.0
|
||||
└── swagger_ui.py # Swagger UI integration
|
||||
|
||||
# Usar flask-restx o flasgger
|
||||
from flask_restx import Api, Resource, fields
|
||||
|
||||
api = Api(app,
|
||||
version='1.0',
|
||||
title='CBCFacil API',
|
||||
description='API para procesamiento de documentos'
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐳 10. Containerización Mejorada
|
||||
|
||||
### Estado Actual
|
||||
- `.dockerignore` existe pero no Dockerfile completo
|
||||
- Sin docker-compose
|
||||
- Sin multi-stage builds
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 10.1 Docker Multi-stage
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM python:3.11-slim as builder
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip wheel --no-cache-dir -w /wheels -r requirements.txt
|
||||
|
||||
FROM nvidia/cuda:12.1-runtime-ubuntu22.04 as runtime
|
||||
# ... instalación optimizada
|
||||
```
|
||||
|
||||
#### 10.2 Docker Compose
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "5000:5000"
|
||||
environment:
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
|
||||
celery-worker:
|
||||
build: .
|
||||
command: celery -A celery_app worker -l info
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana
|
||||
ports:
|
||||
- "3000:3000"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 11. Unificación y Refactoring
|
||||
|
||||
### 11.1 AIProvider Unificado
|
||||
Actualmente existe lógica duplicada entre:
|
||||
- `services/ai_service.py`
|
||||
- `services/ai/gemini_provider.py`
|
||||
- `services/ai/claude_provider.py`
|
||||
|
||||
**Recomendación**: Crear interfaz unificada con Chain of Responsibility:
|
||||
```python
|
||||
class AIProviderChain:
|
||||
"""Cadena de proveedores con fallback automático"""
|
||||
|
||||
def __init__(self, providers: List[AIProvider]):
|
||||
self.providers = providers
|
||||
|
||||
def generate(self, prompt: str) -> str:
|
||||
for provider in self.providers:
|
||||
try:
|
||||
if provider.is_available():
|
||||
return provider.generate_text(prompt)
|
||||
except Exception as e:
|
||||
logging.warning(f"{provider.name} failed: {e}")
|
||||
raise AllProvidersFailedError()
|
||||
```
|
||||
|
||||
### 11.2 Procesadores Unificados
|
||||
Crear pipeline unificado:
|
||||
```python
|
||||
class ProcessingPipeline:
|
||||
def __init__(self):
|
||||
self.steps = []
|
||||
|
||||
def add_step(self, processor: FileProcessor):
|
||||
self.steps.append(processor)
|
||||
return self
|
||||
|
||||
def process(self, file_path: str) -> Dict[str, Any]:
|
||||
result = {}
|
||||
for step in self.steps:
|
||||
if step.can_process(file_path):
|
||||
result.update(step.process(file_path))
|
||||
return result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 12. Notificaciones Mejoradas
|
||||
|
||||
### Estado Actual
|
||||
- Solo Telegram
|
||||
- Sin templates de mensajes
|
||||
- Sin notificaciones push
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
#### 12.1 Multi-canal
|
||||
```python
|
||||
# services/notifications/
|
||||
├── __init__.py
|
||||
├── base_notifier.py # Interface base
|
||||
├── telegram_notifier.py # Actual optimizado
|
||||
├── email_notifier.py # Nuevo
|
||||
├── slack_notifier.py # Nuevo
|
||||
├── webhook_notifier.py # Para integraciones
|
||||
└── notification_manager.py # Orquestador
|
||||
```
|
||||
|
||||
#### 12.2 Templates de Mensaje
|
||||
```python
|
||||
TEMPLATES = {
|
||||
"processing_started": "🎵 Procesando: {filename}\n⏱️ Estimado: {eta}",
|
||||
"processing_completed": "✅ Completado: {filename}\n📄 Resumen: {summary_url}",
|
||||
"processing_failed": "❌ Error en {filename}\n🔍 Detalles: {error}",
|
||||
"daily_summary": "📊 Resumen del día:\n- Procesados: {count}\n- Tiempo total: {time}"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ 13. Sistema de Plugins
|
||||
|
||||
### Recomendaciones
|
||||
|
||||
```python
|
||||
# plugins/
|
||||
├── __init__.py
|
||||
├── base_plugin.py # Interface de plugin
|
||||
├── plugin_manager.py # Gestor de plugins
|
||||
└── examples/
|
||||
├── custom_ocr/ # Plugin OCR personalizado
|
||||
├── s3_storage/ # Plugin para AWS S3
|
||||
└── discord_notifier/ # Plugin Discord
|
||||
```
|
||||
|
||||
#### Interfaz de Plugin
|
||||
```python
|
||||
class BasePlugin(ABC):
|
||||
"""Base class for plugins"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str: ...
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def version(self) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
def initialize(self, config: dict) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def execute(self, context: dict) -> dict: ...
|
||||
|
||||
@abstractmethod
|
||||
def cleanup(self) -> None: ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 14. Mejoras de Rendimiento
|
||||
|
||||
### 14.1 Caching Avanzado
|
||||
```python
|
||||
# services/cache/
|
||||
├── __init__.py
|
||||
├── cache_manager.py # Gestor de cache
|
||||
├── redis_cache.py # Cache en Redis
|
||||
└── file_cache.py # Cache en disco
|
||||
```
|
||||
|
||||
### 14.2 Batch Processing
|
||||
```python
|
||||
class BatchProcessor:
|
||||
"""Procesar múltiples archivos en paralelo"""
|
||||
|
||||
def process_batch(self, files: List[str], max_workers: int = 4) -> List[dict]:
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = {executor.submit(self.process_single, f): f for f in files}
|
||||
results = []
|
||||
for future in as_completed(futures):
|
||||
results.append(future.result())
|
||||
return results
|
||||
```
|
||||
|
||||
### 14.3 Streaming de Archivos Grandes
|
||||
```python
|
||||
def stream_process(file_path: str, chunk_size: int = 1024*1024):
|
||||
"""Procesar archivos grandes en streaming"""
|
||||
with open(file_path, 'rb') as f:
|
||||
while chunk := f.read(chunk_size):
|
||||
yield process_chunk(chunk)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 15. Seguridad Adicional
|
||||
|
||||
### 15.1 Validación de Archivos
|
||||
```python
|
||||
# services/security/
|
||||
├── __init__.py
|
||||
├── file_validator.py # Validación de archivos
|
||||
├── malware_scanner.py # Escaneo de malware
|
||||
└── rate_limiter.py # Rate limiting por IP
|
||||
```
|
||||
|
||||
### 15.2 Checks de Seguridad
|
||||
- [ ] Validar tipos MIME reales (no solo extensiones)
|
||||
- [ ] Limitar tamaño máximo de archivo
|
||||
- [ ] Sanitizar nombres de archivo
|
||||
- [ ] Escanear con ClamAV
|
||||
- [ ] Rate limiting por usuario/IP
|
||||
- [ ] Logs de auditoría
|
||||
|
||||
---
|
||||
|
||||
## 📝 16. CLI Mejorado
|
||||
|
||||
### Estado Actual
|
||||
- Solo comandos básicos en `main.py`
|
||||
|
||||
### Recomendaciones
|
||||
```python
|
||||
# cli/
|
||||
├── __init__.py
|
||||
├── main.py # Click/Typer app
|
||||
├── commands/
|
||||
│ ├── process.py # cbcfacil process audio.mp3
|
||||
│ ├── queue.py # cbcfacil queue list/stats
|
||||
│ ├── config.py # cbcfacil config show/set
|
||||
│ └── db.py # cbcfacil db migrate/seed
|
||||
```
|
||||
|
||||
#### Ejemplo con Typer
|
||||
```python
|
||||
import typer
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
@app.command()
|
||||
def process(
|
||||
file: str,
|
||||
output_dir: str = ".",
|
||||
format: str = "docx",
|
||||
ai_provider: str = "auto"
|
||||
):
|
||||
"""Procesar archivo de audio o PDF"""
|
||||
...
|
||||
|
||||
@app.command()
|
||||
def status():
|
||||
"""Mostrar estado del servicio"""
|
||||
...
|
||||
|
||||
@app.command()
|
||||
def queue(action: str):
|
||||
"""Gestionar cola de procesamiento"""
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Resumen de Nuevos Archivos/Directorios
|
||||
|
||||
```
|
||||
cbc/
|
||||
├── tests/
|
||||
│ ├── unit/
|
||||
│ ├── integration/
|
||||
│ ├── e2e/
|
||||
│ └── fixtures/
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ └── public/
|
||||
├── services/
|
||||
│ ├── queue/
|
||||
│ ├── cache/
|
||||
│ ├── notifications/
|
||||
│ ├── observability/
|
||||
│ └── security/
|
||||
├── api/
|
||||
│ ├── auth/
|
||||
│ ├── websocket/
|
||||
│ └── openapi/
|
||||
├── storage/
|
||||
│ └── models/
|
||||
├── processors/
|
||||
│ └── video_processor.py
|
||||
├── plugins/
|
||||
├── cli/
|
||||
├── docker-compose.yml
|
||||
├── prometheus.yml
|
||||
└── grafana/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist de Implementación
|
||||
|
||||
### Fase 1 - Fundamentos (2-3 semanas)
|
||||
- [ ] Implementar tests unitarios básicos
|
||||
- [ ] Agregar autenticación JWT
|
||||
- [ ] Migrar a base de datos SQLite
|
||||
|
||||
### Fase 2 - Mejoras Core (3-4 semanas)
|
||||
- [ ] Implementar sistema de colas con Celery
|
||||
- [ ] Agregar WebSockets
|
||||
- [ ] Crear dashboard frontend básico
|
||||
|
||||
### Fase 3 - Observabilidad (1-2 semanas)
|
||||
- [ ] Prometheus metrics
|
||||
- [ ] Grafana dashboards
|
||||
- [ ] Logging estructurado
|
||||
|
||||
### Fase 4 - Extensiones (2-3 semanas)
|
||||
- [ ] Video processor
|
||||
- [ ] Multi-canal de notificaciones
|
||||
- [ ] Sistema de plugins
|
||||
|
||||
### Fase 5 - Producción (2 semanas)
|
||||
- [ ] Docker compose completo
|
||||
- [ ] CI/CD pipeline
|
||||
- [ ] Documentación completa
|
||||
|
||||
---
|
||||
|
||||
*Documento generado por análisis exhaustivo del proyecto CBCFacil v9*
|
||||
Reference in New Issue
Block a user