diff --git a/kubectl b/kubectl new file mode 100644 index 0000000..ff031a1 Binary files /dev/null and b/kubectl differ diff --git a/main.py b/main.py index 09f6bee..edfc152 100644 --- a/main.py +++ b/main.py @@ -9,10 +9,15 @@ import time import fcntl import os import json +import threading from pathlib import Path from datetime import datetime from typing import Optional +# Load environment variables from .env file +from dotenv import load_dotenv +load_dotenv() + # Configure logging with JSON formatter for production class JSONFormatter(logging.Formatter): """JSON formatter for structured logging in production""" @@ -234,6 +239,41 @@ def send_error_notification(error_type: str, error_message: str) -> None: logger.warning(f"Failed to send error notification: {e}") +def run_dashboard_thread() -> None: + """Run Flask dashboard in a separate thread""" + try: + from api.routes import create_app + app = create_app() + + # Run Flask in production mode with threaded=True + app.run( + host='0.0.0.0', + port=5000, + debug=False, + threaded=True, + use_reloader=False # Important: disable reloader in thread + ) + except Exception as e: + logger.error(f"Dashboard thread error: {e}") + logger.exception("Dashboard thread exception details") + + +def start_dashboard() -> threading.Thread: + """Start dashboard in a background daemon thread""" + dashboard_port = int(os.getenv('DASHBOARD_PORT', '5000')) + logger.info(f"Starting dashboard on port {dashboard_port}...") + + # Create daemon thread so it doesn't block shutdown + dashboard_thread = threading.Thread( + target=run_dashboard_thread, + name="DashboardThread", + daemon=True + ) + dashboard_thread.start() + logger.info(f"Dashboard thread started (Thread-ID: {dashboard_thread.ident})") + return dashboard_thread + + def run_main_loop() -> None: """Main processing loop with improved error handling""" from config import settings @@ -418,13 +458,19 @@ def run_main_loop() -> None: def main(): """Main entry point""" lock_fd = None + dashboard_thread = None try: logger.info("=== CBCFacil Service Started ===") logger.info(f"Version: {os.getenv('APP_VERSION', '8.0')}") logger.info(f"Environment: {'production' if os.getenv('DEBUG', 'false').lower() != 'true' else 'development'}") - + lock_fd = acquire_lock() initialize_services() + + # Start dashboard in background thread + dashboard_thread = start_dashboard() + + # Run main processing loop run_main_loop() except KeyboardInterrupt: diff --git a/plus.md b/plus.md new file mode 100644 index 0000000..62ecd5e --- /dev/null +++ b/plus.md @@ -0,0 +1,799 @@ +# 🚀 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* diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..d370cb9 --- /dev/null +++ b/todo.md @@ -0,0 +1,1198 @@ +# 🎯 TODO: Dashboard de Monitoreo y Reprocesamiento + +## 📋 Objetivo Principal + +Crear un dashboard funcional en localhost que permita: +1. **Monitorear** archivos procesados y pendientes +2. **Visualizar** transcripciones y resúmenes generados +3. **Regenerar resúmenes** cuando no son satisfactorios (usando la transcripción existente) +4. **Reprocesar** archivos completos si es necesario + +--- + +## 📊 Estado Actual del Proyecto + +### ✅ Lo que YA existe: + +| Componente | Ubicación | Estado | +|------------|-----------|--------| +| UI Dashboard | `templates/index.html` | ✅ Completo (1586 líneas) | +| API Routes | `api/routes.py` | ✅ Parcial (281 líneas) | +| Backend Flask | `api/__init__.py` | ✅ Básico | +| Generador Docs | `document/generators.py` | ✅ Completo | +| Audio Processor | `processors/audio_processor.py` | ✅ Completo | +| PDF Processor | `processors/pdf_processor.py` | ✅ Completo | +| AI Providers | `services/ai/` | ✅ Completo | + +### ❌ Lo que FALTA implementar: + +| Funcionalidad | Prioridad | Complejidad | +|---------------|-----------|-------------| +| Endpoint regenerar resumen | 🔴 Alta | Media | +| Vista previa de transcripción | 🔴 Alta | Baja | +| Vista previa de resumen | 🔴 Alta | Baja | +| Editor de prompts | 🟡 Media | Media | +| Historial de versiones | 🟢 Baja | Alta | + +--- + +## 🏗️ ARQUITECTURA DEL DASHBOARD + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ FRONTEND (index.html) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Stats │ │ File List │ │ Preview │ │ Actions │ │ +│ │ Cards │ │ + Filters │ │ Panel │ │ Panel │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ API ROUTES (routes.py) │ +│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ GET /api/files │ │ GET /api/preview │ │ POST /api/regen │ │ +│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ BACKEND SERVICES │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Document │ │ AI Provider │ │ WebDAV │ │ +│ │ Generator │ │ (Gemini) │ │ Service │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 📁 ARCHIVOS A MODIFICAR/CREAR + +### 1. `api/routes.py` - Nuevos Endpoints + +#### 1.1 Endpoint: Obtener contenido de transcripción +``` +GET /api/transcription/ +``` + +**Propósito**: Devolver el contenido de la transcripción (.txt) de un archivo de audio. + +**Request**: +``` +GET /api/transcription/clase_historia_01 +``` + +**Response**: +```json +{ + "success": true, + "filename": "clase_historia_01", + "transcription": "Contenido completo de la transcripción...", + "file_path": "/home/ren/proyectos/cbc/downloads/clase_historia_01.txt", + "word_count": 3456, + "char_count": 18234 +} +``` + +**Implementación**: +```python +@app.route('/api/transcription/') +def get_transcription(filename): + """Obtener contenido de transcripción""" + try: + # Buscar archivo .txt en downloads/ + txt_path = settings.LOCAL_DOWNLOADS_PATH / f"{filename}.txt" + + if not txt_path.exists(): + return jsonify({ + 'success': False, + 'message': f"Transcripción no encontrada: {filename}.txt" + }), 404 + + with open(txt_path, 'r', encoding='utf-8') as f: + content = f.read() + + return jsonify({ + 'success': True, + 'filename': filename, + 'transcription': content, + 'file_path': str(txt_path), + 'word_count': len(content.split()), + 'char_count': len(content) + }) + except Exception as e: + app.logger.error(f"Error getting transcription: {e}") + return jsonify({ + 'success': False, + 'message': f"Error: {str(e)}" + }), 500 +``` + +--- + +#### 1.2 Endpoint: Obtener contenido de resumen +``` +GET /api/summary/ +``` + +**Propósito**: Devolver el contenido del resumen (.md) de un archivo procesado. + +**Request**: +``` +GET /api/summary/clase_historia_01_unificado +``` + +**Response**: +```json +{ + "success": true, + "filename": "clase_historia_01_unificado", + "summary": "# Resumen\n\n## Contenido...", + "file_path": "/home/ren/proyectos/cbc/downloads/clase_historia_01_unificado.md", + "formats_available": { + "md": true, + "docx": true, + "pdf": true + } +} +``` + +**Implementación**: +```python +@app.route('/api/summary/') +def get_summary(filename): + """Obtener contenido de resumen""" + try: + # Normalizar nombre (agregar _unificado si no lo tiene) + base_name = filename.replace('_unificado', '') + unified_name = f"{base_name}_unificado" + + # Buscar archivo .md en downloads/ + md_path = settings.LOCAL_DOWNLOADS_PATH / f"{unified_name}.md" + + if not md_path.exists(): + return jsonify({ + 'success': False, + 'message': f"Resumen no encontrado: {unified_name}.md" + }), 404 + + with open(md_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Verificar formatos disponibles + formats = { + 'md': md_path.exists(), + 'docx': (settings.LOCAL_DOCX / f"{unified_name}.docx").exists(), + 'pdf': (settings.LOCAL_DOWNLOADS_PATH / f"{unified_name}.pdf").exists() + } + + return jsonify({ + 'success': True, + 'filename': unified_name, + 'summary': content, + 'file_path': str(md_path), + 'formats_available': formats + }) + except Exception as e: + app.logger.error(f"Error getting summary: {e}") + return jsonify({ + 'success': False, + 'message': f"Error: {str(e)}" + }), 500 +``` + +--- + +#### 1.3 Endpoint: Regenerar resumen (CRÍTICO) +``` +POST /api/regenerate-summary +``` + +**Propósito**: Regenerar el resumen usando la transcripción existente, sin reprocesar el audio. + +**Request**: +```json +{ + "filename": "clase_historia_01", + "custom_prompt": "Opcional: prompt personalizado para el resumen", + "ai_provider": "gemini" +} +``` + +**Response**: +```json +{ + "success": true, + "message": "Resumen regenerado exitosamente", + "new_summary": "# Nuevo Resumen...", + "files_updated": { + "md": "/path/to/clase_historia_01_unificado.md", + "docx": "/path/to/clase_historia_01_unificado.docx", + "pdf": "/path/to/clase_historia_01_unificado.pdf" + }, + "processing_time": 4.5 +} +``` + +**Implementación detallada**: +```python +@app.route('/api/regenerate-summary', methods=['POST']) +def regenerate_summary(): + """Regenerar resumen desde transcripción existente""" + try: + data = request.get_json() + filename = data.get('filename') + custom_prompt = data.get('custom_prompt', None) + + if not filename: + return jsonify({ + 'success': False, + 'message': "Nombre de archivo requerido" + }), 400 + + # Paso 1: Obtener transcripción + base_name = filename.replace('_unificado', '').replace('.txt', '').replace('.md', '') + txt_path = settings.LOCAL_DOWNLOADS_PATH / f"{base_name}.txt" + + if not txt_path.exists(): + return jsonify({ + 'success': False, + 'message': f"Transcripción no encontrada: {base_name}.txt" + }), 404 + + with open(txt_path, 'r', encoding='utf-8') as f: + transcription_text = f.read() + + if not transcription_text.strip(): + return jsonify({ + 'success': False, + 'message': "La transcripción está vacía" + }), 400 + + # Paso 2: Regenerar resumen con IA + import time + start_time = time.time() + + from document.generators import DocumentGenerator + doc_generator = DocumentGenerator() + + success, summary, output_files = doc_generator.generate_summary( + transcription_text, + base_name + ) + + processing_time = time.time() - start_time + + if not success: + return jsonify({ + 'success': False, + 'message': "Error al generar el resumen con IA" + }), 500 + + # Paso 3: Subir a Nextcloud si está configurado + if settings.has_webdav_config: + from services.webdav_service import webdav_service + + for folder in [settings.RESUMENES_FOLDER, settings.DOCX_FOLDER]: + try: + webdav_service.makedirs(folder) + except Exception: + pass + + # Subir MD + md_path = Path(output_files.get('markdown_path', '')) + if md_path.exists(): + remote_md = f"{settings.RESUMENES_FOLDER}/{md_path.name}" + webdav_service.upload(md_path, remote_md) + + # Subir DOCX + docx_path = Path(output_files.get('docx_path', '')) + if docx_path.exists(): + remote_docx = f"{settings.DOCX_FOLDER}/{docx_path.name}" + webdav_service.upload(docx_path, remote_docx) + + # Subir PDF + pdf_path = Path(output_files.get('pdf_path', '')) + if pdf_path.exists(): + remote_pdf = f"{settings.DOCX_FOLDER}/{pdf_path.name}" + webdav_service.upload(pdf_path, remote_pdf) + + return jsonify({ + 'success': True, + 'message': "Resumen regenerado exitosamente", + 'new_summary': summary[:500] + "..." if len(summary) > 500 else summary, + 'files_updated': output_files, + 'processing_time': round(processing_time, 2) + }) + + except Exception as e: + app.logger.error(f"Error regenerating summary: {e}") + return jsonify({ + 'success': False, + 'message': f"Error: {str(e)}" + }), 500 +``` + +--- + +#### 1.4 Endpoint: Listar todos los archivos procesados con detalles +``` +GET /api/files-detailed +``` + +**Propósito**: Obtener lista de archivos con información sobre transcripciones y resúmenes disponibles. + +**Response**: +```json +{ + "success": true, + "files": [ + { + "filename": "clase_historia_01.mp3", + "base_name": "clase_historia_01", + "audio_path": "/path/to/audio.mp3", + "has_transcription": true, + "transcription_path": "/path/to/clase_historia_01.txt", + "transcription_words": 3456, + "has_summary": true, + "summary_path": "/path/to/clase_historia_01_unificado.md", + "formats": { + "txt": true, + "md": true, + "docx": true, + "pdf": true + }, + "processed": true, + "last_modified": "2024-01-10 15:30:00" + } + ] +} +``` + +**Implementación**: +```python +@app.route('/api/files-detailed') +def get_files_detailed(): + """Obtener lista detallada de archivos con transcripciones y resúmenes""" + try: + files = [] + downloads_path = settings.LOCAL_DOWNLOADS_PATH + docx_path = settings.LOCAL_DOCX + + # Buscar archivos de audio + for ext in settings.AUDIO_EXTENSIONS: + for audio_file in downloads_path.glob(f"*{ext}"): + base_name = audio_file.stem + + # Verificar transcripción + txt_path = downloads_path / f"{base_name}.txt" + has_transcription = txt_path.exists() + transcription_words = 0 + if has_transcription: + with open(txt_path, 'r', encoding='utf-8') as f: + transcription_words = len(f.read().split()) + + # Verificar resumen + md_path = downloads_path / f"{base_name}_unificado.md" + has_summary = md_path.exists() + + # Verificar formatos + formats = { + 'txt': has_transcription, + 'md': has_summary, + 'docx': (docx_path / f"{base_name}_unificado.docx").exists(), + 'pdf': (downloads_path / f"{base_name}_unificado.pdf").exists() + } + + files.append({ + 'filename': audio_file.name, + 'base_name': base_name, + 'audio_path': str(audio_file), + 'has_transcription': has_transcription, + 'transcription_path': str(txt_path) if has_transcription else None, + 'transcription_words': transcription_words, + 'has_summary': has_summary, + 'summary_path': str(md_path) if has_summary else None, + 'formats': formats, + 'processed': has_transcription and has_summary, + 'last_modified': datetime.fromtimestamp( + audio_file.stat().st_mtime + ).strftime('%Y-%m-%d %H:%M:%S') + }) + + return jsonify({ + 'success': True, + 'files': sorted(files, key=lambda x: x['last_modified'], reverse=True), + 'total': len(files), + 'with_transcription': sum(1 for f in files if f['has_transcription']), + 'with_summary': sum(1 for f in files if f['has_summary']) + }) + + except Exception as e: + app.logger.error(f"Error getting detailed files: {e}") + return jsonify({ + 'success': False, + 'message': f"Error: {str(e)}" + }), 500 +``` + +--- + +### 2. `templates/index.html` - Nuevos Componentes UI + +#### 2.1 Panel de Preview (Sidebar Derecho) + +**Ubicación**: Después del `files-container` existente + +**HTML a agregar** (después de línea 1093): +```html + +
+
+

Selecciona un archivo

+ +
+ +
+ + +
+ +
+
+
+ 0 palabras + 0 caracteres +
+
+ Haz clic en un archivo para ver su transcripción +
+
+ +
+
+ Haz clic en un archivo para ver su resumen +
+
+
+ +
+ + +
+ + +
+
+

Regenerando resumen con IA...

+ Esto puede tomar 10-30 segundos +
+
+``` + +--- + +#### 2.2 Estilos CSS para el Panel de Preview + +**Agregar al bloque `