import React, { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTenant } from '../../contexts/TenantContext'; import { Search, AlertTriangle, Download, FileText, XCircle, CheckCircle, ChevronDown, ChevronUp, Info } from 'lucide-react'; import { validateChileanRut, formatChileanRut, apiClient } from '../../services/api'; // Assuming apiClient is your configured axios instance // It's better to have specific service functions, but using apiClient directly for now // import { EvaluationService } from '../../services/evaluationService'; // Original, might not be needed for new flow import toast from 'react-hot-toast'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import HumanReadableJson from '../../components/common/HumanReadableJson'; import DuxiterSummaryCard from '../../components/common/DuxiterSummaryCard'; import GeneralInfoCard from '../../components/common/GeneralInfoCard'; import MarkdownResumeCard from '../../components/common/MarkdownResumeCard'; import jsPDF from 'jspdf'; import html2canvas from 'html2canvas'; import { SheriffDataLogResponse } from '../../types/sheriff'; import { logger } from '../../utils/logger'; import { useTranslation } from 'react-i18next'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Alert } from '@mui/material'; // Define types for the resume response and PDF data interface AssociatedPdf { title: string; filename: string; contentType?: string; originalUrl: string; sourcePath: string; downloaded: boolean; contentBase64?: string; // Will be in sheriffLogData.associatedPdfs error?: string; } interface SheriffLogAssociatedPdf { // Matches backend model structure for base64 sourcePath: string; originalUrl: string; documentTitle?: string; contentBase64?: string; contentType?: string; downloadedAt?: Date; error?: string; } interface RutResumeResponse { rut: string; markdownResume: string; duxiterMD: string; sheriffLogData: SheriffDataLogResponse; availablePdfs: AssociatedPdf[]; failedPdfDownloads: AssociatedPdf[]; riskAssessment?: { summaryDocumentMD?: string; allRules?: any[]; riskSummary?: any; }; } const showJsonData = false; // This can be a prop or state if you want to toggle it const formatJsonToMarkdown = (data: Record): string => { let markdown = '## Interpretación de Datos\n\n'; // SII Data if (data.summaryData?.data?.sii) { const sii = data.summaryData.data.sii; markdown += '### Información SII\n'; markdown += `- **Razón Social**: ${sii.razonSocial || 'No disponible'}\n`; markdown += `- **Inicio Actividades**: ${sii.inicioActividades || 'No disponible'}\n`; markdown += `- **Situación Actual**: ${sii.situacionActual || 'No disponible'}\n`; if (sii.actividadEconomicaVigente?.length > 0) { markdown += '\n#### Actividades Económicas\n'; sii.actividadEconomicaVigente.forEach((act: any) => { markdown += `- ${act.activities} (Código: ${act.code})\n`; }); } } // Business Size Information if (data.summaryData?.data?.siiBusinessSize) { const size = data.summaryData.data.siiBusinessSize; markdown += '\n### Tamaño de la Empresa\n'; markdown += `- **Clasificación**: ${size.textSize || 'No disponible'}\n`; markdown += `- **Ventas Anuales (UF)**: ${size.annualSales || 'No disponible'}\n`; markdown += `- **Trabajadores**: ${size.dependentWorkers || 'No disponible'}\n`; } // Properties and Vehicles if (data.summaryData?.data?.propertiesSummary || data.summaryData?.data?.vehiclesSummary) { markdown += '\n### Activos\n'; if (data.summaryData?.data?.propertiesSummary) { const props = data.summaryData.data.propertiesSummary; markdown += `- **Propiedades**: ${props.propertiesQuantity || 0} (Avalúo Total: ${props.totalAppraisal || '0'})\n`; } if (data.summaryData?.data?.vehiclesSummary) { markdown += `- **Vehículos**: ${data.summaryData.data.vehiclesSummary.vehiclesQuantity || 0}\n`; } } // Credit Scoring if (data.creditScoringData?.data) { const scoring = data.creditScoringData.data; markdown += '\n### Evaluación Crediticia\n'; markdown += `- **Clasificación**: ${scoring.classificationLabel || 'No disponible'}\n`; markdown += `- **Porcentaje**: ${scoring.classificationPercentage ? scoring.classificationPercentage + '%' : 'No disponible'}\n`; if (scoring.financesPercentage !== undefined) { markdown += `- **Finanzas**: ${scoring.financesPercentage}%\n`; } if (scoring.judicialPercentage !== undefined) { markdown += `- **Judicial**: ${scoring.judicialPercentage}%\n`; } } // Legal Cases Summary const hasLegalCases = data.civilCasesData?.data?.length > 0 || data.laboralCasesData?.data?.length > 0 || data.cobranzaCasesData?.data?.length > 0; if (hasLegalCases) { markdown += '\n### Casos Legales\n'; markdown += `- **Casos Civiles**: ${data.civilCasesData?.data?.length || 0}\n`; markdown += `- **Casos Laborales**: ${data.laboralCasesData?.data?.length || 0}\n`; markdown += `- **Casos de Cobranza**: ${data.cobranzaCasesData?.data?.length || 0}\n`; } return markdown; }; const SingleEvaluation: React.FC = () => { const { t } = useTranslation(); const [rut, setRut] = useState(''); const [formattedRut, setFormattedRut] = useState(''); const [name, setName] = useState(''); // Kept for potential future use or if form still needs it const [isValidRut, setIsValidRut] = useState(null); // const [isSubmitting, setIsSubmitting] = useState(false); // Replaced by more granular loading states const [isLoadingLookup, setIsLoadingLookup] = useState(false); const [isLoadingResume, setIsLoadingResume] = useState(false); const [markdownResult, setMarkdownResult] = useState(null); const [downloadablePdfs, setDownloadablePdfs] = useState([]); const [erroredPdfs, setErroredPdfs] = useState([]); const [processError, setProcessError] = useState(null); const [termsAccepted, setTermsAccepted] = useState(false); const [rawData, setRawData] = useState(null); const [showRawData, setShowRawData] = useState(false); const [showHumanReadableData, setShowHumanReadableData] = useState(false); const [showInterpretation, setShowInterpretation] = useState(false); const [showDuxiterSummary, setShowDuxiterSummary] = useState(false); const markdownRef = useRef(null); // Estado para el modal de créditos agotados const [showNoCreditsDialog, setShowNoCreditsDialog] = useState(false); const { tenant, loading: tenantLoading, updateTenant } = useTenant(); const availableCredits = tenant?.creditBalance?.availableCredits || 0; const navigate = useNavigate(); useEffect(() => { if (!tenantLoading && tenant && availableCredits === 0) { setShowNoCreditsDialog(true); } }, [availableCredits, tenantLoading, tenant]); const handleCloseNoCreditsDialog = () => { setShowNoCreditsDialog(false); }; useEffect(() => { if (markdownResult || processError) { setMarkdownResult(null); setDownloadablePdfs([]); setErroredPdfs([]); setProcessError(null); setRawData(null); } }, [rut]); const handleRutChange = (e: React.ChangeEvent) => { const value = e.target.value; setRut(value); // Format RUT as user types if (value) { const formatted = formatChileanRut(value); setFormattedRut(formatted); // Validate RUT const isValid = validateChileanRut(value); setIsValidRut(isValid); } else { setFormattedRut(''); setIsValidRut(null); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!isValidRut) { toast.error(t('singleEvaluation.toast.invalidRut')); return; } if (!termsAccepted) { toast.error(t('singleEvaluation.toast.acceptTerms')); return; } if (!tenant) { toast.error(t('singleEvaluation.toast.tenantError')); return; } setIsLoadingLookup(true); // Sanitize RUT for API: remove dots, keep hyphen const rutForApi = formattedRut.replace(/\./g, ''); try { // First check if we already have an evaluation in the database try { const checkResponse = await apiClient.get(`/rut/results/rut/${rutForApi}`); if (checkResponse.data) { // Evaluation already exists, redirect to FastCheck logger.log('Evaluation already exists, redirecting to FastCheck'); navigate(`/fast-check-ex?rut=${rutForApi}`, { replace: true }); setIsLoadingLookup(false); return; } } catch (error: any) { // If 404, it means no evaluation exists, which is expected // For other errors, log but continue with creating a new evaluation if (error.response?.status !== 404) { logger.error('Error checking for existing evaluation:', error); } } // Start a new evaluation and wait for it to complete setIsLoadingResume(true); toast.loading(t('singleEvaluation.searching'), { id: 'evaluation-toast' }); const response = await apiClient.post('/rut/lookup', { rut: rutForApi, isMonitoring: false }); logger.log(229, 'Evaluation response:', response); // Evaluation completed successfully toast.success(t('singleEvaluation.toast.success'), { id: 'evaluation-toast' }); // Now navigate to FastCheck page with the RUT as a parameter navigate(`/fast-check-ex?rut=${rutForApi}`, { replace: true }); } catch (error) { logger.error('Error during evaluation process:', error); toast.error(t('singleEvaluation.toast.unknownError'), { id: 'evaluation-toast' }); } finally { // Always reset loading states setIsLoadingLookup(false); setIsLoadingResume(false); } }; const createDataUrl = (base64: string, contentType: string): string => { return `data:${contentType};base64,${base64}`; }; const handlePdfDownload = async () => { if (!markdownRef.current || !rawData?.riskAssessment?.summaryDocumentMD) return; try { // Show loading state const loadingToast = document.createElement('div'); loadingToast.className = 'fixed top-4 right-4 bg-blue-600 text-white px-4 py-2 rounded-lg z-50'; loadingToast.textContent = 'Generando PDF...'; document.body.appendChild(loadingToast); // Wait for next render cycle await new Promise(resolve => setTimeout(resolve, 100)); const canvas = await html2canvas(markdownRef.current, { scale: 2, logging: false, useCORS: true, allowTaint: true }); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); const imgWidth = 210; // A4 width in mm const pageHeight = 297; // A4 height in mm const imgHeight = (canvas.height * imgWidth) / canvas.width; let heightLeft = imgHeight; let position = 0; pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; // Add new pages if content exceeds one page while (heightLeft > 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } pdf.save(`reporte_${formattedRut}.pdf`); // Remove loading toast document.body.removeChild(loadingToast); } catch (error) { logger.error('Error generating PDF:', error); alert('Error al generar el PDF. Por favor, intente nuevamente.'); } }; return (
{/* Modal de créditos agotados */} Créditos no disponibles No quedan créditos disponibles
{/* Header */}

{t('singleEvaluation.reportLabel')}

{t('singleEvaluation.title')}

Creditos disponibles: {availableCredits}

{/* Form Card */}

{t('singleEvaluation.searchSupplierTitle')}

{t('singleEvaluation.searchSupplierSubtitle')}

{tenant && tenant.usageStats.evaluationsRemaining <= 5 && (

{t('singleEvaluation.warning.remaining', { count: tenant.usageStats.evaluationsRemaining })}

{t('singleEvaluation.warning.planAlmostExhausted')}

)}
{isValidRut === true && } {isValidRut === false && }
setName(e.target.value)} placeholder={t('singleEvaluation.namePlaceholder')} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white" disabled={isLoadingLookup || isLoadingResume} />
setTermsAccepted(e.target.checked)} className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded bg-gray-100 dark:bg-gray-700" />
{/* Results Section */}
{/* Left Column: Report */}
{(markdownResult || processError || isLoadingLookup || isLoadingResume) && (

{t('singleEvaluation.reportTitle')}

{processError && (

{t('singleEvaluation.toast.errorPrefix')}

{processError}

)} {(isLoadingLookup || isLoadingResume) && !processError && (

{isLoadingLookup ? t('singleEvaluation.searching') : t('singleEvaluation.generatingSummary')}

)}
)} {rawData?.duxiterMD && (
{showDuxiterSummary && rawData && (
)}
)} {rawData?.riskAssessment?.summaryDocumentMD && (
)}
{/* Right Column: Documents & Data */}
{(downloadablePdfs.length > 0 || erroredPdfs.length > 0) && (

{t('singleEvaluation.documents.title')}

{t('singleEvaluation.documents.subtitle')}

{downloadablePdfs.length > 0 && (

{t('singleEvaluation.documents.available')}

)} {erroredPdfs.length > 0 && (

{t('singleEvaluation.documents.unavailable')}

    {erroredPdfs.map((pdf, index) => (
  • {pdf.title}

    {t('singleEvaluation.documents.errorPrefix')} {pdf.error}

  • ))}
)}
)} {rawData && showJsonData && (
{showRawData && (
                        {JSON.stringify({
                          ...rawData.sheriffLogData,
                          associatedPdfs: `[${rawData.sheriffLogData.associatedPdfs?.length || 0} PDFs]`
                        }, null, 2)}
                      
)}
)}
); }; export default SingleEvaluation;