Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DevCarlosRodriguez/Documentacion-Arccnova-2.0/llms.txt

Use this file to discover all available pages before exploring further.

El módulo Identity de ArccNova v2 no delega la autenticación a un proveedor externo: ArccNova actúa como su propio IdP con JWT HS256. Sobre esa base de identidad, ADR-015 extiende el modelo de autorización con un RBAC granular y dinámico —roles personalizables por empresa, matriz de permisos por submodulo y acción, y permisos individuales por usuario— que entra en vigor desde la fase inicial del proyecto. ADR-019 cierra el ciclo de tiempo real: los cambios de estado de pagos se notifican al navegador mediante Server-Sent Events.

ADR-003 — Identity Propio con JWT HS256

Fecha: 2026-06-05 | Estado: Aceptado (actualizado 2026-06-10)

Contexto

ArccNova necesita autenticación multi-tenant con claims personalizados (tenant_id, branch_id, user_pk, role). Se evaluaron las alternativas de mercado frente a implementar el propio issuer.

Opciones evaluadas

OpciónDescripción
Identity propio JWT HS256ArccNova implementa su propio issuer con BCrypt + JWT
Auth0SaaS de identidad gestionado
KeycloakIdP open source self-hosted
AWS CognitoIdP gestionado de AWS

Decisión

Identity propio. El módulo Identity de ArccNova implementa login, registro, refresh token rotativo y emisión de JWT HS256 con claims personalizados. ArccNova actúa como IdP del ecosistema: los sistemas futuros validarán el JWT contra los endpoints de ArccNova sin requerir un IdP externo separado.

Estructura de claims del JWT

{
  "sub": "<PublicId del usuario (UUID)>",
  "name": "Juan García",
  "email": "juan@empresa.com",
  "tenant_id": "<PublicId del Tenant>",
  "branch_id": "<PublicId del Branch activo>",
  "role": "Admin",
  "imp": false
}
El claim imp (impersonación) indica si el token fue emitido por un SysAdmin actuando en nombre de otro usuario. El branch_id cambia cuando el usuario selecciona un proyecto diferente en el WebClient; el backend siempre lee estos valores del JWT y nunca del body de la petición.

Consecuencias

  • Sin costo recurrente de IdP externo.
  • Control total del schema de claims y del ciclo de vida del token.
  • El equipo es responsable de la seguridad del issuer (almacenamiento seguro de la clave HS256).
  • Se debe documentar el contrato OIDC-compatible para integraciones futuras.

Actualización 2026-06-10

La decisión se mantiene. Dos ajustes de contexto: (1) RH/Nómina ya no es un sistema externo que valide tokens — vive dentro del monolito (ADR-009); el rol de IdP del ecosistema queda reservado para sistemas futuros reales. (2) El modelo de autorización sobre este Identity se extiende a RBAC granular y dinámico — ver ADR-015.

ADR-015 — RBAC Granular y Dinámico en Identity

Fecha: 2026-06-10 | Estado: Aceptado (actualizado 2026-06-11)

Contexto

El módulo Identity implementado opera con 4 roles fijos (SysAdmin, Admin, Agent, Auditor). Los requerimientos del núcleo piden roles personalizados por empresa, una matriz de permisos por área y acción, y permisos individuales por usuario. Congelar los 4 roles y refactorizar después invalidaría buena parte de las historias de usuario ya escritas.

Opciones evaluadas

OpciónDescripción
RBAC granular desde la baseTablas de Roles, Permisos y Membresías; matriz área × acción; overrides por usuario
Congelar los 4 roles fijosPosponer el RBAC como épica posterior; refactor crítico después
Autorización por políticas en códigoPolicies estáticas de ASP.NET sin administración por el usuario

Decisión

El modelo de datos de Identity se extiende desde la fase inicial con cinco componentes:
1

Roles personalizados por tenant

Tabla Roles con TenantId. El rol CEO es protegido (IsProtected = true): no editable ni eliminable por ningún usuario del tenant.
2

Catálogo de permisos: matriz submodulo × acción

Tabla Permissions con Code único, ModuleGroup, Submodule y Action (Ver / Escribir / Actualizar / Eliminar / Descargar). Arranca con los 18 permisos confirmados en 5 áreas (conceptos de mora, cobros, ventas, contratos, usuarios) y es extensible.
3

Permisos individuales por usuario

Tabla UserPermissions que otorga permisos adicionales por encima del rol del usuario, con registro del usuario que los concedió (GrantedByUserId).
4

Membresías usuario-branch con rol propio

Tabla UserBranches con campo RoleId, dejando la estructura lista para que el CRM asocie roles a proyectos sin refactor. Un usuario puede tener roles y permisos distintos en proyectos distintos (confirmado en C12).
5

Auditoría de uso de permisos

Tabla PermissionAuditLogs con retención permanente. Particionable a futuro si el volumen lo requiere.

Permisos protegidos (C33/C34)

La lista de 18 permisos protegidos es exhaustiva y cubre exactamente 5 áreas:
ÁreaAcciones protegidas
Conceptos de moraEditar, Eliminar
CobrosVer, Escribir, Actualizar, Eliminar
VentasVer, Escribir, Actualizar, Eliminar
ContratosVer, Escribir, Actualizar, Eliminar
UsuariosVer, Escribir, Actualizar, Eliminar
Estas acciones se configuran por empresa en TenantProtectedActions y pueden desactivarse (la acción deja de pedir verificación adicional). El rol CEO y Admin están exentos.
Flag de arquitectura abierto (Flag 6): La mecánica actual del v1 usa una contraseña secundaria estática generada por el sistema (campo ActionPasswordHash en Users). Queda pendiente de decisión del arquitecto si v2 replica esa contraseña estática o la sustituye por re-autenticación + permiso RBAC equivalente. Este flag debe resolverse antes de construir el flujo de permisos protegidos.

Política de contraseñas (decisión interna 22)

La política aplica a todo el sistema —login, alta de usuarios y restablecimiento— y corrige la inconsistencia detectada en los documentos fuente (C06 decía “8 dígitos”):
  • Mínimo 8 caracteres
  • Al menos una mayúscula
  • Al menos una minúscula
  • Al menos un carácter especial

Consecuencias

  • Es la épica más grande sobre un módulo ya implementado; precede a casi todo el control de acceso fino.
  • Los guards del WebClient pasan de rol fijo a permiso granular — el contrato del JWT/bootstrap debe exponer los permisos efectivos del usuario.
  • La auditoría permanente implica crecimiento de datos: tabla particionable a futuro.

ADR-019 — Tiempo Real con Server-Sent Events (SSE)

Fecha: 2026-06-10 | Estado: Aceptado

Contexto

El módulo Collections requiere tiempo real para el contador de cobros no identificados y los cambios de estado de pagos en línea. El v1 usa SignalR (WebSockets). El despliegue v2 es distroless detrás de nginx (ADR-011) y se busca mantener el stack sobre HTTP puro.

Opciones evaluadas

OpciónDescripción
SSE nativo de .NET 10TypedResults.ServerSentEvents; unidireccional servidor→navegador
SignalR (WebSockets)Bidireccional; requiere upgrade de protocolo a través del proxy
PollingSimple pero ineficiente y con latencia percibida

Decisión

Tiempo real con SSE usando el soporte nativo de .NET 10 (TypedResults.ServerSentEvents). No se usa SignalR ni WebSockets.

Qué usa SSE

  • Cobros no identificados: contador en tiempo real en la pantalla de cobranza cuando llega un pago cuyo SaleId/ContactId es null.
  • Confirmación de pagos en línea: Stripe notifica por webhook (HTTP POST) al backend → el backend transmite el cambio de estado por SSE al navegador del usuario.

Flujo de pago en línea con SSE

Cliente final         WebClient          ArccNova API         Stripe
     │                    │                    │                  │
     │── pago en línea ──>│                    │                  │
     │                    │── POST /payments ─>│                  │
     │                    │                    │── PaymentIntent ─>│
     │                    │<── SSE stream ─────│                  │
     │                    │                    │<── webhook POST ──│
     │                    │                    │    (payment_intent.succeeded)
     │                    │<── SSE event ──────│
     │<── UI actualizada ─│                    │                  │

Configuración obligatoria de nginx

location /api/payments/stream {
    proxy_pass         http://api_backend;
    proxy_buffering    off;
    proxy_read_timeout 3600s;
    # Alternativa: el backend responde con X-Accel-Buffering: no
}
Sin proxy_buffering off y un proxy_read_timeout amplio, nginx almacena los eventos en buffer y el cliente los recibe con retraso o la conexión se corta. Esta configuración es obligatoria en el endpoint SSE.

Consecuencias

  • Stack HTTP puro: sin negociación de upgrade ni configuración de WebSockets en el proxy.
  • SSE es unidireccional — suficiente para notificar estado; las acciones del usuario siguen siendo requests normales.
  • Cada conexión SSE mantiene un request abierto: dimensionar keep-alives y límites de conexiones de nginx y Kestrel.
  • El patrón webhook + SSE queda sentado con Stripe y se reutiliza cuando el CRM incorpore sus canales de tiempo real.

Build docs developers (and LLMs) love