import { useEffect, useMemo, useState } from 'react'; import { useAuth } from '../../auth/AuthContext'; import { Alert, LoadingBlock, errorMessage } from '../../components/Feedback'; import { Icon } from '../../components/Icon'; import { getInspectionSignatureBlob } from '../../lib/api'; import type { InspectionActSignature } from '../../lib/api'; import { getInspectionClosureF4, type InspectionActF4, type InspectionClosureF4, } from '../../lib/inspectionActF4Api'; import { formatDate } from '../../lib/format'; function signatureStatusLabel(value: InspectionActSignature['status']): string { if (value === 'SIGNED') return 'Firmada'; if (value === 'REFUSED') return 'Se negó a firmar'; return 'Ausente'; } function signerTypeLabel(value: InspectionActSignature['signerType']): string { return value === 'INSPECTOR' ? 'Inspector/a' : 'Responsable de la empresa'; } export function InspectionClosurePanel({ act }: { act: InspectionActF4 }) { const { hasPermission } = useAuth(); const canRead = hasPermission('inspection_closure.read'); const [closure, setClosure] = useState(null); const [loading, setLoading] = useState(canRead); const [error, setError] = useState(''); useEffect(() => { if (!canRead) return; setLoading(true); getInspectionClosureF4(act.id) .then(setClosure) .catch((requestError) => setError(errorMessage(requestError))) .finally(() => setLoading(false)); }, [act.id, canRead]); const inspectorSignatures = useMemo( () => closure?.signatures.filter((item) => item.signerType === 'INSPECTOR') ?? [], [closure], ); const companyOutcome = useMemo( () => closure?.signatures.find((item) => item.signerType === 'COMPANY_RESPONSIBLE') ?? null, [closure], ); const viewSignature = async (signature: InspectionActSignature) => { const tab = window.open('about:blank', '_blank'); if (tab) tab.opener = null; try { const blob = await getInspectionSignatureBlob(signature.id); const url = URL.createObjectURL(blob); if (tab) tab.location.href = url; else window.open(url, '_blank', 'noopener,noreferrer'); window.setTimeout(() => URL.revokeObjectURL(url), 60_000); } catch (requestError) { tab?.close(); setError(errorMessage(requestError)); } }; if (!canRead) return null; if (loading || !closure) return
; const constanciasCompletas = inspectorSignatures.length > 0 && Boolean(companyOutcome); const isSealed = act.status === 'SEALED' || act.status === 'CLOSED'; const isLocked = act.status === 'LOCKED' || act.status === 'READY'; const lifecycleLabel = isSealed ? 'Acta sellada' : isLocked ? 'Esperando manifestación' : act.status === 'CANCELLED' ? 'Acta cancelada' : 'Borrador en campo'; const lifecycleClass = isSealed ? 'active' : isLocked ? 'observed' : act.status === 'CANCELLED' ? 'inactive' : 'pending'; return
CIERRE DEL ACTA · SÓLO LECTURA

Responsable, firmas y sellado

Al bloquearse, el contenido del Acta queda inmutable. La Inspección puede continuar y generar otras Actas.

{lifecycleLabel}
{error && {error}}

El dashboard no modifica el Acta. El bloqueo y la firma del Inspector se realizan desde la APK. La manifestación de la empresa puede completarse en campo o mediante el enlace seguro posterior.

1Responsable{closure.responsible ? 'Registrado' : 'Pendiente'}
2Bloqueo{closure.closure?.isCurrent ? 'Contenido inmutable' : 'Pendiente'}
3Manifestaciones{inspectorSignatures.length} inspector · {companyOutcome ? 'empresa resuelta' : 'empresa pendiente'}
4Sellado{isSealed ? 'Definitivo' : 'Pendiente'}
Urgencia{closure.act.urgency === 'URGENT' ? 'Urgente' : closure.act.urgency === 'NON_URGENT' ? 'No urgente' : 'Pendiente de cierre'}
Plazo configurado{closure.act.deadlineDays ? `${closure.act.deadlineDays} días ${closure.act.deadlineDayType === 'BUSINESS' ? 'hábiles' : 'corridos'}` : 'Pendiente'}
Inicio del plazo{closure.act.deadlineBaseAt ? formatDate(closure.act.deadlineBaseAt) : 'Pendiente de evento válido'}
Vencimiento{closure.act.deadlineAt ? formatDate(closure.act.deadlineAt) : 'Todavía no iniciado'}
{closure.responsible &&
Situación{closure.responsible.attendanceStatus === 'PRESENT' ? 'Presente' : 'Ausente'}
Responsable{closure.responsible.fullName ?? 'No estuvo presente'}
Documento / cargo{closure.responsible.documentNumber ? `${closure.responsible.documentType} ${closure.responsible.documentNumber}` : 'No informado'}{closure.responsible.position ? ` · ${closure.responsible.position}` : ''}
Contacto{closure.responsible.email ?? closure.responsible.phone ?? 'No informado'}
} {closure.closure?.isCurrent &&
CONTENIDO BLOQUEADO{closure.closure.schemaVersion}Bloqueado {formatDate(closure.act.lockedAt ?? closure.closure.preparedAt)}
{closure.act.lockedSha256 ?? closure.closure.preparedSha256}
} {closure.signatures.length > 0 &&
{closure.signatures.map((signature) =>
{signatureStatusLabel(signature.status)}{signature.signerName}{signerTypeLabel(signature.signerType)} · {formatDate(signature.createdAt)}
{signature.signaturePayloadSha256}{signature.status === 'SIGNED' ? <>{signature.signerType === 'COMPANY_RESPONSIBLE' &&

{signature.companyManifestation === 'DISSENT' ? 'Firma en disidencia' : 'Firma en conformidad'}{signature.companyStatement ? ` · ${signature.companyStatement}` : ''}

} :

{signature.reason}

}
)}
} {isSealed && closure.closure?.finalSha256 &&
ACTA SELLADA{formatDate(closure.act.sealedAt ?? closure.closure.serverClosedAt)}

El Acta quedó sellada e inmutable. La Inspección y sus demás Actas continúan con ciclo independiente.

{closure.closure.finalSha256}
}
; }