import React from 'react'; import { motion } from 'framer-motion'; import { Clock, DollarSign, TrendingUp, AlertTriangle, CheckCircle, ArrowRight, Info, Users, Target, Zap, Shield, ChevronDown, ChevronUp, BookOpen, Bot, Settings, Rocket, AlertCircle } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { RoadmapPhase } from '../../types'; import type { AnalysisData, RoadmapInitiative, HeatmapDataPoint, DrilldownDataPoint, OriginalQueueMetrics, AgenticTier } from '../../types'; import { Card, Badge, SectionHeader, DistributionBar, Stat, Collapsible, } from '../ui'; import { cn, COLORS, STATUS_CLASSES, getStatusFromScore, formatCurrency, formatNumber, formatPercent, } from '../../config/designSystem'; import OpportunityMatrixPro from '../OpportunityMatrixPro'; import OpportunityPrioritizer from '../OpportunityPrioritizer'; interface RoadmapTabProps { data: AnalysisData; } // ========== TIPOS PARA ROADMAP HONESTO ========== interface WaveData { id: string; nombre: string; titulo: string; trimestre: string; tipo: 'consulting' | 'beyond' | 'beyond_consulting'; icon: React.ReactNode; color: string; bgColor: string; borderColor: string; inversionSetup: number; costoRecurrenteAnual: number; ahorroAnual: number; esCondicional: boolean; condicion?: string; porQueNecesario: string; skills: string[]; iniciativas: { nombre: string; setup: number; recurrente: number; kpi: string; }[]; criteriosExito: string[]; riesgo: 'bajo' | 'medio' | 'alto'; riesgoDescripcion: string; proveedor: string; } // v3.9: Información detallada de Payback interface PaybackInfo { meses: number; // Meses totales hasta recuperar inversión mesesImplementacion: number; // Meses de implementación antes de ahorro mesesRecuperacion: number; // Meses para recuperar después de implementar texto: string; // Texto formateado para display clase: string; // Clase CSS para color esRecuperable: boolean; // True si hay payback finito tooltip: string; // Explicación del cálculo } interface EscenarioData { id: string; nombre: string; descripcion: string; waves: string[]; inversionTotal: number; costoRecurrenteAnual: number; ahorroAnual: number; ahorroAjustado: number; // v3.7: Ahorro ajustado por riesgo margenAnual: number; paybackMeses: number; // Mantener para compatibilidad paybackInfo: PaybackInfo; // v3.9: Info detallada de payback roi3Anos: number; roi3AnosAjustado: number; // v3.7: ROI ajustado por riesgo riesgo: 'bajo' | 'medio' | 'alto'; recomendacion: string; esRecomendado: boolean; esRentable: boolean; // v3.7: Flag de rentabilidad // v3.8: Waves habilitadoras esHabilitador: boolean; // True si es principalmente habilitador potencialHabilitado: number; // Ahorro de waves posteriores que habilita wavesHabilitadas: string[]; // Nombres de waves que habilita incluyeQuickWin: boolean; // v3.9: True si incluye Wave 4 (Quick Wins) } /** * v3.8: Detecta si un escenario es principalmente habilitador * Un escenario es habilitador si: * 1. margen_anual <= 0 * 2. margen_anual < (inversion × 0.20) - Recupera menos del 20% al año * 3. Solo incluye waves habilitadoras (Wave 1 Foundation siempre lo es) */ const isEnablingScenario = ( margenAnual: number, inversionTotal: number, waves: string[] ): boolean => { // Condición 1: Margen negativo o cero if (margenAnual <= 0) return true; // Condición 2: Recupera menos del 20% del setup al año if (margenAnual < inversionTotal * 0.20) return true; // Condición 3: Solo incluye Wave 1-2 (habilitadoras por definición) const onlyEnablingWaves = waves.every(w => w === 'wave1' || w === 'wave2'); if (onlyEnablingWaves && margenAnual < inversionTotal * 0.30) return true; return false; }; // ========== FÓRMULAS DE CÁLCULO v3.7 ========== // Factores de éxito por wave (probabilidad de alcanzar ahorro proyectado) const RISK_FACTORS = { wave1: 0.90, // Foundation: bajo riesgo de ejecución wave2: 0.75, // Augment: riesgo medio wave3: 0.60, // Assist: riesgo medio-alto, depende de adopción wave4: 0.50 // Automate: riesgo alto, depende de tecnología }; // v3.9: Tiempos de implementación por tipo de wave (en meses) const WAVE_IMPLEMENTATION_TIME: Record = { wave1: 6, // FOUNDATION: Q1-Q2 = 6 meses wave2: 3, // AUGMENT: Q3 = 3 meses wave3: 3, // ASSIST: Q4 = 3 meses wave4: 6 // AUTOMATE: Q1-Q2 año siguiente = 6 meses }; /** * v3.9: Calcula meses hasta que comienza el ahorro * El ahorro empieza cuando las waves productivas (3-4) comienzan a dar resultados */ const calcularMesesImplementacion = (waves: string[], incluyeQuickWin: boolean): number => { // Si incluye Quick Win (Wave 4 AUTOMATE), el ahorro puede empezar antes if (incluyeQuickWin && waves.includes('wave4')) { // Quick Wins empiezan a dar ahorro en ~3 meses (piloto) return 3; } // Calcular tiempo acumulado hasta que la última wave productiva da resultados // Wave 1 y 2 son principalmente habilitadoras (poco ahorro directo) // Wave 3 y 4 son las que generan ahorro real const ultimaWaveProductiva = waves.includes('wave4') ? 'wave4' : waves.includes('wave3') ? 'wave3' : waves.includes('wave2') ? 'wave2' : 'wave1'; let tiempoAcumulado = 0; // Sumar tiempos de waves previas for (const wave of ['wave1', 'wave2', 'wave3', 'wave4']) { if (wave === ultimaWaveProductiva) { // Añadir la mitad del tiempo de la última wave (cuando empieza a dar resultados) tiempoAcumulado += Math.ceil(WAVE_IMPLEMENTATION_TIME[wave] / 2); break; } if (waves.includes(wave)) { tiempoAcumulado += WAVE_IMPLEMENTATION_TIME[wave]; } } return tiempoAcumulado; }; /** * v3.9: Calcula payback completo considerando tiempo de implementación */ const calcularPaybackCompleto = ( inversion: number, margenAnual: number, ahorroAnual: number, waves: string[], esHabilitador: boolean, incluyeQuickWin: boolean, t: any ): PaybackInfo => { // 1. Caso especial: escenario habilitador con poco ahorro directo if (esHabilitador || ahorroAnual < inversion * 0.1) { return { meses: -1, mesesImplementacion: calcularMesesImplementacion(waves, incluyeQuickWin), mesesRecuperacion: -1, texto: t('roadmap.payback.seeWave34'), clase: 'text-blue-600', esRecuperable: false, tooltip: 'Esta inversión se recupera con las waves de automatización (W3-W4). ' + 'El payback se calcula sobre el roadmap completo, no sobre waves habilitadoras aisladas.' }; } // 2. Calcular margen mensual neto const margenMensual = margenAnual / 12; // 3. Si margen negativo o cero, no hay payback if (margenMensual <= 0) { return { meses: -1, mesesImplementacion: 0, mesesRecuperacion: -1, texto: t('roadmap.payback.notRecoverable'), clase: 'text-red-600', esRecuperable: false, tooltip: 'El ahorro anual no supera los costes recurrentes. ' + `Margen neto: ${formatCurrency(margenAnual)}/año` }; } // 4. Calcular tiempo hasta que comienza el ahorro const mesesImplementacion = calcularMesesImplementacion(waves, incluyeQuickWin); // 5. Calcular meses para recuperar la inversión (después de implementación) const mesesRecuperacion = Math.ceil(inversion / margenMensual); // 6. Payback total = implementación + recuperación const paybackTotal = mesesImplementacion + mesesRecuperacion; // 7. Formatear resultado según duración return formatearPaybackResult(paybackTotal, mesesImplementacion, mesesRecuperacion, margenMensual, inversion, t); }; /** * v3.9: Formatea el resultado del payback */ const formatearPaybackResult = ( meses: number, mesesImpl: number, mesesRec: number, margenMensual: number, inversion: number, t: any ): PaybackInfo => { const tooltipBase = `Implementación: ${mesesImpl} meses → Recuperación: ${mesesRec} meses. ` + `Margen: ${formatCurrency(margenMensual * 12)}/año.`; if (meses <= 0) { return { meses: 0, mesesImplementacion: mesesImpl, mesesRecuperacion: mesesRec, texto: t('roadmap.payback.immediate'), clase: 'text-emerald-600', esRecuperable: true, tooltip: tooltipBase }; } if (meses <= 12) { return { meses, mesesImplementacion: mesesImpl, mesesRecuperacion: mesesRec, texto: `${meses} meses`, clase: 'text-emerald-600', esRecuperable: true, tooltip: tooltipBase }; } if (meses <= 18) { return { meses, mesesImplementacion: mesesImpl, mesesRecuperacion: mesesRec, texto: `${meses} meses`, clase: 'text-yellow-600', esRecuperable: true, tooltip: tooltipBase }; } if (meses <= 24) { return { meses, mesesImplementacion: mesesImpl, mesesRecuperacion: mesesRec, texto: `${meses} meses`, clase: 'text-amber-600', esRecuperable: true, tooltip: tooltipBase + ' ⚠️ Periodo de recuperación moderado.' }; } // > 24 meses: mostrar en años const anos = Math.round(meses / 12 * 10) / 10; return { meses, mesesImplementacion: mesesImpl, mesesRecuperacion: mesesRec, texto: `${anos} años`, clase: 'text-orange-600', esRecuperable: true, tooltip: tooltipBase + ' ⚠️ Periodo de recuperación largo. Considerar escenario menos ambicioso.' }; }; /** * Calcula payback simple (mantener para compatibilidad) */ const calculatePayback = (inversion: number, margenAnual: number): number => { if (inversion <= 0) return 0; if (margenAnual <= 0) return -1; return Math.ceil(inversion / (margenAnual / 12)); }; /** * Calcula ROI a 3 años con fórmula correcta * Fórmula: ROI = ((ahorro_total_3a - coste_total_3a) / coste_total_3a) × 100 * Donde: coste_total_3a = inversion + (recurrente × 3) */ const calculateROI3Years = ( inversion: number, recurrenteAnual: number, ahorroAnual: number ): number => { const costeTotalTresAnos = inversion + (recurrenteAnual * 3); if (costeTotalTresAnos <= 0) return 0; const ahorroTotalTresAnos = ahorroAnual * 3; const roi = ((ahorroTotalTresAnos - costeTotalTresAnos) / costeTotalTresAnos) * 100; // Devolver con 1 decimal return Math.round(roi * 10) / 10; }; /** * Calcula ahorro ajustado por riesgo por wave */ const calculateRiskAdjustedSavings = ( wave2Savings: number, wave3Savings: number, wave4Savings: number, includeWaves: string[] ): number => { let adjusted = 0; if (includeWaves.includes('wave2')) { adjusted += wave2Savings * RISK_FACTORS.wave2; } if (includeWaves.includes('wave3')) { adjusted += wave3Savings * RISK_FACTORS.wave3; } if (includeWaves.includes('wave4')) { adjusted += wave4Savings * RISK_FACTORS.wave4; } return Math.round(adjusted); }; // v3.9: formatPayback eliminado - usar calcularPaybackCompleto() en su lugar /** * Formatea ROI para display con warnings */ const formatROI = (roi: number, roiAjustado: number): { text: string; showAjustado: boolean; isHighWarning: boolean; } => { const roiDisplay = roi > 0 ? `${roi.toFixed(1)}%` : 'N/A'; const showAjustado = roi > 500; const isHighWarning = roi > 1000; return { text: roiDisplay, showAjustado, isHighWarning }; }; // ========== COMPONENTE: MAPA DE OPORTUNIDADES v3.5 ========== // Ejes actualizados: // - X: FACTIBILIDAD = Score Agentic Readiness (0-10) // - Y: IMPACTO ECONÓMICO = Ahorro anual TCO (€) // - Tamaño: Volumen mensual de interacciones // - Color: Tier (Verde=AUTOMATE, Azul=ASSIST, Naranja=AUGMENT, Rojo=HUMAN-ONLY) interface BubbleDataPoint { id: string; name: string; feasibility: number; // Score Agentic Readiness (0-10) economicImpact: number; // Ahorro anual TCO (€) volume: number; // Volumen mensual tier: AgenticTier; rank?: number; } // v3.5: Colores por Tier // Note: labels are now set dynamically using t() in the component const TIER_COLORS: Record = { 'AUTOMATE': { fill: '#059669', stroke: '#047857', label: '' }, 'ASSIST': { fill: '#3B82F6', stroke: '#2563EB', label: '' }, 'AUGMENT': { fill: '#F59E0B', stroke: '#D97706', label: '' }, 'HUMAN-ONLY': { fill: '#EF4444', stroke: '#DC2626', label: '' } }; // v3.6: Constantes CPI para cálculo de ahorro TCO const CPI_CONFIG = { CPI_HUMANO: 2.33, // €/interacción - coste actual agente humano CPI_BOT: 0.15, // €/interacción - coste bot/automatización CPI_ASSIST: 1.50, // €/interacción - coste con copilot CPI_AUGMENT: 2.00, // €/interacción - coste optimizado // Tasas de éxito/contención por tier RATE_AUTOMATE: 0.70, // 70% contención en automatización RATE_ASSIST: 0.30, // 30% eficiencia en asistencia RATE_AUGMENT: 0.15 // 15% mejora en optimización }; // Período de datos: el volumen corresponde a 11 meses, no es mensual const DATA_PERIOD_MONTHS = 11; // v4.2: Calcular ahorro TCO realista con fórmula explícita // IMPORTANTE: El volumen es de 11 meses, se convierte a anual: (Vol/11) × 12 function calculateTCOSavings(volume: number, tier: AgenticTier): number { if (volume === 0) return 0; const { CPI_HUMANO, CPI_BOT, CPI_ASSIST, CPI_AUGMENT, RATE_AUTOMATE, RATE_ASSIST, RATE_AUGMENT } = CPI_CONFIG; // Convertir volumen del período (11 meses) a volumen anual const annualVolume = (volume / DATA_PERIOD_MONTHS) * 12; switch (tier) { case 'AUTOMATE': // Ahorro = VolAnual × 70% × (CPI_humano - CPI_bot) return Math.round(annualVolume * RATE_AUTOMATE * (CPI_HUMANO - CPI_BOT)); case 'ASSIST': // Ahorro = VolAnual × 30% × (CPI_humano - CPI_assist) return Math.round(annualVolume * RATE_ASSIST * (CPI_HUMANO - CPI_ASSIST)); case 'AUGMENT': // Ahorro = VolAnual × 15% × (CPI_humano - CPI_augment) return Math.round(annualVolume * RATE_AUGMENT * (CPI_HUMANO - CPI_AUGMENT)); case 'HUMAN-ONLY': default: return 0; } } function OpportunityBubbleChart({ heatmapData, drilldownData }: { heatmapData: HeatmapDataPoint[]; drilldownData?: DrilldownDataPoint[] }) { const { t } = useTranslation(); // v3.5: Usar drilldownData si está disponible para tener info de Tier por cola let chartData: BubbleDataPoint[] = []; if (drilldownData && drilldownData.length > 0) { // Aplanar todas las colas de todos los skills const allQueues = drilldownData.flatMap(skill => skill.originalQueues.map(q => ({ queue: q, skillName: skill.skill })) ); // Generar puntos de datos para el chart chartData = allQueues .filter(item => item.queue.tier !== 'HUMAN-ONLY') // Excluir HUMAN-ONLY del chart principal .slice(0, 15) // Limitar a 15 burbujas para legibilidad .map((item, idx) => { const savings = calculateTCOSavings(item.queue.volume, item.queue.tier); return { id: `opp-${idx + 1}`, name: item.queue.original_queue_id, feasibility: item.queue.agenticScore, economicImpact: savings, volume: item.queue.volume, tier: item.queue.tier }; }); } else { // Fallback: usar heatmapData si no hay drilldown chartData = heatmapData.slice(0, 10).map((item, idx) => { const score = (item.automation_readiness || 50) / 10; const tier: AgenticTier = score >= 7.5 ? 'AUTOMATE' : score >= 5.5 ? 'ASSIST' : score >= 3.5 ? 'AUGMENT' : 'HUMAN-ONLY'; const savings = calculateTCOSavings(item.volume, tier); return { id: `opp-${idx + 1}`, name: item.skill, feasibility: score, economicImpact: savings, volume: item.volume, tier }; }); } // Ordenar por ahorro y asignar ranks const rankedData = chartData .sort((a, b) => b.economicImpact - a.economicImpact) .map((item, idx) => ({ ...item, rank: idx + 1 })); // Calcular límites para escalas const maxSavings = Math.max(...rankedData.map(d => d.economicImpact), 1000); const maxVolume = Math.max(...rankedData.map(d => d.volume), 100); const minBubbleSize = 20; const maxBubbleSize = 50; const padding = 10; return (

{t('roadmap.opportunityMapTitle')}

{t('roadmap.opportunityMapSubtitle')}

{/* Bubble Chart */}
{/* Y-axis label */}
{t('roadmap.economicImpactAxis')}
{/* X-axis label */}
{t('roadmap.feasibilityAxis')}
{/* Chart area */}
{/* Grid lines */}
{[...Array(20)].map((_, i) => (
))}
{/* Threshold lines */} {/* Vertical line at score = 7.5 (AUTOMATE threshold) */}
{t('roadmap.tierAutomateThreshold')}
{/* Vertical line at score = 5.5 (ASSIST threshold) */}
{/* Quadrant labels - basados en Score (X) y Ahorro (Y) */} {/* Top-right: High Score + High Savings = QUICK WINS */}
🎯 {t('roadmap.quadrantQuickWins')}
{t('roadmap.quadrantQuickWinsDesc')}
{t('roadmap.quadrantQuickWinsPriority')}
{/* Top-left: Low Score + High Savings = OPTIMIZE */}
⚙️ {t('roadmap.quadrantOptimize')}
{t('roadmap.quadrantOptimizeDesc')}
{t('roadmap.quadrantOptimizePriority')}
{/* Bottom-right: High Score + Low Savings = STRATEGIC */}
📊 {t('roadmap.quadrantStrategic')}
{t('roadmap.quadrantStrategicDesc')}
{t('roadmap.quadrantStrategicPriority')}
{/* Bottom-left: Low Score + Low Savings = DEFER */}
📋 {t('roadmap.quadrantDefer')}
{t('roadmap.quadrantDeferDesc')}
{t('roadmap.quadrantDeferPriority')}
{/* Bubbles */} {rankedData.map((item, idx) => { // X: feasibility (score 0-10) → left to right const x = padding + (item.feasibility / 10) * (100 - 2 * padding); // Y: economicImpact → bottom to top (invert) const y = (100 - padding) - (item.economicImpact / maxSavings) * (100 - 2 * padding); // Size: based on volume const size = minBubbleSize + (item.volume / maxVolume) * (maxBubbleSize - minBubbleSize); const tierColor = TIER_COLORS[item.tier]; const shortName = item.name.length > 12 ? item.name.substring(0, 10) + '...' : item.name; return ( {item.rank} {size >= 32 && ( {shortName} )} {/* Tooltip */}
{item.name}
{t('roadmap.tooltipScore')} {item.feasibility.toFixed(1)}/10
{t('roadmap.tooltipVolume')} {item.volume.toLocaleString()}{t('roadmap.perMonth')}
{t('roadmap.tooltipSavingsTco')} {formatCurrency(item.economicImpact)}{t('roadmap.perYear')}
{t('roadmap.tooltipTier')} {tierColor.label}
); })} {/* Y-axis ticks */}
{formatCurrency(maxSavings)}
{formatCurrency(maxSavings / 2)}
€0
{/* X-axis ticks */}
0
2.5
5
7.5
10
{/* Priority List with Tier badges */}
{rankedData.slice(0, 8).map((item) => { const tierColor = TIER_COLORS[item.tier]; return (
{item.rank}
{item.name}
{formatCurrency(item.economicImpact)} {item.tier}
); })}
{/* Leyenda por Tier */}

{t('roadmap.interpretation')} {t('roadmap.opportunityMapInterpretation')}

{t('roadmap.bubbleSize')} {t('roadmap.volume')}
AUTOMATE (≥7.5)
ASSIST (≥5.5)
AUGMENT (≥3.5)
HUMAN (<3.5)
{/* Metodología detallada */}

{t('roadmap.methodologyTitle')}

{/* Ejes */}
📊 {t('roadmap.axisXFactibility')}

{t('roadmap.axisXFactibilityDesc')}

  • {t('roadmap.factorPredictability')}: {t('roadmap.factorPredictabilityDesc')}
  • {t('roadmap.factorResolution')}: {t('roadmap.factorResolutionDesc')}
  • {t('roadmap.factorVolumeWeight')}: {t('roadmap.factorVolumeDesc')}
  • {t('roadmap.factorDataQuality')}: {t('roadmap.factorDataQualityDesc')}
  • {t('roadmap.factorSimplicity')}: {t('roadmap.factorSimplicityDesc')}
💰 {t('roadmap.axisYEconomicImpact')}

{t('roadmap.axisYEconomicImpactDesc')}

CPI Humano = €{CPI_CONFIG.CPI_HUMANO.toFixed(2)}/int

CPI Bot = €{CPI_CONFIG.CPI_BOT.toFixed(2)}/int

CPI Assist = €{CPI_CONFIG.CPI_ASSIST.toFixed(2)}/int

CPI Augment = €{CPI_CONFIG.CPI_AUGMENT.toFixed(2)}/int

{/* Fórmulas por Tier */}
🧮 {t('roadmap.savingsFormulas')}

{t('roadmap.formulaAutomate')}

{t('roadmap.formulaAutomateCalc')}

{t('roadmap.formulaAutomateResult')}

{t('roadmap.formulaAssist')}

{t('roadmap.formulaAssistCalc')}

{t('roadmap.formulaAssistResult')}

{t('roadmap.formulaAugment')}

{t('roadmap.formulaAugmentCalc')}

{t('roadmap.formulaAugmentResult')}

{t('roadmap.formulaHumanOnly')}

{t('roadmap.formulaHumanOnlyCalc')}

{t('roadmap.formulaHumanOnlyRequires')}

{/* Clasificación de Tier */}
🏷️ {t('roadmap.tierClassificationCriteria')}

AUTOMATE

  • • Score ≥ 7.5
  • • CV ≤ 75%
  • • Transfer ≤ 20%
  • • FCR ≥ 50%

ASSIST

  • • Score ≥ 5.5
  • • CV ≤ 90%
  • • Transfer ≤ 30%
  • • Sin red flags

AUGMENT

  • • Score ≥ 3.5
  • • Sin red flags
  • • Requiere optimización
  •  

HUMAN-ONLY

  • • Score < 3.5, o
  • • CV > 120%
  • • Transfer > 50%
  • • Vol < 50 o Valid < 30%
{/* Nota metodológica */}

{t('roadmap.methodologicalNote')} {t('roadmap.methodologicalNoteText')}

); } // ========== TIPOS ADICIONALES PARA WAVE CARDS MEJORADOS ========== interface WaveEntryCriteria { tierFrom: string[]; scoreRange: string; requiredMetrics: string[]; } interface WaveExitCriteria { tierTo: string; scoreTarget: string; kpiTargets: string[]; } interface PriorityQueue { name: string; volume: number; currentScore: number; currentTier: AgenticTier; potentialSavings: number; redFlags?: string[]; // v3.7: Red flags que explican tier } // v3.7: Detectar red flags que explican por qué una cola tiene tier inferior al score function detectRedFlags(queue: { agenticScore: number; tier: AgenticTier; cv_aht: number; transfer_rate: number; volume: number; volumeValid: number; }): string[] { const flags: string[] = []; // CV AHT muy alto (umbral >120% = alta variabilidad) if (queue.cv_aht > 120) { flags.push(`CV ${queue.cv_aht.toFixed(0)}%`); } // Transfer rate alto (>50% = proceso mal diseñado) if (queue.transfer_rate > 50) { flags.push(`Transfer ${queue.transfer_rate.toFixed(0)}%`); } // Volumen muy bajo (< 50/mes = muestra insuficiente) if (queue.volume < 50) { flags.push(`Vol <50`); } // Porcentaje de registros válidos bajo (< 30% = datos ruidosos) const validPct = queue.volume > 0 ? (queue.volumeValid / queue.volume) * 100 : 0; if (validPct < 30 && queue.volume > 0) { flags.push(`Valid ${validPct.toFixed(0)}%`); } return flags; } // ========== COMPONENTE: WAVE CARD MEJORADO ========== function WaveCard({ wave, delay = 0, entryCriteria, exitCriteria, priorityQueues }: { wave: WaveData; delay?: number; entryCriteria?: WaveEntryCriteria; exitCriteria?: WaveExitCriteria; priorityQueues?: PriorityQueue[]; }) { const { t } = useTranslation(); const [expanded, setExpanded] = React.useState(false); const margenAnual = wave.ahorroAnual - wave.costoRecurrenteAnual; const roiWave = wave.inversionSetup > 0 ? Math.round((margenAnual / wave.inversionSetup) * 100) : 0; const riesgoColors = { bajo: 'bg-emerald-100 text-emerald-700', medio: 'bg-amber-100 text-amber-700', alto: 'bg-red-100 text-red-700' }; const riesgoIcons = { bajo: '🟢', medio: '🟡', alto: '🔴' }; return ( {/* Header */}
{wave.icon}

{wave.titulo}

{wave.esCondicional && ( {t('roadmap.conditional')} )}

{wave.trimestre}

{riesgoIcons[wave.riesgo]} {t('roadmap.risk')} {t(`roadmap.risk${wave.riesgo.charAt(0).toUpperCase() + wave.riesgo.slice(1)}`)}
{/* Content */}
{/* Criterios de Entrada/Salida - NUEVO */} {(entryCriteria || exitCriteria) && (
{/* Criterios de Entrada */} {entryCriteria && (

ENTRADA

Tier: {entryCriteria.tierFrom.join(', ')}

Score: {entryCriteria.scoreRange}

{entryCriteria.requiredMetrics.map((m, i) => (

• {m}

))}
)} {/* Criterios de Salida */} {exitCriteria && (

SALIDA

Tier: {exitCriteria.tierTo}

Score: {exitCriteria.scoreTarget}

{exitCriteria.kpiTargets.map((k, i) => (

• {k}

))}
)}
)} {/* Por qué es necesario */}

🎯 {t('roadmap.whyNecessary')}

{wave.porQueNecesario}

{/* Tabla de Colas Prioritarias - NUEVO */} {priorityQueues && priorityQueues.length > 0 && (

Top Colas por Volumen × Impacto

{priorityQueues.slice(0, 5).map((q, idx) => ( ))}
Cola Vol/mes Score Tier Red Flags Potencial
{q.name.length > 15 ? q.name.substring(0, 13) + '...' : q.name} {q.volume.toLocaleString()} {q.currentScore.toFixed(1)} {q.currentTier === 'HUMAN-ONLY' ? 'HUMAN' : q.currentTier} {q.redFlags && q.redFlags.length > 0 ? (
{q.redFlags.map((flag, i) => ( {flag} ))}
) : ( ✓ OK )}
{formatCurrency(q.potentialSavings)}
{/* v3.7: Nota explicativa de Red Flags */}
Red Flags: CV >120% (alta variabilidad) · Transfer >50% (proceso fragmentado) · Vol <50 (muestra pequeña) · Valid <30% (datos ruidosos)
)} {/* Skills afectados */}

Skills ({wave.skills.length}):

{wave.skills.map((skill, idx) => ( {skill} ))}
{/* Métricas financieras */}

Setup

{formatCurrency(wave.inversionSetup)}

Recurrente/año

{formatCurrency(wave.costoRecurrenteAnual)}

Ahorro/año

{formatCurrency(wave.ahorroAnual)}

Margen/año

{formatCurrency(margenAnual)}

{/* Expandible: Iniciativas y criterios */} {expanded && (
{/* Iniciativas */}

Iniciativas:

{wave.iniciativas.map((init, idx) => (
{idx + 1}

{init.nombre}

Setup: {formatCurrency(init.setup)} | Rec: {formatCurrency(init.recurrente)}/mes

KPI: {init.kpi}

))}
{/* Criterios de éxito */}

✅ Criterios de éxito:

    {wave.criteriosExito.map((criterio, idx) => (
  • {criterio}
  • ))}
{/* Condición si aplica */} {wave.esCondicional && wave.condicion && (

⚠️ Condición: {wave.condicion}

)} {/* Proveedor */}
Proveedor: {wave.proveedor}
)}
); } // ========== COMPONENTE: COMPARACIÓN DE ESCENARIOS v3.7 ========== function ScenarioComparison({ escenarios }: { escenarios: EscenarioData[] }) { const riesgoColors = { bajo: 'text-emerald-600 bg-emerald-100', medio: 'text-amber-600 bg-amber-100', alto: 'text-red-600 bg-red-100' }; return (

Escenarios de Inversión

Comparación de opciones según nivel de compromiso ℹ️

{escenarios.map((esc) => { // v3.9: Usar el nuevo paybackInfo detallado const pInfo = esc.paybackInfo; const roiInfo = formatROI(esc.roi3Anos, esc.roi3AnosAjustado); return ( ); })}
Escenario Inversión Recurrente Ahorro (ajustado) Margen Payback ROI 3a (ajustado) Riesgo
{esc.esHabilitador && ( 💡 )} {!esc.esRentable && !esc.esHabilitador && ( )} {esc.nombre} {esc.esHabilitador && ( Habilitador )} {esc.esRecomendado && !esc.esHabilitador && esc.esRentable && ( Recomendado )}

{esc.descripcion}

{formatCurrency(esc.inversionTotal)} {formatCurrency(esc.costoRecurrenteAnual)}/año
{formatCurrency(esc.ahorroAnual)}/año
{esc.esHabilitador && esc.potencialHabilitado > 0 && (
(habilita {formatCurrency(esc.potencialHabilitado)})
)} {!esc.esHabilitador && esc.ahorroAjustado !== esc.ahorroAnual && (
({formatCurrency(esc.ahorroAjustado)} ajust.)
)}
{esc.esHabilitador ? ( Prerrequisito ) : ( {esc.margenAnual <= 0 ? '-' : ''}{formatCurrency(Math.abs(esc.margenAnual))}/año )}
{pInfo.texto} {/* v3.9: Mostrar desglose si es recuperable */} {pInfo.esRecuperable && pInfo.meses > 12 && (
({pInfo.mesesImplementacion}m impl + {pInfo.mesesRecuperacion}m rec)
)} {/* Advertencia si payback largo */} {pInfo.meses > 24 && pInfo.esRecuperable && ( ⚠️ )}
{esc.esHabilitador ? ( Prerrequisito ) : (
{roiInfo.text} {roiInfo.isHighWarning && ( ⚠️ )} {roiInfo.showAjustado && esc.roi3AnosAjustado > 0 && ( ({esc.roi3AnosAjustado.toFixed(1)}% ajust.) )}
)}
{esc.riesgo.charAt(0).toUpperCase() + esc.riesgo.slice(1)}
{/* Nota sobre cálculos */}
Payback: Tiempo implementación + tiempo recuperación. Wave 1: 6m, W2: 3m, W3: 3m, W4: 6m. Ahorro comienza al 50% de última wave.
ROI: (Ahorro 3a - Coste Total 3a) / Coste Total 3a × 100. Ajustado aplica riesgo: W1-2: 75-90%, W3: 60%, W4: 50%.
💡 Habilitador: Waves que desbloquean ROI de waves posteriores. Su payback se evalúa con el roadmap completo.
{/* Recomendación destacada */} {(() => { const recomendado = escenarios.find(e => e.esRecomendado); const isEnabling = recomendado?.esHabilitador; const bgColor = isEnabling ? 'bg-blue-50 border-blue-200' : recomendado?.esRentable ? 'bg-emerald-50 border-emerald-200' : 'bg-amber-50 border-amber-200'; const textColor = isEnabling ? 'text-blue-800' : recomendado?.esRentable ? 'text-emerald-800' : 'text-amber-800'; const subTextColor = isEnabling ? 'text-blue-700' : recomendado?.esRentable ? 'text-emerald-700' : 'text-amber-700'; return (
{isEnabling ? ( ) : recomendado?.esRentable ? ( ) : ( )}

{isEnabling ? 'Recomendación (Habilitador)' : 'Recomendación'}

{recomendado?.recomendacion || 'Iniciar con escenario conservador para validar modelo antes de escalar.'}

{isEnabling && recomendado?.potencialHabilitado > 0 && (

💡 Valor real de esta inversión: Desbloquea {formatCurrency(recomendado.potencialHabilitado)}/año en {recomendado.wavesHabilitadas.join(' y ')}. Sin esta base, las waves posteriores no son viables.

)}
); })()}
); } // ========== COMPONENTE: TIMELINE VISUAL CON CONECTORES Y DECISIONES ========== interface DecisionGate { id: string; afterWave: string; question: string; criteria: string; goAction: string; noGoAction: string; } // v3.6: Decision Gates alineados con nueva nomenclatura y criterios de Tier const DECISION_GATES: DecisionGate[] = [ { id: 'gate1', afterWave: 'wave1', question: '¿CV ≤75% en 3+ colas?', criteria: 'Red flags eliminados, Tier 4→3', goAction: 'Iniciar AUGMENT', noGoAction: 'Extender FOUNDATION' }, { id: 'gate2', afterWave: 'wave2', question: '¿Score ≥5.5 en target?', criteria: 'CV ≤90%, Transfer ≤30%', goAction: 'Iniciar ASSIST', noGoAction: 'Consolidar AUGMENT' }, { id: 'gate3', afterWave: 'wave3', question: '¿Score ≥7.5 en 2+ colas?', criteria: 'CV ≤75%, FCR ≥50%', goAction: 'Lanzar AUTOMATE', noGoAction: 'Expandir ASSIST' } ]; function RoadmapTimeline({ waves }: { waves: WaveData[] }) { const waveColors: Record = { wave1: { bg: 'bg-blue-100', border: 'border-blue-400', connector: 'bg-blue-400' }, wave2: { bg: 'bg-emerald-100', border: 'border-emerald-400', connector: 'bg-emerald-400' }, wave3: { bg: 'bg-purple-100', border: 'border-purple-400', connector: 'bg-purple-400' }, wave4: { bg: 'bg-amber-100', border: 'border-amber-400', connector: 'bg-amber-400' } }; return (

Roadmap de Transformación 2026-2027

Cada wave depende del éxito de la anterior. Los puntos de decisión permiten ajustar según resultados reales.

{/* Timeline horizontal con waves y gates */}
{/* Main connector line */}
{/* Waves and Gates flow */}
{waves.map((wave, idx) => { const colors = waveColors[wave.id] || waveColors.wave1; const gate = DECISION_GATES.find(g => g.afterWave === wave.id); const isLast = idx === waves.length - 1; return ( {/* Wave box */}
{/* Wave header */}
{wave.icon}

{wave.nombre}: {wave.titulo}

{wave.trimestre}

{/* Wave metrics */}
Setup: {formatCurrency(wave.inversionSetup)}
Ahorro: {formatCurrency(wave.ahorroAnual)}
{/* Conditional badge */} {wave.esCondicional && (
Condicional
)} {/* Risk indicator */}
{wave.riesgo === 'bajo' ? '● Bajo' : wave.riesgo === 'medio' ? '● Medio' : '● Alto'}
{/* Decision Gate (connector between waves) */} {gate && !isLast && ( {/* Connector arrow */}
{/* Line before gate */}
{/* Decision diamond */}
?
{/* Tooltip on hover */}

Go/No-Go

{gate.question}

Criterio: {gate.criteria}

✓ Go: {gate.goAction}
✗ No: {gate.noGoAction}
{/* Go/No-Go labels */}
Go
No
{/* Line after gate */}
)} {/* Simple connector for last wave */} {!gate && !isLast && (
)} ); })}
{/* Quarter timeline below */}
Q1 2026 Q2 2026 Q3 2026 Q4 2026 Q1 2027 Q2 2027
{/* Legend */}
Wave confirmada
Wave condicional
Punto de decisión Go/No-Go
● Bajo ● Medio ● Alto = Riesgo
); } // ========== COMPONENTE PRINCIPAL: ROADMAP TAB ========== export function RoadmapTab({ data }: RoadmapTabProps) { const { t } = useTranslation(); // Analizar datos de heatmap para determinar skills listos const heatmapData = data.heatmapData || []; // UMBRAL ÚNICO: Score >= 6 (automation_readiness >= 60) = Listo para Copilot const COPILOT_THRESHOLD = 60; // automation_readiness en escala 0-100 // Clasificar skills según umbrales coherentes const skillsCopilot = heatmapData.filter(s => (s.automation_readiness || 0) >= COPILOT_THRESHOLD); const skillsOptimizar = heatmapData.filter(s => { const score = s.automation_readiness || 0; return score >= 40 && score < COPILOT_THRESHOLD; }); const skillsHumano = heatmapData.filter(s => (s.automation_readiness || 0) < 40); const skillsListos = skillsCopilot.length; const totalSkills = heatmapData.length || 9; // Encontrar el skill con mejor score para Wave 2 (el mejor candidato) const sortedByScore = [...heatmapData].sort((a, b) => (b.automation_readiness || 0) - (a.automation_readiness || 0)); const bestSkill = sortedByScore[0]; const bestSkillScore = bestSkill ? (bestSkill.automation_readiness || 0) / 10 : 0; const bestSkillVolume = bestSkill?.volume || 0; // Skills que necesitan estandarización (CV AHT > 60% benchmark) const skillsNeedStandardization = heatmapData.filter(s => (s.variability?.cv_aht || 0) > 60); const skillsHighCV = heatmapData.filter(s => (s.variability?.cv_aht || 0) > 100); // Generar texto dinámico para Wave 2 const wave2Description = skillsListos > 0 ? `${bestSkill?.skill || 'Skill principal'} es el skill con mejor Score (${bestSkillScore.toFixed(1)}/10, categoría "Copilot"). Volumen ${bestSkillVolume.toLocaleString()}/año = mayor impacto económico.` : `Ningún skill alcanza actualmente Score ≥6. El mejor candidato es ${bestSkill?.skill || 'N/A'} con Score ${bestSkillScore.toFixed(1)}/10. Requiere optimización previa en Wave 1.`; const wave2Skills = skillsListos > 0 ? skillsCopilot.map(s => s.skill) : [bestSkill?.skill || 'Mejor candidato post-Wave 1']; // ═══════════════════════════════════════════════════════════════════════════ // v3.6: Calcular métricas dinámicas desde drilldownData si está disponible // ═══════════════════════════════════════════════════════════════════════════ const drilldownData = data.drilldownData || []; const allQueues = drilldownData.flatMap(skill => skill.originalQueues); // Contar colas por tier const tierCounts = { AUTOMATE: allQueues.filter(q => q.tier === 'AUTOMATE'), ASSIST: allQueues.filter(q => q.tier === 'ASSIST'), AUGMENT: allQueues.filter(q => q.tier === 'AUGMENT'), 'HUMAN-ONLY': allQueues.filter(q => q.tier === 'HUMAN-ONLY') }; // Volúmenes por tier const tierVolumes = { AUTOMATE: tierCounts.AUTOMATE.reduce((s, q) => s + q.volume, 0), ASSIST: tierCounts.ASSIST.reduce((s, q) => s + q.volume, 0), AUGMENT: tierCounts.AUGMENT.reduce((s, q) => s + q.volume, 0), 'HUMAN-ONLY': tierCounts['HUMAN-ONLY'].reduce((s, q) => s + q.volume, 0) }; const totalVolume = Object.values(tierVolumes).reduce((a, b) => a + b, 0) || 1; // Calcular ahorros potenciales por tier usando fórmula TCO // IMPORTANTE: El volumen es de 11 meses, se convierte a anual: (Vol/11) × 12 const { CPI_HUMANO, CPI_BOT, CPI_ASSIST, CPI_AUGMENT, RATE_AUTOMATE, RATE_ASSIST, RATE_AUGMENT } = CPI_CONFIG; const potentialSavings = { AUTOMATE: Math.round((tierVolumes.AUTOMATE / DATA_PERIOD_MONTHS) * 12 * RATE_AUTOMATE * (CPI_HUMANO - CPI_BOT)), ASSIST: Math.round((tierVolumes.ASSIST / DATA_PERIOD_MONTHS) * 12 * RATE_ASSIST * (CPI_HUMANO - CPI_ASSIST)), AUGMENT: Math.round((tierVolumes.AUGMENT / DATA_PERIOD_MONTHS) * 12 * RATE_AUGMENT * (CPI_HUMANO - CPI_AUGMENT)) }; // Colas que necesitan Wave 1 (Tier 3 + 4) const wave1Queues = [...tierCounts.AUGMENT, ...tierCounts['HUMAN-ONLY']]; const wave1Volume = tierVolumes.AUGMENT + tierVolumes['HUMAN-ONLY']; // ═══════════════════════════════════════════════════════════════════════════ // WAVES con nueva nomenclatura: FOUNDATION → AUGMENT → ASSIST → AUTOMATE // ═══════════════════════════════════════════════════════════════════════════ const waves: WaveData[] = [ { id: 'wave1', nombre: t('roadmap.waves.wave1Name'), titulo: t('roadmap.waves.wave1Title'), trimestre: t('roadmap.waves.wave1Quarter'), tipo: 'consulting', icon: , color: 'text-gray-600', bgColor: 'bg-gray-50', borderColor: 'border-gray-300', inversionSetup: 47000, costoRecurrenteAnual: 0, ahorroAnual: 0, // Wave habilitadora esCondicional: false, porQueNecesario: t('roadmap.porQueNecesarioTemplates.wave1', { count: tierCounts['HUMAN-ONLY'].length + tierCounts.AUGMENT.length, total: allQueues.length, pct: Math.round((wave1Volume / totalVolume) * 100) }), skills: wave1Queues.length > 0 ? [...new Set(drilldownData.filter(s => s.originalQueues.some(q => q.tier === 'HUMAN-ONLY' || q.tier === 'AUGMENT')).map(s => s.skill))].slice(0, 5) : skillsNeedStandardization.map(s => s.skill).slice(0, 5), iniciativas: [ { nombre: t('roadmap.initiatives.wave1Init1'), setup: 15000, recurrente: 0, kpi: t('roadmap.initiatives.wave1Init1Kpi') }, { nombre: t('roadmap.initiatives.wave1Init2'), setup: 20000, recurrente: 0, kpi: t('roadmap.initiatives.wave1Init2Kpi') }, { nombre: t('roadmap.initiatives.wave1Init3'), setup: 12000, recurrente: 0, kpi: t('roadmap.initiatives.wave1Init3Kpi') } ], criteriosExito: [ t('roadmap.successCriteriaTemplates.wave1Criterion1', { count: Math.max(3, Math.ceil(wave1Queues.length * 0.3)) }), t('roadmap.successCriteriaTemplates.wave1Criterion2'), t('roadmap.successCriteriaTemplates.wave1Criterion3'), t('roadmap.successCriteriaTemplates.wave1Criterion4', { count: Math.ceil(wave1Queues.length * 0.2) }) ], riesgo: 'bajo', riesgoDescripcion: t('roadmap.waves.wave1RiskDescription'), proveedor: t('roadmap.waves.wave1Provider') }, { id: 'wave2', nombre: t('roadmap.waves.wave2Name'), titulo: t('roadmap.waves.wave2Title'), trimestre: t('roadmap.waves.wave2Quarter'), tipo: 'beyond_consulting', icon: , color: 'text-amber-600', bgColor: 'bg-amber-50', borderColor: 'border-amber-200', inversionSetup: 35000, costoRecurrenteAnual: 40000, ahorroAnual: potentialSavings.AUGMENT, // 15% efficiency - calculado desde datos reales esCondicional: true, condicion: t('roadmap.waves.wave2Condition'), porQueNecesario: t('roadmap.porQueNecesarioTemplates.wave2', { count: tierCounts.AUGMENT.length, volume: tierVolumes.AUGMENT.toLocaleString() }), skills: tierCounts.AUGMENT.length > 0 ? [...new Set(drilldownData.filter(s => s.originalQueues.some(q => q.tier === 'AUGMENT')).map(s => s.skill))].slice(0, 4) : [t('roadmap.fallbackSkills.wave1')], iniciativas: [ { nombre: t('roadmap.initiatives.wave2Init1'), setup: 20000, recurrente: 2000, kpi: t('roadmap.initiatives.wave2Init1Kpi') }, { nombre: t('roadmap.initiatives.wave2Init2'), setup: 15000, recurrente: 1500, kpi: t('roadmap.initiatives.wave2Init2Kpi') } ], criteriosExito: [ t('roadmap.successCriteriaTemplates.wave2Criterion1'), t('roadmap.successCriteriaTemplates.wave2Criterion2'), t('roadmap.successCriteriaTemplates.wave2Criterion3'), t('roadmap.successCriteriaTemplates.wave2Criterion4', { count: Math.ceil(tierCounts.AUGMENT.length * 0.5) }) ], riesgo: 'bajo', riesgoDescripcion: t('roadmap.waves.wave2RiskDescription'), proveedor: t('roadmap.waves.wave2Provider') }, { id: 'wave3', nombre: t('roadmap.waves.wave3Name'), titulo: t('roadmap.waves.wave3Title'), trimestre: t('roadmap.waves.wave3Quarter'), tipo: 'beyond', icon: , color: 'text-blue-600', bgColor: 'bg-blue-50', borderColor: 'border-blue-200', inversionSetup: 70000, costoRecurrenteAnual: 78000, ahorroAnual: potentialSavings.ASSIST, // 30% efficiency - calculado desde datos reales esCondicional: true, condicion: t('roadmap.waves.wave3Condition'), porQueNecesario: t('roadmap.porQueNecesarioTemplates.wave3', { count: tierCounts.ASSIST.length, volume: tierVolumes.ASSIST.toLocaleString() }), skills: tierCounts.ASSIST.length > 0 ? [...new Set(drilldownData.filter(s => s.originalQueues.some(q => q.tier === 'ASSIST')).map(s => s.skill))].slice(0, 4) : [t('roadmap.fallbackSkills.wave2')], iniciativas: [ { nombre: t('roadmap.initiatives.wave3Init1'), setup: 45000, recurrente: 4500, kpi: t('roadmap.initiatives.wave3Init1Kpi') }, { nombre: t('roadmap.initiatives.wave3Init2'), setup: 25000, recurrente: 3000, kpi: t('roadmap.initiatives.wave3Init2Kpi') } ], criteriosExito: [ t('roadmap.successCriteriaTemplates.wave3Criterion1'), t('roadmap.successCriteriaTemplates.wave3Criterion2'), t('roadmap.successCriteriaTemplates.wave3Criterion3'), t('roadmap.successCriteriaTemplates.wave3Criterion4'), t('roadmap.successCriteriaTemplates.wave3Criterion5', { count: Math.ceil(tierCounts.ASSIST.length * 0.4) }) ], riesgo: 'medio', riesgoDescripcion: t('roadmap.waves.wave3RiskDescription'), proveedor: t('roadmap.waves.wave3Provider') }, { id: 'wave4', nombre: t('roadmap.waves.wave4Name'), titulo: t('roadmap.waves.wave4Title'), trimestre: t('roadmap.waves.wave4Quarter'), tipo: 'beyond', icon: , color: 'text-emerald-600', bgColor: 'bg-emerald-50', borderColor: 'border-emerald-200', inversionSetup: 85000, costoRecurrenteAnual: 108000, ahorroAnual: potentialSavings.AUTOMATE, // 70% containment - calculado desde datos reales esCondicional: true, condicion: t('roadmap.waves.wave4Condition'), porQueNecesario: t('roadmap.porQueNecesarioTemplates.wave4', { count: tierCounts.AUTOMATE.length, volume: tierVolumes.AUTOMATE.toLocaleString() }), skills: tierCounts.AUTOMATE.length > 0 ? [...new Set(drilldownData.filter(s => s.originalQueues.some(q => q.tier === 'AUTOMATE')).map(s => s.skill))].slice(0, 4) : [t('roadmap.fallbackSkills.wave3')], iniciativas: [ { nombre: t('roadmap.initiatives.wave4Init1'), setup: 55000, recurrente: 6000, kpi: t('roadmap.initiatives.wave4Init1Kpi') }, { nombre: t('roadmap.initiatives.wave4Init2'), setup: 30000, recurrente: 3000, kpi: t('roadmap.initiatives.wave4Init2Kpi') } ], criteriosExito: [ t('roadmap.successCriteriaTemplates.wave4Criterion1'), t('roadmap.successCriteriaTemplates.wave4Criterion2'), t('roadmap.successCriteriaTemplates.wave4Criterion3'), t('roadmap.successCriteriaTemplates.wave4Criterion4') ], riesgo: 'alto', riesgoDescripcion: t('roadmap.waves.wave4RiskDescription'), proveedor: t('roadmap.waves.wave4Provider') } ]; // ═══════════════════════════════════════════════════════════════════════════ // v3.7: Escenarios con cálculos TCO y ROI corregidos // Fórmulas: // - AUGMENT: Vol × 12 × 15% × (€2.33 - €2.00) = Vol × 12 × 0.15 × €0.33 // - ASSIST: Vol × 12 × 30% × (€2.33 - €1.50) = Vol × 12 × 0.30 × €0.83 // - AUTOMATE: Vol × 12 × 70% × (€2.33 - €0.15) = Vol × 12 × 0.70 × €2.18 // // ROI 3 años = ((Ahorro×3) - (Inversión + Recurrente×3)) / (Inversión + Recurrente×3) × 100 // ═══════════════════════════════════════════════════════════════════════════ // Calcular valores dinámicos para escenarios const wave1Setup = 47000; const wave2Setup = 35000; const wave2Rec = 40000; const wave3Setup = 70000; const wave3Rec = 78000; const wave4Setup = 85000; const wave4Rec = 108000; // Usar potentialSavings (ya corregidos con factor 12/11) const wave2Savings = potentialSavings.AUGMENT; const wave3Savings = potentialSavings.ASSIST; const wave4Savings = potentialSavings.AUTOMATE; // Escenario 1: Conservador (Wave 1-2: FOUNDATION + AUGMENT) const consInversion = wave1Setup + wave2Setup; const consRec = wave2Rec; const consSavings = wave2Savings; const consMargen = consSavings - consRec; const consSavingsAjustado = calculateRiskAdjustedSavings(wave2Savings, 0, 0, ['wave2']); // Escenario 2: Moderado (Wave 1-3: + ASSIST) const modInversion = consInversion + wave3Setup; const modRec = consRec + wave3Rec; const modSavings = consSavings + wave3Savings; const modMargen = modSavings - modRec; const modSavingsAjustado = calculateRiskAdjustedSavings(wave2Savings, wave3Savings, 0, ['wave2', 'wave3']); // Escenario 3: Agresivo (Wave 1-4: + AUTOMATE) const agrInversion = modInversion + wave4Setup; const agrRec = modRec + wave4Rec; const agrSavings = modSavings + wave4Savings; const agrMargen = agrSavings - agrRec; const agrSavingsAjustado = calculateRiskAdjustedSavings(wave2Savings, wave3Savings, wave4Savings, ['wave2', 'wave3', 'wave4']); // v3.8: Calcular si cada escenario es habilitador y qué potencial desbloquea const consEsHabilitador = isEnablingScenario(consMargen, consInversion, ['wave1', 'wave2']); const modEsHabilitador = isEnablingScenario(modMargen, modInversion, ['wave1', 'wave2', 'wave3']); const agrEsHabilitador = isEnablingScenario(agrMargen, agrInversion, ['wave1', 'wave2', 'wave3', 'wave4']); // Potencial que habilita cada escenario (ahorro de waves que desbloquea) const consPotencialHabilitado = wave3Savings + wave4Savings; // Conservador habilita Wave 3-4 const modPotencialHabilitado = wave4Savings; // Moderado habilita Wave 4 const agrPotencialHabilitado = 0; // Agresivo ya incluye todo // v3.9: Calcular payback completo para cada escenario const consPaybackInfo = calcularPaybackCompleto( consInversion, consMargen, consSavings, ['wave1', 'wave2'], consEsHabilitador, false, t ); const modPaybackInfo = calcularPaybackCompleto( modInversion, modMargen, modSavings, ['wave1', 'wave2', 'wave3'], modEsHabilitador, false, t ); // Agresivo incluye Wave 4 (Quick Wins potenciales si hay AUTOMATE queues) const agrIncluyeQuickWin = tierCounts.AUTOMATE.length >= 3; const agrPaybackInfo = calcularPaybackCompleto( agrInversion, agrMargen, agrSavings, ['wave1', 'wave2', 'wave3', 'wave4'], agrEsHabilitador, agrIncluyeQuickWin, t ); const escenarios: EscenarioData[] = [ { id: 'conservador', nombre: t('roadmap.scenarios.conservativeName'), descripcion: t('roadmap.scenarios.conservativeDesc'), waves: ['wave1', 'wave2'], inversionTotal: consInversion, costoRecurrenteAnual: consRec, ahorroAnual: consSavings, ahorroAjustado: consSavingsAjustado, margenAnual: consMargen, paybackMeses: calculatePayback(consInversion, consMargen), paybackInfo: consPaybackInfo, roi3Anos: calculateROI3Years(consInversion, consRec, consSavings), roi3AnosAjustado: calculateROI3Years(consInversion, consRec, consSavingsAjustado), riesgo: 'bajo', recomendacion: consEsHabilitador ? `✅ Recomendado como HABILITADOR. Desbloquea ${formatCurrency(consPotencialHabilitado)}/año en Wave 3-4. Objetivo: mover ${Math.ceil(wave1Queues.length * 0.3)} colas de Tier 4→3.` : `✅ Recomendado. Validar modelo con riesgo bajo. Objetivo: mover ${Math.ceil(wave1Queues.length * 0.3)} colas de Tier 4→3.`, esRecomendado: true, esRentable: consMargen > 0, esHabilitador: consEsHabilitador, potencialHabilitado: consPotencialHabilitado, wavesHabilitadas: ['Wave 3', 'Wave 4'], incluyeQuickWin: false }, { id: 'moderado', nombre: t('roadmap.scenarios.moderateName'), descripcion: t('roadmap.scenarios.moderateDesc'), waves: ['wave1', 'wave2', 'wave3'], inversionTotal: modInversion, costoRecurrenteAnual: modRec, ahorroAnual: modSavings, ahorroAjustado: modSavingsAjustado, margenAnual: modMargen, paybackMeses: calculatePayback(modInversion, modMargen), paybackInfo: modPaybackInfo, roi3Anos: calculateROI3Years(modInversion, modRec, modSavings), roi3AnosAjustado: calculateROI3Years(modInversion, modRec, modSavingsAjustado), riesgo: 'medio', recomendacion: modEsHabilitador ? `Habilitador parcial. Desbloquea ${formatCurrency(modPotencialHabilitado)}/año en Wave 4. Decidir Go/No-Go en Q3 2026.` : `Decidir Go/No-Go en Q3 2026 basado en resultados Wave 1-2. Requiere Score ≥5.5 en colas target.`, esRecomendado: false, esRentable: modMargen > 0, esHabilitador: modEsHabilitador, potencialHabilitado: modPotencialHabilitado, wavesHabilitadas: ['Wave 4'], incluyeQuickWin: false }, { id: 'agresivo', nombre: t('roadmap.scenarios.aggressiveName'), descripcion: t('roadmap.scenarios.aggressiveDesc'), waves: ['wave1', 'wave2', 'wave3', 'wave4'], inversionTotal: agrInversion, costoRecurrenteAnual: agrRec, ahorroAnual: agrSavings, ahorroAjustado: agrSavingsAjustado, margenAnual: agrMargen, paybackMeses: calculatePayback(agrInversion, agrMargen), paybackInfo: agrPaybackInfo, roi3Anos: calculateROI3Years(agrInversion, agrRec, agrSavings), roi3AnosAjustado: calculateROI3Years(agrInversion, agrRec, agrSavingsAjustado), riesgo: 'alto', recomendacion: agrMargen > 0 ? `⚠️ Aspiracional. Solo si Waves 1-3 exitosas y hay colas con Score ≥7.5. Decisión en Q1 2027.` : `❌ No rentable con el volumen actual. Requiere escala significativamente mayor.`, esRecomendado: false, esRentable: agrMargen > 0, esHabilitador: false, // Agresivo incluye todo, no es habilitador potencialHabilitado: 0, wavesHabilitadas: [], incluyeQuickWin: agrIncluyeQuickWin } ]; const escenarioRecomendado = escenarios.find(e => e.esRecomendado)!; // ═══════════════════════════════════════════════════════════════════════════ // v3.11: Cálculo de métricas para footer (considera Enfoque Dual si aplica) // ═══════════════════════════════════════════════════════════════════════════ // Lógica para determinar tipo de recomendación (misma que en sección DUAL) const automateQueuesWithVolume = tierCounts.AUTOMATE.filter(q => q.volume >= 50); const automateVolumeSignificant = tierVolumes.AUTOMATE >= 10000; const hasQuickWinsGlobal = automateQueuesWithVolume.length >= 3 && automateVolumeSignificant; const assistPctGlobal = totalVolume > 0 ? (tierVolumes.ASSIST / totalVolume) * 100 : 0; const hasAssistOpportunityGlobal = tierCounts.ASSIST.length >= 3 && assistPctGlobal >= 10; type RecommendationType = 'DUAL' | 'FOUNDATION' | 'STANDARDIZATION'; const recTypeGlobal: RecommendationType = hasQuickWinsGlobal ? 'DUAL' : hasAssistOpportunityGlobal ? 'FOUNDATION' : 'STANDARDIZATION'; // Métricas de Quick Win piloto (para combinar si es DUAL) const pilotQueuesGlobal = tierCounts.AUTOMATE .sort((a, b) => b.volume - a.volume) .slice(0, 3); const pilotVolumeGlobal = pilotQueuesGlobal.reduce((s, q) => s + q.volume, 0); const pilotPctOfAutomateGlobal = tierVolumes.AUTOMATE > 0 ? pilotVolumeGlobal / tierVolumes.AUTOMATE : 0; const FACTOR_RIESGO_GLOBAL = 0.50; const pilotSetupGlobal = Math.round(wave4Setup * 0.35); const pilotRecurrenteGlobal = Math.round(wave4Rec * 0.35); const pilotAhorroBrutoGlobal = Math.round(potentialSavings.AUTOMATE * pilotPctOfAutomateGlobal); const pilotAhorroAjustadoGlobal = Math.round(pilotAhorroBrutoGlobal * FACTOR_RIESGO_GLOBAL); // Métricas combinadas para footer (Quick Win + Foundation si es DUAL) const footerMetrics = recTypeGlobal === 'DUAL' ? { tipo: 'dual' as const, inversion: pilotSetupGlobal + escenarioRecomendado.inversionTotal, recurrente: pilotRecurrenteGlobal + escenarioRecomendado.costoRecurrenteAnual, ahorro: pilotAhorroAjustadoGlobal + escenarioRecomendado.ahorroAnual, // ROI combinado a 3 años roi3Anos: (() => { const invTotal = pilotSetupGlobal + escenarioRecomendado.inversionTotal; const recTotal = pilotRecurrenteGlobal + escenarioRecomendado.costoRecurrenteAnual; const ahorroTotal = pilotAhorroAjustadoGlobal + escenarioRecomendado.ahorroAnual; const costoTotal3a = invTotal + (recTotal * 3); const beneficio3a = ahorroTotal * 3; return costoTotal3a > 0 ? Math.round(((beneficio3a - costoTotal3a) / costoTotal3a) * 100) : 0; })(), pilotSetup: pilotSetupGlobal, pilotRecurrente: pilotRecurrenteGlobal, pilotAhorro: pilotAhorroAjustadoGlobal, foundationInversion: escenarioRecomendado.inversionTotal, foundationAhorro: escenarioRecomendado.ahorroAnual } : { tipo: 'escenario' as const, inversion: escenarioRecomendado.inversionTotal, recurrente: escenarioRecomendado.costoRecurrenteAnual, ahorro: escenarioRecomendado.ahorroAnual, roi3Anos: escenarioRecomendado.roi3Anos, pilotSetup: 0, pilotRecurrente: 0, pilotAhorro: 0, foundationInversion: 0, foundationAhorro: 0 }; // ═══════════════════════════════════════════════════════════════════════════ // v3.7: Criterios de Entrada/Salida y Colas Prioritarias por Wave // ═══════════════════════════════════════════════════════════════════════════ // Wave 1: FOUNDATION - Colas Tier 3-4 que necesitan estandarización const wave1EntryCriteria: WaveEntryCriteria = { tierFrom: ['HUMAN-ONLY (4)', 'AUGMENT (3)'], scoreRange: '<5.5', requiredMetrics: ['CV >75% o Transfer >20%', 'Red Flags activos', 'Procesos no documentados'] }; const wave1ExitCriteria: WaveExitCriteria = { tierTo: 'AUGMENT (3) mínimo', scoreTarget: '≥3.5', kpiTargets: ['CV ≤75%', 'Transfer ≤20%', 'Red flags eliminados'] }; const wave1PriorityQueues: PriorityQueue[] = [...tierCounts['HUMAN-ONLY'], ...tierCounts.AUGMENT] .sort((a, b) => b.volume - a.volume) .slice(0, 5) .map(q => ({ name: q.original_queue_id, volume: q.volume, currentScore: q.agenticScore, currentTier: q.tier, potentialSavings: calculateTCOSavings(q.volume, 'AUGMENT'), // Potencial si llega a Tier 3 redFlags: detectRedFlags(q) // v3.7: Detectar red flags })); // Wave 2: AUGMENT - Colas Tier 3 con potencial de mejora const wave2EntryCriteria: WaveEntryCriteria = { tierFrom: ['AUGMENT (3)'], scoreRange: '3.5-5.5', requiredMetrics: ['CV ≤75%', 'Transfer ≤20%', 'Sin Red Flags'] }; const wave2ExitCriteria: WaveExitCriteria = { tierTo: 'ASSIST (2)', scoreTarget: '≥5.5', kpiTargets: ['CV ≤90%', 'Transfer ≤30%', 'AHT -15%'] }; const wave2PriorityQueues: PriorityQueue[] = tierCounts.AUGMENT .sort((a, b) => b.volume - a.volume) .slice(0, 5) .map(q => ({ name: q.original_queue_id, volume: q.volume, currentScore: q.agenticScore, currentTier: q.tier, potentialSavings: calculateTCOSavings(q.volume, 'ASSIST'), redFlags: detectRedFlags(q) // v3.7: Detectar red flags })); // Wave 3: ASSIST - Colas Tier 2 listas para copilot const wave3EntryCriteria: WaveEntryCriteria = { tierFrom: ['ASSIST (2)'], scoreRange: '5.5-7.5', requiredMetrics: ['CV ≤90%', 'Transfer ≤30%', 'AHT estable'] }; const wave3ExitCriteria: WaveExitCriteria = { tierTo: 'AUTOMATE (1)', scoreTarget: '≥7.5', kpiTargets: ['CV ≤75%', 'Transfer ≤20%', 'FCR ≥50%', 'AHT -30%'] }; const wave3PriorityQueues: PriorityQueue[] = tierCounts.ASSIST .sort((a, b) => b.volume - a.volume) .slice(0, 5) .map(q => ({ name: q.original_queue_id, volume: q.volume, currentScore: q.agenticScore, currentTier: q.tier, potentialSavings: calculateTCOSavings(q.volume, 'AUTOMATE'), redFlags: detectRedFlags(q) // v3.7: Detectar red flags })); // Wave 4: AUTOMATE - Colas Tier 1 listas para automatización completa const wave4EntryCriteria: WaveEntryCriteria = { tierFrom: ['AUTOMATE (1)'], scoreRange: '≥7.5', requiredMetrics: ['CV ≤75%', 'Transfer ≤20%', 'FCR ≥50%', 'Sin Red Flags'] }; const wave4ExitCriteria: WaveExitCriteria = { tierTo: 'AUTOMATIZADO', scoreTarget: 'Contención ≥70%', kpiTargets: ['Bot resolution ≥70%', 'CSAT ≥4/5', 'Escalado <30%'] }; const wave4PriorityQueues: PriorityQueue[] = tierCounts.AUTOMATE .sort((a, b) => b.volume - a.volume) .slice(0, 5) .map(q => ({ name: q.original_queue_id, volume: q.volume, currentScore: q.agenticScore, currentTier: q.tier, potentialSavings: calculateTCOSavings(q.volume, 'AUTOMATE'), redFlags: detectRedFlags(q) // v3.7: Detectar red flags })); // Map de criterios y colas por wave const waveConfigs: Record = { wave1: { entry: wave1EntryCriteria, exit: wave1ExitCriteria, queues: wave1PriorityQueues }, wave2: { entry: wave2EntryCriteria, exit: wave2ExitCriteria, queues: wave2PriorityQueues }, wave3: { entry: wave3EntryCriteria, exit: wave3ExitCriteria, queues: wave3PriorityQueues }, wave4: { entry: wave4EntryCriteria, exit: wave4ExitCriteria, queues: wave4PriorityQueues } }; // ═══════════════════════════════════════════════════════════════════════════ // Calcular totales para Resumen Ejecutivo // ═══════════════════════════════════════════════════════════════════════════ const totalSavingsPotential = potentialSavings.AUTOMATE + potentialSavings.ASSIST + potentialSavings.AUGMENT; const totalQueues = allQueues.length || heatmapData.length || 1; // Determinar recomendación específica según estado actual const getSpecificRecommendation = (): { action: string; rationale: string; nextStep: string } => { const automateCount = tierCounts.AUTOMATE.length; const assistCount = tierCounts.ASSIST.length; const humanOnlyCount = tierCounts['HUMAN-ONLY'].length; const totalHighTier = automateCount + assistCount; const pctHighTier = totalQueues > 0 ? (totalHighTier / totalQueues) * 100 : 0; if (automateCount >= 3) { return { action: 'Lanzar Wave 4 (AUTOMATE) en piloto', rationale: `${automateCount} colas ya tienen Score ≥7.5 con volumen de ${tierVolumes.AUTOMATE.toLocaleString()} int/mes.`, nextStep: `Iniciar piloto de automatización en las 2-3 colas de mayor volumen con ahorro potencial de ${formatCurrency(potentialSavings.AUTOMATE)}/año.` }; } else if (assistCount >= 5 || pctHighTier >= 30) { return { action: 'Iniciar Wave 3 (ASSIST) con Copilot', rationale: `${assistCount} colas tienen Score 5.5-7.5, representando ${Math.round((tierVolumes.ASSIST / totalVolume) * 100)}% del volumen.`, nextStep: `Desplegar Copilot IA en colas Tier 2 para elevar score a ≥7.5 y habilitar Wave 4. Inversión: ${formatCurrency(wave3Setup)}.` }; } else if (humanOnlyCount > totalQueues * 0.5) { return { action: 'Priorizar Wave 1 (FOUNDATION)', rationale: `${humanOnlyCount} colas (${Math.round((humanOnlyCount / totalQueues) * 100)}%) tienen Red Flags que impiden automatización.`, nextStep: `Estandarizar procesos antes de invertir en IA. La automatización sin fundamentos sólidos fracasa en 80%+ de casos.` }; } else { return { action: 'Ejecutar Wave 1-2 secuencialmente', rationale: `Operación mixta: ${automateCount} colas Tier 1, ${assistCount} Tier 2, ${tierCounts.AUGMENT.length} Tier 3, ${humanOnlyCount} Tier 4.`, nextStep: `Comenzar con FOUNDATION para eliminar red flags, seguido de AUGMENT para elevar scores. Inversión inicial: ${formatCurrency(wave1Setup + wave2Setup)}.` }; } }; const recommendation = getSpecificRecommendation(); // v3.16: Estados para secciones colapsables - detalle expandido por defecto const [waveDetailExpanded, setWaveDetailExpanded] = React.useState(true); const [showAllWaves, setShowAllWaves] = React.useState(true); return (
{/* ═══════════════════════════════════════════════════════════════════════════ v3.17: BLOQUE 1 - RESUMEN EJECUTIVO (primero) ═══════════════════════════════════════════════════════════════════════════ */} {/* Header */}

{t('roadmap.classificationByAutomationTier')}

{t('roadmap.queuesClassifiedDescription', { count: totalQueues, volume: totalVolume.toLocaleString() })}

{/* Distribución por Tier */}
{/* Tier 1: AUTOMATE */}

{t('roadmap.tier1')}

AUTOMATE

{tierCounts.AUTOMATE.length}

{tierVolumes.AUTOMATE.toLocaleString()} {t('roadmap.intPerMonth')}

{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes.AUTOMATE / totalVolume) * 100) })}

{formatCurrency(potentialSavings.AUTOMATE)}{t('roadmap.perYear')}

{/* Tier 2: ASSIST */}

{t('roadmap.tier2')}

ASSIST

{tierCounts.ASSIST.length}

{tierVolumes.ASSIST.toLocaleString()} {t('roadmap.intPerMonth')}

{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes.ASSIST / totalVolume) * 100) })}

{formatCurrency(potentialSavings.ASSIST)}{t('roadmap.perYear')}

{/* Tier 3: AUGMENT */}

{t('roadmap.tier3')}

AUGMENT

{tierCounts.AUGMENT.length}

{tierVolumes.AUGMENT.toLocaleString()} {t('roadmap.intPerMonth')}

{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes.AUGMENT / totalVolume) * 100) })}

{formatCurrency(potentialSavings.AUGMENT)}{t('roadmap.perYear')}

{/* Tier 4: HUMAN-ONLY */}

{t('roadmap.tier4')}

HUMAN-ONLY

{tierCounts['HUMAN-ONLY'].length}

{tierVolumes['HUMAN-ONLY'].toLocaleString()} {t('roadmap.intPerMonth')}

{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes['HUMAN-ONLY'] / totalVolume) * 100) })}

{t('roadmap.noSavingsRedFlags')}

{/* Barra de distribución visual */}

{t('roadmap.volumeDistributionByTier')}

{tierVolumes.AUTOMATE > 0 && (
{(tierVolumes.AUTOMATE / totalVolume) >= 0.1 && ( {Math.round((tierVolumes.AUTOMATE / totalVolume) * 100)}% )}
)} {tierVolumes.ASSIST > 0 && (
{(tierVolumes.ASSIST / totalVolume) >= 0.1 && ( {Math.round((tierVolumes.ASSIST / totalVolume) * 100)}% )}
)} {tierVolumes.AUGMENT > 0 && (
{(tierVolumes.AUGMENT / totalVolume) >= 0.1 && ( {Math.round((tierVolumes.AUGMENT / totalVolume) * 100)}% )}
)} {tierVolumes['HUMAN-ONLY'] > 0 && (
{(tierVolumes['HUMAN-ONLY'] / totalVolume) >= 0.1 && ( {Math.round((tierVolumes['HUMAN-ONLY'] / totalVolume) * 100)}% )}
)}
{/* ═══════════════════════════════════════════════════════════════════════ */} {/* RECOMENDACIÓN ESTRATÉGICA - Unifica mensajes con lógica condicional */} {/* ═══════════════════════════════════════════════════════════════════════ */} {(() => { // Lógica de decisión const automateQueuesWithVolume = tierCounts.AUTOMATE.filter(q => q.volume >= 50); const automateVolumeSignificant = tierVolumes.AUTOMATE >= 10000; // ≥10K int/mes const hasQuickWins = automateQueuesWithVolume.length >= 3 && automateVolumeSignificant; const assistPct = totalVolume > 0 ? (tierVolumes.ASSIST / totalVolume) * 100 : 0; const hasAssistOpportunity = tierCounts.ASSIST.length >= 3 && assistPct >= 10; const humanOnlyPct = totalVolume > 0 ? (tierVolumes['HUMAN-ONLY'] / totalVolume) * 100 : 0; const augmentPct = totalVolume > 0 ? (tierVolumes.AUGMENT / totalVolume) * 100 : 0; const needsStandardization = (humanOnlyPct + augmentPct) >= 60; // Determinar tipo de recomendación type RecommendationType = 'DUAL' | 'FOUNDATION' | 'STANDARDIZATION'; let recType: RecommendationType; if (hasQuickWins) { recType = 'DUAL'; // Quick Win paralelo + Foundation secuencial } else if (hasAssistOpportunity) { recType = 'FOUNDATION'; // Wave 1-2 para habilitar } else { recType = 'STANDARDIZATION'; // Solo Wave 1 } // Calcular métricas para Quick Win piloto const pilotQueues = tierCounts.AUTOMATE .sort((a, b) => b.volume - a.volume) .slice(0, 3); const pilotVolume = pilotQueues.reduce((s, q) => s + q.volume, 0); const pilotPctOfAutomate = tierVolumes.AUTOMATE > 0 ? pilotVolume / tierVolumes.AUTOMATE : 0; // v3.11: Cálculo completo de ROI piloto (setup + recurrente + factor riesgo) const FACTOR_RIESGO_PILOTO = 0.50; // 50% éxito conservador para piloto const pilotSetup = Math.round(wave4Setup * 0.35); // 35% del setup Wave 4 const pilotRecurrente = Math.round(wave4Rec * 0.35); // 35% del recurrente anual const pilotInversionTotal = pilotSetup + pilotRecurrente; // Coste total Year 1 const pilotAhorroBruto = Math.round(potentialSavings.AUTOMATE * pilotPctOfAutomate); const pilotAhorroAjustado = Math.round(pilotAhorroBruto * FACTOR_RIESGO_PILOTO); const pilotROICalculado = pilotInversionTotal > 0 ? Math.round(((pilotAhorroAjustado - pilotInversionTotal) / pilotInversionTotal) * 100) : 0; // Formatear ROI para credibilidad ejecutiva (cap visual) const formatPilotROI = (roi: number): { display: string; tooltip: string; showCap: boolean } => { if (roi > 1000) { return { display: '>1000%', tooltip: `ROI calculado: ${roi.toLocaleString()}%`, showCap: true }; } if (roi > 500) { return { display: '>500%', tooltip: `ROI calculado: ${roi}%`, showCap: true }; } if (roi > 300) { return { display: `${roi}%`, tooltip: 'ROI alto - validar con piloto', showCap: false }; } return { display: `${roi}%`, tooltip: '', showCap: false }; }; const pilotROIDisplay = formatPilotROI(pilotROICalculado); // Skills afectados const quickWinSkills = [...new Set( drilldownData .filter(s => s.originalQueues.some(q => q.tier === 'AUTOMATE' && pilotQueues.some(p => p.original_queue_id === q.original_queue_id))) .map(s => s.skill) )].slice(0, 3); const wave1Skills = [...new Set( drilldownData .filter(s => s.originalQueues.some(q => q.tier === 'HUMAN-ONLY' || q.tier === 'AUGMENT')) .map(s => s.skill) )].slice(0, 3); // Configuración simplificada por tipo const typeConfig = { DUAL: { label: t('roadmap.dualStrategyLabel'), sublabel: t('roadmap.dualStrategySublabel') }, FOUNDATION: { label: t('roadmap.foundationFirstLabel'), sublabel: t('roadmap.foundationFirstSublabel') }, STANDARDIZATION: { label: t('roadmap.standardizationLabel'), sublabel: t('roadmap.standardizationSublabel') } }; const config = typeConfig[recType]; return (
{/* Header */}

{config.label}

{config.sublabel}

{/* ENFOQUE DUAL: Párrafo explicativo */} {recType === 'DUAL' && (

La Estrategia Dual consiste en ejecutar dos líneas de trabajo en paralelo: Quick Win automatiza inmediatamente las {pilotQueues.length} colas ya preparadas (Tier AUTOMATE, {Math.round(totalVolume > 0 ? (tierVolumes.AUTOMATE / totalVolume) * 100 : 0)}% del volumen), generando retorno desde el primer mes; mientras que Foundation prepara el {Math.round(assistPct + augmentPct)}% restante del volumen (Tiers ASSIST y AUGMENT) estandarizando procesos y reduciendo variabilidad para habilitar automatización futura. Este enfoque maximiza el time-to-value: Quick Win financia la transformación y genera confianza organizacional, mientras Foundation amplía progresivamente el alcance de la automatización.

)} {/* FOUNDATION PRIMERO */} {recType === 'FOUNDATION' && ( <> {/* Explicación */}

{t('roadmap.whatIsFoundation')}

{t('roadmap.foundationExplanation')}

{t('roadmap.assistQueuesCanElevate', { count: tierCounts.ASSIST.length, pct: Math.round(assistPct) })}

{t('roadmap.investment')}

{formatCurrency(wave1Setup + wave2Setup)}

{t('roadmap.timeline')}

6-9 {t('roadmap.months', { count: 6 }).toLowerCase()}

{t('roadmap.enabledSavings')}

{formatCurrency(potentialSavings.ASSIST)}{t('roadmap.perYear')}

{t('roadmap.criteriaForAutomation')} {t('roadmap.criteriaForAutomationValues')}
)} {/* ESTANDARIZACIÓN URGENTE */} {recType === 'STANDARDIZATION' && ( <> {/* Explicación */}

{t('roadmap.whyStandardizationFirst')}

{t('roadmap.standardizationExplanation')}

{t('roadmap.volumeWithRedFlags', { pct: Math.round(humanOnlyPct + augmentPct) })}

{t('roadmap.investmentWave1')}

{formatCurrency(wave1Setup)}

{t('roadmap.timeline')}

3-4 {t('roadmap.months', { count: 3 }).toLowerCase()}

{t('roadmap.directSavings')}

{t('roadmap.enablingNoDirectSavings')}

{t('roadmap.objective')} {t('roadmap.objectiveReduceRedFlags', { count: Math.min(10, tierCounts['HUMAN-ONLY'].length + tierCounts.AUGMENT.length) })}
)} {/* Siguiente paso */}

{t('roadmap.nextRecommendedStep')}

{recType === 'DUAL' && ( t('roadmap.nextStepDual', { count: pilotQueues.length }) )} {recType === 'FOUNDATION' && ( t('roadmap.nextStepFoundation', { count: Math.min(10, tierCounts['HUMAN-ONLY'].length) }) )} {recType === 'STANDARDIZATION' && ( t('roadmap.nextStepStandardization') )}

); })()}
{/* ═══════════════════════════════════════════════════════════════════════════ v3.17: BLOQUE 2 - TIMELINE VISUAL DEL ROADMAP ═══════════════════════════════════════════════════════════════════════════ */} {/* ═══════════════════════════════════════════════════════════════════════════ v3.17: BLOQUE 3 - DETALLE POR WAVE (expandido por defecto) ═══════════════════════════════════════════════════════════════════════════ */} {/* Header colapsable */} {/* Contenido expandible */} {waveDetailExpanded && (
{/* Botón para expandir/colapsar todas las waves */}
{waves.map((wave, idx) => { const config = waveConfigs[wave.id]; return ( ); })}
)}
{/* ═══════════════════════════════════════════════════════════════════════════ OPORTUNIDADES PRIORIZADAS - Nueva visualización clara y accionable ═══════════════════════════════════════════════════════════════════════════ */} {data.opportunities && data.opportunities.length > 0 && ( )}
); } export default RoadmapTab;