Tornar al blog
Intel·ligència Artificial

Nexo: anatomia tècnica del nostre SaaS multi-tenant — Laravel 12 + React + Inertia.js + Brain (agents IA)

Nexo — arquitectura d'un SaaS multi-tenant amb Laravel 12, React + Inertia.js i agents IA Brain

Nexo: anatomia tècnica del nostre SaaS multi-tenant — Laravel 12 + React + Inertia.js + Brain (agents IA)

Per l'equip de Kiwop · Agència Digital especialitzada en Desenvolupament de Programari i Intel·ligència Artificial aplicada per a clients globals a Europa i els EUA · Publicat el 19 d'abril de 2026 · Última actualització: 19 d'abril de 2026

TL;DR — Nexo és el SaaS operatiu de Kiwop (HR, CRM, projectes, facturació, nòmines) sobre Laravel 12 + React + Inertia.js, amb agents IA propis (Brain). Tres decisions el defineixen: jerarquia Organization → Workspace → Member amb toggles de mòduls per workspace, Inertia.js com a pont entre monòlit i SPA, i coexistència controlada entre rutes legacy (Blade) i noves (Inertia) mitjançant un middleware guardià que bloqueja escriptures antigues.

Nexo — arquitectura d'un SaaS multi-tenant amb Laravel 12, React + Inertia.js i agents IA Brain

A Kiwop fa anys que construïm programari a mida per a clients d'Europa i els EUA, però Nexo és diferent: és el producte que fem servir cada dia nosaltres mateixos per operar l'agència, i alhora el SaaS que oferim com a cas d'èxit a empreses B2B mid-market que necessiten centralitzar HR, CRM, projectes i facturació en una sola plataforma. Quan construeixes alguna cosa que el teu propi equip fa servir diàriament, l'arquitectura deixa de ser un exercici teòric: cada decisió es paga o es cobra cada dilluns al matí.

Aquest article entra al detall tècnic. És la documentació de l'estat real del sistema a l'abril de 2026 — decisions, trade-offs i coses que faríem diferent. Encaixa al mateix cluster que el nostre post sobre reconstruir un assistent personal amb Claude Code i MCP, la guia per construir el teu PA amb Claude Code, els patrons i antipatrons d'agents IA en producció i els protocols MCP, WebMCP i A2A el 2026. Si aquests posts tracten agents fora de l'app, Nexo és la demostració de com fiques agents dins d'una aplicació que ja té un domini ric, dades reals i un tenancy seriós.

Què és Nexo i per a què el vam construir

Nexo neix d'un problema recurrent a empreses B2B d'entre deu i dos-cents empleats: l'operativa diària viu repartida en mitja dotzena d'eines desconnectades. Control horari en una app, CRM en una altra, projectes a ClickUp, facturació en un ERP, nòmines a la gestoria, docs a Notion. Cada eina funciona bé per separat, però ningú veu l'operació com un tot, les dades de client es dupliquen entre sistemes, i cada informe requereix exportar CSVs de quatre llocs i creuar-los a mà.

A Kiwop patíem aquest problema. Vam construir Nexo primer per a nosaltres i després el vam convertir en producte. A dia d'avui cobreix cinc mòduls:

  • HR: control horari (fitxatges amb geolocalització i traçabilitat legal de quatre anys), gestió de vacances i absències, fitxes d'empleat, equips i festius.
  • CRM: empreses, contactes, leads amb pipeline, activitats comercials i campanyes outbound.
  • Projects: projectes, tasques amb temps registrable, comentaris, adjunts i sessions de temps.
  • Billing: contractes, fites de facturació, avals de licitació, sincronització bidireccional amb Holded ERP.
  • Payroll: nòmines amb càlcul legal espanyol, suport per a diversos convenis col·lectius (TIC, Comerç Catalunya), exportació SILTRA.

A sobre de tot això viuen dues peces que el separen d'un SaaS operatiu clàssic: un subsistema d'extracció de contractes amb IA (puges un PDF de contracte, la IA emplena l'alta) i un motor d'agents IA propi anomenat Brain, que permet configurar protocols multi-pas perquè la plataforma executi feina autònoma sobre les dades del workspace.

Nexo corre en producció a nexo.kiwop.com amb desenes de workspaces actius. No són milers: és un SaaS B2B mid-market, no una app de consum. I es mostra a la nostra web com a cas d'èxit encara que sigui producte nostre, perquè la història tècnica és exactament la mateixa que explicaríem si el client fos extern.

Per què Laravel + Inertia.js + React (i no SPA pura)

La primera decisió gran va ser l'esquelet del frontend. El 2025 el consens per inèrcia era "SPA en React/Vue consumint API REST o GraphQL". Nosaltres vam anar per una altra via: Laravel 12 al backend, React 18 + TypeScript al frontend, i Inertia.js com a pont. No una SPA separada. Un monòlit modern amb vistes React renderitzades des de controladors Laravel.

Quan separes backend i frontend en dos repositoris connectats per API, guanyes una cosa i en perds cinc. Guanyes llibertat per posar un altre frontend demà (una app mòbil, una altra marca amb la mateixa API). Perds: autenticació unificada, validació compartida, SEO trivial, deploy atòmic, estat de sessió server-side, i la capacitat d'iterar una feature en un sol commit. Per a una aplicació autenticada interna com Nexo, aquestes cinc coses pesen molt més que la flexibilitat teòrica.

Inertia resol l'encaix. Els controladors Laravel, en comptes de Blade o JSON, tornen Inertia::render('NomPagina', $props). Això renderitza un component React concret (resources/js/Pages/Workspace/Dashboard.tsx) amb les props que el backend ja ha calculat. Navegar és un fetch intern que intercanvia component i props sense recarregar: UX de SPA, veritat al servidor, autenticació per cookies i middleware Laravel de tota la vida.

El middleware HandleInertiaRequests comparteix props globals (usuari, workspace, rol, flags) a totes les pàgines, així cap component demana aquest estat a cada pàgina ni muntem un Redux/Zustand per a alguna cosa que el servidor ja sap. Deploy únic: git pull, composer install, npm run build, neteja de cachés, sense finestres d'incoherència entre API i client.

El cost és que en obrir Nexo a integradors externs necessitem API formal paral·lela. La vam muntar aviat: REST API `/api/v1` amb Laravel Sanctum (tasques, projectes, registres, docs, leads, billing, webhooks), vivint al costat de les rutes Inertia. Frontend propi per Inertia, integradors per REST amb tokens.

Nexo — stack i capes: Laravel 12 + Inertia.js + React + MySQL + agents Brain

El model multi-tenant: Organization → Workspace → Member

La segona decisió estructural és com fas multi-tenancy. Dues escoles enfrontades: shared database amb columna de tenant contra schema per tenant. Vam triar la primera, amb una jerarquia de tres nivells.

  • Organization: agrupació opcional per a comptes enterprise amb diversos workspaces sota una mateixa marca.
  • Workspace: la unitat real de tenancy. Dades, configuració, mòduls i identitat visual propis. Identificat per slug a la URL: /w/kiwop/dashboard.
  • WorkspaceMember: taula pivot que connecta usuaris i workspaces amb un rol (owner, admin, comercial, operations, member, viewer).

Vam triar shared database amb columna workspace_id per tres raons pragmàtiques: evolucionar el model sense migrar N esquemes, analítiques creuades de producte sense federar queries, i una operació de backup/restore única. El cost és que un bug que ometi un filtre per workspace_id pot, en teoria, creuar dades entre tenants. Ho mitiguem amb dues peces.

La primera és el middleware `EnsureWorkspaceContext`, que resol el slug de la URL al model Workspace, verifica membresia de l'usuari i, si el workspace pertany a una organització, també la membresia en ella. Falla amb 403 si alguna cosa no quadra. Corre a totes les rutes sota /w/{workspaceSlug}/*.

La segona és el trait `WorkspaceScoped` aplicat als models Eloquent sensibles (BrainProtocol, BrainProtocolRun, etc.), de manera que qualsevol query aplica per defecte where workspace_id = ?. Desactivar-lo requereix fer-ho explícit, i això es veu en code review.

Hi ha un detall operatiu que documentem com a regla absoluta: els controladors de workspace han d'incloure string $workspaceSlug com a segon paràmetre (després de Request $request) abans de qualsevol altre. Laravel injecta paràmetres posicionalment, i ometre el slug fa que el següent paràmetre ($id) rebi el slug i la request mori en un 404 silenciós molt difícil de diagnosticar. La ruta index funciona, cosa que fa que el bug només aparegui en obrir el primer detall.

Nexo — jerarquia multi-tenant: Organization > Workspace > WorkspaceMember > User

La següent taula resumeix les decisions arquitectòniques troncals que vam prendre a Nexo, amb la raó i el trade-off assumit. Si estàs dissenyant un sistema semblant, aquesta taula és probablement la part de l'article que et convé copiar-enganxar en un document intern.

Module toggle system: activar/desactivar features per workspace

Cada workspace activa o desactiva mòduls de forma independent. El mecanisme: una columna JSON enabled_modules a la taula workspaces que mapeja mòdul a booleà. La llista és tancada i viu al codi com a constant Workspace::MODULES (no deixem que l'usuari creï mòduls nous en temps d'execució). Inclou els blocs core (dashboard sempre actiu, fichaje, registros, solicitudes), analítica (rentabilidad, informes), feina (projects, tasks), vendes (leads, outbound, licitaciones), finances (billing_contracts, billing, contabilidad), eines (booking, decision_requests, chat), coneixement (docs), settings (branding, notifications, settings), HR (employees, practicas, personal_documents, payroll) i mòduls més recents com chatbot, pulse_surveys, proactive_agents, capacity_planner, project_pulses i meetings.

Dos punts consulten el toggle: el mètode isModuleEnabled('leads') del model, i sobretot el middleware `EnsureModuleEnabled` aplicat als grups de rutes:

El middleware comprova dues coses en ordre: (1) si el mòdul està activat per al workspace, i (2) si el rol de l'usuari en aquest workspace pot accedir al mòdul. Davant de qualsevol fallada, torna 404 (no 403, per no filtrar l'existència de la ruta). És una petita mesura de security through obscurity que, combinada amb les altres capes, puja el llistó del reconeixement per a un atacant intern.

Per què no fem servir feature flags clàssics (LaunchDarkly, Unleash, Flagsmith): els nostres toggles no són experiments A/B ni roll-outs graduals, són configuració de producte per tenant. Un client sense mòdul Payroll no el tindrà fins que el contracti. Ficar un SaaS extern per gestionar això seria complexitat gratuïta.

El mòdul branding mereix menció a part: quan està actiu, el workspace pot personalitzar el seu color primari (CSS variable --workspace-primary), pujar logo i adaptar la identitat visual. Útil per a white-label o clients enterprise que volen la marca interna. El CSS base es manté, només canvia l'accent.

RBAC: rols + permisos sense biblioteques externes

Per a autorització, Nexo no fa servir Spatie Laravel-permission ni cap altra llibreria. Tenim sistema RBAC propi: taules roles, permissions i role_permissions, amb els rols fixos (owner, admin, comercial, operations, member, viewer) i permisos granulars definits a RbacSeeder. Tres raons per no fer servir llibreria.

Primer, el domini de permisos està acoblat a conceptes propis: workspace, organització, mòduls, accions sobre entitats. No és "usuari pot fer X", sinó "aquest usuari, en aquest workspace, amb aquest rol, pot fer X sobre Y". Amb una biblioteca genèrica hauríem acabat reescrivint la meitat de les capes a sobre.

Segon, el Gate de Laravel ja resol l'autorització. Definim gates a AuthServiceProvider apuntant a policies (DocumentationPolicy::viewCategory) i fem servir Gate::allows(...) o @can(...) a les vistes. L'únic que afegim és la resolució del rol de workspace: $workspace->getUserRole($user) i $workspace->canRoleAccessModule($role, 'leads'), cachejat a $request->attributes durant el cicle de vida de la request.

Tercer, tenim un mecanisme de simulació ("veure l'app com un altre usuari", útil a suport) que interactua amb el RBAC de forma específica: quan l'admin simula, el bypass habitual "admin ho pot tot" es desactiva a propòsit, i veus l'app tal com la veu l'usuari simulat. Això viu a EnsureModuleEnabled amb $request->attributes->get('is_simulating', false). A sobre d'una biblioteca externa hauria estat fràgil.

Legacy coexistence: Blade antic + Inertia nou en paral·lel

Nexo té una història prèvia. La versió original de l'aplicació estava construïda en Laravel 10 amb Blade + jQuery + Bootstrap. El frontend Inertia/React és posterior. La migració no es va fer de cop: encara queda codi Blade viu en producció. Aquesta coexistència és la realitat de qualsevol aplicació real que porta diversos anys en producció.

La pregunta és com evites que aquesta coexistència es converteixi en un problema. La nostra resposta és el middleware `LegacyRouteGuard`, registrat com a legacy.guard, aplicat a totes les rutes antigues. Fa tres coses:

  1. Logueja qualsevol accés a rutes legacy a storage/logs/legacy-routes-*.log. Telemetria real de quines rutes antigues segueixen vives, guia la priorització.
  2. Bloqueja POST/PUT/PATCH/DELETE amb 409 Conflict i missatge "Legacy interface is read-only". Cap canvi de dades va per la via antiga.
  3. Redirigeix GETs a l'equivalent nova quan hi ha un mapeig definit. El redirectMap associa ruta legacy a ruta workspace ('admin.horarios' => 'workspace.registros.index', etc.) i resol paràmetres (workspaceSlug, articleId, contract) al moment.

La regla clau és "GETs oberts, writes tancats". Si bloqueges tot, trenques bookmarks i enllaços vells en correus. Si permets writes, tens dos camins per modificar les mateixes dades i no pots garantir que les validacions, esdeveniments i logs d'auditoria del camí nou es disparin. La combinació "llegeix pel camí vell, escriu només pel nou" permet no trencar bookmarks mentre migrem vistes una a una sense por.

Hi ha una excepció: les rutes de simulació d'admin (/admin/simulate-employee, /admin/stop-simulation, /admin/toggle-view-mode) no porten el guardià, perquè són portes legítimes de suport. La resta, sí.

És un patró replicable: si tens una app Laravel de 2019 amb Blade i vols migrar a Inertia sense parar sis mesos el negoci, afegeix middleware guardià, bloqueja writes, logueja accessos, mapeja redirects, i ataca la migració per freqüència d'accés real (no per ordre alfabètic ni per gust estètic).

Nexo — coexistència legacy Blade i nou Inertia amb middleware guardià

Brain — el sistema d'agents IA de Nexo

Aquí entra la peça més pròpia de Nexo: Brain, un motor d'agents IA multi-pas que viu dins de l'aplicació. No és un wrapper de LangChain ni un framework genèric. És un motor dissenyat per executar automatitzacions sobre les dades d'un workspace, amb límits explícits i traçabilitat total.

El domini són dos models: `BrainProtocol` (definició: nom, descripció, passos, configuració) i `BrainProtocolRun` (execució: context, resultats pas a pas, estat, tokens). Tots dos viuen sota workspace_id amb el trait WorkspaceScoped, així que mai es creuen entre tenants.

El motor `BrainProtocolEngine` processa passos seqüencialment amb un context mutable en memòria. Cada pas llegeix del context, executa la seva lògica i escriu sota una output_key que els següents passos poden llegir. Aplica dos límits durs escrits a foc:

Un protocol amb més de deu passos es trunca en executar. Si el context acumulat supera 300.000 caràcters, loguegem warning i abans de cridar el model el truncem amb un marcador [CONTEXT TRUNCAT PER LÍMIT DE MIDA]. Aquests dos límits són la barrera contra el risc més real d'un agent: l'explosió combinatòria de context i cost quan itera sobre els seus propis outputs.

Brain suporta sis tipus de pas:

Nexo — Brain Protocol Engine: sis tipus de pas executats seqüencialment sobre un context mutable

El pas ai_call és el que més feina fa. Llegeix les claus marcades com a input_keys, les serialitza a JSON sota capçaleres ### clau i les afegeix a una secció ## DATOS DISPONIBLES concatenada al prompt. Això fa explícit el contracte prompt-context, evita que el model inventi dades fora de la secció, i facilita el debug: el context exacte queda a l'audit trail.

El pas action és el que fa de Brain "un agent" i no un pipeline. Tipus suportats: create_task, create_proposal, save_memory, alert. Cadascun resol paràmetres amb plantilles {{key.subkey}} contra el context: el títol d'una tasca pot ser {{ai_summary.content}} i el motor el substitueix abans d'executar. Connecta el raonament del model amb l'acció real sobre el domini.

El pas condition és el tallafoc. Amb skip_remaining_if_false: true, si la condició no es compleix, la resta del protocol es salta i els passos queden marcats com a skipped_remaining. Permet protocols del tipus "si no hi ha res a fer, no gastis tokens".

Exemple: protocol d'onboarding de leads. (1) connector_fetch de leads nous de les últimes 24h. (2) condition que avorta si la llista està buida. (3) data_filter que es queda amb leads de score > 60, límit 20. (4) ai_call que genera un primer esborrany de correu. (5) action tipus create_proposal que deixa la proposta a la safata del comercial — no un correu enviat, perquè les accions amb efecte extern passen per aprovació humana.

Tot queda registrat a BrainProtocolRun: context final, resultats de cada pas amb duració i estat, tokens, usuari, font del trigger (manual, schedule, connector_update, agent_alert) i timestamps. Aquest audit trail és la taula per la qual un compliance officer pot auditar què va fer la IA sobre les dades del client. Sense ell, un SaaS amb agents no és auditable; amb ell, ho és al gra més fi. Brain és la nostra materialització interna de molts dels patrons que documentem a agents IA en producció: patrons i antipatrons per al 2026.

Extracció de contractes amb IA + anti-al·lucinació

L'altra peça important d'IA a Nexo és anterior a Brain i resol un cas concret: donar d'alta un contracte de facturació a partir del seu PDF. La feina manual de copiar nom de client, import, dates, dades e-Fact, avals i fites és enganxosa i propensa a errates.

El subsistema `AiContractExtraction` resol aquest flux. Un endpoint POST /billing/contracts/ai-extract rep el PDF (màxim 10MB), el passa per PdfTextExtractor (basat en smalot/pdfparser), i l'entrega a l'extractor triat per AiExtractorFactory. La factory tria entre OpenAiContractExtractor (GPT-4o en la seva versió estable més recent, configurable per OPENAI_MODEL) o el seu equivalent Anthropic segons AI_PROVIDER. La interfície ContractExtractorInterface garanteix que canviar de proveïdor és un flag.

El prompt està optimitzat per a contractes en castellà, amb regles anti-al·lucinació explícites. La resposta és JSON estructurat on cada camp és un objecte amb value, confidence (0-1) i evidence (cita literal del text del PDF). Això canvia la UX radicalment: el formulari s'autoemplena només quan confidence >= 0.5, els camps emplenats apareixen amb vora blau clar i tooltip "Emplenat per IA (confiança: 85%)", i un acordió "què va trobar la IA?" mostra l'evidència literal. L'humà mai confia cegament: revisa, ajusta i desa.

Les regles anti-al·lucinació del system prompt són el nucli de per què això funciona. Si la IA no troba un camp, el valor ha de ser null i confidence ha de ser 0. Si infereix un valor per context (assumir 21% d'IVA quan no està desglossat), ha de baixar la confidence i afegir una entrada a warnings. Els warnings es mostren abans de desar: "No s'ha trobat IVA desglossat, assumint 21%", "Data de fi no especificada clarament". La combinació confidence + evidence + warnings + revisió humana obligatòria és el que fa que una extracció amb LLM sigui operable en un entorn on un error et porta a emetre malament una factura.

Tot queda a la taula ai_contract_extractions: qui va pujar, quan, quin arxiu (amb hash SHA256; no desem el text complet, és important per a privacitat), quin proveïdor, quin model, tokens, cost estimat, resultat JSON, warnings, estat. Si cinc mesos després algú pregunta "com van arribar aquestes dades al contracte?", la resposta és a la base de dades.

El motor general al darrere — el que permet swap entre OpenAI i Anthropic amb un flag, el que gestiona límits de pressupost (AiBudgetGuard), el que resol quina connexió fer servir segons workspace (AiConnectionResolver) — és l'`AiGateway`, el mateix que fan servir els ai_call de Brain. Centralitzar la sortida a LLMs dona tres coses: punt únic per a rate limits i budgets, punt únic per loguejar tokens per feature (chat, agent, extraction), i punt únic per canviar proveïdor sense tocar codi de negoci. Si demà provem un Sonnet 5 o un GPT-6, el canvi viu en una sola línia.

Aquesta abstracció l'apliquem també en projectes de client sota integració de LLMs i desenvolupament d'agents IA. Centralitzar la porta de sortida al model és un dels primers consells que donem a qui integra IA en un producte en producció.

Integració amb Holded ERP (bidireccional)

Nexo no reemplaça l'ERP de facturació. Reemplaça el panell de gestió que mira a l'ERP. Per a facturació real seguim fent servir Holded i sincronitzem bidireccionalment amb ell. La integració viu sota app/Services/Holded/ amb quatre peces: HoldedClient (client HTTP), HoldedSyncService (orquestrador), HoldedStatusMapper (mapeig d'estats) i SyncResult / BatchSyncResult (resultats tipats).

La sincronització segueix el patró clàssic d'integració amb ERP extern: jobs en queue amb reintents, sincronització programada cada 15 minuts, i un Artisan command holded:sync-invoices --sync per forçar una ronda manual. La direcció bidireccional vol dir dos fluxos: de Nexo a Holded quan una fita es marca com a "emesa" (es crea la factura) o "pagada" manualment, i de Holded a Nexo quan un client paga una factura a l'ERP i el canvi d'estat torna a la següent ronda.

La gestió de conflictes és on aquestes integracions s'embruten. La nostra regla: "Holded guanya en estats financers, Nexo guanya en metadata comercial". L'estat de pagament el decideix Holded (sistema financer oficial). Les etiquetes internes, notes i vincles a projectes els decideix Nexo. Aquesta divisió redueix els conflictes a un subconjunt manejable on una de les dues parts és sempre autoritativa.

HOLDED_ENABLED activa o desactiva la integració per entorn. La configuració es fa a nivell de workspace, no globalment — cada workspace pot apuntar a la seva pròpia instància de Holded amb la seva pròpia clau.

Nexo — integració bidireccional amb Holded: jobs en queue, reintents, resolució de conflictes per capa d'autoritat

Observabilitat i operació en producció

Si no ho pots operar, no ho tens. Nexo corre en producció sota nexo.kiwop.com amb un stack senzill: PHP 8.4 FPM amb pool dedicat, MySQL/MariaDB 10.4, Node 20 via nvm per a build de frontend, i nginx al davant. En desenvolupament, DDEV amb rèplica de l'stack a Docker. Paritat dev/prod acotada (PHP 8.2 local vs 8.4 producció).

L'observabilitat diària es recolza en tres fonts. storage/logs/laravel.log és el log principal (errors, excepcions, warnings de l'engine de Brain quan el context se'n va de mida). storage/logs/legacy-routes-*.log és el log dedicat d'accessos a rutes legacy, rotat per dia: si una ruta no apareix en dues setmanes, és candidata a esborrat. I BrainProtocolRun és l'audit trail estructurat de cada execució d'agent, consultable per SQL.

El deploy segueix ./scripts/deploy.sh: artisan down, backup de DB, git pull, composer install --no-dev, build de frontend si van canviar deps, artisan migrate --force, cachés, artisan up. Rollback: restaurar últim mysqldump i git checkout <sha>. Ràtio de deploys baix, revisió humana per deploy.

Una peça operativa interessant és el relay de Claude CLI (claude-relay.service). Alguns workspaces fan servir connexió IA per autenticació CLI (estil OpenClaw) en comptes d'API key, per consumir una subscripció Claude Max dedicada. El relay és un daemon PHP sota systemd com a usuari proves (mai root) que tradueix requests HTTP internes a crides al CLI claude -p. Patró per desacoblar cost d'IA de l'ús real de la plataforma.

Escalat i lliçons apreses

Convé parar i explicar què ha fet mal, què faríem diferent i què afegiríem si comencéssim avui.

El que fa mal avui. Els controllers de workspace requereixen recordar la firma (Request $request, string $workspaceSlug, ...). Error humà comès prou vegades. Amb perspectiva, ho hauríem resolt amb injecció de dependència del CurrentWorkspace DTO en comptes de llegir el slug del paràmetre de ruta — ja el registra EnsureWorkspaceContext, només no el fem servir consistentment com a porta única. Migrar els controllers a aquest patró és feina pendent.

La convivència Blade + Inertia ha durat més del planejat. El pla inicial era migrar tota l'app en sis mesos. A dia d'avui, amb el middleware guardià bloquejant writes des de fa més d'un any, segueixen existint vistes Blade de només lectura que ningú ha prioritzat migrar. No és un problema de seguretat però sí de manteniment (dos stacks en paral·lel) i de consistència UX. Lliçó: la migració incremental amb guardià funciona, però necessita calendari explícit i un owner; sense amo clar, la migració es penja.

El que no afegiríem. Hem resistit a ficar un framework d'agents extern (LangChain, CrewAI, Haystack) per a Brain. Brain fa exactament el que necessitem i un framework genèric hauria multiplicat la superfície sense afegir capacitat funcional. Per a un agent que viu dins d'una app Laravel amb domini propi, un motor a mida de poques línies ben enteses pesa menys que un framework de milers de línies que no controles.

El que afegiríem. Estem començant a pensar en WebMCP com a protocol extern per exposar Brain a agents de tercers (Claude Code, Cursor, un PA del client). Si Brain ja executa feina autònoma sobre el workspace amb audit trail complet, exposar-lo com a MCP server permetria a un agent extern executar protocols de Nexo com a tools natives. És el patró que analitzem a l'article sobre MCP, WebMCP i A2A. Feina dels propers mesos.

El que no hem publicat. No donem xifres concretes de workspaces actius, usuaris, tokens mensuals ni runs de protocol al dia. No per secretisme: qualsevol número queda desactualitzat en tres mesos i preferim no donar mètriques que es converteixin en "dada històrica incorrecta". Si aplica a una avaluació tècnica prèvia a un projecte, pregunta'ns directament.

Escala. Nexo no és SaaS massiu: desenes de workspaces B2B mid-market, cadascun amb desenes a baixos centenars d'usuaris. L'arquitectura suporta folgadament un ordre de magnitud més sense redisseny; el coll d'ampolla real — a l'ordre de milers de workspaces — seria la sincronització amb Holded (per rate limit de l'API externa, no per Nexo) i la capa de cache.

Preguntes freqüents

Per què Inertia.js en comptes d'una SPA separada amb API REST?

Perquè Nexo és una aplicació autenticada interna, no un producte públic massiu. Inertia dona UX de SPA amb deploy atòmic, auth unificada, validació compartida i sessió server-side. Una SPA separada només té sentit quan necessites múltiples frontends sobre la mateixa API o quan frontend i backend viuen en infraestructura diferent, cap dels quals aplica. Mantenim alhora una REST API /api/v1 per a integradors externs.

Compartir base de dades amb columna `workspace_id` és segur davant de schema per tenant?

És segur si apliques disciplina: middleware que resolgui el workspace a cada request, trait WorkspaceScoped que apliqui el filtre per defecte, i code review que bloquegi desactivacions del scope. El cost de schema per tenant (migracions N vegades, backups per tenant, analítiques federades) no compensava. Per a càrregues molt altes o requisits regulatoris específics (healthcare) el càlcul canvia.

Què fa un `BrainProtocol` i com es configura?

Un protocol és una seqüència de fins a 10 passos tipats (connector_fetch, data_filter, ai_call, db_query, action, condition) que el motor executa seqüencialment compartint un context mutable. Es configura des del panell del workspace triant el trigger (connector, schedule o manual), definint els passos en ordre i marcant quins són crítics. Cada run genera un BrainProtocolRun amb audit trail complet.

Quant costa Brain en tokens al mes?

Depèn de l'ús i del model. Els límits MAX_STEPS = 10 i MAX_CONTEXT_CHARS = 300000 són els tallafocs durs, i cada ai_call inclou max_tokens propi (per defecte 2000). Per al perfil d'ús actual, el cost és una línia menor de despeses operatives i es monitoritza per feature (chat, agent, extraction) a través de l'AiGateway.

Com gestioneu la integració amb Holded si Holded cau?

Els jobs de sincronització estan en queue amb reintents. Si Holded està caigut, la sincronització es degrada sense trencar Nexo: les fites es poden seguir creant i modificant, la creació a Holded queda encuada i es reintenta quan l'API respon. L'estat visible és "pendent de sincronització". No bloquegem l'operació del workspace per una caiguda de l'ERP.

Per què heu mantingut Blade antic durant més d'un any?

Perquè un big-bang rewrite d'una app en producció amb dades reals és un anti-patró amb històries de terror. El middleware legacy.guard bloqueja writes des del dia u, eliminant el risc d'inconsistències. Migrar vistes de només lectura és feina sense data crítica, guiada per logs d'ús real. L'alternativa — parar features sis mesos — hauria estat molt pitjor comercialment.

Què passa si un desenvolupador oblida `string $workspaceSlug` en un controller de workspace?

La petició arriba al controller però Laravel injecta el slug al següent paràmetre ($id), el lookup falla i la request torna 404. Símptoma: 404 silenciós molt difícil de diagnosticar. La ruta index funciona, així que el bug només apareix en obrir el primer detall. Està documentat com a regla absoluta i és la primera cosa que expliquem a qualsevol desenvolupador nou.

Es pot exposar Brain com a MCP server perquè un agent extern el faci servir?

Avui no, està al roadmap. La peça tècnica existeix (motor, audit trail, límits per-workspace) i el pas natural és embolicar-la en un MCP server que respecti RBAC i toggles de mòduls. Obre Nexo a ecosistemes d'agents externs sense obrir la base de dades. Si t'interessa el cas d'ús per al teu propi SaaS, és part de desenvolupament d'agents IA i consultoria d'IA.

Conclusió: construir un SaaS el 2026 no és triar moda, és triar disciplina

La conclusió honesta de convertir Nexo d'una app Laravel 10 amb Blade en una plataforma operativa completa amb Laravel 12, Inertia + React i agents IA és que l'arquitectura tècnica pesa molt menys del que la gent creu a l'hora de triar stack, i molt més del que la gent creu a l'hora de mantenir-lo. Res del que expliquem aquí no és exòtic: Laravel és madur des de fa una dècada, Inertia és una peça simple, el multi-tenancy amb columna workspace_id és el patró per defecte de l'ecosistema PHP, el RBAC amb taules roles + permissions + role_permissions és a qualsevol curs de disseny de bases de dades. La diferència entre un SaaS que escala i un que s'encalla no és al nom del framework — és a la disciplina amb què apliques els middleware que bloquegen accessos creuats, als límits explícits que escrius a foc quan fiques IA al flux, i a la teva capacitat de conviure amb codi legacy sense convertir-lo en una bomba de rellotgeria.

Nexo és el producte que millor il·lustra com treballem. Si la teva empresa es planteja construir un SaaS multi-tenant, si necessita introduir agents IA de forma auditable en una aplicació que ja porta anys en producció, o si estàs avaluant Nexo directament com a plataforma operativa, parlem-ne. Som Kiwop, Agència Digital especialitzada en Desenvolupament de Programari i Intel·ligència Artificial aplicada per a clients globals a Europa i els EUA, i oferim desenvolupament a mida d'agents IA, consultoria d'intel·ligència artificial, integració de LLMs i RAG empresarial per a contextos de producció. Pots veure el cas d'èxit públic de Nexo o escriure'ns directament.

Consulta
tècnica inicial.

IA, seguretat i rendiment. Diagnòstic i proposta tancada per fases.

NDA disponible
Resposta <24h
Proposta per fases

La teva primera reunió és amb un Arquitecte de Solucions, no amb un comercial.

Sol·licitar diagnòstic