diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index 67a4e4c..184db7e 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -23,7 +23,14 @@ export async function POST(req: NextRequest) { console.error('Error parsing chat body:', e); } - const query = userMessage.toLowerCase().trim(); + // Normalizar consulta eliminando tildes, signos de interrogación y puntuación + const normQuery = userMessage + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[¿?¡!.,;:_()]/g, ' ') + .replace(/\s+/g, ' ') + .trim(); // 2. Obtener tenant_id dinámico del usuario autenticado let tenantId = '0b3c5fc0-6901-4caf-b26a-b6a0201d8541'; @@ -51,39 +58,64 @@ export async function POST(req: NextRequest) { return NextResponse.json([{ text }]); }; - // 3. Intención: Saludos y Presentación - if ( - query === '' || - query === 'hola' || - query === 'buenas' || - query === 'buenos dias' || - query === 'buenas tardes' || - query === 'hey' || - query.includes('que puedes hacer') || - query.includes('quien eres') || - query.includes('ayuda') - ) { + // 3. Intención: Saludos, Capacidades y "¿Qué puedo preguntar?" + const isHelpQuery = + normQuery === '' || + normQuery === 'hola' || + normQuery === 'buenas' || + normQuery === 'buenos dias' || + normQuery === 'buenas tardes' || + normQuery === 'hey' || + normQuery.includes('que puedo preguntar') || + normQuery.includes('que te puedo preguntar') || + normQuery.includes('que preguntar') || + normQuery.includes('que sabes') || + normQuery.includes('que puedes hacer') || + normQuery.includes('quien eres') || + normQuery.includes('ayuda') || + normQuery.includes('ejemplos') || + normQuery.includes('comandos') || + normQuery.includes('opciones') || + normQuery.includes('como funciona el asistente') || + normQuery.includes('instrucciones'); + + if (isHelpQuery) { return reply( - `👋 **¡Hola! Soy el Asistente Financiero y de Crecimiento de 4Ventures** para **${companyName}**.\n\n` + - `Estoy conectado directamente a tu base de datos analítica y ejecuto cálculos matemáticos deterministas (temperatura 0.0, sin alucinaciones):\n\n` + - `💡 **¿Qué puedes consultarme?**\n` + - `1. **Simulaciones presupuestarias:** *«¿Qué pasa si invierto 1.500 € en Meta?»* o *«Simular 2.000 € en Google»*.\n` + - `2. **Cumplimiento de Objetivos:** *«¿Cómo vamos con los objetivos de este mes?»*.\n` + - `3. **Embudo y Conversión:** *«Muéstrame el embudo comercial y tasa de cierre»*.\n` + - `4. **Métricas de CAC y ROAS:** *«¿Cuál es el CAC y ROAS de los últimos 30 días?»*.\n` + - `5. **Ranking de Anuncios:** *«¿Cuáles son las mejores campañas activas?»*.\n` + - `6. **Biblioteca de Activos:** *«Busca copys de Meta Ads o informes de auditoría»*.\n\n` + - `¿Qué métrica o escenario te gustaría analizar hoy?` + `👋 **¡Hola! Soy tu Asistente Financiero & de Crecimiento de 4Ventures** para **${companyName}**.\n\n` + + `Estoy conectado directamente a tu base de datos analítica y ejecuto cálculos matemáticos deterministas (temperatura 0.0, sin alucinaciones). Aquí tienes todo lo que puedes preguntarme:\n\n` + + `1. **🔮 Simulaciones de Inversión ("¿Qué pasa si...?"):**\n` + + ` - *«¿Qué pasa si invierto 1.000 € en Meta?»*\n` + + ` - *«Simular 2.500 € en Google Ads»*\n` + + ` - *«Proyección con 5.000 € de presupuesto»*\n` + + ` *(Calcula retornos p10 conservador, p50 esperado y p90 optimista según tu histórico real).* \n\n` + + `2. **🎯 Objetivos y Cumplimiento Mensual:**\n` + + ` - *«¿Cómo van los objetivos de este mes?»*\n` + + ` - *«¿Cuánto llevamos facturado vs la meta?»*\n` + + ` - *«Progreso de captación de leads»*\n\n` + + `3. **🔄 Embudo Comercial y Conversión:**\n` + + ` - *«Muéstrame el embudo comercial»*\n` + + ` - *«¿Cuál es nuestra tasa de cierre lead a venta?»*\n` + + ` - *«Ticket medio de los clientes ganados»*\n\n` + + `4. **📊 Métricas CAC y ROAS por Canal:**\n` + + ` - *«¿Cuál es el CAC y ROAS de los últimos 30 días?»*\n` + + ` - *«Rendimiento por canal publicitario»*\n\n` + + `5. **🏆 Ranking de Campañas:**\n` + + ` - *«¿Cuáles son las mejores campañas activas?»*\n` + + ` - *«¿Dónde estamos invirtiendo más?»*\n\n` + + `6. **📖 Metodología y Glosario 4Ventures:**\n` + + ` - *«¿Qué diferencia hay entre CAC de cohorte y CAC de caja?»*\n` + + ` - *«¿Cómo funciona la atribución universal a 90 días?»*\n\n` + + `💡 *Puedes hacer clic en cualquiera de los botones rápidos de abajo o escribir directamente tu consulta.*` ); } // 4. Intención: Simulación Determinista "¿Qué pasa si...?" if ( - query.includes('simul') || - query.includes('invierto') || - query.includes('pasa si') || - query.includes('proyec') || - query.includes('presupuesto') + normQuery.includes('simul') || + normQuery.includes('invierto') || + normQuery.includes('pasa si') || + normQuery.includes('proyec') || + normQuery.includes('presupuesto') ) { try { const matchNumber = query.match(/(\d+[\d\.,]*)/); @@ -93,10 +125,10 @@ export async function POST(req: NextRequest) { } let channel: string | null = null; - if (query.includes('meta') || query.includes('facebook') || query.includes('instagram')) channel = 'meta'; - else if (query.includes('google')) channel = 'google'; - else if (query.includes('tiktok')) channel = 'tiktok'; - else if (query.includes('linkedin')) channel = 'linkedin'; + if (normQuery.includes('meta') || normQuery.includes('facebook') || normQuery.includes('instagram')) channel = 'meta'; + else if (normQuery.includes('google')) channel = 'google'; + else if (normQuery.includes('tiktok')) channel = 'tiktok'; + else if (normQuery.includes('linkedin')) channel = 'linkedin'; const res = await fetch(`${supabaseUrl}/rest/v1/rpc/simulate_budget`, { method: 'POST', @@ -137,11 +169,11 @@ export async function POST(req: NextRequest) { // 5. Intención: Objetivos vs Real / Estrategia if ( - query.includes('objetivo') || - query.includes('meta') || - query.includes('factura') || - query.includes('target') || - query.includes('cumplimiento') + normQuery.includes('objetivo') || + normQuery.includes('meta') || + normQuery.includes('factura') || + normQuery.includes('target') || + normQuery.includes('cumplimiento') ) { try { const now = new Date(); @@ -176,12 +208,12 @@ export async function POST(req: NextRequest) { // 6. Intención: Embudo / Funnel / Conversión if ( - query.includes('funnel') || - query.includes('embudo') || - query.includes('tasa') || - query.includes('ticket') || - query.includes('conversion') || - query.includes('cierre') + normQuery.includes('funnel') || + normQuery.includes('embudo') || + normQuery.includes('tasa') || + normQuery.includes('ticket') || + normQuery.includes('conversion') || + normQuery.includes('cierre') ) { try { const res = await fetch(`${supabaseUrl}/rest/v1/rpc/lead_funnel`, { @@ -213,11 +245,11 @@ export async function POST(req: NextRequest) { // 7. Intención: Ranking de Campañas / Anuncios if ( - query.includes('campa') || - query.includes('anuncio') || - query.includes('ranking') || - query.includes('mejor') || - query.includes('ads') + normQuery.includes('campa') || + normQuery.includes('anuncio') || + normQuery.includes('ranking') || + normQuery.includes('mejor') || + normQuery.includes('ads') ) { try { const res = await fetch(`${supabaseUrl}/rest/v1/rpc/rank_campaigns`, { @@ -245,12 +277,12 @@ export async function POST(req: NextRequest) { // 8. Intención: RAG Documental Híbrido (Biblioteca de Activos) if ( - query.includes('document') || - query.includes('copy') || - query.includes('informe') || - query.includes('brief') || - query.includes('activo') || - query.includes('creativo') + normQuery.includes('document') || + normQuery.includes('copy') || + normQuery.includes('informe') || + normQuery.includes('brief') || + normQuery.includes('activo') || + normQuery.includes('creativo') ) { try { const res = await fetch(`${supabaseUrl}/rest/v1/rpc/match_documents`, { @@ -290,12 +322,12 @@ export async function POST(req: NextRequest) { // 9. Intención: Metodología y Glosario 4Ventures if ( - query.includes('que es cac') || - query.includes('cohorte') || - query.includes('caja') || - query.includes('atribucion') || - query.includes('90 dias') || - query.includes('metodologia') + normQuery.includes('que es cac') || + normQuery.includes('cohorte') || + normQuery.includes('caja') || + normQuery.includes('atribucion') || + normQuery.includes('90 dias') || + normQuery.includes('metodologia') ) { return reply( `📖 **Metodología Oficial de Crecimiento — 4Ventures:**\n\n` + diff --git a/src/app/dashboard/asistente/page.tsx b/src/app/dashboard/asistente/page.tsx index 9ffe7d1..3ff9aca 100644 --- a/src/app/dashboard/asistente/page.tsx +++ b/src/app/dashboard/asistente/page.tsx @@ -1,7 +1,105 @@ "use client"; import React, { useState, useRef, useEffect } from 'react'; -import { Bot, Send, Sparkles, TrendingUp, Target, Filter, DollarSign, BookOpen } from 'lucide-react'; +import { Bot, Send, Sparkles, TrendingUp, Target, Filter, DollarSign, BookOpen, HelpCircle } from 'lucide-react'; + +function FormattedMessage({ content }: { content: string }) { + const lines = content.split('\n'); + + const renderInline = (text: string) => { + const parts: React.ReactNode[] = []; + const regex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g; + let lastIndex = 0; + let match: RegExpExecArray | null; + + while ((match = regex.exec(text)) !== null) { + if (match.index > lastIndex) { + parts.push(text.substring(lastIndex, match.index)); + } + const matchStr = match[0]; + if (matchStr.startsWith('**') && matchStr.endsWith('**')) { + parts.push( + + {matchStr.slice(2, -2)} + + ); + } else if (matchStr.startsWith('*') && matchStr.endsWith('*')) { + parts.push( + + {matchStr.slice(1, -1)} + + ); + } else if (matchStr.startsWith('`') && matchStr.endsWith('`')) { + parts.push( + + {matchStr.slice(1, -1)} + + ); + } + lastIndex = regex.lastIndex; + } + + if (lastIndex < text.length) { + parts.push(text.substring(lastIndex)); + } + + return parts; + }; + + return ( +
+ {lines.map((line, lineIdx) => { + const trimmed = line.trim(); + if (!trimmed) { + return
; + } + + const isBullet = trimmed.startsWith('- ') || trimmed.startsWith('• '); + const isNumbered = /^\d+\.\s/.test(trimmed); + const isQuote = trimmed.startsWith('> '); + + let displayText = trimmed; + if (isBullet) displayText = trimmed.slice(2); + else if (isNumbered) displayText = trimmed.replace(/^\d+\.\s/, ''); + else if (isQuote) displayText = trimmed.slice(2); + + if (isBullet) { + return ( +
+ ◆ +
{renderInline(displayText)}
+
+ ); + } + + if (isNumbered) { + const numberMatch = trimmed.match(/^(\d+)\.\s/); + const num = numberMatch ? numberMatch[1] : '•'; + return ( +
+ {num}. +
{renderInline(displayText)}
+
+ ); + } + + if (isQuote) { + return ( +
+ {renderInline(displayText)} +
+ ); + } + + return ( +

+ {renderInline(trimmed)} +

+ ); + })} +
+ ); +} export default function AsistentePage() { const [input, setInput] = useState(''); @@ -88,22 +186,22 @@ export default function AsistentePage() { className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`} >
- {m.content} +
))} {loading && (
-
+
- Calculando proyecciones y consultando base de datos... + Calculando métricas sin alucinaciones...
)} @@ -112,6 +210,13 @@ export default function AsistentePage() { {/* Quick Prompts Chips */}
+