diff --git a/.agent/atualizar-agente.global.sh b/.agent/atualizar-agente.global.sh
index b4707bf..52978aa 100755
--- a/.agent/atualizar-agente.global.sh
+++ b/.agent/atualizar-agente.global.sh
@@ -5,13 +5,25 @@ BASE="https://paiol.idz.one/desenvolvimento/agent-ia"
ERROS=0
baixar() {
+ TMP=$(mktemp)
echo "baixando $1"
- ERRO_CURL="$(curl -sSfL --create-dirs "$1" -o "$2" 2>&1)" || {
+ ERRO_CURL="$(curl -sSfL --create-dirs "$1" -o "$TMP" 2>&1)" || {
echo " ⚠ $1"
echo " $ERRO_CURL"
+ rm -f "$TMP"
ERROS=$((ERROS + 1))
return 1
}
+ # Rejeitar arquivos menores que 50 bytes (prováveis erros de proxy/WAF)
+ TAMANHO=$(wc -c < "$TMP" | tr -d ' ')
+ if [ "$TAMANHO" -lt 50 ]; then
+ echo " ⚠ $1 — arquivo muito pequeno ($TAMANHO bytes), ignorado"
+ rm -f "$TMP"
+ ERROS=$((ERROS + 1))
+ return 1
+ fi
+ mkdir -p "$(dirname "$2")"
+ mv "$TMP" "$2"
}
# ignorar se não auto atualizar
@@ -27,7 +39,14 @@ fi
baixar "$BASE/rules/carta-principios-agente-azteca.global.md" "$DIR/rules/carta-principios-agente-azteca.global.md"
baixar "$BASE/rules/escrita-natural.global.md" "$DIR/rules/escrita-natural.global.md"
baixar "$BASE/rules/typescript-sem-any.global.md" "$DIR/rules/typescript-sem-any.global.md"
-baixar "$BASE/rules/regras_negocio_md.global.md" "$DIR/rules/regras_negocio_md.global.md"
+baixar "$BASE/rules/regras-negocio-md.global.md" "$DIR/rules/regras-negocio-md.global.md"
+baixar "$BASE/rules/zod-validacao-padrao.global.md" "$DIR/rules/zod-validacao-padrao.global.md"
+baixar "$BASE/rules/limite-linhas.global.md" "$DIR/rules/limite-linhas.global.md"
+baixar "$BASE/rules/estrutura-vue.global.md" "$DIR/rules/estrutura-vue.global.md"
+baixar "$BASE/rules/jsdoc-padrao.global.md" "$DIR/rules/jsdoc-padrao.global.md"
+baixar "$BASE/rules/autonomia-agente.global.md" "$DIR/rules/autonomia-agente.global.md"
+baixar "$BASE/rules/fronteiras-monorepo.global.md" "$DIR/rules/fronteiras-monorepo.global.md"
+baixar "$BASE/rules/mensagens-usuario.global.md" "$DIR/rules/mensagens-usuario.global.md"
# workflows
baixar "$BASE/workflows/aprimorar-IA.global.md" "$DIR/workflows/aprimorar-IA.global.md"
@@ -48,8 +67,11 @@ baixar "$BASE/skills/mapear-codigo-fonte.global/SKILL.md" "$DIR/sk
baixar "$BASE/skills/diagnosticar-erros-build.global/SKILL.md" "$DIR/skills/diagnosticar-erros-build.global/SKILL.md"
baixar "$BASE/skills/documentar-decisao-tecnica.global/SKILL.md" "$DIR/skills/documentar-decisao-tecnica.global/SKILL.md"
baixar "$BASE/skills/documentar-regras-negocio.global/SKILL.md" "$DIR/skills/documentar-regras-negocio.global/SKILL.md"
+baixar "$BASE/skills/criar-schema-zod.global/SKILL.md" "$DIR/skills/criar-schema-zod.global/SKILL.md"
+baixar "$BASE/skills/documentar-dominio-modulo.global/SKILL.md" "$DIR/skills/documentar-dominio-modulo.global/SKILL.md"
# raiz (.agent)
baixar "$BASE/tools.md" "$DIR/tools.md"
+baixar "$BASE/verificar-linhas.sh" "$DIR/verificar-linhas.sh"
exit "$ERROS"
\ No newline at end of file
diff --git a/.agent/rules/autonomia-agente.global.md b/.agent/rules/autonomia-agente.global.md
new file mode 100644
index 0000000..91b1517
--- /dev/null
+++ b/.agent/rules/autonomia-agente.global.md
@@ -0,0 +1,60 @@
+---
+type: rule
+name: autonomia-agente
+description: Define limites de autonomia do agente — o que pode fazer sozinho, o que requer confirmação e o que é proibido
+scope: global
+trigger: always_on
+priority: alta
+updatedAt: "2026-06-01"
+---
+
+# Regra: Limites de Autonomia do Agente
+
+## Contexto
+
+O agente existe para **ampliar a capacidade humana**, não para substituir decisões. Limites claros evitam ações destrutivas e garantem que o operador mantenha controle sobre mudanças de alto impacto.
+
+## Ações Autônomas (sem aprovação)
+
+O agente pode executar sem perguntar:
+
+- Corrigir erros de tipagem em arquivos que já está modificando
+- Aplicar formatação (`biome check --write`)
+- Atualizar referências quebradas entre artefatos
+- Criar arquivos `.regras-negocio.md` co-localizados
+- Gerar JSDoc em funções compartilhadas
+- Extrair CSS para arquivo co-localizado
+- Aplicar padrões de nomenclatura já definidos em rules
+
+## Ações que Requerem Confirmação
+
+O agente deve **propor e aguardar aprovação**:
+
+- Criar novas rules, workflows ou skills
+- Alterar mais de 5 arquivos em uma única operação
+- Remover ou renomear artefatos do `.agent/`
+- Mudar contratos de API (parâmetros, retornos, rotas)
+- Alterar schemas Zod compartilhados no pacote `*-comuns`
+- Propor novas dependências ao `package.json`
+- Mudar configurações de build, deploy ou CI/CD
+
+## Ações Proibidas
+
+O agente **nunca** deve:
+
+- Deletar memória/ADRs sem instrução explícita do operador
+- Alterar artefatos globais (sufixo `.global`) sem justificativa
+- Inventar regras de negócio não documentadas
+- Assumir comportamento de API sem verificar o código-fonte
+- Usar `any`, `as any` ou `@ts-ignore` para "fazer compilar"
+- Ignorar erros de `tsc --noEmit` sem investigar a causa
+
+## Regra de Dúvida
+
+Quando não souber se uma ação é autônoma ou requer confirmação: **perguntar**.
+
+O custo de uma pergunta é zero. O custo de uma ação destrutiva é alto.
+
+## Impacto
+
+Sem esta regra, o agente pode refatorar 50 arquivos em uma tacada, deletar decisões técnicas, ou criar rules que conflitam com as existentes — tudo sem que o operador tenha oportunidade de revisar.
diff --git a/.agent/rules/carta-principios-agente-azteca.global.md b/.agent/rules/carta-principios-agente-azteca.global.md
index 3929246..4a9b20e 100644
--- a/.agent/rules/carta-principios-agente-azteca.global.md
+++ b/.agent/rules/carta-principios-agente-azteca.global.md
@@ -3,9 +3,10 @@ trigger: always_on
type: rule
name: carta-principios-agente-azteca
description: Princípios morais e operacionais que orientam o comportamento de agentes de IA no ecossistema Azteca
-version: 1.0
+version: "1.0"
scope: global
priority: alta
+updatedAt: "2026-06-01"
---
# Carta de Princípios do Agente de IA da Azteca
diff --git a/.agent/rules/escrita-natural.global.md b/.agent/rules/escrita-natural.global.md
index 1e8a175..2c92584 100644
--- a/.agent/rules/escrita-natural.global.md
+++ b/.agent/rules/escrita-natural.global.md
@@ -1,6 +1,14 @@
---
-trigger: always_on
-description: Garantir escrita natural, fluida e humana em textos técnicos e comerciais, evitando linguagem robótica ou excessivamente genérica.
+type: rule
+name: escrita-natural
+trigger: context_match
+description: Garantir escrita natural, fluida e humana em textos comerciais e de comunicação, evitando linguagem robótica ou excessivamente genérica.
+scope: global
+updatedAt: "2026-06-01"
+ativacao: >
+ Aplicar quando o agente estiver produzindo textos comerciais, de marketing,
+ orçamentos, propostas, reels ou qualquer comunicação voltada ao público externo.
+ NÃO aplicar em documentação técnica (ADRs, rules, JSDoc, regras de negócio).
---
# Regra: Escrita Estratégica e Natural
diff --git a/.agent/rules/estrutura-vue.global.md b/.agent/rules/estrutura-vue.global.md
new file mode 100644
index 0000000..de0a4cf
--- /dev/null
+++ b/.agent/rules/estrutura-vue.global.md
@@ -0,0 +1,86 @@
+---
+type: rule
+name: estrutura-vue
+description: Padrões obrigatórios de estrutura para componentes Vue 3 (defineComponent, setup, CSS, dispatch)
+scope: global
+trigger: file_match
+globs:
+ - "**/*.vue"
+updatedAt: "2026-06-01"
+---
+
+# Regra: Estrutura de Componentes Vue
+
+## 1. `defineComponent` obrigatório
+
+Todo componente Vue deve usar `defineComponent`:
+
+```vue
+
+```
+
+## 2. `setup()` não pode ser externo
+
+O `setup()` deve estar sempre dentro do `.vue`. Nunca delegar para arquivo externo.
+
+```ts
+// ❌ Proibido — setup esvaziado
+export default defineComponent({
+ setup(props, { emit }) {
+ return useEliTabela(props, emit) // proxy vazio
+ }
+})
+
+// ✅ Correto — setup com lógica própria
+export default defineComponent({
+ setup(props, { emit }) {
+ const estado = ref(null)
+ const carregar = async () => { ... }
+ return { estado, carregar }
+ }
+})
+```
+
+> Composables `use*.ts` podem encapsular lógica reutilizável pontual, mas nunca substituir o `setup()`. Se ultrapassar 250 linhas → extrair subcomponentes, CSS ou Dispatch Table.
+
+## 3. CSS co-localizado
+
+Separação de estilos em arquivo externo quando o CSS contribui para o total de linhas:
+
+```
+MeuComponente.vue
+MeuComponente.css
+```
+
+Importar via `import './MeuComponente.css'` ou ``.
+
+## 4. Dispatch Table
+
+Uso de mapa de funções para eliminar `if/else` ou `switch` longos:
+
+```ts
+// handlers.ts
+export const acaoHandlers: Record void> = {
+ salvar: () => { ... },
+ cancelar: () => { ... },
+ excluir: () => { ... },
+}
+```
+
+## Impacto
+
+Sem estes padrões, componentes perdem inferência de tipos, lógica fica inauditável em composables externos, e CSS cresce inline sem controle.
+
+## Referências
+
+- Rule: `.agent/rules/limite-linhas.global.md`
+- Rule: `.agent/rules/typescript-sem-any.global.md`
diff --git a/.agent/rules/fronteiras-monorepo.global.md b/.agent/rules/fronteiras-monorepo.global.md
new file mode 100644
index 0000000..c041716
--- /dev/null
+++ b/.agent/rules/fronteiras-monorepo.global.md
@@ -0,0 +1,120 @@
+---
+type: rule
+name: fronteiras-monorepo
+description: Define a convenção de pacotes e regras de importação entre módulos do monorepo
+scope: global
+trigger: always_on
+updatedAt: "2026-06-01"
+---
+
+# Regra: Fronteiras de Pacote no Monorepo
+
+## Contexto
+
+Cada módulo SaaS do ecossistema é organizado em até 4 pacotes dentro de um monorepo. O agente **deve** respeitar as fronteiras de importação entre pacotes para evitar acoplamento indevido.
+
+## Convenção de Pacotes
+
+Cada módulo segue a estrutura:
+
+```
+-back ← Servidor: rotas, serviços, filas, CRON
+-front ← Interface: páginas Vue, componentes, composables
+-comuns ← Compartilhado: tipos, schemas Zod, enums, utils puros
+-migracao ← Banco de dados: migrations, seeds, scripts de carga
+```
+
+Nem todo módulo terá os 4 pacotes. O mínimo funcional é `back` + `front` + `comuns`.
+
+## Regras de Importação
+
+### Permitido ✅
+
+```
+-front → pode importar de → -comuns
+-back → pode importar de → -comuns
+-migracao → pode importar de → -comuns
+```
+
+### Proibido ❌
+
+```
+-front ✗ nunca importar de → -back
+-back ✗ nunca importar de → -front
+-migracao ✗ nunca importar de → -front
+-front ✗ nunca importar de → -back
+ ✗ nunca importar de → -back (direto)
+```
+
+### Comunicação entre módulos
+
+Módulos diferentes **nunca** importam código um do outro diretamente. Comunicação entre módulos deve ocorrer via:
+
+- **API HTTP** (chamada de serviço entre backs)
+- **Fila** (BullMQ, Redis) para processamento assíncrono
+- **Banco compartilhado** com schemas/tabelas separados por módulo
+- **Pacote `*-comuns` compartilhado** apenas para tipos e schemas de contrato
+
+## O que vive em cada pacote
+
+### `-comuns`
+
+O pacote comuns é a **única fonte de verdade** para tipos e schemas compartilhados:
+
+```
+-comuns/src/
+├── tabelas/ ← schemas Zod por entidade (zEntidadeCriar, etc.)
+├── enums/ ← enums de domínio do módulo
+├── tipos/ ← tipos derivados dos schemas
+└── utils/ ← funções puras sem side-effect
+```
+
+**Proibido** no comuns:
+- Componentes Vue ou React
+- Código com `import` de Node.js (`fs`, `path`, `http`)
+- Código com `import` de bibliotecas de frontend (`vue`, `vuetify`)
+- Lógica de negócio que depende de banco ou serviço externo
+
+### `-back`
+
+```
+-back/src/
+├── rotas/ ← endpoints Express/Fastify
+├── servicos/ ← lógica de negócio
+├── banco/ ← queries (Knex, Prisma, etc.)
+├── filas/ ← processadores de fila
+├── cron/ ← tarefas agendadas
+└── utils/ ← helpers exclusivos do back
+```
+
+### `-front`
+
+```
+-front/src/
+├── paginas/ ← páginas/views por rota
+├── componentes/ ← componentes reutilizáveis do módulo
+├── composables/ ← lógica reutilizável (use*.ts)
+└── utils/ ← helpers exclusivos do front
+```
+
+### `-migracao`
+
+```
+-migracao/src/
+├── migrations/ ← criação/alteração de tabelas
+├── seeds/ ← dados iniciais
+└── scripts/ ← scripts de carga/transformação
+```
+
+## Impacto
+
+Sem esta regra:
+- Front importa serviço de back (quebra em build de produção)
+- Módulo A acessa tabelas do Módulo B diretamente (acoplamento oculto)
+- Schemas ficam duplicados entre back e front
+- Mudança em um módulo quebra outro sem aviso
+
+## Referências
+
+- Rule: `.agent/rules/zod-validacao-padrao.global.md` (schemas no `*-comuns`)
+- Skill: `.agent/skills/mapear-codigo-fonte.global/SKILL.md`
diff --git a/.agent/rules/jsdoc-padrao.global.md b/.agent/rules/jsdoc-padrao.global.md
new file mode 100644
index 0000000..d1833d3
--- /dev/null
+++ b/.agent/rules/jsdoc-padrao.global.md
@@ -0,0 +1,59 @@
+---
+type: rule
+name: jsdoc-padrao
+description: Padrões de documentação em código (JSDoc, casts temporários e TODOs estruturados)
+scope: global
+trigger: file_match
+globs:
+ - "**/*.ts"
+ - "**/*.tsx"
+ - "**/*.vue"
+updatedAt: "2026-06-01"
+---
+
+# Regra: Documentação em Código
+
+## 1. JSDoc — Princípios
+
+- **Por que, não o Quê**: documente o raciocínio, não o mecanismo que o TypeScript já expressa.
+- **Nomenclatura limpa**: confie em nomes expressivos antes de comentários.
+
+## 2. JSDoc obrigatório em funções compartilhadas
+
+Toda função central ou de uso compartilhado deve ter cabeçalho JSDoc:
+
+```typescript
+/**
+ * Busca detalhes da entidade e calcula privilégios aplicáveis.
+ * Não utilizar em processamento em lote para evitar sobrecarga.
+ *
+ * @param tenantId - Identificador do tenant no sistema
+ * @param entityId - ID do registro na base de dados
+ * @returns Objeto consolidado ou `null` se não encontrado
+ */
+export const obterDetalhes = async (tenantId: string, entityId: string) => { ... }
+```
+
+## 3. Casts temporários — justificativa obrigatória
+
+Coerções agressivas requerem comentário explicando o porquê:
+
+```typescript
+// Necessário: biblioteca externa não provê tipagem genérica na versão atual
+const dados = payload.raw as unknown as RespostaFormatada
+```
+
+## 4. TODOs estruturados
+
+Dívida técnica ou dúvida de negócio deve seguir formato padronizado:
+
+- **Tipagem**: `// TODO de tipagem: Criar interface para o retorno da API`
+- **Negócio**: `// TODO de regra: Validar com Produto se taxa se aplica a clientes antigos`
+
+## Impacto
+
+Sem este padrão, funções compartilhadas ficam sem documentação, casts aparecem sem justificativa, e TODOs são inconsistentes e difíceis de buscar.
+
+## Referências
+
+- Rule: `.agent/rules/typescript-sem-any.global.md`
diff --git a/.agent/rules/limite-linhas.global.md b/.agent/rules/limite-linhas.global.md
new file mode 100644
index 0000000..f866abd
--- /dev/null
+++ b/.agent/rules/limite-linhas.global.md
@@ -0,0 +1,66 @@
+---
+type: rule
+name: limite-linhas
+description: Nenhum arquivo de código pode ultrapassar 250 linhas
+scope: global
+trigger: file_match
+globs:
+ - "**/*.ts"
+ - "**/*.tsx"
+ - "**/*.vue"
+ - "**/*.css"
+updatedAt: "2026-06-01"
+---
+
+# Regra: Limite de 250 Linhas por Arquivo
+
+## Regra
+
+Nenhum arquivo `*.vue`, `*.ts`, `*.tsx` ou `*.css` pode ultrapassar **250 linhas**.
+
+> Validação automática: `.agent/verificar-linhas.sh`
+
+## Estratégias de Decomposição
+
+| Situação | Ação |
+|---|---|
+| Template longo | Extrair seções como subcomponentes |
+| CSS extenso | Mover para `.css` co-localizado |
+| `setup()` extenso | Extrair subcomponentes, CSS ou Dispatch Table |
+| Modal/Dialog embutido | Sempre em componente separado (`*Modal.vue`) |
+| Handlers e dispatch | Extrair para `handlers.ts` ou `dispatch.ts` |
+
+## Divisão em Pasta
+
+Quando um componente precisa de múltiplos sub-arquivos, converter em **pasta homônima** com `index.vue`:
+
+```
+# Antes
+ComponenteGrande.vue ← monolítico (+250 linhas)
+
+# Depois
+ComponenteGrande/
+├── index.vue ← orquestração (≤250 linhas)
+├── SecaoPrincipal.vue ← bloco de template extraído
+├── SecaoSecundaria.vue ← outro bloco extraído
+├── componenteGrande.mapeadores.ts ← lógica de eventos
+└── ComponenteGrande.css ← estilos co-localizados
+```
+
+| Arquivo | Responsabilidade |
+|---|---|
+| `index.vue` | Orquestração: importa e compõe. Sem lógica densa. |
+| `Secao*.vue` | Template isolado. Props + eventos. |
+| `*.mapeadores.ts` | Funções de ação desacopladas. |
+| `*.css` | Estilos co-localizados. |
+
+> Importações externas não mudam: `ComponenteGrande/` resolve para `index.vue`.
+
+## Impacto
+
+Sem esta regra, arquivos crescem silenciosamente até se tornarem intocáveis — ninguém quer revisar ou refatorar um componente de 800 linhas.
+
+## Referências
+
+- Script: `.agent/verificar-linhas.sh`
+- Workflow: `.agent/workflows/otimizar-codigos.global.md`
diff --git a/.agent/rules/mensagens-usuario.global.md b/.agent/rules/mensagens-usuario.global.md
new file mode 100644
index 0000000..2085e92
--- /dev/null
+++ b/.agent/rules/mensagens-usuario.global.md
@@ -0,0 +1,123 @@
+---
+type: rule
+name: mensagens-usuario
+description: Toda mensagem visível ao usuário final deve ser escrita em linguagem do domínio, sem termos técnicos de TI
+scope: global
+trigger: file_match
+globs:
+ - "**/*.vue"
+ - "**/*.ts"
+ - "**/*.tsx"
+updatedAt: "2026-06-01"
+---
+
+# Regra: Mensagens Orientadas ao Domínio
+
+## Contexto
+
+O usuário final dos módulos SaaS é profissional de **engenharia, ciência ou gestão ambiental**. Ele entende de legislação, processos técnicos e ciência aplicada — mas **não tem obrigação de entender tecnologia**.
+
+Toda mensagem visível na interface — erro, validação, confirmação, tooltip, empty state, ajuda — deve ser escrita na linguagem do domínio do módulo.
+
+## Regra
+
+### 1. Erros de validação devem indicar O QUE preencher, não O QUE falhou
+
+```typescript
+// ❌ ERRADO — linguagem técnica
+"Campo obrigatório"
+"Erro de validação"
+"Formato inválido"
+"Payload inválido"
+
+// ✅ CORRETO — linguagem do domínio
+"Informe a Área do Empreendimento (em hectares)"
+"Selecione o Tipo de Licença: LP, LI ou LO"
+"A Data da Vistoria deve ser anterior à data atual"
+"Informe o CNPJ no formato 00.000.000/0000-00"
+```
+
+### 2. Erros de sistema devem ser traduzidos para o usuário
+
+```typescript
+// ❌ ERRADO — erro técnico exposto
+"Erro 500: Internal Server Error"
+"Timeout na requisição"
+"Network Error"
+"ECONNREFUSED"
+
+// ✅ CORRETO — mensagem humanizada
+"Não foi possível salvar. Tente novamente em instantes."
+"A operação demorou mais que o esperado. Tente novamente."
+"Sem conexão com o servidor. Verifique sua internet."
+```
+
+### 3. Estados vazios devem explicar o contexto
+
+Quando uma lista ou tabela está vazia, a mensagem deve orientar o usuário:
+
+```typescript
+// ❌ ERRADO — genérico
+"Nenhum registro encontrado"
+"Lista vazia"
+
+// ✅ CORRETO — explica o domínio e orienta
+"Nenhuma condicionante cadastrada para esta licença."
+"Nenhum relatório de monitoramento registrado neste período."
+"Nenhum empreendimento encontrado com os filtros aplicados. Tente ampliar a busca."
+```
+
+### 4. Confirmações devem descrever a consequência
+
+```typescript
+// ❌ ERRADO — genérico
+"Tem certeza que deseja excluir?"
+"Confirmar operação?"
+
+// ✅ CORRETO — descreve o impacto
+"Excluir esta condicionante? O prazo de cumprimento e os documentos vinculados serão removidos."
+"Arquivar esta licença? Ela não aparecerá mais nas listagens ativas."
+```
+
+### 5. Labels e placeholders devem usar vocabulário do domínio
+
+```typescript
+// ❌ ERRADO — genérico
+label: "Nome"
+placeholder: "Digite aqui"
+
+// ✅ CORRETO — específico do domínio
+label: "Razão Social do Empreendedor"
+placeholder: "Ex: Mineração Vale Verde Ltda."
+
+label: "Área Total do Empreendimento"
+placeholder: "Em hectares (ex: 150,5)"
+```
+
+### 6. Schemas Zod devem ter mensagens de domínio
+
+```typescript
+// ❌ ERRADO — mensagens padrão do Zod
+const zEmpreendimento = z.object({
+ nome: z.string().min(1), // "String must contain at least 1 character(s)"
+ area: z.number().positive(), // "Number must be greater than 0"
+})
+
+// ✅ CORRETO — mensagens customizadas
+const zEmpreendimento = z.object({
+ nome: z.string().min(1, "Informe o nome do empreendimento"),
+ area: z.number({ invalid_type_error: "A área deve ser um número" })
+ .positive("A área deve ser maior que zero"),
+ cnpj: z.string().regex(/^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$/,
+ "CNPJ inválido. Use o formato 00.000.000/0000-00"),
+})
+```
+
+## Impacto
+
+Sem esta regra, o agente gera mensagens técnicas que confundem gestores ambientais, consultores e técnicos de campo — forçando a equipe de suporte a traduzir erros de sistema para o vocabulário do cliente.
+
+## Referências
+
+- Rule: `.agent/rules/zod-validacao-padrao.global.md` (schemas com mensagens customizadas)
+- Workflow: `.agent/workflows/ux.global.md` (design de interface)
diff --git a/.agent/rules/regras-negocio-md.global.md b/.agent/rules/regras-negocio-md.global.md
new file mode 100644
index 0000000..7b04df9
--- /dev/null
+++ b/.agent/rules/regras-negocio-md.global.md
@@ -0,0 +1,120 @@
+---
+type: rule
+name: regras-negocio-md
+description: Arquivo {modulo}.regras-negocio.md obrigatório para módulos com lógica complexa
+scope: global
+trigger: always_on
+updatedAt: "2026-06-01"
+---
+
+# Regra: Arquivo `{modulo}.regras-negocio.md` Obrigatório para Módulos com Lógica Complexa
+
+> **Escopo global** — aplica-se a todos os projetos do ecossistema Azteca.
+
+## Contexto
+
+Qualquer arquivo TypeScript que contenha lógica de negócio não-trivial — múltiplas regras de decisão, critérios de bloqueio, estratégias de processamento — deve ter suas regras documentadas em linguagem natural em um arquivo Markdown co-localizado.
+
+Isso garante que produto, suporte e desenvolvimento possam entender as regras sem precisar ler TypeScript, e que o agente de IA encontre rapidamente os critérios corretos antes de alterar qualquer código.
+
+## Regra
+
+**Todo módulo com lógica de negócio não-trivial DEVE ter um arquivo `{modulo}.regras-negocio.md` co-localizado.**
+
+Considera-se lógica não-trivial qualquer módulo que:
+- possui 3 ou mais critérios de decisão distintos
+- realiza eleição/priorização entre registros
+- aplica diferentes estratégias por tipo de dado
+- pode resultar em revisão manual (caminho alternativo ao fluxo automático)
+- é consumido por CRON, worker ou importador automático
+
+O módulo TypeScript deve conter, na primeira linha do arquivo, a referência:
+
+```typescript
+// Regras de negócio: ver {modulo}.regras-negocio.md
+```
+
+O arquivo `.md` é a **fonte de verdade**. Os JSDoc do código devem referenciar o `.md`, não duplicá-lo.
+
+## Estrutura Obrigatória do Arquivo
+
+```markdown
+# Regras de Negócio — [Nome do Módulo]
+
+Módulo: `_arquivo.ts`
+Localização: `caminho/relativo/ao/arquivo`
+
+---
+
+## Contexto
+Por que este módulo existe, qual problema resolve.
+
+## Critérios de Entrada / Identificação
+O que aciona o módulo, quais dados recebe, filtros aplicados.
+
+## Regras de Processamento
+As regras numeradas em ordem de avaliação.
+
+## Casos de Revisão Manual (quando aplicável)
+Quando o processamento automático não pode prosseguir.
+
+## Estratégia de Fusão / Resultado (quando aplicável)
+O que o módulo produz, como campos são combinados.
+
+## Dependências Externas
+Tabelas, filas, serviços externos tocados pelo módulo.
+```
+
+## Exemplos
+
+✅ **Correto** — referência no topo do módulo, regras no `.md`:
+```typescript
+// Regras de negócio: ver juntar_execucoes.regras-negocio.md
+
+import { ... } from "..."
+
+export const compilarJuntarExecucoes = async (...) => {
+ // implementação conforme regras-negocio.md
+}
+```
+
+❌ **Errado** — regras embutidas no código como comentário de bloco:
+```typescript
+/**
+ * Regras de negócio:
+ * [x] Pode ter apenas uma execução por data de vencimento.
+ * [x] Se tiver apenas uma execução consolidada → ela é a principal.
+ * ...
+ */
+```
+
+❌ **Errado** — JSDoc duplicando as regras em vez de referenciar o `.md`:
+```typescript
+/**
+ * Retorna false quando:
+ * - vencimentos divergentes
+ * - executada com datas distintas
+ * - vencimento_prorrogado distintos
+ * ...
+ */
+```
+
+## Impacto
+
+Sem este padrão:
+- Regras de negócio ficam implícitas no código ou em JSDoc que envelhece sem controle
+- O agente de IA precisa inferir regras do código ao invés de lê-las diretamente
+- Produto e suporte não conseguem validar o comportamento sem ajuda técnica
+- Mudanças de regra geram JSDoc desatualizado espalhado pelo código
+
+## Aplicação
+
+- Módulos de CRON com lógica de auditoria/fusão
+- Importadores automáticos (ex: importação de licenças via IA)
+- Workers de processamento em lote
+- Qualquer utilitário com lógica de decisão multi-critério
+
+## Referências
+
+- Skill: `.agent/skills/documentar-regras-negocio.global/SKILL.md`
+- Workflow: `.agent/workflows/documentar-regras-negocio.global.md`
diff --git a/.agent/rules/typescript-sem-any.global.md b/.agent/rules/typescript-sem-any.global.md
index 0d12388..1fbd2e5 100644
--- a/.agent/rules/typescript-sem-any.global.md
+++ b/.agent/rules/typescript-sem-any.global.md
@@ -1,6 +1,10 @@
---
+type: rule
+name: typescript-sem-any
trigger: always_on
description: Proíbe uso de any, casts inseguros e padroniza uso de enums em código TypeScript
+scope: global
+updatedAt: "2026-06-01"
globs:
- "**/*.ts"
- "**/*.tsx"
@@ -68,6 +72,6 @@ Se encontrar uma string que represente um valor de domínio do sistema, o agente
## Referências
-- Skill: `.agent/skills/diagnosticar-erros-build/SKILL.md` (quando erros de tipagem causam falhas de build em cascata)
-- Workflow: `.agent/workflows/documentar-codigo.global.md` (padrão de documentação para casts temporários)
+- Skill: `.agent/skills/diagnosticar-erros-build.global/SKILL.md` (quando erros de tipagem causam falhas de build em cascata)
+- Workflow: `.agent/workflows/otimizar-codigos.global.md` (padrão de documentação para casts temporários)
diff --git a/.agent/rules/zod-validacao-padrao.global.md b/.agent/rules/zod-validacao-padrao.global.md
new file mode 100644
index 0000000..f62ee21
--- /dev/null
+++ b/.agent/rules/zod-validacao-padrao.global.md
@@ -0,0 +1,315 @@
+---
+type: rule
+name: zod-validacao-padrao
+description: Estabelece Zod como biblioteca obrigatória de validação em todo o ecossistema Azteca
+version: "1.0"
+scope: global
+priority: alta
+trigger: always_on
+globs:
+ - "**/*.ts"
+ - "**/*.tsx"
+ - "**/*.vue"
+alwaysApply: true
+updatedAt: "2026-06-01"
+references:
+ - .agent/rules/typescript-sem-any.global.md
+ - .agent/skills/criar-schema-zod.global/SKILL.md
+---
+
+# Regra: Zod como Validador Padrão
+
+## Contexto
+
+Todo projeto do ecossistema Azteca que receba dados externos — API, formulário, webhook, importação, fila, variáveis de ambiente — **deve** validar esses dados com [Zod](https://zod.dev).
+
+Zod é a única biblioteca de validação autorizada porque:
+
+- Inferência nativa de tipos TypeScript (`z.infer<>`) — elimina duplicação entre schema e tipo
+- Bundle pequeno (~13KB gzipped) — viável no frontend
+- Compartilhável entre back e front via pacotes `*-comuns`
+- Ecossistema maduro: `zod-to-json-schema`, `@anatine/zod-nestjs`, integração com `tRPC`
+
+---
+
+## Regra
+
+### 1. Toda entrada externa deve ser validada com Zod
+
+Entradas externas incluem:
+
+- **Corpo de requisição** (API REST, webhook, GraphQL)
+- **Query params e path params**
+- **Formulários de frontend**
+- **Dados de fila** (BullMQ, Redis, etc.)
+- **Arquivos importados** (CSV, JSON, Excel)
+- **Variáveis de ambiente**
+- **Respostas de APIs externas** (quando a resposta não é confiável)
+
+### 2. Schemas devem residir no pacote `*-comuns`
+
+Para que front e back compartilhem os mesmos schemas, eles devem residir no pacote `-comuns`:
+
+```
+-comuns/src/
+├── tabelas/
+│ ├── usuarios/
+│ │ ├── zUsuarioCriar.ts
+│ │ ├── zUsuarioEditar.ts
+│ │ └── index.ts
+│ ├── empresas/
+│ │ ├── zEmpresaCriar.ts
+│ │ └── index.ts
+│ └── index.ts
+```
+
+### 3. Tipos devem ser derivados dos schemas (nunca o inverso)
+
+```typescript
+// ✅ CORRETO — tipo derivado do schema
+export const zUsuarioCriar = z.object({
+ nome: z.string().min(1),
+ email: z.string().email(),
+ ativo: z.boolean().default(true),
+})
+export type tipoUsuarioCriar = z.infer
+
+// ❌ ERRADO — tipo manual separado do schema
+interface UsuarioCriar {
+ nome: string
+ email: string
+ ativo?: boolean
+}
+const zUsuarioCriar = z.object({ ... }) // duplicação!
+```
+
+### 4. Nomenclatura obrigatória de schemas
+
+| Padrão | Uso | Exemplo |
+|--------|-----|---------|
+| `z{Entidade}Criar` | Criação de registro | `zUsuarioCriar` |
+| `z{Entidade}Editar` | Edição de registro | `zUsuarioEditar` |
+| `z{Entidade}Filtro` | Filtros de listagem | `zUsuarioFiltro` |
+| `z{Entidade}Resposta` | Resposta de API | `zUsuarioResposta` |
+| `z{Entidade}Params` | Parâmetros de rota | `zUsuarioParams` |
+| `z{Modulo}Config` | Configuração do módulo | `zGeeConfig` |
+| `z{Modulo}Ambiente` | Variáveis de ambiente | `zBackAmbiente` |
+
+Prefixo `z` minúsculo + nome em `PascalCase`.
+
+### 5. Usar `safeParse` em vez de `parse` para erros controlados
+
+```typescript
+// ✅ CORRETO — erro controlado
+const resultado = zUsuarioCriar.safeParse(dados)
+if (!resultado.success) {
+ return respostaComuns.erro(resultado.error.issues[0].message)
+}
+const usuario = resultado.data // tipado automaticamente
+
+// ❌ ERRADO — throw não controlado
+const usuario = zUsuarioCriar.parse(dados) // lança ZodError
+```
+
+A exceção é em contextos onde o throw é desejável (middleware global, início de pipeline).
+
+### 6. Nunca usar validação manual quando Zod resolve
+
+```typescript
+// ❌ ERRADO — validação manual
+if (!dados.nome || typeof dados.nome !== "string") {
+ return erro("Nome é obrigatório")
+}
+if (!dados.email || !dados.email.includes("@")) {
+ return erro("Email inválido")
+}
+
+// ✅ CORRETO — Zod faz tudo
+const resultado = zUsuarioCriar.safeParse(dados)
+if (!resultado.success) {
+ return erro(resultado.error.issues[0].message)
+}
+```
+
+### 7. Transformações e defaults pertencem ao schema
+
+```typescript
+// ✅ CORRETO — transformação no schema
+export const zProdutoCriar = z.object({
+ nome: z.string().trim().min(1),
+ preco: z.coerce.number().positive(),
+ ativo: z.boolean().default(true),
+ codigo: z.string().toUpperCase().optional(),
+ tags: z.array(z.string()).default([]),
+})
+
+// ❌ ERRADO — transformação manual após validação
+const produto = zProdutoCriar.parse(dados)
+produto.nome = produto.nome.trim()
+produto.codigo = produto.codigo?.toUpperCase()
+```
+
+### 8. Composição de schemas em vez de duplicação
+
+```typescript
+// ✅ CORRETO — composição
+const zEndereco = z.object({
+ rua: z.string(),
+ cidade: z.string(),
+ uf: z.string().length(2),
+})
+
+const zEmpresaCriar = z.object({
+ nome: z.string(),
+ endereco: zEndereco,
+})
+
+const zEmpresaEditar = zEmpresaCriar.partial() // todos opcionais
+
+const zEmpresaResposta = zEmpresaCriar.extend({
+ codigo: z.string().uuid(),
+ criadoEm: z.string().datetime(),
+})
+
+// ❌ ERRADO — copiar campos entre schemas
+const zEmpresaEditar = z.object({
+ nome: z.string().optional(), // duplicou!
+ endereco: z.object({ // duplicou!
+ rua: z.string().optional(),
+ cidade: z.string().optional(),
+ }),
+})
+```
+
+### 9. Enums Zod para valores de domínio
+
+```typescript
+// ✅ CORRETO — enum Zod quando os valores vêm de dados externos
+export const zStatusLicenca = z.enum(["ativa", "vencida", "suspensa", "cancelada"])
+export type tipoStatusLicenca = z.infer
+
+// Usar em outros schemas
+const zLicencaCriar = z.object({
+ status: zStatusLicenca,
+ // ...
+})
+
+// ✅ TAMBÉM CORRETO — TypeScript enum quando já existe no projeto
+// e o schema referencia o enum
+import { StatusLicenca } from "./enums"
+const zLicencaCriar = z.object({
+ status: z.nativeEnum(StatusLicenca),
+})
+```
+
+### 10. Bibliotecas proibidas para validação
+
+| ❌ Proibido | Motivo |
+|-------------|--------|
+| `joi` | Não infere tipos TS, bundle pesado |
+| `yup` | Inferência fraca, API menos ergonômica |
+| `class-validator` | Decorators, não compartilhável com frontend |
+| `io-ts` | Verboso, curva de aprendizado alta |
+| `ajv` (direto) | JSON Schema — usar `zod-to-json-schema` se precisar de JSON Schema |
+| Validação manual com `if/typeof` | Quando Zod resolve o mesmo caso |
+
+---
+
+## Exemplos
+
+### API backend (Express)
+
+```typescript
+// ✅ Correto
+import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
+
+app.post("/usuarios", async (req, res) => {
+ const resultado = zUsuarioCriar.safeParse(req.body)
+ if (!resultado.success) {
+ return res.status(400).json({ erro: resultado.error.issues })
+ }
+ const usuario = await criarUsuario(resultado.data)
+ return res.json(usuario)
+})
+```
+
+### Webhook com fila
+
+```typescript
+// ✅ Correto — valida antes de enfileirar
+const zPayloadWebhook = z.object({
+ tabela: z.enum(["empreendedores", "empreendimentos", "usuarios"]),
+ dados: z.array(z.unknown()).min(1),
+})
+
+app.post("/webhook/:tabela", async (req, res) => {
+ const resultado = zPayloadWebhook.safeParse({
+ tabela: req.params.tabela,
+ dados: req.body,
+ })
+ if (!resultado.success) {
+ return res.status(400).json({ erro: "Payload inválido" })
+ }
+ await fila.add("sync", resultado.data)
+ return res.json({ ok: true })
+})
+```
+
+### Variáveis de ambiente
+
+```typescript
+// ✅ Correto — validar no boot da aplicação
+const zAmbiente = z.object({
+ DATABASE_URL: z.string().url(),
+ REDIS_HOST: z.string().default("localhost"),
+ REDIS_PORT: z.coerce.number().default(6379),
+ BASE_URL: z.string().url(),
+ SESSAO_BACK: z.string().uuid(),
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
+})
+
+export const ambiente = () => zAmbiente.parse(process.env)
+```
+
+### Frontend (formulário Vue)
+
+```typescript
+// ✅ Correto — schema compartilhado validando antes de enviar
+import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
+
+const salvar = async () => {
+ const resultado = zUsuarioCriar.safeParse(formulario.value)
+ if (!resultado.success) {
+ erros.value = resultado.error.flatten().fieldErrors
+ return
+ }
+ await api.post("/usuarios", resultado.data)
+}
+```
+
+---
+
+## Impacto
+
+Sem esta regra:
+
+- Cada desenvolvedor escolhe uma estratégia diferente de validação (if/typeof, joi, yup, etc.)
+- Tipos e schemas ficam dessincronizados — mudança no schema não reflete no tipo e vice-versa
+- Validações manuais são incompletas e inconsistentes entre endpoints
+- Código de validação é duplicado entre front e back
+- Dados inválidos passam despercebidos e corrompem a base
+
+Com esta regra:
+
+- Uma única fonte de verdade: o schema Zod
+- Tipos TypeScript sempre sincronizados via `z.infer<>`
+- Validação consistente em todos os pontos de entrada
+- Reutilização entre front, back e workers via pacote `*-comuns`
+- Erros de validação estruturados e internacionalizáveis
+
+---
+
+## Referências
+
+- Rule: `.agent/rules/typescript-sem-any.global.md` (narrowing com Zod mencionado como alternativa)
+- Skill: `.agent/skills/criar-schema-zod.global/SKILL.md` (guia prático para criar schemas)
diff --git a/.agent/skills/criar-schema-zod.global/SKILL.md b/.agent/skills/criar-schema-zod.global/SKILL.md
new file mode 100644
index 0000000..92e2d4b
--- /dev/null
+++ b/.agent/skills/criar-schema-zod.global/SKILL.md
@@ -0,0 +1,184 @@
+---
+name: criar-schema-zod
+scope: global
+description: >
+ Guia operacional para criar, organizar e manter schemas de validação Zod em projetos TypeScript.
+---
+
+# Skill: Criar Schema Zod
+
+Use esta skill ao adicionar validação de dados em qualquer ponto de entrada do sistema — API, formulário, webhook, importação, fila ou variáveis de ambiente.
+
+---
+
+## PASSO 1 — Identificar a Entrada Externa
+
+Determinar qual dado externo precisa de validação:
+
+| Tipo de entrada | Onde validar |
+|---|---|
+| Corpo de requisição (POST/PUT) | Handler da rota, antes de qualquer lógica |
+| Query/path params | Handler da rota |
+| Formulário frontend | Antes de chamar a API |
+| Dados de fila (BullMQ) | No processador da fila |
+| Arquivo importado (CSV/JSON) | No parser, linha a linha |
+| Variáveis de ambiente | No boot da aplicação |
+| Resposta de API externa | Após receber a resposta |
+
+---
+
+## PASSO 2 — Localizar ou Criar o Schema no Pacote Comuns
+
+Schemas compartilhados vivem no pacote `*-comuns`:
+
+```
+projeto-comuns/src/tabelas/
+├── usuarios/
+│ ├── zUsuarioCriar.ts
+│ ├── zUsuarioEditar.ts
+│ └── index.ts
+└── index.ts
+```
+
+### 2.1. Verificar se já existe
+
+```bash
+rg "z{NomeDaEntidade}" --include='*.ts' -l
+```
+
+Se já existir → reutilizar ou compor a partir dele (`.extend()`, `.pick()`, `.partial()`).
+
+### 2.2. Criar o schema
+
+```typescript
+import { z } from "zod"
+
+export const zUsuarioCriar = z.object({
+ nome: z.string().trim().min(1, "Nome é obrigatório"),
+ email: z.string().email("E-mail inválido"),
+ ativo: z.boolean().default(true),
+})
+```
+
+### 2.3. Nomenclatura obrigatória
+
+| Padrão | Uso |
+|---|---|
+| `z{Entidade}Criar` | Criação |
+| `z{Entidade}Editar` | Edição (geralmente `.partial()`) |
+| `z{Entidade}Filtro` | Filtros de listagem |
+| `z{Entidade}Resposta` | Resposta de API |
+| `z{Entidade}Params` | Parâmetros de rota |
+| `z{Modulo}Config` | Configuração |
+| `z{Modulo}Ambiente` | Variáveis de ambiente |
+
+---
+
+## PASSO 3 — Derivar o Tipo com `z.infer<>`
+
+```typescript
+export type tipoUsuarioCriar = z.infer
+```
+
+**Nunca** criar um `interface` ou `type` manual separado do schema. O tipo **sempre** deriva do schema.
+
+---
+
+## PASSO 4 — Implementar `safeParse` no Ponto de Entrada
+
+### Backend (Express)
+
+```typescript
+import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
+
+app.post("/usuarios", async (req, res) => {
+ const resultado = zUsuarioCriar.safeParse(req.body)
+ if (!resultado.success) {
+ return res.status(400).json({ erro: resultado.error.issues[0].message })
+ }
+ const usuario = await criarUsuario(resultado.data)
+ return res.json(usuario)
+})
+```
+
+### Frontend (Vue)
+
+```typescript
+import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
+
+const salvar = async () => {
+ const resultado = zUsuarioCriar.safeParse(formulario.value)
+ if (!resultado.success) {
+ erros.value = resultado.error.flatten().fieldErrors
+ return
+ }
+ await api.post("/usuarios", resultado.data)
+}
+```
+
+### Variáveis de ambiente
+
+```typescript
+const zAmbiente = z.object({
+ DATABASE_URL: z.string().url(),
+ REDIS_HOST: z.string().default("localhost"),
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
+})
+
+// Aqui o throw é desejável — a app não deve subir com env inválido
+export const ambiente = () => zAmbiente.parse(process.env)
+```
+
+---
+
+## PASSO 5 — Compor Schemas Derivados
+
+Quando precisar de variações do mesmo schema, **compor** em vez de duplicar:
+
+```typescript
+// Schema base
+const zProduto = z.object({
+ nome: z.string().trim().min(1),
+ preco: z.coerce.number().positive(),
+ categoria: z.string(),
+})
+
+// Edição: todos os campos opcionais
+const zProdutoEditar = zProduto.partial()
+
+// Resposta: base + campos gerados
+const zProdutoResposta = zProduto.extend({
+ codigo: z.string().uuid(),
+ criadoEm: z.string().datetime(),
+})
+
+// Filtro: subset de campos opcionais
+const zProdutoFiltro = zProduto.pick({ categoria: true }).partial()
+```
+
+---
+
+## PASSO 6 — Validar Tipagem
+
+```bash
+npx tsc --noEmit
+```
+
+Zero erros = concluído.
+
+---
+
+## Checklist Final
+
+- [ ] O schema está no pacote `*-comuns` (quando compartilhado entre front/back)?
+- [ ] O tipo é derivado do schema com `z.infer<>`?
+- [ ] A validação usa `safeParse` (exceto em boot/middleware)?
+- [ ] A nomenclatura segue o padrão `z{Entidade}{Acao}`?
+- [ ] Transformações (trim, coerce, default) estão no schema, não no handler?
+- [ ] Schemas derivados usam composição (.partial, .extend, .pick), não duplicação?
+- [ ] `tsc --noEmit` passa sem erros?
+
+## Referências
+
+- Rule: `.agent/rules/zod-validacao-padrao.global.md`
+- Rule: `.agent/rules/typescript-sem-any.global.md`
diff --git a/.agent/skills/diagnosticar-erros-build.global/SKILL.md b/.agent/skills/diagnosticar-erros-build.global/SKILL.md
index f4d0131..50e6769 100644
--- a/.agent/skills/diagnosticar-erros-build.global/SKILL.md
+++ b/.agent/skills/diagnosticar-erros-build.global/SKILL.md
@@ -1,6 +1,7 @@
---
name: diagnosticar-erros-build
scope: global
+updatedAt: "2026-06-01"
description: >
Guia operacional para diagnosticar e resolver erros de build em cascata em monorepos TypeScript.
---
@@ -181,5 +182,5 @@ pnpm install && pnpm build
## Referências
- Rules: `.agent/rules/typescript-sem-any.global.md`
-- Workflow: `.agent/workflows/documentar-codigo.global.md`
-- Skill: `.agent/skills/mapear-codigo-fonte/SKILL.md`
+- Workflow: `.agent/workflows/otimizar-codigos.global.md`
+- Skill: `.agent/skills/mapear-codigo-fonte.global/SKILL.md`
diff --git a/.agent/skills/documentar-decisao-tecnica.global/SKILL.md b/.agent/skills/documentar-decisao-tecnica.global/SKILL.md
index b97a5d2..4b5e175 100644
--- a/.agent/skills/documentar-decisao-tecnica.global/SKILL.md
+++ b/.agent/skills/documentar-decisao-tecnica.global/SKILL.md
@@ -1,6 +1,7 @@
---
name: documentar-decisao-tecnica
scope: global
+updatedAt: "2026-06-01"
description: >
Guia operacional para registrar decisões técnicas e arquiteturais como ADRs (Architecture Decision Records).
---
@@ -157,4 +158,4 @@ Antes de considerar completo:
## Referências
- Workflow: `.agent/workflows/aprimorar-IA.global.md` (seção de classificação de artefatos)
-- Workflow: `.agent/workflows/documentar-codigo.global.md`
+- Workflow: `.agent/workflows/otimizar-codigos.global.md`
diff --git a/.agent/skills/documentar-dominio-modulo.global/SKILL.md b/.agent/skills/documentar-dominio-modulo.global/SKILL.md
new file mode 100644
index 0000000..fe69bfe
--- /dev/null
+++ b/.agent/skills/documentar-dominio-modulo.global/SKILL.md
@@ -0,0 +1,131 @@
+---
+name: documentar-dominio-modulo
+scope: global
+updatedAt: "2026-06-01"
+description: >
+ Guia para documentar o domínio científico/técnico de cada módulo SaaS,
+ garantindo que o agente compreenda a terminologia e as regras do domínio
+ ao gerar código, texto e interface.
+---
+
+# Skill: Documentar Domínio de um Módulo
+
+Use esta skill ao iniciar um novo módulo SaaS ou ao perceber que o agente não conhece o vocabulário e as regras do domínio que o módulo aborda.
+
+Cada módulo opera em uma disciplina diferente da ciência/gestão ambiental (licenciamento, gases de efeito estufa, resíduos, projetos, etc.). O agente precisa de uma **base factual do domínio** para gerar labels, mensagens, regras de negócio e textos comerciais corretos.
+
+---
+
+## PASSO 1 — Identificar o Domínio do Módulo
+
+Determinar qual área da ciência/gestão o módulo aborda:
+
+| Exemplo de módulo | Domínio |
+|---|---|
+| Licenciamento | Licenciamento ambiental (LP, LI, LO, condicionantes) |
+| GEE | Gases de Efeito Estufa (inventário, emissões, fatores) |
+| Resíduos | Gestão de resíduos sólidos (PGRS, MTR, classificação) |
+| Outorga | Outorga de uso de recursos hídricos |
+| Flora/Fauna | Monitoramento de biodiversidade |
+| Projetos | Gestão de projetos ambientais e consultorias |
+
+---
+
+## PASSO 2 — Criar o Arquivo de Domínio
+
+Criar em `.agent/memoria/dominio-.md`:
+
+```markdown
+# Domínio:
+
+## Área de Conhecimento
+Breve descrição da disciplina (1-3 linhas).
+
+## Glossário do Módulo
+
+| Termo | Definição | Sigla |
+|---|---|---|
+| Licença Prévia | Autorização para planejamento do empreendimento | LP |
+| Licença de Instalação | Autorização para início das obras | LI |
+| ... | ... | ... |
+
+## Entidades Principais
+
+Lista das entidades de negócio do módulo com descrição e relações:
+
+- **Empreendimento**: unidade física onde ocorre a atividade potencialmente poluidora
+- **Licença**: autorização ambiental vinculada a um empreendimento
+- **Condicionante**: obrigação imposta pelo órgão ambiental na licença
+- ...
+
+## Legislação Aplicável
+
+| Norma | Descrição | Relevância |
+|---|---|---|
+| Lei 6.938/1981 | Política Nacional do Meio Ambiente | Base legal do licenciamento |
+| Resolução CONAMA 237/1997 | Regulamenta o licenciamento ambiental | Tipos de licença, prazos |
+| ... | ... | ... |
+
+## Regras de citação legal
+- Formato: "Resolução CONAMA nº 237/1997, art. 8º, inciso I"
+- Toda sigla na primeira ocorrência com nome por extenso
+- Legislação federal > estadual > municipal (hierarquia)
+
+## Fluxos do Domínio
+
+Descrever os processos-chave que o módulo implementa:
+
+1. Solicitação de licença → Análise técnica → Emissão → Monitoramento
+2. Cadastro de condicionante → Acompanhamento de prazo → Cumprimento
+
+## Vocabulário de Interface
+
+Termos que devem ser usados em labels, botões e mensagens:
+
+| Contexto | ❌ Genérico | ✅ Correto para o módulo |
+|---|---|---|
+| Campo de nome | "Nome" | "Razão Social do Empreendedor" |
+| Status | "Ativo/Inativo" | "Vigente/Vencida/Suspensa" |
+| Ação principal | "Criar novo" | "Solicitar nova licença" |
+| Lista vazia | "Nenhum registro" | "Nenhuma licença cadastrada" |
+```
+
+---
+
+## PASSO 3 — Validar com o Operador
+
+Antes de consolidar:
+
+- [ ] Os termos do glossário estão corretos para o contexto do módulo?
+- [ ] As entidades cobrem os conceitos centrais?
+- [ ] A legislação listada é a principal (não exaustiva)?
+- [ ] O vocabulário de interface reflete como o usuário fala no dia a dia?
+
+---
+
+## PASSO 4 — Conectar com as Rules
+
+Após documentar o domínio:
+
+1. Verificar se labels e mensagens no código usam o vocabulário definido (rule `mensagens-usuario.global.md`)
+2. Verificar se schemas Zod têm mensagens de erro no idioma do domínio (rule `zod-validacao-padrao.global.md`)
+3. Verificar se os arquivos `.ajuda.md` usam termos do glossário (workflow `adicionar-ajuda.global.md`)
+4. Verificar se textos comerciais citam a legislação corretamente (workflow `comercial.global.md`)
+
+---
+
+## Checklist Final
+
+- [ ] Arquivo `dominio-.md` criado em `.agent/memoria/`?
+- [ ] Glossário com pelo menos os 10 termos centrais do módulo?
+- [ ] Entidades principais listadas com descrição?
+- [ ] Legislação base documentada com formato de citação?
+- [ ] Vocabulário de interface definido (labels, status, ações, empty states)?
+- [ ] Operador validou a terminologia?
+
+## Referências
+
+- Rule: `.agent/rules/mensagens-usuario.global.md`
+- Rule: `.agent/rules/regras-negocio-md.global.md`
+- Workflow: `.agent/workflows/comercial.global.md`
+- Workflow: `.agent/workflows/adicionar-ajuda.global.md`
diff --git a/.agent/skills/documentar-regras-negocio.global/SKILL.md b/.agent/skills/documentar-regras-negocio.global/SKILL.md
index 83f5bf5..9f7924f 100644
--- a/.agent/skills/documentar-regras-negocio.global/SKILL.md
+++ b/.agent/skills/documentar-regras-negocio.global/SKILL.md
@@ -1,5 +1,7 @@
---
-name: documentar-regras-negocio.global
+name: documentar-regras-negocio
+scope: global
+updatedAt: "2026-06-01"
description: >
Guia operacional para criar, preencher e manter arquivos {modulo}.regras-negocio.md
co-localizados com módulos TypeScript de lógica de negócio complexa.
@@ -206,7 +208,5 @@ Percorrer cada regra listada no `.md` e verificar que há código correspondente
## Referências
-- Rule: `.agent/rules/regras_negocio_md.global.md`
+- Rule: `.agent/rules/regras-negocio-md.global.md`
- Workflow: `.agent/workflows/documentar-regras-negocio.global.md`
-- Skill: `.agent/skills/consultar-banco-knex/SKILL.md` (orderBy em whereIn)
-- ADR: `.agent/memoria/adr-003-crons-complexos-compilacao-execucao.md`
diff --git a/.agent/skills/mapear-codigo-fonte.global/SKILL.md b/.agent/skills/mapear-codigo-fonte.global/SKILL.md
index 01a5311..ebca7c7 100644
--- a/.agent/skills/mapear-codigo-fonte.global/SKILL.md
+++ b/.agent/skills/mapear-codigo-fonte.global/SKILL.md
@@ -1,6 +1,7 @@
---
name: mapear-codigo-fonte
scope: global
+updatedAt: "2026-06-01"
description: >
Guia operacional para mapear, navegar e compreender a estrutura de um monorepo TypeScript.
---
@@ -156,4 +157,4 @@ Descrever como os dados fluem entre pacotes.
## Referências
- Rules: `.agent/rules/typescript-sem-any.global.md`
-- Workflow: `.agent/workflows/documentar-codigo.global.md`
+- Workflow: `.agent/workflows/otimizar-codigos.global.md`
diff --git a/.agent/tools.md b/.agent/tools.md
index caf834e..cf568ac 100644
--- a/.agent/tools.md
+++ b/.agent/tools.md
@@ -98,7 +98,7 @@ Para instalar as ferramentas base de forma ágil e em lote, copie os blocos de c
```bash
# 1. Atualizar e instalar ferramentas base do sistema
sudo apt-get update
-sudo apt-get install -y ripgrep fd-find jq shellcheck bat tree httpie gh
+sudo apt-get install -y ripgrep fd-find jq shellcheck bat tree eza httpie gh
# 2. Instalar ferramentas em Node
npm install -g @ast-grep/cli @biomejs/biome
diff --git a/.agent/verificar-linhas.sh b/.agent/verificar-linhas.sh
new file mode 100644
index 0000000..6ebd840
--- /dev/null
+++ b/.agent/verificar-linhas.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env sh
+
+# comando sh ../.agent/verificar-linhas.sh || exit 1
+
+LIMITE=250
+
+RAIZ=$(cd "$(dirname "$0")/.." && pwd)
+
+RESULTADO=$(
+ find "$RAIZ" \
+ -type d \( \
+ -name node_modules -o \
+ -name .git -o \
+ -name dist -o \
+ -name build \
+ \) -prune \
+ -o \
+ -type f \( \
+ -name "*.ts" -o \
+ -name "*.tsx" -o \
+ -name "*.vue" -o \
+ -name "*.css" \
+ \) \
+ -print | while IFS= read -r arquivo; do
+ linhas=$(wc -l < "$arquivo" | tr -d ' ')
+ if [ "$linhas" -gt "$LIMITE" ]; then
+ relativo="${arquivo#$RAIZ/}"
+ printf "%5s linhas -> %s\n" "$linhas" "$relativo"
+ fi
+ done
+)
+
+if [ -n "$RESULTADO" ]; then
+ echo "$RESULTADO"
+ VIOLACOES=$(printf '%s\n' "$RESULTADO" | wc -l | tr -d ' ')
+ printf "\n❌ %s arquivo(s) violam o limite de %s linhas.\n" "$VIOLACOES" "$LIMITE"
+ printf "\n Resolva usando o arquivo: .agent/workflows/otimizar-codigos.global.md\n"
+ exit 1
+fi
+
+printf "✅ Todos os arquivos dentro do limite de %s linhas.\n" "$LIMITE"
diff --git a/.agent/workflows/adicionar-ajuda.global.md b/.agent/workflows/adicionar-ajuda.global.md
index b499da2..2dff1e9 100644
--- a/.agent/workflows/adicionar-ajuda.global.md
+++ b/.agent/workflows/adicionar-ajuda.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: adicionar-ajuda
description: Adicionar ou atualizar botão de ajuda e documentação em telas (Frontend)
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Adicionar Ajuda em Telas
diff --git a/.agent/workflows/aprimorar-IA.global.md b/.agent/workflows/aprimorar-IA.global.md
index 12ca269..369cacb 100644
--- a/.agent/workflows/aprimorar-IA.global.md
+++ b/.agent/workflows/aprimorar-IA.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: aprimorar-IA
description: Consolidar aprendizados da conversa atual e evoluir continuamente as regras, skills e fluxos da aplicação
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Aprimorar Conhecimento da Aplicação
@@ -74,7 +78,7 @@ Para cada item identificado, classificar obrigatoriamente:
### Estrutura obrigatória:
- Um arquivo = um único assunto.
-- Nome claro em `snake_case`.
+- Nome claro em `kebab-case`.
- Limite máximo: ~10.000 caracteres.
### É obrigatório:
@@ -196,105 +200,64 @@ Exemplos de skills de projeto:
Sempre que decisões críticas sobre a arquitetura da aplicação ou o contexto do domínio precisarem ser arquivadas temporária ou permanentemente de maneira descritiva (e não diretamente injetável em código ou regra de lint):
* Registre o novo documento no formato adequado mantendo a pasta limpa em `.agent/memoria/`.
-* Exemplo: Manter diário de pendências (`diario_pendencias_modulo_x.md`) ou Arquivo de Registro de Decisão Arquitetural (`adr-001-migracao-nuxt-4.md`).
-* Atenção! Tudo que puder ser abstraído para um formato técnico estrito/guiado, deve virar **Skill** ou **Rule**. Use a Memória apenas como extensão de base factual.
+* Exemplo: Manter diário de pendências (`diario-pendencias-modulo-x.md`) ou ADR (`adr-001-migracao-nuxt-4.md`).
+* Atenção! Tudo que puder ser abstraído para formato técnico estrito deve virar **Skill** ou **Rule**. Use a Memória apenas como extensão de base factual.
---
-## 6️⃣ Revisão Global de TODO o Sistema de Conhecimento (OBRIGATÓRIA E INEGOCIÁVEL)
+## 6️⃣ Revisão de Impacto (Dirigida por Grafo)
-Esta é a etapa mais crítica.
+Quando um artefato é alterado, a revisão deve ser **dirigida**, não exaustiva.
-Sempre que qualquer alteração ocorrer, é **OBRIGATÓRIO revisar TODO o sistema de conhecimento**, incluindo:
+### Processo:
-* **TODOS os arquivos em `.agent/rules/`**
-* **TODOS os arquivos em `.agent/workflows/`**
-* **TODOS os arquivos em `.agent/skills/`**
-* **TODOS os arquivos em `.agent/memoria/`**
+1. Identificar o artefato alterado
+2. Consultar a seção `Referências` do artefato para encontrar dependentes diretos
+3. Verificar consistência APENAS entre o artefato alterado e seus dependentes
+4. Se uma inconsistência for encontrada, propagar para o próximo nível
+
+### Regras de escopo:
+
+| Se alterou... | Revisar... |
+|---|---|
+| Uma **Rule** | Workflows e skills que a **referenciam** |
+| Um **Workflow** | Skills que o **referenciam** |
+| Uma **Skill** | Rules e workflows na seção Referências |
+| **Memória/ADR** | Nada — memória é registro, não dependência |
### É PROIBIDO:
-* Atualizar apenas um arquivo isolado.
-* Assumir que o impacto é local.
-* Ignorar arquivos existentes.
-* Criar artefatos sem validar o restante do sistema.
+* Revisar artefatos **sem relação** com a mudança
+* Afirmar que "tudo foi revisado" sem listar quais artefatos foram verificados
+* Criar artefatos sem verificar se já existe um com o mesmo escopo
+
+### A revisão DEVE verificar:
+
+* Não existem regras conflitantes entre o artefato alterado e seus dependentes
+* Referências cruzadas continuam válidas (arquivos existem, nomes corretos)
+* Skills refletem fielmente as rules que referenciam
+* Não há duplicação de conteúdo entre artefatos
---
-### A revisão DEVE obrigatoriamente verificar:
-
-#### Consistência
-
-* Não existem regras conflitantes.
-* Não existem interpretações ambíguas.
-* Skills refletem fielmente as rules e workflows atuais.
-
-#### Atualização
-
-* Nenhuma rule está desatualizada.
-* Nenhum workflow usa regra antiga.
-* Nenhuma skill contém padrões obsoletos.
-
-#### Não duplicação
-
-* Não existem regras repetidas em arquivos diferentes.
-* Conteúdos redundantes foram consolidados.
-* Skills referenciam rules (não copiam o conteúdo).
-
-#### Propagação de impacto
-
-* Toda mudança foi refletida em:
-
- * Rules relacionadas
- * Workflows relacionados
- * Skills relacionadas
- * Memória afetada pela mudança (ADRs, Diários)
-
-#### Coerência estrutural
-
-* Nomes de arquivos continuam válidos.
-* Assuntos estão bem separados.
-* Arquivos grandes foram divididos.
-* Skills estão organizadas em diretórios individuais.
-
----
-
-### Regras obrigatórias desta etapa:
-
-* **Toda alteração é considerada global até prova contrária.**
-* **Se uma rule muda, todos os workflows e skills relacionados DEVEM ser revisados.**
-* **Se um workflow muda, todas as rules e skills relacionadas DEVEM ser revisadas.**
-* **Se uma skill muda, as rules e workflows que ela referencia DEVEM ser verificados.**
-* **Atualizações de contexto devem garantir que toda a memória conflitante seja suprimida ou tagueada.**
-* **Nenhuma inconsistência pode permanecer.**
-* **Nenhuma duplicação é aceitável.**
-
----
-
-## 7️⃣ Verificação Final (Checklist obrigatório)
+## 7⃣ Verificação Final (Checklist obrigatório)
Só é permitido encerrar se TODOS forem verdadeiros:
-* [ ] Todas as rules foram revisadas
-* [ ] Todos os workflows foram revisados
-* [ ] Todas as skills foram revisadas
-* [ ] Não existe conflito entre artefatos
+* [ ] Artefatos dependentes do alterado foram revisados
+* [ ] Não existe conflito entre artefatos revisados
* [ ] Não existe duplicação de conteúdo
* [ ] Nenhuma regra ficou implícita
-* [ ] Todas as mudanças foram propagadas (rules ↔ workflows ↔ skills)
+* [ ] Referências cruzadas estão válidas
* [ ] Estrutura está organizada por assunto
-* [ ] Nenhum arquivo excede ~10k caracteres
-* [ ] Workflows refletem fielmente as rules atuais
* [ ] Skills referenciam (não duplicam) rules
-* [ ] O sistema está consistente como um todo
---
## ⚠️ Regra Suprema
-> Se existir qualquer dúvida, conflito, duplicação ou inconsistência,
+> Se existir qualquer dúvida, conflito ou inconsistência
+> entre o artefato alterado e seus dependentes,
> o trabalho é considerado **INCOMPLETO**.
-Não existe atualização parcial.
-Não existe impacto local.
-Não existe exceção.
+A revisão é dirigida, não opcional.
diff --git a/.agent/workflows/atualizar-agente.global.md b/.agent/workflows/atualizar-agente.global.md
index 34e82b8..b91cc26 100644
--- a/.agent/workflows/atualizar-agente.global.md
+++ b/.agent/workflows/atualizar-agente.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: atualizar-agente
description: Aplica atualizações globais para o agente de IA
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Atualizar Agente Global
diff --git a/.agent/workflows/comercial.global.md b/.agent/workflows/comercial.global.md
index 9d50012..4e33b7c 100644
--- a/.agent/workflows/comercial.global.md
+++ b/.agent/workflows/comercial.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: comercial
description: Criar uma descrição comercial das funcionalidades e do sistema
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Criar Descrição Comercial
@@ -137,4 +141,4 @@ O arquivo `./COMERCIAL/.md` deve seguir esta estrutura:
- Regra global: `.agent/rules/escrita-natural.global.md` (padrão obrigatório de redação)
- Workflow: `.agent/workflows/orcamento.global.md` (texto para documentos de orçamento e proposta — apresentação + lista de funcionalidades)
- Workflow: `.agent/workflows/marketing.global.md` (peças de marketing digital — LinkedIn, Instagram, WhatsApp, E-mail)
-- Workflow: `.agent/workflows/documentar-codigo.global.md` (para documentação técnica, não comercial)
+- Workflow: `.agent/workflows/otimizar-codigos.global.md` (para documentação técnica, não comercial)
diff --git a/.agent/workflows/criar-fluxograma.global.md b/.agent/workflows/criar-fluxograma.global.md
index 5bd5a05..d7651d2 100644
--- a/.agent/workflows/criar-fluxograma.global.md
+++ b/.agent/workflows/criar-fluxograma.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: criar-fluxograma
description: Criar ou atualizar uma página de fluxograma interativo em qualquer sistema
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Criar Fluxograma de Decisões
@@ -181,5 +185,5 @@ Caso contrário, seguir os passos acima para criar a primeira implementação.
## Referências
-- Workflow: `.agent/workflows/documentar-codigo.global.md`
-- Skill: `.agent/skills/documentar-decisao-tecnica/SKILL.md` (para decisões que geram fluxogramas)
+- Workflow: `.agent/workflows/otimizar-codigos.global.md`
+- Skill: `.agent/skills/documentar-decisao-tecnica.global/SKILL.md` (para decisões que geram fluxogramas)
diff --git a/.agent/workflows/documentar-regras-negocio.global.md b/.agent/workflows/documentar-regras-negocio.global.md
index f836df5..41925bc 100644
--- a/.agent/workflows/documentar-regras-negocio.global.md
+++ b/.agent/workflows/documentar-regras-negocio.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: documentar-regras-negocio
description: Criar ou atualizar o arquivo {modulo}.regras-negocio.md co-localizado com um módulo TypeScript e garantir consistência entre código e documentação
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Documentar Regras de Negócio de um Módulo
@@ -86,7 +90,7 @@ Para cada regra listada no `.md`:
// turbo
```bash
-cd e-licencie-back && node_modules/.bin/tsc --noEmit
+npx tsc --noEmit
```
Zero erros = concluído.
diff --git a/.agent/workflows/marketing.global.md b/.agent/workflows/marketing.global.md
index 10f221a..6b6c3fb 100644
--- a/.agent/workflows/marketing.global.md
+++ b/.agent/workflows/marketing.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: marketing
description: Criar peças de marketing digital para funcionalidades e módulos do sistema
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Criar Peças de Marketing
diff --git a/.agent/workflows/orcamento.global.md b/.agent/workflows/orcamento.global.md
index 908b20e..9d46b27 100644
--- a/.agent/workflows/orcamento.global.md
+++ b/.agent/workflows/orcamento.global.md
@@ -1,5 +1,9 @@
---
+type: workflow
+name: orcamento
description: Criar texto de orçamento pronto para copiar e colar em peças de orçamento/proposta
+scope: global
+updatedAt: "2026-06-01"
---
# Workflow: Criar Texto de Orçamento
diff --git a/.agent/workflows/otimizar-codigos.global.md b/.agent/workflows/otimizar-codigos.global.md
index b8ad87d..584b19b 100644
--- a/.agent/workflows/otimizar-codigos.global.md
+++ b/.agent/workflows/otimizar-codigos.global.md
@@ -1,221 +1,76 @@
---
-description: Otimizar código e documentar padrões de qualidade (estrutura, limites, JSDoc e tipagem)
+type: workflow
+name: otimizar-codigos
+description: Processo de refatoração e validação de qualidade de código
+scope: global
+updatedAt: "2026-06-01"
---
-
-
-# Workflow: Qualidade e Documentação de Código
+# Workflow: Qualidade e Otimização de Código
## 🎯 Objetivo
-Unificar as regras de **estrutura de arquivos**, **otimização** e **documentação** em um único fluxo de referência para o projeto `eli-vue`.
+Aplicar as rules de qualidade do ecossistema Azteca em código existente ou novo, garantindo que todos os padrões sejam cumpridos antes de finalizar uma tarefa.
---
-## 1️⃣ Limite de Linhas por Arquivo
+## Passos
-Nenhum arquivo `*.vue`, `*.ts` ou `*.tsx` pode ultrapassar **250 linhas**.
+### 1. Verificar limite de linhas
-Essa regra existe para facilitar a leitura humana, a revisão de código e a manutenção a longo prazo.
-
-> O script `scripts/check-vue-linhas.sh` valida automaticamente os arquivos `.vue` no `prebuild`.
-
-**Estratégias quando o limite se aproxima:**
-
-| Situação | Ação |
-|---|---|
-| Template longo | Extrair seções como subcomponentes |
-| CSS extenso | Mover para `.css` co-localizado |
-| `setup()` extenso | Extrair subcomponentes, CSS ou Dispatch Table |
-| Modal/Dialog embutido | Sempre em componente separado (`*Modal.vue`) |
-| Handlers e dispatch | Extrair para `handlers.ts` ou `dispatch.ts` |
-
----
-
-## 2️⃣ Estrutura de Componentes Vue
-
-### `defineComponent` obrigatório
-
-Todo componente Vue deve usar `defineComponent` para garantir inferência de tipos correta e consistência.
-
-```vue
-
+```bash
+sh .agent/verificar-linhas.sh
```
-**Por que é importante:**
-- Inferência de tipos TypeScript correta dentro do componente
-- Reconhecimento por ferramentas de análise estática e IDEs
-- Compatível com Options API e Composition API com `setup()`
-- Padronização entre todos os componentes do projeto
+Se houver violações, aplicar estratégias da rule `limite-linhas.global.md`:
+- Extrair subcomponentes, mover CSS, usar Dispatch Table
+- Se necessário, converter em pasta com `index.vue`
----
+### 2. Validar estrutura Vue
-### `setup()` não pode ser externo
+Para cada componente `.vue` modificado, verificar conforme `estrutura-vue.global.md`:
+- `defineComponent` presente?
+- `setup()` contém lógica própria (não delegada)?
+- CSS extenso foi movido para arquivo co-localizado?
-O `setup()` deve estar sempre dentro do componente `.vue`. Não é permitido extrair ou delegar o `setup` para nenhum arquivo externo.
+### 3. Documentar código
-```ts
-// ❌ Proibido — setup delegado inteiramente para arquivo externo
-// useEliTabela.ts
-export function useEliTabela(props, emit) { ... }
+Para cada função compartilhada criada/modificada, aplicar `jsdoc-padrao.global.md`:
+- JSDoc com `@param` e `@returns`?
+- Casts temporários têm comentário justificando?
+- TODOs no formato padronizado?
-// EliTabela.vue
-export default defineComponent({
- setup(props, { emit }) {
- return useEliTabela(props, emit) // setup é apenas um proxy vazio
- }
-})
+### 4. Validar tipagem
+
+```bash
+npx tsc --noEmit
```
-```ts
-// ✅ Correto — setup com lógica própria dentro do componente
-// EliTabela.vue
-export default defineComponent({
- setup(props, { emit }) {
- const estado = ref(null)
- const carregar = async () => { ... }
- return { estado, carregar }
- }
-})
-```
+Zero erros = prosseguir. Se houver erros, aplicar `typescript-sem-any.global.md` — nunca resolver com `any`.
-> Composables `use*.ts` podem encapsular lógica reutilizável pontual (ex: `useFormatador`, `usePermissao`), mas nunca substituir ou esvaziar o `setup()` do componente. Se o componente ultrapassar 250 linhas, a solução é extrair subcomponentes, mover CSS ou usar Dispatch Table — não esvaziar o `setup`.
+### 5. Formatar
----
-
-### Desassociação de CSS
-
-A separação de estilos em arquivo externo é permitida e recomendada quando o CSS contribui significativamente para o total de linhas.
-
-**Padrão de nomenclatura:**
-
-```
-MeuComponente.vue
-MeuComponente.css
-```
-
-O `.vue` importa o arquivo CSS no script:
-
-```ts
-import './MeuComponente.css'
-```
-
-Ou via tag `
diff --git a/src/componentes/EliTabela/EliTabelaModalColunas.css b/src/componentes/EliTabela/EliTabelaModalColunas.css
index 4069069..f3717d8 100644
--- a/src/componentes/EliTabela/EliTabelaModalColunas.css
+++ b/src/componentes/EliTabela/EliTabelaModalColunas.css
@@ -1,3 +1,5 @@
+@import "./EliTabelaModalColunasItens.css";
+
.eli-tabela-modal-colunas__overlay {
position: fixed;
inset: 0;
@@ -162,181 +164,3 @@
.eli-tabela-modal-colunas__lista--invisiveis {
background: var(--cor-eli-fundo);
}
-
-/* ── Item ── */
-.eli-tabela-modal-colunas__item {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 9px 10px;
- border-radius: 8px;
- border: 1px solid var(--cor-eli-borda);
- background: var(--cor-eli-superficie);
- cursor: grab;
- user-select: none;
- transition: box-shadow 0.15s ease, opacity 0.15s ease, border-color 0.15s ease;
-}
-
-.eli-tabela-modal-colunas__item + .eli-tabela-modal-colunas__item {
- margin-top: 6px;
-}
-
-.eli-tabela-modal-colunas__item:hover {
- border-color: var(--cor-eli-borda-forte);
- box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
-}
-
-.eli-tabela-modal-colunas__item:active {
- cursor: grabbing;
-}
-
-.eli-tabela-modal-colunas__item--invisivel {
- opacity: 0.72;
-}
-
-.eli-tabela-modal-colunas__item--arrastando {
- opacity: 0.15;
- border-style: dashed;
- border-color: var(--cor-eli-primaria-media);
-}
-
-/* ── Partes do item ── */
-.eli-tabela-modal-colunas__item-handle {
- color: var(--cor-eli-borda-forte);
- flex-shrink: 0;
- transition: color 0.15s ease;
-}
-
-.eli-tabela-modal-colunas__item:hover .eli-tabela-modal-colunas__item-handle {
- color: var(--cor-eli-neutro);
-}
-
-.eli-tabela-modal-colunas__item-texto {
- flex: 1;
- min-width: 0;
- font-size: 0.875rem;
- color: var(--cor-eli-neutro-escuro);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.eli-tabela-modal-colunas__item-badge-icone {
- flex-shrink: 0;
-}
-
-.eli-tabela-modal-colunas__item-badge-icone--visivel {
- color: var(--cor-eli-primaria-acao);
-}
-
-.eli-tabela-modal-colunas__item-badge-icone--invisivel {
- color: var(--cor-eli-borda-forte);
-}
-
-/* ── Estado vazio ── */
-.eli-tabela-modal-colunas__vazio {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 24px 12px;
- text-align: center;
- font-size: 0.8rem;
- color: var(--cor-eli-neutro);
- line-height: 1.5;
-}
-
-.eli-tabela-modal-colunas__vazio-icone {
- color: var(--cor-eli-borda-forte);
-}
-
-.eli-tabela-modal-colunas__vazio-icone--ok {
- color: var(--cor-eli-primaria-acao);
-}
-
-/* ── Animações TransitionGroup ── */
-.list-move,
-.list-enter-active,
-.list-leave-active {
- transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.list-enter-from,
-.list-leave-to {
- opacity: 0;
- transform: scale(0.95);
-}
-
-.list-leave-active {
- position: absolute;
- width: calc(100% - 16px);
-}
-
-/* ── Footer ── */
-.eli-tabela-modal-colunas__footer {
- display: flex;
- justify-content: flex-end;
- gap: 8px;
- padding: 12px 16px;
- border-top: 1px solid var(--cor-eli-borda);
- background: var(--cor-eli-fundo);
-}
-
-.eli-tabela-modal-colunas__botao {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- height: 34px;
- padding: 0 16px;
- border-radius: 8px;
- border: 1px solid var(--cor-eli-borda);
- background: var(--cor-eli-superficie);
- cursor: pointer;
- font-size: 0.875rem;
- font-weight: 500;
- transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
-}
-
-.eli-tabela-modal-colunas__botao--sec {
- color: var(--cor-eli-neutro-escuro);
-}
-
-.eli-tabela-modal-colunas__botao--sec:hover,
-.eli-tabela-modal-colunas__botao--sec:focus-visible {
- background: var(--cor-eli-fundo);
- border-color: var(--cor-eli-borda-forte);
-}
-
-.eli-tabela-modal-colunas__botao--sec:focus-visible {
- outline: 2px solid var(--cor-eli-primaria-clara);
- outline-offset: 2px;
-}
-
-.eli-tabela-modal-colunas__botao--prim {
- border: none;
- background: var(--cor-eli-primaria);
- color: #fff;
-}
-
-.eli-tabela-modal-colunas__botao--prim:hover,
-.eli-tabela-modal-colunas__botao--prim:focus-visible {
- background: var(--cor-eli-primaria-escura);
-}
-
-.eli-tabela-modal-colunas__botao--prim:focus-visible {
- outline: 2px solid var(--cor-eli-primaria-clara);
- outline-offset: 2px;
-}
-
-/* ── Responsivo ── */
-@media (max-width: 720px) {
- .eli-tabela-modal-colunas__conteudo {
- grid-template-columns: 1fr;
- }
-
- .eli-tabela-modal-colunas__coluna + .eli-tabela-modal-colunas__coluna {
- border-left: none;
- border-top: 1px solid var(--cor-eli-borda);
- }
-}
diff --git a/src/componentes/EliTabela/EliTabelaModalColunasItens.css b/src/componentes/EliTabela/EliTabelaModalColunasItens.css
new file mode 100644
index 0000000..1578638
--- /dev/null
+++ b/src/componentes/EliTabela/EliTabelaModalColunasItens.css
@@ -0,0 +1,182 @@
+/* =========================
+ * EliTabelaModalColunas — Itens, estados e footer
+ * Extraído de EliTabelaModalColunas.css para manter cada arquivo abaixo de 250 linhas.
+ * ========================= */
+
+/* ── Item ── */
+.eli-tabela-modal-colunas__item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 10px;
+ border-radius: 8px;
+ border: 1px solid var(--cor-eli-borda);
+ background: var(--cor-eli-superficie);
+ cursor: grab;
+ user-select: none;
+ transition: box-shadow 0.15s ease, opacity 0.15s ease, border-color 0.15s ease;
+}
+
+.eli-tabela-modal-colunas__item + .eli-tabela-modal-colunas__item {
+ margin-top: 6px;
+}
+
+.eli-tabela-modal-colunas__item:hover {
+ border-color: var(--cor-eli-borda-forte);
+ box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
+}
+
+.eli-tabela-modal-colunas__item:active {
+ cursor: grabbing;
+}
+
+.eli-tabela-modal-colunas__item--invisivel {
+ opacity: 0.72;
+}
+
+.eli-tabela-modal-colunas__item--arrastando {
+ opacity: 0.15;
+ border-style: dashed;
+ border-color: var(--cor-eli-primaria-media);
+}
+
+/* ── Partes do item ── */
+.eli-tabela-modal-colunas__item-handle {
+ color: var(--cor-eli-borda-forte);
+ flex-shrink: 0;
+ transition: color 0.15s ease;
+}
+
+.eli-tabela-modal-colunas__item:hover .eli-tabela-modal-colunas__item-handle {
+ color: var(--cor-eli-neutro);
+}
+
+.eli-tabela-modal-colunas__item-texto {
+ flex: 1;
+ min-width: 0;
+ font-size: 0.875rem;
+ color: var(--cor-eli-neutro-escuro);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.eli-tabela-modal-colunas__item-badge-icone {
+ flex-shrink: 0;
+}
+
+.eli-tabela-modal-colunas__item-badge-icone--visivel {
+ color: var(--cor-eli-primaria-acao);
+}
+
+.eli-tabela-modal-colunas__item-badge-icone--invisivel {
+ color: var(--cor-eli-borda-forte);
+}
+
+/* ── Estado vazio ── */
+.eli-tabela-modal-colunas__vazio {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 24px 12px;
+ text-align: center;
+ font-size: 0.8rem;
+ color: var(--cor-eli-neutro);
+ line-height: 1.5;
+}
+
+.eli-tabela-modal-colunas__vazio-icone {
+ color: var(--cor-eli-borda-forte);
+}
+
+.eli-tabela-modal-colunas__vazio-icone--ok {
+ color: var(--cor-eli-primaria-acao);
+}
+
+/* ── Animações TransitionGroup ── */
+.list-move,
+.list-enter-active,
+.list-leave-active {
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.list-enter-from,
+.list-leave-to {
+ opacity: 0;
+ transform: scale(0.95);
+}
+
+.list-leave-active {
+ position: absolute;
+ width: calc(100% - 16px);
+}
+
+/* ── Footer ── */
+.eli-tabela-modal-colunas__footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 12px 16px;
+ border-top: 1px solid var(--cor-eli-borda);
+ background: var(--cor-eli-fundo);
+}
+
+.eli-tabela-modal-colunas__botao {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ height: 34px;
+ padding: 0 16px;
+ border-radius: 8px;
+ border: 1px solid var(--cor-eli-borda);
+ background: var(--cor-eli-superficie);
+ cursor: pointer;
+ font-size: 0.875rem;
+ font-weight: 500;
+ transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
+}
+
+.eli-tabela-modal-colunas__botao--sec {
+ color: var(--cor-eli-neutro-escuro);
+}
+
+.eli-tabela-modal-colunas__botao--sec:hover,
+.eli-tabela-modal-colunas__botao--sec:focus-visible {
+ background: var(--cor-eli-fundo);
+ border-color: var(--cor-eli-borda-forte);
+}
+
+.eli-tabela-modal-colunas__botao--sec:focus-visible {
+ outline: 2px solid var(--cor-eli-primaria-clara);
+ outline-offset: 2px;
+}
+
+.eli-tabela-modal-colunas__botao--prim {
+ border: none;
+ background: var(--cor-eli-primaria);
+ color: #fff;
+}
+
+.eli-tabela-modal-colunas__botao--prim:hover,
+.eli-tabela-modal-colunas__botao--prim:focus-visible {
+ background: var(--cor-eli-primaria-escura);
+}
+
+.eli-tabela-modal-colunas__botao--prim:focus-visible {
+ outline: 2px solid var(--cor-eli-primaria-clara);
+ outline-offset: 2px;
+}
+
+/* ── Responsivo ── */
+@media (max-width: 720px) {
+ .eli-tabela-modal-colunas__conteudo {
+ grid-template-columns: 1fr;
+ }
+
+ .eli-tabela-modal-colunas__coluna + .eli-tabela-modal-colunas__coluna {
+ border-left: none;
+ border-top: 1px solid var(--cor-eli-borda);
+ }
+}
diff --git a/src/componentes/EliTabela/EliTabelaModalFiltroAvancado.css b/src/componentes/EliTabela/EliTabelaModalFiltroAvancado.css
index 4965b6a..f821609 100644
--- a/src/componentes/EliTabela/EliTabelaModalFiltroAvancado.css
+++ b/src/componentes/EliTabela/EliTabelaModalFiltroAvancado.css
@@ -1,3 +1,5 @@
+@import "./EliTabelaModalFiltroFormula.css";
+
.eli-tabela-modal-filtro__overlay {
position: fixed;
inset: 0;
@@ -217,227 +219,3 @@
outline: none;
border-color: var(--cor-eli-primaria);
}
-
-/* ── Fórmula de combinação ───────────────────── */
-.eli-tabela-modal-filtro__formula-container {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 8px;
- margin-left: auto;
-}
-
-.eli-tabela-modal-filtro__formula-label {
- font-size: 0.82rem;
- font-weight: 500;
- color: rgba(15, 23, 42, 0.55);
- white-space: nowrap;
- flex-shrink: 0;
-}
-
-
-
-.eli-tabela-modal-filtro__formula-ajuda {
- position: relative;
- display: inline-flex;
- align-items: center;
- color: rgba(15, 23, 42, 0.35);
- cursor: help;
- transition: color 0.15s;
-}
-
-.eli-tabela-modal-filtro__formula-ajuda:hover,
-.eli-tabela-modal-filtro__formula-ajuda:focus-visible {
- color: var(--cor-eli-primaria);
- outline: none;
-}
-
-.eli-tabela-modal-filtro__formula-ajuda:hover .eli-tabela-modal-filtro__formula-tooltip,
-.eli-tabela-modal-filtro__formula-ajuda:focus-visible .eli-tabela-modal-filtro__formula-tooltip {
- opacity: 1;
- pointer-events: auto;
- transform: translateY(0);
-}
-
-.eli-tabela-modal-filtro__formula-tooltip {
- position: absolute;
- bottom: calc(100% + 10px);
- right: 0;
- width: 280px;
- background: #0f172a;
- color: #e2e8f0;
- border-radius: 10px;
- padding: 14px 16px;
- font-size: 0.8rem;
- line-height: 1.55;
- z-index: 100;
- opacity: 0;
- pointer-events: none;
- transform: translateY(4px);
- transition: opacity 0.18s ease, transform 0.18s ease;
- box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
-}
-
-.eli-tabela-modal-filtro__formula-tooltip::after {
- content: '';
- position: absolute;
- top: 100%;
- right: 6px;
- border: 6px solid transparent;
- border-top-color: #0f172a;
-}
-
-.eli-tabela-modal-filtro__formula-tooltip strong {
- font-size: 0.82rem;
- color: var(--cor-eli-fundo);
- display: block;
- margin-bottom: 6px;
-}
-
-.eli-tabela-modal-filtro__formula-tooltip p {
- margin: 6px 0 4px;
- color: #94a3b8;
-}
-
-.eli-tabela-modal-filtro__formula-tooltip ul {
- margin: 4px 0 8px 14px;
- padding: 0;
-}
-
-.eli-tabela-modal-filtro__formula-tooltip li {
- margin-bottom: 3px;
- color: #cbd5e1;
-}
-
-.eli-tabela-modal-filtro__formula-tooltip code {
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- color: #7dd3fc;
- font-size: 0.78rem;
- background: rgba(255, 255, 255, 0.07);
- border-radius: 4px;
- padding: 1px 4px;
-}
-
-.eli-tabela-modal-filtro__formula-input {
- width: 220px;
- height: 36px;
- border-radius: 10px;
- border: 1px solid rgba(15, 23, 42, 0.12);
- padding: 0 10px;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 0.85rem;
- background: var(--cor-eli-fundo);
- transition: border-color 0.15s, background 0.15s;
- box-sizing: border-box;
-}
-
-.eli-tabela-modal-filtro__formula-input:focus {
- outline: none;
- border-color: var(--cor-eli-primaria);
- background: #fff;
-}
-
-.eli-tabela-modal-filtro__formula-tooltip-nota {
- margin-top: 8px;
- opacity: 0.75;
-}
-
-/* ── Footer ──────────────────────────────────── */
-.eli-tabela-modal-filtro__footer {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 14px 20px;
- border-top: 1px solid rgba(15, 23, 42, 0.08);
-}
-
-.eli-tabela-modal-filtro__footer-spacer {
- flex: 1;
-}
-
-/* ── Botões ──────────────────────────────────── */
-.eli-tabela-modal-filtro__botao {
- display: inline-flex;
- align-items: center;
- gap: 5px;
- height: 36px;
- padding: 0 16px;
- border-radius: 10px;
- border: 1px solid rgba(15, 23, 42, 0.12);
- background: #fff;
- cursor: pointer;
- font-size: 0.875rem;
- font-weight: 500;
- transition: background 0.15s, color 0.15s, border-color 0.15s;
-}
-
-.eli-tabela-modal-filtro__botao:disabled {
- opacity: 0.45;
- cursor: not-allowed;
-}
-
-.eli-tabela-modal-filtro__botao--sec:hover:not(:disabled),
-.eli-tabela-modal-filtro__botao--sec:focus-visible {
- background: rgba(15, 23, 42, 0.05);
-}
-
-.eli-tabela-modal-filtro__botao--prim {
- border: none;
- background: var(--cor-eli-primaria);
- color: #fff;
-}
-
-.eli-tabela-modal-filtro__botao--prim:hover:not(:disabled),
-.eli-tabela-modal-filtro__botao--prim:focus-visible {
- background: var(--cor-eli-primaria-escura);
-}
-
-.eli-tabela-modal-filtro__botao--perigo {
- color: rgba(220, 38, 38, 0.85);
- border-color: rgba(220, 38, 38, 0.18);
- background: #fff;
-}
-
-.eli-tabela-modal-filtro__botao--perigo:hover:not(:disabled),
-.eli-tabela-modal-filtro__botao--perigo:focus-visible {
- background: rgba(239, 68, 68, 0.06);
- border-color: rgba(220, 38, 38, 0.3);
- color: rgba(220, 38, 38, 1);
-}
-
-/* ── Responsivo ──────────────────────────────── */
-@media (max-width: 600px) {
- .eli-tabela-modal-filtro__lista {
- grid-template-columns: 1fr;
- }
- .eli-tabela-modal-filtro__rodape-filtros {
- flex-direction: column;
- align-items: stretch;
- }
- .eli-tabela-modal-filtro__acoes {
- flex-wrap: wrap;
- }
-}
-
-/* ── Transição de abertura/fechamento ────────── */
-.eli-tabela-modal-filtro-enter-active,
-.eli-tabela-modal-filtro-leave-active {
- transition: opacity 0.2s ease;
-}
-
-.eli-tabela-modal-filtro-enter-active .eli-tabela-modal-filtro__modal,
-.eli-tabela-modal-filtro-leave-active .eli-tabela-modal-filtro__modal {
- transition: transform 0.2s ease, opacity 0.2s ease;
-}
-
-.eli-tabela-modal-filtro-enter-from,
-.eli-tabela-modal-filtro-leave-to {
- opacity: 0;
-}
-
-.eli-tabela-modal-filtro-enter-from .eli-tabela-modal-filtro__modal,
-.eli-tabela-modal-filtro-leave-to .eli-tabela-modal-filtro__modal {
- transform: scale(0.96);
- opacity: 0;
-}
-
diff --git a/src/componentes/EliTabela/EliTabelaModalFiltroFormula.css b/src/componentes/EliTabela/EliTabelaModalFiltroFormula.css
new file mode 100644
index 0000000..943ec29
--- /dev/null
+++ b/src/componentes/EliTabela/EliTabelaModalFiltroFormula.css
@@ -0,0 +1,225 @@
+/* =========================
+ * EliTabelaModalFiltroAvancado — Fórmula, tooltip, botões e transições
+ * Extraído de EliTabelaModalFiltroAvancado.css para manter cada arquivo abaixo de 250 linhas.
+ * ========================= */
+
+/* ── Fórmula de combinação ───────────────────── */
+.eli-tabela-modal-filtro__formula-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 8px;
+ margin-left: auto;
+}
+
+.eli-tabela-modal-filtro__formula-label {
+ font-size: 0.82rem;
+ font-weight: 500;
+ color: rgba(15, 23, 42, 0.55);
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.eli-tabela-modal-filtro__formula-ajuda {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ color: rgba(15, 23, 42, 0.35);
+ cursor: help;
+ transition: color 0.15s;
+}
+
+.eli-tabela-modal-filtro__formula-ajuda:hover,
+.eli-tabela-modal-filtro__formula-ajuda:focus-visible {
+ color: var(--cor-eli-primaria);
+ outline: none;
+}
+
+.eli-tabela-modal-filtro__formula-ajuda:hover .eli-tabela-modal-filtro__formula-tooltip,
+.eli-tabela-modal-filtro__formula-ajuda:focus-visible .eli-tabela-modal-filtro__formula-tooltip {
+ opacity: 1;
+ pointer-events: auto;
+ transform: translateY(0);
+}
+
+.eli-tabela-modal-filtro__formula-tooltip {
+ position: absolute;
+ bottom: calc(100% + 10px);
+ right: 0;
+ width: 280px;
+ background: #0f172a;
+ color: #e2e8f0;
+ border-radius: 10px;
+ padding: 14px 16px;
+ font-size: 0.8rem;
+ line-height: 1.55;
+ z-index: 100;
+ opacity: 0;
+ pointer-events: none;
+ transform: translateY(4px);
+ transition: opacity 0.18s ease, transform 0.18s ease;
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
+}
+
+.eli-tabela-modal-filtro__formula-tooltip::after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ right: 6px;
+ border: 6px solid transparent;
+ border-top-color: #0f172a;
+}
+
+.eli-tabela-modal-filtro__formula-tooltip strong {
+ font-size: 0.82rem;
+ color: var(--cor-eli-fundo);
+ display: block;
+ margin-bottom: 6px;
+}
+
+.eli-tabela-modal-filtro__formula-tooltip p {
+ margin: 6px 0 4px;
+ color: #94a3b8;
+}
+
+.eli-tabela-modal-filtro__formula-tooltip ul {
+ margin: 4px 0 8px 14px;
+ padding: 0;
+}
+
+.eli-tabela-modal-filtro__formula-tooltip li {
+ margin-bottom: 3px;
+ color: #cbd5e1;
+}
+
+.eli-tabela-modal-filtro__formula-tooltip code {
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ color: #7dd3fc;
+ font-size: 0.78rem;
+ background: rgba(255, 255, 255, 0.07);
+ border-radius: 4px;
+ padding: 1px 4px;
+}
+
+.eli-tabela-modal-filtro__formula-input {
+ width: 220px;
+ height: 36px;
+ border-radius: 10px;
+ border: 1px solid rgba(15, 23, 42, 0.12);
+ padding: 0 10px;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 0.85rem;
+ background: var(--cor-eli-fundo);
+ transition: border-color 0.15s, background 0.15s;
+ box-sizing: border-box;
+}
+
+.eli-tabela-modal-filtro__formula-input:focus {
+ outline: none;
+ border-color: var(--cor-eli-primaria);
+ background: #fff;
+}
+
+.eli-tabela-modal-filtro__formula-tooltip-nota {
+ margin-top: 8px;
+ opacity: 0.75;
+}
+
+/* ── Footer ──────────────────────────────────── */
+.eli-tabela-modal-filtro__footer {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 14px 20px;
+ border-top: 1px solid rgba(15, 23, 42, 0.08);
+}
+
+.eli-tabela-modal-filtro__footer-spacer {
+ flex: 1;
+}
+
+/* ── Botões ──────────────────────────────────── */
+.eli-tabela-modal-filtro__botao {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ height: 36px;
+ padding: 0 16px;
+ border-radius: 10px;
+ border: 1px solid rgba(15, 23, 42, 0.12);
+ background: #fff;
+ cursor: pointer;
+ font-size: 0.875rem;
+ font-weight: 500;
+ transition: background 0.15s, color 0.15s, border-color 0.15s;
+}
+
+.eli-tabela-modal-filtro__botao:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
+.eli-tabela-modal-filtro__botao--sec:hover:not(:disabled),
+.eli-tabela-modal-filtro__botao--sec:focus-visible {
+ background: rgba(15, 23, 42, 0.05);
+}
+
+.eli-tabela-modal-filtro__botao--prim {
+ border: none;
+ background: var(--cor-eli-primaria);
+ color: #fff;
+}
+
+.eli-tabela-modal-filtro__botao--prim:hover:not(:disabled),
+.eli-tabela-modal-filtro__botao--prim:focus-visible {
+ background: var(--cor-eli-primaria-escura);
+}
+
+.eli-tabela-modal-filtro__botao--perigo {
+ color: rgba(220, 38, 38, 0.85);
+ border-color: rgba(220, 38, 38, 0.18);
+ background: #fff;
+}
+
+.eli-tabela-modal-filtro__botao--perigo:hover:not(:disabled),
+.eli-tabela-modal-filtro__botao--perigo:focus-visible {
+ background: rgba(239, 68, 68, 0.06);
+ border-color: rgba(220, 38, 38, 0.3);
+ color: rgba(220, 38, 38, 1);
+}
+
+/* ── Responsivo ──────────────────────────────── */
+@media (max-width: 600px) {
+ .eli-tabela-modal-filtro__lista {
+ grid-template-columns: 1fr;
+ }
+ .eli-tabela-modal-filtro__rodape-filtros {
+ flex-direction: column;
+ align-items: stretch;
+ }
+ .eli-tabela-modal-filtro__acoes {
+ flex-wrap: wrap;
+ }
+}
+
+/* ── Transição de abertura/fechamento ────────── */
+.eli-tabela-modal-filtro-enter-active,
+.eli-tabela-modal-filtro-leave-active {
+ transition: opacity 0.2s ease;
+}
+
+.eli-tabela-modal-filtro-enter-active .eli-tabela-modal-filtro__modal,
+.eli-tabela-modal-filtro-leave-active .eli-tabela-modal-filtro__modal {
+ transition: transform 0.2s ease, opacity 0.2s ease;
+}
+
+.eli-tabela-modal-filtro-enter-from,
+.eli-tabela-modal-filtro-leave-to {
+ opacity: 0;
+}
+
+.eli-tabela-modal-filtro-enter-from .eli-tabela-modal-filtro__modal,
+.eli-tabela-modal-filtro-leave-to .eli-tabela-modal-filtro__modal {
+ transform: scale(0.96);
+ opacity: 0;
+}
diff --git a/src/componentes/EliTabela/EliTabelaSelecao.css b/src/componentes/EliTabela/EliTabelaSelecao.css
new file mode 100644
index 0000000..d219e5e
--- /dev/null
+++ b/src/componentes/EliTabela/EliTabelaSelecao.css
@@ -0,0 +1,66 @@
+/* =========================================
+ * Seleção (Checkboxes)
+ * Extraído de EliTabela.css para manter cada arquivo abaixo de 250 linhas.
+ * ========================================= */
+.eli-tabela__th--selecao,
+.eli-tabela__td--selecao {
+ width: 48px;
+ min-width: 48px;
+ max-width: 48px;
+ text-align: center;
+ padding: 0 8px;
+ vertical-align: middle;
+}
+
+.eli-tabela__checkbox {
+ appearance: none;
+ -webkit-appearance: none;
+ width: 15px;
+ height: 15px;
+ cursor: pointer;
+ border: 2px solid rgba(0, 0, 0, 0.54);
+ border-radius: 2px;
+ margin: 0;
+ vertical-align: middle;
+ position: relative;
+ transition: all 0.2s ease-in-out;
+ background-color: transparent;
+ outline: none;
+}
+
+.eli-tabela__checkbox:hover {
+ border-color: rgba(0, 0, 0, 0.87);
+}
+
+.eli-tabela__checkbox:checked,
+.eli-tabela__checkbox:indeterminate {
+ background-color: var(--cor-eli-primaria);
+ border-color: var(--cor-eli-primaria);
+}
+
+.eli-tabela__checkbox:checked::after {
+ content: '';
+ position: absolute;
+ top: 0px;
+ left: 4px;
+ width: 4px;
+ height: 9px;
+ border: solid white;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg);
+}
+
+.eli-tabela__checkbox:indeterminate::after {
+ content: '';
+ position: absolute;
+ top: 5px;
+ left: 2px;
+ width: 8px;
+ height: 2px;
+ background-color: white;
+ border-radius: 1px;
+}
+
+.eli-tabela__checkbox:focus-visible {
+ box-shadow: 0 0 0 2px var(--cor-eli-primaria-clara);
+}
diff --git a/src/componentes/EliTabela/types-eli-tabela.ts b/src/componentes/EliTabela/types-eli-tabela.ts
index 70fb000..39c76af 100644
--- a/src/componentes/EliTabela/types-eli-tabela.ts
+++ b/src/componentes/EliTabela/types-eli-tabela.ts
@@ -1,5 +1,5 @@
import type { LucideIcon } from "@lucide/vue"
-import type { tipoFiltroDeltaColunaTipada } from "p-comuns"
+import type { tipoCondicaoFiltroDeltaTipado, tipoFiltroDeltaColunaTipada } from "p-comuns"
import type { tipoResposta } from "p-respostas"
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
@@ -160,9 +160,17 @@ export type tipoEliTabelaConsulta = {
chave: string
/** Rótulo exibido no select de adicionar filtro */
rotulo: string
- /** vai gerar filtro e mescla com os filtros já existentes */
- filtro: (valor: unknown) => tipoFiltroDeltaColunaTipada> | undefined
+ /** vai gerar filtro e mescla com os filtros já existentes , id é gerado de forma altomatica para esse local*/
+ filtro: (
+ valor: unknown,
+ ) =>
+ | Pick<
+ tipoCondicaoFiltroDeltaTipado>,
+ "coluna" | "operador" | "valor"
+ >
+ | undefined
/** componente de entrada que vai receber o valor para o filtro */
entrada: ComponenteEntrada
}[]
}
+;("")
diff --git a/src/componentes/EliTabela/useEliTabelaFiltrosAvancados.ts b/src/componentes/EliTabela/useEliTabelaFiltrosAvancados.ts
index dcdc843..ad20ef4 100644
--- a/src/componentes/EliTabela/useEliTabelaFiltrosAvancados.ts
+++ b/src/componentes/EliTabela/useEliTabelaFiltrosAvancados.ts
@@ -49,12 +49,13 @@ export function useEliTabelaFiltrosAvancados(
.map((f) => {
const b = base.find((x) => String(x.chave) === String(f.chave))
if (!b) return null
- return { chave: f.chave, filtro: b.filtro(f.valor) }
+ const condicao = b.filtro(f.valor)
+ if (!condicao) return null
+ return { chave: f.chave, condicao }
})
- .filter(
- (f): f is { chave: string; filtro: tipoFiltroDeltaColunaTipada } =>
- f !== null && f.filtro !== undefined && f.filtro !== null,
- )
+ .filter((f): f is { chave: string; condicao: tipoCondicaoFiltroDelta } => f !== null)
+
+ if (filtrosIndividuais.length === 0) return []
if (filtrosFormula.value && filtrosIndividuais.length > 1) {
const condicoesFinais: tipoCondicaoFiltroDelta[] = []
@@ -62,25 +63,22 @@ export function useEliTabelaFiltrosAvancados(
filtrosIndividuais.forEach(({ chave }, i) => {
formulaFinal = formulaFinal.replace(new RegExp(`\\{${chave}\\}`, "g"), `{c${i + 1}}`)
})
- let globalIdx = 1
- for (let i = 0; i < filtrosIndividuais.length; i++) {
- const { filtro } = filtrosIndividuais[i]
- const idMap: Record = {}
- for (const cond of filtro.condicoes) {
- const novoId = `{c${globalIdx++}}`
- idMap[cond.id] = novoId
- condicoesFinais.push({ ...cond, id: novoId })
- }
- let formulaLocal = filtro.formula
- for (const [oldId, newId] of Object.entries(idMap)) {
- formulaLocal = formulaLocal.replace(new RegExp(`\\{${oldId.slice(1, -1)}\\}`, "g"), newId)
- }
- const subst = filtro.condicoes.length > 1 ? `(${formulaLocal})` : formulaLocal
- formulaFinal = formulaFinal.replace(new RegExp(`\\{c${i + 1}\\}`, "g"), subst)
- }
+ filtrosIndividuais.forEach(({ condicao }, i) => {
+ const novoId = `{c${i + 1}}`
+ condicoesFinais.push({ ...condicao, id: novoId })
+ })
return [{ condicoes: condicoesFinais, formula: formulaFinal }]
}
- return filtrosIndividuais.map((f) => f.filtro)
+
+ /** Sem fórmula customizada: monta condições com IDs sequenciais e fórmula "E" */
+ const condicoesFinais: tipoCondicaoFiltroDelta[] = filtrosIndividuais.map(
+ ({ condicao }, i) => ({
+ ...condicao,
+ id: `{c${i + 1}}`,
+ }),
+ )
+ const formula = condicoesFinais.map((c) => c.id).join(" E ")
+ return [{ condicoes: condicoesFinais, formula }]
})
function abrirModalFiltro() {
@@ -114,12 +112,82 @@ export function useEliTabelaFiltrosAvancados(
else void carregar()
}
+ const nomesOperadores: Record = {
+ "=": "igual a",
+ "!=": "diferente de",
+ ">": "maior que",
+ "<": "menor que",
+ ">=": "maior ou igual a",
+ "<=": "menor ou igual a",
+ like: "contém",
+ in: "em",
+ "∩": "possui",
+ }
+
+ function formatarFiltroAtivo(entrada: unknown, valor: unknown): string {
+ const [tipo, opcoes] = Array.isArray(entrada)
+ ? (entrada as [string, Record])
+ : [entrada as string, {} as Record]
+
+ if (tipo === "confirmacao") {
+ const opt = opcoes || {}
+ if (valor === true) return (opt.rotuloAtivo as string) || "Sim"
+ if (valor === false) return (opt.rotuloInativo as string) || "Não"
+ return String(valor)
+ }
+
+ if ((tipo === "selecao" || tipo === "selecaoMultipla") && typeof opcoes?.itens === "function") {
+ try {
+ const itensResult = opcoes.itens()
+ if (Array.isArray(itensResult)) {
+ const findLabel = (key: string) => {
+ const found = (itensResult as { chave: string; rotulo: string }[]).find(
+ (item) => String(item.chave) === String(key),
+ )
+ return found ? found.rotulo : key
+ }
+ if (Array.isArray(valor)) {
+ return valor.map((v) => findLabel(String(v))).join(", ")
+ }
+ return findLabel(String(valor))
+ }
+ } catch {
+ // Fallback
+ }
+ }
+
+ if (Array.isArray(valor)) {
+ return valor.map(String).join(", ")
+ }
+ return String(valor)
+ }
+
+ /** Resumo dos filtros ativos para badge e tooltip do cabecalho */
+ const filtrosAtivosResumo = computed(() => {
+ const base = props.tabela.filtroAvancado ?? []
+ const ativos = (filtrosUi.value ?? []).filter((f) => f && f.chave !== undefined)
+ const rotulos: string[] = []
+ for (const f of ativos) {
+ const b = base.find((x) => String(x.chave) === String(f.chave))
+ if (b) {
+ const condicao = b.filtro(f.valor)
+ if (condicao) {
+ const opStr = nomesOperadores[condicao.operador] || condicao.operador
+ const valStr = formatarFiltroAtivo(b.entrada, condicao.valor)
+ rotulos.push(`${b.rotulo} ${opStr} "${valStr}"`)
+ }
+ }
+ }
+ return { quantidade: rotulos.length, rotulos }
+ })
+
return {
filtrosUi,
filtrosFormula,
modalFiltroAberto,
exibirFiltroAvancado,
filtrosAvancadosAtivos,
+ filtrosAtivosResumo,
abrirModalFiltro,
fecharModalFiltro,
limparFiltrosAvancados,
diff --git a/src/componentes/EliTabela/useFiltroAvancado.ts b/src/componentes/EliTabela/useFiltroAvancado.ts
index 010420a..861d7ed 100644
--- a/src/componentes/EliTabela/useFiltroAvancado.ts
+++ b/src/componentes/EliTabela/useFiltroAvancado.ts
@@ -10,8 +10,16 @@
*
* Extraído de `EliTabelaModalFiltroAvancado.vue` para manter o componente abaixo de 250 linhas.
*/
-import { computed, type Ref, ref, watch } from "vue"
-import { EliEntradaDataHora, EliEntradaNumero, EliEntradaTexto } from "../EliEntrada"
+import { type Component, computed, type Ref, ref, watch } from "vue"
+import {
+ EliEntradaConfirmacao,
+ EliEntradaDataHora,
+ EliEntradaNumero,
+ EliEntradaParagrafo,
+ EliEntradaSelecao,
+ EliEntradaSelecaoMultipla,
+ EliEntradaTexto,
+} from "../EliEntrada"
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
@@ -97,10 +105,14 @@ export function useFiltroAvancado({
return (filtrosBase.value ?? []).filter((b) => !usadas.has(String(b.chave)))
})
- function componenteEntrada(entrada: ComponenteEntrada) {
+ function componenteEntrada(entrada: ComponenteEntrada): Component {
const tipo = entrada?.[0]
if (tipo === "numero") return EliEntradaNumero
if (tipo === "dataHora") return EliEntradaDataHora
+ if (tipo === "selecao") return EliEntradaSelecao
+ if (tipo === "selecaoMultipla") return EliEntradaSelecaoMultipla
+ if (tipo === "paragrafo") return EliEntradaParagrafo
+ if (tipo === "confirmacao") return EliEntradaConfirmacao
return EliEntradaTexto
}
@@ -114,6 +126,9 @@ export function useFiltroAvancado({
function valorInicialPorEntrada(entrada: ComponenteEntrada) {
if (entrada?.[0] === "numero") return null
+ if (entrada?.[0] === "selecao") return null
+ if (entrada?.[0] === "selecaoMultipla") return []
+ if (entrada?.[0] === "confirmacao") return false
return ""
}
diff --git a/src/playground/api_simulador/banco.ts b/src/playground/api_simulador/banco.ts
index 0c51243..6ab633f 100644
--- a/src/playground/api_simulador/banco.ts
+++ b/src/playground/api_simulador/banco.ts
@@ -3,6 +3,26 @@ import type { tipoCondicaoFiltroDelta, zFiltroDelta } from "p-comuns"
import type z from "zod"
import { _dados_empreendedores, type tipoEmpreendedores } from "./dados"
+/**
+ * UDF registrada no Alasql para suportar o operador ∩ (interseção).
+ * Verifica se dois arrays possuem ao menos um elemento em comum.
+ */
+alasql.fn.arr_intersecta = (coluna: unknown, filtro: unknown): boolean => {
+ if (!Array.isArray(coluna) || !Array.isArray(filtro)) return false
+ return coluna.some((item) => filtro.includes(item))
+}
+
+/**
+ * UDF universal para o operador "in".
+ * - Coluna escalar: verifica se o valor está na lista de filtro (IN clássico).
+ * - Coluna array: verifica se há interseção entre o array da coluna e a lista de filtro.
+ */
+alasql.fn.arr_in = (coluna: unknown, filtro: unknown): boolean => {
+ const lista = Array.isArray(filtro) ? filtro : [filtro]
+ if (Array.isArray(coluna)) return coluna.some((item) => lista.includes(item))
+ return lista.includes(coluna)
+}
+
const zCondicaoFiltroDeltaParaQuery = ({
condicao,
}: {
@@ -39,14 +59,21 @@ const zCondicaoFiltroDeltaParaQuery = ({
? condicao.valor
: `%${condicao.valor}%`
} else if (condicao.operador === "in") {
- // Alasql usa @(?) para arrays. Porém, o passo 2 substitui {id} por ?.
- // O que resultará em @(?).
- retorno.sql = `${coluna} IN @(${condicao.id})`
+ // UDF universal: funciona tanto para colunas escalares (IN clássico)
+ // quanto para colunas array (interseção).
+ retorno.sql = `arr_in(${coluna}, ${condicao.id}) = TRUE`
retorno.parametro[condicao.id] = Array.isArray(condicao.valor)
? condicao.valor
: String(condicao.valor)
.split(",")
.map((s) => s.trim())
+ } else if (condicao.operador === "∩") {
+ // Interseção: verifica se o array da coluna contém ao menos um dos valores informados.
+ // Como o Alasql não suporta nativamente comparação de arrays, usamos uma UDF.
+ retorno.sql = `arr_intersecta(${coluna}, ${condicao.id}) = TRUE`
+ retorno.parametro[condicao.id] = Array.isArray(condicao.valor)
+ ? condicao.valor
+ : [condicao.valor]
} else {
throw new Error(`Operador inválido: ${condicao.operador as string}`)
}
diff --git a/src/playground/api_simulador/dados.ts b/src/playground/api_simulador/dados.ts
index 37c43ef..431f094 100644
--- a/src/playground/api_simulador/dados.ts
+++ b/src/playground/api_simulador/dados.ts
@@ -1,8 +1,13 @@
import z from "zod"
-let _id = 0
-/** Gera um ID incremental único para uso no seed de dados do playground. Não usar em produção. */
-export const gerarId = () => `item-${_id++}`
+export const zAreaAtuacao = z.enum([
+ "mineracao",
+ "agro",
+ "turismo",
+ "tecnologia",
+ "educacao",
+ "saude",
+])
/** Schema Zod do registro de empreendedor — usado para validação e inferência de tipo no playground. */
export const zEmpreendedores = z.object({
@@ -14,237 +19,21 @@ export const zEmpreendedores = z.object({
telefone: z.string(),
faturamento: z.number(),
peso_kg: z.number(),
- areas_atuacao: z.array(
- z.enum(["mineracao", "agro", "turismo", "tecnologia", "educacao", "saude"]),
- ),
+ ativo: z.boolean(),
+ areas_atuacao: z.array(zAreaAtuacao),
criado_em: z.string(),
atualizado_em: z.string(),
})
+export const AreaAtuacaoDetalhes: Record, string> = {
+ mineracao: "Mineração",
+ agro: "Agropecuária",
+ turismo: "Turismo",
+ tecnologia: "Tecnologia",
+ educacao: "Educação",
+ saude: "Saúde",
+}
+
export type tipoEmpreendedores = z.infer
-// == SIMULAÇÃO DE API (CRUD) ==
-// Dados isolados (simulando um banco de dados no servidor)
-export const _dados_empreendedores: tipoEmpreendedores[] = [
- {
- codigo: gerarId(),
- empreendedor: "Maria Silva",
- empreendimento: "Doces da Maria",
- documento: "12.345.678/0001-90",
- email: "contato.comercial.super.longo@doces-da-maria-exemplo-muito-grande.com.br",
- telefone: "(11) 91234-5678",
- faturamento: 12500.5,
- peso_kg: 12.4,
- areas_atuacao: ["agro", "turismo"],
- criado_em: "2026-01-09T16:15:00Z",
- atualizado_em: "2026-01-29T15:30:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "João Pereira",
- empreendimento: "Café Peregrino",
- documento: "98.765.432/0001-12",
- email: "contato@cafeperegrino.com",
- telefone: "(11) 93456-7810",
- faturamento: 8000,
- peso_kg: 6.2,
- areas_atuacao: ["tecnologia", "agro"],
- criado_em: "2026-01-02T10:00:00-03:00",
- atualizado_em: "2026-01-20T08:10:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Ana Costa",
- empreendimento: "Flor de Sal Gastronomia",
- documento: "45.678.912/0001-55",
- email: "ana@flordesal.com",
- telefone: "(21) 99876-5432",
- faturamento: 3200.75,
- peso_kg: 4.8,
- areas_atuacao: ["educacao"],
- criado_em: "2025-12-10T09:30:00-03:00",
- atualizado_em: "2026-01-15T19:20:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Rafael Lima",
- empreendimento: "Tech Agro Solutions",
- documento: "07.654.321/0001-00",
- email: "rafael@techagro.com",
- telefone: "(31) 94567-8899",
- faturamento: 15999.9,
- peso_kg: 9.1,
- areas_atuacao: ["tecnologia"],
- criado_em: "2026-01-12T12:00:00-03:00",
- atualizado_em: "2026-01-28T11:00:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Juliana Torres",
- empreendimento: "Torres Moda Sustentável",
- documento: "33.210.987/0001-44",
- email: "juliana@torresmoda.com",
- telefone: "(71) 97766-5544",
- faturamento: 4500,
- peso_kg: 3.3,
- areas_atuacao: ["turismo", "agro"],
- criado_em: "2026-01-01T00:00:00Z",
- atualizado_em: "2026-01-10T13:45:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Marcos Vieira",
- empreendimento: "Padaria Pão Quentinho",
- documento: "21.109.876/0001-32",
- email: "marcos@paoquentinho.com",
- telefone: "(48) 99654-3210",
- faturamento: 2200.4,
- peso_kg: 15.7,
- areas_atuacao: ["saude"],
- criado_em: "2026-01-05T07:25:00-03:00",
- atualizado_em: "2026-01-23T20:15:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Bianca Rocha",
- empreendimento: "Studio Beleza em Casa",
- documento: "54.321.098/0001-21",
- email: "contato@belezaemcasa.com",
- telefone: "(85) 98877-1122",
- faturamento: 990,
- peso_kg: 1.9,
- areas_atuacao: ["agro", "tecnologia"],
- criado_em: "2026-01-03T14:00:00-03:00",
- atualizado_em: "2026-01-29T09:00:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Caio Albuquerque",
- empreendimento: "Albuquerque Engenharia Verde",
- documento: "65.432.109/0001-09",
- email: "caio@engenhariaverde.com",
- telefone: "(61) 98123-4567",
- faturamento: 100000,
- peso_kg: 21.3,
- areas_atuacao: ["mineracao"],
- criado_em: "2025-11-18T16:10:00-03:00",
- atualizado_em: "2026-01-25T10:40:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Fernanda Almeida",
- empreendimento: "Faz Bem Produtos Naturais",
- documento: "87.654.210/0001-98",
- email: "fernanda@fazbem.com",
- telefone: "(41) 99777-6655",
- faturamento: 5600.2,
- peso_kg: 8.6,
- areas_atuacao: ["mineracao", "turismo"],
- criado_em: "2026-01-08T12:12:00-03:00",
- atualizado_em: "2026-01-21T17:55:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Gabriel Martins",
- empreendimento: "Martins Tech Repair",
- documento: "19.876.543/0001-76",
- email: "suporte@martinstech.com",
- telefone: "(19) 98888-9090",
- faturamento: 7550,
- peso_kg: 7.7,
- areas_atuacao: ["mineracao", "turismo"],
- criado_em: "2026-01-11T11:00:00-03:00",
- atualizado_em: "2026-01-18T11:30:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Helena Duarte",
- empreendimento: "Ateliê Costura Criativa",
- documento: "23.456.789/0001-65",
- email: "helena@costuracriativa.com",
- telefone: "(51) 98765-4433",
- faturamento: 2700,
- peso_kg: 2.5,
- areas_atuacao: ["turismo", "mineracao"],
- criado_em: "2026-01-07T08:00:00-03:00",
- atualizado_em: "2026-01-16T14:10:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Igor Santos",
- empreendimento: "Santos Consultoria Financeira",
- documento: "43.219.876/0001-54",
- email: "igor@santosconsultoria.com",
- telefone: "(31) 99332-1100",
- faturamento: 12000,
- peso_kg: 5.4,
- areas_atuacao: ["saude"],
- criado_em: "2026-01-06T10:30:00-03:00",
- atualizado_em: "2026-01-22T18:30:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Jéssica Nunes",
- empreendimento: "Nunes Eventos & Decoração",
- documento: "09.876.543/0001-33",
- email: "jessica@nunesdecora.com",
- telefone: "(62) 99922-3344",
- faturamento: 3300.1,
- peso_kg: 4.1,
- areas_atuacao: ["turismo"],
- criado_em: "2026-01-04T13:30:00-03:00",
- atualizado_em: "2026-01-26T09:20:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Leonardo Prado",
- empreendimento: "Prado Bike Courier",
- documento: "72.345.098/0001-27",
- email: "contato@pradobike.com",
- telefone: "(47) 98444-6677",
- faturamento: 4100,
- peso_kg: 6.8,
- areas_atuacao: ["mineracao"],
- criado_em: "2026-01-02T18:40:00-03:00",
- atualizado_em: "2026-01-27T12:00:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Marina Lopes",
- empreendimento: "Lopes Design Digital",
- documento: "56.789.012/0001-11",
- email: "marina@lopesdesign.com",
- telefone: "(27) 99911-2233",
- faturamento: 2100,
- peso_kg: 3.7,
- areas_atuacao: ["agro"],
- criado_em: "2026-01-09T09:00:00-03:00",
- atualizado_em: "2026-01-19T15:35:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Nicolas Teixeira",
- empreendimento: "QualiVida Fitness",
- documento: "34.567.890/0001-02",
- email: "nicolas@qualivida.com",
- telefone: "(92) 99456-7788",
- faturamento: 8600.9,
- peso_kg: 11.2,
- areas_atuacao: ["agro", "saude"],
- criado_em: "2026-01-12T19:15:00-03:00",
- atualizado_em: "2026-01-29T10:50:00-03:00",
- },
- {
- codigo: gerarId(),
- empreendedor: "Olívia Azevedo",
- empreendimento: "Azevedo Pet Care",
- documento: "88.901.234/0001-45",
- email: "olivia@petcare.com",
- telefone: "(16) 99788-6655",
- faturamento: 1900.3,
- peso_kg: 2.2,
- areas_atuacao: ["agro"],
- criado_em: "2026-01-10T15:00:00-03:00",
- atualizado_em: "2026-01-14T16:00:00-03:00",
- },
-]
+export { _dados_empreendedores } from "./dadosSeed"
diff --git a/src/playground/api_simulador/dadosSeed.ts b/src/playground/api_simulador/dadosSeed.ts
new file mode 100644
index 0000000..6189b98
--- /dev/null
+++ b/src/playground/api_simulador/dadosSeed.ts
@@ -0,0 +1,249 @@
+/** dadosSeed — dados de seed (mock) para o playground da EliTabela. */
+import type { tipoEmpreendedores } from "./dados"
+
+let _id = 0
+/** Gera um ID incremental único para uso no seed de dados do playground. Não usar em produção. */
+export const gerarId = () => `item-${_id++}`
+
+// == SIMULAÇÃO DE API (CRUD) ==
+// Dados isolados (simulando um banco de dados no servidor)
+export const _dados_empreendedores: tipoEmpreendedores[] = [
+ {
+ codigo: gerarId(),
+ empreendedor: "Maria Silva",
+ empreendimento: "Doces da Maria",
+ documento: "12.345.678/0001-90",
+ email: "contato.comercial.super.longo@doces-da-maria-exemplo-muito-grande.com.br",
+ telefone: "(11) 91234-5678",
+ faturamento: 12500.5,
+ peso_kg: 12.4,
+ ativo: true,
+ areas_atuacao: ["agro", "turismo"],
+ criado_em: "2026-01-09T16:15:00Z",
+ atualizado_em: "2026-01-29T15:30:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "João Pereira",
+ empreendimento: "Café Peregrino",
+ documento: "98.765.432/0001-12",
+ email: "contato@cafeperegrino.com",
+ telefone: "(11) 93456-7810",
+ faturamento: 8000,
+ peso_kg: 6.2,
+ ativo: true,
+ areas_atuacao: ["tecnologia", "agro"],
+ criado_em: "2026-01-02T10:00:00-03:00",
+ atualizado_em: "2026-01-20T08:10:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Ana Costa",
+ empreendimento: "Flor de Sal Gastronomia",
+ documento: "45.678.912/0001-55",
+ email: "ana@flordesal.com",
+ telefone: "(21) 99876-5432",
+ faturamento: 3200.75,
+ peso_kg: 4.8,
+ ativo: false,
+ areas_atuacao: ["educacao"],
+ criado_em: "2025-12-10T09:30:00-03:00",
+ atualizado_em: "2026-01-15T19:20:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Rafael Lima",
+ empreendimento: "Tech Agro Solutions",
+ documento: "07.654.321/0001-00",
+ email: "rafael@techagro.com",
+ telefone: "(31) 94567-8899",
+ faturamento: 15999.9,
+ peso_kg: 9.1,
+ ativo: true,
+ areas_atuacao: ["tecnologia"],
+ criado_em: "2026-01-12T12:00:00-03:00",
+ atualizado_em: "2026-01-28T11:00:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Juliana Torres",
+ empreendimento: "Torres Moda Sustentável",
+ documento: "33.210.987/0001-44",
+ email: "juliana@torresmoda.com",
+ telefone: "(71) 97766-5544",
+ faturamento: 4500,
+ peso_kg: 3.3,
+ ativo: true,
+ areas_atuacao: ["turismo", "agro"],
+ criado_em: "2026-01-01T00:00:00Z",
+ atualizado_em: "2026-01-10T13:45:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Marcos Vieira",
+ empreendimento: "Padaria Pão Quentinho",
+ documento: "21.109.876/0001-32",
+ email: "marcos@paoquentinho.com",
+ telefone: "(48) 99654-3210",
+ faturamento: 2200.4,
+ peso_kg: 15.7,
+ ativo: false,
+ areas_atuacao: ["saude"],
+ criado_em: "2026-01-05T07:25:00-03:00",
+ atualizado_em: "2026-01-23T20:15:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Bianca Rocha",
+ empreendimento: "Studio Beleza em Casa",
+ documento: "54.321.098/0001-21",
+ email: "contato@belezaemcasa.com",
+ telefone: "(85) 98877-1122",
+ faturamento: 990,
+ peso_kg: 1.9,
+ ativo: true,
+ areas_atuacao: ["agro", "tecnologia"],
+ criado_em: "2026-01-03T14:00:00-03:00",
+ atualizado_em: "2026-01-29T09:00:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Caio Albuquerque",
+ empreendimento: "Albuquerque Engenharia Verde",
+ documento: "65.432.109/0001-09",
+ email: "caio@engenhariaverde.com",
+ telefone: "(61) 98123-4567",
+ faturamento: 100000,
+ peso_kg: 21.3,
+ ativo: true,
+ areas_atuacao: ["mineracao"],
+ criado_em: "2025-11-18T16:10:00-03:00",
+ atualizado_em: "2026-01-25T10:40:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Fernanda Almeida",
+ empreendimento: "Faz Bem Produtos Naturais",
+ documento: "87.654.210/0001-98",
+ email: "fernanda@fazbem.com",
+ telefone: "(41) 99777-6655",
+ faturamento: 5600.2,
+ peso_kg: 8.6,
+ ativo: false,
+ areas_atuacao: ["mineracao", "turismo"],
+ criado_em: "2026-01-08T12:12:00-03:00",
+ atualizado_em: "2026-01-21T17:55:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Gabriel Martins",
+ empreendimento: "Martins Tech Repair",
+ documento: "19.876.543/0001-76",
+ email: "suporte@martinstech.com",
+ telefone: "(19) 98888-9090",
+ faturamento: 7550,
+ peso_kg: 7.7,
+ ativo: true,
+ areas_atuacao: ["mineracao", "turismo"],
+ criado_em: "2026-01-11T11:00:00-03:00",
+ atualizado_em: "2026-01-18T11:30:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Helena Duarte",
+ empreendimento: "Ateliê Costura Criativa",
+ documento: "23.456.789/0001-65",
+ email: "helena@costuracriativa.com",
+ telefone: "(51) 98765-4433",
+ faturamento: 2700,
+ peso_kg: 2.5,
+ ativo: true,
+ areas_atuacao: ["turismo", "mineracao"],
+ criado_em: "2026-01-07T08:00:00-03:00",
+ atualizado_em: "2026-01-16T14:10:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Igor Santos",
+ empreendimento: "Santos Consultoria Financeira",
+ documento: "43.219.876/0001-54",
+ email: "igor@santosconsultoria.com",
+ telefone: "(31) 99332-1100",
+ faturamento: 12000,
+ peso_kg: 5.4,
+ ativo: false,
+ areas_atuacao: ["saude"],
+ criado_em: "2026-01-06T10:30:00-03:00",
+ atualizado_em: "2026-01-22T18:30:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Jéssica Nunes",
+ empreendimento: "Nunes Eventos & Decoração",
+ documento: "09.876.543/0001-33",
+ email: "jessica@nunesdecora.com",
+ telefone: "(62) 99922-3344",
+ faturamento: 3300.1,
+ peso_kg: 4.1,
+ ativo: true,
+ areas_atuacao: ["turismo"],
+ criado_em: "2026-01-04T13:30:00-03:00",
+ atualizado_em: "2026-01-26T09:20:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Leonardo Prado",
+ empreendimento: "Prado Bike Courier",
+ documento: "72.345.098/0001-27",
+ email: "contato@pradobike.com",
+ telefone: "(47) 98444-6677",
+ faturamento: 4100,
+ peso_kg: 6.8,
+ ativo: true,
+ areas_atuacao: ["mineracao"],
+ criado_em: "2026-01-02T18:40:00-03:00",
+ atualizado_em: "2026-01-27T12:00:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Marina Lopes",
+ empreendimento: "Lopes Design Digital",
+ documento: "56.789.012/0001-11",
+ email: "marina@lopesdesign.com",
+ telefone: "(27) 99911-2233",
+ faturamento: 2100,
+ peso_kg: 3.7,
+ ativo: false,
+ areas_atuacao: ["agro"],
+ criado_em: "2026-01-09T09:00:00-03:00",
+ atualizado_em: "2026-01-19T15:35:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Nicolas Teixeira",
+ empreendimento: "QualiVida Fitness",
+ documento: "34.567.890/0001-02",
+ email: "nicolas@qualivida.com",
+ telefone: "(92) 99456-7788",
+ faturamento: 8600.9,
+ peso_kg: 11.2,
+ ativo: true,
+ areas_atuacao: ["agro", "saude"],
+ criado_em: "2026-01-12T19:15:00-03:00",
+ atualizado_em: "2026-01-29T10:50:00-03:00",
+ },
+ {
+ codigo: gerarId(),
+ empreendedor: "Olívia Azevedo",
+ empreendimento: "Azevedo Pet Care",
+ documento: "88.901.234/0001-45",
+ email: "olivia@petcare.com",
+ telefone: "(16) 99788-6655",
+ faturamento: 1900.3,
+ peso_kg: 2.2,
+ ativo: true,
+ areas_atuacao: ["agro"],
+ criado_em: "2026-01-10T15:00:00-03:00",
+ atualizado_em: "2026-01-14T16:00:00-03:00",
+ },
+]
diff --git a/src/playground/api_simulador/index.ts b/src/playground/api_simulador/index.ts
index 18406e7..4ca5193 100644
--- a/src/playground/api_simulador/index.ts
+++ b/src/playground/api_simulador/index.ts
@@ -39,6 +39,7 @@ const api_criar: tipoFuncaoApiSimulacao<"criar"> = async (dados) => {
telefone: dados.telefone || "(00) 0000-0000",
faturamento: dados.faturamento || 0,
peso_kg: dados.peso_kg || 0,
+ ativo: dados.ativo ?? true,
areas_atuacao: dados.areas_atuacao || [],
criado_em: new Date().toISOString(),
atualizado_em: new Date().toISOString(),
diff --git a/src/playground/useTabelaPlayground.ts b/src/playground/useTabelaPlayground.ts
index 165d9b1..4f08838 100644
--- a/src/playground/useTabelaPlayground.ts
+++ b/src/playground/useTabelaPlayground.ts
@@ -37,6 +37,7 @@ export function useTabelaPlayground() {
telefone: "",
faturamento: 0,
peso_kg: 0,
+ ativo: true,
areas_atuacao: [],
})
const erroCadastro = ref(undefined)
@@ -54,6 +55,7 @@ export function useTabelaPlayground() {
telefone: "",
faturamento: 0,
peso_kg: 0,
+ ativo: true,
areas_atuacao: [],
}
erroCadastro.value = undefined
diff --git a/src/playground/useTabelaPlaygroundConfig.ts b/src/playground/useTabelaPlaygroundConfig.ts
index 8ebc242..30f2f9e 100644
--- a/src/playground/useTabelaPlaygroundConfig.ts
+++ b/src/playground/useTabelaPlaygroundConfig.ts
@@ -6,15 +6,22 @@
* @see useTabelaPlayground.ts — composable que consome estas configurações
*/
import { BadgeCheck, Eye, Pencil, Trash2 } from "@lucide/vue"
-import { iniciarFiltroDelta } from "p-comuns"
import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas"
import type { tipoEliTabelaConsulta } from "@/componentes/EliTabela"
import { celulaTabela } from "@/componentes/EliTabela"
-import type { tipoEmpreendedores } from "./api_simulador/dados"
+import { AreaAtuacaoDetalhes, type tipoEmpreendedores } from "./api_simulador/dados"
-const { criarFiltroDelta } = iniciarFiltroDelta>()
+/** Mapeamento de cor por área de atuação para uso nos chips da tabela */
+const corAreaAtuacao: Record = {
+ mineracao: "#78909C",
+ agro: "#4CAF50",
+ turismo: "#FF9800",
+ tecnologia: "#2196F3",
+ educacao: "#9C27B0",
+ saude: "#E91E63",
+}
/** Ações por linha usadas nas 3 instâncias de tabela do playground */
export function criarAcoesLinha(
@@ -79,6 +86,17 @@ export const colunasTabela: tipoEliTabelaConsulta["colunas"]
coluna_ordem: "telefone",
visivel: true,
},
+ {
+ rotulo: "Áreas de Atuação",
+ celula: (l) =>
+ celulaTabela("tags", {
+ opcoes: l.areas_atuacao.map((area) => ({
+ rotulo: AreaAtuacaoDetalhes[area],
+ cor: corAreaAtuacao[area],
+ })),
+ }),
+ visivel: true,
+ },
{
rotulo: "Faturamento",
celula: (l) =>
@@ -134,12 +152,7 @@ export const filtrosAvancados: tipoEliTabelaConsulta["filtro
rotulo: "Empreendedor",
filtro: (valor: unknown) => {
if (!valor) return undefined
- return criarFiltroDelta({
- condicoes: [
- { id: "{c1}", coluna: "empreendedor", operador: "like", valor: valor as string },
- ],
- formula: "{c1}",
- })
+ return { coluna: "empreendedor", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendedor" }] as ComponenteEntrada,
},
@@ -148,12 +161,7 @@ export const filtrosAvancados: tipoEliTabelaConsulta["filtro
rotulo: "Empreendimento",
filtro: (valor: unknown) => {
if (!valor) return undefined
- return criarFiltroDelta({
- condicoes: [
- { id: "{c1}", coluna: "empreendimento", operador: "like", valor: valor as string },
- ],
- formula: "{c1}",
- })
+ return { coluna: "empreendimento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendimento" }] as ComponenteEntrada,
},
@@ -162,10 +170,7 @@ export const filtrosAvancados: tipoEliTabelaConsulta["filtro
rotulo: "Documento",
filtro: (valor: unknown) => {
if (!valor) return undefined
- return criarFiltroDelta({
- condicoes: [{ id: "{c1}", coluna: "documento", operador: "like", valor: valor as string }],
- formula: "{c1}",
- })
+ return { coluna: "documento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as ComponenteEntrada,
},
@@ -174,13 +179,65 @@ export const filtrosAvancados: tipoEliTabelaConsulta["filtro
rotulo: "E-mail",
filtro: (valor: unknown) => {
if (!valor) return undefined
- return criarFiltroDelta({
- condicoes: [{ id: "{c1}", coluna: "email", operador: "like", valor: valor as string }],
- formula: "{c1}",
- })
+ return { coluna: "email", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "E-mail", formato: "email" }] as ComponenteEntrada,
},
+ {
+ chave: "ativo",
+ rotulo: "Ativo",
+ filtro: (valor: unknown) => {
+ return { coluna: "ativo", operador: "=" as const, valor: valor as boolean }
+ },
+ entrada: [
+ "confirmacao",
+ { rotulo: "Ativo", rotuloAtivo: "Apenas ativos", rotuloInativo: "Apenas inativos" },
+ ] as ComponenteEntrada,
+ },
+ {
+ chave: "faturamento_min",
+ rotulo: "Faturamento mínimo",
+ filtro: (valor: unknown) => {
+ if (valor === null || valor === undefined) return undefined
+ return { coluna: "faturamento", operador: ">=" as const, valor: valor as number }
+ },
+ entrada: ["numero", { rotulo: "Faturamento mínimo (R$)" }] as ComponenteEntrada,
+ },
+ {
+ chave: "areas_atuacao",
+ rotulo: "Área de Atuação",
+ filtro: (valor: unknown) => {
+ const arr = valor as string[]
+ if (!arr.length) return undefined
+ return { coluna: "areas_atuacao", operador: "∩" as const, valor: arr }
+ },
+ entrada: [
+ "selecaoMultipla",
+ {
+ rotulo: "Área de Atuação",
+ limpavel: true,
+ itens: () =>
+ Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
+ },
+ ] as ComponenteEntrada,
+ },
+ {
+ chave: "areas_atuacao2",
+ rotulo: "Área de Atuação (2)",
+ filtro: (valor: unknown) => {
+ if (!valor) return undefined
+ return { coluna: "areas_atuacao", operador: "in" as const, valor }
+ },
+ entrada: [
+ "selecao",
+ {
+ rotulo: "Área de Atuação",
+ limpavel: true,
+ itens: () =>
+ Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
+ },
+ ] as ComponenteEntrada,
+ },
]
/**