fix(web): mostrar empresas heredadas en yacimientos y proteger avanzado

This commit is contained in:
2026-09-11 10:56:16 -03:00
parent d18375b85b
commit 6f79abbcff
+39 -10
View File
@@ -2,17 +2,19 @@ import { useEffect, useMemo, useState } from 'react';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import { Link, useSearchParams } from 'react-router'; import { Link, useSearchParams } from 'react-router';
import { useAuth } from '../auth/AuthContext'; import { useAuth } from '../auth/AuthContext';
import { hasAdministratorRole } from '../auth/adminAccess';
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback'; import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
import { Icon } from '../components/Icon'; import { Icon } from '../components/Icon';
import { AssetHistoryPanel } from '../features/assets/AssetHistoryPanel'; import { AssetHistoryPanel } from '../features/assets/AssetHistoryPanel';
import { assetStatusClass, assetStatusLabel } from '../features/assets/assetPresentation'; import { assetStatusClass, assetStatusLabel } from '../features/assets/assetPresentation';
import { import {
getAssetLineage,
listAreaCompanyRelations, listAreaCompanyRelations,
listAssetTreeChildren, listAssetTreeChildren,
listInspectionVisits, listInspectionVisits,
updateAsset, updateAsset,
} from '../lib/api'; } from '../lib/api';
import type { AreaCompanyRelation, AssetDetail, AssetListItem } from '../lib/api'; import type { AreaCompanyRelation, AssetDetail, AssetLineageItem, AssetListItem } from '../lib/api';
import { import {
listInspectionActsGlobalF4, listInspectionActsGlobalF4,
type InspectionActStatusF4, type InspectionActStatusF4,
@@ -31,8 +33,12 @@ function normalized(value: string) {
.trim(); .trim();
} }
function tokensForType(type: { code: string; name: string }) {
return new Set(`${normalized(type.code)} ${normalized(type.name)}`.split(' ').filter(Boolean));
}
function typeTokens(asset: AssetDetail) { function typeTokens(asset: AssetDetail) {
return new Set(`${normalized(asset.type.code)} ${normalized(asset.type.name)}`.split(' ').filter(Boolean)); return tokensForType(asset.type);
} }
export function isTerritorialInventoryAsset(asset: AssetDetail) { export function isTerritorialInventoryAsset(asset: AssetDetail) {
@@ -44,6 +50,14 @@ function isOperationalArea(asset: AssetDetail) {
return typeTokens(asset).has('area'); return typeTokens(asset).has('area');
} }
function isYacimiento(asset: AssetDetail) {
return typeTokens(asset).has('yacimiento');
}
function lineageArea(lineage: AssetLineageItem[]) {
return [...lineage].reverse().find((item) => tokensForType(item.type).has('area')) ?? null;
}
function relationRoleLabel(role: AreaCompanyRelation['relationRole']) { function relationRoleLabel(role: AreaCompanyRelation['relationRole']) {
if (role === 'OPERATOR') return 'Operadora'; if (role === 'OPERATOR') return 'Operadora';
if (role === 'TECHNICAL_OPERATOR') return 'Operadora técnica'; if (role === 'TECHNICAL_OPERATOR') return 'Operadora técnica';
@@ -57,7 +71,7 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
const [params, setParams] = useSearchParams(); const [params, setParams] = useSearchParams();
const requestedTab = params.get('tab') as TerritoryTab | null; const requestedTab = params.get('tab') as TerritoryTab | null;
const tab: TerritoryTab = requestedTab === 'history' ? 'history' : 'summary'; const tab: TerritoryTab = requestedTab === 'history' ? 'history' : 'summary';
const { hasPermission } = useAuth(); const { user, hasPermission } = useAuth();
const canEdit = hasPermission('assets.update'); const canEdit = hasPermission('assets.update');
const canCreate = hasPermission('assets.create'); const canCreate = hasPermission('assets.create');
const canReadHistory = hasPermission('assets.read_history'); const canReadHistory = hasPermission('assets.read_history');
@@ -65,10 +79,12 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
const canReadActs = hasPermission('inspection_acts.read'); const canReadActs = hasPermission('inspection_acts.read');
const canReadReports = hasPermission('inspection_reports.read'); const canReadReports = hasPermission('inspection_reports.read');
const canReadInspections = hasPermission('inspections.read'); const canReadInspections = hasPermission('inspections.read');
const canAdvanced = hasAdministratorRole(user);
const [asset, setAsset] = useState(initialAsset); const [asset, setAsset] = useState(initialAsset);
const [children, setChildren] = useState<AssetListItem[]>([]); const [children, setChildren] = useState<AssetListItem[]>([]);
const [childrenHasMore, setChildrenHasMore] = useState(false); const [childrenHasMore, setChildrenHasMore] = useState(false);
const [lineage, setLineage] = useState<AssetLineageItem[]>([]);
const [relations, setRelations] = useState<AreaCompanyRelation[]>([]); const [relations, setRelations] = useState<AreaCompanyRelation[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(''); const [error, setError] = useState('');
@@ -80,18 +96,31 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
const [metrics, setMetrics] = useState({ activeInspections: 0, acts: 0, reports: 0 }); const [metrics, setMetrics] = useState({ activeInspections: 0, acts: 0, reports: 0 });
const area = isOperationalArea(asset); const area = isOperationalArea(asset);
const yacimiento = isYacimiento(asset);
const activeRelations = useMemo(() => relations.filter((item) => item.active), [relations]); const activeRelations = useMemo(() => relations.filter((item) => item.active), [relations]);
const relationAreaContext = useMemo(() => {
if (area) return { id: asset.id, code: asset.code, name: asset.name };
if (asset.operationalArea) return asset.operationalArea;
const ancestor = lineageArea(lineage);
return ancestor ? { id: ancestor.id, code: ancestor.code, name: ancestor.name } : null;
}, [area, asset.id, asset.code, asset.name, asset.operationalArea, lineage]);
const loadSummary = async () => { const loadSummary = async () => {
setLoading(true); setLoading(true);
setError(''); setError('');
try { try {
const loadedLineage = await getAssetLineage(asset.id);
const ancestorArea = lineageArea(loadedLineage);
const relationAreaId = area
? asset.id
: asset.operationalArea?.id ?? ancestorArea?.id ?? null;
const [childPage, loadedRelations] = await Promise.all([ const [childPage, loadedRelations] = await Promise.all([
listAssetTreeChildren({ parentId: asset.id, limit: 100 }), listAssetTreeChildren({ parentId: asset.id, limit: 100 }),
area && canReadRelations relationAreaId && canReadRelations
? listAreaCompanyRelations({ areaId: asset.id, includeHistory: true }) ? listAreaCompanyRelations({ areaId: relationAreaId, includeHistory: true })
: Promise.resolve([]), : Promise.resolve([]),
]); ]);
setLineage(loadedLineage);
setChildren(childPage.data); setChildren(childPage.data);
setChildrenHasMore(childPage.meta.hasMore); setChildrenHasMore(childPage.meta.hasMore);
setRelations(loadedRelations); setRelations(loadedRelations);
@@ -223,10 +252,10 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
{childrenHasMore && <div className="territory-card-actions"><Link className="button secondary" to={`/inventarios?section=territory&parentId=${asset.id}`}>Ver contenido completo</Link></div>} {childrenHasMore && <div className="territory-card-actions"><Link className="button secondary" to={`/inventarios?section=territory&parentId=${asset.id}`}>Ver contenido completo</Link></div>}
</article> </article>
{area && canReadRelations && <article className="panel territory-card"> {relationAreaContext && canReadRelations && <article className="panel territory-card">
<div className="territory-card-heading"><div><span className="eyebrow">OPERACIÓN</span><h2>Empresas vinculadas</h2><p>Quién opera o participa actualmente en esta área.</p></div><span className="count-pill">{activeRelations.length}</span></div> <div className="territory-card-heading"><div><span className="eyebrow">OPERACIÓN</span><h2>Empresas vinculadas</h2><p>{area ? 'Quién opera o participa actualmente en esta área.' : yacimiento ? `Empresas que operan o participan en el Área ${relationAreaContext.name}, contexto operativo de este yacimiento.` : `Empresas vinculadas al Área ${relationAreaContext.name}.`}</p></div><span className="count-pill">{activeRelations.length}</span></div>
{loading ? <LoadingBlock label="Cargando empresas…" /> : activeRelations.length === 0 ? <div className="inline-empty">No hay empresas con vínculo vigente.</div> : <div className="territory-company-list"> {loading ? <LoadingBlock label="Cargando empresas…" /> : activeRelations.length === 0 ? <div className="inline-empty">No hay empresas con vínculo vigente en el Área {relationAreaContext.name}.</div> : <div className="territory-company-list">
{activeRelations.map((relation) => <Link to={`/inventarios/${relation.company.id}`} key={relation.id} className="territory-company-row"><span><strong>{relation.company.name}</strong><small>{relationRoleLabel(relation.relationRole)}</small></span><Icon name="chevron" size={16} /></Link>)} {activeRelations.map((relation) => <Link to={`/inventarios/${relation.company.id}`} key={relation.id} className="territory-company-row"><span><strong>{relation.company.name}</strong><small>{relationRoleLabel(relation.relationRole)}{!area ? ` · ${relation.area.name}` : ''}</small></span><Icon name="chevron" size={16} /></Link>)}
</div>} </div>}
</article>} </article>}
@@ -239,7 +268,7 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
</div> </div>
</article>} </article>}
<div className="territory-advanced-link"><span>Los datos estructurales, procedencia, ubicación y opciones técnicas quedan fuera de la vista cotidiana.</span><Link to={`/inventarios/${asset.id}?advanced=1`}>Administración avanzada</Link></div> {canAdvanced && <div className="territory-advanced-link"><span>Los datos estructurales, procedencia, ubicación y opciones técnicas quedan fuera de la vista cotidiana.</span><Link to={`/inventarios/${asset.id}?advanced=1`}>Administración avanzada</Link></div>}
</div>} </div>}
{tab === 'history' && canReadHistory && <AssetHistoryPanel assetId={asset.id} />} {tab === 'history' && canReadHistory && <AssetHistoryPanel assetId={asset.id} />}