jsegd-bpm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,48 @@
1
+ # END USER LICENSE AGREEMENT (EULA)
2
+
3
+ Este Contrato de Licença de Usuário Final ("EULA") é um acordo legal entre você (seja uma pessoa física ou jurídica) e EGD CONSULTORIA EM TECNOLOGIA LTDA, uma empresa devidamente registrada no Brasil, com sede em AV PAULISTA, 1106 SALA 01 ANDAR 16, BELA VISTA, SÃO PAULO/SP ("Licenciante"), que rege o uso do software jsEGD BPM (o "Software"). Ao instalar, copiar ou de outra forma usar o Software, você concorda em se vincular aos termos deste EULA.
4
+
5
+ ## Concessão de Licença
6
+
7
+ O Licenciante concede ao Usuário uma licença limitada, não exclusiva e intransferível para usar o Software, de acordo com os termos e condições deste EULA.
8
+
9
+ Uso Permitido: O Usuário pode usar o Software apenas para fins internos, em conformidade com a documentação fornecida.
10
+
11
+ Propriedade: O Software é licenciado, não vendido. O Usuário reconhece que o Licenciante mantém a propriedade intelectual de todo o Software e quaisquer atualizações ou versões subsequentes.
12
+
13
+ ## Restrições
14
+
15
+ O Usuário não pode, sob nenhuma circunstância, realizar qualquer uma das seguintes ações:
16
+
17
+ * Modificação: Modificar, adaptar, traduzir ou criar obras derivadas com base no Software.
18
+ * Descompilação e Engenharia Reversa: Descompilar, fazer engenharia reversa ou tentar derivar o código-fonte do Software, exceto se expressamente permitido pela legislação aplicável.
19
+ * Redistribuição: Redistribuir, sublicenciar, vender ou transferir de qualquer forma o Software a terceiros.
20
+ * Uso Comercial: O Software é fornecido para uso interno. O Usuário não pode usá-lo para fins comerciais de revenda, aluguel ou locação.
21
+
22
+ ## Licença Limitada
23
+
24
+ A licença concedida ao Usuário é não exclusiva, não transferível e revogável. O Licenciante se reserva o direito de revogar a licença a qualquer momento, caso o Usuário viole os termos deste EULA.
25
+
26
+ ## Garantias e Isenção de Responsabilidade
27
+
28
+ O Licenciante fornece o Software "no estado em que se encontra", sem quaisquer garantias explícitas ou implícitas, incluindo, mas não se limitando a, garantias de adequação a um propósito específico ou de não violação.
29
+
30
+ O Licenciante não será responsável por qualquer dano direto, indireto, incidental, especial ou consequente que possa resultar do uso ou incapacidade de usar o Software, mesmo que o Licenciante tenha sido avisado da possibilidade de tais danos.
31
+
32
+ ## Propriedade Intelectual
33
+
34
+ O Usuário reconhece que todos os direitos de propriedade intelectual sobre o Software, incluindo, mas não se limitando a, patentes, direitos autorais e marcas registradas, pertencem exclusivamente ao Licenciante.
35
+
36
+ O Usuário não adquire nenhum direito de propriedade sobre o Software, exceto pela licença limitada concedida por este EULA.
37
+
38
+ ## Rescisão
39
+
40
+ Este EULA entrará em vigor na data de aceitação do Usuário e continuará em vigor até ser rescindido. O Licenciante pode rescindir este EULA a qualquer momento, caso o Usuário viole qualquer termo deste contrato. Após a rescisão, o Usuário deve desinstalar e destruir todas as cópias do Software.
41
+
42
+ ## Lei Aplicável
43
+
44
+ Este EULA será regido e interpretado de acordo com as leis do Brasil. Qualquer disputa relacionada a este EULA será resolvida exclusivamente nos tribunais competentes de São Paulo/SP, independente de outro, por mais privilegiado que seja.
45
+
46
+ ## Aceitação
47
+
48
+ Ao instalar ou usar o Software, o Usuário concorda com os termos deste EULA. Se o Usuário não concordar com os termos, deverá interromper imediatamente a instalação ou o uso do Software e destruir qualquer cópia do Software em sua posse.
package/README.md ADDED
@@ -0,0 +1,553 @@
1
+ # jsEGD BPM
2
+
3
+ Biblioteca TypeScript de runtime para desenvolvimento de formulários e workflows no ambiente **EGD BPM** — EGD Tecnologia.
4
+ Fornece componentes de anexo, gerenciamento de workers para datasets, controle do ciclo de vida do workflow e utilitários de formulário integrados ao DOM do EGD BPM.
5
+
6
+ > **Dependências par obrigatórias:** [`jsegd`](https://jsegd.netlify.app) ≥ 1.1.0 e [`alpinejs`](https://alpinejs.dev) ≥ 3.0.0
7
+
8
+ ---
9
+
10
+ ## Instalação
11
+
12
+ ```bash
13
+ npm install jsegd-bpm jsegd alpinejs
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Entradas do pacote
19
+
20
+ | Entrada | Importação | Conteúdo |
21
+ | --------- | ------------- | ----------------------------------------------------------------------------- |
22
+ | Principal | `jsegd-bpm` | Workers, anexos, validação, workflow, form-repository, workflow-view e alpine |
23
+
24
+ ---
25
+
26
+ ## `WorkerManager` — Consultas de Dataset em Web Workers
27
+
28
+ Gerencia um pool de Web Workers para executar consultas à API de Datasets do EGD BPM fora da thread principal. Suporta deduplicação de requisições, cache em IndexedDB e cancelamento HTTP real via `AbortController`.
29
+
30
+ ```typescript
31
+ import { WorkerManager, ConstraintsType } from 'jsegd-bpm'
32
+ import { LocalCache } from 'jsegd'
33
+
34
+ // Compartilhando um cache existente entre componentes:
35
+ const cache = LocalCache.getInstance()
36
+ cache.initialize({ storeName: 'datasets', cacheExpirationMs: 60 * 60 * 1000 })
37
+
38
+ const manager = new WorkerManager({ poolSize: 4, cache })
39
+
40
+ const { promise, cancel } = manager.query('https://tenant.bpm.empresa.com', {
41
+ datasetId: 'dsColaboradores',
42
+ field: ['nome', 'email', 'matricula'],
43
+ constraintsField: ['ativo'],
44
+ constraintsInitialValue: ['true'],
45
+ constraintsType: [ConstraintsType.MUST],
46
+ })
47
+
48
+ // Cancelar requisição em andamento, se necessário:
49
+ // cancel()
50
+
51
+ const result = await promise
52
+ console.log(result.columns) // ['nome', 'email', 'matricula']
53
+ console.log(result.values) // [{ nome: 'Ana', email: 'ana@co.com', matricula: '1234' }, ...]
54
+
55
+ // Liberar recursos ao desmontar o componente:
56
+ manager.terminate()
57
+ ```
58
+
59
+ **`WorkerManagerOptions`**
60
+
61
+ | Opção | Tipo | Padrão | Descrição |
62
+ | -------------- | -------------- | ------ | -------------------------------------------------------------- |
63
+ | `poolSize` | `number` | `4` | Número de workers paralelos |
64
+ | `cache` | `ICache` | — | Instância de cache existente (compartilhada entre componentes) |
65
+ | `cacheOptions` | `CacheOptions` | — | Cria um `LocalCache` interno com `storeName` e TTL em ms |
66
+
67
+ > Se nem `cache` nem `cacheOptions` for informado, o gerenciador opera sem cache.
68
+
69
+ **`ConstraintsType`** — enum: `MUST`, `SHOULD`, `MUST_NOT`.
70
+
71
+ ---
72
+
73
+ ## `AttachButton` / `AttachmentsTable` — Componentes de Anexo
74
+
75
+ Custom Elements prontos para uso em formulários EGD BPM. Registre-os uma vez no ponto de entrada da aplicação.
76
+
77
+ ```typescript
78
+ import { AttachButton, AttachmentsTable } from 'jsegd-bpm'
79
+
80
+ customElements.define('attachment-button', AttachButton)
81
+ customElements.define('attachments-table', AttachmentsTable)
82
+ ```
83
+
84
+ ### `<attachment-button>`
85
+
86
+ Botão com dropdown para upload de arquivos vinculados ao processo.
87
+
88
+ ```html
89
+ <!-- Tipo de anexo fixo -->
90
+ <attachment-button description="CONTRATO"></attachment-button>
91
+
92
+ <!-- Prompt ao usuário para informar a descrição no upload -->
93
+ <attachment-button></attachment-button>
94
+
95
+ <!-- Somente leitura: oculta botão de adicionar -->
96
+ <attachment-button description="CONTRATO" noaddbutton></attachment-button>
97
+ ```
98
+
99
+ | Atributo | Tipo | Descrição |
100
+ | ---------------- | -------- | ---------------------------------------------------------------- |
101
+ | `description` | `string` | Tipo do anexo. Vazio abre modal para o usuário informar |
102
+ | `accept` | `string` | Filtro de tipos aceitos pelo seletor nativo (ex: `".pdf,.docx"`) |
103
+ | `noaddbutton` | — | Oculta o botão de adicionar |
104
+ | `noviewbutton` | — | Oculta o botão de visualizar |
105
+ | `noeditbutton` | — | Oculta o botão de editar descrição |
106
+ | `nodeletebutton` | — | Oculta o botão de excluir |
107
+
108
+ ### `<attachments-table>`
109
+
110
+ Tabela que lista os anexos do processo com suporte a drag-and-drop para reordenação.
111
+
112
+ ```html
113
+ <attachments-table mode="edit"></attachments-table>
114
+ ```
115
+
116
+ ---
117
+
118
+ ## `BeforeSendValidate` — Validação com feedback visual
119
+
120
+ Integra validação de campos com o sistema de erros visuais do tema EGD BPM (`data-group` / `data-helper`).
121
+
122
+ ```typescript
123
+ import { BeforeSendValidate } from 'jsegd-bpm'
124
+ import type { FieldError } from 'jsegd-bpm'
125
+
126
+ function validarFormulario() {
127
+ const erros: FieldError[] = []
128
+
129
+ const campoNome = document.querySelector<HTMLInputElement>('[name="nome_solicitante"]')
130
+ if (!campoNome?.value) {
131
+ erros.push({ field: campoNome, errorMessage: 'Informe o nome do solicitante' })
132
+ }
133
+
134
+ if (erros.length > 0) {
135
+ BeforeSendValidate.throwValidationError(erros)
136
+ // Lança Error com HTML formatado para o widget de processo exibir
137
+ }
138
+ }
139
+ ```
140
+
141
+ `throwValidationError` lança sempre — use dentro de `beforeSendValidate` do EGD BPM ou em hooks de `WorkflowViewPatcher`.
142
+
143
+ ---
144
+
145
+ ## `WorkflowViewPatcher` — Hooks no widget de processo
146
+
147
+ Substitui métodos do widget nativo `ECM_WKFView` para interceptar o ciclo de envio do processo sem modificar o EGD BPM.
148
+
149
+ ```typescript
150
+ import { WorkflowViewPatcher } from 'jsegd-bpm'
151
+
152
+ // 1. Inicializar — aguarda o widget carregar no frame pai
153
+ await WorkflowViewPatcher.init()
154
+
155
+ // 2. Aplicar todos os patches registrados
156
+ WorkflowViewPatcher.patchAll()
157
+
158
+ // 3. Registrar hooks
159
+ WorkflowViewPatcher.addBeforeValidate(async () => {
160
+ // Executado antes da validação do widget.
161
+ // Lançar exceção cancela o envio e reabilita os botões.
162
+ const valor = document.querySelector<HTMLInputElement>('[name="campo_critico"]')?.value
163
+ if (!valor) throw new Error('Campo crítico obrigatório')
164
+ })
165
+
166
+ WorkflowViewPatcher.addAfterValidate(async () => {
167
+ // Executado após validação bem-sucedida, antes do envio efetivo.
168
+ await salvarRascunho()
169
+ })
170
+
171
+ WorkflowViewPatcher.addOnMoveSuccess(() => {
172
+ console.log('Processo movimentado com sucesso')
173
+ })
174
+
175
+ WorkflowViewPatcher.addOnMoveError(() => {
176
+ console.error('Erro na movimentação — verifique os logs')
177
+ })
178
+
179
+ WorkflowViewPatcher.addOnCompletionLinkAdd((message) => {
180
+ // Adiciona links à tela de conclusão após a movimentação
181
+ message.links.push({ description: 'Abrir tarefa seguinte', href: '/minha-tarefa' })
182
+ })
183
+
184
+ // Ao desmontar o formulário (SPAs): restaura os métodos originais
185
+ WorkflowViewPatcher.restore()
186
+ ```
187
+
188
+ **Métodos estáticos**
189
+
190
+ | Método | Descrição |
191
+ | ---------------------------- | ------------------------------------------------------- |
192
+ | `init(timeout?)` | Aguarda `ECM_WKFView` ficar disponível no frame pai |
193
+ | `patchAll()` | Aplica todos os overrides registrados |
194
+ | `patch(attr)` | Aplica um override específico |
195
+ | `restore()` | Reverte todos os métodos e limpa hooks e flags internos |
196
+ | `addBeforeValidate(fn)` | Hook antes da validação do widget |
197
+ | `addAfterValidate(fn)` | Hook após validação, antes do envio |
198
+ | `addOnMoveSuccess(fn)` | Callback de sucesso na movimentação |
199
+ | `addOnMoveError(fn)` | Callback de erro na movimentação |
200
+ | `addOnCompletionLinkAdd(fn)` | Manipula links da tela de conclusão |
201
+
202
+ ---
203
+
204
+ ## `IFormRepository` — Acesso tipado ao formulário
205
+
206
+ Interface agnóstica ao DOM com duas implementações: `BpmFormRepository` (produção) e `MockFormRepository` (testes unitários).
207
+
208
+ ### Convenção de nomenclatura de campos
209
+
210
+ Os campos do formulário EGD BPM devem seguir o padrão:
211
+
212
+ ```
213
+ {domínio}_{atributo_snake_case}
214
+ ```
215
+
216
+ onde `{domínio}` é o identificador do **agregado de negócio** ao qual o campo pertence — não a tela em que aparece nem a fase do workflow. O total não deve ultrapassar **30 caracteres** (limite de coluna no banco EGD).
217
+
218
+ | Campo no DOM | Domínio | Atributo no modelo |
219
+ | ------------------- | ----------- | ------------------ |
220
+ | `contrato_indice` | `contrato` | `indice` |
221
+ | `imovel_cep` | `imovel` | `cep` |
222
+ | `vigencia_prazo` | `vigencia` | `prazo` |
223
+ | `fornecedor_cep` | `fornecedor`| `cep` |
224
+
225
+ > **Importante:** o `{domínio}` é o prefixo persistido no banco pelo Fluig — é parte integrante do nome da coluna e **não deve ser omitido** no HTML. O `load` e o `save` gerenciam a conversão entre o nome DOM e o atributo do modelo de forma transparente.
226
+
227
+ O `moduleId` do workflow (quem exibe o campo na tela) e o `stage` (em que fase do processo estamos) são **independentes** do domínio do campo — um mesmo módulo pode exibir campos de múltiplos domínios.
228
+
229
+ ### Produção — `BpmFormRepository`
230
+
231
+ Lê e escreve diretamente nos elementos `input`, `select` e `textarea` do formulário EGD BPM. Encapsula toda a interação com `wdkAddChild` para tabelas filhas.
232
+
233
+ `load(section)` remove o prefixo `{section}_` e converte `snake_case → camelCase` antes de retornar.
234
+ `save(section, data)` faz o caminho inverso: converte `camelCase → snake_case` e adiciona o prefixo ao escrever no DOM.
235
+
236
+ ```typescript
237
+ import { BpmFormRepository } from 'jsegd-bpm'
238
+
239
+ interface Contrato {
240
+ indice: string
241
+ primeiroPagamento: string
242
+ reajuste: string
243
+ }
244
+
245
+ interface Imovel {
246
+ cep: string
247
+ logradouro: string
248
+ municipio: string
249
+ }
250
+
251
+ interface ItemTabela {
252
+ descricao: string
253
+ valor: string
254
+ }
255
+
256
+ const repo = new BpmFormRepository()
257
+
258
+ // Ler domínio 'contrato' — lê campos com name="contrato_*" do DOM
259
+ // DOM: contrato_indice, contrato_primeiro_pagamento, contrato_reajuste
260
+ const contrato = repo.load<Contrato>('contrato')
261
+ console.log(contrato.indice) // valor de input[name="contrato_indice"]
262
+ console.log(contrato.primeiroPagamento) // valor de input[name="contrato_primeiro_pagamento"]
263
+
264
+ // Ler domínio 'imovel' — desambigua de 'fornecedor_cep', 'contrato_cep', etc.
265
+ const imovel = repo.load<Imovel>('imovel')
266
+ console.log(imovel.cep) // valor de input[name="imovel_cep"]
267
+
268
+ // Ler tabela filha
269
+ const itens = repo.loadTable<ItemTabela>('tabItens')
270
+
271
+ // Persistir no DOM — save converte camelCase → prefixo+snake_case
272
+ repo.save<Contrato>('contrato', { indice: 'IGPM', primeiroPagamento: '2026-02-01' })
273
+ // escreve: input[name="contrato_indice"] e input[name="contrato_primeiro_pagamento"]
274
+
275
+ // Substituir linhas da tabela filha
276
+ repo.saveTable<ItemTabela>('tabItens', [
277
+ { descricao: 'Notebook', valor: '4500.00' },
278
+ { descricao: 'Mouse', valor: '120.00' },
279
+ ])
280
+ ```
281
+
282
+ ### Testes — `MockFormRepository`
283
+
284
+ Implementação in-memory sem dependência de DOM. Aplica a mesma lógica de prefixo e conversão de `BpmFormRepository` para que os testes reflitam o comportamento real.
285
+
286
+ ```typescript
287
+ import { MockFormRepository } from 'jsegd-bpm'
288
+
289
+ interface Vigencia {
290
+ prazo: string
291
+ tipoDePrazo: string
292
+ }
293
+
294
+ const repo = new MockFormRepository()
295
+
296
+ // save armazena internamente como 'vigencia_prazo', 'vigencia_tipo_de_prazo'
297
+ repo.save<Vigencia>('vigencia', { prazo: '12', tipoDePrazo: 'Meses' })
298
+
299
+ // load recupera { prazo: '12', tipoDePrazo: 'Meses' }
300
+ const data = repo.load<Vigencia>('vigencia')
301
+ // data.prazo === '12'
302
+ // data.tipoDePrazo === 'Meses'
303
+
304
+ repo.saveTable('tabItens', [{ descricao: 'Cadeira', valor: '800.00' }])
305
+ const itens = repo.loadTable('tabItens')
306
+
307
+ repo.reset() // limpa todos os dados
308
+ ```
309
+
310
+ ### Processos legados — `loadFields`
311
+
312
+ Processos existentes cujos campos não seguem a convenção `{domínio}_{atributo}` não devem ser alterados para evitar migração de dados no banco. Para esses casos, `jsegd-bpm` exporta a função utilitária `loadFields`, que recebe um mapeamento explícito `{ propriedadeModelo: nomeDOM }`:
313
+
314
+ ```typescript
315
+ import { loadFields } from 'jsegd-bpm'
316
+
317
+ interface Solicitante {
318
+ nome: string
319
+ email: string
320
+ }
321
+
322
+ // Campo legado: sufixo em vez de prefixo — nome_solicitante, email_solicitante
323
+ const dados = loadFields<Solicitante>({
324
+ nome: 'nome_solicitante',
325
+ email: 'email_solicitante',
326
+ })
327
+ // dados.nome === valor de input[name="nome_solicitante"]
328
+ // dados.email === valor de input[name="email_solicitante"]
329
+ ```
330
+
331
+ > **Regra:** `loadFields` **não faz parte de `IFormRepository`** — é um utilitário de compatibilidade para processos legados. Novos processos devem usar `repository.load(section)` exclusivamente.
332
+
333
+ ---
334
+
335
+ ## Módulo `workflow/` — Configuração e controle de tela
336
+
337
+ Conjunto de utilitários para mapear as atividades BPM em estados de tela — barra de progresso, visibilidade de módulos e editabilidade — sem hardcode de números de sequência nos Presenters.
338
+
339
+ ### `defineWorkflowConfig` — Configuração tipada
340
+
341
+ Define módulos e atividades em uma declaração única. O tipo `ModuleId` é inferido automaticamente — nenhum type alias manual é necessário.
342
+
343
+ ```typescript
344
+ import { defineWorkflowConfig } from 'jsegd-bpm'
345
+
346
+ export const workflowConfig = defineWorkflowConfig({
347
+ modules: [
348
+ {
349
+ moduleId: 'solicitante',
350
+ label: 'Dados Iniciais',
351
+ icon: 'file-text',
352
+ order: 1,
353
+ showInNavigation: true,
354
+ renderContent: true,
355
+ contentIncludePath: 'solicitante.html',
356
+ },
357
+ {
358
+ moduleId: 'juridico',
359
+ label: 'Jurídico',
360
+ icon: 'gavel',
361
+ order: 2,
362
+ showInNavigation: true,
363
+ renderContent: true,
364
+ contentIncludePath: 'juridico.html',
365
+ },
366
+ {
367
+ moduleId: 'financeiro',
368
+ label: 'Financeiro',
369
+ icon: 'dollar-sign',
370
+ order: 3,
371
+ showInNavigation: true,
372
+ renderContent: true,
373
+ contentIncludePath: 'financeiro.html',
374
+ },
375
+ ],
376
+ activities: [
377
+ {
378
+ sequence: 9,
379
+ description: 'Solicitação inicial',
380
+ kind: 'human',
381
+ stage: 'solicitacao',
382
+ modules: { visible: ['solicitante'], editable: ['solicitante'], active: 'solicitante' },
383
+ },
384
+ {
385
+ sequence: 53,
386
+ description: 'Aprovação jurídica',
387
+ kind: 'human',
388
+ stage: 'juridico',
389
+ modules: { visible: ['solicitante', 'juridico'], editable: ['juridico'], active: 'juridico' },
390
+ },
391
+ {
392
+ sequence: 61,
393
+ description: 'Análise financeira',
394
+ kind: 'human',
395
+ stage: 'financeiro',
396
+ modules: { visible: ['solicitante', 'juridico', 'financeiro'], editable: ['financeiro'] },
397
+ },
398
+ { sequence: 88, description: 'Roteamento interno', kind: 'routing' },
399
+ ],
400
+ } as const)
401
+ // Tipo de ModuleId inferido: 'solicitante' | 'juridico' | 'financeiro'
402
+ ```
403
+
404
+ **`ActivityKind`** — controla o comportamento na barra de progresso:
405
+
406
+ | Valor | Comportamento |
407
+ | ----------- | ------------------------------------------------------------------- |
408
+ | `'human'` | Ação de usuário — estágio atual destacado na barra |
409
+ | `'system'` | Aguardando retorno externo (webhook) — exibe spinner |
410
+ | `'support'` | Suporte transversal — sobrepõe banner de alerta; não avança a barra |
411
+ | `'routing'` | Roteamento interno do motor BPM — invisível na barra |
412
+
413
+ ---
414
+
415
+ ### `extractCompletedStages` / `extractCurrentStage` — Histórico do processo
416
+
417
+ Parseia o histórico bruto retornado por `parent.ECM.workflowView.processDefinition.urlHistory` e produz um `ProcessHistory` para consumo pelo `ScreenContext`.
418
+
419
+ ```typescript
420
+ import { extractCompletedStages, extractCurrentStage } from 'jsegd-bpm'
421
+ import type { BpmHistoryMap, ProcessHistory } from 'jsegd-bpm'
422
+
423
+ const rawHistory = parent.ECM.workflowView.processDefinition.urlHistory as BpmHistoryMap
424
+ const currentSequence: number = parent.ECM.workflowView.processDefinition.cardIndex
425
+ const historyType: string | null = parent.ECM.workflowView.processDefinition.historyType ?? null
426
+
427
+ const completedStages = extractCompletedStages(rawHistory, workflowConfig.activities)
428
+ // Ex: ['solicitacao', 'juridico']
429
+
430
+ const currentStage = extractCurrentStage(currentSequence, historyType, workflowConfig.activities)
431
+ // Ex: { stage: 'financeiro', status: 'current' }
432
+
433
+ const processHistory: ProcessHistory = { completedStages, currentStage }
434
+ ```
435
+
436
+ ---
437
+
438
+ ### `resolveWorkflowScreen` + `ScreenContext` — Estado da tela
439
+
440
+ Cruza a configuração com o histórico para produzir um `ScreenContext` — o único objeto que os Presenters precisam consultar.
441
+
442
+ ```typescript
443
+ import { resolveWorkflowScreen } from 'jsegd-bpm'
444
+
445
+ const screen = resolveWorkflowScreen(workflowConfig, currentSequence, processHistory)
446
+
447
+ // Visibilidade e editabilidade
448
+ screen.isVisible('juridico') // true
449
+ screen.isEditable('financeiro') // false
450
+ screen.isActive('juridico') // true
451
+
452
+ // Estado da atividade
453
+ screen.isAwaitingSystem() // true quando kind === 'system' ou status === 'waiting'
454
+ screen.isInSupport() // true para kind === 'support'
455
+
456
+ // Barra de progresso
457
+ const stages = screen.getProgressBarStages()
458
+ // [
459
+ // { stage: 'solicitacao', status: 'done' },
460
+ // { stage: 'juridico', status: 'done' },
461
+ // { stage: 'financeiro', status: 'current' },
462
+ // ]
463
+
464
+ // Módulos
465
+ const visiveis = screen.getVisibleModules() // ResolvedModule[] — apenas isVisible = true
466
+ const navegacao = screen.getNavigationModules() // ResolvedModule[] — apenas showInNavigation = true e visível
467
+ ```
468
+
469
+ ---
470
+
471
+ ## `registerStores` — Alpine stores tipados
472
+
473
+ Registra múltiplos Alpine stores em uma única chamada e retorna um proxy tipado para acesso direto sem casting manual.
474
+
475
+ ```typescript
476
+ import Alpine from 'alpinejs'
477
+ import { registerStores } from 'jsegd-bpm'
478
+
479
+ type PerfilId = 'admin' | 'analista' | 'visualizador'
480
+
481
+ const stores = registerStores(Alpine, {
482
+ usuario: { nome: '', perfil: '' as PerfilId, matricula: '' },
483
+ ui: { carregando: false, erro: null as string | null },
484
+ })
485
+
486
+ Alpine.start()
487
+
488
+ // Acesso tipado — sem Alpine.store('usuario') as UsuarioStore
489
+ stores.usuario.nome // string
490
+ stores.usuario.perfil // PerfilId
491
+ stores.ui.carregando // boolean
492
+ ```
493
+
494
+ > `registerStores` aceita `Pick<Alpine, 'store'>` — compatível com qualquer mock em testes unitários.
495
+
496
+ ---
497
+
498
+ ## Uso típico em um formulário EGD BPM
499
+
500
+ ```typescript
501
+ import Alpine from 'alpinejs'
502
+ import {
503
+ AttachButton,
504
+ AttachmentsTable,
505
+ WorkflowViewPatcher,
506
+ BpmFormRepository,
507
+ extractCompletedStages,
508
+ extractCurrentStage,
509
+ resolveWorkflowScreen,
510
+ registerStores,
511
+ } from 'jsegd-bpm'
512
+ import type { BpmHistoryMap } from 'jsegd-bpm'
513
+ import { workflowConfig } from './workflow.config'
514
+
515
+ // Registrar Custom Elements
516
+ customElements.define('attachment-button', AttachButton)
517
+ customElements.define('attachments-table', AttachmentsTable)
518
+
519
+ // Alpine stores
520
+ const stores = registerStores(Alpine, {
521
+ solicitante: { nome: '', departamento: '' },
522
+ ui: { carregando: false },
523
+ })
524
+
525
+ // Histórico e tela
526
+ const rawHistory = parent.ECM.workflowView.processDefinition.urlHistory as BpmHistoryMap
527
+ const seq: number = parent.ECM.workflowView.processDefinition.cardIndex
528
+ const histType: string | null = parent.ECM.workflowView.processDefinition.historyType ?? null
529
+
530
+ const processHistory = {
531
+ completedStages: extractCompletedStages(rawHistory, workflowConfig.activities),
532
+ currentStage: extractCurrentStage(seq, histType, workflowConfig.activities),
533
+ }
534
+
535
+ const screen = resolveWorkflowScreen(workflowConfig, seq, processHistory)
536
+
537
+ // Patcher
538
+ await WorkflowViewPatcher.init()
539
+ WorkflowViewPatcher.patchAll()
540
+ WorkflowViewPatcher.addBeforeValidate(async () => {
541
+ const repo = new BpmFormRepository()
542
+ const dados = repo.load<{ nome: string }>('solicitante')
543
+ if (!dados.nome) throw new Error('Informe o nome do solicitante')
544
+ })
545
+
546
+ Alpine.start()
547
+ ```
548
+
549
+ ---
550
+
551
+ ## Licença
552
+
553
+ Proprietária — EGD Tecnologia. Uso restrito a projetos autorizados.
@@ -0,0 +1,72 @@
1
+ :host {
2
+ display: inline-block;
3
+ position: relative;
4
+ font-family: Arial, sans-serif;
5
+ }
6
+
7
+ .dropdown {
8
+ position: relative;
9
+ display: inline-block;
10
+ }
11
+
12
+ .dropdown-button {
13
+ background-color: #fff;
14
+ color: #555;
15
+ padding: 6px 10px;
16
+ font-size: 14px;
17
+ border: 1px solid #ccc;
18
+ cursor: pointer;
19
+ border-radius: 0 5px 5px 0;
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 5px;
23
+ height: 32px;
24
+ }
25
+
26
+ .dropdown-button:hover {
27
+ color: #555;
28
+ border-color: #adadad;
29
+ background-color: #e6e6e6;
30
+ }
31
+
32
+ .dropdown-menu {
33
+ display: none;
34
+ position: absolute;
35
+ top: 100%;
36
+ right: 0;
37
+ background-color: #fff;
38
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
39
+ min-width: 150px;
40
+ list-style: none;
41
+ padding: 0;
42
+ margin: 0;
43
+ border-radius: 0 0 2px 2px;
44
+ }
45
+
46
+ .dropdown-menu a {
47
+ display: flex;
48
+ gap: 10px;
49
+ padding: 5px;
50
+ box-sizing: border-box;
51
+ width: 100%;
52
+ text-decoration: none;
53
+ color: #333;
54
+ font-size: 14px;
55
+ justify-content: end;
56
+ align-items: center;
57
+ line-height: 1.5rem;
58
+ }
59
+
60
+ .dropdown-menu a:hover {
61
+ background-color: #f1f1f1;
62
+ color: #1d1b1b;
63
+ }
64
+
65
+ .dropdown:hover .dropdown-menu {
66
+ display: block;
67
+ z-index: 9999;
68
+ }
69
+
70
+ .fs-display-none {
71
+ display: none !important;
72
+ }