- Update vision, architecture and technical documentation - Update module definitions (PMC-001 to PMC-008) - Update requirements documentation - Add CONTEXT-MAP.yml and ENVIRONMENT-INVENTORY.yml - Add orchestration guidelines and references 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
14 KiB
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
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
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
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
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
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
// 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)
@Process('generate')
async handleGeneration(bullJob: Job<JobData>): Promise<void> {
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
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
// 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
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
// 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
// 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
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
// 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