diff --git a/web-v2/src/features/assets/AssetHierarchyView.tsx b/web-v2/src/features/assets/AssetHierarchyView.tsx index 0d113bf..32c76fc 100644 --- a/web-v2/src/features/assets/AssetHierarchyView.tsx +++ b/web-v2/src/features/assets/AssetHierarchyView.tsx @@ -4,7 +4,7 @@ import { useAuth } from '../../auth/AuthContext'; import { Alert, EmptyState, LoadingBlock, errorMessage } from '../../components/Feedback'; import { Icon } from '../../components/Icon'; import { getAssetLineage } from '../../lib/api'; -import type { AssetLineageItem } from '../../lib/api'; +import type { AssetLineageItem, AssetType } from '../../lib/api'; import { listInventoryCompanies, listInventoryDepartments, @@ -18,16 +18,61 @@ import type { } from '../../lib/inventoryBrowserApi'; import { assetOperationalStatusLabel, assetStatusClass, assetStatusLabel } from './assetPresentation'; +const structuredInventorySections = [ + { + code: 'empresa', + label: 'Empresas', + description: 'Maestro de empresas operadoras. Se vinculan a una o más Áreas sin formar parte del árbol territorial.', + icon: 'users' as const, + }, + { + code: 'departamento', + label: 'Departamentos', + description: 'Primer nivel territorial. Desde cada Departamento se accede a sus Áreas.', + icon: 'map' as const, + }, + { + code: 'area', + label: 'Áreas', + description: 'Cada Área pertenece a un Departamento y puede tener una o más Empresas operadoras vinculadas.', + icon: 'map' as const, + }, + { + code: 'yacimiento', + label: 'Yacimientos', + description: 'Nivel operativo dentro de un Área. Organiza las Instalaciones que pertenecen a ese Yacimiento.', + icon: 'map' as const, + }, + { + code: 'instalacion', + label: 'Instalaciones', + description: 'Elementos técnicos ubicados dentro de un Yacimiento y clasificados por su familia de Inventario.', + icon: 'layers' as const, + }, + { + code: 'subinstalacion', + label: 'Subinstalaciones', + description: 'Último nivel estructural. Dependen de una Instalación y conservan su clasificación técnica.', + icon: 'layers' as const, + }, +] as const; + function navigationHref(base: URLSearchParams, parentId?: string) { const params = new URLSearchParams(base); params.delete('view'); params.delete('page'); params.delete('section'); params.delete('companyId'); + params.delete('typeId'); parentId ? params.set('parentId', parentId) : params.delete('parentId'); return `/inventarios${params.size ? `?${params}` : ''}`; } +function inventoryTypeForSection(types: AssetType[], code: string) { + if (code === 'empresa') return types.find((type) => type.operationalRole === 'COMPANY') ?? null; + return types.find((type) => type.code.toLowerCase() === code) ?? null; +} + function DepartmentCard({ department, href }: { department: InventoryBrowserDepartment; href: string }) { return @@ -80,7 +125,7 @@ function nextLevelLabel(typeCode: string | undefined) { } } -export function AssetHierarchyView({ filters }: { filters: InventoryQuery }) { +export function AssetHierarchyView({ filters, types }: { filters: InventoryQuery; types: AssetType[] }) { const { hasPermission } = useAuth(); const canCreate = hasPermission('assets.create'); const [searchParams] = useSearchParams(); @@ -123,24 +168,48 @@ export function AssetHierarchyView({ filters }: { filters: InventoryQuery }) { return
{error && {error}}
-
INVENTARIO COMPLETO

Departamentos

Entrá por Departamento y navegá Área → Yacimiento → Instalación → Subinstalación hasta llegar a su clasificación y Hallazgos asociados.

+
+ INVENTARIOS ESTRUCTURADOS +

Elegí qué querés administrar

+

La estructura queda fija y fácil de leer: Empresas como maestro de operadoras y, por separado, Departamento → Área → Yacimiento → Instalación → Subinstalación.

+
{departments.length} Departamentos{companies.length} Empresas
- {departments.length === 0 - ? - :
{departments.map((department) => )}
} -
+
+ {structuredInventorySections.map((section, index) => { + const assetType = inventoryTypeForSection(types, section.code); + const content = <> + + + {section.label} + {section.description} + + {section.code === 'empresa' ? 'Maestro independiente' : `Nivel ${index}`} + {assetType && } + ; + return assetType + ? {content} + :
{content}
; + })} +
+ +
1Departamentoraíz territorial
-
2Áreadentro del Departamento
+
2Áreaoperadoras vinculadas
3Yacimientodentro del Área
4Instalaciónclasificación técnica
5Subinstalaciónclasificación técnica
-

Empresas

Maestro independiente. La Empresa se vincula al Área como operadora sin alterar la estructura física.

{companies.length}
- {companies.length === 0 ?
Todavía no hay Empresas cargadas.
:
{companies.map((company) => )}
} +
+

Recorrido por territorio

También podés navegar como árbol: entrá por un Departamento y descendé hasta la Instalación o Subinstalación concreta.

+ {departments.length} +
+ {departments.length === 0 + ? + :
{departments.map((department) => )}
}
; } diff --git a/web-v2/src/pages/AssetsPage.tsx b/web-v2/src/pages/AssetsPage.tsx index 25cd931..798c63a 100644 --- a/web-v2/src/pages/AssetsPage.tsx +++ b/web-v2/src/pages/AssetsPage.tsx @@ -33,6 +33,40 @@ const quickViews = [ { key: 'out', label: 'Fuera de servicio' }, ] as const; +const structuredTypeCopy: Record = { + departamento: { + title: 'Departamentos', + description: 'Raíz territorial del Inventario. Cada Departamento contiene sus Áreas.', + }, + area: { + title: 'Áreas', + description: 'Áreas dentro de un Departamento, con sus Empresas operadoras vinculadas.', + }, + yacimiento: { + title: 'Yacimientos', + description: 'Yacimientos pertenecientes a un Área y utilizados para organizar sus Instalaciones.', + }, + instalacion: { + title: 'Instalaciones', + description: 'Instalaciones dentro de un Yacimiento, con clasificación técnica y contexto operativo.', + }, + subinstalacion: { + title: 'Subinstalaciones', + description: 'Subinstalaciones dependientes de una Instalación, último nivel de la estructura.', + }, +}; + +function structuredPresentation(type: AssetType | undefined) { + if (!type) return null; + if (type.operationalRole === 'COMPANY') { + return { + title: 'Empresas', + description: 'Maestro independiente de Empresas operadoras. Se vinculan a una o más Áreas.', + }; + } + return structuredTypeCopy[type.code.toLowerCase()] ?? null; +} + export function AssetsPage() { const [urlParams, setUrlParams] = useSearchParams(); const operationalContext = useOperationalContext(); @@ -58,6 +92,8 @@ export function AssetsPage() { const needsValidation = quick === 'validation' ? true : undefined; const hasGeometry = quick === 'location' ? false : undefined; const effectiveOperationalStatus = quick === 'out' ? 'OUT_OF_SERVICE' as AssetOperationalStatus : operationalStatus; + const selectedType = types.find((type) => type.id === typeId); + const selectedPresentation = structuredPresentation(selectedType); useEffect(() => { listAssetTypes().then((data) => setTypes(data.filter((type) => @@ -102,15 +138,22 @@ export function AssetsPage() { value > 1 ? next.set('page', String(value)) : next.delete('page'); setUrlParams(next); }; const advancedActive = Boolean(typeId || status || rawOperationalStatus); + const sectionTitle = view === 'list' && selectedPresentation ? selectedPresentation.title : 'Inventarios'; + const sectionDescription = view === 'list' && selectedPresentation + ? selectedPresentation.description + : 'Empresas y estructura territorial: Departamento → Área → Yacimiento → Instalación → Subinstalación.'; return
- INVENTARIOS -

Inventarios

-

Todos los registros: Departamento → Área → Yacimiento → Instalación → Subinstalación, más el maestro independiente de Empresas.

+ {selectedPresentation && view === 'list' ? 'INVENTARIOS · SECCIÓN' : 'INVENTARIOS'} +

{sectionTitle}

+

{sectionDescription}

+
+
+ {selectedPresentation && view === 'list' && Ver estructura} + Nuevo registro
- Nuevo registro
@@ -126,7 +169,7 @@ export function AssetsPage() { {view === 'list' && }
{view === 'list' && (advancedOpen || advancedActive) &&
- + @@ -135,14 +178,14 @@ export function AssetsPage() { {error && {error}} {view === 'hierarchy' - ? + ? : loading ? : assets.length === 0 - ? + ? :
{meta.total} registro{meta.total === 1 ? '' : 's'}Página {meta.page} de {Math.max(meta.totalPages, 1)}
-
+
RegistroNivelUbicación / OperadoraEstadoActualizado
{assets.map((asset) =>
RegistroSecciónUbicación / OperadoraEstadoActualizado
{asset.name}{asset.code}{asset.parent ? ` · en ${asset.parent.name}` : ''}
{asset.type.name}