Vista general
Las piezas
| Pieza | Qué es | Dónde |
|---|---|---|
crm-core | Dominio puro: enums, reglas, evaluación de KPI. Sin IO, con tests | crates/crm-core |
crm-db | Repositorios SQLx sobre PostgreSQL | crates/crm-db |
crm-api | Axum + async-graphql, auth, webhooks, tareas de fondo | crates/crm-api |
| Migraciones | Esquema, triggers, horas hábiles, funciones de KPI | db/migrations |
| Contrato | schema.graphql — la frontera entre backend y clientes | packages/graphql-contract |
| Tokens | Sistema de diseño compartido | packages/design-tokens |
| Web | React + Vite + Tailwind | apps/web |
| Móvil | Expo + expo-router + NativeWind | apps/mobile |
Por qué tres crates y no uno
La separación no es ceremonia: cada capa tiene una regla distinta sobre lo que puede hacer.
crm-coreno puede tocar IO. Eso lo hace testeable sin base de datos, y es donde vive la lógica que hay que poder defender: transiciones de estado, elegibilidad de FCR, evaluación contra meta.crm-dbno puede tomar decisiones. Lee y escribe; no interpreta.crm-apies lo único que habla con el exterior.
El flujo de una petición
Navegador / app
│ POST /graphql + Authorization: Bearer …
▼
Axum ── CORS ── traza ── extractor de sesión
│
▼
async-graphql ── guarda de rol (@auth) ── resolver
│ │
│ ▼
│ DataLoader
▼ │
crm-core (reglas) ▼
│ crm-db
└────────────────────────────────────────┤
▼
PostgreSQL
(triggers, funciones KPI)
Qué vive dónde, y por qué
| En la base | Motivo |
|---|---|
| Enums de estado, prioridad, canal | Un valor inválido debe ser imposible |
| Horas hábiles | Lo necesitan triggers, vistas y consultas por igual |
| TPA, MTTR, FCR, vencimiento de SLA | Son consecuencia de hechos registrados |
Funciones kpi_* | Un indicador se calcula en un sitio |
| Agregados del cliente | Listar clientes es la consulta más frecuente |
Ver ADR-05 Los indicadores se calculan en la base.
Enlaces
Backend · Base de datos · Contrato GraphQL · Aplicación web · Aplicación móvil