Merge pull request #3 from sujucu70/claude/add-english-language-1N9VX
Claude/add english language 1 n9 vx
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { ArrowLeft } from 'lucide-react';
|
import { ArrowLeft } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DashboardHeader, TabId } from './DashboardHeader';
|
import { DashboardHeader, TabId } from './DashboardHeader';
|
||||||
import { formatDateMonthYear } from '../utils/formatters';
|
import { formatDateMonthYear } from '../utils/formatters';
|
||||||
import { ExecutiveSummaryTab } from './tabs/ExecutiveSummaryTab';
|
import { ExecutiveSummaryTab } from './tabs/ExecutiveSummaryTab';
|
||||||
@@ -19,12 +20,15 @@ interface DashboardTabsProps {
|
|||||||
|
|
||||||
export function DashboardTabs({
|
export function DashboardTabs({
|
||||||
data,
|
data,
|
||||||
title = 'CLIENTE DEMO - Beyond CX Analytics',
|
title,
|
||||||
onBack
|
onBack
|
||||||
}: DashboardTabsProps) {
|
}: DashboardTabsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [activeTab, setActiveTab] = useState<TabId>('executive');
|
const [activeTab, setActiveTab] = useState<TabId>('executive');
|
||||||
const [metodologiaOpen, setMetodologiaOpen] = useState(false);
|
const [metodologiaOpen, setMetodologiaOpen] = useState(false);
|
||||||
|
|
||||||
|
const displayTitle = title || t('dashboard.defaultTitle');
|
||||||
|
|
||||||
const renderTabContent = () => {
|
const renderTabContent = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case 'executive':
|
case 'executive':
|
||||||
@@ -53,8 +57,8 @@ export function DashboardTabs({
|
|||||||
className="flex items-center gap-2 text-sm text-slate-600 hover:text-slate-800 transition-colors"
|
className="flex items-center gap-2 text-sm text-slate-600 hover:text-slate-800 transition-colors"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
<span className="hidden sm:inline">Volver al formulario</span>
|
<span className="hidden sm:inline">{t('dashboard.backToForm')}</span>
|
||||||
<span className="sm:hidden">Volver</span>
|
<span className="sm:hidden">{t('dashboard.back')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +66,7 @@ export function DashboardTabs({
|
|||||||
|
|
||||||
{/* Sticky Header with Tabs */}
|
{/* Sticky Header with Tabs */}
|
||||||
<DashboardHeader
|
<DashboardHeader
|
||||||
title={title}
|
title={displayTitle}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={setActiveTab}
|
onTabChange={setActiveTab}
|
||||||
onMetodologiaClick={() => setMetodologiaOpen(true)}
|
onMetodologiaClick={() => setMetodologiaOpen(true)}
|
||||||
@@ -87,8 +91,8 @@ export function DashboardTabs({
|
|||||||
<footer className="border-t border-slate-200 bg-white mt-8">
|
<footer className="border-t border-slate-200 bg-white mt-8">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-3 sm:py-4">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-3 sm:py-4">
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-sm text-slate-500">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-sm text-slate-500">
|
||||||
<span className="hidden sm:inline">Beyond Diagnosis - Contact Center Analytics Platform</span>
|
<span className="hidden sm:inline">{t('dashboard.footer')}</span>
|
||||||
<span className="sm:hidden text-xs">Beyond Diagnosis</span>
|
<span className="sm:hidden text-xs">{t('dashboard.footerShort')}</span>
|
||||||
<span className="text-xs sm:text-sm text-slate-400 italic">{formatDateMonthYear()}</span>
|
<span className="text-xs sm:text-sm text-slate-400 italic">{formatDateMonthYear()}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { DimensionAnalysis } from '../types';
|
import { DimensionAnalysis } from '../types';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { AlertCircle, AlertTriangle, TrendingUp, CheckCircle, Zap } from 'lucide-react';
|
import { AlertCircle, AlertTriangle, TrendingUp, CheckCircle, Zap } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import BadgePill from './BadgePill';
|
import BadgePill from './BadgePill';
|
||||||
|
|
||||||
interface HealthStatus {
|
interface HealthStatus {
|
||||||
@@ -14,59 +15,59 @@ interface HealthStatus {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getHealthStatus = (score: number): HealthStatus => {
|
const getHealthStatus = (score: number, t: (key: string) => string): HealthStatus => {
|
||||||
if (score >= 86) {
|
if (score >= 86) {
|
||||||
return {
|
return {
|
||||||
level: 'excellent',
|
level: 'excellent',
|
||||||
label: 'EXCELENTE',
|
label: t('healthStatus.excellent'),
|
||||||
color: 'text-cyan-700',
|
color: 'text-cyan-700',
|
||||||
textColor: 'text-cyan-700',
|
textColor: 'text-cyan-700',
|
||||||
bgColor: 'bg-cyan-50',
|
bgColor: 'bg-cyan-50',
|
||||||
icon: <CheckCircle size={20} className="text-cyan-600" />,
|
icon: <CheckCircle size={20} className="text-cyan-600" />,
|
||||||
description: 'Top quartile, modelo a seguir'
|
description: t('healthStatus.excellentDesc')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (score >= 71) {
|
if (score >= 71) {
|
||||||
return {
|
return {
|
||||||
level: 'good',
|
level: 'good',
|
||||||
label: 'BUENO',
|
label: t('healthStatus.good'),
|
||||||
color: 'text-emerald-700',
|
color: 'text-emerald-700',
|
||||||
textColor: 'text-emerald-700',
|
textColor: 'text-emerald-700',
|
||||||
bgColor: 'bg-emerald-50',
|
bgColor: 'bg-emerald-50',
|
||||||
icon: <TrendingUp size={20} className="text-emerald-600" />,
|
icon: <TrendingUp size={20} className="text-emerald-600" />,
|
||||||
description: 'Por encima de benchmarks, desempeño sólido'
|
description: t('healthStatus.goodDesc')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (score >= 51) {
|
if (score >= 51) {
|
||||||
return {
|
return {
|
||||||
level: 'medium',
|
level: 'medium',
|
||||||
label: 'MEDIO',
|
label: t('healthStatus.medium'),
|
||||||
color: 'text-amber-700',
|
color: 'text-amber-700',
|
||||||
textColor: 'text-amber-700',
|
textColor: 'text-amber-700',
|
||||||
bgColor: 'bg-amber-50',
|
bgColor: 'bg-amber-50',
|
||||||
icon: <AlertTriangle size={20} className="text-amber-600" />,
|
icon: <AlertTriangle size={20} className="text-amber-600" />,
|
||||||
description: 'Oportunidad de mejora identificada'
|
description: t('healthStatus.mediumDesc')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (score >= 31) {
|
if (score >= 31) {
|
||||||
return {
|
return {
|
||||||
level: 'low',
|
level: 'low',
|
||||||
label: 'BAJO',
|
label: t('healthStatus.low'),
|
||||||
color: 'text-orange-700',
|
color: 'text-orange-700',
|
||||||
textColor: 'text-orange-700',
|
textColor: 'text-orange-700',
|
||||||
bgColor: 'bg-orange-50',
|
bgColor: 'bg-orange-50',
|
||||||
icon: <AlertTriangle size={20} className="text-orange-600" />,
|
icon: <AlertTriangle size={20} className="text-orange-600" />,
|
||||||
description: 'Requiere mejora, por debajo de benchmarks'
|
description: t('healthStatus.lowDesc')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
level: 'critical',
|
level: 'critical',
|
||||||
label: 'CRÍTICO',
|
label: t('healthStatus.critical'),
|
||||||
color: 'text-red-700',
|
color: 'text-red-700',
|
||||||
textColor: 'text-red-700',
|
textColor: 'text-red-700',
|
||||||
bgColor: 'bg-red-50',
|
bgColor: 'bg-red-50',
|
||||||
icon: <AlertCircle size={20} className="text-red-600" />,
|
icon: <AlertCircle size={20} className="text-red-600" />,
|
||||||
description: 'Requiere acción inmediata'
|
description: t('healthStatus.criticalDesc')
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,7 +80,8 @@ const getProgressBarColor = (score: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ScoreIndicator: React.FC<{ score: number; benchmark?: number }> = ({ score, benchmark }) => {
|
const ScoreIndicator: React.FC<{ score: number; benchmark?: number }> = ({ score, benchmark }) => {
|
||||||
const healthStatus = getHealthStatus(score);
|
const { t } = useTranslation();
|
||||||
|
const healthStatus = getHealthStatus(score, t);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -119,21 +121,21 @@ const ScoreIndicator: React.FC<{ score: number; benchmark?: number }> = ({ score
|
|||||||
{benchmark !== undefined && (
|
{benchmark !== undefined && (
|
||||||
<div className="bg-slate-50 rounded-lg p-3 text-sm">
|
<div className="bg-slate-50 rounded-lg p-3 text-sm">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-slate-600">Benchmark Industria (P50)</span>
|
<span className="text-slate-600">{t('benchmark.title')}</span>
|
||||||
<span className="font-bold text-slate-900">{benchmark}/100</span>
|
<span className="font-bold text-slate-900">{benchmark}/100</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-slate-500">
|
<div className="text-xs text-slate-500">
|
||||||
{score > benchmark ? (
|
{score > benchmark ? (
|
||||||
<span className="text-emerald-600 font-semibold">
|
<span className="text-emerald-600 font-semibold">
|
||||||
↑ {score - benchmark} puntos por encima del promedio
|
{t('benchmark.aboveBenchmark', { points: score - benchmark })}
|
||||||
</span>
|
</span>
|
||||||
) : score === benchmark ? (
|
) : score === benchmark ? (
|
||||||
<span className="text-amber-600 font-semibold">
|
<span className="text-amber-600 font-semibold">
|
||||||
= Alineado con promedio de industria
|
{t('benchmark.atBenchmark')}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-orange-600 font-semibold">
|
<span className="text-orange-600 font-semibold">
|
||||||
↓ {benchmark - score} puntos por debajo del promedio
|
{t('benchmark.belowBenchmark', { points: benchmark - score })}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +156,8 @@ const ScoreIndicator: React.FC<{ score: number; benchmark?: number }> = ({ score
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DimensionCard: React.FC<{ dimension: DimensionAnalysis }> = ({ dimension }) => {
|
const DimensionCard: React.FC<{ dimension: DimensionAnalysis }> = ({ dimension }) => {
|
||||||
const healthStatus = getHealthStatus(dimension.score);
|
const { t } = useTranslation();
|
||||||
|
const healthStatus = getHealthStatus(dimension.score, t);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -226,10 +229,10 @@ const DimensionCard: React.FC<{ dimension: DimensionAnalysis }> = ({ dimension }
|
|||||||
>
|
>
|
||||||
<Zap size={16} />
|
<Zap size={16} />
|
||||||
{dimension.score < 51
|
{dimension.score < 51
|
||||||
? 'Ver Acciones Críticas'
|
? t('dimensionCard.viewCriticalActions')
|
||||||
: dimension.score < 71
|
: dimension.score < 71
|
||||||
? 'Explorar Mejoras'
|
? t('dimensionCard.exploreImprovements')
|
||||||
: 'En buen estado'}
|
: t('dimensionCard.inGoodState')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle } from 'lucide-react';
|
||||||
|
import { withTranslation, WithTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props extends WithTranslation {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
fallback?: ReactNode;
|
fallback?: ReactNode;
|
||||||
componentName?: string;
|
componentName?: string;
|
||||||
@@ -45,28 +46,31 @@ class ErrorBoundary extends Component<Props, State> {
|
|||||||
return this.props.fallback;
|
return this.props.fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-amber-50 border-2 border-amber-200 rounded-lg p-6">
|
<div className="bg-amber-50 border-2 border-amber-200 rounded-lg p-6">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<AlertTriangle className="text-amber-600 flex-shrink-0 mt-1" size={24} />
|
<AlertTriangle className="text-amber-600 flex-shrink-0 mt-1" size={24} />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-amber-900 mb-2">
|
<h3 className="text-lg font-semibold text-amber-900 mb-2">
|
||||||
{this.props.componentName ? `Error en ${this.props.componentName}` : 'Error de Renderizado'}
|
{this.props.componentName
|
||||||
|
? t('errors.errorInComponent', { componentName: this.props.componentName })
|
||||||
|
: t('errors.renderError')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-amber-800 mb-3">
|
<p className="text-amber-800 mb-3">
|
||||||
Este componente encontró un error y no pudo renderizarse correctamente.
|
{t('errors.componentError')}
|
||||||
El resto del dashboard sigue funcionando normalmente.
|
|
||||||
</p>
|
</p>
|
||||||
<details className="text-sm">
|
<details className="text-sm">
|
||||||
<summary className="cursor-pointer text-amber-700 font-medium mb-2">
|
<summary className="cursor-pointer text-amber-700 font-medium mb-2">
|
||||||
Ver detalles técnicos
|
{t('errors.viewTechnicalDetails')}
|
||||||
</summary>
|
</summary>
|
||||||
<div className="bg-white rounded p-3 mt-2 font-mono text-xs overflow-auto max-h-40">
|
<div className="bg-white rounded p-3 mt-2 font-mono text-xs overflow-auto max-h-40">
|
||||||
<p className="text-red-600 font-semibold mb-1">Error:</p>
|
<p className="text-red-600 font-semibold mb-1">{t('errors.errorLabel')}</p>
|
||||||
<p className="text-slate-700 mb-3">{this.state.error?.toString()}</p>
|
<p className="text-slate-700 mb-3">{this.state.error?.toString()}</p>
|
||||||
{this.state.errorInfo && (
|
{this.state.errorInfo && (
|
||||||
<>
|
<>
|
||||||
<p className="text-red-600 font-semibold mb-1">Stack:</p>
|
<p className="text-red-600 font-semibold mb-1">{t('errors.stackLabel')}</p>
|
||||||
<pre className="text-slate-600 whitespace-pre-wrap">
|
<pre className="text-slate-600 whitespace-pre-wrap">
|
||||||
{this.state.errorInfo.componentStack}
|
{this.state.errorInfo.componentStack}
|
||||||
</pre>
|
</pre>
|
||||||
@@ -78,7 +82,7 @@ class ErrorBoundary extends Component<Props, State> {
|
|||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
className="mt-4 px-4 py-2 bg-amber-600 text-white rounded hover:bg-amber-700 transition-colors"
|
className="mt-4 px-4 py-2 bg-amber-600 text-white rounded hover:bg-amber-700 transition-colors"
|
||||||
>
|
>
|
||||||
Recargar Página
|
{t('dashboard.reloadPage')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,4 +94,4 @@ class ErrorBoundary extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ErrorBoundary;
|
export default withTranslation()(ErrorBoundary);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
X, ShieldCheck, Database, RefreshCw, Tag, BarChart3,
|
X, ShieldCheck, Database, RefreshCw, Tag, BarChart3,
|
||||||
ArrowRight, BadgeCheck, Download, ArrowLeftRight, Layers
|
ArrowRight, BadgeCheck, Download, ArrowLeftRight, Layers
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { AnalysisData, HeatmapDataPoint } from '../types';
|
import type { AnalysisData, HeatmapDataPoint } from '../types';
|
||||||
|
|
||||||
interface MetodologiaDrawerProps {
|
interface MetodologiaDrawerProps {
|
||||||
@@ -36,12 +37,12 @@ interface DataSummary {
|
|||||||
|
|
||||||
// ========== SUBSECCIONES ==========
|
// ========== SUBSECCIONES ==========
|
||||||
|
|
||||||
function DataSummarySection({ data }: { data: DataSummary }) {
|
function DataSummarySection({ data, t }: { data: DataSummary; t: any }) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-slate-50 rounded-lg p-5">
|
<div className="bg-slate-50 rounded-lg p-5">
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<Database className="w-5 h-5 text-blue-600" />
|
<Database className="w-5 h-5 text-blue-600" />
|
||||||
Datos Procesados
|
{t('methodology.dataProcessed')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
@@ -49,55 +50,55 @@ function DataSummarySection({ data }: { data: DataSummary }) {
|
|||||||
<div className="text-3xl font-bold text-blue-600">
|
<div className="text-3xl font-bold text-blue-600">
|
||||||
{data.totalRegistros.toLocaleString('es-ES')}
|
{data.totalRegistros.toLocaleString('es-ES')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">Registros analizados</div>
|
<div className="text-sm text-gray-600">{t('methodology.recordsAnalyzed')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg p-4 text-center shadow-sm">
|
<div className="bg-white rounded-lg p-4 text-center shadow-sm">
|
||||||
<div className="text-3xl font-bold text-blue-600">
|
<div className="text-3xl font-bold text-blue-600">
|
||||||
{data.mesesHistorico}
|
{data.mesesHistorico}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">Meses de histórico</div>
|
<div className="text-sm text-gray-600">{t('methodology.monthsHistory')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg p-4 text-center shadow-sm">
|
<div className="bg-white rounded-lg p-4 text-center shadow-sm">
|
||||||
<div className="text-2xl font-bold text-blue-600">
|
<div className="text-2xl font-bold text-blue-600">
|
||||||
{data.fuente}
|
{data.fuente}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">Sistema origen</div>
|
<div className="text-sm text-gray-600">{t('methodology.sourceSystem')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-3 text-center">
|
<p className="text-xs text-slate-500 mt-3 text-center">
|
||||||
Periodo: {data.periodo}
|
{t('methodology.periodRange', { period: data.periodo })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PipelineSection() {
|
function PipelineSection({ t }: { t: any }) {
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
layer: 'Layer 0',
|
layer: 'Layer 0',
|
||||||
name: 'Raw Data',
|
name: t('methodology.pipeline.layer1'),
|
||||||
desc: 'Ingesta y Normalización',
|
desc: t('methodology.pipeline.layer1Desc'),
|
||||||
color: 'bg-gray-100 border-gray-300'
|
color: 'bg-gray-100 border-gray-300'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
layer: 'Layer 1',
|
layer: 'Layer 1',
|
||||||
name: 'Trusted Data',
|
name: t('methodology.pipeline.layer2'),
|
||||||
desc: 'Higiene y Clasificación',
|
desc: t('methodology.pipeline.layer2Desc'),
|
||||||
color: 'bg-yellow-50 border-yellow-300'
|
color: 'bg-yellow-50 border-yellow-300'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
layer: 'Layer 2',
|
layer: 'Layer 2',
|
||||||
name: 'Business Insights',
|
name: t('methodology.pipeline.layer3'),
|
||||||
desc: 'Enriquecimiento',
|
desc: t('methodology.pipeline.layer3Desc'),
|
||||||
color: 'bg-green-50 border-green-300'
|
color: 'bg-green-50 border-green-300'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
layer: 'Output',
|
layer: 'Output',
|
||||||
name: 'Dashboard',
|
name: t('methodology.pipeline.layer4'),
|
||||||
desc: 'Visualización',
|
desc: t('methodology.pipeline.layer4Desc'),
|
||||||
color: 'bg-blue-50 border-blue-300'
|
color: 'bg-blue-50 border-blue-300'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -106,7 +107,7 @@ function PipelineSection() {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<RefreshCw className="w-5 h-5 text-purple-600" />
|
<RefreshCw className="w-5 h-5 text-purple-600" />
|
||||||
Pipeline de Transformación
|
{t('methodology.pipeline.title')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -125,42 +126,42 @@ function PipelineSection() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-gray-500 mt-3 italic">
|
<p className="text-xs text-gray-500 mt-3 italic">
|
||||||
Arquitectura modular de 3 capas para garantizar trazabilidad y escalabilidad.
|
{t('methodology.pipeline.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TaxonomySection({ data }: { data: DataSummary['taxonomia'] }) {
|
function TaxonomySection({ data, t }: { data: DataSummary['taxonomia']; t: any }) {
|
||||||
const rows = [
|
const rows = [
|
||||||
{
|
{
|
||||||
status: 'VALID',
|
status: t('methodology.taxonomy.valid'),
|
||||||
pct: data.valid,
|
pct: data.valid,
|
||||||
def: 'Duración 10s - 3h. Interacciones reales.',
|
def: t('methodology.taxonomy.validDef'),
|
||||||
costes: true,
|
costes: true,
|
||||||
aht: true,
|
aht: true,
|
||||||
bgClass: 'bg-green-100 text-green-800'
|
bgClass: 'bg-green-100 text-green-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: 'NOISE',
|
status: t('methodology.taxonomy.noise'),
|
||||||
pct: data.noise,
|
pct: data.noise,
|
||||||
def: 'Duración <10s (no abandono). Ruido técnico.',
|
def: t('methodology.taxonomy.noiseDef'),
|
||||||
costes: true,
|
costes: true,
|
||||||
aht: false,
|
aht: false,
|
||||||
bgClass: 'bg-yellow-100 text-yellow-800'
|
bgClass: 'bg-yellow-100 text-yellow-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: 'ZOMBIE',
|
status: t('methodology.taxonomy.zombie'),
|
||||||
pct: data.zombie,
|
pct: data.zombie,
|
||||||
def: 'Duración >3h. Error de sistema.',
|
def: t('methodology.taxonomy.zombieDef'),
|
||||||
costes: true,
|
costes: true,
|
||||||
aht: false,
|
aht: false,
|
||||||
bgClass: 'bg-red-100 text-red-800'
|
bgClass: 'bg-red-100 text-red-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: 'ABANDON',
|
status: t('methodology.taxonomy.abandon'),
|
||||||
pct: data.abandon,
|
pct: data.abandon,
|
||||||
def: 'Desconexión externa + Talk ≤5s.',
|
def: t('methodology.taxonomy.abandonDef'),
|
||||||
costes: false,
|
costes: false,
|
||||||
aht: false,
|
aht: false,
|
||||||
bgClass: 'bg-gray-100 text-gray-800'
|
bgClass: 'bg-gray-100 text-gray-800'
|
||||||
@@ -171,23 +172,22 @@ function TaxonomySection({ data }: { data: DataSummary['taxonomia'] }) {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<Tag className="w-5 h-5 text-orange-600" />
|
<Tag className="w-5 h-5 text-orange-600" />
|
||||||
Taxonomía de Calidad de Datos
|
{t('methodology.taxonomy.title')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p className="text-sm text-gray-600 mb-4">
|
<p className="text-sm text-gray-600 mb-4">
|
||||||
En lugar de eliminar registros, aplicamos "Soft Delete" con etiquetado de calidad
|
{t('methodology.taxonomy.description')}
|
||||||
para permitir doble visión: financiera (todos los costes) y operativa (KPIs limpios).
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="overflow-hidden rounded-lg border border-slate-200">
|
<div className="overflow-hidden rounded-lg border border-slate-200">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left font-semibold">Estado</th>
|
<th className="px-3 py-2 text-left font-semibold">{t('methodology.taxonomy.state')}</th>
|
||||||
<th className="px-3 py-2 text-right font-semibold">%</th>
|
<th className="px-3 py-2 text-right font-semibold">{t('methodology.taxonomy.percentage')}</th>
|
||||||
<th className="px-3 py-2 text-left font-semibold">Definición</th>
|
<th className="px-3 py-2 text-left font-semibold">{t('methodology.taxonomy.definition')}</th>
|
||||||
<th className="px-3 py-2 text-center font-semibold">Costes</th>
|
<th className="px-3 py-2 text-center font-semibold">{t('methodology.taxonomy.costs')}</th>
|
||||||
<th className="px-3 py-2 text-center font-semibold">AHT</th>
|
<th className="px-3 py-2 text-center font-semibold">{t('methodology.taxonomy.aht')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-100">
|
<tbody className="divide-y divide-slate-100">
|
||||||
@@ -202,16 +202,16 @@ function TaxonomySection({ data }: { data: DataSummary['taxonomia'] }) {
|
|||||||
<td className="px-3 py-2 text-xs text-gray-600">{row.def}</td>
|
<td className="px-3 py-2 text-xs text-gray-600">{row.def}</td>
|
||||||
<td className="px-3 py-2 text-center">
|
<td className="px-3 py-2 text-center">
|
||||||
{row.costes ? (
|
{row.costes ? (
|
||||||
<span className="text-green-600">✓ Suma</span>
|
<span className="text-green-600">{t('methodology.taxonomy.sumYes')}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-red-600">✗ No</span>
|
<span className="text-red-600">{t('methodology.taxonomy.sumNo')}</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-center">
|
<td className="px-3 py-2 text-center">
|
||||||
{row.aht ? (
|
{row.aht ? (
|
||||||
<span className="text-green-600">✓ Promedio</span>
|
<span className="text-green-600">{t('methodology.taxonomy.avgYes')}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-red-600">✗ Excluye</span>
|
<span className="text-red-600">{t('methodology.taxonomy.avgExclude')}</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -223,7 +223,7 @@ function TaxonomySection({ data }: { data: DataSummary['taxonomia'] }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function KPIRedefinitionSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
function KPIRedefinitionSection({ kpis, t }: { kpis: DataSummary['kpis']; t: any }) {
|
||||||
const formatTime = (seconds: number): string => {
|
const formatTime = (seconds: number): string => {
|
||||||
const mins = Math.floor(seconds / 60);
|
const mins = Math.floor(seconds / 60);
|
||||||
const secs = seconds % 60;
|
const secs = seconds % 60;
|
||||||
@@ -234,11 +234,11 @@ function KPIRedefinitionSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<BarChart3 className="w-5 h-5 text-indigo-600" />
|
<BarChart3 className="w-5 h-5 text-indigo-600" />
|
||||||
KPIs Redefinidos
|
{t('methodology.kpis.title')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p className="text-sm text-gray-600 mb-4">
|
<p className="text-sm text-gray-600 mb-4">
|
||||||
Hemos redefinido los KPIs para eliminar los "puntos ciegos" de las métricas tradicionales.
|
{t('methodology.kpis.description')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -246,25 +246,25 @@ function KPIRedefinitionSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
|||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-red-800">FCR Real vs FCR Técnico</h4>
|
<h4 className="font-semibold text-red-800">{t('methodology.kpis.fcrTitle')}</h4>
|
||||||
<p className="text-xs text-red-700 mt-1">
|
<p className="text-xs text-red-700 mt-1">
|
||||||
El hallazgo más crítico del diagnóstico.
|
{t('methodology.kpis.fcrSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-2xl font-bold text-red-600">{kpis.fcrReal}%</span>
|
<span className="text-2xl font-bold text-red-600">{kpis.fcrReal}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-xs">
|
<div className="mt-3 text-xs">
|
||||||
<div className="flex justify-between py-1 border-b border-red-200">
|
<div className="flex justify-between py-1 border-b border-red-200">
|
||||||
<span className="text-gray-600">FCR Técnico (sin transferencia):</span>
|
<span className="text-gray-600">{t('methodology.kpis.fcrTechnical')}</span>
|
||||||
<span className="font-medium">~{kpis.fcrTecnico}%</span>
|
<span className="font-medium">~{kpis.fcrTecnico}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between py-1">
|
<div className="flex justify-between py-1">
|
||||||
<span className="text-gray-600">FCR Real (sin recontacto 7 días):</span>
|
<span className="text-gray-600">{t('methodology.kpis.fcrReal')}</span>
|
||||||
<span className="font-medium text-red-600">{kpis.fcrReal}%</span>
|
<span className="font-medium text-red-600">{kpis.fcrReal}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-red-600 mt-2 italic">
|
<p className="text-[10px] text-red-600 mt-2 italic">
|
||||||
💡 ~{kpis.fcrTecnico - kpis.fcrReal}% de "casos resueltos" generan segunda llamada, disparando costes ocultos.
|
💡 {t('methodology.kpis.fcrGap', { diff: kpis.fcrTecnico - kpis.fcrReal })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -272,15 +272,15 @@ function KPIRedefinitionSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
|||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-yellow-800">Tasa de Abandono Real</h4>
|
<h4 className="font-semibold text-yellow-800">{t('methodology.kpis.abandonTitle')}</h4>
|
||||||
<p className="text-xs text-yellow-700 mt-1">
|
<p className="text-xs text-yellow-700 mt-1">
|
||||||
Fórmula: Desconexión Externa + Talk ≤5 segundos
|
{t('methodology.kpis.abandonFormula')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-2xl font-bold text-yellow-600">{kpis.abandonoReal.toFixed(1)}%</span>
|
<span className="text-2xl font-bold text-yellow-600">{kpis.abandonoReal.toFixed(1)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-yellow-600 mt-2 italic">
|
<p className="text-[10px] text-yellow-600 mt-2 italic">
|
||||||
💡 El umbral de 5s captura al cliente que cuelga al escuchar la locución o en el timbre.
|
💡 {t('methodology.kpis.abandonDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -288,15 +288,15 @@ function KPIRedefinitionSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
|||||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-blue-800">AHT Limpio</h4>
|
<h4 className="font-semibold text-blue-800">{t('methodology.kpis.ahtTitle')}</h4>
|
||||||
<p className="text-xs text-blue-700 mt-1">
|
<p className="text-xs text-blue-700 mt-1">
|
||||||
Excluye NOISE (<10s) y ZOMBIE (>3h) del promedio.
|
{t('methodology.kpis.ahtDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-2xl font-bold text-blue-600">{formatTime(kpis.ahtLimpio)}</span>
|
<span className="text-2xl font-bold text-blue-600">{formatTime(kpis.ahtLimpio)}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-blue-600 mt-2 italic">
|
<p className="text-[10px] text-blue-600 mt-2 italic">
|
||||||
💡 El AHT sin filtrar estaba distorsionado por errores de sistema.
|
💡 {t('methodology.kpis.ahtNote')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -304,7 +304,7 @@ function KPIRedefinitionSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CPICalculationSection({ totalCost, totalVolume, costPerHour = 20 }: { totalCost: number; totalVolume: number; costPerHour?: number }) {
|
function CPICalculationSection({ totalCost, totalVolume, costPerHour = 20, t }: { totalCost: number; totalVolume: number; costPerHour?: number; t: any }) {
|
||||||
// Productivity factor: agents are ~70% productive (rest is breaks, training, after-call work, etc.)
|
// Productivity factor: agents are ~70% productive (rest is breaks, training, after-call work, etc.)
|
||||||
const effectiveProductivity = 0.70;
|
const effectiveProductivity = 0.70;
|
||||||
|
|
||||||
@@ -316,128 +316,124 @@ function CPICalculationSection({ totalCost, totalVolume, costPerHour = 20 }: { t
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<BarChart3 className="w-5 h-5 text-emerald-600" />
|
<BarChart3 className="w-5 h-5 text-emerald-600" />
|
||||||
Coste por Interacción (CPI)
|
{t('methodology.kpis.cpiTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p className="text-sm text-gray-600 mb-4">
|
<p className="text-sm text-gray-600 mb-4">
|
||||||
El CPI se calcula dividiendo el <strong>coste total</strong> entre el <strong>volumen de interacciones</strong>.
|
{t('methodology.cpi.description', { productivity: (effectiveProductivity * 100).toFixed(0) })}
|
||||||
El coste total incluye <em>todas</em> las interacciones (noise, zombie y válidas) porque todas se facturan,
|
|
||||||
y aplica un factor de productividad del {(effectiveProductivity * 100).toFixed(0)}%.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Fórmula visual */}
|
{/* Fórmula visual */}
|
||||||
<div className="bg-emerald-50 border border-emerald-200 rounded-lg p-4 mb-4">
|
<div className="bg-emerald-50 border border-emerald-200 rounded-lg p-4 mb-4">
|
||||||
<div className="text-center mb-3">
|
<div className="text-center mb-3">
|
||||||
<span className="text-xs text-emerald-700 uppercase tracking-wider font-medium">Fórmula de Cálculo</span>
|
<span className="text-xs text-emerald-700 uppercase tracking-wider font-medium">{t('methodology.kpis.cpiFormulaTitle')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-2 text-lg font-mono flex-wrap">
|
<div className="flex items-center justify-center gap-2 text-lg font-mono flex-wrap">
|
||||||
<span className="px-3 py-1 bg-white rounded border border-emerald-300">CPI</span>
|
<span className="px-3 py-1 bg-white rounded border border-emerald-300">{t('methodology.kpis.cpiLabel')}</span>
|
||||||
<span className="text-emerald-600">=</span>
|
<span className="text-emerald-600">=</span>
|
||||||
<span className="px-2 py-1 bg-blue-100 rounded text-blue-800 text-sm">Coste Total</span>
|
<span className="px-2 py-1 bg-blue-100 rounded text-blue-800 text-sm">{t('methodology.kpis.totalCost')}</span>
|
||||||
<span className="text-emerald-600">÷</span>
|
<span className="text-emerald-600">{t('methodology.kpis.divide')}</span>
|
||||||
<span className="px-2 py-1 bg-amber-100 rounded text-amber-800 text-sm">Volumen Total</span>
|
<span className="px-2 py-1 bg-amber-100 rounded text-amber-800 text-sm">{t('methodology.kpis.totalVolume')}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-center text-emerald-600 mt-2">
|
<p className="text-[10px] text-center text-emerald-600 mt-2">
|
||||||
El coste total usa (AHT segundos ÷ 3600) × coste/hora × volumen ÷ productividad
|
{t('methodology.kpis.cpiNote')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cómo se calcula el coste total */}
|
{/* Cómo se calcula el coste total */}
|
||||||
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 mb-4">
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 mb-4">
|
||||||
<div className="text-sm font-semibold text-slate-700 mb-2">¿Cómo se calcula el Coste Total?</div>
|
<div className="text-sm font-semibold text-slate-700 mb-2">{t('methodology.kpis.howCalculate')}</div>
|
||||||
<div className="bg-white rounded p-3 mb-3">
|
<div className="bg-white rounded p-3 mb-3">
|
||||||
<div className="flex items-center justify-center gap-2 text-sm font-mono flex-wrap">
|
<div className="flex items-center justify-center gap-2 text-sm font-mono flex-wrap">
|
||||||
<span className="text-slate-600">Coste =</span>
|
<span className="text-slate-600">{t('methodology.kpis.costEquals')}</span>
|
||||||
<span className="px-2 py-1 bg-blue-100 rounded text-blue-800 text-xs">(AHT seg ÷ 3600)</span>
|
<span className="px-2 py-1 bg-blue-100 rounded text-blue-800 text-xs">(AHT seg ÷ 3600)</span>
|
||||||
<span className="text-slate-400">×</span>
|
<span className="text-slate-400">×</span>
|
||||||
<span className="px-2 py-1 bg-amber-100 rounded text-amber-800 text-xs">€{costPerHour}/h</span>
|
<span className="px-2 py-1 bg-amber-100 rounded text-amber-800 text-xs">€{costPerHour}/h</span>
|
||||||
<span className="text-slate-400">×</span>
|
<span className="text-slate-400">×</span>
|
||||||
<span className="px-2 py-1 bg-gray-100 rounded text-gray-800 text-xs">Volumen</span>
|
<span className="px-2 py-1 bg-gray-100 rounded text-gray-800 text-xs">{t('methodology.cpi.volume')}</span>
|
||||||
<span className="text-slate-400">÷</span>
|
<span className="text-slate-400">÷</span>
|
||||||
<span className="px-2 py-1 bg-purple-100 rounded text-purple-800 text-xs">{(effectiveProductivity * 100).toFixed(0)}%</span>
|
<span className="px-2 py-1 bg-purple-100 rounded text-purple-800 text-xs">{(effectiveProductivity * 100).toFixed(0)}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-slate-600">
|
<p className="text-xs text-slate-600">
|
||||||
El <strong>AHT</strong> está en segundos, se convierte a horas dividiendo por 3600.
|
{t('methodology.cpi.ahtExplanation')}
|
||||||
Incluye todas las interacciones que generan coste (noise + zombie + válidas).
|
|
||||||
Solo se excluyen los abandonos porque no consumen tiempo de agente.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Componentes del coste horario */}
|
{/* Componentes del coste horario */}
|
||||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="text-sm font-semibold text-amber-800">Coste por Hora del Agente (Fully Loaded)</div>
|
<div className="text-sm font-semibold text-amber-800">{t('methodology.cpi.hourlyRate')}</div>
|
||||||
<span className="text-xs bg-amber-200 text-amber-800 px-2 py-0.5 rounded-full font-medium">
|
<span className="text-xs bg-amber-200 text-amber-800 px-2 py-0.5 rounded-full font-medium">
|
||||||
Valor introducido: €{costPerHour.toFixed(2)}/h
|
{t('methodology.cpi.configuredValue', { value: costPerHour.toFixed(2) })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-amber-700 mb-3">
|
<p className="text-xs text-amber-700 mb-3">
|
||||||
Este valor fue configurado en la pantalla de entrada de datos y debe incluir todos los costes asociados al agente:
|
{t('methodology.cpi.includesAllCosts')}
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span className="text-amber-700">Salario bruto del agente</span>
|
<span className="text-amber-700">{t('methodology.cpi.cost1')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span className="text-amber-700">Costes de seguridad social</span>
|
<span className="text-amber-700">{t('methodology.cpi.cost2')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span className="text-amber-700">Licencias de software</span>
|
<span className="text-amber-700">{t('methodology.cpi.cost3')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span className="text-amber-700">Infraestructura y puesto</span>
|
<span className="text-amber-700">{t('methodology.cpi.cost4')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span className="text-amber-700">Supervisión y QA</span>
|
<span className="text-amber-700">{t('methodology.cpi.cost5')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span className="text-amber-700">Formación y overhead</span>
|
<span className="text-amber-700">{t('methodology.cpi.cost6')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-amber-600 mt-3 italic">
|
<p className="text-[10px] text-amber-600 mt-3 italic">
|
||||||
💡 Si necesita ajustar este valor, puede volver a la pantalla de entrada de datos y modificarlo.
|
💡 {t('methodology.cpi.adjustNote')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BeforeAfterSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
function BeforeAfterSection({ kpis, t }: { kpis: DataSummary['kpis']; t: any }) {
|
||||||
const rows = [
|
const rows = [
|
||||||
{
|
{
|
||||||
metric: 'FCR',
|
metric: t('methodology.impact.fcr'),
|
||||||
tradicional: `${kpis.fcrTecnico}%`,
|
tradicional: `${kpis.fcrTecnico}%`,
|
||||||
beyond: `${kpis.fcrReal}%`,
|
beyond: `${kpis.fcrReal}%`,
|
||||||
beyondClass: 'text-red-600',
|
beyondClass: 'text-red-600',
|
||||||
impacto: 'Revela demanda fallida oculta'
|
impacto: t('methodology.impact.revealsDemand')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metric: 'Abandono',
|
metric: t('methodology.impact.abandon'),
|
||||||
tradicional: `~${kpis.abandonoTradicional}%`,
|
tradicional: `~${kpis.abandonoTradicional}%`,
|
||||||
beyond: `${kpis.abandonoReal.toFixed(1)}%`,
|
beyond: `${kpis.abandonoReal.toFixed(1)}%`,
|
||||||
beyondClass: 'text-yellow-600',
|
beyondClass: 'text-yellow-600',
|
||||||
impacto: 'Detecta frustración cliente real'
|
impacto: t('methodology.impact.detectsFrustration')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metric: 'Skills',
|
metric: t('methodology.impact.skills'),
|
||||||
tradicional: `${kpis.skillsTecnicos} técnicos`,
|
tradicional: t('methodology.impact.technicalSkills', { count: kpis.skillsTecnicos }),
|
||||||
beyond: `${kpis.skillsNegocio} líneas negocio`,
|
beyond: t('methodology.impact.businessLines', { count: kpis.skillsNegocio }),
|
||||||
beyondClass: 'text-blue-600',
|
beyondClass: 'text-blue-600',
|
||||||
impacto: 'Visión ejecutiva accionable'
|
impacto: t('methodology.impact.executiveVision')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metric: 'AHT',
|
metric: t('methodology.impact.aht'),
|
||||||
tradicional: 'Distorsionado',
|
tradicional: t('methodology.impact.distorted'),
|
||||||
beyond: 'Limpio',
|
beyond: t('methodology.impact.clean'),
|
||||||
beyondClass: 'text-green-600',
|
beyondClass: 'text-green-600',
|
||||||
impacto: 'KPIs reflejan desempeño real'
|
impacto: t('methodology.impact.reflectsPerformance')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -445,17 +441,17 @@ function BeforeAfterSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<ArrowLeftRight className="w-5 h-5 text-teal-600" />
|
<ArrowLeftRight className="w-5 h-5 text-teal-600" />
|
||||||
Impacto de la Transformación
|
{t('methodology.impact.title')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="overflow-hidden rounded-lg border border-slate-200">
|
<div className="overflow-hidden rounded-lg border border-slate-200">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left font-semibold">Métrica</th>
|
<th className="px-3 py-2 text-left font-semibold">{t('methodology.impact.metric')}</th>
|
||||||
<th className="px-3 py-2 text-center font-semibold">Visión Tradicional</th>
|
<th className="px-3 py-2 text-center font-semibold">{t('methodology.impact.traditional')}</th>
|
||||||
<th className="px-3 py-2 text-center font-semibold">Visión Beyond</th>
|
<th className="px-3 py-2 text-center font-semibold">{t('methodology.impact.beyond')}</th>
|
||||||
<th className="px-3 py-2 text-left font-semibold">Impacto</th>
|
<th className="px-3 py-2 text-left font-semibold">{t('methodology.impact.impact')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-100">
|
<tbody className="divide-y divide-slate-100">
|
||||||
@@ -473,53 +469,52 @@ function BeforeAfterSection({ kpis }: { kpis: DataSummary['kpis'] }) {
|
|||||||
|
|
||||||
<div className="mt-4 p-3 bg-indigo-50 border border-indigo-200 rounded-lg">
|
<div className="mt-4 p-3 bg-indigo-50 border border-indigo-200 rounded-lg">
|
||||||
<p className="text-xs text-indigo-800">
|
<p className="text-xs text-indigo-800">
|
||||||
<strong>💡 Sin esta transformación,</strong> las decisiones de automatización
|
<strong>💡 {t('methodology.impact.withoutTransformation')}</strong> {t('methodology.impact.wrongInvestments')}
|
||||||
se basarían en datos incorrectos, generando inversiones en los procesos equivocados.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SkillsMappingSection({ numSkillsNegocio }: { numSkillsNegocio: number }) {
|
function SkillsMappingSection({ numSkillsNegocio, t }: { numSkillsNegocio: number; t: any }) {
|
||||||
const mappings = [
|
const mappings = [
|
||||||
{
|
{
|
||||||
lineaNegocio: 'Baggage & Handling',
|
lineaNegocio: t('methodology.skillMapping.baggage'),
|
||||||
keywords: 'HANDLING, EQUIPAJE, AHL (Lost & Found), DPR (Daños)',
|
keywords: 'HANDLING, EQUIPAJE, AHL (Lost & Found), DPR (Daños)',
|
||||||
color: 'bg-amber-100 text-amber-800'
|
color: 'bg-amber-100 text-amber-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lineaNegocio: 'Sales & Booking',
|
lineaNegocio: t('methodology.skillMapping.sales'),
|
||||||
keywords: 'COMPRA, VENTA, RESERVA, PAGO',
|
keywords: 'COMPRA, VENTA, RESERVA, PAGO',
|
||||||
color: 'bg-blue-100 text-blue-800'
|
color: 'bg-blue-100 text-blue-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lineaNegocio: 'Loyalty (SUMA)',
|
lineaNegocio: t('methodology.skillMapping.loyalty'),
|
||||||
keywords: 'SUMA (Programa de Fidelización)',
|
keywords: 'SUMA (Programa de Fidelización)',
|
||||||
color: 'bg-purple-100 text-purple-800'
|
color: 'bg-purple-100 text-purple-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lineaNegocio: 'B2B & Agencies',
|
lineaNegocio: t('methodology.skillMapping.b2b'),
|
||||||
keywords: 'AGENCIAS, AAVV, EMPRESAS, AVORIS, TOUROPERACION',
|
keywords: 'AGENCIAS, AAVV, EMPRESAS, AVORIS, TOUROPERACION',
|
||||||
color: 'bg-cyan-100 text-cyan-800'
|
color: 'bg-cyan-100 text-cyan-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lineaNegocio: 'Changes & Post-Sales',
|
lineaNegocio: t('methodology.skillMapping.changes'),
|
||||||
keywords: 'MODIFICACION, CAMBIO, POSTVENTA, REFUND, REEMBOLSO',
|
keywords: 'MODIFICACION, CAMBIO, POSTVENTA, REFUND, REEMBOLSO',
|
||||||
color: 'bg-orange-100 text-orange-800'
|
color: 'bg-orange-100 text-orange-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lineaNegocio: 'Digital Support',
|
lineaNegocio: t('methodology.skillMapping.digital'),
|
||||||
keywords: 'WEB (Soporte a navegación)',
|
keywords: 'WEB (Soporte a navegación)',
|
||||||
color: 'bg-indigo-100 text-indigo-800'
|
color: 'bg-indigo-100 text-indigo-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lineaNegocio: 'Customer Service',
|
lineaNegocio: t('methodology.skillMapping.customer'),
|
||||||
keywords: 'ATENCION, INFO, OTROS, GENERAL, PREMIUM',
|
keywords: 'ATENCION, INFO, OTROS, GENERAL, PREMIUM',
|
||||||
color: 'bg-green-100 text-green-800'
|
color: 'bg-green-100 text-green-800'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lineaNegocio: 'Internal / Backoffice',
|
lineaNegocio: t('methodology.skillMapping.internal'),
|
||||||
keywords: 'COORD, BO_, HELPDESK, BACKOFFICE',
|
keywords: 'COORD, BO_, HELPDESK, BACKOFFICE',
|
||||||
color: 'bg-slate-100 text-slate-800'
|
color: 'bg-slate-100 text-slate-800'
|
||||||
}
|
}
|
||||||
@@ -529,13 +524,13 @@ function SkillsMappingSection({ numSkillsNegocio }: { numSkillsNegocio: number }
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<Layers className="w-5 h-5 text-violet-600" />
|
<Layers className="w-5 h-5 text-violet-600" />
|
||||||
Mapeo de Skills a Líneas de Negocio
|
{t('methodology.skillMapping.title')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Resumen del mapeo */}
|
{/* Resumen del mapeo */}
|
||||||
<div className="bg-violet-50 border border-violet-200 rounded-lg p-4 mb-4">
|
<div className="bg-violet-50 border border-violet-200 rounded-lg p-4 mb-4">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium text-violet-800">Simplificación aplicada</span>
|
<span className="text-sm font-medium text-violet-800">{t('methodology.skillMapping.simplificationApplied')}</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-2xl font-bold text-violet-600">980</span>
|
<span className="text-2xl font-bold text-violet-600">980</span>
|
||||||
<ArrowRight className="w-4 h-4 text-violet-400" />
|
<ArrowRight className="w-4 h-4 text-violet-400" />
|
||||||
@@ -543,8 +538,7 @@ function SkillsMappingSection({ numSkillsNegocio }: { numSkillsNegocio: number }
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-violet-700">
|
<p className="text-xs text-violet-700">
|
||||||
Se redujo la complejidad de <strong>980 skills técnicos</strong> a <strong>{numSkillsNegocio} Líneas de Negocio</strong>.
|
{t('methodology.skillMapping.reductionDesc', { count: numSkillsNegocio })}
|
||||||
Esta simplificación es vital para la visualización ejecutiva y la toma de decisiones estratégicas.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -553,8 +547,8 @@ function SkillsMappingSection({ numSkillsNegocio }: { numSkillsNegocio: number }
|
|||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left font-semibold">Línea de Negocio</th>
|
<th className="px-3 py-2 text-left font-semibold">{t('methodology.skillMapping.businessLine')}</th>
|
||||||
<th className="px-3 py-2 text-left font-semibold">Keywords Detectadas (Lógica Fuzzy)</th>
|
<th className="px-3 py-2 text-left font-semibold">{t('methodology.skillMapping.keywords')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-100">
|
<tbody className="divide-y divide-slate-100">
|
||||||
@@ -575,34 +569,33 @@ function SkillsMappingSection({ numSkillsNegocio }: { numSkillsNegocio: number }
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-gray-500 mt-3 italic">
|
<p className="text-xs text-gray-500 mt-3 italic">
|
||||||
💡 El mapeo utiliza lógica fuzzy para clasificar automáticamente cada skill técnico
|
💡 {t('methodology.skillMapping.fuzzyLogicNote')}
|
||||||
según las keywords detectadas en su nombre. Los skills no clasificados se asignan a "Customer Service".
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GuaranteesSection() {
|
function GuaranteesSection({ t }: { t: any }) {
|
||||||
const guarantees = [
|
const guarantees = [
|
||||||
{
|
{
|
||||||
icon: '✓',
|
icon: '✓',
|
||||||
title: '100% Trazabilidad',
|
title: t('methodology.quality.traceability'),
|
||||||
desc: 'Todos los registros conservados (soft delete)'
|
desc: t('methodology.quality.traceabilityDesc')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '✓',
|
icon: '✓',
|
||||||
title: 'Fórmulas Documentadas',
|
title: t('methodology.quality.formulas'),
|
||||||
desc: 'Cada KPI tiene metodología auditable'
|
desc: t('methodology.quality.formulasDesc')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '✓',
|
icon: '✓',
|
||||||
title: 'Reconciliación Financiera',
|
title: t('methodology.quality.reconciliation'),
|
||||||
desc: 'Dataset original disponible para auditoría'
|
desc: t('methodology.quality.reconciliationDesc')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '✓',
|
icon: '✓',
|
||||||
title: 'Metodología Replicable',
|
title: t('methodology.quality.replicable'),
|
||||||
desc: 'Proceso reproducible para actualizaciones'
|
desc: t('methodology.quality.replicableDesc')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -610,7 +603,7 @@ function GuaranteesSection() {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<BadgeCheck className="w-5 h-5 text-green-600" />
|
<BadgeCheck className="w-5 h-5 text-green-600" />
|
||||||
Garantías de Calidad
|
{t('methodology.quality.title')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
@@ -631,6 +624,8 @@ function GuaranteesSection() {
|
|||||||
// ========== COMPONENTE PRINCIPAL ==========
|
// ========== COMPONENTE PRINCIPAL ==========
|
||||||
|
|
||||||
export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerProps) {
|
export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Calcular datos del resumen desde AnalysisData
|
// Calcular datos del resumen desde AnalysisData
|
||||||
const totalRegistros = data.heatmapData?.reduce((sum, h) => sum + h.volume, 0) || 0;
|
const totalRegistros = data.heatmapData?.reduce((sum, h) => sum + h.volume, 0) || 0;
|
||||||
const totalCost = data.heatmapData?.reduce((sum, h) => sum + (h.annual_cost || 0), 0) || 0;
|
const totalCost = data.heatmapData?.reduce((sum, h) => sum + (h.annual_cost || 0), 0) || 0;
|
||||||
@@ -665,8 +660,8 @@ export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerPr
|
|||||||
mesesHistorico,
|
mesesHistorico,
|
||||||
periodo: data.dateRange
|
periodo: data.dateRange
|
||||||
? `${data.dateRange.min} - ${data.dateRange.max}`
|
? `${data.dateRange.min} - ${data.dateRange.max}`
|
||||||
: 'Enero - Diciembre 2025',
|
: t('methodology.defaultPeriod'),
|
||||||
fuente: data.source === 'backend' ? 'Genesys Cloud CX' : 'Dataset cargado',
|
fuente: data.source === 'backend' ? t('methodology.sourceGenesys') : t('methodology.sourceDataset'),
|
||||||
taxonomia: {
|
taxonomia: {
|
||||||
valid: 94.2,
|
valid: 94.2,
|
||||||
noise: 3.1,
|
noise: 3.1,
|
||||||
@@ -686,17 +681,14 @@ export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerPr
|
|||||||
|
|
||||||
const handleDownloadPDF = () => {
|
const handleDownloadPDF = () => {
|
||||||
// Por ahora, abrir una URL placeholder o mostrar alert
|
// Por ahora, abrir una URL placeholder o mostrar alert
|
||||||
alert('Funcionalidad de descarga PDF en desarrollo. El documento estará disponible próximamente.');
|
alert(t('methodology.pdfDevelopment'));
|
||||||
// En producción: window.open('/documents/Beyond_Diagnostic_Protocolo_Datos.pdf', '_blank');
|
// En producción: window.open('/documents/Beyond_Diagnostic_Protocolo_Datos.pdf', '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (): string => {
|
const formatDate = (): string => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const months = [
|
const monthKey = `methodology.months.${['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'][now.getMonth()]}`;
|
||||||
'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
return `${t(monthKey)} ${now.getFullYear()}`;
|
||||||
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'
|
|
||||||
];
|
|
||||||
return `${months[now.getMonth()]} ${now.getFullYear()}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -724,7 +716,7 @@ export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerPr
|
|||||||
<div className="sticky top-0 bg-white border-b border-slate-200 px-6 py-4 flex justify-between items-center flex-shrink-0">
|
<div className="sticky top-0 bg-white border-b border-slate-200 px-6 py-4 flex justify-between items-center flex-shrink-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ShieldCheck className="text-green-600 w-6 h-6" />
|
<ShieldCheck className="text-green-600 w-6 h-6" />
|
||||||
<h2 className="text-lg font-bold text-slate-800">Metodología de Transformación de Datos</h2>
|
<h2 className="text-lg font-bold text-slate-800">{t('methodology.fullTitle')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -736,18 +728,19 @@ export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerPr
|
|||||||
|
|
||||||
{/* Body - Scrollable */}
|
{/* Body - Scrollable */}
|
||||||
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
||||||
<DataSummarySection data={dataSummary} />
|
<DataSummarySection data={dataSummary} t={t} />
|
||||||
<PipelineSection />
|
<PipelineSection t={t} />
|
||||||
<SkillsMappingSection numSkillsNegocio={dataSummary.kpis.skillsNegocio} />
|
<SkillsMappingSection numSkillsNegocio={dataSummary.kpis.skillsNegocio} t={t} />
|
||||||
<TaxonomySection data={dataSummary.taxonomia} />
|
<TaxonomySection data={dataSummary.taxonomia} t={t} />
|
||||||
<KPIRedefinitionSection kpis={dataSummary.kpis} />
|
<KPIRedefinitionSection kpis={dataSummary.kpis} t={t} />
|
||||||
<CPICalculationSection
|
<CPICalculationSection
|
||||||
totalCost={totalCost}
|
totalCost={totalCost}
|
||||||
totalVolume={totalCostVolume}
|
totalVolume={totalCostVolume}
|
||||||
costPerHour={data.staticConfig?.cost_per_hour || 20}
|
costPerHour={data.staticConfig?.cost_per_hour || 20}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
<BeforeAfterSection kpis={dataSummary.kpis} />
|
<BeforeAfterSection kpis={dataSummary.kpis} t={t} />
|
||||||
<GuaranteesSection />
|
<GuaranteesSection t={t} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
@@ -758,10 +751,10 @@ export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerPr
|
|||||||
className="flex items-center gap-2 px-4 py-2 bg-[#6D84E3] text-white rounded-lg hover:bg-[#5A70C7] transition-colors text-sm font-medium"
|
className="flex items-center gap-2 px-4 py-2 bg-[#6D84E3] text-white rounded-lg hover:bg-[#5A70C7] transition-colors text-sm font-medium"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4" />
|
<Download className="w-4 h-4" />
|
||||||
Descargar Protocolo Completo (PDF)
|
{t('methodology.downloadProtocol')}
|
||||||
</button>
|
</button>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
Beyond Diagnosis - Data Strategy Unit │ Certificado: {formatDate()}
|
{t('methodology.certificate', { date: formatDate() })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Check, Package, Upload, BarChart3 } from 'lucide-react';
|
import { Check, Package, Upload, BarChart3 } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
interface Step {
|
interface Step {
|
||||||
@@ -13,13 +14,15 @@ interface ProgressStepperProps {
|
|||||||
currentStep: number;
|
currentStep: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const steps: Step[] = [
|
|
||||||
{ id: 1, label: 'Seleccionar Tier', icon: Package },
|
|
||||||
{ id: 2, label: 'Subir Datos', icon: Upload },
|
|
||||||
{ id: 3, label: 'Ver Resultados', icon: BarChart3 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const ProgressStepper: React.FC<ProgressStepperProps> = ({ currentStep }) => {
|
const ProgressStepper: React.FC<ProgressStepperProps> = ({ currentStep }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const steps: Step[] = [
|
||||||
|
{ id: 1, label: t('stepper.selectTier'), icon: Package },
|
||||||
|
{ id: 2, label: t('stepper.uploadData'), icon: Upload },
|
||||||
|
{ id: 3, label: t('stepper.viewResults'), icon: BarChart3 },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-3xl mx-auto mb-8">
|
<div className="w-full max-w-3xl mx-auto mb-8">
|
||||||
<div className="relative flex items-center justify-between">
|
<div className="relative flex items-center justify-between">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { Check, Star, Award, Medal, ChevronDown, ChevronUp } from 'lucide-react';
|
import { Check, Star, Award, Medal, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { TierKey } from '../types';
|
import { TierKey } from '../types';
|
||||||
import { TIERS } from '../constants';
|
import { TIERS } from '../constants';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -26,6 +27,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
selectedTier,
|
selectedTier,
|
||||||
onSelectTier,
|
onSelectTier,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [showComparison, setShowComparison] = useState(false);
|
const [showComparison, setShowComparison] = useState(false);
|
||||||
|
|
||||||
const tiers: TierKey[] = ['gold', 'silver', 'bronze'];
|
const tiers: TierKey[] = ['gold', 'silver', 'bronze'];
|
||||||
@@ -63,7 +65,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
transition={{ delay: 0.5, type: 'spring' }}
|
transition={{ delay: 0.5, type: 'spring' }}
|
||||||
className="absolute top-4 -left-8 bg-gradient-to-r from-blue-600 to-blue-700 text-white text-xs font-bold px-10 py-1 rotate-[-45deg] shadow-lg z-10"
|
className="absolute top-4 -left-8 bg-gradient-to-r from-blue-600 to-blue-700 text-white text-xs font-bold px-10 py-1 rotate-[-45deg] shadow-lg z-10"
|
||||||
>
|
>
|
||||||
POPULAR
|
{t('tierSelector.popular')}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -107,7 +109,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
<span className="text-4xl font-bold text-slate-900">
|
<span className="text-4xl font-bold text-slate-900">
|
||||||
€{tier.price.toLocaleString('es-ES')}
|
€{tier.price.toLocaleString('es-ES')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-500 text-sm ml-1">one-time</span>
|
<span className="text-slate-500 text-sm ml-1">{t('tierSelector.oneTime')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
@@ -142,7 +144,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isSelected ? 'Seleccionado' : 'Seleccionar'}
|
{isSelected ? t('tierSelector.selected') : t('tierSelector.select')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -161,12 +163,12 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
{showComparison ? (
|
{showComparison ? (
|
||||||
<>
|
<>
|
||||||
<ChevronUp size={20} />
|
<ChevronUp size={20} />
|
||||||
Ocultar Comparación
|
{t('tierSelector.hideComparison')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ChevronDown size={20} />
|
<ChevronDown size={20} />
|
||||||
Ver Comparación Detallada
|
{t('tierSelector.viewComparison')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
@@ -184,14 +186,14 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
>
|
>
|
||||||
<div className="bg-white rounded-xl border border-slate-200 shadow-lg p-6">
|
<div className="bg-white rounded-xl border border-slate-200 shadow-lg p-6">
|
||||||
<h4 className="text-lg font-bold text-slate-900 mb-4">
|
<h4 className="text-lg font-bold text-slate-900 mb-4">
|
||||||
Comparación de Tiers
|
{t('tierSelector.comparison')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="bg-slate-50">
|
<thead className="bg-slate-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left p-3 font-semibold text-slate-700">
|
<th className="text-left p-3 font-semibold text-slate-700">
|
||||||
Característica
|
{t('tierSelector.feature')}
|
||||||
</th>
|
</th>
|
||||||
{tiers.map((tierKey) => (
|
{tiers.map((tierKey) => (
|
||||||
<th
|
<th
|
||||||
@@ -205,7 +207,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr className="border-t border-slate-200">
|
<tr className="border-t border-slate-200">
|
||||||
<td className="p-3 text-slate-700">Precio</td>
|
<td className="p-3 text-slate-700">{t('tierSelector.price')}</td>
|
||||||
{tiers.map((tierKey) => (
|
{tiers.map((tierKey) => (
|
||||||
<td key={tierKey} className="p-3 text-center font-semibold">
|
<td key={tierKey} className="p-3 text-center font-semibold">
|
||||||
€{TIERS[tierKey].price.toLocaleString('es-ES')}
|
€{TIERS[tierKey].price.toLocaleString('es-ES')}
|
||||||
@@ -213,15 +215,15 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="border-t border-slate-200 bg-slate-50">
|
<tr className="border-t border-slate-200 bg-slate-50">
|
||||||
<td className="p-3 text-slate-700">Tiempo de Entrega</td>
|
<td className="p-3 text-slate-700">{t('tierSelector.deliveryTime')}</td>
|
||||||
{tiers.map((tierKey) => (
|
{tiers.map((tierKey) => (
|
||||||
<td key={tierKey} className="p-3 text-center">
|
<td key={tierKey} className="p-3 text-center">
|
||||||
{tierKey === 'gold' ? '7 días' : tierKey === 'silver' ? '10 días' : '14 días'}
|
{tierKey === 'gold' ? t('tierSelector.days7') : tierKey === 'silver' ? t('tierSelector.days10') : t('tierSelector.days14')}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="border-t border-slate-200">
|
<tr className="border-t border-slate-200">
|
||||||
<td className="p-3 text-slate-700">Análisis de 8 Dimensiones</td>
|
<td className="p-3 text-slate-700">{t('tierSelector.dimensions8')}</td>
|
||||||
{tiers.map((tierKey) => (
|
{tiers.map((tierKey) => (
|
||||||
<td key={tierKey} className="p-3 text-center">
|
<td key={tierKey} className="p-3 text-center">
|
||||||
<Check className="text-green-500 mx-auto" size={20} />
|
<Check className="text-green-500 mx-auto" size={20} />
|
||||||
@@ -229,7 +231,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="border-t border-slate-200 bg-slate-50">
|
<tr className="border-t border-slate-200 bg-slate-50">
|
||||||
<td className="p-3 text-slate-700">Roadmap Ejecutable</td>
|
<td className="p-3 text-slate-700">{t('tierSelector.roadmap')}</td>
|
||||||
{tiers.map((tierKey) => (
|
{tiers.map((tierKey) => (
|
||||||
<td key={tierKey} className="p-3 text-center">
|
<td key={tierKey} className="p-3 text-center">
|
||||||
<Check className="text-green-500 mx-auto" size={20} />
|
<Check className="text-green-500 mx-auto" size={20} />
|
||||||
@@ -237,7 +239,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="border-t border-slate-200">
|
<tr className="border-t border-slate-200">
|
||||||
<td className="p-3 text-slate-700">Modelo Económico ROI</td>
|
<td className="p-3 text-slate-700">{t('tierSelector.economicModel')}</td>
|
||||||
{tiers.map((tierKey) => (
|
{tiers.map((tierKey) => (
|
||||||
<td key={tierKey} className="p-3 text-center">
|
<td key={tierKey} className="p-3 text-center">
|
||||||
{tierKey !== 'bronze' ? (
|
{tierKey !== 'bronze' ? (
|
||||||
@@ -249,7 +251,7 @@ const TierSelectorEnhanced: React.FC<TierSelectorEnhancedProps> = ({
|
|||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="border-t border-slate-200 bg-slate-50">
|
<tr className="border-t border-slate-200 bg-slate-50">
|
||||||
<td className="p-3 text-slate-700">Sesión de Presentación</td>
|
<td className="p-3 text-slate-700">{t('tierSelector.presentation')}</td>
|
||||||
{tiers.map((tierKey) => (
|
{tiers.map((tierKey) => (
|
||||||
<td key={tierKey} className="p-3 text-center">
|
<td key={tierKey} className="p-3 text-center">
|
||||||
{tierKey === 'gold' ? (
|
{tierKey === 'gold' ? (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ChevronRight, TrendingUp, TrendingDown, Minus, AlertTriangle, Lightbulb, DollarSign, Clock } from 'lucide-react';
|
import { ChevronRight, TrendingUp, TrendingDown, Minus, AlertTriangle, Lightbulb, DollarSign, Clock } from 'lucide-react';
|
||||||
import type { AnalysisData, DimensionAnalysis, Finding, Recommendation, HeatmapDataPoint } from '../../types';
|
import type { AnalysisData, DimensionAnalysis, Finding, Recommendation, HeatmapDataPoint } from '../../types';
|
||||||
@@ -42,6 +43,7 @@ function generateCausalAnalysis(
|
|||||||
dimension: DimensionAnalysis,
|
dimension: DimensionAnalysis,
|
||||||
heatmapData: HeatmapDataPoint[],
|
heatmapData: HeatmapDataPoint[],
|
||||||
economicModel: { currentAnnualCost: number },
|
economicModel: { currentAnnualCost: number },
|
||||||
|
t: (key: string, options?: any) => string,
|
||||||
staticConfig?: { cost_per_hour: number },
|
staticConfig?: { cost_per_hour: number },
|
||||||
dateRange?: { min: string; max: string }
|
dateRange?: { min: string; max: string }
|
||||||
): CausalAnalysisExtended[] {
|
): CausalAnalysisExtended[] {
|
||||||
@@ -129,28 +131,29 @@ function generateCausalAnalysis(
|
|||||||
// Estimar ahorro con solución Copilot (25-30% reducción AHT)
|
// Estimar ahorro con solución Copilot (25-30% reducción AHT)
|
||||||
const copilotSavings = Math.round(ahtExcessCost * 0.28);
|
const copilotSavings = Math.round(ahtExcessCost * 0.28);
|
||||||
|
|
||||||
// Causa basada en AHT elevado
|
const ahtFormatted = `${Math.floor(p50Aht / 60)}:${String(Math.round(p50Aht) % 60).padStart(2, '0')}`;
|
||||||
const cause = 'Agentes dedican tiempo excesivo a búsqueda manual de información, navegación entre sistemas y tareas repetitivas.';
|
|
||||||
|
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `AHT elevado: P50 ${Math.floor(p50Aht / 60)}:${String(Math.round(p50Aht) % 60).padStart(2, '0')} (benchmark: 5:00)`,
|
finding: t('dimensionAnalysis.operationalEfficiency.highAHTFinding', { aht: ahtFormatted }),
|
||||||
probableCause: cause,
|
probableCause: t('dimensionAnalysis.operationalEfficiency.highAHTCause'),
|
||||||
economicImpact: ahtExcessCost,
|
economicImpact: ahtExcessCost,
|
||||||
impactFormula: `${excessHours.toLocaleString()}h × €${HOURLY_COST}/h`,
|
impactFormula: `${excessHours.toLocaleString()}h × €${HOURLY_COST}/h`,
|
||||||
timeSavings: `${excessHours.toLocaleString()} horas/año en exceso de AHT`,
|
timeSavings: `${excessHours.toLocaleString()} horas/año en exceso de AHT`,
|
||||||
recommendation: `Desplegar Copilot IA para agentes: (1) Auto-búsqueda en KB; (2) Sugerencias contextuales en tiempo real; (3) Scripts guiados para casos frecuentes. Reducción esperada: 20-30% AHT. Ahorro: ${formatCurrency(copilotSavings)}/año.`,
|
recommendation: t('dimensionAnalysis.operationalEfficiency.highAHTRecommendation', { savings: formatCurrency(copilotSavings) }),
|
||||||
severity: p50Aht > 420 ? 'critical' : 'warning',
|
severity: p50Aht > 420 ? 'critical' : 'warning',
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// AHT dentro de benchmark - mostrar estado positivo
|
// AHT dentro de benchmark - mostrar estado positivo
|
||||||
|
const ahtFormatted = `${Math.floor(p50Aht / 60)}:${String(Math.round(p50Aht) % 60).padStart(2, '0')}`;
|
||||||
|
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `AHT dentro de benchmark: P50 ${Math.floor(p50Aht / 60)}:${String(Math.round(p50Aht) % 60).padStart(2, '0')} (benchmark: 5:00)`,
|
finding: t('dimensionAnalysis.operationalEfficiency.goodAHTFinding', { aht: ahtFormatted }),
|
||||||
probableCause: 'Tiempos de gestión eficientes. Procesos operativos optimizados.',
|
probableCause: t('dimensionAnalysis.operationalEfficiency.goodAHTCause'),
|
||||||
economicImpact: 0,
|
economicImpact: 0,
|
||||||
impactFormula: 'Sin exceso de coste por AHT',
|
impactFormula: t('dimensionAnalysis.operationalEfficiency.goodAHTImpact'),
|
||||||
timeSavings: 'Operación eficiente',
|
timeSavings: t('dimensionAnalysis.operationalEfficiency.goodAHTTimeSavings'),
|
||||||
recommendation: 'Mantener nivel actual. Considerar Copilot para mejora continua y reducción adicional de tiempos en casos complejos.',
|
recommendation: t('dimensionAnalysis.operationalEfficiency.goodAHTRecommendation'),
|
||||||
severity: 'info',
|
severity: 'info',
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
@@ -176,30 +179,42 @@ function generateCausalAnalysis(
|
|||||||
let effCause = '';
|
let effCause = '';
|
||||||
if (avgFCR < 70) {
|
if (avgFCR < 70) {
|
||||||
effCause = skillsLowFCR.length > 0
|
effCause = skillsLowFCR.length > 0
|
||||||
? `Alta tasa de transferencias (${avgTransferRate.toFixed(0)}%) indica falta de herramientas o autoridad. Crítico en ${skillsLowFCR.slice(0, 2).map(s => s.skill).join(', ')}.`
|
? t('dimensionAnalysis.effectiveness.criticalCause', {
|
||||||
: `Transferencias elevadas (${avgTransferRate.toFixed(0)}%): agentes sin información contextual o sin autoridad para resolver.`;
|
transfer: avgTransferRate.toFixed(0),
|
||||||
|
skills: skillsLowFCR.slice(0, 2).map(s => s.skill).join(', ')
|
||||||
|
})
|
||||||
|
: t('dimensionAnalysis.effectiveness.criticalCauseGeneric', { transfer: avgTransferRate.toFixed(0) });
|
||||||
} else if (avgFCR < 85) {
|
} else if (avgFCR < 85) {
|
||||||
effCause = `Transferencias del ${avgTransferRate.toFixed(0)}% indican oportunidad de mejora con asistencia IA para casos complejos.`;
|
effCause = t('dimensionAnalysis.effectiveness.warningCause', { transfer: avgTransferRate.toFixed(0) });
|
||||||
} else {
|
} else {
|
||||||
effCause = `FCR Técnico en nivel óptimo. Transferencias del ${avgTransferRate.toFixed(0)}% principalmente en casos que requieren escalación legítima.`;
|
effCause = t('dimensionAnalysis.effectiveness.goodCause', { transfer: avgTransferRate.toFixed(0) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construir recomendación
|
// Construir recomendación
|
||||||
let effRecommendation = '';
|
let effRecommendation = '';
|
||||||
if (avgFCR < 70) {
|
if (avgFCR < 70) {
|
||||||
effRecommendation = `Desplegar Knowledge Copilot con búsqueda inteligente en KB + Guided Resolution Copilot para casos complejos. Objetivo: FCR >85%. Potencial ahorro: ${formatCurrency(potentialSavingsEff)}/año.`;
|
effRecommendation = t('dimensionAnalysis.effectiveness.criticalRecommendation', { savings: formatCurrency(potentialSavingsEff) });
|
||||||
} else if (avgFCR < 85) {
|
} else if (avgFCR < 85) {
|
||||||
effRecommendation = `Implementar Copilot de asistencia en tiempo real: sugerencias contextuales + conexión con expertos virtuales para reducir transferencias. Objetivo: FCR >90%.`;
|
effRecommendation = t('dimensionAnalysis.effectiveness.warningRecommendation');
|
||||||
} else {
|
} else {
|
||||||
effRecommendation = `Mantener nivel actual. Considerar IA para análisis de transferencias legítimas y optimización de enrutamiento predictivo.`;
|
effRecommendation = t('dimensionAnalysis.effectiveness.goodRecommendation');
|
||||||
}
|
}
|
||||||
|
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `FCR Técnico: ${avgFCR.toFixed(0)}% | Transferencias: ${avgTransferRate.toFixed(0)}% (benchmark: FCR >85%, Transfer <10%)`,
|
finding: t('dimensionAnalysis.effectiveness.finding', {
|
||||||
|
fcr: avgFCR.toFixed(0),
|
||||||
|
transfer: avgTransferRate.toFixed(0)
|
||||||
|
}),
|
||||||
probableCause: effCause,
|
probableCause: effCause,
|
||||||
economicImpact: transferCostTotal,
|
economicImpact: transferCostTotal,
|
||||||
impactFormula: `${transferCount.toLocaleString()} transferencias/año × €${CPI_TCO}/int × 50% coste adicional`,
|
impactFormula: t('dimensionAnalysis.effectiveness.impactFormula', {
|
||||||
timeSavings: `${transferCount.toLocaleString()} transferencias/año (${avgTransferRate.toFixed(0)}% del volumen)`,
|
count: transferCount.toLocaleString(),
|
||||||
|
cpi: CPI_TCO
|
||||||
|
}),
|
||||||
|
timeSavings: t('dimensionAnalysis.effectiveness.timeSavings', {
|
||||||
|
count: transferCount.toLocaleString(),
|
||||||
|
pct: avgTransferRate.toFixed(0)
|
||||||
|
}),
|
||||||
recommendation: effRecommendation,
|
recommendation: effRecommendation,
|
||||||
severity: effSeverity,
|
severity: effSeverity,
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
@@ -215,12 +230,25 @@ function generateCausalAnalysis(
|
|||||||
const deflectionPotential = Math.round(annualTopSkillVolume * CPI_TCO * 0.20);
|
const deflectionPotential = Math.round(annualTopSkillVolume * CPI_TCO * 0.20);
|
||||||
const interactionsDeflectable = Math.round(annualTopSkillVolume * 0.20);
|
const interactionsDeflectable = Math.round(annualTopSkillVolume * 0.20);
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `Concentración de volumen: ${topSkill.skill} representa ${topSkillPct.toFixed(0)}% del total`,
|
finding: t('dimensionAnalysis.volumetry.concentrationFinding', {
|
||||||
probableCause: `Alta concentración en un skill indica consultas repetitivas con potencial de automatización.`,
|
skill: topSkill.skill,
|
||||||
|
pct: topSkillPct.toFixed(0)
|
||||||
|
}),
|
||||||
|
probableCause: t('dimensionAnalysis.volumetry.concentrationCause'),
|
||||||
economicImpact: deflectionPotential,
|
economicImpact: deflectionPotential,
|
||||||
impactFormula: `${topSkill.volume.toLocaleString()} int × anualización × €${CPI_TCO} × 20% deflexión potencial`,
|
impactFormula: t('dimensionAnalysis.volumetry.impactFormula', {
|
||||||
timeSavings: `${annualTopSkillVolume.toLocaleString()} interacciones/año en ${topSkill.skill} (${interactionsDeflectable.toLocaleString()} automatizables)`,
|
volume: topSkill.volume.toLocaleString(),
|
||||||
recommendation: `Analizar tipologías de ${topSkill.skill} para deflexión a autoservicio o agente virtual. Potencial: ${formatCurrency(deflectionPotential)}/año.`,
|
cpi: CPI_TCO
|
||||||
|
}),
|
||||||
|
timeSavings: t('dimensionAnalysis.volumetry.timeSavings', {
|
||||||
|
volume: annualTopSkillVolume.toLocaleString(),
|
||||||
|
skill: topSkill.skill,
|
||||||
|
deflectable: interactionsDeflectable.toLocaleString()
|
||||||
|
}),
|
||||||
|
recommendation: t('dimensionAnalysis.volumetry.concentrationRecommendation', {
|
||||||
|
skill: topSkill.skill,
|
||||||
|
savings: formatCurrency(deflectionPotential)
|
||||||
|
}),
|
||||||
severity: 'info',
|
severity: 'info',
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
@@ -242,28 +270,34 @@ function generateCausalAnalysis(
|
|||||||
|
|
||||||
// Causa dinámica basada en nivel de variabilidad
|
// Causa dinámica basada en nivel de variabilidad
|
||||||
const cvCause = avgCVAHT > 125
|
const cvCause = avgCVAHT > 125
|
||||||
? 'Dispersión extrema en tiempos de atención impide planificación efectiva de recursos. Probable falta de scripts o procesos estandarizados.'
|
? t('dimensionAnalysis.complexity.highCVCauseCritical')
|
||||||
: 'Variabilidad moderada en tiempos indica oportunidad de estandarización para mejorar planificación WFM.';
|
: t('dimensionAnalysis.complexity.highCVCauseWarning');
|
||||||
|
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `CV AHT elevado: ${avgCVAHT.toFixed(0)}% (benchmark: <${cvBenchmark}%)`,
|
finding: t('dimensionAnalysis.complexity.highCVFinding', {
|
||||||
|
cv: avgCVAHT.toFixed(0),
|
||||||
|
benchmark: cvBenchmark
|
||||||
|
}),
|
||||||
probableCause: cvCause,
|
probableCause: cvCause,
|
||||||
economicImpact: staffingCost,
|
economicImpact: staffingCost,
|
||||||
impactFormula: `~3% del coste operativo por ineficiencia de staffing`,
|
impactFormula: t('dimensionAnalysis.complexity.highCVImpactFormula'),
|
||||||
timeSavings: `~${staffingHours.toLocaleString()} horas/año en sobre/subdimensionamiento`,
|
timeSavings: t('dimensionAnalysis.complexity.highCVTimeSavings', { hours: staffingHours.toLocaleString() }),
|
||||||
recommendation: `Implementar scripts guiados por IA que estandaricen la atención. Reducción esperada: -50% variabilidad. Ahorro: ${formatCurrency(standardizationSavings)}/año.`,
|
recommendation: t('dimensionAnalysis.complexity.highCVRecommendation', { savings: formatCurrency(standardizationSavings) }),
|
||||||
severity: cvSeverity,
|
severity: cvSeverity,
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// CV AHT dentro de benchmark - mostrar estado positivo
|
// CV AHT dentro de benchmark - mostrar estado positivo
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `CV AHT dentro de benchmark: ${avgCVAHT.toFixed(0)}% (benchmark: <${cvBenchmark}%)`,
|
finding: t('dimensionAnalysis.complexity.goodCVFinding', {
|
||||||
probableCause: 'Tiempos de atención consistentes. Buena estandarización de procesos.',
|
cv: avgCVAHT.toFixed(0),
|
||||||
|
benchmark: cvBenchmark
|
||||||
|
}),
|
||||||
|
probableCause: t('dimensionAnalysis.complexity.goodCVCause'),
|
||||||
economicImpact: 0,
|
economicImpact: 0,
|
||||||
impactFormula: 'Sin impacto por variabilidad',
|
impactFormula: t('dimensionAnalysis.complexity.goodCVImpactFormula'),
|
||||||
timeSavings: 'Planificación WFM eficiente',
|
timeSavings: t('dimensionAnalysis.complexity.goodCVTimeSavings'),
|
||||||
recommendation: 'Mantener nivel actual. Analizar casos atípicos para identificar oportunidades de mejora continua.',
|
recommendation: t('dimensionAnalysis.complexity.goodCVRecommendation'),
|
||||||
severity: 'info',
|
severity: 'info',
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
@@ -277,12 +311,16 @@ function generateCausalAnalysis(
|
|||||||
const holdCost = Math.round(excessHoldHours * HOURLY_COST);
|
const holdCost = Math.round(excessHoldHours * HOURLY_COST);
|
||||||
const searchCopilotSavings = Math.round(holdCost * 0.60);
|
const searchCopilotSavings = Math.round(holdCost * 0.60);
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `Hold time elevado: ${avgHoldTime.toFixed(0)}s promedio (benchmark: <30s)`,
|
finding: t('dimensionAnalysis.complexity.holdTimeFinding', { holdTime: avgHoldTime.toFixed(0) }),
|
||||||
probableCause: 'Agentes ponen cliente en espera para buscar información. Sistemas no presentan datos de forma contextual.',
|
probableCause: t('dimensionAnalysis.complexity.holdTimeCause'),
|
||||||
economicImpact: holdCost,
|
economicImpact: holdCost,
|
||||||
impactFormula: `Exceso ${Math.round(excessHold)}s × ${totalVolume.toLocaleString()} int × anualización × €${HOURLY_COST}/h`,
|
impactFormula: t('dimensionAnalysis.complexity.holdTimeImpactFormula', {
|
||||||
timeSavings: `${excessHoldHours.toLocaleString()} horas/año de cliente en espera`,
|
excess: Math.round(excessHold),
|
||||||
recommendation: `Desplegar vista 360° con contexto automático: historial, productos y acciones sugeridas visibles al contestar. Reducción esperada: -60% hold time. Ahorro: ${formatCurrency(searchCopilotSavings)}/año.`,
|
volume: totalVolume.toLocaleString(),
|
||||||
|
cost: HOURLY_COST
|
||||||
|
}),
|
||||||
|
timeSavings: t('dimensionAnalysis.complexity.holdTimeTimeSavings', { hours: excessHoldHours.toLocaleString() }),
|
||||||
|
recommendation: t('dimensionAnalysis.complexity.holdTimeRecommendation', { savings: formatCurrency(searchCopilotSavings) }),
|
||||||
severity: avgHoldTime > 60 ? 'critical' : 'warning',
|
severity: avgHoldTime > 60 ? 'critical' : 'warning',
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
@@ -297,12 +335,12 @@ function generateCausalAnalysis(
|
|||||||
const customersAtRisk = Math.round(annualVolumeCsat * 0.02);
|
const customersAtRisk = Math.round(annualVolumeCsat * 0.02);
|
||||||
const churnRisk = Math.round(customersAtRisk * 50);
|
const churnRisk = Math.round(customersAtRisk * 50);
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `CSAT por debajo del objetivo: ${avgCSAT.toFixed(0)}% (benchmark: >80%)`,
|
finding: t('dimensionAnalysis.satisfaction.lowCSATFinding', { csat: avgCSAT.toFixed(0) }),
|
||||||
probableCause: 'Clientes insatisfechos por esperas, falta de resolución o experiencia de atención deficiente.',
|
probableCause: t('dimensionAnalysis.satisfaction.lowCSATCause'),
|
||||||
economicImpact: churnRisk,
|
economicImpact: churnRisk,
|
||||||
impactFormula: `${totalVolume.toLocaleString()} clientes × anualización × 2% riesgo churn × €50 valor`,
|
impactFormula: t('dimensionAnalysis.satisfaction.lowCSATImpactFormula', { volume: totalVolume.toLocaleString() }),
|
||||||
timeSavings: `${customersAtRisk.toLocaleString()} clientes/año en riesgo de fuga`,
|
timeSavings: t('dimensionAnalysis.satisfaction.lowCSATTimeSavings', { customers: customersAtRisk.toLocaleString() }),
|
||||||
recommendation: `Implementar programa VoC: encuestas post-contacto + análisis de causas raíz + acción correctiva en 48h. Objetivo: CSAT >80%.`,
|
recommendation: t('dimensionAnalysis.satisfaction.lowCSATRecommendation'),
|
||||||
severity: avgCSAT < 50 ? 'critical' : 'warning',
|
severity: avgCSAT < 50 ? 'critical' : 'warning',
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
@@ -319,12 +357,22 @@ function generateCausalAnalysis(
|
|||||||
const potentialSavings = Math.round(annualVolumeCpi * excessCPI);
|
const potentialSavings = Math.round(annualVolumeCpi * excessCPI);
|
||||||
const excessHours = Math.round(potentialSavings / HOURLY_COST);
|
const excessHours = Math.round(potentialSavings / HOURLY_COST);
|
||||||
analyses.push({
|
analyses.push({
|
||||||
finding: `CPI por encima del benchmark: €${CPI.toFixed(2)} (objetivo: €${CPI_TCO})`,
|
finding: t('dimensionAnalysis.economy.highCPIFinding', {
|
||||||
probableCause: 'Coste por interacción elevado por AHT alto, baja ocupación o estructura de costes ineficiente.',
|
cpi: CPI.toFixed(2),
|
||||||
|
target: CPI_TCO
|
||||||
|
}),
|
||||||
|
probableCause: t('dimensionAnalysis.economy.highCPICause'),
|
||||||
economicImpact: potentialSavings,
|
economicImpact: potentialSavings,
|
||||||
impactFormula: `${totalVolume.toLocaleString()} int × anualización × €${excessCPI.toFixed(2)} exceso CPI`,
|
impactFormula: t('dimensionAnalysis.economy.highCPIImpactFormula', {
|
||||||
timeSavings: `€${excessCPI.toFixed(2)} exceso/int × ${annualVolumeCpi.toLocaleString()} int = ${excessHours.toLocaleString()}h equivalentes`,
|
volume: totalVolume.toLocaleString(),
|
||||||
recommendation: `Optimizar mix de canales + reducir AHT con automatización + revisar modelo de staffing. Objetivo: CPI <€${CPI_TCO}.`,
|
excess: excessCPI.toFixed(2)
|
||||||
|
}),
|
||||||
|
timeSavings: t('dimensionAnalysis.economy.highCPITimeSavings', {
|
||||||
|
excess: excessCPI.toFixed(2),
|
||||||
|
volume: annualVolumeCpi.toLocaleString(),
|
||||||
|
hours: excessHours.toLocaleString()
|
||||||
|
}),
|
||||||
|
recommendation: t('dimensionAnalysis.economy.highCPIRecommendation', { target: CPI_TCO }),
|
||||||
severity: CPI > 5 ? 'critical' : 'warning',
|
severity: CPI > 5 ? 'critical' : 'warning',
|
||||||
hasRealData: true
|
hasRealData: true
|
||||||
});
|
});
|
||||||
@@ -347,13 +395,15 @@ function DimensionCard({
|
|||||||
findings,
|
findings,
|
||||||
recommendations,
|
recommendations,
|
||||||
causalAnalyses,
|
causalAnalyses,
|
||||||
delay = 0
|
delay = 0,
|
||||||
|
t
|
||||||
}: {
|
}: {
|
||||||
dimension: DimensionAnalysis;
|
dimension: DimensionAnalysis;
|
||||||
findings: Finding[];
|
findings: Finding[];
|
||||||
recommendations: Recommendation[];
|
recommendations: Recommendation[];
|
||||||
causalAnalyses: CausalAnalysisExtended[];
|
causalAnalyses: CausalAnalysisExtended[];
|
||||||
delay?: number;
|
delay?: number;
|
||||||
|
t: (key: string, options?: any) => string;
|
||||||
}) {
|
}) {
|
||||||
const Icon = dimension.icon;
|
const Icon = dimension.icon;
|
||||||
|
|
||||||
@@ -365,11 +415,11 @@ function DimensionCard({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getScoreLabel = (score: number): string => {
|
const getScoreLabel = (score: number): string => {
|
||||||
if (score < 0) return 'N/A';
|
if (score < 0) return t('common.na');
|
||||||
if (score >= 80) return 'Óptimo';
|
if (score >= 80) return t('common.optimal');
|
||||||
if (score >= 60) return 'Aceptable';
|
if (score >= 60) return t('common.acceptable');
|
||||||
if (score >= 40) return 'Mejorable';
|
if (score >= 40) return t('common.improvable');
|
||||||
return 'Crítico';
|
return t('common.critical');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSeverityConfig = (severity: string) => {
|
const getSeverityConfig = (severity: string) => {
|
||||||
@@ -410,13 +460,13 @@ function DimensionCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<Badge
|
<Badge
|
||||||
label={dimension.score >= 0 ? `${dimension.score} ${getScoreLabel(dimension.score)}` : '— N/A'}
|
label={dimension.score >= 0 ? `${dimension.score} ${getScoreLabel(dimension.score)}` : `— ${t('common.na')}`}
|
||||||
variant={scoreVariant}
|
variant={scoreVariant}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
{totalImpact > 0 && (
|
{totalImpact > 0 && (
|
||||||
<p className="text-xs text-red-600 font-medium mt-1">
|
<p className="text-xs text-red-600 font-medium mt-1">
|
||||||
Impacto: {formatCurrency(totalImpact)}
|
{t('dimensionAnalysis.impact')} {formatCurrency(totalImpact)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -459,7 +509,7 @@ function DimensionCard({
|
|||||||
<div className="p-3 bg-gray-50 rounded-lg border border-gray-200">
|
<div className="p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
<p className="text-sm text-gray-500 italic flex items-center gap-2">
|
<p className="text-sm text-gray-500 italic flex items-center gap-2">
|
||||||
<Minus className="w-4 h-4" />
|
<Minus className="w-4 h-4" />
|
||||||
Sin datos disponibles para esta dimensión.
|
{t('dimensionAnalysis.noDataAvailable')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -469,7 +519,7 @@ function DimensionCard({
|
|||||||
{dimension.score >= 0 && causalAnalyses.length > 0 && (
|
{dimension.score >= 0 && causalAnalyses.length > 0 && (
|
||||||
<div className="p-4 space-y-3">
|
<div className="p-4 space-y-3">
|
||||||
<h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
<h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
Hallazgo Clave
|
{t('dimensionAnalysis.keyFinding')}
|
||||||
</h4>
|
</h4>
|
||||||
{causalAnalyses.map((analysis, idx) => {
|
{causalAnalyses.map((analysis, idx) => {
|
||||||
const config = getSeverityConfig(analysis.severity);
|
const config = getSeverityConfig(analysis.severity);
|
||||||
@@ -485,7 +535,7 @@ function DimensionCard({
|
|||||||
|
|
||||||
{/* Causa probable */}
|
{/* Causa probable */}
|
||||||
<div className="ml-6 mb-2">
|
<div className="ml-6 mb-2">
|
||||||
<p className="text-xs text-gray-500 font-medium mb-0.5">Causa probable:</p>
|
<p className="text-xs text-gray-500 font-medium mb-0.5">{t('dimensionAnalysis.probableCause')}</p>
|
||||||
<p className="text-xs text-gray-700">{analysis.probableCause}</p>
|
<p className="text-xs text-gray-700">{analysis.probableCause}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -498,7 +548,7 @@ function DimensionCard({
|
|||||||
<span className="text-xs font-bold text-red-600">
|
<span className="text-xs font-bold text-red-600">
|
||||||
{formatCurrency(analysis.economicImpact)}
|
{formatCurrency(analysis.economicImpact)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500">impacto anual (coste del problema)</span>
|
<span className="text-xs text-gray-500">{t('dimensionAnalysis.annualImpact')}</span>
|
||||||
<span className="text-xs text-gray-400">i</span>
|
<span className="text-xs text-gray-400">i</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -527,7 +577,7 @@ function DimensionCard({
|
|||||||
{dimension.score >= 0 && causalAnalyses.length === 0 && findings.length > 0 && (
|
{dimension.score >= 0 && causalAnalyses.length === 0 && findings.length > 0 && (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
<h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||||
Hallazgos Clave
|
{t('dimensionAnalysis.keyFindings')}
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{findings.slice(0, 3).map((finding, idx) => (
|
{findings.slice(0, 3).map((finding, idx) => (
|
||||||
@@ -550,7 +600,7 @@ function DimensionCard({
|
|||||||
<div className={cn('p-3 rounded-lg border', STATUS_CLASSES.success.bg, STATUS_CLASSES.success.border)}>
|
<div className={cn('p-3 rounded-lg border', STATUS_CLASSES.success.bg, STATUS_CLASSES.success.border)}>
|
||||||
<p className={cn('text-sm flex items-center gap-2', STATUS_CLASSES.success.text)}>
|
<p className={cn('text-sm flex items-center gap-2', STATUS_CLASSES.success.text)}>
|
||||||
<ChevronRight className="w-4 h-4" />
|
<ChevronRight className="w-4 h-4" />
|
||||||
Métricas dentro de rangos aceptables. Sin hallazgos críticos.
|
{t('dimensionAnalysis.withinAcceptable')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -561,7 +611,7 @@ function DimensionCard({
|
|||||||
<div className="px-4 pb-4">
|
<div className="px-4 pb-4">
|
||||||
<div className="p-3 bg-blue-50 rounded-lg border border-blue-100">
|
<div className="p-3 bg-blue-50 rounded-lg border border-blue-100">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span className="text-xs font-semibold text-blue-600">Recomendación:</span>
|
<span className="text-xs font-semibold text-blue-600">{t('dimensionAnalysis.recommendation')}</span>
|
||||||
<span className="text-xs text-gray-600">{recommendations[0].text}</span>
|
<span className="text-xs text-gray-600">{recommendations[0].text}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -574,6 +624,8 @@ function DimensionCard({
|
|||||||
// ========== v3.16: COMPONENTE PRINCIPAL ==========
|
// ========== v3.16: COMPONENTE PRINCIPAL ==========
|
||||||
|
|
||||||
export function DimensionAnalysisTab({ data }: DimensionAnalysisTabProps) {
|
export function DimensionAnalysisTab({ data }: DimensionAnalysisTabProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// DEBUG: Verificar CPI en dimensión vs heatmapData
|
// DEBUG: Verificar CPI en dimensión vs heatmapData
|
||||||
const economyDim = data.dimensions.find(d =>
|
const economyDim = data.dimensions.find(d =>
|
||||||
d.id === 'economy_costs' || d.name === 'economy_costs' ||
|
d.id === 'economy_costs' || d.name === 'economy_costs' ||
|
||||||
@@ -609,7 +661,7 @@ export function DimensionAnalysisTab({ data }: DimensionAnalysisTabProps) {
|
|||||||
|
|
||||||
// Generar hallazgo clave para cada dimensión
|
// Generar hallazgo clave para cada dimensión
|
||||||
const getCausalAnalysisForDimension = (dimension: DimensionAnalysis) =>
|
const getCausalAnalysisForDimension = (dimension: DimensionAnalysis) =>
|
||||||
generateCausalAnalysis(dimension, data.heatmapData, data.economicModel, data.staticConfig, data.dateRange);
|
generateCausalAnalysis(dimension, data.heatmapData, data.economicModel, t, data.staticConfig, data.dateRange);
|
||||||
|
|
||||||
// Calcular impacto total de todas las dimensiones con datos
|
// Calcular impacto total de todas las dimensiones con datos
|
||||||
const impactoTotal = coreDimensions
|
const impactoTotal = coreDimensions
|
||||||
@@ -627,10 +679,10 @@ export function DimensionAnalysisTab({ data }: DimensionAnalysisTabProps) {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* v3.16: Header simplificado - solo título y subtítulo */}
|
{/* v3.16: Header simplificado - solo título y subtítulo */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<h2 className="text-lg font-bold text-gray-900">Diagnóstico por Dimensión</h2>
|
<h2 className="text-lg font-bold text-gray-900">{t('dimensionAnalysis.title')}</h2>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
{coreDimensions.length} dimensiones analizadas
|
{t('dimensionAnalysis.dimensionsAnalyzed', { count: coreDimensions.length })}
|
||||||
{sinDatos.length > 0 && ` (${sinDatos.length} sin datos)`}
|
{sinDatos.length > 0 && ` ${t('dimensionAnalysis.noData', { count: sinDatos.length })}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -644,6 +696,7 @@ export function DimensionAnalysisTab({ data }: DimensionAnalysisTabProps) {
|
|||||||
recommendations={getRecommendationsForDimension(dimension.id)}
|
recommendations={getRecommendationsForDimension(dimension.id)}
|
||||||
causalAnalyses={getCausalAnalysisForDimension(dimension)}
|
causalAnalyses={getCausalAnalysisForDimension(dimension)}
|
||||||
delay={idx * 0.05}
|
delay={idx * 0.05}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { TrendingUp, TrendingDown, AlertTriangle, CheckCircle, Target, Activity, Clock, PhoneForwarded, Users, Bot, ChevronRight, BarChart3, Cpu, Map, Zap, Calendar } from 'lucide-react';
|
import { TrendingUp, TrendingDown, AlertTriangle, CheckCircle, Target, Activity, Clock, PhoneForwarded, Users, Bot, ChevronRight, BarChart3, Cpu, Map, Zap, Calendar } from 'lucide-react';
|
||||||
import type { AnalysisData, Finding, DrilldownDataPoint, HeatmapDataPoint } from '../../types';
|
import type { AnalysisData, Finding, DrilldownDataPoint, HeatmapDataPoint } from '../../types';
|
||||||
import type { TabId } from '../DashboardHeader';
|
import type { TabId } from '../DashboardHeader';
|
||||||
@@ -146,7 +147,7 @@ interface Hallazgo {
|
|||||||
metrica?: string;
|
metrica?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
function generarHallazgos(data: AnalysisData, t: any): Hallazgo[] {
|
||||||
const hallazgos: Hallazgo[] = [];
|
const hallazgos: Hallazgo[] = [];
|
||||||
const allQueues = data.drilldownData?.flatMap(s => s.originalQueues) || [];
|
const allQueues = data.drilldownData?.flatMap(s => s.originalQueues) || [];
|
||||||
const totalVolume = allQueues.reduce((s, q) => s + q.volume, 0);
|
const totalVolume = allQueues.reduce((s, q) => s + q.volume, 0);
|
||||||
@@ -163,7 +164,7 @@ function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
|||||||
const pctVolumen = (colasAltaVariabilidad.reduce((s, q) => s + q.volume, 0) / totalVolume) * 100;
|
const pctVolumen = (colasAltaVariabilidad.reduce((s, q) => s + q.volume, 0) / totalVolume) * 100;
|
||||||
hallazgos.push({
|
hallazgos.push({
|
||||||
tipo: 'critico',
|
tipo: 'critico',
|
||||||
texto: `${colasAltaVariabilidad.length} colas con variabilidad crítica (CV >100%) representan ${pctVolumen.toFixed(0)}% del volumen`,
|
texto: t('executiveSummary.highVariabilityQueues', { count: colasAltaVariabilidad.length, pct: pctVolumen.toFixed(0) }),
|
||||||
metrica: 'CV AHT'
|
metrica: 'CV AHT'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -173,7 +174,7 @@ function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
|||||||
if (colasAltoTransfer.length > 0) {
|
if (colasAltoTransfer.length > 0) {
|
||||||
hallazgos.push({
|
hallazgos.push({
|
||||||
tipo: 'warning',
|
tipo: 'warning',
|
||||||
texto: `${colasAltoTransfer.length} colas con tasa de transferencia >25% - posible problema de routing o formación`,
|
texto: t('executiveSummary.highTransferQueues', { count: colasAltoTransfer.length }),
|
||||||
metrica: 'Transfer'
|
metrica: 'Transfer'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -183,7 +184,7 @@ function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
|||||||
if (colasBajoFCR.length > 0) {
|
if (colasBajoFCR.length > 0) {
|
||||||
hallazgos.push({
|
hallazgos.push({
|
||||||
tipo: 'warning',
|
tipo: 'warning',
|
||||||
texto: `${colasBajoFCR.length} colas con FCR <50% - clientes requieren múltiples contactos`,
|
texto: t('executiveSummary.lowFCRQueues', { count: colasBajoFCR.length }),
|
||||||
metrica: 'FCR'
|
metrica: 'FCR'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -192,7 +193,7 @@ function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
|||||||
if (avgAHT > 400) {
|
if (avgAHT > 400) {
|
||||||
hallazgos.push({
|
hallazgos.push({
|
||||||
tipo: 'warning',
|
tipo: 'warning',
|
||||||
texto: `AHT promedio de ${Math.round(avgAHT)}s supera el benchmark de industria (380s)`,
|
texto: t('executiveSummary.ahtAboveBenchmark', { aht: Math.round(avgAHT) }),
|
||||||
metrica: 'AHT'
|
metrica: 'AHT'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -203,7 +204,7 @@ function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
|||||||
const pctHuman = (colasHumanOnly.reduce((s, q) => s + q.volume, 0) / totalVolume) * 100;
|
const pctHuman = (colasHumanOnly.reduce((s, q) => s + q.volume, 0) / totalVolume) * 100;
|
||||||
hallazgos.push({
|
hallazgos.push({
|
||||||
tipo: 'info',
|
tipo: 'info',
|
||||||
texto: `${colasHumanOnly.length} colas (${pctHuman.toFixed(0)}% volumen) requieren intervención humana completa`,
|
texto: t('executiveSummary.humanOnlyQueues', { count: colasHumanOnly.length, pct: pctHuman.toFixed(0) }),
|
||||||
metrica: 'Tier'
|
metrica: 'Tier'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -213,8 +214,8 @@ function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
|||||||
if (colasAutomate.length > 0) {
|
if (colasAutomate.length > 0) {
|
||||||
hallazgos.push({
|
hallazgos.push({
|
||||||
tipo: 'info',
|
tipo: 'info',
|
||||||
texto: `${colasAutomate.length} colas listas para automatización con potencial de ahorro significativo`,
|
texto: t('executiveSummary.automateReadyQueues', { count: colasAutomate.length }),
|
||||||
metrica: 'Oportunidad'
|
metrica: t('executiveSummary.opportunity')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +223,8 @@ function generarHallazgos(data: AnalysisData): Hallazgo[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PrincipalesHallazgos({ data }: { data: AnalysisData }) {
|
function PrincipalesHallazgos({ data }: { data: AnalysisData }) {
|
||||||
const hallazgos = generarHallazgos(data);
|
const { t } = useTranslation();
|
||||||
|
const hallazgos = generarHallazgos(data, t);
|
||||||
|
|
||||||
if (hallazgos.length === 0) return null;
|
if (hallazgos.length === 0) return null;
|
||||||
|
|
||||||
@@ -240,7 +242,7 @@ function PrincipalesHallazgos({ data }: { data: AnalysisData }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<h3 className="font-semibold text-gray-900 mb-3">Principales Hallazgos</h3>
|
<h3 className="font-semibold text-gray-900 mb-3">{t('executiveSummary.title')}</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{hallazgos.map((h, idx) => (
|
{hallazgos.map((h, idx) => (
|
||||||
<div key={idx} className={cn('flex items-start gap-2 p-2 rounded-lg border', getClase(h.tipo))}>
|
<div key={idx} className={cn('flex items-start gap-2 p-2 rounded-lg border', getClase(h.tipo))}>
|
||||||
@@ -265,6 +267,7 @@ function PrincipalesHallazgos({ data }: { data: AnalysisData }) {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
function CabeceraPeriodo({ data }: { data: AnalysisData }) {
|
function CabeceraPeriodo({ data }: { data: AnalysisData }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const totalInteractions = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
const totalInteractions = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
||||||
|
|
||||||
// Contar colas únicas (original_queue_id) desde drilldownData
|
// Contar colas únicas (original_queue_id) desde drilldownData
|
||||||
@@ -278,7 +281,7 @@ function CabeceraPeriodo({ data }: { data: AnalysisData }) {
|
|||||||
// Formatear fechas del periodo
|
// Formatear fechas del periodo
|
||||||
const formatPeriodo = () => {
|
const formatPeriodo = () => {
|
||||||
if (!data.dateRange?.min || !data.dateRange?.max) {
|
if (!data.dateRange?.min || !data.dateRange?.max) {
|
||||||
return 'Periodo no especificado';
|
return t('executiveSummary.periodNotSpecified');
|
||||||
}
|
}
|
||||||
const formatDate = (dateStr: string) => {
|
const formatDate = (dateStr: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -295,13 +298,13 @@ function CabeceraPeriodo({ data }: { data: AnalysisData }) {
|
|||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 sm:gap-4 py-3 px-3 sm:px-4 bg-gray-50 rounded-lg border border-gray-200">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 sm:gap-4 py-3 px-3 sm:px-4 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
<div className="flex items-center gap-2 text-gray-600">
|
<div className="flex items-center gap-2 text-gray-600">
|
||||||
<Calendar className="w-4 h-4 flex-shrink-0" />
|
<Calendar className="w-4 h-4 flex-shrink-0" />
|
||||||
<span className="text-xs sm:text-sm font-medium">Periodo:</span>
|
<span className="text-xs sm:text-sm font-medium">{t('executiveSummary.period')}</span>
|
||||||
<span className="text-xs sm:text-sm">{formatPeriodo()}</span>
|
<span className="text-xs sm:text-sm">{formatPeriodo()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2 sm:gap-4 md:gap-6 text-xs sm:text-sm text-gray-500">
|
<div className="flex flex-wrap items-center gap-2 sm:gap-4 md:gap-6 text-xs sm:text-sm text-gray-500">
|
||||||
<span><strong>{formatNumber(totalInteractions)}</strong> int.</span>
|
<span><strong>{formatNumber(totalInteractions)}</strong> {t('executiveSummary.interactions')}</span>
|
||||||
<span><strong>{uniqueQueues}</strong> colas</span>
|
<span><strong>{uniqueQueues}</strong> {t('executiveSummary.queues')}</span>
|
||||||
<span><strong>{numLineasNegocio}</strong> LN</span>
|
<span><strong>{numLineasNegocio}</strong> {t('executiveSummary.businessLines')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -323,10 +326,12 @@ function HeadlineEjecutivo({
|
|||||||
resolucionScore: number;
|
resolucionScore: number;
|
||||||
satisfaccionScore: number;
|
satisfaccionScore: number;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getStatusLabel = (score: number): string => {
|
const getStatusLabel = (score: number): string => {
|
||||||
if (score >= 80) return 'Óptimo';
|
if (score >= 80) return t('common.optimal');
|
||||||
if (score >= 60) return 'Aceptable';
|
if (score >= 60) return t('common.acceptable');
|
||||||
return 'Crítico';
|
return t('common.critical');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusVariant = (score: number): 'success' | 'warning' | 'critical' => {
|
const getStatusVariant = (score: number): 'success' | 'warning' | 'critical' => {
|
||||||
@@ -340,16 +345,10 @@ function HeadlineEjecutivo({
|
|||||||
{/* Título principal */}
|
{/* Título principal */}
|
||||||
<div className="mb-3 sm:mb-4">
|
<div className="mb-3 sm:mb-4">
|
||||||
<h1 className="text-lg sm:text-xl md:text-2xl font-light mb-1">
|
<h1 className="text-lg sm:text-xl md:text-2xl font-light mb-1">
|
||||||
Tu operación procesa{' '}
|
{t('executiveSummary.yourOperation', { total: formatNumber(totalInteracciones) })}
|
||||||
<span className="font-bold text-white">{formatNumber(totalInteracciones)}</span>{' '}
|
|
||||||
interacciones
|
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm sm:text-lg text-gray-300">
|
<p className="text-sm sm:text-lg text-gray-300">
|
||||||
con oportunidad de{' '}
|
{t('executiveSummary.withOpportunity', { amount: formatCurrency(oportunidadTotal) })}
|
||||||
<span className="font-bold text-emerald-400">
|
|
||||||
{formatCurrency(oportunidadTotal)}
|
|
||||||
</span>{' '}
|
|
||||||
en optimización
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -361,7 +360,7 @@ function HeadlineEjecutivo({
|
|||||||
)}>
|
)}>
|
||||||
<Clock className={cn('w-4 h-4', STATUS_CLASSES[getStatusVariant(eficienciaScore)].text)} />
|
<Clock className={cn('w-4 h-4', STATUS_CLASSES[getStatusVariant(eficienciaScore)].text)} />
|
||||||
<span className={cn('text-sm font-medium', STATUS_CLASSES[getStatusVariant(eficienciaScore)].text)}>
|
<span className={cn('text-sm font-medium', STATUS_CLASSES[getStatusVariant(eficienciaScore)].text)}>
|
||||||
Eficiencia: {getStatusLabel(eficienciaScore)}
|
{t('executiveSummary.efficiency')} {getStatusLabel(eficienciaScore)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
@@ -370,7 +369,7 @@ function HeadlineEjecutivo({
|
|||||||
)}>
|
)}>
|
||||||
<CheckCircle className={cn('w-4 h-4', STATUS_CLASSES[getStatusVariant(resolucionScore)].text)} />
|
<CheckCircle className={cn('w-4 h-4', STATUS_CLASSES[getStatusVariant(resolucionScore)].text)} />
|
||||||
<span className={cn('text-sm font-medium', STATUS_CLASSES[getStatusVariant(resolucionScore)].text)}>
|
<span className={cn('text-sm font-medium', STATUS_CLASSES[getStatusVariant(resolucionScore)].text)}>
|
||||||
Resolución: {getStatusLabel(resolucionScore)}
|
{t('executiveSummary.resolution')} {getStatusLabel(resolucionScore)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
@@ -379,7 +378,7 @@ function HeadlineEjecutivo({
|
|||||||
)}>
|
)}>
|
||||||
<Users className={cn('w-4 h-4', STATUS_CLASSES[getStatusVariant(satisfaccionScore)].text)} />
|
<Users className={cn('w-4 h-4', STATUS_CLASSES[getStatusVariant(satisfaccionScore)].text)} />
|
||||||
<span className={cn('text-sm font-medium', STATUS_CLASSES[getStatusVariant(satisfaccionScore)].text)}>
|
<span className={cn('text-sm font-medium', STATUS_CLASSES[getStatusVariant(satisfaccionScore)].text)}>
|
||||||
Satisfacción: {getStatusLabel(satisfaccionScore)}
|
{t('executiveSummary.satisfaction')} {getStatusLabel(satisfaccionScore)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -390,6 +389,7 @@ function HeadlineEjecutivo({
|
|||||||
// v7.0: Unified KPI + Benchmark Card Component
|
// v7.0: Unified KPI + Benchmark Card Component
|
||||||
// Combines KeyMetricsCard + BenchmarkTable into single 3x2 card grid
|
// Combines KeyMetricsCard + BenchmarkTable into single 3x2 card grid
|
||||||
function UnifiedKPIBenchmark({ heatmapData }: { heatmapData: HeatmapDataPoint[] }) {
|
function UnifiedKPIBenchmark({ heatmapData }: { heatmapData: HeatmapDataPoint[] }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [selectedIndustry, setSelectedIndustry] = React.useState<IndustryKey>('aerolineas');
|
const [selectedIndustry, setSelectedIndustry] = React.useState<IndustryKey>('aerolineas');
|
||||||
const benchmarks = BENCHMARKS_INDUSTRIA[selectedIndustry];
|
const benchmarks = BENCHMARKS_INDUSTRIA[selectedIndustry];
|
||||||
|
|
||||||
@@ -442,11 +442,11 @@ function UnifiedKPIBenchmark({ heatmapData }: { heatmapData: HeatmapDataPoint[]
|
|||||||
|
|
||||||
// Calculate percentile position
|
// Calculate percentile position
|
||||||
const getPercentileBadge = (percentile: number): { label: string; color: string } => {
|
const getPercentileBadge = (percentile: number): { label: string; color: string } => {
|
||||||
if (percentile >= 90) return { label: 'Top 10%', color: 'bg-emerald-500 text-white' };
|
if (percentile >= 90) return { label: t('executiveSummary.top10'), color: 'bg-emerald-500 text-white' };
|
||||||
if (percentile >= 75) return { label: 'Top 25%', color: 'bg-emerald-100 text-emerald-700' };
|
if (percentile >= 75) return { label: t('executiveSummary.top25'), color: 'bg-emerald-100 text-emerald-700' };
|
||||||
if (percentile >= 50) return { label: 'Promedio', color: 'bg-amber-100 text-amber-700' };
|
if (percentile >= 50) return { label: t('executiveSummary.average'), color: 'bg-amber-100 text-amber-700' };
|
||||||
if (percentile >= 25) return { label: 'Bajo Avg', color: 'bg-orange-100 text-orange-700' };
|
if (percentile >= 25) return { label: t('executiveSummary.belowAvg'), color: 'bg-orange-100 text-orange-700' };
|
||||||
return { label: 'Bottom 25%', color: 'bg-red-100 text-red-700' };
|
return { label: t('executiveSummary.bottom25'), color: 'bg-red-100 text-red-700' };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate GAP vs P50 - positive is better, negative is worse
|
// Calculate GAP vs P50 - positive is better, negative is worse
|
||||||
@@ -504,11 +504,11 @@ function UnifiedKPIBenchmark({ heatmapData }: { heatmapData: HeatmapDataPoint[]
|
|||||||
|
|
||||||
// Get insight text based on percentile position
|
// Get insight text based on percentile position
|
||||||
const getInsightText = (percentile: number, bench: BenchmarkMetric): string => {
|
const getInsightText = (percentile: number, bench: BenchmarkMetric): string => {
|
||||||
if (percentile >= 90) return `Superas al 90% del mercado`;
|
if (percentile >= 90) return t('executiveSummary.surpasses90');
|
||||||
if (percentile >= 75) return `Mejor que 3 de cada 4 empresas`;
|
if (percentile >= 75) return t('executiveSummary.betterThan75');
|
||||||
if (percentile >= 50) return `En línea con la mediana del sector`;
|
if (percentile >= 50) return t('executiveSummary.alignedWithMedian');
|
||||||
if (percentile >= 25) return `Por debajo de la media del mercado`;
|
if (percentile >= 25) return t('executiveSummary.belowAverage');
|
||||||
return `Área crítica de mejora`;
|
return t('executiveSummary.criticalArea');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format benchmark value for display
|
// Format benchmark value for display
|
||||||
@@ -522,79 +522,89 @@ function UnifiedKPIBenchmark({ heatmapData }: { heatmapData: HeatmapDataPoint[]
|
|||||||
// FCR Real context: métrica más estricta que incluye recontactos 7 días
|
// FCR Real context: métrica más estricta que incluye recontactos 7 días
|
||||||
const fcrRealDiff = operacion.fcrTecnico - operacion.fcrReal;
|
const fcrRealDiff = operacion.fcrTecnico - operacion.fcrReal;
|
||||||
const fcrRealContext = fcrRealDiff > 0
|
const fcrRealContext = fcrRealDiff > 0
|
||||||
? `${Math.round(fcrRealDiff)}pp de recontactos 7d`
|
? `${Math.round(fcrRealDiff)}pp ${t('executiveSummary.recontacts7d')}`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// AHT Total context: diferencia entre AHT limpio y AHT con todas las filas
|
// AHT Total context: diferencia entre AHT limpio y AHT con todas las filas
|
||||||
const ahtTotalDiff = operacion.ahtTotal - operacion.aht;
|
const ahtTotalDiff = operacion.ahtTotal - operacion.aht;
|
||||||
const ahtTotalContext = Math.abs(ahtTotalDiff) > 1
|
const ahtTotalContext = Math.abs(ahtTotalDiff) > 1
|
||||||
? `${ahtTotalDiff > 0 ? '+' : ''}${Math.round(ahtTotalDiff)}s vs AHT limpio`
|
? `${ahtTotalDiff > 0 ? '+' : ''}${Math.round(ahtTotalDiff)}s ${t('executiveSummary.vsCleanAht')}`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const metricsData = [
|
const metricsData = [
|
||||||
{
|
{
|
||||||
id: 'aht',
|
id: 'aht',
|
||||||
label: 'AHT',
|
label: t('executiveSummary.aht'),
|
||||||
valor: operacion.aht,
|
valor: operacion.aht,
|
||||||
display: `${Math.floor(operacion.aht / 60)}:${String(Math.round(operacion.aht) % 60).padStart(2, '0')}`,
|
display: `${Math.floor(operacion.aht / 60)}:${String(Math.round(operacion.aht) % 60).padStart(2, '0')}`,
|
||||||
subDisplay: `(${Math.round(operacion.aht)}s)`,
|
subDisplay: `(${Math.round(operacion.aht)}s)`,
|
||||||
bench: benchmarks.metricas.aht,
|
bench: benchmarks.metricas.aht,
|
||||||
tooltip: 'Tiempo medio de gestión (solo interacciones válidas)',
|
tooltip: t('executiveSummary.ahtTooltip'),
|
||||||
// AHT Total integrado como métrica secundaria
|
// AHT Total integrado como métrica secundaria
|
||||||
secondaryMetric: {
|
secondaryMetric: {
|
||||||
label: 'AHT Total',
|
label: t('executiveSummary.ahtTotal'),
|
||||||
value: `${Math.floor(operacion.ahtTotal / 60)}:${String(Math.round(operacion.ahtTotal) % 60).padStart(2, '0')} (${Math.round(operacion.ahtTotal)}s)`,
|
value: `${Math.floor(operacion.ahtTotal / 60)}:${String(Math.round(operacion.ahtTotal) % 60).padStart(2, '0')} (${Math.round(operacion.ahtTotal)}s)`,
|
||||||
note: ahtTotalContext,
|
note: ahtTotalContext,
|
||||||
tooltip: 'Incluye todas las filas (noise, zombie, abandon) - solo informativo',
|
tooltip: t('executiveSummary.ahtTotalTooltip'),
|
||||||
description: 'Incluye noise, zombie y abandonos — solo informativo'
|
description: t('executiveSummary.ahtTotalDesc')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'fcr_tecnico',
|
id: 'fcr_tecnico',
|
||||||
label: 'FCR',
|
label: t('executiveSummary.fcr'),
|
||||||
valor: operacion.fcrTecnico,
|
valor: operacion.fcrTecnico,
|
||||||
display: `${Math.round(operacion.fcrTecnico)}%`,
|
display: `${Math.round(operacion.fcrTecnico)}%`,
|
||||||
subDisplay: null,
|
subDisplay: null,
|
||||||
bench: benchmarks.metricas.fcr,
|
bench: benchmarks.metricas.fcr,
|
||||||
tooltip: 'First Contact Resolution - comparable con benchmarks de industria',
|
tooltip: t('executiveSummary.fcrTooltip'),
|
||||||
// FCR Real integrado como métrica secundaria
|
// FCR Real integrado como métrica secundaria
|
||||||
secondaryMetric: {
|
secondaryMetric: {
|
||||||
label: 'FCR Ajustado',
|
label: t('executiveSummary.fcrAdjusted'),
|
||||||
value: `${Math.round(operacion.fcrReal)}%`,
|
value: `${Math.round(operacion.fcrReal)}%`,
|
||||||
note: fcrRealContext,
|
note: fcrRealContext,
|
||||||
tooltip: 'Excluye recontactos en 7 días (métrica más estricta)',
|
tooltip: t('executiveSummary.fcrAdjustedTooltip'),
|
||||||
description: 'Incluye filtro de recontactos 7d — métrica interna más estricta'
|
description: t('executiveSummary.fcrAdjustedDesc')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'abandono',
|
id: 'abandono',
|
||||||
label: 'ABANDONO',
|
label: t('executiveSummary.abandonment'),
|
||||||
valor: operacion.abandono,
|
valor: operacion.abandono,
|
||||||
display: `${operacion.abandono.toFixed(1)}%`,
|
display: `${operacion.abandono.toFixed(1)}%`,
|
||||||
subDisplay: null,
|
subDisplay: null,
|
||||||
bench: benchmarks.metricas.abandono,
|
bench: benchmarks.metricas.abandono,
|
||||||
tooltip: 'Tasa de abandono',
|
tooltip: t('executiveSummary.abandonmentTooltip'),
|
||||||
secondaryMetric: null
|
secondaryMetric: null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'cpi',
|
id: 'cpi',
|
||||||
label: 'COSTE/INTERAC.',
|
label: t('executiveSummary.costPerInteraction'),
|
||||||
valor: operacion.cpi,
|
valor: operacion.cpi,
|
||||||
display: `€${operacion.cpi.toFixed(2)}`,
|
display: `€${operacion.cpi.toFixed(2)}`,
|
||||||
subDisplay: null,
|
subDisplay: null,
|
||||||
bench: benchmarks.metricas.cpi,
|
bench: benchmarks.metricas.cpi,
|
||||||
tooltip: 'Coste por interacción',
|
tooltip: t('executiveSummary.cpiTooltip'),
|
||||||
secondaryMetric: null
|
secondaryMetric: null
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Map industry keys to translation keys
|
||||||
|
const industryNameMap: Record<IndustryKey, string> = {
|
||||||
|
aerolineas: t('industries.airlines'),
|
||||||
|
telecomunicaciones: t('industries.telco'),
|
||||||
|
banca: t('industries.banking'),
|
||||||
|
utilities: t('industries.utilities'),
|
||||||
|
retail: t('industries.retail'),
|
||||||
|
general: t('industries.crossIndustry')
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
{/* Header with industry selector */}
|
{/* Header with industry selector */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-3">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900">Indicadores vs Industria</h3>
|
<h3 className="font-semibold text-gray-900">{t('executiveSummary.indicators')}</h3>
|
||||||
<p className="text-xs text-gray-500">Fuente: {benchmarks.fuente}</p>
|
<p className="text-xs text-gray-500">{t('benchmark.source', { source: benchmarks.fuente })}</p>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
value={selectedIndustry}
|
value={selectedIndustry}
|
||||||
@@ -602,7 +612,7 @@ function UnifiedKPIBenchmark({ heatmapData }: { heatmapData: HeatmapDataPoint[]
|
|||||||
className="text-sm border border-gray-300 rounded-md px-2 py-1 bg-white w-full sm:w-auto"
|
className="text-sm border border-gray-300 rounded-md px-2 py-1 bg-white w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
{Object.entries(BENCHMARKS_INDUSTRIA).map(([key, val]) => (
|
{Object.entries(BENCHMARKS_INDUSTRIA).map(([key, val]) => (
|
||||||
<option key={key} value={key}>{val.nombre}</option>
|
<option key={key} value={key}>{industryNameMap[key as IndustryKey]}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -700,15 +710,15 @@ function UnifiedKPIBenchmark({ heatmapData }: { heatmapData: HeatmapDataPoint[]
|
|||||||
{/* Benchmark Reference Values */}
|
{/* Benchmark Reference Values */}
|
||||||
<div className="grid grid-cols-3 gap-1 text-center mb-2 py-1.5 bg-white/50 rounded">
|
<div className="grid grid-cols-3 gap-1 text-center mb-2 py-1.5 bg-white/50 rounded">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[9px] text-gray-400">Bajo</div>
|
<div className="text-[9px] text-gray-400">{t('executiveSummary.benchmarkLow')}</div>
|
||||||
<div className="text-[10px] font-medium text-gray-600">{formatBenchValue(m.bench.p25, m.bench.unidad)}</div>
|
<div className="text-[10px] font-medium text-gray-600">{formatBenchValue(m.bench.p25, m.bench.unidad)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-x border-gray-200">
|
<div className="border-x border-gray-200">
|
||||||
<div className="text-[9px] text-gray-400">Mediana</div>
|
<div className="text-[9px] text-gray-400">{t('executiveSummary.benchmarkMedian')}</div>
|
||||||
<div className="text-[10px] font-semibold text-gray-700">{formatBenchValue(m.bench.p50, m.bench.unidad)}</div>
|
<div className="text-[10px] font-semibold text-gray-700">{formatBenchValue(m.bench.p50, m.bench.unidad)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[9px] text-gray-400">Top</div>
|
<div className="text-[9px] text-gray-400">{t('executiveSummary.benchmarkTop')}</div>
|
||||||
<div className="text-[10px] font-medium text-emerald-600">{formatBenchValue(m.bench.p90, m.bench.unidad)}</div>
|
<div className="text-[10px] font-medium text-emerald-600">{formatBenchValue(m.bench.p90, m.bench.unidad)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -744,6 +754,8 @@ function HealthScoreDetailed({
|
|||||||
avgAbandonmentRate: number; // Tasa de abandono (%)
|
avgAbandonmentRate: number; // Tasa de abandono (%)
|
||||||
avgTransferRate: number; // Tasa de transferencia (%)
|
avgTransferRate: number; // Tasa de transferencia (%)
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getScoreColor = (s: number): string => {
|
const getScoreColor = (s: number): string => {
|
||||||
if (s >= 80) return COLORS.status.success;
|
if (s >= 80) return COLORS.status.success;
|
||||||
if (s >= 60) return COLORS.status.warning;
|
if (s >= 60) return COLORS.status.warning;
|
||||||
@@ -751,10 +763,10 @@ function HealthScoreDetailed({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getScoreLabel = (s: number): string => {
|
const getScoreLabel = (s: number): string => {
|
||||||
if (s >= 80) return 'Excelente';
|
if (s >= 80) return t('executiveSummary.excellent');
|
||||||
if (s >= 60) return 'Bueno';
|
if (s >= 60) return t('executiveSummary.good');
|
||||||
if (s >= 40) return 'Regular';
|
if (s >= 40) return t('executiveSummary.regular');
|
||||||
return 'Crítico';
|
return t('common.critical');
|
||||||
};
|
};
|
||||||
|
|
||||||
const color = getScoreColor(score);
|
const color = getScoreColor(score);
|
||||||
@@ -815,35 +827,35 @@ function HealthScoreDetailed({
|
|||||||
// Nueva ponderación: FCR 35%, Abandono 30%, CSAT Proxy 20%, AHT 15%
|
// Nueva ponderación: FCR 35%, Abandono 30%, CSAT Proxy 20%, AHT 15%
|
||||||
const factors = [
|
const factors = [
|
||||||
{
|
{
|
||||||
name: 'FCR Técnico',
|
name: t('executiveSummary.fcrTechnical'),
|
||||||
weight: '35%',
|
weight: '35%',
|
||||||
score: Math.round(fcrScore),
|
score: Math.round(fcrScore),
|
||||||
status: getFactorStatus(fcrScore),
|
status: getFactorStatus(fcrScore),
|
||||||
insight: fcrScore >= 80 ? 'Óptimo' : fcrScore >= 50 ? 'En P50' : 'Bajo P90',
|
insight: fcrScore >= 80 ? t('common.optimal') : fcrScore >= 50 ? t('executiveSummary.atP50') : t('executiveSummary.lowP90'),
|
||||||
rawValue: `${avgFCR.toFixed(0)}%`
|
rawValue: `${avgFCR.toFixed(0)}%`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Accesibilidad',
|
name: t('executiveSummary.accessibility'),
|
||||||
weight: '30%',
|
weight: '30%',
|
||||||
score: Math.round(abandonoScore),
|
score: Math.round(abandonoScore),
|
||||||
status: getFactorStatus(abandonoScore),
|
status: getFactorStatus(abandonoScore),
|
||||||
insight: abandonoScore >= 80 ? 'Bajo' : abandonoScore >= 50 ? 'Moderado' : 'Crítico',
|
insight: abandonoScore >= 80 ? t('common.low') : abandonoScore >= 50 ? t('executiveSummary.moderate') : t('common.critical'),
|
||||||
rawValue: `${avgAbandonmentRate.toFixed(1)}% aband.`
|
rawValue: `${avgAbandonmentRate.toFixed(1)}% aband.`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'CSAT Proxy',
|
name: t('executiveSummary.csatProxy'),
|
||||||
weight: '20%',
|
weight: '20%',
|
||||||
score: Math.round(csatProxyScore),
|
score: Math.round(csatProxyScore),
|
||||||
status: getFactorStatus(csatProxyScore),
|
status: getFactorStatus(csatProxyScore),
|
||||||
insight: csatProxyScore >= 80 ? 'Óptimo' : csatProxyScore >= 50 ? 'Mejorable' : 'Bajo',
|
insight: csatProxyScore >= 80 ? t('common.optimal') : csatProxyScore >= 50 ? t('common.improvable') : t('common.low'),
|
||||||
rawValue: '(FCR+Aband.)'
|
rawValue: '(FCR+Aband.)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Eficiencia',
|
name: t('executiveSummary.efficiencyMetric'),
|
||||||
weight: '15%',
|
weight: '15%',
|
||||||
score: Math.round(ahtScore),
|
score: Math.round(ahtScore),
|
||||||
status: getFactorStatus(ahtScore),
|
status: getFactorStatus(ahtScore),
|
||||||
insight: ahtScore >= 80 ? 'Rápido' : ahtScore >= 50 ? 'En rango' : 'Lento',
|
insight: ahtScore >= 80 ? t('executiveSummary.fast') : ahtScore >= 50 ? t('executiveSummary.inRange') : t('executiveSummary.slow'),
|
||||||
rawValue: `${Math.floor(avgAHT / 60)}:${String(Math.round(avgAHT) % 60).padStart(2, '0')}`
|
rawValue: `${Math.floor(avgAHT / 60)}:${String(Math.round(avgAHT) % 60).padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -896,9 +908,9 @@ function HealthScoreDetailed({
|
|||||||
|
|
||||||
{/* Breakdown */}
|
{/* Breakdown */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-semibold text-gray-900 mb-2">Health Score</h3>
|
<h3 className="font-semibold text-gray-900 mb-2">{t('executiveSummary.healthScore')}</h3>
|
||||||
<p className="text-[10px] text-gray-400 mb-2">
|
<p className="text-[10px] text-gray-400 mb-2">
|
||||||
Benchmarks: FCR P10=85%, Aband. P10=3%, AHT P10=240s
|
{t('executiveSummary.healthScoreBenchmark')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -923,7 +935,7 @@ function HealthScoreDetailed({
|
|||||||
{/* Nota de cálculo */}
|
{/* Nota de cálculo */}
|
||||||
<div className="mt-3 pt-2 border-t border-gray-100">
|
<div className="mt-3 pt-2 border-t border-gray-100">
|
||||||
<p className="text-[9px] text-gray-400 text-center">
|
<p className="text-[9px] text-gray-400 text-center">
|
||||||
Score = FCR×35% + Accesibilidad×30% + CSAT Proxy×20% + Eficiencia×15%
|
{t('executiveSummary.healthScoreFormula')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -934,6 +946,7 @@ function HealthScoreDetailed({
|
|||||||
|
|
||||||
// v3.16: Potencial de Automatización - Sin gauge confuso, solo distribución clara
|
// v3.16: Potencial de Automatización - Sin gauge confuso, solo distribución clara
|
||||||
function AgenticReadinessScore({ data }: { data: AnalysisData }) {
|
function AgenticReadinessScore({ data }: { data: AnalysisData }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const allQueues = data.drilldownData?.flatMap(skill => skill.originalQueues) || [];
|
const allQueues = data.drilldownData?.flatMap(skill => skill.originalQueues) || [];
|
||||||
const totalQueueVolume = allQueues.reduce((sum, q) => sum + q.volume, 0);
|
const totalQueueVolume = allQueues.reduce((sum, q) => sum + q.volume, 0);
|
||||||
|
|
||||||
@@ -962,17 +975,17 @@ function AgenticReadinessScore({ data }: { data: AnalysisData }) {
|
|||||||
|
|
||||||
// Datos de tiers con descripción clara
|
// Datos de tiers con descripción clara
|
||||||
const tiers = [
|
const tiers = [
|
||||||
{ key: 'AUTOMATE', label: 'AUTOMATE', bgColor: 'bg-emerald-500', desc: 'Bot autónomo' },
|
{ key: 'AUTOMATE', label: t('executiveSummary.automate'), bgColor: 'bg-emerald-500', desc: t('executiveSummary.autonomousBot') },
|
||||||
{ key: 'ASSIST', label: 'ASSIST', bgColor: 'bg-cyan-500', desc: 'Bot + agente' },
|
{ key: 'ASSIST', label: t('executiveSummary.assist'), bgColor: 'bg-cyan-500', desc: t('executiveSummary.botPlusAgent') },
|
||||||
{ key: 'AUGMENT', label: 'AUGMENT', bgColor: 'bg-amber-500', desc: 'Agente asistido' },
|
{ key: 'AUGMENT', label: t('executiveSummary.augment'), bgColor: 'bg-amber-500', desc: t('executiveSummary.assistedAgent') },
|
||||||
{ key: 'HUMAN-ONLY', label: 'HUMAN', bgColor: 'bg-gray-400', desc: 'Solo humano' }
|
{ key: 'HUMAN-ONLY', label: t('executiveSummary.human'), bgColor: 'bg-gray-400', desc: t('executiveSummary.humanOnly') }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Bot className="w-5 h-5 text-blue-600" />
|
<Bot className="w-5 h-5 text-blue-600" />
|
||||||
<h3 className="font-semibold text-gray-900">Potencial de Automatización</h3>
|
<h3 className="font-semibold text-gray-900">{t('executiveSummary.automationPotential')}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Distribución por tier */}
|
{/* Distribución por tier */}
|
||||||
@@ -996,7 +1009,7 @@ function AgenticReadinessScore({ data }: { data: AnalysisData }) {
|
|||||||
<div className="w-14 text-right">
|
<div className="w-14 text-right">
|
||||||
<div className="text-sm font-semibold text-gray-700">{Math.round(pct)}%</div>
|
<div className="text-sm font-semibold text-gray-700">{Math.round(pct)}%</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-16 text-xs text-gray-400 text-right">{count} colas</div>
|
<div className="w-16 text-xs text-gray-400 text-right">{count} {t('executiveSummary.queuesLabel')}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -1007,15 +1020,15 @@ function AgenticReadinessScore({ data }: { data: AnalysisData }) {
|
|||||||
<div className="grid grid-cols-2 gap-3 text-center">
|
<div className="grid grid-cols-2 gap-3 text-center">
|
||||||
<div className="p-2 bg-emerald-50 rounded-lg">
|
<div className="p-2 bg-emerald-50 rounded-lg">
|
||||||
<p className="text-lg font-bold text-emerald-700">{Math.round(tierPcts.AUTOMATE)}%</p>
|
<p className="text-lg font-bold text-emerald-700">{Math.round(tierPcts.AUTOMATE)}%</p>
|
||||||
<p className="text-[10px] text-emerald-600">Automatización completa</p>
|
<p className="text-[10px] text-emerald-600">{t('executiveSummary.fullAutomation')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 bg-cyan-50 rounded-lg">
|
<div className="p-2 bg-cyan-50 rounded-lg">
|
||||||
<p className="text-lg font-bold text-cyan-700">{Math.round(tierPcts.AUTOMATE + tierPcts.ASSIST)}%</p>
|
<p className="text-lg font-bold text-cyan-700">{Math.round(tierPcts.AUTOMATE + tierPcts.ASSIST)}%</p>
|
||||||
<p className="text-[10px] text-cyan-600">Con asistencia IA</p>
|
<p className="text-[10px] text-cyan-600">{t('executiveSummary.withAIAssistance')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-gray-400 text-center mt-2">
|
<p className="text-[10px] text-gray-400 text-center mt-2">
|
||||||
Basado en {formatNumber(totalQueueVolume)} interacciones analizadas
|
{t('executiveSummary.basedOnInteractions', { total: formatNumber(totalQueueVolume) })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -1094,29 +1107,31 @@ function TopOpportunities({ findings, opportunities }: {
|
|||||||
|
|
||||||
// v3.15: Economic Summary Compact
|
// v3.15: Economic Summary Compact
|
||||||
function EconomicSummary({ economicModel }: { economicModel: AnalysisData['economicModel'] }) {
|
function EconomicSummary({ economicModel }: { economicModel: AnalysisData['economicModel'] }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card padding="md">
|
<Card padding="md">
|
||||||
<h3 className="font-semibold text-gray-900 mb-3">Impacto Económico</h3>
|
<h3 className="font-semibold text-gray-900 mb-3">{t('executiveSummary.economicImpact')}</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 mb-3">
|
<div className="grid grid-cols-2 gap-3 mb-3">
|
||||||
<Stat
|
<Stat
|
||||||
value={formatCurrency(economicModel.currentAnnualCost)}
|
value={formatCurrency(economicModel.currentAnnualCost)}
|
||||||
label="Coste Anual"
|
label={t('executiveSummary.annualCost')}
|
||||||
/>
|
/>
|
||||||
<Stat
|
<Stat
|
||||||
value={formatCurrency(economicModel.annualSavings)}
|
value={formatCurrency(economicModel.annualSavings)}
|
||||||
label="Ahorro Potencial"
|
label={t('executiveSummary.potentialSavings')}
|
||||||
status="success"
|
status="success"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-2.5 bg-blue-50 rounded-lg">
|
<div className="flex items-center justify-between p-2.5 bg-blue-50 rounded-lg">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-blue-600">ROI 3 años</p>
|
<p className="text-xs text-blue-600">{t('executiveSummary.roi3Years')}</p>
|
||||||
<p className="text-lg font-bold text-blue-600">{economicModel.roi3yr}%</p>
|
<p className="text-lg font-bold text-blue-600">{economicModel.roi3yr}%</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-xs text-gray-500">Payback</p>
|
<p className="text-xs text-gray-500">{t('executiveSummary.payback')}</p>
|
||||||
<p className="text-lg font-bold text-gray-700">{economicModel.paybackMonths}m</p>
|
<p className="text-lg font-bold text-gray-700">{economicModel.paybackMonths}m</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1125,6 +1140,8 @@ function EconomicSummary({ economicModel }: { economicModel: AnalysisData['econo
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ExecutiveSummaryTab({ data, onTabChange }: ExecutiveSummaryTabProps) {
|
export function ExecutiveSummaryTab({ data, onTabChange }: ExecutiveSummaryTabProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Métricas básicas - VOLUME-WEIGHTED para consistencia con calculateHealthScore()
|
// Métricas básicas - VOLUME-WEIGHTED para consistencia con calculateHealthScore()
|
||||||
const totalInteractions = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
const totalInteractions = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
||||||
|
|
||||||
@@ -1204,7 +1221,7 @@ export function ExecutiveSummaryTab({ data, onTabChange }: ExecutiveSummaryTabPr
|
|||||||
{onTabChange && (
|
{onTabChange && (
|
||||||
<div className="bg-gray-50 rounded-lg p-4">
|
<div className="bg-gray-50 rounded-lg p-4">
|
||||||
<p className="text-xs font-medium text-gray-500 uppercase tracking-wider mb-3">
|
<p className="text-xs font-medium text-gray-500 uppercase tracking-wider mb-3">
|
||||||
Explorar análisis detallado
|
{t('executiveSummary.exploreDetailed')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||||
@@ -1218,12 +1235,12 @@ export function ExecutiveSummaryTab({ data, onTabChange }: ExecutiveSummaryTabPr
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium text-gray-700 text-sm">Dimensiones</span>
|
<span className="font-medium text-gray-700 text-sm">{t('executiveSummary.dimensionsTab')}</span>
|
||||||
{dimensionesConProblemas > 0 && (
|
{dimensionesConProblemas > 0 && (
|
||||||
<Badge label={`${dimensionesConProblemas} críticas`} variant="warning" size="sm" />
|
<Badge label={`${dimensionesConProblemas} ${t('executiveSummary.criticalQueues')}`} variant="warning" size="sm" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400">Eficiencia, resolución, satisfacción</p>
|
<p className="text-xs text-gray-400">{t('executiveSummary.dimensionsDesc')}</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-gray-500 group-hover:translate-x-0.5 transition-all" />
|
<ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-gray-500 group-hover:translate-x-0.5 transition-all" />
|
||||||
</button>
|
</button>
|
||||||
@@ -1238,12 +1255,12 @@ export function ExecutiveSummaryTab({ data, onTabChange }: ExecutiveSummaryTabPr
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium text-gray-700 text-sm">Agentic Readiness</span>
|
<span className="font-medium text-gray-700 text-sm">{t('executiveSummary.agenticReadinessTab')}</span>
|
||||||
{colasAutomate.length > 0 && (
|
{colasAutomate.length > 0 && (
|
||||||
<Badge label={`${colasAutomate.length} listas`} variant="success" size="sm" />
|
<Badge label={`${colasAutomate.length} ${t('executiveSummary.readyQueues')}`} variant="success" size="sm" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400">Colas elegibles para automatización</p>
|
<p className="text-xs text-gray-400">{t('executiveSummary.agenticReadinessDesc')}</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-gray-500 group-hover:translate-x-0.5 transition-all" />
|
<ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-gray-500 group-hover:translate-x-0.5 transition-all" />
|
||||||
</button>
|
</button>
|
||||||
@@ -1258,11 +1275,11 @@ export function ExecutiveSummaryTab({ data, onTabChange }: ExecutiveSummaryTabPr
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium text-gray-700 text-sm">Plan de Acción</span>
|
<span className="font-medium text-gray-700 text-sm">{t('executiveSummary.actionPlan')}</span>
|
||||||
<Badge label="Prioridad" variant="critical" size="sm" />
|
<Badge label={t('executiveSummary.priority')} variant="critical" size="sm" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400">
|
<p className="text-xs text-gray-400">
|
||||||
{ahorroTotal > 0 ? `Potencial: ${formatCurrency(ahorroTotal)}/año` : 'Roadmap de implementación'}
|
{ahorroTotal > 0 ? t('executiveSummary.potentialPerYear', { amount: formatCurrency(ahorroTotal) }) : t('executiveSummary.roadmapImplementation')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-gray-500 group-hover:translate-x-0.5 transition-all" />
|
<ChevronRight className="w-4 h-4 text-gray-300 group-hover:text-gray-500 group-hover:translate-x-0.5 transition-all" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Scale,
|
Scale,
|
||||||
Clock,
|
Clock,
|
||||||
@@ -65,7 +66,7 @@ const LAW_10_2025 = {
|
|||||||
// FUNCIONES DE EVALUACION DE COMPLIANCE
|
// FUNCIONES DE EVALUACION DE COMPLIANCE
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
function evaluateLaw07Compliance(data: AnalysisData): ComplianceResult {
|
function evaluateLaw07Compliance(data: AnalysisData, t: (key: string, options?: any) => string): ComplianceResult {
|
||||||
// Evaluar cobertura horaria basado en off_hours_pct
|
// Evaluar cobertura horaria basado en off_hours_pct
|
||||||
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
|
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
|
||||||
const offHoursPct = volumetryDim?.distribution_data?.off_hours_pct ?? null;
|
const offHoursPct = volumetryDim?.distribution_data?.off_hours_pct ?? null;
|
||||||
@@ -74,39 +75,39 @@ function evaluateLaw07Compliance(data: AnalysisData): ComplianceResult {
|
|||||||
return {
|
return {
|
||||||
status: 'SIN_DATOS',
|
status: 'SIN_DATOS',
|
||||||
score: 0,
|
score: 0,
|
||||||
gap: 'Sin datos de distribucion horaria',
|
gap: t('law10.compliance.law07.noData'),
|
||||||
details: ['No se encontraron datos de distribucion horaria en el analisis'],
|
details: [t('law10.compliance.law07.noDataDetails')],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const details: string[] = [];
|
const details: string[] = [];
|
||||||
details.push(`${offHoursPct.toFixed(1)}% de interacciones fuera de horario laboral`);
|
details.push(t('law10.compliance.law07.offHoursPercent', { percent: offHoursPct.toFixed(1) }));
|
||||||
|
|
||||||
if (offHoursPct < 5) {
|
if (offHoursPct < 5) {
|
||||||
return {
|
return {
|
||||||
status: 'CUMPLE',
|
status: 'CUMPLE',
|
||||||
score: 100,
|
score: 100,
|
||||||
gap: '-',
|
gap: '-',
|
||||||
details: [...details, 'Cobertura horaria adecuada'],
|
details: [...details, t('law10.compliance.law07.adequateCoverage')],
|
||||||
};
|
};
|
||||||
} else if (offHoursPct <= 15) {
|
} else if (offHoursPct <= 15) {
|
||||||
return {
|
return {
|
||||||
status: 'PARCIAL',
|
status: 'PARCIAL',
|
||||||
score: Math.round(100 - ((offHoursPct - 5) / 10) * 50),
|
score: Math.round(100 - ((offHoursPct - 5) / 10) * 50),
|
||||||
gap: `${(offHoursPct - 5).toFixed(1)}pp sobre optimo`,
|
gap: t('law10.compliance.law07.gapOverOptimal', { gap: (offHoursPct - 5).toFixed(1) }),
|
||||||
details: [...details, 'Cobertura horaria mejorable - considerar ampliar horarios'],
|
details: [...details, t('law10.compliance.law07.improvableCoverage')],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: 'NO_CUMPLE',
|
status: 'NO_CUMPLE',
|
||||||
score: Math.max(0, Math.round(50 - ((offHoursPct - 15) / 10) * 50)),
|
score: Math.max(0, Math.round(50 - ((offHoursPct - 15) / 10) * 50)),
|
||||||
gap: `${(offHoursPct - 15).toFixed(1)}pp sobre limite`,
|
gap: t('law10.compliance.law07.gapOverLimit', { gap: (offHoursPct - 15).toFixed(1) }),
|
||||||
details: [...details, 'Cobertura horaria insuficiente - requiere accion inmediata'],
|
details: [...details, t('law10.compliance.law07.insufficientCoverage')],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateLaw01Compliance(data: AnalysisData): ComplianceResult {
|
function evaluateLaw01Compliance(data: AnalysisData, t: (key: string, options?: any) => string): ComplianceResult {
|
||||||
// Evaluar tiempo de espera (hold_time) vs limite de 180 segundos
|
// Evaluar tiempo de espera (hold_time) vs limite de 180 segundos
|
||||||
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
||||||
|
|
||||||
@@ -114,8 +115,8 @@ function evaluateLaw01Compliance(data: AnalysisData): ComplianceResult {
|
|||||||
return {
|
return {
|
||||||
status: 'SIN_DATOS',
|
status: 'SIN_DATOS',
|
||||||
score: 0,
|
score: 0,
|
||||||
gap: 'Sin datos de tiempos de espera',
|
gap: t('law10.compliance.law01.noData'),
|
||||||
details: ['No se encontraron datos de hold_time en el analisis'],
|
details: [t('law10.compliance.law01.noDataDetails')],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,35 +136,35 @@ function evaluateLaw01Compliance(data: AnalysisData): ComplianceResult {
|
|||||||
const pctDentroLimite = (volDentroLimite / totalVolume) * 100;
|
const pctDentroLimite = (volDentroLimite / totalVolume) * 100;
|
||||||
|
|
||||||
const details: string[] = [];
|
const details: string[] = [];
|
||||||
details.push(`Tiempo de espera promedio: ${Math.round(avgHoldTime)}s (limite: 180s)`);
|
details.push(t('law10.compliance.law01.avgHoldTime', { time: Math.round(avgHoldTime) }));
|
||||||
details.push(`${pctDentroLimite.toFixed(1)}% de interacciones dentro del limite`);
|
details.push(t('law10.compliance.law01.withinLimit', { percent: pctDentroLimite.toFixed(1) }));
|
||||||
details.push(`${colasExceden.length} de ${data.heatmapData.length} colas exceden el limite`);
|
details.push(t('law10.compliance.law01.queuesExceedLimit', { count: colasExceden.length, total: data.heatmapData.length }));
|
||||||
|
|
||||||
if (avgHoldTime < 180 && pctColasExceden < 10) {
|
if (avgHoldTime < 180 && pctColasExceden < 10) {
|
||||||
return {
|
return {
|
||||||
status: 'CUMPLE',
|
status: 'CUMPLE',
|
||||||
score: 100,
|
score: 100,
|
||||||
gap: `-${Math.round(180 - avgHoldTime)}s`,
|
gap: t('law10.compliance.law01.gapNegative', { gap: Math.round(180 - avgHoldTime) }),
|
||||||
details,
|
details,
|
||||||
};
|
};
|
||||||
} else if (avgHoldTime < 180) {
|
} else if (avgHoldTime < 180) {
|
||||||
return {
|
return {
|
||||||
status: 'PARCIAL',
|
status: 'PARCIAL',
|
||||||
score: Math.round(90 - pctColasExceden),
|
score: Math.round(90 - pctColasExceden),
|
||||||
gap: `${colasExceden.length} colas fuera`,
|
gap: t('law10.compliance.law01.queuesOutside', { count: colasExceden.length }),
|
||||||
details,
|
details,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: 'NO_CUMPLE',
|
status: 'NO_CUMPLE',
|
||||||
score: Math.max(0, Math.round(50 - ((avgHoldTime - 180) / 60) * 25)),
|
score: Math.max(0, Math.round(50 - ((avgHoldTime - 180) / 60) * 25)),
|
||||||
gap: `+${Math.round(avgHoldTime - 180)}s`,
|
gap: t('law10.compliance.law01.gapPositive', { gap: Math.round(avgHoldTime - 180) }),
|
||||||
details,
|
details,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
|
function evaluateLaw02Compliance(data: AnalysisData, t: (key: string, options?: any) => string): ComplianceResult {
|
||||||
// Evaluar FCR y tasa de transferencia
|
// Evaluar FCR y tasa de transferencia
|
||||||
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
||||||
|
|
||||||
@@ -171,8 +172,8 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
|
|||||||
return {
|
return {
|
||||||
status: 'SIN_DATOS',
|
status: 'SIN_DATOS',
|
||||||
score: 0,
|
score: 0,
|
||||||
gap: 'Sin datos de resolucion',
|
gap: t('law10.compliance.law02.noData'),
|
||||||
details: ['No se encontraron datos de FCR o transferencias'],
|
details: [t('law10.compliance.law02.noDataDetails')],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,13 +188,13 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
|
|||||||
) / totalVolume;
|
) / totalVolume;
|
||||||
|
|
||||||
const details: string[] = [];
|
const details: string[] = [];
|
||||||
details.push(`FCR Tecnico: ${avgFCR.toFixed(1)}% (objetivo: >75%)`);
|
details.push(t('law10.compliance.law02.fcrTechnical', { fcr: avgFCR.toFixed(1) }));
|
||||||
details.push(`Tasa de transferencia: ${avgTransfer.toFixed(1)}% (objetivo: <15%)`);
|
details.push(t('law10.compliance.law02.transferRate', { rate: avgTransfer.toFixed(1) }));
|
||||||
|
|
||||||
// Colas con alto transfer
|
// Colas con alto transfer
|
||||||
const colasAltoTransfer = data.heatmapData.filter(h => h.metrics.transfer_rate > 25);
|
const colasAltoTransfer = data.heatmapData.filter(h => h.metrics.transfer_rate > 25);
|
||||||
if (colasAltoTransfer.length > 0) {
|
if (colasAltoTransfer.length > 0) {
|
||||||
details.push(`${colasAltoTransfer.length} colas con transfer >25%`);
|
details.push(t('law10.compliance.law02.highTransferQueues', { count: colasAltoTransfer.length }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const cumpleFCR = avgFCR >= 75;
|
const cumpleFCR = avgFCR >= 75;
|
||||||
@@ -205,7 +206,7 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
|
|||||||
return {
|
return {
|
||||||
status: 'CUMPLE',
|
status: 'CUMPLE',
|
||||||
score: 100,
|
score: 100,
|
||||||
gap: '-',
|
gap: t('law10.compliance.law02.gapDash'),
|
||||||
details,
|
details,
|
||||||
};
|
};
|
||||||
} else if (parcialFCR && parcialTransfer) {
|
} else if (parcialFCR && parcialTransfer) {
|
||||||
@@ -213,31 +214,33 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
|
|||||||
(Math.min(avgFCR, 75) / 75 * 50) +
|
(Math.min(avgFCR, 75) / 75 * 50) +
|
||||||
(Math.max(0, 25 - avgTransfer) / 25 * 50)
|
(Math.max(0, 25 - avgTransfer) / 25 * 50)
|
||||||
);
|
);
|
||||||
|
const fcrGap = avgFCR < 75 ? t('law10.compliance.law02.gapNegative', { gap: (75 - avgFCR).toFixed(0) }) : t('law10.compliance.law02.gapOk');
|
||||||
|
const transferGap = avgTransfer > 15 ? t('law10.compliance.law02.gapPositive', { gap: (avgTransfer - 15).toFixed(0) }) : t('law10.compliance.law02.gapOk');
|
||||||
return {
|
return {
|
||||||
status: 'PARCIAL',
|
status: 'PARCIAL',
|
||||||
score,
|
score,
|
||||||
gap: `FCR ${avgFCR < 75 ? `-${(75 - avgFCR).toFixed(0)}pp` : 'OK'}, Transfer ${avgTransfer > 15 ? `+${(avgTransfer - 15).toFixed(0)}pp` : 'OK'}`,
|
gap: t('law10.compliance.law02.gapFcr', { fcrGap, transferGap }),
|
||||||
details,
|
details,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: 'NO_CUMPLE',
|
status: 'NO_CUMPLE',
|
||||||
score: Math.max(0, Math.round((avgFCR / 75 * 30) + ((30 - avgTransfer) / 30 * 20))),
|
score: Math.max(0, Math.round((avgFCR / 75 * 30) + ((30 - avgTransfer) / 30 * 20))),
|
||||||
gap: `FCR -${(75 - avgFCR).toFixed(0)}pp, Transfer +${(avgTransfer - 15).toFixed(0)}pp`,
|
gap: `FCR ${t('law10.compliance.law02.gapNegative', { gap: (75 - avgFCR).toFixed(0) })}, Transfer ${t('law10.compliance.law02.gapPositive', { gap: (avgTransfer - 15).toFixed(0) })}`,
|
||||||
details,
|
details,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateLaw09Compliance(_data: AnalysisData): ComplianceResult {
|
function evaluateLaw09Compliance(_data: AnalysisData, t: (key: string, options?: any) => string): ComplianceResult {
|
||||||
// Los datos de idioma no estan disponibles en el modelo actual
|
// Los datos de idioma no estan disponibles en el modelo actual
|
||||||
return {
|
return {
|
||||||
status: 'SIN_DATOS',
|
status: 'SIN_DATOS',
|
||||||
score: 0,
|
score: 0,
|
||||||
gap: 'Requiere datos',
|
gap: t('law10.compliance.law09.noData'),
|
||||||
details: [
|
details: [
|
||||||
'No se dispone de datos de idioma en las interacciones',
|
t('law10.compliance.law09.noLanguageData'),
|
||||||
'Para evaluar este requisito se necesita el campo "language" en el CSV',
|
t('law10.compliance.law09.needsLanguageField'),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -273,12 +276,12 @@ function getStatusBadgeVariant(status: ComplianceStatus): 'success' | 'warning'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusLabel(status: ComplianceStatus): string {
|
function getStatusLabel(status: ComplianceStatus, t: (key: string) => string): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'CUMPLE': return 'Cumple';
|
case 'CUMPLE': return t('law10.status.CUMPLE');
|
||||||
case 'PARCIAL': return 'Parcial';
|
case 'PARCIAL': return t('law10.status.PARCIAL');
|
||||||
case 'NO_CUMPLE': return 'No Cumple';
|
case 'NO_CUMPLE': return t('law10.status.NO_CUMPLE');
|
||||||
default: return 'Sin Datos';
|
default: return t('law10.status.SIN_DATOS');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +291,7 @@ function Law10HeaderCountdown({
|
|||||||
}: {
|
}: {
|
||||||
complianceResults: { law07: ComplianceResult; law01: ComplianceResult; law02: ComplianceResult; law09: ComplianceResult };
|
complianceResults: { law07: ComplianceResult; law01: ComplianceResult; law02: ComplianceResult; law09: ComplianceResult };
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const deadline = LAW_10_2025.deadline;
|
const deadline = LAW_10_2025.deadline;
|
||||||
const diffTime = deadline.getTime() - now.getTime();
|
const diffTime = deadline.getTime() - now.getTime();
|
||||||
@@ -314,18 +318,14 @@ function Law10HeaderCountdown({
|
|||||||
<Lightbulb className="w-5 h-5 text-amber-600" />
|
<Lightbulb className="w-5 h-5 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="font-semibold text-gray-900">Sobre este Analisis</h2>
|
<h2 className="font-semibold text-gray-900">{t('law10.header.aboutThisAnalysis')}</h2>
|
||||||
<p className="text-sm text-gray-500">Ley 10/2025 de Atencion al Cliente</p>
|
<p className="text-sm text-gray-500">{t('law10.header.lawTitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Descripcion */}
|
{/* Descripcion */}
|
||||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-4">
|
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-4">
|
||||||
<p className="text-sm text-gray-700 leading-relaxed">
|
<p className="text-sm text-gray-700 leading-relaxed" dangerouslySetInnerHTML={{ __html: t('law10.header.description') }} />
|
||||||
Este modulo conecta tus <strong>metricas operacionales actuales</strong> con los requisitos de la
|
|
||||||
Ley 10/2025. No mide compliance directamente (requeriria datos adicionales), pero <strong>SI
|
|
||||||
identifica patrones</strong> que impactan en tu capacidad de cumplir con la normativa.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Metricas de estado */}
|
{/* Metricas de estado */}
|
||||||
@@ -334,9 +334,9 @@ function Law10HeaderCountdown({
|
|||||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||||
<Calendar className="w-5 h-5 text-gray-400" />
|
<Calendar className="w-5 h-5 text-gray-400" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Deadline de cumplimiento</p>
|
<p className="text-xs text-gray-500">{t('law10.header.complianceDeadline')}</p>
|
||||||
<p className="text-sm font-semibold text-gray-900">28 Diciembre 2026</p>
|
<p className="text-sm font-semibold text-gray-900">{t('law10.header.december282026')}</p>
|
||||||
<p className="text-xs text-gray-500">{diffDays} dias restantes</p>
|
<p className="text-xs text-gray-500">{t('law10.header.daysRemaining', { days: diffDays })}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -344,9 +344,9 @@ function Law10HeaderCountdown({
|
|||||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||||
<Scale className="w-5 h-5 text-gray-400" />
|
<Scale className="w-5 h-5 text-gray-400" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Requisitos evaluados</p>
|
<p className="text-xs text-gray-500">{t('law10.header.requirementsEvaluated')}</p>
|
||||||
<p className="text-sm font-semibold text-gray-900">{cumplidos} de {total} cumplen</p>
|
<p className="text-sm font-semibold text-gray-900">{t('law10.header.requirementsMet', { met: cumplidos, total })}</p>
|
||||||
<p className="text-xs text-gray-500">Basado en datos disponibles</p>
|
<p className="text-xs text-gray-500">{t('law10.header.basedOnData')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -354,18 +354,18 @@ function Law10HeaderCountdown({
|
|||||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||||
<StatusIcon status={overallStatus} />
|
<StatusIcon status={overallStatus} />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Estado general</p>
|
<p className="text-xs text-gray-500">{t('law10.header.overallStatus')}</p>
|
||||||
<p className={cn(
|
<p className={cn(
|
||||||
'text-sm font-semibold',
|
'text-sm font-semibold',
|
||||||
overallStatus === 'CUMPLE' && 'text-emerald-600',
|
overallStatus === 'CUMPLE' && 'text-emerald-600',
|
||||||
overallStatus === 'PARCIAL' && 'text-amber-600',
|
overallStatus === 'PARCIAL' && 'text-amber-600',
|
||||||
overallStatus === 'NO_CUMPLE' && 'text-red-600',
|
overallStatus === 'NO_CUMPLE' && 'text-red-600',
|
||||||
)}>
|
)}>
|
||||||
{getStatusLabel(overallStatus)}
|
{getStatusLabel(overallStatus, t)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
{overallStatus === 'CUMPLE' ? 'Buen estado' :
|
{overallStatus === 'CUMPLE' ? t('law10.header.goodState') :
|
||||||
overallStatus === 'PARCIAL' ? 'Requiere atencion' : 'Accion urgente'}
|
overallStatus === 'PARCIAL' ? t('law10.header.requiresAttention') : t('law10.header.urgentAction')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -376,6 +376,7 @@ function Law10HeaderCountdown({
|
|||||||
|
|
||||||
// Seccion: Cobertura Horaria (LAW-07)
|
// Seccion: Cobertura Horaria (LAW-07)
|
||||||
function TimeCoverageSection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
|
function TimeCoverageSection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
|
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
|
||||||
const hourlyData = volumetryDim?.distribution_data?.hourly || [];
|
const hourlyData = volumetryDim?.distribution_data?.hourly || [];
|
||||||
const dailyData = volumetryDim?.distribution_data?.daily || [];
|
const dailyData = volumetryDim?.distribution_data?.daily || [];
|
||||||
@@ -447,13 +448,13 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
|
|||||||
<Target className="w-5 h-5 text-blue-600" />
|
<Target className="w-5 h-5 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900">Cobertura Temporal: Disponibilidad del Servicio</h3>
|
<h3 className="font-semibold text-gray-900">{t('law10.timeCoverage.title')}</h3>
|
||||||
<p className="text-sm text-gray-500">Relacionado con Art. 14 - Servicios basicos 24/7</p>
|
<p className="text-sm text-gray-500">{t('law10.timeCoverage.article')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StatusIcon status={result.status} />
|
<StatusIcon status={result.status} />
|
||||||
<Badge label={getStatusLabel(result.status)} variant={getStatusBadgeVariant(result.status)} />
|
<Badge label={getStatusLabel(result.status, t)} variant={getStatusBadgeVariant(result.status)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -461,12 +462,12 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
|
|||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
|
||||||
<CheckCircle className="w-4 h-4" />
|
<CheckCircle className="w-4 h-4" />
|
||||||
LO QUE SABEMOS
|
{t('law10.timeCoverage.whatWeKnow')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{/* Heatmap 24x7 */}
|
{/* Heatmap 24x7 */}
|
||||||
<div className="bg-gray-50 rounded-lg p-4 mb-4">
|
<div className="bg-gray-50 rounded-lg p-4 mb-4">
|
||||||
<p className="text-xs text-gray-500 mb-3 font-medium">HEATMAP VOLUMETRICO 24x7</p>
|
<p className="text-xs text-gray-500 mb-3 font-medium">{t('law10.timeCoverage.heatmap247')}</p>
|
||||||
|
|
||||||
{/* Header de horas */}
|
{/* Header de horas */}
|
||||||
<div className="flex items-center mb-1">
|
<div className="flex items-center mb-1">
|
||||||
@@ -500,32 +501,32 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
|
|||||||
|
|
||||||
{/* Leyenda */}
|
{/* Leyenda */}
|
||||||
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
|
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
|
||||||
<span>Intensidad:</span>
|
<span>{t('law10.timeCoverage.intensity')}</span>
|
||||||
<span className="text-blue-200">▁ Bajo</span>
|
<span className="text-blue-200">▁ {t('law10.timeCoverage.intensityLow')}</span>
|
||||||
<span className="text-blue-400">▄ Medio</span>
|
<span className="text-blue-400">▄ {t('law10.timeCoverage.intensityMedium')}</span>
|
||||||
<span className="text-blue-600">█ Alto</span>
|
<span className="text-blue-600">█ {t('law10.timeCoverage.intensityHigh')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Hallazgos operacionales */}
|
{/* Hallazgos operacionales */}
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<p className="font-medium text-gray-700 mb-2">Hallazgos operacionales:</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.timeCoverage.operationalFindings')}</p>
|
||||||
<ul className="space-y-1.5 text-gray-600">
|
<ul className="space-y-1.5 text-gray-600">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span>Horario detectado: <strong>L-V 08:00-22:00</strong>, S-D horario reducido</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.timeCoverage.detectedSchedule') }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span>Volumen nocturno (22:00-08:00): <strong>{formatNumber(nightVolume)}</strong> interacciones ({nightPct.toFixed(1)}%)</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.timeCoverage.nightVolume', { volume: formatNumber(nightVolume), percent: nightPct.toFixed(1) }) }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span>Volumen madrugada (00:00-06:00): <strong>{formatNumber(earlyMorningVolume)}</strong> interacciones ({earlyMorningPct.toFixed(1)}%)</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.timeCoverage.earlyMorningVolume', { volume: formatNumber(earlyMorningVolume), percent: earlyMorningPct.toFixed(1) }) }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span>Pico maximo: <strong>{maxHourIndex}:00-{maxHourIndex + 1}:00</strong> ({maxHourPct.toFixed(1)}% del volumen diario)</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.timeCoverage.peakHour', { hour: maxHourIndex, hourEnd: maxHourIndex + 1, percent: maxHourPct.toFixed(1) }) }} />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -535,25 +536,25 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
|
|||||||
<div className="mb-5 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
<div className="mb-5 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
||||||
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
||||||
<Scale className="w-4 h-4" />
|
<Scale className="w-4 h-4" />
|
||||||
IMPLICACION LEY 10/2025
|
{t('law10.timeCoverage.lawImplication')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="space-y-3 text-sm text-gray-700">
|
<div className="space-y-3 text-sm text-gray-700">
|
||||||
<p>
|
<p>
|
||||||
<strong>Transporte aereo = Servicio basico</strong><br />
|
<strong>{t('law10.timeCoverage.basicServiceRequirement')}</strong><br />
|
||||||
<span className="text-gray-600">→ Art. 14 requiere atencion 24/7 para incidencias</span>
|
<span className="text-gray-600">{t('law10.timeCoverage.article14Requirement')}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="border-t border-amber-200 pt-3">
|
<div className="border-t border-amber-200 pt-3">
|
||||||
<p className="font-medium text-amber-900 mb-2">Gap identificado:</p>
|
<p className="font-medium text-amber-900 mb-2">{t('law10.timeCoverage.gapIdentified')}</p>
|
||||||
<ul className="space-y-1 text-gray-600">
|
<ul className="space-y-1 text-gray-600">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span><strong>{nightPct.toFixed(1)}%</strong> de tus clientes contactan fuera del horario actual</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.timeCoverage.clientsOutsideHours', { percent: nightPct.toFixed(1) }) }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span>Si estas son incidencias (equipaje perdido, cambios urgentes), <strong>NO cumples Art. 14</strong></span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.timeCoverage.complianceIssue') }} />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -564,32 +565,32 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
|
|||||||
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||||
<h4 className="text-sm font-semibold text-blue-800 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-blue-800 mb-3 flex items-center gap-2">
|
||||||
<Target className="w-4 h-4" />
|
<Target className="w-4 h-4" />
|
||||||
ACCION SUGERIDA
|
{t('law10.timeCoverage.suggestedAction')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="space-y-4 text-sm">
|
<div className="space-y-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-700 mb-2">1. Clasificar volumen nocturno por tipo:</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.timeCoverage.classifyNightVolume')}</p>
|
||||||
<ul className="space-y-1 text-gray-600 ml-4">
|
<ul className="space-y-1 text-gray-600 ml-4">
|
||||||
<li>• ¿Que % son incidencias criticas? → Requiere 24/7</li>
|
<li>• {t('law10.timeCoverage.criticalIncidents')}</li>
|
||||||
<li>• ¿Que % son consultas generales? → Pueden esperar</li>
|
<li>• {t('law10.timeCoverage.generalQueries')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-700 mb-2">2. Opciones de cobertura:</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.timeCoverage.coverageOptions')}</p>
|
||||||
<div className="space-y-2 ml-4">
|
<div className="space-y-2 ml-4">
|
||||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
||||||
<span className="text-gray-700">A) Chatbot IA + agente on-call</span>
|
<span className="text-gray-700">{t('law10.timeCoverage.optionAChatbot')}</span>
|
||||||
<span className="font-semibold text-emerald-600">~65K/año</span>
|
<span className="font-semibold text-emerald-600">{t('law10.timeCoverage.costPerYear', { cost: '65K' })}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
||||||
<span className="text-gray-700">B) Redirigir a call center 24/7 externo</span>
|
<span className="text-gray-700">{t('law10.timeCoverage.optionBExternal')}</span>
|
||||||
<span className="font-semibold text-amber-600">~95K/año</span>
|
<span className="font-semibold text-amber-600">{t('law10.timeCoverage.costPerYear', { cost: '95K' })}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
||||||
<span className="text-gray-700">C) Agentes nocturnos (3 turnos)</span>
|
<span className="text-gray-700">{t('law10.timeCoverage.optionCNight')}</span>
|
||||||
<span className="font-semibold text-red-600">~180K/año</span>
|
<span className="font-semibold text-red-600">{t('law10.timeCoverage.costPerYear', { cost: '180K' })}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -601,6 +602,7 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
|
|||||||
|
|
||||||
// Seccion: Velocidad de Respuesta (LAW-01)
|
// Seccion: Velocidad de Respuesta (LAW-01)
|
||||||
function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
|
function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
||||||
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
|
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
|
||||||
const hourlyData = volumetryDim?.distribution_data?.hourly || [];
|
const hourlyData = volumetryDim?.distribution_data?.hourly || [];
|
||||||
@@ -677,13 +679,13 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
|
|||||||
<Clock className="w-5 h-5 text-purple-600" />
|
<Clock className="w-5 h-5 text-purple-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900">Velocidad de Atencion: Eficiencia Operativa</h3>
|
<h3 className="font-semibold text-gray-900">{t('law10.responseSpeed.title')}</h3>
|
||||||
<p className="text-sm text-gray-500">Relacionado con Art. 8.2 - 95% llamadas <3min</p>
|
<p className="text-sm text-gray-500">{t('law10.responseSpeed.article')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StatusIcon status={result.status} />
|
<StatusIcon status={result.status} />
|
||||||
<Badge label={getStatusLabel(result.status)} variant={getStatusBadgeVariant(result.status)} />
|
<Badge label={getStatusLabel(result.status, t)} variant={getStatusBadgeVariant(result.status)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -691,22 +693,22 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
|
|||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
|
||||||
<CheckCircle className="w-4 h-4" />
|
<CheckCircle className="w-4 h-4" />
|
||||||
LO QUE SABEMOS
|
{t('law10.responseSpeed.whatWeKnow')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{/* Metricas principales */}
|
{/* Metricas principales */}
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
|
||||||
<div className="p-3 bg-gray-50 rounded-lg">
|
<div className="p-3 bg-gray-50 rounded-lg">
|
||||||
<p className="text-2xl font-bold text-gray-900">{abandonRate.toFixed(1)}%</p>
|
<p className="text-2xl font-bold text-gray-900">{abandonRate.toFixed(1)}%</p>
|
||||||
<p className="text-xs text-gray-600">Tasa abandono</p>
|
<p className="text-xs text-gray-600">{t('law10.responseSpeed.abandonmentRate')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-gray-50 rounded-lg">
|
<div className="p-3 bg-gray-50 rounded-lg">
|
||||||
<p className="text-2xl font-bold text-gray-900">{Math.round(ahtP50)}s</p>
|
<p className="text-2xl font-bold text-gray-900">{Math.round(ahtP50)}s</p>
|
||||||
<p className="text-xs text-gray-600">AHT P50 ({Math.floor(ahtP50 / 60)}m {Math.round(ahtP50 % 60)}s)</p>
|
<p className="text-xs text-gray-600">{t('law10.responseSpeed.ahtP50', { min: Math.floor(ahtP50 / 60), sec: Math.round(ahtP50 % 60) })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-gray-50 rounded-lg">
|
<div className="p-3 bg-gray-50 rounded-lg">
|
||||||
<p className="text-2xl font-bold text-gray-900">{Math.round(ahtP90)}s</p>
|
<p className="text-2xl font-bold text-gray-900">{Math.round(ahtP90)}s</p>
|
||||||
<p className="text-xs text-gray-600">AHT P90 ({Math.floor(ahtP90 / 60)}m {Math.round(ahtP90 % 60)}s)</p>
|
<p className="text-xs text-gray-600">{t('law10.responseSpeed.ahtP90', { min: Math.floor(ahtP90 / 60), sec: Math.round(ahtP90 % 60) })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'p-3 rounded-lg',
|
'p-3 rounded-lg',
|
||||||
@@ -716,13 +718,13 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
|
|||||||
'text-2xl font-bold',
|
'text-2xl font-bold',
|
||||||
ahtRatio > 2 ? 'text-amber-600' : 'text-gray-900'
|
ahtRatio > 2 ? 'text-amber-600' : 'text-gray-900'
|
||||||
)}>{ahtRatio.toFixed(1)}</p>
|
)}>{ahtRatio.toFixed(1)}</p>
|
||||||
<p className="text-xs text-gray-600">Ratio P90/P50 {ahtRatio > 2 && '(elevado)'}</p>
|
<p className="text-xs text-gray-600">{t('law10.responseSpeed.ratioP90P50', { elevated: ahtRatio > 2 ? t('law10.responseSpeed.elevated') : '' })}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grafico de abandonos por hora */}
|
{/* Grafico de abandonos por hora */}
|
||||||
<div className="bg-gray-50 rounded-lg p-4 mb-4">
|
<div className="bg-gray-50 rounded-lg p-4 mb-4">
|
||||||
<p className="text-xs text-gray-500 mb-3 font-medium">DISTRIBUCION DE ABANDONOS POR HORA</p>
|
<p className="text-xs text-gray-500 mb-3 font-medium">{t('law10.responseSpeed.abandonmentByHour')}</p>
|
||||||
<div className="flex items-end gap-0.5 h-16 mb-2">
|
<div className="flex items-end gap-0.5 h-16 mb-2">
|
||||||
{hourlyAbandonment.map((h, idx) => (
|
{hourlyAbandonment.map((h, idx) => (
|
||||||
<div
|
<div
|
||||||
@@ -744,28 +746,28 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
|
|||||||
<span>24:00</span>
|
<span>24:00</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
|
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
|
||||||
<span>Abandono:</span>
|
<span>{t('law10.responseSpeed.abandonmentLegend')}</span>
|
||||||
<span className="text-emerald-500">▁ <8%</span>
|
<span className="text-emerald-500" dangerouslySetInnerHTML={{ __html: `▁ ${t('law10.responseSpeed.abandonmentLow')}` }} />
|
||||||
<span className="text-amber-400">▃ 8-15%</span>
|
<span className="text-amber-400" dangerouslySetInnerHTML={{ __html: `▃ ${t('law10.responseSpeed.abandonmentMedium')}` }} />
|
||||||
<span className="text-red-500">█ >20%</span>
|
<span className="text-red-500" dangerouslySetInnerHTML={{ __html: `█ ${t('law10.responseSpeed.abandonmentHigh')}` }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Patrones observados */}
|
{/* Patrones observados */}
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<p className="font-medium text-gray-700 mb-2">Patrones observados:</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.responseSpeed.patternsObserved')}</p>
|
||||||
<ul className="space-y-1.5 text-gray-600">
|
<ul className="space-y-1.5 text-gray-600">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span>Mayor abandono: <strong>{maxAbandonHour.hour}:00-{maxAbandonHour.hour + 2}:00</strong> ({maxAbandonHour.abandonRate.toFixed(1)}% vs {abandonRate.toFixed(1)}% media)</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.maxAbandonment', { hourStart: maxAbandonHour.hour, hourEnd: maxAbandonHour.hour + 2, rate: maxAbandonHour.abandonRate.toFixed(1), avg: abandonRate.toFixed(1) }) }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span>AHT mas alto: <strong>Lunes 09:00-11:00</strong> ({Math.round(ahtP50 * 1.18)}s vs {Math.round(ahtP50)}s P50)</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.highestAht', { high: Math.round(ahtP50 * 1.18), p50: Math.round(ahtP50) }) }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span>Menor abandono: <strong>{minAbandonHour.hour}:00-{minAbandonHour.hour + 2}:00</strong> ({minAbandonHour.abandonRate.toFixed(1)}%)</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.minAbandonment', { hourStart: minAbandonHour.hour, hourEnd: minAbandonHour.hour + 2, rate: minAbandonHour.abandonRate.toFixed(1) }) }} />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -775,54 +777,47 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
|
|||||||
<div className="mb-5 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
<div className="mb-5 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
||||||
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
||||||
<Scale className="w-4 h-4" />
|
<Scale className="w-4 h-4" />
|
||||||
IMPLICACION LEY 10/2025
|
{t('law10.responseSpeed.lawImplication')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="space-y-3 text-sm text-gray-700">
|
<div className="space-y-3 text-sm text-gray-700">
|
||||||
<p>
|
<p dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.article82Requirement') }} />
|
||||||
<strong>Art. 8.2 requiere:</strong> "95% de llamadas atendidas en <3 minutos"
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="p-3 bg-amber-100/50 rounded border border-amber-300">
|
<div className="p-3 bg-amber-100/50 rounded border border-amber-300">
|
||||||
<p className="font-medium text-amber-900 mb-1 flex items-center gap-2">
|
<p className="font-medium text-amber-900 mb-1 flex items-center gap-2">
|
||||||
<AlertTriangle className="w-4 h-4" />
|
<AlertTriangle className="w-4 h-4" />
|
||||||
LIMITACION DE DATOS
|
{t('law10.responseSpeed.dataLimitation')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-600 text-xs">
|
<p className="text-gray-600 text-xs">
|
||||||
Tu CDR actual NO incluye ASA (tiempo en cola antes de responder),
|
{t('law10.responseSpeed.noAsaData')}
|
||||||
por lo que NO podemos medir este requisito directamente.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-amber-200 pt-3">
|
<div className="border-t border-amber-200 pt-3">
|
||||||
<p className="font-medium text-gray-700 mb-2">PERO SI sabemos:</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.responseSpeed.butWeKnow')}</p>
|
||||||
<ul className="space-y-1 text-gray-600">
|
<ul className="space-y-1 text-gray-600">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span><strong>{abandonRate.toFixed(1)}%</strong> de clientes abandonan → Probablemente esperaron mucho</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.customersAbandon', { rate: abandonRate.toFixed(1) }) }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span>Alta variabilidad AHT (P90/P50={ahtRatio.toFixed(1)}) → Cola impredecible</span>
|
<span>{t('law10.responseSpeed.highVariability', { ratio: ahtRatio.toFixed(1) })}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span>Picos de abandono coinciden con picos de volumen</span>
|
<span>{t('law10.responseSpeed.peaksCoincide')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 bg-white rounded border border-gray-200">
|
<div className="p-3 bg-white rounded border border-gray-200">
|
||||||
<p className="text-xs text-gray-500 mb-1">Estimacion conservadora (±10% margen error):</p>
|
<p className="text-xs text-gray-500 mb-1">{t('law10.responseSpeed.conservativeEstimate')}</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium" dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.likelyFastResponse', { percent: estimatedFastResponse.toFixed(0) }) }} />
|
||||||
→ ~<strong>{estimatedFastResponse.toFixed(0)}%</strong> de llamadas probablemente atendidas "rapido"
|
|
||||||
</p>
|
|
||||||
<p className={cn(
|
<p className={cn(
|
||||||
'font-medium',
|
'font-medium',
|
||||||
gapVs95 > 0 ? 'text-red-600' : 'text-emerald-600'
|
gapVs95 > 0 ? 'text-red-600' : 'text-emerald-600'
|
||||||
)}>
|
)} dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.gapVs95', { operator: gapVs95 > 0 ? '-' : '+', gap: Math.abs(gapVs95).toFixed(0) }) }} />
|
||||||
→ Gap vs 95% requerido: <strong>{gapVs95 > 0 ? '-' : '+'}{Math.abs(gapVs95).toFixed(0)}</strong> puntos porcentuales
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -831,32 +826,32 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
|
|||||||
<div className="p-4 bg-purple-50 border border-purple-200 rounded-lg">
|
<div className="p-4 bg-purple-50 border border-purple-200 rounded-lg">
|
||||||
<h4 className="text-sm font-semibold text-purple-800 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-purple-800 mb-3 flex items-center gap-2">
|
||||||
<Target className="w-4 h-4" />
|
<Target className="w-4 h-4" />
|
||||||
ACCION SUGERIDA
|
{t('law10.responseSpeed.suggestedAction')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="space-y-4 text-sm">
|
<div className="space-y-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-700 mb-2">1. CORTO PLAZO: Reducir AHT para aumentar capacidad</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.responseSpeed.shortTerm')}</p>
|
||||||
<ul className="space-y-1 text-gray-600 ml-4">
|
<ul className="space-y-1 text-gray-600 ml-4">
|
||||||
<li>• Tu Dimension 2 (Eficiencia) ya identifica:</li>
|
<li>• {t('law10.responseSpeed.dimension2Identifies')}</li>
|
||||||
<li className="ml-4 text-xs">- AHT elevado ({Math.round(ahtP50)}s vs 380s benchmark)</li>
|
<li className="ml-4 text-xs">{t('law10.responseSpeed.highAht', { aht: Math.round(ahtP50) })}</li>
|
||||||
<li className="ml-4 text-xs">- Oportunidad Copilot IA: -18% AHT proyectado</li>
|
<li className="ml-4 text-xs">{t('law10.responseSpeed.copilotOpportunity')}</li>
|
||||||
<li>• Beneficio dual: ↓ AHT = ↑ capacidad = ↓ cola = ↑ ASA</li>
|
<li>• {t('law10.responseSpeed.dualBenefit')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-700 mb-2">2. MEDIO PLAZO: Implementar tracking ASA real</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.responseSpeed.mediumTerm')}</p>
|
||||||
<div className="space-y-2 ml-4">
|
<div className="space-y-2 ml-4">
|
||||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
||||||
<span className="text-gray-700">Configuracion en plataforma</span>
|
<span className="text-gray-700">{t('law10.responseSpeed.platformConfig')}</span>
|
||||||
<span className="font-semibold text-purple-600">5-8K</span>
|
<span className="font-semibold text-purple-600">5-8K</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200">
|
||||||
<span className="text-gray-700">Timeline implementacion</span>
|
<span className="text-gray-700">{t('law10.responseSpeed.implementationTimeline')}</span>
|
||||||
<span className="font-semibold text-gray-600">4-6 semanas</span>
|
<span className="font-semibold text-gray-600">{t('law10.responseSpeed.implementationWeeks')}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500">Beneficio: Medicion precisa para auditoria ENAC</p>
|
<p className="text-xs text-gray-500">{t('law10.responseSpeed.benefit')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -867,6 +862,7 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
|
|||||||
|
|
||||||
// Seccion: Calidad de Resolucion (LAW-02)
|
// Seccion: Calidad de Resolucion (LAW-02)
|
||||||
function ResolutionQualitySection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
|
function ResolutionQualitySection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
|
||||||
|
|
||||||
// FCR Tecnico y Real
|
// FCR Tecnico y Real
|
||||||
@@ -925,13 +921,13 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
|
|||||||
<Target className="w-5 h-5 text-emerald-600" />
|
<Target className="w-5 h-5 text-emerald-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900">Calidad de Resolucion: Efectividad</h3>
|
<h3 className="font-semibold text-gray-900">{t('law10.resolutionQuality.title')}</h3>
|
||||||
<p className="text-sm text-gray-500">Relacionado con Art. 17 - Resolucion en 15 dias</p>
|
<p className="text-sm text-gray-500">{t('law10.resolutionQuality.article')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StatusIcon status={result.status} />
|
<StatusIcon status={result.status} />
|
||||||
<Badge label={getStatusLabel(result.status)} variant={getStatusBadgeVariant(result.status)} />
|
<Badge label={getStatusLabel(result.status, t)} variant={getStatusBadgeVariant(result.status)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -939,7 +935,7 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
|
|||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
|
||||||
<CheckCircle className="w-4 h-4" />
|
<CheckCircle className="w-4 h-4" />
|
||||||
LO QUE SABEMOS
|
{t('law10.resolutionQuality.whatWeKnow')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{/* Metricas principales */}
|
{/* Metricas principales */}
|
||||||
@@ -952,21 +948,21 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
|
|||||||
'text-2xl font-bold',
|
'text-2xl font-bold',
|
||||||
avgFCRReal >= 60 ? 'text-gray-900' : 'text-red-600'
|
avgFCRReal >= 60 ? 'text-gray-900' : 'text-red-600'
|
||||||
)}>{avgFCRReal.toFixed(0)}%</p>
|
)}>{avgFCRReal.toFixed(0)}%</p>
|
||||||
<p className="text-xs text-gray-600">FCR Real (fcr_real_flag)</p>
|
<p className="text-xs text-gray-600">{t('law10.resolutionQuality.fcrReal')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-gray-50 rounded-lg">
|
<div className="p-3 bg-gray-50 rounded-lg">
|
||||||
<p className="text-2xl font-bold text-gray-900">{recontactRate7d.toFixed(0)}%</p>
|
<p className="text-2xl font-bold text-gray-900">{recontactRate7d.toFixed(0)}%</p>
|
||||||
<p className="text-xs text-gray-600">Tasa recontacto 7 dias</p>
|
<p className="text-xs text-gray-600">{t('law10.resolutionQuality.recontactRate7d')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-gray-50 rounded-lg">
|
<div className="p-3 bg-gray-50 rounded-lg">
|
||||||
<p className="text-2xl font-bold text-gray-900">{repeatCallsPct.toFixed(0)}%</p>
|
<p className="text-2xl font-bold text-gray-900">{repeatCallsPct.toFixed(0)}%</p>
|
||||||
<p className="text-xs text-gray-600">Llamadas repetidas</p>
|
<p className="text-xs text-gray-600">{t('law10.resolutionQuality.repeatCalls')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grafico FCR por skill */}
|
{/* Grafico FCR por skill */}
|
||||||
<div className="bg-gray-50 rounded-lg p-4 mb-4">
|
<div className="bg-gray-50 rounded-lg p-4 mb-4">
|
||||||
<p className="text-xs text-gray-500 mb-3 font-medium">FCR POR SKILL/QUEUE</p>
|
<p className="text-xs text-gray-500 mb-3 font-medium">{t('law10.resolutionQuality.fcrBySkill')}</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{skillFCRData.slice(0, 8).map((s, idx) => (
|
{skillFCRData.slice(0, 8).map((s, idx) => (
|
||||||
<div key={idx} className="flex items-center gap-2">
|
<div key={idx} className="flex items-center gap-2">
|
||||||
@@ -994,22 +990,22 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
|
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
|
||||||
<span>FCR:</span>
|
<span>{t('law10.resolutionQuality.fcrLegend')}</span>
|
||||||
<span className="text-red-500">▁ <45%</span>
|
<span className="text-red-500" dangerouslySetInnerHTML={{ __html: `▁ ${t('law10.resolutionQuality.fcrLow')}` }} />
|
||||||
<span className="text-amber-400">▃ 45-65%</span>
|
<span className="text-amber-400" dangerouslySetInnerHTML={{ __html: `▃ ${t('law10.resolutionQuality.fcrMedium')}` }} />
|
||||||
<span className="text-emerald-500">█ >75%</span>
|
<span className="text-emerald-500" dangerouslySetInnerHTML={{ __html: `█ ${t('law10.resolutionQuality.fcrHigh')}` }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top skills con FCR bajo */}
|
{/* Top skills con FCR bajo */}
|
||||||
{lowFCRSkills.length > 0 && (
|
{lowFCRSkills.length > 0 && (
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<p className="font-medium text-gray-700 mb-2">Top skills con FCR bajo:</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.resolutionQuality.topLowFcr')}</p>
|
||||||
<ul className="space-y-1.5 text-gray-600">
|
<ul className="space-y-1.5 text-gray-600">
|
||||||
{lowFCRSkills.map((s, idx) => (
|
{lowFCRSkills.map((s, idx) => (
|
||||||
<li key={idx} className="flex items-start gap-2">
|
<li key={idx} className="flex items-start gap-2">
|
||||||
<span className="text-gray-400">{idx + 1}.</span>
|
<span className="text-gray-400">{idx + 1}.</span>
|
||||||
<span><strong>{s.skill}</strong>: {s.fcrReal.toFixed(0)}% FCR</span>
|
<span><strong>{s.skill}</strong>: {t('law10.resolutionQuality.fcrValue', { fcr: s.fcrReal.toFixed(0) })}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -1021,48 +1017,44 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
|
|||||||
<div className="mb-5 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
<div className="mb-5 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
||||||
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
||||||
<Scale className="w-4 h-4" />
|
<Scale className="w-4 h-4" />
|
||||||
IMPLICACION LEY 10/2025
|
{t('law10.resolutionQuality.lawImplication')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="space-y-3 text-sm text-gray-700">
|
<div className="space-y-3 text-sm text-gray-700">
|
||||||
<p>
|
<p dangerouslySetInnerHTML={{ __html: t('law10.resolutionQuality.article17Requirement') }} />
|
||||||
<strong>Art. 17 requiere:</strong> "Resolucion de reclamaciones ≤15 dias"
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="p-3 bg-amber-100/50 rounded border border-amber-300">
|
<div className="p-3 bg-amber-100/50 rounded border border-amber-300">
|
||||||
<p className="font-medium text-amber-900 mb-1 flex items-center gap-2">
|
<p className="font-medium text-amber-900 mb-1 flex items-center gap-2">
|
||||||
<AlertTriangle className="w-4 h-4" />
|
<AlertTriangle className="w-4 h-4" />
|
||||||
LIMITACION DE DATOS
|
{t('law10.resolutionQuality.dataLimitation')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-600 text-xs">
|
<p className="text-gray-600 text-xs">
|
||||||
Tu CDR solo registra interacciones individuales, NO casos multi-touch
|
{t('law10.resolutionQuality.noCaseTracking')}
|
||||||
ni tiempo total de resolucion.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-amber-200 pt-3">
|
<div className="border-t border-amber-200 pt-3">
|
||||||
<p className="font-medium text-gray-700 mb-2">PERO SI sabemos:</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.resolutionQuality.butWeKnow')}</p>
|
||||||
<ul className="space-y-1 text-gray-600">
|
<ul className="space-y-1 text-gray-600">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span><strong>{recontactRate7d.toFixed(0)}%</strong> de casos requieren multiples contactos</span>
|
<span dangerouslySetInnerHTML={{ __html: t('law10.resolutionQuality.multipleContactsRequired', { percent: recontactRate7d.toFixed(0) }) }} />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span>FCR {avgFCRReal.toFixed(0)}% = {recontactRate7d.toFixed(0)}% NO resuelto en primera interaccion</span>
|
<span>{t('law10.resolutionQuality.fcrGap', { fcr: avgFCRReal.toFixed(0), gap: recontactRate7d.toFixed(0) })}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-500">•</span>
|
<span className="text-amber-500">•</span>
|
||||||
<span>Esto sugiere procesos complejos o informacion fragmentada</span>
|
<span>{t('law10.resolutionQuality.complexProcesses')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 bg-red-50 rounded border border-red-200">
|
<div className="p-3 bg-red-50 rounded border border-red-200">
|
||||||
<p className="font-medium text-red-800 mb-1">Senal de alerta:</p>
|
<p className="font-medium text-red-800 mb-1">{t('law10.resolutionQuality.alertSignal')}</p>
|
||||||
<p className="text-gray-600 text-xs">
|
<p className="text-gray-600 text-xs">
|
||||||
Si los clientes recontactan multiples veces por el mismo tema, es probable
|
{t('law10.resolutionQuality.resolutionTimeRisk')}
|
||||||
que el tiempo TOTAL de resolucion supere los 15 dias requeridos por ley.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1072,30 +1064,30 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
|
|||||||
<div className="p-4 bg-emerald-50 border border-emerald-200 rounded-lg">
|
<div className="p-4 bg-emerald-50 border border-emerald-200 rounded-lg">
|
||||||
<h4 className="text-sm font-semibold text-emerald-800 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-emerald-800 mb-3 flex items-center gap-2">
|
||||||
<Target className="w-4 h-4" />
|
<Target className="w-4 h-4" />
|
||||||
ACCION SUGERIDA
|
{t('law10.resolutionQuality.suggestedAction')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="space-y-4 text-sm">
|
<div className="space-y-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-700 mb-2">1. DIAGNOSTICO: Implementar sistema de casos/tickets</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.resolutionQuality.diagnosis')}</p>
|
||||||
<ul className="space-y-1 text-gray-600 ml-4">
|
<ul className="space-y-1 text-gray-600 ml-4">
|
||||||
<li>• Registrar fecha apertura + cierre</li>
|
<li>• {t('law10.resolutionQuality.registerOpenClose')}</li>
|
||||||
<li>• Vincular multiples interacciones al mismo caso</li>
|
<li>• {t('law10.resolutionQuality.linkInteractions')}</li>
|
||||||
<li>• Tipologia: consulta / reclamacion / incidencia</li>
|
<li>• {t('law10.resolutionQuality.typology')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200 mt-2 ml-4">
|
<div className="flex items-center justify-between p-2 bg-white rounded border border-gray-200 mt-2 ml-4">
|
||||||
<span className="text-gray-700">Inversion CRM/Ticketing</span>
|
<span className="text-gray-700">{t('law10.resolutionQuality.crmInvestment')}</span>
|
||||||
<span className="font-semibold text-emerald-600">15-25K</span>
|
<span className="font-semibold text-emerald-600">15-25K</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-700 mb-2">2. MEJORA OPERATIVA: Aumentar FCR</p>
|
<p className="font-medium text-gray-700 mb-2">{t('law10.resolutionQuality.operationalImprovement')}</p>
|
||||||
<ul className="space-y-1 text-gray-600 ml-4">
|
<ul className="space-y-1 text-gray-600 ml-4">
|
||||||
<li>• Tu Dimension 3 (Efectividad) ya identifica:</li>
|
<li>• {t('law10.resolutionQuality.dimension3Identifies')}</li>
|
||||||
<li className="ml-4 text-xs">- Root causes: info fragmentada, falta empowerment</li>
|
<li className="ml-4 text-xs">{t('law10.resolutionQuality.rootCauses')}</li>
|
||||||
<li className="ml-4 text-xs">- Solucion: Knowledge base + decision trees</li>
|
<li className="ml-4 text-xs">{t('law10.resolutionQuality.solution')}</li>
|
||||||
<li>• Beneficio: ↑ FCR = ↓ recontactos = ↓ tiempo total</li>
|
<li>• {t('law10.resolutionQuality.fcrBenefit')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1112,86 +1104,88 @@ function Law10SummaryRoadmap({
|
|||||||
complianceResults: { law07: ComplianceResult; law01: ComplianceResult; law02: ComplianceResult; law09: ComplianceResult };
|
complianceResults: { law07: ComplianceResult; law01: ComplianceResult; law02: ComplianceResult; law09: ComplianceResult };
|
||||||
data: AnalysisData;
|
data: AnalysisData;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Resultado por defecto para requisitos sin datos
|
// Resultado por defecto para requisitos sin datos
|
||||||
const sinDatos: ComplianceResult = {
|
const sinDatos: ComplianceResult = {
|
||||||
status: 'SIN_DATOS',
|
status: 'SIN_DATOS',
|
||||||
score: 0,
|
score: 0,
|
||||||
gap: 'Requiere datos',
|
gap: t('law10.common.requiredData'),
|
||||||
details: ['No se dispone de datos para evaluar este requisito'],
|
details: [t('law10.common.noData')],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Todos los requisitos de la Ley 10/2025 con descripciones
|
// Todos los requisitos de la Ley 10/2025 con descripciones
|
||||||
const allRequirements = [
|
const allRequirements = [
|
||||||
{
|
{
|
||||||
id: 'LAW-01',
|
id: 'LAW-01',
|
||||||
name: 'Tiempo de Espera',
|
name: t('law10.summary.requirements.LAW-01.name'),
|
||||||
description: 'Tiempo maximo de espera de 3 minutos para atencion telefonica',
|
description: t('law10.summary.requirements.LAW-01.description'),
|
||||||
result: complianceResults.law01,
|
result: complianceResults.law01,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-02',
|
id: 'LAW-02',
|
||||||
name: 'Resolucion Efectiva',
|
name: t('law10.summary.requirements.LAW-02.name'),
|
||||||
description: 'Resolucion en primera contacto sin transferencias innecesarias',
|
description: t('law10.summary.requirements.LAW-02.description'),
|
||||||
result: complianceResults.law02,
|
result: complianceResults.law02,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-03',
|
id: 'LAW-03',
|
||||||
name: 'Acceso a Agente Humano',
|
name: t('law10.summary.requirements.LAW-03.name'),
|
||||||
description: 'Derecho a hablar con un agente humano en cualquier momento',
|
description: t('law10.summary.requirements.LAW-03.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-04',
|
id: 'LAW-04',
|
||||||
name: 'Grabacion de Llamadas',
|
name: t('law10.summary.requirements.LAW-04.name'),
|
||||||
description: 'Notificacion previa de grabacion y acceso a la misma',
|
description: t('law10.summary.requirements.LAW-04.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-05',
|
id: 'LAW-05',
|
||||||
name: 'Accesibilidad',
|
name: t('law10.summary.requirements.LAW-05.name'),
|
||||||
description: 'Canales accesibles para personas con discapacidad',
|
description: t('law10.summary.requirements.LAW-05.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-06',
|
id: 'LAW-06',
|
||||||
name: 'Confirmacion Escrita',
|
name: t('law10.summary.requirements.LAW-06.name'),
|
||||||
description: 'Confirmacion por escrito de reclamaciones y gestiones',
|
description: t('law10.summary.requirements.LAW-06.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-07',
|
id: 'LAW-07',
|
||||||
name: 'Cobertura Horaria',
|
name: t('law10.summary.requirements.LAW-07.name'),
|
||||||
description: 'Atencion 24/7 para servicios esenciales o horario ampliado',
|
description: t('law10.summary.requirements.LAW-07.description'),
|
||||||
result: complianceResults.law07,
|
result: complianceResults.law07,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-08',
|
id: 'LAW-08',
|
||||||
name: 'Formacion de Agentes',
|
name: t('law10.summary.requirements.LAW-08.name'),
|
||||||
description: 'Personal cualificado y formado en atencion al cliente',
|
description: t('law10.summary.requirements.LAW-08.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-09',
|
id: 'LAW-09',
|
||||||
name: 'Idiomas Cooficiales',
|
name: t('law10.summary.requirements.LAW-09.name'),
|
||||||
description: 'Atencion en catalan, euskera, gallego y valenciano',
|
description: t('law10.summary.requirements.LAW-09.description'),
|
||||||
result: complianceResults.law09,
|
result: complianceResults.law09,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-10',
|
id: 'LAW-10',
|
||||||
name: 'Plazos de Resolucion',
|
name: t('law10.summary.requirements.LAW-10.name'),
|
||||||
description: 'Resolucion de reclamaciones en maximo 15 dias habiles',
|
description: t('law10.summary.requirements.LAW-10.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-11',
|
id: 'LAW-11',
|
||||||
name: 'Gratuidad del Servicio',
|
name: t('law10.summary.requirements.LAW-11.name'),
|
||||||
description: 'Atencion telefonica sin coste adicional (numeros 900)',
|
description: t('law10.summary.requirements.LAW-11.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'LAW-12',
|
id: 'LAW-12',
|
||||||
name: 'Trazabilidad',
|
name: t('law10.summary.requirements.LAW-12.name'),
|
||||||
description: 'Numero de referencia para seguimiento de gestiones',
|
description: t('law10.summary.requirements.LAW-12.description'),
|
||||||
result: sinDatos,
|
result: sinDatos,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1496,12 +1490,14 @@ function DataMaturitySummary({ data }: { data: AnalysisData }) {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export function Law10Tab({ data }: Law10TabProps) {
|
export function Law10Tab({ data }: Law10TabProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Evaluar compliance para cada requisito
|
// Evaluar compliance para cada requisito
|
||||||
const complianceResults = {
|
const complianceResults = {
|
||||||
law07: evaluateLaw07Compliance(data),
|
law07: evaluateLaw07Compliance(data, t),
|
||||||
law01: evaluateLaw01Compliance(data),
|
law01: evaluateLaw01Compliance(data, t),
|
||||||
law02: evaluateLaw02Compliance(data),
|
law02: evaluateLaw02Compliance(data, t),
|
||||||
law09: evaluateLaw09Compliance(data),
|
law09: evaluateLaw09Compliance(data, t),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
ArrowRight, Info, Users, Target, Zap, Shield,
|
ArrowRight, Info, Users, Target, Zap, Shield,
|
||||||
ChevronDown, ChevronUp, BookOpen, Bot, Settings, Rocket, AlertCircle
|
ChevronDown, ChevronUp, BookOpen, Bot, Settings, Rocket, AlertCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RoadmapPhase } from '../../types';
|
import { RoadmapPhase } from '../../types';
|
||||||
import type { AnalysisData, RoadmapInitiative, HeatmapDataPoint, DrilldownDataPoint, OriginalQueueMetrics, AgenticTier } from '../../types';
|
import type { AnalysisData, RoadmapInitiative, HeatmapDataPoint, DrilldownDataPoint, OriginalQueueMetrics, AgenticTier } from '../../types';
|
||||||
import {
|
import {
|
||||||
@@ -450,6 +451,8 @@ function OpportunityBubbleChart({
|
|||||||
heatmapData: HeatmapDataPoint[];
|
heatmapData: HeatmapDataPoint[];
|
||||||
drilldownData?: DrilldownDataPoint[]
|
drilldownData?: DrilldownDataPoint[]
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// v3.5: Usar drilldownData si está disponible para tener info de Tier por cola
|
// v3.5: Usar drilldownData si está disponible para tener info de Tier por cola
|
||||||
let chartData: BubbleDataPoint[] = [];
|
let chartData: BubbleDataPoint[] = [];
|
||||||
|
|
||||||
@@ -517,10 +520,10 @@ function OpportunityBubbleChart({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-800 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-800 flex items-center gap-2">
|
||||||
<Target className="w-5 h-5 text-[#6D84E3]" />
|
<Target className="w-5 h-5 text-[#6D84E3]" />
|
||||||
Mapa de Oportunidades por Tier
|
{t('roadmap.opportunityMapTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
Factibilidad (Score) vs Impacto Económico (Ahorro TCO) • Tamaño = Volumen • Color = Tier
|
{t('roadmap.opportunityMapSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -529,12 +532,12 @@ function OpportunityBubbleChart({
|
|||||||
<div className="relative" style={{ height: '340px' }}>
|
<div className="relative" style={{ height: '340px' }}>
|
||||||
{/* Y-axis label */}
|
{/* Y-axis label */}
|
||||||
<div className="absolute -left-2 top-1/2 -translate-y-1/2 -rotate-90 text-xs text-gray-600 font-semibold whitespace-nowrap">
|
<div className="absolute -left-2 top-1/2 -translate-y-1/2 -rotate-90 text-xs text-gray-600 font-semibold whitespace-nowrap">
|
||||||
IMPACTO ECONÓMICO (Ahorro TCO €/año)
|
{t('roadmap.economicImpactAxis')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* X-axis label */}
|
{/* X-axis label */}
|
||||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 text-xs text-gray-600 font-semibold">
|
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 text-xs text-gray-600 font-semibold">
|
||||||
FACTIBILIDAD (Agentic Readiness Score 0-10)
|
{t('roadmap.feasibilityAxis')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Chart area */}
|
{/* Chart area */}
|
||||||
@@ -553,7 +556,7 @@ function OpportunityBubbleChart({
|
|||||||
style={{ left: `${(7.5 / 10) * 100}%` }}
|
style={{ left: `${(7.5 / 10) * 100}%` }}
|
||||||
>
|
>
|
||||||
<span className="absolute -top-5 left-1/2 -translate-x-1/2 text-[9px] text-emerald-600 font-medium whitespace-nowrap">
|
<span className="absolute -top-5 left-1/2 -translate-x-1/2 text-[9px] text-emerald-600 font-medium whitespace-nowrap">
|
||||||
Tier AUTOMATE ≥7.5
|
{t('roadmap.tierAutomateThreshold')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -566,27 +569,27 @@ function OpportunityBubbleChart({
|
|||||||
{/* Quadrant labels - basados en Score (X) y Ahorro (Y) */}
|
{/* Quadrant labels - basados en Score (X) y Ahorro (Y) */}
|
||||||
{/* Top-right: High Score + High Savings = QUICK WINS */}
|
{/* Top-right: High Score + High Savings = QUICK WINS */}
|
||||||
<div className="absolute top-2 right-2 text-xs bg-emerald-100 px-2.5 py-1.5 rounded-lg border-2 border-emerald-400 shadow-sm">
|
<div className="absolute top-2 right-2 text-xs bg-emerald-100 px-2.5 py-1.5 rounded-lg border-2 border-emerald-400 shadow-sm">
|
||||||
<div className="font-bold text-emerald-700">🎯 QUICK WINS</div>
|
<div className="font-bold text-emerald-700">🎯 {t('roadmap.quadrantQuickWins')}</div>
|
||||||
<div className="text-[9px] text-emerald-600">Score ≥7.5 + Ahorro alto</div>
|
<div className="text-[9px] text-emerald-600">{t('roadmap.quadrantQuickWinsDesc')}</div>
|
||||||
<div className="text-[9px] text-emerald-500 font-medium">→ Prioridad 1</div>
|
<div className="text-[9px] text-emerald-500 font-medium">{t('roadmap.quadrantQuickWinsPriority')}</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Top-left: Low Score + High Savings = OPTIMIZE */}
|
{/* Top-left: Low Score + High Savings = OPTIMIZE */}
|
||||||
<div className="absolute top-2 left-2 text-xs bg-amber-100 px-2.5 py-1.5 rounded-lg border-2 border-amber-400 shadow-sm">
|
<div className="absolute top-2 left-2 text-xs bg-amber-100 px-2.5 py-1.5 rounded-lg border-2 border-amber-400 shadow-sm">
|
||||||
<div className="font-bold text-amber-700">⚙️ OPTIMIZE</div>
|
<div className="font-bold text-amber-700">⚙️ {t('roadmap.quadrantOptimize')}</div>
|
||||||
<div className="text-[9px] text-amber-600">Score <7.5 + Ahorro alto</div>
|
<div className="text-[9px] text-amber-600">{t('roadmap.quadrantOptimizeDesc')}</div>
|
||||||
<div className="text-[9px] text-amber-500 font-medium">→ Wave 1 primero</div>
|
<div className="text-[9px] text-amber-500 font-medium">{t('roadmap.quadrantOptimizePriority')}</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Bottom-right: High Score + Low Savings = STRATEGIC */}
|
{/* Bottom-right: High Score + Low Savings = STRATEGIC */}
|
||||||
<div className="absolute bottom-10 right-2 text-xs bg-blue-100 px-2.5 py-1.5 rounded-lg border-2 border-blue-400 shadow-sm">
|
<div className="absolute bottom-10 right-2 text-xs bg-blue-100 px-2.5 py-1.5 rounded-lg border-2 border-blue-400 shadow-sm">
|
||||||
<div className="font-bold text-blue-700">📊 STRATEGIC</div>
|
<div className="font-bold text-blue-700">📊 {t('roadmap.quadrantStrategic')}</div>
|
||||||
<div className="text-[9px] text-blue-600">Score ≥7.5 + Ahorro bajo</div>
|
<div className="text-[9px] text-blue-600">{t('roadmap.quadrantStrategicDesc')}</div>
|
||||||
<div className="text-[9px] text-blue-500 font-medium">→ Evaluar ROI</div>
|
<div className="text-[9px] text-blue-500 font-medium">{t('roadmap.quadrantStrategicPriority')}</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Bottom-left: Low Score + Low Savings = DEFER */}
|
{/* Bottom-left: Low Score + Low Savings = DEFER */}
|
||||||
<div className="absolute bottom-10 left-2 text-xs bg-gray-100 px-2.5 py-1.5 rounded-lg border-2 border-gray-300 shadow-sm">
|
<div className="absolute bottom-10 left-2 text-xs bg-gray-100 px-2.5 py-1.5 rounded-lg border-2 border-gray-300 shadow-sm">
|
||||||
<div className="font-bold text-gray-600">📋 DEFER</div>
|
<div className="font-bold text-gray-600">📋 {t('roadmap.quadrantDefer')}</div>
|
||||||
<div className="text-[9px] text-gray-500">Score <7.5 + Ahorro bajo</div>
|
<div className="text-[9px] text-gray-500">{t('roadmap.quadrantDeferDesc')}</div>
|
||||||
<div className="text-[9px] text-gray-400 font-medium">→ Backlog</div>
|
<div className="text-[9px] text-gray-400 font-medium">{t('roadmap.quadrantDeferPriority')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bubbles */}
|
{/* Bubbles */}
|
||||||
@@ -629,19 +632,19 @@ function OpportunityBubbleChart({
|
|||||||
<div className="font-semibold text-sm">{item.name}</div>
|
<div className="font-semibold text-sm">{item.name}</div>
|
||||||
<div className="mt-1 space-y-0.5">
|
<div className="mt-1 space-y-0.5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-gray-400">Score:</span>
|
<span className="text-gray-400">{t('roadmap.tooltipScore')}</span>
|
||||||
<span className="font-medium">{item.feasibility.toFixed(1)}/10</span>
|
<span className="font-medium">{item.feasibility.toFixed(1)}/10</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-gray-400">Volumen:</span>
|
<span className="text-gray-400">{t('roadmap.tooltipVolume')}</span>
|
||||||
<span className="font-medium">{item.volume.toLocaleString()}/mes</span>
|
<span className="font-medium">{item.volume.toLocaleString()}{t('roadmap.perMonth')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-gray-400">Ahorro TCO:</span>
|
<span className="text-gray-400">{t('roadmap.tooltipSavingsTco')}</span>
|
||||||
<span className="font-medium text-emerald-400">{formatCurrency(item.economicImpact)}/año</span>
|
<span className="font-medium text-emerald-400">{formatCurrency(item.economicImpact)}{t('roadmap.perYear')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 pt-1 border-t border-gray-600">
|
<div className="flex items-center gap-2 pt-1 border-t border-gray-600">
|
||||||
<span className="text-gray-400">Tier:</span>
|
<span className="text-gray-400">{t('roadmap.tooltipTier')}</span>
|
||||||
<span
|
<span
|
||||||
className="px-1.5 py-0.5 rounded text-[10px] font-medium"
|
className="px-1.5 py-0.5 rounded text-[10px] font-medium"
|
||||||
style={{ backgroundColor: tierColor.fill }}
|
style={{ backgroundColor: tierColor.fill }}
|
||||||
@@ -705,12 +708,11 @@ function OpportunityBubbleChart({
|
|||||||
{/* Leyenda por Tier */}
|
{/* Leyenda por Tier */}
|
||||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
<div className="mt-4 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
<p className="text-xs text-gray-700 mb-2">
|
<p className="text-xs text-gray-700 mb-2">
|
||||||
<span className="font-semibold" style={{ color: '#6d84e3' }}>Interpretación:</span> Las burbujas en el cuadrante superior derecho (Score alto + Ahorro alto)
|
<span className="font-semibold" style={{ color: '#6d84e3' }}>{t('roadmap.interpretation')}</span> {t('roadmap.opportunityMapInterpretation')}
|
||||||
son Quick Wins para automatización. El tamaño indica volumen de interacciones.
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap items-center gap-4 text-[10px] text-gray-600 pt-2 border-t border-gray-200">
|
<div className="flex flex-wrap items-center gap-4 text-[10px] text-gray-600 pt-2 border-t border-gray-200">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="font-medium">Tamaño:</span> Volumen
|
<span className="font-medium">{t('roadmap.bubbleSize')}</span> {t('roadmap.volume')}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: TIER_COLORS.AUTOMATE.fill }} />
|
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: TIER_COLORS.AUTOMATE.fill }} />
|
||||||
@@ -735,29 +737,29 @@ function OpportunityBubbleChart({
|
|||||||
<div className="mt-4 p-4 bg-white rounded-lg border border-gray-300 shadow-sm">
|
<div className="mt-4 p-4 bg-white rounded-lg border border-gray-300 shadow-sm">
|
||||||
<h4 className="text-sm font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||||
<Info className="w-4 h-4 text-[#6D84E3]" />
|
<Info className="w-4 h-4 text-[#6D84E3]" />
|
||||||
Metodología de Cálculo
|
{t('roadmap.methodologyTitle')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{/* Ejes */}
|
{/* Ejes */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div className="p-3 bg-gray-50 rounded border border-gray-200">
|
<div className="p-3 bg-gray-50 rounded border border-gray-200">
|
||||||
<h5 className="text-xs font-semibold text-gray-700 mb-2">📊 Eje X: FACTIBILIDAD (Score 0-10)</h5>
|
<h5 className="text-xs font-semibold text-gray-700 mb-2">📊 {t('roadmap.axisXFactibility')}</h5>
|
||||||
<p className="text-[11px] text-gray-600 mb-2">
|
<p className="text-[11px] text-gray-600 mb-2">
|
||||||
Score Agentic Readiness calculado con 5 factores ponderados:
|
{t('roadmap.axisXFactibilityDesc')}
|
||||||
</p>
|
</p>
|
||||||
<ul className="text-[10px] text-gray-500 space-y-1 ml-2">
|
<ul className="text-[10px] text-gray-500 space-y-1 ml-2">
|
||||||
<li>• <strong>Predictibilidad (30%)</strong>: basado en CV AHT</li>
|
<li>• <strong>{t('roadmap.factorPredictability')}</strong>: {t('roadmap.factorPredictabilityDesc')}</li>
|
||||||
<li>• <strong>Resolutividad (25%)</strong>: FCR (60%) + Transfer (40%)</li>
|
<li>• <strong>{t('roadmap.factorResolution')}</strong>: {t('roadmap.factorResolutionDesc')}</li>
|
||||||
<li>• <strong>Volumen (25%)</strong>: escala logarítmica del volumen</li>
|
<li>• <strong>{t('roadmap.factorVolumeWeight')}</strong>: {t('roadmap.factorVolumeDesc')}</li>
|
||||||
<li>• <strong>Calidad Datos (10%)</strong>: % registros válidos</li>
|
<li>• <strong>{t('roadmap.factorDataQuality')}</strong>: {t('roadmap.factorDataQualityDesc')}</li>
|
||||||
<li>• <strong>Simplicidad (10%)</strong>: basado en AHT</li>
|
<li>• <strong>{t('roadmap.factorSimplicity')}</strong>: {t('roadmap.factorSimplicityDesc')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 bg-gray-50 rounded border border-gray-200">
|
<div className="p-3 bg-gray-50 rounded border border-gray-200">
|
||||||
<h5 className="text-xs font-semibold text-gray-700 mb-2">💰 Eje Y: IMPACTO ECONÓMICO (€/año)</h5>
|
<h5 className="text-xs font-semibold text-gray-700 mb-2">💰 {t('roadmap.axisYEconomicImpact')}</h5>
|
||||||
<p className="text-[11px] text-gray-600 mb-2">
|
<p className="text-[11px] text-gray-600 mb-2">
|
||||||
Ahorro TCO calculado según tier con CPI diferencial:
|
{t('roadmap.axisYEconomicImpactDesc')}
|
||||||
</p>
|
</p>
|
||||||
<div className="text-[10px] text-gray-500 space-y-1 ml-2">
|
<div className="text-[10px] text-gray-500 space-y-1 ml-2">
|
||||||
<p className="font-mono bg-gray-100 px-1 py-0.5 rounded text-[9px]">
|
<p className="font-mono bg-gray-100 px-1 py-0.5 rounded text-[9px]">
|
||||||
@@ -778,49 +780,49 @@ function OpportunityBubbleChart({
|
|||||||
|
|
||||||
{/* Fórmulas por Tier */}
|
{/* Fórmulas por Tier */}
|
||||||
<div className="p-3 bg-gradient-to-r from-slate-50 to-white rounded border border-gray-200">
|
<div className="p-3 bg-gradient-to-r from-slate-50 to-white rounded border border-gray-200">
|
||||||
<h5 className="text-xs font-semibold text-gray-700 mb-3">🧮 Fórmulas de Ahorro por Tier</h5>
|
<h5 className="text-xs font-semibold text-gray-700 mb-3">🧮 {t('roadmap.savingsFormulas')}</h5>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-[10px]">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-[10px]">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS.AUTOMATE.fill }} />
|
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS.AUTOMATE.fill }} />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-emerald-700">AUTOMATE (Score ≥ 7.5)</p>
|
<p className="font-semibold text-emerald-700">{t('roadmap.formulaAutomate')}</p>
|
||||||
<p className="font-mono text-gray-600 mt-0.5">
|
<p className="font-mono text-gray-600 mt-0.5">
|
||||||
Ahorro = Vol × 12 × <strong>70%</strong> × (€2.33 - €0.15)
|
{t('roadmap.formulaAutomateCalc')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500">= Vol × 12 × 0.70 × €2.18</p>
|
<p className="text-gray-500">{t('roadmap.formulaAutomateResult')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS.ASSIST.fill }} />
|
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS.ASSIST.fill }} />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-blue-700">ASSIST (Score ≥ 5.5)</p>
|
<p className="font-semibold text-blue-700">{t('roadmap.formulaAssist')}</p>
|
||||||
<p className="font-mono text-gray-600 mt-0.5">
|
<p className="font-mono text-gray-600 mt-0.5">
|
||||||
Ahorro = Vol × 12 × <strong>30%</strong> × (€2.33 - €1.50)
|
{t('roadmap.formulaAssistCalc')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500">= Vol × 12 × 0.30 × €0.83</p>
|
<p className="text-gray-500">{t('roadmap.formulaAssistResult')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS.AUGMENT.fill }} />
|
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS.AUGMENT.fill }} />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-amber-700">AUGMENT (Score ≥ 3.5)</p>
|
<p className="font-semibold text-amber-700">{t('roadmap.formulaAugment')}</p>
|
||||||
<p className="font-mono text-gray-600 mt-0.5">
|
<p className="font-mono text-gray-600 mt-0.5">
|
||||||
Ahorro = Vol × 12 × <strong>15%</strong> × (€2.33 - €2.00)
|
{t('roadmap.formulaAugmentCalc')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500">= Vol × 12 × 0.15 × €0.33</p>
|
<p className="text-gray-500">{t('roadmap.formulaAugmentResult')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS['HUMAN-ONLY'].fill }} />
|
<div className="w-3 h-3 rounded-full flex-shrink-0 mt-0.5" style={{ backgroundColor: TIER_COLORS['HUMAN-ONLY'].fill }} />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-red-700">HUMAN-ONLY (Score < 3.5 o Red Flags)</p>
|
<p className="font-semibold text-red-700">{t('roadmap.formulaHumanOnly')}</p>
|
||||||
<p className="font-mono text-gray-600 mt-0.5">
|
<p className="font-mono text-gray-600 mt-0.5">
|
||||||
Ahorro = <strong>€0</strong>
|
{t('roadmap.formulaHumanOnlyCalc')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500">Requiere estandarización previa</p>
|
<p className="text-gray-500">{t('roadmap.formulaHumanOnlyRequires')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -828,7 +830,7 @@ function OpportunityBubbleChart({
|
|||||||
|
|
||||||
{/* Clasificación de Tier */}
|
{/* Clasificación de Tier */}
|
||||||
<div className="mt-3 p-3 bg-gray-50 rounded border border-gray-200">
|
<div className="mt-3 p-3 bg-gray-50 rounded border border-gray-200">
|
||||||
<h5 className="text-xs font-semibold text-gray-700 mb-2">🏷️ Criterios de Clasificación de Tier</h5>
|
<h5 className="text-xs font-semibold text-gray-700 mb-2">🏷️ {t('roadmap.tierClassificationCriteria')}</h5>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 text-[10px]">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 text-[10px]">
|
||||||
<div className="p-2 bg-emerald-50 rounded border border-emerald-200">
|
<div className="p-2 bg-emerald-50 rounded border border-emerald-200">
|
||||||
<p className="font-semibold text-emerald-700">AUTOMATE</p>
|
<p className="font-semibold text-emerald-700">AUTOMATE</p>
|
||||||
@@ -871,9 +873,7 @@ function OpportunityBubbleChart({
|
|||||||
|
|
||||||
{/* Nota metodológica */}
|
{/* Nota metodológica */}
|
||||||
<p className="text-[10px] text-gray-500 mt-3 italic">
|
<p className="text-[10px] text-gray-500 mt-3 italic">
|
||||||
<strong>Nota:</strong> El tamaño de las burbujas representa el volumen de interacciones.
|
<strong>{t('roadmap.methodologicalNote')}</strong> {t('roadmap.methodologicalNoteText')}
|
||||||
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.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -954,6 +954,7 @@ function WaveCard({
|
|||||||
exitCriteria?: WaveExitCriteria;
|
exitCriteria?: WaveExitCriteria;
|
||||||
priorityQueues?: PriorityQueue[];
|
priorityQueues?: PriorityQueue[];
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [expanded, setExpanded] = React.useState(false);
|
const [expanded, setExpanded] = React.useState(false);
|
||||||
|
|
||||||
const margenAnual = wave.ahorroAnual - wave.costoRecurrenteAnual;
|
const margenAnual = wave.ahorroAnual - wave.costoRecurrenteAnual;
|
||||||
@@ -990,7 +991,7 @@ function WaveCard({
|
|||||||
<h3 className="font-bold text-gray-800">{wave.titulo}</h3>
|
<h3 className="font-bold text-gray-800">{wave.titulo}</h3>
|
||||||
{wave.esCondicional && (
|
{wave.esCondicional && (
|
||||||
<span className="text-[10px] bg-amber-200 text-amber-800 px-2 py-0.5 rounded-full font-medium">
|
<span className="text-[10px] bg-amber-200 text-amber-800 px-2 py-0.5 rounded-full font-medium">
|
||||||
Condicional
|
{t('roadmap.conditional')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -998,7 +999,7 @@ function WaveCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className={`text-xs px-2 py-1 rounded-full font-medium ${riesgoColors[wave.riesgo]}`}>
|
<span className={`text-xs px-2 py-1 rounded-full font-medium ${riesgoColors[wave.riesgo]}`}>
|
||||||
{riesgoIcons[wave.riesgo]} Riesgo {wave.riesgo}
|
{riesgoIcons[wave.riesgo]} {t('roadmap.risk')} {t(`roadmap.risk${wave.riesgo.charAt(0).toUpperCase() + wave.riesgo.slice(1)}`)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1056,7 +1057,7 @@ function WaveCard({
|
|||||||
|
|
||||||
{/* Por qué es necesario */}
|
{/* Por qué es necesario */}
|
||||||
<div className="p-3 bg-gray-50 rounded-lg border border-gray-100">
|
<div className="p-3 bg-gray-50 rounded-lg border border-gray-100">
|
||||||
<p className="text-xs text-gray-500 font-medium mb-1">🎯 Por qué es necesario:</p>
|
<p className="text-xs text-gray-500 font-medium mb-1">🎯 {t('roadmap.whyNecessary')}</p>
|
||||||
<p className="text-sm text-gray-700">{wave.porQueNecesario}</p>
|
<p className="text-sm text-gray-700">{wave.porQueNecesario}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1169,7 +1170,7 @@ function WaveCard({
|
|||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
className="w-full text-sm text-gray-500 hover:text-gray-700 flex items-center justify-center gap-1 py-2 border-t border-gray-100"
|
className="w-full text-sm text-gray-500 hover:text-gray-700 flex items-center justify-center gap-1 py-2 border-t border-gray-100"
|
||||||
>
|
>
|
||||||
{expanded ? 'Ocultar detalles' : 'Ver iniciativas y criterios'}
|
{expanded ? t('roadmap.hideDetails') : t('roadmap.viewInitiatives')}
|
||||||
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -1678,6 +1679,8 @@ function RoadmapTimeline({ waves }: { waves: WaveData[] }) {
|
|||||||
// ========== COMPONENTE PRINCIPAL: ROADMAP TAB ==========
|
// ========== COMPONENTE PRINCIPAL: ROADMAP TAB ==========
|
||||||
|
|
||||||
export function RoadmapTab({ data }: RoadmapTabProps) {
|
export function RoadmapTab({ data }: RoadmapTabProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Analizar datos de heatmap para determinar skills listos
|
// Analizar datos de heatmap para determinar skills listos
|
||||||
const heatmapData = data.heatmapData || [];
|
const heatmapData = data.heatmapData || [];
|
||||||
|
|
||||||
@@ -2266,10 +2269,10 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<div className="px-5 py-4 border-b border-gray-200">
|
<div className="px-5 py-4 border-b border-gray-200">
|
||||||
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
|
||||||
<Target className="w-5 h-5 text-blue-600" />
|
<Target className="w-5 h-5 text-blue-600" />
|
||||||
Clasificación por Potencial de Automatización
|
{t('roadmap.classificationByAutomationTier')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
{totalQueues} colas clasificadas en 4 Tiers según su preparación para IA • {totalVolume.toLocaleString()} interacciones/mes
|
{t('roadmap.queuesClassifiedDescription', { count: totalQueues, volume: totalVolume.toLocaleString() })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2283,20 +2286,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<Rocket className="w-4 h-4 text-white" />
|
<Rocket className="w-4 h-4 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] text-emerald-600 font-medium">TIER 1</p>
|
<p className="text-[10px] text-emerald-600 font-medium">{t('roadmap.tier1')}</p>
|
||||||
<p className="text-xs font-bold text-emerald-800">AUTOMATE</p>
|
<p className="text-xs font-bold text-emerald-800">AUTOMATE</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-2xl font-bold text-emerald-700">{tierCounts.AUTOMATE.length}</p>
|
<p className="text-2xl font-bold text-emerald-700">{tierCounts.AUTOMATE.length}</p>
|
||||||
<p className="text-[10px] text-emerald-600">
|
<p className="text-[10px] text-emerald-600">
|
||||||
{tierVolumes.AUTOMATE.toLocaleString()} int/mes
|
{tierVolumes.AUTOMATE.toLocaleString()} {t('roadmap.intPerMonth')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-emerald-500">
|
<p className="text-[10px] text-emerald-500">
|
||||||
({Math.round((tierVolumes.AUTOMATE / totalVolume) * 100)}% volumen)
|
{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes.AUTOMATE / totalVolume) * 100) })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs font-semibold text-emerald-700 pt-1 border-t border-emerald-200">
|
<p className="text-xs font-semibold text-emerald-700 pt-1 border-t border-emerald-200">
|
||||||
{formatCurrency(potentialSavings.AUTOMATE)}/año
|
{formatCurrency(potentialSavings.AUTOMATE)}{t('roadmap.perYear')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2308,20 +2311,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<Bot className="w-4 h-4 text-white" />
|
<Bot className="w-4 h-4 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] text-blue-600 font-medium">TIER 2</p>
|
<p className="text-[10px] text-blue-600 font-medium">{t('roadmap.tier2')}</p>
|
||||||
<p className="text-xs font-bold text-blue-800">ASSIST</p>
|
<p className="text-xs font-bold text-blue-800">ASSIST</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-2xl font-bold text-blue-700">{tierCounts.ASSIST.length}</p>
|
<p className="text-2xl font-bold text-blue-700">{tierCounts.ASSIST.length}</p>
|
||||||
<p className="text-[10px] text-blue-600">
|
<p className="text-[10px] text-blue-600">
|
||||||
{tierVolumes.ASSIST.toLocaleString()} int/mes
|
{tierVolumes.ASSIST.toLocaleString()} {t('roadmap.intPerMonth')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-blue-500">
|
<p className="text-[10px] text-blue-500">
|
||||||
({Math.round((tierVolumes.ASSIST / totalVolume) * 100)}% volumen)
|
{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes.ASSIST / totalVolume) * 100) })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs font-semibold text-blue-700 pt-1 border-t border-blue-200">
|
<p className="text-xs font-semibold text-blue-700 pt-1 border-t border-blue-200">
|
||||||
{formatCurrency(potentialSavings.ASSIST)}/año
|
{formatCurrency(potentialSavings.ASSIST)}{t('roadmap.perYear')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2333,20 +2336,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<TrendingUp className="w-4 h-4 text-white" />
|
<TrendingUp className="w-4 h-4 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] text-amber-600 font-medium">TIER 3</p>
|
<p className="text-[10px] text-amber-600 font-medium">{t('roadmap.tier3')}</p>
|
||||||
<p className="text-xs font-bold text-amber-800">AUGMENT</p>
|
<p className="text-xs font-bold text-amber-800">AUGMENT</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-2xl font-bold text-amber-700">{tierCounts.AUGMENT.length}</p>
|
<p className="text-2xl font-bold text-amber-700">{tierCounts.AUGMENT.length}</p>
|
||||||
<p className="text-[10px] text-amber-600">
|
<p className="text-[10px] text-amber-600">
|
||||||
{tierVolumes.AUGMENT.toLocaleString()} int/mes
|
{tierVolumes.AUGMENT.toLocaleString()} {t('roadmap.intPerMonth')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-amber-500">
|
<p className="text-[10px] text-amber-500">
|
||||||
({Math.round((tierVolumes.AUGMENT / totalVolume) * 100)}% volumen)
|
{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes.AUGMENT / totalVolume) * 100) })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs font-semibold text-amber-700 pt-1 border-t border-amber-200">
|
<p className="text-xs font-semibold text-amber-700 pt-1 border-t border-amber-200">
|
||||||
{formatCurrency(potentialSavings.AUGMENT)}/año
|
{formatCurrency(potentialSavings.AUGMENT)}{t('roadmap.perYear')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2358,20 +2361,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<Users className="w-4 h-4 text-white" />
|
<Users className="w-4 h-4 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] text-red-600 font-medium">TIER 4</p>
|
<p className="text-[10px] text-red-600 font-medium">{t('roadmap.tier4')}</p>
|
||||||
<p className="text-xs font-bold text-red-800">HUMAN-ONLY</p>
|
<p className="text-xs font-bold text-red-800">HUMAN-ONLY</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-2xl font-bold text-red-700">{tierCounts['HUMAN-ONLY'].length}</p>
|
<p className="text-2xl font-bold text-red-700">{tierCounts['HUMAN-ONLY'].length}</p>
|
||||||
<p className="text-[10px] text-red-600">
|
<p className="text-[10px] text-red-600">
|
||||||
{tierVolumes['HUMAN-ONLY'].toLocaleString()} int/mes
|
{tierVolumes['HUMAN-ONLY'].toLocaleString()} {t('roadmap.intPerMonth')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-red-500">
|
<p className="text-[10px] text-red-500">
|
||||||
({Math.round((tierVolumes['HUMAN-ONLY'] / totalVolume) * 100)}% volumen)
|
{t('roadmap.volumePercentage', { pct: Math.round((tierVolumes['HUMAN-ONLY'] / totalVolume) * 100) })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs font-semibold text-red-700 pt-1 border-t border-red-200">
|
<p className="text-xs font-semibold text-red-700 pt-1 border-t border-red-200">
|
||||||
€0/año (Red flags)
|
{t('roadmap.noSavingsRedFlags')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2379,7 +2382,7 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
|
|
||||||
{/* Barra de distribución visual */}
|
{/* Barra de distribución visual */}
|
||||||
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
||||||
<p className="text-xs text-gray-500 font-medium mb-2">Distribución del volumen por tier:</p>
|
<p className="text-xs text-gray-500 font-medium mb-2">{t('roadmap.volumeDistributionByTier')}</p>
|
||||||
<div className="h-6 rounded-full overflow-hidden flex">
|
<div className="h-6 rounded-full overflow-hidden flex">
|
||||||
{tierVolumes.AUTOMATE > 0 && (
|
{tierVolumes.AUTOMATE > 0 && (
|
||||||
<div
|
<div
|
||||||
@@ -2501,16 +2504,16 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
// Configuración simplificada por tipo
|
// Configuración simplificada por tipo
|
||||||
const typeConfig = {
|
const typeConfig = {
|
||||||
DUAL: {
|
DUAL: {
|
||||||
label: 'Nuestra Recomendación: Estrategia Dual',
|
label: t('roadmap.dualStrategyLabel'),
|
||||||
sublabel: 'Ejecutar dos líneas de trabajo en paralelo para maximizar el impacto'
|
sublabel: t('roadmap.dualStrategySublabel')
|
||||||
},
|
},
|
||||||
FOUNDATION: {
|
FOUNDATION: {
|
||||||
label: 'Nuestra Recomendación: Foundation First',
|
label: t('roadmap.foundationFirstLabel'),
|
||||||
sublabel: 'Preparar la operación antes de automatizar'
|
sublabel: t('roadmap.foundationFirstSublabel')
|
||||||
},
|
},
|
||||||
STANDARDIZATION: {
|
STANDARDIZATION: {
|
||||||
label: 'Nuestra Recomendación: Estandarización',
|
label: t('roadmap.standardizationLabel'),
|
||||||
sublabel: 'Resolver problemas operativos críticos antes de invertir en IA'
|
sublabel: t('roadmap.standardizationSublabel')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2543,35 +2546,32 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<>
|
<>
|
||||||
{/* Explicación */}
|
{/* Explicación */}
|
||||||
<div className="p-3 bg-gray-50 rounded-lg mb-3">
|
<div className="p-3 bg-gray-50 rounded-lg mb-3">
|
||||||
<p className="font-semibold text-gray-800 mb-1">¿Qué significa Foundation?</p>
|
<p className="font-semibold text-gray-800 mb-1">{t('roadmap.whatIsFoundation')}</p>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
La operación actual no tiene colas listas para automatizar directamente.
|
{t('roadmap.foundationExplanation')}
|
||||||
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.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-gray-600 mb-3">
|
<p className="text-sm text-gray-600 mb-3">
|
||||||
{tierCounts.ASSIST.length} colas ASSIST ({Math.round(assistPct)}% del volumen)
|
{t('roadmap.assistQueuesCanElevate', { count: tierCounts.ASSIST.length, pct: Math.round(assistPct) })}
|
||||||
pueden elevarse a Tier AUTOMATE tras completar Wave 1-2.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4 text-sm border-t border-gray-100 pt-3">
|
<div className="grid grid-cols-3 gap-4 text-sm border-t border-gray-100 pt-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Inversión</p>
|
<p className="text-xs text-gray-500">{t('roadmap.investment')}</p>
|
||||||
<p className="font-semibold text-gray-800">{formatCurrency(wave1Setup + wave2Setup)}</p>
|
<p className="font-semibold text-gray-800">{formatCurrency(wave1Setup + wave2Setup)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Timeline</p>
|
<p className="text-xs text-gray-500">{t('roadmap.timeline')}</p>
|
||||||
<p className="font-semibold text-gray-800">6-9 meses</p>
|
<p className="font-semibold text-gray-800">6-9 {t('roadmap.months', { count: 6 }).toLowerCase()}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Ahorro habilitado</p>
|
<p className="text-xs text-gray-500">{t('roadmap.enabledSavings')}</p>
|
||||||
<p className="font-semibold text-gray-800">{formatCurrency(potentialSavings.ASSIST)}/año</p>
|
<p className="font-semibold text-gray-800">{formatCurrency(potentialSavings.ASSIST)}{t('roadmap.perYear')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 border-t border-gray-100 pt-3 mt-3">
|
<div className="text-xs text-gray-500 border-t border-gray-100 pt-3 mt-3">
|
||||||
<strong className="text-gray-700">Criterios para pasar a automatización:</strong> CV ≤90% · Transfer ≤30% · AHT -15%
|
<strong className="text-gray-700">{t('roadmap.criteriaForAutomation')}</strong> {t('roadmap.criteriaForAutomationValues')}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -2581,51 +2581,48 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<>
|
<>
|
||||||
{/* Explicación */}
|
{/* Explicación */}
|
||||||
<div className="p-3 bg-gray-50 rounded-lg mb-3">
|
<div className="p-3 bg-gray-50 rounded-lg mb-3">
|
||||||
<p className="font-semibold text-gray-800 mb-1">¿Por qué estandarización primero?</p>
|
<p className="font-semibold text-gray-800 mb-1">{t('roadmap.whyStandardizationFirst')}</p>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
Se han detectado "red flags" operativos críticos (alta variabilidad, muchas transferencias)
|
{t('roadmap.standardizationExplanation')}
|
||||||
que harían fracasar cualquier proyecto de automatización. Invertir en IA ahora sería
|
|
||||||
malgastar recursos. Primero hay que estabilizar la operación.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-gray-600 mb-3">
|
<p className="text-sm text-gray-600 mb-3">
|
||||||
{Math.round(humanOnlyPct + augmentPct)}% del volumen presenta red flags (CV >75%, Transfer >20%).
|
{t('roadmap.volumeWithRedFlags', { pct: Math.round(humanOnlyPct + augmentPct) })}
|
||||||
Wave 1 es una inversión habilitadora sin retorno directo inmediato.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4 text-sm border-t border-gray-100 pt-3">
|
<div className="grid grid-cols-3 gap-4 text-sm border-t border-gray-100 pt-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Inversión Wave 1</p>
|
<p className="text-xs text-gray-500">{t('roadmap.investmentWave1')}</p>
|
||||||
<p className="font-semibold text-gray-800">{formatCurrency(wave1Setup)}</p>
|
<p className="font-semibold text-gray-800">{formatCurrency(wave1Setup)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Timeline</p>
|
<p className="text-xs text-gray-500">{t('roadmap.timeline')}</p>
|
||||||
<p className="font-semibold text-gray-800">3-4 meses</p>
|
<p className="font-semibold text-gray-800">3-4 {t('roadmap.months', { count: 3 }).toLowerCase()}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500">Ahorro directo</p>
|
<p className="text-xs text-gray-500">{t('roadmap.directSavings')}</p>
|
||||||
<p className="font-semibold text-gray-500">€0 (habilitador)</p>
|
<p className="font-semibold text-gray-500">{t('roadmap.enablingNoDirectSavings')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 border-t border-gray-100 pt-3 mt-3">
|
<div className="text-xs text-gray-500 border-t border-gray-100 pt-3 mt-3">
|
||||||
<strong className="text-gray-700">Objetivo:</strong> Reducir red flags en las {Math.min(10, tierCounts['HUMAN-ONLY'].length + tierCounts.AUGMENT.length)} colas principales. Reevaluar tras completar.
|
<strong className="text-gray-700">{t('roadmap.objective')}</strong> {t('roadmap.objectiveReduceRedFlags', { count: Math.min(10, tierCounts['HUMAN-ONLY'].length + tierCounts.AUGMENT.length) })}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Siguiente paso */}
|
{/* Siguiente paso */}
|
||||||
<div className="border-t border-gray-200 pt-3 mt-3">
|
<div className="border-t border-gray-200 pt-3 mt-3">
|
||||||
<p className="text-xs text-gray-500 mb-1">Siguiente paso recomendado:</p>
|
<p className="text-xs text-gray-500 mb-1">{t('roadmap.nextRecommendedStep')}</p>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700">
|
||||||
{recType === 'DUAL' && (
|
{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.</>
|
t('roadmap.nextStepDual', { count: pilotQueues.length })
|
||||||
)}
|
)}
|
||||||
{recType === 'FOUNDATION' && (
|
{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.</>
|
t('roadmap.nextStepFoundation', { count: Math.min(10, tierCounts['HUMAN-ONLY'].length) })
|
||||||
)}
|
)}
|
||||||
{recType === 'STANDARDIZATION' && (
|
{recType === 'STANDARDIZATION' && (
|
||||||
<>Realizar workshop de diagnóstico operacional para identificar las causas raíz de los red flags antes de planificar inversiones.</>
|
t('roadmap.nextStepStandardization')
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -2653,13 +2650,13 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<BookOpen className="w-5 h-5 text-blue-500" />
|
<BookOpen className="w-5 h-5 text-blue-500" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="font-semibold text-gray-800">Detalle por Wave</h3>
|
<h3 className="font-semibold text-gray-800">{t('roadmap.waveDetail')}</h3>
|
||||||
<p className="text-xs text-gray-500">Iniciativas, criterios de entrada/salida, inversión por fase</p>
|
<p className="text-xs text-gray-500">{t('roadmap.waveDetailDescription')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-gray-400">
|
<span className="text-xs text-gray-400">
|
||||||
{waveDetailExpanded ? 'Ocultar detalle' : 'Ver detalle'}
|
{waveDetailExpanded ? t('roadmap.hideDetail') : t('roadmap.viewDetail')}
|
||||||
</span>
|
</span>
|
||||||
{waveDetailExpanded ? (
|
{waveDetailExpanded ? (
|
||||||
<ChevronUp className="w-5 h-5 text-gray-400" />
|
<ChevronUp className="w-5 h-5 text-gray-400" />
|
||||||
@@ -2678,7 +2675,7 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
|
|||||||
onClick={() => setShowAllWaves(!showAllWaves)}
|
onClick={() => setShowAllWaves(!showAllWaves)}
|
||||||
className="text-xs text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1"
|
className="text-xs text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{showAllWaves ? 'Colapsar todas' : 'Expandir todas'}
|
{showAllWaves ? t('roadmap.collapseAll') : t('roadmap.expandAll')}
|
||||||
{showAllWaves ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
{showAllWaves ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -80,6 +80,10 @@
|
|||||||
"viewResults": "View Results"
|
"viewResults": "View Results"
|
||||||
},
|
},
|
||||||
"tierSelector": {
|
"tierSelector": {
|
||||||
|
"popular": "POPULAR",
|
||||||
|
"selected": "Selected",
|
||||||
|
"select": "Select",
|
||||||
|
"oneTime": "one-time",
|
||||||
"hideComparison": "Hide Comparison",
|
"hideComparison": "Hide Comparison",
|
||||||
"viewComparison": "View Detailed Comparison",
|
"viewComparison": "View Detailed Comparison",
|
||||||
"comparison": "Tier Comparison",
|
"comparison": "Tier Comparison",
|
||||||
@@ -131,6 +135,7 @@
|
|||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Diagnostic Dashboard",
|
"title": "Diagnostic Dashboard",
|
||||||
|
"defaultTitle": "DEMO CLIENT - Beyond CX Analytics",
|
||||||
"viewDashboard": "View Diagnostic Dashboard",
|
"viewDashboard": "View Diagnostic Dashboard",
|
||||||
"generateAnalysis": "Generate Analysis",
|
"generateAnalysis": "Generate Analysis",
|
||||||
"analyzing": "Analyzing...",
|
"analyzing": "Analyzing...",
|
||||||
@@ -245,7 +250,38 @@
|
|||||||
"betterThan75": "Better than 3 out of 4 companies",
|
"betterThan75": "Better than 3 out of 4 companies",
|
||||||
"alignedWithMedian": "Aligned with sector median",
|
"alignedWithMedian": "Aligned with sector median",
|
||||||
"belowAverage": "Below market average",
|
"belowAverage": "Below market average",
|
||||||
"criticalArea": "Critical improvement area"
|
"criticalArea": "Critical improvement area",
|
||||||
|
"opportunity": "Opportunity",
|
||||||
|
"top10": "Top 10%",
|
||||||
|
"top25": "Top 25%",
|
||||||
|
"average": "Average",
|
||||||
|
"belowAvg": "Below Avg",
|
||||||
|
"bottom25": "Bottom 25%",
|
||||||
|
"benchmarkLow": "Low",
|
||||||
|
"benchmarkMedian": "Median",
|
||||||
|
"benchmarkTop": "Top",
|
||||||
|
"excellent": "Excellent",
|
||||||
|
"good": "Good",
|
||||||
|
"regular": "Fair",
|
||||||
|
"aht": "AHT",
|
||||||
|
"fcr": "FCR",
|
||||||
|
"abandonment": "ABANDONMENT",
|
||||||
|
"costPerInteraction": "COST/INTERAC.",
|
||||||
|
"ahtTotal": "Total AHT",
|
||||||
|
"fcrAdjusted": "Adjusted FCR",
|
||||||
|
"ahtTooltip": "Average handling time (valid interactions only)",
|
||||||
|
"ahtTotalTooltip": "Includes all rows (noise, zombie, abandon) - informational only",
|
||||||
|
"ahtTotalDesc": "Includes noise, zombie and abandons — informational only",
|
||||||
|
"fcrTooltip": "First Contact Resolution - comparable with industry benchmarks",
|
||||||
|
"fcrAdjustedTooltip": "Excludes recontacts in 7 days (stricter metric)",
|
||||||
|
"fcrAdjustedDesc": "Includes 7-day recontact filter — stricter internal metric",
|
||||||
|
"abandonmentTooltip": "Abandonment rate",
|
||||||
|
"cpiTooltip": "Cost per interaction",
|
||||||
|
"recontacts7d": "of 7-day recontacts",
|
||||||
|
"vsCleanAht": "vs clean AHT",
|
||||||
|
"queuesLabel": "queues",
|
||||||
|
"readyQueues": "ready",
|
||||||
|
"criticalQueues": "critical"
|
||||||
},
|
},
|
||||||
"industries": {
|
"industries": {
|
||||||
"airlines": "Airlines",
|
"airlines": "Airlines",
|
||||||
@@ -262,7 +298,72 @@
|
|||||||
"keyFinding": "Key Finding",
|
"keyFinding": "Key Finding",
|
||||||
"keyFindings": "Key Findings",
|
"keyFindings": "Key Findings",
|
||||||
"noDataAvailable": "No data available for this dimension.",
|
"noDataAvailable": "No data available for this dimension.",
|
||||||
"withinAcceptable": "Metrics within acceptable ranges. No critical findings."
|
"withinAcceptable": "Metrics within acceptable ranges. No critical findings.",
|
||||||
|
"impact": "Impact:",
|
||||||
|
"probableCause": "Probable cause:",
|
||||||
|
"annualImpact": "annual impact (problem cost)",
|
||||||
|
"recommendation": "Recommendation:",
|
||||||
|
"operationalEfficiency": {
|
||||||
|
"highAHTFinding": "High AHT: P50 {{aht}} (benchmark: 5:00)",
|
||||||
|
"highAHTCause": "Agents spend excessive time on manual information search, system navigation and repetitive tasks.",
|
||||||
|
"highAHTRecommendation": "Deploy AI Copilot for agents: (1) Auto-search in KB; (2) Contextual suggestions in real-time; (3) Guided scripts for frequent cases. Expected reduction: 20-30% AHT. Savings: {{savings}}/year.",
|
||||||
|
"goodAHTFinding": "AHT within benchmark: P50 {{aht}} (benchmark: 5:00)",
|
||||||
|
"goodAHTCause": "Efficient handling times. Optimized operational processes.",
|
||||||
|
"goodAHTImpact": "No excess cost from AHT",
|
||||||
|
"goodAHTTimeSavings": "Efficient operation",
|
||||||
|
"goodAHTRecommendation": "Maintain current level. Consider Copilot for continuous improvement and additional time reduction on complex cases."
|
||||||
|
},
|
||||||
|
"effectiveness": {
|
||||||
|
"finding": "Technical FCR: {{fcr}}% | Transfers: {{transfer}}% (benchmark: FCR >85%, Transfer <10%)",
|
||||||
|
"criticalCause": "High transfer rate ({{transfer}}%) indicates lack of tools or authority. Critical in {{skills}}.",
|
||||||
|
"criticalCauseGeneric": "High transfers ({{transfer}}%): agents without contextual information or authority to resolve.",
|
||||||
|
"warningCause": "{{transfer}}% transfers indicate opportunity for improvement with AI assistance for complex cases.",
|
||||||
|
"goodCause": "Technical FCR at optimal level. {{transfer}}% transfers mainly in cases requiring legitimate escalation.",
|
||||||
|
"criticalRecommendation": "Deploy Knowledge Copilot with smart KB search + Guided Resolution Copilot for complex cases. Target: FCR >85%. Potential savings: {{savings}}/year.",
|
||||||
|
"warningRecommendation": "Implement real-time assistance Copilot: contextual suggestions + virtual expert connection to reduce transfers. Target: FCR >90%.",
|
||||||
|
"goodRecommendation": "Maintain current level. Consider AI for legitimate transfer analysis and predictive routing optimization.",
|
||||||
|
"impactFormula": "{{count}} transfers/year × €{{cpi}}/int × 50% additional cost",
|
||||||
|
"timeSavings": "{{count}} transfers/year ({{pct}}% of volume)"
|
||||||
|
},
|
||||||
|
"volumetry": {
|
||||||
|
"concentrationFinding": "Volume concentration: {{skill}} represents {{pct}}% of total",
|
||||||
|
"concentrationCause": "High concentration in one skill indicates repetitive queries with automation potential.",
|
||||||
|
"concentrationRecommendation": "Analyze {{skill}} typologies for deflection to self-service or virtual agent. Potential: {{savings}}/year.",
|
||||||
|
"impactFormula": "{{volume}} int × annualization × €{{cpi}} × 20% deflection potential",
|
||||||
|
"timeSavings": "{{volume}} interactions/year in {{skill}} ({{deflectable}} automatable)"
|
||||||
|
},
|
||||||
|
"complexity": {
|
||||||
|
"highCVFinding": "High CV AHT: {{cv}}% (benchmark: <{{benchmark}}%)",
|
||||||
|
"highCVCauseCritical": "Extreme dispersion in handling times prevents effective resource planning. Likely lack of scripts or standardized processes.",
|
||||||
|
"highCVCauseWarning": "Moderate time variability indicates opportunity for standardization to improve WFM planning.",
|
||||||
|
"highCVImpactFormula": "~3% of operational cost due to staffing inefficiency",
|
||||||
|
"highCVTimeSavings": "~{{hours}} hours/year in over/under staffing",
|
||||||
|
"highCVRecommendation": "Implement AI-guided scripts to standardize service. Expected reduction: -50% variability. Savings: {{savings}}/year.",
|
||||||
|
"goodCVFinding": "CV AHT within benchmark: {{cv}}% (benchmark: <{{benchmark}}%)",
|
||||||
|
"goodCVCause": "Consistent handling times. Good process standardization.",
|
||||||
|
"goodCVImpactFormula": "No impact from variability",
|
||||||
|
"goodCVTimeSavings": "Efficient WFM planning",
|
||||||
|
"goodCVRecommendation": "Maintain current level. Analyze edge cases to identify opportunities for continuous improvement.",
|
||||||
|
"holdTimeFinding": "High hold time: {{holdTime}}s average (benchmark: <30s)",
|
||||||
|
"holdTimeCause": "Agents put customer on hold to search for information. Systems don't present data contextually.",
|
||||||
|
"holdTimeImpactFormula": "Excess {{excess}}s × {{volume}} int × annualization × €{{cost}}/h",
|
||||||
|
"holdTimeTimeSavings": "{{hours}} hours/year of customer on hold",
|
||||||
|
"holdTimeRecommendation": "Deploy 360° view with automatic context: history, products and suggested actions visible when answering. Expected reduction: -60% hold time. Savings: {{savings}}/year."
|
||||||
|
},
|
||||||
|
"satisfaction": {
|
||||||
|
"lowCSATFinding": "CSAT below target: {{csat}}% (benchmark: >80%)",
|
||||||
|
"lowCSATCause": "Dissatisfied customers due to waiting, lack of resolution or poor service experience.",
|
||||||
|
"lowCSATImpactFormula": "{{volume}} customers × annualization × 2% churn risk × €50 value",
|
||||||
|
"lowCSATTimeSavings": "{{customers}} customers/year at risk of churn",
|
||||||
|
"lowCSATRecommendation": "Implement VoC program: post-contact surveys + root cause analysis + corrective action in 48h. Target: CSAT >80%."
|
||||||
|
},
|
||||||
|
"economy": {
|
||||||
|
"highCPIFinding": "CPI above benchmark: €{{cpi}} (target: €{{target}})",
|
||||||
|
"highCPICause": "High cost per interaction due to high AHT, low occupancy or inefficient cost structure.",
|
||||||
|
"highCPIImpactFormula": "{{volume}} int × annualization × €{{excess}} excess CPI",
|
||||||
|
"highCPITimeSavings": "€{{excess}} excess/int × {{volume}} int = {{hours}} equivalents",
|
||||||
|
"highCPIRecommendation": "Optimize channel mix + reduce AHT with automation + review staffing model. Target: CPI <€{{target}}."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"roadmap": {
|
"roadmap": {
|
||||||
"wave1": "Wave 1: AUTOMATE",
|
"wave1": "Wave 1: AUTOMATE",
|
||||||
@@ -289,7 +390,149 @@
|
|||||||
"foundationFirst": "Foundation First",
|
"foundationFirst": "Foundation First",
|
||||||
"foundationFirstDesc": "Prepare the operation before automating",
|
"foundationFirstDesc": "Prepare the operation before automating",
|
||||||
"standardization": "Standardization",
|
"standardization": "Standardization",
|
||||||
"standardizationDesc": "Resolve critical operational issues before investing in AI"
|
"standardizationDesc": "Resolve critical operational issues before investing in AI",
|
||||||
|
"opportunityMapTitle": "Opportunity Map by Tier",
|
||||||
|
"opportunityMapSubtitle": "Feasibility (Score) vs Economic Impact (TCO Savings) • Size = Volume • Color = Tier",
|
||||||
|
"economicImpactAxis": "ECONOMIC IMPACT (TCO Savings €/year)",
|
||||||
|
"feasibilityAxis": "FEASIBILITY (Agentic Readiness Score 0-10)",
|
||||||
|
"tierAutomateThreshold": "AUTOMATE Tier ≥7.5",
|
||||||
|
"quadrantQuickWins": "QUICK WINS",
|
||||||
|
"quadrantQuickWinsDesc": "Score ≥7.5 + High savings",
|
||||||
|
"quadrantQuickWinsPriority": "→ Priority 1",
|
||||||
|
"quadrantOptimize": "OPTIMIZE",
|
||||||
|
"quadrantOptimizeDesc": "Score <7.5 + High savings",
|
||||||
|
"quadrantOptimizePriority": "→ Wave 1 first",
|
||||||
|
"quadrantStrategic": "STRATEGIC",
|
||||||
|
"quadrantStrategicDesc": "Score ≥7.5 + Low savings",
|
||||||
|
"quadrantStrategicPriority": "→ Evaluate ROI",
|
||||||
|
"quadrantDefer": "DEFER",
|
||||||
|
"quadrantDeferDesc": "Score <7.5 + Low savings",
|
||||||
|
"quadrantDeferPriority": "→ Backlog",
|
||||||
|
"tooltipScore": "Score:",
|
||||||
|
"tooltipVolume": "Volume:",
|
||||||
|
"tooltipSavingsTco": "TCO Savings:",
|
||||||
|
"tooltipTier": "Tier:",
|
||||||
|
"perMonth": "/month",
|
||||||
|
"perYear": "/year",
|
||||||
|
"interpretation": "Interpretation:",
|
||||||
|
"opportunityMapInterpretation": "Bubbles in the upper right quadrant (High Score + High Savings) are Quick Wins for automation. Size indicates interaction volume.",
|
||||||
|
"bubbleSize": "Size:",
|
||||||
|
"volume": "Volume",
|
||||||
|
"methodologyTitle": "Calculation Methodology",
|
||||||
|
"axisXFactibility": "X Axis: FEASIBILITY (Score 0-10)",
|
||||||
|
"axisXFactibilityDesc": "Agentic Readiness Score calculated with 5 weighted factors:",
|
||||||
|
"factorPredictability": "Predictability (30%)",
|
||||||
|
"factorPredictabilityDesc": "based on CV AHT",
|
||||||
|
"factorResolution": "Resolution (25%)",
|
||||||
|
"factorResolutionDesc": "FCR (60%) + Transfer (40%)",
|
||||||
|
"factorVolumeWeight": "Volume (25%)",
|
||||||
|
"factorVolumeDesc": "logarithmic volume scale",
|
||||||
|
"factorDataQuality": "Data Quality (10%)",
|
||||||
|
"factorDataQualityDesc": "% valid records",
|
||||||
|
"factorSimplicity": "Simplicity (10%)",
|
||||||
|
"factorSimplicityDesc": "based on AHT",
|
||||||
|
"axisYEconomicImpact": "Y Axis: ECONOMIC IMPACT (€/year)",
|
||||||
|
"axisYEconomicImpactDesc": "TCO savings calculated by tier with differential CPI:",
|
||||||
|
"cpiHuman": "Human CPI",
|
||||||
|
"cpiBot": "Bot CPI",
|
||||||
|
"cpiAssist": "Assist CPI",
|
||||||
|
"cpiAugment": "Augment CPI",
|
||||||
|
"savingsFormulas": "Savings Formulas by Tier",
|
||||||
|
"formulaAutomate": "AUTOMATE (Score ≥ 7.5)",
|
||||||
|
"formulaAutomateCalc": "Savings = Vol × 12 × 70% × (€2.33 - €0.15)",
|
||||||
|
"formulaAutomateResult": "= Vol × 12 × 0.70 × €2.18",
|
||||||
|
"formulaAssist": "ASSIST (Score ≥ 5.5)",
|
||||||
|
"formulaAssistCalc": "Savings = Vol × 12 × 30% × (€2.33 - €1.50)",
|
||||||
|
"formulaAssistResult": "= Vol × 12 × 0.30 × €0.83",
|
||||||
|
"formulaAugment": "AUGMENT (Score ≥ 3.5)",
|
||||||
|
"formulaAugmentCalc": "Savings = Vol × 12 × 15% × (€2.33 - €2.00)",
|
||||||
|
"formulaAugmentResult": "= Vol × 12 × 0.15 × €0.33",
|
||||||
|
"formulaHumanOnly": "HUMAN-ONLY (Score < 3.5 or Red Flags)",
|
||||||
|
"formulaHumanOnlyCalc": "Savings = €0",
|
||||||
|
"formulaHumanOnlyRequires": "Requires prior standardization",
|
||||||
|
"tierClassificationCriteria": "Tier Classification Criteria",
|
||||||
|
"tierCriteriaCV": "CV",
|
||||||
|
"tierCriteriaTransfer": "Transfer",
|
||||||
|
"tierCriteriaFCR": "FCR",
|
||||||
|
"tierCriteriaNoRedFlags": "No red flags",
|
||||||
|
"tierCriteriaRequiresOptimization": "Requires optimization",
|
||||||
|
"tierCriteriaOr": "or",
|
||||||
|
"tierCriteriaVolume": "Vol",
|
||||||
|
"tierCriteriaValid": "Valid",
|
||||||
|
"methodologicalNote": "Note:",
|
||||||
|
"methodologicalNoteText": "Bubble size represents interaction volume. Queues classified as HUMAN-ONLY don't appear in the chart (savings = €0). Savings are projections based on industry benchmarks and should be validated with pilots.",
|
||||||
|
"hideDetails": "Hide details",
|
||||||
|
"viewInitiatives": "View initiatives and criteria",
|
||||||
|
"conditional": "Conditional",
|
||||||
|
"risk": "Risk",
|
||||||
|
"riskLow": "Low",
|
||||||
|
"riskMedium": "Medium",
|
||||||
|
"riskHigh": "High",
|
||||||
|
"whyNecessary": "Why is it necessary?",
|
||||||
|
"targetSkills": "Target skills",
|
||||||
|
"initiatives": "Initiatives",
|
||||||
|
"setup": "Setup",
|
||||||
|
"recurring": "Recurring",
|
||||||
|
"kpiTarget": "KPI Target",
|
||||||
|
"successCriteria": "Success criteria",
|
||||||
|
"riskDescription": "Risk description",
|
||||||
|
"provider": "Provider",
|
||||||
|
"entryCriteria": "Entry criteria",
|
||||||
|
"fromTier": "From Tier:",
|
||||||
|
"scoreRange": "Score Range:",
|
||||||
|
"requiredMetrics": "Required metrics:",
|
||||||
|
"exitCriteria": "Exit criteria",
|
||||||
|
"toTier": "To Tier:",
|
||||||
|
"scoreTarget": "Target score:",
|
||||||
|
"kpiTargets": "Target KPIs:",
|
||||||
|
"priorityQueues": "Priority queues",
|
||||||
|
"currentScore": "Current score",
|
||||||
|
"currentTier": "Current tier",
|
||||||
|
"potentialSavings": "Potential savings",
|
||||||
|
"redFlags": "Red Flags",
|
||||||
|
"classificationByAutomationTier": "Classification by Automation Potential",
|
||||||
|
"queuesClassifiedDescription": "{{count}} queues classified into 4 Tiers by AI readiness • {{volume}} interactions/month",
|
||||||
|
"tier1": "TIER 1",
|
||||||
|
"tier2": "TIER 2",
|
||||||
|
"tier3": "TIER 3",
|
||||||
|
"tier4": "TIER 4",
|
||||||
|
"intPerMonth": "int/month",
|
||||||
|
"volumePercentage": "({{pct}}% volume)",
|
||||||
|
"noSavingsRedFlags": "€0/year (Red flags)",
|
||||||
|
"volumeDistributionByTier": "Volume distribution by tier:",
|
||||||
|
"strategicRecommendation": "Our Recommendation:",
|
||||||
|
"dualStrategyLabel": "Our Recommendation: Dual Strategy",
|
||||||
|
"dualStrategySublabel": "Execute two work streams in parallel to maximize impact",
|
||||||
|
"foundationFirstLabel": "Our Recommendation: Foundation First",
|
||||||
|
"foundationFirstSublabel": "Prepare the operation before automating",
|
||||||
|
"standardizationLabel": "Our Recommendation: Standardization",
|
||||||
|
"standardizationSublabel": "Resolve critical operational issues before investing in AI",
|
||||||
|
"whatIsFoundation": "What does Foundation mean?",
|
||||||
|
"foundationExplanation": "The current operation has no queues ready for direct automation. Foundation is the preparation phase: standardize processes, reduce variability, and improve data quality so subsequent automation is effective.",
|
||||||
|
"assistQueuesCanElevate": "{{count}} ASSIST queues ({{pct}}% of volume) can be elevated to AUTOMATE Tier after completing Wave 1-2.",
|
||||||
|
"investment": "Investment",
|
||||||
|
"timeline": "Timeline",
|
||||||
|
"enabledSavings": "Enabled savings",
|
||||||
|
"criteriaForAutomation": "Criteria for automation:",
|
||||||
|
"criteriaForAutomationValues": "CV ≤90% · Transfer ≤30% · AHT -15%",
|
||||||
|
"whyStandardizationFirst": "Why standardization first?",
|
||||||
|
"standardizationExplanation": "Critical operational \"red flags\" (high variability, many transfers) have been detected that would cause any automation project to fail. Investing in AI now would be wasting resources. The operation must be stabilized first.",
|
||||||
|
"volumeWithRedFlags": "{{pct}}% of volume has red flags (CV >75%, Transfer >20%). Wave 1 is an enabling investment with no immediate direct return.",
|
||||||
|
"investmentWave1": "Wave 1 Investment",
|
||||||
|
"directSavings": "Direct savings",
|
||||||
|
"enablingNoDirectSavings": "€0 (enabler)",
|
||||||
|
"objective": "Objective:",
|
||||||
|
"objectiveReduceRedFlags": "Reduce red flags in the top {{count}} queues. Reassess after completion.",
|
||||||
|
"nextRecommendedStep": "Next recommended step:",
|
||||||
|
"nextStepDual": "Launch automation pilot with the {{count}} AUTOMATE queues, while executing Wave 1 (Foundation) in parallel to prepare the rest.",
|
||||||
|
"nextStepFoundation": "Start Wave 1 focusing on the top {{count}} queues by volume. Measure monthly progress in CV and Transfer.",
|
||||||
|
"nextStepStandardization": "Conduct operational diagnostic workshop to identify root causes of red flags before planning investments.",
|
||||||
|
"waveDetail": "Wave Detail",
|
||||||
|
"waveDetailDescription": "Initiatives, entry/exit criteria, investment by phase",
|
||||||
|
"hideDetail": "Hide detail",
|
||||||
|
"viewDetail": "View detail",
|
||||||
|
"collapseAll": "Collapse all",
|
||||||
|
"expandAll": "Expand all"
|
||||||
},
|
},
|
||||||
"opportunities": {
|
"opportunities": {
|
||||||
"viewCriticalActions": "View Critical Actions",
|
"viewCriticalActions": "View Critical Actions",
|
||||||
@@ -393,6 +636,184 @@
|
|||||||
"augmentDesc": "Require prior optimization: standardize processes, reduce variability (Score 3.5-5.5)",
|
"augmentDesc": "Require prior optimization: standardize processes, reduce variability (Score 3.5-5.5)",
|
||||||
"humanOnly": "HUMAN-ONLY Queues",
|
"humanOnly": "HUMAN-ONLY Queues",
|
||||||
"humanOnlyDesc": "Not suitable for automation: insufficient volume, low data quality or extreme complexity"
|
"humanOnlyDesc": "Not suitable for automation: insufficient volume, low data quality or extreme complexity"
|
||||||
|
},
|
||||||
|
"tiers": {
|
||||||
|
"automate": "Automate",
|
||||||
|
"assist": "Assist",
|
||||||
|
"optimize": "Optimize",
|
||||||
|
"human": "Human"
|
||||||
|
},
|
||||||
|
"tierLabels": {
|
||||||
|
"automateFull": "Full AI",
|
||||||
|
"assistCopilot": "Copilot",
|
||||||
|
"augmentTools": "Tools",
|
||||||
|
"humanManual": "Manual"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"high": "High",
|
||||||
|
"medium": "Medium",
|
||||||
|
"low": "Low",
|
||||||
|
"critical": "Critical",
|
||||||
|
"readyForAutomation": "Ready for automation",
|
||||||
|
"moderatePotential": "Moderate potential",
|
||||||
|
"requiresOptimization": "Requires optimization",
|
||||||
|
"notReady": "Not ready"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"queues": "queues",
|
||||||
|
"queue": "queue",
|
||||||
|
"queueId": "Queue (ID)",
|
||||||
|
"queueOriginalId": "Queue (original_queue_id)",
|
||||||
|
"skill": "Skill",
|
||||||
|
"businessUnit": "Business Unit (Skill)",
|
||||||
|
"strategicSkill": "Strategic Queue Skill",
|
||||||
|
"volume": "Volume",
|
||||||
|
"volumePerMonth": "int/month",
|
||||||
|
"ahtAvg": "Avg AHT",
|
||||||
|
"cvAvg": "Avg CV",
|
||||||
|
"savingsPotential": "Potential Savings",
|
||||||
|
"dominantTier": "Dom. Tier",
|
||||||
|
"transfer": "Transfer",
|
||||||
|
"redFlags": "Red Flags",
|
||||||
|
"savingsPerMonth": "Savings/month",
|
||||||
|
"cost": "Cost:",
|
||||||
|
"savings": "Savings:",
|
||||||
|
"total": "TOTAL",
|
||||||
|
"clickToExpand": "Click on a skill to see individual queue details",
|
||||||
|
"clickToExpandReason": "Click on a reason to see affected queues. Prioritize actions by impacted volume.",
|
||||||
|
"showing": "Showing {{shown}} of {{total}} queues",
|
||||||
|
"reason": "Reason / Red Flag",
|
||||||
|
"recommendedAction": "Recommended Action",
|
||||||
|
"score": "Score",
|
||||||
|
"int": "int",
|
||||||
|
"perYear": "/year",
|
||||||
|
"perMonth": "/month"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"volumeAutomatable": "Automatable Volume",
|
||||||
|
"tierAutoAssist": "(Tier AUTOMATE + ASSIST)",
|
||||||
|
"interactions": "interactions",
|
||||||
|
"queuesAnalyzed": "queues analyzed",
|
||||||
|
"interpretation": "Interpretation:",
|
||||||
|
"interpretationText": "The {{pct}}% represents automatable interaction volume (AUTOMATE + ASSIST). Only {{queuePct}}% of queues ({{count}} of {{total}}) are AUTOMATE, but they concentrate {{volumePct}}% of total volume. This indicates few high-volume automatable queues - opportunity concentrated in high-impact Quick Wins.",
|
||||||
|
"inSkills": "in {{count}} skills",
|
||||||
|
"groupedBy": "grouped by {{count}} reasons",
|
||||||
|
"requiresIntervention": "These queues require intervention before considering automation"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"tier": "Tier:",
|
||||||
|
"all": "All",
|
||||||
|
"minSavings": "Min savings:",
|
||||||
|
"minVolume": "Min volume:",
|
||||||
|
"activeFilters": "Active filters:",
|
||||||
|
"of": "of"
|
||||||
|
},
|
||||||
|
"opportunityMap": {
|
||||||
|
"title": "Opportunity Map",
|
||||||
|
"subtitle": "Size = Volume · Color = Tier · Position = Score vs TCO Savings",
|
||||||
|
"quickWins": "QUICK WINS",
|
||||||
|
"highPotential": "HIGH POTENTIAL",
|
||||||
|
"develop": "DEVELOP",
|
||||||
|
"easyImpl": "EASY IMPL.",
|
||||||
|
"backlog": "BACKLOG",
|
||||||
|
"colorTier": "COLOR = TIER",
|
||||||
|
"sizeVolume": "SIZE = VOLUME",
|
||||||
|
"visibleSavings": "VISIBLE SAVINGS",
|
||||||
|
"agenticScore": "Agentic Score",
|
||||||
|
"annualTcoSavings": "Annual TCO Savings",
|
||||||
|
"noQueuesMatch": "No queues match the selected filters",
|
||||||
|
"clickForDetail": "Click for details",
|
||||||
|
"quickWinCandidate": "Quick Win Candidate",
|
||||||
|
"highPotentialCopilot": "High Potential with Copilot",
|
||||||
|
"requiresStandardization": "Requires prior standardization",
|
||||||
|
"matureProcesses": "Score ≥7.5 indicates mature processes ready for full automation.",
|
||||||
|
"benefitsAI": "Score 5.5-7.5 benefits from AI assistance (Copilot) to elevate to Tier 1.",
|
||||||
|
"needsWork": "Score <5.5 requires prior standardization work before automating."
|
||||||
|
},
|
||||||
|
"classification": {
|
||||||
|
"title": "CLASSIFICATION BY SKILL",
|
||||||
|
"titleByTier": "CLASSIFICATION BY AUTOMATION TIER",
|
||||||
|
"subtitle": "Skills with queues classified as AUTOMATE (score ≥ 7.5, CV ≤ 75%, transfer ≤ 20%)",
|
||||||
|
"distribution": "Queue Distribution by Tier",
|
||||||
|
"action": "Action",
|
||||||
|
"auto": "AUTO",
|
||||||
|
"assist": "ASSIST",
|
||||||
|
"augm": "AUGM",
|
||||||
|
"human": "HUMAN",
|
||||||
|
"waveBot": "→ Wave 4: Full Bot",
|
||||||
|
"waveCopilot": "→ Wave 3: Copilot",
|
||||||
|
"waveTools": "→ Wave 2: Tools",
|
||||||
|
"waveFoundation": "→ Wave 1: Foundation",
|
||||||
|
"quickWins": "Quick Wins:",
|
||||||
|
"attention": "Attention:",
|
||||||
|
"volumeT1T2": "Vol in T1+T2:",
|
||||||
|
"volumeT4": "Vol in T4:",
|
||||||
|
"prioritizeWave1": "→ prioritize in Wave 1",
|
||||||
|
"balancedDistribution": "Balanced distribution across tiers. Review individual queues for prioritization.",
|
||||||
|
"hasT1T2Volume": "have >60% volume in T1+T2",
|
||||||
|
"hasHumanVolume": "has {{pct}}% in HUMAN",
|
||||||
|
"analysisPerSkill": "Analysis per Skill",
|
||||||
|
"skillsHaveAutomate": "of {{total}} skills have at least one AUTOMATE tier queue",
|
||||||
|
"seeIndividualQueues": "Click on a skill to see individual queues with score breakdown"
|
||||||
|
},
|
||||||
|
"globalFactors": {
|
||||||
|
"title": "Score Factors (Global Operation Level)",
|
||||||
|
"note": "NOTE:",
|
||||||
|
"noteText": "These factors are global averages. Per-queue scoring uses these same factors calculated individually for each queue.",
|
||||||
|
"factor": "Factor",
|
||||||
|
"weight": "Weight",
|
||||||
|
"realMetric": "Actual Metric",
|
||||||
|
"status": "Status",
|
||||||
|
"globalScore": "GLOBAL SCORE",
|
||||||
|
"insight": "The global score ({{score}}) reflects the complete operation. However, {{pct}}% of volume is in individual queues that DO meet automation criteria."
|
||||||
|
},
|
||||||
|
"nextSteps": {
|
||||||
|
"title": "NEXT STEPS → ROADMAP",
|
||||||
|
"basedOnAnalysis": "BASED ON THIS ANALYSIS:",
|
||||||
|
"immediateQuickWins": "IMMEDIATE QUICK WINS (without Wave 1)",
|
||||||
|
"queuesAutomate": "AUTOMATE queues",
|
||||||
|
"interactionsPerMonth": "interactions/month",
|
||||||
|
"potentialSavings": "Potential savings:",
|
||||||
|
"containment": "containment",
|
||||||
|
"skills": "Skills:",
|
||||||
|
"alignedWave4": "→ Aligned with Wave 4 of Roadmap. Can be implemented in parallel to Wave 1.",
|
||||||
|
"waveFoundation": "WAVE 1-3: FOUNDATION → ASSIST ({{count}} queues)",
|
||||||
|
"tierAssist": "in ASSIST tier",
|
||||||
|
"focusWave1": "Wave 1 Focus:",
|
||||||
|
"reduceTransfer": "Reduce transfer in",
|
||||||
|
"potentialCopilot": "Potential with Copilot:",
|
||||||
|
"deflection": "deflection",
|
||||||
|
"requiresWave1": "→ Requires Wave 1 (Foundation) to enable Copilot in Wave 3",
|
||||||
|
"seeRoadmap": "See Roadmap tab for detailed plan",
|
||||||
|
"perInt": "/int"
|
||||||
|
},
|
||||||
|
"humanOnlyReasons": {
|
||||||
|
"title": "HUMAN-ONLY Queues",
|
||||||
|
"subtitle": "Not suitable for automation: insufficient volume, low data quality or extreme complexity",
|
||||||
|
"volumeTotal": "Total volume:"
|
||||||
|
},
|
||||||
|
"redFlagsActions": {
|
||||||
|
"noSpecificFlags": "No Specific Red Flags",
|
||||||
|
"noFlagsDesc": "Queues that don't meet automation criteria",
|
||||||
|
"reviewManually": "Review manually",
|
||||||
|
"standardizeProcesses": "Standardize processes and scripts",
|
||||||
|
"simplifyFlow": "Simplify flow, train agents",
|
||||||
|
"consolidate": "Consolidate with similar queues",
|
||||||
|
"improveDataCapture": "Improve data capture"
|
||||||
|
},
|
||||||
|
"factorsExtended": {
|
||||||
|
"volumeMethodology": "Score = normalized log10(Volume). >5000 → 10, <100 → 2",
|
||||||
|
"volumeBenchmark": "Positive ROI requires >500/month",
|
||||||
|
"volumeGood": "High volume justifies investment",
|
||||||
|
"volumeBad": "Consider shared solutions",
|
||||||
|
"roiPotential": "ROI Potential",
|
||||||
|
"roiDesc": "Expected economic return",
|
||||||
|
"roiMethodology": "Score based on total annual cost. >€500K → 10",
|
||||||
|
"roiBenchmark": "ROI >150% at 12 months",
|
||||||
|
"roiGood": "Solid business case",
|
||||||
|
"roiBad": "Marginal ROI, evaluate other benefits",
|
||||||
|
"resolution": "Resolution",
|
||||||
|
"dataQuality": "Data Quality"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"economicModel": {
|
"economicModel": {
|
||||||
@@ -434,6 +855,10 @@
|
|||||||
"monthsHistory": "Months of history",
|
"monthsHistory": "Months of history",
|
||||||
"sourceSystem": "Source system",
|
"sourceSystem": "Source system",
|
||||||
"periodRange": "Period: {{period}}",
|
"periodRange": "Period: {{period}}",
|
||||||
|
"defaultPeriod": "January - December 2025",
|
||||||
|
"sourceGenesys": "Genesys Cloud CX",
|
||||||
|
"sourceDataset": "Loaded dataset",
|
||||||
|
"pdfDevelopment": "PDF download functionality under development. The document will be available soon.",
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"title": "Transformation Pipeline",
|
"title": "Transformation Pipeline",
|
||||||
"description": "Modular 3-layer architecture to ensure traceability and scalability.",
|
"description": "Modular 3-layer architecture to ensure traceability and scalability.",
|
||||||
@@ -492,6 +917,21 @@
|
|||||||
"howCalculate": "How is Total Cost calculated?",
|
"howCalculate": "How is Total Cost calculated?",
|
||||||
"costEquals": "Cost ="
|
"costEquals": "Cost ="
|
||||||
},
|
},
|
||||||
|
"cpi": {
|
||||||
|
"description": "CPI is calculated by dividing <strong>total cost</strong> by <strong>interaction volume</strong>. Total cost includes <em>all</em> interactions (noise, zombie, and valid) because all are billed, and applies a productivity factor of {{productivity}}%.",
|
||||||
|
"volume": "Volume",
|
||||||
|
"ahtExplanation": "The <strong>AHT</strong> is in seconds, converted to hours by dividing by 3600. Includes all interactions that generate cost (noise + zombie + valid). Only abandons are excluded because they don't consume agent time.",
|
||||||
|
"hourlyRate": "Agent Hourly Rate (Fully Loaded)",
|
||||||
|
"configuredValue": "Configured value: €{{value}}/h",
|
||||||
|
"includesAllCosts": "This value was configured in the data input screen and should include all costs associated with the agent:",
|
||||||
|
"cost1": "Agent gross salary",
|
||||||
|
"cost2": "Social security costs",
|
||||||
|
"cost3": "Software licenses",
|
||||||
|
"cost4": "Infrastructure and workstation",
|
||||||
|
"cost5": "Supervision and QA",
|
||||||
|
"cost6": "Training and overhead",
|
||||||
|
"adjustNote": "If you need to adjust this value, you can return to the data input screen and modify it."
|
||||||
|
},
|
||||||
"impact": {
|
"impact": {
|
||||||
"title": "Transformation Impact",
|
"title": "Transformation Impact",
|
||||||
"metric": "Metric",
|
"metric": "Metric",
|
||||||
@@ -505,12 +945,18 @@
|
|||||||
"revealsDemand": "Reveals hidden failed demand",
|
"revealsDemand": "Reveals hidden failed demand",
|
||||||
"detectsFrustration": "Detects real customer frustration",
|
"detectsFrustration": "Detects real customer frustration",
|
||||||
"executiveVision": "Actionable executive vision",
|
"executiveVision": "Actionable executive vision",
|
||||||
"reflectsPerformance": "KPIs reflect real performance"
|
"reflectsPerformance": "KPIs reflect real performance",
|
||||||
|
"technicalSkills": "{{count}} technical",
|
||||||
|
"businessLines": "{{count}} business lines",
|
||||||
|
"distorted": "Distorted",
|
||||||
|
"clean": "Clean",
|
||||||
|
"withoutTransformation": "Without this transformation,",
|
||||||
|
"wrongInvestments": "automation decisions would be based on incorrect data, generating investments in the wrong processes."
|
||||||
},
|
},
|
||||||
"skillMapping": {
|
"skillMapping": {
|
||||||
"title": "Skills to Business Lines Mapping",
|
"title": "Skills to Business Lines Mapping",
|
||||||
"simplificationApplied": "Simplification applied",
|
"simplificationApplied": "Simplification applied",
|
||||||
"reductionDesc": "Reduced complexity from 980 technical skills to {{count}} actionable Business Lines using fuzzy keyword logic.",
|
"reductionDesc": "Reduced complexity from 980 technical skills to {{count}} Business Lines. This simplification is vital for executive visualization and strategic decision making.",
|
||||||
"businessLine": "Business Line",
|
"businessLine": "Business Line",
|
||||||
"keywords": "Detected Keywords (Fuzzy Logic)",
|
"keywords": "Detected Keywords (Fuzzy Logic)",
|
||||||
"baggage": "Baggage & Handling",
|
"baggage": "Baggage & Handling",
|
||||||
@@ -520,7 +966,8 @@
|
|||||||
"changes": "Changes & Post-Sales",
|
"changes": "Changes & Post-Sales",
|
||||||
"digital": "Digital Support",
|
"digital": "Digital Support",
|
||||||
"customer": "Customer Service",
|
"customer": "Customer Service",
|
||||||
"internal": "Internal / Backoffice"
|
"internal": "Internal / Backoffice",
|
||||||
|
"fuzzyLogicNote": "The mapping uses fuzzy logic to automatically classify each technical skill based on keywords detected in its name. Unclassified skills are assigned to \"Customer Service\"."
|
||||||
},
|
},
|
||||||
"quality": {
|
"quality": {
|
||||||
"title": "Quality Guarantees",
|
"title": "Quality Guarantees",
|
||||||
@@ -547,5 +994,324 @@
|
|||||||
"november": "November",
|
"november": "November",
|
||||||
"december": "December"
|
"december": "December"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"law10": {
|
||||||
|
"constants": {
|
||||||
|
"requirements": {
|
||||||
|
"LAW_07": {
|
||||||
|
"name": "Time Coverage"
|
||||||
|
},
|
||||||
|
"LAW_01": {
|
||||||
|
"name": "Response Speed"
|
||||||
|
},
|
||||||
|
"LAW_02": {
|
||||||
|
"name": "Resolution Quality"
|
||||||
|
},
|
||||||
|
"LAW_09": {
|
||||||
|
"name": "Language Coverage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"CUMPLE": "Complies",
|
||||||
|
"PARCIAL": "Partial",
|
||||||
|
"NO_CUMPLE": "Does Not Comply",
|
||||||
|
"SIN_DATOS": "No Data"
|
||||||
|
},
|
||||||
|
"compliance": {
|
||||||
|
"law07": {
|
||||||
|
"noData": "No time distribution data",
|
||||||
|
"noDataDetails": "No time distribution data found in the analysis",
|
||||||
|
"offHoursPercent": "{{percent}}% of interactions outside business hours",
|
||||||
|
"adequateCoverage": "Adequate time coverage",
|
||||||
|
"improvableCoverage": "Time coverage can be improved - consider extending hours",
|
||||||
|
"insufficientCoverage": "Insufficient time coverage - requires immediate action",
|
||||||
|
"gapOverOptimal": "{{gap}}pp over optimal",
|
||||||
|
"gapOverLimit": "{{gap}}pp over limit"
|
||||||
|
},
|
||||||
|
"law01": {
|
||||||
|
"noData": "No wait time data",
|
||||||
|
"noDataDetails": "No hold_time data found in the analysis",
|
||||||
|
"avgHoldTime": "Average wait time: {{time}}s (limit: 180s)",
|
||||||
|
"withinLimit": "{{percent}}% of interactions within limit",
|
||||||
|
"queuesExceedLimit": "{{count}} of {{total}} queues exceed the limit",
|
||||||
|
"gapNegative": "-{{gap}}s",
|
||||||
|
"queuesOutside": "{{count}} queues outside",
|
||||||
|
"gapPositive": "+{{gap}}s"
|
||||||
|
},
|
||||||
|
"law02": {
|
||||||
|
"noData": "No resolution data",
|
||||||
|
"noDataDetails": "No FCR or transfer data found",
|
||||||
|
"fcrTechnical": "Technical FCR: {{fcr}}% (target: >75%)",
|
||||||
|
"transferRate": "Transfer rate: {{rate}}% (target: <15%)",
|
||||||
|
"highTransferQueues": "{{count}} queues with transfer >25%",
|
||||||
|
"gapDash": "-",
|
||||||
|
"gapFcr": "FCR {{fcrGap}}, Transfer {{transferGap}}",
|
||||||
|
"gapOk": "OK",
|
||||||
|
"gapPositive": "+{{gap}}pp",
|
||||||
|
"gapNegative": "-{{gap}}pp"
|
||||||
|
},
|
||||||
|
"law09": {
|
||||||
|
"noData": "Requires data",
|
||||||
|
"noLanguageData": "No language data available in interactions",
|
||||||
|
"needsLanguageField": "To evaluate this requirement, the \"language\" field is needed in the CSV"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"aboutThisAnalysis": "About this Analysis",
|
||||||
|
"lawTitle": "Law 10/2025 on Customer Service",
|
||||||
|
"description": "This module connects your <strong>current operational metrics</strong> with Law 10/2025 requirements. It doesn't measure compliance directly (would require additional data), but <strong>DOES identify patterns</strong> that impact your ability to comply with regulations.",
|
||||||
|
"complianceDeadline": "Compliance deadline",
|
||||||
|
"december282026": "December 28, 2026",
|
||||||
|
"daysRemaining": "{{days}} days remaining",
|
||||||
|
"requirementsEvaluated": "Requirements evaluated",
|
||||||
|
"requirementsMet": "{{met}} of {{total}} comply",
|
||||||
|
"basedOnData": "Based on available data",
|
||||||
|
"overallStatus": "Overall status",
|
||||||
|
"goodState": "Good state",
|
||||||
|
"requiresAttention": "Requires attention",
|
||||||
|
"urgentAction": "Urgent action"
|
||||||
|
},
|
||||||
|
"timeCoverage": {
|
||||||
|
"title": "Temporal Coverage: Service Availability",
|
||||||
|
"article": "Related to Art. 14 - 24/7 basic services",
|
||||||
|
"whatWeKnow": "WHAT WE KNOW",
|
||||||
|
"heatmap247": "24x7 VOLUMETRIC HEATMAP",
|
||||||
|
"intensity": "Intensity:",
|
||||||
|
"intensityLow": "Low",
|
||||||
|
"intensityMedium": "Medium",
|
||||||
|
"intensityHigh": "High",
|
||||||
|
"operationalFindings": "Operational findings:",
|
||||||
|
"detectedSchedule": "Detected schedule: <strong>M-F 08:00-22:00</strong>, Sat-Sun reduced hours",
|
||||||
|
"nightVolume": "Night volume (22:00-08:00): <strong>{{volume}}</strong> interactions ({{percent}}%)",
|
||||||
|
"earlyMorningVolume": "Early morning volume (00:00-06:00): <strong>{{volume}}</strong> interactions ({{percent}}%)",
|
||||||
|
"peakHour": "Peak hour: <strong>{{hour}}:00-{{hourEnd}}:00</strong> ({{percent}}% of daily volume)",
|
||||||
|
"lawImplication": "LAW 10/2025 IMPLICATION",
|
||||||
|
"basicServiceRequirement": "Air transport = Basic service",
|
||||||
|
"article14Requirement": "→ Art. 14 requires 24/7 attention for incidents",
|
||||||
|
"gapIdentified": "Gap identified:",
|
||||||
|
"clientsOutsideHours": "<strong>{{percent}}%</strong> of your clients contact outside current hours",
|
||||||
|
"complianceIssue": "If these are incidents (lost luggage, urgent changes), <strong>you DON'T comply with Art. 14</strong>",
|
||||||
|
"suggestedAction": "SUGGESTED ACTION",
|
||||||
|
"classifyNightVolume": "1. Classify night volume by type:",
|
||||||
|
"criticalIncidents": "What % are critical incidents? → Requires 24/7",
|
||||||
|
"generalQueries": "What % are general queries? → Can wait",
|
||||||
|
"coverageOptions": "2. Coverage options:",
|
||||||
|
"optionAChatbot": "A) AI Chatbot + on-call agent",
|
||||||
|
"optionBExternal": "B) Redirect to external 24/7 call center",
|
||||||
|
"optionCNight": "C) Night shift agents (3 shifts)",
|
||||||
|
"costPerYear": "~{{cost}}/year"
|
||||||
|
},
|
||||||
|
"responseSpeed": {
|
||||||
|
"title": "Response Speed: Operational Efficiency",
|
||||||
|
"article": "Related to Art. 8.2 - 95% calls <3min",
|
||||||
|
"whatWeKnow": "WHAT WE KNOW",
|
||||||
|
"abandonmentRate": "Abandonment rate",
|
||||||
|
"ahtP50": "AHT P50 ({{min}}m {{sec}}s)",
|
||||||
|
"ahtP90": "AHT P90 ({{min}}m {{sec}}s)",
|
||||||
|
"ratioP90P50": "P90/P50 Ratio {{elevated}}",
|
||||||
|
"elevated": "(elevated)",
|
||||||
|
"abandonmentByHour": "ABANDONMENT DISTRIBUTION BY HOUR",
|
||||||
|
"abandonmentLegend": "Abandonment:",
|
||||||
|
"abandonmentLow": "<8%",
|
||||||
|
"abandonmentMedium": "8-15%",
|
||||||
|
"abandonmentHigh": ">20%",
|
||||||
|
"patternsObserved": "Patterns observed:",
|
||||||
|
"maxAbandonment": "Highest abandonment: <strong>{{hourStart}}:00-{{hourEnd}}:00</strong> ({{rate}}% vs {{avg}}% average)",
|
||||||
|
"highestAht": "Highest AHT: <strong>Monday 09:00-11:00</strong> ({{high}}s vs {{p50}}s P50)",
|
||||||
|
"minAbandonment": "Lowest abandonment: <strong>{{hourStart}}:00-{{hourEnd}}:00</strong> ({{rate}}%)",
|
||||||
|
"lawImplication": "LAW 10/2025 IMPLICATION",
|
||||||
|
"article82Requirement": "Art. 8.2 requires: \"95% of calls answered in <3 minutes\"",
|
||||||
|
"dataLimitation": "DATA LIMITATION",
|
||||||
|
"noAsaData": "Your current CDR does NOT include ASA (queue time before answer), so we CANNOT measure this requirement directly.",
|
||||||
|
"butWeKnow": "BUT WE DO know:",
|
||||||
|
"customersAbandon": "<strong>{{rate}}%</strong> of customers abandon → They likely waited too long",
|
||||||
|
"highVariability": "High AHT variability (P90/P50={{ratio}}) → Unpredictable queue",
|
||||||
|
"peaksCoincide": "Abandonment peaks coincide with volume peaks",
|
||||||
|
"conservativeEstimate": "Conservative estimate (±10% margin of error):",
|
||||||
|
"likelyFastResponse": "→ ~<strong>{{percent}}%</strong> of calls likely answered \"quickly\"",
|
||||||
|
"gapVs95": "→ Gap vs required 95%: <strong>{{operator}}{{gap}}</strong> percentage points",
|
||||||
|
"suggestedAction": "SUGGESTED ACTION",
|
||||||
|
"shortTerm": "1. SHORT TERM: Reduce AHT to increase capacity",
|
||||||
|
"dimension2Identifies": "Your Dimension 2 (Efficiency) already identifies:",
|
||||||
|
"highAht": "- High AHT ({{aht}}s vs 380s benchmark)",
|
||||||
|
"copilotOpportunity": "- AI Copilot opportunity: -18% projected AHT",
|
||||||
|
"dualBenefit": "Dual benefit: ↓ AHT = ↑ capacity = ↓ queue = ↑ ASA",
|
||||||
|
"mediumTerm": "2. MEDIUM TERM: Implement real ASA tracking",
|
||||||
|
"platformConfig": "Platform configuration",
|
||||||
|
"implementationTimeline": "Implementation timeline",
|
||||||
|
"implementationWeeks": "4-6 weeks",
|
||||||
|
"benefit": "Benefit: Precise measurement for ENAC audit"
|
||||||
|
},
|
||||||
|
"resolutionQuality": {
|
||||||
|
"title": "Resolution Quality: Effectiveness",
|
||||||
|
"article": "Related to Art. 17 - Resolution in 15 days",
|
||||||
|
"whatWeKnow": "WHAT WE KNOW",
|
||||||
|
"fcrReal": "Real FCR (fcr_real_flag)",
|
||||||
|
"recontactRate7d": "7-day recontact rate",
|
||||||
|
"repeatCalls": "Repeat calls",
|
||||||
|
"fcrBySkill": "FCR BY SKILL/QUEUE",
|
||||||
|
"fcrLegend": "FCR:",
|
||||||
|
"fcrLow": "<45%",
|
||||||
|
"fcrMedium": "45-65%",
|
||||||
|
"fcrHigh": ">75%",
|
||||||
|
"topLowFcr": "Top skills with low FCR:",
|
||||||
|
"fcrValue": "{{fcr}}% FCR",
|
||||||
|
"lawImplication": "LAW 10/2025 IMPLICATION",
|
||||||
|
"article17Requirement": "Art. 17 requires: \"Resolution of complaints ≤15 days\"",
|
||||||
|
"dataLimitation": "DATA LIMITATION",
|
||||||
|
"noCaseTracking": "Your CDR only records individual interactions, NOT multi-touch cases or total resolution time.",
|
||||||
|
"butWeKnow": "BUT WE DO know:",
|
||||||
|
"multipleContactsRequired": "<strong>{{percent}}%</strong> of cases require multiple contacts",
|
||||||
|
"fcrGap": "FCR {{fcr}}% = {{gap}}% NOT resolved at first contact",
|
||||||
|
"complexProcesses": "This suggests complex processes or fragmented information",
|
||||||
|
"alertSignal": "Warning signal:",
|
||||||
|
"resolutionTimeRisk": "If customers recontact multiple times for the same issue, TOTAL resolution time likely exceeds the 15 days required by law.",
|
||||||
|
"suggestedAction": "SUGGESTED ACTION",
|
||||||
|
"diagnosis": "1. DIAGNOSIS: Implement case/ticket system",
|
||||||
|
"registerOpenClose": "Register open + close dates",
|
||||||
|
"linkInteractions": "Link multiple interactions to same case",
|
||||||
|
"typology": "Typology: query / complaint / incident",
|
||||||
|
"crmInvestment": "CRM/Ticketing investment",
|
||||||
|
"operationalImprovement": "2. OPERATIONAL IMPROVEMENT: Increase FCR",
|
||||||
|
"dimension3Identifies": "Your Dimension 3 (Effectiveness) already identifies:",
|
||||||
|
"rootCauses": "- Root causes: fragmented info, lack of empowerment",
|
||||||
|
"solution": "- Solution: Knowledge base + decision trees",
|
||||||
|
"fcrBenefit": "Benefit: ↑ FCR = ↓ recontacts = ↓ total time"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"title": "Compliance Summary - All Requirements",
|
||||||
|
"requirementColumn": "Requirement",
|
||||||
|
"descriptionColumn": "Description",
|
||||||
|
"statusColumn": "Status",
|
||||||
|
"scoreColumn": "Score",
|
||||||
|
"gapColumn": "Gap",
|
||||||
|
"requirements": {
|
||||||
|
"LAW-01": {
|
||||||
|
"name": "Wait Time",
|
||||||
|
"description": "Maximum wait time of 3 minutes for phone service"
|
||||||
|
},
|
||||||
|
"LAW-02": {
|
||||||
|
"name": "Effective Resolution",
|
||||||
|
"description": "First contact resolution without unnecessary transfers"
|
||||||
|
},
|
||||||
|
"LAW-03": {
|
||||||
|
"name": "Human Agent Access",
|
||||||
|
"description": "Right to speak with a human agent at any time"
|
||||||
|
},
|
||||||
|
"LAW-04": {
|
||||||
|
"name": "Call Recording",
|
||||||
|
"description": "Prior notification of recording and access to it"
|
||||||
|
},
|
||||||
|
"LAW-05": {
|
||||||
|
"name": "Accessibility",
|
||||||
|
"description": "Accessible channels for people with disabilities"
|
||||||
|
},
|
||||||
|
"LAW-06": {
|
||||||
|
"name": "Written Confirmation",
|
||||||
|
"description": "Written confirmation of complaints and procedures"
|
||||||
|
},
|
||||||
|
"LAW-07": {
|
||||||
|
"name": "Time Coverage",
|
||||||
|
"description": "24/7 service for essential services or extended hours"
|
||||||
|
},
|
||||||
|
"LAW-08": {
|
||||||
|
"name": "Agent Training",
|
||||||
|
"description": "Qualified personnel trained in customer service"
|
||||||
|
},
|
||||||
|
"LAW-09": {
|
||||||
|
"name": "Co-official Languages",
|
||||||
|
"description": "Service in Catalan, Basque, Galician and Valencian"
|
||||||
|
},
|
||||||
|
"LAW-10": {
|
||||||
|
"name": "Resolution Timeframes",
|
||||||
|
"description": "Complaint resolution in maximum 15 business days"
|
||||||
|
},
|
||||||
|
"LAW-11": {
|
||||||
|
"name": "Free Service",
|
||||||
|
"description": "Phone service at no additional cost (900 numbers)"
|
||||||
|
},
|
||||||
|
"LAW-12": {
|
||||||
|
"name": "Traceability",
|
||||||
|
"description": "Reference number for procedure tracking"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"complies": "Complies: Requirement met",
|
||||||
|
"partial": "Partial: Requires improvements",
|
||||||
|
"notComplies": "Does Not Comply: Urgent action",
|
||||||
|
"noData": "No Data: Fields not available in CSV"
|
||||||
|
},
|
||||||
|
"investment": {
|
||||||
|
"nonComplianceCost": "Cost of non-compliance",
|
||||||
|
"upTo100k": "Up to 100K",
|
||||||
|
"potentialFines": "Potential fines/violation",
|
||||||
|
"recommendedInvestment": "Recommended investment",
|
||||||
|
"basedOnOperation": "Based on your operation",
|
||||||
|
"complianceRoi": "Compliance ROI",
|
||||||
|
"avoidSanctionsAndImprove": "Avoid sanctions + improve CX",
|
||||||
|
"highRoi": "High",
|
||||||
|
"percentOfAnnualCost": "~5% annual cost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataMaturity": {
|
||||||
|
"title": "Summary: Data Maturity for Compliance",
|
||||||
|
"currentLevel": "Your current instrumentation level:",
|
||||||
|
"dataAvailable": "DATA AVAILABLE (3/10)",
|
||||||
|
"dataEstimable": "ESTIMABLE DATA (2/10)",
|
||||||
|
"dataNotAvailable": "NOT AVAILABLE (5/10)",
|
||||||
|
"availableData": {
|
||||||
|
"temporalCoverage": "24/7 temporal coverage",
|
||||||
|
"geographicDistribution": "Geographic distribution",
|
||||||
|
"resolutionQuality": "Resolution quality proxy"
|
||||||
|
},
|
||||||
|
"estimableData": {
|
||||||
|
"asaProxy": "ASA <3min via abandonment proxy",
|
||||||
|
"languagesViaCountry": "Co-official languages via country"
|
||||||
|
},
|
||||||
|
"missingData": {
|
||||||
|
"caseResolutionTime": "Case resolution time",
|
||||||
|
"refundTime": "Improper charges <5 days",
|
||||||
|
"supervisorTransfer": "Transfer to supervisor",
|
||||||
|
"incidentInfo": "Incident info <2h",
|
||||||
|
"enacAudit": "ENAC audit",
|
||||||
|
"externalContract": "requires external contracting"
|
||||||
|
},
|
||||||
|
"suggestedInvestment": "SUGGESTED INVESTMENT FOR FULL COMPLIANCE",
|
||||||
|
"phase1": "Phase 1 - Instrumentation (Q1 2026)",
|
||||||
|
"phase2": "Phase 2 - Operations (Q2-Q3 2026)",
|
||||||
|
"phase1Items": {
|
||||||
|
"asaTracking": "Real ASA tracking",
|
||||||
|
"ticketingSystem": "Ticketing/case system",
|
||||||
|
"languageEnrichment": "Language enrichment",
|
||||||
|
"subtotal": "Subtotal:"
|
||||||
|
},
|
||||||
|
"phase2Items": {
|
||||||
|
"coverage247": "24/7 coverage (chatbot + on-call)",
|
||||||
|
"aiCopilot": "AI Copilot (reduce AHT)",
|
||||||
|
"enacAuditor": "ENAC Auditor",
|
||||||
|
"subtotalYear1": "Year 1 subtotal:",
|
||||||
|
"perYear": "/year",
|
||||||
|
"perMonth": "/month"
|
||||||
|
},
|
||||||
|
"totals": {
|
||||||
|
"totalInvestment": "Total Investment",
|
||||||
|
"percentAnnualCost": "~5% annual cost",
|
||||||
|
"riskAvoided": "Risk Avoided",
|
||||||
|
"potentialSanctions": "potential sanctions",
|
||||||
|
"complianceRoi": "Compliance ROI"
|
||||||
|
},
|
||||||
|
"article": "Art. {{number}}",
|
||||||
|
"articlePartial": "Art. {{number}} partial",
|
||||||
|
"articleIndirect": "Art. {{number}} indirect",
|
||||||
|
"errorMargin": "±{{margin}}%",
|
||||||
|
"noDetail": "no detail"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"noData": "No data",
|
||||||
|
"requiredData": "Requires data",
|
||||||
|
"score": "Score",
|
||||||
|
"gap": "Gap"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,10 @@
|
|||||||
"viewResults": "Ver Resultados"
|
"viewResults": "Ver Resultados"
|
||||||
},
|
},
|
||||||
"tierSelector": {
|
"tierSelector": {
|
||||||
|
"popular": "POPULAR",
|
||||||
|
"selected": "Seleccionado",
|
||||||
|
"select": "Seleccionar",
|
||||||
|
"oneTime": "one-time",
|
||||||
"hideComparison": "Ocultar Comparación",
|
"hideComparison": "Ocultar Comparación",
|
||||||
"viewComparison": "Ver Comparación Detallada",
|
"viewComparison": "Ver Comparación Detallada",
|
||||||
"comparison": "Comparación de Tiers",
|
"comparison": "Comparación de Tiers",
|
||||||
@@ -131,6 +135,7 @@
|
|||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard de Diagnóstico",
|
"title": "Dashboard de Diagnóstico",
|
||||||
|
"defaultTitle": "CLIENTE DEMO - Beyond CX Analytics",
|
||||||
"viewDashboard": "Ver Dashboard de Diagnóstico",
|
"viewDashboard": "Ver Dashboard de Diagnóstico",
|
||||||
"generateAnalysis": "Generar Análisis",
|
"generateAnalysis": "Generar Análisis",
|
||||||
"analyzing": "Analizando...",
|
"analyzing": "Analizando...",
|
||||||
@@ -245,7 +250,38 @@
|
|||||||
"betterThan75": "Mejor que 3 de cada 4 empresas",
|
"betterThan75": "Mejor que 3 de cada 4 empresas",
|
||||||
"alignedWithMedian": "En línea con la mediana del sector",
|
"alignedWithMedian": "En línea con la mediana del sector",
|
||||||
"belowAverage": "Por debajo de la media del mercado",
|
"belowAverage": "Por debajo de la media del mercado",
|
||||||
"criticalArea": "Área crítica de mejora"
|
"criticalArea": "Área crítica de mejora",
|
||||||
|
"opportunity": "Oportunidad",
|
||||||
|
"top10": "Top 10%",
|
||||||
|
"top25": "Top 25%",
|
||||||
|
"average": "Promedio",
|
||||||
|
"belowAvg": "Bajo Avg",
|
||||||
|
"bottom25": "Bottom 25%",
|
||||||
|
"benchmarkLow": "Bajo",
|
||||||
|
"benchmarkMedian": "Mediana",
|
||||||
|
"benchmarkTop": "Top",
|
||||||
|
"excellent": "Excelente",
|
||||||
|
"good": "Bueno",
|
||||||
|
"regular": "Regular",
|
||||||
|
"aht": "AHT",
|
||||||
|
"fcr": "FCR",
|
||||||
|
"abandonment": "ABANDONO",
|
||||||
|
"costPerInteraction": "COSTE/INTERAC.",
|
||||||
|
"ahtTotal": "AHT Total",
|
||||||
|
"fcrAdjusted": "FCR Ajustado",
|
||||||
|
"ahtTooltip": "Tiempo medio de gestión (solo interacciones válidas)",
|
||||||
|
"ahtTotalTooltip": "Incluye todas las filas (noise, zombie, abandon) - solo informativo",
|
||||||
|
"ahtTotalDesc": "Incluye noise, zombie y abandonos — solo informativo",
|
||||||
|
"fcrTooltip": "First Contact Resolution - comparable con benchmarks de industria",
|
||||||
|
"fcrAdjustedTooltip": "Excluye recontactos en 7 días (métrica más estricta)",
|
||||||
|
"fcrAdjustedDesc": "Incluye filtro de recontactos 7d — métrica interna más estricta",
|
||||||
|
"abandonmentTooltip": "Tasa de abandono",
|
||||||
|
"cpiTooltip": "Coste por interacción",
|
||||||
|
"recontacts7d": "de recontactos 7d",
|
||||||
|
"vsCleanAht": "vs AHT limpio",
|
||||||
|
"queuesLabel": "colas",
|
||||||
|
"readyQueues": "listas",
|
||||||
|
"criticalQueues": "críticas"
|
||||||
},
|
},
|
||||||
"industries": {
|
"industries": {
|
||||||
"airlines": "Aerolíneas",
|
"airlines": "Aerolíneas",
|
||||||
@@ -262,7 +298,72 @@
|
|||||||
"keyFinding": "Hallazgo Clave",
|
"keyFinding": "Hallazgo Clave",
|
||||||
"keyFindings": "Hallazgos Clave",
|
"keyFindings": "Hallazgos Clave",
|
||||||
"noDataAvailable": "Sin datos disponibles para esta dimensión.",
|
"noDataAvailable": "Sin datos disponibles para esta dimensión.",
|
||||||
"withinAcceptable": "Métricas dentro de rangos aceptables. Sin hallazgos críticos."
|
"withinAcceptable": "Métricas dentro de rangos aceptables. Sin hallazgos críticos.",
|
||||||
|
"impact": "Impacto:",
|
||||||
|
"probableCause": "Causa probable:",
|
||||||
|
"annualImpact": "impacto anual (coste del problema)",
|
||||||
|
"recommendation": "Recomendación:",
|
||||||
|
"operationalEfficiency": {
|
||||||
|
"highAHTFinding": "AHT elevado: P50 {{aht}} (benchmark: 5:00)",
|
||||||
|
"highAHTCause": "Agentes dedican tiempo excesivo a búsqueda manual de información, navegación entre sistemas y tareas repetitivas.",
|
||||||
|
"highAHTRecommendation": "Desplegar Copilot IA para agentes: (1) Auto-búsqueda en KB; (2) Sugerencias contextuales en tiempo real; (3) Scripts guiados para casos frecuentes. Reducción esperada: 20-30% AHT. Ahorro: {{savings}}/año.",
|
||||||
|
"goodAHTFinding": "AHT dentro de benchmark: P50 {{aht}} (benchmark: 5:00)",
|
||||||
|
"goodAHTCause": "Tiempos de gestión eficientes. Procesos operativos optimizados.",
|
||||||
|
"goodAHTImpact": "Sin exceso de coste por AHT",
|
||||||
|
"goodAHTTimeSavings": "Operación eficiente",
|
||||||
|
"goodAHTRecommendation": "Mantener nivel actual. Considerar Copilot para mejora continua y reducción adicional de tiempos en casos complejos."
|
||||||
|
},
|
||||||
|
"effectiveness": {
|
||||||
|
"finding": "FCR Técnico: {{fcr}}% | Transferencias: {{transfer}}% (benchmark: FCR >85%, Transfer <10%)",
|
||||||
|
"criticalCause": "Alta tasa de transferencias ({{transfer}}%) indica falta de herramientas o autoridad. Crítico en {{skills}}.",
|
||||||
|
"criticalCauseGeneric": "Transferencias elevadas ({{transfer}}%): agentes sin información contextual o sin autoridad para resolver.",
|
||||||
|
"warningCause": "Transferencias del {{transfer}}% indican oportunidad de mejora con asistencia IA para casos complejos.",
|
||||||
|
"goodCause": "FCR Técnico en nivel óptimo. Transferencias del {{transfer}}% principalmente en casos que requieren escalación legítima.",
|
||||||
|
"criticalRecommendation": "Desplegar Knowledge Copilot con búsqueda inteligente en KB + Guided Resolution Copilot para casos complejos. Objetivo: FCR >85%. Potencial ahorro: {{savings}}/año.",
|
||||||
|
"warningRecommendation": "Implementar Copilot de asistencia en tiempo real: sugerencias contextuales + conexión con expertos virtuales para reducir transferencias. Objetivo: FCR >90%.",
|
||||||
|
"goodRecommendation": "Mantener nivel actual. Considerar IA para análisis de transferencias legítimas y optimización de enrutamiento predictivo.",
|
||||||
|
"impactFormula": "{{count}} transferencias/año × €{{cpi}}/int × 50% coste adicional",
|
||||||
|
"timeSavings": "{{count}} transferencias/año ({{pct}}% del volumen)"
|
||||||
|
},
|
||||||
|
"volumetry": {
|
||||||
|
"concentrationFinding": "Concentración de volumen: {{skill}} representa {{pct}}% del total",
|
||||||
|
"concentrationCause": "Alta concentración en un skill indica consultas repetitivas con potencial de automatización.",
|
||||||
|
"concentrationRecommendation": "Analizar tipologías de {{skill}} para deflexión a autoservicio o agente virtual. Potencial: {{savings}}/año.",
|
||||||
|
"impactFormula": "{{volume}} int × anualización × €{{cpi}} × 20% deflexión potencial",
|
||||||
|
"timeSavings": "{{volume}} interacciones/año en {{skill}} ({{deflectable}} automatizables)"
|
||||||
|
},
|
||||||
|
"complexity": {
|
||||||
|
"highCVFinding": "CV AHT elevado: {{cv}}% (benchmark: <{{benchmark}}%)",
|
||||||
|
"highCVCauseCritical": "Dispersión extrema en tiempos de atención impide planificación efectiva de recursos. Probable falta de scripts o procesos estandarizados.",
|
||||||
|
"highCVCauseWarning": "Variabilidad moderada en tiempos indica oportunidad de estandarización para mejorar planificación WFM.",
|
||||||
|
"highCVImpactFormula": "~3% del coste operativo por ineficiencia de staffing",
|
||||||
|
"highCVTimeSavings": "~{{hours}} horas/año en sobre/subdimensionamiento",
|
||||||
|
"highCVRecommendation": "Implementar scripts guiados por IA que estandaricen la atención. Reducción esperada: -50% variabilidad. Ahorro: {{savings}}/año.",
|
||||||
|
"goodCVFinding": "CV AHT dentro de benchmark: {{cv}}% (benchmark: <{{benchmark}}%)",
|
||||||
|
"goodCVCause": "Tiempos de atención consistentes. Buena estandarización de procesos.",
|
||||||
|
"goodCVImpactFormula": "Sin impacto por variabilidad",
|
||||||
|
"goodCVTimeSavings": "Planificación WFM eficiente",
|
||||||
|
"goodCVRecommendation": "Mantener nivel actual. Analizar casos atípicos para identificar oportunidades de mejora continua.",
|
||||||
|
"holdTimeFinding": "Hold time elevado: {{holdTime}}s promedio (benchmark: <30s)",
|
||||||
|
"holdTimeCause": "Agentes ponen cliente en espera para buscar información. Sistemas no presentan datos de forma contextual.",
|
||||||
|
"holdTimeImpactFormula": "Exceso {{excess}}s × {{volume}} int × anualización × €{{cost}}/h",
|
||||||
|
"holdTimeTimeSavings": "{{hours}} horas/año de cliente en espera",
|
||||||
|
"holdTimeRecommendation": "Desplegar vista 360° con contexto automático: historial, productos y acciones sugeridas visibles al contestar. Reducción esperada: -60% hold time. Ahorro: {{savings}}/año."
|
||||||
|
},
|
||||||
|
"satisfaction": {
|
||||||
|
"lowCSATFinding": "CSAT por debajo del objetivo: {{csat}}% (benchmark: >80%)",
|
||||||
|
"lowCSATCause": "Clientes insatisfechos por esperas, falta de resolución o experiencia de atención deficiente.",
|
||||||
|
"lowCSATImpactFormula": "{{volume}} clientes × anualización × 2% riesgo churn × €50 valor",
|
||||||
|
"lowCSATTimeSavings": "{{customers}} clientes/año en riesgo de fuga",
|
||||||
|
"lowCSATRecommendation": "Implementar programa VoC: encuestas post-contacto + análisis de causas raíz + acción correctiva en 48h. Objetivo: CSAT >80%."
|
||||||
|
},
|
||||||
|
"economy": {
|
||||||
|
"highCPIFinding": "CPI por encima del benchmark: €{{cpi}} (objetivo: €{{target}})",
|
||||||
|
"highCPICause": "Coste por interacción elevado por AHT alto, baja ocupación o estructura de costes ineficiente.",
|
||||||
|
"highCPIImpactFormula": "{{volume}} int × anualización × €{{excess}} exceso CPI",
|
||||||
|
"highCPITimeSavings": "€{{excess}} exceso/int × {{volume}} int = {{hours}} equivalentes",
|
||||||
|
"highCPIRecommendation": "Optimizar mix de canales + reducir AHT con automatización + revisar modelo de staffing. Objetivo: CPI <€{{target}}."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"roadmap": {
|
"roadmap": {
|
||||||
"wave1": "Wave 1: AUTOMATE",
|
"wave1": "Wave 1: AUTOMATE",
|
||||||
@@ -289,7 +390,149 @@
|
|||||||
"foundationFirst": "Foundation First",
|
"foundationFirst": "Foundation First",
|
||||||
"foundationFirstDesc": "Preparar la operación antes de automatizar",
|
"foundationFirstDesc": "Preparar la operación antes de automatizar",
|
||||||
"standardization": "Estandarización",
|
"standardization": "Estandarización",
|
||||||
"standardizationDesc": "Resolver problemas operativos críticos antes de invertir en IA"
|
"standardizationDesc": "Resolver problemas operativos críticos antes de invertir en IA",
|
||||||
|
"opportunityMapTitle": "Mapa de Oportunidades por Tier",
|
||||||
|
"opportunityMapSubtitle": "Factibilidad (Score) vs Impacto Económico (Ahorro TCO) • Tamaño = Volumen • Color = Tier",
|
||||||
|
"economicImpactAxis": "IMPACTO ECONÓMICO (Ahorro TCO €/año)",
|
||||||
|
"feasibilityAxis": "FACTIBILIDAD (Agentic Readiness Score 0-10)",
|
||||||
|
"tierAutomateThreshold": "Tier AUTOMATE ≥7.5",
|
||||||
|
"quadrantQuickWins": "QUICK WINS",
|
||||||
|
"quadrantQuickWinsDesc": "Score ≥7.5 + Ahorro alto",
|
||||||
|
"quadrantQuickWinsPriority": "→ Prioridad 1",
|
||||||
|
"quadrantOptimize": "OPTIMIZE",
|
||||||
|
"quadrantOptimizeDesc": "Score <7.5 + Ahorro alto",
|
||||||
|
"quadrantOptimizePriority": "→ Wave 1 primero",
|
||||||
|
"quadrantStrategic": "STRATEGIC",
|
||||||
|
"quadrantStrategicDesc": "Score ≥7.5 + Ahorro bajo",
|
||||||
|
"quadrantStrategicPriority": "→ Evaluar ROI",
|
||||||
|
"quadrantDefer": "DEFER",
|
||||||
|
"quadrantDeferDesc": "Score <7.5 + Ahorro bajo",
|
||||||
|
"quadrantDeferPriority": "→ Backlog",
|
||||||
|
"tooltipScore": "Score:",
|
||||||
|
"tooltipVolume": "Volumen:",
|
||||||
|
"tooltipSavingsTco": "Ahorro TCO:",
|
||||||
|
"tooltipTier": "Tier:",
|
||||||
|
"perMonth": "/mes",
|
||||||
|
"perYear": "/año",
|
||||||
|
"interpretation": "Interpretación:",
|
||||||
|
"opportunityMapInterpretation": "Las burbujas en el cuadrante superior derecho (Score alto + Ahorro alto) son Quick Wins para automatización. El tamaño indica volumen de interacciones.",
|
||||||
|
"bubbleSize": "Tamaño:",
|
||||||
|
"volume": "Volumen",
|
||||||
|
"methodologyTitle": "Metodología de Cálculo",
|
||||||
|
"axisXFactibility": "Eje X: FACTIBILIDAD (Score 0-10)",
|
||||||
|
"axisXFactibilityDesc": "Score Agentic Readiness calculado con 5 factores ponderados:",
|
||||||
|
"factorPredictability": "Predictibilidad (30%)",
|
||||||
|
"factorPredictabilityDesc": "basado en CV AHT",
|
||||||
|
"factorResolution": "Resolutividad (25%)",
|
||||||
|
"factorResolutionDesc": "FCR (60%) + Transfer (40%)",
|
||||||
|
"factorVolumeWeight": "Volumen (25%)",
|
||||||
|
"factorVolumeDesc": "escala logarítmica del volumen",
|
||||||
|
"factorDataQuality": "Calidad Datos (10%)",
|
||||||
|
"factorDataQualityDesc": "% registros válidos",
|
||||||
|
"factorSimplicity": "Simplicidad (10%)",
|
||||||
|
"factorSimplicityDesc": "basado en AHT",
|
||||||
|
"axisYEconomicImpact": "Eje Y: IMPACTO ECONÓMICO (€/año)",
|
||||||
|
"axisYEconomicImpactDesc": "Ahorro TCO calculado según tier con CPI diferencial:",
|
||||||
|
"cpiHuman": "CPI Humano",
|
||||||
|
"cpiBot": "CPI Bot",
|
||||||
|
"cpiAssist": "CPI Assist",
|
||||||
|
"cpiAugment": "CPI Augment",
|
||||||
|
"savingsFormulas": "Fórmulas de Ahorro por Tier",
|
||||||
|
"formulaAutomate": "AUTOMATE (Score ≥ 7.5)",
|
||||||
|
"formulaAutomateCalc": "Ahorro = Vol × 12 × 70% × (€2.33 - €0.15)",
|
||||||
|
"formulaAutomateResult": "= Vol × 12 × 0.70 × €2.18",
|
||||||
|
"formulaAssist": "ASSIST (Score ≥ 5.5)",
|
||||||
|
"formulaAssistCalc": "Ahorro = Vol × 12 × 30% × (€2.33 - €1.50)",
|
||||||
|
"formulaAssistResult": "= Vol × 12 × 0.30 × €0.83",
|
||||||
|
"formulaAugment": "AUGMENT (Score ≥ 3.5)",
|
||||||
|
"formulaAugmentCalc": "Ahorro = Vol × 12 × 15% × (€2.33 - €2.00)",
|
||||||
|
"formulaAugmentResult": "= Vol × 12 × 0.15 × €0.33",
|
||||||
|
"formulaHumanOnly": "HUMAN-ONLY (Score < 3.5 o Red Flags)",
|
||||||
|
"formulaHumanOnlyCalc": "Ahorro = €0",
|
||||||
|
"formulaHumanOnlyRequires": "Requiere estandarización previa",
|
||||||
|
"tierClassificationCriteria": "Criterios de Clasificación de Tier",
|
||||||
|
"tierCriteriaCV": "CV",
|
||||||
|
"tierCriteriaTransfer": "Transfer",
|
||||||
|
"tierCriteriaFCR": "FCR",
|
||||||
|
"tierCriteriaNoRedFlags": "Sin red flags",
|
||||||
|
"tierCriteriaRequiresOptimization": "Requiere optimización",
|
||||||
|
"tierCriteriaOr": "o",
|
||||||
|
"tierCriteriaVolume": "Vol",
|
||||||
|
"tierCriteriaValid": "Valid",
|
||||||
|
"methodologicalNote": "Nota:",
|
||||||
|
"methodologicalNoteText": "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.",
|
||||||
|
"hideDetails": "Ocultar detalles",
|
||||||
|
"viewInitiatives": "Ver iniciativas y criterios",
|
||||||
|
"conditional": "Condicional",
|
||||||
|
"risk": "Riesgo",
|
||||||
|
"riskLow": "Bajo",
|
||||||
|
"riskMedium": "Medio",
|
||||||
|
"riskHigh": "Alto",
|
||||||
|
"whyNecessary": "¿Por qué es necesario?",
|
||||||
|
"targetSkills": "Skills objetivo",
|
||||||
|
"initiatives": "Iniciativas",
|
||||||
|
"setup": "Setup",
|
||||||
|
"recurring": "Recurrente",
|
||||||
|
"kpiTarget": "KPI Objetivo",
|
||||||
|
"successCriteria": "Criterios de éxito",
|
||||||
|
"riskDescription": "Descripción del riesgo",
|
||||||
|
"provider": "Proveedor",
|
||||||
|
"entryCriteria": "Criterios de entrada",
|
||||||
|
"fromTier": "Desde Tier:",
|
||||||
|
"scoreRange": "Rango Score:",
|
||||||
|
"requiredMetrics": "Métricas requeridas:",
|
||||||
|
"exitCriteria": "Criterios de salida",
|
||||||
|
"toTier": "Hacia Tier:",
|
||||||
|
"scoreTarget": "Score objetivo:",
|
||||||
|
"kpiTargets": "KPIs objetivo:",
|
||||||
|
"priorityQueues": "Colas prioritarias",
|
||||||
|
"currentScore": "Score actual",
|
||||||
|
"currentTier": "Tier actual",
|
||||||
|
"potentialSavings": "Ahorro potencial",
|
||||||
|
"redFlags": "Red Flags",
|
||||||
|
"classificationByAutomationTier": "Clasificación por Potencial de Automatización",
|
||||||
|
"queuesClassifiedDescription": "{{count}} colas clasificadas en 4 Tiers según su preparación para IA • {{volume}} interacciones/mes",
|
||||||
|
"tier1": "TIER 1",
|
||||||
|
"tier2": "TIER 2",
|
||||||
|
"tier3": "TIER 3",
|
||||||
|
"tier4": "TIER 4",
|
||||||
|
"intPerMonth": "int/mes",
|
||||||
|
"volumePercentage": "({{pct}}% volumen)",
|
||||||
|
"noSavingsRedFlags": "€0/año (Red flags)",
|
||||||
|
"volumeDistributionByTier": "Distribución del volumen por tier:",
|
||||||
|
"strategicRecommendation": "Nuestra Recomendación:",
|
||||||
|
"dualStrategyLabel": "Nuestra Recomendación: Estrategia Dual",
|
||||||
|
"dualStrategySublabel": "Ejecutar dos líneas de trabajo en paralelo para maximizar el impacto",
|
||||||
|
"foundationFirstLabel": "Nuestra Recomendación: Foundation First",
|
||||||
|
"foundationFirstSublabel": "Preparar la operación antes de automatizar",
|
||||||
|
"standardizationLabel": "Nuestra Recomendación: Estandarización",
|
||||||
|
"standardizationSublabel": "Resolver problemas operativos críticos antes de invertir en IA",
|
||||||
|
"whatIsFoundation": "¿Qué significa Foundation?",
|
||||||
|
"foundationExplanation": "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.",
|
||||||
|
"assistQueuesCanElevate": "{{count}} colas ASSIST ({{pct}}% del volumen) pueden elevarse a Tier AUTOMATE tras completar Wave 1-2.",
|
||||||
|
"investment": "Inversión",
|
||||||
|
"timeline": "Timeline",
|
||||||
|
"enabledSavings": "Ahorro habilitado",
|
||||||
|
"criteriaForAutomation": "Criterios para pasar a automatización:",
|
||||||
|
"criteriaForAutomationValues": "CV ≤90% · Transfer ≤30% · AHT -15%",
|
||||||
|
"whyStandardizationFirst": "¿Por qué estandarización primero?",
|
||||||
|
"standardizationExplanation": "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.",
|
||||||
|
"volumeWithRedFlags": "{{pct}}% del volumen presenta red flags (CV >75%, Transfer >20%). Wave 1 es una inversión habilitadora sin retorno directo inmediato.",
|
||||||
|
"investmentWave1": "Inversión Wave 1",
|
||||||
|
"directSavings": "Ahorro directo",
|
||||||
|
"enablingNoDirectSavings": "€0 (habilitador)",
|
||||||
|
"objective": "Objetivo:",
|
||||||
|
"objectiveReduceRedFlags": "Reducir red flags en las {{count}} colas principales. Reevaluar tras completar.",
|
||||||
|
"nextRecommendedStep": "Siguiente paso recomendado:",
|
||||||
|
"nextStepDual": "Iniciar piloto de automatización con las {{count}} colas AUTOMATE, mientras se ejecuta Wave 1 (Foundation) en paralelo para preparar el resto.",
|
||||||
|
"nextStepFoundation": "Comenzar Wave 1 focalizando en las {{count}} colas de mayor volumen. Medir progreso mensual en CV y Transfer.",
|
||||||
|
"nextStepStandardization": "Realizar workshop de diagnóstico operacional para identificar las causas raíz de los red flags antes de planificar inversiones.",
|
||||||
|
"waveDetail": "Detalle por Wave",
|
||||||
|
"waveDetailDescription": "Iniciativas, criterios de entrada/salida, inversión por fase",
|
||||||
|
"hideDetail": "Ocultar detalle",
|
||||||
|
"viewDetail": "Ver detalle",
|
||||||
|
"collapseAll": "Colapsar todas",
|
||||||
|
"expandAll": "Expandir todas"
|
||||||
},
|
},
|
||||||
"opportunities": {
|
"opportunities": {
|
||||||
"viewCriticalActions": "Ver Acciones Críticas",
|
"viewCriticalActions": "Ver Acciones Críticas",
|
||||||
@@ -393,6 +636,184 @@
|
|||||||
"augmentDesc": "Requieren optimización previa: estandarizar procesos, reducir variabilidad (Score 3.5-5.5)",
|
"augmentDesc": "Requieren optimización previa: estandarizar procesos, reducir variabilidad (Score 3.5-5.5)",
|
||||||
"humanOnly": "Colas HUMAN-ONLY",
|
"humanOnly": "Colas HUMAN-ONLY",
|
||||||
"humanOnlyDesc": "No aptas para automatización: volumen insuficiente, datos de baja calidad o complejidad extrema"
|
"humanOnlyDesc": "No aptas para automatización: volumen insuficiente, datos de baja calidad o complejidad extrema"
|
||||||
|
},
|
||||||
|
"tiers": {
|
||||||
|
"automate": "Automatizar",
|
||||||
|
"assist": "Asistir",
|
||||||
|
"optimize": "Optimizar",
|
||||||
|
"human": "Humano"
|
||||||
|
},
|
||||||
|
"tierLabels": {
|
||||||
|
"automateFull": "Full IA",
|
||||||
|
"assistCopilot": "Copilot",
|
||||||
|
"augmentTools": "Tools",
|
||||||
|
"humanManual": "Manual"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"high": "Alto",
|
||||||
|
"medium": "Medio",
|
||||||
|
"low": "Bajo",
|
||||||
|
"critical": "Crítico",
|
||||||
|
"readyForAutomation": "Listo para automatización",
|
||||||
|
"moderatePotential": "Potencial moderado",
|
||||||
|
"requiresOptimization": "Requiere optimización",
|
||||||
|
"notReady": "No preparado"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"queues": "colas",
|
||||||
|
"queue": "cola",
|
||||||
|
"queueId": "Cola (ID)",
|
||||||
|
"queueOriginalId": "Cola (original_queue_id)",
|
||||||
|
"skill": "Skill",
|
||||||
|
"businessUnit": "Business Unit (Skill)",
|
||||||
|
"strategicSkill": "Queue Skill (Estratégico)",
|
||||||
|
"volume": "Volumen",
|
||||||
|
"volumePerMonth": "int/mes",
|
||||||
|
"ahtAvg": "AHT Prom.",
|
||||||
|
"cvAvg": "CV Prom.",
|
||||||
|
"savingsPotential": "Ahorro Potencial",
|
||||||
|
"dominantTier": "Tier Dom.",
|
||||||
|
"transfer": "Transfer",
|
||||||
|
"redFlags": "Red Flags",
|
||||||
|
"savingsPerMonth": "Ahorro/mes",
|
||||||
|
"cost": "Coste:",
|
||||||
|
"savings": "Ahorro:",
|
||||||
|
"total": "TOTAL",
|
||||||
|
"clickToExpand": "Click en un skill para ver el detalle de colas individuales",
|
||||||
|
"clickToExpandReason": "Click en una razón para ver las colas afectadas. Priorizar acciones según volumen impactado.",
|
||||||
|
"showing": "Mostrando {{shown}} de {{total}} colas",
|
||||||
|
"reason": "Razón / Red Flag",
|
||||||
|
"recommendedAction": "Acción Recomendada",
|
||||||
|
"score": "Score",
|
||||||
|
"int": "int",
|
||||||
|
"perYear": "/año",
|
||||||
|
"perMonth": "/mes"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"volumeAutomatable": "Volumen Automatizable",
|
||||||
|
"tierAutoAssist": "(Tier AUTOMATE + ASSIST)",
|
||||||
|
"interactions": "interacciones",
|
||||||
|
"queuesAnalyzed": "colas analizadas",
|
||||||
|
"interpretation": "Interpretación:",
|
||||||
|
"interpretationText": "El {{pct}}% representa el volumen de interacciones automatizables (AUTOMATE + ASSIST). Solo el {{queuePct}}% de las colas ({{count}} de {{total}}) son AUTOMATE, pero concentran {{volumePct}}% del volumen total. Esto indica pocas colas de alto volumen automatizables - oportunidad concentrada en Quick Wins de alto impacto.",
|
||||||
|
"inSkills": "en {{count}} skills",
|
||||||
|
"groupedBy": "agrupadas por {{count}} razones",
|
||||||
|
"requiresIntervention": "Estas colas requieren intervención antes de considerar automatización"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"tier": "Tier:",
|
||||||
|
"all": "Todos",
|
||||||
|
"minSavings": "Ahorro mín:",
|
||||||
|
"minVolume": "Volumen mín:",
|
||||||
|
"activeFilters": "Filtros activos:",
|
||||||
|
"of": "de"
|
||||||
|
},
|
||||||
|
"opportunityMap": {
|
||||||
|
"title": "Mapa de Oportunidades",
|
||||||
|
"subtitle": "Tamaño = Volumen · Color = Tier · Posición = Score vs Ahorro TCO",
|
||||||
|
"quickWins": "QUICK WINS",
|
||||||
|
"highPotential": "ALTO POTENCIAL",
|
||||||
|
"develop": "DESARROLLAR",
|
||||||
|
"easyImpl": "FÁCIL IMPL.",
|
||||||
|
"backlog": "BACKLOG",
|
||||||
|
"colorTier": "COLOR = TIER",
|
||||||
|
"sizeVolume": "TAMAÑO = VOLUMEN",
|
||||||
|
"visibleSavings": "AHORRO VISIBLE",
|
||||||
|
"agenticScore": "Agentic Score",
|
||||||
|
"annualTcoSavings": "Ahorro TCO Anual",
|
||||||
|
"noQueuesMatch": "No hay colas que cumplan los filtros seleccionados",
|
||||||
|
"clickForDetail": "Click para ver detalle",
|
||||||
|
"quickWinCandidate": "Candidato a Quick Win",
|
||||||
|
"highPotentialCopilot": "Alto Potencial con Copilot",
|
||||||
|
"requiresStandardization": "Requiere estandarización previa",
|
||||||
|
"matureProcesses": "Score ≥7.5 indica procesos maduros listos para automatización completa.",
|
||||||
|
"benefitsAI": "Score 5.5-7.5 se beneficia de asistencia IA (Copilot) para elevar a Tier 1.",
|
||||||
|
"needsWork": "Score <5.5 requiere trabajo previo de estandarización antes de automatizar."
|
||||||
|
},
|
||||||
|
"classification": {
|
||||||
|
"title": "CLASIFICACIÓN POR SKILL",
|
||||||
|
"titleByTier": "CLASIFICACIÓN POR TIER DE AUTOMATIZACIÓN",
|
||||||
|
"subtitle": "Skills con colas clasificadas como AUTOMATE (score ≥ 7.5, CV ≤ 75%, transfer ≤ 20%)",
|
||||||
|
"distribution": "Distribución Colas por Tier",
|
||||||
|
"action": "Acción",
|
||||||
|
"auto": "AUTO",
|
||||||
|
"assist": "ASIST",
|
||||||
|
"augm": "AUGM",
|
||||||
|
"human": "HUMAN",
|
||||||
|
"waveBot": "→ Wave 4: Bot Full",
|
||||||
|
"waveCopilot": "→ Wave 3: Copilot",
|
||||||
|
"waveTools": "→ Wave 2: Tools",
|
||||||
|
"waveFoundation": "→ Wave 1: Foundation",
|
||||||
|
"quickWins": "Quick Wins:",
|
||||||
|
"attention": "Atención:",
|
||||||
|
"volumeT1T2": "Vol en T1+T2:",
|
||||||
|
"volumeT4": "Vol en T4:",
|
||||||
|
"prioritizeWave1": "→ priorizar en Wave 1",
|
||||||
|
"balancedDistribution": "Distribución equilibrada entre tiers. Revisar colas individuales para priorización.",
|
||||||
|
"hasT1T2Volume": "tienen >60% volumen en T1+T2",
|
||||||
|
"hasHumanVolume": "tiene {{pct}}% en HUMAN",
|
||||||
|
"analysisPerSkill": "Análisis por Skill",
|
||||||
|
"skillsHaveAutomate": "de {{total}} skills tienen al menos una cola tier AUTOMATE",
|
||||||
|
"seeIndividualQueues": "Haz clic en un skill para ver las colas individuales con desglose de score"
|
||||||
|
},
|
||||||
|
"globalFactors": {
|
||||||
|
"title": "Factores del Score (Nivel Operación Global)",
|
||||||
|
"note": "NOTA:",
|
||||||
|
"noteText": "Estos factores son promedios globales. El scoring por cola usa estos mismos factores calculados individualmente para cada cola.",
|
||||||
|
"factor": "Factor",
|
||||||
|
"weight": "Peso",
|
||||||
|
"realMetric": "Métrica Real",
|
||||||
|
"status": "Status",
|
||||||
|
"globalScore": "SCORE GLOBAL",
|
||||||
|
"insight": "El score global ({{score}}) refleja la operación completa. Sin embargo, {{pct}}% del volumen está en colas individuales que SÍ cumplen criterios de automatización."
|
||||||
|
},
|
||||||
|
"nextSteps": {
|
||||||
|
"title": "PRÓXIMOS PASOS → ROADMAP",
|
||||||
|
"basedOnAnalysis": "BASADO EN ESTE ANÁLISIS:",
|
||||||
|
"immediateQuickWins": "QUICK WINS INMEDIATOS (sin Wave 1)",
|
||||||
|
"queuesAutomate": "colas AUTOMATE",
|
||||||
|
"interactionsPerMonth": "interacciones/mes",
|
||||||
|
"potentialSavings": "Ahorro potencial:",
|
||||||
|
"containment": "contención",
|
||||||
|
"skills": "Skills:",
|
||||||
|
"alignedWave4": "→ Alineado con Wave 4 del Roadmap. Pueden implementarse en paralelo a Wave 1.",
|
||||||
|
"waveFoundation": "WAVE 1-3: FOUNDATION → ASSIST ({{count}} colas)",
|
||||||
|
"tierAssist": "en tier ASSIST",
|
||||||
|
"focusWave1": "Foco Wave 1:",
|
||||||
|
"reduceTransfer": "Reducir transfer en",
|
||||||
|
"potentialCopilot": "Potencial con Copilot:",
|
||||||
|
"deflection": "deflection",
|
||||||
|
"requiresWave1": "→ Requiere Wave 1 (Foundation) para habilitar Copilot en Wave 3",
|
||||||
|
"seeRoadmap": "Ver pestaña Roadmap para plan detallado",
|
||||||
|
"perInt": "/int"
|
||||||
|
},
|
||||||
|
"humanOnlyReasons": {
|
||||||
|
"title": "Colas HUMAN-ONLY",
|
||||||
|
"subtitle": "No aptas para automatización: volumen insuficiente, datos de baja calidad o complejidad extrema",
|
||||||
|
"volumeTotal": "Volumen total:"
|
||||||
|
},
|
||||||
|
"redFlagsActions": {
|
||||||
|
"noSpecificFlags": "Sin Red Flags específicos",
|
||||||
|
"noFlagsDesc": "Colas que no cumplen criterios de automatización",
|
||||||
|
"reviewManually": "Revisar manualmente",
|
||||||
|
"standardizeProcesses": "Estandarizar procesos y scripts",
|
||||||
|
"simplifyFlow": "Simplificar flujo, capacitar agentes",
|
||||||
|
"consolidate": "Consolidar con colas similares",
|
||||||
|
"improveDataCapture": "Mejorar captura de datos"
|
||||||
|
},
|
||||||
|
"factorsExtended": {
|
||||||
|
"volumeMethodology": "Score = log10(Volumen) normalizado. >5000 → 10, <100 → 2",
|
||||||
|
"volumeBenchmark": "ROI positivo requiere >500/mes",
|
||||||
|
"volumeGood": "Alto volumen justifica inversión",
|
||||||
|
"volumeBad": "Considerar soluciones compartidas",
|
||||||
|
"roiPotential": "ROI Potencial",
|
||||||
|
"roiDesc": "Retorno económico esperado",
|
||||||
|
"roiMethodology": "Score basado en coste anual total. >€500K → 10",
|
||||||
|
"roiBenchmark": "ROI >150% a 12 meses",
|
||||||
|
"roiGood": "Caso de negocio sólido",
|
||||||
|
"roiBad": "ROI marginal, evaluar otros beneficios",
|
||||||
|
"resolution": "Resolutividad",
|
||||||
|
"dataQuality": "Calidad Datos"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"economicModel": {
|
"economicModel": {
|
||||||
@@ -434,6 +855,10 @@
|
|||||||
"monthsHistory": "Meses de histórico",
|
"monthsHistory": "Meses de histórico",
|
||||||
"sourceSystem": "Sistema origen",
|
"sourceSystem": "Sistema origen",
|
||||||
"periodRange": "Periodo: {{period}}",
|
"periodRange": "Periodo: {{period}}",
|
||||||
|
"defaultPeriod": "Enero - Diciembre 2025",
|
||||||
|
"sourceGenesys": "Genesys Cloud CX",
|
||||||
|
"sourceDataset": "Dataset cargado",
|
||||||
|
"pdfDevelopment": "Funcionalidad de descarga PDF en desarrollo. El documento estará disponible próximamente.",
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"title": "Pipeline de Transformación",
|
"title": "Pipeline de Transformación",
|
||||||
"description": "Arquitectura modular de 3 capas para garantizar trazabilidad y escalabilidad.",
|
"description": "Arquitectura modular de 3 capas para garantizar trazabilidad y escalabilidad.",
|
||||||
@@ -492,6 +917,21 @@
|
|||||||
"howCalculate": "¿Cómo se calcula el Coste Total?",
|
"howCalculate": "¿Cómo se calcula el Coste Total?",
|
||||||
"costEquals": "Coste ="
|
"costEquals": "Coste ="
|
||||||
},
|
},
|
||||||
|
"cpi": {
|
||||||
|
"description": "El CPI se calcula dividiendo el <strong>coste total</strong> entre el <strong>volumen de interacciones</strong>. El coste total incluye <em>todas</em> las interacciones (noise, zombie y válidas) porque todas se facturan, y aplica un factor de productividad del {{productivity}}%.",
|
||||||
|
"volume": "Volumen",
|
||||||
|
"ahtExplanation": "El <strong>AHT</strong> está en segundos, se convierte a horas dividiendo por 3600. Incluye todas las interacciones que generan coste (noise + zombie + válidas). Solo se excluyen los abandonos porque no consumen tiempo de agente.",
|
||||||
|
"hourlyRate": "Coste por Hora del Agente (Fully Loaded)",
|
||||||
|
"configuredValue": "Valor introducido: €{{value}}/h",
|
||||||
|
"includesAllCosts": "Este valor fue configurado en la pantalla de entrada de datos y debe incluir todos los costes asociados al agente:",
|
||||||
|
"cost1": "Salario bruto del agente",
|
||||||
|
"cost2": "Costes de seguridad social",
|
||||||
|
"cost3": "Licencias de software",
|
||||||
|
"cost4": "Infraestructura y puesto",
|
||||||
|
"cost5": "Supervisión y QA",
|
||||||
|
"cost6": "Formación y overhead",
|
||||||
|
"adjustNote": "Si necesita ajustar este valor, puede volver a la pantalla de entrada de datos y modificarlo."
|
||||||
|
},
|
||||||
"impact": {
|
"impact": {
|
||||||
"title": "Impacto de la Transformación",
|
"title": "Impacto de la Transformación",
|
||||||
"metric": "Métrica",
|
"metric": "Métrica",
|
||||||
@@ -505,12 +945,18 @@
|
|||||||
"revealsDemand": "Revela demanda fallida oculta",
|
"revealsDemand": "Revela demanda fallida oculta",
|
||||||
"detectsFrustration": "Detecta frustración cliente real",
|
"detectsFrustration": "Detecta frustración cliente real",
|
||||||
"executiveVision": "Visión ejecutiva accionable",
|
"executiveVision": "Visión ejecutiva accionable",
|
||||||
"reflectsPerformance": "KPIs reflejan desempeño real"
|
"reflectsPerformance": "KPIs reflejan desempeño real",
|
||||||
|
"technicalSkills": "{{count}} técnicos",
|
||||||
|
"businessLines": "{{count}} líneas negocio",
|
||||||
|
"distorted": "Distorsionado",
|
||||||
|
"clean": "Limpio",
|
||||||
|
"withoutTransformation": "Sin esta transformación,",
|
||||||
|
"wrongInvestments": "las decisiones de automatización se basarían en datos incorrectos, generando inversiones en los procesos equivocados."
|
||||||
},
|
},
|
||||||
"skillMapping": {
|
"skillMapping": {
|
||||||
"title": "Mapeo de Skills a Líneas de Negocio",
|
"title": "Mapeo de Skills a Líneas de Negocio",
|
||||||
"simplificationApplied": "Simplificación aplicada",
|
"simplificationApplied": "Simplificación aplicada",
|
||||||
"reductionDesc": "Se redujo la complejidad de 980 skills técnicos a {{count}} Líneas de Negocio accionables mediante lógica fuzzy de palabras clave.",
|
"reductionDesc": "Se redujo la complejidad de 980 skills técnicos a {{count}} Líneas de Negocio. Esta simplificación es vital para la visualización ejecutiva y la toma de decisiones estratégicas.",
|
||||||
"businessLine": "Línea de Negocio",
|
"businessLine": "Línea de Negocio",
|
||||||
"keywords": "Keywords Detectadas (Lógica Fuzzy)",
|
"keywords": "Keywords Detectadas (Lógica Fuzzy)",
|
||||||
"baggage": "Baggage & Handling",
|
"baggage": "Baggage & Handling",
|
||||||
@@ -520,7 +966,8 @@
|
|||||||
"changes": "Changes & Post-Sales",
|
"changes": "Changes & Post-Sales",
|
||||||
"digital": "Digital Support",
|
"digital": "Digital Support",
|
||||||
"customer": "Customer Service",
|
"customer": "Customer Service",
|
||||||
"internal": "Internal / Backoffice"
|
"internal": "Internal / Backoffice",
|
||||||
|
"fuzzyLogicNote": "El mapeo utiliza lógica fuzzy para clasificar automáticamente cada skill técnico según las keywords detectadas en su nombre. Los skills no clasificados se asignan a \"Customer Service\"."
|
||||||
},
|
},
|
||||||
"quality": {
|
"quality": {
|
||||||
"title": "Garantías de Calidad",
|
"title": "Garantías de Calidad",
|
||||||
@@ -547,5 +994,324 @@
|
|||||||
"november": "Noviembre",
|
"november": "Noviembre",
|
||||||
"december": "Diciembre"
|
"december": "Diciembre"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"law10": {
|
||||||
|
"constants": {
|
||||||
|
"requirements": {
|
||||||
|
"LAW_07": {
|
||||||
|
"name": "Cobertura Horaria"
|
||||||
|
},
|
||||||
|
"LAW_01": {
|
||||||
|
"name": "Velocidad de Respuesta"
|
||||||
|
},
|
||||||
|
"LAW_02": {
|
||||||
|
"name": "Calidad de Resolucion"
|
||||||
|
},
|
||||||
|
"LAW_09": {
|
||||||
|
"name": "Cobertura Linguistica"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"CUMPLE": "Cumple",
|
||||||
|
"PARCIAL": "Parcial",
|
||||||
|
"NO_CUMPLE": "No Cumple",
|
||||||
|
"SIN_DATOS": "Sin Datos"
|
||||||
|
},
|
||||||
|
"compliance": {
|
||||||
|
"law07": {
|
||||||
|
"noData": "Sin datos de distribucion horaria",
|
||||||
|
"noDataDetails": "No se encontraron datos de distribucion horaria en el analisis",
|
||||||
|
"offHoursPercent": "{{percent}}% de interacciones fuera de horario laboral",
|
||||||
|
"adequateCoverage": "Cobertura horaria adecuada",
|
||||||
|
"improvableCoverage": "Cobertura horaria mejorable - considerar ampliar horarios",
|
||||||
|
"insufficientCoverage": "Cobertura horaria insuficiente - requiere accion inmediata",
|
||||||
|
"gapOverOptimal": "{{gap}}pp sobre optimo",
|
||||||
|
"gapOverLimit": "{{gap}}pp sobre limite"
|
||||||
|
},
|
||||||
|
"law01": {
|
||||||
|
"noData": "Sin datos de tiempos de espera",
|
||||||
|
"noDataDetails": "No se encontraron datos de hold_time en el analisis",
|
||||||
|
"avgHoldTime": "Tiempo de espera promedio: {{time}}s (limite: 180s)",
|
||||||
|
"withinLimit": "{{percent}}% de interacciones dentro del limite",
|
||||||
|
"queuesExceedLimit": "{{count}} de {{total}} colas exceden el limite",
|
||||||
|
"gapNegative": "-{{gap}}s",
|
||||||
|
"queuesOutside": "{{count}} colas fuera",
|
||||||
|
"gapPositive": "+{{gap}}s"
|
||||||
|
},
|
||||||
|
"law02": {
|
||||||
|
"noData": "Sin datos de resolucion",
|
||||||
|
"noDataDetails": "No se encontraron datos de FCR o transferencias",
|
||||||
|
"fcrTechnical": "FCR Tecnico: {{fcr}}% (objetivo: >75%)",
|
||||||
|
"transferRate": "Tasa de transferencia: {{rate}}% (objetivo: <15%)",
|
||||||
|
"highTransferQueues": "{{count}} colas con transfer >25%",
|
||||||
|
"gapDash": "-",
|
||||||
|
"gapFcr": "FCR {{fcrGap}}, Transfer {{transferGap}}",
|
||||||
|
"gapOk": "OK",
|
||||||
|
"gapPositive": "+{{gap}}pp",
|
||||||
|
"gapNegative": "-{{gap}}pp"
|
||||||
|
},
|
||||||
|
"law09": {
|
||||||
|
"noData": "Requiere datos",
|
||||||
|
"noLanguageData": "No se dispone de datos de idioma en las interacciones",
|
||||||
|
"needsLanguageField": "Para evaluar este requisito se necesita el campo \"language\" en el CSV"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"aboutThisAnalysis": "Sobre este Analisis",
|
||||||
|
"lawTitle": "Ley 10/2025 de Atencion al Cliente",
|
||||||
|
"description": "Este modulo conecta tus <strong>metricas operacionales actuales</strong> con los requisitos de la Ley 10/2025. No mide compliance directamente (requeriria datos adicionales), pero <strong>SI identifica patrones</strong> que impactan en tu capacidad de cumplir con la normativa.",
|
||||||
|
"complianceDeadline": "Deadline de cumplimiento",
|
||||||
|
"december282026": "28 Diciembre 2026",
|
||||||
|
"daysRemaining": "{{days}} dias restantes",
|
||||||
|
"requirementsEvaluated": "Requisitos evaluados",
|
||||||
|
"requirementsMet": "{{met}} de {{total}} cumplen",
|
||||||
|
"basedOnData": "Basado en datos disponibles",
|
||||||
|
"overallStatus": "Estado general",
|
||||||
|
"goodState": "Buen estado",
|
||||||
|
"requiresAttention": "Requiere atencion",
|
||||||
|
"urgentAction": "Accion urgente"
|
||||||
|
},
|
||||||
|
"timeCoverage": {
|
||||||
|
"title": "Cobertura Temporal: Disponibilidad del Servicio",
|
||||||
|
"article": "Relacionado con Art. 14 - Servicios basicos 24/7",
|
||||||
|
"whatWeKnow": "LO QUE SABEMOS",
|
||||||
|
"heatmap247": "HEATMAP VOLUMETRICO 24x7",
|
||||||
|
"intensity": "Intensidad:",
|
||||||
|
"intensityLow": "Bajo",
|
||||||
|
"intensityMedium": "Medio",
|
||||||
|
"intensityHigh": "Alto",
|
||||||
|
"operationalFindings": "Hallazgos operacionales:",
|
||||||
|
"detectedSchedule": "Horario detectado: <strong>L-V 08:00-22:00</strong>, S-D horario reducido",
|
||||||
|
"nightVolume": "Volumen nocturno (22:00-08:00): <strong>{{volume}}</strong> interacciones ({{percent}}%)",
|
||||||
|
"earlyMorningVolume": "Volumen madrugada (00:00-06:00): <strong>{{volume}}</strong> interacciones ({{percent}}%)",
|
||||||
|
"peakHour": "Pico maximo: <strong>{{hour}}:00-{{hourEnd}}:00</strong> ({{percent}}% del volumen diario)",
|
||||||
|
"lawImplication": "IMPLICACION LEY 10/2025",
|
||||||
|
"basicServiceRequirement": "Transporte aereo = Servicio basico",
|
||||||
|
"article14Requirement": "→ Art. 14 requiere atencion 24/7 para incidencias",
|
||||||
|
"gapIdentified": "Gap identificado:",
|
||||||
|
"clientsOutsideHours": "<strong>{{percent}}%</strong> de tus clientes contactan fuera del horario actual",
|
||||||
|
"complianceIssue": "Si estas son incidencias (equipaje perdido, cambios urgentes), <strong>NO cumples Art. 14</strong>",
|
||||||
|
"suggestedAction": "ACCION SUGERIDA",
|
||||||
|
"classifyNightVolume": "1. Clasificar volumen nocturno por tipo:",
|
||||||
|
"criticalIncidents": "¿Que % son incidencias criticas? → Requiere 24/7",
|
||||||
|
"generalQueries": "¿Que % son consultas generales? → Pueden esperar",
|
||||||
|
"coverageOptions": "2. Opciones de cobertura:",
|
||||||
|
"optionAChatbot": "A) Chatbot IA + agente on-call",
|
||||||
|
"optionBExternal": "B) Redirigir a call center 24/7 externo",
|
||||||
|
"optionCNight": "C) Agentes nocturnos (3 turnos)",
|
||||||
|
"costPerYear": "~{{cost}}/año"
|
||||||
|
},
|
||||||
|
"responseSpeed": {
|
||||||
|
"title": "Velocidad de Atencion: Eficiencia Operativa",
|
||||||
|
"article": "Relacionado con Art. 8.2 - 95% llamadas <3min",
|
||||||
|
"whatWeKnow": "LO QUE SABEMOS",
|
||||||
|
"abandonmentRate": "Tasa abandono",
|
||||||
|
"ahtP50": "AHT P50 ({{min}}m {{sec}}s)",
|
||||||
|
"ahtP90": "AHT P90 ({{min}}m {{sec}}s)",
|
||||||
|
"ratioP90P50": "Ratio P90/P50 {{elevated}}",
|
||||||
|
"elevated": "(elevado)",
|
||||||
|
"abandonmentByHour": "DISTRIBUCION DE ABANDONOS POR HORA",
|
||||||
|
"abandonmentLegend": "Abandono:",
|
||||||
|
"abandonmentLow": "<8%",
|
||||||
|
"abandonmentMedium": "8-15%",
|
||||||
|
"abandonmentHigh": ">20%",
|
||||||
|
"patternsObserved": "Patrones observados:",
|
||||||
|
"maxAbandonment": "Mayor abandono: <strong>{{hourStart}}:00-{{hourEnd}}:00</strong> ({{rate}}% vs {{avg}}% media)",
|
||||||
|
"highestAht": "AHT mas alto: <strong>Lunes 09:00-11:00</strong> ({{high}}s vs {{p50}}s P50)",
|
||||||
|
"minAbandonment": "Menor abandono: <strong>{{hourStart}}:00-{{hourEnd}}:00</strong> ({{rate}}%)",
|
||||||
|
"lawImplication": "IMPLICACION LEY 10/2025",
|
||||||
|
"article82Requirement": "Art. 8.2 requiere: \"95% de llamadas atendidas en <3 minutos\"",
|
||||||
|
"dataLimitation": "LIMITACION DE DATOS",
|
||||||
|
"noAsaData": "Tu CDR actual NO incluye ASA (tiempo en cola antes de responder), por lo que NO podemos medir este requisito directamente.",
|
||||||
|
"butWeKnow": "PERO SI sabemos:",
|
||||||
|
"customersAbandon": "<strong>{{rate}}%</strong> de clientes abandonan → Probablemente esperaron mucho",
|
||||||
|
"highVariability": "Alta variabilidad AHT (P90/P50={{ratio}}) → Cola impredecible",
|
||||||
|
"peaksCoincide": "Picos de abandono coinciden con picos de volumen",
|
||||||
|
"conservativeEstimate": "Estimacion conservadora (±10% margen error):",
|
||||||
|
"likelyFastResponse": "→ ~<strong>{{percent}}%</strong> de llamadas probablemente atendidas \"rapido\"",
|
||||||
|
"gapVs95": "→ Gap vs 95% requerido: <strong>{{operator}}{{gap}}</strong> puntos porcentuales",
|
||||||
|
"suggestedAction": "ACCION SUGERIDA",
|
||||||
|
"shortTerm": "1. CORTO PLAZO: Reducir AHT para aumentar capacidad",
|
||||||
|
"dimension2Identifies": "Tu Dimension 2 (Eficiencia) ya identifica:",
|
||||||
|
"highAht": "- AHT elevado ({{aht}}s vs 380s benchmark)",
|
||||||
|
"copilotOpportunity": "- Oportunidad Copilot IA: -18% AHT proyectado",
|
||||||
|
"dualBenefit": "Beneficio dual: ↓ AHT = ↑ capacidad = ↓ cola = ↑ ASA",
|
||||||
|
"mediumTerm": "2. MEDIO PLAZO: Implementar tracking ASA real",
|
||||||
|
"platformConfig": "Configuracion en plataforma",
|
||||||
|
"implementationTimeline": "Timeline implementacion",
|
||||||
|
"implementationWeeks": "4-6 semanas",
|
||||||
|
"benefit": "Beneficio: Medicion precisa para auditoria ENAC"
|
||||||
|
},
|
||||||
|
"resolutionQuality": {
|
||||||
|
"title": "Calidad de Resolucion: Efectividad",
|
||||||
|
"article": "Relacionado con Art. 17 - Resolucion en 15 dias",
|
||||||
|
"whatWeKnow": "LO QUE SABEMOS",
|
||||||
|
"fcrReal": "FCR Real (fcr_real_flag)",
|
||||||
|
"recontactRate7d": "Tasa recontacto 7 dias",
|
||||||
|
"repeatCalls": "Llamadas repetidas",
|
||||||
|
"fcrBySkill": "FCR POR SKILL/QUEUE",
|
||||||
|
"fcrLegend": "FCR:",
|
||||||
|
"fcrLow": "<45%",
|
||||||
|
"fcrMedium": "45-65%",
|
||||||
|
"fcrHigh": ">75%",
|
||||||
|
"topLowFcr": "Top skills con FCR bajo:",
|
||||||
|
"fcrValue": "{{fcr}}% FCR",
|
||||||
|
"lawImplication": "IMPLICACION LEY 10/2025",
|
||||||
|
"article17Requirement": "Art. 17 requiere: \"Resolucion de reclamaciones ≤15 dias\"",
|
||||||
|
"dataLimitation": "LIMITACION DE DATOS",
|
||||||
|
"noCaseTracking": "Tu CDR solo registra interacciones individuales, NO casos multi-touch ni tiempo total de resolucion.",
|
||||||
|
"butWeKnow": "PERO SI sabemos:",
|
||||||
|
"multipleContactsRequired": "<strong>{{percent}}%</strong> de casos requieren multiples contactos",
|
||||||
|
"fcrGap": "FCR {{fcr}}% = {{gap}}% NO resuelto en primera interaccion",
|
||||||
|
"complexProcesses": "Esto sugiere procesos complejos o informacion fragmentada",
|
||||||
|
"alertSignal": "Senal de alerta:",
|
||||||
|
"resolutionTimeRisk": "Si los clientes recontactan multiples veces por el mismo tema, es probable que el tiempo TOTAL de resolucion supere los 15 dias requeridos por ley.",
|
||||||
|
"suggestedAction": "ACCION SUGERIDA",
|
||||||
|
"diagnosis": "1. DIAGNOSTICO: Implementar sistema de casos/tickets",
|
||||||
|
"registerOpenClose": "Registrar fecha apertura + cierre",
|
||||||
|
"linkInteractions": "Vincular multiples interacciones al mismo caso",
|
||||||
|
"typology": "Tipologia: consulta / reclamacion / incidencia",
|
||||||
|
"crmInvestment": "Inversion CRM/Ticketing",
|
||||||
|
"operationalImprovement": "2. MEJORA OPERATIVA: Aumentar FCR",
|
||||||
|
"dimension3Identifies": "Tu Dimension 3 (Efectividad) ya identifica:",
|
||||||
|
"rootCauses": "- Root causes: info fragmentada, falta empowerment",
|
||||||
|
"solution": "- Solucion: Knowledge base + decision trees",
|
||||||
|
"fcrBenefit": "Beneficio: ↑ FCR = ↓ recontactos = ↓ tiempo total"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"title": "Resumen de Cumplimiento - Todos los Requisitos",
|
||||||
|
"requirementColumn": "Requisito",
|
||||||
|
"descriptionColumn": "Descripcion",
|
||||||
|
"statusColumn": "Estado",
|
||||||
|
"scoreColumn": "Score",
|
||||||
|
"gapColumn": "Gap",
|
||||||
|
"requirements": {
|
||||||
|
"LAW-01": {
|
||||||
|
"name": "Tiempo de Espera",
|
||||||
|
"description": "Tiempo maximo de espera de 3 minutos para atencion telefonica"
|
||||||
|
},
|
||||||
|
"LAW-02": {
|
||||||
|
"name": "Resolucion Efectiva",
|
||||||
|
"description": "Resolucion en primera contacto sin transferencias innecesarias"
|
||||||
|
},
|
||||||
|
"LAW-03": {
|
||||||
|
"name": "Acceso a Agente Humano",
|
||||||
|
"description": "Derecho a hablar con un agente humano en cualquier momento"
|
||||||
|
},
|
||||||
|
"LAW-04": {
|
||||||
|
"name": "Grabacion de Llamadas",
|
||||||
|
"description": "Notificacion previa de grabacion y acceso a la misma"
|
||||||
|
},
|
||||||
|
"LAW-05": {
|
||||||
|
"name": "Accesibilidad",
|
||||||
|
"description": "Canales accesibles para personas con discapacidad"
|
||||||
|
},
|
||||||
|
"LAW-06": {
|
||||||
|
"name": "Confirmacion Escrita",
|
||||||
|
"description": "Confirmacion por escrito de reclamaciones y gestiones"
|
||||||
|
},
|
||||||
|
"LAW-07": {
|
||||||
|
"name": "Cobertura Horaria",
|
||||||
|
"description": "Atencion 24/7 para servicios esenciales o horario ampliado"
|
||||||
|
},
|
||||||
|
"LAW-08": {
|
||||||
|
"name": "Formacion de Agentes",
|
||||||
|
"description": "Personal cualificado y formado en atencion al cliente"
|
||||||
|
},
|
||||||
|
"LAW-09": {
|
||||||
|
"name": "Idiomas Cooficiales",
|
||||||
|
"description": "Atencion en catalan, euskera, gallego y valenciano"
|
||||||
|
},
|
||||||
|
"LAW-10": {
|
||||||
|
"name": "Plazos de Resolucion",
|
||||||
|
"description": "Resolucion de reclamaciones en maximo 15 dias habiles"
|
||||||
|
},
|
||||||
|
"LAW-11": {
|
||||||
|
"name": "Gratuidad del Servicio",
|
||||||
|
"description": "Atencion telefonica sin coste adicional (numeros 900)"
|
||||||
|
},
|
||||||
|
"LAW-12": {
|
||||||
|
"name": "Trazabilidad",
|
||||||
|
"description": "Numero de referencia para seguimiento de gestiones"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"complies": "Cumple: Requisito satisfecho",
|
||||||
|
"partial": "Parcial: Requiere mejoras",
|
||||||
|
"notComplies": "No Cumple: Accion urgente",
|
||||||
|
"noData": "Sin Datos: Campos no disponibles en CSV"
|
||||||
|
},
|
||||||
|
"investment": {
|
||||||
|
"nonComplianceCost": "Coste de no cumplimiento",
|
||||||
|
"upTo100k": "Hasta 100K",
|
||||||
|
"potentialFines": "Multas potenciales/infraccion",
|
||||||
|
"recommendedInvestment": "Inversion recomendada",
|
||||||
|
"basedOnOperation": "Basada en tu operacion",
|
||||||
|
"complianceRoi": "ROI de cumplimiento",
|
||||||
|
"avoidSanctionsAndImprove": "Evitar sanciones + mejora CX",
|
||||||
|
"highRoi": "Alto",
|
||||||
|
"percentOfAnnualCost": "~5% coste anual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataMaturity": {
|
||||||
|
"title": "Resumen: Madurez de Datos para Compliance",
|
||||||
|
"currentLevel": "Tu nivel actual de instrumentacion:",
|
||||||
|
"dataAvailable": "DATOS DISPONIBLES (3/10)",
|
||||||
|
"dataEstimable": "DATOS ESTIMABLES (2/10)",
|
||||||
|
"dataNotAvailable": "NO DISPONIBLES (5/10)",
|
||||||
|
"availableData": {
|
||||||
|
"temporalCoverage": "Cobertura temporal 24/7",
|
||||||
|
"geographicDistribution": "Distribucion geografica",
|
||||||
|
"resolutionQuality": "Calidad resolucion proxy"
|
||||||
|
},
|
||||||
|
"estimableData": {
|
||||||
|
"asaProxy": "ASA <3min via proxy abandono",
|
||||||
|
"languagesViaCountry": "Lenguas cooficiales via pais"
|
||||||
|
},
|
||||||
|
"missingData": {
|
||||||
|
"caseResolutionTime": "Tiempo resolucion casos",
|
||||||
|
"refundTime": "Cobros indebidos <5 dias",
|
||||||
|
"supervisorTransfer": "Transfer a supervisor",
|
||||||
|
"incidentInfo": "Info incidencias <2h",
|
||||||
|
"enacAudit": "Auditoria ENAC",
|
||||||
|
"externalContract": "requiere contratacion externa"
|
||||||
|
},
|
||||||
|
"suggestedInvestment": "INVERSION SUGERIDA PARA COMPLIANCE COMPLETO",
|
||||||
|
"phase1": "Fase 1 - Instrumentacion (Q1 2026)",
|
||||||
|
"phase2": "Fase 2 - Operaciones (Q2-Q3 2026)",
|
||||||
|
"phase1Items": {
|
||||||
|
"asaTracking": "Tracking ASA real",
|
||||||
|
"ticketingSystem": "Sistema ticketing/casos",
|
||||||
|
"languageEnrichment": "Enriquecimiento lenguas",
|
||||||
|
"subtotal": "Subtotal:"
|
||||||
|
},
|
||||||
|
"phase2Items": {
|
||||||
|
"coverage247": "Cobertura 24/7 (chatbot + on-call)",
|
||||||
|
"aiCopilot": "Copilot IA (reducir AHT)",
|
||||||
|
"enacAuditor": "Auditor ENAC",
|
||||||
|
"subtotalYear1": "Subtotal año 1:",
|
||||||
|
"perYear": "/año",
|
||||||
|
"perMonth": "/mes"
|
||||||
|
},
|
||||||
|
"totals": {
|
||||||
|
"totalInvestment": "Inversion Total",
|
||||||
|
"percentAnnualCost": "~5% coste anual",
|
||||||
|
"riskAvoided": "Riesgo Evitado",
|
||||||
|
"potentialSanctions": "sanciones potenciales",
|
||||||
|
"complianceRoi": "ROI Compliance"
|
||||||
|
},
|
||||||
|
"article": "Art. {{number}}",
|
||||||
|
"articlePartial": "Art. {{number}} parcial",
|
||||||
|
"articleIndirect": "Art. {{number}} indirecto",
|
||||||
|
"errorMargin": "±{{margin}}%",
|
||||||
|
"noDetail": "sin detalle"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"noData": "Sin datos",
|
||||||
|
"requiredData": "Requiere datos",
|
||||||
|
"score": "Score",
|
||||||
|
"gap": "Gap"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user