# Directiva: Generacion de Contenido IA - PMC **Version:** 1.0.0 **Fecha:** 2025-12-08 **Estado:** Activa **Modulo:** PMC-004 Generation --- ## Proposito Define los patrones, convenciones y flujos para implementar el motor de generacion de contenido IA en PMC, incluyendo integracion con ComfyUI, gestion de workflows, modelos personalizados y colas de procesamiento. --- ## Stack de Generacion ```yaml Imagenes: Motor: ComfyUI Modelo base: SDXL 1.0 Extensiones: - ControlNet (poses, depth) - IP-Adapter (consistencia) - LoRA (personalizacion) Formato salida: PNG, WebP Texto: API: OpenAI GPT-4 / Claude API Uso: Copys, hashtags, adaptacion de tono Colas: Sistema: BullMQ + Redis Colas definidas: - generation:image (concurrency: 2) - generation:text (concurrency: 10) - generation:training (concurrency: 1) WebSocket: Framework: Socket.io Eventos: progress, completed, failed ``` --- ## Arquitectura ``` ┌─────────────────────────────────────────────────────────────┐ │ Client (React) │ │ │ │ 1. Usuario configura generacion │ │ 2. Submit POST /api/v1/generation/jobs │ │ 3. Suscribe a WebSocket job:{jobId} │ │ 4. Recibe eventos de progreso │ │ 5. Muestra resultado al completar │ └─────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Backend (NestJS) │ │ │ │ GenerationController: │ │ - Valida permisos (tenant, usuario) │ │ - Verifica quota del tenant │ │ - Valida inputs contra schema del workflow │ │ - Crea registro GenerationJob │ │ - Encola en BullMQ │ │ - Retorna jobId │ └─────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Bull Processor (Worker) │ │ │ │ ImageGenerationProcessor: │ │ 1. Actualiza status a "processing" │ │ 2. Carga workflow template │ │ 3. Inyecta parametros del usuario │ │ 4. Carga LoRA de marca si aplica │ │ 5. Envia a ComfyUI API │ │ 6. Escucha progreso via WebSocket │ │ 7. Emite eventos al cliente │ │ 8. Descarga imagenes resultado │ │ 9. Crea Assets en storage │ │ 10. Actualiza job a "completed" │ └─────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ComfyUI Server │ │ │ │ Hardware: GPU NVIDIA 12-24GB VRAM │ │ Modelos cargados: │ │ - sd_xl_base_1.0.safetensors │ │ - sd_xl_refiner_1.0.safetensors │ │ - ControlNets (canny, depth, openpose) │ │ - IP-Adapter │ │ - LoRAs del tenant (cargados dinamicamente) │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Workflows Predefinidos ### WF-001: Product Photo Synthetic ```yaml ID: product_photo_synthetic Proposito: Fotos de producto en contexto comercial Tiempo estimado: 30-60 segundos Inputs: - product_description: string (REQUERIDO) - reference_image: file (opcional) - background: enum [white, lifestyle, custom] - style: enum [minimalist, premium, casual] - lora_id: UUID (opcional, o autodetectar de marca) - seed: number (opcional) Outputs: - 5 variaciones - 1024x1024 PNG - Fondo transparente disponible Nodos ComfyUI: - CheckpointLoaderSimple (SDXL) - LoraLoader (si lora_id) - CLIPTextEncode (positive + negative) - KSampler - VAEDecode - RemoveBackground (si fondo transparente) - SaveImage ``` ### WF-002: Social Media Post ```yaml ID: social_media_post Proposito: Imagen + copy para redes sociales Tiempo estimado: 45-90 segundos Inputs: - brief: object {objetivo, audiencia, tono} - product_id: UUID (opcional) - channel: enum [instagram, facebook, linkedin, tiktok] - format: enum [post, story, carousel] - brand_id: UUID Outputs: - 3-5 variaciones de imagen - Copy sugerido por variacion - Hashtags recomendados Proceso especial: 1. Generar imagen con ComfyUI 2. Llamar LLM para copy basado en imagen + brief 3. Llamar LLM para hashtags ``` ### WF-003: Ad Variations ```yaml ID: ad_variations Proposito: Variaciones para A/B testing Tiempo estimado: Variable Inputs: - base_image: asset_id o URL - variations_count: number (2-10) - variation_type: enum [color, background, composition] Outputs: - N variaciones - Metadata de diferencias ``` ### WF-004: Virtual Avatar ```yaml ID: virtual_avatar Proposito: Avatar/influencer virtual consistente Tiempo estimado: 60-120 segundos Inputs: - character_lora_id: UUID (REQUERIDO) - pose: enum [standing, sitting, walking, custom] - outfit: string - background: string - expression: enum [happy, serious, surprised, neutral] Outputs: - Imagen del avatar - Consistencia facial garantizada Nodos especiales: - IP-Adapter para consistencia - ControlNet OpenPose para pose ``` --- ## Flujos de Trabajo ### Flujo: Crear Job de Generacion ```typescript // 1. Validar permisos const canGenerate = await this.checkPermissions(user, tenant); if (!canGenerate) throw new ForbiddenException(); // 2. Verificar quota const hasQuota = await this.quotaService.check(tenant.id, 'generations'); if (!hasQuota) throw new PaymentRequiredException('Quota exceeded'); // 3. Cargar y validar workflow const workflow = await this.workflowService.findById(dto.workflow_id); this.validateInputs(dto.inputs, workflow.input_schema); // 4. Inyectar contexto de marca si aplica if (dto.brand_id) { dto.inputs = await this.injectBrandContext(dto.brand_id, dto.inputs); } // 5. Crear job en BD const job = await this.jobRepository.save({ tenant_id: tenant.id, user_id: user.id, workflow_id: workflow.id, status: 'queued', priority: this.calculatePriority(tenant), input_params: dto.inputs, }); // 6. Encolar en Bull await this.generationQueue.add('generate', { jobId: job.id, tenantId: tenant.id, workflowId: workflow.id, params: dto.inputs, }, { priority: job.priority, attempts: 3, backoff: { type: 'exponential', delay: 5000 }, }); // 7. Incrementar uso await this.quotaService.increment(tenant.id, 'generations'); return job; ``` ### Flujo: Procesar Job (Worker) ```typescript @Process('generate') async handleGeneration(bullJob: Job): Promise { const { jobId, tenantId, workflowId, params } = bullJob.data; try { // 1. Marcar como procesando await this.updateStatus(jobId, 'processing'); this.gateway.emit(jobId, 'started'); // 2. Construir workflow ComfyUI const workflow = await this.buildComfyWorkflow(workflowId, params); // 3. Enviar a ComfyUI const promptId = await this.comfyUI.queuePrompt(workflow); // 4. Monitorear progreso await this.monitorProgress(promptId, jobId); // 5. Descargar resultados const images = await this.downloadResults(promptId); // 6. Crear assets const assetIds = await this.createAssets(tenantId, jobId, images); // 7. Completar job await this.completeJob(jobId, assetIds); this.gateway.emit(jobId, 'completed', { assets: assetIds }); } catch (error) { await this.failJob(jobId, error.message); this.gateway.emit(jobId, 'failed', { error: error.message }); throw error; } } ``` --- ## Rate Limiting ### Limites por Plan ```yaml Starter: generaciones_hora: 10 generaciones_mes: 100 entrenamiento_mes: 0 modelos_custom: 0 Pro: generaciones_hora: 50 generaciones_mes: 1000 entrenamiento_mes: 5 modelos_custom: 10 Business: generaciones_hora: 200 generaciones_mes: 10000 entrenamiento_mes: 20 modelos_custom: 50 Enterprise: generaciones_hora: unlimited generaciones_mes: unlimited entrenamiento_mes: unlimited modelos_custom: unlimited ``` ### Implementacion ```typescript // Usar @CATALOG_RATELIMIT @UseGuards(TenantThrottlerGuard) @Throttle({ default: { ttl: 3600, limit: 50 } }) // Por defecto, ajustado por plan @Post('jobs') async createJob(...) { ... } ``` --- ## Modelos Personalizados (LoRAs) ### Estructura de Almacenamiento ``` storage/ └── {tenant_slug}/ └── models/ ├── loras/ │ └── {lora_name}.safetensors ├── checkpoints/ └── embeddings/ ``` ### Registro de Modelo ```typescript interface RegisterModelDto { name: string; type: 'lora' | 'checkpoint' | 'embedding'; file: Express.Multer.File; trigger_word: string; // Para LoRAs brand_id?: string; preview_images?: Express.Multer.File[]; } // Validaciones - Tamano maximo: 2GB - Formatos: .safetensors, .ckpt (solo checkpoint) - Nombre unico por tenant ``` ### Carga Dinamica en ComfyUI ```typescript // Al construir workflow, si hay lora_id if (params.lora_id) { const model = await this.modelService.findById(params.lora_id); // Agregar nodo LoraLoader al workflow workflow['lora_loader'] = { class_type: 'LoraLoader', inputs: { model: ['checkpoint', 0], clip: ['checkpoint', 1], lora_name: model.file_path, strength_model: params.lora_strength || 0.8, strength_clip: params.lora_strength || 0.8, } }; // Redirigir conexiones workflow['sampler'].inputs.model = ['lora_loader', 0]; } ``` --- ## Eventos WebSocket ### Eventos Emitidos ```typescript // Suscripcion socket.emit('subscribe:job', jobId); // Eventos recibidos socket.on('generation:started', ({ jobId }) => {}); socket.on('generation:progress', ({ jobId, progress, step, total }) => {}); socket.on('generation:completed', ({ jobId, assets }) => {}); socket.on('generation:failed', ({ jobId, error }) => {}); ``` ### Formato de Eventos ```typescript interface ProgressEvent { jobId: string; progress: number; // 0-100 step: number; total: number; message?: string; // "Loading model...", "Sampling...", etc } interface CompletedEvent { jobId: string; assets: string[]; // Asset IDs duration: number; // milliseconds } interface FailedEvent { jobId: string; error: string; retryable: boolean; } ``` --- ## Manejo de Errores ### Errores Comunes | Error | Causa | Accion | |-------|-------|--------| | QUOTA_EXCEEDED | Limite mensual alcanzado | Mostrar mensaje, sugerir upgrade | | COMFYUI_UNAVAILABLE | Servidor no responde | Reintentar 3 veces, luego fallar | | MODEL_NOT_FOUND | LoRA no existe | Verificar modelo antes de enviar | | INVALID_WORKFLOW | Workflow corrupto | Notificar admin | | OUT_OF_MEMORY | GPU sin memoria | Reducir batch o esperar | ### Estrategia de Reintentos ```typescript // Configuracion Bull { attempts: 3, backoff: { type: 'exponential', delay: 5000, // 5s, 10s, 20s }, removeOnComplete: 100, removeOnFail: 50, } ``` --- ## Validaciones Obligatorias ### Antes de Crear Job - [ ] Usuario autenticado - [ ] Usuario pertenece al tenant - [ ] Tenant tiene quota disponible - [ ] Workflow existe y esta activo - [ ] Inputs validos segun schema - [ ] LoRA existe si se especifica - [ ] Brand existe si se especifica ### Despues de Completar Job - [ ] Imagenes descargadas correctamente - [ ] Assets creados en storage - [ ] Assets registrados en BD - [ ] Job actualizado a "completed" - [ ] Evento emitido via WebSocket - [ ] Quota incrementada --- ## Referencias - Definicion modulo: `docs/02-definicion-modulos/PMC-004-GENERATION.md` - Requerimientos: `docs/03-requerimientos/RF-PMC-004-GENERATION.md` - User Stories: `docs/05-user-stories/EPIC-004-GENERATION.md` - ADR Motor: `docs/97-adr/ADR-003-motor-generacion.md` - ADR Cola: `docs/97-adr/ADR-004-cola-tareas.md` - Prompt Agente: `orchestration/prompts/PROMPT-GENERATION-PMC.md` - Catalogo RateLimit: `shared/catalog/rate-limiting/` - Catalogo WebSocket: `shared/catalog/websocket/` --- **Generado por:** Requirements-Analyst **Fecha:** 2025-12-08