const API_BASE = '/api/v3'; export class ApiError extends Error { constructor( public readonly status: number, public readonly code: string, message: string, public readonly requestId?: string, ) { super(message); this.name = 'ApiError'; } } export interface AuthUser { id: string; username: string; firstName: string; lastName: string; email: string | null; mustChangePassword: boolean; roles: string[]; permissions: string[]; } export interface AuthResponse { user: AuthUser; csrfToken: string; accessExpiresInSeconds: number; } export interface HealthResponse { status: string; service: string; api: string; version: string; phase: string; database: string; time: string; latencyMs: number; } export interface DashboardSummary { counts: { totalAssets: number; assetsNeedValidation: number; assetsWithoutGeometry: number; plannedInspections: number; activeUsers: number; inactiveUsers: number; activeSessions: number; openFindings: number; awaitingCompanyResponse: number; overdueCompanyResponses: number; companyResponsesDueNext7Days: number; awaitingVerificationSchedule: number; overdueControls: number; controlsNext30Days: number; }; recentAudit: Array<{ id: string; occurredAt: string; actorUsername: string | null; action: string; entityType: string | null; entityId: string | null; }>; upcomingControls: Array<{ id: string; code: string; title: string; nextControlOn: string; actId: string; actCode: string; visitId: string; visitCode: string; assetId: string; assetCode: string; assetName: string; }>; generatedAt: string; } export interface RoleSummary { id: string; code: string; name: string } export interface AdministrativeUser { id: string; username: string; email: string | null; firstName: string; lastName: string; status: 'ACTIVE' | 'INACTIVE'; mustChangePassword: boolean; failedLoginAttempts: number; lockedUntil: string | null; lastLoginAt: string | null; passwordChangedAt: string | null; createdAt: string; updatedAt: string; roles: RoleSummary[]; } export interface Permission { id: string; code: string; description: string } export interface AdministrativeRole { id: string; code: string; name: string; description: string; isSystem: boolean; createdAt: string; updatedAt: string; userCount: number; permissions: Permission[]; } export interface AuditEventSummary { id: string; occurredAt: string; actorUserId: string | null; actorUsername: string | null; action: string; entityType: string | null; entityId: string | null; requestId: string | null; source: 'WEB' | 'ANDROID' | 'SYSTEM' | 'IMPORT'; ip: string | null; hasDetails: boolean; } export interface AuditEventDetail extends AuditEventSummary { userAgent: string | null; beforeData: Record | null; afterData: Record | null; metadata: Record | null; } export interface PageMeta { page: number; pageSize: number; total: number; totalPages: number } interface Page { data: T[]; meta: PageMeta } export type AssetInformationStatus = | 'DRAFT' | 'PENDING_SURVEY' | 'SURVEYED' | 'VALIDATED' | 'OBSERVED' | 'OUTDATED' | 'INACTIVE'; export type AssetOperationalStatus = | 'UNKNOWN' | 'IN_SERVICE' | 'TEMPORARILY_OUT_OF_SERVICE' | 'OUT_OF_SERVICE' | 'DECOMMISSIONED' | 'ABANDONED'; export type AssetDataOrigin = | 'MANUAL' | 'FIELD_SURVEY' | 'PROVIDED_DOCUMENT' | 'IMPORT' | 'SYSTEM'; export type AssetTypeOperationalRole = 'GENERIC' | 'AREA' | 'COMPANY'; export interface AssetProvenance { assetId: string; origin: AssetDataOrigin; sourceName: string | null; sourceReference: string | null; observedAt: string | null; notes: string | null; verifiedAt: string | null; verifiedBy: string | null; verifiedByUsername: string | null; updatedAt: string; updatedBy: string | null; updatedByUsername: string | null; } export type AssetAttributeDataType = | 'TEXT' | 'NUMBER' | 'BOOLEAN' | 'DATE' | 'DATETIME' | 'SELECT'; export interface AssetAttributeDefinition { id: string; code: string; name: string; dataType: AssetAttributeDataType; isRequired: boolean; isActive: boolean; unit: string | null; options: string[] | null; sortOrder: number; } export interface AssetTypeSummary { id: string; code: string; name: string; isActive: boolean; operationalRole: AssetTypeOperationalRole; } export interface AssetType extends AssetTypeSummary { description: string; canBeRoot: boolean; createdAt: string; updatedAt: string; assetCount: number; allowedParentTypes: AssetTypeSummary[]; attributes: AssetAttributeDefinition[]; } export interface MasterBootstrapStatus { presetCode: string; presetName: string; typeCount: number; canApply: boolean; reason: string | null; types: Array<{ code: string; name: string; operationalRole: AssetTypeOperationalRole; parentCodes: string[]; attributeCount: number; }>; } export interface MasterBootstrapResult { presetCode: string; presetName: string; createdTypeCount: number; createdAttributeCount: number; createdParentRuleCount: number; data: AssetType[]; } export interface MasterEnrichmentStatus { presetCode: string; presetName: string; typeCount: number; canApply: boolean; complete: boolean; reason: string | null; missingTypeCodes: string[]; missingAttributeCount: number; missingParentRuleCount: number; } export interface AssetListItem { id: string; code: string; name: string; commonName: string | null; type: { id: string; code: string; name: string }; parent: { id: string; code: string; name: string } | null; operationalArea: { id: string; code: string; name: string } | null; operatorCompany: { id: string; code: string; name: string } | null; informationStatus: AssetInformationStatus; operationalStatus: AssetOperationalStatus; childrenCount: number; hasGeometry: boolean; geometryType: AssetGeometryType | null; mediaCount: number; dataOrigin: AssetDataOrigin; provenanceVerified: boolean; currentVersion: number; updatedAt: string; } export interface AssetLineageItem { id: string; code: string; name: string; commonName: string | null; type: { id: string; code: string; name: string }; } export interface AssetDetail extends AssetListItem { description: string | null; createdAt: string; createdBy: string | null; updatedBy: string | null; attributes: Array<{ definitionId: string; code: string; name: string; dataType: AssetAttributeDataType; isRequired: boolean; unit: string | null; options: string[] | null; sortOrder: number; value: unknown; }>; } export type AssetDossierTimelineKind = | 'INVENTORY_CHANGE' | 'INSPECTION' | 'ACT' | 'FINDING' | 'FINDING_CLOSED' | 'COMMUNICATION' | 'PHOTO' | 'DOCUMENT' | 'REPORT' | 'SOURCE_DOCUMENT' | 'VERIFICATION'; export interface AssetDossierTimelineEvent { id: string; kind: AssetDossierTimelineKind; occurredAt: string; title: string; description: string | null; href?: string; meta?: Record; } export interface AssetDossier { asset: { id: string; code: string; name: string; commonName: string | null }; counters: { inspections: number; acts: number; findings: number; openFindings: number; closedFindings: number; evidence: number; documents: number; photos: number; reports: number; verifications: number; }; visits: Array<{ id: string; code: string; title: string; status: InspectionVisitStatus; plannedStartAt: string | null; actualStartedAt: string | null; actualClosedAt: string | null; createdAt: string }>; acts: Array<{ id: string; visitId: string; code: string; status: InspectionActStatus; occurredAt: string; title: string; summary: string; closedAt: string | null; currentVersion: number; visitCode: string; visitTitle: string }>; findings: Array<{ id: string; actId: string; code: string; status: InspectionFindingStatus; title: string; description: string; correctionDueOn: string | null; companyResponseReceivedOn: string | null; nextControlOn: string | null; closedAt: string | null; closureNotes: string | null; createdAt: string; updatedAt: string; actCode: string; actOccurredAt: string; visitId: string; visitCode: string; visitTitle: string }>; evidence: Array<{ id: string; findingId: string; communicationId: string | null; kind: InspectionEvidenceKind; purpose: InspectionEvidencePurpose; originalName: string; title: string | null; description: string | null; capturedAt: string | null; createdAt: string; findingCode: string; findingTitle: string }>; communications: Array<{ id: string; findingId: string; direction: InspectionFindingCommunication['direction']; channel: InspectionFindingCommunication['channel']; type: InspectionFindingCommunication['type']; occurredAt: string; subject: string; details: string | null; contactName: string | null; createdAt: string; findingCode: string; findingTitle: string }>; verificationResults: Array<{ id: string; findingId: string; findingCode: string; findingTitle: string; visitId: string; visitCode: string; visitTitle: string; visitStatus: InspectionVisitStatus; targetControlOn: string | null; outcome: InspectionVerificationOutcome | null; resultNotes: string | null; verifiedAt: string | null; resultRecordedAt: string | null; rescheduledControlOn: string | null; evidenceCount: number }>; documents: Array<{ id: string; documentType: SourceDocumentType; documentNumber: string | null; title: string; issuer: string | null; documentDate: string | null; externalReference: string | null; relationType: AssetSourceDocumentRelationType; notes: string | null; linkedAt: string }>; inspectionReports: Array<{ id: string; code: string; status: InspectionReportStatus; pdfStatus: InspectionReportPdfStatus; title: string; generatedAt: string; frozenSha256: string; actId: string; actCode: string; visitId: string; visitCode: string }>; reports: Array<{ id: string; documentType: SourceDocumentType; documentNumber: string | null; title: string; issuer: string | null; documentDate: string | null; externalReference: string | null; relationType: AssetSourceDocumentRelationType; notes: string | null; linkedAt: string }>; media: Array<{ id: string; kind: AssetMediaKind; originalName: string; title: string | null; description: string | null; capturedAt: string | null; createdAt: string; source: 'WEB' | 'ANDROID' | 'IMPORT' }>; versions: Array<{ id: string; versionNumber: number; changeType: AssetVersionChangeType; changedFields: string[]; occurredAt: string; actorUsername: string | null; source: string }>; timeline: AssetDossierTimelineEvent[]; } export interface AssetContextHistoryItem { id: string; assetId: string; validFrom: string; validUntil: string | null; changeReason: string; endReason: string | null; assetVersionNumber: number; source: string; requestId: string | null; createdAt: string; parent: { id: string; code: string; name: string } | null; operationalArea: { id: string; code: string; name: string } | null; operatorCompany: { id: string; code: string; name: string } | null; creator: { id: string; username: string; firstName: string; lastName: string } | null; isCurrent: boolean; } export type FieldDiscoveryStatus = 'PENDING' | 'APPROVED' | 'MATCHED' | 'REJECTED'; export interface FieldDiscovery { id: string; status: FieldDiscoveryStatus; discoveryNotes: string | null; observedAt: string; reviewedAt: string | null; reviewNotes: string | null; createdAt: string; asset: { id: string; code: string; name: string; commonName: string | null; informationStatus: AssetInformationStatus; typeId: string; typeName: string; parentId: string | null; operationalAreaId: string | null; operatorCompanyId: string | null; }; visit: { id: string; code: string; title: string; status: InspectionVisitStatus }; creator: { id: string; username: string; firstName: string; lastName: string }; reviewer: { id: string; username: string; firstName: string; lastName: string } | null; matchedAsset: { id: string; code: string; name: string; commonName: string | null } | null; } export interface OperationalAssetSummary { id: string; code: string; name: string; commonName: string | null; typeName: string; } export type AreaOrganizationRole = 'OPERATOR' | 'TECHNICAL_OPERATOR' | 'CONCESSIONAIRE' | 'PERMIT_HOLDER' | 'PARTICIPANT' | 'OTHER'; export interface AreaCompanyRelation { id: string; area: OperationalAssetSummary; company: OperationalAssetSummary; relationRole: AreaOrganizationRole; participationPercent: number | null; legalInstrument: string | null; sourceDocumentId: string | null; validFrom: string; validUntil: string | null; startReason: string; endReason: string | null; createdBy: { id: string; username: string } | null; endedBy: { id: string; username: string } | null; createdAt: string; updatedAt: string; active: boolean; assignedAssetCount: number; } export type OrganizationKind = 'COMPANY' | 'UTE' | 'PUBLIC_ENTITY' | 'OTHER'; export type SourceDocumentType = 'NOTE' | 'TECHNICAL_REPORT' | 'INSPECTION_ACT' | 'INVENTORY' | 'RESOLUTION' | 'DECREE' | 'CONTRACT' | 'SPREADSHEET' | 'OTHER'; export type AssetSourceDocumentRelationType = 'SOURCE' | 'MENTIONS' | 'VALIDATES' | 'SUPERSEDES' | 'OTHER'; export type OrganizationMembershipRole = 'MEMBER' | 'LEAD_MEMBER' | 'OTHER'; export type AreaLegalRightType = 'EXPLOITATION_CONCESSION' | 'EXPLORATION_PERMIT' | 'TRANSPORT_CONCESSION' | 'OTHER'; export type AreaLegalRightStatus = 'ACTIVE' | 'EXPIRED' | 'REVOKED' | 'PENDING'; export type AreaLegalRightOrganizationRole = 'HOLDER' | 'PARTICIPANT' | 'OPERATOR' | 'OTHER'; export interface SourceDocumentSummary { id: string; documentType: SourceDocumentType; documentNumber: string | null; title: string; issuer: string | null; documentDate: string | null; externalReference: string | null; notes: string | null; createdAt?: string; updatedAt?: string; } export interface AssetRegistry { organizationProfile: null | { assetId: string; organizationKind: OrganizationKind; legalName: string | null; taxId: string | null; notificationEmail: string | null; notes: string | null; createdAt: string; updatedAt: string }; organizationMemberships: Array<{ id: string; parent: { id: string; code: string; name: string }; member: { id: string; code: string; name: string }; role: OrganizationMembershipRole; participationPercent: number | null; validFrom: string; validUntil: string | null; sourceDocumentId: string | null; notes: string | null; endReason: string | null }>; externalIdentifiers: Array<{ id: string; namespace: string; value: string; validFrom: string; validUntil: string | null; sourceDocumentId: string | null; notes: string | null; endReason: string | null }>; sourceDocuments: Array; legalRights: Array<{ id: string; rightType: AreaLegalRightType; name: string; instrumentNumber: string | null; validFrom: string | null; validUntil: string | null; status: AreaLegalRightStatus; sourceDocumentId: string | null; notes: string | null; organizations: Array<{ id: string; organizationId: string; organizationName: string; role: AreaLegalRightOrganizationRole; participationPercent: number | null; validFrom: string; validUntil: string | null; notes: string | null; endReason: string | null }> }>; } export type AssetImportBatchStatus = 'ANALYZED' | 'REVIEW_REQUIRED' | 'CANCELLED' | 'FAILED'; export type AssetImportRowStatus = 'READY' | 'WARNING' | 'CONFLICT' | 'IGNORED'; export type AssetImportProfileCode = 'MENDOZA_INVENTORY_V1' | 'MENDOZA_YACIMIENTOS_V1' | 'UNKNOWN'; export interface AssetImportBatch { id: string; originalName: string; mimeType: string; sizeBytes: number; sha256: string; profileCode: AssetImportProfileCode; profileConfidence: number; worksheetName: string | null; headerRow: number | null; totalRows: number; readyRows: number; warningRows: number; conflictRows: number; ignoredRows: number; status: AssetImportBatchStatus; sourceDocumentId: string | null; sourceLabel: string | null; notes: string | null; analysis: Record; uploadedBy: string | null; uploadedByUsername: string | null; analyzedAt: string; createdAt: string; updatedAt: string; } export interface AssetImportBatchDetail extends AssetImportBatch { topIssues: Array<{ code: string; count: number }>; duplicateFiles: Array<{ id: string; originalName: string; createdAt: string; status: AssetImportBatchStatus }>; } export interface AssetImportRow { id: string; worksheetName: string; rowNumber: number; status: AssetImportRowStatus; suggestedAction: 'CREATE' | 'MATCH' | 'REVIEW' | 'IGNORE'; rawData: Record; normalizedData: Record; issues: string[]; fingerprint: string; matchedAssetId: string | null; importedAssetId: string | null; createdAt: string; updatedAt: string; } export type AssetImportPlanStatus = 'REVIEW_REQUIRED' | 'READY' | 'APPLIED' | 'ROLLED_BACK' | 'SUPERSEDED' | 'FAILED'; export type AssetImportPlanEntityKind = 'DEPARTMENT' | 'ORGANIZATION' | 'AREA' | 'AREA_DEPARTMENT_RELATION' | 'FIELD' | 'OPERATOR_RELATION' | 'LEGAL_RIGHT' | 'LEGAL_RIGHT_ORGANIZATION' | 'INSTALLATION' | 'LOCAL_STRUCTURE' | 'TECHNICAL_ASSET'; export type AssetImportPlanAction = 'CREATE' | 'MATCH' | 'REVIEW' | 'IGNORE'; export type AssetImportPlanItemStatus = 'PLANNED' | 'MATCHED' | 'REVIEW' | 'IGNORED' | 'APPLIED' | 'ROLLED_BACK' | 'FAILED'; export interface AssetImportPlanItem { id: string; planId: string; itemOrder: number; entityKey: string; entityKind: AssetImportPlanEntityKind; action: AssetImportPlanAction; status: AssetImportPlanItemStatus; assetTypeCode: string | null; displayName: string; generatedCode: string | null; parentEntityKey: string | null; matchedAssetId: string | null; appliedAssetId: string | null; appliedObjectId: string | null; payload: Record; sourceRowNumbers: number[]; reviewCodes: string[]; resolutionNote: string | null; resolvedBy: string | null; resolvedAt: string | null; } export interface AssetImportPlan { id: string; batchId: string; revision: number; status: AssetImportPlanStatus; externalIdNamespace: string | null; operatorAssetId: string | null; summary: Record; planHash: string; generatedBy: string | null; generatedAt: string; appliedBy: string | null; appliedAt: string | null; applicationSummary: Record | null; rolledBackBy: string | null; rolledBackAt: string | null; rollbackSummary: Record | null; supersededAt: string | null; createdAt: string; updatedAt: string; masterStateStale: boolean; masterStateHashAvailable: boolean; masterStateCheckedAt: string; previewItems: AssetImportPlanItem[]; reviewItems: AssetImportPlanItem[]; } export interface AssetImportReviewItem extends AssetImportPlanItem { batchId: string; batchName: string; sourceLabel: string | null; profileCode: AssetImportProfileCode; planRevision: number; planStatus: AssetImportPlanStatus; dependency: boolean; } export interface AssetImportOrganizationOption { id: string; code: string; name: string; legalName: string | null; } export type Position = [number, number]; export type AssetGeometryType = 'POINT' | 'LINESTRING' | 'POLYGON'; export type GeoJsonGeometry = | { type: 'POINT'; coordinates: Position } | { type: 'LINESTRING'; coordinates: Position[] } | { type: 'POLYGON'; coordinates: Position[][] }; export interface AssetGeometry { assetId: string; geometry: GeoJsonGeometry; geometryType: AssetGeometryType; source: 'WEB' | 'ANDROID' | 'IMPORT' | 'SURVEY'; accuracyM: number | null; capturedAt: string | null; deviceLabel: string | null; createdAt: string; updatedAt: string; updatedBy: string | null; } export interface MapAssetProperties { id: string; code: string; name: string; commonName?: string | null; typeId: string; typeCode: string; typeName: string; parentId: string | null; parentName: string | null; informationStatus: AssetInformationStatus; geometryType: AssetGeometryType; accuracyM: number | null; capturedAt: string | null; updatedAt: string; } export interface MapAssetFeature { type: 'Feature'; id: string; geometry: GeoJsonGeometry; properties: MapAssetProperties; } export interface MapAssetFeatureCollection { type: 'FeatureCollection'; features: MapAssetFeature[]; meta: { count: number; truncated: boolean }; } export type AssetMediaKind = 'PHOTO' | 'DOCUMENT'; export interface AssetMedia { id: string; assetId: string; kind: AssetMediaKind; originalName: string; mimeType: 'image/jpeg' | 'image/png' | 'image/webp' | 'application/pdf'; sizeBytes: number; sha256: string; title: string | null; description: string | null; capturedAt: string | null; latitude: number | null; longitude: number | null; accuracyM: number | null; source: 'WEB' | 'ANDROID' | 'IMPORT'; uploadedBy: string | null; uploadedByUsername: string | null; createdAt: string; updatedAt: string; } export type AssetVersionChangeType = | 'BASELINE' | 'CREATED' | 'UPDATED' | 'CONTEXT_CHANGED' | 'STATUS_CHANGED' | 'OPERATIONAL_STATUS_CHANGED' | 'REGISTRY_UPDATED' | 'GEOMETRY_UPDATED' | 'GEOMETRY_REMOVED' | 'MEDIA_UPLOADED' | 'MEDIA_UPDATED' | 'MEDIA_REMOVED' | 'PROVENANCE_BASELINE' | 'PROVENANCE_UPDATED' | 'PROVENANCE_VERIFIED'; export interface AssetVersionSnapshot { id: string; code: string; name: string; description: string | null; type: { id: string; code: string; name: string; operationalRole?: AssetTypeOperationalRole }; parent: { id: string; code: string; name: string } | null; operationalArea?: { id: string; code: string; name: string } | null; operatorCompany?: { id: string; code: string; name: string } | null; informationStatus: AssetInformationStatus; operationalStatus: AssetOperationalStatus; organizationProfile?: Record | null; organizationMemberships?: Array>; externalIdentifiers?: Array>; sourceDocuments?: Array>; legalRights?: Array>; attributes: Array<{ definitionId: string; code: string; name: string; dataType: AssetAttributeDataType; isRequired: boolean; unit: string | null; options: string[] | null; sortOrder: number; value: unknown; }>; geometry: AssetGeometry | null; media?: AssetMedia[]; provenance?: { origin: AssetDataOrigin; sourceName: string | null; sourceReference: string | null; observedAt: string | null; notes: string | null; verifiedAt: string | null; verifiedBy: string | null; updatedAt: string; updatedBy: string | null; }; createdAt: string; updatedAt: string; createdBy: string | null; updatedBy: string | null; currentVersion: number; } export interface AssetVersionSummary { id: string; assetId: string; assetCode: string; assetName: string; typeId: string; typeName: string; informationStatus: AssetInformationStatus; operationalStatus: AssetOperationalStatus; versionNumber: number; changeType: AssetVersionChangeType; changedFields: string[]; occurredAt: string; actorUserId: string | null; actorUsername: string | null; source: 'WEB' | 'ANDROID' | 'SYSTEM' | 'IMPORT'; requestId: string | null; isCurrent: boolean; } export interface AssetVersionDetail extends AssetVersionSummary { snapshot: AssetVersionSnapshot; } export interface TemporalAssetSummary extends AssetVersionSummary { effectiveUntil: string | null; } export interface TemporalAssetDetail extends TemporalAssetSummary { snapshot: AssetVersionSnapshot; asOf: string; } export type SurveyCampaignStatus = | 'DRAFT' | 'PLANNED' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'; export type SurveyTargetStatus = | 'PENDING' | 'IN_PROGRESS' | 'SUBMITTED' | 'COMPLETED' | 'SKIPPED'; export interface SurveyPerson { id: string; username: string; firstName: string; lastName: string; } export interface SurveyCampaignListItem { id: string; code: string; name: string; description: string | null; status: SurveyCampaignStatus; plannedStartAt: string | null; plannedEndAt: string | null; scopeAsset: { id: string; code: string; name: string } | null; coordinator: SurveyPerson | null; targetCount: number; pendingCount: number; inProgressCount: number; submittedCount: number; completedCount: number; skippedCount: number; createdAt: string; updatedAt: string; } export interface SurveyTarget { id: string; campaignId: string; asset: { id: string; code: string; name: string; typeName: string }; assignedUser: SurveyPerson | null; status: SurveyTargetStatus; dueAt: string | null; instructions: string | null; createdAt: string; updatedAt: string; } export interface SurveyCampaign extends SurveyCampaignListItem { targets: SurveyTarget[]; } export type SurveyReportOutcome = 'CONFIRMED' | 'CHANGES_RECORDED' | 'NOT_LOCATED'; export type SurveyReportStatus = 'DRAFT' | 'SUBMITTED' | 'APPROVED' | 'REJECTED'; export type SurveyReportVersionEvent = 'SUBMITTED' | 'APPROVED' | 'REJECTED'; export interface SurveyTargetReport { id: string; targetId: string; outcome: SurveyReportOutcome | null; status: SurveyReportStatus; observedAt: string | null; latitude: number | null; longitude: number | null; accuracyM: number | null; notes: string | null; assetVersionAtSubmission: number | null; submittedAt: string | null; submittedBy: SurveyPerson | null; reviewedAt: string | null; reviewedBy: SurveyPerson | null; reviewNotes: string | null; selectedMediaIds: string[]; createdAt: string; updatedAt: string; } export interface SurveyReportMedia extends AssetMedia { kind: 'PHOTO'; selected: boolean; } export interface SurveyReportVersion { id: string; versionNumber: number; event: SurveyReportVersionEvent; snapshot: Record; actorUserId: string | null; actorUsername: string | null; createdAt: string; } export interface SurveyExecution { target: { id: string; status: SurveyTargetStatus; dueAt: string | null; instructions: string | null; assignedUser: SurveyPerson | null; }; campaign: { id: string; code: string; name: string; status: SurveyCampaignStatus; }; asset: { id: string; code: string; name: string; typeName: string; informationStatus: AssetInformationStatus; currentVersion: number; }; report: SurveyTargetReport | null; availableMedia: SurveyReportMedia[]; versions: SurveyReportVersion[]; } export type InspectionVisitStatus = | 'DRAFT' | 'PLANNED' | 'IN_PROGRESS' | 'CLOSED' | 'CANCELLED'; export interface InspectionPerson { id: string; username: string; firstName: string; lastName: string; } export interface InspectionAssetSummary { id: string; code: string; name: string; commonName?: string | null; typeName: string; operatorCompany?: { id: string; code: string; name: string } | null; operationalArea?: { id: string; code: string; name: string } | null; } export interface InspectionPlanningContextAsset { id: string; code: string; name: string; } export type InspectionVisitAssetPlanningSource = 'LEGACY' | 'AUTOMATIC' | 'PREVENTIVE' | 'VERIFICATION'; export type InspectionChecklistItemKind = 'ANTECEDENT' | 'COMPANY_OVERDUE' | 'VERIFICATION_OVERDUE' | 'UPCOMING_CONTROL'; export interface InspectionPlannedAsset extends InspectionAssetSummary { included: boolean; planningSource: InspectionVisitAssetPlanningSource; exclusionReason: string | null; excludedAt: string | null; excludedBy: InspectionPerson | null; } export interface InspectionVisitChecklistItem { id: string; findingId: string; findingCode: string; findingTitle: string; findingStatus: InspectionFindingStatus; severity: number | null; itemKind: InspectionChecklistItemKind; referenceOn: string | null; asset: InspectionAssetSummary; assetIncluded: boolean; } export interface InspectionVisitChecklistSummary { generation: number; generatedAt: string | null; stale: boolean; antecedents: number; companyOverdue: number; verificationOverdue: number; upcomingControls: number; actionableAssets: number; excludedAssets: number; items: InspectionVisitChecklistItem[]; } export interface InspectionVisitListItem { id: string; code: string; title: string; objective: string | null; status: InspectionVisitStatus; scopeAsset: InspectionAssetSummary | null; operationalArea: InspectionPlanningContextAsset | null; operatorCompany: InspectionPlanningContextAsset | null; leadInspector: InspectionPerson | null; plannedStartAt: string | null; plannedEndAt: string | null; actualStartedAt: string | null; actualClosedAt: string | null; instructions: string | null; cancellationReason: string | null; checklistGeneration: number; checklistGeneratedAt: string | null; assetCount: number; memberCount: number; createdAt: string; updatedAt: string; } export type InspectionVerificationOutcome = 'RESOLVED' | 'NOT_RESOLVED' | 'REQUIRES_NEW_DATE'; export interface InspectionVerificationFindingSummary { id: string; code: string; title: string; status: InspectionFindingStatus; nextControlOn: string | null; assetId: string; assetCode: string; assetName: string; targetControlOn: string | null; outcome: InspectionVerificationOutcome | null; resultNotes: string | null; verifiedAt: string | null; resultRecordedAt: string | null; rescheduledControlOn: string | null; verificationEvidenceCount: number; } export interface InspectionVisit extends InspectionVisitListItem { assets: InspectionAssetSummary[]; planningAssets: InspectionPlannedAsset[]; team: InspectionPerson[]; verificationFindings: InspectionVerificationFindingSummary[]; checklist: InspectionVisitChecklistSummary; } export type InspectionActStatus = | 'DRAFT' | 'READY' | 'CLOSED' | 'CANCELLED' | 'RECTIFIED'; export type InspectionActVersionEvent = | 'CREATED' | 'UPDATED' | 'READY' | 'REOPENED' | 'CLOSED' | 'CANCELLED'; export interface InspectionActListItem { id: string; visitId: string; visit: { id: string; code: string; title: string; status: InspectionVisitStatus; actualStartedAt: string | null; }; actYear: number; actNumber: number; code: string; status: InspectionActStatus; occurredAt: string; title: string; summary: string; observations: string | null; currentVersion: number; cancellationReason: string | null; closedAt: string | null; closedBy: string | null; closureSha256: string | null; assetCount: number; findingCount: number; companies: Array<{ id: string; code: string; name: string }>; areas: Array<{ id: string; code: string; name: string }>; report: null | { id: string; code: string; status: InspectionReportStatus; pdfStatus: InspectionReportPdfStatus; generatedAt: string; }; createdBy: InspectionPerson | null; updatedBy: InspectionPerson | null; createdAt: string; updatedAt: string; } export interface InspectionActVersion { id: string; versionNumber: number; event: InspectionActVersionEvent; snapshot: Record; actorUserId: string | null; actorUsername: string | null; createdAt: string; } export interface InspectionAct extends InspectionActListItem { assets: InspectionAssetSummary[]; versions: InspectionActVersion[]; } export type InspectionReportStatus = 'FROZEN' | 'CANCELLED'; export type InspectionReportPdfStatus = 'PENDING' | 'READY' | 'FAILED'; export type InspectionReportWordStatus = 'PENDING' | 'READY' | 'FAILED'; export type InspectionReportReviewStatus = 'PENDING_REVIEW' | 'APPROVED' | 'SIGNED'; export interface InspectionReportListItem { id: string; visitId: string; actId: string; reportYear: number; reportNumber: number; code: string; status: InspectionReportStatus; pdfStatus: InspectionReportPdfStatus; wordStatus: InspectionReportWordStatus; wordGeneratedAt: string | null; reviewStatus: InspectionReportReviewStatus; currentRevisionNumber: number; approvedAt: string | null; signedAt: string | null; title: string; actVersion: number; actClosureSha256: string; frozenSha256: string; generatedAt: string; generatedBy: InspectionPerson; act: { id: string; code: string; title: string; status: InspectionActStatus; occurredAt: string; closedAt: string | null; }; visit: { id: string; code: string; title: string; status: InspectionVisitStatus; }; companies: Array<{ id: string; code: string; name: string }>; areas: Array<{ id: string; code: string; name: string }>; findingCount: number; } export interface InspectionReport extends InspectionReportListItem { frozenSnapshot: Record; } export type InspectionReportRevisionSource = 'AUTO' | 'DIRECTOR_UPLOAD'; export interface InspectionReportRevision { id: string; reportId: string; revisionNumber: number; source: InspectionReportRevisionSource; originalName: string; mimeType: string; sizeBytes: number; sha256: string; changeSummary: string | null; createdAt: string; createdBy: InspectionPerson; } export interface InspectionReportSignature { id: string; revisionId: string; signedAt: string; confirmationText: string; signatureSha256: string; signedBy: InspectionPerson; } export interface InspectionReportReview { reportId: string; reportCode: string; reviewStatus: InspectionReportReviewStatus; currentRevisionNumber: number; approvedRevisionId: string | null; approvedAt: string | null; reviewNote: string | null; approvedBy: InspectionPerson | null; signature: InspectionReportSignature | null; revisions: InspectionReportRevision[]; } export interface PendingInspectionReportItem { actId: string; visitId: string; actCode: string; actTitle: string; actYear: number; occurredAt: string; closedAt: string; closureSha256: string; visitCode: string; visitTitle: string; companies: Array<{ id: string; code: string; name: string }>; areas: Array<{ id: string; code: string; name: string }>; findingCount: number; } export type InspectionResponsibleAttendanceStatus = 'PRESENT' | 'ABSENT'; export type InspectionResponsibleDocumentType = 'DNI' | 'CUIL' | 'PASSPORT' | 'OTHER'; export type InspectionActSignatureStatus = 'SIGNED' | 'REFUSED' | 'ABSENT'; export type InspectionActSignerType = 'INSPECTOR' | 'COMPANY_RESPONSIBLE'; export type InspectionActUploadMode = 'IMMEDIATE' | 'DEFERRED'; export interface InspectionActResponsible { actId: string; attendanceStatus: InspectionResponsibleAttendanceStatus; fullName: string | null; documentType: InspectionResponsibleDocumentType | null; documentNumber: string | null; position: string | null; email: string | null; phone: string | null; absenceReason: string | null; updatedBy: string | null; createdAt: string; updatedAt: string; } export interface InspectionActSignature { id: string; actId: string; signerType: InspectionActSignerType; signerUserId: string | null; signerName: string; documentType: InspectionResponsibleDocumentType | null; documentNumber: string | null; position: string | null; status: InspectionActSignatureStatus; reason: string | null; mimeType: string | null; sizeBytes: number | null; imageSha256: string | null; consentText: string | null; consentVersion: string | null; consentAcceptedAt: string | null; clientSignedAt: string | null; signedAt: string | null; latitude: number | null; longitude: number | null; accuracyM: number | null; deviceLabel: string | null; source: 'WEB' | 'ANDROID'; preparedSha256: string; signaturePayloadSha256: string; uploadedBy: string; uploadedByUsername: string; createdAt: string; } export interface InspectionClosure { act: { id: string; code: string; status: InspectionActStatus; visitId: string; currentVersion: number; closedAt: string | null; closedBy: string | null; closureSha256: string | null; }; visit: { id: string; code: string; status: InspectionVisitStatus; actualClosedAt: string | null; }; responsible: InspectionActResponsible | null; closure: null | { schemaVersion: string; preparedSha256: string; preparedAt: string; preparedBy: string; finalSha256: string | null; deviceClosedAt: string | null; serverClosedAt: string | null; uploadMode: InspectionActUploadMode | null; closedBy: string | null; isCurrent: boolean; }; signatures: InspectionActSignature[]; consents: { version: string; inspector: string; company: string; }; } export interface FindingCategory { id: string; code: string; name: string; sortOrder: number; } export interface FindingCatalogItem { id: string; categoryId: string; categoryName: string; code: string; sourceNumber: number; title: string; legalBasis: string | null; glossary: string | null; suggestedSeverity: number | null; revision: number; } export interface FindingCatalog { categories: FindingCategory[]; items: FindingCatalogItem[]; } export interface FindingAdminCategory extends FindingCategory { isActive: boolean; itemCount: number; activeItemCount: number; createdAt: string; updatedAt: string; } export interface FindingAdminCatalogItem extends FindingCatalogItem { categoryCode: string; categoryActive: boolean; importNote: string | null; isActive: boolean; usageCount: number; createdAt: string; updatedAt: string; } export interface FindingAdminCatalog { categories: FindingAdminCategory[]; items: FindingAdminCatalogItem[]; } export interface FindingCatalogSelectionItem { id: string; categoryId: string; categoryName: string; code: string; sourceNumber: number; title: string; suggestedSeverity: number | null; enabled: boolean; } export interface FindingCatalogAssetTypeSelection { assetType: { id: string; code: string; name: string; operationalRole: AssetTypeOperationalRole }; configured: boolean; reason: string | null; items: FindingCatalogSelectionItem[]; } export interface FindingCatalogAssetSelection { asset: { id: string; code: string; name: string; assetTypeId: string; assetTypeCode: string; assetTypeName: string; }; typeConfigured: boolean; typeReason: string | null; items: Array; } export interface FindingCatalogApplicable extends FindingCatalog { asset: FindingCatalogAssetSelection['asset']; typeConfigured: boolean; configurationReason: string | null; other: { enabled: true; code: 'OTHER'; label: string; help: string }; } export type FindingCatalogProposalStatus = 'PENDING' | 'MATCHED' | 'REJECTED'; export interface FindingCatalogProposal { id: string; findingId: string; findingCode: string; assetId: string; assetCode: string; assetName: string; assetTypeId: string; assetTypeCode: string; assetTypeName: string; proposedTitle: string; proposedLegalBasis: string | null; proposedSeverity: number | null; description: string; status: FindingCatalogProposalStatus; resolvedCatalogItemId: string | null; resolvedCatalogCode: string | null; resolvedCatalogTitle: string | null; officeNotes: string | null; reviewedBy: string | null; reviewedByUsername: string | null; reviewedAt: string | null; createdAt: string; updatedAt: string; } export type InspectionFindingWorkflow = | 'ALL' | 'OPEN' | 'WAITING_COMPANY' | 'COMPANY_OVERDUE' | 'TO_SCHEDULE_VERIFICATION' | 'TO_VERIFY' | 'VERIFICATION_OVERDUE' | 'READY_TO_CLOSE' | 'CLOSED'; export interface InspectionFindingWorkflowCounters { open: number; waitingCompany: number; companyOverdue: number; companyDueNext7Days: number; awaitingVerificationSchedule: number; toVerify: number; verificationOverdue: number; verificationNext30Days: number; readyToClose: number; closed: number; } export interface InspectionFindingGlobalPage extends Page { counters: InspectionFindingWorkflowCounters; } export type VerificationPlanningStatus = 'ALL' | 'UNPLANNED' | 'PLANNED'; export interface VerificationPlanningItem { id: string; code: string; title: string; status: 'OPEN'; nextControlOn: string; companyResponseReceivedOn: string; asset: { id: string; code: string; name: string; typeName: string }; company: { id: string; code: string; name: string } | null; area: { id: string; code: string; name: string } | null; act: { id: string; code: string }; sourceVisit: { id: string; code: string; title: string }; verificationVisit: null | { id: string; code: string; title: string; status: InspectionVisitStatus; plannedStartAt: string | null; plannedEndAt: string | null; }; } export interface VerificationPlanningCounters { eligible: number; unplanned: number; overdueUnplanned: number; dueNext30Days: number; planned: number; } export interface VerificationPlanningPage extends Page { counters: VerificationPlanningCounters; } export interface PlannedVerificationVisitResult { visit: { id: string; code: string; title: string; status: InspectionVisitStatus; plannedStartAt: string | null; plannedEndAt: string | null; }; company: { id: string; code: string; name: string }; area: { id: string; code: string; name: string }; findingCount: number; assetCount: number; } export type InspectionFindingStatus = 'OPEN' | 'CLOSED' | 'VOIDED'; export type InspectionFindingVerificationEventType = | 'CONTROL_DATE_DEFINED' | 'CONTROL_DATE_CHANGED' | 'CONTROL_DATE_CLEARED' | 'VISIT_PLANNED' | 'RESULT_RECORDED'; export interface InspectionFindingVerificationEvent { id: string; eventType: InspectionFindingVerificationEventType; occurredAt: string; targetControlOn: string | null; previousControlOn: string | null; nextControlOn: string | null; outcome: InspectionVerificationOutcome | null; notes: string | null; actorUserId: string | null; actorUsername: string | null; verificationVisit: null | { id: string; code: string; title: string; status: InspectionVisitStatus; plannedStartAt: string | null; }; evidenceCount: number; } export type InspectionFindingVersionEvent = 'CREATED' | 'UPDATED' | 'FOLLOW_UP_UPDATED' | 'VERIFICATION_RECORDED'; export type InspectionFindingOfficeStage = | 'WAITING_COMPANY' | 'COMPANY_OVERDUE' | 'DEFINE_VERIFICATION' | 'PLAN_VERIFICATION' | 'VERIFICATION_PLANNED' | 'RESCHEDULE_VERIFICATION' | 'READY_TO_CLOSE' | 'CLOSED'; export interface InspectionFindingOfficeWorkflow { stage: InspectionFindingOfficeStage; label: string; action: string; dueOn: string | null; overdue: boolean; } export interface InspectionFinding { id: string; actId: string; assetId: string; catalogItemId: string | null; findingNumber: number; code: string; status: InspectionFindingStatus; title: string; description: string; legalBasis: string | null; glossary: string | null; catalogRevision: number | null; suggestedSeverity: number | null; severity: number | null; correctionDueOn: string | null; responseDueBasis: 'FINDING_DATE' | 'REPORT_NOTIFICATION' | null; responseDueDays: number | null; responseDueBaseOn: string | null; reportNotifiedOn: string | null; companyResponse: string | null; companyResponseReceivedOn: string | null; companyCommittedCorrectionOn: string | null; nextControlOn: string | null; currentVersion: number; closedAt: string | null; closureNotes: string | null; asset: InspectionAssetSummary; catalog: { id: string; code: string; categoryId: string; categoryName: string; sourceNumber: number; revision: number; suggestedSeverity: number | null; } | null; document: { actId: string; actCode: string; actStatus: InspectionActStatus; visitId: string; visitCode: string; visitTitle: string; visitStatus: InspectionVisitStatus; }; verificationVisit: null | { id: string; code: string; title: string; status: InspectionVisitStatus; plannedStartAt: string | null; }; latestVerification: null | { visitId: string; visitCode: string; visitStatus: InspectionVisitStatus; targetControlOn: string | null; outcome: InspectionVerificationOutcome | null; resultNotes: string | null; verifiedAt: string | null; resultRecordedAt: string | null; rescheduledControlOn: string | null; evidenceCount: number; }; verificationHistory?: InspectionFindingVerificationEvent[]; createdAt: string; updatedAt: string; officeWorkflow?: InspectionFindingOfficeWorkflow; versions?: Array<{ id: string; versionNumber: number; event: InspectionFindingVersionEvent; snapshot: Record; actorUserId: string | null; actorUsername: string | null; createdAt: string; }>; } export type InspectionEvidenceKind = 'PHOTO' | 'DOCUMENT'; export type InspectionEvidencePurpose = | 'OBSERVATION' | 'VERIFICATION' | 'COMPANY_RESPONSE' | 'COMMUNICATION_ATTACHMENT' | 'OTHER_DOCUMENT'; export interface InspectionFindingCommunication { id: string; findingId: string; direction: 'INBOUND' | 'OUTBOUND' | 'INTERNAL'; channel: 'EMAIL' | 'IN_PERSON' | 'PHONE' | 'LETTER' | 'SYSTEM' | 'OTHER'; type: 'COMPANY_RESPONSE' | 'AUTHORITY_NOTICE' | 'FOLLOW_UP' | 'OTHER'; occurredAt: string; subject: string; details: string | null; contactName: string | null; contactEmail: string | null; createdBy: string | null; createdByUsername: string | null; attachmentCount: number; createdAt: string; } export interface InspectionFindingEvidence { id: string; findingId: string; communicationId: string | null; verificationVisitId: string | null; communication: { id: string; type: InspectionFindingCommunication['type']; direction: InspectionFindingCommunication['direction']; subject: string; } | null; kind: InspectionEvidenceKind; purpose: InspectionEvidencePurpose; originalName: string; mimeType: string; sizeBytes: number; sha256: string; title: string | null; description: string | null; capturedAt: string | null; latitude: number | null; longitude: number | null; accuracyM: number | null; deviceLabel: string | null; source: 'WEB' | 'ANDROID' | 'IMPORT'; uploadedBy: string | null; uploadedByUsername: string | null; createdAt: string; } let csrfToken: string | null = null; let refreshPromise: Promise | null = null; function readCsrfCookie(): string | null { if (typeof document === 'undefined') return null; const cookies = document.cookie.split(';').map((item) => item.trim()); const exact = cookies.find((item) => item.startsWith('dhv2_csrf=')); const fallback = cookies.find((item) => item.split('=', 1)[0]?.endsWith('_csrf')); const raw = exact ?? fallback; if (!raw) return null; const separator = raw.indexOf('='); return separator >= 0 ? decodeURIComponent(raw.slice(separator + 1)) : null; } function isUnsafe(method: string): boolean { return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase()); } async function decodeResponse(response: Response): Promise { const body = await response.json().catch(() => null) as { code?: string; message?: string; requestId?: string; } | null; if (!response.ok) { throw new ApiError( response.status, body?.code ?? 'REQUEST_ERROR', body?.message ?? `La solicitud falló con HTTP ${response.status}`, body?.requestId, ); } return body as T; } async function rawRequest(path: string, init: RequestInit = {}): Promise { const method = (init.method ?? 'GET').toUpperCase(); const headers = new Headers(init.headers); headers.set('Accept', 'application/json'); if (init.body && !(init.body instanceof FormData)) { headers.set('Content-Type', 'application/json'); } if (isUnsafe(method) && path !== '/auth/login') { const token = csrfToken ?? readCsrfCookie(); if (token) headers.set('x-csrf-token', token); } const response = await fetch(`${API_BASE}${path}`, { ...init, method, headers, credentials: 'include', cache: 'no-store', }); return decodeResponse(response); } async function refreshAccess(): Promise { if (!refreshPromise) { refreshPromise = rawRequest('/auth/refresh', { method: 'POST' }) .then((response) => { csrfToken = response.csrfToken; }) .finally(() => { refreshPromise = null; }); } return refreshPromise; } export async function apiRequest( path: string, init: RequestInit = {}, retrySession = true, ): Promise { try { return await rawRequest(path, init); } catch (error) { if ( retrySession && error instanceof ApiError && error.status === 401 && path !== '/auth/login' && path !== '/auth/refresh' ) { try { await refreshAccess(); return await rawRequest(path, init); } catch (refreshError) { csrfToken = null; window.dispatchEvent(new Event('dhv2:unauthorized')); throw refreshError; } } throw error; } } async function rawBlobRequest(path: string): Promise { const response = await fetch(`${API_BASE}${path}`, { method: 'GET', headers: { Accept: '*/*' }, credentials: 'include', cache: 'no-store', }); if (!response.ok) return decodeResponse(response); return response.blob(); } async function apiBlobRequest(path: string): Promise { try { return await rawBlobRequest(path); } catch (error) { if (error instanceof ApiError && error.status === 401) { try { await refreshAccess(); return await rawBlobRequest(path); } catch (refreshError) { csrfToken = null; window.dispatchEvent(new Event('dhv2:unauthorized')); throw refreshError; } } throw error; } } export function getHealth() { return apiRequest('/health', {}, false); } export async function login(identifier: string, password: string) { const response = await apiRequest('/auth/login', { method: 'POST', body: JSON.stringify({ identifier, password, deviceLabel: 'Panel web' }), }, false); csrfToken = response.csrfToken; return response; } export async function getMe() { const response = await apiRequest<{ user: AuthUser }>('/auth/me'); return response.user; } export async function logout() { await apiRequest<{ status: string }>('/auth/logout', { method: 'POST' }); csrfToken = null; } export function changePassword(currentPassword: string, newPassword: string) { return apiRequest<{ status: string; mustChangePassword: false }>('/auth/change-password', { method: 'POST', body: JSON.stringify({ currentPassword, newPassword }), }); } export function getDashboardSummary() { return apiRequest('/dashboard/summary'); } export function listUsers(params: { page?: number; pageSize?: number; search?: string; status?: 'ACTIVE' | 'INACTIVE' | ''; } = {}) { const query = new URLSearchParams(); query.set('page', String(params.page ?? 1)); query.set('pageSize', String(params.pageSize ?? 20)); if (params.search) query.set('search', params.search); if (params.status) query.set('status', params.status); return apiRequest>(`/users?${query}`); } export function getUser(id: string) { return apiRequest(`/users/${id}`); } export function createUser(input: { username: string; email?: string; firstName: string; lastName: string; password: string; mustChangePassword: boolean; roleIds: string[]; }) { return apiRequest('/users', { method: 'POST', body: JSON.stringify(input), }); } export function updateUser(id: string, input: { username?: string; email?: string | null; firstName?: string; lastName?: string; }) { return apiRequest(`/users/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function resetUserPassword(id: string, password: string, mustChangePassword = true) { return apiRequest(`/users/${id}/reset-password`, { method: 'POST', body: JSON.stringify({ password, mustChangePassword }), }); } export function updateUserStatus(id: string, status: 'ACTIVE' | 'INACTIVE') { return apiRequest(`/users/${id}/status`, { method: 'PATCH', body: JSON.stringify({ status }), }); } export function replaceUserRoles(id: string, roleIds: string[]) { return apiRequest(`/users/${id}/roles`, { method: 'PUT', body: JSON.stringify({ roleIds }), }); } export async function listRoles() { return (await apiRequest<{ data: AdministrativeRole[] }>('/roles')).data; } export async function listPermissions() { return (await apiRequest<{ data: Permission[] }>('/roles/permissions')).data; } export function createRole(input: { code: string; name: string; description: string; permissionIds: string[]; }) { return apiRequest('/roles', { method: 'POST', body: JSON.stringify(input), }); } export function updateRole(id: string, input: { name: string; description: string }) { return apiRequest(`/roles/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function replaceRolePermissions(id: string, permissionIds: string[]) { return apiRequest(`/roles/${id}/permissions`, { method: 'PUT', body: JSON.stringify({ permissionIds }), }); } export function listAudit(params: Record) { const query = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== '') query.set(key, String(value)); }); return apiRequest>(`/audit?${query}`); } export function getAuditEvent(id: string) { return apiRequest(`/audit/${id}`); } export async function listAssetTypes() { return (await apiRequest<{ data: AssetType[] }>('/asset-types')).data; } export function getMasterBootstrapStatus() { return apiRequest('/asset-types/bootstrap-status'); } export function bootstrapMasterDefaults() { return apiRequest('/asset-types/bootstrap-defaults', { method: 'POST', }); } export function getMasterEnrichmentStatus() { return apiRequest('/asset-types/enrichment-status'); } export function enrichMasterDefaults() { return apiRequest('/asset-types/enrich-defaults', { method: 'POST', }); } export async function listOperationalAreas(parentId?: string) { const query = parentId ? `?${new URLSearchParams({ parentId })}` : ''; return (await apiRequest<{ data: OperationalAssetSummary[] }>(`/asset-operational-relations/areas${query}`)).data; } export async function listOperationalCompanies() { return (await apiRequest<{ data: OperationalAssetSummary[] }>('/asset-operational-relations/companies')).data; } export async function listCompaniesForArea(areaId: string) { return (await apiRequest<{ data: OperationalAssetSummary[] }>(`/asset-operational-relations/areas/${areaId}/companies`)).data; } export async function listAreasForCompany(companyId: string) { return (await apiRequest<{ data: OperationalAssetSummary[] }>(`/asset-operational-relations/companies/${companyId}/areas`)).data; } export async function listAreaCompanyRelations(params: { areaId?: string; companyId?: string; includeHistory?: boolean; } = {}) { const query = new URLSearchParams(); if (params.areaId) query.set('areaId', params.areaId); if (params.companyId) query.set('companyId', params.companyId); if (params.includeHistory) query.set('includeHistory', 'true'); const suffix = query.size ? `?${query}` : ''; return (await apiRequest<{ data: AreaCompanyRelation[] }>(`/asset-operational-relations${suffix}`)).data; } export function createAreaCompanyRelation(input: { areaId: string; companyId: string; relationRole?: AreaOrganizationRole; participationPercent?: number | null; legalInstrument?: string | null; sourceDocumentId?: string | null; reason: string }) { return apiRequest('/asset-operational-relations', { method: 'POST', body: JSON.stringify(input), }); } export function endAreaCompanyRelation(id: string, reason: string) { return apiRequest(`/asset-operational-relations/${id}/end`, { method: 'POST', body: JSON.stringify({ reason }), }); } export function createAssetType(input: { code: string; name: string; description: string; canBeRoot: boolean; operationalRole: AssetTypeOperationalRole; allowedParentTypeIds: string[]; }) { return apiRequest('/asset-types', { method: 'POST', body: JSON.stringify(input), }); } export function updateAssetType(id: string, input: { name?: string; description?: string; canBeRoot?: boolean; isActive?: boolean; operationalRole?: AssetTypeOperationalRole; allowedParentTypeIds?: string[]; }) { return apiRequest(`/asset-types/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function createAssetAttribute(typeId: string, input: { code: string; name: string; dataType: AssetAttributeDataType; isRequired: boolean; unit?: string | null; options?: string[]; sortOrder: number; }) { return apiRequest(`/asset-types/${typeId}/attributes`, { method: 'POST', body: JSON.stringify(input), }); } export function updateAssetAttribute( typeId: string, attributeId: string, input: { name?: string; dataType?: AssetAttributeDataType; isRequired?: boolean; isActive?: boolean; unit?: string | null; options?: string[] | null; sortOrder?: number; }, ) { return apiRequest(`/asset-types/${typeId}/attributes/${attributeId}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function listAssets(params: { page?: number; pageSize?: number; search?: string; typeId?: string; status?: AssetInformationStatus | ''; operationalStatus?: AssetOperationalStatus | ''; needsValidation?: boolean; hasGeometry?: boolean; parentId?: string; operationalAreaId?: string; operatorCompanyId?: string; } = {}) { const query = new URLSearchParams(); query.set('page', String(params.page ?? 1)); query.set('pageSize', String(params.pageSize ?? 25)); if (params.search) query.set('search', params.search); if (params.typeId) query.set('typeId', params.typeId); if (params.status) query.set('status', params.status); if (params.operationalStatus) query.set('operationalStatus', params.operationalStatus); if (params.needsValidation !== undefined) query.set('needsValidation', String(params.needsValidation)); if (params.hasGeometry !== undefined) query.set('hasGeometry', String(params.hasGeometry)); if (params.parentId) query.set('parentId', params.parentId); if (params.operationalAreaId) query.set('operationalAreaId', params.operationalAreaId); if (params.operatorCompanyId) query.set('operatorCompanyId', params.operatorCompanyId); return apiRequest>(`/assets?${query}`); } export async function listAssetTree(params: { search?: string; typeId?: string; status?: AssetInformationStatus | ''; operationalStatus?: AssetOperationalStatus | ''; operationalAreaId?: string; operatorCompanyId?: string; needsValidation?: boolean; hasGeometry?: boolean; } = {}) { const query = new URLSearchParams(); if (params.search) query.set('search', params.search); if (params.typeId) query.set('typeId', params.typeId); if (params.status) query.set('status', params.status); if (params.operationalStatus) query.set('operationalStatus', params.operationalStatus); if (params.operationalAreaId) query.set('operationalAreaId', params.operationalAreaId); if (params.operatorCompanyId) query.set('operatorCompanyId', params.operatorCompanyId); if (params.needsValidation !== undefined) query.set('needsValidation', String(params.needsValidation)); if (params.hasGeometry !== undefined) query.set('hasGeometry', String(params.hasGeometry)); const suffix = query.toString() ? `?${query}` : ''; return apiRequest<{ data: AssetListItem[]; meta: { count: number; truncated: boolean } }>(`/assets/tree${suffix}`); } export async function listAssetTreeChildren(params: { parentId?: string; search?: string; typeId?: string; status?: AssetInformationStatus | ''; operationalStatus?: AssetOperationalStatus | ''; operationalAreaId?: string; operatorCompanyId?: string; needsValidation?: boolean; hasGeometry?: boolean; limit?: number; } = {}) { const query = new URLSearchParams(); if (params.parentId) query.set('parentId', params.parentId); if (params.search) query.set('search', params.search); if (params.typeId) query.set('typeId', params.typeId); if (params.status) query.set('status', params.status); if (params.operationalStatus) query.set('operationalStatus', params.operationalStatus); if (params.operationalAreaId) query.set('operationalAreaId', params.operationalAreaId); if (params.operatorCompanyId) query.set('operatorCompanyId', params.operatorCompanyId); if (params.needsValidation !== undefined) query.set('needsValidation', String(params.needsValidation)); if (params.hasGeometry !== undefined) query.set('hasGeometry', String(params.hasGeometry)); query.set('limit', String(params.limit ?? 100)); return apiRequest<{ data: AssetListItem[]; meta: { count: number; hasMore: boolean; parentId: string | null } }>(`/assets/tree-children?${query}`); } export async function listAssetParentOptions( childTypeId: string, assetId?: string, search?: string, ) { const query = new URLSearchParams({ childTypeId }); if (assetId) query.set('assetId', assetId); if (search) query.set('search', search); return (await apiRequest<{ data: AssetListItem[] }>(`/assets/parent-options?${query}`)).data; } export function getAsset(id: string) { return apiRequest(`/assets/${id}`); } export function getAssetDossier(id: string) { return apiRequest(`/assets/${id}/dossier`); } export async function getAssetLineage(id: string) { return (await apiRequest<{ data: AssetLineageItem[] }>(`/assets/${id}/lineage`)).data; } export function createAsset(input: { code: string; name: string; commonName?: string | null; typeId: string; parentId?: string | null; operationalAreaId?: string | null; operatorCompanyId?: string | null; description?: string | null; informationStatus?: AssetInformationStatus; attributes: Record; }) { return apiRequest('/assets', { method: 'POST', body: JSON.stringify(input), }); } export function updateAsset(id: string, input: { typeId?: string; code?: string; name?: string; commonName?: string | null; parentId?: string | null; operationalAreaId?: string | null; operatorCompanyId?: string | null; description?: string | null; attributes?: Record; }) { return apiRequest(`/assets/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function updateAssetInformationStatus( id: string, informationStatus: AssetInformationStatus, ) { return apiRequest(`/assets/${id}/information-status`, { method: 'PATCH', body: JSON.stringify({ informationStatus }), }); } export function updateAssetOperationalStatus(id: string, status: AssetOperationalStatus) { return apiRequest(`/assets/${id}/operational-status`, { method: 'PATCH', body: JSON.stringify({ status }), }); } export async function listAssetContextHistory(assetId: string) { return (await apiRequest<{ data: AssetContextHistoryItem[] }>(`/assets/${assetId}/context-history`)).data; } export function changeAssetContext(assetId: string, input: { parentId?: string | null; operationalAreaId?: string | null; operatorCompanyId?: string | null; effectiveAt?: string; reason: string; }) { return apiRequest(`/assets/${assetId}/context`, { method: 'POST', body: JSON.stringify(input), }); } export function listFieldDiscoveries(params: { page?: number; pageSize?: number; status?: FieldDiscoveryStatus; search?: string } = {}) { const query = new URLSearchParams(); query.set('page', String(params.page ?? 1)); query.set('pageSize', String(params.pageSize ?? 25)); if (params.status) query.set('status', params.status); if (params.search) query.set('search', params.search); return apiRequest>(`/assets/field-discoveries?${query}`); } export function approveFieldDiscovery(id: string, notes?: string | null) { return apiRequest(`/assets/field-discoveries/${id}/approve`, { method: 'POST', body: JSON.stringify({ notes: notes || null }) }); } export function rejectFieldDiscovery(id: string, reason: string) { return apiRequest(`/assets/field-discoveries/${id}/reject`, { method: 'POST', body: JSON.stringify({ reason }) }); } export function matchFieldDiscovery(id: string, matchedAssetId: string, reason: string) { return apiRequest(`/assets/field-discoveries/${id}/match`, { method: 'POST', body: JSON.stringify({ matchedAssetId, reason }) }); } export function getAssetRegistry(assetId: string) { return apiRequest(`/assets/${assetId}/registry`); } export async function listSourceDocuments(search = '') { const suffix = search.trim() ? `?search=${encodeURIComponent(search.trim())}` : ''; return (await apiRequest<{ data: SourceDocumentSummary[] }>(`/source-documents${suffix}`)).data; } export function createSourceDocument(input: { documentType: SourceDocumentType; documentNumber?: string | null; title: string; issuer?: string | null; documentDate?: string | null; externalReference?: string | null; notes?: string | null }) { return apiRequest('/source-documents', { method: 'POST', body: JSON.stringify(input) }); } export function linkAssetSourceDocument(assetId: string, documentId: string, input: { relationType: AssetSourceDocumentRelationType; notes?: string | null }) { return apiRequest(`/assets/${assetId}/source-documents/${documentId}`, { method: 'POST', body: JSON.stringify(input) }); } export function addAssetExternalIdentifier(assetId: string, input: { namespace: string; value: string; validFrom?: string | null; sourceDocumentId?: string | null; notes?: string | null }) { return apiRequest(`/assets/${assetId}/external-identifiers`, { method: 'POST', body: JSON.stringify(input) }); } export function endAssetExternalIdentifier(id: string, reason: string) { return apiRequest(`/asset-external-identifiers/${id}/end`, { method: 'POST', body: JSON.stringify({ reason }) }); } export function upsertOrganizationProfile(assetId: string, input: { organizationKind: OrganizationKind; legalName?: string | null; taxId?: string | null; notificationEmail?: string | null; notes?: string | null }) { return apiRequest(`/assets/${assetId}/organization-profile`, { method: 'PATCH', body: JSON.stringify(input) }); } export function addOrganizationMembership(organizationId: string, input: { memberOrganizationId: string; role: OrganizationMembershipRole; participationPercent?: number | null; validFrom?: string | null; sourceDocumentId?: string | null; notes?: string | null }) { return apiRequest(`/organizations/${organizationId}/memberships`, { method: 'POST', body: JSON.stringify(input) }); } export function endOrganizationMembership(id: string, reason: string) { return apiRequest(`/organization-memberships/${id}/end`, { method: 'POST', body: JSON.stringify({ reason }) }); } export function createAreaLegalRight(areaId: string, input: { rightType: AreaLegalRightType; name: string; instrumentNumber?: string | null; validFrom?: string | null; validUntil?: string | null; status?: AreaLegalRightStatus; sourceDocumentId?: string | null; notes?: string | null }) { return apiRequest(`/areas/${areaId}/legal-rights`, { method: 'POST', body: JSON.stringify(input) }); } export function addAreaLegalRightOrganization(rightId: string, input: { organizationId: string; role: AreaLegalRightOrganizationRole; participationPercent?: number | null; validFrom?: string | null; notes?: string | null }) { return apiRequest(`/area-legal-rights/${rightId}/organizations`, { method: 'POST', body: JSON.stringify(input) }); } export function endAreaLegalRightOrganization(id: string, reason: string) { return apiRequest(`/area-legal-right-organizations/${id}/end`, { method: 'POST', body: JSON.stringify({ reason }) }); } export async function getAssetGeometry(assetId: string) { return (await apiRequest<{ data: AssetGeometry | null }>(`/assets/${assetId}/geometry`)).data; } export function upsertAssetGeometry(assetId: string, input: { geometry: GeoJsonGeometry; accuracyM?: number | null; capturedAt?: string | null; deviceLabel?: string | null; }) { return apiRequest(`/assets/${assetId}/geometry`, { method: 'PUT', body: JSON.stringify(input), }); } export function removeAssetGeometry(assetId: string) { return apiRequest<{ status: 'removed' | 'absent' }>(`/assets/${assetId}/geometry`, { method: 'DELETE', }); } export function getMapAssets(params: { bbox?: string; typeId?: string; status?: AssetInformationStatus | ''; geometryType?: AssetGeometryType | ''; } = {}) { const query = new URLSearchParams(); if (params.bbox) query.set('bbox', params.bbox); if (params.typeId) query.set('typeId', params.typeId); if (params.status) query.set('status', params.status); if (params.geometryType) query.set('geometryType', params.geometryType); const suffix = query.size ? `?${query}` : ''; return apiRequest(`/map/assets${suffix}`); } export function listAssetVersions(params: { page?: number; pageSize?: number; search?: string; typeId?: string; status?: AssetInformationStatus | ''; changeType?: AssetVersionChangeType | ''; from?: string; to?: string; } = {}) { const query = new URLSearchParams(); query.set('page', String(params.page ?? 1)); query.set('pageSize', String(params.pageSize ?? 25)); if (params.search) query.set('search', params.search); if (params.typeId) query.set('typeId', params.typeId); if (params.status) query.set('status', params.status); if (params.changeType) query.set('changeType', params.changeType); if (params.from) query.set('from', params.from); if (params.to) query.set('to', params.to); return apiRequest>(`/asset-versions?${query}`); } export function listAssetVersionTimeline( assetId: string, params: { page?: number; pageSize?: number } = {}, ) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 10), }); return apiRequest>(`/assets/${assetId}/versions?${query}`); } export function getAssetVersion(assetId: string, versionNumber: number) { return apiRequest(`/assets/${assetId}/versions/${versionNumber}`); } export async function listAssetMedia(assetId: string) { return (await apiRequest<{ data: AssetMedia[] }>(`/assets/${assetId}/media`)).data; } export function uploadAssetMedia(assetId: string, input: { file: File; kind: AssetMediaKind; title?: string; description?: string; capturedAt?: string; latitude?: number; longitude?: number; accuracyM?: number; }) { const body = new FormData(); body.append('file', input.file); body.append('kind', input.kind); if (input.title) body.append('title', input.title); if (input.description) body.append('description', input.description); if (input.capturedAt) body.append('capturedAt', input.capturedAt); if (input.latitude !== undefined) body.append('latitude', String(input.latitude)); if (input.longitude !== undefined) body.append('longitude', String(input.longitude)); if (input.accuracyM !== undefined) body.append('accuracyM', String(input.accuracyM)); return apiRequest(`/assets/${assetId}/media`, { method: 'POST', body, }); } export function updateAssetMedia(mediaId: string, input: { title?: string | null; description?: string | null; capturedAt?: string | null; latitude?: number | null; longitude?: number | null; accuracyM?: number | null; }) { return apiRequest(`/asset-media/${mediaId}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function removeAssetMedia(mediaId: string) { return apiRequest<{ status: 'removed' }>(`/asset-media/${mediaId}`, { method: 'DELETE', }); } export function getAssetMediaBlob(mediaId: string, download = false) { return apiBlobRequest(`/asset-media/${mediaId}/content${download ? '?download=1' : ''}`); } export function getAssetProvenance(assetId: string) { return apiRequest(`/assets/${assetId}/provenance`); } export function updateAssetProvenance(assetId: string, input: { origin: AssetDataOrigin; sourceName?: string | null; sourceReference?: string | null; observedAt?: string | null; notes?: string | null; }) { return apiRequest(`/assets/${assetId}/provenance`, { method: 'PUT', body: JSON.stringify(input), }); } export function verifyAssetProvenance(assetId: string) { return apiRequest(`/assets/${assetId}/provenance/verify`, { method: 'POST', }); } export function listTemporalAssets(params: { at: string; page?: number; pageSize?: number; search?: string; typeId?: string; status?: AssetInformationStatus | ''; }) { const query = new URLSearchParams({ at: params.at, page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.typeId) query.set('typeId', params.typeId); if (params.status) query.set('status', params.status); return apiRequest & { asOf: string }>( `/temporal-assets?${query}`, ); } export function getTemporalAsset(assetId: string, at: string) { const query = new URLSearchParams({ at }); return apiRequest(`/temporal-assets/${assetId}?${query}`); } export function listSurveyCampaigns(params: { page?: number; pageSize?: number; search?: string; status?: SurveyCampaignStatus | ''; coordinatorUserId?: string; } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.status) query.set('status', params.status); if (params.coordinatorUserId) query.set('coordinatorUserId', params.coordinatorUserId); return apiRequest>(`/survey-campaigns?${query}`); } export function getSurveyCampaign(id: string) { return apiRequest(`/survey-campaigns/${id}`); } export async function listSurveyAssignees() { return (await apiRequest<{ data: SurveyPerson[] }>('/survey-campaigns/assignees')).data; } export function createSurveyCampaign(input: { code: string; name: string; description?: string | null; plannedStartAt?: string | null; plannedEndAt?: string | null; scopeAssetId?: string | null; coordinatorUserId?: string | null; }) { return apiRequest('/survey-campaigns', { method: 'POST', body: JSON.stringify(input), }); } export function updateSurveyCampaign(id: string, input: { code?: string; name?: string; description?: string | null; plannedStartAt?: string | null; plannedEndAt?: string | null; scopeAssetId?: string | null; coordinatorUserId?: string | null; }) { return apiRequest(`/survey-campaigns/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function updateSurveyCampaignStatus(id: string, status: SurveyCampaignStatus) { return apiRequest(`/survey-campaigns/${id}/status`, { method: 'PUT', body: JSON.stringify({ status }), }); } export function addSurveyTarget(campaignId: string, input: { assetId: string; assignedUserId?: string | null; dueAt?: string | null; instructions?: string | null; }) { return apiRequest(`/survey-campaigns/${campaignId}/targets`, { method: 'POST', body: JSON.stringify(input), }); } export function updateSurveyTarget(id: string, input: { dueAt?: string | null; instructions?: string | null; }) { return apiRequest(`/survey-campaign-targets/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function assignSurveyTarget(id: string, assignedUserId: string | null) { return apiRequest(`/survey-campaign-targets/${id}/assignment`, { method: 'PUT', body: JSON.stringify({ assignedUserId }), }); } export function updateSurveyTargetStatus(id: string, status: SurveyTargetStatus) { return apiRequest(`/survey-campaign-targets/${id}/status`, { method: 'PUT', body: JSON.stringify({ status }), }); } export function getSurveyExecution(targetId: string) { return apiRequest(`/survey-campaign-targets/${targetId}/report`); } export function saveSurveyReport(targetId: string, input: { outcome: SurveyReportOutcome | null; observedAt: string | null; latitude: number | null; longitude: number | null; accuracyM: number | null; notes: string | null; mediaIds: string[]; }) { return apiRequest(`/survey-campaign-targets/${targetId}/report`, { method: 'PUT', body: JSON.stringify(input), }); } export function submitSurveyReport(targetId: string) { return apiRequest(`/survey-campaign-targets/${targetId}/report/submit`, { method: 'POST', }); } export function reviewSurveyReport( targetId: string, decision: 'APPROVE' | 'REJECT', notes: string | null, ) { return apiRequest(`/survey-campaign-targets/${targetId}/report/review`, { method: 'POST', body: JSON.stringify({ decision, notes }), }); } export function listInspectionVisits(params: { page?: number; pageSize?: number; search?: string; status?: InspectionVisitStatus | ''; companyId?: string; areaId?: string; inspectorId?: string; dateFrom?: string; dateTo?: string; }) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.status) query.set('status', params.status); if (params.companyId) query.set('companyId', params.companyId); if (params.areaId) query.set('areaId', params.areaId); if (params.inspectorId) query.set('inspectorId', params.inspectorId); if (params.dateFrom) query.set('dateFrom', params.dateFrom); if (params.dateTo) query.set('dateTo', params.dateTo); return apiRequest>(`/inspection-visits?${query}`); } export function getInspectionVisit(id: string) { return apiRequest(`/inspection-visits/${id}`); } export async function listInspectionAssignees() { return (await apiRequest<{ data: InspectionPerson[] }>('/inspection-visits/assignees')).data; } export async function listInspectionPlanningAreas() { return (await apiRequest<{ data: InspectionPlanningContextAsset[] }>('/inspection-visits/planning-context/areas')).data; } export async function listInspectionPlanningOperators(areaId: string) { return (await apiRequest<{ data: InspectionPlanningContextAsset[] }>(`/inspection-visits/planning-context/areas/${areaId}/operators`)).data; } export function createInspectionVisit(input: { operationalAreaId: string; operatorCompanyId: string; plannedStartAt: string; leadInspectorUserId: string; }) { return apiRequest('/inspection-visits', { method: 'POST', body: JSON.stringify(input), }); } export function updateInspectionVisit(id: string, input: { objective?: string | null; scopeAssetId?: string | null; operationalAreaId?: string | null; operatorCompanyId?: string | null; plannedStartAt?: string | null; instructions?: string | null; }) { return apiRequest(`/inspection-visits/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export function replaceInspectionVisitAssets(id: string, assetIds: string[]) { return apiRequest(`/inspection-visits/${id}/assets`, { method: 'PUT', body: JSON.stringify({ assetIds }), }); } export function generateInspectionVisitChecklist(id: string) { return apiRequest(`/inspection-visits/${id}/checklist/generate`, { method: 'POST', }); } export function excludeInspectionVisitAsset(id: string, assetId: string, reason: string) { return apiRequest(`/inspection-visits/${id}/assets/${assetId}/exclude`, { method: 'POST', body: JSON.stringify({ reason }), }); } export function includeInspectionVisitAsset(id: string, assetId: string) { return apiRequest(`/inspection-visits/${id}/assets/${assetId}/include`, { method: 'POST', }); } export function replaceInspectionVisitTeam( id: string, leadInspectorUserId: string | null, memberUserIds: string[], ) { return apiRequest(`/inspection-visits/${id}/team`, { method: 'PUT', body: JSON.stringify({ leadInspectorUserId, memberUserIds }), }); } export function updateInspectionVisitStatus( id: string, status: InspectionVisitStatus, reason?: string | null, ) { return apiRequest(`/inspection-visits/${id}/status`, { method: 'PUT', body: JSON.stringify({ status, reason: reason ?? null }), }); } export function listInspectionActs(visitId: string, params: { page?: number; pageSize?: number; search?: string; status?: InspectionActStatus | ''; } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.status) query.set('status', params.status); return apiRequest>( `/inspection-visits/${visitId}/acts?${query}`, ); } export function listInspectionActsGlobal(params: { page?: number; pageSize?: number; search?: string; status?: InspectionActStatus | ''; year?: number | ''; companyId?: string; areaId?: string; inspectorId?: string; dateFrom?: string; dateTo?: string; } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.status) query.set('status', params.status); if (params.year) query.set('year', String(params.year)); if (params.companyId) query.set('companyId', params.companyId); if (params.areaId) query.set('areaId', params.areaId); if (params.inspectorId) query.set('inspectorId', params.inspectorId); if (params.dateFrom) query.set('dateFrom', params.dateFrom); if (params.dateTo) query.set('dateTo', params.dateTo); return apiRequest>(`/inspection-acts?${query}`); } export function listInspectionReports(params: { page?: number; pageSize?: number; search?: string; year?: number | ''; companyId?: string; areaId?: string; inspectorId?: string; dateFrom?: string; dateTo?: string; } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.year) query.set('year', String(params.year)); if (params.companyId) query.set('companyId', params.companyId); if (params.areaId) query.set('areaId', params.areaId); if (params.inspectorId) query.set('inspectorId', params.inspectorId); if (params.dateFrom) query.set('dateFrom', params.dateFrom); if (params.dateTo) query.set('dateTo', params.dateTo); return apiRequest>(`/inspection-reports?${query}`); } export function listPendingInspectionReports(params: { page?: number; pageSize?: number; search?: string; year?: number | ''; companyId?: string; areaId?: string; inspectorId?: string; dateFrom?: string; dateTo?: string; } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.year) query.set('year', String(params.year)); if (params.companyId) query.set('companyId', params.companyId); if (params.areaId) query.set('areaId', params.areaId); if (params.inspectorId) query.set('inspectorId', params.inspectorId); if (params.dateFrom) query.set('dateFrom', params.dateFrom); if (params.dateTo) query.set('dateTo', params.dateTo); return apiRequest>(`/inspection-reports/pending?${query}`); } export function getInspectionReport(id: string) { return apiRequest(`/inspection-reports/${id}`); } export function getInspectionReportReview(id: string) { return apiRequest(`/inspection-reports/${id}/review`); } export function uploadInspectionReportRevision(id: string, file: File, changeSummary: string) { const form = new FormData(); form.append('file', file); form.append('changeSummary', changeSummary); return apiRequest(`/inspection-reports/${id}/review/revisions`, { method: 'POST', body: form }); } export function approveInspectionReport(id: string, note?: string) { return apiRequest(`/inspection-reports/${id}/review/approve`, { method: 'POST', body: JSON.stringify({ note: note?.trim() || undefined }) }); } export function signFinalInspectionReport(id: string) { return apiRequest(`/inspection-reports/${id}/review/sign-final`, { method: 'POST', body: JSON.stringify({ confirmation: true }) }); } export function inspectionReportRevisionUrl(revisionId: string) { return `${API_BASE}/inspection-report-revisions/${revisionId}/content`; } export function getInspectionAct(id: string) { return apiRequest(`/inspection-acts/${id}`); } export function getInspectionClosure(actId: string) { return apiRequest(`/inspection-acts/${actId}/closure`); } export function getInspectionSignatureBlob(signatureId: string) { return apiBlobRequest(`/inspection-act-signatures/${signatureId}/content`); } export function getFindingCatalog(params: { categoryId?: string; search?: string } = {}) { const query = new URLSearchParams(); if (params.categoryId) query.set('categoryId', params.categoryId); if (params.search) query.set('search', params.search); return apiRequest(`/finding-catalog${query.size ? `?${query}` : ''}`); } export function getFindingCatalogAdmin() { return apiRequest('/finding-catalog/admin'); } export function getApplicableFindingCatalog(assetId: string, params: { categoryId?: string; search?: string } = {}) { const query = new URLSearchParams(); if (params.categoryId) query.set('categoryId', params.categoryId); if (params.search) query.set('search', params.search); return apiRequest(`/finding-catalog/applicable/${assetId}${query.size ? `?${query}` : ''}`); } export function getFindingCatalogAssetTypeSelection(assetTypeId: string) { return apiRequest(`/finding-catalog/asset-types/${assetTypeId}/selection`); } export function replaceFindingCatalogAssetTypeSelection(assetTypeId: string, input: { enabledItemIds: string[]; reason: string }) { return apiRequest(`/finding-catalog/asset-types/${assetTypeId}/selection`, { method: 'PUT', body: JSON.stringify(input), }); } export function getFindingCatalogAssetSelection(assetId: string) { return apiRequest(`/finding-catalog/assets/${assetId}/selection`); } export function replaceFindingCatalogAssetSelection(assetId: string, input: { enabledItemIds: string[]; reason: string }) { return apiRequest(`/finding-catalog/assets/${assetId}/selection`, { method: 'PUT', body: JSON.stringify(input), }); } export function listFindingCatalogProposals(params: { status?: FindingCatalogProposalStatus; assetTypeId?: string } = {}) { const query = new URLSearchParams(); if (params.status) query.set('status', params.status); if (params.assetTypeId) query.set('assetTypeId', params.assetTypeId); return apiRequest(`/finding-catalog/proposals${query.size ? `?${query}` : ''}`); } export function reviewFindingCatalogProposal(id: string, input: { decision: 'MATCH' | 'REJECT'; catalogItemId?: string; notes?: string | null; }) { return apiRequest(`/finding-catalog/proposals/${id}/review`, { method: 'PATCH', body: JSON.stringify(input), }); } export function createFindingCategory(input: { code: string; name: string; sortOrder: number; }) { return apiRequest('/finding-catalog/categories', { method: 'POST', body: JSON.stringify(input), }); } export function updateFindingCategory(id: string, input: { name?: string; sortOrder?: number; isActive?: boolean; }) { return apiRequest(`/finding-catalog/categories/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export interface FindingCatalogItemInput { categoryId: string; sourceNumber: number; title: string; legalBasis?: string | null; glossary?: string | null; importNote?: string | null; suggestedSeverity?: number | null; } export function createFindingCatalogItem(input: FindingCatalogItemInput & { code: string }) { return apiRequest('/finding-catalog/items', { method: 'POST', body: JSON.stringify(input), }); } export function updateFindingCatalogItem(id: string, input: FindingCatalogItemInput & { isActive: boolean; }) { return apiRequest(`/finding-catalog/items/${id}`, { method: 'PATCH', body: JSON.stringify(input), }); } export async function listInspectionFindings(actId: string) { return (await apiRequest<{ data: InspectionFinding[] }>( `/inspection-acts/${actId}/findings`, )).data; } export function listInspectionFindingsGlobal(params: { page?: number; pageSize?: number; search?: string; workflow?: InspectionFindingWorkflow; companyId?: string; areaId?: string; inspectorId?: string; dateFrom?: string; dateTo?: string; } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25), }); if (params.search) query.set('search', params.search); if (params.workflow) query.set('workflow', params.workflow); if (params.companyId) query.set('companyId', params.companyId); if (params.areaId) query.set('areaId', params.areaId); if (params.inspectorId) query.set('inspectorId', params.inspectorId); if (params.dateFrom) query.set('dateFrom', params.dateFrom); if (params.dateTo) query.set('dateTo', params.dateTo); return apiRequest(`/inspection-findings?${query}`); } export function getInspectionFinding(id: string) { return apiRequest(`/inspection-findings/${id}`); } export function getInspectionFindingVerificationHistory(id: string) { return apiRequest(`/inspection-findings/${id}/verification-history`); } export function listVerificationPlanning(params: { page?: number; pageSize?: number; search?: string; planningStatus?: VerificationPlanningStatus; companyId?: string; areaId?: string; dueFrom?: string; dueTo?: string; } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 50), }); if (params.search) query.set('search', params.search); if (params.planningStatus) query.set('planningStatus', params.planningStatus); if (params.companyId) query.set('companyId', params.companyId); if (params.areaId) query.set('areaId', params.areaId); if (params.dueFrom) query.set('dueFrom', params.dueFrom); if (params.dueTo) query.set('dueTo', params.dueTo); return apiRequest(`/inspection-verifications?${query}`); } export function planVerificationVisit(input: { findingIds: string[]; plannedStartAt: string; notes?: string | null; }) { return apiRequest('/inspection-verifications/plan', { method: 'POST', body: JSON.stringify(input), }); } export function recordVerificationResult(visitId: string, findingId: string, input: { outcome: InspectionVerificationOutcome; notes: string; verifiedAt?: string; nextControlOn?: string; }) { return apiRequest<{ findingId: string; visitId: string; targetControlOn: string | null; outcome: InspectionVerificationOutcome; notes: string; verifiedAt: string; recordedAt: string; rescheduledControlOn: string | null; nextControlOn: string | null; findingStatus: InspectionFindingStatus; visitStatus: InspectionVisitStatus; }>(`/inspection-verifications/visits/${visitId}/findings/${findingId}/result`, { method: 'PATCH', body: JSON.stringify(input), }); } export function closeInspectionFinding(id: string, closureNotes: string) { return apiRequest(`/inspection-findings/${id}/close`, { method: 'PATCH', body: JSON.stringify({ closureNotes }), }); } export function updateInspectionFindingFollowUp(id: string, input: { responseDueBasis?: 'FINDING_DATE' | 'REPORT_NOTIFICATION' | null; responseDueDays?: number | null; reportNotifiedOn?: string | null; companyResponse?: string | null; companyResponseReceivedOn?: string | null; companyCommittedCorrectionOn?: string | null; nextControlOn?: string | null; }) { return apiRequest(`/inspection-findings/${id}/follow-up`, { method: 'PATCH', body: JSON.stringify(input), }); } export async function listInspectionFindingEvidence(findingId: string) { return (await apiRequest<{ data: InspectionFindingEvidence[] }>( `/inspection-findings/${findingId}/evidence`, )).data; } export function uploadInspectionFindingEvidence(findingId: string, input: { file: File; kind: InspectionEvidenceKind; purpose: InspectionEvidencePurpose; communicationId?: string; verificationVisitId?: string; title?: string; description?: string; capturedAt?: string; latitude?: number; longitude?: number; accuracyM?: number; deviceLabel?: string; }) { const body = new FormData(); body.append('file', input.file); body.append('kind', input.kind); body.append('purpose', input.purpose); if (input.communicationId) body.append('communicationId', input.communicationId); if (input.verificationVisitId) body.append('verificationVisitId', input.verificationVisitId); if (input.title) body.append('title', input.title); if (input.description) body.append('description', input.description); if (input.capturedAt) body.append('capturedAt', input.capturedAt); if (input.latitude !== undefined) body.append('latitude', String(input.latitude)); if (input.longitude !== undefined) body.append('longitude', String(input.longitude)); if (input.accuracyM !== undefined) body.append('accuracyM', String(input.accuracyM)); if (input.deviceLabel) body.append('deviceLabel', input.deviceLabel); return apiRequest( `/inspection-findings/${findingId}/evidence`, { method: 'POST', body }, ); } export function getInspectionFindingEvidenceBlob(evidenceId: string, download = false) { return apiBlobRequest( `/inspection-finding-evidence/${evidenceId}/content${download ? '?download=1' : ''}`, ); } export async function listInspectionFindingCommunications(findingId: string) { return (await apiRequest<{ data: InspectionFindingCommunication[] }>( `/inspection-findings/${findingId}/communications`, )).data; } export function createInspectionFindingCommunication(findingId: string, input: { direction: InspectionFindingCommunication['direction']; channel: InspectionFindingCommunication['channel']; type: InspectionFindingCommunication['type']; occurredAt: string; subject: string; details?: string | null; contactName?: string | null; contactEmail?: string | null; }) { return apiRequest( `/inspection-findings/${findingId}/communications`, { method: 'POST', body: JSON.stringify(input) }, ); } export function listAssetImportBatches(params: { page?: number; pageSize?: number; status?: AssetImportBatchStatus | '' } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 20) }); if (params.status) query.set('status', params.status); return apiRequest>(`/asset-imports?${query}`); } export function listAssetImportReviews(params: { page?: number; pageSize?: number; kind?: 'ALL' | 'DIRECT' | 'DEPENDENCY' } = {}) { const query = new URLSearchParams(); if (params.page) query.set('page', String(params.page)); if (params.pageSize) query.set('pageSize', String(params.pageSize)); if (params.kind && params.kind !== 'ALL') query.set('kind', params.kind); const suffix = query.toString() ? `?${query}` : ''; return apiRequest<{ data: AssetImportReviewItem[]; meta: { page: number; pageSize: number; total: number; totalPages: number } }>(`/asset-imports/reviews${suffix}`); } export function getAssetImportBatch(id: string) { return apiRequest(`/asset-imports/${id}`); } export function listAssetImportRows(id: string, params: { page?: number; pageSize?: number; status?: AssetImportRowStatus | ''; search?: string } = {}) { const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 50) }); if (params.status) query.set('status', params.status); if (params.search) query.set('search', params.search); return apiRequest>(`/asset-imports/${id}/rows?${query}`); } export function uploadAssetImport(input: { file: File; sourceLabel?: string; notes?: string; profileCode?: Exclude }) { const body = new FormData(); body.append('file', input.file); if (input.sourceLabel?.trim()) body.append('sourceLabel', input.sourceLabel.trim()); if (input.notes?.trim()) body.append('notes', input.notes.trim()); if (input.profileCode) body.append('profileCode', input.profileCode); return apiRequest('/asset-imports', { method: 'POST', body }); } export function reconcileAssetImport(id: string) { return apiRequest(`/asset-imports/${id}/reconcile`, { method: 'POST' }); } export function listAssetImportOrganizations(search?: string) { const query = new URLSearchParams(); if (search?.trim()) query.set('search', search.trim()); const suffix = query.toString() ? `?${query}` : ''; return apiRequest<{ data: AssetImportOrganizationOption[] }>(`/asset-imports/context/organizations${suffix}`); } export function getAssetImportPlan(id: string) { return apiRequest(`/asset-imports/${id}/plan`); } export function generateAssetImportPlan(id: string, input: { operatorAssetId?: string; externalIdNamespace?: string }) { return apiRequest(`/asset-imports/${id}/plan`, { method: 'POST', body: JSON.stringify(input) }); } export function listAssetImportPlanItems(id: string, input: { entityKind?: AssetImportPlanEntityKind; action?: AssetImportPlanAction; page?: number; pageSize?: number } = {}) { const query = new URLSearchParams(); if (input.entityKind) query.set('entityKind', input.entityKind); if (input.action) query.set('action', input.action); if (input.page) query.set('page', String(input.page)); if (input.pageSize) query.set('pageSize', String(input.pageSize)); const suffix = query.toString() ? `?${query}` : ''; return apiRequest<{ data: AssetImportPlanItem[]; meta: { page: number; pageSize: number; total: number; totalPages: number } }>(`/asset-imports/${id}/plan/items${suffix}`); } export function resolveAssetImportPlanItem(id: string, itemId: string, input: { action: 'CREATE' | 'MATCH' | 'IGNORE'; matchedAssetId?: string; reason: string }) { return apiRequest(`/asset-imports/${id}/plan/items/${itemId}/resolve`, { method: 'POST', body: JSON.stringify(input) }); } export function applySafeAssetImportPlan(id: string, planHash: string) { return apiRequest(`/asset-imports/${id}/apply-safe`, { method: 'POST', body: JSON.stringify({ planHash }) }); } export function applyAssetImportPlan(id: string, planHash: string) { return apiRequest(`/asset-imports/${id}/apply`, { method: 'POST', body: JSON.stringify({ planHash }) }); } export function rollbackAssetImportPlan(id: string, reason: string) { return apiRequest(`/asset-imports/${id}/rollback`, { method: 'POST', body: JSON.stringify({ reason }) }); } export function cancelAssetImport(id: string, reason: string) { return apiRequest(`/asset-imports/${id}/cancel`, { method: 'POST', body: JSON.stringify({ reason }) }); } export function inspectionReportWordUrl(id: string) { return `${API_BASE}/inspection-reports/${id}/word`; } export function generateInspectionReportWord(id: string) { return apiRequest(`/inspection-reports/${id}/word`, { method: 'POST' }); } export interface DocumentDeliverySettings { officeEmail:string|null; directorEmail:string|null; updatedAt:string; smtpConfigured:boolean; mailFrom:string|null; } export interface DocumentDeliveryItem { id:string; actId:string; reportId:string|null; documentKind:'ACT_PDF'|'REPORT_WORD'; recipientKind:'COMPANY'|'OFFICE'|'DIRECTOR'; recipientAssetId:string|null; recipientAssetName:string|null; recipientEmail:string|null; status:'PENDING'|'WAITING_RECIPIENT'|'WAITING_TRANSPORT'|'WAITING_ARTIFACT'|'SENT'|'FAILED'; attempts:number; lastAttemptAt:string|null; sentAt:string|null; providerMessageId:string|null; lastError:string|null; createdAt:string; actCode:string; reportCode:string|null; } export function getDocumentDeliverySettings(){return apiRequest('/document-delivery/settings');} export function updateDocumentDeliverySettings(input:{officeEmail?:string|null;directorEmail?:string|null}){return apiRequest('/document-delivery/settings',{method:'PATCH',body:JSON.stringify(input)});} export function listDocumentDeliveries(){return apiRequest<{data:DocumentDeliveryItem[]}>('/document-delivery/outbox');} export function retryDocumentDelivery(id:string){return apiRequest(`/document-delivery/outbox/${id}/retry`,{method:'POST'});} export function retryPendingDocumentDeliveries(){return apiRequest<{processed:number}>('/document-delivery/outbox/retry-pending',{method:'POST'});}