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 { 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'; 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 ): 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: 'Ver Wave 3-4', 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: 'No recuperable', 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); }; /** * v3.9: Formatea el resultado del payback */ const formatearPaybackResult = ( meses: number, mesesImpl: number, mesesRec: number, margenMensual: number, inversion: number ): 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: 'Inmediato', 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 }; }; const formatCurrency = (value: number): string => { if (value >= 1000000) return `€${(value / 1000000).toFixed(1)}M`; if (value >= 1000) return `€${Math.round(value / 1000)}K`; return `€${value.toLocaleString()}`; }; // ========== 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 const TIER_COLORS: Record = { 'AUTOMATE': { fill: '#059669', stroke: '#047857', label: 'Automatizar' }, 'ASSIST': { fill: '#3B82F6', stroke: '#2563EB', label: 'Asistir' }, 'AUGMENT': { fill: '#F59E0B', stroke: '#D97706', label: 'Optimizar' }, 'HUMAN-ONLY': { fill: '#EF4444', stroke: '#DC2626', label: 'Humano' } }; // 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 }; // v3.6: Calcular ahorro TCO realista con fórmula explícita 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; switch (tier) { case 'AUTOMATE': // Ahorro = Vol × 12 × 70% × (CPI_humano - CPI_bot) return Math.round(volume * 12 * RATE_AUTOMATE * (CPI_HUMANO - CPI_BOT)); case 'ASSIST': // Ahorro = Vol × 12 × 30% × (CPI_humano - CPI_assist) return Math.round(volume * 12 * RATE_ASSIST * (CPI_HUMANO - CPI_ASSIST)); case 'AUGMENT': // Ahorro = Vol × 12 × 15% × (CPI_humano - CPI_augment) return Math.round(volume * 12 * RATE_AUGMENT * (CPI_HUMANO - CPI_AUGMENT)); case 'HUMAN-ONLY': default: return 0; } } function OpportunityBubbleChart({ heatmapData, drilldownData }: { heatmapData: HeatmapDataPoint[]; drilldownData?: DrilldownDataPoint[] }) { // 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 (

Mapa de Oportunidades por Tier

Factibilidad (Score) vs Impacto Económico (Ahorro TCO) • Tamaño = Volumen • Color = Tier

{/* Bubble Chart */}
{/* Y-axis label */}
IMPACTO ECONÓMICO (Ahorro TCO €/año)
{/* X-axis label */}
FACTIBILIDAD (Agentic Readiness Score 0-10)
{/* Chart area */}
{/* Grid lines */}
{[...Array(20)].map((_, i) => (
))}
{/* Threshold lines */} {/* Vertical line at score = 7.5 (AUTOMATE threshold) */}
Tier AUTOMATE ≥7.5
{/* 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 */}
🎯 QUICK WINS
Score ≥7.5 + Ahorro alto
→ Prioridad 1
{/* Top-left: Low Score + High Savings = OPTIMIZE */}
⚙️ OPTIMIZE
Score <7.5 + Ahorro alto
→ Wave 1 primero
{/* Bottom-right: High Score + Low Savings = STRATEGIC */}
📊 STRATEGIC
Score ≥7.5 + Ahorro bajo
→ Evaluar ROI
{/* Bottom-left: Low Score + Low Savings = DEFER */}
📋 DEFER
Score <7.5 + Ahorro bajo
→ Backlog
{/* 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}
Score: {item.feasibility.toFixed(1)}/10
Volumen: {item.volume.toLocaleString()}/mes
Ahorro TCO: {formatCurrency(item.economicImpact)}/año
Tier: {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 */}

Interpretación: Las burbujas en el cuadrante superior derecho (Score alto + Ahorro alto) son Quick Wins para automatización. El tamaño indica volumen de interacciones.

Tamaño: Volumen
AUTOMATE (≥7.5)
ASSIST (≥5.5)
AUGMENT (≥3.5)
HUMAN (<3.5)
{/* Metodología detallada */}

Metodología de Cálculo

{/* Ejes */}
📊 Eje X: FACTIBILIDAD (Score 0-10)

Score Agentic Readiness calculado con 5 factores ponderados:

  • Predictibilidad (30%): basado en CV AHT
  • Resolutividad (25%): FCR (60%) + Transfer (40%)
  • Volumen (25%): escala logarítmica del volumen
  • Calidad Datos (10%): % registros válidos
  • Simplicidad (10%): basado en AHT
💰 Eje Y: IMPACTO ECONÓMICO (€/año)

Ahorro TCO calculado según tier con CPI diferencial:

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 */}
🧮 Fórmulas de Ahorro por Tier

AUTOMATE (Score ≥ 7.5)

Ahorro = Vol × 12 × 70% × (€2.33 - €0.15)

= Vol × 12 × 0.70 × €2.18

ASSIST (Score ≥ 5.5)

Ahorro = Vol × 12 × 30% × (€2.33 - €1.50)

= Vol × 12 × 0.30 × €0.83

AUGMENT (Score ≥ 3.5)

Ahorro = Vol × 12 × 15% × (€2.33 - €2.00)

= Vol × 12 × 0.15 × €0.33

HUMAN-ONLY (Score < 3.5 o Red Flags)

Ahorro = €0

Requiere estandarización previa

{/* Clasificación de Tier */}
🏷️ Criterios de Clasificación de Tier

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 */}

Nota: El tamaño de las burbujas representa el volumen de interacciones. Las colas clasificadas como HUMAN-ONLY no aparecen en el gráfico (ahorro = €0). Los ahorros son proyecciones basadas en benchmarks de industria y deben validarse con pilotos.

); } // ========== 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 [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 && ( Condicional )}

{wave.trimestre}

{riesgoIcons[wave.riesgo]} Riesgo {wave.riesgo}
{/* 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 */}

🎯 Por qué es necesario:

{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) { // 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 const { CPI_HUMANO, CPI_BOT, CPI_ASSIST, CPI_AUGMENT, RATE_AUTOMATE, RATE_ASSIST, RATE_AUGMENT } = CPI_CONFIG; const potentialSavings = { AUTOMATE: Math.round(tierVolumes.AUTOMATE * 12 * RATE_AUTOMATE * (CPI_HUMANO - CPI_BOT)), ASSIST: Math.round(tierVolumes.ASSIST * 12 * RATE_ASSIST * (CPI_HUMANO - CPI_ASSIST)), AUGMENT: Math.round(tierVolumes.AUGMENT * 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: 'Wave 1', titulo: 'FOUNDATION', trimestre: 'Q1-Q2 2026', 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: `${tierCounts['HUMAN-ONLY'].length + tierCounts.AUGMENT.length} de ${allQueues.length} colas están en Tier 3-4 (${Math.round((wave1Volume / totalVolume) * 100)}% del volumen). Red flags: CV >75%, Transfer >20%. Automatizar sin estandarizar = fracaso garantizado.`, 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: 'Análisis de variabilidad y red flags', setup: 15000, recurrente: 0, kpi: 'Mapear causas de CV >75% y Transfer >20%' }, { nombre: 'Rediseño y documentación de procesos', setup: 20000, recurrente: 0, kpi: 'Scripts estandarizados para 80% casuística' }, { nombre: 'Training y certificación de agentes', setup: 12000, recurrente: 0, kpi: 'Certificación 90% agentes, adherencia >85%' } ], criteriosExito: [ `CV AHT ≤75% en al menos ${Math.max(3, Math.ceil(wave1Queues.length * 0.3))} colas de alto volumen`, 'Transfer ≤20% global', 'Red flags eliminados en colas prioritarias', `Al menos ${Math.ceil(wave1Queues.length * 0.2)} colas migran de Tier 4 → Tier 3` ], riesgo: 'bajo', riesgoDescripcion: 'Consultoría con entregables tangibles. No requiere tecnología.', proveedor: 'Beyond Consulting o tercero especializado' }, { id: 'wave2', nombre: 'Wave 2', titulo: 'AUGMENT', trimestre: 'Q3 2026', tipo: 'beyond_consulting', icon: , color: 'text-amber-600', bgColor: 'bg-amber-50', borderColor: 'border-amber-200', inversionSetup: 35000, costoRecurrenteAnual: 40000, ahorroAnual: potentialSavings.AUGMENT || 58000, // 15% efficiency esCondicional: true, condicion: 'Requiere CV ≤75% post-Wave 1 en colas target', porQueNecesario: `Implementar herramientas de soporte para colas Tier 3 (Score 3.5-5.5). Objetivo: elevar score a ≥5.5 para habilitar Wave 3. Foco en ${tierCounts.AUGMENT.length} colas con ${tierVolumes.AUGMENT.toLocaleString()} int/mes.`, skills: tierCounts.AUGMENT.length > 0 ? [...new Set(drilldownData.filter(s => s.originalQueues.some(q => q.tier === 'AUGMENT')).map(s => s.skill))].slice(0, 4) : ['Colas que alcancen Score 3.5-5.5 post Wave 1'], iniciativas: [ { nombre: 'Knowledge Base contextual', setup: 20000, recurrente: 2000, kpi: 'Hold time -25%, uso KB +40%' }, { nombre: 'Scripts dinámicos con IA', setup: 15000, recurrente: 1500, kpi: 'Adherencia scripts +30%' } ], criteriosExito: [ 'Score promedio sube de 3.5-5.5 → ≥5.5', 'AHT -15% vs baseline', 'CV ≤90% en colas target', `${Math.ceil(tierCounts.AUGMENT.length * 0.5)} colas migran de Tier 3 → Tier 2` ], riesgo: 'bajo', riesgoDescripcion: 'Herramientas de soporte, bajo riesgo de integración.', proveedor: 'BEYOND (KB + Scripts IA)' }, { id: 'wave3', nombre: 'Wave 3', titulo: 'ASSIST', trimestre: 'Q4 2026', tipo: 'beyond', icon: , color: 'text-blue-600', bgColor: 'bg-blue-50', borderColor: 'border-blue-200', inversionSetup: 70000, costoRecurrenteAnual: 78000, ahorroAnual: potentialSavings.ASSIST || 145000, // 30% efficiency esCondicional: true, condicion: 'Requiere Score ≥5.5 Y CV ≤90% Y Transfer ≤30%', porQueNecesario: `Copilot IA para agentes en colas Tier 2. Sugerencias en tiempo real, autocompletado, next-best-action. Objetivo: elevar score a ≥7.5 para Wave 4. Target: ${tierCounts.ASSIST.length} colas con ${tierVolumes.ASSIST.toLocaleString()} int/mes.`, skills: tierCounts.ASSIST.length > 0 ? [...new Set(drilldownData.filter(s => s.originalQueues.some(q => q.tier === 'ASSIST')).map(s => s.skill))].slice(0, 4) : ['Colas que alcancen Score ≥5.5 post Wave 2'], iniciativas: [ { nombre: 'Agent Assist / Copilot IA', setup: 45000, recurrente: 4500, kpi: 'AHT -30%, sugerencias aceptadas >60%' }, { nombre: 'Automatización parcial (FAQs, routing)', setup: 25000, recurrente: 3000, kpi: 'Deflection rate 15%' } ], criteriosExito: [ 'Score promedio sube de 5.5-7.5 → ≥7.5', 'AHT -30% vs baseline Wave 2', 'CV ≤75% en colas target', 'Transfer ≤20%', `${Math.ceil(tierCounts.ASSIST.length * 0.4)} colas migran de Tier 2 → Tier 1` ], riesgo: 'medio', riesgoDescripcion: 'Integración con plataforma contact center. Piloto 4 semanas mitiga.', proveedor: 'BEYOND (Copilot + Routing IA)' }, { id: 'wave4', nombre: 'Wave 4', titulo: 'AUTOMATE', trimestre: 'Q1-Q2 2027', tipo: 'beyond', icon: , color: 'text-emerald-600', bgColor: 'bg-emerald-50', borderColor: 'border-emerald-200', inversionSetup: 85000, costoRecurrenteAnual: 108000, ahorroAnual: potentialSavings.AUTOMATE || 380000, // 70% containment esCondicional: true, condicion: 'Requiere Score ≥7.5 Y CV ≤75% Y Transfer ≤20% Y FCR ≥50%', porQueNecesario: `Automatización end-to-end para colas Tier 1. Voicebot/Chatbot transaccional con 70% contención. Solo viable con procesos maduros. Target actual: ${tierCounts.AUTOMATE.length} colas con ${tierVolumes.AUTOMATE.toLocaleString()} int/mes.`, skills: tierCounts.AUTOMATE.length > 0 ? [...new Set(drilldownData.filter(s => s.originalQueues.some(q => q.tier === 'AUTOMATE')).map(s => s.skill))].slice(0, 4) : ['Colas que alcancen Score ≥7.5 post Wave 3'], iniciativas: [ { nombre: 'Voicebot/Chatbot transaccional', setup: 55000, recurrente: 6000, kpi: 'Contención 70%+, CSAT ≥4/5' }, { nombre: 'IVR inteligente con NLU', setup: 30000, recurrente: 3000, kpi: 'Pre-calificación 80%+, transferencia warm' } ], criteriosExito: [ 'Contención ≥70% en colas automatizadas', 'CSAT se mantiene o mejora (≥4/5)', 'Escalado a humano <30%', 'ROI acumulado >300%' ], riesgo: 'alto', riesgoDescripcion: 'Muy condicional. Requiere éxito demostrado en Waves 1-3.', proveedor: 'BEYOND (Voicebot + IVR + Chatbot)' } ]; // ═══════════════════════════════════════════════════════════════════════════ // 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; const wave2Savings = potentialSavings.AUGMENT || Math.round(tierVolumes.AUGMENT * 12 * 0.15 * 0.33); const wave3Savings = potentialSavings.ASSIST || Math.round(tierVolumes.ASSIST * 12 * 0.30 * 0.83); const wave4Savings = potentialSavings.AUTOMATE || Math.round(tierVolumes.AUTOMATE * 12 * 0.70 * 2.18); // 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 ); const modPaybackInfo = calcularPaybackCompleto( modInversion, modMargen, modSavings, ['wave1', 'wave2', 'wave3'], modEsHabilitador, false ); // 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 ); const escenarios: EscenarioData[] = [ { id: 'conservador', nombre: 'Conservador', descripcion: 'FOUNDATION + AUGMENT (Wave 1-2)', 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: 'Moderado', descripcion: 'FOUNDATION + AUGMENT + ASSIST (Wave 1-3)', 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: 'Agresivo', descripcion: 'Roadmap completo (Wave 1-4)', 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 */}

Clasificación por Potencial de Automatización

{totalQueues} colas clasificadas en 4 Tiers según su preparación para IA • {totalVolume.toLocaleString()} interacciones/mes

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

TIER 1

AUTOMATE

{tierCounts.AUTOMATE.length}

{tierVolumes.AUTOMATE.toLocaleString()} int/mes

({Math.round((tierVolumes.AUTOMATE / totalVolume) * 100)}% volumen)

{formatCurrency(potentialSavings.AUTOMATE)}/año

{/* Tier 2: ASSIST */}

TIER 2

ASSIST

{tierCounts.ASSIST.length}

{tierVolumes.ASSIST.toLocaleString()} int/mes

({Math.round((tierVolumes.ASSIST / totalVolume) * 100)}% volumen)

{formatCurrency(potentialSavings.ASSIST)}/año

{/* Tier 3: AUGMENT */}

TIER 3

AUGMENT

{tierCounts.AUGMENT.length}

{tierVolumes.AUGMENT.toLocaleString()} int/mes

({Math.round((tierVolumes.AUGMENT / totalVolume) * 100)}% volumen)

{formatCurrency(potentialSavings.AUGMENT)}/año

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

TIER 4

HUMAN-ONLY

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

{tierVolumes['HUMAN-ONLY'].toLocaleString()} int/mes

({Math.round((tierVolumes['HUMAN-ONLY'] / totalVolume) * 100)}% volumen)

€0/año (Red flags)

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

Distribución del volumen por tier:

{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: 'Nuestra Recomendación: Estrategia Dual', sublabel: 'Ejecutar dos líneas de trabajo en paralelo para maximizar el impacto' }, FOUNDATION: { label: 'Nuestra Recomendación: Foundation First', sublabel: 'Preparar la operación antes de automatizar' }, STANDARDIZATION: { label: 'Nuestra Recomendación: Estandarización', sublabel: 'Resolver problemas operativos críticos antes de invertir en IA' } }; const config = typeConfig[recType]; return (
{/* Header */}

{config.label}

{config.sublabel}

{/* ENFOQUE DUAL: Explicación + Tabla comparativa */} {recType === 'DUAL' && ( <> {/* Explicación de los dos tracks */}

Track A: Quick Win

Automatización inmediata de las colas ya preparadas (Tier AUTOMATE). Genera retorno desde el primer mes y valida el modelo de IA con bajo riesgo.

Track B: Foundation

Preparación de las colas que aún no están listas (Tier 3-4). Estandariza procesos y reduce variabilidad para habilitar automatización futura.

{/* Tabla comparativa */}
Quick Win Foundation
Alcance {pilotQueues.length} colas {pilotVolume.toLocaleString()} int/mes {tierCounts['HUMAN-ONLY'].length + tierCounts.AUGMENT.length} colas Wave 1 + Wave 2
Inversión {formatCurrency(pilotInversionTotal)} {formatCurrency(wave1Setup + wave2Setup)}
Retorno {formatCurrency(pilotAhorroAjustado)}/año directo (ajustado 50%) {formatCurrency(potentialSavings.ASSIST + potentialSavings.AUGMENT)}/año habilitado (indirecto)
Timeline 2-3 meses 6-9 meses
ROI Year 1 {pilotROIDisplay.display} No aplica (habilitador)
¿Por qué dos tracks? Quick Win genera caja y confianza desde el inicio. Foundation prepara el {Math.round(assistPct + augmentPct)}% restante del volumen para fases posteriores. Ejecutarlos en paralelo acelera el time-to-value total.
)} {/* FOUNDATION PRIMERO */} {recType === 'FOUNDATION' && ( <> {/* Explicación */}

¿Qué significa Foundation?

La operación actual no tiene colas listas para automatizar directamente. Foundation es la fase de preparación: estandarizar procesos, reducir variabilidad y mejorar la calidad de datos para que la automatización posterior sea efectiva.

{tierCounts.ASSIST.length} colas ASSIST ({Math.round(assistPct)}% del volumen) pueden elevarse a Tier AUTOMATE tras completar Wave 1-2.

Inversión

{formatCurrency(wave1Setup + wave2Setup)}

Timeline

6-9 meses

Ahorro habilitado

{formatCurrency(potentialSavings.ASSIST)}/año

Criterios para pasar a automatización: CV ≤90% · Transfer ≤30% · AHT -15%
)} {/* ESTANDARIZACIÓN URGENTE */} {recType === 'STANDARDIZATION' && ( <> {/* Explicación */}

¿Por qué estandarización primero?

Se han detectado "red flags" operativos críticos (alta variabilidad, muchas transferencias) que harían fracasar cualquier proyecto de automatización. Invertir en IA ahora sería malgastar recursos. Primero hay que estabilizar la operación.

{Math.round(humanOnlyPct + augmentPct)}% del volumen presenta red flags (CV >75%, Transfer >20%). Wave 1 es una inversión habilitadora sin retorno directo inmediato.

Inversión Wave 1

{formatCurrency(wave1Setup)}

Timeline

3-4 meses

Ahorro directo

€0 (habilitador)

Objetivo: Reducir red flags en las {Math.min(10, tierCounts['HUMAN-ONLY'].length + tierCounts.AUGMENT.length)} colas principales. Reevaluar tras completar.
)} {/* Siguiente paso */}

Siguiente paso recomendado:

{recType === 'DUAL' && ( <>Iniciar piloto de automatización con las {pilotQueues.length} colas AUTOMATE, mientras se ejecuta Wave 1 (Foundation) en paralelo para preparar el resto. )} {recType === 'FOUNDATION' && ( <>Comenzar Wave 1 focalizando en las {Math.min(10, tierCounts['HUMAN-ONLY'].length)} colas de mayor volumen. Medir progreso mensual en CV y Transfer. )} {recType === 'STANDARDIZATION' && ( <>Realizar workshop de diagnóstico operacional para identificar las causas raíz de los red flags antes de planificar inversiones. )}

); })()}
{/* ═══════════════════════════════════════════════════════════════════════════ 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 ( ); })}
)}
); } export default RoadmapTab;