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...
{error}
Revisa la consola del navegador (F12) para más detalles.
Métricas analíticas de entrevistas de salida
{encuestasEnviadas}
{totalRegistros}
{totalRegistros > 0 ? Math.round((regresarian / totalRegistros) * 100) : 0}%
{regresarian} empleados interesados
{totalRegistros > 0 ? Math.round((recomendarian / totalRegistros) * 100) : 0}%
{recomendarian} empleados satisfechos
| 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} ); })()} |