diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index f5a9db8..ab4d968 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -3,7 +3,97 @@ import { NextRequest } from 'next/server'; export async function POST(req: NextRequest) { const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://supabase.4ventures.es'; const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''; + const tenantId = process.env.DEFAULT_TENANT_ID || '0b3c5fc0-6901-4caf-b26a-b6a0201d8541'; + let userMessage = ''; + try { + const body = await req.json(); + userMessage = (body.message || body.prompt || '').toLowerCase(); + } catch { + // empty body fallback + } + + const headers = { + apikey: serviceKey, + Authorization: `Bearer ${serviceKey}`, + 'Content-Type': 'application/json' + }; + + // 1. Intención: Objetivos vs Real / Estrategia + if (userMessage.includes('objetivo') || userMessage.includes('meta') || userMessage.includes('factura') || userMessage.includes('estrategia')) { + try { + const now = new Date(); + const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`; + const res = await fetch(`${supabaseUrl}/rest/v1/rpc/targets_vs_actual`, { + method: 'POST', + headers, + body: JSON.stringify({ p_month: currentMonth }) + }); + if (res.ok) { + const data = await res.json(); + const t = data[0] || {}; + const revAct = Number(t.revenue_actual || 0).toLocaleString('es-ES', { minimumFractionDigits: 2 }); + const revTar = Number(t.revenue_target || 0).toLocaleString('es-ES'); + const text = `🎯 **Estado de Objetivos Mensuales (Mes ${currentMonth}):**\n\n` + + `- **Facturación:** ${revAct} € de ${revTar} € (${t.revenue_achievement_pct || 0}% de la meta)\n` + + `- **Leads Captados:** ${t.leads_actual || 0} de ${t.leads_target || 0} objetivo\n` + + `- **Clientes Ganados:** ${t.clients_actual || 0} de ${t.clients_target || 0} objetivo\n\n` + + `*Fuente: RPC targets_vs_actual (Supabase PostgreSQL)*`; + return Response.json([{ text }]); + } + } catch (e) { + console.error('Error in targets_vs_actual:', e); + } + } + + // 2. Intención: Embudo / Funnel / Conversión + if (userMessage.includes('funnel') || userMessage.includes('embudo') || userMessage.includes('tasa') || userMessage.includes('ticket')) { + try { + const res = await fetch(`${supabaseUrl}/rest/v1/rpc/lead_funnel`, { + method: 'POST', + headers, + body: JSON.stringify({ p_tenant_id: tenantId, p_days: 30 }) + }); + if (res.ok) { + const f = await res.json(); + const text = `🔄 **Embudo de Conversión Comercial (Últimos 30 días):**\n\n` + + `- **Leads Registrados:** ${f.total_leads || 0}\n` + + `- **Leads Contactados:** ${f.contacted_leads || 0}\n` + + `- **Deals en Pipeline:** ${f.deals_in_pipeline || 0}\n` + + `- **Deals Ganados:** ${f.deals_won || 0}\n` + + `- **Facturación Cerrada:** ${Number(f.revenue_won || 0).toLocaleString('es-ES', { minimumFractionDigits: 2 })} €\n` + + `- **Ticket Medio:** ${Number(f.avg_ticket || 0).toLocaleString('es-ES', { minimumFractionDigits: 2 })} €\n` + + `- **Tasa de Cierre Lead -> Venta:** ${f.conversion_lead_to_deal_pct || 0}%\n\n` + + `*Fuente: RPC lead_funnel (Supabase PostgreSQL)*`; + return Response.json([{ text }]); + } + } catch (e) { + console.error('Error in lead_funnel:', e); + } + } + + // 3. Intención: Ranking de Campañas / Anuncios + if (userMessage.includes('campa') || userMessage.includes('anuncio') || userMessage.includes('ranking') || userMessage.includes('mejor')) { + try { + const res = await fetch(`${supabaseUrl}/rest/v1/rpc/rank_campaigns`, { + method: 'POST', + headers, + body: JSON.stringify({ p_tenant_id: tenantId, p_metric: 'spend', p_limit: 5 }) + }); + if (res.ok) { + const campaigns = await res.json(); + const list = campaigns.map((c: any) => + `- **${c.campaign_name}** (${c.platform.toUpperCase()}): Inversión ${Number(c.spend || 0).toFixed(2)} €, ${c.clicks || 0} clics, ${c.impressions || 0} imp., ${c.leads || 0} leads.` + ).join('\n'); + const text = `🏆 **Top Campañas por Inversión Publicitaria:**\n\n${list || 'No constan campañas publicitarias activas en este periodo.'}\n\n*Fuente: RPC rank_campaigns (Windsor.ai pipeline)*`; + return Response.json([{ text }]); + } + } catch (e) { + console.error('Error in rank_campaigns:', e); + } + } + + // 4. Default: Consulta de KPIs Semanales (CAC & ROAS) let kpis: any[] = []; try { const res = await fetch(`${supabaseUrl}/rest/v1/kpi_weekly?select=*`, { @@ -26,10 +116,10 @@ export async function POST(req: NextRequest) { } const channelsSummary = kpis.map((k: any) => - `- Canal ${k.channel.toUpperCase()}: ${k.total_leads} leads captados, inversión de ${Number(k.total_spend || 0).toFixed(2)} €, ${k.cohort_deals_won} clientes ganados, CAC: ${k.cac_cohort} €, ROAS: ${k.roas_cohort}x.` + `- Canal **${k.channel.toUpperCase()}**: ${k.total_leads} leads captados, inversión de ${Number(k.total_spend || 0).toFixed(2)} €, ${k.cohort_deals_won} clientes ganados, facturación de ${Number(k.cohort_revenue_won || 0).toFixed(2)} €, CAC: ${k.cac_cohort} €, ROAS: ${k.roas_cohort}x.` ).join('\n'); - const answer = `📊 Resumen de Rendimiento Analítico (Fuente: kpi_weekly, corte horario):\n\n${channelsSummary}\n\n💡 Regla de Atribución: Calculado según el modelo oficial de cohorte a 60 días.`; + const answer = `📊 **Resumen de Rendimiento Analítico (Fuente: kpi_weekly, corte horario):**\n\n${channelsSummary}\n\n💡 **Regla de Atribución:** Calculado según el modelo oficial de cohorte a 60 días sin alucinación. Puedes preguntarme también sobre *objetivos*, *embudo de conversión* o *mejores campañas*.`; return Response.json([{ text: answer }]); } diff --git a/src/app/dashboard/estrategia/page.tsx b/src/app/dashboard/estrategia/page.tsx index fad75ba..fdb9dd7 100644 --- a/src/app/dashboard/estrategia/page.tsx +++ b/src/app/dashboard/estrategia/page.tsx @@ -4,12 +4,57 @@ import { Target, CheckCircle2, TrendingUp } from 'lucide-react'; export const revalidate = 0; export default async function EstrategiaPage() { + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://supabase.4ventures.es'; + const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''; + + const now = new Date(); + const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`; + + let targetData: any = { + revenue_target: 10000, + revenue_actual: 0, + revenue_achievement_pct: 0, + leads_target: 20, + leads_actual: 0, + clients_target: 4, + clients_actual: 0 + }; + + try { + const res = await fetch(`${supabaseUrl}/rest/v1/rpc/targets_vs_actual`, { + method: 'POST', + headers: { + apikey: serviceKey, + Authorization: `Bearer ${serviceKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ p_month: currentMonth }), + cache: 'no-store' + }); + if (res.ok) { + const data = await res.json(); + if (Array.isArray(data) && data.length > 0) { + targetData = data[0]; + } + } + } catch (e) { + console.error('Error fetching targets_vs_actual:', e); + } + + const revenuePct = Math.min(100, Math.round(targetData.revenue_achievement_pct || 0)); + const leadsPct = targetData.leads_target > 0 + ? Math.min(100, Math.round((Number(targetData.leads_actual) / Number(targetData.leads_target)) * 100)) + : 0; + const clientsPct = targetData.clients_target > 0 + ? Math.min(100, Math.round((Number(targetData.clients_actual) / Number(targetData.clients_target)) * 100)) + : 0; + return (

Estrategia de Crecimiento & Objetivos

- Monitoreo mensual de metas de facturación, captación de leads y posicionamiento. + Monitoreo mensual en tiempo real de metas fijadas frente al rendimiento real consolidado en Supabase.

@@ -19,11 +64,15 @@ export default async function EstrategiaPage() {

Objetivo de Facturación

-

50.000 €

+

+ {Number(targetData.revenue_actual || 0).toLocaleString('es-ES', { minimumFractionDigits: 2 })} € +

-
+
-

65% completado este mes

+

+ Meta: {Number(targetData.revenue_target || 0).toLocaleString('es-ES')} € ({revenuePct}% completado) +

@@ -31,23 +80,27 @@ export default async function EstrategiaPage() {

Meta de Leads Cualificados

-

150

+

{targetData.leads_actual || 0}

-
+
-

123 leads registrados (82%)

+

+ {targetData.leads_actual || 0} de {targetData.leads_target || 0} leads captados ({leadsPct}%) +

-

Nuevos Clientes

+

Nuevos Clientes (Won)

-

12

+

{targetData.clients_actual || 0}

-
+
-

9 cerrados ganados (75%)

+

+ {targetData.clients_actual || 0} de {targetData.clients_target || 0} clientes ganados ({clientsPct}%) +