466 lines
22 KiB
TypeScript
466 lines
22 KiB
TypeScript
import React, { useState, useMemo } from 'react';
|
||
import { motion, AnimatePresence } from 'framer-motion';
|
||
import { Opportunity, HeatmapDataPoint } from '../types';
|
||
import { HelpCircle, TrendingUp, Zap, DollarSign, X, Target, AlertCircle } from 'lucide-react';
|
||
import MethodologyFooter from './MethodologyFooter';
|
||
|
||
interface OpportunityMatrixProProps {
|
||
data: Opportunity[];
|
||
heatmapData?: HeatmapDataPoint[]; // v2.0: Datos de variabilidad para ajustar factibilidad
|
||
}
|
||
|
||
interface QuadrantInfo {
|
||
label: string;
|
||
subtitle: string;
|
||
recommendation: string;
|
||
priority: number;
|
||
color: string;
|
||
bgColor: string;
|
||
icon: string;
|
||
}
|
||
|
||
const OpportunityMatrixPro: React.FC<OpportunityMatrixProProps> = ({ data, heatmapData }) => {
|
||
const [selectedOpportunity, setSelectedOpportunity] = useState<Opportunity | null>(null);
|
||
const [hoveredOpportunity, setHoveredOpportunity] = useState<string | null>(null);
|
||
|
||
const maxSavings = data && data.length > 0 ? Math.max(...data.map(d => d.savings || 0), 1) : 1;
|
||
|
||
// v2.0: Ajustar factibilidad con automation readiness del heatmap
|
||
const adjustFeasibilityWithReadiness = (opp: Opportunity): number => {
|
||
if (!heatmapData) return opp.feasibility;
|
||
|
||
// Buscar skill relacionada en heatmap
|
||
const relatedSkill = heatmapData.find(h => {
|
||
if (!h.skill || !opp.name) return false;
|
||
const skillLower = h.skill.toLowerCase();
|
||
const oppNameLower = opp.name.toLowerCase();
|
||
const firstWord = oppNameLower.split(' ')[0] || ''; // Validar que existe
|
||
return oppNameLower.includes(skillLower) || (firstWord && skillLower.includes(firstWord));
|
||
});
|
||
|
||
if (!relatedSkill) return opp.feasibility;
|
||
|
||
// Ajustar factibilidad: readiness alto aumenta factibilidad, bajo la reduce
|
||
const readinessFactor = relatedSkill.automation_readiness / 100; // 0-1
|
||
const adjustedFeasibility = opp.feasibility * 0.6 + (readinessFactor * 10) * 0.4;
|
||
|
||
return Math.min(10, Math.max(1, adjustedFeasibility));
|
||
};
|
||
|
||
// Calculate priorities (Impact × Feasibility × Savings)
|
||
const dataWithPriority = useMemo(() => {
|
||
try {
|
||
if (!data || !Array.isArray(data)) return [];
|
||
return data.map(opp => {
|
||
const adjustedFeasibility = adjustFeasibilityWithReadiness(opp);
|
||
const priorityScore = (opp.impact / 10) * (adjustedFeasibility / 10) * (opp.savings / maxSavings);
|
||
return { ...opp, adjustedFeasibility, priorityScore };
|
||
}).sort((a, b) => b.priorityScore - a.priorityScore)
|
||
.map((opp, index) => ({ ...opp, priority: index + 1 }));
|
||
} catch (error) {
|
||
console.error('❌ Error in dataWithPriority useMemo:', error);
|
||
return [];
|
||
}
|
||
}, [data, maxSavings, heatmapData]);
|
||
|
||
// Calculate portfolio summary
|
||
const portfolioSummary = useMemo(() => {
|
||
const quickWins = dataWithPriority.filter(o => o.impact >= 5 && o.feasibility >= 5);
|
||
const strategic = dataWithPriority.filter(o => o.impact >= 5 && o.feasibility < 5);
|
||
const consider = dataWithPriority.filter(o => o.impact < 5 && o.feasibility >= 5);
|
||
|
||
const totalSavings = dataWithPriority.reduce((sum, o) => sum + o.savings, 0);
|
||
const quickWinsSavings = quickWins.reduce((sum, o) => sum + o.savings, 0);
|
||
const strategicSavings = strategic.reduce((sum, o) => sum + o.savings, 0);
|
||
|
||
return {
|
||
totalSavings,
|
||
quickWins: { count: quickWins.length, savings: quickWinsSavings },
|
||
strategic: { count: strategic.length, savings: strategicSavings },
|
||
consider: { count: consider.length, savings: 0 },
|
||
};
|
||
}, [dataWithPriority]);
|
||
|
||
// Dynamic title - v4.3: Top 10 iniciativas por potencial económico
|
||
const dynamicTitle = useMemo(() => {
|
||
const totalQueues = dataWithPriority.length;
|
||
const totalSavings = portfolioSummary.totalSavings;
|
||
if (totalQueues === 0) {
|
||
return 'No hay iniciativas con potencial de ahorro identificadas';
|
||
}
|
||
return `Top ${totalQueues} iniciativas por potencial económico | Ahorro total: €${(totalSavings / 1000).toFixed(0)}K/año`;
|
||
}, [portfolioSummary, dataWithPriority]);
|
||
|
||
const getQuadrantInfo = (impact: number, feasibility: number): QuadrantInfo => {
|
||
if (impact >= 5 && feasibility >= 5) {
|
||
return {
|
||
label: '🎯 Quick Wins',
|
||
subtitle: `${portfolioSummary.quickWins.count} iniciativas | €${(portfolioSummary.quickWins.savings / 1000).toFixed(0)}K ahorro | 3-6 meses`,
|
||
recommendation: 'Prioridad 1: Implementar Inmediatamente',
|
||
priority: 1,
|
||
color: 'text-green-700',
|
||
bgColor: 'bg-green-50',
|
||
icon: '🎯',
|
||
};
|
||
}
|
||
if (impact >= 5 && feasibility < 5) {
|
||
return {
|
||
label: '🚀 Proyectos Estratégicos',
|
||
subtitle: `${portfolioSummary.strategic.count} iniciativas | €${(portfolioSummary.strategic.savings / 1000).toFixed(0)}K ahorro | 12-18 meses`,
|
||
recommendation: 'Prioridad 2: Planificar Roadmap H2',
|
||
priority: 2,
|
||
color: 'text-blue-700',
|
||
bgColor: 'bg-blue-50',
|
||
icon: '🚀',
|
||
};
|
||
}
|
||
if (impact < 5 && feasibility >= 5) {
|
||
return {
|
||
label: '🔍 Evaluar',
|
||
subtitle: `${portfolioSummary.consider.count} iniciativas | Bajo impacto | 2-4 meses`,
|
||
recommendation: 'Prioridad 3: Considerar si hay capacidad',
|
||
priority: 3,
|
||
color: 'text-amber-700',
|
||
bgColor: 'bg-amber-50',
|
||
icon: '🔍',
|
||
};
|
||
}
|
||
return {
|
||
label: '⏸️ Descartar',
|
||
subtitle: 'Bajo impacto y factibilidad',
|
||
recommendation: 'No priorizar - No invertir recursos',
|
||
priority: 4,
|
||
color: 'text-slate-500',
|
||
bgColor: 'bg-slate-50',
|
||
icon: '⏸️',
|
||
};
|
||
};
|
||
|
||
const getQuadrantColor = (impact: number, feasibility: number): string => {
|
||
if (impact >= 5 && feasibility >= 5) return 'bg-green-500';
|
||
if (impact >= 5 && feasibility < 5) return 'bg-blue-500';
|
||
if (impact < 5 && feasibility >= 5) return 'bg-amber-500';
|
||
return 'bg-slate-400';
|
||
};
|
||
|
||
const getFeasibilityLabel = (value: number): string => {
|
||
if (value >= 7.5) return 'Fácil';
|
||
if (value >= 5) return 'Moderado';
|
||
if (value >= 2.5) return 'Complejo';
|
||
return 'Muy Difícil';
|
||
};
|
||
|
||
const getImpactLabel = (value: number): string => {
|
||
if (value >= 7.5) return 'Muy Alto';
|
||
if (value >= 5) return 'Alto';
|
||
if (value >= 2.5) return 'Medio';
|
||
return 'Bajo';
|
||
};
|
||
|
||
return (
|
||
<div id="opportunities" className="bg-white p-8 rounded-xl border border-slate-200 shadow-sm">
|
||
{/* Header with Dynamic Title */}
|
||
<div className="mb-6">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<div className="flex items-center gap-2">
|
||
<h3 className="font-bold text-2xl text-slate-800">Opportunity Matrix - Top 10 Iniciativas</h3>
|
||
<div className="group relative">
|
||
<HelpCircle size={18} className="text-slate-400 cursor-pointer" />
|
||
<div className="absolute bottom-full mb-2 w-80 bg-slate-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none z-10">
|
||
Top 10 colas por potencial económico (todos los tiers). Eje X = Factibilidad (Agentic Score), Eje Y = Impacto (Ahorro TCO). Tamaño = Ahorro potencial. 🤖=AUTOMATE, 🤝=ASSIST, 📚=AUGMENT.
|
||
<div className="absolute top-full left-1/2 -translate-x-1/2 w-0 h-0 border-x-4 border-x-transparent border-t-4 border-t-slate-800"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p className="text-xs text-slate-500 italic">Priorizadas por potencial de ahorro TCO (🤖 AUTOMATE, 🤝 ASSIST, 📚 AUGMENT)</p>
|
||
</div>
|
||
<p className="text-base text-slate-700 font-medium leading-relaxed mb-1">
|
||
{dynamicTitle}
|
||
</p>
|
||
<p className="text-sm text-slate-500">
|
||
{dataWithPriority.length} iniciativas identificadas | Ahorro TCO según tier (AUTOMATE 70%, ASSIST 30%, AUGMENT 15%)
|
||
</p>
|
||
</div>
|
||
|
||
{/* Portfolio Summary */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||
<div className="bg-gradient-to-br from-slate-50 to-slate-100 p-4 rounded-lg border border-slate-200">
|
||
<div className="text-xs text-slate-600 mb-1">Total Ahorro Potencial</div>
|
||
<div className="text-2xl font-bold text-slate-800">
|
||
€{(portfolioSummary.totalSavings / 1000).toFixed(0)}K
|
||
</div>
|
||
<div className="text-xs text-slate-500 mt-1">anuales</div>
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-br from-green-50 to-emerald-50 p-4 rounded-lg border border-green-200">
|
||
<div className="text-xs text-green-700 mb-1">Quick Wins ({portfolioSummary.quickWins.count})</div>
|
||
<div className="text-2xl font-bold text-green-600">
|
||
€{(portfolioSummary.quickWins.savings / 1000).toFixed(0)}K
|
||
</div>
|
||
<div className="text-xs text-green-600 mt-1">6 meses</div>
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-4 rounded-lg border border-blue-200">
|
||
<div className="text-xs text-blue-700 mb-1">Estratégicos ({portfolioSummary.strategic.count})</div>
|
||
<div className="text-2xl font-bold text-blue-600">
|
||
€{(portfolioSummary.strategic.savings / 1000).toFixed(0)}K
|
||
</div>
|
||
<div className="text-xs text-blue-600 mt-1">18 meses</div>
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-br from-purple-50 to-violet-50 p-4 rounded-lg border border-purple-200">
|
||
<div className="text-xs text-purple-700 mb-1">ROI Portfolio</div>
|
||
<div className="text-2xl font-bold text-purple-600">
|
||
4.3x
|
||
</div>
|
||
<div className="text-xs text-purple-600 mt-1">3 años</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Matrix */}
|
||
<div className="relative w-full h-[500px] border-l-2 border-b-2 border-slate-400 rounded-bl-lg bg-gradient-to-tr from-slate-50 to-white">
|
||
{/* Y-axis Label */}
|
||
<div className="absolute -left-20 top-1/2 -translate-y-1/2 -rotate-90 text-sm font-bold text-slate-700 flex items-center gap-2">
|
||
<TrendingUp size={18} /> IMPACTO (Ahorro TCO)
|
||
</div>
|
||
|
||
{/* X-axis Label */}
|
||
<div className="absolute -bottom-14 left-1/2 -translate-x-1/2 text-sm font-bold text-slate-700 flex items-center gap-2">
|
||
<Zap size={18} /> FACTIBILIDAD (Agentic Score)
|
||
</div>
|
||
|
||
{/* Axis scale labels */}
|
||
<div className="absolute -left-2 top-0 -translate-x-full text-xs text-slate-500 font-medium">
|
||
Alto (10)
|
||
</div>
|
||
<div className="absolute -left-2 top-1/2 -translate-x-full -translate-y-1/2 text-xs text-slate-500 font-medium">
|
||
Medio (5)
|
||
</div>
|
||
<div className="absolute -left-2 bottom-0 -translate-x-full text-xs text-slate-500 font-medium">
|
||
Bajo (1)
|
||
</div>
|
||
|
||
<div className="absolute left-0 -bottom-2 translate-y-full text-xs text-slate-500 font-medium">
|
||
0
|
||
</div>
|
||
<div className="absolute left-1/2 -bottom-2 -translate-x-1/2 translate-y-full text-xs text-slate-500 font-medium">
|
||
5
|
||
</div>
|
||
<div className="absolute right-0 -bottom-2 translate-y-full text-xs text-slate-500 font-medium">
|
||
10
|
||
</div>
|
||
|
||
{/* Quadrant Lines */}
|
||
<div className="absolute top-1/2 left-0 w-full border-t-2 border-dashed border-slate-300"></div>
|
||
<div className="absolute left-1/2 top-0 h-full border-l-2 border-dashed border-slate-300"></div>
|
||
|
||
{/* Enhanced Quadrant Labels */}
|
||
<div className="absolute top-6 left-6 max-w-[200px]">
|
||
<div className={`text-sm font-bold ${getQuadrantInfo(3, 8).color} ${getQuadrantInfo(3, 8).bgColor} px-3 py-2 rounded-lg shadow-sm border-2 border-amber-200`}>
|
||
<div>{getQuadrantInfo(3, 8).label}</div>
|
||
<div className="text-xs font-normal mt-1">{getQuadrantInfo(3, 8).recommendation}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="absolute top-6 right-6 max-w-[200px]">
|
||
<div className={`text-sm font-bold ${getQuadrantInfo(8, 8).color} ${getQuadrantInfo(8, 8).bgColor} px-3 py-2 rounded-lg shadow-sm border-2 border-green-300`}>
|
||
<div>{getQuadrantInfo(8, 8).label}</div>
|
||
<div className="text-xs font-normal mt-1">{getQuadrantInfo(8, 8).recommendation}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="absolute bottom-6 left-6 max-w-[200px]">
|
||
<div className={`text-sm font-bold ${getQuadrantInfo(3, 3).color} ${getQuadrantInfo(3, 3).bgColor} px-3 py-2 rounded-lg shadow-sm border-2 border-slate-200`}>
|
||
<div>{getQuadrantInfo(3, 3).label}</div>
|
||
<div className="text-xs font-normal mt-1">{getQuadrantInfo(3, 3).recommendation}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="absolute bottom-6 right-6 max-w-[200px]">
|
||
<div className={`text-sm font-bold ${getQuadrantInfo(8, 3).color} ${getQuadrantInfo(8, 3).bgColor} px-3 py-2 rounded-lg shadow-sm border-2 border-blue-200`}>
|
||
<div>{getQuadrantInfo(8, 3).label}</div>
|
||
<div className="text-xs font-normal mt-1">{getQuadrantInfo(8, 3).recommendation}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Opportunities */}
|
||
{dataWithPriority.map((opp, index) => {
|
||
const size = 40 + (opp.savings / maxSavings) * 60; // Bubble size from 40px to 100px
|
||
const isHovered = hoveredOpportunity === opp.id;
|
||
const isSelected = selectedOpportunity?.id === opp.id;
|
||
|
||
return (
|
||
<motion.div
|
||
key={opp.id}
|
||
className="absolute cursor-pointer"
|
||
style={{
|
||
left: `calc(${(opp.feasibility / 10) * 100}% - ${size / 2}px)`,
|
||
bottom: `calc(${(opp.impact / 10) * 100}% - ${size / 2}px)`,
|
||
width: `${size}px`,
|
||
height: `${size}px`,
|
||
}}
|
||
initial={{ scale: 0, opacity: 0 }}
|
||
animate={{ scale: 1, opacity: 1 }}
|
||
transition={{ delay: index * 0.08, type: 'spring', stiffness: 200 }}
|
||
whileHover={{ scale: 1.15, zIndex: 10 }}
|
||
onMouseEnter={() => setHoveredOpportunity(opp.id)}
|
||
onMouseLeave={() => setHoveredOpportunity(null)}
|
||
onClick={() => setSelectedOpportunity(opp)}
|
||
>
|
||
<div
|
||
className={`w-full h-full rounded-full transition-all flex items-center justify-center relative ${
|
||
isSelected ? 'ring-4 ring-blue-400' : ''
|
||
} ${getQuadrantColor(opp.impact, opp.feasibility)}`}
|
||
style={{ opacity: isHovered || isSelected ? 0.95 : 0.75 }}
|
||
>
|
||
<span className="text-white font-bold text-lg">#{opp.priority}</span>
|
||
{/* v2.0: Indicador de variabilidad si hay datos de heatmap */}
|
||
{heatmapData && (() => {
|
||
const relatedSkill = heatmapData.find(h => {
|
||
if (!h.skill || !opp.name) return false;
|
||
const skillLower = h.skill.toLowerCase();
|
||
const oppNameLower = opp.name.toLowerCase();
|
||
return oppNameLower.includes(skillLower) || skillLower.includes(oppNameLower.split(' ')[0]);
|
||
});
|
||
if (relatedSkill && relatedSkill.automation_readiness < 60) {
|
||
return (
|
||
<div className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center border-2 border-white">
|
||
<AlertCircle size={12} className="text-white" />
|
||
</div>
|
||
);
|
||
}
|
||
return null;
|
||
})()}
|
||
</div>
|
||
|
||
{/* Hover Tooltip */}
|
||
{isHovered && !selectedOpportunity && (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 10 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
className="absolute bottom-full mb-3 left-1/2 -translate-x-1/2 w-56 bg-slate-900 text-white p-4 rounded-lg text-xs shadow-2xl z-20 pointer-events-none"
|
||
>
|
||
<div className="flex items-start justify-between mb-2">
|
||
<h4 className="font-bold text-sm flex-1">{opp.name}</h4>
|
||
<span className="text-green-400 font-bold ml-2">#{opp.priority}</span>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-slate-300">Impacto:</span>
|
||
<span className="font-semibold">{opp.impact}/10 ({getImpactLabel(opp.impact)})</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-slate-300">Factibilidad:</span>
|
||
<span className="font-semibold">{opp.feasibility}/10 ({getFeasibilityLabel(opp.feasibility)})</span>
|
||
</div>
|
||
<div className="flex items-center justify-between pt-2 border-t border-slate-700">
|
||
<span className="text-slate-300">Ahorro Anual:</span>
|
||
<span className="font-bold text-green-400">€{opp.savings.toLocaleString('es-ES')}</span>
|
||
</div>
|
||
</div>
|
||
<div className="absolute top-full left-1/2 -translate-x-1/2 w-0 h-0 border-x-4 border-x-transparent border-t-4 border-t-slate-900"></div>
|
||
</motion.div>
|
||
)}
|
||
</motion.div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Enhanced Legend */}
|
||
<div className="mt-8 p-4 bg-slate-50 rounded-lg">
|
||
<div className="flex flex-wrap items-center gap-4 text-xs">
|
||
<span className="font-semibold text-slate-700">Tier:</span>
|
||
<div className="flex items-center gap-1">
|
||
<span>🤖</span>
|
||
<span className="text-emerald-600 font-medium">AUTOMATE</span>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<span>🤝</span>
|
||
<span className="text-blue-600 font-medium">ASSIST</span>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<span>📚</span>
|
||
<span className="text-amber-600 font-medium">AUGMENT</span>
|
||
</div>
|
||
<span className="text-slate-400">|</span>
|
||
<span className="font-semibold text-slate-700">Tamaño = Ahorro TCO</span>
|
||
<span className="text-slate-400">|</span>
|
||
<span className="font-semibold text-slate-700">Número = Ranking</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Selected Opportunity Detail Panel */}
|
||
<AnimatePresence>
|
||
{selectedOpportunity && (
|
||
<motion.div
|
||
initial={{ opacity: 0, height: 0 }}
|
||
animate={{ opacity: 1, height: 'auto' }}
|
||
exit={{ opacity: 0, height: 0 }}
|
||
className="mt-6 overflow-hidden"
|
||
>
|
||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 border-2 border-blue-200 rounded-xl p-6">
|
||
<div className="flex items-start justify-between mb-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className={`w-12 h-12 rounded-full ${getQuadrantColor(selectedOpportunity.impact, selectedOpportunity.feasibility)} flex items-center justify-center`}>
|
||
<span className="text-white font-bold text-lg">#{selectedOpportunity.priority}</span>
|
||
</div>
|
||
<div>
|
||
<h4 className="font-bold text-xl text-slate-800">{selectedOpportunity.name}</h4>
|
||
<p className="text-sm text-blue-700 font-medium">
|
||
{getQuadrantInfo(selectedOpportunity.impact, selectedOpportunity.feasibility).label}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => setSelectedOpportunity(null)}
|
||
className="text-slate-400 hover:text-slate-600 transition-colors"
|
||
>
|
||
<X size={24} />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||
<div className="bg-white rounded-lg p-4 border border-blue-100">
|
||
<div className="text-xs text-slate-600 mb-1">Impacto</div>
|
||
<div className="text-2xl font-bold text-blue-600">{selectedOpportunity.impact}/10</div>
|
||
<div className="text-xs text-slate-500 mt-1">{getImpactLabel(selectedOpportunity.impact)}</div>
|
||
</div>
|
||
<div className="bg-white rounded-lg p-4 border border-blue-100">
|
||
<div className="text-xs text-slate-600 mb-1">Factibilidad</div>
|
||
<div className="text-2xl font-bold text-blue-600">{selectedOpportunity.feasibility}/10</div>
|
||
<div className="text-xs text-slate-500 mt-1">{getFeasibilityLabel(selectedOpportunity.feasibility)}</div>
|
||
</div>
|
||
<div className="bg-white rounded-lg p-4 border border-green-100">
|
||
<div className="text-xs text-slate-600 mb-1">Ahorro Anual</div>
|
||
<div className="text-2xl font-bold text-green-600">€{selectedOpportunity.savings.toLocaleString('es-ES')}</div>
|
||
<div className="text-xs text-slate-500 mt-1">Potencial</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-lg p-4 border border-blue-100">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Target size={16} className="text-blue-600" />
|
||
<span className="font-semibold text-slate-800">Recomendación:</span>
|
||
</div>
|
||
<p className="text-sm text-slate-700">
|
||
{getQuadrantInfo(selectedOpportunity.impact, selectedOpportunity.feasibility).recommendation}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
{/* Methodology Footer */}
|
||
<MethodologyFooter
|
||
sources="Agentic Readiness Score (5 factores ponderados) | Modelo TCO con CPI diferenciado por tier"
|
||
methodology="Factibilidad = Agentic Score (0-10) | Impacto = Ahorro TCO anual según tier: AUTOMATE (Vol/11×12×70%×€2.18), ASSIST (×30%×€0.83), AUGMENT (×15%×€0.33)"
|
||
notes="Top 10 iniciativas ordenadas por potencial económico | CPI: Humano €2.33, Bot €0.15, Assist €1.50, Augment €2.00"
|
||
lastUpdated="Enero 2026"
|
||
/>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default OpportunityMatrixPro;
|