# `modules/companies` `companies` es el módulo independiente y fuente de verdad de la ficha operativa de empresa. ## Responsabilidades Gestiona la entidad `Company` funcional del ERP: - datos legales - datos operativos básicos - `defaultPaymentTermId` - `slug` - `status` Separación arquitectónica: - `modules/identity`: autenticación, cuentas, memberships, acceso account-company - `modules/companies`: ficha real de empresa y validación de company activa ## Tabla `companies` Campos actuales: ```txt id legal_name trade_name tin slug email phone website default_payment_term_id status created_at updated_at ``` Índices esperados: ```txt UNIQUE(slug) INDEX(status) INDEX(tin) ``` ## Servicios públicos El módulo expone: ```txt companies:general ``` Responsabilidad: - validar existencia y estado activo de la company - devolver datos públicos/operativos de empresa No debe asumir: - memberships - autenticación por cuenta - roles/permisos de identity ## Dependencias `modules/companies` depende de `identity` y `catalogs`. Uso actual: - sus endpoints usan `requireIdentityAuthenticated(params)` - `GET /companies/available` no usa `requireIdentityTenant(params)` - `GET /companies/current` y `PATCH /companies/current` usan auth de `identity` + `requireCompanyContext(...)` cableado con `companyAccessChecker` de `identity` y `publicFinder` propio de `companies` - los endpoints legacy por `:company_id` siguen tenant-scoped y comparan `req.user.companyId` con `:company_id` ## Endpoints relevantes ```http POST /companies GET /companies/available GET /companies/current PATCH /companies/current GET /companies/:company_id PATCH /companies/:company_id POST /companies/:company_id/enable POST /companies/:company_id/disable ``` ### Settings de empresa activa `/settings/company/details` consume: ```http GET /companies/current PATCH /companies/current ``` Resolución real del target: ```txt Authorization + X-Company-Id + requireCompanyContext(...) => req.user.companyId ``` El cliente no selecciona la empresa a editar mediante `companyId` en URL, body ni query. ## Política V1 de autorización En la V1 cualquier miembro activo de la compañía puede editar sus datos generales mediante: ```http PATCH /companies/current ``` Política real: ```txt usuario autenticado + X-Company-Id válido + membership activa en esa company + company activa ``` No existe todavía enforcement real de RBAC ni permisos granulares en runtime. `identity:companies:update` puede existir como código futuro de catálogo, pero no representa autorización aplicada hoy. ## Campos editables actuales `PATCH /companies/current` admite solo: ```txt legal_name trade_name tin email phone website default_payment_term_id ``` En frontend, la feature: ```txt /settings/company/details ``` modela esos mismos campos como ficha editable de la company activa. La UI actual agrupa la edición en tres secciones: ```txt Datos generales → legalName → tradeName → tin Datos de contacto → email → phone → website Condiciones comerciales → defaultPaymentTermId ``` Semántica PATCH esperada en la pantalla: ```txt propiedad omitida -> conservar valor actual propiedad presente con valor -> actualizar propiedad nullable presente con null -> limpiar ``` Flujo de guardado esperado en frontend: ```txt GET /companies/current → hydrata formulario editar campos dirty → construir PATCH parcial PATCH /companies/current → respuesta servidor como SSOT → updateActiveCompany(...) → reset del formulario con la respuesta ``` Política de errores frontend en esta pantalla: ```txt fallo inicial sin datos utilizables → PageErrorState error de refetch con datos previos → mantener formulario → ErrorAlert inline cancelled → sin pantalla bloqueante 422 con paths mapeables → errores de campo en RHF error general de guardado → toast → sin reset del formulario ``` La UI no debe seleccionar el target mediante `companyId`. Fuera de alcance de esta API: ```txt slug status fiscalidad avanzada VeriFactu document series payment methods branding bank accounts defaultPaymentMethodId ``` ## `defaultPaymentTermId` Contrato actual: ```txt omitido -> mantener null -> desvincular UUID -> validar dentro de la company activa ``` La referencia es: ```txt nullable company-scoped validada mediante catalogs ``` Semántica HTTP actual: ```txt UUID activo de la misma company -> 200 UUID de otra company -> 422 UUID inactivo -> 422 ``` El error de asignación es deliberadamente opaco: ```txt Default payment term is not assignable to the active company. ``` No expone si el problema concreto es: - inexistente - cross-company - inactivo El PATCH es transaccional: - si falla `default_payment_term_id` - no se persisten otros cambios del mismo request ## Limitaciones V1 de `Company Details` Fuera del alcance funcional actual: - RBAC granular - address - country - language - currency - timezone - branding - logo - bank accounts - fiscalidad avanzada - VeriFactu - series documentales - `defaultPaymentMethodId` ## Reglas - `CompanyMembership` vive en `identity`. - No mover memberships a `companies`. - No usar `customers` para representar la empresa tenant. - `companySlug` no debe venir de token, `X-Company-Slug` ni `req.user.companySlug`.