#49 feat: reemplazar cytoscape por vis-network con semantica de certeza en graficos de provenance

This commit is contained in:
2026-08-16 01:41:27 -05:00
parent 58b42d956b
commit 2967ea4f4d
18 changed files with 732 additions and 593 deletions

View File

@@ -6,7 +6,7 @@ import {
hasAnyTerritory,
} from '@/components/provenance/provenance-graph'
const provenance = [
const singleSupplier = [
{
product: {
id: 1,
@@ -25,9 +25,37 @@ const provenance = [
},
]
const twoSuppliers = (overrides = {}) => [
{
product: { id: 1, name: 'Panela' },
suppliers: [
{
supplier: { id: 5, name: 'Asociación A' },
organization: { id: 3, name: 'Red' },
municipality: { id: 7, name: 'La Mesa' },
department: { id: 2, name: 'Cundinamarca' },
country: { id: 1, name: 'Colombia' },
...(overrides.first || {}),
},
{
supplier: { id: 6, name: 'Asociación B' },
organization: { id: 3, name: 'Red' },
municipality: { id: 8, name: 'San Antonio' },
department: { id: 3, name: 'Antioquia' },
country: { id: 1, name: 'Colombia' },
...(overrides.second || {}),
},
],
},
]
function edgeOf (graph, from, to) {
return graph.edges.find(edge => edge.from === from && edge.to === to)
}
describe('hasAnySupplier', () => {
it('es true cuando algún producto tiene proveedores', () => {
expect(hasAnySupplier(provenance)).toBe(true)
expect(hasAnySupplier(singleSupplier)).toBe(true)
})
it('es false cuando todos los productos están sin proveedores', () => {
@@ -42,7 +70,7 @@ describe('hasAnySupplier', () => {
describe('hasAnyTerritory', () => {
it('es true cuando algún proveedor tiene territorio', () => {
expect(hasAnyTerritory(provenance)).toBe(true)
expect(hasAnyTerritory(singleSupplier)).toBe(true)
})
it('es false cuando todos los territorios vienen en null', () => {
@@ -62,20 +90,41 @@ describe('hasAnyTerritory', () => {
})
describe('buildSupplierOrganizationGraph', () => {
it('crea nodos de producto, proveedor y organización con sus aristas', () => {
const graph = buildSupplierOrganizationGraph(provenance)
it('con un único proveedor conecta con arista cierta y sin junction', () => {
const graph = buildSupplierOrganizationGraph(singleSupplier)
const ids = graph.nodes.map(n => n.id)
expect(ids).toContain('product:1')
expect(ids).toContain('supplier:5')
expect(ids).toContain('organization:3')
expect(graph.nodes.map(n => n.id)).not.toContain('junction:1')
expect(edgeOf(graph, 'product:1', 'supplier:5')).toEqual({ from: 'product:1', to: 'supplier:5', certain: true })
})
expect(graph.edges).toContainEqual({ from: 'product:1', to: 'supplier:5' })
expect(graph.edges).toContainEqual({ from: 'supplier:5', to: 'organization:3' })
it('con varios proveedores crea un punto de disyunción: sólida hasta él y dudosa hacia cada proveedor', () => {
const graph = buildSupplierOrganizationGraph(twoSuppliers())
expect(graph.nodes.map(n => n.id)).toContain('junction:1')
expect(edgeOf(graph, 'product:1', 'junction:1')).toEqual({ from: 'product:1', to: 'junction:1', certain: true })
expect(edgeOf(graph, 'junction:1', 'supplier:5')).toEqual({ from: 'junction:1', to: 'supplier:5', certain: false })
expect(edgeOf(graph, 'junction:1', 'supplier:6')).toEqual({ from: 'junction:1', to: 'supplier:6', certain: false })
})
it('una organización compartida por todos los proveedores es cierta', () => {
const graph = buildSupplierOrganizationGraph(twoSuppliers())
expect(edgeOf(graph, 'supplier:5', 'organization:3')).toEqual({ from: 'supplier:5', to: 'organization:3', certain: true })
expect(edgeOf(graph, 'supplier:6', 'organization:3')).toEqual({ from: 'supplier:6', to: 'organization:3', certain: true })
})
it('organizaciones distintas entre proveedores son dudosas', () => {
const graph = buildSupplierOrganizationGraph(twoSuppliers({
first: { organization: { id: 3, name: 'Red' } },
second: { organization: { id: 4, name: 'Cooperativa' } },
}))
expect(edgeOf(graph, 'supplier:5', 'organization:3').certain).toBe(false)
expect(edgeOf(graph, 'supplier:6', 'organization:4').certain).toBe(false)
})
it('incluye la imagen del producto y el detalle de la entidad', () => {
const graph = buildSupplierOrganizationGraph(provenance)
const graph = buildSupplierOrganizationGraph(singleSupplier)
const product = graph.nodes.find(n => n.id === 'product:1')
expect(product.image).toBe('http://localhost/media/panela.jpg')
@@ -94,7 +143,7 @@ describe('buildSupplierOrganizationGraph', () => {
const graph = buildSupplierOrganizationGraph(data)
expect(graph.nodes.map(n => n.id)).not.toContain('organization:')
expect(graph.edges).toEqual([{ from: 'product:1', to: 'supplier:5' }])
expect(graph.edges).toEqual([{ from: 'product:1', to: 'supplier:5', certain: true }])
})
it('no duplica nodos que se repiten entre productos', () => {
@@ -109,15 +158,12 @@ describe('buildSupplierOrganizationGraph', () => {
expect(graph.edges).toHaveLength(4)
})
it('ubica producto, proveedor y organización en columnas crecientes', () => {
const graph = buildSupplierOrganizationGraph(provenance)
it('no incluye posiciones de layout', () => {
const graph = buildSupplierOrganizationGraph(singleSupplier)
const product = graph.nodes.find(n => n.id === 'product:1')
const supplier = graph.nodes.find(n => n.id === 'supplier:5')
const organization = graph.nodes.find(n => n.id === 'organization:3')
expect(product.x).toBeLessThan(supplier.x)
expect(supplier.x).toBeLessThan(organization.x)
expect(product.x).toBeUndefined()
expect(product.y).toBeUndefined()
})
it('devuelve un grafo vacío cuando no hay provenance', () => {
@@ -129,19 +175,49 @@ describe('buildSupplierOrganizationGraph', () => {
})
describe('buildTerritoryGraph', () => {
it('crea la cadena producto → proveedor → municipio → departamento → país', () => {
const graph = buildTerritoryGraph(provenance)
it('con un único proveedor toda la cadena es cierta', () => {
const graph = buildTerritoryGraph(singleSupplier)
const ids = graph.nodes.map(n => n.id)
expect(ids).toContain('product:1')
expect(ids).toContain('supplier:5')
expect(ids).toContain('municipality:7')
expect(ids).toContain('department:2')
expect(ids).toContain('country:1')
expect(edgeOf(graph, 'product:1', 'supplier:5')).toEqual({ from: 'product:1', to: 'supplier:5', certain: true })
expect(edgeOf(graph, 'supplier:5', 'municipality:7')).toEqual({ from: 'supplier:5', to: 'municipality:7', certain: true })
expect(edgeOf(graph, 'municipality:7', 'department:2')).toEqual({ from: 'municipality:7', to: 'department:2', certain: true })
expect(edgeOf(graph, 'department:2', 'country:1')).toEqual({ from: 'department:2', to: 'country:1', certain: true })
})
expect(graph.edges).toContainEqual({ from: 'supplier:5', to: 'municipality:7' })
expect(graph.edges).toContainEqual({ from: 'municipality:7', to: 'department:2' })
expect(graph.edges).toContainEqual({ from: 'department:2', to: 'country:1' })
it('cuando los proveedores comparten municipio, el territorio es cierto desde allí', () => {
const graph = buildTerritoryGraph(twoSuppliers({
second: { municipality: { id: 7, name: 'La Mesa' }, department: { id: 2, name: 'Cundinamarca' } },
}))
expect(edgeOf(graph, 'product:1', 'junction:1').certain).toBe(true)
expect(edgeOf(graph, 'junction:1', 'supplier:5').certain).toBe(false)
expect(edgeOf(graph, 'supplier:5', 'municipality:7')).toEqual({ from: 'supplier:5', to: 'municipality:7', certain: true })
expect(edgeOf(graph, 'supplier:6', 'municipality:7')).toEqual({ from: 'supplier:6', to: 'municipality:7', certain: true })
expect(edgeOf(graph, 'municipality:7', 'department:2').certain).toBe(true)
expect(edgeOf(graph, 'department:2', 'country:1').certain).toBe(true)
})
it('municipios distintos pero mismo departamento: dudoso hasta el municipio y cierto desde el departamento', () => {
const graph = buildTerritoryGraph(twoSuppliers({
second: { department: { id: 2, name: 'Cundinamarca' } },
}))
expect(edgeOf(graph, 'supplier:5', 'municipality:7').certain).toBe(false)
expect(edgeOf(graph, 'supplier:6', 'municipality:8').certain).toBe(false)
expect(edgeOf(graph, 'municipality:7', 'department:2')).toEqual({ from: 'municipality:7', to: 'department:2', certain: true })
expect(edgeOf(graph, 'municipality:8', 'department:2')).toEqual({ from: 'municipality:8', to: 'department:2', certain: true })
expect(edgeOf(graph, 'department:2', 'country:1').certain).toBe(true)
})
it('municipios y departamentos distintos mantienen la duda, y el país común es cierto', () => {
const graph = buildTerritoryGraph(twoSuppliers())
expect(edgeOf(graph, 'supplier:5', 'municipality:7').certain).toBe(false)
expect(edgeOf(graph, 'municipality:7', 'department:2').certain).toBe(false)
expect(edgeOf(graph, 'supplier:6', 'municipality:8').certain).toBe(false)
expect(edgeOf(graph, 'municipality:8', 'department:3').certain).toBe(false)
expect(edgeOf(graph, 'department:2', 'country:1')).toEqual({ from: 'department:2', to: 'country:1', certain: true })
expect(edgeOf(graph, 'department:3', 'country:1')).toEqual({ from: 'department:3', to: 'country:1', certain: true })
})
it('no crea nodos de territorio cuando el proveedor no tiene municipio', () => {
@@ -154,6 +230,6 @@ describe('buildTerritoryGraph', () => {
const graph = buildTerritoryGraph(data)
expect(graph.nodes.map(n => n.id)).toEqual(['product:1', 'supplier:5'])
expect(graph.edges).toEqual([{ from: 'product:1', to: 'supplier:5' }])
expect(graph.edges).toEqual([{ from: 'product:1', to: 'supplier:5', certain: true }])
})
})