Proeycto de images-worker creado

This commit is contained in:
unknown
2026-06-07 18:11:44 -04:00
parent fec365bb57
commit cb44779349
45 changed files with 6410 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# Generador de imágenes — via OpenRouter
## Responsabilidad
Recibir un prompt en inglés y una foto "antes", devolver el render "después" como data URI base64.
## Llamada a OpenRouter
POST https://openrouter.ai/api/v1/chat/completions
Authorization: Bearer {OPENROUTER_API_KEY}
Content-Type: application/json
Body:
{
"model": "{OPENROUTER_MODEL_IMAGEN}", // google/gemini-2.0-flash-exp-image-generation
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": promptGenerado },
{ "type": "image_url", "image_url": { "url": fotoAntesDataUri } }
]
}
]
}
## Manejo de la respuesta
Extraer la imagen generada de la respuesta. Buscar en:
1. content directo como data URI
2. expresion regular data:image/...;base64,...
3. URL de imagen
4. Partes del mensaje (choices[0].message.content si es array)
Devolver siempre como data:image/png;base64,...
## Errores
- Error de red → lanzar excepción, pipeline.service reintentará
- Respuesta 429 (rate limit) → esperar 5s y reintentar 1 vez
- Respuesta 5xx → lanzar excepción inmediatamente

View File

@@ -0,0 +1,26 @@
# Pipeline de 3 etapas
## Responsabilidad
Orquestar el procesamiento completo de un lead: desde recibir el perfil hasta entregar los renders a la app principal.
## Flujo por zona
Para cada zona del lead que tenga fotos "antes":
1. Etapa 1 → prompt-builder genera el prompt en inglés
2. Etapa 2 → image-generator produce el render
3. Etapa 3 → supervisor valida la coherencia
4. Si rechazado → reintentar máximo MAX_RETRIES veces desde Etapa 2
5. Si sigue rechazado → usar el último render de todos modos y loguear
## Reglas
- Zonas sin fotos "antes": saltar y loguear, nunca lanzar error
- Procesar todas las zonas antes de llamar a /ingesta
- Enviar todos los renders en una sola llamada con finalizar: true
- El pipeline corre en background, no bloquea el webhook
## Logs obligatorios
- [leadId] Iniciando pipeline para N zonas
- [leadId] Zona X: prompt generado
- [leadId] Zona X: imagen generada
- [leadId] Zona X: aprobada/rechazada (score: N)
- [leadId] Zona X: reintento N de MAX_RETRIES
- [leadId] Renders entregados correctamente

View File

@@ -0,0 +1,26 @@
# Prompt Builder — Claude Haiku 4.5 via OpenRouter
## Responsabilidad
Generar un prompt técnico detallado en inglés para el modelo de image-to-image a partir del contexto del lead (tipo de reforma, m², calidad, notas del cliente).
## Llamada a OpenRouter
POST https://openrouter.ai/api/v1/chat/completions
Authorization: Bearer {OPENROUTER_API_KEY}
Content-Type: application/json
Body:
{
"model": "{OPENROUTER_MODEL_TEXTO}", // anthropic/claude-3.5-haiku-20241022
"messages": [
{ "role": "system", "content": "You are an expert interior designer..." },
{ "role": "user", "content": "Generate a render prompt for a cocina renovation..." }
],
"max_tokens": 512,
"temperature": 0.5
}
## System prompt
Contenido de prompts/prompt-builder.txt
## Respuesta
Texto plano con el prompt en inglés. Sin markdown, sin JSON, sin explicaciones.

View File

@@ -0,0 +1,40 @@
# API de la app principal Reformix
## Responsabilidad
Entregar los renders generados al endpoint /ingesta de la app Reformix.
## Endpoint
POST {REFORMIX_API_URL}/api/leads/{leadId}/ingesta
Authorization: Bearer {FUNNEL_API_KEY}
Content-Type: application/json
## Body
{
"items": [
{
"tipo": "foto",
"zona": "cocina", // zona que se procesó
"momento": "despues", // siempre "despues" para renders generados
"imagen": "data:image/png;base64,..."
}
// un item por cada zona procesada
],
"finalizar": true // siempre true, dispara PDF + email + WhatsApp
}
## Enums válidos (no usar otros valores)
tipo item: "foto" | "texto"
momento: "antes" | "despues"
zona: "cocina" | "bano" | "salon" | "comedor" | "integral" | "otro"
## Códigos de respuesta
200 { ok: true } → éxito
401 → FUNNEL_API_KEY incorrecta
404 → leadId no existe, no reintentar
422 → payload mal formado, revisar el body
## Reintentos
En caso de error 5xx o error de red:
→ reintentar 3 veces con 2 segundos de espera entre intentos
→ si sigue fallando, loguear como error crítico con el leadId
→ nunca reintentar en caso de 404

View File

@@ -0,0 +1,43 @@
# Supervisor de calidad — Claude Haiku 4.5 Vision via OpenRouter
## Responsabilidad
Comparar la foto "antes" con el render "después" y verificar que el resultado es coherente
con el brief del cliente. Devolver aprobación y score.
## Llamada a OpenRouter
POST https://openrouter.ai/api/v1/chat/completions
Authorization: Bearer {OPENROUTER_API_KEY}
Content-Type: application/json
Body:
{
"model": "{OPENROUTER_MODEL_TEXTO}", // anthropic/claude-3.5-haiku-20241022
"messages": [
{ "role": "system", "content": "You are a quality supervisor..." },
{
"role": "user",
"content": [
{ "type": "text", "text": "Reforma tipo: cocina\nMetros: 12..." },
{ "type": "image_url", "image_url": { "url": "data:image/...foto_antes" } },
{ "type": "image_url", "image_url": { "url": "data:image/...render_despues" } }
]
}
],
"max_tokens": 256,
"temperature": 0.2
}
## System prompt
Contenido de prompts/supervisor.txt
## Respuesta esperada
JSON estricto:
{ "aprobado": boolean, "score": number, "motivo": string }
## Manejo de respuesta malformada
Si el modelo no devuelve JSON válido:
→ devolver { aprobado: false, score: 0, motivo: "Error parseando respuesta del supervisor" }
Nunca lanzar excepción desde el supervisor, siempre devolver el objeto.
## Umbral de aprobación
aprobado: true AND score >= SUPERVISOR_MIN_SCORE (del .env, por defecto 70)

View File

@@ -0,0 +1,35 @@
# Webhook entrante — /perfil-completo
## Responsabilidad
Recibir el payload de la app Reformix, validarlo y arrancar el pipeline en background.
## Endpoint
POST /perfil-completo
## Respuesta inmediata (siempre)
{ "ok": true, "message": "Procesando renders en background..." }
Nunca bloquear esperando al pipeline.
## Payload esperado
{
leadId: string (UUID),
cliente: { nombre, telefono, email, provincia },
reforma: {
tipo: "cocina" | "bano" | "salon" | "comedor" | "integral" | "otro",
m2Suelo: number,
calidad: "basica" | "media" | "premium",
estructural: boolean,
urgencia: "alta" | "media" | "baja",
presupuestoTarget: number // en céntimos
},
empresa: { tenantId: string, nombre: string },
zonas: Array<{
zona: string,
notas: string[],
fotos: { antes: string[], despues: string[] }
}>
}
## Validación
Usar class-validator con decoradores en webhook.dto.ts.
Si el payload no es válido → responder 400 con el error, no arrancar el pipeline.