feat(whatsapp): respetar tipo de publicacion explicito (noticia, evento o publicidad) indicado en el mensaje
This commit is contained in:
parent
1f8498e5a7
commit
bbce2a7f9a
@ -207,11 +207,41 @@ function isDuplicateMessage(chatId: string, text: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function detectExplicitPublicationType(inputText: string): 'noticia' | 'evento' | 'aviso' | null {
|
||||
if (!inputText) return null;
|
||||
const lower = inputText.toLowerCase();
|
||||
|
||||
// Detección de palabras clave explícitas enviadas por el usuario:
|
||||
// Ejemplos: "Noticia: ...", "Evento: ...", "Publicidad: ...", "#noticia", "publicar como evento", etc.
|
||||
if (
|
||||
/^\s*(?:#|tipo:\s*|categoria:\s*|publicar\s+como:\s*|\[)?\s*noticia\b/i.test(lower) ||
|
||||
/\b(?:publicar\s+como\s+noticia|es\s+una\s+noticia)\b/i.test(lower)
|
||||
) {
|
||||
return 'noticia';
|
||||
}
|
||||
|
||||
if (
|
||||
/^\s*(?:#|tipo:\s*|categoria:\s*|publicar\s+como:\s*|\[)?\s*evento\b/i.test(lower) ||
|
||||
/\b(?:publicar\s+como\s+evento|es\s+un\s+evento|agenda\s+cultural|agenda\s+de\s+eventos)\b/i.test(lower)
|
||||
) {
|
||||
return 'evento';
|
||||
}
|
||||
|
||||
if (
|
||||
/^\s*(?:#|tipo:\s*|categoria:\s*|publicar\s+como:\s*|\[)?\s*(?:publicidad|aviso|clasificado|anuncio)\b/i.test(lower) ||
|
||||
/\b(?:publicar\s+como\s+publicidad|publicar\s+como\s+aviso|publicar\s+como\s+clasificado|es\s+publicidad|es\s+un\s+aviso)\b/i.test(lower)
|
||||
) {
|
||||
return 'aviso';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// LOGIC 2: PROCESAR PUBLICACIÓN ÚNICA
|
||||
// En WhatsApp, los álbumes de fotos (ej. 25 fotos + 1 texto) llegan como 26 eventos separados.
|
||||
// Las fotos individuales sin texto sustancial NO deben generar publicaciones independientes;
|
||||
// se recolectan como recursos para la publicación principal que contiene el texto de la noticia.
|
||||
const hasSubstantialText = Boolean(text && text.trim().length >= 15);
|
||||
const hasSubstantialText = Boolean(text && text.trim().length >= 10);
|
||||
|
||||
if (!hasSubstantialText) {
|
||||
console.log(`[WhatsApp Webhook] Mensaje multimedia sin texto editorial (${text?.length || 0} chars). Registrado para lote de fotos.`);
|
||||
@ -224,17 +254,24 @@ function isDuplicateMessage(chatId: string, text: string): boolean {
|
||||
return NextResponse.json({ success: true, message: 'Ignored duplicate text in burst' }, { status: 200 });
|
||||
}
|
||||
|
||||
// Detectar si el usuario definió explícitamente el tipo en el texto (noticia, evento o publicidad/aviso)
|
||||
const explicitType = detectExplicitPublicationType(text);
|
||||
if (explicitType) {
|
||||
console.log(`[WhatsApp Webhook] Tipo de publicación explícito detectado: "${explicitType.toUpperCase()}"`);
|
||||
}
|
||||
|
||||
// Descubrir el grupo central de Notificaciones y Aprobaciones (Notificaciones Almeira)
|
||||
const adminNotificationJid = await getNotificationGroupJid() || '120363428012905103@g.us';
|
||||
const destinationJid = adminNotificationJid;
|
||||
|
||||
// Notificación de Progreso 1: Enviada EXCLUSIVAMENTE a Notificaciones Almeira
|
||||
const typeInfoText = explicitType ? ` (${explicitType.toUpperCase()})` : '';
|
||||
sendWhatsAppMessage(
|
||||
destinationJid,
|
||||
`📥 *Nueva Información Recibida*\n` +
|
||||
`📥 *Nueva Información Recibida${typeInfoText}*\n` +
|
||||
`📍 *Fuente:* ${filterResult.resolvedName}\n` +
|
||||
`_Almeira Web • Asistente Editorial con IA_\n\n` +
|
||||
`🤖 Recolectando lote de imágenes y analizando contenido...`
|
||||
`🤖 Recolectando lote de imágenes y estructurando contenido...`
|
||||
).catch(e => console.warn('[WhatsApp Webhook] No se pudo enviar notificación de inicio:', e.message));
|
||||
|
||||
// Recolectamos todas las imágenes/multimedia candidatas (mensaje actual + álbum de fotos reciente del chat)
|
||||
@ -260,10 +297,10 @@ function isDuplicateMessage(chatId: string, text: string): boolean {
|
||||
console.warn('[WhatsApp Webhook] No se pudo consultar lote multimedia reciente:', e);
|
||||
}
|
||||
|
||||
console.log(`[WhatsApp Webhook] Procesando ${candidateMediaList.length} imágenes del álbum con Gemini...`);
|
||||
console.log(`[WhatsApp Webhook] Procesando ${candidateMediaList.length} imágenes del álbum con Gemini (Tipo forzado: ${explicitType || 'Auto'})...`);
|
||||
|
||||
// Llamar a Gemini para estructurar la publicación y seleccionar la mejor imagen
|
||||
const parsed = await processWhatsAppMessage(text, candidateMediaList);
|
||||
// Llamar a Gemini para estructurar la publicación y seleccionar la mejor imagen respetando el tipo explícito si existe
|
||||
const parsed = await processWhatsAppMessage(text, candidateMediaList, undefined, explicitType);
|
||||
|
||||
// Determinar dimensiones según el tipo de publicación
|
||||
// Noticias: 640x360 (16:9) | Clasificados y Eventos: 640x640 (1:1)
|
||||
|
||||
@ -23,7 +23,11 @@ export interface ProcessedPublication {
|
||||
/**
|
||||
* Fallback parser in case Gemini API is rate-limited (429) or unavailable
|
||||
*/
|
||||
function fallbackRuleBasedParser(text: string, mediaCount: number): ProcessedPublication {
|
||||
function fallbackRuleBasedParser(
|
||||
text: string,
|
||||
mediaCount: number,
|
||||
explicitType?: 'noticia' | 'evento' | 'aviso' | null
|
||||
): ProcessedPublication {
|
||||
const clean = (text || '').trim();
|
||||
const lines = clean.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
|
||||
@ -34,11 +38,13 @@ function fallbackRuleBasedParser(text: string, mediaCount: number): ProcessedPub
|
||||
}
|
||||
|
||||
const lower = clean.toLowerCase();
|
||||
let tipo: 'noticia' | 'evento' | 'aviso' = 'noticia';
|
||||
if (lower.includes('evento') || lower.includes('invitación') || lower.includes('invitacion') || lower.includes('jornada') || lower.includes('taller') || lower.includes('festival') || lower.includes('feria') || lower.includes('convocatoria')) {
|
||||
tipo = 'evento';
|
||||
} else if (lower.includes('se vende') || lower.includes('se arrienda') || lower.includes('clasificado') || lower.includes('empleo') || lower.includes('vacante') || lower.includes('servicio')) {
|
||||
tipo = 'aviso';
|
||||
let tipo: 'noticia' | 'evento' | 'aviso' = explicitType || 'noticia';
|
||||
if (!explicitType) {
|
||||
if (lower.includes('evento') || lower.includes('invitación') || lower.includes('invitacion') || lower.includes('jornada') || lower.includes('taller') || lower.includes('festival') || lower.includes('feria') || lower.includes('convocatoria')) {
|
||||
tipo = 'evento';
|
||||
} else if (lower.includes('se vende') || lower.includes('se arrienda') || lower.includes('clasificado') || lower.includes('empleo') || lower.includes('vacante') || lower.includes('servicio') || lower.includes('publicidad')) {
|
||||
tipo = 'aviso';
|
||||
}
|
||||
}
|
||||
|
||||
const seo = clean.replace(/\n+/g, ' ').substring(0, 150) || rawTitle;
|
||||
@ -63,7 +69,8 @@ function fallbackRuleBasedParser(text: string, mediaCount: number): ProcessedPub
|
||||
export async function processWhatsAppMessage(
|
||||
text: string,
|
||||
mediaParam?: string | MediaInput[] | MediaInput,
|
||||
mimeTypeParam?: string
|
||||
mimeTypeParam?: string,
|
||||
explicitType?: 'noticia' | 'evento' | 'aviso' | null
|
||||
): Promise<ProcessedPublication> {
|
||||
// Normalize mediaParam into an array of MediaInput
|
||||
let mediaList: MediaInput[] = [];
|
||||
@ -77,18 +84,23 @@ export async function processWhatsAppMessage(
|
||||
|
||||
if (!process.env.GEMINI_API_KEY) {
|
||||
console.warn('[Gemini] Sin GEMINI_API_KEY configurada, usando parser de respaldo');
|
||||
return fallbackRuleBasedParser(text, mediaList.length);
|
||||
return fallbackRuleBasedParser(text, mediaList.length, explicitType);
|
||||
}
|
||||
|
||||
const explicitTypeDirective = explicitType
|
||||
? `\n IMPORTANTE: El usuario ha definido explícitamente que la categoría DEBE SER "${explicitType}". Debes asignar tipo: "${explicitType}" obligatoriamente.\n`
|
||||
: '';
|
||||
|
||||
const systemInstruction = `
|
||||
Eres un asistente editorial experto y editor gráfico de Almeira Web.
|
||||
Analiza los mensajes y recursos multimedia recibidos desde WhatsApp para estructurarlos como publicaciones del portal.
|
||||
${explicitTypeDirective}
|
||||
Debes estructurar el mensaje en base a lo siguiente:
|
||||
1. titulo: Asigna un título claro, profesional y conciso (máx. 70 caracteres).
|
||||
2. tipo: Determina la categoría exacta:
|
||||
2. tipo: Determina la categoría exacta ${explicitType ? `(Usa obligatoriamente "${explicitType}")` : ''}:
|
||||
- "noticia": Noticias generales, política, actualidad, comunicados y reportajes (dimensiones recomendadas de imagen: 640x360 px, relación 16:9).
|
||||
- "evento": Invitaciones a actividades, jornadas, ferias, foros, congresos, festivales o convocatorias con fecha (dimensiones de imagen: 640x640 px, relación 1:1).
|
||||
- "aviso": Clasificados, ofertas de empleo, compra/venta, servicios profesionales, comunicados comunitarios (dimensiones de imagen: 640x640 px, relación 1:1).
|
||||
- "aviso": Publicidad comercial, clasificados, ofertas de empleo, compra/venta, servicios profesionales, comunicados comunitarios (dimensiones de imagen: 640x640 px, relación 1:1).
|
||||
3. seo_descripcion: Un resumen conciso de máximo 150 caracteres optimizado para redes y motores de búsqueda.
|
||||
4. contenido: El texto detallado de la publicación en HTML válido y limpio (usa <p>, <strong>, <ul>/<li> si es apropiado), preservando nombres, fechas, contactos y enlaces originales.
|
||||
5. fecha_vencimiento: Si el texto indica fecha límite del evento o del aviso (Ej. "hasta el 30 de noviembre", o "el próximo sábado"), devuélvela en formato YYYY-MM-DD. Si no aplica o es una noticia general, pasa null.
|
||||
@ -152,6 +164,9 @@ export async function processWhatsAppMessage(
|
||||
|
||||
if (response.text) {
|
||||
const result = JSON.parse(response.text) as ProcessedPublication;
|
||||
if (explicitType) {
|
||||
result.tipo = explicitType;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} catch (err: any) {
|
||||
@ -161,5 +176,5 @@ export async function processWhatsAppMessage(
|
||||
|
||||
// Si todos los modelos de Gemini superaron cuota (429), usamos el fallback seguro para no fallar el webhook
|
||||
console.warn('[Gemini] Modelos de IA agotados por cuota, activando fallback editorial inteligente');
|
||||
return fallbackRuleBasedParser(text, mediaList.length);
|
||||
return fallbackRuleBasedParser(text, mediaList.length, explicitType);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user