diff --git a/frontend/components/tabs/ExecutiveSummaryTab.tsx b/frontend/components/tabs/ExecutiveSummaryTab.tsx
index f48b91f..43e783d 100644
--- a/frontend/components/tabs/ExecutiveSummaryTab.tsx
+++ b/frontend/components/tabs/ExecutiveSummaryTab.tsx
@@ -1,17 +1,124 @@
-import { TrendingUp, TrendingDown, Minus, AlertTriangle, CheckCircle, Target } from 'lucide-react';
-import { BulletChart } from '../charts/BulletChart';
+import { TrendingUp, TrendingDown, AlertTriangle, CheckCircle, Target, Activity, Clock, PhoneForwarded, Users } from 'lucide-react';
import type { AnalysisData, Finding } from '../../types';
interface ExecutiveSummaryTabProps {
data: AnalysisData;
}
-// Health Score Gauge Component
-function HealthScoreGauge({ score }: { score: number }) {
+// Compact KPI Row Component
+function KeyMetricsCard({
+ totalInteractions,
+ avgAHT,
+ avgFCR,
+ avgTransferRate,
+ ahtBenchmark,
+ fcrBenchmark
+}: {
+ totalInteractions: number;
+ avgAHT: number;
+ avgFCR: number;
+ avgTransferRate: number;
+ ahtBenchmark?: number;
+ fcrBenchmark?: number;
+}) {
+ const formatNumber = (n: number) => n >= 1000 ? `${(n / 1000).toFixed(1)}K` : n.toString();
+
+ const getAHTStatus = (aht: number) => {
+ if (aht <= 300) return { color: 'text-emerald-600', bg: 'bg-emerald-50', label: 'Excelente' };
+ if (aht <= 420) return { color: 'text-emerald-600', bg: 'bg-emerald-50', label: 'Bueno' };
+ if (aht <= 480) return { color: 'text-amber-600', bg: 'bg-amber-50', label: 'Aceptable' };
+ return { color: 'text-red-600', bg: 'bg-red-50', label: 'Alto' };
+ };
+
+ const getFCRStatus = (fcr: number) => {
+ if (fcr >= 85) return { color: 'text-emerald-600', bg: 'bg-emerald-50', label: 'Excelente' };
+ if (fcr >= 75) return { color: 'text-emerald-600', bg: 'bg-emerald-50', label: 'Bueno' };
+ if (fcr >= 65) return { color: 'text-amber-600', bg: 'bg-amber-50', label: 'Mejorable' };
+ return { color: 'text-red-600', bg: 'bg-red-50', label: 'Crítico' };
+ };
+
+ const ahtStatus = getAHTStatus(avgAHT);
+ const fcrStatus = getFCRStatus(avgFCR);
+
+ const metrics = [
+ {
+ icon: Users,
+ label: 'Interacciones',
+ value: formatNumber(totalInteractions),
+ sublabel: 'mensuales',
+ status: null
+ },
+ {
+ icon: Clock,
+ label: 'AHT Promedio',
+ value: `${Math.floor(avgAHT / 60)}:${String(avgAHT % 60).padStart(2, '0')}`,
+ sublabel: ahtBenchmark ? `Benchmark: ${Math.floor(ahtBenchmark / 60)}:${String(Math.round(ahtBenchmark) % 60).padStart(2, '0')}` : 'min:seg',
+ status: ahtStatus
+ },
+ {
+ icon: CheckCircle,
+ label: 'FCR',
+ value: `${avgFCR}%`,
+ sublabel: fcrBenchmark ? `Benchmark: ${fcrBenchmark}%` : 'Resolución 1er contacto',
+ status: fcrStatus
+ },
+ {
+ icon: PhoneForwarded,
+ label: 'Transferencias',
+ value: `${avgTransferRate}%`,
+ sublabel: avgTransferRate > 20 ? 'Requiere atención' : 'Bajo control',
+ status: avgTransferRate > 20
+ ? { color: 'text-amber-600', bg: 'bg-amber-50', label: 'Alto' }
+ : { color: 'text-emerald-600', bg: 'bg-emerald-50', label: 'OK' }
+ }
+ ];
+
+ return (
+
+
+ {metrics.map((metric) => {
+ const Icon = metric.icon;
+ return (
+
+
+
+ {metric.label}
+
+
+ {metric.value}
+ {metric.status && (
+
+ {metric.status.label}
+
+ )}
+
+
{metric.sublabel}
+
+ );
+ })}
+
+
+ );
+}
+
+// Health Score with Breakdown
+function HealthScoreDetailed({
+ score,
+ avgFCR,
+ avgAHT,
+ avgTransferRate,
+ avgCSAT
+}: {
+ score: number;
+ avgFCR: number;
+ avgAHT: number;
+ avgTransferRate: number;
+ avgCSAT: number;
+}) {
const getColor = (s: number) => {
- if (s >= 80) return '#059669'; // emerald-600
- if (s >= 60) return '#D97706'; // amber-600
- return '#DC2626'; // red-600
+ if (s >= 80) return '#059669';
+ if (s >= 60) return '#D97706';
+ return '#DC2626';
};
const getLabel = (s: number) => {
@@ -22,79 +129,129 @@ function HealthScoreGauge({ score }: { score: number }) {
};
const color = getColor(score);
- const circumference = 2 * Math.PI * 45;
+ const circumference = 2 * Math.PI * 40;
const strokeDasharray = `${(score / 100) * circumference} ${circumference}`;
+ // Calculate individual factor scores (0-100)
+ const fcrScore = Math.min(100, Math.round((avgFCR / 85) * 100));
+ const ahtScore = Math.min(100, Math.round(Math.max(0, (1 - (avgAHT - 240) / 360) * 100)));
+ const transferScore = Math.min(100, Math.round(Math.max(0, (1 - avgTransferRate / 30) * 100)));
+ const csatScore = avgCSAT;
+
+ const factors = [
+ {
+ name: 'FCR',
+ score: fcrScore,
+ weight: '30%',
+ status: fcrScore >= 80 ? 'good' : fcrScore >= 60 ? 'warning' : 'critical',
+ insight: fcrScore >= 80 ? 'Óptimo' : fcrScore >= 60 ? 'Mejorable' : 'Requiere acción'
+ },
+ {
+ name: 'Eficiencia (AHT)',
+ score: ahtScore,
+ weight: '25%',
+ status: ahtScore >= 80 ? 'good' : ahtScore >= 60 ? 'warning' : 'critical',
+ insight: ahtScore >= 80 ? 'Óptimo' : ahtScore >= 60 ? 'En rango' : 'Muy alto'
+ },
+ {
+ name: 'Transferencias',
+ score: transferScore,
+ weight: '25%',
+ status: transferScore >= 80 ? 'good' : transferScore >= 60 ? 'warning' : 'critical',
+ insight: transferScore >= 80 ? 'Bajo' : transferScore >= 60 ? 'Moderado' : 'Excesivo'
+ },
+ {
+ name: 'CSAT',
+ score: csatScore,
+ weight: '20%',
+ status: csatScore >= 80 ? 'good' : csatScore >= 60 ? 'warning' : 'critical',
+ insight: csatScore >= 80 ? 'Óptimo' : csatScore >= 60 ? 'Aceptable' : 'Bajo'
+ }
+ ];
+
+ const statusColors = {
+ good: 'bg-emerald-500',
+ warning: 'bg-amber-500',
+ critical: 'bg-red-500'
+ };
+
+ const getMainInsight = () => {
+ const weakest = factors.reduce((min, f) => f.score < min.score ? f : min, factors[0]);
+ const strongest = factors.reduce((max, f) => f.score > max.score ? f : max, factors[0]);
+
+ if (score >= 80) {
+ return `Rendimiento destacado en ${strongest.name}. Mantener estándares actuales.`;
+ } else if (score >= 60) {
+ return `Oportunidad de mejora en ${weakest.name} (${weakest.insight.toLowerCase()}).`;
+ } else {
+ return `Priorizar mejora en ${weakest.name}: impacto directo en satisfacción del cliente.`;
+ }
+ };
+
return (
-
-
Health Score General
-
-
-
-
{score}
-
/100
+
+
+ {/* Gauge */}
+
+
+ {/* Breakdown */}
+
+
Health Score - Desglose
+
+
+ {factors.map((factor) => (
+
+
{factor.name}
+
+
{factor.score}
+
+ {factor.insight}
+
+
+ ))}
+
+
+ {/* Key Insight */}
+
+
+ Insight:
+ {getMainInsight()}
+
+
-
{getLabel(score)}
);
}
-// KPI Card Component
-function KpiCard({ label, value, change, changeType }: {
- label: string;
- value: string;
- change?: string;
- changeType?: 'positive' | 'negative' | 'neutral';
-}) {
- const ChangeIcon = changeType === 'positive' ? TrendingUp :
- changeType === 'negative' ? TrendingDown : Minus;
-
- const changeColor = changeType === 'positive' ? 'text-emerald-600' :
- changeType === 'negative' ? 'text-red-600' : 'text-slate-500';
-
- return (
-
-
{label}
-
{value}
- {change && (
-
-
- {change}
-
- )}
-
- );
-}
-
-// Top Opportunities Component (McKinsey style)
+// Top Opportunities Component
function TopOpportunities({ findings, opportunities }: {
findings: Finding[];
opportunities: { name: string; impact: number; savings: number }[];
}) {
- // Combine critical findings and high-impact opportunities
const items = [
...findings
.filter(f => f.type === 'critical' || f.type === 'warning')
@@ -108,49 +265,49 @@ function TopOpportunities({ findings, opportunities }: {
})),
].slice(0, 3);
- // Fill with opportunities if not enough findings
if (items.length < 3) {
const remaining = 3 - items.length;
opportunities
.sort((a, b) => b.savings - a.savings)
.slice(0, remaining)
- .forEach((opp, i) => {
- items.push({
- rank: items.length + 1,
- title: opp.name,
- metric: `€${opp.savings.toLocaleString()} ahorro potencial`,
- action: 'Implementar',
- type: 'info' as const
- });
+ .forEach(() => {
+ const opp = opportunities[items.length];
+ if (opp) {
+ items.push({
+ rank: items.length + 1,
+ title: opp.name,
+ metric: `€${opp.savings.toLocaleString()} ahorro potencial`,
+ action: 'Implementar',
+ type: 'info' as const
+ });
+ }
});
}
const getIcon = (type: string) => {
- if (type === 'critical') return
;
- if (type === 'warning') return
;
- return
;
+ if (type === 'critical') return
;
+ if (type === 'warning') return
;
+ return
;
};
return (
-
Top 3 Oportunidades
-
+
Top 3 Oportunidades
+
{items.map((item) => (
-
-
+
+
{item.rank}
-
+
{getIcon(item.type)}
- {item.title}
+ {item.title}
{item.metric && (
-
{item.metric}
+
{item.metric}
)}
-
- → {item.action}
-
+
→ {item.action}
))}
@@ -159,8 +316,38 @@ function TopOpportunities({ findings, opportunities }: {
);
}
+// Economic Summary Compact
+function EconomicSummary({ economicModel }: { economicModel: AnalysisData['economicModel'] }) {
+ return (
+
+
Impacto Económico
+
+
+
+
Coste Anual
+
€{(economicModel.currentAnnualCost / 1000).toFixed(0)}K
+
+
+
Ahorro Potencial
+
€{(economicModel.annualSavings / 1000).toFixed(0)}K
+
+
+
+
+
+
ROI 3 años
+
{economicModel.roi3yr}%
+
+
+
Payback
+
{economicModel.paybackMonths}m
+
+
+
+ );
+}
+
export function ExecutiveSummaryTab({ data }: ExecutiveSummaryTabProps) {
- // Extract key KPIs for bullet charts
const totalInteractions = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
const avgAHT = data.heatmapData.length > 0
? Math.round(data.heatmapData.reduce((sum, h) => sum + h.aht_seconds, 0) / data.heatmapData.length)
@@ -171,119 +358,41 @@ export function ExecutiveSummaryTab({ data }: ExecutiveSummaryTabProps) {
const avgTransferRate = data.heatmapData.length > 0
? Math.round(data.heatmapData.reduce((sum, h) => sum + h.metrics.transfer_rate, 0) / data.heatmapData.length)
: 0;
+ const avgCSAT = data.heatmapData.length > 0
+ ? Math.round(data.heatmapData.reduce((sum, h) => sum + h.metrics.csat, 0) / data.heatmapData.length)
+ : 0;
- // Find benchmark data
const ahtBenchmark = data.benchmarkData.find(b => b.kpi.toLowerCase().includes('aht'));
const fcrBenchmark = data.benchmarkData.find(b => b.kpi.toLowerCase().includes('fcr'));
return (
-
- {/* Main Grid: KPIs + Health Score */}
-
- {/* Summary KPIs */}
- {data.summaryKpis.slice(0, 3).map((kpi) => (
-
- ))}
+
+ {/* Key Metrics Bar */}
+
- {/* Health Score Gauge */}
-
-
+ {/* Health Score with Breakdown */}
+
- {/* Bullet Charts Row */}
-
- v >= 1000 ? `${(v / 1000).toFixed(1)}K` : v.toString()}
- />
-
- 480s poor, 420-480 ok, <420 good
- unit="s"
- percentile={ahtBenchmark?.percentile}
- inverse={true}
- formatValue={(v) => v.toString()}
- />
-
- 75 good
- unit="%"
- percentile={fcrBenchmark?.percentile}
- formatValue={(v) => v.toString()}
- />
-
- 25% poor, 15-25 ok, <15 good
- unit="%"
- inverse={true}
- formatValue={(v) => v.toString()}
- />
-
-
- {/* Bottom Row: Top Opportunities + Economic Summary */}
-
+ {/* Bottom Row */}
+
-
- {/* Economic Impact Summary */}
-
-
Impacto Económico
-
-
-
Coste Anual Actual
-
- €{data.economicModel.currentAnnualCost.toLocaleString()}
-
-
-
-
Ahorro Potencial
-
- €{data.economicModel.annualSavings.toLocaleString()}
-
-
-
-
Inversión Inicial
-
- €{data.economicModel.initialInvestment.toLocaleString()}
-
-
-
-
ROI a 3 Años
-
- {data.economicModel.roi3yr}%
-
-
-
-
- {/* Payback indicator */}
-
-
- Payback
-
- {data.economicModel.paybackMonths} meses
-
-
-
-
+
);