import React, { useEffect, useState } from 'react'; import { historicalConsultasApi, HistoricalConsulta, HistoricalConsultaFilters, HistoricalConsultasStats } from '../services/api'; import logger from '../utils/logger'; import jsPDF from 'jspdf'; import { useTranslation } from 'react-i18next'; const HistoricalConsultas: React.FC = () => { const { t, i18n } = useTranslation(); const [consultas, setConsultas] = useState([]); const [stats, setStats] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isLoadingStats, setIsLoadingStats] = useState(true); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [total, setTotal] = useState(0); const [isExportingAll, setIsExportingAll] = useState(false); const [expandedIds, setExpandedIds] = useState>(new Set()); const toggleExpand = (id: string) => { setExpandedIds(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; // Filters const [filters, setFilters] = useState({ page: 1, limit: 20, }); // Filter form state const [consultaType, setConsultaType] = useState<'individual' | 'massive' | ''>(''); const [rutFilter, setRutFilter] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const fetchConsultas = async (newFilters: HistoricalConsultaFilters = filters) => { try { setIsLoading(true); setError(null); const response = await historicalConsultasApi.getHistoricalConsultas(newFilters); const filtered = Array.isArray(response.data) ? response.data.filter((c: any) => { const md = c?.metadata || {}; const rd = c?.requestData || md?.requestData || {}; const pepFlag = md?.isPep === true || rd?.isPep === true; const tag = md?.tag || md?.evaluationType; const tagIsPep = typeof tag === 'string' && tag.toLowerCase() === 'pep'; return !(pepFlag || tagIsPep); }) : []; setConsultas(filtered); setCurrentPage(response.pagination.page); setTotalPages(response.pagination.totalPages); setTotal(filtered.length); } catch (err) { setError(err instanceof Error ? err.message : 'Error al cargar las consultas históricas'); logger.error('Error fetching historical consultas:', err); } finally { setIsLoading(false); } }; const fetchStats = async () => { try { setIsLoadingStats(true); const statsData = await historicalConsultasApi.getHistoricalConsultasStats( startDate || undefined, endDate || undefined ); setStats(statsData); } catch (err) { logger.error('Error fetching historical consultas stats:', err); } finally { setIsLoadingStats(false); } }; useEffect(() => { fetchConsultas(); fetchStats(); }, []); const applyQuickTypeFilter = (type: 'individual' | 'massive' | '') => { setConsultaType(type); const newFilters: HistoricalConsultaFilters = { page: 1, limit: 20, ...(type && { consultaType: type as 'individual' | 'massive' }), ...(rutFilter && { rut: rutFilter }), ...(statusFilter && { responseStatus: parseInt(statusFilter) }), ...(startDate && { startDate }), ...(endDate && { endDate }), }; setFilters(newFilters); setCurrentPage(1); fetchConsultas(newFilters); fetchStats(); }; const handleApplyFilters = () => { const newFilters: HistoricalConsultaFilters = { page: 1, limit: 20, ...(consultaType && { consultaType: consultaType as 'individual' | 'massive' }), ...(rutFilter && { rut: rutFilter }), ...(statusFilter && { responseStatus: parseInt(statusFilter) }), ...(startDate && { startDate }), ...(endDate && { endDate }), }; setFilters(newFilters); setCurrentPage(1); fetchConsultas(newFilters); fetchStats(); }; const handleClearFilters = () => { setConsultaType(''); setRutFilter(''); setStatusFilter(''); setStartDate(''); setEndDate(''); const newFilters: HistoricalConsultaFilters = { page: 1, limit: 20, }; setFilters(newFilters); setCurrentPage(1); fetchConsultas(newFilters); fetchStats(); }; const handlePageChange = (page: number) => { const newFilters = { ...filters, page }; setFilters(newFilters); fetchConsultas(newFilters); }; const formatDate = (dateString: string) => { const locale = i18n.language === 'es' ? 'es-CL' : undefined; return new Date(dateString).toLocaleString(locale); }; const formatProcessingTime = (time: unknown) => { const num = typeof time === 'number' ? time : typeof time === 'string' ? Number(time) : NaN; if (!Number.isFinite(num)) return 'N/A'; return `${num.toFixed(0)}ms`; }; const getStatusBadge = (status: number) => { if (status >= 200 && status < 300) { return {t('consultationHistory.status.success')}; } else if (status >= 400 && status < 500) { return {t('consultationHistory.status.clientError')}; } else if (status >= 500) { return {t('consultationHistory.status.serverError')}; } return {status}; }; const getConsultaTypeBadge = (type: string) => { if (type === 'individual') { return {t('consultationHistory.type.individual')}; } else if (type === 'massive') { return {t('consultationHistory.type.massive')}; } return {type}; }; const fetchAllConsultas = async (): Promise => { const allData: HistoricalConsulta[] = []; let page = 1; let totalPagesAll = 1; do { const response = await historicalConsultasApi.getHistoricalConsultas({ ...filters, page, limit: 100, }); const filtered = Array.isArray(response.data) ? response.data.filter((c: any) => { const md = c?.metadata || {}; const rd = c?.requestData || md?.requestData || {}; const pepFlag = md?.isPep === true || rd?.isPep === true; const tag = md?.tag || md?.evaluationType; const tagIsPep = typeof tag === 'string' && tag.toLowerCase() === 'pep'; return !(pepFlag || tagIsPep); }) : []; allData.push(...filtered); totalPagesAll = response.pagination.totalPages; page++; } while (page <= totalPagesAll); return allData; }; const buildPDF = (data: HistoricalConsulta[], totalCount: number): jsPDF => { const doc = new jsPDF('landscape'); doc.setFontSize(20); doc.text(t('consultationHistory.title'), 10, 20); doc.setFontSize(12); doc.text(`${t('consultationHistory.pdf.generatedAt')}: ${new Date().toLocaleString(i18n.language === 'es' ? 'es-CL' : undefined)}`, 10, 35); doc.text(`${t('consultationHistory.pdf.total')}: ${totalCount}`, 10, 45); let filterInfo = ''; if (consultaType) filterInfo += `${t('consultationHistory.filters.type')}: ${consultaType} `; if (rutFilter) filterInfo += `${t('consultationHistory.filters.rut')}: ${rutFilter} `; if (statusFilter) filterInfo += `${t('consultationHistory.filters.status')}: ${statusFilter} `; if (startDate || endDate) filterInfo += `${t('consultationHistory.filters.period')}: ${startDate || t('consultationHistory.filters.start')} - ${endDate || t('consultationHistory.filters.end')} `; if (filterInfo) { doc.setFontSize(10); doc.text(`${t('consultationHistory.pdf.appliedFilters')}: ${filterInfo}`, 10, 55); } const COL = { fecha: 10, usuario: 48, email: 90, tipo: 148, tag: 170, ruts: 198, estado: 228, tiempo: 252, creditos: 278, }; const drawHeaders = (y: number) => { doc.setFontSize(8); doc.setFont('helvetica', 'bold'); doc.text(t('consultationHistory.table.date'), COL.fecha, y); doc.text(t('consultationHistory.table.user'), COL.usuario, y); doc.text('Email', COL.email, y); doc.text(t('consultationHistory.table.type'), COL.tipo, y); doc.text(t('consultationHistory.table.tag'), COL.tag, y); doc.text(t('consultationHistory.table.ruts'), COL.ruts, y); doc.text(t('consultationHistory.table.status'), COL.estado, y); doc.text(t('consultationHistory.table.time'), COL.tiempo, y); doc.text(t('consultationHistory.table.credits'), COL.creditos, y); doc.line(10, y + 3, 287, y + 3); }; let yPosition = filterInfo ? 75 : 65; drawHeaders(yPosition); yPosition += 12; doc.setFont('helvetica', 'normal'); doc.setFontSize(7); data.forEach((consulta) => { if (yPosition > 190) { doc.addPage(); yPosition = 20; drawHeaders(yPosition); yPosition += 12; doc.setFont('helvetica', 'normal'); doc.setFontSize(7); } const fecha = formatDate(consulta.createdAt).split(',')[0]; const usuario = (consulta.userId?.name || 'N/A').substring(0, 18); const email = (consulta.userId?.email || 'N/A').substring(0, 28); const tipo = consulta.consultaType === 'individual' ? t('consultationHistory.type.individual') : t('consultationHistory.type.massive'); const ruts = consulta.consultaType === 'individual' ? (consulta.rut || 'N/A') : (() => { const arr = Array.isArray((consulta as any).ruts) ? (consulta as any).ruts as string[] : []; const shown = arr.slice(0, 3).join(', '); const extra = arr.length > 3 ? ` (+${arr.length - 3})` : ''; return shown ? `${shown}${extra}` : `0 ${t('consultationHistory.table.rutsShort')}`; })(); const estado = consulta.responseStatus >= 200 && consulta.responseStatus < 300 ? 'Éxito' : 'Error'; const tiempoMsRaw = (consulta as { processingTime?: unknown }).processingTime; const tiempoMs = typeof tiempoMsRaw === 'number' ? tiempoMsRaw : typeof tiempoMsRaw === 'string' ? Number(tiempoMsRaw) : NaN; const tiempo = tiempoMs > 31_500_000 ? 'N/A' : formatProcessingTime(tiempoMs); const creditosRaw = (consulta as { creditsUsed?: unknown }).creditsUsed; const creditos = typeof creditosRaw === 'number' || typeof creditosRaw === 'string' ? String(creditosRaw) : '0'; const tag = consulta.metadata ? String(consulta.metadata.tag || consulta.metadata.evaluationType || '-') : '-'; doc.text(fecha, COL.fecha, yPosition); doc.text(usuario, COL.usuario, yPosition); doc.text(email, COL.email, yPosition); doc.text(tipo, COL.tipo, yPosition); doc.text(tag.substring(0, 15), COL.tag, yPosition); doc.text(ruts.substring(0, 12), COL.ruts, yPosition); doc.text(estado, COL.estado, yPosition); doc.text(tiempo, COL.tiempo, yPosition); doc.text(creditos, COL.creditos, yPosition); yPosition += 7; }); if (stats) { yPosition += 10; doc.line(10, yPosition, 287, yPosition); yPosition += 10; doc.setFontSize(10); doc.setFont('helvetica', 'bold'); doc.text('Estadísticas del Período:', 10, yPosition); yPosition += 10; doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.text(`Total de consultas: ${stats.totalConsultas.toLocaleString()}`, 10, yPosition); doc.text(`Créditos utilizados: ${stats.totalCreditsUsed.toLocaleString()}`, 110, yPosition); yPosition += 8; doc.text(`Tiempo promedio: ${formatProcessingTime(stats.averageProcessingTime)}`, 10, yPosition); doc.text(`Consultas individuales: ${(stats.consultasByType.individual || 0).toLocaleString()}`, 110, yPosition); } return doc; }; const exportToPDF = () => { const doc = buildPDF(consultas, total); doc.save(`historial-consultas-${new Date().toISOString().split('T')[0]}.pdf`); }; const exportAllToPDF = async () => { try { setIsExportingAll(true); const allConsultas = await fetchAllConsultas(); const doc = buildPDF(allConsultas, allConsultas.length); doc.save(`historial-completo-${new Date().toISOString().split('T')[0]}.pdf`); } catch (err) { logger.error('Error exportando historial completo:', err); } finally { setIsExportingAll(false); } }; return (

{t('consultationHistory.title')}

{t('consultationHistory.subtitle')}

{/* Statistics Cards */} {stats && !isLoadingStats && (

{t('consultationHistory.cards.totalConsultas')}

{stats.totalConsultas.toLocaleString()}

{t('consultationHistory.cards.creditsUsed')}

{stats.totalCreditsUsed.toLocaleString()}

{t('consultationHistory.cards.averageTime')}

{formatProcessingTime(stats.averageProcessingTime)}

{t('consultationHistory.cards.individualConsultas')}

{(stats.consultasByType.individual || 0).toLocaleString()}

)} {/* Filters */}

{t('consultationHistory.filters.title')}

setRutFilter(e.target.value)} placeholder={t('consultationHistory.filters.rutPlaceholder')} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
setStartDate(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
setEndDate(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
{/* Results Table */}

{t('consultationHistory.resultsTitle', { total: total.toLocaleString() })}

{isLoading ? (
{t('consultationHistory.loading')}
) : error ? (
{t('consultationHistory.errorTitle')}
{error}
) : consultas.length === 0 ? (
{t('consultationHistory.noResults')}
) : ( <>
{consultas.map((consulta) => { const isMassive = consulta.consultaType === 'massive'; const rutsArr = Array.isArray((consulta as any).ruts) ? (consulta as any).ruts as string[] : []; const isOpen = expandedIds.has(consulta._id); return ( {isMassive && isOpen && ( )} ); })}
{t('consultationHistory.table.date')} {t('consultationHistory.table.user')} {t('consultationHistory.table.type')} {t('consultationHistory.table.tag')} {t('consultationHistory.table.ruts')} {t('consultationHistory.table.status')} {t('consultationHistory.table.time')} {t('consultationHistory.table.credits')}
{formatDate(consulta.createdAt)} {consulta.userId ? (
{consulta.userId.name}
{consulta.userId.email}
) : ( Usuario no disponible )}
{getConsultaTypeBadge(consulta.consultaType)} {consulta.metadata && (consulta.metadata.tag || consulta.metadata.evaluationType) ? (consulta.metadata.tag || consulta.metadata.evaluationType) : '-'} {isMassive ? ( ) : ( consulta.rut || '' )} {getStatusBadge(consulta.responseStatus)} {formatProcessingTime(consulta.processingTime)} {consulta.creditsUsed}
{rutsArr.map((rut, idx) => ( ))}
RUT
{rut}
{/* Pagination */} {totalPages > 1 && (
{t('consultationHistory.pagination.info', { currentPage, totalPages, total: total.toLocaleString() })}
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { const page = Math.max(1, Math.min(totalPages - 4, currentPage - 2)) + i; return ( ); })}
)} )}
); }; export default HistoricalConsultas;