← Volver a proyectos

Plataforma multi-tenant de core asegurador para aseguradoras y despachos de agentes. Un prototipo falsable con 10 millones de pólizas decidió el modelo de datos antes de construir ninguna función.

Rol: Líder de Arquitectura (proyecto paralelo) Periodo: sep 2026 – presente Estado: building

TypeScriptNestJSNext.jsPostgreSQLPrismaDocker AI: Claude Code
10,000,000

Pólizas de prueba en el benchmark: escala de la cartera de una aseguradora grande

La velocidad de búsqueda se comprobó con millones de pólizas antes de construir ninguna función

Informe del prototipo R1, 2026-09-22

3,030 MB vs 492 MB (6x menos)

Tamaño del índice de búsqueda en disco: diseño alternativo vs diseño elegido

Seis veces menos almacenamiento para mantener rápidas las búsquedas, así que el costo de hosting crece más lento con la cartera

Informe del prototipo R1, 2026-09-22

118 ms vs 1.2 ms

Tiempo para filtrar pólizas por dos atributos: diseño alternativo vs diseño elegido

Resultados instantáneos en la pantalla de búsqueda aunque haya millones de pólizas

Informe del prototipo R1, 2026-09-22

233 s vs 113 s

Tiempo para hacer buscable un atributo nuevo: cartera en línea vs cartera bloqueada

Agregar un campo nuevo a un ramo no deja el sistema fuera de servicio; los agentes siguen trabajando

Informe del prototipo R1, 2026-09-22

Arquitectura

ARQUITECTURA / REYBER 01 Alta del riesgo campos según el ramo 02 Atributos JSONB fuente de verdad 03 Copia por trigger escalares a columna 04 Columna indexada índice simple, 1.2 ms 05 Tabla proyección atributos de listas 06 Aislamiento pruebas RLS, BD, HTTP Elegido con un benchmark de 10M de pólizas: filtro de dos atributos de 118 ms a 1.2 ms.
Atributos dinámicos de REYBER: JSONB, columna indexada por trigger y tabla de proyección.

Contexto

REYBER es una plataforma multi-tenant que gestiona el ciclo de vida del negocio asegurador: productos, pólizas, coberturas, cobranza, siniestros y comunicación con el asegurado. Sirve a dos segmentos con una sola base: aseguradoras, y despachos de agentes que administran pólizas de muchas aseguradoras en todos los ramos. Lo difícil es el multirramo de verdad: vida, salud, autos, embarcaciones, aeronaves, mascotas y carga deben convivir en el mismo motor, con los atributos de riesgo de cada ramo configurables sin programar y sin migrar el esquema.

Es un proyecto paralelo en construcción: 25 documentos de investigación y requerimientos, 71 reglas de negocio y un plan de 36 paquetes; el código arrancó el 22 de septiembre de 2026.

Mi rol

  • Decidí: la arquitectura, el orden del trabajo y qué preguntas deben resolverse midiendo antes de construir.
  • Verifiqué: cada compuerta rompiéndola a propósito, y cada resultado del benchmark contra los planes de ejecución, no solo contra el reloj.
  • Delegué a la IA: el código y los scripts del prototipo, escritos por Claude Code bajo el protocolo escrito, y los borradores de los documentos de investigación, que reviso antes de que cuenten.

Investigación

La pregunta riesgosa era el modelo de datos de los atributos dinámicos: cómo guardar un riesgo cuya forma cambia por ramo y aun así buscarlo rápido. Escribí la hipótesis para que pudiera fallar: una tabla con columnas relacionales para lo estable y una columna JSONB para lo variable, sosteniendo la cartera de un tenant de 10 millones de pólizas (13,027,325 objetos asegurados), respondiendo los filtros de la pantalla de búsqueda dentro de un presupuesto de tiempo y sin cambio de esquema por ramo nuevo. Las decisiones para cada resultado se escribieron antes de medir. Se compararon cuatro configuraciones sobre los mismos datos y la misma tabla: un índice GIN general, índices de expresión por atributo, una tabla de proyección (una fila por valor de atributo) y columnas generadas.

Los primeros resultados estaban mal de una forma instructiva. Con la seguridad a nivel de fila activa, que es como corre el producto, PostgreSQL no usa un índice sobre una condición cuyo operador no está marcado como leakproof, y ningún operador JSONB lo está. Dos de las cuatro estrategias ni siquiera se medían como índices: eran recorridos de tabla con índices ociosos al lado. El protocolo exigía medir con el rol de la aplicación porque “medir sin RLS es medir otro producto”, y el error apareció antes de construir una decisión encima.

Decisiones

DecisiónAlternativas descartadasPor qué
Híbrido G′+C: los atributos viven en JSONB; los escalares buscables se copian a una columna normal mantenida por un disparador; los de tipo lista van a una tabla de proyecciónProyección para todo (C); columnas generadas para todo (G); GIN soloC cuesta 6 veces más índice (3,030 vs 492 MB), escribe 1.7 veces más lento y va 98 veces peor en un filtro de dos atributos (118 vs 1.2 ms). G no puede buscar dentro de listas, que en seguros no son casos de borde (coberturas, beneficiarios, conductores adicionales).
G′ en lugar de G para las columnas escalaresColumnas generadas (G)Hacer buscable un atributo nuevo tardó 113 s con la cartera bloqueada en G. G′ tarda 233 s pero con la cartera viva: un lector concurrente vio un p95 de 3.6 ms contra 4.1 ms en reposo. El precio, un disparador que puede desincronizarse, se acepta y se deja escrito.
El CI corre el Docker Compose del propio proyectoEl bloque services: del proveedor de CIUn contenedor de base pelado no crea los roles, así que CI se conectaría como superusuario que ignora RLS y las pruebas de aislamiento pasarían sin probar nada.
Mismo stack que VetNova con cinco cambios deliberados: cola de trabajos, API multiinstancia sin estado, API oficial de WhatsApp, observabilidad desde el día uno, CI en cada pushEl mismo stack sin cambios; un stack nuevoReutilizar lo que llega con sus smoke tests y cambiar solo lo que los nuevos requerimientos no funcionales obligan.

Validación y QA

El aislamiento se prueba dos veces: una suite a nivel de base de datos y un smoke por HTTP que prueba lo que la primera no puede. El caso cero de la suite de aislamiento comprueba que la prueba misma puede fallar; sin él, los demás pasarían contra una conexión que esquiva RLS. Cada compuerta se rompió a propósito, sobre el artefacto compilado y nunca sobre el código fuente, antes de confiar en ella. El diccionario de datos se genera desde la base, y CI falla si se queda atrás o si una columna no tiene comentario. Las pantallas se comparan par por par contra los diseños aprobados.

Resultados

Atributos dinámicos con 10,000,000 de pólizas sintéticas

Dos opciones de modelo de datos medidas con los mismos datos y seguridad por fila. Menos es mejor en ambos paneles; cada panel tiene su escala.

Atributos dinámicos con 10,000,000 de pólizas sintéticas Dos opciones de modelo de datos medidas con los mismos datos y seguridad por fila. Menos es mejor en ambos paneles; cada panel tiene su escala. Tamaño del índice C · tabla de proyección 3,030 MB C · tabla de proyección: 3,030 MB G′ · híbrido 492 MB G′ · híbrido: 492 MB Filtro de dos atributos C · tabla de proyección 118.0 ms C · tabla de proyección: 118.0 ms G′ · híbrido 1.2 ms G′ · híbrido: 1.2 ms
Fuente: Reporte del benchmark de REYBER · 2026-09-22
Tabla de datos
PanelSerieValor
Tamaño del índiceC · tabla de proyección3,030 MB
Tamaño del índiceG′ · híbrido492 MB
Filtro de dos atributosC · tabla de proyección118.0 ms
Filtro de dos atributosG′ · híbrido1.2 ms
  • El modelo de datos de los atributos dinámicos está decidido con evidencia, con las cifras de arriba, antes de escribir la primera función.
  • 38 migraciones, 70 tablas con tenant y seguridad a nivel de fila forzada en todas.
  • La ronda uno del prototipo cerró el 22 de septiembre de 2026. El informe conserva sus propios errores: el hallazgo de leakproof, una búsqueda por prefijo que estaba rota en los dos candidatos y un resultado que parecía una victoria y no lo era.

Qué resuelve y dónde más sirve

Las agencias de seguros venden muchos ramos, y cada ramo describe su riesgo distinto: un auto tiene placas, una mascota tiene raza. Construir un sistema por ramo es lento y caro. REYBER mantiene un solo motor donde los atributos de cada ramo se configuran, no se programan, y un prototipo medido mostró que la búsqueda sigue rápida con 10 millones de pólizas.

El mismo patrón aplica en otros lugares:

  • Catálogos de e-commerce: productos con atributos distintos por categoría (talla en calzado, capacidad en electrodomésticos) que se buscan sin cambiar la base de datos cada vez.
  • CRMs con campos personalizados: cada cliente define sus propios campos y aun así filtra y reporta sobre ellos con rapidez.
  • Expedientes médicos por especialidad: cada especialidad registra datos distintos en un solo sistema, con búsqueda rápida entre todos los pacientes.

Qué haría distinto

Escribiría la regla “medir con el rol real” desde el primer día de cualquier prototipo, porque solo confié en ella después de ver qué pasa sin ella. El benchmark midió un tenant dominante de 10 millones de pólizas; varios tenants de ese tamaño a la vez no se midieron, y agregaría ese escenario antes de dar el modelo por cerrado. Las consultas que ningún índice arregla (contar una lista enorme, agregarla) resultaron ser una decisión de producto y no de base de datos. El producto todavía no está desplegado: el hosting está decidido pero no aprovisionado.

Evidencia

Versión completa: repositorio privado, disponible bajo solicitud.