import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Scale,
Clock,
Target,
Calendar,
AlertTriangle,
CheckCircle,
XCircle,
HelpCircle,
Lightbulb,
FileText,
TrendingUp,
} from 'lucide-react';
import type { AnalysisData, HeatmapDataPoint, DrilldownDataPoint } from '../../types';
import {
Card,
Badge,
Stat,
} from '../ui';
import {
cn,
STATUS_CLASSES,
formatCurrency,
formatNumber,
} from '../../config/designSystem';
// ============================================
// TIPOS Y CONSTANTES
// ============================================
type ComplianceStatus = 'CUMPLE' | 'PARCIAL' | 'NO_CUMPLE' | 'SIN_DATOS';
interface ComplianceResult {
status: ComplianceStatus;
score: number; // 0-100
gap: string;
details: string[];
}
const LAW_10_2025 = {
deadline: new Date('2026-12-28'),
requirements: {
LAW_07: {
name: 'Cobertura Horaria',
maxOffHoursPct: 15,
},
LAW_01: {
name: 'Velocidad de Respuesta',
maxHoldTimeSeconds: 180,
},
LAW_02: {
name: 'Calidad de Resolucion',
minFCR: 75,
maxTransfer: 15,
},
LAW_09: {
name: 'Cobertura Linguistica',
languages: ['es', 'ca', 'eu', 'gl', 'va'],
},
},
};
// ============================================
// FUNCIONES DE EVALUACION DE COMPLIANCE
// ============================================
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;
if (offHoursPct === null) {
return {
status: 'SIN_DATOS',
score: 0,
gap: t('law10.compliance.law07.noData'),
details: [t('law10.compliance.law07.noDataDetails')],
};
}
const details: string[] = [];
details.push(t('law10.compliance.law07.offHoursPercent', { percent: offHoursPct.toFixed(1) }));
if (offHoursPct < 5) {
return {
status: 'CUMPLE',
score: 100,
gap: '-',
details: [...details, t('law10.compliance.law07.adequateCoverage')],
};
} else if (offHoursPct <= 15) {
return {
status: 'PARCIAL',
score: Math.round(100 - ((offHoursPct - 5) / 10) * 50),
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: t('law10.compliance.law07.gapOverLimit', { gap: (offHoursPct - 15).toFixed(1) }),
details: [...details, t('law10.compliance.law07.insufficientCoverage')],
};
}
}
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);
if (totalVolume === 0) {
return {
status: 'SIN_DATOS',
score: 0,
gap: t('law10.compliance.law01.noData'),
details: [t('law10.compliance.law01.noDataDetails')],
};
}
// Calcular hold_time promedio ponderado por volumen
const avgHoldTime = data.heatmapData.reduce(
(sum, h) => sum + h.metrics.hold_time * h.volume, 0
) / totalVolume;
// Contar colas que exceden el limite
const colasExceden = data.heatmapData.filter(h => h.metrics.hold_time > 180);
const pctColasExceden = (colasExceden.length / data.heatmapData.length) * 100;
// Calcular % de interacciones dentro del limite
const volDentroLimite = data.heatmapData
.filter(h => h.metrics.hold_time <= 180)
.reduce((sum, h) => sum + h.volume, 0);
const pctDentroLimite = (volDentroLimite / totalVolume) * 100;
const details: string[] = [];
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: t('law10.compliance.law01.gapNegative', { gap: Math.round(180 - avgHoldTime) }),
details,
};
} else if (avgHoldTime < 180) {
return {
status: 'PARCIAL',
score: Math.round(90 - pctColasExceden),
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: t('law10.compliance.law01.gapPositive', { gap: Math.round(avgHoldTime - 180) }),
details,
};
}
}
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);
if (totalVolume === 0) {
return {
status: 'SIN_DATOS',
score: 0,
gap: t('law10.compliance.law02.noData'),
details: [t('law10.compliance.law02.noDataDetails')],
};
}
// FCR Tecnico ponderado (comparable con benchmarks)
const avgFCR = data.heatmapData.reduce(
(sum, h) => sum + (h.metrics.fcr_tecnico ?? (100 - h.metrics.transfer_rate)) * h.volume, 0
) / totalVolume;
// Transfer rate ponderado
const avgTransfer = data.heatmapData.reduce(
(sum, h) => sum + h.metrics.transfer_rate * h.volume, 0
) / totalVolume;
const details: string[] = [];
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(t('law10.compliance.law02.highTransferQueues', { count: colasAltoTransfer.length }));
}
const cumpleFCR = avgFCR >= 75;
const cumpleTransfer = avgTransfer <= 15;
const parcialFCR = avgFCR >= 60;
const parcialTransfer = avgTransfer <= 25;
if (cumpleFCR && cumpleTransfer) {
return {
status: 'CUMPLE',
score: 100,
gap: t('law10.compliance.law02.gapDash'),
details,
};
} else if (parcialFCR && parcialTransfer) {
const score = Math.round(
(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: 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 ${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, 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: t('law10.compliance.law09.noData'),
details: [
t('law10.compliance.law09.noLanguageData'),
t('law10.compliance.law09.needsLanguageField'),
],
};
}
// ============================================
// COMPONENTES DE SECCION
// ============================================
interface Law10TabProps {
data: AnalysisData;
}
// Status Icon Component
function StatusIcon({ status }: { status: ComplianceStatus }) {
switch (status) {
case 'CUMPLE':
return ;
case 'PARCIAL':
return ;
case 'NO_CUMPLE':
return ;
default:
return ;
}
}
function getStatusBadgeVariant(status: ComplianceStatus): 'success' | 'warning' | 'critical' | 'default' {
switch (status) {
case 'CUMPLE': return 'success';
case 'PARCIAL': return 'warning';
case 'NO_CUMPLE': return 'critical';
default: return 'default';
}
}
function getStatusLabel(status: ComplianceStatus, t: (key: string) => string): string {
switch (status) {
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');
}
}
// Header con descripcion del analisis
function Law10HeaderCountdown({
complianceResults,
}: {
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();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
// Contar requisitos cumplidos
const results = [complianceResults.law07, complianceResults.law01, complianceResults.law02];
const cumplidos = results.filter(r => r.status === 'CUMPLE').length;
const total = results.length;
// Determinar estado general
const getOverallStatus = () => {
if (results.every(r => r.status === 'CUMPLE')) return 'CUMPLE';
if (results.some(r => r.status === 'NO_CUMPLE')) return 'NO_CUMPLE';
return 'PARCIAL';
};
const overallStatus = getOverallStatus();
return (
{/* Header */}
{t('law10.header.aboutThisAnalysis')}
{t('law10.header.lawTitle')}
{/* Descripcion */}
{/* Metricas de estado */}
{/* Deadline */}
{t('law10.header.complianceDeadline')}
{t('law10.header.december282026')}
{t('law10.header.daysRemaining', { days: diffDays })}
{/* Requisitos evaluados */}
{t('law10.header.requirementsEvaluated')}
{t('law10.header.requirementsMet', { met: cumplidos, total })}
{t('law10.header.basedOnData')}
{/* Estado general */}
{t('law10.header.overallStatus')}
{getStatusLabel(overallStatus, t)}
{overallStatus === 'CUMPLE' ? t('law10.header.goodState') :
overallStatus === 'PARCIAL' ? t('law10.header.requiresAttention') : t('law10.header.urgentAction')}
);
}
// 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 || [];
const totalVolume = data.heatmapData.reduce((sum, h) => sum + h.volume, 0);
// Calcular metricas detalladas
const hourlyTotal = hourlyData.reduce((sum, v) => sum + v, 0);
const nightVolume = hourlyData.slice(22).concat(hourlyData.slice(0, 8)).reduce((sum, v) => sum + v, 0);
const nightPct = hourlyTotal > 0 ? (nightVolume / hourlyTotal) * 100 : 0;
const earlyMorningVolume = hourlyData.slice(0, 6).reduce((sum, v) => sum + v, 0);
const earlyMorningPct = hourlyTotal > 0 ? (earlyMorningVolume / hourlyTotal) * 100 : 0;
// Encontrar hora pico
const maxHourIndex = hourlyData.indexOf(Math.max(...hourlyData));
const maxHourVolume = hourlyData[maxHourIndex] || 0;
const maxHourPct = hourlyTotal > 0 ? (maxHourVolume / hourlyTotal) * 100 : 0;
// Dias de la semana
const dayNames = ['Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa', 'Do'];
// Generar datos de heatmap 7x24 (simulado basado en hourly y daily)
const generateHeatmapData = () => {
const heatmap: number[][] = [];
const maxHourly = Math.max(...hourlyData, 1);
for (let day = 0; day < 7; day++) {
const dayRow: number[] = [];
const dayMultiplier = dailyData[day] ? dailyData[day] / Math.max(...dailyData, 1) : (day < 5 ? 1 : 0.6);
for (let hour = 0; hour < 24; hour++) {
const hourValue = hourlyData[hour] || 0;
const normalizedValue = (hourValue / maxHourly) * dayMultiplier;
dayRow.push(normalizedValue);
}
heatmap.push(dayRow);
}
return heatmap;
};
const heatmapData = generateHeatmapData();
// Funcion para obtener el caracter de barra segun intensidad
const getBarChar = (value: number): string => {
if (value < 0.1) return '▁';
if (value < 0.25) return '▂';
if (value < 0.4) return '▃';
if (value < 0.55) return '▄';
if (value < 0.7) return '▅';
if (value < 0.85) return '▆';
if (value < 0.95) return '▇';
return '█';
};
// Funcion para obtener color segun intensidad
const getBarColor = (value: number): string => {
if (value < 0.2) return 'text-blue-200';
if (value < 0.4) return 'text-blue-300';
if (value < 0.6) return 'text-blue-400';
if (value < 0.8) return 'text-blue-500';
return 'text-blue-600';
};
return (
{/* Header */}
{t('law10.timeCoverage.title')}
{t('law10.timeCoverage.article')}
{/* Lo que sabemos */}
{t('law10.timeCoverage.whatWeKnow')}
{/* Heatmap 24x7 */}
{t('law10.timeCoverage.heatmap247')}
{/* Header de horas */}
{[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22].map(h => (
{h.toString().padStart(2, '0')}
))}
{/* Filas por dia */}
{heatmapData.map((dayRow, dayIdx) => (
{dayNames[dayIdx]}
{dayRow.map((value, hourIdx) => (
{getBarChar(value)}
))}
))}
{/* Leyenda */}
{t('law10.timeCoverage.intensity')}
▁ {t('law10.timeCoverage.intensityLow')}
▄ {t('law10.timeCoverage.intensityMedium')}
█ {t('law10.timeCoverage.intensityHigh')}
{/* Hallazgos operacionales */}
{t('law10.timeCoverage.operationalFindings')}
{/* Implicacion Ley 10/2025 */}
{t('law10.timeCoverage.lawImplication')}
{t('law10.timeCoverage.basicServiceRequirement')}
{t('law10.timeCoverage.article14Requirement')}
{t('law10.timeCoverage.gapIdentified')}
{/* Accion sugerida */}
{t('law10.timeCoverage.suggestedAction')}
{t('law10.timeCoverage.classifyNightVolume')}
- • {t('law10.timeCoverage.criticalIncidents')}
- • {t('law10.timeCoverage.generalQueries')}
{t('law10.timeCoverage.coverageOptions')}
{t('law10.timeCoverage.optionAChatbot')}
{t('law10.timeCoverage.costPerYear', { cost: '65K' })}
{t('law10.timeCoverage.optionBExternal')}
{t('law10.timeCoverage.costPerYear', { cost: '95K' })}
{t('law10.timeCoverage.optionCNight')}
{t('law10.timeCoverage.costPerYear', { cost: '180K' })}
);
}
// 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 || [];
// Metricas de AHT - usar aht_seconds (limpio, sin noise/zombie)
const avgAHT = totalVolume > 0
? data.heatmapData.reduce((sum, h) => sum + h.aht_seconds * h.volume, 0) / totalVolume
: 0;
// Calcular AHT P50 y P90 aproximados desde drilldown
let ahtP50 = avgAHT;
let ahtP90 = avgAHT * 1.8;
if (data.drilldownData && data.drilldownData.length > 0) {
const allAHTs = data.drilldownData.flatMap(d =>
d.originalQueues?.map(q => q.aht_mean) || []
).filter(v => v > 0);
if (allAHTs.length > 0) {
allAHTs.sort((a, b) => a - b);
ahtP50 = allAHTs[Math.floor(allAHTs.length * 0.5)] || avgAHT;
ahtP90 = allAHTs[Math.floor(allAHTs.length * 0.9)] || avgAHT * 1.8;
}
}
const ahtRatio = ahtP50 > 0 ? ahtP90 / ahtP50 : 1;
// Tasa de abandono - usar abandonment_rate (campo correcto)
const abandonRate = totalVolume > 0
? data.heatmapData.reduce((sum, h) => sum + (h.metrics.abandonment_rate || 0) * h.volume, 0) / totalVolume
: 0;
// Generar datos de abandono por hora (simulado basado en volumetria)
const hourlyAbandonment = hourlyData.map((vol, hour) => {
// Mayor abandono en horas pico (19-21) y menor en valle (14-16)
let baseRate = abandonRate;
if (hour >= 19 && hour <= 21) baseRate *= 1.5;
else if (hour >= 14 && hour <= 16) baseRate *= 0.6;
else if (hour >= 9 && hour <= 11) baseRate *= 1.2;
return { hour, volume: vol, abandonRate: Math.min(baseRate, 35) };
});
// Encontrar patrones
const maxAbandonHour = hourlyAbandonment.reduce((max, h) =>
h.abandonRate > max.abandonRate ? h : max, hourlyAbandonment[0]);
const minAbandonHour = hourlyAbandonment.reduce((min, h) =>
h.abandonRate < min.abandonRate && h.volume > 0 ? h : min, hourlyAbandonment[0]);
// Funcion para obtener el caracter de barra segun tasa de abandono
const getBarChar = (rate: number): string => {
if (rate < 5) return '▁';
if (rate < 10) return '▂';
if (rate < 15) return '▃';
if (rate < 20) return '▅';
if (rate < 25) return '▆';
return '█';
};
// Funcion para obtener color segun tasa de abandono
const getAbandonColor = (rate: number): string => {
if (rate < 8) return 'text-emerald-500';
if (rate < 12) return 'text-amber-400';
if (rate < 18) return 'text-orange-500';
return 'text-red-500';
};
// Estimacion conservadora
const estimatedFastResponse = Math.max(0, 100 - abandonRate - 7);
const gapVs95 = 95 - estimatedFastResponse;
return (
{/* Header */}
{t('law10.responseSpeed.title')}
{t('law10.responseSpeed.article')}
{/* Lo que sabemos */}
{t('law10.responseSpeed.whatWeKnow')}
{/* Metricas principales */}
{abandonRate.toFixed(1)}%
{t('law10.responseSpeed.abandonmentRate')}
{Math.round(ahtP50)}s
{t('law10.responseSpeed.ahtP50', { min: Math.floor(ahtP50 / 60), sec: Math.round(ahtP50 % 60) })}
{Math.round(ahtP90)}s
{t('law10.responseSpeed.ahtP90', { min: Math.floor(ahtP90 / 60), sec: Math.round(ahtP90 % 60) })}
2 ? 'bg-amber-50' : 'bg-gray-50'
)}>
2 ? 'text-amber-600' : 'text-gray-900'
)}>{ahtRatio.toFixed(1)}
{t('law10.responseSpeed.ratioP90P50', { elevated: ahtRatio > 2 ? t('law10.responseSpeed.elevated') : '' })}
{/* Grafico de abandonos por hora */}
{t('law10.responseSpeed.abandonmentByHour')}
{hourlyAbandonment.map((h, idx) => (
{getBarChar(h.abandonRate)}
))}
00:00
06:00
12:00
18:00
24:00
{t('law10.responseSpeed.abandonmentLegend')}
{/* Patrones observados */}
{t('law10.responseSpeed.patternsObserved')}
{/* Implicacion Ley 10/2025 */}
{t('law10.responseSpeed.lawImplication')}
{t('law10.responseSpeed.dataLimitation')}
{t('law10.responseSpeed.noAsaData')}
{t('law10.responseSpeed.butWeKnow')}
-
•
-
•
{t('law10.responseSpeed.highVariability', { ratio: ahtRatio.toFixed(1) })}
-
•
{t('law10.responseSpeed.peaksCoincide')}
{t('law10.responseSpeed.conservativeEstimate')}
0 ? 'text-red-600' : 'text-emerald-600'
)} dangerouslySetInnerHTML={{ __html: t('law10.responseSpeed.gapVs95', { operator: gapVs95 > 0 ? '-' : '+', gap: Math.abs(gapVs95).toFixed(0) }) }} />
{/* Accion sugerida */}
{t('law10.responseSpeed.suggestedAction')}
{t('law10.responseSpeed.shortTerm')}
- • {t('law10.responseSpeed.dimension2Identifies')}
- {t('law10.responseSpeed.highAht', { aht: Math.round(ahtP50) })}
- {t('law10.responseSpeed.copilotOpportunity')}
- • {t('law10.responseSpeed.dualBenefit')}
{t('law10.responseSpeed.mediumTerm')}
{t('law10.responseSpeed.platformConfig')}
5-8K
{t('law10.responseSpeed.implementationTimeline')}
{t('law10.responseSpeed.implementationWeeks')}
{t('law10.responseSpeed.benefit')}
);
}
// 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
const avgFCRTecnico = totalVolume > 0
? data.heatmapData.reduce((sum, h) => sum + (h.metrics.fcr_tecnico ?? (100 - h.metrics.transfer_rate)) * h.volume, 0) / totalVolume
: 0;
const avgFCRReal = totalVolume > 0
? data.heatmapData.reduce((sum, h) => sum + h.metrics.fcr * h.volume, 0) / totalVolume
: 0;
// Recontactos (diferencia entre FCR Tecnico y Real)
const recontactRate7d = 100 - avgFCRReal;
// Calcular llamadas repetidas
const repeatCallsPct = Math.min(recontactRate7d * 0.8, 35);
// Datos por skill para el grafico
const skillFCRData = data.heatmapData
.map(h => ({
skill: h.skill,
fcrReal: h.metrics.fcr,
fcrTecnico: h.metrics.fcr_tecnico ?? (100 - h.metrics.transfer_rate),
volume: h.volume,
}))
.sort((a, b) => a.fcrReal - b.fcrReal);
// Top skills con FCR bajo
const lowFCRSkills = skillFCRData
.filter(s => s.fcrReal < 60)
.slice(0, 5);
// Funcion para obtener caracter de barra segun FCR
const getFCRBarChar = (fcr: number): string => {
if (fcr >= 80) return '█';
if (fcr >= 70) return '▇';
if (fcr >= 60) return '▅';
if (fcr >= 50) return '▃';
if (fcr >= 40) return '▂';
return '▁';
};
// Funcion para obtener color segun FCR
const getFCRColor = (fcr: number): string => {
if (fcr >= 75) return 'text-emerald-500';
if (fcr >= 60) return 'text-amber-400';
if (fcr >= 45) return 'text-orange-500';
return 'text-red-500';
};
return (
{/* Header */}
{t('law10.resolutionQuality.title')}
{t('law10.resolutionQuality.article')}
{/* Lo que sabemos */}
{t('law10.resolutionQuality.whatWeKnow')}
{/* Metricas principales */}
= 60 ? 'bg-gray-50' : 'bg-red-50'
)}>
= 60 ? 'text-gray-900' : 'text-red-600'
)}>{avgFCRReal.toFixed(0)}%
{t('law10.resolutionQuality.fcrReal')}
{recontactRate7d.toFixed(0)}%
{t('law10.resolutionQuality.recontactRate7d')}
{repeatCallsPct.toFixed(0)}%
{t('law10.resolutionQuality.repeatCalls')}
{/* Grafico FCR por skill */}
{t('law10.resolutionQuality.fcrBySkill')}
{skillFCRData.slice(0, 8).map((s, idx) => (
{s.skill}
{Array.from({ length: 10 }).map((_, i) => (
{i < Math.round(s.fcrReal / 10) ? getFCRBarChar(s.fcrReal) : '▁'}
))}
{s.fcrReal.toFixed(0)}%
))}
{t('law10.resolutionQuality.fcrLegend')}
{/* Top skills con FCR bajo */}
{lowFCRSkills.length > 0 && (
{t('law10.resolutionQuality.topLowFcr')}
{lowFCRSkills.map((s, idx) => (
-
{idx + 1}.
{s.skill}: {t('law10.resolutionQuality.fcrValue', { fcr: s.fcrReal.toFixed(0) })}
))}
)}
{/* Implicacion Ley 10/2025 */}
{t('law10.resolutionQuality.lawImplication')}
{t('law10.resolutionQuality.dataLimitation')}
{t('law10.resolutionQuality.noCaseTracking')}
{t('law10.resolutionQuality.butWeKnow')}
-
•
-
•
{t('law10.resolutionQuality.fcrGap', { fcr: avgFCRReal.toFixed(0), gap: recontactRate7d.toFixed(0) })}
-
•
{t('law10.resolutionQuality.complexProcesses')}
{t('law10.resolutionQuality.alertSignal')}
{t('law10.resolutionQuality.resolutionTimeRisk')}
{/* Accion sugerida */}
{t('law10.resolutionQuality.suggestedAction')}
{t('law10.resolutionQuality.diagnosis')}
- • {t('law10.resolutionQuality.registerOpenClose')}
- • {t('law10.resolutionQuality.linkInteractions')}
- • {t('law10.resolutionQuality.typology')}
{t('law10.resolutionQuality.crmInvestment')}
15-25K
{t('law10.resolutionQuality.operationalImprovement')}
- • {t('law10.resolutionQuality.dimension3Identifies')}
- {t('law10.resolutionQuality.rootCauses')}
- {t('law10.resolutionQuality.solution')}
- • {t('law10.resolutionQuality.fcrBenefit')}
);
}
// Seccion: Resumen de Cumplimiento
function Law10SummaryRoadmap({
complianceResults,
data,
}: {
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: 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: t('law10.summary.requirements.LAW-01.name'),
description: t('law10.summary.requirements.LAW-01.description'),
result: complianceResults.law01,
},
{
id: 'LAW-02',
name: t('law10.summary.requirements.LAW-02.name'),
description: t('law10.summary.requirements.LAW-02.description'),
result: complianceResults.law02,
},
{
id: 'LAW-03',
name: t('law10.summary.requirements.LAW-03.name'),
description: t('law10.summary.requirements.LAW-03.description'),
result: sinDatos,
},
{
id: 'LAW-04',
name: t('law10.summary.requirements.LAW-04.name'),
description: t('law10.summary.requirements.LAW-04.description'),
result: sinDatos,
},
{
id: 'LAW-05',
name: t('law10.summary.requirements.LAW-05.name'),
description: t('law10.summary.requirements.LAW-05.description'),
result: sinDatos,
},
{
id: 'LAW-06',
name: t('law10.summary.requirements.LAW-06.name'),
description: t('law10.summary.requirements.LAW-06.description'),
result: sinDatos,
},
{
id: 'LAW-07',
name: t('law10.summary.requirements.LAW-07.name'),
description: t('law10.summary.requirements.LAW-07.description'),
result: complianceResults.law07,
},
{
id: 'LAW-08',
name: t('law10.summary.requirements.LAW-08.name'),
description: t('law10.summary.requirements.LAW-08.description'),
result: sinDatos,
},
{
id: 'LAW-09',
name: t('law10.summary.requirements.LAW-09.name'),
description: t('law10.summary.requirements.LAW-09.description'),
result: complianceResults.law09,
},
{
id: 'LAW-10',
name: t('law10.summary.requirements.LAW-10.name'),
description: t('law10.summary.requirements.LAW-10.description'),
result: sinDatos,
},
{
id: 'LAW-11',
name: t('law10.summary.requirements.LAW-11.name'),
description: t('law10.summary.requirements.LAW-11.description'),
result: sinDatos,
},
{
id: 'LAW-12',
name: t('law10.summary.requirements.LAW-12.name'),
description: t('law10.summary.requirements.LAW-12.description'),
result: sinDatos,
},
];
// Calcular inversion estimada basada en datos reales
const estimatedInvestment = () => {
// Base: 3% del coste anual actual o minimo 15K
const currentCost = data.economicModel?.currentAnnualCost || 0;
let base = currentCost > 0 ? Math.max(15000, currentCost * 0.03) : 15000;
// Incrementos por gaps de compliance
if (complianceResults.law01.status === 'NO_CUMPLE') base += currentCost > 0 ? currentCost * 0.01 : 25000;
if (complianceResults.law02.status === 'NO_CUMPLE') base += currentCost > 0 ? currentCost * 0.008 : 20000;
if (complianceResults.law07.status === 'NO_CUMPLE') base += currentCost > 0 ? currentCost * 0.015 : 35000;
return Math.round(base);
};
return (
{t('law10.summary.title')}
{/* Scorecard con todos los requisitos */}
| {t('law10.summaryTable.requirement')} |
{t('law10.summaryTable.description')} |
{t('law10.summaryTable.status')} |
{t('law10.summaryTable.score')} |
{t('law10.summaryTable.gap')} |
{allRequirements.map((req) => (
|
{req.id}
{req.name}
|
{req.description}
|
|
{req.result.status !== 'SIN_DATOS' ? (
= 80 ? 'text-emerald-600' :
req.result.score >= 50 ? 'text-amber-600' : 'text-red-600'
)}>
{req.result.score}
) : (
-
)}
|
{req.result.gap} |
))}
{/* Leyenda */}
{t('law10.summaryTable.legend.complies')}
{t('law10.summaryTable.legend.partial')}
{t('law10.summaryTable.legend.notComply')}
{t('law10.summaryTable.legend.noData')}
{/* Inversion Estimada */}
{t('law10.summaryTable.investment.nonComplianceCost')}
{t('law10.summaryTable.investment.upTo100k')}
{t('law10.summaryTable.investment.potentialFines')}
{t('law10.summaryTable.investment.recommendedInvestment')}
{formatCurrency(estimatedInvestment())}
{t('law10.summaryTable.investment.basedOnOperation')}
{t('law10.summaryTable.investment.complianceRoi')}
{data.economicModel?.roi3yr ? `${Math.round(data.economicModel.roi3yr / 2)}%` : 'Alto'}
{t('law10.summaryTable.investment.avoidSanctions')}
);
}
// Seccion: Resumen de Madurez de Datos
function DataMaturitySummary({ data }: { data: AnalysisData }) {
const { t } = useTranslation();
// Usar datos economicos reales cuando esten disponibles
const currentAnnualCost = data.economicModel?.currentAnnualCost || 0;
const annualSavings = data.economicModel?.annualSavings || 0;
// Datos disponibles
const availableData = [
{ name: t('law10.dataMaturity.items.coverage247'), article: t('law10.dataMaturity.article', { number: '14' }) },
{ name: t('law10.dataMaturity.items.geoDistribution'), article: t('law10.dataMaturity.articlePartial', { number: '15' }) },
{ name: t('law10.dataMaturity.items.resolutionQuality'), article: t('law10.dataMaturity.articleIndirect', { number: '17' }) },
];
// Datos estimables
const estimableData = [
{ name: t('law10.dataMaturity.items.asa3min'), article: t('law10.dataMaturity.article', { number: '8.2' }), error: t('law10.dataMaturity.errorMargin', { margin: '10' }) },
{ name: t('law10.dataMaturity.items.officialLanguages'), article: t('law10.dataMaturity.article', { number: '15' }), error: t('law10.dataMaturity.noDetail') },
];
// Datos no disponibles
const missingData = [
{ name: t('law10.dataMaturity.items.caseResolutionTime'), article: t('law10.dataMaturity.article', { number: '17' }) },
{ name: t('law10.dataMaturity.items.undueBilling'), article: t('law10.dataMaturity.article', { number: '17' }) },
{ name: t('law10.dataMaturity.items.supervisorTransfer'), article: t('law10.dataMaturity.article', { number: '8' }) },
{ name: t('law10.dataMaturity.items.incidentInfo'), article: t('law10.dataMaturity.article', { number: '17' }) },
{ name: t('law10.dataMaturity.items.enacAudit'), article: t('law10.dataMaturity.article', { number: '22' }), note: t('law10.dataMaturity.items.externalContractRequired') },
];
return (
{t('law10.dataMaturity.title')}
{t('law10.dataMaturity.currentLevel')}
{/* Datos disponibles */}
{t('law10.dataMaturity.availableData')}
{availableData.map((item, idx) => (
-
•
{item.name} ({item.article})
))}
{/* Datos estimables */}
{t('law10.dataMaturity.estimableData')}
{estimableData.map((item, idx) => (
-
•
{item.name} ({item.article}) - {item.error}
))}
{/* Datos no disponibles */}
{t('law10.dataMaturity.unavailableData')}
{missingData.map((item, idx) => (
-
•
{item.name} ({item.article})
{item.note && - {item.note}}
))}
{/* Inversion sugerida */}
{t('law10.dataMaturity.investment.title')}
{/* Fase 1 */}
{t('law10.dataMaturity.investment.phase1.title')}
-
{t('law10.dataMaturity.investment.phase1.realAsaTracking')}
5-8K
-
{t('law10.dataMaturity.investment.phase1.ticketingSystem')}
15-25K
-
{t('law10.dataMaturity.investment.phase1.languageEnrichment')}
2K
-
{t('law10.dataMaturity.investment.phase1.subtotal')}
22-35K
{/* Fase 2 */}
{t('law10.dataMaturity.investment.phase2.title')}
-
{t('law10.dataMaturity.investment.phase2.coverage247')}
65K/año
-
{t('law10.dataMaturity.investment.phase2.aiCopilot')}
35K + 8K/mes
-
{t('law10.dataMaturity.investment.phase2.enacAuditor')}
12-18K/año
-
{t('law10.dataMaturity.investment.phase2.subtotalYear1')}
112-118K
{/* Totales - usar datos reales cuando disponibles */}
{t('law10.dataMaturity.investment.totals.totalInvestment')}
{currentAnnualCost > 0 ? formatCurrency(Math.round(currentAnnualCost * 0.05)) : '134-153K'}
{t('law10.dataMaturity.investment.totals.percentAnnualCost')}
{t('law10.dataMaturity.investment.totals.riskAvoided')}
{currentAnnualCost > 0 ? formatCurrency(Math.min(1000000, currentAnnualCost * 0.3)) : '750K-1M'}
{t('law10.dataMaturity.investment.totals.potentialSanctions')}
{t('law10.dataMaturity.investment.totals.complianceRoi')}
{data.economicModel?.roi3yr ? `${data.economicModel.roi3yr}%` : '490-650%'}
);
}
// ============================================
// COMPONENTE PRINCIPAL
// ============================================
export function Law10Tab({ data }: Law10TabProps) {
const { t } = useTranslation();
// Evaluar compliance para cada requisito
const complianceResults = {
law07: evaluateLaw07Compliance(data, t),
law01: evaluateLaw01Compliance(data, t),
law02: evaluateLaw02Compliance(data, t),
law09: evaluateLaw09Compliance(data, t),
};
return (
{/* Header con Countdown */}
{/* Secciones de Analisis - Formato horizontal sin columnas */}
{/* LAW-01: Velocidad de Respuesta */}
{/* LAW-02: Calidad de Resolucion */}
{/* LAW-07: Cobertura Horaria */}
{/* Resumen de Cumplimiento */}
{/* Madurez de Datos para Compliance */}
);
}
export default Law10Tab;