erp-core/docs/00-vision-general/ARQUITECTURA-SAAS.md
rckrdmrd 0086695b4c
Some checks failed
ERP Core CI / Backend Lint (push) Has been cancelled
ERP Core CI / Backend Unit Tests (push) Has been cancelled
ERP Core CI / Backend Integration Tests (push) Has been cancelled
ERP Core CI / Frontend Lint (push) Has been cancelled
ERP Core CI / Frontend Unit Tests (push) Has been cancelled
ERP Core CI / Frontend E2E Tests (push) Has been cancelled
ERP Core CI / Database DDL Validation (push) Has been cancelled
ERP Core CI / Backend Build (push) Has been cancelled
ERP Core CI / Frontend Build (push) Has been cancelled
ERP Core CI / CI Success (push) Has been cancelled
Performance Tests / Lighthouse CI (push) Has been cancelled
Performance Tests / Bundle Size Analysis (push) Has been cancelled
Performance Tests / k6 Load Tests (push) Has been cancelled
Performance Tests / Performance Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0 + cambios backend
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones en modulos CRM y OpenAPI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:05 -06:00

559 lines
14 KiB
Markdown

---
id: ARQUITECTURA-SAAS-ERP-CORE
title: Arquitectura SaaS - ERP Core
type: Architecture
status: Published
version: 1.0.0
created_date: 2026-01-10
updated_date: 2026-01-10
---
# Arquitectura SaaS - ERP Core
> Detalle de la arquitectura de plataforma SaaS multi-tenant
## Resumen
ERP Core implementa una arquitectura SaaS completa que permite a multiples organizaciones (tenants) usar la misma instancia de la aplicacion con aislamiento total de datos.
---
## 1. Diagrama de Arquitectura
```mermaid
graph TB
subgraph "Clientes"
U1[Usuario Tenant A]
U2[Usuario Tenant B]
U3[SuperAdmin]
end
subgraph "Frontend"
FE[React App]
PA[Portal Admin]
PSA[Portal SuperAdmin]
end
subgraph "API Gateway"
AG[Express.js]
MW1[Auth Middleware]
MW2[Tenant Middleware]
MW3[Rate Limiter]
end
subgraph "Servicios Backend"
AUTH[Auth Service]
USER[User Service]
BILL[Billing Service]
PLAN[Plans Service]
NOTIF[Notification Service]
WH[Webhook Service]
FF[Feature Flags]
end
subgraph "Base de Datos"
PG[(PostgreSQL)]
RLS[Row-Level Security]
end
subgraph "Servicios Externos"
STRIPE[Stripe]
SG[SendGrid]
REDIS[Redis]
end
U1 --> FE
U2 --> FE
U3 --> PSA
FE --> AG
PA --> AG
PSA --> AG
AG --> MW1 --> MW2 --> MW3
MW3 --> AUTH
MW3 --> USER
MW3 --> BILL
MW3 --> PLAN
MW3 --> NOTIF
MW3 --> WH
MW3 --> FF
AUTH --> PG
USER --> PG
BILL --> PG
PLAN --> PG
NOTIF --> PG
WH --> PG
FF --> PG
PG --> RLS
BILL --> STRIPE
NOTIF --> SG
WH --> REDIS
```
---
## 2. Multi-Tenancy con Row-Level Security (RLS)
### 2.1 Concepto
Row-Level Security (RLS) es una caracteristica de PostgreSQL que permite filtrar automaticamente las filas de una tabla basandose en politicas definidas. Esto garantiza aislamiento de datos entre tenants a nivel de base de datos.
### 2.2 Implementacion
#### 2.2.1 Estructura de Tabla Multi-Tenant
```sql
CREATE TABLE tenants.tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL,
slug VARCHAR(50) UNIQUE NOT NULL,
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Tabla de ejemplo con tenant_id
CREATE TABLE users.users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id),
email VARCHAR(255) NOT NULL,
name VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, email)
);
```
#### 2.2.2 Politicas RLS
```sql
-- Habilitar RLS en la tabla
ALTER TABLE users.users ENABLE ROW LEVEL SECURITY;
-- Politica de SELECT
CREATE POLICY users_tenant_isolation_select
ON users.users FOR SELECT
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
-- Politica de INSERT
CREATE POLICY users_tenant_isolation_insert
ON users.users FOR INSERT
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::UUID);
-- Politica de UPDATE
CREATE POLICY users_tenant_isolation_update
ON users.users FOR UPDATE
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
-- Politica de DELETE
CREATE POLICY users_tenant_isolation_delete
ON users.users FOR DELETE
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
```
#### 2.2.3 Middleware de Contexto
```typescript
// tenant.middleware.ts
export async function tenantMiddleware(req, res, next) {
const tenantId = req.user?.tenantId;
if (!tenantId) {
return res.status(401).json({ error: 'Tenant not found' });
}
// Establecer contexto de tenant en PostgreSQL
await db.query(`SET app.current_tenant_id = '${tenantId}'`);
next();
}
```
### 2.3 Ventajas de RLS
| Ventaja | Descripcion |
|---------|-------------|
| Seguridad | Aislamiento a nivel de base de datos |
| Simplicidad | Una sola base de datos para todos los tenants |
| Performance | Indices compartidos, optimizacion global |
| Migraciones | Una migracion aplica a todos los tenants |
| Escalabilidad | Puede manejar millones de tenants |
---
## 3. Billing y Suscripciones
### 3.1 Diagrama de Flujo
```mermaid
sequenceDiagram
participant U as Usuario
participant FE as Frontend
participant BE as Backend
participant S as Stripe
participant DB as Database
U->>FE: Selecciona plan
FE->>BE: POST /billing/checkout
BE->>S: Crear Checkout Session
S-->>BE: Session URL
BE-->>FE: Redirect URL
FE->>S: Redirect a Stripe
U->>S: Completa pago
S->>BE: Webhook: checkout.session.completed
BE->>DB: Crear suscripcion
BE->>S: Obtener detalles
S-->>BE: Subscription details
BE->>DB: Actualizar tenant
BE-->>FE: Success
```
### 3.2 Estados de Suscripcion
```mermaid
stateDiagram-v2
[*] --> trialing: Registro
trialing --> active: Pago exitoso
trialing --> cancelled: No paga
active --> past_due: Pago fallido
past_due --> active: Pago recuperado
past_due --> cancelled: Sin pago 30 dias
active --> cancelled: Cancelacion
cancelled --> [*]
```
### 3.3 Modelo de Datos
```sql
-- Schema: billing
CREATE TABLE billing.subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id),
stripe_subscription_id VARCHAR(100) UNIQUE,
stripe_customer_id VARCHAR(100),
plan_id UUID REFERENCES plans.plans(id),
status VARCHAR(20) NOT NULL, -- trialing, active, past_due, cancelled
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
trial_end TIMESTAMPTZ,
cancel_at_period_end BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE billing.invoices (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL,
subscription_id UUID REFERENCES billing.subscriptions(id),
stripe_invoice_id VARCHAR(100) UNIQUE,
amount_due INTEGER NOT NULL, -- en centavos
amount_paid INTEGER DEFAULT 0,
currency VARCHAR(3) DEFAULT 'USD',
status VARCHAR(20), -- draft, open, paid, void, uncollectible
invoice_url TEXT,
invoice_pdf TEXT,
due_date TIMESTAMPTZ,
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### 3.4 Webhooks de Stripe
| Evento | Accion |
|--------|--------|
| `customer.subscription.created` | Crear registro de suscripcion |
| `customer.subscription.updated` | Actualizar plan/status |
| `customer.subscription.deleted` | Marcar como cancelado |
| `invoice.paid` | Registrar pago exitoso |
| `invoice.payment_failed` | Notificar fallo, marcar past_due |
---
## 4. Planes y Feature Gating
### 4.1 Modelo de Planes
```sql
-- Schema: plans
CREATE TABLE plans.plans (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(50) NOT NULL,
slug VARCHAR(50) UNIQUE NOT NULL,
stripe_price_id VARCHAR(100),
price_monthly INTEGER NOT NULL, -- en centavos
price_yearly INTEGER,
currency VARCHAR(3) DEFAULT 'USD',
trial_days INTEGER DEFAULT 14,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE plans.plan_features (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
plan_id UUID NOT NULL REFERENCES plans.plans(id),
feature_key VARCHAR(50) NOT NULL, -- ej: 'ai_assistant'
feature_value JSONB NOT NULL, -- true/false o {limit: 100}
UNIQUE(plan_id, feature_key)
);
```
### 4.2 Planes Propuestos
| Plan | Precio/mes | Usuarios | Storage | AI | Webhooks |
|------|-----------|----------|---------|-----|----------|
| Free | $0 | 1 | 100MB | No | No |
| Starter | $29 | 5 | 1GB | No | No |
| Pro | $79 | 20 | 10GB | Si | Si |
| Enterprise | $199 | Unlimited | Unlimited | Si | Si |
### 4.3 Feature Gating
```typescript
// plans.service.ts
// Verificar si tenant tiene feature
async hasFeature(tenantId: string, feature: string): Promise<boolean> {
const subscription = await this.getActiveSubscription(tenantId);
const planFeature = await this.getPlanFeature(subscription.planId, feature);
return planFeature?.feature_value === true;
}
// Verificar limite numerico
async checkLimit(tenantId: string, limitKey: string, currentCount: number): Promise<boolean> {
const subscription = await this.getActiveSubscription(tenantId);
const planFeature = await this.getPlanFeature(subscription.planId, limitKey);
const limit = planFeature?.feature_value?.limit ?? 0;
return limit === -1 || currentCount < limit; // -1 = unlimited
}
```
### 4.4 Uso en Controllers
```typescript
// users.controller.ts
@Post()
@RequiresFeature('users.create')
@CheckLimit('users')
async createUser(@Body() dto: CreateUserDto) {
// Solo se ejecuta si tiene la feature y no excede el limite
}
```
---
## 5. Webhooks Outbound
### 5.1 Diagrama de Flujo
```mermaid
sequenceDiagram
participant App as Aplicacion
participant WS as Webhook Service
participant Q as Redis Queue
participant W as Worker
participant EP as Endpoint Externo
App->>WS: Evento ocurrio
WS->>Q: Encolar trabajo
Q-->>W: Procesar
W->>EP: POST con payload firmado
alt Exito (2xx)
EP-->>W: 200 OK
W->>DB: Marcar entregado
else Fallo
EP-->>W: Error
W->>Q: Re-encolar (retry)
end
```
### 5.2 Firma HMAC
```typescript
// webhook.service.ts
function signPayload(payload: string, secret: string, timestamp: number): string {
const signatureInput = `${timestamp}.${payload}`;
const signature = crypto
.createHmac('sha256', secret)
.update(signatureInput)
.digest('hex');
return `t=${timestamp},v1=${signature}`;
}
// Header enviado
// X-Webhook-Signature: t=1704067200000,v1=abc123...
```
### 5.3 Politica de Reintentos
| Intento | Delay |
|---------|-------|
| 1 | Inmediato |
| 2 | +1 minuto |
| 3 | +5 minutos |
| 4 | +30 minutos |
| 5 | +2 horas |
| 6 | +6 horas |
| Fallo | Marcar como fallido |
### 5.4 Eventos Disponibles
| Evento | Descripcion |
|--------|-------------|
| `user.created` | Usuario creado |
| `user.updated` | Usuario actualizado |
| `user.deleted` | Usuario eliminado |
| `subscription.created` | Suscripcion creada |
| `subscription.updated` | Suscripcion actualizada |
| `subscription.cancelled` | Suscripcion cancelada |
| `invoice.paid` | Factura pagada |
| `invoice.failed` | Pago fallido |
---
## 6. Feature Flags
### 6.1 Modelo de Datos
```sql
-- Schema: feature_flags
CREATE TABLE feature_flags.flags (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
key VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
default_value BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE feature_flags.tenant_flags (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id),
flag_id UUID NOT NULL REFERENCES feature_flags.flags(id),
value BOOLEAN NOT NULL,
UNIQUE(tenant_id, flag_id)
);
```
### 6.2 Evaluacion de Flags
```typescript
// feature-flags.service.ts
async isEnabled(tenantId: string, flagKey: string): Promise<boolean> {
// 1. Buscar override de tenant
const tenantFlag = await this.getTenantFlag(tenantId, flagKey);
if (tenantFlag !== null) return tenantFlag.value;
// 2. Buscar valor default del flag
const flag = await this.getFlag(flagKey);
if (flag) return flag.default_value;
// 3. Flag no existe
return false;
}
```
---
## 7. Patrones de Extension SaaS
### 7.1 Extension de Billing
Las verticales pueden extender el sistema de billing agregando:
```typescript
// En vertical: erp-construccion
// Agregar producto de Stripe para servicios adicionales
await billingService.addOneTimeCharge(tenantId, {
name: 'Cotizacion Premium',
amount: 9900, // $99.00
description: 'Generacion de cotizacion con IA'
});
```
### 7.2 Extension de Planes
Las verticales pueden definir features adicionales:
```sql
-- Feature especifica de construccion
INSERT INTO plans.plan_features (plan_id, feature_key, feature_value)
VALUES
('pro-plan-id', 'construction.budgets', '{"limit": 100}'),
('enterprise-plan-id', 'construction.budgets', '{"limit": -1}');
```
### 7.3 Extension de Webhooks
Las verticales pueden agregar eventos adicionales:
```typescript
// Registrar evento personalizado
webhookService.registerEvent('construction.budget.approved', {
description: 'Presupuesto aprobado',
payload_schema: BudgetApprovedPayload
});
```
---
## 8. Seguridad SaaS
### 8.1 Checklist de Seguridad
- [ ] RLS habilitado en todas las tablas multi-tenant
- [ ] Tokens JWT con expiracion corta (15 min)
- [ ] Refresh tokens con rotacion
- [ ] Rate limiting por tenant
- [ ] Webhooks firmados con HMAC
- [ ] Secrets encriptados en base de datos
- [ ] Audit log de acciones sensibles
### 8.2 Headers de Seguridad
```typescript
// Helmet configuration
app.use(helmet({
contentSecurityPolicy: true,
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: true,
dnsPrefetchControl: true,
frameguard: true,
hidePoweredBy: true,
hsts: true,
ieNoOpen: true,
noSniff: true,
originAgentCluster: true,
permittedCrossDomainPolicies: true,
referrerPolicy: true,
xssFilter: true
}));
```
---
## Referencias
- [VISION-ERP-CORE.md](VISION-ERP-CORE.md) - Vision general
- [INTEGRACIONES-EXTERNAS.md](INTEGRACIONES-EXTERNAS.md) - Integraciones
- [PostgreSQL RLS](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) - Documentacion oficial
- [Stripe Billing](https://stripe.com/docs/billing) - Documentacion oficial
---
*Actualizado: 2026-01-10*