@luanpdd/kit-mcp 1.33.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +168 -168
- package/gates/agent-no-recursive-dispatch.md +84 -84
- package/kit/COMANDOS.md +138 -138
- package/kit/COMPATIBILITY.md +70 -70
- package/kit/README.md +76 -76
- package/kit/agents/advisor-researcher.md +109 -109
- package/kit/agents/ai-mutation-tester.md +289 -289
- package/kit/agents/assumptions-analyzer.md +110 -110
- package/kit/agents/audit-log-implementer.md +314 -314
- package/kit/agents/auditor-consistencia-isolamento.md +414 -414
- package/kit/agents/b2b-saas-architect.md +157 -157
- package/kit/agents/burn-rate-forecaster.md +153 -153
- package/kit/agents/cascading-failures-auditor.md +299 -299
- package/kit/agents/codebase-mapper.md +769 -769
- package/kit/agents/crm-pipeline-implementer.md +257 -257
- package/kit/agents/debugger.md +814 -814
- package/kit/agents/designer-ui.md +216 -216
- package/kit/agents/detector-tenant-quente.md +338 -338
- package/kit/agents/evolution-go-integrator.md +201 -201
- package/kit/agents/example-reviewer.md +22 -22
- package/kit/agents/executor.md +565 -565
- package/kit/agents/golden-signals-instrumenter.md +232 -232
- package/kit/agents/incident-investigator.md +238 -238
- package/kit/agents/integration-checker.md +203 -203
- package/kit/agents/invite-flow-implementer.md +190 -190
- package/kit/agents/legacy-characterizer.md +369 -369
- package/kit/agents/lgpd-compliance-auditor.md +296 -296
- package/kit/agents/load-shedding-instrumenter.md +290 -290
- package/kit/agents/multi-tenant-isolation-auditor.md +254 -254
- package/kit/agents/multi-tenant-rls-writer.md +341 -341
- package/kit/agents/nyquist-auditor.md +181 -181
- package/kit/agents/observability-coverage-auditor.md +316 -316
- package/kit/agents/observability-instrumenter.md +191 -191
- package/kit/agents/omm-auditor.md +291 -291
- package/kit/agents/org-onboarding-implementer.md +224 -224
- package/kit/agents/payload-capture-instrumenter.md +274 -274
- package/kit/agents/phase-researcher.md +697 -697
- package/kit/agents/plan-checker.md +275 -275
- package/kit/agents/planner.md +923 -923
- package/kit/agents/postmortem-writer.md +273 -273
- package/kit/agents/project-researcher.md +653 -653
- package/kit/agents/prr-conductor.md +287 -287
- package/kit/agents/refactor-safety-auditor.md +405 -405
- package/kit/agents/release-pipeline-auditor.md +364 -364
- package/kit/agents/research-synthesizer.md +246 -246
- package/kit/agents/roadmapper.md +678 -678
- package/kit/agents/schema-checker.md +160 -160
- package/kit/agents/seam-finder.md +360 -360
- package/kit/agents/shotgun-surgery-detector.md +350 -350
- package/kit/agents/slo-engineer.md +217 -217
- package/kit/agents/storytelling-analyst.md +300 -300
- package/kit/agents/supabase-architect.md +249 -249
- package/kit/agents/supabase-auth-bootstrapper.md +400 -400
- package/kit/agents/supabase-auth-hook-writer.md +418 -418
- package/kit/agents/supabase-branching-architect.md +563 -563
- package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -778
- package/kit/agents/supabase-column-privileges-writer.md +400 -400
- package/kit/agents/supabase-edge-fn-tester.md +288 -288
- package/kit/agents/supabase-edge-fn-writer.md +341 -341
- package/kit/agents/supabase-mfa-implementer.md +439 -439
- package/kit/agents/supabase-migration-writer.md +386 -386
- package/kit/agents/supabase-oauth-server-implementer.md +507 -507
- package/kit/agents/supabase-rbac-implementer.md +393 -393
- package/kit/agents/supabase-realtime-implementer.md +364 -364
- package/kit/agents/supabase-rls-hardener.md +522 -522
- package/kit/agents/supabase-rls-writer.md +324 -324
- package/kit/agents/supabase-roles-implementer.md +356 -356
- package/kit/agents/supabase-social-auth-implementer.md +451 -451
- package/kit/agents/supabase-sso-saml-architect.md +549 -549
- package/kit/agents/supabase-storage-implementer.md +407 -407
- package/kit/agents/super-admin-implementer.md +282 -282
- package/kit/agents/toil-auditor.md +268 -268
- package/kit/agents/ui-auditor.md +438 -438
- package/kit/agents/ui-checker.md +305 -305
- package/kit/agents/ui-researcher.md +356 -356
- package/kit/agents/user-profiler.md +176 -176
- package/kit/agents/validador-evolucao-schema.md +336 -336
- package/kit/agents/verifier.md +729 -729
- package/kit/agents/workflow-generator.md +167 -0
- package/kit/commands/adicionar-backlog.md +75 -75
- package/kit/commands/adicionar-fase.md +42 -42
- package/kit/commands/adicionar-tarefa.md +45 -45
- package/kit/commands/adicionar-testes.md +41 -41
- package/kit/commands/ajuda.md +21 -21
- package/kit/commands/atualizar.md +37 -37
- package/kit/commands/auditar-cascading.md +111 -111
- package/kit/commands/auditar-marco.md +179 -179
- package/kit/commands/auditar-observabilidade-cobertura-workflow.md +121 -0
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
- package/kit/commands/auditar-refactor.md +219 -219
- package/kit/commands/auditar-release.md +109 -109
- package/kit/commands/auditar-uat.md +23 -23
- package/kit/commands/autonomo.md +40 -40
- package/kit/commands/branch-pr.md +24 -24
- package/kit/commands/burn-rate-status.md +408 -408
- package/kit/commands/capturar-payloads.md +193 -193
- package/kit/commands/caracterizar.md +212 -212
- package/kit/commands/concluir-marco.md +247 -247
- package/kit/commands/configuracoes.md +36 -36
- package/kit/commands/criar-workflow.md +158 -0
- package/kit/commands/dados-distribuidos.md +188 -188
- package/kit/commands/definir-perfil.md +10 -10
- package/kit/commands/depurar.md +190 -190
- package/kit/commands/detectar-duplicacao.md +197 -197
- package/kit/commands/discutir-fase.md +131 -131
- package/kit/commands/encontrar-seams.md +136 -136
- package/kit/commands/entrar-discord.md +17 -17
- package/kit/commands/estatisticas.md +18 -18
- package/kit/commands/example-greeting.md +33 -33
- package/kit/commands/executar-fase.md +58 -58
- package/kit/commands/expresso.md +56 -56
- package/kit/commands/fase-ui.md +34 -34
- package/kit/commands/fazer.md +57 -57
- package/kit/commands/fio.md +125 -125
- package/kit/commands/fluxos-trabalho.md +64 -64
- package/kit/commands/forense.md +176 -176
- package/kit/commands/gerenciador.md +38 -38
- package/kit/commands/inserir-fase.md +31 -31
- package/kit/commands/legacy.md +263 -263
- package/kit/commands/limpeza.md +17 -17
- package/kit/commands/listar-hipoteses-fase.md +45 -45
- package/kit/commands/listar-workspaces.md +18 -18
- package/kit/commands/load-shedding.md +117 -117
- package/kit/commands/mapear-codebase.md +70 -70
- package/kit/commands/multi-tenant.md +163 -163
- package/kit/commands/nota.md +33 -33
- package/kit/commands/novo-marco.md +43 -43
- package/kit/commands/novo-projeto.md +41 -41
- package/kit/commands/novo-workspace.md +43 -43
- package/kit/commands/pausar-trabalho.md +37 -37
- package/kit/commands/perfil-usuario.md +45 -45
- package/kit/commands/pesquisar-fase.md +195 -195
- package/kit/commands/planejar-fase.md +67 -67
- package/kit/commands/planejar-lacunas.md +33 -33
- package/kit/commands/plantar-ideia.md +25 -25
- package/kit/commands/progresso.md +24 -24
- package/kit/commands/proximo.md +30 -30
- package/kit/commands/publicar.md +490 -490
- package/kit/commands/rapido.md +35 -35
- package/kit/commands/reaplicar-patches.md +124 -124
- package/kit/commands/refactor-seguro.md +321 -321
- package/kit/commands/relatorio-sessao.md +19 -19
- package/kit/commands/remover-fase.md +31 -31
- package/kit/commands/remover-workspace.md +26 -26
- package/kit/commands/resumo-marco.md +50 -50
- package/kit/commands/retomar-trabalho.md +40 -40
- package/kit/commands/revisar-backlog.md +60 -60
- package/kit/commands/revisar-ui.md +32 -32
- package/kit/commands/revisar.md +37 -37
- package/kit/commands/saude.md +21 -21
- package/kit/commands/setup-notion.md +93 -93
- package/kit/commands/storytelling.md +179 -179
- package/kit/commands/supabase.md +238 -238
- package/kit/commands/sync-main.md +68 -68
- package/kit/commands/validar-fase.md +35 -35
- package/kit/commands/verificar-tarefas.md +44 -44
- package/kit/commands/verificar-trabalho.md +64 -64
- package/kit/file-manifest.json +424 -419
- package/kit/framework/bin/lib/commands.cjs +959 -959
- package/kit/framework/bin/lib/config.cjs +442 -442
- package/kit/framework/bin/lib/core.cjs +1230 -1230
- package/kit/framework/bin/lib/frontmatter.cjs +336 -336
- package/kit/framework/bin/lib/init.cjs +1442 -1442
- package/kit/framework/bin/lib/milestone.cjs +252 -252
- package/kit/framework/bin/lib/model-profiles.cjs +68 -68
- package/kit/framework/bin/lib/phase.cjs +888 -888
- package/kit/framework/bin/lib/profile-output.cjs +952 -952
- package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
- package/kit/framework/bin/lib/roadmap.cjs +329 -329
- package/kit/framework/bin/lib/security.cjs +382 -382
- package/kit/framework/bin/lib/state.cjs +1031 -1031
- package/kit/framework/bin/lib/template.cjs +222 -222
- package/kit/framework/bin/lib/uat.cjs +282 -282
- package/kit/framework/bin/lib/verify.cjs +888 -888
- package/kit/framework/bin/lib/workstream.cjs +491 -491
- package/kit/framework/bin/tools.cjs +918 -918
- package/kit/framework/commands/workstreams.md +63 -63
- package/kit/framework/references/checkpoints.md +778 -778
- package/kit/framework/references/continuation-format.md +249 -249
- package/kit/framework/references/decimal-phase-calculation.md +64 -64
- package/kit/framework/references/git-integration.md +295 -295
- package/kit/framework/references/git-planning-commit.md +38 -38
- package/kit/framework/references/model-profile-resolution.md +36 -36
- package/kit/framework/references/model-profiles.md +139 -139
- package/kit/framework/references/phase-argument-parsing.md +61 -61
- package/kit/framework/references/planning-config.md +202 -202
- package/kit/framework/references/questioning.md +162 -162
- package/kit/framework/references/tdd.md +263 -263
- package/kit/framework/references/ui-brand.md +160 -160
- package/kit/framework/references/user-profiling.md +657 -657
- package/kit/framework/references/verification-patterns.md +612 -612
- package/kit/framework/references/workstream-flag.md +58 -58
- package/kit/framework/templates/DEBUG.md +164 -164
- package/kit/framework/templates/UAT.md +265 -265
- package/kit/framework/templates/UI-SPEC.md +100 -100
- package/kit/framework/templates/VALIDATION.md +76 -76
- package/kit/framework/templates/claude-md.md +122 -122
- package/kit/framework/templates/codebase/architecture.md +185 -185
- package/kit/framework/templates/codebase/concerns.md +205 -205
- package/kit/framework/templates/codebase/conventions.md +204 -204
- package/kit/framework/templates/codebase/integrations.md +192 -192
- package/kit/framework/templates/codebase/stack.md +158 -158
- package/kit/framework/templates/codebase/structure.md +199 -199
- package/kit/framework/templates/codebase/testing.md +301 -301
- package/kit/framework/templates/config.json +44 -44
- package/kit/framework/templates/context.md +352 -352
- package/kit/framework/templates/continue-here.md +78 -78
- package/kit/framework/templates/copilot-instructions.md +7 -7
- package/kit/framework/templates/debug-subagent-prompt.md +91 -91
- package/kit/framework/templates/dev-preferences.md +20 -20
- package/kit/framework/templates/discovery.md +146 -146
- package/kit/framework/templates/discussion-log.md +63 -63
- package/kit/framework/templates/milestone-archive.md +123 -123
- package/kit/framework/templates/milestone.md +115 -115
- package/kit/framework/templates/phase-prompt.md +610 -610
- package/kit/framework/templates/planner-subagent-prompt.md +117 -117
- package/kit/framework/templates/project.md +186 -186
- package/kit/framework/templates/requirements.md +231 -231
- package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
- package/kit/framework/templates/research-project/FEATURES.md +147 -147
- package/kit/framework/templates/research-project/PITFALLS.md +200 -200
- package/kit/framework/templates/research-project/STACK.md +120 -120
- package/kit/framework/templates/research-project/SUMMARY.md +170 -170
- package/kit/framework/templates/research.md +419 -419
- package/kit/framework/templates/retrospective.md +54 -54
- package/kit/framework/templates/roadmap.md +202 -202
- package/kit/framework/templates/state.md +176 -176
- package/kit/framework/templates/summary-complex.md +59 -59
- package/kit/framework/templates/summary-minimal.md +41 -41
- package/kit/framework/templates/summary-standard.md +48 -48
- package/kit/framework/templates/summary.md +209 -209
- package/kit/framework/templates/user-profile.md +146 -146
- package/kit/framework/templates/user-setup.md +256 -256
- package/kit/framework/templates/verification-report.md +258 -258
- package/kit/framework/workflows/add-phase.md +112 -112
- package/kit/framework/workflows/add-tests.md +351 -351
- package/kit/framework/workflows/add-todo.md +158 -158
- package/kit/framework/workflows/audit-milestone.md +340 -340
- package/kit/framework/workflows/audit-uat.md +109 -109
- package/kit/framework/workflows/autonomous.md +891 -891
- package/kit/framework/workflows/check-todos.md +177 -177
- package/kit/framework/workflows/cleanup.md +152 -152
- package/kit/framework/workflows/complete-milestone.md +696 -696
- package/kit/framework/workflows/diagnose-issues.md +231 -231
- package/kit/framework/workflows/discovery-phase.md +289 -289
- package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
- package/kit/framework/workflows/discuss-phase.md +784 -784
- package/kit/framework/workflows/do.md +104 -104
- package/kit/framework/workflows/execute-phase.md +838 -838
- package/kit/framework/workflows/execute-plan.md +510 -510
- package/kit/framework/workflows/fast.md +102 -102
- package/kit/framework/workflows/forensics.md +265 -265
- package/kit/framework/workflows/health.md +181 -181
- package/kit/framework/workflows/help.md +619 -619
- package/kit/framework/workflows/insert-phase.md +130 -130
- package/kit/framework/workflows/list-phase-assumptions.md +178 -178
- package/kit/framework/workflows/list-workspaces.md +56 -56
- package/kit/framework/workflows/manager.md +362 -362
- package/kit/framework/workflows/map-codebase.md +377 -377
- package/kit/framework/workflows/milestone-summary.md +223 -223
- package/kit/framework/workflows/new-milestone.md +486 -486
- package/kit/framework/workflows/new-project.md +1159 -1159
- package/kit/framework/workflows/new-workspace.md +237 -237
- package/kit/framework/workflows/next.md +97 -97
- package/kit/framework/workflows/node-repair.md +92 -92
- package/kit/framework/workflows/note.md +156 -156
- package/kit/framework/workflows/pause-work.md +176 -176
- package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
- package/kit/framework/workflows/plan-phase.md +765 -765
- package/kit/framework/workflows/plant-seed.md +169 -169
- package/kit/framework/workflows/pr-branch.md +129 -129
- package/kit/framework/workflows/profile-user.md +450 -450
- package/kit/framework/workflows/progress.md +507 -507
- package/kit/framework/workflows/quick.md +757 -757
- package/kit/framework/workflows/remove-phase.md +155 -155
- package/kit/framework/workflows/remove-workspace.md +90 -90
- package/kit/framework/workflows/research-phase.md +82 -82
- package/kit/framework/workflows/resume-project.md +326 -326
- package/kit/framework/workflows/review.md +228 -228
- package/kit/framework/workflows/session-report.md +146 -146
- package/kit/framework/workflows/settings.md +283 -283
- package/kit/framework/workflows/ship.md +228 -228
- package/kit/framework/workflows/stats.md +60 -60
- package/kit/framework/workflows/transition.md +671 -671
- package/kit/framework/workflows/ui-phase.md +302 -302
- package/kit/framework/workflows/ui-review.md +165 -165
- package/kit/framework/workflows/update.md +323 -323
- package/kit/framework/workflows/validate-phase.md +174 -174
- package/kit/framework/workflows/verify-phase.md +252 -252
- package/kit/framework/workflows/verify-work.md +637 -637
- package/kit/hooks/check-update.js +118 -118
- package/kit/hooks/context-monitor.js +163 -163
- package/kit/hooks/kit-attribution-reminder.cjs +92 -92
- package/kit/hooks/kit-router.cjs +137 -137
- package/kit/hooks/prompt-guard.js +103 -103
- package/kit/hooks/statusline.js +125 -125
- package/kit/hooks/workflow-guard.js +101 -101
- package/kit/settings.json +45 -45
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
- package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
- package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
- package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
- package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
- package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
- package/kit/skills/dynamic-workflow-authoring/SKILL.md +223 -0
- package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
- package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
- package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
- package/kit/skills/example-skill/SKILL.md +42 -42
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
- package/kit/skills/legacy-extract-class/SKILL.md +203 -203
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
- package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
- package/kit/skills/member-invite-flow/SKILL.md +305 -305
- package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
- package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
- package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
- package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
- package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
- package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
- package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
- package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
- package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
- package/kit/skills/supabase-auth-hardening/SKILL.md +674 -674
- package/kit/skills/supabase-auth-hooks/SKILL.md +875 -875
- package/kit/skills/supabase-auth-methods/SKILL.md +486 -486
- package/kit/skills/supabase-auth-sessions/SKILL.md +579 -579
- package/kit/skills/supabase-auth-ssr/SKILL.md +306 -306
- package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
- package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
- package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
- package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
- package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
- package/kit/skills/supabase-edge-functions/SKILL.md +330 -330
- package/kit/skills/supabase-edge-functions-auth/SKILL.md +309 -309
- package/kit/skills/supabase-edge-functions-limits/SKILL.md +302 -302
- package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +279 -279
- package/kit/skills/supabase-edge-functions-testing/SKILL.md +277 -277
- package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +357 -357
- package/kit/skills/supabase-enterprise-sso-saml/SKILL.md +545 -545
- package/kit/skills/supabase-jwt-signing-keys/SKILL.md +399 -399
- package/kit/skills/supabase-mfa/SKILL.md +488 -488
- package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
- package/kit/skills/supabase-migrations/SKILL.md +297 -297
- package/kit/skills/supabase-oauth-server/SKILL.md +537 -537
- package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
- package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
- package/kit/skills/supabase-realtime/SKILL.md +460 -460
- package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
- package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
- package/kit/skills/supabase-social-oauth/SKILL.md +480 -480
- package/kit/skills/supabase-third-party-auth/SKILL.md +450 -450
- package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
- package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
- package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
- package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
- package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
- package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
- package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
- package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
- package/kit/skills/ui-tipografia/SKILL.md +211 -211
- package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
- package/kit/workflows/auditar-observabilidade-cobertura.workflow.js +250 -0
- package/package.json +65 -63
- package/src/core/kit.js +333 -216
- package/src/core/reflect.js +247 -247
- package/src/core/registry.js +123 -112
- package/src/core/reverse-sync.js +448 -372
- package/src/core/sync.js +477 -437
- package/src/core/watch.js +121 -121
- package/src/mcp-server/index.js +794 -794
|
@@ -1,343 +1,343 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: crm-lead-pipeline-patterns
|
|
3
|
-
description: Use ao implementar CRM lead pipeline em B2B SaaS Supabase — 6 stages canônicos lead→qualified→proposal→negotiation→won|lost, trigger PG BEFORE UPDATE valida transições (CHECK cons…
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# CRM Lead Pipeline — Patterns Canônicos
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill ao implementar CRM lead pipeline em B2B multi-tenant. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "CRM lead pipeline", "sales pipeline stages"
|
|
13
|
-
- "lead state machine Postgres", "transition validation"
|
|
14
|
-
- "ownership transfer lead", "lead assignment"
|
|
15
|
-
- "lead dedup phone email"
|
|
16
|
-
- "integração WhatsApp CRM lead"
|
|
17
|
-
|
|
18
|
-
## Regras absolutas
|
|
19
|
-
|
|
20
|
-
**REGRA #1 (6 stages canônicos):** Pipeline tem 6 stages: `lead → qualified → proposal → negotiation → won | lost`. Custom stages permitidos via prefix `custom_*` mas estes 6 são obrigatórios.
|
|
21
|
-
|
|
22
|
-
**REGRA #2 (trigger PG > CHECK constraint):** Validar transições via **trigger BEFORE UPDATE** com `RAISE EXCEPTION`, não apenas CHECK constraint. CHECK valida valor, mas não valida **transição** (lead → won direto = bug, deve passar por qualified+proposal+negotiation).
|
|
23
|
-
|
|
24
|
-
**REGRA #3 (ownership transfer com audit):** Mudança em `leads.owner_id` SEMPRE dispara: (a) notificação ao novo owner, (b) entry em audit_logs com `previous_owner_id, new_owner_id, reason`. Trigger AFTER UPDATE.
|
|
25
|
-
|
|
26
|
-
**REGRA #4 (dedup unique constraints):** `unique(org_id, contact_phone)` + `unique(org_id, contact_email)` em `leads`. Insert duplicado falha — app code precisa fazer lookup ANTES.
|
|
27
|
-
|
|
28
|
-
**REGRA #5 (lookup ANTES de criar via WhatsApp):** Webhook handler WhatsApp inbound: `SELECT id FROM leads WHERE org_id=$1 AND contact_phone=$2`. Se existe, append message à conversa do lead. Se não existe, criar lead novo com `source='whatsapp_inbound'`.
|
|
29
|
-
|
|
30
|
-
## Patterns canônicos
|
|
31
|
-
|
|
32
|
-
### Tabela `leads`
|
|
33
|
-
|
|
34
|
-
```sql
|
|
35
|
-
create table public.leads (
|
|
36
|
-
id uuid primary key default gen_random_uuid(),
|
|
37
|
-
org_id uuid not null references public.organizations(id) on delete cascade,
|
|
38
|
-
dept_id uuid references public.departments(id) on delete set null,
|
|
39
|
-
|
|
40
|
-
-- Contato
|
|
41
|
-
contact_name text not null,
|
|
42
|
-
contact_email text,
|
|
43
|
-
contact_phone text,
|
|
44
|
-
contact_company text,
|
|
45
|
-
|
|
46
|
-
-- Pipeline
|
|
47
|
-
stage text not null default 'lead'
|
|
48
|
-
check (stage in ('lead', 'qualified', 'proposal', 'negotiation', 'won', 'lost')
|
|
49
|
-
or stage like 'custom\_%'),
|
|
50
|
-
source text, -- 'whatsapp_inbound', 'website_form', 'manual', etc.
|
|
51
|
-
|
|
52
|
-
-- Ownership
|
|
53
|
-
owner_id uuid references auth.users(id) on delete set null,
|
|
54
|
-
|
|
55
|
-
-- Dados financeiros
|
|
56
|
-
expected_value numeric(12, 2),
|
|
57
|
-
expected_close_date date,
|
|
58
|
-
closed_at timestamptz,
|
|
59
|
-
closed_reason text,
|
|
60
|
-
|
|
61
|
-
-- Metadata
|
|
62
|
-
metadata jsonb not null default '{}'::jsonb,
|
|
63
|
-
created_at timestamptz not null default now(),
|
|
64
|
-
updated_at timestamptz not null default now(),
|
|
65
|
-
|
|
66
|
-
-- REGRA #4: dedup
|
|
67
|
-
unique (org_id, contact_phone),
|
|
68
|
-
unique (org_id, contact_email)
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
create index leads_org_stage_idx on public.leads (org_id, stage);
|
|
72
|
-
create index leads_org_owner_idx on public.leads (org_id, owner_id) where owner_id is not null;
|
|
73
|
-
create index leads_org_dept_idx on public.leads (org_id, dept_id) where dept_id is not null;
|
|
74
|
-
|
|
75
|
-
-- RLS: aplicar pattern multi-tenant-rls-hierarchy
|
|
76
|
-
alter table public.leads enable row level security;
|
|
77
|
-
|
|
78
|
-
create policy "leads_select_member" on public.leads
|
|
79
|
-
for select to authenticated
|
|
80
|
-
using (private.is_member_of(org_id));
|
|
81
|
-
|
|
82
|
-
create policy "leads_insert_with_permission" on public.leads
|
|
83
|
-
for insert to authenticated
|
|
84
|
-
with check (private.has_permission('create', 'leads', org_id));
|
|
85
|
-
|
|
86
|
-
create policy "leads_update_with_permission_or_owner" on public.leads
|
|
87
|
-
for update to authenticated
|
|
88
|
-
using (
|
|
89
|
-
private.has_permission('update', 'leads', org_id)
|
|
90
|
-
or owner_id = (select auth.uid())
|
|
91
|
-
)
|
|
92
|
-
with check (
|
|
93
|
-
private.has_permission('update', 'leads', org_id)
|
|
94
|
-
or owner_id = (select auth.uid())
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
create policy "leads_delete_admin" on public.leads
|
|
98
|
-
for delete to authenticated
|
|
99
|
-
using (private.has_role(org_id, 'admin') or private.has_role(org_id, 'owner'));
|
|
100
|
-
|
|
101
|
-
create policy "leads_super_admin_bypass" on public.leads
|
|
102
|
-
as permissive for all to authenticated
|
|
103
|
-
using (private.is_super_admin())
|
|
104
|
-
with check (private.is_super_admin());
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Trigger validação de transição (REGRA #2)
|
|
108
|
-
|
|
109
|
-
```sql
|
|
110
|
-
-- Tabela de transições permitidas (data-driven)
|
|
111
|
-
create table public.lead_stage_transitions (
|
|
112
|
-
from_stage text not null,
|
|
113
|
-
to_stage text not null,
|
|
114
|
-
primary key (from_stage, to_stage)
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
-- Insert transições canônicas
|
|
118
|
-
insert into public.lead_stage_transitions (from_stage, to_stage) values
|
|
119
|
-
('lead', 'qualified'),
|
|
120
|
-
('lead', 'lost'),
|
|
121
|
-
('qualified', 'proposal'),
|
|
122
|
-
('qualified', 'lost'),
|
|
123
|
-
('proposal', 'negotiation'),
|
|
124
|
-
('proposal', 'lost'),
|
|
125
|
-
('negotiation', 'won'),
|
|
126
|
-
('negotiation', 'lost'),
|
|
127
|
-
('negotiation', 'proposal'), -- back-step permitido
|
|
128
|
-
('won', 'closed'),
|
|
129
|
-
('lost', 'lead'), -- reativar lost
|
|
130
|
-
-- self-transition (no-op) sempre permitida
|
|
131
|
-
('lead', 'lead'), ('qualified', 'qualified'), ('proposal', 'proposal'),
|
|
132
|
-
('negotiation', 'negotiation'), ('won', 'won'), ('lost', 'lost')
|
|
133
|
-
on conflict do nothing;
|
|
134
|
-
|
|
135
|
-
-- Trigger BEFORE UPDATE valida transição
|
|
136
|
-
create or replace function private.validate_lead_stage_transition()
|
|
137
|
-
returns trigger
|
|
138
|
-
language plpgsql
|
|
139
|
-
security invoker
|
|
140
|
-
set search_path = ''
|
|
141
|
-
as $$
|
|
142
|
-
begin
|
|
143
|
-
if new.stage = old.stage then
|
|
144
|
-
return new; -- no-op
|
|
145
|
-
end if;
|
|
146
|
-
|
|
147
|
-
-- Custom stages: aceitar qualquer transição (admin responsibility)
|
|
148
|
-
if new.stage like 'custom\_%' or old.stage like 'custom\_%' then
|
|
149
|
-
return new;
|
|
150
|
-
end if;
|
|
151
|
-
|
|
152
|
-
-- Validar transição na tabela
|
|
153
|
-
if not exists (
|
|
154
|
-
select 1 from public.lead_stage_transitions
|
|
155
|
-
where from_stage = old.stage and to_stage = new.stage
|
|
156
|
-
) then
|
|
157
|
-
raise exception 'invalid_lead_transition: % → % not allowed', old.stage, new.stage;
|
|
158
|
-
end if;
|
|
159
|
-
|
|
160
|
-
-- Auto-popular closed_at em won/lost
|
|
161
|
-
if new.stage in ('won', 'lost') and new.closed_at is null then
|
|
162
|
-
new.closed_at := now();
|
|
163
|
-
end if;
|
|
164
|
-
|
|
165
|
-
return new;
|
|
166
|
-
end;
|
|
167
|
-
$$;
|
|
168
|
-
|
|
169
|
-
create trigger validate_lead_stage_transition_trigger
|
|
170
|
-
before update of stage on public.leads
|
|
171
|
-
for each row execute function private.validate_lead_stage_transition();
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Trigger ownership transfer (REGRA #3)
|
|
175
|
-
|
|
176
|
-
```sql
|
|
177
|
-
create or replace function private.audit_lead_ownership_change()
|
|
178
|
-
returns trigger
|
|
179
|
-
language plpgsql
|
|
180
|
-
security definer -- precisa escrever em audit_logs mesmo sem permission do user
|
|
181
|
-
set search_path = ''
|
|
182
|
-
as $$
|
|
183
|
-
begin
|
|
184
|
-
if old.owner_id is distinct from new.owner_id then
|
|
185
|
-
-- Audit log
|
|
186
|
-
perform private.audit_log(
|
|
187
|
-
'custom_lead_ownership_transfer',
|
|
188
|
-
new.org_id,
|
|
189
|
-
new.id, 'lead', null,
|
|
190
|
-
jsonb_build_object(
|
|
191
|
-
'previous_owner_id', old.owner_id,
|
|
192
|
-
'new_owner_id', new.owner_id,
|
|
193
|
-
'lead_stage', new.stage,
|
|
194
|
-
'lead_value', new.expected_value
|
|
195
|
-
)
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
-- TODO: notificar novo owner (delegar para Edge Function de notification)
|
|
199
|
-
-- perform net.http_post('<edge_fn_url>', ...);
|
|
200
|
-
end if;
|
|
201
|
-
return new;
|
|
202
|
-
end;
|
|
203
|
-
$$;
|
|
204
|
-
|
|
205
|
-
create trigger audit_lead_ownership_change_trigger
|
|
206
|
-
after update of owner_id on public.leads
|
|
207
|
-
for each row execute function private.audit_lead_ownership_change();
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Lookup contact → lead (integração WhatsApp — REGRA #5)
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
// supabase/functions/whatsapp-webhook/index.ts (cross-ref Phase 112)
|
|
214
|
-
import { createClient } from 'jsr:@supabase/supabase-js@2'
|
|
215
|
-
|
|
216
|
-
async function handleInboundWhatsApp(orgId: string, contactPhone: string, contactName: string, content: string) {
|
|
217
|
-
const admin = createClient(
|
|
218
|
-
Deno.env.get('SUPABASE_URL')!,
|
|
219
|
-
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
// REGRA #5: lookup ANTES de criar
|
|
223
|
-
const { data: existingLead } = await admin
|
|
224
|
-
.from('leads')
|
|
225
|
-
.select('id, owner_id, stage')
|
|
226
|
-
.eq('org_id', orgId)
|
|
227
|
-
.eq('contact_phone', contactPhone)
|
|
228
|
-
.maybeSingle()
|
|
229
|
-
|
|
230
|
-
if (existingLead) {
|
|
231
|
-
// Append message à conversa existente do lead (não criar novo)
|
|
232
|
-
return existingLead
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Criar lead novo
|
|
236
|
-
const { data: newLead } = await admin
|
|
237
|
-
.from('leads')
|
|
238
|
-
.insert({
|
|
239
|
-
org_id: orgId,
|
|
240
|
-
contact_phone: contactPhone,
|
|
241
|
-
contact_name: contactName,
|
|
242
|
-
source: 'whatsapp_inbound',
|
|
243
|
-
stage: 'lead',
|
|
244
|
-
metadata: { first_message: content, channel: 'whatsapp' }
|
|
245
|
-
})
|
|
246
|
-
.select()
|
|
247
|
-
.single()
|
|
248
|
-
|
|
249
|
-
return newLead
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Frontend kanban — drag&drop entre stages
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
// LeadsKanban.tsx (sketch para Phase 115)
|
|
257
|
-
async function moveLead(leadId: string, toStage: string) {
|
|
258
|
-
const { error } = await supabase
|
|
259
|
-
.from('leads')
|
|
260
|
-
.update({ stage: toStage })
|
|
261
|
-
.eq('id', leadId)
|
|
262
|
-
|
|
263
|
-
if (error?.message.includes('invalid_lead_transition')) {
|
|
264
|
-
toast.error('Transição inválida — siga ordem do funil')
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Anti-patterns
|
|
270
|
-
|
|
271
|
-
### Anti-pattern 1: Apenas CHECK constraint (sem trigger)
|
|
272
|
-
|
|
273
|
-
**Errado:**
|
|
274
|
-
```sql
|
|
275
|
-
stage text check (stage in ('lead', 'qualified', 'proposal', 'negotiation', 'won', 'lost'))
|
|
276
|
-
-- Update lead diretamente lead → won (sem passar pelos intermediários)
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
**Por quê:** CHECK valida valor final, não transição. Lead pula etapas → métricas erradas (conversion rate por stage), forecasting quebrado.
|
|
280
|
-
|
|
281
|
-
**Certo:** REGRA #2 — trigger BEFORE UPDATE valida `lead_stage_transitions`.
|
|
282
|
-
|
|
283
|
-
### Anti-pattern 2: Ownership transfer sem notification
|
|
284
|
-
|
|
285
|
-
**Errado:**
|
|
286
|
-
```sql
|
|
287
|
-
update leads set owner_id = '<new_owner>' where id = '<lead>';
|
|
288
|
-
-- Owner antigo não sabe que perdeu lead, novo não sabe que ganhou
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
**Por quê:** transferência silenciosa = lead "esquecido", ninguém follow up, SLA perdido.
|
|
292
|
-
|
|
293
|
-
**Certo:** REGRA #3 — trigger AFTER UPDATE dispara notification (Slack, email, in-app) via Edge Function.
|
|
294
|
-
|
|
295
|
-
### Anti-pattern 3: Lead duplicate sem dedup
|
|
296
|
-
|
|
297
|
-
**Errado:**
|
|
298
|
-
```sql
|
|
299
|
-
-- Sem unique constraints
|
|
300
|
-
-- WhatsApp inbound + website form mesmo phone = 2 leads
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
**Por quê:** vendedor liga 2× mesmo contato, dashboard com count errado, embaraçoso para client.
|
|
304
|
-
|
|
305
|
-
**Certo:** REGRA #4 — `unique(org_id, contact_phone)` + lookup before insert (REGRA #5).
|
|
306
|
-
|
|
307
|
-
### Anti-pattern 4: Hard delete lead com pipeline activities órfãs
|
|
308
|
-
|
|
309
|
-
**Errado:**
|
|
310
|
-
```sql
|
|
311
|
-
delete from public.leads where id = '<lead_id>';
|
|
312
|
-
-- pipeline_activities (FK lead_id) ficam órfãs ou cascade deleta histórico
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
**Por quê:** atividades históricas perdidas = audit trail compromised + analytics afetada.
|
|
316
|
-
|
|
317
|
-
**Certo:** soft delete (`status = 'archived'`) ou FK CASCADE com cuidado + audit log antes.
|
|
318
|
-
|
|
319
|
-
## Prevenção de Lost Update em Stage Transition (v1.22+)
|
|
320
|
-
|
|
321
|
-
> A trigger `validate_lead_stage_transition` deve usar `SELECT ... FOR UPDATE` em rows lidas para prevenir lost update quando 2 reps tentam mover o mesmo lead simultaneamente. Padrão completo em [`postgres-isolamento-concorrencia`](../postgres-isolamento-concorrencia/SKILL.md) (v1.22 — DDIA Ch 7).
|
|
322
|
-
|
|
323
|
-
Exemplo aplicado:
|
|
324
|
-
|
|
325
|
-
```sql
|
|
326
|
-
CREATE OR REPLACE FUNCTION validate_lead_stage_transition()
|
|
327
|
-
RETURNS TRIGGER AS $$
|
|
328
|
-
BEGIN
|
|
329
|
-
-- Lock row para prevenir lost update
|
|
330
|
-
PERFORM 1 FROM leads WHERE id = NEW.id FOR UPDATE;
|
|
331
|
-
-- ... validação ...
|
|
332
|
-
END;
|
|
333
|
-
$$ LANGUAGE plpgsql;
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
## Ver também
|
|
337
|
-
|
|
338
|
-
- [b2b-saas-architecture](../b2b-saas-architecture/SKILL.md) — schema base
|
|
339
|
-
- [multi-tenant-rls-hierarchy](../multi-tenant-rls-hierarchy/SKILL.md) — RLS policies
|
|
340
|
-
- [evolution-go-whatsapp-integration](../evolution-go-whatsapp-integration/SKILL.md) — Phase 112, integração inbound
|
|
341
|
-
- [whatsapp-conversation-state-machine](../whatsapp-conversation-state-machine/SKILL.md) — Phase 112, conversa.action_taken → lead
|
|
342
|
-
- [audit-log-multi-tenant](../audit-log-multi-tenant/SKILL.md) — Phase 109, eventos `custom_lead_*`
|
|
343
|
-
- [_shared-multi-tenant/glossary.md](../_shared-multi-tenant/glossary.md) — `lead`, `stages canônicos`, `ownership transfer`, `lead dedup`
|
|
1
|
+
---
|
|
2
|
+
name: crm-lead-pipeline-patterns
|
|
3
|
+
description: Use ao implementar CRM lead pipeline em B2B SaaS Supabase — 6 stages canônicos lead→qualified→proposal→negotiation→won|lost, trigger PG BEFORE UPDATE valida transições (CHECK cons…
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CRM Lead Pipeline — Patterns Canônicos
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill ao implementar CRM lead pipeline em B2B multi-tenant. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "CRM lead pipeline", "sales pipeline stages"
|
|
13
|
+
- "lead state machine Postgres", "transition validation"
|
|
14
|
+
- "ownership transfer lead", "lead assignment"
|
|
15
|
+
- "lead dedup phone email"
|
|
16
|
+
- "integração WhatsApp CRM lead"
|
|
17
|
+
|
|
18
|
+
## Regras absolutas
|
|
19
|
+
|
|
20
|
+
**REGRA #1 (6 stages canônicos):** Pipeline tem 6 stages: `lead → qualified → proposal → negotiation → won | lost`. Custom stages permitidos via prefix `custom_*` mas estes 6 são obrigatórios.
|
|
21
|
+
|
|
22
|
+
**REGRA #2 (trigger PG > CHECK constraint):** Validar transições via **trigger BEFORE UPDATE** com `RAISE EXCEPTION`, não apenas CHECK constraint. CHECK valida valor, mas não valida **transição** (lead → won direto = bug, deve passar por qualified+proposal+negotiation).
|
|
23
|
+
|
|
24
|
+
**REGRA #3 (ownership transfer com audit):** Mudança em `leads.owner_id` SEMPRE dispara: (a) notificação ao novo owner, (b) entry em audit_logs com `previous_owner_id, new_owner_id, reason`. Trigger AFTER UPDATE.
|
|
25
|
+
|
|
26
|
+
**REGRA #4 (dedup unique constraints):** `unique(org_id, contact_phone)` + `unique(org_id, contact_email)` em `leads`. Insert duplicado falha — app code precisa fazer lookup ANTES.
|
|
27
|
+
|
|
28
|
+
**REGRA #5 (lookup ANTES de criar via WhatsApp):** Webhook handler WhatsApp inbound: `SELECT id FROM leads WHERE org_id=$1 AND contact_phone=$2`. Se existe, append message à conversa do lead. Se não existe, criar lead novo com `source='whatsapp_inbound'`.
|
|
29
|
+
|
|
30
|
+
## Patterns canônicos
|
|
31
|
+
|
|
32
|
+
### Tabela `leads`
|
|
33
|
+
|
|
34
|
+
```sql
|
|
35
|
+
create table public.leads (
|
|
36
|
+
id uuid primary key default gen_random_uuid(),
|
|
37
|
+
org_id uuid not null references public.organizations(id) on delete cascade,
|
|
38
|
+
dept_id uuid references public.departments(id) on delete set null,
|
|
39
|
+
|
|
40
|
+
-- Contato
|
|
41
|
+
contact_name text not null,
|
|
42
|
+
contact_email text,
|
|
43
|
+
contact_phone text,
|
|
44
|
+
contact_company text,
|
|
45
|
+
|
|
46
|
+
-- Pipeline
|
|
47
|
+
stage text not null default 'lead'
|
|
48
|
+
check (stage in ('lead', 'qualified', 'proposal', 'negotiation', 'won', 'lost')
|
|
49
|
+
or stage like 'custom\_%'),
|
|
50
|
+
source text, -- 'whatsapp_inbound', 'website_form', 'manual', etc.
|
|
51
|
+
|
|
52
|
+
-- Ownership
|
|
53
|
+
owner_id uuid references auth.users(id) on delete set null,
|
|
54
|
+
|
|
55
|
+
-- Dados financeiros
|
|
56
|
+
expected_value numeric(12, 2),
|
|
57
|
+
expected_close_date date,
|
|
58
|
+
closed_at timestamptz,
|
|
59
|
+
closed_reason text,
|
|
60
|
+
|
|
61
|
+
-- Metadata
|
|
62
|
+
metadata jsonb not null default '{}'::jsonb,
|
|
63
|
+
created_at timestamptz not null default now(),
|
|
64
|
+
updated_at timestamptz not null default now(),
|
|
65
|
+
|
|
66
|
+
-- REGRA #4: dedup
|
|
67
|
+
unique (org_id, contact_phone),
|
|
68
|
+
unique (org_id, contact_email)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
create index leads_org_stage_idx on public.leads (org_id, stage);
|
|
72
|
+
create index leads_org_owner_idx on public.leads (org_id, owner_id) where owner_id is not null;
|
|
73
|
+
create index leads_org_dept_idx on public.leads (org_id, dept_id) where dept_id is not null;
|
|
74
|
+
|
|
75
|
+
-- RLS: aplicar pattern multi-tenant-rls-hierarchy
|
|
76
|
+
alter table public.leads enable row level security;
|
|
77
|
+
|
|
78
|
+
create policy "leads_select_member" on public.leads
|
|
79
|
+
for select to authenticated
|
|
80
|
+
using (private.is_member_of(org_id));
|
|
81
|
+
|
|
82
|
+
create policy "leads_insert_with_permission" on public.leads
|
|
83
|
+
for insert to authenticated
|
|
84
|
+
with check (private.has_permission('create', 'leads', org_id));
|
|
85
|
+
|
|
86
|
+
create policy "leads_update_with_permission_or_owner" on public.leads
|
|
87
|
+
for update to authenticated
|
|
88
|
+
using (
|
|
89
|
+
private.has_permission('update', 'leads', org_id)
|
|
90
|
+
or owner_id = (select auth.uid())
|
|
91
|
+
)
|
|
92
|
+
with check (
|
|
93
|
+
private.has_permission('update', 'leads', org_id)
|
|
94
|
+
or owner_id = (select auth.uid())
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
create policy "leads_delete_admin" on public.leads
|
|
98
|
+
for delete to authenticated
|
|
99
|
+
using (private.has_role(org_id, 'admin') or private.has_role(org_id, 'owner'));
|
|
100
|
+
|
|
101
|
+
create policy "leads_super_admin_bypass" on public.leads
|
|
102
|
+
as permissive for all to authenticated
|
|
103
|
+
using (private.is_super_admin())
|
|
104
|
+
with check (private.is_super_admin());
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Trigger validação de transição (REGRA #2)
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
-- Tabela de transições permitidas (data-driven)
|
|
111
|
+
create table public.lead_stage_transitions (
|
|
112
|
+
from_stage text not null,
|
|
113
|
+
to_stage text not null,
|
|
114
|
+
primary key (from_stage, to_stage)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
-- Insert transições canônicas
|
|
118
|
+
insert into public.lead_stage_transitions (from_stage, to_stage) values
|
|
119
|
+
('lead', 'qualified'),
|
|
120
|
+
('lead', 'lost'),
|
|
121
|
+
('qualified', 'proposal'),
|
|
122
|
+
('qualified', 'lost'),
|
|
123
|
+
('proposal', 'negotiation'),
|
|
124
|
+
('proposal', 'lost'),
|
|
125
|
+
('negotiation', 'won'),
|
|
126
|
+
('negotiation', 'lost'),
|
|
127
|
+
('negotiation', 'proposal'), -- back-step permitido
|
|
128
|
+
('won', 'closed'),
|
|
129
|
+
('lost', 'lead'), -- reativar lost
|
|
130
|
+
-- self-transition (no-op) sempre permitida
|
|
131
|
+
('lead', 'lead'), ('qualified', 'qualified'), ('proposal', 'proposal'),
|
|
132
|
+
('negotiation', 'negotiation'), ('won', 'won'), ('lost', 'lost')
|
|
133
|
+
on conflict do nothing;
|
|
134
|
+
|
|
135
|
+
-- Trigger BEFORE UPDATE valida transição
|
|
136
|
+
create or replace function private.validate_lead_stage_transition()
|
|
137
|
+
returns trigger
|
|
138
|
+
language plpgsql
|
|
139
|
+
security invoker
|
|
140
|
+
set search_path = ''
|
|
141
|
+
as $$
|
|
142
|
+
begin
|
|
143
|
+
if new.stage = old.stage then
|
|
144
|
+
return new; -- no-op
|
|
145
|
+
end if;
|
|
146
|
+
|
|
147
|
+
-- Custom stages: aceitar qualquer transição (admin responsibility)
|
|
148
|
+
if new.stage like 'custom\_%' or old.stage like 'custom\_%' then
|
|
149
|
+
return new;
|
|
150
|
+
end if;
|
|
151
|
+
|
|
152
|
+
-- Validar transição na tabela
|
|
153
|
+
if not exists (
|
|
154
|
+
select 1 from public.lead_stage_transitions
|
|
155
|
+
where from_stage = old.stage and to_stage = new.stage
|
|
156
|
+
) then
|
|
157
|
+
raise exception 'invalid_lead_transition: % → % not allowed', old.stage, new.stage;
|
|
158
|
+
end if;
|
|
159
|
+
|
|
160
|
+
-- Auto-popular closed_at em won/lost
|
|
161
|
+
if new.stage in ('won', 'lost') and new.closed_at is null then
|
|
162
|
+
new.closed_at := now();
|
|
163
|
+
end if;
|
|
164
|
+
|
|
165
|
+
return new;
|
|
166
|
+
end;
|
|
167
|
+
$$;
|
|
168
|
+
|
|
169
|
+
create trigger validate_lead_stage_transition_trigger
|
|
170
|
+
before update of stage on public.leads
|
|
171
|
+
for each row execute function private.validate_lead_stage_transition();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Trigger ownership transfer (REGRA #3)
|
|
175
|
+
|
|
176
|
+
```sql
|
|
177
|
+
create or replace function private.audit_lead_ownership_change()
|
|
178
|
+
returns trigger
|
|
179
|
+
language plpgsql
|
|
180
|
+
security definer -- precisa escrever em audit_logs mesmo sem permission do user
|
|
181
|
+
set search_path = ''
|
|
182
|
+
as $$
|
|
183
|
+
begin
|
|
184
|
+
if old.owner_id is distinct from new.owner_id then
|
|
185
|
+
-- Audit log
|
|
186
|
+
perform private.audit_log(
|
|
187
|
+
'custom_lead_ownership_transfer',
|
|
188
|
+
new.org_id,
|
|
189
|
+
new.id, 'lead', null,
|
|
190
|
+
jsonb_build_object(
|
|
191
|
+
'previous_owner_id', old.owner_id,
|
|
192
|
+
'new_owner_id', new.owner_id,
|
|
193
|
+
'lead_stage', new.stage,
|
|
194
|
+
'lead_value', new.expected_value
|
|
195
|
+
)
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
-- TODO: notificar novo owner (delegar para Edge Function de notification)
|
|
199
|
+
-- perform net.http_post('<edge_fn_url>', ...);
|
|
200
|
+
end if;
|
|
201
|
+
return new;
|
|
202
|
+
end;
|
|
203
|
+
$$;
|
|
204
|
+
|
|
205
|
+
create trigger audit_lead_ownership_change_trigger
|
|
206
|
+
after update of owner_id on public.leads
|
|
207
|
+
for each row execute function private.audit_lead_ownership_change();
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Lookup contact → lead (integração WhatsApp — REGRA #5)
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// supabase/functions/whatsapp-webhook/index.ts (cross-ref Phase 112)
|
|
214
|
+
import { createClient } from 'jsr:@supabase/supabase-js@2'
|
|
215
|
+
|
|
216
|
+
async function handleInboundWhatsApp(orgId: string, contactPhone: string, contactName: string, content: string) {
|
|
217
|
+
const admin = createClient(
|
|
218
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
219
|
+
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
// REGRA #5: lookup ANTES de criar
|
|
223
|
+
const { data: existingLead } = await admin
|
|
224
|
+
.from('leads')
|
|
225
|
+
.select('id, owner_id, stage')
|
|
226
|
+
.eq('org_id', orgId)
|
|
227
|
+
.eq('contact_phone', contactPhone)
|
|
228
|
+
.maybeSingle()
|
|
229
|
+
|
|
230
|
+
if (existingLead) {
|
|
231
|
+
// Append message à conversa existente do lead (não criar novo)
|
|
232
|
+
return existingLead
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Criar lead novo
|
|
236
|
+
const { data: newLead } = await admin
|
|
237
|
+
.from('leads')
|
|
238
|
+
.insert({
|
|
239
|
+
org_id: orgId,
|
|
240
|
+
contact_phone: contactPhone,
|
|
241
|
+
contact_name: contactName,
|
|
242
|
+
source: 'whatsapp_inbound',
|
|
243
|
+
stage: 'lead',
|
|
244
|
+
metadata: { first_message: content, channel: 'whatsapp' }
|
|
245
|
+
})
|
|
246
|
+
.select()
|
|
247
|
+
.single()
|
|
248
|
+
|
|
249
|
+
return newLead
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Frontend kanban — drag&drop entre stages
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// LeadsKanban.tsx (sketch para Phase 115)
|
|
257
|
+
async function moveLead(leadId: string, toStage: string) {
|
|
258
|
+
const { error } = await supabase
|
|
259
|
+
.from('leads')
|
|
260
|
+
.update({ stage: toStage })
|
|
261
|
+
.eq('id', leadId)
|
|
262
|
+
|
|
263
|
+
if (error?.message.includes('invalid_lead_transition')) {
|
|
264
|
+
toast.error('Transição inválida — siga ordem do funil')
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Anti-patterns
|
|
270
|
+
|
|
271
|
+
### Anti-pattern 1: Apenas CHECK constraint (sem trigger)
|
|
272
|
+
|
|
273
|
+
**Errado:**
|
|
274
|
+
```sql
|
|
275
|
+
stage text check (stage in ('lead', 'qualified', 'proposal', 'negotiation', 'won', 'lost'))
|
|
276
|
+
-- Update lead diretamente lead → won (sem passar pelos intermediários)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Por quê:** CHECK valida valor final, não transição. Lead pula etapas → métricas erradas (conversion rate por stage), forecasting quebrado.
|
|
280
|
+
|
|
281
|
+
**Certo:** REGRA #2 — trigger BEFORE UPDATE valida `lead_stage_transitions`.
|
|
282
|
+
|
|
283
|
+
### Anti-pattern 2: Ownership transfer sem notification
|
|
284
|
+
|
|
285
|
+
**Errado:**
|
|
286
|
+
```sql
|
|
287
|
+
update leads set owner_id = '<new_owner>' where id = '<lead>';
|
|
288
|
+
-- Owner antigo não sabe que perdeu lead, novo não sabe que ganhou
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Por quê:** transferência silenciosa = lead "esquecido", ninguém follow up, SLA perdido.
|
|
292
|
+
|
|
293
|
+
**Certo:** REGRA #3 — trigger AFTER UPDATE dispara notification (Slack, email, in-app) via Edge Function.
|
|
294
|
+
|
|
295
|
+
### Anti-pattern 3: Lead duplicate sem dedup
|
|
296
|
+
|
|
297
|
+
**Errado:**
|
|
298
|
+
```sql
|
|
299
|
+
-- Sem unique constraints
|
|
300
|
+
-- WhatsApp inbound + website form mesmo phone = 2 leads
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Por quê:** vendedor liga 2× mesmo contato, dashboard com count errado, embaraçoso para client.
|
|
304
|
+
|
|
305
|
+
**Certo:** REGRA #4 — `unique(org_id, contact_phone)` + lookup before insert (REGRA #5).
|
|
306
|
+
|
|
307
|
+
### Anti-pattern 4: Hard delete lead com pipeline activities órfãs
|
|
308
|
+
|
|
309
|
+
**Errado:**
|
|
310
|
+
```sql
|
|
311
|
+
delete from public.leads where id = '<lead_id>';
|
|
312
|
+
-- pipeline_activities (FK lead_id) ficam órfãs ou cascade deleta histórico
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Por quê:** atividades históricas perdidas = audit trail compromised + analytics afetada.
|
|
316
|
+
|
|
317
|
+
**Certo:** soft delete (`status = 'archived'`) ou FK CASCADE com cuidado + audit log antes.
|
|
318
|
+
|
|
319
|
+
## Prevenção de Lost Update em Stage Transition (v1.22+)
|
|
320
|
+
|
|
321
|
+
> A trigger `validate_lead_stage_transition` deve usar `SELECT ... FOR UPDATE` em rows lidas para prevenir lost update quando 2 reps tentam mover o mesmo lead simultaneamente. Padrão completo em [`postgres-isolamento-concorrencia`](../postgres-isolamento-concorrencia/SKILL.md) (v1.22 — DDIA Ch 7).
|
|
322
|
+
|
|
323
|
+
Exemplo aplicado:
|
|
324
|
+
|
|
325
|
+
```sql
|
|
326
|
+
CREATE OR REPLACE FUNCTION validate_lead_stage_transition()
|
|
327
|
+
RETURNS TRIGGER AS $$
|
|
328
|
+
BEGIN
|
|
329
|
+
-- Lock row para prevenir lost update
|
|
330
|
+
PERFORM 1 FROM leads WHERE id = NEW.id FOR UPDATE;
|
|
331
|
+
-- ... validação ...
|
|
332
|
+
END;
|
|
333
|
+
$$ LANGUAGE plpgsql;
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Ver também
|
|
337
|
+
|
|
338
|
+
- [b2b-saas-architecture](../b2b-saas-architecture/SKILL.md) — schema base
|
|
339
|
+
- [multi-tenant-rls-hierarchy](../multi-tenant-rls-hierarchy/SKILL.md) — RLS policies
|
|
340
|
+
- [evolution-go-whatsapp-integration](../evolution-go-whatsapp-integration/SKILL.md) — Phase 112, integração inbound
|
|
341
|
+
- [whatsapp-conversation-state-machine](../whatsapp-conversation-state-machine/SKILL.md) — Phase 112, conversa.action_taken → lead
|
|
342
|
+
- [audit-log-multi-tenant](../audit-log-multi-tenant/SKILL.md) — Phase 109, eventos `custom_lead_*`
|
|
343
|
+
- [_shared-multi-tenant/glossary.md](../_shared-multi-tenant/glossary.md) — `lead`, `stages canônicos`, `ownership transfer`, `lead dedup`
|