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 = ({ data, heatmapData }) => { const [selectedOpportunity, setSelectedOpportunity] = useState(null); const [hoveredOpportunity, setHoveredOpportunity] = useState(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 (
{/* Header with Dynamic Title */}

Opportunity Matrix - Top 10 Iniciativas

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.

Priorizadas por potencial de ahorro TCO (🤖 AUTOMATE, 🤝 ASSIST, 📚 AUGMENT)

{dynamicTitle}

{dataWithPriority.length} iniciativas identificadas | Ahorro TCO según tier (AUTOMATE 70%, ASSIST 30%, AUGMENT 15%)

{/* Portfolio Summary */}
Total Ahorro Potencial
€{(portfolioSummary.totalSavings / 1000).toFixed(0)}K
anuales
Quick Wins ({portfolioSummary.quickWins.count})
€{(portfolioSummary.quickWins.savings / 1000).toFixed(0)}K
6 meses
Estratégicos ({portfolioSummary.strategic.count})
€{(portfolioSummary.strategic.savings / 1000).toFixed(0)}K
18 meses
ROI Portfolio
4.3x
3 años
{/* Matrix */}
{/* Y-axis Label */}
IMPACTO (Ahorro TCO)
{/* X-axis Label */}
FACTIBILIDAD (Agentic Score)
{/* Axis scale labels */}
Alto (10)
Medio (5)
Bajo (1)
0
5
10
{/* Quadrant Lines */}
{/* Enhanced Quadrant Labels */}
{getQuadrantInfo(3, 8).label}
{getQuadrantInfo(3, 8).recommendation}
{getQuadrantInfo(8, 8).label}
{getQuadrantInfo(8, 8).recommendation}
{getQuadrantInfo(3, 3).label}
{getQuadrantInfo(3, 3).recommendation}
{getQuadrantInfo(8, 3).label}
{getQuadrantInfo(8, 3).recommendation}
{/* 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 ( setHoveredOpportunity(opp.id)} onMouseLeave={() => setHoveredOpportunity(null)} onClick={() => setSelectedOpportunity(opp)} >
#{opp.priority} {/* 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 (
); } return null; })()}
{/* Hover Tooltip */} {isHovered && !selectedOpportunity && (

{opp.name}

#{opp.priority}
Impacto: {opp.impact}/10 ({getImpactLabel(opp.impact)})
Factibilidad: {opp.feasibility}/10 ({getFeasibilityLabel(opp.feasibility)})
Ahorro Anual: €{opp.savings.toLocaleString('es-ES')}
)}
); })}
{/* Enhanced Legend */}
Tier:
🤖 AUTOMATE
🤝 ASSIST
📚 AUGMENT
| Tamaño = Ahorro TCO | Número = Ranking
{/* Selected Opportunity Detail Panel */} {selectedOpportunity && (
#{selectedOpportunity.priority}

{selectedOpportunity.name}

{getQuadrantInfo(selectedOpportunity.impact, selectedOpportunity.feasibility).label}

Impacto
{selectedOpportunity.impact}/10
{getImpactLabel(selectedOpportunity.impact)}
Factibilidad
{selectedOpportunity.feasibility}/10
{getFeasibilityLabel(selectedOpportunity.feasibility)}
Ahorro Anual
€{selectedOpportunity.savings.toLocaleString('es-ES')}
Potencial
Recomendación:

{getQuadrantInfo(selectedOpportunity.impact, selectedOpportunity.feasibility).recommendation}

)}
{/* Methodology Footer */}
); }; export default OpportunityMatrixPro;