fix: parsear todos los pools en colaboraciones BPMN multi-proceso

El parser usaba querySelector() que retornaba solo el primer <bpmn:process>.
En diagramas de colaboración con múltiples pools, las actividades de los
pools 2..N nunca se cargaban en la store, haciendo imposible configurarlas.

Ahora itera todos los <bpmn:process> hijos directos de <definitions> y
combina sus nodos y flujos en un único grafo. Pools de soporte sin StartEvent
propio (SAP, bandeja manual) son válidos — la validación exige al menos un
StartEvent entre todos los pools del diagrama.

Agrega 3 tests de regresión para el escenario de colaboración.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 02:13:50 -03:00
parent bed161c92a
commit e95cfc42ac
2 changed files with 82 additions and 31 deletions

View File

@@ -64,49 +64,58 @@ export function parseBpmnXml(xml: string): ProcessGraph {
const flows = new Map<string, ProcessGraphFlow>()
const startEventIds: BpmnElementId[] = []
// Buscar el proceso principal (puede estar anidado bajo definitions)
const processEl = doc.querySelector('[*|localName="process"], process')
if (!processEl) {
// Buscar TODOS los procesos del documento. Las colaboraciones tienen múltiples
// <bpmn:process> como hijos directos de <definitions>, uno por pool.
const definitions = doc.documentElement
const processEls = Array.from(definitions.children).filter(
(el) => normalizeLocalName(el.tagName) === 'process'
)
if (processEls.length === 0) {
throw new BpmnParseError('No se encontró un elemento <process> en el BPMN')
}
// Iterar todos los hijos directos del proceso
for (const child of Array.from(processEl.children)) {
const local = normalizeLocalName(child.tagName)
const type: BpmnNodeType = TAG_TO_TYPE[local] ?? 'unknown'
// Parsear cada proceso y acumular nodos y flujos en mapas compartidos
for (const processEl of processEls) {
for (const child of Array.from(processEl.children)) {
const local = normalizeLocalName(child.tagName)
const type: BpmnNodeType = TAG_TO_TYPE[local] ?? 'unknown'
if (local === 'sequenceflow') {
const flow: ProcessGraphFlow = {
id: getAttr(child, 'id'),
sourceRef: getAttr(child, 'sourceRef'),
targetRef: getAttr(child, 'targetRef'),
name: getAttr(child, 'name') || undefined,
if (local === 'sequenceflow') {
const flow: ProcessGraphFlow = {
id: getAttr(child, 'id'),
sourceRef: getAttr(child, 'sourceRef'),
targetRef: getAttr(child, 'targetRef'),
name: getAttr(child, 'name') || undefined,
}
if (flow.id) flows.set(flow.id, flow)
continue
}
if (flow.id) flows.set(flow.id, flow)
continue
if (type === 'unknown') continue
const id = getAttr(child, 'id')
if (!id) continue
const node: ProcessGraphNode = {
id,
name: getAttr(child, 'name'),
type,
outgoing: parseFlowRefs(child, 'outgoing'),
incoming: parseFlowRefs(child, 'incoming'),
}
nodes.set(id, node)
if (type === 'startEvent') startEventIds.push(id)
}
if (type === 'unknown') continue
const id = getAttr(child, 'id')
if (!id) continue
const node: ProcessGraphNode = {
id,
name: getAttr(child, 'name'),
type,
outgoing: parseFlowRefs(child, 'outgoing'),
incoming: parseFlowRefs(child, 'incoming'),
}
nodes.set(id, node)
if (type === 'startEvent') startEventIds.push(id)
}
if (nodes.size === 0) {
throw new BpmnParseError('El proceso no contiene elementos reconocibles')
}
// Pools de soporte (SAP, bandeja manual, etc.) pueden no tener StartEvent propio —
// exigimos al menos uno entre todos los pools del diagrama.
if (startEventIds.length === 0) {
throw new BpmnParseError('El proceso no tiene evento de inicio (StartEvent)')
}