refactor: implement i18n in RoadmapTab and Law10Tab (phase 5)

Refactored two major tab components to use react-i18next:

RoadmapTab (2,719 lines):
- Opportunity bubble chart with all quadrants and methodology
- Wave cards with risk levels and strategic recommendations
- Tier classification (TIER 1-4) with distribution analysis
- Economic impact and feasibility axis labels
- 100+ translation keys for visualization and strategy content

Law10Tab (1,533 lines):
- Spanish regulatory compliance (Ley 10/2025) analysis
- All 12 requirements with status evaluations
- Time coverage, response speed, resolution quality sections
- Compliance messages and action recommendations
- 80+ translation keys for regulatory content

Added comprehensive translation keys to es.json and en.json:
- roadmap section: 100+ keys
- law10 section: 80+ keys

Build verified successfully.

https://claude.ai/code/session_4f888c33-8937-4db8-8a9d-ddc9ac51a725
This commit is contained in:
Claude
2026-02-06 19:43:01 +00:00
parent 92931ea2dd
commit cbe074f43c
4 changed files with 1230 additions and 315 deletions

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Scale,
Clock,
@@ -65,7 +66,7 @@ const LAW_10_2025 = {
// 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
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
const offHoursPct = volumetryDim?.distribution_data?.off_hours_pct ?? null;
@@ -74,39 +75,39 @@ function evaluateLaw07Compliance(data: AnalysisData): ComplianceResult {
return {
status: 'SIN_DATOS',
score: 0,
gap: 'Sin datos de distribucion horaria',
details: ['No se encontraron datos de distribucion horaria en el analisis'],
gap: t('law10.compliance.law07.noData'),
details: [t('law10.compliance.law07.noDataDetails')],
};
}
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) {
return {
status: 'CUMPLE',
score: 100,
gap: '-',
details: [...details, 'Cobertura horaria adecuada'],
details: [...details, t('law10.compliance.law07.adequateCoverage')],
};
} else if (offHoursPct <= 15) {
return {
status: 'PARCIAL',
score: Math.round(100 - ((offHoursPct - 5) / 10) * 50),
gap: `${(offHoursPct - 5).toFixed(1)}pp sobre optimo`,
details: [...details, 'Cobertura horaria mejorable - considerar ampliar horarios'],
gap: t('law10.compliance.law07.gapOverOptimal', { gap: (offHoursPct - 5).toFixed(1) }),
details: [...details, t('law10.compliance.law07.improvableCoverage')],
};
} else {
return {
status: 'NO_CUMPLE',
score: Math.max(0, Math.round(50 - ((offHoursPct - 15) / 10) * 50)),
gap: `${(offHoursPct - 15).toFixed(1)}pp sobre limite`,
details: [...details, 'Cobertura horaria insuficiente - requiere accion inmediata'],
gap: t('law10.compliance.law07.gapOverLimit', { gap: (offHoursPct - 15).toFixed(1) }),
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
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
@@ -114,8 +115,8 @@ function evaluateLaw01Compliance(data: AnalysisData): ComplianceResult {
return {
status: 'SIN_DATOS',
score: 0,
gap: 'Sin datos de tiempos de espera',
details: ['No se encontraron datos de hold_time en el analisis'],
gap: t('law10.compliance.law01.noData'),
details: [t('law10.compliance.law01.noDataDetails')],
};
}
@@ -135,35 +136,35 @@ function evaluateLaw01Compliance(data: AnalysisData): ComplianceResult {
const pctDentroLimite = (volDentroLimite / totalVolume) * 100;
const details: string[] = [];
details.push(`Tiempo de espera promedio: ${Math.round(avgHoldTime)}s (limite: 180s)`);
details.push(`${pctDentroLimite.toFixed(1)}% de interacciones dentro del limite`);
details.push(`${colasExceden.length} de ${data.heatmapData.length} colas exceden el limite`);
details.push(t('law10.compliance.law01.avgHoldTime', { time: Math.round(avgHoldTime) }));
details.push(t('law10.compliance.law01.withinLimit', { percent: pctDentroLimite.toFixed(1) }));
details.push(t('law10.compliance.law01.queuesExceedLimit', { count: colasExceden.length, total: data.heatmapData.length }));
if (avgHoldTime < 180 && pctColasExceden < 10) {
return {
status: 'CUMPLE',
score: 100,
gap: `-${Math.round(180 - avgHoldTime)}s`,
gap: t('law10.compliance.law01.gapNegative', { gap: Math.round(180 - avgHoldTime) }),
details,
};
} else if (avgHoldTime < 180) {
return {
status: 'PARCIAL',
score: Math.round(90 - pctColasExceden),
gap: `${colasExceden.length} colas fuera`,
gap: t('law10.compliance.law01.queuesOutside', { count: colasExceden.length }),
details,
};
} else {
return {
status: 'NO_CUMPLE',
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,
};
}
}
function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
function evaluateLaw02Compliance(data: AnalysisData, t: (key: string, options?: any) => string): ComplianceResult {
// Evaluar FCR y tasa de transferencia
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
@@ -171,8 +172,8 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
return {
status: 'SIN_DATOS',
score: 0,
gap: 'Sin datos de resolucion',
details: ['No se encontraron datos de FCR o transferencias'],
gap: t('law10.compliance.law02.noData'),
details: [t('law10.compliance.law02.noDataDetails')],
};
}
@@ -187,13 +188,13 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
) / totalVolume;
const details: string[] = [];
details.push(`FCR Tecnico: ${avgFCR.toFixed(1)}% (objetivo: >75%)`);
details.push(`Tasa de transferencia: ${avgTransfer.toFixed(1)}% (objetivo: <15%)`);
details.push(t('law10.compliance.law02.fcrTechnical', { fcr: avgFCR.toFixed(1) }));
details.push(t('law10.compliance.law02.transferRate', { rate: avgTransfer.toFixed(1) }));
// Colas con alto transfer
const colasAltoTransfer = data.heatmapData.filter(h => h.metrics.transfer_rate > 25);
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;
@@ -205,7 +206,7 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
return {
status: 'CUMPLE',
score: 100,
gap: '-',
gap: t('law10.compliance.law02.gapDash'),
details,
};
} else if (parcialFCR && parcialTransfer) {
@@ -213,31 +214,33 @@ function evaluateLaw02Compliance(data: AnalysisData): ComplianceResult {
(Math.min(avgFCR, 75) / 75 * 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 {
status: 'PARCIAL',
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,
};
} else {
return {
status: 'NO_CUMPLE',
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,
};
}
}
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
return {
status: 'SIN_DATOS',
score: 0,
gap: 'Requiere datos',
gap: t('law10.compliance.law09.noData'),
details: [
'No se dispone de datos de idioma en las interacciones',
'Para evaluar este requisito se necesita el campo "language" en el CSV',
t('law10.compliance.law09.noLanguageData'),
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) {
case 'CUMPLE': return 'Cumple';
case 'PARCIAL': return 'Parcial';
case 'NO_CUMPLE': return 'No Cumple';
default: return 'Sin Datos';
case 'CUMPLE': return t('law10.status.CUMPLE');
case 'PARCIAL': return t('law10.status.PARCIAL');
case 'NO_CUMPLE': return t('law10.status.NO_CUMPLE');
default: return t('law10.status.SIN_DATOS');
}
}
@@ -288,6 +291,7 @@ function Law10HeaderCountdown({
}: {
complianceResults: { law07: ComplianceResult; law01: ComplianceResult; law02: ComplianceResult; law09: ComplianceResult };
}) {
const { t } = useTranslation();
const now = new Date();
const deadline = LAW_10_2025.deadline;
const diffTime = deadline.getTime() - now.getTime();
@@ -314,18 +318,14 @@ function Law10HeaderCountdown({
<Lightbulb className="w-5 h-5 text-amber-600" />
</div>
<div>
<h2 className="font-semibold text-gray-900">Sobre este Analisis</h2>
<p className="text-sm text-gray-500">Ley 10/2025 de Atencion al Cliente</p>
<h2 className="font-semibold text-gray-900">{t('law10.header.aboutThisAnalysis')}</h2>
<p className="text-sm text-gray-500">{t('law10.header.lawTitle')}</p>
</div>
</div>
{/* Descripcion */}
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-4">
<p className="text-sm text-gray-700 leading-relaxed">
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>
<p className="text-sm text-gray-700 leading-relaxed" dangerouslySetInnerHTML={{ __html: t('law10.header.description') }} />
</div>
{/* Metricas de estado */}
@@ -334,9 +334,9 @@ function Law10HeaderCountdown({
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
<Calendar className="w-5 h-5 text-gray-400" />
<div>
<p className="text-xs text-gray-500">Deadline de cumplimiento</p>
<p className="text-sm font-semibold text-gray-900">28 Diciembre 2026</p>
<p className="text-xs text-gray-500">{diffDays} dias restantes</p>
<p className="text-xs text-gray-500">{t('law10.header.complianceDeadline')}</p>
<p className="text-sm font-semibold text-gray-900">{t('law10.header.december282026')}</p>
<p className="text-xs text-gray-500">{t('law10.header.daysRemaining', { days: diffDays })}</p>
</div>
</div>
@@ -344,9 +344,9 @@ function Law10HeaderCountdown({
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
<Scale className="w-5 h-5 text-gray-400" />
<div>
<p className="text-xs text-gray-500">Requisitos evaluados</p>
<p className="text-sm font-semibold text-gray-900">{cumplidos} de {total} cumplen</p>
<p className="text-xs text-gray-500">Basado en datos disponibles</p>
<p className="text-xs text-gray-500">{t('law10.header.requirementsEvaluated')}</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">{t('law10.header.basedOnData')}</p>
</div>
</div>
@@ -354,18 +354,18 @@ function Law10HeaderCountdown({
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
<StatusIcon status={overallStatus} />
<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(
'text-sm font-semibold',
overallStatus === 'CUMPLE' && 'text-emerald-600',
overallStatus === 'PARCIAL' && 'text-amber-600',
overallStatus === 'NO_CUMPLE' && 'text-red-600',
)}>
{getStatusLabel(overallStatus)}
{getStatusLabel(overallStatus, t)}
</p>
<p className="text-xs text-gray-500">
{overallStatus === 'CUMPLE' ? 'Buen estado' :
overallStatus === 'PARCIAL' ? 'Requiere atencion' : 'Accion urgente'}
{overallStatus === 'CUMPLE' ? t('law10.header.goodState') :
overallStatus === 'PARCIAL' ? t('law10.header.requiresAttention') : t('law10.header.urgentAction')}
</p>
</div>
</div>
@@ -376,6 +376,7 @@ function Law10HeaderCountdown({
// Seccion: Cobertura Horaria (LAW-07)
function TimeCoverageSection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
const { t } = useTranslation();
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
const hourlyData = volumetryDim?.distribution_data?.hourly || [];
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" />
</div>
<div>
<h3 className="font-semibold text-gray-900">Cobertura Temporal: Disponibilidad del Servicio</h3>
<p className="text-sm text-gray-500">Relacionado con Art. 14 - Servicios basicos 24/7</p>
<h3 className="font-semibold text-gray-900">{t('law10.timeCoverage.title')}</h3>
<p className="text-sm text-gray-500">{t('law10.timeCoverage.article')}</p>
</div>
</div>
<div className="flex items-center gap-2">
<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>
@@ -461,12 +462,12 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
<div className="mb-5">
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
LO QUE SABEMOS
{t('law10.timeCoverage.whatWeKnow')}
</h4>
{/* Heatmap 24x7 */}
<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 */}
<div className="flex items-center mb-1">
@@ -500,32 +501,32 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
{/* Leyenda */}
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
<span>Intensidad:</span>
<span className="text-blue-200"> Bajo</span>
<span className="text-blue-400"> Medio</span>
<span className="text-blue-600"> Alto</span>
<span>{t('law10.timeCoverage.intensity')}</span>
<span className="text-blue-200"> {t('law10.timeCoverage.intensityLow')}</span>
<span className="text-blue-400"> {t('law10.timeCoverage.intensityMedium')}</span>
<span className="text-blue-600"> {t('law10.timeCoverage.intensityHigh')}</span>
</div>
</div>
{/* Hallazgos operacionales */}
<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">
<li className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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>
</ul>
</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">
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
<Scale className="w-4 h-4" />
IMPLICACION LEY 10/2025
{t('law10.timeCoverage.lawImplication')}
</h4>
<div className="space-y-3 text-sm text-gray-700">
<p>
<strong>Transporte aereo = Servicio basico</strong><br />
<span className="text-gray-600"> Art. 14 requiere atencion 24/7 para incidencias</span>
<strong>{t('law10.timeCoverage.basicServiceRequirement')}</strong><br />
<span className="text-gray-600">{t('law10.timeCoverage.article14Requirement')}</span>
</p>
<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">
<li className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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>
</ul>
</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">
<h4 className="text-sm font-semibold text-blue-800 mb-3 flex items-center gap-2">
<Target className="w-4 h-4" />
ACCION SUGERIDA
{t('law10.timeCoverage.suggestedAction')}
</h4>
<div className="space-y-4 text-sm">
<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">
<li> ¿Que % son incidencias criticas? Requiere 24/7</li>
<li> ¿Que % son consultas generales? Pueden esperar</li>
<li> {t('law10.timeCoverage.criticalIncidents')}</li>
<li> {t('law10.timeCoverage.generalQueries')}</li>
</ul>
</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="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="font-semibold text-emerald-600">~65K/año</span>
<span className="text-gray-700">{t('law10.timeCoverage.optionAChatbot')}</span>
<span className="font-semibold text-emerald-600">{t('law10.timeCoverage.costPerYear', { cost: '65K' })}</span>
</div>
<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="font-semibold text-amber-600">~95K/año</span>
<span className="text-gray-700">{t('law10.timeCoverage.optionBExternal')}</span>
<span className="font-semibold text-amber-600">{t('law10.timeCoverage.costPerYear', { cost: '95K' })}</span>
</div>
<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="font-semibold text-red-600">~180K/año</span>
<span className="text-gray-700">{t('law10.timeCoverage.optionCNight')}</span>
<span className="font-semibold text-red-600">{t('law10.timeCoverage.costPerYear', { cost: '180K' })}</span>
</div>
</div>
</div>
@@ -601,6 +602,7 @@ function TimeCoverageSection({ data, result }: { data: AnalysisData; result: Com
// Seccion: Velocidad de Respuesta (LAW-01)
function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
const { t } = useTranslation();
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
const volumetryDim = data.dimensions.find(d => d.name === 'volumetry_distribution');
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" />
</div>
<div>
<h3 className="font-semibold text-gray-900">Velocidad de Atencion: Eficiencia Operativa</h3>
<p className="text-sm text-gray-500">Relacionado con Art. 8.2 - 95% llamadas &lt;3min</p>
<h3 className="font-semibold text-gray-900">{t('law10.responseSpeed.title')}</h3>
<p className="text-sm text-gray-500">{t('law10.responseSpeed.article')}</p>
</div>
</div>
<div className="flex items-center gap-2">
<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>
@@ -691,22 +693,22 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
<div className="mb-5">
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
LO QUE SABEMOS
{t('law10.responseSpeed.whatWeKnow')}
</h4>
{/* Metricas principales */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<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-xs text-gray-600">Tasa abandono</p>
<p className="text-xs text-gray-600">{t('law10.responseSpeed.abandonmentRate')}</p>
</div>
<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-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 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-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 className={cn(
'p-3 rounded-lg',
@@ -716,13 +718,13 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
'text-2xl font-bold',
ahtRatio > 2 ? 'text-amber-600' : 'text-gray-900'
)}>{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>
{/* Grafico de abandonos por hora */}
<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">
{hourlyAbandonment.map((h, idx) => (
<div
@@ -744,28 +746,28 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
<span>24:00</span>
</div>
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
<span>Abandono:</span>
<span className="text-emerald-500"> &lt;8%</span>
<span className="text-amber-400"> 8-15%</span>
<span className="text-red-500"> &gt;20%</span>
<span>{t('law10.responseSpeed.abandonmentLegend')}</span>
<span className="text-emerald-500" dangerouslySetInnerHTML={{ __html: `${t('law10.responseSpeed.abandonmentLow')}` }} />
<span className="text-amber-400" dangerouslySetInnerHTML={{ __html: `${t('law10.responseSpeed.abandonmentMedium')}` }} />
<span className="text-red-500" dangerouslySetInnerHTML={{ __html: `${t('law10.responseSpeed.abandonmentHigh')}` }} />
</div>
</div>
{/* Patrones observados */}
<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">
<li className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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>
</ul>
</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">
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
<Scale className="w-4 h-4" />
IMPLICACION LEY 10/2025
{t('law10.responseSpeed.lawImplication')}
</h4>
<div className="space-y-3 text-sm text-gray-700">
<p>
<strong>Art. 8.2 requiere:</strong> "95% de llamadas atendidas en &lt;3 minutos"
</p>
<p dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.article82Requirement') }} />
<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">
<AlertTriangle className="w-4 h-4" />
LIMITACION DE DATOS
{t('law10.responseSpeed.dataLimitation')}
</p>
<p className="text-gray-600 text-xs">
Tu CDR actual NO incluye ASA (tiempo en cola antes de responder),
por lo que NO podemos medir este requisito directamente.
{t('law10.responseSpeed.noAsaData')}
</p>
</div>
<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">
<li className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<span className="text-amber-500"></span>
<span>Picos de abandono coinciden con picos de volumen</span>
<span>{t('law10.responseSpeed.peaksCoincide')}</span>
</li>
</ul>
</div>
<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="font-medium">
~<strong>{estimatedFastResponse.toFixed(0)}%</strong> de llamadas probablemente atendidas "rapido"
</p>
<p className="text-xs text-gray-500 mb-1">{t('law10.responseSpeed.conservativeEstimate')}</p>
<p className="font-medium" dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.likelyFastResponse', { percent: estimatedFastResponse.toFixed(0) }) }} />
<p className={cn(
'font-medium',
gapVs95 > 0 ? 'text-red-600' : 'text-emerald-600'
)}>
Gap vs 95% requerido: <strong>{gapVs95 > 0 ? '-' : '+'}{Math.abs(gapVs95).toFixed(0)}</strong> puntos porcentuales
</p>
)} dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.gapVs95', { operator: gapVs95 > 0 ? '-' : '+', gap: Math.abs(gapVs95).toFixed(0) }) }} />
</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">
<h4 className="text-sm font-semibold text-purple-800 mb-3 flex items-center gap-2">
<Target className="w-4 h-4" />
ACCION SUGERIDA
{t('law10.responseSpeed.suggestedAction')}
</h4>
<div className="space-y-4 text-sm">
<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">
<li> Tu Dimension 2 (Eficiencia) ya identifica:</li>
<li className="ml-4 text-xs">- AHT elevado ({Math.round(ahtP50)}s vs 380s benchmark)</li>
<li className="ml-4 text-xs">- Oportunidad Copilot IA: -18% AHT proyectado</li>
<li> Beneficio dual: AHT = capacidad = cola = ASA</li>
<li> {t('law10.responseSpeed.dimension2Identifies')}</li>
<li className="ml-4 text-xs">{t('law10.responseSpeed.highAht', { aht: Math.round(ahtP50) })}</li>
<li className="ml-4 text-xs">{t('law10.responseSpeed.copilotOpportunity')}</li>
<li> {t('law10.responseSpeed.dualBenefit')}</li>
</ul>
</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="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>
</div>
<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="font-semibold text-gray-600">4-6 semanas</span>
<span className="text-gray-700">{t('law10.responseSpeed.implementationTimeline')}</span>
<span className="font-semibold text-gray-600">{t('law10.responseSpeed.implementationWeeks')}</span>
</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>
@@ -867,6 +862,7 @@ function ResponseSpeedSection({ data, result }: { data: AnalysisData; result: Co
// Seccion: Calidad de Resolucion (LAW-02)
function ResolutionQualitySection({ data, result }: { data: AnalysisData; result: ComplianceResult }) {
const { t } = useTranslation();
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
// FCR Tecnico y Real
@@ -925,13 +921,13 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
<Target className="w-5 h-5 text-emerald-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900">Calidad de Resolucion: Efectividad</h3>
<p className="text-sm text-gray-500">Relacionado con Art. 17 - Resolucion en 15 dias</p>
<h3 className="font-semibold text-gray-900">{t('law10.resolutionQuality.title')}</h3>
<p className="text-sm text-gray-500">{t('law10.resolutionQuality.article')}</p>
</div>
</div>
<div className="flex items-center gap-2">
<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>
@@ -939,7 +935,7 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
<div className="mb-5">
<h4 className="text-sm font-semibold text-emerald-700 mb-3 flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
LO QUE SABEMOS
{t('law10.resolutionQuality.whatWeKnow')}
</h4>
{/* Metricas principales */}
@@ -952,21 +948,21 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
'text-2xl font-bold',
avgFCRReal >= 60 ? 'text-gray-900' : 'text-red-600'
)}>{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 className="p-3 bg-gray-50 rounded-lg">
<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 className="p-3 bg-gray-50 rounded-lg">
<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>
{/* Grafico FCR por skill */}
<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">
{skillFCRData.slice(0, 8).map((s, idx) => (
<div key={idx} className="flex items-center gap-2">
@@ -994,22 +990,22 @@ function ResolutionQualitySection({ data, result }: { data: AnalysisData; result
))}
</div>
<div className="flex items-center gap-4 mt-3 text-[10px] text-gray-500">
<span>FCR:</span>
<span className="text-red-500"> &lt;45%</span>
<span className="text-amber-400"> 45-65%</span>
<span className="text-emerald-500"> &gt;75%</span>
<span>{t('law10.resolutionQuality.fcrLegend')}</span>
<span className="text-red-500" dangerouslySetInnerHTML={{ __html: `${t('law10.resolutionQuality.fcrLow')}` }} />
<span className="text-amber-400" dangerouslySetInnerHTML={{ __html: `${t('law10.resolutionQuality.fcrMedium')}` }} />
<span className="text-emerald-500" dangerouslySetInnerHTML={{ __html: `${t('law10.resolutionQuality.fcrHigh')}` }} />
</div>
</div>
{/* Top skills con FCR bajo */}
{lowFCRSkills.length > 0 && (
<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">
{lowFCRSkills.map((s, idx) => (
<li key={idx} className="flex items-start gap-2">
<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>
))}
</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">
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
<Scale className="w-4 h-4" />
IMPLICACION LEY 10/2025
{t('law10.resolutionQuality.lawImplication')}
</h4>
<div className="space-y-3 text-sm text-gray-700">
<p>
<strong>Art. 17 requiere:</strong> "Resolucion de reclamaciones ≤15 dias"
</p>
<p dangerouslySetInnerHTML={{ __html: t('law10.resolutionQuality.article17Requirement') }} />
<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">
<AlertTriangle className="w-4 h-4" />
LIMITACION DE DATOS
{t('law10.resolutionQuality.dataLimitation')}
</p>
<p className="text-gray-600 text-xs">
Tu CDR solo registra interacciones individuales, NO casos multi-touch
ni tiempo total de resolucion.
{t('law10.resolutionQuality.noCaseTracking')}
</p>
</div>
<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">
<li className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<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 className="flex items-start gap-2">
<span className="text-amber-500"></span>
<span>Esto sugiere procesos complejos o informacion fragmentada</span>
<span>{t('law10.resolutionQuality.complexProcesses')}</span>
</li>
</ul>
</div>
<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">
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.
{t('law10.resolutionQuality.resolutionTimeRisk')}
</p>
</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">
<h4 className="text-sm font-semibold text-emerald-800 mb-3 flex items-center gap-2">
<Target className="w-4 h-4" />
ACCION SUGERIDA
{t('law10.resolutionQuality.suggestedAction')}
</h4>
<div className="space-y-4 text-sm">
<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">
<li> Registrar fecha apertura + cierre</li>
<li> Vincular multiples interacciones al mismo caso</li>
<li> Tipologia: consulta / reclamacion / incidencia</li>
<li> {t('law10.resolutionQuality.registerOpenClose')}</li>
<li> {t('law10.resolutionQuality.linkInteractions')}</li>
<li> {t('law10.resolutionQuality.typology')}</li>
</ul>
<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>
</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">
<li> Tu Dimension 3 (Efectividad) ya identifica:</li>
<li className="ml-4 text-xs">- Root causes: info fragmentada, falta empowerment</li>
<li className="ml-4 text-xs">- Solucion: Knowledge base + decision trees</li>
<li> Beneficio: FCR = recontactos = tiempo total</li>
<li> {t('law10.resolutionQuality.dimension3Identifies')}</li>
<li className="ml-4 text-xs">{t('law10.resolutionQuality.rootCauses')}</li>
<li className="ml-4 text-xs">{t('law10.resolutionQuality.solution')}</li>
<li> {t('law10.resolutionQuality.fcrBenefit')}</li>
</ul>
</div>
</div>
@@ -1112,86 +1104,88 @@ function Law10SummaryRoadmap({
complianceResults: { law07: ComplianceResult; law01: ComplianceResult; law02: ComplianceResult; law09: ComplianceResult };
data: AnalysisData;
}) {
const { t } = useTranslation();
// Resultado por defecto para requisitos sin datos
const sinDatos: ComplianceResult = {
status: 'SIN_DATOS',
score: 0,
gap: 'Requiere datos',
details: ['No se dispone de datos para evaluar este requisito'],
gap: t('law10.common.requiredData'),
details: [t('law10.common.noData')],
};
// Todos los requisitos de la Ley 10/2025 con descripciones
const allRequirements = [
{
id: 'LAW-01',
name: 'Tiempo de Espera',
description: 'Tiempo maximo de espera de 3 minutos para atencion telefonica',
name: t('law10.summary.requirements.LAW-01.name'),
description: t('law10.summary.requirements.LAW-01.description'),
result: complianceResults.law01,
},
{
id: 'LAW-02',
name: 'Resolucion Efectiva',
description: 'Resolucion en primera contacto sin transferencias innecesarias',
name: t('law10.summary.requirements.LAW-02.name'),
description: t('law10.summary.requirements.LAW-02.description'),
result: complianceResults.law02,
},
{
id: 'LAW-03',
name: 'Acceso a Agente Humano',
description: 'Derecho a hablar con un agente humano en cualquier momento',
name: t('law10.summary.requirements.LAW-03.name'),
description: t('law10.summary.requirements.LAW-03.description'),
result: sinDatos,
},
{
id: 'LAW-04',
name: 'Grabacion de Llamadas',
description: 'Notificacion previa de grabacion y acceso a la misma',
name: t('law10.summary.requirements.LAW-04.name'),
description: t('law10.summary.requirements.LAW-04.description'),
result: sinDatos,
},
{
id: 'LAW-05',
name: 'Accesibilidad',
description: 'Canales accesibles para personas con discapacidad',
name: t('law10.summary.requirements.LAW-05.name'),
description: t('law10.summary.requirements.LAW-05.description'),
result: sinDatos,
},
{
id: 'LAW-06',
name: 'Confirmacion Escrita',
description: 'Confirmacion por escrito de reclamaciones y gestiones',
name: t('law10.summary.requirements.LAW-06.name'),
description: t('law10.summary.requirements.LAW-06.description'),
result: sinDatos,
},
{
id: 'LAW-07',
name: 'Cobertura Horaria',
description: 'Atencion 24/7 para servicios esenciales o horario ampliado',
name: t('law10.summary.requirements.LAW-07.name'),
description: t('law10.summary.requirements.LAW-07.description'),
result: complianceResults.law07,
},
{
id: 'LAW-08',
name: 'Formacion de Agentes',
description: 'Personal cualificado y formado en atencion al cliente',
name: t('law10.summary.requirements.LAW-08.name'),
description: t('law10.summary.requirements.LAW-08.description'),
result: sinDatos,
},
{
id: 'LAW-09',
name: 'Idiomas Cooficiales',
description: 'Atencion en catalan, euskera, gallego y valenciano',
name: t('law10.summary.requirements.LAW-09.name'),
description: t('law10.summary.requirements.LAW-09.description'),
result: complianceResults.law09,
},
{
id: 'LAW-10',
name: 'Plazos de Resolucion',
description: 'Resolucion de reclamaciones en maximo 15 dias habiles',
name: t('law10.summary.requirements.LAW-10.name'),
description: t('law10.summary.requirements.LAW-10.description'),
result: sinDatos,
},
{
id: 'LAW-11',
name: 'Gratuidad del Servicio',
description: 'Atencion telefonica sin coste adicional (numeros 900)',
name: t('law10.summary.requirements.LAW-11.name'),
description: t('law10.summary.requirements.LAW-11.description'),
result: sinDatos,
},
{
id: 'LAW-12',
name: 'Trazabilidad',
description: 'Numero de referencia para seguimiento de gestiones',
name: t('law10.summary.requirements.LAW-12.name'),
description: t('law10.summary.requirements.LAW-12.description'),
result: sinDatos,
},
];
@@ -1496,12 +1490,14 @@ function DataMaturitySummary({ data }: { data: AnalysisData }) {
// ============================================
export function Law10Tab({ data }: Law10TabProps) {
const { t } = useTranslation();
// Evaluar compliance para cada requisito
const complianceResults = {
law07: evaluateLaw07Compliance(data),
law01: evaluateLaw01Compliance(data),
law02: evaluateLaw02Compliance(data),
law09: evaluateLaw09Compliance(data),
law07: evaluateLaw07Compliance(data, t),
law01: evaluateLaw01Compliance(data, t),
law02: evaluateLaw02Compliance(data, t),
law09: evaluateLaw09Compliance(data, t),
};
return (

View File

@@ -5,6 +5,7 @@ import {
ArrowRight, Info, Users, Target, Zap, Shield,
ChevronDown, ChevronUp, BookOpen, Bot, Settings, Rocket, AlertCircle
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { RoadmapPhase } from '../../types';
import type { AnalysisData, RoadmapInitiative, HeatmapDataPoint, DrilldownDataPoint, OriginalQueueMetrics, AgenticTier } from '../../types';
import {
@@ -450,6 +451,8 @@ function OpportunityBubbleChart({
heatmapData: HeatmapDataPoint[];
drilldownData?: DrilldownDataPoint[]
}) {
const { t } = useTranslation();
// v3.5: Usar drilldownData si está disponible para tener info de Tier por cola
let chartData: BubbleDataPoint[] = [];
@@ -517,10 +520,10 @@ function OpportunityBubbleChart({
<div>
<h3 className="font-semibold text-gray-800 flex items-center gap-2">
<Target className="w-5 h-5 text-[#6D84E3]" />
Mapa de Oportunidades por Tier
{t('roadmap.opportunityMapTitle')}
</h3>
<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>
</div>
</div>
@@ -529,12 +532,12 @@ function OpportunityBubbleChart({
<div className="relative" style={{ height: '340px' }}>
{/* 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">
IMPACTO ECONÓMICO (Ahorro TCO /año)
{t('roadmap.economicImpactAxis')}
</div>
{/* X-axis label */}
<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>
{/* Chart area */}
@@ -553,7 +556,7 @@ function OpportunityBubbleChart({
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">
Tier AUTOMATE 7.5
{t('roadmap.tierAutomateThreshold')}
</span>
</div>
@@ -566,27 +569,27 @@ function OpportunityBubbleChart({
{/* Quadrant labels - basados en Score (X) y Ahorro (Y) */}
{/* 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="font-bold text-emerald-700">🎯 QUICK WINS</div>
<div className="text-[9px] text-emerald-600">Score 7.5 + Ahorro alto</div>
<div className="text-[9px] text-emerald-500 font-medium"> Prioridad 1</div>
<div className="font-bold text-emerald-700">🎯 {t('roadmap.quadrantQuickWins')}</div>
<div className="text-[9px] text-emerald-600">{t('roadmap.quadrantQuickWinsDesc')}</div>
<div className="text-[9px] text-emerald-500 font-medium">{t('roadmap.quadrantQuickWinsPriority')}</div>
</div>
{/* 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="font-bold text-amber-700"> OPTIMIZE</div>
<div className="text-[9px] text-amber-600">Score &lt;7.5 + Ahorro alto</div>
<div className="text-[9px] text-amber-500 font-medium"> Wave 1 primero</div>
<div className="font-bold text-amber-700"> {t('roadmap.quadrantOptimize')}</div>
<div className="text-[9px] text-amber-600">{t('roadmap.quadrantOptimizeDesc')}</div>
<div className="text-[9px] text-amber-500 font-medium">{t('roadmap.quadrantOptimizePriority')}</div>
</div>
{/* 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="font-bold text-blue-700">📊 STRATEGIC</div>
<div className="text-[9px] text-blue-600">Score 7.5 + Ahorro bajo</div>
<div className="text-[9px] text-blue-500 font-medium"> Evaluar ROI</div>
<div className="font-bold text-blue-700">📊 {t('roadmap.quadrantStrategic')}</div>
<div className="text-[9px] text-blue-600">{t('roadmap.quadrantStrategicDesc')}</div>
<div className="text-[9px] text-blue-500 font-medium">{t('roadmap.quadrantStrategicPriority')}</div>
</div>
{/* 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="font-bold text-gray-600">📋 DEFER</div>
<div className="text-[9px] text-gray-500">Score &lt;7.5 + Ahorro bajo</div>
<div className="text-[9px] text-gray-400 font-medium"> Backlog</div>
<div className="font-bold text-gray-600">📋 {t('roadmap.quadrantDefer')}</div>
<div className="text-[9px] text-gray-500">{t('roadmap.quadrantDeferDesc')}</div>
<div className="text-[9px] text-gray-400 font-medium">{t('roadmap.quadrantDeferPriority')}</div>
</div>
{/* Bubbles */}
@@ -629,19 +632,19 @@ function OpportunityBubbleChart({
<div className="font-semibold text-sm">{item.name}</div>
<div className="mt-1 space-y-0.5">
<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>
</div>
<div className="flex items-center gap-2">
<span className="text-gray-400">Volumen:</span>
<span className="font-medium">{item.volume.toLocaleString()}/mes</span>
<span className="text-gray-400">{t('roadmap.tooltipVolume')}</span>
<span className="font-medium">{item.volume.toLocaleString()}{t('roadmap.perMonth')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-gray-400">Ahorro TCO:</span>
<span className="font-medium text-emerald-400">{formatCurrency(item.economicImpact)}/año</span>
<span className="text-gray-400">{t('roadmap.tooltipSavingsTco')}</span>
<span className="font-medium text-emerald-400">{formatCurrency(item.economicImpact)}{t('roadmap.perYear')}</span>
</div>
<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
className="px-1.5 py-0.5 rounded text-[10px] font-medium"
style={{ backgroundColor: tierColor.fill }}
@@ -705,12 +708,11 @@ function OpportunityBubbleChart({
{/* Leyenda por Tier */}
<div className="mt-4 p-3 bg-gray-50 rounded-lg border border-gray-200">
<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)
son Quick Wins para automatización. El tamaño indica volumen de interacciones.
<span className="font-semibold" style={{ color: '#6d84e3' }}>{t('roadmap.interpretation')}</span> {t('roadmap.opportunityMapInterpretation')}
</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 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 className="flex items-center gap-1">
<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">
<h4 className="text-sm font-semibold text-gray-800 mb-3 flex items-center gap-2">
<Info className="w-4 h-4 text-[#6D84E3]" />
Metodología de Cálculo
{t('roadmap.methodologyTitle')}
</h4>
{/* Ejes */}
<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">
<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">
Score Agentic Readiness calculado con 5 factores ponderados:
{t('roadmap.axisXFactibilityDesc')}
</p>
<ul className="text-[10px] text-gray-500 space-y-1 ml-2">
<li> <strong>Predictibilidad (30%)</strong>: basado en CV AHT</li>
<li> <strong>Resolutividad (25%)</strong>: FCR (60%) + Transfer (40%)</li>
<li> <strong>Volumen (25%)</strong>: escala logarítmica del volumen</li>
<li> <strong>Calidad Datos (10%)</strong>: % registros válidos</li>
<li> <strong>Simplicidad (10%)</strong>: basado en AHT</li>
<li> <strong>{t('roadmap.factorPredictability')}</strong>: {t('roadmap.factorPredictabilityDesc')}</li>
<li> <strong>{t('roadmap.factorResolution')}</strong>: {t('roadmap.factorResolutionDesc')}</li>
<li> <strong>{t('roadmap.factorVolumeWeight')}</strong>: {t('roadmap.factorVolumeDesc')}</li>
<li> <strong>{t('roadmap.factorDataQuality')}</strong>: {t('roadmap.factorDataQualityDesc')}</li>
<li> <strong>{t('roadmap.factorSimplicity')}</strong>: {t('roadmap.factorSimplicityDesc')}</li>
</ul>
</div>
<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">
Ahorro TCO calculado según tier con CPI diferencial:
{t('roadmap.axisYEconomicImpactDesc')}
</p>
<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]">
@@ -778,49 +780,49 @@ function OpportunityBubbleChart({
{/* Fórmulas por Tier */}
<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="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>
<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">
Ahorro = Vol × 12 × <strong>70%</strong> × (2.33 - 0.15)
{t('roadmap.formulaAutomateCalc')}
</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 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>
<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">
Ahorro = Vol × 12 × <strong>30%</strong> × (2.33 - 1.50)
{t('roadmap.formulaAssistCalc')}
</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 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>
<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">
Ahorro = Vol × 12 × <strong>15%</strong> × (2.33 - 2.00)
{t('roadmap.formulaAugmentCalc')}
</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 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>
<p className="font-semibold text-red-700">HUMAN-ONLY (Score &lt; 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">
Ahorro = <strong>0</strong>
{t('roadmap.formulaHumanOnlyCalc')}
</p>
<p className="text-gray-500">Requiere estandarización previa</p>
<p className="text-gray-500">{t('roadmap.formulaHumanOnlyRequires')}</p>
</div>
</div>
</div>
@@ -828,7 +830,7 @@ function OpportunityBubbleChart({
{/* Clasificación de Tier */}
<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="p-2 bg-emerald-50 rounded border border-emerald-200">
<p className="font-semibold text-emerald-700">AUTOMATE</p>
@@ -871,9 +873,7 @@ function OpportunityBubbleChart({
{/* Nota metodológica */}
<p className="text-[10px] text-gray-500 mt-3 italic">
<strong>Nota:</strong> 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.
<strong>{t('roadmap.methodologicalNote')}</strong> {t('roadmap.methodologicalNoteText')}
</p>
</div>
</div>
@@ -954,6 +954,7 @@ function WaveCard({
exitCriteria?: WaveExitCriteria;
priorityQueues?: PriorityQueue[];
}) {
const { t } = useTranslation();
const [expanded, setExpanded] = React.useState(false);
const margenAnual = wave.ahorroAnual - wave.costoRecurrenteAnual;
@@ -990,7 +991,7 @@ function WaveCard({
<h3 className="font-bold text-gray-800">{wave.titulo}</h3>
{wave.esCondicional && (
<span className="text-[10px] bg-amber-200 text-amber-800 px-2 py-0.5 rounded-full font-medium">
Condicional
{t('roadmap.conditional')}
</span>
)}
</div>
@@ -998,7 +999,7 @@ function WaveCard({
</div>
</div>
<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>
</div>
</div>
@@ -1056,7 +1057,7 @@ function WaveCard({
{/* Por qué es necesario */}
<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>
</div>
@@ -1169,7 +1170,7 @@ function WaveCard({
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"
>
{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" />}
</button>
@@ -1678,6 +1679,8 @@ function RoadmapTimeline({ waves }: { waves: WaveData[] }) {
// ========== COMPONENTE PRINCIPAL: ROADMAP TAB ==========
export function RoadmapTab({ data }: RoadmapTabProps) {
const { t } = useTranslation();
// Analizar datos de heatmap para determinar skills listos
const heatmapData = data.heatmapData || [];
@@ -2266,10 +2269,10 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<div className="px-5 py-4 border-b border-gray-200">
<h3 className="font-semibold text-gray-900 flex items-center gap-2">
<Target className="w-5 h-5 text-blue-600" />
Clasificación por Potencial de Automatización
{t('roadmap.classificationByAutomationTier')}
</h3>
<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>
</div>
@@ -2283,20 +2286,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<Rocket className="w-4 h-4 text-white" />
</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>
</div>
</div>
<div className="space-y-1">
<p className="text-2xl font-bold text-emerald-700">{tierCounts.AUTOMATE.length}</p>
<p className="text-[10px] text-emerald-600">
{tierVolumes.AUTOMATE.toLocaleString()} int/mes
{tierVolumes.AUTOMATE.toLocaleString()} {t('roadmap.intPerMonth')}
</p>
<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 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>
</div>
</div>
@@ -2308,20 +2311,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<Bot className="w-4 h-4 text-white" />
</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>
</div>
</div>
<div className="space-y-1">
<p className="text-2xl font-bold text-blue-700">{tierCounts.ASSIST.length}</p>
<p className="text-[10px] text-blue-600">
{tierVolumes.ASSIST.toLocaleString()} int/mes
{tierVolumes.ASSIST.toLocaleString()} {t('roadmap.intPerMonth')}
</p>
<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 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>
</div>
</div>
@@ -2333,20 +2336,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<TrendingUp className="w-4 h-4 text-white" />
</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>
</div>
</div>
<div className="space-y-1">
<p className="text-2xl font-bold text-amber-700">{tierCounts.AUGMENT.length}</p>
<p className="text-[10px] text-amber-600">
{tierVolumes.AUGMENT.toLocaleString()} int/mes
{tierVolumes.AUGMENT.toLocaleString()} {t('roadmap.intPerMonth')}
</p>
<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 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>
</div>
</div>
@@ -2358,20 +2361,20 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<Users className="w-4 h-4 text-white" />
</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>
</div>
</div>
<div className="space-y-1">
<p className="text-2xl font-bold text-red-700">{tierCounts['HUMAN-ONLY'].length}</p>
<p className="text-[10px] text-red-600">
{tierVolumes['HUMAN-ONLY'].toLocaleString()} int/mes
{tierVolumes['HUMAN-ONLY'].toLocaleString()} {t('roadmap.intPerMonth')}
</p>
<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 className="text-xs font-semibold text-red-700 pt-1 border-t border-red-200">
0/año (Red flags)
{t('roadmap.noSavingsRedFlags')}
</p>
</div>
</div>
@@ -2379,7 +2382,7 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
{/* Barra de distribución visual */}
<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">
{tierVolumes.AUTOMATE > 0 && (
<div
@@ -2501,16 +2504,16 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
// Configuración simplificada por tipo
const typeConfig = {
DUAL: {
label: 'Nuestra Recomendación: Estrategia Dual',
sublabel: 'Ejecutar dos líneas de trabajo en paralelo para maximizar el impacto'
label: t('roadmap.dualStrategyLabel'),
sublabel: t('roadmap.dualStrategySublabel')
},
FOUNDATION: {
label: 'Nuestra Recomendación: Foundation First',
sublabel: 'Preparar la operación antes de automatizar'
label: t('roadmap.foundationFirstLabel'),
sublabel: t('roadmap.foundationFirstSublabel')
},
STANDARDIZATION: {
label: 'Nuestra Recomendación: Estandarización',
sublabel: 'Resolver problemas operativos críticos antes de invertir en IA'
label: t('roadmap.standardizationLabel'),
sublabel: t('roadmap.standardizationSublabel')
}
};
@@ -2543,35 +2546,32 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<>
{/* Explicación */}
<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">
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.
{t('roadmap.foundationExplanation')}
</p>
</div>
<p className="text-sm text-gray-600 mb-3">
{tierCounts.ASSIST.length} colas ASSIST ({Math.round(assistPct)}% del volumen)
pueden elevarse a Tier AUTOMATE tras completar Wave 1-2.
{t('roadmap.assistQueuesCanElevate', { count: tierCounts.ASSIST.length, pct: Math.round(assistPct) })}
</p>
<div className="grid grid-cols-3 gap-4 text-sm border-t border-gray-100 pt-3">
<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>
</div>
<div>
<p className="text-xs text-gray-500">Timeline</p>
<p className="font-semibold text-gray-800">6-9 meses</p>
<p className="text-xs text-gray-500">{t('roadmap.timeline')}</p>
<p className="font-semibold text-gray-800">6-9 {t('roadmap.months', { count: 6 }).toLowerCase()}</p>
</div>
<div>
<p className="text-xs text-gray-500">Ahorro habilitado</p>
<p className="font-semibold text-gray-800">{formatCurrency(potentialSavings.ASSIST)}/año</p>
<p className="text-xs text-gray-500">{t('roadmap.enabledSavings')}</p>
<p className="font-semibold text-gray-800">{formatCurrency(potentialSavings.ASSIST)}{t('roadmap.perYear')}</p>
</div>
</div>
<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>
</>
)}
@@ -2581,51 +2581,48 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<>
{/* Explicación */}
<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">
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.
{t('roadmap.standardizationExplanation')}
</p>
</div>
<p className="text-sm text-gray-600 mb-3">
{Math.round(humanOnlyPct + augmentPct)}% del volumen presenta red flags (CV &gt;75%, Transfer &gt;20%).
Wave 1 es una inversión habilitadora sin retorno directo inmediato.
{t('roadmap.volumeWithRedFlags', { pct: Math.round(humanOnlyPct + augmentPct) })}
</p>
<div className="grid grid-cols-3 gap-4 text-sm border-t border-gray-100 pt-3">
<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>
</div>
<div>
<p className="text-xs text-gray-500">Timeline</p>
<p className="font-semibold text-gray-800">3-4 meses</p>
<p className="text-xs text-gray-500">{t('roadmap.timeline')}</p>
<p className="font-semibold text-gray-800">3-4 {t('roadmap.months', { count: 3 }).toLowerCase()}</p>
</div>
<div>
<p className="text-xs text-gray-500">Ahorro directo</p>
<p className="font-semibold text-gray-500">0 (habilitador)</p>
<p className="text-xs text-gray-500">{t('roadmap.directSavings')}</p>
<p className="font-semibold text-gray-500">{t('roadmap.enablingNoDirectSavings')}</p>
</div>
</div>
<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>
</>
)}
{/* Siguiente paso */}
<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">
{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' && (
<>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' && (
<>Realizar workshop de diagnóstico operacional para identificar las causas raíz de los red flags antes de planificar inversiones.</>
t('roadmap.nextStepStandardization')
)}
</p>
</div>
@@ -2653,13 +2650,13 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
<div className="flex items-center gap-3">
<BookOpen className="w-5 h-5 text-blue-500" />
<div className="text-left">
<h3 className="font-semibold text-gray-800">Detalle por Wave</h3>
<p className="text-xs text-gray-500">Iniciativas, criterios de entrada/salida, inversión por fase</p>
<h3 className="font-semibold text-gray-800">{t('roadmap.waveDetail')}</h3>
<p className="text-xs text-gray-500">{t('roadmap.waveDetailDescription')}</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-400">
{waveDetailExpanded ? 'Ocultar detalle' : 'Ver detalle'}
{waveDetailExpanded ? t('roadmap.hideDetail') : t('roadmap.viewDetail')}
</span>
{waveDetailExpanded ? (
<ChevronUp className="w-5 h-5 text-gray-400" />
@@ -2678,7 +2675,7 @@ export function RoadmapTab({ data }: RoadmapTabProps) {
onClick={() => setShowAllWaves(!showAllWaves)}
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" />}
</button>
</div>

View File

@@ -390,7 +390,149 @@
"foundationFirst": "Foundation First",
"foundationFirstDesc": "Prepare the operation before automating",
"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": {
"viewCriticalActions": "View Critical Actions",
@@ -852,5 +994,324 @@
"november": "November",
"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 &lt;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": "&lt;8%",
"abandonmentMedium": "8-15%",
"abandonmentHigh": "&gt;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 &lt;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": "&lt;45%",
"fcrMedium": "45-65%",
"fcrHigh": "&gt;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"
}
}
}

View File

@@ -390,7 +390,149 @@
"foundationFirst": "Foundation First",
"foundationFirstDesc": "Preparar la operación antes de automatizar",
"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": {
"viewCriticalActions": "Ver Acciones Críticas",
@@ -852,5 +994,324 @@
"november": "Noviembre",
"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 &lt;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": "&lt;8%",
"abandonmentMedium": "8-15%",
"abandonmentHigh": "&gt;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 &lt;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": "&lt;45%",
"fcrMedium": "45-65%",
"fcrHigh": "&gt;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"
}
}
}