import React, { useState, useEffect, useMemo } from 'react'; import { supabaseData } from '../services/supabaseClient'; import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid } from 'recharts'; import NavBar from '../components/NavBar'; const GLM_COLORS = { positivo: '#6CC24A', neutro: '#F0C75E', negativo: '#FF6A13' }; const CHART_PALETTE = ['#6CC24A', '#4F758B', '#A4D65E', '#5B7F95', '#C4D600', '#6B8FA3']; export default function Dashboard() { const [rawData, setRawData] = useState([]); const [filteredData, setFilteredData] = useState([]); const [encuestasEnviadas, setEncuestasEnviadas] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const opcionesUnicas = (campo) => [ ...new Set( rawData .map(item => (item?.[campo] ?? '').toString().trim()) .filter(v => v !== '') ), ].sort((a, b) => a.localeCompare(b, 'es')); const posiciones = useMemo(() => opcionesUnicas('posicion'), [rawData]); const homologaciones = useMemo(() => opcionesUnicas('homologacion_ia'), [rawData]); const paises = useMemo(() => opcionesUnicas('pais'), [rawData]); const cuentas = useMemo(() => opcionesUnicas('cuenta_proyecto'), [rawData]); const jefes = useMemo(() => opcionesUnicas('jefe_directo'), [rawData]); const [filtroPosicion, setFiltroPosicion] = useState(''); const [filtroHomologacion, setFiltroHomologacion] = useState(''); const [filtroPais, setFiltroPais] = useState(''); const [filtroCuenta, setFiltroCuenta] = useState(''); const [filtroJefe, setFiltroJefe] = useState(''); const [busquedaNombre, setBusquedaNombre] = useState(''); const [paginaActual, setPaginaActual] = useState(1); useEffect(() => { fetchData(); }, []); useEffect(() => { aplicarFiltros(); setPaginaActual(1); }, [filtroPosicion, filtroHomologacion, filtroPais, filtroCuenta, filtroJefe, rawData]); const fetchData = async () => { setLoading(true); setError(null); console.log('[DashboardTerminacion] Iniciando fetch...'); const { data, error: fetchError } = await supabaseData .from('encuesta_terminacion_glm') .select('*') .order('timestamp', { ascending: false }); if (fetchError) { console.error('[DashboardTerminacion] Error de Supabase:', { message: fetchError.message, details: fetchError.details, hint: fetchError.hint, code: fetchError.code, }); setError(fetchError.message || 'Error al cargar los datos de encuestas.'); } else { console.log('[DashboardTerminacion] Datos recibidos:', data?.length ?? 0, 'registros'); setRawData(data ?? []); } const { data: kpiData, error: kpiError } = await supabaseData .from('encuesta_terminacion_kpi_glm') .select('encuestas_enviadas'); if (kpiError) { console.error('[DashboardTerminacion] Error al cargar KPI:', kpiError.message); } else { const totalEnviadas = (kpiData ?? []).reduce( (acc, curr) => acc + (Number(curr.encuestas_enviadas) || 0), 0 ); setEncuestasEnviadas(totalEnviadas); } setLoading(false); }; const aplicarFiltros = () => { let result = rawData; if (filtroPosicion) result = result.filter(item => item.posicion === filtroPosicion); if (filtroHomologacion) result = result.filter(item => item.homologacion_ia === filtroHomologacion); if (filtroPais) result = result.filter(item => item.pais === filtroPais); if (filtroCuenta) result = result.filter(item => item.cuenta_proyecto === filtroCuenta); if (filtroJefe) result = result.filter(item => item.jefe_directo === filtroJefe); // Ordenar por fecha de más reciente a más vieja result = [...result].sort((a, b) => { const dateA = a.timestamp ? new Date(a.timestamp) : 0; const dateB = b.timestamp ? new Date(b.timestamp) : 0; return dateB - dateA; }); setFilteredData(result); }; const filasPorPagina = 10; const totalPaginas = Math.ceil(filteredData.length / filasPorPagina) || 1; const indiceUltimoItem = paginaActual * filasPorPagina; const indicePrimerItem = indiceUltimoItem - filasPorPagina; const datosFiltradosTabla = busquedaNombre.trim() ? filteredData.filter(item => (item?.nombre ?? '').toString().toLowerCase().includes(busquedaNombre.trim().toLowerCase()) ) : filteredData; const totalRegistrosTabla = datosFiltradosTabla.length; const datosPaginados = datosFiltradosTabla.slice(indicePrimerItem, indiceUltimoItem); const totalRegistros = filteredData.length; const porcentajeAprobacion = encuestasEnviadas > 0 ? Math.round((totalRegistros / encuestasEnviadas) * 100) : 0; const esVerdadero = (v) => v === true || String(v).toLowerCase().trim() === 'true'; const regresarian = filteredData.filter(d => esVerdadero(d.considerar_regresar)).length; const recomendarian = filteredData.filter(d => esVerdadero(d.siento_comodo_recomendando)).length; const sentimientoCount = filteredData.reduce((acc, curr) => { const sent = curr.sentimiento_ia || 'neutro'; acc[sent] = (acc[sent] || 0) + 1; return acc; }, {}); const chartDataSentimiento = Object.keys(sentimientoCount).map(key => ({ name: key, value: sentimientoCount[key] })); const pendientes = Math.max(encuestasEnviadas - totalRegistros, 0); const chartDataAprobacion = [ { name: 'Completadas', value: totalRegistros }, { name: 'Pendientes', value: pendientes }, ]; const esFalso = (v) => v === false || String(v).toLowerCase().trim() === 'false'; const chartDataRegresar = [ { name: 'Sí', value: filteredData.filter(d => esVerdadero(d.considerar_regresar)).length }, { name: 'No', value: filteredData.filter(d => esFalso(d.considerar_regresar)).length }, ]; const chartDataRecomendar = [ { name: 'Sí', value: filteredData.filter(d => esVerdadero(d.siento_comodo_recomendando)).length }, { name: 'No', value: filteredData.filter(d => esFalso(d.siento_comodo_recomendando)).length }, ]; const contarPorCampo = (campo) => { const conteo = filteredData.reduce((acc, curr) => { const valor = (curr?.[campo] ?? '').toString().trim() || 'Sin especificar'; acc[valor] = (acc[valor] || 0) + 1; return acc; }, {}); return Object.keys(conteo) .sort((a, b) => conteo[b] - conteo[a]) .map((key, index) => ({ name: key, value: conteo[key], fill: CHART_PALETTE[index % CHART_PALETTE.length], })); }; const chartDataMotivoSalida = contarPorCampo('homologacion_ia'); const chartDataPorPais = contarPorCampo('pais'); const chartDataPorProyecto = contarPorCampo('cuenta_proyecto'); if (loading) return (

Cargando datos...

); if (error) return (
⚠️

Error al cargar datos

{error}

Revisa la consola del navegador (F12) para más detalles.

); return (
{/* HEADER GLM */}
Logo GLM

Dashboard de Encuestas de Terminación

Métricas analíticas de entrevistas de salida

{/* NAVEGACIÓN GLM */} {/* FILTROS GLM */}
{/* KPIs GLM */}

Total Encuestas Enviadas

{encuestasEnviadas}

Total Encuestas Completadas

{totalRegistros}

Consideraría Regresar

{totalRegistros > 0 ? Math.round((regresarian / totalRegistros) * 100) : 0}%

{regresarian} empleados interesados

Recomendaría la Empresa

{totalRegistros > 0 ? Math.round((recomendarian / totalRegistros) * 100) : 0}%

{recomendarian} empleados satisfechos

{/* GRÁFICA GLM */}

Sentimiento del Motivo

`${name} ${(percent * 100).toFixed(0)}%`} > {chartDataSentimiento.map((entry, index) => ( ))}

% de Aprobación

{porcentajeAprobacion}% {totalRegistros} de {encuestasEnviadas}
{/* GRÁFICAS SÍ / NO */}

¿Regresaría a la organización en el futuro?

{chartDataRegresar.map((entry) => ( ))}

Recomendaría a otra persona para trabajar en la organización

{chartDataRecomendar.map((entry) => ( ))}
{/* GRÁFICAS DE SALIDA LABORAL */}

Motivo de Salida Laboral

{chartDataMotivoSalida.map((entry, index) => ( ))}

Salida Laboral Según País

{chartDataPorPais.map((entry, index) => ( ))}

Salida Laboral Según Proyecto

{chartDataPorProyecto.map((entry, index) => ( ))}
{/* TABLA GLM */}

Detalle de Registros

{ setBusquedaNombre(e.target.value); setPaginaActual(1); }} placeholder="Escriba el nombre..." className="w-56 rounded-lg p-2.5 border text-sm text-gris-oscuro bg-amarillo-input focus:bg-white focus:ring-2 focus:ring-verde-glm focus:outline-none" />
{datosPaginados.map((item, idx) => ( ))}
Fecha Nombre País Cliente/Proyecto Posición Jefe Directo PDV Trade Partner Motivo Sentimiento
{new Date(item.timestamp).toLocaleDateString()} {item.nombre} {item.pais} {item.cuenta_proyecto} {item.posicion} {item.jefe_directo} {item.pdv} {item.trade_partner} {item.homologacion_ia} {(() => { const sent = String(item.sentimiento_ia || '').toLowerCase().trim(); let classes = 'bg-gray-100 text-gray-800'; if (sent === 'positivo') { classes = 'bg-verde-ok text-green-700'; } else if (sent === 'negativo') { classes = 'bg-rojo-suave text-red-600'; } else if (sent === 'neutro') { classes = 'bg-amarillo-medio text-yellow-700'; } return ( {item.sentimiento_ia} ); })()}
{/* Controles de Pagina */}
{totalRegistros > 0 ? `Mostrando registros ${indicePrimerItem + 1} - ${Math.min(indiceUltimoItem, totalRegistros)} de ${totalRegistros}` : 'No hay registros para mostrar' }
Página {paginaActual} de {totalPaginas}
); }