import { useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router'; import { Alert, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { getFindingCatalogAdmin, listAssetTypes } from '../lib/api'; import type { AssetType, FindingAdminCatalog } from '../lib/api'; import { listInventoryFamiliesAdmin } from '../lib/inventoryStructureApi'; import type { InventoryFamily } from '../lib/inventoryStructureApi'; type CanonicalKind = 'EMPRESA' | 'DEPARTAMENTO' | 'AREA' | 'YACIMIENTO' | 'INSTALACION' | 'SUBINSTALACION'; type StructuralField = { label: string; detail: string; required?: boolean; relation?: boolean; }; const LEVELS: Array<{ kind: CanonicalKind; label: string; description: string; fields: StructuralField[] }> = [ { kind: 'EMPRESA', label: 'Empresa', description: 'Maestro independiente. La Empresa se relaciona directamente con uno o más Yacimientos.', fields: [{ label: 'Nombre', detail: 'Nombre de la Empresa / Operadora.', required: true }], }, { kind: 'DEPARTAMENTO', label: 'Departamento', description: 'Raíz territorial. No depende de ningún otro nivel.', fields: [{ label: 'Nombre', detail: 'Nombre del Departamento.', required: true }], }, { kind: 'AREA', label: 'Área', description: 'El Área sólo tiene Nombre y pertenece obligatoriamente a un Departamento.', fields: [ { label: 'Departamento', detail: 'Relación obligatoria Departamento → Área.', required: true, relation: true }, { label: 'Nombre', detail: 'Nombre del Área.', required: true }, ], }, { kind: 'YACIMIENTO', label: 'Yacimiento', description: 'El Yacimiento concentra el contexto operativo: Área, Tipo de concesión y Empresa relacionada.', fields: [ { label: 'Área', detail: 'Relación obligatoria Área → Yacimiento.', required: true, relation: true }, { label: 'Tipo de concesión', detail: 'Explotación o Exploración, según la fuente.', required: true, relation: true }, { label: 'Empresa relacionada', detail: 'Empresa / Operadora asociada directamente al Yacimiento.', required: true, relation: true }, { label: 'Nombre', detail: 'Nombre del Yacimiento. Puede repetirse en otra Área.', required: true }, ], }, { kind: 'INSTALACION', label: 'Instalación', description: 'Cada Instalación pertenece a un Yacimiento y posee una clasificación técnica.', fields: [ { label: 'Yacimiento', detail: 'Relación obligatoria Yacimiento → Instalación.', required: true, relation: true }, { label: 'Tipo de instalación', detail: 'Clasificación técnica tomada del modelo de Instalaciones.', required: true, relation: true }, ], }, { kind: 'SUBINSTALACION', label: 'Subinstalación', description: 'Cada Subinstalación pertenece a una Instalación y usa una clasificación compatible con ella.', fields: [ { label: 'Instalación', detail: 'Relación obligatoria Instalación → Subinstalación.', required: true, relation: true }, { label: 'Tipo de subinstalación', detail: 'Clasificación técnica compatible con el Tipo de instalación padre.', required: true, relation: true }, ], }, ]; const EMPTY_CATALOG: FindingAdminCatalog = { categories: [], items: [] }; function canonicalType(types: AssetType[], kind: CanonicalKind): AssetType | null { if (kind === 'EMPRESA') return types.find((type) => type.operationalRole === 'COMPANY' && type.isActive) ?? null; if (kind === 'AREA') return types.find((type) => type.operationalRole === 'AREA' && type.isActive) ?? null; return types.find((type) => type.code.toLowerCase() === kind.toLowerCase() && type.isActive) ?? null; } export function AuthoritativeInventoryConfigPage() { const [types, setTypes] = useState([]); const [families, setFamilies] = useState([]); const [catalog, setCatalog] = useState(EMPTY_CATALOG); const [selectedKind, setSelectedKind] = useState('AREA'); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { Promise.all([listAssetTypes(), listInventoryFamiliesAdmin(), getFindingCatalogAdmin()]) .then(([loadedTypes, loadedFamilies, loadedCatalog]) => { setTypes(loadedTypes); setFamilies(loadedFamilies); setCatalog(loadedCatalog); }) .catch((requestError) => setError(errorMessage(requestError))) .finally(() => setLoading(false)); }, []); const level = LEVELS.find((item) => item.kind === selectedKind) ?? LEVELS[0]!; const selectedType = canonicalType(types, selectedKind); const commonAttributes = useMemo(() => { const attributes = selectedType?.attributes.filter((attribute) => attribute.isActive) ?? []; return selectedKind === 'INSTALACION' ? attributes.filter((attribute) => attribute.code !== 'tipo_instalacion') : attributes; }, [selectedKind, selectedType]); const installationFamilies = families.filter((family) => family.level === 'INSTALLATION' && family.isActive !== false); const subinstallationFamilies = families.filter((family) => family.level === 'SUBINSTALLATION' && family.isActive !== false); const activeFindings = catalog.items.filter((item) => item.isActive).length; if (loading) return ; return
ADMINISTRACIÓN · MODELO AUTORITATIVO

Configuración de Inventarios

Relaciones fijas cargadas desde los SQL definitivos. Los vínculos estructurales no son campos de texto editables.

Agregar registro
{error && {error}}
ESTRUCTURA FÍSICA

Jerarquía obligatoria

Empresa es un maestro independiente y se asocia al Yacimiento. El árbol físico queda separado y sin ambigüedades.

1Departamentoraíz
2ÁreaDepartamento
3YacimientoÁrea + Empresa + concesión
4InstalaciónYacimiento
5SubinstalaciónInstalación

Empresa: ya no pertenece al Área. La relación canónica es Yacimiento → Empresa relacionada.

INFORMACIÓN ESTRUCTURAL

{level.label}

{level.description}

Modelo SQL
{LEVELS.map((item) => )}
{level.fields.map((field, index) =>
{index + 1} {field.label} {field.detail} {field.relation && Relación} {field.required && Obligatorio}
)} {commonAttributes.map((attribute, index) =>
{level.fields.length + index + 1} {attribute.name} {attribute.code} · Campo común del nivel {attribute.isRequired && Obligatorio}
)}
MODELO TÉCNICO

Tipos de Instalación

{installationFamilies.length} clasificaciones exactas del SQL.

{installationFamilies.map((family) =>
{family.name}{family.findingCount ?? 0} Hallazgos asociados{family.code}
)}
MODELO TÉCNICO

Tipos de Subinstalación

{subinstallationFamilies.length} clasificaciones, cada una vinculada a su Tipo de instalación.

{subinstallationFamilies.map((family) =>
{family.name}{family.parentFamilies.map((parent) => parent.name).join(' · ') || 'Sin padre'} · {family.findingCount ?? 0} Hallazgos
)}
HALLAZGOS

Catálogo contextual

{activeFindings} Hallazgos cargados con sus relaciones exactas a Instalaciones y Subinstalaciones.

Abrir catálogo
; }