import { useEffect, useState } from 'react'; import type { FormEvent } from 'react'; import { Link, useNavigate, useParams } from 'react-router'; import { useAuth } from '../auth/AuthContext'; import { Alert, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { InspectionEvidencePanel } from '../features/inspections/InspectionEvidencePanel'; import { closeInspectionFinding, getInspectionFinding } from '../lib/api'; import type { InspectionFinding } from '../lib/api'; import { formatDate, formatDateOnly } from '../lib/format'; export function FindingDetailPage() { const { id } = useParams(); const navigate = useNavigate(); const { hasPermission } = useAuth(); const [finding, setFinding] = useState(null); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [closureNotes, setClosureNotes] = useState(''); const [evidenceRefreshKey, setEvidenceRefreshKey] = useState(0); const canClose = hasPermission('inspection_findings.close'); const canReadEvidence = hasPermission('inspection_evidence.read'); const canCreateEvidence = hasPermission('inspection_evidence.create'); const canReadCommunications = hasPermission('inspection_communications.read'); const canCreateCommunications = hasPermission('inspection_communications.create'); useEffect(() => { if (!id) return; setLoading(true); setError(''); getInspectionFinding(id) .then(setFinding) .catch((requestError) => setError(errorMessage(requestError))) .finally(() => setLoading(false)); }, [id]); if (loading) return ; if (!finding) return
{error || 'Hallazgo no encontrado'}
; const latestVerification = finding.latestVerification; const verificationReadyToClose = latestVerification?.outcome === 'RESOLVED' && latestVerification.visitStatus === 'CLOSED'; const verificationOutcomeLabel = latestVerification?.outcome === 'RESOLVED' ? 'Solucionado' : latestVerification?.outcome === 'NOT_RESOLVED' ? 'No solucionado' : latestVerification?.outcome === 'REQUIRES_NEW_DATE' ? 'Requiere nueva fecha' : 'Sin resultado'; const closeFinding = async (event: FormEvent) => { event.preventDefault(); if (!closureNotes.trim()) return; setBusy(true); setError(''); setSuccess(''); try { const updated = await closeInspectionFinding(finding.id, closureNotes.trim()); setFinding(updated); setClosureNotes(''); setSuccess('Hallazgo cerrado. La conclusión quedó incorporada al historial sin modificar el Hallazgo original.'); } catch (requestError) { setError(errorMessage(requestError)); } finally { setBusy(false); } }; return
{finding.code}
{finding.code}

{finding.title}

{finding.asset.operatorCompany?.name ?? 'Empresa sin asignar'} · {finding.asset.operationalArea?.name ?? 'Área sin asignar'}

{finding.status === 'OPEN' ? 'Abierto' : 'Cerrado'}
{error && {error}} {success && {success}}
CONTENIDO ORIGINAL

Descripción

Texto libre registrado por el inspector. Una vez bloqueada el Acta, este contenido es inmutable.

{finding.description || 'Sin descripción registrada.'}

{finding.legalBasis &&

Base legal / normativa: {finding.legalBasis}

}
Elemento del Inventario {finding.asset.name} {finding.asset.code} · {finding.asset.typeName}
Acta / Inspección {finding.document.actCode} {finding.document.visitCode}
Gravedad {finding.severity ? `${finding.severity}/10` : 'Sin calificar'} {finding.suggestedSeverity ? `Sugerida por catálogo: ${finding.suggestedSeverity}/10` : finding.catalogItemId ? 'El catálogo no define una sugerencia' : 'Hallazgo OTROS'}
Reincidencia {finding.isRecurrence ? 'Sí' : 'No'} {finding.recurrenceOfFindingCode ? `Antecedente: ${finding.recurrenceOfFindingCode}` : finding.isRecurrence ? 'Antecedente vinculado' : 'Sin antecedente marcado'}
Próxima verificación {formatDateOnly(finding.nextControlOn)} {finding.verificationVisit ? `Planificada en ${finding.verificationVisit.code}` : finding.nextControlOn ? 'Pendiente de planificación' : 'Sin fecha definida'}
{finding.verificationVisit &&
VERIFICACIÓN PLANIFICADA

{finding.verificationVisit.code}

Este Hallazgo está incorporado a una Inspección de verificación.

Abrir Inspección
FECHA OBJETIVO{formatDateOnly(finding.nextControlOn)}

Fecha técnica de control del Hallazgo.

INSPECCIÓN{formatDate(finding.verificationVisit.plannedStartAt)}

{finding.verificationVisit.code}

} {latestVerification?.outcome &&
ÚLTIMA VERIFICACIÓN DE CAMPO

{verificationOutcomeLabel}

Resultado registrado durante {latestVerification.visitCode}.

Abrir verificación
RESULTADO{verificationOutcomeLabel}

{latestVerification.resultNotes || 'Sin observaciones adicionales.'}

VERIFICADO{formatDate(latestVerification.verifiedAt)}

{latestVerification.evidenceCount} evidencia{latestVerification.evidenceCount === 1 ? '' : 's'} de verificación.

{latestVerification.rescheduledControlOn &&
NUEVA FECHA{formatDateOnly(latestVerification.rescheduledControlOn)}

El Hallazgo vuelve a planificación de control.

}
{verificationReadyToClose && finding.status === 'OPEN' && La verificación indicó que el Hallazgo está solucionado y quedó listo para cierre técnico.} {latestVerification.outcome === 'NOT_RESOLVED' && finding.status === 'OPEN' && La verificación indicó que el Hallazgo continúa abierto.}
} {verificationReadyToClose && finding.status === 'OPEN' && canClose &&
CIERRE TÉCNICO

Cerrar Hallazgo

El cierre agrega una conclusión al historial; nunca modifica la descripción original.