Hecha por ingenieros, para ingenieros.
Arquitectura, seguridad y operación, expuestas para quienes van a integrarse con esta plataforma, construir módulos sobre ella y aprobar la revisión de arquitectura. Aquí no hay nada suavizado para marketing.
dotnet build CleverInit.slnx
0 Warning(s) · 0 Error(s)
dotnet test CleverInit.slnx
4,028 passed · 0 failed
- 4028
- Pruebas automatizadas, solución host
- 0
- Avisos de compilación tolerados
- 5
- Behaviors en cada comando
- 5
- Pasos para resolver un tenant
Cada decisión de arquitectura es intencionada. Nada está incluido por accidente.
Diez decisiones que marcaron todo lo demás.
Cada una de ellas se impone en el código o se comprueba en CI, no se escribe en un wiki y se confía en la suerte.
- 01
Clean Architecture impuesta por el compilador
Dominio ← Aplicación ← Infraestructura ← API son referencias entre proyectos, no convenciones. Un handler que alcanza la infraestructura rompe la compilación.
- 02
Un único pipeline para cada petición
Registro, trazas, validación, invalidación de caché, transacción: siempre en el mismo orden, de modo que los tiempos, la validación y la semántica de errores se mantienen uniformes en todos los endpoints.
- 03
Una base de datos por cliente registrado
Una cuenta creada a través del registro recibe su propia base de datos SQL, y su cadena de conexión se cifra mediante ASP.NET Core Data Protection antes de almacenarse.
- 04
Las lecturas son proyecciones con contrato de paginación
Toda lectura es una proyección sin seguimiento a un DTO, y todo endpoint de listado devuelve un resultado paginado con un tope estricto. Un conjunto de resultados sin límite no es una opción que ofrezca el código.
- 05
Nada de SQL en crudo, en ningún sitio
Solo consultas parametrizadas de EF Core. La entrada del usuario nunca se interpola en SQL, porque el camino de construcción de cadenas sencillamente no existe.
- 06
Eventos que sobreviven a una caída, con semántica honesta
Lo que hay que anunciar se guarda en la base de datos junto al propio trabajo y luego lo publica un único barredor. La entrega es al menos una vez, los reintentos son finitos y los mensajes muertos se declaran, no se esconden.
- 07
Las migraciones solo se añaden
Una migración ya publicada no se edita nunca. Cada cambio de esquema es una nueva migración hacia delante, en el mismo commit que el cambio de entidad que la necesita.
- 08
Las API están versionadas
La superficie HTTP funciona sobre Asp.Versioning, así que un cambio incompatible es una versión nueva y explícita, no una edición silenciosa bajo su integración.
- 09
Los errores siguen RFC 7807, en lenguaje claro
La validación se traduce en un 400 con un mapa de errores por campo, lo no encontrado en 404, los duplicados en 409, las reglas de negocio en 422, y el detalle es siempre una frase completa, nunca un nombre de clase ni un fragmento de SQL.
- 10
Los módulos tienen que demostrar que se descargan
Cargar un módulo, soltar todas las referencias, forzar una recolección de basura y comprobar que la referencia débil a su contexto de carga está muerta. Si un módulo no puede descargarse de verdad, esa comprobación falla.
Cuatro capas, una sola dirección.
Las flechas de dependencia de abajo son referencias entre proyectos. Cruzarlas no genera un comentario en la revisión de código: rompe la compilación.
- Dominio La más interna · cero dependencias
- Entidades, objetos de valor, eventos de dominio, excepciones de dominio, constantes.
- Aplicación Casos de uso
- Depende solo del dominio. Comandos, consultas, handlers, validadores, mappers, behaviors del pipeline.
- Infraestructura Adaptadores
- Depende de Aplicación y Dominio. EF Core, Redis, MassTransit, JWT, BCrypt, repositorios.
- API Raíz de composición
- Depende de las tres. Controladores, middleware, inyección de dependencias, OpenAPI, mapeo de problem details.
Las capas internas nunca referencian hacia fuera. La dirección se impone donde no admite discusión: en los archivos de proyecto.
Diagrama: cuatro anillos concéntricos — API en el exterior, luego Infraestructura, luego Aplicación, con Dominio en el centro. Las referencias de dependencia apuntan solo hacia dentro; una referencia hacia fuera rompe la compilación.
El pipeline de peticiones.
Cada comando atraviesa los mismos cinco behaviors, del más externo al más interno. Las consultas atraviesan el paso de transacción sin tocarlo.
01
Registro
Nombre del comando y milisegundos transcurridos. Los payloads no se registran nunca.
02
Trazas
Todo el ciclo de vida del handler envuelto en un span de OpenTelemetry.
03
Validación
Se ejecutan todos los validadores registrados; el fallo se convierte en un 400 con un mapa de errores por campo.
04
Invalidación de caché
Las claves de caché se marcan como sucias tras un comando correcto.
05
Transacción
Los comandos se envuelven en una transacción de EF Core. Las consultas se saltan este paso.
Un cliente, una base de datos.
Una cuenta creada a través del registro recibe su propia base de datos SQL, aprovisionada y migrada en el momento en que se crea la cuenta.
La cadena de conexión de esa base de datos se cifra mediante ASP.NET Core Data Protection antes de almacenarse.

Averiguar a qué cliente pertenece una petición es una cadena de cinco pasos. Se detiene en la primera coincidencia.
- 1El claim del token firmadoEl token lleva nuestra firma, que se comprueba antes de confiar en el claim que contiene.
- 2La cabecera X-Tenant-IdPara llamadas de servidor a servidor que no llevan token de usuario.
- 3Una coincidencia de dominio propioEl dominio del propio cliente, asociado a su cuenta.
- 4El subdominio de la plataformaSu nombre en nuestro dominio, cuando no ha traído el suyo.
- 5Un parámetro en la cadena de consultaPor nombre: solo en desarrolloEl valor más fácil de la lista para escribir a mano, y por eso va el último.
Trabajo que sobrevive a una caída.
01
El trabajo ocurre
El estado cambia y se guarda. Todavía nada ha tocado la red.
02
El aviso se guarda, no se envía
El mensaje que el resto del sistema necesita se escribe en una tabla de la base de datos, junto al trabajo que lo produjo.
03
Un único barredor publica
Un procesador en segundo plano recorre la tabla y publica lo que encuentra. Nada más en el código habla con el broker.
04
Los consumidores esperan repeticiones
La entrega es al menos una vez. Los consumidores comprueban una clave de evento procesado o se apoyan en un índice único, de modo que un segundo intento falla sin consecuencias.
Los reintentos son finitos. Tras un número fijo de fallos, un mensaje se marca como muerto y deja de reintentarse; preferimos decirlo a dar a entender lo contrario.
Los módulos son invitados en el proceso del host.
Instalar o quitar uno no requiere reinicio ni nuevo despliegue.
Cada módulo se carga en su propio contexto de carga de ensamblados descargable. Instalar uno no reinicia el host, y quitarlo tampoco.
El aislamiento es real, no nominal. Las dependencias propias de un módulo se cargan de forma privada para él. Lo que viene del host es una lista corta y explícita de contratos compartidos — el SDK de módulos, EF Core, MediatR, FluentValidation y las bibliotecas base de ASP.NET y .NET — más los ensamblados de contratos que los módulos publican unos para otros.
La comprobación que nos importa es la que demuestra esto en lugar de afirmarlo: cargar un módulo, soltar todas las referencias a él, forzar una recolección de basura y confirmar después que la referencia débil a su contexto de carga está muerta. Si un módulo no pudiera descargarse de verdad, esa comprobación falla.
La firma es una firma RSA sobre un hash SHA-256, verificada contra una clave pública que instala el operador. Instalar desde una URL exige algo más que una firma válida: el host de origen tiene que estar en una lista de permitidos, y esa lista se entrega vacía, así que la instalación desde URL no hace nada hasta que un operador añade un origen.
Publicación — un evento del host
- La firma del artefacto — una firma RSA sobre un hash SHA-256 — se verifica contra una clave pública que instala el operador.
- Los ensamblados se cargan en un contexto de carga privado y descargable.
- El manifiesto registra el módulo en el marketplace. Sin nuevo despliegue.
Instalación — un evento del tenant
- Las migraciones se ejecutan en el esquema propio del módulo, dentro de la base de datos de ese cliente.
- El menú y la lista de permisos se actualizan al instante.
- Desinstalar apaga el módulo y conserva sus datos: borrar las tablas es una decisión aparte y deliberada.
Un módulo declara lo que es en un único archivo:
{
"slug": "chat",
"name": "Chat",
"version": "1.0.0",
"sdkVersion": "1.1.0",
"entryAssembly": "CleverInit.Module.Chat.dll",
"schema": "chat",
"dependencies": [],
"frontend": {
"remoteEntry": "panel/remoteEntry.json",
"exposedModule": "./Routes"
},
"navSections": [
{ "items": [
{ "label": "Chat", "routerLink": null, "children": [
{ "label": "chat.menu.conversations",
"routerLink": "/m/chat/conversations" }
] }
] }
],
"permissions": [
{ "name": "Chat.View", "displayName": "View chat" },
{ "name": "Chat.ManageBots", "displayName": "Manage bots" }
],
"dashboardWidgets": [
{ "key": "chat.unread", "requiredPermission": "Chat.View" }
]
}La suite de pruebas es el contrato.
4.028 pruebas automatizadas se ejecutan sobre la solución host, y las suites de los módulos corren encima, con seguimiento por módulo. La regla es binaria: terminado significa que toda la solución reporta cero fallos.
Pruebas de la solución host por capa, según el seguimiento del README del repositorio. Las suites de pruebas de los módulos se cuentan aparte.
Los objetivos de cobertura por capa de la carta técnica, en porcentaje.
- Una compilación con cualquier aviso es una compilación incompleta. El listón es 0 avisos, 0 errores, en cada commit.
- Cada módulo incluye una prueba de paridad: un controlador que pide un permiso que su manifiesto no declara rompe la compilación.
- Todo cambio relevante queda en un historial de auditoría que solo se añade — que solo se añade a través de la aplicación, no sellado criptográficamente frente a alguien con acceso directo a la base de datos.
Fijado, deliberado, aburrido.
Las versiones están fijadas y añadir una biblioteca exige aprobación explícita. Esto es lo que hay hoy en la solución, no una lista de deseos.
Backend
.NET 10 · Clean Architecture · CQRS
- .NET 10 · C# 13 · ASP.NET Core
- EF Core 10 · SQL Server
- MediatR 14 · FluentValidation 12
- Mapperly 4.3 · Ardalis.Specification 9.3
- Redis · MassTransit + RabbitMQ 8.5
- BCrypt · Otp.NET · Asp.Versioning
- Scalar OpenAPI
- xUnit · Shouldly · NSubstitute
Panel
Angular 21 · zoneless · signals
- Angular 21, detección de cambios sin zonas
- Estado guardado en signals
- Frontales de módulos como remotes cargados en ejecución
- Vitest
- inglés · neerlandés · alemán
Operación
Deliberadamente pequeña
- Docker · docker-compose para levantar en local
- Spans de OpenTelemetry en el pipeline de peticiones
- Registro estructurado: secretos, tokens y datos personales nunca llegan a un destino de logs
- RFC 7807 en toda la superficie de errores
Los números detrás del acceso.
Un resumen para el cuestionario; la imagen completa está en la página de seguridad.
- Tokens de acceso
- Caducan en 15 minutos. Los permisos viajan como claims, así que las comprobaciones de autorización son consultas en memoria.
- Tokens de refresco
- Valores aleatorios de 256 bits, almacenados solo como hash, de un solo uso y rotados en cada refresco.
- Contraseñas
- BCrypt con factor de trabajo 12, fijado en el código. Bloqueo tras 5 intentos fallidos por defecto, configurable por tenant.
- Códigos de un solo uso
- Códigos de 6 dígitos por correo o SMS, hasheados en reposo, con caducidad de 5 minutos y un máximo de 3 intentos.
Tres respuestas directas.
- Hay un portal público de API. Cubre las API públicas de la plataforma — exactamente esas, y nada más. Lo interno se queda interno.
- Kubernetes es hacia donde va la plataforma. Qué proveedor la ejecutará es algo que deliberadamente no publicamos: ese silencio es una decisión de seguridad, no un descuido.
- Sin facturación por uso y sin prorrateo: ni hoy, ni previsto. La factura predecible es la función.
¿Quiere profundizar?
Treinta minutos con las personas que lo escribieron. Traiga su pregunta de arquitectura más difícil.
Reservar una llamada