- 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>
503 lines
14 KiB
Markdown
503 lines
14 KiB
Markdown
# 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<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
|
|
|
|
```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
|