ConTodo
Consultor Senior — Consolidación Técnica (Big Four)

ConTodo ERP — Documento Técnico Consolidado: Stack, Arquitectura de Software, Estándares de Código, Testing, Observabilidad, Performance y ADRs

ConTodo ERP — Documento Técnico Consolidado

Propósito. Este es el contrato técnico único y vinculante de ConTodo, un ERP SaaS cloud-native multi-tenant para PYMEs y medianas empresas de Perú y LATAM (textiles, importadoras, comercializadoras, distribuidoras y manufactura ligera). Consolida los entregables de Arquitectura de Solución, Backend Rails, Frontend, DevOps/AWS y Ciberseguridad, y —de forma deliberada— reconcilia las contradicciones inter-documento que la revisión crítica (Debate 02) identificó. Donde los agentes individuales divergían, este documento decide. Es la fuente de verdad para Ingeniería, QA, Seguridad y Plataforma.

Disclaimer anti-overclaiming. Ninguna arquitectura es "la correcta" en abstracto: se selecciona la que mejor equilibra el perfil real de ConTodo (multi-tenancy de PYMEs, compliance SUNAT, equipo de 4–6, presupuesto de startup). Toda decisión es reversible solo a un costo; las menos reversibles —modelo de tenancy, tipo de clave de tenant, región— se tratan con el mayor rigor. No declaramos "compliance" sino readiness: un control diseñado no es un control con evidencia de operación.


0. Resumen ejecutivo y decisiones reconciliadas

La revisión adversarial (Debate 02) emitió un veredicto contundente: las piezas eran de calidad superior, pero el ensamble contenía 8 contradicciones (5 de severidad Alta), un modelo de costos que omitía IA y BI, y un riesgo de aislamiento cross-tenant no resuelto en la intersección de pooler + RLS. Este documento incorpora esas correcciones como decisiones cerradas.

#Contradicción detectadaDecisión consolidada (vinculante)
C1Región AWS (us-east-1 vs sa-east-1)us-east-1 primaria para MVP y crecimiento (menor costo, latencia ~90 ms aceptable). sa-east-1 (São Paulo) se ofrece como SKU "Residencia LATAM" para Enterprise. DR en us-west-2.
C2Nombre de columna de tenant (tenant_id vs company_id)Canónico: tenant_id. Único nombre en esquema, RLS, jobs, cache keys y headers (X-Tenant-Id).
C3Tipo de tenant_id (UUID vs BIGINT)BIGINT para PK internas y tenant_id (índices y particionado eficientes); UUID solo para identificadores expuestos públicamente (slugs, links de comprobante, event_id).
C4/C6Bus de eventos y stack de BI (Redis vs SNS/EventBridge vs Kafka/MSK)Un solo pipeline en MVP: Outbox transaccional → relay Sidekiq → EventBridge para integración. BI se alimenta del mismo outbox (batch). Kafka/MSK/Debezium se difieren a Fase 3 y solo si la latencia BI <5 min lo exige.
C5OpenAPI vs GraphQL / SSRREST versionado + OpenAPI como contrato único. No hay GraphQL ni SSR. Frontend es SPA pura sobre CloudFront+S3.
C7Modelo de Silo EnterpriseSilo = RDS dedicada vía Rails connects_to, compartiendo el cluster ECS por defecto; ECS dedicado solo en el SKU "Aislamiento físico total".
C8Costo de IA ausente del modeloModelo de costos consolidado incluye líneas de IA (Bedrock managed) y BI. Margen de infra realista 65–75%, no >80%.
RT1Fuga cross-tenant vía pooler + RLSMandato: toda query corre en transacción con SET LOCAL; tests de aislamiento a través del pooler; validación de tenant_id tras cada checkout de conexión. Es el riesgo de seguridad nº 1.

Principio rector unificado: "deliberadamente aburrido". Stack maduro, contratable en Perú/LATAM, sin servicios exóticos en el MVP. La complejidad operativa es una restricción de primer orden para un equipo de 4–6. Cada servicio gestionado adicional debe justificar su costo operativo, no solo su beneficio técnico.


1. Stack tecnológico completo

1.1 Tabla maestra de tecnologías

CapaTecnologíaVersión objetivoJustificación
Lenguaje backendRuby3.3 (YJIT activo)+15–25% throughput sin cambios de código; ecosistema maduro.
Framework backendRuby on Rails7.2 (modo API)Productividad, transaccionalidad ACID, multiple databases nativo.
Modularizaciónpackwerk (modular monolith)última estableBounded contexts con dependencias enforced en CI.
Base de datosPostgreSQL16RLS, particionado nativo, MERGE, paralelismo.
Cache / colas / sesionesRedis7Broker Sidekiq, fragment cache, rate-limit, idempotency store.
Jobs asíncronosSidekiq7Colas por prioridad, retries, observabilidad.
Pool de conexionesRDS Proxy / PgBouncer (modo transaction)Multiplexa conexiones; obligatorio desde 100 tenants (con mandato RT1).
AutenticaciónDevise + JWT (access/refresh)Access 15 min + refresh rotatorio; MFA para admins.
AutorizaciónPundit + Rolify (RBAC)Policies testeables; RBAC módulo × sucursal × acción.
APIREST /api/v1 (JSON:API-like) + OpenAPIOpenAPI 3.1Cacheable, integrable con SUNAT/bancos; contrato tipado FE.
Lenguaje frontendTypeScript5 (strict)Tipado extremo a extremo; cero any en CI.
Framework frontendReact18Estándar de mercado, ecosistema amplio.
Bundler / devViteúltima estableHMR excelente, build a estáticos para S3/CloudFront.
Routing FEReact Routerv6Rutas anidadas, lazy por módulo, guards RBAC.
Server stateTanStack Queryv5Caché/sincronización con tenant_id en cada key.
Client stateZustandúltimaSession/tenant/UI/prefs; persistencia selectiva.
EstilosTailwind CSS + Radix UI (headless)Control total de a11y y estilo; design system "Telar".
Validación FEZod + React Hook FormSchemas derivados del dominio, sin drift cliente/servidor.
GráficosRechartsComposable, accesible, ligero para módulo BI.
i18ni18next + react-i18next + Intles-PE base; localización fiscal separada de UI.
CómputoAWS ECS FargateStateless, sin gestión de nodos (vs EKS).
Base de datos gestionadaAmazon RDS PostgreSQL Multi-AZOLTP; Aurora a evaluar a escala.
ObjetosAmazon S3 (+ Intelligent-Tiering, Object Lock)SPA, CPE/XML/CDR, backups; WORM para audit log.
CDN / EdgeCloudFront + ACMTLS, cache SPA, PoP Lima.
Firewall L7AWS WAF + Shield StandardOWASP, rate-limit, geo.
DNSRoute53Failover DR, health checks.
Cache gestionadaElastiCache Redis (Multi-AZ)Cluster mode desde ~1.000 tenants.
SecretosAWS Secrets Manager + KMSCredenciales, certificados SUNAT, rotación.
Bus de eventos (integración)Amazon EventBridge / SNSDestino del outbox relay.
IaCTerraform≥ 1.7State en S3 + lock DynamoDB; policy-as-code (checkov).
CI/CDGitHub Actions (OIDC)Sin llaves estáticas; deploy gated a prod.
Registro de imágenesAmazon ECREscaneo on-push.
ObservabilidadCloudWatch + X-RayLogs, métricas, trazas, alarmas, SLO.
IA (MVP)Amazon Bedrock (managed)Sin self-host GPU en MVP; Llama-VPC como add-on enterprise.

1.2 Decisiones de stack descartadas y por qué

AlternativaEstadoRazón del descarte
Microservicios desde el día 1DescartadoTransacciones cross-módulo (venta→kardex→contabilidad) exigen ACID; complejidad operativa 5x sin demanda de escala.
Hanami/dry-rbDescartadoEcosistema menor, curva de equipo más alta.
Schema-per-tenant (gema apartment)DescartadoTecho de escala (~3–5k schemas), migraciones O(N), lo peor de ambos mundos.
Database-per-tenant universalDescartado como defaultCosto US$ 12k–40k/mes a 800 tenants; ofrecido solo como Silo Enterprise.
Next.js / Remix (SSR)DescartadoApp tras login: SEO irrelevante; SSR runtime es TCO y complejidad evitables.
GraphQL como entrada únicaPospuestoN+1 y autorización por campo complican control fiscal; REST se cachea mejor.
EKS (Kubernetes)PospuestoOverhead de control plane/nodos para equipo de 6; reconsiderar >2.000 tenants.
Kafka/MSK + Debezium en MVPDiferido a Fase 3Contradice "stack aburrido"; +US$ 1.500–2.500/mes y ops; el outbox ya captura cambios.
Llama self-host (GPU) en MVPDiferidog5/p4 24/7 = US$ 1.000–6.000/mes; Bedrock managed cubre el MVP.
Cifrado por-tenant (BYOK/KMS)DiferidoMiles de CMK = costo/ops; add-on enterprise "Soberanía".

2. Arquitectura de software

2.1 Estilo arquitectónico

ConTodo es un Modular Monolith sobre Rails, organizado por bounded contexts (DDD), con comunicación interna por eventos de dominio + Outbox pattern transaccional, y exposición vía API REST versionada + webhooks firmados. La evolución a microservicios es selectiva y bajo demanda (un pack se extrae solo cuando su perfil de escala lo justifica), nunca prematura.

PrincipioDecisiónJustificación
Consistencia transaccionalModular monolith con packsEl asiento contable derivado de una venta no puede vivir en una saga distribuida frágil.
Comunicación internaEventos asíncronos (outbox) salvo invariantes ACIDDesacopla módulos; invariantes (venta+kardex) se resuelven in-process en una TX.
Lecturas pesadasCQRS ligero (vistas materializadas + read replica)Reportes/EEFF no compiten por locks con la operación transaccional.
Contrato de APIREST /api/v1 + OpenAPI como fuente de verdadTipos FE generados; gate en CI si el spec cambia.
Aislamiento de tenantDefensa en profundidad (app + RLS + storage + jobs)Una sola barrera nunca es suficiente para un ERP contable.

2.2 Mapa de bounded contexts

Reglas de dependencia (enforced por packwerk en CI):

  • SharedKernel no depende de nadie; todos dependen de él.
  • Operaciones y Finanzas nunca se referencian por clase; solo por eventos o published APIs (Sales::PublicApi).
  • IAM/ORG son dependencias síncronas permitidas (tenant y usuario están en todo request).
  • Un PR que introduce una dependencia no declarada falla el build.

2.3 Eventos de dominio y Outbox pattern (pipeline único)

El evento se persiste en la misma transacción que el cambio de estado; un relay Sidekiq lo publica después a EventBridge. Garantía at-least-once → consumidores idempotentes por event_id.

CREATE TABLE outbox_events (
  id           BIGSERIAL PRIMARY KEY,
  tenant_id    BIGINT NOT NULL,
  event_id     UUID NOT NULL UNIQUE,        -- idempotency key (público)
  name         TEXT NOT NULL,               -- 'sales.sale_confirmed'
  schema_ver   INT  NOT NULL DEFAULT 1,
  aggregate    TEXT NOT NULL,               -- 'Sale#1234'
  payload      JSONB NOT NULL,
  occurred_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  published_at TIMESTAMPTZ
);
CREATE INDEX idx_outbox_unpublished ON outbox_events (id) WHERE published_at IS NULL;

Decisión de reconciliación (RT3/C4/C6): un solo mecanismo de captura de cambios en MVP — el outbox. BI consume el outbox (batch). No se introduce CDC/Debezium/Kafka mientras el batch satisfaga la latencia objetivo de BI (<5 min no es requisito de MVP). Esto elimina el "doble pipeline de eventos" y ~US$ 1.500–2.500/mes de infra prematura.

2.4 Modelo de tenancy (decisión menos reversible)

Shared Schema + PostgreSQL Row-Level Security (RLS), con Silo on-demand para Enterprise.

CapaMecanismoFalla segura
HTTPMiddleware resuelve tenant; rechaza request sin tenant_id.401/403
ORMTenantable concern: scope implícito + asignación automática.Excepción si falta Current.tenant_id
DB (RLS)USING (tenant_id = current_setting('app.current_tenant')::bigint)0 filas (no fuga)
StoragePrefijos S3 tenant/<id>/... + condiciones IAMAcceso denegado
BackgroundTenantSetter.with(args[:tenant_id]) envuelve cada jobJob aborta sin tenant

Mandato crítico RT1 (pooler + RLS). Con RDS Proxy/PgBouncer en modo transaction, una variable de sesión mal scopeada puede filtrar contexto entre tenants. Reglas obligatorias:

  1. Toda query (lectura incluida) corre dentro de una transacción explícita.
  2. Se usa SET LOCAL (transaccional), nunca SET de sesión.
  3. Tests de aislamiento corren a través del pooler, no solo contra Postgres directo.
  4. Validación de app.current_tenant tras cada checkout de conexión. Sin esto validado y probado en CI, la promesa de aislamiento es papel. Es bloqueante de GA.

Particionado: tablas calientes (comprobantes, kardex_movimientos, asientos_contables) particionadas por HASH/LIST (tenant_id) y/o fecha desde el MVP. Para el ~5% de tenants pesados (power-law) cuyo particionado no resuelve noisy-neighbor, el disparador de promoción a Silo se define en §7.


3. Arquitectura frontend

3.1 Decisiones

SPA Vite + React 18 + TypeScript servida desde CloudFront + S3, organización feature-first (vertical slices), un slice por módulo del ERP. Separación estricta server state (TanStack Query) vs client state (Zustand).

PrincipioDecisiónJustificación
Velocidad percibidaCode-splitting por módulo (lazy routes)El usuario abre la app una vez al día; navegar entre módulos debe ser instantáneo.
Estado servidor ≠ clienteTanStack Query (server) + Zustand (UI/sesión)Elimina ~80% de bugs de estado.
Tipado E2ETS strict + tipos generados de OpenAPIErrores fiscales (CPE/PLE) se detectan en compilación.
Multi-tenant en clientetenant_id en cada query key + queryClient.clear() al cambiar empresaEvita fuga de datos en caché.
A11yRadix headless + tokens accesiblesWCAG 2.1 AA; amplía mercado (sector público/licitaciones).

3.2 Estructura y design system

src/
├── app/          # providers, router, guards (Auth/Tenant/Permission)
├── shared/       # ui (design system "Telar"), lib (apiClient, money), hooks, i18n, types (OpenAPI)
├── features/     # 18 slices: ventas, inventario, kardex, contabilidad, ...
└── stores/       # Zustand: session, tenant, ui, preferences

Regla de dependencias (ESLint boundaries): features/*shared/*, nunca al revés; un feature no importa otro feature directamente.

Design system "Telar" sobre Radix + Tailwind, distribuido como @contodo/ui. Componentes de plataforma clave: <DataTable> (TanStack Table + virtualización para 10k+ filas), <FormBuilder> (Zod + React Hook Form, campos condicionales como tipo de cambio), <KpiCard>, <Money> (tabular-nums, nunca toFixed() suelto), <CompanySwitcher>, <RucLookup>, <CommandPalette> (Ctrl+K).

3.3 Multimoneda e i18n

ConceptoTratamiento UI
Moneda base (funcional, PEN)Reportes consolidados, EEFF
Moneda del documento (PEN/USD)Captura en formulario; aparece tipo de cambio si ≠ base
Moneda de presentaciónToggle en dashboards; conversión la calcula el backend (consistencia contable)

i18n con i18next + Intl (no hardcodear separadores). Locale base es-PE; roadmap es-CO/CL/MX/EC + en-US. La localización fiscal (SUNAT, PLE, SIRE, detracción) vive en namespaces por país, separada de la traducción de UI.

Política fiscal innegociable: la emisión de comprobantes nunca usa optimistic update. El usuario ve estado "Procesando" explícito hasta el CDR de SUNAT. La integridad fiscal pesa más que la fluidez percibida.


4. Infraestructura cloud (AWS)

4.1 Diagrama de arquitectura

4.2 Red y seguridad de infraestructura

  • VPC 10.0.0.0/16 en us-east-1, 2 AZ (3 AZ desde 1.000 tenants).
  • Subnets públicas (ALB, NAT); privadas app (Fargate); privadas datos (RDS, Redis sin ruta a internet).
  • VPC Endpoints (S3, ECR, Secrets Manager, CloudWatch) para reducir egreso por NAT.
  • WAF managed rules + rate limit 2.000 req/5min/IP + geo-allow LATAM+EEUU; Shield Standard.
  • Security Groups mínimos (ALB→web:3000, web→RDS:5432, web→Redis:6379).
  • CloudTrail org-wide, GuardDuty, Security Hub, Config rules.

4.3 CI/CD

  • Migraciones como ECS one-off task con advisory lock, siempre expand/contract (backward-compatible).
  • Mandato de migración a escala (RT5): estrategia pg-osc/expand-contract + runbook desde la primera tabla. A 5.000 tenants, un add_index sobre una tabla particionada de cientos de millones de filas es un evento de mantenimiento planificado, no un rails db:migrate trivial.
  • Deploy a prod gated (OIDC, sin llaves estáticas).

4.4 IaC, backups y DR

  • Terraform ≥1.7, state en S3 + lock DynamoDB, módulos versionados, prevent_destroy en RDS/S3, checkov policy-as-code.
  • RDS: snapshots diarios + PITR (retención 14 días) + copia cross-region a us-west-2.
  • S3: versioning + CRR + lifecycle a Glacier para CPE/XML (SUNAT exige 5 años).
  • RPO/RTO: Pyme RPO 1h / RTO 4h (Pilot Light); Enterprise RPO 5min / RTO 1h (Warm Standby).
  • Game days trimestrales de failover.

4.5 Modelo de costos consolidado (reconciliado)

El modelo original de DevOps omitía IA y BI. Esta versión los incorpora (Debate 02 §2.3). Cifras us-east-1 ~junio 2026, ±20%, a revalidar trimestralmente con factura real y prácticas FinOps.

ServicioMVP1005001.0005.000
ECS web601805209503.800
ECS Sidekiq (Spot)25702204201.700
RDS PostgreSQL Multi-AZ1302906201.1508.000–12.000 (sharded/Aurora)
RDS Read Replicas0702204803.000–4.500
ElastiCache Redis50902304301.500
S3 + CloudFront + transfer23903306402.750
ALB + NAT + Route53 + WAF + KMS + Secrets77104161229520
CloudWatch + X-Ray1540110200700
AWS Backup + CRR103090170650
IA (Bedrock managed, tokens)201507001.5003.000–15.000
BI (batch outbox; sin MSK en MVP)030120300800
Subtotal infra (rango medio)~410~1.140~3.300~6.470~30.000–42.000
TOTAL con buffer 15%~470~1.310~3.800~7.440~35.000–48.000
Costo por cliente/mes~94~13~7,6~7,4~7–10

Lectura: economías de escala reales (de ~US$ 94 a ~US$ 7–10/cliente), pero el margen de infra realista es 65–75% (no >80%) una vez costeados IA y BI —los diferenciadores que el negocio venderá. Si el pricing es US$ 25–40/cliente/mes, el margen sigue siendo sano. RDS es el mayor centro de costo (Graviton, Reserved Instances, Aurora Serverless v2 como palancas). Cola critical siempre On-Demand, nunca Spot (RT6: riesgo fiscal de job a medias en interrupción).


5. Seguridad

5.1 RBAC + ABAC multi-tenant

Unidad atómica de autorización: el permiso (recurso:acción), no el rol. Usuarios reciben roles dentro del scope de un tenant (y opcionalmente sucursal). Soporte ABAC selectivo (sucursal propia, monto ≤ X, horario).

Rol baseScopePrincipio
super_admin (plataforma)GlobalSin acceso a datos de negocio en claro salvo break-glass auditado
owner (tenant)TenantÚnico que crea otros admins
contadorTenantRegistra, no aprueba pagos (SoD)
tesoreroTenant/sucursalAprueba pagos (SoD con contador)
vendedorSucursalABAC: emite CPE de su sucursal, límite de descuento
almaceneroAlmacénMovimientos kardex; ajustes valorizados requieren contador
rrhhTenantCalcula planilla; aprobación = owner/admin
auditorTenantRead-only total + audit log (para revisores Big Four)

Segregación de funciones (SoD) codificada y validada en tiempo de asignación:

Combinación prohibidaRiesgo
compra:registrar + pago:aprobarPago a proveedor ficticio
proveedor:crear + pago:aprobarProveedor fantasma + autopago
planilla:calcular + planilla:aprobarPago indebido de remuneraciones
asiento:registrar + periodo:cerrarOcultamiento de errores contables

Enforcement: Pundit deny-by-default + scope where(tenant_id) + RLS como doble candado de DB.

5.2 Auditoría inmutable

Tabla audit_events append-only (revocado UPDATE/DELETE al rol app + trigger bloqueante + hash chain prev_hash para tamper-evidence). Réplica a S3 Object Lock (WORM, modo Compliance) + SIEM. Retención: 1 año caliente, 7 años Glacier (plazos tributarios). El hash chain detecta manipulación; el WORM la previene.

5.3 Cifrado y llaves

CapaControl
In-transit externoTLS 1.3, CloudFront+ACM, HSTS, OCSP stapling
In-transit internoRDS force_ssl, Redis TLS, mTLS en ECS donde aplique
At-rest DB/objetosAES-256 KMS (CMK por dominio), S3 SSE-KMS + bucket key
Application-levelActive Record Encryption (AEAD) para PII/secretos: DNI, CUSPP, cuentas, claves SOL, certificados .pfx
SecretosSecrets Manager, rotación automática, sin secretos en código/env claro

5.4 STRIDE, OWASP y riesgos

Modelo STRIDE y controles OWASP Top 10 (2021) completos: A01 (Pundit deny-by-default + RLS + tests cross-tenant), A02 (TLS 1.3 + KMS + AR Encryption), A03 (AR parametrizado + escape React + CSP), A06 (Dependabot + bundler-audit + SCA + SBOM), A10 (allowlist de destinos para webhooks/SUNAT).

IDRiesgoNivelMitigación
R01Fuga cross-tenant (IDOR/scope)AltoRLS + Pundit + tests cross-tenant bloqueantes en CI
RT1Fuga cross-tenant vía pooler (SET multiplexado)CríticaSET LOCAL en TX + tests a través del pooler (§2.4)
R02Compromiso de certificado SUNAT/.pfx/clave SOLAltoCifrado de campo + Secrets Manager + auditoría de uso
R03Exfiltración PII planillas (DNI/CUSPP)AltoAEAD + RBAC rrhh + masking en logs
R08Ransomware/pérdida de datosAltoPITR + snapshots inmutables + DR
R13Incumplimiento Ley 29733 (ARCO)AltoRegistro de tratamiento, flujos ARCO, DPA subprocesadores

Hueco operativo reconocido (Debate 02 §2.4): sostener SOC 2 Type II + ISO 27001 + pentest + bug bounty + ARCO con 1 Security Engineer es trabajo de 3–4 personas. Decisión: outsourcing explícito —vCISO + plataforma Vanta/Drata (automatiza ~60–70% de la evidencia)— presupuestado en el P&L.

5.5 Readiness SOC 2 / ISO 27001

Camino: política de seguridad → controles CC6–CC9 → Type I (~mes 6) → ventana de observación 6–12 meses → Type II. ISO 27001:2022 en paralelo (comparte ~80% de controles; palanca en licitaciones públicas peruanas). SoA sobre los 93 controles del Anexo A.


6. Estándares de código, testing, observabilidad y performance

6.1 Estándares de código

ÁreaEstándar
Backend lint/formatRuboCop (config compartida), bloqueante en CI
Backend seguridadBrakeman (SAST), bundler-audit, Dependabot
Modularizaciónpackwerk valida dependencias entre packs; published APIs explícitas
Migracionesstrong_migrations + expand/contract + pg-osc desde día 1
Frontend lintESLint + eslint-plugin-boundaries (reglas de dependencia entre features)
Frontend tipostsc --strict, cero any en CI
Contrato APIOpenAPI 3.1 como fuente de verdad; dueño designado del spec + proceso de breaking changes documentado (deprecación con header Sunset + 6 meses de solape)
IdempotenciaIdempotency-Key obligatorio en POST de mutación financiera (24h en Redis)
Errores APIRFC 7807 (application/problem+json) con code, detail, errors[]

6.2 Estrategia de testing

CapaBackendFrontendObjetivo
UnitRSpec (models, services, policies)Vitest (hooks, formatters)Lógica pura
IntegraciónRSpec request specsTesting Library + jest-axeComportamiento + a11y
Aislamiento tenantTest cross-tenant por modelo Tenantable, a través del poolertenant_id en query keysBloqueante de merge (R01/RT1)
ContratoValidación OpenAPI ↔ tipos FE en CIopenapi-typescript con gateSin drift
E2EPlaywright (emisión CPE, login, kardex)Flujos críticos
SeguridadBrakeman, bundler-audit, Trivynpm auditSAST/SCA
VisualStorybook + ChromaticRegresión del design system
Cobertura≥ 80%≥ 80%Gate en CI

6.3 Observabilidad

  • Logs centralizados CloudWatch (Fargate awslogs), retención 30 días + export a S3/Athena.
  • Métricas custom: latencia de colas Sidekiq, tiempo de facturación electrónica por tenant, errores SUNAT, p95 por endpoint, lag del outbox relay (now()-occurred_at), medidor de tokens/costo IA por tenant (integrado a CloudWatch, no aislado).
  • Trazas: AWS X-Ray cross-service.
  • Alarmas: 5xx > 1%, p95 > 800 ms, CPU RDS > 80%, cola Sidekiq > 5 min, free storage RDS < 15%, lag outbox > 60s → SNS → Slack/PagerDuty.
  • SLO: 99,9% disponibilidad mensual; dashboards por dominio (App, DB, Colas, Costos, Margen por tenant).

6.4 Performance

VectorEstrategia / presupuesto
Throughput backendYJIT activo; ECS auto-scaling por CPU 60% + RequestCountPerTarget
Conexiones DBRDS Proxy obligatorio desde 100 tenants (con mandato SET LOCAL)
Lecturas pesadasCQRS: read replica + vistas materializadas (kardex valorizado, EEFF)
Locks kardexAdvisory lock por producto en escritura serializada
Overhead de RLSBenchmark obligatorio (Debate 02 §2.2.1): índices alineados con current_setting; objetivo p95 ≤ 300 ms en tablas particionadas a escala
Bundle FEShell+auth+dashboard ≤ 250 KB gzip; cada módulo lazy ≤ 150 KB; Lighthouse ≥ 90
DataTableVirtualización (TanStack Virtual) para 10k+ filas; paginación server-side
Web VitalsReportados a BI para monitoreo continuo

7. ADRs resumidos (registro de decisiones)

ADRDecisiónAlternativas descartadasEstado
ADR-01Shared Schema + RLS como tenancy primarioSchema-per-tenant (techo escala), DB-per-tenant universal (costo)Aceptada
ADR-02Silo on-demand vía Rails connects_to (RDS dedicada, ECS compartido)Forzar todos a Silo; ECS dedicado por defectoAceptada
ADR-03Modular monolith + DDD + packwerkMicroservicios prematurosAceptada
ADR-04Outbox transaccional → EventBridge como pipeline único de eventosDoble pipeline (outbox + CDC/Kafka); publicar directo al brokerAceptada
ADR-05ECS Fargate statelessEKS (overhead), EC2 puroAceptada
ADR-06RDS PostgreSQL Multi-AZ + read replicas; Aurora a escalaAurora desde MVPAceptada
ADR-07tenant_id BIGINT interno; UUID solo públicoUUID para PK; nombre company_idAceptada (reconcilia C2/C3)
ADR-08Región us-east-1 primaria; sa-east-1 como SKU residencia; DR us-west-2sa-east-1 primariaAceptada (reconcilia C1)
ADR-09REST + OpenAPI como contrato único; sin GraphQL ni SSRGraphQL entrada única; SSRAceptada (reconcilia C5)
ADR-10Mandato SET LOCAL en TX + tests a través del poolerSET de sesión con RDS ProxyAceptada (mitiga RT1)
ADR-11IA = Bedrock managed en MVP; Llama-VPC como add-on enterpriseMulti-proveedor + self-host GPU en MVPAceptada (reconcilia C8)
ADR-12BI = batch sobre outbox en MVP; CDC/Kafka diferido a Fase 3 con disparadorMSK/Debezium/Redshift desde MVPAceptada (reconcilia C6)
ADR-13Cola critical On-Demand, Spot solo para reports/aiTodos los workers en SpotAceptada (mitiga RT6)
ADR-14Particionado por tenant_id desde MVP + ADR de sharding con métrica gatillo antes de 1.000 tenantsDiferir sharding sin disparador ni dueñoAceptada (mitiga RT7)
ADR-15Secretos/certificados SUNAT en KMS/Secrets Manager; AEAD para PIICifrado app en DB planaAceptada
ADR-16Seguridad operada con vCISO + Vanta/Drata1 Security Engineer sostiene todoAceptada (mitiga hueco §5.4)

7.1 Disparadores explícitos (lo que no se difiere "al futuro")

Decisión diferidaMétrica gatilloDueño (R) / Aprobador (A)
ADR de sharding por cohorte>70% CPU sostenido en RDS al máximo de su claseR: Solution Architect / A: CTO — antes de 1.000 tenants
Migración a AuroraRDS escalado al tope + variabilidad de carga altaR: DevOps / A: CTO
Introducir CDC/Kafka (BI)Requisito de latencia BI <5 min confirmado por negocioR: Data / A: CTO
Promoción tenant shared→siloTenant en el ~5% pesado con noisy-neighbor reincidente, o contrato de residencia/aislamientoR: SRE+Architect / A: CTO
Contratar SRE dedicado + 3 AZ500 tenantsR: Eng Manager / A: CTO

8. Roadmap técnico incremental

  1. Fase 0 — Cimientos: SharedKernel (tenancy, RLS, Money, EventBus), IAM, Organization, API v1 esqueleto, CI con packwerk + strong_migrations + tests de aislamiento a través del pooler, OpenAPI spec con dueño.
  2. Fase 1 — Núcleo operativo: Inventory/Kardex, Purchasing, Sales con outbox y subscribers contables.
  3. Fase 2 — Núcleo financiero: Accounting (PCGE), Treasury, SUNAT (SIRE/PLE), facturación electrónica.
  4. Fase 3 — Verticales + escala: Manufacturing, Imports, Logistics, Payroll/RRHH; evaluar CDC/Kafka, sharding, Aurora según disparadores.
  5. Fase 4 — Inteligencia: BI (vistas materializadas + read replica), IA (Bedrock: forecast/anomalías/copiloto) consumiendo el changelog del outbox.

9. Conclusión

ConTodo nace pooled (Shared Schema + RLS) para maximizar margen, simplicidad operativa y onboarding —el perfil exacto de un SaaS de PYMEs LATAM— y gradúa a Silo los pocos tenants enterprise que lo exijan, sin reescribir código. La combinación modular monolith + DDD + outbox transaccional + RLS entrega la integridad contable que un ERP exige y el aislamiento que un SaaS demanda, con una ruta de evolución selectiva a microservicios.

A diferencia de los entregables individuales —de calidad superior pero escritos en paralelo sin contrato maestro—, este documento resuelve las contradicciones: una sola región, un solo nombre y tipo de tenant_id, un solo pipeline de eventos, un solo contrato de API, y un modelo de costos consolidado que incluye IA y BI (margen realista 65–75%, no >80%). Trata el riesgo dominante —fuga cross-tenant en la intersección de pooler + RLS— como bloqueante de GA con mandato técnico y verificación automatizada. El stack es deliberadamente aburrido, probado y operable por un equipo de 4–6, con disparadores explícitos —no "al futuro"— para cada decisión de escala diferida. La arquitectura pasó de ser excelente en sus piezas a ser coherente en su ensamble.