[{"data":1,"prerenderedAt":1412},["ShallowReactive",2],{"navigation:en":3,"\u002Fblog\u002Fmulti-tenancy-rbac-seguridad-saas":311},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Nuxfire Docs","\u002Fdocs","docs\u002F0.index",[9,11,152],{"title":5,"path":6,"stem":7,"icon":10},"i-lucide-layout-grid",{"title":12,"path":13,"stem":14,"children":15,"icon":151},"SaaS Starter Kit","\u002Fdocs\u002Fsaas-starter-kit","docs\u002F1.saas-starter-kit\u002F0.index",[16,19,63,129],{"title":17,"path":13,"stem":14,"icon":18},"What is the SaaS Starter Kit?","i-lucide-info",{"title":20,"icon":21,"hide":22,"path":23,"stem":24,"children":25,"page":62},"Getting Started","i-lucide-play",true,"\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started","docs\u002F1.saas-starter-kit\u002F1.getting-started",[26,30,34,38,42,46,50,54,58],{"title":27,"path":28,"stem":29},"Quick Start","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fquick-start","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F0.quick-start",{"title":31,"path":32,"stem":33},"Installation","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Finstallation","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F1.installation",{"title":35,"path":36,"stem":37},"Cloudflare Access","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fcloudflare-access","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F2.cloudflare-access",{"title":39,"path":40,"stem":41},"Configuration","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fconfiguration","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F3.configuration",{"title":43,"path":44,"stem":45},"Secrets","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fsecrets","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F4.secrets",{"title":47,"path":48,"stem":49},"Environment Variables","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fenvironment-variables","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F5.environment-variables",{"title":51,"path":52,"stem":53},"Deploy","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fdeploy","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F6.deploy",{"title":55,"path":56,"stem":57},"Local Development","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Flocal-development","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F7.local-development",{"title":59,"path":60,"stem":61},"Project Structure","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fproject-structure","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F8.project-structure",false,{"title":64,"icon":65,"hide":22,"path":66,"stem":67,"children":68,"page":62},"Features","i-lucide-box","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures","docs\u002F1.saas-starter-kit\u002F2.features",[69,73,77,81,85,89,93,97,101,105,109,113,117,121,125],{"title":70,"path":71,"stem":72},"Authentication","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fauth","docs\u002F1.saas-starter-kit\u002F2.features\u002F1.auth",{"title":74,"path":75,"stem":76},"Rate Limiting","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frate-limiting","docs\u002F1.saas-starter-kit\u002F2.features\u002F10.rate-limiting",{"title":78,"path":79,"stem":80},"WebSockets","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fwebsockets","docs\u002F1.saas-starter-kit\u002F2.features\u002F11.websockets",{"title":82,"path":83,"stem":84},"Blog","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fblog","docs\u002F1.saas-starter-kit\u002F2.features\u002F14.blog",{"title":86,"path":87,"stem":88},"Documentation","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdocumentation","docs\u002F1.saas-starter-kit\u002F2.features\u002F15.documentation",{"title":90,"path":91,"stem":92},"Analytics","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fanalytics","docs\u002F1.saas-starter-kit\u002F2.features\u002F16.analytics",{"title":94,"path":95,"stem":96},"Platform Admin Console","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fplatform-admin","docs\u002F1.saas-starter-kit\u002F2.features\u002F17.platform-admin",{"title":98,"path":99,"stem":100},"Database","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdatabase","docs\u002F1.saas-starter-kit\u002F2.features\u002F2.database",{"title":102,"path":103,"stem":104},"Payments & Billing","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fpayments","docs\u002F1.saas-starter-kit\u002F2.features\u002F3.payments",{"title":106,"path":107,"stem":108},"Emails","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Femails","docs\u002F1.saas-starter-kit\u002F2.features\u002F4.emails",{"title":110,"path":111,"stem":112},"Notifications","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fnotifications","docs\u002F1.saas-starter-kit\u002F2.features\u002F5.notifications",{"title":114,"path":115,"stem":116},"Role-Based Access Control","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frbac","docs\u002F1.saas-starter-kit\u002F2.features\u002F6.rbac",{"title":118,"path":119,"stem":120},"Async Jobs","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fasync-jobs","docs\u002F1.saas-starter-kit\u002F2.features\u002F7.async-jobs",{"title":122,"path":123,"stem":124},"Storage","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fstorage","docs\u002F1.saas-starter-kit\u002F2.features\u002F8.storage",{"title":126,"path":127,"stem":128},"Internationalization","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Finternationalization","docs\u002F1.saas-starter-kit\u002F2.features\u002F9.internationalization",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":62},"Concepts","i-lucide-graduation-cap","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts","docs\u002F1.saas-starter-kit\u002F3.concepts",[135,139,143,147],{"title":136,"path":137,"stem":138},"Technology Stack","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Ftech-stack","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F0.tech-stack",{"title":140,"path":141,"stem":142},"SST.dev","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fsst","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F1.sst",{"title":144,"path":145,"stem":146},"NuxtHub","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fnuxt-hub","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F2.nuxt-hub",{"title":148,"path":149,"stem":150},"Design System & Branding","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fdesign-system","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F3.design-system","i-lucide-zap",{"title":153,"path":154,"stem":155,"children":156,"icon":159},"Fireskills","\u002Fdocs\u002Ffireskills","docs\u002F2.fireskills\u002F0.index",[157,160,219,249,275,293],{"title":158,"path":154,"stem":155,"icon":159},"What is Fireskills?","i-lucide-flame",{"title":20,"icon":21,"hide":22,"path":161,"stem":162,"children":163,"page":62},"\u002Fdocs\u002Ffireskills\u002Fgetting-started","docs\u002F2.fireskills\u002F1.getting-started",[164,167,171,175,179,183,187,191,195,199,203,207,211,215],{"title":27,"path":165,"stem":166},"\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fquick-start","docs\u002F2.fireskills\u002F1.getting-started\u002F00.quick-start",{"title":168,"path":169,"stem":170},"Workflow & Governance","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fworkflow","docs\u002F2.fireskills\u002F1.getting-started\u002F01.workflow",{"title":172,"path":173,"stem":174},"Project Context","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fproject-context","docs\u002F2.fireskills\u002F1.getting-started\u002F02.project-context",{"title":176,"path":177,"stem":178},"Step 0: Setup","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-setup","docs\u002F2.fireskills\u002F1.getting-started\u002F03.step-setup",{"title":180,"path":181,"stem":182},"Step 1: Inbox","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-inbox","docs\u002F2.fireskills\u002F1.getting-started\u002F04.step-inbox",{"title":184,"path":185,"stem":186},"Step 2: Backlog","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-backlog","docs\u002F2.fireskills\u002F1.getting-started\u002F05.step-backlog",{"title":188,"path":189,"stem":190},"Step 3: Specify","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-specify","docs\u002F2.fireskills\u002F1.getting-started\u002F06.step-specify",{"title":192,"path":193,"stem":194},"Step 4: Validate","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-validate","docs\u002F2.fireskills\u002F1.getting-started\u002F07.step-validate",{"title":196,"path":197,"stem":198},"Step 5: Tasks","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tasks","docs\u002F2.fireskills\u002F1.getting-started\u002F08.step-tasks",{"title":200,"path":201,"stem":202},"Step 6: TDD\u002FBDD","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tdd-bdd","docs\u002F2.fireskills\u002F1.getting-started\u002F09.step-tdd-bdd",{"title":204,"path":205,"stem":206},"Step 7: Implement","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-implement","docs\u002F2.fireskills\u002F1.getting-started\u002F10.step-implement",{"title":208,"path":209,"stem":210},"Step 8: Documentator","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-documentator","docs\u002F2.fireskills\u002F1.getting-started\u002F11.step-documentator",{"title":212,"path":213,"stem":214},"Late Changes","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Flate-changes","docs\u002F2.fireskills\u002F1.getting-started\u002F12.late-changes",{"title":216,"path":217,"stem":218},"Other Entry Points","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fother-entry-points","docs\u002F2.fireskills\u002F1.getting-started\u002F13.other-entry-points",{"title":220,"icon":221,"hide":22,"path":222,"stem":223,"children":224,"page":62},"Methodology","i-lucide-git-branch","\u002Fdocs\u002Ffireskills\u002Fmethodology","docs\u002F2.fireskills\u002F2.methodology",[225,229,233,237,241,245],{"title":226,"path":227,"stem":228},"The Three Acts","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-acts","docs\u002F2.fireskills\u002F2.methodology\u002F0.three-acts",{"title":230,"path":231,"stem":232},"The Three Gates","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-gates","docs\u002F2.fireskills\u002F2.methodology\u002F1.three-gates",{"title":234,"path":235,"stem":236},"The Canonical Spec","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fcanonical-spec","docs\u002F2.fireskills\u002F2.methodology\u002F2.canonical-spec",{"title":238,"path":239,"stem":240},"Lifecycle States","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Flifecycle-states","docs\u002F2.fireskills\u002F2.methodology\u002F3.lifecycle-states",{"title":242,"path":243,"stem":244},"Effort Scale","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Feffort-scale","docs\u002F2.fireskills\u002F2.methodology\u002F4.effort-scale",{"title":246,"path":247,"stem":248},"Milestones & Governance","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fgovernance-and-milestones","docs\u002F2.fireskills\u002F2.methodology\u002F5.governance-and-milestones",{"title":250,"icon":251,"hide":22,"path":252,"stem":253,"children":254,"page":62},"Skills","i-lucide-layers","\u002Fdocs\u002Ffireskills\u002Fskills","docs\u002F2.fireskills\u002F3.skills",[255,259,263,267,271],{"title":256,"path":257,"stem":258},"Catalog","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fcatalog","docs\u002F2.fireskills\u002F3.skills\u002F0.catalog",{"title":260,"path":261,"stem":262},"Act I: Define","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-1-define","docs\u002F2.fireskills\u002F3.skills\u002F1.act-1-define",{"title":264,"path":265,"stem":266},"Act II: Design & Prove","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-2-design-prove","docs\u002F2.fireskills\u002F3.skills\u002F2.act-2-design-prove",{"title":268,"path":269,"stem":270},"Act III: Deliver","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-3-deliver","docs\u002F2.fireskills\u002F3.skills\u002F3.act-3-deliver",{"title":272,"path":273,"stem":274},"Governance & Decisions","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fgovernance-and-decisions","docs\u002F2.fireskills\u002F3.skills\u002F4.governance-and-decisions",{"title":276,"icon":277,"hide":22,"path":278,"stem":279,"children":280,"page":62},"Specialists","i-lucide-sparkles","\u002Fdocs\u002Ffireskills\u002Fspecialists","docs\u002F2.fireskills\u002F4.specialists",[281,285,289],{"title":282,"path":283,"stem":284},"Overview","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Foverview","docs\u002F2.fireskills\u002F4.specialists\u002F0.overview",{"title":286,"path":287,"stem":288},"Nuxfire Stack","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fnuxfire-stack","docs\u002F2.fireskills\u002F4.specialists\u002F1.nuxfire-stack",{"title":290,"path":291,"stem":292},"Engineering Specialists","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fengineering-specialists","docs\u002F2.fireskills\u002F4.specialists\u002F2.engineering-specialists",{"title":294,"icon":295,"hide":22,"path":296,"stem":297,"children":298,"page":62},"CLI & Tooling","i-lucide-terminal","\u002Fdocs\u002Ffireskills\u002Fcli","docs\u002F2.fireskills\u002F5.cli",[299,303,307],{"title":300,"path":301,"stem":302},"CLI Overview","\u002Fdocs\u002Ffireskills\u002Fcli\u002Foverview","docs\u002F2.fireskills\u002F5.cli\u002F0.overview",{"title":304,"path":305,"stem":306},"Commands","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fcommands","docs\u002F2.fireskills\u002F5.cli\u002F1.commands",{"title":308,"path":309,"stem":310},"Safety Scripts","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fgovernance-scripts","docs\u002F2.fireskills\u002F5.cli\u002F2.governance-scripts",{"id":312,"title":313,"author":314,"authorType":315,"body":316,"category":1391,"date":1392,"description":1393,"extension":1394,"image":1395,"lang":1396,"meta":1397,"navigation":22,"ogImage":1398,"path":1399,"readTime":1400,"seo":1401,"stem":1402,"tags":1403,"translations":1407,"updated":315,"__hash__":1411},"blog\u002Fblog\u002Fmulti-tenancy-rbac-seguridad-saas.md","Multi-Tenancy Corporativo y RBAC Granular en PostgreSQL con Drizzle ORM","Equipo Nuxfire",null,{"type":317,"value":318,"toc":1385},"minimark",[319,332,335,354,357,362,365,385,388,911,913,917,923,1320,1322,1326,1329,1353,1355,1359,1369,1372,1381],[320,321,322,323,327,328,331],"p",{},"La mayor línea divisoria entre un proyecto amateur y un producto de software corporativo listo para las ventas B2B es la seguridad del ",[324,325,326],"strong",{},"multi-tenancy"," y del ",[324,329,330],{},"RBAC (Role-Based Access Control)",".",[320,333,334],{},"Si un cliente corporativo descubre que un usuario de otra empresa pudo ver datos de su workspace, la reputación de tu startup queda destruida el primer día.",[320,336,337,338,342,343,346,347,350,351,331],{},"En este artículo mostramos cómo la capa ",[339,340,341],"code",{},"layers\u002Fteams"," de ",[324,344,345],{},"Nuxfire"," implementa un aislamiento de datos a prueba de fallos con ",[324,348,349],{},"PostgreSQL"," y ",[324,352,353],{},"Drizzle ORM",[355,356],"hr",{},[358,359,361],"h2",{"id":360},"modelado-de-datos-el-tenant-id-en-cada-tabla","Modelado de Datos: el Tenant Id en Cada Tabla",[320,363,364],{},"Existen tres formas clásicas de multi-tenancy:",[366,367,368,372,375],"ol",{},[369,370,371],"li",{},"Una base de datos separada por cliente (cara y difícil de mantener en las migraciones).",[369,373,374],{},"Un schema separado por cliente (límites de conexiones y complejidad en el ORM).",[369,376,377,384],{},[324,378,379,380,383],{},"Tablas compartidas con una columna discriminadora (",[339,381,382],{},"team_id",")"," — El estándar moderno de la industria adoptado por gigantes como Stripe, Slack y Linear.",[320,386,387],{},"En Nuxfire, todas las entidades pertenecen a un equipo:",[389,390,395],"pre",{"className":391,"code":392,"language":393,"meta":394,"style":394},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark monokai","\u002F\u002F layers\u002Fteams\u002Fserver\u002Fdatabase\u002Fschema.ts\nexport const teams = pgTable(\"teams\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  slug: text(\"slug\").notNull().unique(),\n  ownerId: text(\"owner_id\").notNull(),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull()\n});\n\nexport const teamMembers = pgTable(\"team_members\", {\n  id: text(\"id\").primaryKey(),\n  teamId: text(\"team_id\").notNull().references(() => teams.id, { onDelete: \"cascade\" }),\n  userId: text(\"user_id\").notNull(),\n  role: text(\"role\", { enum: [\"OWNER\", \"ADMIN\", \"MEMBER\"] }).notNull().default(\"MEMBER\"),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull()\n});\n","typescript","",[339,396,397,406,450,485,515,551,580,616,627,633,660,687,756,785,871,902],{"__ignoreMap":394},[398,399,402],"span",{"class":400,"line":401},"line",1,[398,403,405],{"class":404},"ss7Ak","\u002F\u002F layers\u002Fteams\u002Fserver\u002Fdatabase\u002Fschema.ts\n",[398,407,409,413,417,421,425,429,433,437,441,443,447],{"class":400,"line":408},2,[398,410,412],{"class":411},"sRxSC","export",[398,414,416],{"class":415},"srJo8"," const",[398,418,420],{"class":419},"s91G_"," teams",[398,422,424],{"class":423},"sGXK2"," =",[398,426,428],{"class":427},"sD0ED"," pgTable",[398,430,432],{"class":431},"ss--_","(",[398,434,436],{"class":435},"siCPE","\"",[398,438,440],{"class":439},"sLACW","teams",[398,442,436],{"class":435},[398,444,446],{"class":445},"swvn1",",",[398,448,449],{"class":445}," {\n",[398,451,453,457,460,463,465,467,470,472,474,476,479,482],{"class":400,"line":452},3,[398,454,456],{"class":455},"squCx","  id",[398,458,459],{"class":445},":",[398,461,462],{"class":427}," text",[398,464,432],{"class":431},[398,466,436],{"class":435},[398,468,469],{"class":439},"id",[398,471,436],{"class":435},[398,473,383],{"class":431},[398,475,331],{"class":445},[398,477,478],{"class":427},"primaryKey",[398,480,481],{"class":431},"()",[398,483,484],{"class":445},",\n",[398,486,488,491,493,495,497,499,502,504,506,508,511,513],{"class":400,"line":487},4,[398,489,490],{"class":455},"  name",[398,492,459],{"class":445},[398,494,462],{"class":427},[398,496,432],{"class":431},[398,498,436],{"class":435},[398,500,501],{"class":439},"name",[398,503,436],{"class":435},[398,505,383],{"class":431},[398,507,331],{"class":445},[398,509,510],{"class":427},"notNull",[398,512,481],{"class":431},[398,514,484],{"class":445},[398,516,518,521,523,525,527,529,532,534,536,538,540,542,544,547,549],{"class":400,"line":517},5,[398,519,520],{"class":455},"  slug",[398,522,459],{"class":445},[398,524,462],{"class":427},[398,526,432],{"class":431},[398,528,436],{"class":435},[398,530,531],{"class":439},"slug",[398,533,436],{"class":435},[398,535,383],{"class":431},[398,537,331],{"class":445},[398,539,510],{"class":427},[398,541,481],{"class":431},[398,543,331],{"class":445},[398,545,546],{"class":427},"unique",[398,548,481],{"class":431},[398,550,484],{"class":445},[398,552,554,557,559,561,563,565,568,570,572,574,576,578],{"class":400,"line":553},6,[398,555,556],{"class":455},"  ownerId",[398,558,459],{"class":445},[398,560,462],{"class":427},[398,562,432],{"class":431},[398,564,436],{"class":435},[398,566,567],{"class":439},"owner_id",[398,569,436],{"class":435},[398,571,383],{"class":431},[398,573,331],{"class":445},[398,575,510],{"class":427},[398,577,481],{"class":431},[398,579,484],{"class":445},[398,581,583,586,588,591,593,595,598,600,602,604,607,609,611,613],{"class":400,"line":582},7,[398,584,585],{"class":455},"  createdAt",[398,587,459],{"class":445},[398,589,590],{"class":427}," timestamp",[398,592,432],{"class":431},[398,594,436],{"class":435},[398,596,597],{"class":439},"created_at",[398,599,436],{"class":435},[398,601,383],{"class":431},[398,603,331],{"class":445},[398,605,606],{"class":427},"defaultNow",[398,608,481],{"class":431},[398,610,331],{"class":445},[398,612,510],{"class":427},[398,614,615],{"class":431},"()\n",[398,617,619,622,624],{"class":400,"line":618},8,[398,620,621],{"class":445},"}",[398,623,383],{"class":431},[398,625,626],{"class":445},";\n",[398,628,630],{"class":400,"line":629},9,[398,631,632],{"emptyLinePlaceholder":22},"\n",[398,634,636,638,640,643,645,647,649,651,654,656,658],{"class":400,"line":635},10,[398,637,412],{"class":411},[398,639,416],{"class":415},[398,641,642],{"class":419}," teamMembers",[398,644,424],{"class":423},[398,646,428],{"class":427},[398,648,432],{"class":431},[398,650,436],{"class":435},[398,652,653],{"class":439},"team_members",[398,655,436],{"class":435},[398,657,446],{"class":445},[398,659,449],{"class":445},[398,661,663,665,667,669,671,673,675,677,679,681,683,685],{"class":400,"line":662},11,[398,664,456],{"class":455},[398,666,459],{"class":445},[398,668,462],{"class":427},[398,670,432],{"class":431},[398,672,436],{"class":435},[398,674,469],{"class":439},[398,676,436],{"class":435},[398,678,383],{"class":431},[398,680,331],{"class":445},[398,682,478],{"class":427},[398,684,481],{"class":431},[398,686,484],{"class":445},[398,688,690,693,695,697,699,701,703,705,707,709,711,713,715,718,720,722,725,727,729,731,733,736,739,741,744,747,749,752,754],{"class":400,"line":689},12,[398,691,692],{"class":455},"  teamId",[398,694,459],{"class":445},[398,696,462],{"class":427},[398,698,432],{"class":431},[398,700,436],{"class":435},[398,702,382],{"class":439},[398,704,436],{"class":435},[398,706,383],{"class":431},[398,708,331],{"class":445},[398,710,510],{"class":427},[398,712,481],{"class":431},[398,714,331],{"class":445},[398,716,717],{"class":427},"references",[398,719,432],{"class":431},[398,721,481],{"class":445},[398,723,724],{"class":415}," =>",[398,726,420],{"class":431},[398,728,331],{"class":445},[398,730,469],{"class":431},[398,732,446],{"class":445},[398,734,735],{"class":445}," {",[398,737,738],{"class":455}," onDelete",[398,740,459],{"class":445},[398,742,743],{"class":435}," \"",[398,745,746],{"class":439},"cascade",[398,748,436],{"class":435},[398,750,751],{"class":445}," }",[398,753,383],{"class":431},[398,755,484],{"class":445},[398,757,759,762,764,766,768,770,773,775,777,779,781,783],{"class":400,"line":758},13,[398,760,761],{"class":455},"  userId",[398,763,459],{"class":445},[398,765,462],{"class":427},[398,767,432],{"class":431},[398,769,436],{"class":435},[398,771,772],{"class":439},"user_id",[398,774,436],{"class":435},[398,776,383],{"class":431},[398,778,331],{"class":445},[398,780,510],{"class":427},[398,782,481],{"class":431},[398,784,484],{"class":445},[398,786,788,791,793,795,797,799,802,804,806,808,811,813,816,818,821,823,825,827,830,832,834,836,839,841,844,846,848,850,852,854,856,859,861,863,865,867,869],{"class":400,"line":787},14,[398,789,790],{"class":455},"  role",[398,792,459],{"class":445},[398,794,462],{"class":427},[398,796,432],{"class":431},[398,798,436],{"class":435},[398,800,801],{"class":439},"role",[398,803,436],{"class":435},[398,805,446],{"class":445},[398,807,735],{"class":445},[398,809,810],{"class":455}," enum",[398,812,459],{"class":445},[398,814,815],{"class":431}," [",[398,817,436],{"class":435},[398,819,820],{"class":439},"OWNER",[398,822,436],{"class":435},[398,824,446],{"class":445},[398,826,743],{"class":435},[398,828,829],{"class":439},"ADMIN",[398,831,436],{"class":435},[398,833,446],{"class":445},[398,835,743],{"class":435},[398,837,838],{"class":439},"MEMBER",[398,840,436],{"class":435},[398,842,843],{"class":431},"] ",[398,845,621],{"class":445},[398,847,383],{"class":431},[398,849,331],{"class":445},[398,851,510],{"class":427},[398,853,481],{"class":431},[398,855,331],{"class":445},[398,857,858],{"class":427},"default",[398,860,432],{"class":431},[398,862,436],{"class":435},[398,864,838],{"class":439},[398,866,436],{"class":435},[398,868,383],{"class":431},[398,870,484],{"class":445},[398,872,874,876,878,880,882,884,886,888,890,892,894,896,898,900],{"class":400,"line":873},15,[398,875,585],{"class":455},[398,877,459],{"class":445},[398,879,590],{"class":427},[398,881,432],{"class":431},[398,883,436],{"class":435},[398,885,597],{"class":439},[398,887,436],{"class":435},[398,889,383],{"class":431},[398,891,331],{"class":445},[398,893,606],{"class":427},[398,895,481],{"class":431},[398,897,331],{"class":445},[398,899,510],{"class":427},[398,901,615],{"class":431},[398,903,905,907,909],{"class":400,"line":904},16,[398,906,621],{"class":445},[398,908,383],{"class":431},[398,910,626],{"class":445},[355,912],{},[358,914,916],{"id":915},"middleware-y-procedures-trpc-autenticadas","Middleware y Procedures tRPC Autenticadas",[320,918,919,920,922],{},"Para garantizar que ningún desarrollador olvide filtrar por ",[339,921,382],{},", las peticiones pasan por procedimientos tRPC protegidos:",[389,924,926],{"className":391,"code":925,"language":393,"meta":394,"style":394},"export const teamProcedure = protectedProcedure\n  .input(z.object({ teamId: z.string() }))\n  .use(async ({ ctx, input, next }) => {\n    const membership = await ctx.db.query.teamMembers.findFirst({\n      where: and(\n        eq(teamMembers.teamId, input.teamId),\n        eq(teamMembers.userId, ctx.user.id)\n      )\n    });\n\n    if (!membership) {\n      throw new TRPCError({\n        code: \"FORBIDDEN\",\n        message: \"No tienes acceso a este workspace.\"\n      });\n    }\n\n    return next({\n      ctx: {\n        ...ctx,\n        teamId: input.teamId,\n        role: membership.role\n      }\n    });\n  });\n",[339,927,928,942,984,1021,1061,1074,1100,1129,1134,1143,1147,1166,1181,1197,1212,1221,1226,1231,1243,1253,1264,1280,1295,1301,1310],{"__ignoreMap":394},[398,929,930,932,934,937,939],{"class":400,"line":401},[398,931,412],{"class":411},[398,933,416],{"class":415},[398,935,936],{"class":419}," teamProcedure",[398,938,424],{"class":423},[398,940,941],{"class":431}," protectedProcedure\n",[398,943,944,947,950,953,955,958,960,963,966,968,971,973,976,979,981],{"class":400,"line":408},[398,945,946],{"class":445},"  .",[398,948,949],{"class":427},"input",[398,951,952],{"class":431},"(z",[398,954,331],{"class":445},[398,956,957],{"class":427},"object",[398,959,432],{"class":431},[398,961,962],{"class":445},"{",[398,964,965],{"class":455}," teamId",[398,967,459],{"class":445},[398,969,970],{"class":431}," z",[398,972,331],{"class":445},[398,974,975],{"class":427},"string",[398,977,978],{"class":431},"() ",[398,980,621],{"class":445},[398,982,983],{"class":431},"))\n",[398,985,986,988,991,993,997,1000,1004,1006,1009,1011,1014,1017,1019],{"class":400,"line":452},[398,987,946],{"class":445},[398,989,990],{"class":427},"use",[398,992,432],{"class":431},[398,994,996],{"class":995},"sTNss","async",[398,998,999],{"class":445}," ({",[398,1001,1003],{"class":1002},"sQgqH"," ctx",[398,1005,446],{"class":445},[398,1007,1008],{"class":1002}," input",[398,1010,446],{"class":445},[398,1012,1013],{"class":1002}," next",[398,1015,1016],{"class":445}," })",[398,1018,724],{"class":415},[398,1020,449],{"class":445},[398,1022,1023,1026,1029,1031,1034,1036,1038,1041,1043,1046,1048,1051,1053,1056,1058],{"class":400,"line":487},[398,1024,1025],{"class":415},"    const",[398,1027,1028],{"class":419}," membership",[398,1030,424],{"class":423},[398,1032,1033],{"class":411}," await",[398,1035,1003],{"class":431},[398,1037,331],{"class":445},[398,1039,1040],{"class":431},"db",[398,1042,331],{"class":445},[398,1044,1045],{"class":431},"query",[398,1047,331],{"class":445},[398,1049,1050],{"class":431},"teamMembers",[398,1052,331],{"class":445},[398,1054,1055],{"class":427},"findFirst",[398,1057,432],{"class":455},[398,1059,1060],{"class":445},"{\n",[398,1062,1063,1066,1068,1071],{"class":400,"line":517},[398,1064,1065],{"class":455},"      where",[398,1067,459],{"class":445},[398,1069,1070],{"class":427}," and",[398,1072,1073],{"class":455},"(\n",[398,1075,1076,1079,1081,1083,1085,1088,1090,1092,1094,1096,1098],{"class":400,"line":553},[398,1077,1078],{"class":427},"        eq",[398,1080,432],{"class":455},[398,1082,1050],{"class":431},[398,1084,331],{"class":445},[398,1086,1087],{"class":431},"teamId",[398,1089,446],{"class":445},[398,1091,1008],{"class":431},[398,1093,331],{"class":445},[398,1095,1087],{"class":431},[398,1097,383],{"class":455},[398,1099,484],{"class":445},[398,1101,1102,1104,1106,1108,1110,1113,1115,1117,1119,1122,1124,1126],{"class":400,"line":582},[398,1103,1078],{"class":427},[398,1105,432],{"class":455},[398,1107,1050],{"class":431},[398,1109,331],{"class":445},[398,1111,1112],{"class":431},"userId",[398,1114,446],{"class":445},[398,1116,1003],{"class":431},[398,1118,331],{"class":445},[398,1120,1121],{"class":431},"user",[398,1123,331],{"class":445},[398,1125,469],{"class":431},[398,1127,1128],{"class":455},")\n",[398,1130,1131],{"class":400,"line":618},[398,1132,1133],{"class":455},"      )\n",[398,1135,1136,1139,1141],{"class":400,"line":629},[398,1137,1138],{"class":445},"    }",[398,1140,383],{"class":455},[398,1142,626],{"class":445},[398,1144,1145],{"class":400,"line":635},[398,1146,632],{"emptyLinePlaceholder":22},[398,1148,1149,1152,1155,1158,1161,1164],{"class":400,"line":662},[398,1150,1151],{"class":411},"    if",[398,1153,1154],{"class":455}," (",[398,1156,1157],{"class":423},"!",[398,1159,1160],{"class":431},"membership",[398,1162,1163],{"class":455},") ",[398,1165,1060],{"class":445},[398,1167,1168,1171,1174,1177,1179],{"class":400,"line":689},[398,1169,1170],{"class":411},"      throw",[398,1172,1173],{"class":423}," new",[398,1175,1176],{"class":427}," TRPCError",[398,1178,432],{"class":455},[398,1180,1060],{"class":445},[398,1182,1183,1186,1188,1190,1193,1195],{"class":400,"line":758},[398,1184,1185],{"class":455},"        code",[398,1187,459],{"class":445},[398,1189,743],{"class":435},[398,1191,1192],{"class":439},"FORBIDDEN",[398,1194,436],{"class":435},[398,1196,484],{"class":445},[398,1198,1199,1202,1204,1206,1209],{"class":400,"line":787},[398,1200,1201],{"class":455},"        message",[398,1203,459],{"class":445},[398,1205,743],{"class":435},[398,1207,1208],{"class":439},"No tienes acceso a este workspace.",[398,1210,1211],{"class":435},"\"\n",[398,1213,1214,1217,1219],{"class":400,"line":873},[398,1215,1216],{"class":445},"      }",[398,1218,383],{"class":455},[398,1220,626],{"class":445},[398,1222,1223],{"class":400,"line":904},[398,1224,1225],{"class":445},"    }\n",[398,1227,1229],{"class":400,"line":1228},17,[398,1230,632],{"emptyLinePlaceholder":22},[398,1232,1234,1237,1239,1241],{"class":400,"line":1233},18,[398,1235,1236],{"class":411},"    return",[398,1238,1013],{"class":427},[398,1240,432],{"class":455},[398,1242,1060],{"class":445},[398,1244,1246,1249,1251],{"class":400,"line":1245},19,[398,1247,1248],{"class":455},"      ctx",[398,1250,459],{"class":445},[398,1252,449],{"class":445},[398,1254,1256,1259,1262],{"class":400,"line":1255},20,[398,1257,1258],{"class":423},"        ...",[398,1260,1261],{"class":431},"ctx",[398,1263,484],{"class":445},[398,1265,1267,1270,1272,1274,1276,1278],{"class":400,"line":1266},21,[398,1268,1269],{"class":455},"        teamId",[398,1271,459],{"class":445},[398,1273,1008],{"class":431},[398,1275,331],{"class":445},[398,1277,1087],{"class":431},[398,1279,484],{"class":445},[398,1281,1283,1286,1288,1290,1292],{"class":400,"line":1282},22,[398,1284,1285],{"class":455},"        role",[398,1287,459],{"class":445},[398,1289,1028],{"class":431},[398,1291,331],{"class":445},[398,1293,1294],{"class":431},"role\n",[398,1296,1298],{"class":400,"line":1297},23,[398,1299,1300],{"class":445},"      }\n",[398,1302,1304,1306,1308],{"class":400,"line":1303},24,[398,1305,1138],{"class":445},[398,1307,383],{"class":455},[398,1309,626],{"class":445},[398,1311,1313,1316,1318],{"class":400,"line":1312},25,[398,1314,1315],{"class":445},"  }",[398,1317,383],{"class":431},[398,1319,626],{"class":445},[355,1321],{},[358,1323,1325],{"id":1324},"flujo-de-invitaciones-por-correo-con-token-cifrado","Flujo de Invitaciones por Correo con Token Cifrado",[320,1327,1328],{},"Las invitaciones a nuevos miembros siguen un flujo seguro de doble confirmación:",[366,1330,1331,1340,1343,1350],{},[369,1332,1333,1334,1336,1337,1339],{},"El administrador del equipo ingresa el correo del colaborador y selecciona el rol (",[339,1335,829],{}," o ",[339,1338,838],{},").",[369,1341,1342],{},"El sistema genera un token aleatorio criptográficamente seguro que expira en 7 días.",[369,1344,1345,1346,1349],{},"Un correo elegante construido con ",[324,1347,1348],{},"React Email"," se envía mediante una cola asíncrona.",[369,1351,1352],{},"Al hacer clic en el enlace, el usuario es dirigido a la pantalla de aceptación o de onboarding, y la cuenta queda asociada al equipo al instante.",[355,1354],{},[358,1356,1358],{"id":1357},"registros-de-auditoría-para-el-cumplimiento-corporativo","Registros de Auditoría para el Cumplimiento Corporativo",[320,1360,1361,1362,1154,1365,1368],{},"Para los clientes B2B exigentes, Nuxfire ya incluye una tabla dedicada de ",[324,1363,1364],{},"Audit Logs",[339,1366,1367],{},"\u002Fadmin\u002Faudit-log","). Todos los cambios críticos — cambios de permisos, eliminación de miembros, cambios de plan y descargas de datos — se registran de forma inmutable con marca de tiempo, IP e identificador del autor.",[320,1370,1371],{},"Construye tu SaaS sobre una base que cumple los más altos estándares de seguridad corporativa desde el primer commit.",[320,1373,1374],{},[1375,1376,1378],"a",{"href":1377},"\u002Fes#pricing",[324,1379,1380],{},"Adquiere tu licencia de Nuxfire y asegura el acceso inmediato al código y al Grupo VIP →",[1382,1383,1384],"style",{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}",{"title":394,"searchDepth":408,"depth":408,"links":1386},[1387,1388,1389,1390],{"id":360,"depth":408,"text":361},{"id":915,"depth":408,"text":916},{"id":1324,"depth":408,"text":1325},{"id":1357,"depth":408,"text":1358},"Seguridad y Bases de Datos","2026-09-15","Cómo implementar un aislamiento estricto entre equipos, permisos granulares e invitaciones seguras por token sin brechas de seguridad en tu SaaS B2B.","md","\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql-es.svg","es",{},"\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql-es.png","\u002Fblog\u002Fmulti-tenancy-rbac-seguridad-saas","9 min",{"title":313,"description":1393},"blog\u002Fmulti-tenancy-rbac-seguridad-saas",[1404,1405,349,353,1406],"Multi-Tenancy","RBAC","Seguridad",{"en":1408,"pt-br":1409,"es":1410},"multi-tenancy-rbac-saas-security","multi-tenancy-rbac-seguranca-saas","multi-tenancy-rbac-seguridad-saas","GNHVMWzHphyshkD9jBmOeOk3fIUKw8U2yJD1Ywxdtm0",1790707503200]