import { useEffect, useState } from 'react'; import type { FormEvent } from 'react'; import { Link, useParams } from 'react-router'; import { useAuth } from '../auth/AuthContext'; import { Alert, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { SearchableSelect } from '../components/SearchableSelect'; import { formatDate } from '../lib/format'; import { addInspectionReportFollowUp, getInspectionReportF4, inspectionReportGedoPdfDownloadUrl, inspectionReportWordDownloadUrl, listInspectionReportFollowUps, officializeInspectionReport, updateInspectionReportNarrative, } from '../lib/reportWorkflowApi'; import type { InspectionReportDetailF4, InspectionReportFollowUp, InspectionReportFollowUpType, } from '../lib/reportWorkflowApi'; function names(items: Array<{ name: string }>, empty: string): string { return items.length ? items.map((item) => item.name).join(' · ') : empty; } function localDateTime(value: Date): string { const local = new Date(value.getTime() - value.getTimezoneOffset() * 60_000); return local.toISOString().slice(0, 16); } function reportStatusLabel(status: InspectionReportDetailF4['status']): string { if (status === 'WORKING') return 'En preparación'; if (status === 'OFFICIALIZED') return 'Oficializado en GEDO'; if (status === 'CANCELLED') return 'Cancelado'; return 'Legado congelado'; } function reportStatusClass(status: InspectionReportDetailF4['status']): string { if (status === 'OFFICIALIZED') return 'active'; if (status === 'CANCELLED') return 'danger'; return 'pending'; } function followUpLabel(type: InspectionReportFollowUpType): string { if (type === 'COMPANY_NOTE') return 'Presentación / nota de empresa'; if (type === 'COMPANY_DOCUMENT') return 'Documento de empresa'; if (type === 'INTERNAL_NOTE') return 'Nota interna'; if (type === 'VERIFICATION') return 'Verificación'; return 'Otro antecedente'; } function fileSize(value: number | null): string { if (!value || value < 1) return ''; if (value < 1024) return `${value} B`; if (value < 1024 * 1024) return `${Math.round(value / 1024)} KB`; return `${(value / (1024 * 1024)).toFixed(1)} MB`; } export function ReportDetailPage() { const { id } = useParams(); const { hasPermission } = useAuth(); const canManage = hasPermission('inspection_reports.generate'); const [report, setReport] = useState(null); const [followUps, setFollowUps] = useState([]); const [loading, setLoading] = useState(true); const [working, setWorking] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [executiveSummary, setExecutiveSummary] = useState(''); const [reportDescription, setReportDescription] = useState(''); const [gedoIfIdentifier, setGedoIfIdentifier] = useState(''); const [gedoOfficializedAt, setGedoOfficializedAt] = useState(localDateTime(new Date())); const [gedoFile, setGedoFile] = useState(null); const [followUpType, setFollowUpType] = useState('COMPANY_NOTE'); const [followUpOccurredAt, setFollowUpOccurredAt] = useState(localDateTime(new Date())); const [followUpReference, setFollowUpReference] = useState(''); const [followUpDescription, setFollowUpDescription] = useState(''); const [followUpFile, setFollowUpFile] = useState(null); const reload = async () => { if (!id) return; const [nextReport, nextFollowUps] = await Promise.all([ getInspectionReportF4(id), listInspectionReportFollowUps(id), ]); setReport(nextReport); setFollowUps(nextFollowUps); setExecutiveSummary(nextReport.executiveSummary ?? ''); setReportDescription(nextReport.reportDescription ?? ''); setGedoIfIdentifier(nextReport.gedoIfIdentifier ?? ''); if (nextReport.gedoOfficializedAt) { setGedoOfficializedAt(localDateTime(new Date(nextReport.gedoOfficializedAt))); } }; useEffect(() => { if (!id) return; setLoading(true); setError(''); reload() .catch((requestError) => setError(errorMessage(requestError))) .finally(() => setLoading(false)); }, [id]); const saveNarrative = async (event: FormEvent) => { event.preventDefault(); if (!id || report?.status !== 'WORKING') return; setWorking(true); setError(''); setSuccess(''); try { await updateInspectionReportNarrative(id, { executiveSummary: executiveSummary.trim() || null, description: reportDescription.trim() || null, }); await reload(); setSuccess('Contenido editable del INF actualizado. El Acta fuente y sus Hallazgos no fueron modificados.'); } catch (requestError) { setError(errorMessage(requestError)); } finally { setWorking(false); } }; const officialize = async (event: FormEvent) => { event.preventDefault(); if (!id || !gedoFile || !gedoIfIdentifier.trim() || !gedoOfficializedAt) return; setWorking(true); setError(''); setSuccess(''); try { await officializeInspectionReport(id, { gedoIfIdentifier: gedoIfIdentifier.trim(), gedoOfficializedAt: new Date(gedoOfficializedAt).toISOString(), file: gedoFile, }); setGedoFile(null); await reload(); setSuccess('IF oficial de GEDO registrado. El PDF y su hash quedaron fijados de forma inmutable.'); } catch (requestError) { setError(errorMessage(requestError)); } finally { setWorking(false); } }; const addFollowUp = async (event: FormEvent) => { event.preventDefault(); if (!id || !followUpOccurredAt) return; if (!followUpDescription.trim() && !followUpReference.trim() && !followUpFile) return; setWorking(true); setError(''); setSuccess(''); try { const next = await addInspectionReportFollowUp(id, { type: followUpType, occurredAt: new Date(followUpOccurredAt).toISOString(), externalReference: followUpReference.trim() || null, description: followUpDescription.trim() || null, file: followUpFile, }); setFollowUps(next); setFollowUpReference(''); setFollowUpDescription(''); setFollowUpFile(null); setFollowUpOccurredAt(localDateTime(new Date())); setSuccess('Antecedente agregado al seguimiento del INF. Los registros anteriores permanecen intactos.'); } catch (requestError) { setError(errorMessage(requestError)); } finally { setWorking(false); } }; if (loading) return ; return
Informes/{report?.code ?? 'Informe'}
INFORME DE INSPECCIÓN

{report?.code ?? 'Informe'}

{report ? `Acta ${report.act.code} · generado ${formatDate(report.generatedAt)}` : 'Consulta del informe.'}

{report &&
{report.wordStatus === 'READY' ? 'Word disponible' : report.wordStatus === 'FAILED' ? 'Word con error' : 'Word pendiente'} {reportStatusLabel(report.status)}
}
{error && {error}} {success && {success}} {report && <>

Un INF corresponde a una sola Acta. El Word puede editarse durante la preparación del Informe. El Acta sellada y los Hallazgos que contiene permanecen inmutables.

TRAZABILIDAD DOCUMENTAL

{report.code}

Versión del Acta: {report.actVersion}
Empresa{names(report.companies, 'Sin asignar')}
Área / Yacimiento{names(report.areas, 'Sin asignar')}
Hallazgos{report.findingCount}
Generado por{report.generatedBy.firstName} {report.generatedBy.lastName}
Inspección{report.visit.code} Acta fuente{report.act.code}Contenido inmutable Hallazgos del Acta{report.findingCount}Seguimiento técnico
WORD EDITABLE

Preparación del INF

El Inspector puede revisar y ajustar el texto del Informe antes de incorporarlo a GEDO. Esta edición no altera el Acta fuente.

{report.wordStatus === 'READY' && Descargar Word}