import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
X, ShieldCheck, Database, RefreshCw, Tag, BarChart3,
ArrowRight, BadgeCheck, Download, ArrowLeftRight, Layers
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import type { AnalysisData, HeatmapDataPoint } from '../types';
interface MetodologiaDrawerProps {
isOpen: boolean;
onClose: () => void;
data: AnalysisData;
}
interface DataSummary {
totalRegistros: number;
mesesHistorico: number;
periodo: string;
fuente: string;
taxonomia: {
valid: number;
noise: number;
zombie: number;
abandon: number;
};
kpis: {
fcrTecnico: number;
fcrReal: number;
abandonoTradicional: number;
abandonoReal: number;
ahtLimpio: number;
skillsTecnicos: number;
skillsNegocio: number;
};
}
// ========== SUBSECCIONES ==========
function DataSummarySection({ data, t }: { data: DataSummary; t: any }) {
return (
{t('methodology.dataProcessed')}
{data.totalRegistros.toLocaleString('es-ES')}
{t('methodology.recordsAnalyzed')}
{data.mesesHistorico}
{t('methodology.monthsHistory')}
{data.fuente}
{t('methodology.sourceSystem')}
{t('methodology.periodRange', { period: data.periodo })}
);
}
function PipelineSection({ t }: { t: any }) {
const steps = [
{
layer: 'Layer 0',
name: t('methodology.pipeline.layer1'),
desc: t('methodology.pipeline.layer1Desc'),
color: 'bg-gray-100 border-gray-300'
},
{
layer: 'Layer 1',
name: t('methodology.pipeline.layer2'),
desc: t('methodology.pipeline.layer2Desc'),
color: 'bg-yellow-50 border-yellow-300'
},
{
layer: 'Layer 2',
name: t('methodology.pipeline.layer3'),
desc: t('methodology.pipeline.layer3Desc'),
color: 'bg-green-50 border-green-300'
},
{
layer: 'Output',
name: t('methodology.pipeline.layer4'),
desc: t('methodology.pipeline.layer4Desc'),
color: 'bg-blue-50 border-blue-300'
}
];
return (
{t('methodology.pipeline.title')}
{steps.map((step, index) => (
{step.layer}
{step.name}
{step.desc}
{index < steps.length - 1 && (
)}
))}
{t('methodology.pipeline.description')}
);
}
function TaxonomySection({ data, t }: { data: DataSummary['taxonomia']; t: any }) {
const rows = [
{
status: t('methodology.taxonomy.valid'),
pct: data.valid,
def: t('methodology.taxonomy.validDef'),
costes: true,
aht: true,
bgClass: 'bg-green-100 text-green-800'
},
{
status: t('methodology.taxonomy.noise'),
pct: data.noise,
def: t('methodology.taxonomy.noiseDef'),
costes: true,
aht: false,
bgClass: 'bg-yellow-100 text-yellow-800'
},
{
status: t('methodology.taxonomy.zombie'),
pct: data.zombie,
def: t('methodology.taxonomy.zombieDef'),
costes: true,
aht: false,
bgClass: 'bg-red-100 text-red-800'
},
{
status: t('methodology.taxonomy.abandon'),
pct: data.abandon,
def: t('methodology.taxonomy.abandonDef'),
costes: false,
aht: false,
bgClass: 'bg-gray-100 text-gray-800'
}
];
return (
{t('methodology.taxonomy.title')}
{t('methodology.taxonomy.description')}
| {t('methodology.taxonomy.state')} |
{t('methodology.taxonomy.percentage')} |
{t('methodology.taxonomy.definition')} |
{t('methodology.taxonomy.costs')} |
{t('methodology.taxonomy.aht')} |
{rows.map((row, idx) => (
|
{row.status}
|
{row.pct.toFixed(1)}% |
{row.def} |
{row.costes ? (
{t('methodology.taxonomy.sumYes')}
) : (
{t('methodology.taxonomy.sumNo')}
)}
|
{row.aht ? (
{t('methodology.taxonomy.avgYes')}
) : (
{t('methodology.taxonomy.avgExclude')}
)}
|
))}
);
}
function KPIRedefinitionSection({ kpis, t }: { kpis: DataSummary['kpis']; t: any }) {
const formatTime = (seconds: number): string => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
return (
{t('methodology.kpis.title')}
{t('methodology.kpis.description')}
{/* FCR */}
{t('methodology.kpis.fcrTitle')}
{t('methodology.kpis.fcrSubtitle')}
{kpis.fcrReal}%
{t('methodology.kpis.fcrTechnical')}
~{kpis.fcrTecnico}%
{t('methodology.kpis.fcrReal')}
{kpis.fcrReal}%
💡 {t('methodology.kpis.fcrGap', { diff: kpis.fcrTecnico - kpis.fcrReal })}
{/* Abandono */}
{t('methodology.kpis.abandonTitle')}
{t('methodology.kpis.abandonFormula')}
{kpis.abandonoReal.toFixed(1)}%
💡 {t('methodology.kpis.abandonDesc')}
{/* AHT */}
{t('methodology.kpis.ahtTitle')}
{t('methodology.kpis.ahtDesc')}
{formatTime(kpis.ahtLimpio)}
💡 {t('methodology.kpis.ahtNote')}
);
}
function CPICalculationSection({ totalCost, totalVolume, costPerHour = 20, t }: { totalCost: number; totalVolume: number; costPerHour?: number; t: any }) {
// Productivity factor: agents are ~70% productive (rest is breaks, training, after-call work, etc.)
const effectiveProductivity = 0.70;
// CPI = Total Cost / Total Volume
// El coste total ya incluye: TODOS los registros (noise + zombie + valid) y el factor de productividad
const cpi = totalVolume > 0 ? totalCost / totalVolume : 0;
return (
{t('methodology.kpis.cpiTitle')}
{t('methodology.cpi.description', { productivity: (effectiveProductivity * 100).toFixed(0) })}
{/* Fórmula visual */}
{t('methodology.kpis.cpiFormulaTitle')}
{t('methodology.kpis.cpiLabel')}
=
{t('methodology.kpis.totalCost')}
{t('methodology.kpis.divide')}
{t('methodology.kpis.totalVolume')}
{t('methodology.kpis.cpiNote')}
{/* Cómo se calcula el coste total */}
{t('methodology.kpis.howCalculate')}
{t('methodology.kpis.costEquals')}
(AHT seg ÷ 3600)
×
€{costPerHour}/h
×
{t('methodology.cpi.volume')}
÷
{(effectiveProductivity * 100).toFixed(0)}%
{t('methodology.cpi.ahtExplanation')}
{/* Componentes del coste horario */}
{t('methodology.cpi.hourlyRate')}
{t('methodology.cpi.configuredValue', { value: costPerHour.toFixed(2) })}
{t('methodology.cpi.includesAllCosts')}
•
{t('methodology.cpi.cost1')}
•
{t('methodology.cpi.cost2')}
•
{t('methodology.cpi.cost3')}
•
{t('methodology.cpi.cost4')}
•
{t('methodology.cpi.cost5')}
•
{t('methodology.cpi.cost6')}
💡 {t('methodology.cpi.adjustNote')}
);
}
function BeforeAfterSection({ kpis, t }: { kpis: DataSummary['kpis']; t: any }) {
const rows = [
{
metric: t('methodology.impact.fcr'),
tradicional: `${kpis.fcrTecnico}%`,
beyond: `${kpis.fcrReal}%`,
beyondClass: 'text-red-600',
impacto: t('methodology.impact.revealsDemand')
},
{
metric: t('methodology.impact.abandon'),
tradicional: `~${kpis.abandonoTradicional}%`,
beyond: `${kpis.abandonoReal.toFixed(1)}%`,
beyondClass: 'text-yellow-600',
impacto: t('methodology.impact.detectsFrustration')
},
{
metric: t('methodology.impact.skills'),
tradicional: t('methodology.impact.technicalSkills', { count: kpis.skillsTecnicos }),
beyond: t('methodology.impact.businessLines', { count: kpis.skillsNegocio }),
beyondClass: 'text-blue-600',
impacto: t('methodology.impact.executiveVision')
},
{
metric: t('methodology.impact.aht'),
tradicional: t('methodology.impact.distorted'),
beyond: t('methodology.impact.clean'),
beyondClass: 'text-green-600',
impacto: t('methodology.impact.reflectsPerformance')
}
];
return (
{t('methodology.impact.title')}
| {t('methodology.impact.metric')} |
{t('methodology.impact.traditional')} |
{t('methodology.impact.beyond')} |
{t('methodology.impact.impact')} |
{rows.map((row, idx) => (
| {row.metric} |
{row.tradicional} |
{row.beyond} |
{row.impacto} |
))}
💡 {t('methodology.impact.withoutTransformation')} {t('methodology.impact.wrongInvestments')}
);
}
function SkillsMappingSection({ numSkillsNegocio, t }: { numSkillsNegocio: number; t: any }) {
const mappings = [
{
lineaNegocio: t('methodology.skillMapping.baggage'),
keywords: 'HANDLING, EQUIPAJE, AHL (Lost & Found), DPR (Daños)',
color: 'bg-amber-100 text-amber-800'
},
{
lineaNegocio: t('methodology.skillMapping.sales'),
keywords: 'COMPRA, VENTA, RESERVA, PAGO',
color: 'bg-blue-100 text-blue-800'
},
{
lineaNegocio: t('methodology.skillMapping.loyalty'),
keywords: 'SUMA (Programa de Fidelización)',
color: 'bg-purple-100 text-purple-800'
},
{
lineaNegocio: t('methodology.skillMapping.b2b'),
keywords: 'AGENCIAS, AAVV, EMPRESAS, AVORIS, TOUROPERACION',
color: 'bg-cyan-100 text-cyan-800'
},
{
lineaNegocio: t('methodology.skillMapping.changes'),
keywords: 'MODIFICACION, CAMBIO, POSTVENTA, REFUND, REEMBOLSO',
color: 'bg-orange-100 text-orange-800'
},
{
lineaNegocio: t('methodology.skillMapping.digital'),
keywords: 'WEB (Soporte a navegación)',
color: 'bg-indigo-100 text-indigo-800'
},
{
lineaNegocio: t('methodology.skillMapping.customer'),
keywords: 'ATENCION, INFO, OTROS, GENERAL, PREMIUM',
color: 'bg-green-100 text-green-800'
},
{
lineaNegocio: t('methodology.skillMapping.internal'),
keywords: 'COORD, BO_, HELPDESK, BACKOFFICE',
color: 'bg-slate-100 text-slate-800'
}
];
return (
{t('methodology.skillMapping.title')}
{/* Resumen del mapeo */}
{t('methodology.skillMapping.simplificationApplied')}
{t('methodology.skillMapping.reductionDesc', { count: numSkillsNegocio })}
{/* Tabla de mapeo */}
| {t('methodology.skillMapping.businessLine')} |
{t('methodology.skillMapping.keywords')} |
{mappings.map((m, idx) => (
|
{m.lineaNegocio}
|
{m.keywords}
|
))}
💡 {t('methodology.skillMapping.fuzzyLogicNote')}
);
}
function GuaranteesSection({ t }: { t: any }) {
const guarantees = [
{
icon: '✓',
title: t('methodology.quality.traceability'),
desc: t('methodology.quality.traceabilityDesc')
},
{
icon: '✓',
title: t('methodology.quality.formulas'),
desc: t('methodology.quality.formulasDesc')
},
{
icon: '✓',
title: t('methodology.quality.reconciliation'),
desc: t('methodology.quality.reconciliationDesc')
},
{
icon: '✓',
title: t('methodology.quality.replicable'),
desc: t('methodology.quality.replicableDesc')
}
];
return (
{t('methodology.quality.title')}
{guarantees.map((item, i) => (
))}
);
}
// ========== COMPONENTE PRINCIPAL ==========
export function MetodologiaDrawer({ isOpen, onClose, data }: MetodologiaDrawerProps) {
const { t } = useTranslation();
// Calcular datos del resumen desde AnalysisData
const totalRegistros = data.heatmapData?.reduce((sum, h) => sum + h.volume, 0) || 0;
const totalCost = data.heatmapData?.reduce((sum, h) => sum + (h.annual_cost || 0), 0) || 0;
// cost_volume: volumen usado para calcular coste (non-abandon), fallback a volume si no existe
const totalCostVolume = data.heatmapData?.reduce((sum, h) => sum + (h.cost_volume || h.volume), 0) || totalRegistros;
// Calcular meses de histórico desde dateRange
let mesesHistorico = 1;
if (data.dateRange?.min && data.dateRange?.max) {
const minDate = new Date(data.dateRange.min);
const maxDate = new Date(data.dateRange.max);
mesesHistorico = Math.max(1, Math.round((maxDate.getTime() - minDate.getTime()) / (1000 * 60 * 60 * 24 * 30)));
}
// Calcular FCR promedio
const avgFCR = data.heatmapData?.length > 0
? Math.round(data.heatmapData.reduce((sum, h) => sum + (h.metrics?.fcr || 0), 0) / data.heatmapData.length)
: 46;
// Calcular abandono promedio
const avgAbandonment = data.heatmapData?.length > 0
? data.heatmapData.reduce((sum, h) => sum + (h.metrics?.abandonment_rate || 0), 0) / data.heatmapData.length
: 11;
// Calcular AHT promedio
const avgAHT = data.heatmapData?.length > 0
? Math.round(data.heatmapData.reduce((sum, h) => sum + (h.aht_seconds || 0), 0) / data.heatmapData.length)
: 289;
const dataSummary: DataSummary = {
totalRegistros,
mesesHistorico,
periodo: data.dateRange
? `${data.dateRange.min} - ${data.dateRange.max}`
: t('methodology.defaultPeriod'),
fuente: data.source === 'backend' ? t('methodology.sourceGenesys') : t('methodology.sourceDataset'),
taxonomia: {
valid: 94.2,
noise: 3.1,
zombie: 0.8,
abandon: 1.9
},
kpis: {
fcrTecnico: Math.min(87, avgFCR + 30),
fcrReal: avgFCR,
abandonoTradicional: 0,
abandonoReal: avgAbandonment,
ahtLimpio: avgAHT,
skillsTecnicos: 980,
skillsNegocio: data.heatmapData?.length || 9
}
};
const handleDownloadPDF = () => {
// Por ahora, abrir una URL placeholder o mostrar alert
alert(t('methodology.pdfDevelopment'));
// En producción: window.open('/documents/Beyond_Diagnostic_Protocolo_Datos.pdf', '_blank');
};
const formatDate = (): string => {
const now = new Date();
const monthKey = `methodology.months.${['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'][now.getMonth()]}`;
return `${t(monthKey)} ${now.getFullYear()}`;
};
return (
{isOpen && (
<>
{/* Overlay */}
{/* Drawer */}
{/* Header */}
{t('methodology.fullTitle')}
{/* Body - Scrollable */}
{/* Footer */}
{t('methodology.certificate', { date: formatDate() })}
>
)}
);
}
export default MetodologiaDrawer;