Funciones Avanzadas

Funciones avanzadas y capacidades de Qwen Image Edit

Procesamiento Asíncrono

Webhooks para Tareas Largas

Para imágenes grandes o procesamiento complejo, usa webhooks para recibir notificaciones cuando el trabajo esté completo.

// Iniciar procesamiento asíncrono
const trabajo = await editor.editTextAsync({
  imagen: 'imagen-4k.jpg',
  prompt: 'Edición compleja con múltiples elementos',
  webhook: {
    url: 'https://mi-app.com/webhook/qwen',
    eventos: ['completado', 'error', 'progreso'],
    secreto: process.env.WEBHOOK_SECRET,
    headers: {
      'Authorization': `Bearer ${process.env.API_TOKEN}`,
      'X-App-ID': 'mi-aplicacion'
    }
  },
  opciones: {
    prioridad: 'alta',
    notificarProgreso: true
  }
});

console.log('Trabajo iniciado:', trabajo.id);
console.log('Estado:', trabajo.estado);
console.log('URL de seguimiento:', trabajo.urlSeguimiento);

Manejo de Webhooks

// Endpoint para recibir webhooks
app.post('/webhook/qwen', express.raw({type: 'application/json'}), (req, res) => {
  const firma = req.headers['x-qwen-signature'];
  const payload = req.body;
  
  // Verificar firma del webhook
  if (!verificarFirmaWebhook(payload, firma, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Firma inválida');
  }
  
  const evento = JSON.parse(payload);
  
  switch (evento.tipo) {
    case 'trabajo.completado':
      manejarTrabajoCompletado(evento.datos);
      break;
    case 'trabajo.error':
      manejarTrabajoError(evento.datos);
      break;
    case 'trabajo.progreso':
      manejarProgresoTrabajo(evento.datos);
      break;
  }
  
  res.status(200).send('OK');
});

function verificarFirmaWebhook(payload, firma, secreto) {
  const crypto = require('crypto');
  const firmaEsperada = crypto
    .createHmac('sha256', secreto)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(firma),
    Buffer.from(`sha256=${firmaEsperada}`)
  );
}

Seguimiento de Trabajos

// Consultar estado de trabajo
async function consultarEstadoTrabajo(trabajoId: string) {
  const estado = await editor.getJobStatus(trabajoId);
  
  console.log('Estado del trabajo:', {
    id: estado.id,
    estado: estado.estado, // 'pendiente', 'procesando', 'completado', 'error'
    progreso: estado.progreso, // 0-100
    tiempoEstimado: estado.tiempoEstimadoMs,
    resultado: estado.resultado,
    error: estado.error
  });
  
  return estado;
}

// Polling para trabajos sin webhooks
async function esperarCompletado(trabajoId: string, intervalo = 5000) {
  return new Promise((resolve, reject) => {
    const verificar = async () => {
      try {
        const estado = await consultarEstadoTrabajo(trabajoId);
        
        if (estado.estado === 'completado') {
          resolve(estado.resultado);
        } else if (estado.estado === 'error') {
          reject(new Error(estado.error));
        } else {
          setTimeout(verificar, intervalo);
        }
      } catch (error) {
        reject(error);
      }
    };
    
    verificar();
  });
}

Procesamiento en Lotes

Lotes Optimizados

interface LoteConfig {
  tamañoLote: number;
  concurrencia: number;
  delayEntreLotes: number;
  reintentos: number;
  onProgreso?: (completados: number, total: number) => void;
  onError?: (error: Error, imagen: string) => void;
}

async function procesarLote(
  imagenes: string[],
  operacion: (imagen: string) => Promise<any>,
  config: LoteConfig
) {
  const resultados = [];
  const errores = [];
  
  for (let i = 0; i < imagenes.length; i += config.tamañoLote) {
    const lote = imagenes.slice(i, i + config.tamañoLote);
    
    // Procesar lote con concurrencia limitada
    const promesasLote = lote.map(async (imagen) => {
      for (let intento = 1; intento <= config.reintentos; intento++) {
        try {
          return await operacion(imagen);
        } catch (error) {
          if (intento === config.reintentos) {
            config.onError?.(error, imagen);
            errores.push({ imagen, error });
            return null;
          }
          // Esperar antes del siguiente intento
          await new Promise(resolve => 
            setTimeout(resolve, intento * 1000)
          );
        }
      }
    });
    
    const resultadosLote = await Promise.all(promesasLote);
    resultados.push(...resultadosLote.filter(r => r !== null));
    
    // Reportar progreso
    config.onProgreso?.(resultados.length, imagenes.length);
    
    // Delay entre lotes para evitar rate limiting
    if (i + config.tamañoLote < imagenes.length) {
      await new Promise(resolve => 
        setTimeout(resolve, config.delayEntreLotes)
      );
    }
  }
  
  return { resultados, errores };
}

// Ejemplo de uso
const imagenes = ['img1.jpg', 'img2.jpg', /* ... */];

const { resultados, errores } = await procesarLote(
  imagenes,
  (imagen) => editor.editText({
    imagen,
    prompt: 'Mejorar calidad'
  }),
  {
    tamañoLote: 10,
    concurrencia: 5,
    delayEntreLotes: 2000,
    reintentos: 3,
    onProgreso: (completados, total) => {
      console.log(`Progreso: ${completados}/${total} (${Math.round(completados/total*100)}%)`);
    },
    onError: (error, imagen) => {
      console.error(`Error procesando ${imagen}:`, error.message);
    }
  }
);

Procesamiento Paralelo Inteligente

import pLimit from 'p-limit';
import pRetry from 'p-retry';

class ProcesadorInteligente {
  private limiteConcurrencia: ReturnType<typeof pLimit>;
  private estadisticas = {
    procesados: 0,
    errores: 0,
    tiempoPromedio: 0
  };
  
  constructor(private editor: QwenImageEdit, concurrencia = 5) {
    this.limiteConcurrencia = pLimit(concurrencia);
  }
  
  async procesarImagenes(imagenes: string[], operacion: any) {
    const inicioTiempo = Date.now();
    
    const promesas = imagenes.map((imagen, indice) => 
      this.limiteConcurrencia(async () => {
        const inicioImagen = Date.now();
        
        try {
          const resultado = await pRetry(
            () => this.ejecutarOperacion(imagen, operacion),
            {
              retries: 3,
              factor: 2,
              minTimeout: 1000,
              maxTimeout: 10000,
              onFailedAttempt: (error) => {
                console.warn(`Intento ${error.attemptNumber} falló para ${imagen}:`, error.message);
              }
            }
          );
          
          this.estadisticas.procesados++;
          const tiempoImagen = Date.now() - inicioImagen;
          this.actualizarTiempoPromedio(tiempoImagen);
          
          console.log(`✅ ${imagen} procesada en ${tiempoImagen}ms`);
          return { imagen, resultado, indice };
          
        } catch (error) {
          this.estadisticas.errores++;
          console.error(`❌ Error procesando ${imagen}:`, error.message);
          return { imagen, error, indice };
        }
      })
    );
    
    const resultados = await Promise.all(promesas);
    const tiempoTotal = Date.now() - inicioTiempo;
    
    console.log('📊 Estadísticas de procesamiento:', {
      ...this.estadisticas,
      tiempoTotal: `${tiempoTotal}ms`,
      velocidadPromedio: `${Math.round(imagenes.length / (tiempoTotal / 1000))} img/s`
    });
    
    return resultados;
  }
  
  private async ejecutarOperacion(imagen: string, operacion: any) {
    // Lógica específica de la operación
    return await this.editor.editText({
      imagen,
      ...operacion
    });
  }
  
  private actualizarTiempoPromedio(nuevoTiempo: number) {
    const total = this.estadisticas.procesados;
    this.estadisticas.tiempoPromedio = 
      (this.estadisticas.tiempoPromedio * (total - 1) + nuevoTiempo) / total;
  }
}

Análisis Avanzado de Imágenes

Análisis Profundo

async function analisisCompleto(imagen: string) {
  const analisis = await editor.analyzeImage({
    imagen,
    detectar: ['texto', 'objetos', 'rostros', 'colores', 'estilo', 'calidad'],
    opciones: {
      profundidad: 'alta',
      incluirMetadatos: true,
      detectarEmociones: true,
      analizarComposicion: true
    }
  });
  
  return {
    // Análisis de texto
    texto: analisis.texto?.map(t => ({
      contenido: t.contenido,
      idioma: t.idioma,
      confianza: t.confianza,
      posicion: t.posicion,
      estilo: {
        fuente: t.fuente,
        tamaño: t.tamaño,
        color: t.color,
        negrita: t.negrita,
        cursiva: t.cursiva
      }
    })),
    
    // Análisis de objetos
    objetos: analisis.objetos?.map(o => ({
      nombre: o.nombre,
      categoria: o.categoria,
      confianza: o.confianza,
      posicion: o.posicion,
      atributos: o.atributos
    })),
    
    // Análisis de rostros
    rostros: analisis.rostros?.map(r => ({
      posicion: r.posicion,
      edad: r.edadEstimada,
      genero: r.generoEstimado,
      emociones: r.emociones,
      puntosClave: r.puntosClave
    })),
    
    // Análisis de colores
    colores: {
      dominantes: analisis.colores?.dominantes,
      paleta: analisis.colores?.paleta,
      temperatura: analisis.colores?.temperatura,
      saturacion: analisis.colores?.saturacion
    },
    
    // Análisis de estilo
    estilo: {
      categoria: analisis.estilo?.categoria,
      epoca: analisis.estilo?.epoca,
      tecnica: analisis.estilo?.tecnica,
      mood: analisis.estilo?.mood
    },
    
    // Análisis de calidad
    calidad: {
      puntuacion: analisis.calidad?.puntuacion,
      nitidez: analisis.calidad?.nitidez,
      exposicion: analisis.calidad?.exposicion,
      ruido: analisis.calidad?.ruido,
      compresion: analisis.calidad?.compresion
    }
  };
}

Detección de Contenido Sensible

async function verificarContenido(imagen: string) {
  const verificacion = await editor.moderateContent({
    imagen,
    categorias: [
      'adulto',
      'violencia', 
      'drogas',
      'odio',
      'spam'
    ],
    umbral: 0.7
  });
  
  const esSeguro = verificacion.categorias.every(c => c.puntuacion < 0.7);
  
  if (!esSeguro) {
    console.warn('⚠️ Contenido potencialmente sensible detectado:', 
      verificacion.categorias.filter(c => c.puntuacion >= 0.7)
    );
  }
  
  return {
    esSeguro,
    categorias: verificacion.categorias,
    recomendaciones: verificacion.recomendaciones
  };
}

Edición Condicional

Edición Basada en Condiciones

interface CondicionEdicion {
  tipo: 'texto' | 'objeto' | 'color' | 'region';
  condicion: any;
  accion: any;
}

async function editarCondicional(
  imagen: string, 
  condiciones: CondicionEdicion[]
) {
  // Primero analizar la imagen
  const analisis = await editor.analyzeImage({
    imagen,
    detectar: ['texto', 'objetos', 'colores']
  });
  
  const edicionesAplicar = [];
  
  for (const condicion of condiciones) {
    switch (condicion.tipo) {
      case 'texto':
        const textoEncontrado = analisis.texto?.find(t => 
          t.contenido.includes(condicion.condicion.buscar)
        );
        if (textoEncontrado) {
          edicionesAplicar.push({
            tipo: 'editText',
            opciones: {
              ...condicion.accion,
              posicion: textoEncontrado.posicion
            }
          });
        }
        break;
        
      case 'objeto':
        const objetoEncontrado = analisis.objetos?.find(o => 
          o.nombre === condicion.condicion.nombre
        );
        if (objetoEncontrado) {
          edicionesAplicar.push({
            tipo: 'editElement',
            opciones: {
              ...condicion.accion,
              posicion: objetoEncontrado.posicion
            }
          });
        }
        break;
        
      case 'color':
        const colorDominante = analisis.colores?.dominantes[0];
        if (colorDominante && 
            this.colorSimilar(colorDominante, condicion.condicion.color)) {
          edicionesAplicar.push({
            tipo: 'adjustColors',
            opciones: condicion.accion
          });
        }
        break;
    }
  }
  
  // Aplicar ediciones en secuencia
  let imagenActual = imagen;
  const resultados = [];
  
  for (const edicion of edicionesAplicar) {
    const resultado = await this.aplicarEdicion(imagenActual, edicion);
    resultados.push(resultado);
    imagenActual = resultado.imagenUrl;
  }
  
  return {
    imagenFinal: imagenActual,
    edicionesAplicadas: resultados.length,
    detalles: resultados
  };
}

// Ejemplo de uso
const resultado = await editarCondicional('imagen.jpg', [
  {
    tipo: 'texto',
    condicion: { buscar: 'SALE' },
    accion: { 
      prompt: 'Cambiar "SALE" por "OFERTA"',
      color: '#ff0000'
    }
  },
  {
    tipo: 'objeto',
    condicion: { nombre: 'persona' },
    accion: {
      prompt: 'Difuminar rostro para privacidad',
      intensidad: 0.8
    }
  }
]);

Pipelines de Procesamiento

Pipeline Configurable

interface PasosPipeline {
  nombre: string;
  operacion: string;
  parametros: any;
  condicion?: (resultado: any) => boolean;
  onError?: 'continuar' | 'detener' | 'reintentar';
}

class PipelineProcesamiento {
  constructor(private editor: QwenImageEdit) {}
  
  async ejecutar(imagen: string, pasos: PasosPipeline[]) {
    let imagenActual = imagen;
    const resultados = [];
    
    for (const [indice, paso] of pasos.entries()) {
      try {
        console.log(`🔄 Ejecutando paso ${indice + 1}: ${paso.nombre}`);
        
        const resultado = await this.ejecutarPaso(imagenActual, paso);
        
        // Verificar condición si existe
        if (paso.condicion && !paso.condicion(resultado)) {
          console.log(`⏭️ Saltando paso ${paso.nombre} - condición no cumplida`);
          continue;
        }
        
        resultados.push({
          paso: paso.nombre,
          resultado,
          timestamp: new Date().toISOString()
        });
        
        imagenActual = resultado.imagenUrl || imagenActual;
        
      } catch (error) {
        console.error(`❌ Error en paso ${paso.nombre}:`, error.message);
        
        switch (paso.onError) {
          case 'continuar':
            console.log('⏭️ Continuando con siguiente paso');
            break;
          case 'detener':
            throw error;
          case 'reintentar':
            console.log('🔄 Reintentando paso');
            // Implementar lógica de reintento
            break;
        }
      }
    }
    
    return {
      imagenFinal: imagenActual,
      pasos: resultados,
      duracionTotal: this.calcularDuracion(resultados)
    };
  }
  
  private async ejecutarPaso(imagen: string, paso: PasosPipeline) {
    switch (paso.operacion) {
      case 'editText':
        return await this.editor.editText({ imagen, ...paso.parametros });
      case 'editElement':
        return await this.editor.editElement({ imagen, ...paso.parametros });
      case 'transferStyle':
        return await this.editor.transferStyle({ imagen, ...paso.parametros });
      case 'enhance':
        return await this.editor.enhanceImage({ imagen, ...paso.parametros });
      case 'resize':
        return await this.editor.resizeImage({ imagen, ...paso.parametros });
      default:
        throw new Error(`Operación no soportada: ${paso.operacion}`);
    }
  }
  
  private calcularDuracion(resultados: any[]) {
    if (resultados.length < 2) return 0;
    
    const inicio = new Date(resultados[0].timestamp).getTime();
    const fin = new Date(resultados[resultados.length - 1].timestamp).getTime();
    
    return fin - inicio;
  }
}

// Ejemplo de pipeline
const pipeline = new PipelineProcesamiento(editor);

const resultado = await pipeline.ejecutar('imagen.jpg', [
  {
    nombre: 'Análisis inicial',
    operacion: 'analyze',
    parametros: { detectar: ['calidad'] },
    condicion: (r) => r.calidad.puntuacion > 0.5
  },
  {
    nombre: 'Mejora de calidad',
    operacion: 'enhance',
    parametros: { mejoras: ['nitidez', 'contraste'] },
    onError: 'continuar'
  },
  {
    nombre: 'Edición de texto',
    operacion: 'editText',
    parametros: { prompt: 'Mejorar legibilidad del texto' },
    onError: 'reintentar'
  },
  {
    nombre: 'Optimización final',
    operacion: 'resize',
    parametros: { 
      dimensiones: { ancho: 1920 },
      calidad: 90
    }
  }
]);

Integración con IA Externa

Combinación con GPT para Prompts Inteligentes

import OpenAI from 'openai';

class GeneradorPromptsIA {
  constructor(
    private openai: OpenAI,
    private editor: QwenImageEdit
  ) {}
  
  async generarPromptOptimo(imagen: string, objetivo: string) {
    // Analizar imagen primero
    const analisis = await this.editor.analyzeImage({
      imagen,
      detectar: ['texto', 'objetos', 'estilo']
    });
    
    // Generar prompt con GPT
    const contexto = `
      Imagen analizada:
      - Texto detectado: ${analisis.texto?.map(t => t.contenido).join(', ')}
      - Objetos: ${analisis.objetos?.map(o => o.nombre).join(', ')}
      - Estilo: ${analisis.estilo?.categoria}
      
      Objetivo: ${objetivo}
      
      Genera un prompt específico y detallado para editar esta imagen.
    `;
    
    const respuesta = await this.openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{
        role: 'user',
        content: contexto
      }],
      max_tokens: 200
    });
    
    return respuesta.choices[0].message.content;
  }
  
  async editarConIA(imagen: string, objetivo: string) {
    const promptOptimo = await this.generarPromptOptimo(imagen, objetivo);
    
    console.log('🤖 Prompt generado por IA:', promptOptimo);
    
    return await this.editor.editText({
      imagen,
      prompt: promptOptimo
    });
  }
}

Optimización de Performance

Caché Inteligente

import Redis from 'ioredis';
import crypto from 'crypto';

class CacheInteligente {
  private redis: Redis;
  
  constructor(redisConfig: any) {
    this.redis = new Redis(redisConfig);
  }
  
  private generarClave(operacion: string, parametros: any): string {
    const hash = crypto.createHash('sha256');
    hash.update(operacion + JSON.stringify(parametros));
    return `qwen:${hash.digest('hex')}`;
  }
  
  async obtener(operacion: string, parametros: any) {
    const clave = this.generarClave(operacion, parametros);
    const resultado = await this.redis.get(clave);
    
    if (resultado) {
      console.log('📦 Resultado desde caché');
      return JSON.parse(resultado);
    }
    
    return null;
  }
  
  async guardar(
    operacion: string, 
    parametros: any, 
    resultado: any, 
    ttl = 3600
  ) {
    const clave = this.generarClave(operacion, parametros);
    await this.redis.setex(clave, ttl, JSON.stringify(resultado));
  }
  
  async invalidar(patron: string) {
    const claves = await this.redis.keys(`qwen:${patron}*`);
    if (claves.length > 0) {
      await this.redis.del(...claves);
    }
  }
}

// Wrapper con caché
class QwenConCache {
  constructor(
    private editor: QwenImageEdit,
    private cache: CacheInteligente
  ) {}
  
  async editText(opciones: any) {
    const cacheado = await this.cache.obtener('editText', opciones);
    if (cacheado) return cacheado;
    
    const resultado = await this.editor.editText(opciones);
    await this.cache.guardar('editText', opciones, resultado);
    
    return resultado;
  }
}

Monitoreo y Métricas

Sistema de Métricas Completo

interface Metrica {
  nombre: string;
  valor: number;
  timestamp: number;
  etiquetas?: Record<string, string>;
}

class MonitorQwen {
  private metricas: Metrica[] = [];
  
  constructor(private editor: QwenImageEdit) {
    this.configurarInterceptores();
  }
  
  private configurarInterceptores() {
    const originalEditText = this.editor.editText.bind(this.editor);
    
    this.editor.editText = async (opciones: any) => {
      const inicio = Date.now();
      
      try {
        const resultado = await originalEditText(opciones);
        
        this.registrarMetrica('qwen.edit_text.duracion', Date.now() - inicio, {
          estado: 'exito',
          tamaño_imagen: this.obtenerTamañoImagen(opciones.imagen)
        });
        
        this.registrarMetrica('qwen.edit_text.total', 1, {
          estado: 'exito'
        });
        
        return resultado;
        
      } catch (error) {
        this.registrarMetrica('qwen.edit_text.duracion', Date.now() - inicio, {
          estado: 'error',
          tipo_error: error.codigo
        });
        
        this.registrarMetrica('qwen.edit_text.total', 1, {
          estado: 'error'
        });
        
        throw error;
      }
    };
  }
  
  private registrarMetrica(
    nombre: string, 
    valor: number, 
    etiquetas?: Record<string, string>
  ) {
    this.metricas.push({
      nombre,
      valor,
      timestamp: Date.now(),
      etiquetas
    });
    
    // Limpiar métricas antiguas
    const hace24h = Date.now() - (24 * 60 * 60 * 1000);
    this.metricas = this.metricas.filter(m => m.timestamp > hace24h);
  }
  
  obtenerEstadisticas() {
    const ahora = Date.now();
    const hace1h = ahora - (60 * 60 * 1000);
    
    const metricasRecientes = this.metricas.filter(m => m.timestamp > hace1h);
    
    return {
      solicitudesUltimaHora: metricasRecientes.filter(m => 
        m.nombre === 'qwen.edit_text.total'
      ).length,
      
      tasaExito: this.calcularTasaExito(metricasRecientes),
      
      tiempoPromedioMs: this.calcularTiempoPromedio(metricasRecientes),
      
      erroresPorTipo: this.agruparErrores(metricasRecientes)
    };
  }
  
  private calcularTasaExito(metricas: Metrica[]): number {
    const solicitudes = metricas.filter(m => m.nombre === 'qwen.edit_text.total');
    const exitosas = solicitudes.filter(m => m.etiquetas?.estado === 'exito');
    
    return solicitudes.length > 0 ? exitosas.length / solicitudes.length : 0;
  }
  
  private calcularTiempoPromedio(metricas: Metrica[]): number {
    const duraciones = metricas.filter(m => m.nombre === 'qwen.edit_text.duracion');
    
    if (duraciones.length === 0) return 0;
    
    const suma = duraciones.reduce((acc, m) => acc + m.valor, 0);
    return suma / duraciones.length;
  }
  
  private agruparErrores(metricas: Metrica[]): Record<string, number> {
    const errores = metricas.filter(m => 
      m.nombre === 'qwen.edit_text.total' && 
      m.etiquetas?.estado === 'error'
    );
    
    return errores.reduce((acc, m) => {
      const tipo = m.etiquetas?.tipo_error || 'desconocido';
      acc[tipo] = (acc[tipo] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
  }
}

Estas funciones avanzadas te permiten aprovechar al máximo Qwen Image Edit para casos de uso complejos y aplicaciones de producción robustas.