{"_id":"@benenutri/mitra-kanban","_rev":"8-d043bd7582fafdf638f7dc8e609f295e","name":"@benenutri/mitra-kanban","dist-tags":{"latest":"1.8.0"},"versions":{"0.1.0":{"name":"@benenutri/mitra-kanban","version":"0.1.0","license":"UNLICENSED","_id":"@benenutri/mitra-kanban@0.1.0","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"dist":{"shasum":"414f1fb37d1471c56599d56c4bd7453c79cf7c6b","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-0.1.0.tgz","fileCount":10,"integrity":"sha512-fdOFqrsNWQW1JPi2G4IxEg7g8fVVf7epbN8mfUJ9Oa0nhGliCuDH0sWl/ss4cRHHNwifBzeyNmQVtBmgr6IHgQ==","signatures":[{"sig":"MEUCIBHvVq/JuQ8dD0RTAwV+v1QLqDqJ6cMURHCU4giLlN2lAiEA81JzvBDg4N/XaObqLE5qKwbWzumA4MnpPMdi3DYnfqg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":85260},"type":"module","engines":{"node":">=22.5"},"exports":{"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","prepublishOnly":"npm test"},"_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"dotenv":"^16.4.7","mitra-sdk":"^1.0.62"},"peerDependencies":{"react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0"},"peerDependenciesMeta":{"react":{"optional":true},"radix-ui":{"optional":true},"mitra-sdk":{"optional":true},"react-dom":{"optional":true},"lucide-react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mitra-kanban_0.1.0_1789131834438_0.6712443987162648","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@benenutri/mitra-kanban","version":"1.2.0","license":"UNLICENSED","_id":"@benenutri/mitra-kanban@1.2.0","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"dist":{"shasum":"f8e33a6284d2d56c0148ce08a1d7f951d7c6b821","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-1.2.0.tgz","fileCount":72,"integrity":"sha512-OCkX9glbWadvXfu0PdQiEqfGEeo8yxTbg2IGDxKX+y9PtWs4YwRgIkAVmJxlS8n3hccB9zPN8eQhZ3s5GXdOug==","signatures":[{"sig":"MEYCIQDNfb48E6q3V/QCcLyLXy+0W8QFJkBhjsoNDNYZWLjWRgIhAIOETl+EiRF3bGOk4+F8W+F/ogzhxfj5W+mpTS3tlXHO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":527718},"type":"module","engines":{"node":">=22.5"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"gitHead":"373f3651d95e1d8ca50db29c3343221db0387d97","scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","build:watch":"tsc -p tsconfig.build.json --watch --preserveWatchOutput","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","cmdk":"^1.1.1","react":"^19.2.0","dotenv":"^16.4.7","radix-ui":"^1.6.7","mitra-sdk":"^1.0.62","react-dom":"^19.2.0","typescript":"~5.9.3","@types/react":"^19.2.5","lucide-react":"^0.563.0","tailwind-merge":"^3.6.0","@types/react-dom":"^19.2.3","class-variance-authority":"^0.7.1"},"peerDependencies":{"clsx":">=2.0.0","cmdk":">=1.0.0","react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0","tailwind-merge":">=3.0.0","class-variance-authority":">=0.7.0"},"peerDependenciesMeta":{"mitra-sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mitra-kanban_1.2.0_1789394687324_0.7545329578633344","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@benenutri/mitra-kanban","version":"1.3.0","license":"UNLICENSED","_id":"@benenutri/mitra-kanban@1.3.0","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"dist":{"shasum":"ea09e98ac81274f9c3cca003f153a49938eade13","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-1.3.0.tgz","fileCount":74,"integrity":"sha512-x6w/ccIjXpbB9JZl/JpueELMwoA7OUC0+7ZsY7r4+Kk+/oGys2X0y2tsr3KS7HGuF5gapOZz0Rrx4w9Qq0XtZQ==","signatures":[{"sig":"MEQCIHbK3GgnRIRgdO2jZ8zAkTtee5nJVh8Xp51TDU6gAGGLAiANo3/bnW7t88dnetT65wFRk9oHIX4PK63visOw89GJcw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIFBZXKWqfUPDBpUpjF2n4TK+PHaFi7fbIwMHt/OoDIlDAiEApi8Qt+1UeCFi0Dt5oU5GcozCQsVcpoxA9EzAm/uLcss=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":554564},"type":"module","engines":{"node":">=22.5"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"gitHead":"f6efe66e2d8fc762536437f6618624a486ae3383","scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","build:watch":"tsc -p tsconfig.build.json --watch --preserveWatchOutput","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","cmdk":"^1.1.1","react":"^19.2.0","dotenv":"^16.4.7","radix-ui":"^1.6.7","mitra-sdk":"^1.0.62","react-dom":"^19.2.0","typescript":"~5.9.3","@types/react":"^19.2.5","lucide-react":"^0.563.0","tailwind-merge":"^3.6.0","@types/react-dom":"^19.2.3","class-variance-authority":"^0.7.1"},"peerDependencies":{"clsx":">=2.0.0","cmdk":">=1.0.0","react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0","tailwind-merge":">=3.0.0","class-variance-authority":">=0.7.0"},"peerDependenciesMeta":{"mitra-sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mitra-kanban_1.3.0_1789489417747_0.34262115122814496","host":"s3://npm-registry-packages-npm-production"}},"1.5.0":{"name":"@benenutri/mitra-kanban","version":"1.5.0","license":"UNLICENSED","_id":"@benenutri/mitra-kanban@1.5.0","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"dist":{"shasum":"4a6da52ba4ab2acecd61b10de3385dc917ca2eb9","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-1.5.0.tgz","fileCount":74,"integrity":"sha512-bgRKhSmZp3qwKpleEI8SSbAuZk4lY7ZFO0Nf4f1t08jbnDlKcZmauIv6+K2YVQpoqoDbmTheKI6EhmbSEwj8hQ==","signatures":[{"sig":"MEUCIDV2jQZUhAeS8cIbRSwN6JAY/WY+uGBLaD6x29W9WFvoAiEAoq6eogbYiUx3C2b+0qSgUI1q+E53txU2Fzby6xEsovE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIEpjv3hBkSZ5Usyz0EeEqPQOJROzvjH30yJkU4gUpdbxAiA/tLTDkloV//+AUrFmwykmvbP4NPlbNrkoepfYxLHlUA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":558496},"type":"module","engines":{"node":">=22.5"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"gitHead":"6c251ca149a1842eccef59bede5f4517accb23f7","scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","build:watch":"tsc -p tsconfig.build.json --watch --preserveWatchOutput","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","cmdk":"^1.1.1","react":"^19.2.0","dotenv":"^16.4.7","radix-ui":"^1.6.7","mitra-sdk":"^1.0.62","react-dom":"^19.2.0","typescript":"~5.9.3","@types/react":"^19.2.5","lucide-react":"^0.563.0","tailwind-merge":"^3.6.0","@types/react-dom":"^19.2.3","class-variance-authority":"^0.7.1"},"peerDependencies":{"clsx":">=2.0.0","cmdk":">=1.0.0","react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0","tailwind-merge":">=3.0.0","class-variance-authority":">=0.7.0"},"peerDependenciesMeta":{"mitra-sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mitra-kanban_1.5.0_1789572094866_0.6120200038490851","host":"s3://npm-registry-packages-npm-production"}},"1.6.0":{"name":"@benenutri/mitra-kanban","version":"1.6.0","license":"UNLICENSED","_id":"@benenutri/mitra-kanban@1.6.0","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"dist":{"shasum":"bd43e2d6d1fc6d7589db489b2fc10d8813baf73e","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-1.6.0.tgz","fileCount":74,"integrity":"sha512-QWwbslxJjkNUVUFkvOM3yD42jpBUcaNW1NKGzQTpdLg35IWzF4ZNd9BrQqUPizjTX33oCn4zeBTatg0nM6YwhQ==","signatures":[{"sig":"MEUCIBv27kvfvjfTRx92i2OEN4OqTI9watflJv2x5gnewSazAiEA9ZDX86uvF5Hja3Z8/tS1XBeiK2+mL7P4GiPlnjQ/RUs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQD5nPUrnEQoKBQVjvdWQLdGP5iJpusVwVE6wk/mjV0xoQIgIm6yoSZd64VLcKEohCFoMfNpjCjQB1KefKz6oFdpbu4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":586809},"type":"module","engines":{"node":">=22.5"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"gitHead":"cd0225e496a025cf4eaf6a7f41dc24bb15d47afa","scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","build:watch":"tsc -p tsconfig.build.json --watch --preserveWatchOutput","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","cmdk":"^1.1.1","react":"^19.2.0","dotenv":"^16.4.7","radix-ui":"^1.6.7","mitra-sdk":"^1.0.62","react-dom":"^19.2.0","typescript":"~5.9.3","@types/react":"^19.2.5","lucide-react":"^0.563.0","tailwind-merge":"^3.6.0","@types/react-dom":"^19.2.3","class-variance-authority":"^0.7.1"},"peerDependencies":{"clsx":">=2.0.0","cmdk":">=1.0.0","react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0","tailwind-merge":">=3.0.0","class-variance-authority":">=0.7.0"},"peerDependenciesMeta":{"mitra-sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mitra-kanban_1.6.0_1790262149870_0.8810822282470989","host":"s3://npm-registry-packages-npm-production"}},"1.6.1":{"name":"@benenutri/mitra-kanban","version":"1.6.1","license":"UNLICENSED","_id":"@benenutri/mitra-kanban@1.6.1","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"dist":{"shasum":"004d3cd2e2c9e490da922214ebc0be7af49bc932","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-1.6.1.tgz","fileCount":74,"integrity":"sha512-VJxAaaozncX43R8EO/gEWY3X+W0RxqCddqAIL+pW2FiIPII+Ciw5JDL4zaljZ7z8czc5ln9Om+W/p0+Usut10w==","signatures":[{"sig":"MEUCIE1gwLozlPHU+9ZQrPMPwLVNtijZE0zQ4eU7f6R2xctsAiEAkmfJv4dpaS6+cAJDQ7jGsLNQNO9x0vVH5R3nd/5Cj6A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIHRrOtSF1UNgYq16KYjmbaCphPVOLz4ia2/M6YLTXlrqAiEAqsM8Q9fuo275kdhE88p5Y0SsxNPrMi6K/5kug11ZCvY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":589566},"type":"module","engines":{"node":">=22.5"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"gitHead":"427386b52087989e6bb15b8b26fdf8e3e6076648","scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","build:watch":"tsc -p tsconfig.build.json --watch --preserveWatchOutput","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","cmdk":"^1.1.1","react":"^19.2.0","dotenv":"^16.4.7","radix-ui":"^1.6.7","mitra-sdk":"^1.0.62","react-dom":"^19.2.0","typescript":"~5.9.3","@types/react":"^19.2.5","lucide-react":"^0.563.0","tailwind-merge":"^3.6.0","@types/react-dom":"^19.2.3","class-variance-authority":"^0.7.1"},"peerDependencies":{"clsx":">=2.0.0","cmdk":">=1.0.0","react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0","tailwind-merge":">=3.0.0","class-variance-authority":">=0.7.0"},"peerDependenciesMeta":{"mitra-sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mitra-kanban_1.6.1_1790279092114_0.620747290573261","host":"s3://npm-registry-packages-npm-production"}},"1.7.0":{"name":"@benenutri/mitra-kanban","version":"1.7.0","license":"UNLICENSED","_id":"@benenutri/mitra-kanban@1.7.0","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"dist":{"shasum":"d935e239eaf6ae870d7a777011c8f6b4c3096f7c","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-1.7.0.tgz","fileCount":74,"integrity":"sha512-WrAGcXg2koXMkDy20WdA2dT1SRyks2DGgq0937RUNdMjk75H0y3JpVmjTg8dtnO5z13FwWcvr0JzgUv2eapkXQ==","signatures":[{"sig":"MEQCIDVqz43QfEx2/sntcFHPL25NYI3Wb41leA1J1C3iMy3PAiBOFXP/NfBZs0DCKGIZKpxlHhjcBIrd9mWApyvhloLmsA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDo+/UUQeZ5k5D0Av+Vbv/WovdmcGi1PyAHWSA31UES6wIgGXI7MftzyRn+9Mf30X+wQZCjM2NHdisymkwAe/E+eyg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":599314},"type":"module","engines":{"node":">=22.5"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"gitHead":"c278d237ab442b55c479054e46eb76c334bc3dc0","scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","build:watch":"tsc -p tsconfig.build.json --watch --preserveWatchOutput","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","cmdk":"^1.1.1","react":"^19.2.0","dotenv":"^16.4.7","radix-ui":"^1.6.7","mitra-sdk":"^1.0.62","react-dom":"^19.2.0","typescript":"~5.9.3","@types/react":"^19.2.5","lucide-react":"^0.563.0","tailwind-merge":"^3.6.0","@types/react-dom":"^19.2.3","class-variance-authority":"^0.7.1"},"peerDependencies":{"clsx":">=2.0.0","cmdk":">=1.0.0","react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0","tailwind-merge":">=3.0.0","class-variance-authority":">=0.7.0"},"peerDependenciesMeta":{"mitra-sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mitra-kanban_1.7.0_1790282491372_0.42964166039774754","host":"s3://npm-registry-packages-npm-production"}},"1.8.0":{"_id":"@benenutri/mitra-kanban@1.8.0","dist":{"shasum":"09e11d64246e3074c8bfbefc39f536c5dd1942e2","tarball":"https://registry.npmjs.org/@benenutri/mitra-kanban/-/mitra-kanban-1.8.0.tgz","fileCount":74,"integrity":"sha512-696pHfo0L6+GurwEoZER7lQUY+UHplgybpmyO6aa4VcRlZCw8xbkGkehJI/SMlcXsDWjFCZwWyX+ZeGH+mOUnQ==","signatures":[{"sig":"MEUCIQDQ3bI1fLY9Zsq3yNNswmMIpS9owlva7TfEoY8pqZhXjQIgah3WBUSOyYcQXXErQYQm27Z01lJb2ZYjTcrt8shT+ww=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDqY6ArbEhxbZ4WjeyLKZAvnUP8yBbF/70nU1wrPTl7mgIgaR/dhIL0RAvxMLQsLQ2zVIqaIkM0xbPtRndtNTmDTiQ="}],"unpackedSize":602955},"name":"@benenutri/mitra-kanban","type":"module","engines":{"node":">=22.5"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motor":"./motor/motor.mjs","./backend":"./backend/index.mjs","./backend/schema":"./backend/schema.mjs"},"gitHead":"b8ec6159cb96130a39c99d031bbea32ab7855295","license":"UNLICENSED","scripts":{"sim":"node backend/simulator/server.mjs","test":"npm run sim:check && npm run sim:smoke && npm run test:motor","build":"tsc -p tsconfig.build.json","sim:seed":"node backend/simulator/seed-cli.mjs","sim:check":"node backend/simulator/check.mjs","sim:smoke":"node backend/simulator/smoke.mjs","test:motor":"node --test \"tests/**/*.test.mjs\"","build:watch":"tsc -p tsconfig.build.json --watch --preserveWatchOutput","prepublishOnly":"npm test && npm run build"},"version":"1.8.0","_npmUser":{"name":"grupo-benenutri","email":"ti@benenutri.com"},"_npmVersion":"11.19.0","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","directories":{},"maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","cmdk":"^1.1.1","react":"^19.2.0","dotenv":"^16.4.7","radix-ui":"^1.6.7","mitra-sdk":"^1.0.62","react-dom":"^19.2.0","typescript":"~5.9.3","@types/react":"^19.2.5","lucide-react":"^0.563.0","tailwind-merge":"^3.6.0","@types/react-dom":"^19.2.3","class-variance-authority":"^0.7.1"},"peerDependencies":{"clsx":">=2.0.0","cmdk":">=1.0.0","react":">=19","radix-ui":">=1.0.0","mitra-sdk":">=1.0.60","react-dom":">=19","lucide-react":">=0.400.0","tailwind-merge":">=3.0.0","class-variance-authority":">=0.7.0"},"peerDependenciesMeta":{"mitra-sdk":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mitra-kanban_1.8.0_1790342060387_0.3720247739077398"}}},"time":{"created":"2026-09-11T13:03:54.241Z","modified":"2026-09-25T13:14:20.770Z","0.1.0":"2026-09-11T13:03:54.568Z","1.2.0":"2026-09-14T14:04:47.488Z","1.3.0":"2026-09-15T16:23:37.834Z","1.5.0":"2026-09-16T15:21:34.974Z","1.6.0":"2026-09-24T15:02:29.969Z","1.6.1":"2026-09-24T19:44:52.210Z","1.7.0":"2026-09-24T20:41:31.471Z","1.8.0":"2026-09-25T13:14:20.486Z"},"license":"UNLICENSED","description":"Motor de quadros (kanban) configurável para projetos da plataforma Mitra: tabelas, server functions e componentes React instaláveis por versão.","maintainers":[{"name":"grupo-benenutri","email":"ti@benenutri.com"}],"readme":"# @benenutri/mitra-kanban\r\n\r\nMotor de quadros (kanban) configurável para projetos da plataforma **Mitra**:\r\ntabelas, Server Functions e componentes React que um projeto **novo** instala\r\ne atualiza por versão. O gestor da área monta fases, campos, condições e\r\nautomações sozinho — a estrutura do banco não muda.\r\n\r\nSó para sistemas novos. Legados (SGC, ITSM, CRM Ativa, Comercial 360) não\r\nrecebem a lib.\r\n\r\n## O que vem no pacote\r\n\r\n| Parte | Caminho | O que é |\r\n|---|---|---|\r\n| Backend | `backend/` | `schema.mjs` (13 tabelas `KB_*`, `UPGRADES`), `escritas.mjs` (30 SQL internas), `funcoes.mjs` (27 SF públicas), `install.mjs` (`instalar()`), `index.mjs` |\r\n| Motor | `motor/motor.mjs` | regra pura: condições, visibilidade, validação, normalização — roda na SF, na tela e nos testes; e o modelo de e-mail (`montarEmail`, spec 016) |\r\n| Mapa | `frontend/server-functions.ts` | bloco canônico `SERVER_FUNCTION_NAMES` / `SERVER_FUNCTIONS` para copiar no consumidor |\r\n| Componentes | `dist/` (de `src/`) | `KanbanProvider`, `KanbanBoard`, `CardDrawer`, `BoardAdmin` |\r\n| Importação | `backend/pipefy/` | `extrair.mjs` (Pipefy → pacote), `mapear.mjs` (conversão pura), `carregar.mjs` (pacote → banco; `--conferir`, `--simulador`) — ver **Importar quadros do Pipefy** |\r\n\r\nSpec, plan e tasks: `docs/specs/001-motor-kanban/` (motor), `002-aparencia-quadro/` (cores, cara do card,\r\ngravação ao sair do campo), `003-card-aberto/` (card em três colunas, abas, passagens, vários responsáveis),\r\n`004-construtor-de-fases/` (administração numa tela só com prévia, observação por campo, capa do card por fase,\r\nos três tempos do cartão), `005-tarefas-do-card/`, `006-chat-do-card/` e `007-anexos-do-card/` (as abas Tarefas,\r\nChat e Anexos do card aberto), `010-etiquetas/` (etiquetas do quadro como selo, filtro por etiqueta, cadastro de\r\nopções em linhas, \"+\" para etiqueta e responsável no card), `011-importar-pipefy/` (quadros do Pipefy trazidos\r\npor TI em duas etapas — extrair para um pacote, conferir, carregar — com histórico e origem por card) e\r\n`013-pontos-de-extensao/` (abas, ações e regras do sistema consumidor no card aberto — ganchos de servidor\r\npara mover e excluir, nota no histórico, card por processo sem sessão, `{{anexos}}` no e-mail) e `016-email/` (o\r\nmodelo de e-mail Benenutri: automação, menção, novo responsável e tarefa atribuída saem no mesmo modelo, e o\r\nsistema consumidor o usa nos e-mails próprios).\r\n\r\n## Instalar num projeto Mitra novo\r\n\r\n1. Nos dois lados do projeto: `npm i @benenutri/mitra-kanban` (em `backend/` e em `frontend/`).\r\n2. Copie os dois blocos de `node_modules/@benenutri/mitra-kanban/frontend/server-functions.ts`\r\n   para o `frontend/src/lib/server-functions.ts` do projeto (junto das funções próprias dele).\r\n3. Crie `backend/add-001-kanban.mjs`:\r\n\r\n```js\r\nimport 'dotenv/config';\r\nimport { pathToFileURL, fileURLToPath } from 'node:url';\r\nimport { instalar, montarDefinitions, lerIdsDoMapa } from '@benenutri/mitra-kanban/backend';\r\n\r\n// Ids das escritas vêm do mapa do frontend: é por ele que o simulador resolve\r\n// chamadas por id, antes e depois do sync-server-function-map.\r\nconst MAPA = fileURLToPath(new URL('../frontend/src/lib/server-functions.ts', import.meta.url));\r\nexport const definitions = montarDefinitions(lerIdsDoMapa(MAPA));\r\n\r\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\r\n  const sdk = await import('mitra-sdk');\r\n  sdk.configureSdkMitra({\r\n    baseURL: process.env.MITRA_BASE_URL, token: process.env.MITRA_TOKEN, integrationURL: process.env.MITRA_BASE_URL_INTEGRATIONS,\r\n  });\r\n  await instalar({ sdk, projectId: Number(process.env.MITRA_PROJECT_ID), mapaFrontend: MAPA });\r\n}\r\n```\r\n\r\n4. No simulador do projeto (`backend/simulator/config.mjs`):\r\n\r\n```js\r\nexport const PUBLISH_ORDER = ['add-001-kanban.mjs', /* ...os add-* do projeto */];\r\nexport const DDL_SOURCES = [\r\n  join(HERE, '..', 'setup-backend.mjs'),\r\n  fileURLToPath(import.meta.resolve('@benenutri/mitra-kanban/backend/schema')),\r\n];\r\n```\r\n\r\n5. `frontend/src/index.css`, logo após os `@import`: `@source \"../node_modules/@benenutri/mitra-kanban\";`\r\n   — as telas da lib usam as classes de token do design system da Benenutri (`bg-card`, `text-ink-secondary`,\r\n   `bg-paper-sunken`…), e o Tailwind precisa escanear o pacote para gerá-las. O projeto precisa ter o\r\n   design system instalado (`/benenutri:design`), que é quem define os tokens.\r\n6. Rotas (ver seção **Componentes**).\r\n7. Perfil de acesso do projeto: libere as 27 SF `kb*` e as 30 `kbEsc*` para o perfil business.\r\n8. Anexos (spec 007): passe `enviarArquivo` ao `criarKanbanApi` (ver **Componentes**). Sem ele, a aba Anexos\r\n   aceita só link.\r\n\r\n**Publicar:** commit → no sandbox do projeto: `cd backend && node add-001-kanban.mjs && node sync-server-function-map.mjs`,\r\ndepois `cd frontend && npm run build` e share. A plataforma gera as migrations.\r\n\r\n## Atualizar a versão num projeto\r\n\r\n```bash\r\ncd backend && npm i @benenutri/mitra-kanban@1.4.0\r\ncd frontend && npm i @benenutri/mitra-kanban@1.4.0\r\n```\r\n\r\nAcrescente ao `server-functions.ts` os nomes novos (o `instalar()` avisa quais faltam).\r\nNo sandbox: `node add-001-kanban.mjs && node sync-server-function-map.mjs`, build, share.\r\n`instalar()` é idempotente e só aditivo: `CREATE IF NOT EXISTS`, `UPGRADES` guardados por coluna,\r\nSFs upsertadas por nome, versão em `KB_META`.\r\n\r\nA spec 004 **não acrescenta server function nenhuma**: `kbFaseSalvar` ganhou o parâmetro `capaJson` e a\r\nobservação viaja no `configJson` que `kbCampoSalvar` já aceitava. Quem atualiza não mexe no\r\n`server-functions.ts` — só roda o `add-001-kanban.mjs` no sandbox para ganhar a coluna `KB_FASE.CAPA_JSON`.\r\n\r\nA spec 010 (etiquetas) também **não acrescenta SF, tabela, coluna nem token de cor** — `kbCardsListar` ganhou\r\no parâmetro `etiqueta`, e o selo sai de `--data-N`, `--card` e `--foreground`, que todo projeto com o design\r\nsystem já tem. Quem atualiza não cola nada: roda o `add-001-kanban.mjs` e pronto.\r\n\r\nNada muda de aparência para quem não ativar etiquetas: opção colorida de campo comum continua em ponto +\r\nrótulo, e só a lista de etiquetas vira selo.\r\n\r\nA **1.1.0** (spec 011) acrescenta uma coluna, `KB_CARD.ORIGEM_CHAVE` (com índice), e **nenhuma SF**: quem\r\natualiza só roda o `add-001-kanban.mjs` no sandbox. A mesma coluna serve à spec 008 (`fonte:<id>:<chave>`).\r\n\r\nA **1.2.0** (specs 005, 006 e 007 — abas Tarefas, Chat e Anexos) acrescenta **4 tabelas** (`KB_TAREFA`,\r\n`KB_MENSAGEM`, `KB_MENSAGEM_LEITURA`, `KB_ANEXO`, pelo `CREATE IF NOT EXISTS`, sem coluna nova em tabela\r\nexistente), **10 SF públicas** e **11 escritas**. Quem atualiza:\r\n\r\n1. Copia os dois blocos novos do `frontend/server-functions.ts` da lib (ids locais 8017–8026 e 9020–9030) para o\r\n   `server-functions.ts` do projeto — o `instalar()` avisa o que falta.\r\n2. Libera as 21 SF novas no perfil business.\r\n3. Passa `enviarArquivo` ao `criarKanbanApi` para a aba Anexos enviar arquivo (sem ele, só link).\r\n4. No simulador do projeto, para enviar arquivo localmente: copia de `backend/simulator/server.mjs` da lib a rota\r\n   `POST /interactions/uploadFilePublic` e a `GET /public/:nome`. Sem elas o envio falha só no simulador.\r\n5. No sandbox: `node add-001-kanban.mjs && node sync-server-function-map.mjs`, build, share.\r\n\r\nA **1.3.0** (spec 013 — pontos de extensão do sistema consumidor) **não acrescenta tabela nem coluna**: a\r\nnota é uma linha de `KB_LOG` e as regras ligadas são linhas de `KB_META`. Acrescenta **uma SF pública**,\r\n`kbNotaRegistrar`. Quem atualiza:\r\n\r\n1. Copia a linha `kbNotaRegistrar` nos **dois blocos** (`SERVER_FUNCTION_NAMES` e `SERVER_FUNCTIONS`) do\r\n   `frontend/server-functions.ts` da lib para o `server-functions.ts` do projeto — o id fica o local até o\r\n   `sync-server-function-map.mjs` trazer o real.\r\n2. Libera `kbNotaRegistrar` no perfil business.\r\n3. No sandbox: `node add-001-kanban.mjs && node sync-server-function-map.mjs`, build, share.\r\n4. Quer regras ligadas (mover/excluir por SQL do consumidor)? Passa `ganchos` ao `instalar()` no\r\n   `add-001-kanban.mjs` — ver **Regras ligadas (ganchos de servidor)**. Sem `ganchos`, nada muda.\r\n\r\nOs pontos de tela (`abasExtras`, `acoesExtras`, `podeMover`, `antesDeMover`, `podeExcluir`, `novoCard`,\r\n`atualizarACadaMs`) são props de `KanbanBoard`/`CardDrawer` — não pedem nada no sandbox, só o `npm i` e o\r\n`import` (ver **Pontos de extensão do sistema consumidor**).\r\n\r\nA **1.4.0** (spec 014 — movimento por processo) **não acrescenta tabela, coluna nem SF**: `kbCardMover`\r\nganhou `movidoPor` e `kbCardAtualizar` ganhou `atualizadoPor`, os dois com `origem`, e só valem sem sessão.\r\nQuem atualiza não mexe no `server-functions.ts` nem no perfil — `npm i` nos dois lados e o\r\n`node add-001-kanban.mjs` de sempre no sandbox. Quem não chama as SFs sem sessão não vê diferença nenhuma.\r\n\r\nA **1.6.0** (spec 016 — modelo de e-mail) **não acrescenta tabela, coluna nem SF**: os e-mails que já saíam\r\n(automação e menção no chat) passam a sair no modelo Benenutri, e dois avisos novos entram (novo responsável do\r\ncard, tarefa atribuída). Quem atualiza roda `npm i` nos dois lados e o `add-001-kanban.mjs` de sempre; para o\r\nbotão \"Abrir card\" e o nome do sistema no assunto, passa `email: { sistema, urlDoCard }` ao `instalar()` — ver\r\n**E-mail (spec 016)**. Sem isso, os e-mails saem como `[Benenutri] …` e sem botão.\r\n\r\nA **1.6.1** (correção de tela, spec 002 C-009) **não acrescenta nada no servidor**: só `npm i` no frontend. O quadro\r\npassa a preencher o `main` pelo layout (`KanbanBoard` em `h-full`, Panel e Board em `flex-1`) em vez de descontar\r\n21rem da janela — era isso que deixava o `main` rolando junto com as colunas (duas barras) — e os cabeçalhos das\r\nfases dividem uma linha de grade (subgrid), então a descrição de uma fase não desalinha mais os cards das outras.\r\nA pilha de cada coluna ficou `relative`: os rótulos `sr-only` dos cards (absolutos) passam a ser contidos por ela,\r\ne não pela janela — era isso que criava uma barra de rolagem da página inteira que descia sem mostrar nada.\r\nExige o layout do §10 (`main` com altura definida e `overflow-y-auto`); fora dele o quadro fica nos 30rem do padrão.\r\n\r\nA **1.7.0** (spec 017 — anexo pelo chat) acrescenta **uma coluna**, `KB_ANEXO.MENSAGEM_ID` (com índice, pelo\r\n`UPGRADES`), e **nenhuma SF**: `kbMensagemEnviar` ganhou `anexoNome`, `anexoUrl` e `anexoTamanho` (vazios = como\r\nantes) e `kbMensagensListar` passa a trazer o anexo de cada mensagem. Quem atualiza roda `npm i` nos dois lados e o\r\n`add-001-kanban.mjs` no sandbox — **sem ele a leitura do chat falha**, porque o join usa a coluna nova. O clipe só\r\naparece no chat de quem passa `enviarArquivo` ao `criarKanbanApi` (o mesmo da aba Anexos); sem ele nada muda.\r\n\r\nA **1.8.0** (spec 018 — cabeçalho do quadro) **não acrescenta nada no servidor**: só `npm i` no frontend (o backend\r\npode acompanhar a versão por consistência; o `add-001-kanban.mjs` não traz nada novo). Na tela do quadro o\r\ncabeçalho fica numa linha (nome do quadro, contagem e ações, sem o rótulo \"Quadro\", sem grudar no topo), a\r\ndescrição do quadro vai para um balão atrás do botão de informação ao lado da contagem, e o piso de altura do\r\nquadro cai de 30rem para 22rem. Num notebook 1366×768 a pilha visível passa de 270px para 325px e a página deixa\r\nde rolar por baixo do cabeçalho. Lista de quadros e administração não mudam. `PageHeader` do vocabulário ganhou\r\n`compacto` (sem `module` nem `description`), e `Popover`, `PopoverContent` e `PopoverTrigger` passaram a ser\r\nexportados pelo vocabulário.\r\n\r\n## Importar quadros do Pipefy\r\n\r\nFerramenta de linha de comando para TI (spec 011): um quadro do Pipefy vira um quadro do motor com fases,\r\ncampos, etiquetas, cards (valores, responsáveis, prazo, datas, conclusão), histórico de passagem por fase e um\r\nregistro de origem por card, a conversa (comentários) e os anexos (arquivos enviados). Duas etapas com um\r\n**pacote** em disco no meio — o pacote é o seguro contra desligar o Pipefy e a fonte de tudo: carregar de novo\r\nacrescenta o que faltou, sem voltar ao Pipefy.\r\n\r\n1. `PIPEFY_TOKEN=...` no `.env` do `backend/` do projeto (token pessoal ou de serviço do Pipefy). Nunca vai\r\n   para o git nem para o pacote.\r\n2. Congele o quadro no Pipefy (somente leitura): o que for editado lá depois da extração não entra.\r\n\r\n```bash\r\ncd backend\r\nnode node_modules/@benenutri/mitra-kanban/backend/pipefy/extrair.mjs --listar              # id, cards e nome de cada quadro\r\nnode node_modules/@benenutri/mitra-kanban/backend/pipefy/extrair.mjs --pipe 301234567      # pacote em pipefy/301234567/\r\nnode node_modules/@benenutri/mitra-kanban/backend/pipefy/carregar.mjs --pacote pipefy/301234567 --conferir\r\nnode node_modules/@benenutri/mitra-kanban/backend/pipefy/carregar.mjs --pacote pipefy/301234567 --simulador\r\nnode node_modules/@benenutri/mitra-kanban/backend/pipefy/carregar.mjs --pacote pipefy/301234567 --usuario ti@empresa.com\r\n```\r\n\r\n- **Extrair** lê o quadro pela API do Pipefy (≤ 5 requisições/s; espera e repete quando o Pipefy avisa\r\n  excesso), grava os cards brutos em `cards.jsonl`, baixa os anexos na mesma passagem (o link vale 15 min) e\r\n  confere a quantidade extraída contra a que cada fase declara. Termina \"parcial\", dizendo por quê, quando\r\n  faltou card ou arquivo; `--so-arquivos --saida <dir>` refaz só os anexos perdidos; `--sem-arquivos` pula os\r\n  anexos; `--pagina` muda o tamanho da página (50).\r\n- **Conferir** escreve `relatorio.md` no pacote sem gravar nada: fases e tipos finais, cada campo com a\r\n  conversão de tipo, pessoas sem par no `INT_USER`, opções recriadas, históricos aproximados, valores que não\r\n  couberam no tipo, e quantos comentários e arquivos entram na conversa e nos anexos.\r\n- **Carregar** grava por lotes com o token de desenvolvedor do `.env` — só `INSERT`/`SELECT`, nenhuma\r\n  estrutura (o motor precisa estar na 1.1.0; senão a carga para antes de gravar). Idempotente: rodar de novo\r\n  não duplica nem sobrescreve; card que ficou pela metade é completado. `--usuario` é quem importa (vira\r\n  administrador do quadro, junto dos admins do Pipefy com par por e-mail); `--lote` muda o tamanho do lote (200).\r\n  Os **comentários** entram na conversa do card com a data e o autor do Pipefy (pelo cadastro quando há par; pelo\r\n  nome quando não há), e não contam como mensagens novas. `--arquivos` sobe os arquivos para o PUBLIC do projeto,\r\n  guarda a URL em `arquivos.json` e, na mesma carga, cria os **anexos** dos cards; sem `--arquivos` o relatório\r\n  diz quantos aguardam. Motor anterior à 1.2.0 carrega o resto e deixa conversa e anexos aguardando.\r\n- **`--simulador`** carrega o mesmo pacote no SQLite local (`simulator/data/mitra-sim.db` ou `--banco`) para o\r\n  gestor ver o quadro antes de tocar em produção.\r\n\r\nO que muda de forma (tabela completa no `plan.md` da 011): data e hora vira data (a hora fica no registro de\r\norigem); hora, CPF, CNPJ, identificador e fórmula viram texto com observação; conexão entre cards vira texto\r\n\"Título (#id)\"; responsável de formulário com várias pessoas fica com a primeira; etiquetas do pipe viram o\r\ncampo `etiquetas` (spec 010) com a cor mais próxima da paleta; fase concluída do Pipefy é final — cancelado\r\nquando o nome tem cancel/recus/reprov/perd/arquiv/desist, sucesso nos demais (ajuste na administração). Opção\r\nque sumiu da lista do Pipefy mas ainda está em card é recriada. Histórico: o Pipefy só informa a primeira e a\r\núltima entrada em cada fase, sem quem moveu — entra uma passagem por fase visitada, sem autor; card que voltou\r\nde fase fica \"aproximado\" e as datas brutas vão para o registro de origem, que também guarda o número e o link\r\ndo card no Pipefy (a busca do quadro acha o card por esse número). Automações e condicionais do Pipefy não saem\r\npela API: o relatório conta as condicionais e TI as refaz no construtor de fases.\r\n\r\n## Componentes\r\n\r\nA lib fala com o Mitra pelo transporte **do projeto** (`linhas`/`acao` de `mitra-api.ts`): ela chama as\r\nserver functions pelo nome e o projeto resolve o id. Nenhum SDK, fetch ou id dentro da lib.\r\n\r\n```tsx\r\nimport { KanbanProvider, criarKanbanApi, QuadrosLista, KanbanBoard, BoardAdmin } from '@benenutri/mitra-kanban';\r\nimport { acao, linhas } from '@/lib/mitra-api';\r\n\r\nconst api = criarKanbanApi({\r\n  acao: (nome, input) => acao(nome as Parameters<typeof acao>[0], input),\r\n  linhas: (nome, input) => linhas(nome as Parameters<typeof linhas>[0], input),\r\n  // Opcional (spec 007): sem ele, a aba Anexos aceita só link.\r\n  enviarArquivo: async (arquivo) => {\r\n    const r = await uploadFilePublicMitra({ file: arquivo }); // de 'mitra-interactions-sdk'\r\n    if (!r?.result?.publicUrl) throw new Error(`O envio de ${arquivo.name} não devolveu o endereço.`);\r\n    return r.result.publicUrl;\r\n  },\r\n});\r\n\r\n<KanbanProvider api={api}>\r\n  {/* /quadros */}\r\n  <QuadrosLista onAbrir={(slug) => navigate(`/quadros/${slug}`)} onAdministrar={(slug) => navigate(`/quadros/${slug}/admin`)} />\r\n  {/* /quadros/:slug — o card aberto vive na URL (?card=ID) */}\r\n  <KanbanBoard slug={slug} cardId={cardId} onCardChange={(id) => setParams(id ? { card: String(id) } : {})}\r\n    onAdministrar={() => navigate(`/quadros/${slug}/admin`)} onVoltar={() => navigate('/quadros')} />\r\n  {/* /quadros/:slug/admin */}\r\n  <BoardAdmin slug={slug} onVoltar={() => navigate(`/quadros/${slug}`)} />\r\n</KanbanProvider>\r\n```\r\n\r\n| Componente | O que faz |\r\n|---|---|\r\n| `QuadrosLista` | lista com busca, \"Novo quadro\" (criador vira admin) e ações Abrir/Administrar |\r\n| `KanbanBoard` | colunas por fase numa faixa horizontal (sempre na ordem do fluxo; rola quando não cabem), altura fixa com rolagem por coluna, barra na cor da fase e descrição, busca, filtro por responsável (qualquer um da lista), concluídos, \"Novo card\" com campos visíveis ao vivo, filtro por etiqueta, card aberto. O cartão é **só leitura** (emenda de 2026-09-11): sem arrastar e sem seletor de fase na capa — mover é no card aberto, em \"Mover para\". Cara do card: etiquetas (opções com cor), os campos **fixos do quadro** e depois os que **aquela fase acrescenta**, responsáveis, prazo relativo, mensagens novas na conversa para quem olha (spec 006) e os três tempos (criação, fase — com a fração do SLA — e última alteração) |\r\n| `CardDrawer` | card aberto em três colunas (spec 003): à esquerda responsáveis (vários), prazo, formulário inicial como ficha (\"clique para preencher\") e histórico por passagem (campos da fase e eventos ao abrir); no meio as abas \"Nesta fase\" (chip, SLA, campos da fase, condições das próximas fases), \"Chat\" (spec 006), \"Tarefas\" (spec 005), \"Anexos\" (spec 007) e \"Alterações\" (prosa por dia com filtro); à direita \"Mover para\" em botões e Excluir. Título editável no lugar. Sem botão Salvar: grava ao sair do campo e ao fechar (\"Salvo às HH:MM\"); mover grava o formulário antes |\r\n| `BoardAdmin` | abas Geral, Fases e Automações. \"Fases\" é o construtor da spec 004: fluxo à esquerda, campos da etapa no meio (setas para a ordem, \"+ Adicionar campo\" com os tipos, ✎ para editar) e prévia à direita — o cartão do quadro (com \"Editar capa\") e o card aberto, com dados de exemplo. Campo tem observação e \"Editável depois da fase\"; `CondicaoEditor` e `AcoesEditor` seguem nos diálogos; sem permissão os formulários desabilitam |\r\n| `CampoInput`, `CondicaoEditor`, `AcoesEditor` | peças reutilizáveis para telas próprias do projeto |\r\n| `useKanban()`, `criarKanbanApi` | acesso tipado às 27 server functions públicas; `enviarArquivo` repassado do transporte |\r\n\r\nExemplo completo: `mitra-projects/p-59277/frontend` (App, AdminLayout e as três páginas).\r\n\r\nDesenvolvimento local da lib com um consumidor: `npm link` aqui e `npm link @benenutri/mitra-kanban --no-save`\r\n**nos dois lados** do consumidor (`backend/` — schema, server functions e seed — e `frontend/`), com\r\n`npm run build:watch` aqui. Linkar só o frontend deixa o simulador do consumidor no schema do registry.\r\nMudou o schema? Regere o banco do simulador do consumidor (`npm run sim:seed`, com o simulador parado). O `vite.config.ts` do consumidor precisa de\r\n`resolve.dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'radix-ui', 'cmdk', 'lucide-react']` e\r\n`server.fs.allow` incluindo a pasta da lib (ver o do p-59277) — sem isso o React da lib é outra cópia.\r\n\r\n## Pontos de extensão do sistema consumidor (spec 013)\r\n\r\nUm sistema com regra própria (o SGC 2 é o primeiro) acrescenta abas, ações e regras ao card aberto sem\r\ncopiar o card nem a gravação do motor. `KanbanBoard` e `CardDrawer` aceitam estas props, todas opcionais —\r\nausente, o comportamento é o de hoje:\r\n\r\n- `abasExtras` — abas do sistema depois das do motor (Nesta fase, Chat, Tarefas, Anexos, Alterações), na\r\n  ordem informada, com rótulo, contagem opcional lida do card e o conteúdo desenhado pelo próprio sistema.\r\n  Ausente, o card só mostra as abas do motor.\r\n- `conteudoDaFase` (**1.5.0**, spec 015) — conteúdo do sistema **dentro** da aba \"Nesta fase\", abaixo dos\r\n  campos da fase e acima das condições de saída. O sistema decide pelo `ctx.card.faseId` o que cada fase\r\n  mostra (itens na montagem, notas fiscais na expedição, B.O. quando falta produto): o card muda de assunto\r\n  conforme anda, em vez de uma aba por assunto. Ausente, a aba só tem os campos do motor. Combina com\r\n  `abasExtras` (um assunto que vale em toda fase pode continuar aba).\r\n- `acoesExtras` — botões do sistema na coluna de \"Mover para\", acima do Excluir. Ausente, a coluna mostra\r\n  só o que o motor já mostra.\r\n- `podeMover` — síncrono, roda na renderização de cada destino: devolver um texto desabilita o botão com\r\n  esse motivo (no `title`); `null` libera. Soma-se às condições de fase do motor, não as substitui. Ausente,\r\n  todo destino que a fase permite aparece livre.\r\n- `antesDeMover` — assíncrono, roda no clique, antes de gravar: `{ ok: false, erro }` recusa e mostra o\r\n  erro na faixa de aviso, sem gravar nada; pode consultar o servidor (alertas, prazo). Ausente, só a\r\n  checagem do motor vale.\r\n- `podeExcluir` — `false` esconde o botão Excluir do motor. É espelho de tela: quem decide de verdade é a\r\n  regra ligada no servidor (ver **Regras ligadas** abaixo). Ausente, o Excluir segue a regra de hoje\r\n  (criador do card ou administrador do quadro).\r\n- `novoCard` — troca o botão \"Novo card\" do motor (cabeçalho e estado vazio) pelo do sistema; `false` tira\r\n  o botão do quadro. Ausente, o botão do motor continua.\r\n- `atualizarACadaMs` — o quadro recarrega a lista sozinho a cada N milissegundos, sem indicador de\r\n  carregamento e pausado com a aba escondida. Ausente ou `0`, sem atualização periódica.\r\n\r\nToda aba e ação extra recebe o mesmo contexto (`ExtensaoContexto`): o card, a estrutura, o detalhe, os\r\nusuários, se está travado, e `mover(faseId)`, `recarregar()`, `avisar(tone, texto)`, `fechar()`.\r\n`ctx.mover(faseId)` é o mesmo mover do card aberto: salva o formulário → passa por `antesDeMover` → move\r\npelo motor → mostra o aviso → recarrega o card e avisa o quadro atrás.\r\n\r\nComo o SGC 2 liga:\r\n\r\n```tsx\r\n<KanbanBoard\r\n  slug=\"compras\"\r\n  novoCard={{ rotulo: 'Novo pedido', onClick: () => navigate('/pedidos/novo') }}\r\n  atualizarACadaMs={60000}\r\n  conteudoDaFase={(ctx) => <ConteudoDaFase {...ctx} />}   // itens e alertas na Análise, notas na expedição, B.O. na fase B.O. (spec 015)\r\n  acoesExtras={(ctx) => <AcoesDoPedido {...ctx} />}\r\n  podeMover={(card, fase, estrutura) => motivoDeDestino(persona, card, fase, estrutura)}   // mesma regra da SQL ligada, em TS puro\r\n  antesDeMover={(card, fase) => verificarSaida(card, fase)}                                 // alertas, justificativas, prazo — consulta o servidor\r\n  podeExcluir={() => permissoes.excluirPedido}\r\n/>\r\n```\r\n\r\nA spec 013 também acrescenta, do lado do servidor:\r\n\r\n- `kbNotaRegistrar` (nova SF pública, JAVASCRIPT) — grava uma nota do sistema no histórico do card:\r\n  `cardId`, `texto` (até 1.000 caracteres) e `usuarioId` (só vale sem sessão, para processo automático).\r\n  Não toca a data de atualização (`ATUALIZADO_EM`) do card; recusa card excluído ou inexistente, ou texto\r\n  vazio. Devolve `{ ok, id }`.\r\n- `kbCardCriar` ganha `criadoPor` e `origem: 'sistema'` — só valem sem sessão (ex.: a agenda do consumidor\r\n  criando pedido de madrugada); com sessão, os dois são ignorados e o criador é sempre quem está logado.\r\n  **Premissa de segurança:** \"sem sessão\" é a situação que a plataforma só produz para o token de\r\n  desenvolvedor (cron e scripts do sandbox); um usuário logado nunca chega aqui sem `:VAR_USER`. O mesmo vale\r\n  para o `usuarioId` de `kbNotaRegistrar` e para o `movidoPor` de `kbCardMover` e o `atualizadoPor` de\r\n  `kbCardAtualizar` (spec 014). Se o seu projeto tiver algum caminho em que a sessão do usuário não resolve\r\n  um `INT_USER`, não libere essas quatro SFs no perfil business antes de fechar esse caminho.\r\n- `kbCardMover` ganha `movidoPor` e `kbCardAtualizar` ganha `atualizadoPor`, os dois com `origem: 'sistema'`\r\n  (spec 014, **1.4.0**) — pela mesma regra: só valem sem sessão (ex.: o cron que, com 100 % das notas no ERP,\r\n  escreve o faturado no card e leva o pedido de Trânsito para Faturamento em nome do responsável); com\r\n  sessão, quem move ou altera é quem está logado. A passagem fica com essa pessoa e com origem `sistema`, a\r\n  linha de alteração do histórico fica com ela, e as automações de \"card movido\" e \"campo alterado\" rodam com\r\n  ela. Obrigatórios da fase de origem, condição de entrada, conclusão e \"só campo editável agora\" valem\r\n  igual; o que **não** vale é a regra ligada de `podeMover` — ela é sobre pessoas.\r\n- A ação `enviar_email` das automações entende `{{anexos}}` no assunto ou no corpo: com anexo, o marcador some do\r\n  texto e a lista vira o bloco **Anexos** do modelo de e-mail (um link por arquivo ou link não removido da aba,\r\n  na ordem dela); sem anexo, \"Nenhum anexo\" no lugar do marcador. Só consulta os anexos quando o marcador\r\n  aparece de fato — a palavra \"anexos\" solta na prosa não custa nada (a spec 016 mudou a forma, não a regra).\r\n\r\n## Regras ligadas (ganchos de servidor)\r\n\r\nRegra do sistema consumidor que o motor **consulta no servidor** antes de mover ou excluir um card — não é\r\nsó a tela que decide, para não deixar passar quem chama por fora dela. Sem gancho ligado, vale a regra de\r\nhoje: mover segue as condições da fase, excluir é de quem criou o card ou do administrador do quadro.\r\n\r\nO consumidor publica uma SF **SQL** por regra, com este contrato:\r\n\r\n| Entrada (`{{param}}`) | Saída (uma linha) |\r\n|---|---|\r\n| `usuarioId` (quem age — o motor passa a sessão; nunca `:VAR_USER`, porque a chamada é aninhada), `cardId`, `boardId`, `faseId` (destino do movimento; `0` no excluir) | `PODE` (1 ou 0) e `MOTIVO` (texto; vazio quando pode) |\r\n\r\nExemplo (SGC 2, `sgcPodeMover`):\r\n\r\n```sql\r\nSELECT\r\n  CASE WHEN P.PERSONA IN ('admin', 'analista', 'gestor') THEN 1\r\n       WHEN P.PERSONA = 'diretoria' AND C.FASE_ID = F.FASE_ID THEN 1\r\n       ELSE 0 END AS PODE,\r\n  CASE WHEN P.PERSONA = 'diretoria' AND C.FASE_ID <> F.FASE_ID\r\n       THEN 'Você só movimenta cards na fase Aprovação da Diretoria'\r\n       ELSE '' END AS MOTIVO\r\nFROM KB_CARD C\r\nLEFT JOIN SGC_USUARIO_PERSONA P ON P.USER_ID = {{usuarioId}}\r\n...\r\nWHERE C.ID = {{cardId}}\r\n```\r\n\r\n**Onde fica:** `KB_META`, chaves `gancho:podeMover` e `gancho:podeExcluir`; o valor é o id da SF do\r\nconsumidor.\r\n\r\n**Como ligar**, no `add-001-kanban.mjs` do consumidor:\r\n\r\n```js\r\nawait instalar({\r\n  sdk, projectId,\r\n  ganchos: { podeMover: 'sgcPodeMover', podeExcluir: 'sgcPodeExcluir' },\r\n});\r\n```\r\n\r\n`instalar()` resolve os nomes pelas server functions do projeto e upserta `KB_META` (idempotente). Sem\r\n`ganchos`, nada é gravado e nada muda.\r\n\r\n**Quando o motor consulta:** só em movimento e exclusão feitos por **um usuário**. Automação (`mover_fase`)\r\ne criação ou movimento de card por processo sem sessão (`origem: 'sistema'`) não passam pelo gancho — a regra\r\nligada é sobre pessoas. Quem precisa barrar o processo o barra no próprio processo.\r\n\r\n**Quando a SQL falha ou não devolve linha:** o motor recusa com \"Regra do sistema indisponível.\" — fecha,\r\nem vez de abrir. Foi o consumidor que ligou a regra; deixar passar seria pular a checagem em silêncio.\r\n\r\n**A tela precisa espelhar a mesma regra** em `podeMover`/`podeExcluir` (ver **Pontos de extensão do sistema\r\nconsumidor** acima) — senão a pessoa vê um botão habilitado que o servidor recusa no clique. Há uma diferença\r\nde momento entre as duas recusas: a de `antesDeMover` (tela) acontece antes de qualquer gravação, inclusive do\r\nformulário; a do gancho de servidor chega depois que o card aberto já salvou o que estava no formulário\r\n(o motor grava o formulário e só então pede o movimento). Nada de fase, movimento ou histórico é gravado\r\nem nenhum dos dois casos — mas é mais um motivo para a tela não deixar chegar ao servidor o que ele vai recusar.\r\n\r\n**Ligar com a chave certa:** `instalar()` só aceita `podeMover` e `podeExcluir` em `ganchos`; qualquer outra\r\nchave (ou um nome de SF que não existe no projeto) interrompe a instalação **antes** de criar tabela ou\r\npublicar função — uma regra \"ligada\" numa chave que o motor não lê seria uma regra desligada com mensagem de sucesso.\r\n\r\n## Responsáveis e campos de fase passada\r\n\r\nUm card tem uma **lista** de responsáveis (`KB_CARD_RESPONSAVEL`); `KB_CARD.RESPONSAVEL_ID` continua\r\nexistindo como espelho do primeiro, para quem lê a coluna. `kbCardCriar`/`kbCardAtualizar` recebem\r\n`responsaveisIds` (\"3,4\"); `responsavelId` sozinho ainda vale como lista de um. Filtro do quadro, condição\r\n`$responsavel` e e-mail de automação valem para qualquer um da lista; \"Atribuir responsável\" redefine a\r\nlista como só aquela pessoa.\r\n\r\nCampo de fase é editável com o card na fase. Depois que o card sai, só se o admin marcar **\"Editável\r\ndepois da fase\"** no campo (e só em fase já visitada). A regra é `camposEditaveis` no motor e vale na\r\ntela e no `kbCardAtualizar` (valor de campo não editável é ignorado; de fase não visitada, recusado).\r\n\r\n## Capa do card, observação e os três tempos\r\n\r\n**Capa por fase** (spec 004): a cara do card tem duas partes, que **se somam**.\r\n\r\n1. **Fixos do quadro** — campos marcados \"na capa de todas as fases\" (`NO_CARD`): aparecem em qualquer fase,\r\n   na ordem dos campos (campo de fase continua só na fase dele). É o que existia antes da 004.\r\n2. **O que a fase acrescenta** — a lista de chaves em `KB_FASE.CAPA_JSON`, na ordem dela, inclusive campo de\r\n   outra fase (o que os fixos não fazem).\r\n\r\nA regra é `camposDoCard(campos, card, fase)` no motor; chave morta, campo inativo, condição falsa e valor\r\nvazio não ocupam linha, e um campo citado nas duas partes aparece uma vez. Fixo **não se tira** de uma fase:\r\npara ele valer só em algumas, desmarque \"todas as fases\" e acrescente-o à lista de cada uma. A lista se edita\r\nna prévia da administração (\"Editar capa\"), que mostra as duas partes na ordem em que saem no cartão.\r\n\r\n`kbFaseSalvar` grava o que recebe: **quem chama sem `capaJson` devolve a fase ao padrão do quadro**, do\r\nmesmo jeito que chamar sem `descricao` limpa a descrição. O `BoardAdmin` sempre manda a capa atual junto.\r\n\r\n**Observação do campo**: uma linha guardada em `CONFIG_JSON` (`config.observacao`) e exposta como\r\n`campo.observacao`. Aparece sob o campo onde ele é editável (novo card, formulário inicial, fase atual e\r\npassagem \"editável depois\"); erro de validação ocupa o mesmo lugar e tem precedência. Não vai para a cara\r\ndo card nem para o texto de busca.\r\n\r\n**Três tempos**: todo cartão mostra criação, tempo na fase e última alteração, com ícone e ordem fixa\r\n(`Tempos`, no vocabulário). Vêm de `CRIADO_EM`, `FASE_DESDE` e `ATUALIZADO_EM` — nenhuma consulta nova.\r\nA unidade é a maior que couber (`duracao`: min → h → d) e, com SLA na fase, o relógio mostra a fração\r\n`21 h / 48 h`, em tom de atenção quando passa (`tempoNaFase`). Não é configurável: vale em todo quadro.\r\n\r\n## Cores\r\n\r\nCor de fase e de opção de seleção vem de uma paleta fixa de 8 tokens da **rampa de dados** do design\r\nsystem (`data-1` Azul … `data-8` Vermelho — `CORES` no motor); o motor guarda o nome do token, nunca hex,\r\ne a tela renderiza `var(--data-N)`, que existe nos dois temas. Na coluna a cor pinta a barra (o título\r\ncontinua `ink-muted`, porque três matizes da rampa não passam 4,5:1 como texto) e cada card leva uma\r\nfaixa na borda esquerda. Na administração a cor se escolhe por bolinhas (nome no `title` e no hint).\r\nAs colunas usam a altura `tela` do `Board`: a tela ocupa o `main` inteiro (`h-full`, layout do §10) e o\r\nquadro cresce até o rodapé dele, nunca abaixo de 30rem — quando a janela é mais baixa que isso, quem rola é o\r\n`main`, não uma segunda barra dentro do quadro. Os cabeçalhos das fases dividem uma linha de grade (subgrid):\r\numa descrição de duas linhas numa fase não desalinha os cards das outras.\r\n\r\nA **etiqueta** (e só ela) aparece como **selo** (`Selo`, exportado): o rótulo por dentro da cor. O selo\r\n**contrasta com o tema** — escuro com rótulo claro no tema claro, claro com rótulo escuro no escuro —, e a\r\nregra é simétrica, sem caso por tema: `background: color-mix(in srgb, var(--data-N) 55%, var(--foreground))`\r\ne `color: var(--background)`. 55% é o teto medido em que os oito matizes ainda passam de 4,5:1 nos dois temas\r\n(design.md §12) — nenhum token novo. O cartão mostra três e resume o resto em \"+N\".\r\n\r\n## Etiquetas (spec 010)\r\n\r\nEtiqueta **é campo**, não tabela: cada quadro pode ter um campo reservado de chave `etiquetas`\r\n(seleção múltipla, sem fase, na capa), e a lista de etiquetas é o `config.opcoes` dele. Com isso ela herda\r\nvalidação, condição, automação, capa da fase, aba Alterações e busca — sem nenhuma tabela, coluna ou SF nova.\r\n\r\n- **Ativar:** administração › Fases › formulário inicial › \"Ativar etiquetas\" (cria o campo com a primeira).\r\n- **Cadastrar:** uma linha por etiqueta — rótulo, cor em bolinhas, selo de prévia. O valor nasce do rótulo na\r\n  criação e **não muda**: é ele que está gravado nos cards, e é o que faz renomear valer para todos de uma vez.\r\n- **Marcar:** no card aberto, selos com `×` e um `+` que abre a lista e **não fecha** ao marcar.\r\n- **Filtrar:** um seletor na barra do quadro. O motor grava `#chave#` no `TEXTO_BUSCA` do card e o filtro\r\n  procura essa forma — por isso ele acha pelo que está marcado, e não pela palavra no título.\r\n\r\n## Tarefas, chat e anexos (specs 005–007)\r\n\r\nAs três abas do meio do card aberto. Cada uma tem tabela própria, grava por server function e registra em\r\nAlterações o que importa; abrir o card continua sendo **uma** chamada (`kbCardDetalhe` traz tarefas, anexos e o\r\ntotal da conversa).\r\n\r\n**Tarefas** (`KB_TAREFA`): título, responsável e prazo opcionais; concluir guarda quem e quando; setas para a\r\nordem (subir grava só as duas que trocaram); excluir é de quem criou a tarefa ou do administrador. Condição de\r\nfase, de visibilidade e de automação ganham **\"Tarefas pendentes\"** (`$tarefas`): \"Tarefas pendentes igual a 0\"\r\nna entrada de uma fase exige tudo concluído. Tarefa não mexe no \"alterado há\" do cartão.\r\n\r\n**Chat** (`KB_MENSAGEM`, `KB_MENSAGEM_LEITURA`): texto até 4.000 caracteres; remetente e hora sempre do servidor.\r\nCom a aba visível, um ciclo de `kbMensagensListar` a cada 5 s (20 s depois de 1 min sem mensagem nova), sempre\r\nincremental, só com a janela à mostra e uma consulta por vez — ciclo sem novidade não grava nada. Abre nas 50\r\nmais recentes (\"Carregar anteriores\" traz o resto). \"@\" abre a lista de pessoas; o mencionado recebe e-mail\r\n(quem escreve não; editar não reenvia; desde a 1.6.0 o e-mail sai no modelo Benenutri, com a mensagem citada e o\r\nbotão \"Responder no card\" quando `email.urlDoCard` foi configurado — ver **E-mail (spec 016)**).\r\nO autor edita e exclui nos primeiros 5 minutos; o administrador do quadro exclui qualquer uma, e a exclusão\r\napaga o texto e fica em Alterações. O cartão do quadro mostra as **mensagens novas** para quem olha — de outras\r\npessoas, depois da última que ela viu naquele card — e o número some quando ela abre a conversa.\r\nDesde a **1.7.0** (spec 017) a mensagem leva **um arquivo**: o clipe do composer (só com `enviarArquivo`) sobe o\r\narquivo pelo projeto e a mesma `kbMensagemEnviar` grava mensagem, anexo e registro; o arquivo entra na aba Anexos\r\ncom \"pelo chat\" e o chip da mensagem o abre. Só o arquivo, sem texto, vale. Excluir a mensagem **não** remove o\r\narquivo (é documento do card); remover o arquivo pela aba Anexos deixa a mensagem dizendo \"anexo removido\".\r\n\r\n**Anexos** (`KB_ANEXO`): o arquivo sobe pelo `enviarArquivo` do projeto e a lib só registra o endereço\r\n(`kbAnexoRegistrar`); link entra do mesmo jeito, com nome opcional (sem nome, o domínio). Só `http(s)`; até 20 MB\r\npor arquivo. A aba avisa que arquivo enviado fica em endereço público — documento sensível vai como link.\r\nRemover é de quem anexou ou do administrador; some da lista, fica em Alterações e **não apaga** o arquivo do\r\narmazenamento.\r\n\r\n## E-mail (spec 016)\r\n\r\nTodo e-mail que o motor manda sai num modelo só (design system Benenutri, §15): marca em texto, nome do sistema,\r\ntítulo em frase, blocos opcionais (resumo, aviso, corpo, citação, dados, tabela, anexos), **um** botão e o rodapé\r\ncom o motivo. Quatro envios usam o modelo:\r\n\r\n- a ação **\"Enviar e-mail\"** das automações — o texto do administrador é texto (linha em branco vira parágrafo,\r\n  endereço vira link, `**negrito**`; HTML é escapado) e `{{anexos}}` vira o bloco de links;\r\n- a **menção no chat** — a mensagem como citação e \"Responder no card\";\r\n- **novo responsável** — \"Você é responsável pelo card #N — título\", só para quem **entrou** na lista (criação,\r\n  edição ou ação \"Atribuir responsável\"), nunca para quem age nem para quem já estava;\r\n- **tarefa atribuída** — \"Tarefa para você no card #N: título\", na criação e na troca de responsável; a si mesmo,\r\n  não.\r\n\r\nMovimento, campo alterado e prazo continuam sendo assunto de automação do administrador. Falha no envio nunca\r\ndesfaz a gravação: a tarefa fica, a mensagem fica (`emailFalhou: true`), e na automação a ação registra erro.\r\n\r\nConfiguração, uma vez, no `add-001-kanban.mjs`:\r\n\r\n```js\r\nawait instalar({\r\n  sdk, projectId, mapaFrontend: MAPA,\r\n  email: { sistema: 'SGC 2', urlDoCard: 'https://sgc.benenutri.com/#/quadro?card={id}' },\r\n});\r\n```\r\n\r\n`sistema` é o nome curto (cabeçalho, assunto `[SGC 2] …` e rodapé; sem ele, `Benenutri`); `urlDoCard` é o\r\nendereço do card com `{id}` — sem ele o e-mail sai **sem botão**, nunca com link quebrado. Os dois ficam em\r\n`KB_META` (`email:sistema`, `email:urlDoCard`); sem `email` no `instalar()`, nada é tocado.\r\n\r\nOs e-mails próprios do sistema (pedido à indústria, agenda, cobrança de NF) usam o mesmo modelo:\r\n\r\n```js\r\nimport { montarEmail } from '@benenutri/mitra-kanban/motor';\r\n\r\nconst { assunto, html } = montarEmail({\r\n  sistema: 'SGC 2',\r\n  rotulo: 'Pedido de compra · nº 482',\r\n  titulo: 'Pedido de compra 482 — Benenutri',\r\n  corpo: 'Prezados,\\n\\nSegue o pedido conforme os itens abaixo.',\r\n  dados: [['Fornecedor', 'Nestlé Brasil'], ['Condição', '28 dias · CIF']],\r\n  tabela: {\r\n    titulo: 'Itens',\r\n    colunas: [{ rotulo: 'Produto' }, { rotulo: 'Qtd', numerico: true }, { rotulo: 'Total', numerico: true }],\r\n    linhas: [['Leite em pó 400 g', '120', 'R$ 18.450,00']],\r\n    rodape: ['Total', '120', 'R$ 18.450,00'],\r\n  },\r\n  anexos: [{ nome: 'Pedido-482.pdf', url, tamanho: '184 KB' }],\r\n  // destinatário de fora: sem `acao` (não abre o card) e rodapé que convida a responder\r\n  rodape: 'Enviado pelo SGC 2 · Benenutri. Responda a este e-mail para falar com Compras.',\r\n});\r\nawait sendEmailMitra({ projectId, to, subject: assunto, body: html });\r\n```\r\n\r\nPartes: `sistema`, `rotulo`, `titulo` (obrigatório), `resumo`, `aviso: { tom: 'atencao' | 'alerta', texto }`,\r\n`corpo`, `citacao: { autor, quando, texto }`, `dados: [[rótulo, valor | { antes, depois }]]`, `tabela`, `anexos`,\r\n`acao: { rotulo, url }`, `motivo`, `rodape`, `preheader`, `logoUrl`. Tipos em `motor/motor.d.mts`\r\n(`PartesDeEmail`). Regras que valem para todo e-mail: título diz o que aconteceu; um botão só; texto é texto;\r\ndado ausente é travessão; o tom é uma faixa dentro do cartão e nunca recolore o e-mail; sempre claro; a marca é\r\n`#45963d`, nunca o verde da interface. O e-mail de referência está em `assets/email.html` da skill\r\n`benenutri:design`.\r\n\r\n## Desenvolver a lib\r\n\r\n```bash\r\nnpm install\r\nnpm test            # sim:check + sim:smoke (57 SFs reais em SQLite) + test:motor\r\nnpm run sim         # simulador em http://localhost:3120 (senha mitra123; logins no seed)\r\nnpm run build       # tsc → dist/\r\n```\r\n\r\nFluxo de uma versão nova: mudar → `npm test` verde → bump em `package.json` → coluna nova vai\r\nno `CREATE` **e** em `UPGRADES` → `npm publish` (o `prepublishOnly` roda os testes; `publishConfig`\r\njá é `public`) → atualizar os consumidores.\r\n\r\nVersões: `0.1.0` = só backend (motor, schema, server functions, instalador); `1.0.0` = backend +\r\ncomponentes React (`exports[\".\"]` → `dist/`). Peer dependencies do frontend: `react`, `react-dom`,\r\n`radix-ui`, `cmdk`, `lucide-react`, `class-variance-authority`, `clsx`, `tailwind-merge` — o kit do\r\ndesign system já as instala.\r\n\r\nPara trabalhar com um consumidor local: `npm link` aqui e `npm link @benenutri/mitra-kanban`\r\nno consumidor; o `package.json` do consumidor continua apontando para a versão publicada\r\n(o sandbox não enxerga `file:`).\r\n\r\n## Fuso horário\r\n\r\nO servidor grava instantes em UTC (`AAAA-MM-DDTHH:MM:SS`, sem `Z`). A tela converte para o fuso do\r\nnavegador antes de mostrar hora e antes de decidir o dia (`formatarDataHora`, `formatarData`, `dataLocal`,\r\n`hoje`, `relativo`, `agruparPorDia` — todos em `src/formato.ts`). No servidor, a única decisão por dia — prazo vencido\r\nno cron — usa `hojeNoFuso()` (`FUSO_MINUTOS = -180`, Brasil sem horário de verão), nunca o dia UTC.\r\n\r\n## Regras que não se negocia\r\n\r\n- Nunca rode `instalar()`/`install.mjs` da máquina local: só no sandbox do projeto (migration).\r\n- `schema.mjs` só cresce: nada de `DROP`, `RENAME` ou mudança de tipo.\r\n- Nome de SF é contrato: renomear quebra todo consumidor.\r\n- `motor/motor.mjs` não importa nada e não contém `{{` literal.\r\n- Commit sem trailer de agente (`Co-Authored-By: Claude…` é proibido).\r\n","readmeFilename":"README.md"}