Merge pull request #12 from sujucu70/claude/check-agent-readiness-status-Exnpc
Claude/check agent readiness status exnpc
This commit is contained in:
@@ -221,13 +221,31 @@ const getTierExplanations = (t: any): TierExplanation[] => [
|
|||||||
function AgenticMethodologyIntro({
|
function AgenticMethodologyIntro({
|
||||||
tierData,
|
tierData,
|
||||||
totalVolume,
|
totalVolume,
|
||||||
totalQueues
|
totalQueues,
|
||||||
|
t
|
||||||
}: {
|
}: {
|
||||||
tierData: TierDataType;
|
tierData: TierDataType;
|
||||||
totalVolume: number;
|
totalVolume: number;
|
||||||
totalQueues: number;
|
totalQueues: number;
|
||||||
|
t: any;
|
||||||
}) {
|
}) {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const componentRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const tierExplanations = getTierExplanations(t);
|
||||||
|
|
||||||
|
// Close when clicking outside
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (isExpanded && componentRef.current && !componentRef.current.contains(event.target as Node)) {
|
||||||
|
setIsExpanded(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}
|
||||||
|
}, [isExpanded]);
|
||||||
|
|
||||||
// Calcular estadísticas para el roadmap
|
// Calcular estadísticas para el roadmap
|
||||||
const automatizableQueues = tierData.AUTOMATE.count + tierData.ASSIST.count;
|
const automatizableQueues = tierData.AUTOMATE.count + tierData.ASSIST.count;
|
||||||
@@ -239,6 +257,7 @@ function AgenticMethodologyIntro({
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div ref={componentRef}>
|
||||||
<Card padding="none">
|
<Card padding="none">
|
||||||
{/* Header con toggle */}
|
{/* Header con toggle */}
|
||||||
<div
|
<div
|
||||||
@@ -252,10 +271,10 @@ function AgenticMethodologyIntro({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h2 className="font-semibold text-gray-900 flex items-center gap-2">
|
||||||
¿Qué es el Índice de Agentic Readiness?
|
{t('agenticReadiness.methodology.title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-gray-600 mt-0.5">
|
<p className="text-sm text-gray-600 mt-0.5">
|
||||||
Metodología de evaluación y guía de navegación de este análisis
|
{t('agenticReadiness.methodology.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,38 +295,36 @@ function AgenticMethodologyIntro({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
<Brain className="w-4 h-4 text-blue-600" />
|
<Brain className="w-4 h-4 text-blue-600" />
|
||||||
Definición del Índice
|
{t('agenticReadiness.methodology.definition')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="bg-gray-50 rounded-lg p-4">
|
<div className="bg-gray-50 rounded-lg p-4">
|
||||||
<p className="text-sm text-gray-700 mb-3">
|
<p className="text-sm text-gray-700 mb-3">
|
||||||
El <strong>Índice de Agentic Readiness</strong> evalúa qué porcentaje del volumen de interacciones
|
{t('agenticReadiness.methodology.definitionDesc')}
|
||||||
está preparado para ser gestionado por agentes virtuales o asistido por IA. Se calcula
|
|
||||||
analizando cada cola individualmente según 5 factores clave:
|
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-2 text-xs">
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-2 text-xs">
|
||||||
<div className="bg-white rounded p-2 border border-gray-200">
|
<div className="bg-white rounded p-2 border border-gray-200">
|
||||||
<div className="font-bold text-blue-600">Predictibilidad</div>
|
<div className="font-bold text-blue-600">{t('agenticReadiness.methodology.factor1')}</div>
|
||||||
<div className="text-gray-500">30% peso</div>
|
<div className="text-gray-500">{t('agenticReadiness.methodology.factor1Desc')}</div>
|
||||||
<div className="text-gray-600 mt-1">CV AHT <75%</div>
|
<div className="text-gray-600 mt-1">CV AHT <75%</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white rounded p-2 border border-gray-200">
|
<div className="bg-white rounded p-2 border border-gray-200">
|
||||||
<div className="font-bold text-blue-600">Resolutividad</div>
|
<div className="font-bold text-blue-600">{t('agenticReadiness.methodology.factor2')}</div>
|
||||||
<div className="text-gray-500">25% peso</div>
|
<div className="text-gray-500">{t('agenticReadiness.methodology.factor2Desc')}</div>
|
||||||
<div className="text-gray-600 mt-1">FCR alto, Transfer bajo</div>
|
<div className="text-gray-600 mt-1">FCR alto, Transfer bajo</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white rounded p-2 border border-gray-200">
|
<div className="bg-white rounded p-2 border border-gray-200">
|
||||||
<div className="font-bold text-blue-600">Volumen</div>
|
<div className="font-bold text-blue-600">{t('agenticReadiness.methodology.factor3')}</div>
|
||||||
<div className="text-gray-500">25% peso</div>
|
<div className="text-gray-500">{t('agenticReadiness.methodology.factor3Desc')}</div>
|
||||||
<div className="text-gray-600 mt-1">ROI positivo >500/mes</div>
|
<div className="text-gray-600 mt-1">ROI positivo >500/mes</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white rounded p-2 border border-gray-200">
|
<div className="bg-white rounded p-2 border border-gray-200">
|
||||||
<div className="font-bold text-blue-600">Calidad Datos</div>
|
<div className="font-bold text-blue-600">{t('agenticReadiness.methodology.factor4')}</div>
|
||||||
<div className="text-gray-500">10% peso</div>
|
<div className="text-gray-500">{t('agenticReadiness.methodology.factor4Desc')}</div>
|
||||||
<div className="text-gray-600 mt-1">% registros válidos</div>
|
<div className="text-gray-600 mt-1">% registros válidos</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white rounded p-2 border border-gray-200">
|
<div className="bg-white rounded p-2 border border-gray-200">
|
||||||
<div className="font-bold text-blue-600">Simplicidad</div>
|
<div className="font-bold text-blue-600">{t('agenticReadiness.methodology.factor5')}</div>
|
||||||
<div className="text-gray-500">10% peso</div>
|
<div className="text-gray-500">{t('agenticReadiness.methodology.factor5Desc')}</div>
|
||||||
<div className="text-gray-600 mt-1">AHT bajo, proceso simple</div>
|
<div className="text-gray-600 mt-1">AHT bajo, proceso simple</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,13 +335,13 @@ function AgenticMethodologyIntro({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
<BarChart2 className="w-4 h-4 text-blue-600" />
|
<BarChart2 className="w-4 h-4 text-blue-600" />
|
||||||
Las 4 Categorías de Clasificación
|
{t('agenticReadiness.methodology.categories')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-gray-600 mb-3">
|
<p className="text-sm text-gray-600 mb-3">
|
||||||
Cada cola se clasifica en uno de los siguientes tiers según su score compuesto:
|
{t('agenticReadiness.methodology.categoriesDesc')}
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
{TIER_EXPLANATIONS.map(tier => (
|
{tierExplanations.map(tier => (
|
||||||
<div
|
<div
|
||||||
key={tier.tier}
|
key={tier.tier}
|
||||||
className="rounded-lg border p-3"
|
className="rounded-lg border p-3"
|
||||||
@@ -351,11 +368,11 @@ function AgenticMethodologyIntro({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
<Target className="w-4 h-4 text-blue-600" />
|
<Target className="w-4 h-4 text-blue-600" />
|
||||||
Contenido de este Análisis
|
{t('agenticReadiness.methodology.content')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="bg-blue-50 rounded-lg p-4 border border-blue-100">
|
<div className="bg-blue-50 rounded-lg p-4 border border-blue-100">
|
||||||
<p className="text-sm text-gray-700 mb-4">
|
<p className="text-sm text-gray-700 mb-4">
|
||||||
Este tab presenta el análisis de automatización en el siguiente orden:
|
{t('agenticReadiness.methodology.contentDesc')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -365,10 +382,9 @@ function AgenticMethodologyIntro({
|
|||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-gray-900">Visión Global de Distribución</div>
|
<div className="font-medium text-gray-900">{t('agenticReadiness.methodology.globalVision')}</div>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
Porcentaje de volumen en cada categoría ({automatizablePct}% automatizable).
|
{t('agenticReadiness.methodology.globalVisionDesc', { pct: automatizablePct, total: totalVolume.toLocaleString() })}
|
||||||
Las 4 cajas muestran cómo se distribuyen las {totalVolume.toLocaleString()} interacciones.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -380,14 +396,13 @@ function AgenticMethodologyIntro({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-gray-900">
|
<div className="font-medium text-gray-900">
|
||||||
Candidatos Prioritarios
|
{t('agenticReadiness.methodology.priorityCandidates')}
|
||||||
<span className="ml-2 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700">
|
<span className="ml-2 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700">
|
||||||
{automatizableQueues} colas
|
{automatizableQueues} {t('agenticReadiness.table.queues')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
Colas AUTOMATE y ASSIST ordenadas por potencial de ahorro.
|
{t('agenticReadiness.methodology.priorityCandidatesDesc')}
|
||||||
Quick wins con mayor ROI para priorizar en el roadmap.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -399,14 +414,13 @@ function AgenticMethodologyIntro({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-gray-900">
|
<div className="font-medium text-gray-900">
|
||||||
Colas a Optimizar
|
{t('agenticReadiness.methodology.queuesToOptimize')}
|
||||||
<span className="ml-2 text-xs px-2 py-0.5 rounded-full bg-amber-100 text-amber-700">
|
<span className="ml-2 text-xs px-2 py-0.5 rounded-full bg-amber-100 text-amber-700">
|
||||||
{optimizableQueues} colas
|
{optimizableQueues} {t('agenticReadiness.table.queues')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
Tier AUGMENT: requieren estandarización previa (reducir variabilidad,
|
{t('agenticReadiness.methodology.queuesToOptimizeDesc')}
|
||||||
mejorar FCR, documentar procesos) antes de automatizar.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -418,14 +432,13 @@ function AgenticMethodologyIntro({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-gray-900">
|
<div className="font-medium text-gray-900">
|
||||||
No Automatizables
|
{t('agenticReadiness.methodology.notAutomatable')}
|
||||||
<span className="ml-2 text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-700">
|
<span className="ml-2 text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-700">
|
||||||
{humanOnlyQueues} colas
|
{humanOnlyQueues} {t('agenticReadiness.table.queues')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
Tier HUMAN-ONLY: volumen insuficiente (ROI negativo), calidad de datos baja,
|
{t('agenticReadiness.methodology.notAutomatableDesc')}
|
||||||
variabilidad extrema, o complejidad que requiere juicio humano.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -435,9 +448,7 @@ function AgenticMethodologyIntro({
|
|||||||
|
|
||||||
{/* Nota metodológica */}
|
{/* Nota metodológica */}
|
||||||
<div className="text-xs text-gray-500 border-t border-gray-200 pt-4">
|
<div className="text-xs text-gray-500 border-t border-gray-200 pt-4">
|
||||||
<strong>Nota metodológica:</strong> El índice se calcula por cola individual, no como promedio global.
|
{t('agenticReadiness.methodology.methodNote')}
|
||||||
Esto permite identificar oportunidades específicas incluso cuando la media operativa sea baja.
|
|
||||||
Los umbrales están calibrados según benchmarks de industria (COPC, Gartner).
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -445,15 +456,16 @@ function AgenticMethodologyIntro({
|
|||||||
{/* Mini resumen cuando está colapsado */}
|
{/* Mini resumen cuando está colapsado */}
|
||||||
{!isExpanded && (
|
{!isExpanded && (
|
||||||
<div className="px-5 py-3 bg-gray-50 text-xs text-gray-600 flex items-center gap-4 flex-wrap">
|
<div className="px-5 py-3 bg-gray-50 text-xs text-gray-600 flex items-center gap-4 flex-wrap">
|
||||||
<span><strong>5 factores</strong> ponderados</span>
|
<span><strong>{t('agenticReadiness.methodology.factors5')}</strong></span>
|
||||||
<span>→</span>
|
<span>→</span>
|
||||||
<span><strong>4 categorías</strong> de clasificación</span>
|
<span><strong>{t('agenticReadiness.methodology.categories4')}</strong></span>
|
||||||
<span>→</span>
|
<span>→</span>
|
||||||
<span><strong>{totalQueues} colas</strong> analizadas</span>
|
<span><strong>{totalQueues} {t('agenticReadiness.summary.queuesAnalyzed')}</strong></span>
|
||||||
<span className="ml-auto text-blue-600 font-medium">Click para expandir metodología</span>
|
<span className="ml-auto text-blue-600 font-medium">{t('agenticReadiness.methodology.clickToExpand')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,83 +482,95 @@ interface FactorConfig {
|
|||||||
implications: { high: string; low: string };
|
implications: { high: string; low: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const FACTOR_CONFIGS: FactorConfig[] = [
|
const getFactorConfigs = (t: any): FactorConfig[] => [
|
||||||
{
|
{
|
||||||
id: 'predictibilidad',
|
id: 'predictibilidad',
|
||||||
title: 'Predictibilidad',
|
title: t('agenticReadiness.factorConfigs.predictability.title'),
|
||||||
weight: 0.30,
|
weight: 0.30,
|
||||||
icon: Brain,
|
icon: Brain,
|
||||||
color: '#6D84E3',
|
color: '#6D84E3',
|
||||||
description: 'Consistencia en tiempos de gestión',
|
description: t('agenticReadiness.factorConfigs.predictability.description'),
|
||||||
methodology: 'Score = 10 - (CV_AHT / 10). CV AHT < 30% → Score > 7',
|
methodology: t('agenticReadiness.factorConfigs.predictability.methodology'),
|
||||||
benchmark: 'CV AHT óptimo < 25%',
|
benchmark: t('agenticReadiness.factorConfigs.predictability.benchmark'),
|
||||||
implications: { high: 'Tiempos consistentes, ideal para IA', low: 'Requiere estandarización' }
|
implications: {
|
||||||
|
high: t('agenticReadiness.factorConfigs.predictability.highImplication'),
|
||||||
|
low: t('agenticReadiness.factorConfigs.predictability.lowImplication')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'complejidad_inversa',
|
id: 'complejidad_inversa',
|
||||||
title: 'Simplicidad',
|
title: t('agenticReadiness.factorConfigs.inverseComplexity.title'),
|
||||||
weight: 0.20,
|
weight: 0.20,
|
||||||
icon: Zap,
|
icon: Zap,
|
||||||
color: '#10B981',
|
color: '#10B981',
|
||||||
description: 'Bajo nivel de juicio humano requerido',
|
description: t('agenticReadiness.factorConfigs.inverseComplexity.description'),
|
||||||
methodology: 'Score = 10 - (Tasa_Transfer × 0.4). Transfer <10% → Score > 6',
|
methodology: t('agenticReadiness.factorConfigs.inverseComplexity.methodology'),
|
||||||
benchmark: 'Transferencias óptimas <10%',
|
benchmark: t('agenticReadiness.factorConfigs.inverseComplexity.benchmark'),
|
||||||
implications: { high: 'Procesos simples, automatizables', low: 'Alta complejidad, requiere copilot' }
|
implications: {
|
||||||
|
high: t('agenticReadiness.factorConfigs.inverseComplexity.highImplication'),
|
||||||
|
low: t('agenticReadiness.factorConfigs.inverseComplexity.lowImplication')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'repetitividad',
|
id: 'repetitividad',
|
||||||
title: 'Volumen',
|
title: t('agenticReadiness.factorConfigs.repeatability.title'),
|
||||||
weight: 0.25,
|
weight: 0.25,
|
||||||
icon: Repeat,
|
icon: Repeat,
|
||||||
color: '#F59E0B',
|
color: '#F59E0B',
|
||||||
description: 'Escala para justificar inversión',
|
description: t('agenticReadiness.factorConfigs.repeatability.description'),
|
||||||
methodology: 'Score = log10(Volumen) normalizado. >5000 → 10, <100 → 2',
|
methodology: t('agenticReadiness.factorConfigs.repeatability.methodology'),
|
||||||
benchmark: 'ROI positivo requiere >500/mes',
|
benchmark: t('agenticReadiness.factorConfigs.repeatability.benchmark'),
|
||||||
implications: { high: 'Alto volumen justifica inversión', low: 'Considerar soluciones compartidas' }
|
implications: {
|
||||||
|
high: t('agenticReadiness.factorConfigs.repeatability.highImplication'),
|
||||||
|
low: t('agenticReadiness.factorConfigs.repeatability.lowImplication')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'roi_potencial',
|
id: 'roi_potencial',
|
||||||
title: 'ROI Potencial',
|
title: t('agenticReadiness.factorConfigs.roiPotential.title'),
|
||||||
weight: 0.25,
|
weight: 0.25,
|
||||||
icon: TrendingUp,
|
icon: TrendingUp,
|
||||||
color: '#8B5CF6',
|
color: '#8B5CF6',
|
||||||
description: 'Retorno económico esperado',
|
description: t('agenticReadiness.factorConfigs.roiPotential.description'),
|
||||||
methodology: 'Score basado en coste anual total. >€500K → 10',
|
methodology: t('agenticReadiness.factorConfigs.roiPotential.methodology'),
|
||||||
benchmark: 'ROI >150% a 12 meses',
|
benchmark: t('agenticReadiness.factorConfigs.roiPotential.benchmark'),
|
||||||
implications: { high: 'Caso de negocio sólido', low: 'ROI marginal, evaluar otros beneficios' }
|
implications: {
|
||||||
|
high: t('agenticReadiness.factorConfigs.roiPotential.highImplication'),
|
||||||
|
low: t('agenticReadiness.factorConfigs.roiPotential.lowImplication')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// v3.4: Helper para obtener estilo de Tier
|
// v3.4: Helper para obtener estilo de Tier
|
||||||
function getTierStyle(tier: AgenticTier): { bg: string; text: string; icon: React.ReactNode; label: string } {
|
function getTierStyle(tier: AgenticTier, t: any): { bg: string; text: string; icon: React.ReactNode; label: string } {
|
||||||
switch (tier) {
|
switch (tier) {
|
||||||
case 'AUTOMATE':
|
case 'AUTOMATE':
|
||||||
return {
|
return {
|
||||||
bg: 'bg-emerald-100',
|
bg: 'bg-emerald-100',
|
||||||
text: 'text-emerald-700',
|
text: 'text-emerald-700',
|
||||||
icon: <Sparkles className="w-3 h-3" />,
|
icon: <Sparkles className="w-3 h-3" />,
|
||||||
label: 'Automatizar'
|
label: t('agenticReadiness.tiers.automate')
|
||||||
};
|
};
|
||||||
case 'ASSIST':
|
case 'ASSIST':
|
||||||
return {
|
return {
|
||||||
bg: 'bg-blue-100',
|
bg: 'bg-blue-100',
|
||||||
text: 'text-blue-700',
|
text: 'text-blue-700',
|
||||||
icon: <Bot className="w-3 h-3" />,
|
icon: <Bot className="w-3 h-3" />,
|
||||||
label: 'Asistir'
|
label: t('agenticReadiness.tiers.assist')
|
||||||
};
|
};
|
||||||
case 'AUGMENT':
|
case 'AUGMENT':
|
||||||
return {
|
return {
|
||||||
bg: 'bg-amber-100',
|
bg: 'bg-amber-100',
|
||||||
text: 'text-amber-700',
|
text: 'text-amber-700',
|
||||||
icon: <TrendingUp className="w-3 h-3" />,
|
icon: <TrendingUp className="w-3 h-3" />,
|
||||||
label: 'Optimizar'
|
label: t('agenticReadiness.tiers.optimize')
|
||||||
};
|
};
|
||||||
case 'HUMAN-ONLY':
|
case 'HUMAN-ONLY':
|
||||||
return {
|
return {
|
||||||
bg: 'bg-gray-100',
|
bg: 'bg-gray-100',
|
||||||
text: 'text-gray-600',
|
text: 'text-gray-600',
|
||||||
icon: <Users className="w-3 h-3" />,
|
icon: <Users className="w-3 h-3" />,
|
||||||
label: 'Humano'
|
label: t('agenticReadiness.tiers.human')
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
@@ -559,27 +583,27 @@ function getTierStyle(tier: AgenticTier): { bg: string; text: string; icon: Reac
|
|||||||
}
|
}
|
||||||
|
|
||||||
// v3.4: Componente de desglose de score
|
// v3.4: Componente de desglose de score
|
||||||
function ScoreBreakdownTooltip({ breakdown }: { breakdown: AgenticScoreBreakdown }) {
|
function ScoreBreakdownTooltip({ breakdown, t }: { breakdown: AgenticScoreBreakdown; t: any }) {
|
||||||
return (
|
return (
|
||||||
<div className="text-xs space-y-1">
|
<div className="text-xs space-y-1">
|
||||||
<div className="flex justify-between gap-4">
|
<div className="flex justify-between gap-4">
|
||||||
<span>Predictibilidad (30%)</span>
|
<span>{t('agenticReadiness.scoreBreakdown.predictability')}</span>
|
||||||
<span className="font-medium">{breakdown.predictibilidad.toFixed(1)}</span>
|
<span className="font-medium">{breakdown.predictibilidad.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between gap-4">
|
<div className="flex justify-between gap-4">
|
||||||
<span>Resolutividad (25%)</span>
|
<span>{t('agenticReadiness.scoreBreakdown.resolution')}</span>
|
||||||
<span className="font-medium">{breakdown.resolutividad.toFixed(1)}</span>
|
<span className="font-medium">{breakdown.resolutividad.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between gap-4">
|
<div className="flex justify-between gap-4">
|
||||||
<span>Volumen (25%)</span>
|
<span>{t('agenticReadiness.scoreBreakdown.volume')}</span>
|
||||||
<span className="font-medium">{breakdown.volumen.toFixed(1)}</span>
|
<span className="font-medium">{breakdown.volumen.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between gap-4">
|
<div className="flex justify-between gap-4">
|
||||||
<span>Calidad Datos (10%)</span>
|
<span>{t('agenticReadiness.scoreBreakdown.dataQuality')}</span>
|
||||||
<span className="font-medium">{breakdown.calidadDatos.toFixed(1)}</span>
|
<span className="font-medium">{breakdown.calidadDatos.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between gap-4">
|
<div className="flex justify-between gap-4">
|
||||||
<span>Simplicidad (10%)</span>
|
<span>{t('agenticReadiness.scoreBreakdown.simplicity')}</span>
|
||||||
<span className="font-medium">{breakdown.simplicidad.toFixed(1)}</span>
|
<span className="font-medium">{breakdown.simplicidad.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -651,14 +675,15 @@ function calculateFactorsFromData(heatmapData: HeatmapDataPoint[]): { id: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate weighted global score from factors
|
// Calculate weighted global score from factors
|
||||||
function calculateWeightedScore(factors: { id: string; score: number }[]): number {
|
function calculateWeightedScore(factors: { id: string; score: number }[], t: any): number {
|
||||||
if (factors.length === 0) return 5;
|
if (factors.length === 0) return 5;
|
||||||
|
|
||||||
|
const factorConfigs = getFactorConfigs(t);
|
||||||
let weightedSum = 0;
|
let weightedSum = 0;
|
||||||
let totalWeight = 0;
|
let totalWeight = 0;
|
||||||
|
|
||||||
for (const factor of factors) {
|
for (const factor of factors) {
|
||||||
const config = FACTOR_CONFIGS.find(c => c.id === factor.id);
|
const config = factorConfigs.find(c => c.id === factor.id);
|
||||||
if (config) {
|
if (config) {
|
||||||
weightedSum += factor.score * config.weight;
|
weightedSum += factor.score * config.weight;
|
||||||
totalWeight += config.weight;
|
totalWeight += config.weight;
|
||||||
@@ -741,7 +766,7 @@ interface BubbleData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Componente del Bubble Chart de Oportunidades
|
// Componente del Bubble Chart de Oportunidades
|
||||||
function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDataPoint[] }) {
|
function OpportunityBubbleChart({ drilldownData, t }: { drilldownData: DrilldownDataPoint[]; t: any }) {
|
||||||
// Estados para filtros
|
// Estados para filtros
|
||||||
const [tierFilter, setTierFilter] = useState<'Todos' | AgenticTier>('Todos');
|
const [tierFilter, setTierFilter] = useState<'Todos' | AgenticTier>('Todos');
|
||||||
const [minAhorro, setMinAhorro] = useState<number>(0);
|
const [minAhorro, setMinAhorro] = useState<number>(0);
|
||||||
@@ -894,35 +919,35 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<Target className="w-5 h-5 flex-shrink-0" style={{ color: COLORS.primary }} />
|
<Target className="w-5 h-5 flex-shrink-0" style={{ color: COLORS.primary }} />
|
||||||
<h3 className="font-bold text-sm sm:text-base truncate" style={{ color: COLORS.dark }}>
|
<h3 className="font-bold text-sm sm:text-base truncate" style={{ color: COLORS.dark }}>
|
||||||
Mapa de Oportunidades
|
{t('agenticReadiness.opportunityMap.title')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs px-2 py-1 rounded-full font-medium flex-shrink-0" style={{ backgroundColor: COLORS.primary, color: 'white' }}>
|
<span className="text-xs px-2 py-1 rounded-full font-medium flex-shrink-0" style={{ backgroundColor: COLORS.primary, color: 'white' }}>
|
||||||
{bubbleData.length} colas
|
{bubbleData.length} {t('agenticReadiness.volumeLabels.queues')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] sm:text-xs mt-1" style={{ color: COLORS.medium }}>
|
<p className="text-[10px] sm:text-xs mt-1" style={{ color: COLORS.medium }}>
|
||||||
Tamaño = Volumen · Color = Tier · Posición = Score vs Ahorro TCO
|
{t('agenticReadiness.opportunityMap.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filtros */}
|
{/* Filtros */}
|
||||||
<div className="px-3 sm:px-4 py-2 border-b border-gray-100 flex flex-wrap gap-2 sm:gap-4 items-center bg-gray-50/50">
|
<div className="px-3 sm:px-4 py-2 border-b border-gray-100 flex flex-wrap gap-2 sm:gap-4 items-center bg-gray-50/50">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs font-medium" style={{ color: COLORS.dark }}>Tier:</span>
|
<span className="text-xs font-medium" style={{ color: COLORS.dark }}>{t('agenticReadiness.filters.tier')}</span>
|
||||||
<select
|
<select
|
||||||
value={tierFilter}
|
value={tierFilter}
|
||||||
onChange={(e) => setTierFilter(e.target.value as 'Todos' | AgenticTier)}
|
onChange={(e) => setTierFilter(e.target.value as 'Todos' | AgenticTier)}
|
||||||
className="text-xs border border-gray-200 rounded px-2 py-1"
|
className="text-xs border border-gray-200 rounded px-2 py-1"
|
||||||
>
|
>
|
||||||
<option value="Todos">Todos</option>
|
<option value="Todos">{t('agenticReadiness.filters.all')}</option>
|
||||||
<option value="AUTOMATE">AUTOMATE</option>
|
<option value="AUTOMATE">AUTOMATE</option>
|
||||||
<option value="ASSIST">ASSIST</option>
|
<option value="ASSIST">ASSIST</option>
|
||||||
<option value="AUGMENT">AUGMENT</option>
|
<option value="AUGMENT">AUGMENT</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs font-medium" style={{ color: COLORS.dark }}>Ahorro mín:</span>
|
<span className="text-xs font-medium" style={{ color: COLORS.dark }}>{t('agenticReadiness.filters.minSavings')}</span>
|
||||||
<select
|
<select
|
||||||
value={minAhorro}
|
value={minAhorro}
|
||||||
onChange={(e) => setMinAhorro(Number(e.target.value))}
|
onChange={(e) => setMinAhorro(Number(e.target.value))}
|
||||||
@@ -935,7 +960,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs font-medium" style={{ color: COLORS.dark }}>Volumen mín:</span>
|
<span className="text-xs font-medium" style={{ color: COLORS.dark }}>{t('agenticReadiness.filters.minVolume')}</span>
|
||||||
<select
|
<select
|
||||||
value={minVolumen}
|
value={minVolumen}
|
||||||
onChange={(e) => setMinVolumen(Number(e.target.value))}
|
onChange={(e) => setMinVolumen(Number(e.target.value))}
|
||||||
@@ -951,12 +976,12 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
{/* v3.12: Indicador de filtros activos con resumen de cuadrantes */}
|
{/* v3.12: Indicador de filtros activos con resumen de cuadrantes */}
|
||||||
{hasActiveFilters && (
|
{hasActiveFilters && (
|
||||||
<div className="ml-auto flex items-center gap-2 text-xs bg-amber-50 text-amber-700 px-2 py-1 rounded border border-amber-200">
|
<div className="ml-auto flex items-center gap-2 text-xs bg-amber-50 text-amber-700 px-2 py-1 rounded border border-amber-200">
|
||||||
<span className="font-medium">Filtros activos:</span>
|
<span className="font-medium">{t('agenticReadiness.bubbleChart.activeFiltersLabel')}</span>
|
||||||
{minAhorro > 0 && <span>Ahorro ≥€{minAhorro >= 1000 ? `${minAhorro/1000}K` : minAhorro}</span>}
|
{minAhorro > 0 && <span>{t('agenticReadiness.filters.minSavings')} ≥€{minAhorro >= 1000 ? `${minAhorro/1000}K` : minAhorro}</span>}
|
||||||
{minVolumen > 0 && <span>Vol ≥{minVolumen >= 1000 ? `${minVolumen/1000}K` : minVolumen}</span>}
|
{minVolumen > 0 && <span>Vol ≥{minVolumen >= 1000 ? `${minVolumen/1000}K` : minVolumen}</span>}
|
||||||
{tierFilter !== 'Todos' && <span>Tier: {tierFilter}</span>}
|
{tierFilter !== 'Todos' && <span>Tier: {tierFilter}</span>}
|
||||||
<span className="text-amber-500">|</span>
|
<span className="text-amber-500">|</span>
|
||||||
<span>{quadrantStats.total} de {allQueues.filter(q => q.tier !== 'HUMAN-ONLY').length} colas</span>
|
<span>{quadrantStats.total} {t('agenticReadiness.bubbleChart.ofQueues', { total: allQueues.filter(q => q.tier !== 'HUMAN-ONLY').length })}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1026,33 +1051,33 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
{/* v3.12: Etiquetas de cuadrante sincronizadas con filtros */}
|
{/* v3.12: Etiquetas de cuadrante sincronizadas con filtros */}
|
||||||
{/* Quick Wins (top-right) */}
|
{/* Quick Wins (top-right) */}
|
||||||
<text x={automateThresholdX + 10} y={15} fontSize={10} fill="#059669" fontWeight="bold">
|
<text x={automateThresholdX + 10} y={15} fontSize={10} fill="#059669" fontWeight="bold">
|
||||||
🎯 QUICK WINS
|
🎯 {t('agenticReadiness.bubbleChart.quickWinsLabel')}
|
||||||
</text>
|
</text>
|
||||||
<text x={automateThresholdX + 10} y={28} fontSize={9} fill="#059669">
|
<text x={automateThresholdX + 10} y={28} fontSize={9} fill="#059669">
|
||||||
{quadrantStats.quickWins.count} colas · {formatCurrency(quadrantStats.quickWins.ahorro)}
|
{quadrantStats.quickWins.count} {t('agenticReadiness.volumeLabels.queues')} · {formatCurrency(quadrantStats.quickWins.ahorro)}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
{/* Alto Potencial (top-center) */}
|
{/* Alto Potencial (top-center) */}
|
||||||
<text x={assistThresholdX + 10} y={15} fontSize={10} fill="#4f63b8" fontWeight="bold">
|
<text x={assistThresholdX + 10} y={15} fontSize={10} fill="#4f63b8" fontWeight="bold">
|
||||||
⚡ ALTO POTENCIAL
|
⚡ {t('agenticReadiness.bubbleChart.highPotentialLabel')}
|
||||||
</text>
|
</text>
|
||||||
<text x={assistThresholdX + 10} y={28} fontSize={9} fill="#4f63b8">
|
<text x={assistThresholdX + 10} y={28} fontSize={9} fill="#4f63b8">
|
||||||
{quadrantStats.highPotential.count} colas · {formatCurrency(quadrantStats.highPotential.ahorro)}
|
{quadrantStats.highPotential.count} {t('agenticReadiness.volumeLabels.queues')} · {formatCurrency(quadrantStats.highPotential.ahorro)}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
{/* Desarrollar / Nurture (left column) */}
|
{/* Desarrollar / Nurture (left column) */}
|
||||||
<text x={10} y={15} fontSize={10} fill="#92400e" fontWeight="bold">
|
<text x={10} y={15} fontSize={10} fill="#92400e" fontWeight="bold">
|
||||||
📈 DESARROLLAR
|
📈 {t('agenticReadiness.bubbleChart.developLabel')}
|
||||||
</text>
|
</text>
|
||||||
<text x={10} y={28} fontSize={9} fill="#92400e">
|
<text x={10} y={28} fontSize={9} fill="#92400e">
|
||||||
{quadrantStats.nurture.count} colas · {formatCurrency(quadrantStats.nurture.ahorro)}
|
{quadrantStats.nurture.count} {t('agenticReadiness.volumeLabels.queues')} · {formatCurrency(quadrantStats.nurture.ahorro)}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
{/* Low Hanging Fruit (bottom-right) - Fácil pero bajo ahorro */}
|
{/* Low Hanging Fruit (bottom-right) - Fácil pero bajo ahorro */}
|
||||||
{quadrantStats.lowHanging.count > 0 && (
|
{quadrantStats.lowHanging.count > 0 && (
|
||||||
<>
|
<>
|
||||||
<text x={automateThresholdX + 10} y={innerHeight * 0.75 + 15} fontSize={9} fill="#6b7280" fontWeight="medium">
|
<text x={automateThresholdX + 10} y={innerHeight * 0.75 + 15} fontSize={9} fill="#6b7280" fontWeight="medium">
|
||||||
✅ FÁCIL IMPL.
|
✅ {t('agenticReadiness.bubbleChart.easyImplLabel')}
|
||||||
</text>
|
</text>
|
||||||
<text x={automateThresholdX + 10} y={innerHeight * 0.75 + 27} fontSize={8} fill="#9ca3af">
|
<text x={automateThresholdX + 10} y={innerHeight * 0.75 + 27} fontSize={8} fill="#9ca3af">
|
||||||
{quadrantStats.lowHanging.count} · {formatCurrency(quadrantStats.lowHanging.ahorro)}
|
{quadrantStats.lowHanging.count} · {formatCurrency(quadrantStats.lowHanging.ahorro)}
|
||||||
@@ -1064,7 +1089,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
{quadrantStats.backlog.count > 0 && (
|
{quadrantStats.backlog.count > 0 && (
|
||||||
<>
|
<>
|
||||||
<text x={assistThresholdX + 10} y={innerHeight * 0.75 + 15} fontSize={9} fill="#6b7280" fontWeight="medium">
|
<text x={assistThresholdX + 10} y={innerHeight * 0.75 + 15} fontSize={9} fill="#6b7280" fontWeight="medium">
|
||||||
📋 BACKLOG
|
📋 {t('agenticReadiness.bubbleChart.backlogLabel')}
|
||||||
</text>
|
</text>
|
||||||
<text x={assistThresholdX + 10} y={innerHeight * 0.75 + 27} fontSize={8} fill="#9ca3af">
|
<text x={assistThresholdX + 10} y={innerHeight * 0.75 + 27} fontSize={8} fill="#9ca3af">
|
||||||
{quadrantStats.backlog.count} · {formatCurrency(quadrantStats.backlog.ahorro)}
|
{quadrantStats.backlog.count} · {formatCurrency(quadrantStats.backlog.ahorro)}
|
||||||
@@ -1095,7 +1120,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<text x={innerWidth / 2} y={innerHeight + 38} textAnchor="middle" fontSize={11} fill={COLORS.dark} fontWeight="medium">
|
<text x={innerWidth / 2} y={innerHeight + 38} textAnchor="middle" fontSize={11} fill={COLORS.dark} fontWeight="medium">
|
||||||
Agentic Score
|
{t('agenticReadiness.opportunityMap.agenticScore')}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
{/* Eje Y */}
|
{/* Eje Y */}
|
||||||
@@ -1122,7 +1147,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
fontWeight="medium"
|
fontWeight="medium"
|
||||||
transform={`rotate(-90, -45, ${innerHeight / 2})`}
|
transform={`rotate(-90, -45, ${innerHeight / 2})`}
|
||||||
>
|
>
|
||||||
Ahorro TCO Anual
|
{t('agenticReadiness.opportunityMap.annualTcoSavings')}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
{/* Burbujas */}
|
{/* Burbujas */}
|
||||||
@@ -1165,7 +1190,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
{/* Mensaje si no hay datos */}
|
{/* Mensaje si no hay datos */}
|
||||||
{bubbleData.length === 0 && (
|
{bubbleData.length === 0 && (
|
||||||
<text x={innerWidth / 2} y={innerHeight / 2} textAnchor="middle" fontSize={14} fill={COLORS.medium}>
|
<text x={innerWidth / 2} y={innerHeight / 2} textAnchor="middle" fontSize={14} fill={COLORS.medium}>
|
||||||
No hay colas que cumplan los filtros seleccionados
|
{t('agenticReadiness.bubbleChart.noQueuesFilters')}
|
||||||
</text>
|
</text>
|
||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
@@ -1192,19 +1217,19 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 text-xs">
|
<div className="space-y-1 text-xs">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span style={{ color: COLORS.medium }}>Score:</span>
|
<span style={{ color: COLORS.medium }}>{t('agenticReadiness.table.score')}:</span>
|
||||||
<span className="font-semibold" style={{ color: COLORS.dark }}>{hoveredBubble.score.toFixed(1)}</span>
|
<span className="font-semibold" style={{ color: COLORS.dark }}>{hoveredBubble.score.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span style={{ color: COLORS.medium }}>Volumen:</span>
|
<span style={{ color: COLORS.medium }}>{t('agenticReadiness.table.volume')}:</span>
|
||||||
<span className="font-semibold" style={{ color: COLORS.dark }}>{formatVolume(hoveredBubble.volume)}/mes</span>
|
<span className="font-semibold" style={{ color: COLORS.dark }}>{formatVolume(hoveredBubble.volume)}{t('agenticReadiness.bubbleChart.perMonth')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span style={{ color: COLORS.medium }}>Ahorro:</span>
|
<span style={{ color: COLORS.medium }}>{t('agenticReadiness.table.savings')}:</span>
|
||||||
<span className="font-semibold text-emerald-600">{formatCurrency(hoveredBubble.ahorro)}/año</span>
|
<span className="font-semibold text-emerald-600">{formatCurrency(hoveredBubble.ahorro)}{t('agenticReadiness.table.perYear')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span style={{ color: COLORS.medium }}>CV AHT:</span>
|
<span style={{ color: COLORS.medium }}>{t('agenticReadiness.bubbleChart.cvAht')}</span>
|
||||||
<span className={`font-semibold ${hoveredBubble.cv > 120 ? 'text-red-500' : hoveredBubble.cv > 75 ? 'text-amber-500' : 'text-emerald-500'}`}>
|
<span className={`font-semibold ${hoveredBubble.cv > 120 ? 'text-red-500' : hoveredBubble.cv > 75 ? 'text-amber-500' : 'text-emerald-500'}`}>
|
||||||
{hoveredBubble.cv.toFixed(0)}%
|
{hoveredBubble.cv.toFixed(0)}%
|
||||||
</span>
|
</span>
|
||||||
@@ -1215,7 +1240,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-center mt-2 pt-2 border-t border-gray-100" style={{ color: COLORS.medium }}>
|
<p className="text-[10px] text-center mt-2 pt-2 border-t border-gray-100" style={{ color: COLORS.medium }}>
|
||||||
Click para ver detalle
|
{t('agenticReadiness.bubbleChart.viewDetail')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1226,7 +1251,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
<div className="flex flex-wrap justify-between gap-4">
|
<div className="flex flex-wrap justify-between gap-4">
|
||||||
{/* Leyenda de colores */}
|
{/* Leyenda de colores */}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] font-bold mb-1.5" style={{ color: COLORS.dark }}>COLOR = TIER</p>
|
<p className="text-[10px] font-bold mb-1.5" style={{ color: COLORS.dark }}>{t('agenticReadiness.opportunityMap.colorTier')}</p>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
{(['AUTOMATE', 'ASSIST', 'AUGMENT'] as AgenticTier[]).map(tier => (
|
{(['AUTOMATE', 'ASSIST', 'AUGMENT'] as AgenticTier[]).map(tier => (
|
||||||
<div key={tier} className="flex items-center gap-1">
|
<div key={tier} className="flex items-center gap-1">
|
||||||
@@ -1244,7 +1269,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
|
|
||||||
{/* Leyenda de tamaños */}
|
{/* Leyenda de tamaños */}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] font-bold mb-1.5" style={{ color: COLORS.dark }}>TAMAÑO = VOLUMEN</p>
|
<p className="text-[10px] font-bold mb-1.5" style={{ color: COLORS.dark }}>{t('agenticReadiness.opportunityMap.sizeVolume')}</p>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-2 h-2 rounded-full bg-gray-400" />
|
<div className="w-2 h-2 rounded-full bg-gray-400" />
|
||||||
@@ -1285,7 +1310,7 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-gray-400">
|
<span className="text-gray-400">
|
||||||
= {quadrantStats.total} total
|
= {quadrantStats.total} {t('agenticReadiness.bubbleChart.total')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1379,11 +1404,13 @@ function OpportunityBubbleChart({ drilldownData }: { drilldownData: DrilldownDat
|
|||||||
function AgenticReadinessHeader({
|
function AgenticReadinessHeader({
|
||||||
tierData,
|
tierData,
|
||||||
totalVolume,
|
totalVolume,
|
||||||
totalQueues
|
totalQueues,
|
||||||
|
t
|
||||||
}: {
|
}: {
|
||||||
tierData: TierDataType;
|
tierData: TierDataType;
|
||||||
totalVolume: number;
|
totalVolume: number;
|
||||||
totalQueues: number;
|
totalQueues: number;
|
||||||
|
t: any;
|
||||||
}) {
|
}) {
|
||||||
// Calcular volumen automatizable (AUTOMATE + ASSIST)
|
// Calcular volumen automatizable (AUTOMATE + ASSIST)
|
||||||
const automatizableVolume = tierData.AUTOMATE.volume + tierData.ASSIST.volume;
|
const automatizableVolume = tierData.AUTOMATE.volume + tierData.ASSIST.volume;
|
||||||
@@ -1406,10 +1433,10 @@ function AgenticReadinessHeader({
|
|||||||
|
|
||||||
// Tier card config con colores consistentes con la sección introductoria
|
// Tier card config con colores consistentes con la sección introductoria
|
||||||
const tierConfigs = [
|
const tierConfigs = [
|
||||||
{ key: 'AUTOMATE', label: 'AUTOMATE', emoji: '🤖', sublabel: 'Full IA', color: '#10b981', bgColor: '#d1fae5' },
|
{ key: 'AUTOMATE', label: 'AUTOMATE', emoji: '🤖', sublabel: t('agenticReadiness.tierLabels.automateFull'), color: '#10b981', bgColor: '#d1fae5' },
|
||||||
{ key: 'ASSIST', label: 'ASSIST', emoji: '🤝', sublabel: 'Copilot', color: '#3b82f6', bgColor: '#dbeafe' },
|
{ key: 'ASSIST', label: 'ASSIST', emoji: '🤝', sublabel: t('agenticReadiness.tierLabels.assistCopilot'), color: '#3b82f6', bgColor: '#dbeafe' },
|
||||||
{ key: 'AUGMENT', label: 'AUGMENT', emoji: '📚', sublabel: 'Tools', color: '#f59e0b', bgColor: '#fef3c7' },
|
{ key: 'AUGMENT', label: 'AUGMENT', emoji: '📚', sublabel: t('agenticReadiness.tierLabels.augmentTools'), color: '#f59e0b', bgColor: '#fef3c7' },
|
||||||
{ key: 'HUMAN-ONLY', label: 'HUMAN', emoji: '👤', sublabel: 'Manual', color: '#6b7280', bgColor: '#f3f4f6' }
|
{ key: 'HUMAN-ONLY', label: 'HUMAN', emoji: '👤', sublabel: t('agenticReadiness.tierLabels.humanManual'), color: '#6b7280', bgColor: '#f3f4f6' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Calcular porcentaje de colas AUTOMATE
|
// Calcular porcentaje de colas AUTOMATE
|
||||||
@@ -1417,12 +1444,13 @@ function AgenticReadinessHeader({
|
|||||||
|
|
||||||
// Generar interpretación que explica la diferencia volumen vs colas
|
// Generar interpretación que explica la diferencia volumen vs colas
|
||||||
const getInterpretation = () => {
|
const getInterpretation = () => {
|
||||||
// El score principal (88%) se basa en VOLUMEN de interacciones
|
return t('agenticReadiness.summary.interpretationText', {
|
||||||
// El % de colas AUTOMATE (26%) es diferente porque hay pocas colas de alto volumen
|
pct: Math.round(automatizablePct),
|
||||||
return `El ${Math.round(automatizablePct)}% representa el volumen de interacciones automatizables (AUTOMATE + ASSIST). ` +
|
queuePct: Math.round(pctColasAutomate),
|
||||||
`Solo el ${Math.round(pctColasAutomate)}% de las colas (${tierData.AUTOMATE.count} de ${totalQueues}) son AUTOMATE, ` +
|
count: tierData.AUTOMATE.count,
|
||||||
`pero concentran ${Math.round(tierPcts.AUTOMATE)}% del volumen total. ` +
|
total: totalQueues,
|
||||||
`Esto indica pocas colas de alto volumen automatizables - oportunidad concentrada en Quick Wins de alto impacto.`;
|
volumePct: Math.round(tierPcts.AUTOMATE)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1431,7 +1459,7 @@ function AgenticReadinessHeader({
|
|||||||
<div className="px-5 py-3 bg-gray-50 border-b border-gray-200">
|
<div className="px-5 py-3 bg-gray-50 border-b border-gray-200">
|
||||||
<h2 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h2 className="font-semibold text-gray-900 flex items-center gap-2">
|
||||||
<Bot className="w-4 h-4 text-blue-600" />
|
<Bot className="w-4 h-4 text-blue-600" />
|
||||||
Agentic Readiness Score
|
{t('agenticReadiness.score')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1443,10 +1471,10 @@ function AgenticReadinessHeader({
|
|||||||
{Math.round(automatizablePct)}%
|
{Math.round(automatizablePct)}%
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-semibold mt-1" style={{ color: COLORS.dark }}>
|
<div className="text-sm font-semibold mt-1" style={{ color: COLORS.dark }}>
|
||||||
Volumen Automatizable
|
{t('agenticReadiness.summary.volumeAutomatable')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs" style={{ color: COLORS.medium }}>
|
<div className="text-xs" style={{ color: COLORS.medium }}>
|
||||||
(Tier AUTOMATE + ASSIST)
|
{t('agenticReadiness.summary.tierAutoAssist')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1471,13 +1499,13 @@ function AgenticReadinessHeader({
|
|||||||
{Math.round(pct)}%
|
{Math.round(pct)}%
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs mt-1 text-gray-600">
|
<div className="text-xs mt-1 text-gray-600">
|
||||||
{formatVolume(data.volume)} int
|
{formatVolume(data.volume)} {t('agenticReadiness.volumeLabels.int')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm mt-1 text-gray-700">
|
<div className="text-sm mt-1 text-gray-700">
|
||||||
{config.emoji} {config.sublabel}
|
{config.emoji} {config.sublabel}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs mt-0.5 text-gray-500">
|
<div className="text-xs mt-0.5 text-gray-500">
|
||||||
{data.count} colas
|
{data.count} {t('agenticReadiness.volumeLabels.queues')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1522,7 +1550,7 @@ function AgenticReadinessHeader({
|
|||||||
{/* Interpretación condensada en una línea */}
|
{/* Interpretación condensada en una línea */}
|
||||||
<div className="pt-3" style={{ borderTop: `2px solid ${COLORS.light}` }}>
|
<div className="pt-3" style={{ borderTop: `2px solid ${COLORS.light}` }}>
|
||||||
<p className="text-xs" style={{ color: COLORS.dark }}>
|
<p className="text-xs" style={{ color: COLORS.dark }}>
|
||||||
<span className="font-semibold" style={{ color: COLORS.primary }}>📊 Interpretación: </span>
|
<span className="font-semibold" style={{ color: COLORS.primary }}>📊 {t('agenticReadiness.summary.interpretation')} </span>
|
||||||
{getInterpretation()}
|
{getInterpretation()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -2008,13 +2036,15 @@ function ExpandableSkillRow({
|
|||||||
idx,
|
idx,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
onToggle,
|
onToggle,
|
||||||
redFlagConfigs
|
redFlagConfigs,
|
||||||
|
t
|
||||||
}: {
|
}: {
|
||||||
dataPoint: DrilldownDataPoint;
|
dataPoint: DrilldownDataPoint;
|
||||||
idx: number;
|
idx: number;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
redFlagConfigs: RedFlagConfig[];
|
redFlagConfigs: RedFlagConfig[];
|
||||||
|
t: any;
|
||||||
}) {
|
}) {
|
||||||
// v3.4: Contar colas por Tier
|
// v3.4: Contar colas por Tier
|
||||||
const tierCounts = {
|
const tierCounts = {
|
||||||
@@ -2164,7 +2194,7 @@ function ExpandableSkillRow({
|
|||||||
<tbody className="divide-y divide-slate-100">
|
<tbody className="divide-y divide-slate-100">
|
||||||
{dataPoint.originalQueues.map((queue, queueIdx) => {
|
{dataPoint.originalQueues.map((queue, queueIdx) => {
|
||||||
const queueMonthlySavings = queue.annualCost ? Math.round(queue.annualCost * 0.35 / 12) : 0;
|
const queueMonthlySavings = queue.annualCost ? Math.round(queue.annualCost * 0.35 / 12) : 0;
|
||||||
const tierStyle = getTierStyle(queue.tier);
|
const tierStyle = getTierStyle(queue.tier, t);
|
||||||
const redFlags = detectRedFlags(queue, redFlagConfigs);
|
const redFlags = detectRedFlags(queue, redFlagConfigs);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -2200,7 +2230,7 @@ function ExpandableSkillRow({
|
|||||||
<td className="px-3 py-2 text-gray-600 text-right">{(queue.fcr_tecnico ?? (100 - queue.transfer_rate)).toFixed(0)}%</td>
|
<td className="px-3 py-2 text-gray-600 text-right">{(queue.fcr_tecnico ?? (100 - queue.transfer_rate)).toFixed(0)}%</td>
|
||||||
<td className="px-3 py-2 text-center">
|
<td className="px-3 py-2 text-center">
|
||||||
{queue.scoreBreakdown ? (
|
{queue.scoreBreakdown ? (
|
||||||
<InfoTooltip content={<ScoreBreakdownTooltip breakdown={queue.scoreBreakdown} />}>
|
<InfoTooltip content={<ScoreBreakdownTooltip breakdown={queue.scoreBreakdown} t={t} />}>
|
||||||
<span className={`px-1.5 py-0.5 rounded text-xs font-medium cursor-help ${tierStyle.bg} ${tierStyle.text}`}>
|
<span className={`px-1.5 py-0.5 rounded text-xs font-medium cursor-help ${tierStyle.bg} ${tierStyle.text}`}>
|
||||||
{queue.agenticScore.toFixed(1)}
|
{queue.agenticScore.toFixed(1)}
|
||||||
</span>
|
</span>
|
||||||
@@ -2218,7 +2248,7 @@ function ExpandableSkillRow({
|
|||||||
{redFlags.length > 0 ? (
|
{redFlags.length > 0 ? (
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{redFlags.map(flag => (
|
{redFlags.map(flag => (
|
||||||
<RedFlagBadge key={flag.config.id} flag={flag} size="sm" />
|
<RedFlagBadge key={flag.config.id} flag={flag} size="sm" t={t} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -2783,7 +2813,7 @@ function HumanOnlyByReasonSection({ drilldownData, redFlagConfigs }: { drilldown
|
|||||||
}
|
}
|
||||||
|
|
||||||
// v3.4: Sección de Candidatos Prioritarios - Por queue_skill con drill-down a original_queue_id
|
// v3.4: Sección de Candidatos Prioritarios - Por queue_skill con drill-down a original_queue_id
|
||||||
function PriorityCandidatesSection({ drilldownData, redFlagConfigs }: { drilldownData: DrilldownDataPoint[]; redFlagConfigs: RedFlagConfig[] }) {
|
function PriorityCandidatesSection({ drilldownData, redFlagConfigs, t }: { drilldownData: DrilldownDataPoint[]; redFlagConfigs: RedFlagConfig[]; t: any }) {
|
||||||
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
// Filtrar skills que tienen al menos una cola AUTOMATE
|
// Filtrar skills que tienen al menos una cola AUTOMATE
|
||||||
@@ -2908,6 +2938,7 @@ function PriorityCandidatesSection({ drilldownData, redFlagConfigs }: { drilldow
|
|||||||
isExpanded={expandedRows.has(dataPoint.skill)}
|
isExpanded={expandedRows.has(dataPoint.skill)}
|
||||||
onToggle={() => toggleRow(dataPoint.skill)}
|
onToggle={() => toggleRow(dataPoint.skill)}
|
||||||
redFlagConfigs={redFlagConfigs}
|
redFlagConfigs={redFlagConfigs}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -3684,6 +3715,7 @@ export function AgenticReadinessTab({ data, onTabChange }: AgenticReadinessTabPr
|
|||||||
tierData={tierData}
|
tierData={tierData}
|
||||||
totalVolume={totalVolume}
|
totalVolume={totalVolume}
|
||||||
totalQueues={totalQueues}
|
totalQueues={totalQueues}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* SECCIÓN 1: Cabecera Agentic Readiness Score - Visión Global */}
|
{/* SECCIÓN 1: Cabecera Agentic Readiness Score - Visión Global */}
|
||||||
|
|||||||
@@ -1139,7 +1139,17 @@
|
|||||||
"easyImplCount": "{{count}} · {{amount}}",
|
"easyImplCount": "{{count}} · {{amount}}",
|
||||||
"backlogCount": "{{count}} · {{amount}}",
|
"backlogCount": "{{count}} · {{amount}}",
|
||||||
"total": "total",
|
"total": "total",
|
||||||
"noQueuesFilters": "No queues match the selected filters"
|
"noQueuesFilters": "No queues match the selected filters",
|
||||||
|
"quickWinsLabel": "QUICK WINS",
|
||||||
|
"highPotentialLabel": "HIGH POTENTIAL",
|
||||||
|
"developLabel": "DEVELOP",
|
||||||
|
"easyImplLabel": "EASY IMPL.",
|
||||||
|
"backlogLabel": "BACKLOG",
|
||||||
|
"activeFiltersLabel": "Active filters:",
|
||||||
|
"ofQueues": "of {{total}} queues",
|
||||||
|
"perMonth": "/month",
|
||||||
|
"cvAht": "CV AHT:",
|
||||||
|
"viewDetail": "Click for details"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"skillLabel": "Skill:",
|
"skillLabel": "Skill:",
|
||||||
|
|||||||
@@ -1139,7 +1139,17 @@
|
|||||||
"easyImplCount": "{{count}} · {{amount}}",
|
"easyImplCount": "{{count}} · {{amount}}",
|
||||||
"backlogCount": "{{count}} · {{amount}}",
|
"backlogCount": "{{count}} · {{amount}}",
|
||||||
"total": "total",
|
"total": "total",
|
||||||
"noQueuesFilters": "No hay colas que cumplan los filtros seleccionados"
|
"noQueuesFilters": "No hay colas que cumplan los filtros seleccionados",
|
||||||
|
"quickWinsLabel": "QUICK WINS",
|
||||||
|
"highPotentialLabel": "ALTO POTENCIAL",
|
||||||
|
"developLabel": "DESARROLLAR",
|
||||||
|
"easyImplLabel": "FÁCIL IMPL.",
|
||||||
|
"backlogLabel": "BACKLOG",
|
||||||
|
"activeFiltersLabel": "Filtros activos:",
|
||||||
|
"ofQueues": "de {{total}} colas",
|
||||||
|
"perMonth": "/mes",
|
||||||
|
"cvAht": "CV AHT:",
|
||||||
|
"viewDetail": "Click para ver detalle"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"skillLabel": "Skill:",
|
"skillLabel": "Skill:",
|
||||||
|
|||||||
Reference in New Issue
Block a user