Files
dashboard/src/screens/DashboardTerminacion.jsx
T

587 lines
34 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 (
<div className="p-10 text-center">
<div className="inline-block w-10 h-10 border-4 border-verde-glm border-t-transparent rounded-full animate-spin mb-4" />
<p className="text-xl font-semibold text-azul-glm">Cargando datos...</p>
</div>
);
if (error) return (
<div className="p-10 max-w-xl mx-auto min-h-screen flex items-center justify-center">
<div className="bg-white p-8 rounded-2xl border border-red-200 shadow-sm text-center w-full">
<div className="text-red-500 text-4xl mb-4"></div>
<h2 className="text-xl font-bold text-red-700 mb-2">Error al cargar datos</h2>
<p className="text-sm text-gray-600 mb-1">{error}</p>
<p className="text-xs text-gray-400 mb-6">Revisa la consola del navegador (F12) para más detalles.</p>
<button
onClick={fetchData}
className="bg-azul-glm hover:bg-azul-medio text-white font-semibold py-2.5 px-6 rounded-lg transition-colors"
>
Reintentar
</button>
</div>
</div>
);
return (
<div className="p-6 max-w-7xl mx-auto min-h-screen">
{/* HEADER GLM */}
<header className="flex flex-col sm:flex-row items-center gap-4 mb-8 bg-white p-4 rounded-xl border border-gris-medio/30 shadow-sm">
<div className="flex-shrink-0 bg-verde-bg p-2 rounded-lg">
<img
src="https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM_completo.png"
alt="Logo GLM"
className="h-12 w-auto object-contain max-w-[180px]"
/>
</div>
<div className="text-center sm:text-left flex-1">
<h1 className="text-2xl font-bold text-azul-glm tracking-tight">Dashboard de Encuestas de Terminación</h1>
<p className="text-xs text-acero italic">Métricas analíticas de entrevistas de salida</p>
</div>
</header>
{/* NAVEGACIÓN GLM */}
<NavBar />
{/* FILTROS GLM */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8 bg-white p-5 rounded-xl border border-gris-medio/30 shadow-sm">
<div className="flex-1">
<label className="block text-xs font-bold text-azul-glm uppercase tracking-wider mb-1">Filtrar por Posición</label>
<select
className="w-full 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"
value={filtroPosicion}
onChange={(e) => setFiltroPosicion(e.target.value)}
>
<option value="">Todas las posiciones</option>
{posiciones.map((pos, idx) => (
<option key={idx} value={pos}>{pos}</option>
))}
</select>
</div>
<div className="flex-1">
<label className="block text-xs font-bold text-azul-glm uppercase tracking-wider mb-1">Filtrar por Motivo</label>
<select
className="w-full 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"
value={filtroHomologacion}
onChange={(e) => setFiltroHomologacion(e.target.value)}
>
<option value="">Todos los motivos</option>
{homologaciones.map((hom, idx) => (
<option key={idx} value={hom}>{hom}</option>
))}
</select>
</div>
<div className="flex-1">
<label className="block text-xs font-bold text-azul-glm uppercase tracking-wider mb-1">Filtrar por País</label>
<select
className="w-full 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"
value={filtroPais}
onChange={(e) => setFiltroPais(e.target.value)}
>
<option value="">Todos los países</option>
{paises.map((pais, idx) => (
<option key={idx} value={pais}>{pais}</option>
))}
</select>
</div>
<div className="flex-1">
<label className="block text-xs font-bold text-azul-glm uppercase tracking-wider mb-1">Filtrar por Cuenta/Proyecto</label>
<select
className="w-full 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"
value={filtroCuenta}
onChange={(e) => setFiltroCuenta(e.target.value)}
>
<option value="">Todas las cuentas/proyectos</option>
{cuentas.map((cuenta, idx) => (
<option key={idx} value={cuenta}>{cuenta}</option>
))}
</select>
</div>
<div className="flex-1">
<label className="block text-xs font-bold text-azul-glm uppercase tracking-wider mb-1">Filtrar por Jefe Directo</label>
<select
className="w-full 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"
value={filtroJefe}
onChange={(e) => setFiltroJefe(e.target.value)}
>
<option value="">Todos los jefes directos</option>
{jefes.map((jefe, idx) => (
<option key={idx} value={jefe}>{jefe}</option>
))}
</select>
</div>
<div className="flex items-end">
<button
onClick={() => { setFiltroPosicion(''); setFiltroHomologacion(''); setFiltroPais(''); setFiltroCuenta(''); setFiltroJefe(''); }}
className="w-full bg-verde-bg hover:bg-verde-glm hover:text-white text-azul-glm font-semibold text-sm py-2.5 px-5 rounded-lg border border-verde-glm/40 hover:border-verde-glm transition-colors"
>
Limpiar Filtros
</button>
</div>
</div>
{/* KPIs GLM */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm border-l-[6px] border-naranja">
<h2 className="text-xs text-acero uppercase font-bold tracking-wider">Total Encuestas Enviadas</h2>
<p className="text-3xl font-extrabold text-naranja mt-1">{encuestasEnviadas}</p>
</div>
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm border-l-[6px] border-azul-glm">
<h2 className="text-xs text-acero uppercase font-bold tracking-wider">Total Encuestas Completadas</h2>
<p className="text-3xl font-extrabold text-azul-glm mt-1">{totalRegistros}</p>
</div>
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm border-l-[6px] border-verde-glm">
<h2 className="text-xs text-acero uppercase font-bold tracking-wider">Consideraría Regresar</h2>
<p className="text-3xl font-extrabold text-verde-glm mt-1">
{totalRegistros > 0 ? Math.round((regresarian / totalRegistros) * 100) : 0}%
</p>
<p className="text-xs text-acero mt-1">{regresarian} empleados interesados</p>
</div>
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm border-l-[6px] border-azul-medio">
<h2 className="text-xs text-acero uppercase font-bold tracking-wider">Recomendaría la Empresa</h2>
<p className="text-3xl font-extrabold text-azul-medio mt-1">
{totalRegistros > 0 ? Math.round((recomendarian / totalRegistros) * 100) : 0}%
</p>
<p className="text-xs text-acero mt-1">{recomendarian} empleados satisfechos</p>
</div>
</div>
{/* GRÁFICA GLM */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm">
<h3 className="text-base font-bold text-azul-glm mb-4 pb-2 border-b-2 border-verde-glm">Sentimiento del Motivo</h3>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={chartDataSentimiento}
cx="50%"
cy="50%"
outerRadius={80}
fill="#8884d8"
dataKey="value"
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{chartDataSentimiento.map((entry, index) => (
<Cell key={`cell-${index}`} fill={GLM_COLORS[entry.name.toLowerCase()] || CHART_PALETTE[index % CHART_PALETTE.length]} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm">
<h3 className="text-base font-bold text-azul-glm mb-4 pb-2 border-b-2 border-verde-glm">% de Aprobación</h3>
<div className="h-64 relative">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={chartDataAprobacion}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
dataKey="value"
startAngle={90}
endAngle={-270}
>
<Cell fill="#6CC24A" />
<Cell fill="#E2E8F0" />
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
<div className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none" style={{ top: '-2rem' }}>
<span className="text-4xl font-extrabold text-verde-glm">{porcentajeAprobacion}%</span>
<span className="text-xs text-acero">{totalRegistros} de {encuestasEnviadas}</span>
</div>
</div>
</div>
</div>
{/* GRÁFICAS SÍ / NO */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm">
<h3 className="text-base font-bold text-azul-glm mb-4 pb-2 border-b-2 border-verde-glm">¿Regresaría a la organización en el futuro?</h3>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartDataRegresar} layout="vertical" margin={{ top: 10, right: 30, left: 10, bottom: 10 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#E2E8F0" />
<XAxis type="number" allowDecimals={false} tick={{ fontSize: 12, fill: '#4F758B' }} />
<YAxis type="category" dataKey="name" tick={{ fontSize: 13, fill: '#4F758B' }} width={40} />
<Tooltip />
<Bar dataKey="value" name="Respuestas" radius={[0, 4, 4, 0]}>
{chartDataRegresar.map((entry) => (
<Cell key={entry.name} fill={entry.name === 'Sí' ? '#6CC24A' : '#FF6A13'} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm">
<h3 className="text-base font-bold text-azul-glm mb-4 pb-2 border-b-2 border-verde-glm">Recomendaría a otra persona para trabajar en la organización</h3>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartDataRecomendar} layout="vertical" margin={{ top: 10, right: 30, left: 10, bottom: 10 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#E2E8F0" />
<XAxis type="number" allowDecimals={false} tick={{ fontSize: 12, fill: '#4F758B' }} />
<YAxis type="category" dataKey="name" tick={{ fontSize: 13, fill: '#4F758B' }} width={40} />
<Tooltip />
<Bar dataKey="value" name="Respuestas" radius={[0, 4, 4, 0]}>
{chartDataRecomendar.map((entry) => (
<Cell key={entry.name} fill={entry.name === 'Sí' ? '#6CC24A' : '#FF6A13'} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* GRÁFICAS DE SALIDA LABORAL */}
<div className="grid grid-cols-1 gap-6 mb-8">
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm">
<h3 className="text-base font-bold text-azul-glm mb-4 pb-2 border-b-2 border-verde-glm">Motivo de Salida Laboral</h3>
<div className="h-72">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartDataMotivoSalida} margin={{ top: 10, right: 20, left: 10, bottom: 22 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#E2E8F0" />
<XAxis dataKey="name" tick={{ fontSize: 11, fill: '#4F758B' }} interval={0} angle={-20} textAnchor="end" height={60} />
<YAxis allowDecimals={false} tick={{ fontSize: 12, fill: '#4F758B' }} />
<Tooltip />
<Bar dataKey="value" name="Salidas" radius={[4, 4, 0, 0]}>
{chartDataMotivoSalida.map((entry, index) => (
<Cell key={`motivo-${index}`} fill={entry.fill} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm">
<h3 className="text-base font-bold text-azul-glm mb-4 pb-2 border-b-2 border-verde-glm">Salida Laboral Según País</h3>
<div className="h-72">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartDataPorPais} margin={{ top: 10, right: 20, left: 10, bottom: 10 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#E2E8F0" />
<XAxis dataKey="name" tick={{ fontSize: 11, fill: '#4F758B' }} interval={0} angle={-20} textAnchor="end" height={60} />
<YAxis allowDecimals={false} tick={{ fontSize: 12, fill: '#4F758B' }} />
<Tooltip />
<Bar dataKey="value" name="Salidas" radius={[4, 4, 0, 0]}>
{chartDataPorPais.map((entry, index) => (
<Cell key={`pais-${index}`} fill={entry.fill} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-gris-medio/30 shadow-sm">
<h3 className="text-base font-bold text-azul-glm mb-4 pb-2 border-b-2 border-verde-glm">Salida Laboral Según Proyecto</h3>
<div className="h-72">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartDataPorProyecto} margin={{ top: 10, right: 20, left: 10, bottom: 10 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#E2E8F0" />
<XAxis dataKey="name" tick={{ fontSize: 11, fill: '#4F758B' }} interval={0} angle={-20} textAnchor="end" height={60} />
<YAxis allowDecimals={false} tick={{ fontSize: 12, fill: '#4F758B' }} />
<Tooltip />
<Bar dataKey="value" name="Salidas" radius={[4, 4, 0, 0]}>
{chartDataPorProyecto.map((entry, index) => (
<Cell key={`proyecto-${index}`} fill={entry.fill} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* TABLA GLM */}
<div className="bg-white rounded-xl border border-gris-medio/30 shadow-sm overflow-hidden">
<div className="px-6 py-4 border-b border-gris-medio/30 bg-verde-bg flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<h3 className="text-base font-bold text-azul-glm">Detalle de Registros</h3>
<div className="flex items-center gap-2">
<label className="text-xs font-bold text-azul-glm uppercase tracking-wider">Buscar empleado</label>
<input
type="text"
value={busquedaNombre}
onChange={(e) => { 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"
/>
</div>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gris-medio/50 text-sm">
<thead className="bg-azul-glm">
<tr>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Fecha</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Nombre</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">País</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Cliente/Proyecto</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Posición</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Jefe Directo</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">PDV</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Trade Partner</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Motivo</th>
<th className="px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider">Sentimiento</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gris-medio/30">
{datosPaginados.map((item, idx) => (
<tr key={idx} className={`${idx % 2 === 0 ? 'bg-white' : 'bg-gris-claro'} hover:bg-verde-bg/50 transition-colors`}>
<td className="px-6 py-4 whitespace-nowrap text-acero">
{new Date(item.timestamp).toLocaleDateString()}
</td>
<td className="px-6 py-4 whitespace-nowrap font-semibold text-gris-oscuro">{item.nombre}</td>
<td className="px-6 py-4 whitespace-nowrap text-gris-oscuro">{item.pais}</td>
<td className="px-6 py-4 whitespace-nowrap text-gris-oscuro">{item.cuenta_proyecto}</td>
<td className="px-6 py-4 whitespace-nowrap text-gris-oscuro">{item.posicion}</td>
<td className="px-6 py-4 whitespace-nowrap text-gris-oscuro">{item.jefe_directo}</td>
<td className="px-6 py-4 whitespace-nowrap text-gris-oscuro">{item.pdv}</td>
<td className="px-6 py-4 whitespace-nowrap text-gris-oscuro">{item.trade_partner}</td>
<td className="px-6 py-4 text-gris-oscuro max-w-xs truncate">{item.homologacion_ia}</td>
<td className="px-6 py-4 whitespace-nowrap">
{(() => {
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 (
<span className={`px-2.5 py-1 inline-flex text-xs leading-5 font-bold rounded-full ${classes}`}>
{item.sentimiento_ia}
</span>
);
})()}
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Controles de Pagina */}
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 px-6 py-4 border-t border-gris-medio/30 bg-gris-claro/50">
<div className="text-xs text-acero font-medium">
{totalRegistros > 0
? `Mostrando registros ${indicePrimerItem + 1} - ${Math.min(indiceUltimoItem, totalRegistros)} de ${totalRegistros}`
: 'No hay registros para mostrar'
}
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setPaginaActual(prev => Math.max(prev - 1, 1))}
disabled={paginaActual === 1}
className="px-3 py-1.5 rounded-lg border border-gris-medio text-xs font-semibold text-azul-glm bg-white hover:bg-gris-claro disabled:opacity-50 disabled:cursor-not-allowed transition-all cursor-pointer"
>
Atrás
</button>
<span className="text-xs text-gris-oscuro font-bold">
Página {paginaActual} de {totalPaginas}
</span>
<button
onClick={() => setPaginaActual(prev => Math.min(prev + 1, totalPaginas))}
disabled={paginaActual === totalPaginas}
className="px-3 py-1.5 rounded-lg border border-gris-medio text-xs font-semibold text-azul-glm bg-white hover:bg-gris-claro disabled:opacity-50 disabled:cursor-not-allowed transition-all cursor-pointer"
>
Siguiente
</button>
</div>
</div>
</div>
</div>
);
}