chore: import DH V2 D5.6.4 production baseline

This commit is contained in:
DH V2
2026-09-05 10:12:35 -03:00
commit 82213e72f5
757 changed files with 84218 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
import { useEffect, useState } 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 { FindingOfficeWorkspace } from '../features/inspections/FindingOfficeWorkspace';
import { InspectionEvidencePanel } from '../features/inspections/InspectionEvidencePanel';
import { getInspectionFinding } from '../lib/api';
import type { InspectionFinding } from '../lib/api';
import { formatDate, formatDateOnly } from '../lib/format';
function todayInput(): string {
const now = new Date();
const local = new Date(now.getTime() - now.getTimezoneOffset() * 60_000);
return local.toISOString().slice(0, 10);
}
export function FindingDetailPage() {
const { id } = useParams();
const navigate = useNavigate();
const { hasPermission } = useAuth();
const [finding, setFinding] = useState<InspectionFinding | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [evidenceRefreshKey, setEvidenceRefreshKey] = useState(0);
const canFollowUp = hasPermission('inspection_findings.follow_up');
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 <LoadingBlock label="Cargando hallazgo…" />;
if (!finding) return <section><Alert>{error || 'Hallazgo no encontrado'}</Alert></section>;
const responseOverdue = finding.status === 'OPEN'
&& !finding.companyResponseReceivedOn
&& Boolean(finding.correctionDueOn && finding.correctionDueOn < todayInput());
const verificationOverdue = finding.status === 'OPEN'
&& Boolean(finding.companyResponseReceivedOn && finding.nextControlOn && finding.nextControlOn < todayInput());
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';
return <section>
<div className="finding-detail-breadcrumb"><button type="button" className="text-link" onClick={() => navigate('/hallazgos')}>Hallazgos</button><span></span><strong>{finding.code}</strong></div>
<div className="page-heading finding-detail-heading">
<div><span className="eyebrow">{finding.code}</span><h1>{finding.title}</h1><p>{finding.asset.operatorCompany?.name ?? 'Empresa sin asignar'} · {finding.asset.operationalArea?.name ?? 'Área sin asignar'}</p></div>
<span className={`status-badge large ${finding.status === 'OPEN' ? 'observed' : 'active'}`}>{finding.status === 'OPEN' ? 'Abierto' : 'Cerrado'}</span>
</div>
{error && <Alert>{error}</Alert>}
<div className="finding-detail-summary-grid">
<article><small>Elemento del inventario</small><Link to={`/inventarios/${finding.assetId}`}>{finding.asset.name}</Link><span>{finding.asset.code} · {finding.asset.typeName}</span></article>
<article><small>Acta / inspección</small><Link to={`/inspecciones/actas/${finding.actId}`}>{finding.document.actCode}</Link><span><Link className="text-link" to={`/inspecciones/${finding.document.visitId}`}>{finding.document.visitCode}</Link></span></article>
<article className={responseOverdue ? 'overdue' : ''}><small>Vencimiento administrativo</small><strong>{formatDateOnly(finding.correctionDueOn)}</strong><span>{finding.companyResponseReceivedOn ? `Respondido ${formatDateOnly(finding.companyResponseReceivedOn)}` : 'Esperando respuesta de empresa'}</span></article>
<article className={verificationOverdue ? 'overdue' : ''}><small>Fecha de verificación</small><strong>{formatDateOnly(finding.nextControlOn)}</strong><span>{finding.nextControlOn ? 'Planificación de inspección o control' : 'Todavía sin definir'}</span></article>
<article><small>Gravedad</small><strong>{finding.severity ? `${finding.severity}/10` : 'Sin calificar'}</strong><span>{finding.suggestedSeverity ? `Sugerida por catálogo: ${finding.suggestedSeverity}/10` : finding.catalogItemId ? 'El catálogo no define una sugerencia' : 'Hallazgo OTROS'}</span></article>
</div>
{finding.verificationVisit && <article className="panel verification-linked-visit">
<div className="panel-heading"><div><span className="eyebrow">VERIFICACIÓN PLANIFICADA</span><h2>{finding.verificationVisit.code}</h2><p className="section-copy">Este hallazgo ya está incorporado a una visita de verificación.</p></div><Link className="button secondary" to={`/inspecciones/${finding.verificationVisit.id}`}>Abrir visita <Icon name="chevron" /></Link></div>
<div className="finding-two-deadlines"><div><span>FECHA OBJETIVO</span><strong>{formatDateOnly(finding.nextControlOn)}</strong><p>Fecha definida en el seguimiento del hallazgo.</p></div><div><span>VISITA</span><strong>{formatDate(finding.verificationVisit.plannedStartAt)}</strong><p>{finding.verificationVisit.code}</p></div></div>
</article>}
{latestVerification?.outcome && <article className={`panel verification-result-panel ${latestVerification.outcome === 'RESOLVED' ? 'resolved' : ''}`}>
<div className="panel-heading"><div><span className="eyebrow">ÚLTIMA VERIFICACIÓN DE CAMPO</span><h2>{verificationOutcomeLabel}</h2><p className="section-copy">Resultado registrado por el inspector durante {latestVerification.visitCode}.</p></div><Link className="button secondary" to={`/inspecciones/${latestVerification.visitId}`}>Abrir verificación <Icon name="chevron" /></Link></div>
<div className="finding-two-deadlines">
<div><span>RESULTADO</span><strong>{verificationOutcomeLabel}</strong><p>{latestVerification.resultNotes || 'Sin observaciones adicionales.'}</p></div>
<div><span>VERIFICADO</span><strong>{formatDate(latestVerification.verifiedAt)}</strong><p>{latestVerification.evidenceCount} foto{latestVerification.evidenceCount === 1 ? '' : 's'} de verificación.</p></div>
{latestVerification.rescheduledControlOn && <div><span>NUEVA FECHA</span><strong>{formatDateOnly(latestVerification.rescheduledControlOn)}</strong><p>El hallazgo vuelve a la planificación operativa.</p></div>}
</div>
{verificationReadyToClose && finding.status === 'OPEN' && <Alert type="success">La verificación de campo indicó que el hallazgo está solucionado y quedó listo para revisión de oficina.</Alert>}
{latestVerification.outcome === 'NOT_RESOLVED' && finding.status === 'OPEN' && <Alert>La verificación indicó que el hallazgo continúa abierto. Debe definirse una nueva fecha de control.</Alert>}
</article>}
<FindingOfficeWorkspace
finding={finding}
canFollowUp={canFollowUp}
canClose={canClose}
canCreateEvidence={canCreateEvidence}
canCreateCommunications={canCreateCommunications}
onFindingChanged={setFinding}
onEvidenceChanged={() => setEvidenceRefreshKey((value) => value + 1)}
/>
<div id="documentos-hallazgo">
<InspectionEvidencePanel
finding={finding}
canReadEvidence={canReadEvidence}
canCreateEvidence={canCreateEvidence}
canCreateFieldEvidence={false}
canReadCommunications={canReadCommunications}
canCreateCommunications={canCreateCommunications}
refreshKey={evidenceRefreshKey}
managedCompanyResponse
/>
</div>
{finding.status === 'CLOSED' && <article className="closed-seal finding-closed-seal"><Icon name="check" /><div><span className="eyebrow">HALLAZGO CERRADO</span><strong>{formatDate(finding.closedAt)}</strong><p>{finding.closureNotes}</p></div></article>}
</section>;
}