import { TrendingUp, TrendingDown, AlertTriangle, CheckCircle, Target, Activity, Clock, PhoneForwarded, Users } from 'lucide-react';
import type { AnalysisData, Finding } from '../../types';
interface ExecutiveSummaryTabProps {
data: AnalysisData;
}
// 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';
if (s >= 60) return '#D97706';
return '#DC2626';
};
const getLabel = (s: number) => {
if (s >= 80) return 'Excelente';
if (s >= 60) return 'Bueno';
if (s >= 40) return 'Regular';
return 'Crítico';
};
const color = getColor(score);
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 (
{/* Gauge */}
{/* Breakdown */}
Health Score - Desglose
{factors.map((factor) => (
{factor.name}
{factor.score}
{factor.insight}
))}
{/* Key Insight */}
Insight:
{getMainInsight()}
);
}
// Top Opportunities Component
function TopOpportunities({ findings, opportunities }: {
findings: Finding[];
opportunities: { name: string; impact: number; savings: number }[];
}) {
const items = [
...findings
.filter(f => f.type === 'critical' || f.type === 'warning')
.slice(0, 3)
.map((f, i) => ({
rank: i + 1,
title: f.title || f.text.split(':')[0],
metric: f.text.includes(':') ? f.text.split(':')[1].trim() : '',
action: f.description || 'Acción requerida',
type: f.type as 'critical' | 'warning' | 'info'
})),
].slice(0, 3);
if (items.length < 3) {
const remaining = 3 - items.length;
opportunities
.sort((a, b) => b.savings - a.savings)
.slice(0, remaining)
.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 ;
};
return (
Top 3 Oportunidades
{items.map((item) => (
{item.rank}
{getIcon(item.type)}
{item.title}
{item.metric && (
{item.metric}
)}
→ {item.action}
))}
);
}
// 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) {
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)
: 0;
const avgFCR = data.heatmapData.length > 0
? Math.round(data.heatmapData.reduce((sum, h) => sum + h.metrics.fcr, 0) / data.heatmapData.length)
: 0;
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;
const ahtBenchmark = data.benchmarkData.find(b => b.kpi.toLowerCase().includes('aht'));
const fcrBenchmark = data.benchmarkData.find(b => b.kpi.toLowerCase().includes('fcr'));
return (
{/* Key Metrics Bar */}
{/* Health Score with Breakdown */}
{/* Bottom Row */}
);
}
export default ExecutiveSummaryTab;