rust-crm — decisión de arquitectura
Proyecto: ~/nef/rust-crm. CRM mínimo local-only para clientes de negocio
pequeño (contactos + notas), construido como ejercicio real para aprender Rust.
Contexto
Ya existían 4 intentos previos de CRM en Rails (mvp-crm, crm-demo,
_CRM_ERP, erp-crm-drm-files, todos ~24-26 ago 2026), abandonados. Y hay
un Twenty CRM (TypeScript) en producción real (twenty-vw-crm, self-hosted
PikaPods) — ese sigue siendo la herramienta de trabajo, este proyecto no lo
reemplaza. rust-crm es explícitamente aprendizaje, no producto.
Stack elegido
- SQLite (no Postgres) — el “motor” corre embebido en el proceso, no
como servicio aparte. Correcto mientras sea local-only, un solo usuario.
Si algún día se necesita acceso multi-dispositivo, el repo layer ya usa
sintaxis compatible con Postgres (
RETURNING, placeholders numerados) — el swap sería cambiar connection string + feature flag de sqlx, no reescribir. - axum + sqlx + askama + htmx — server-rendered HTML, sin build step de JS. htmx solo para el único bit dinámico (agregar nota sin reload).
- sqlx con
query_asen runtime, no macrosquery!/query_as!— evita necesitarDATABASE_URLen compile-time para un proyecto que arranca de cero. Camino de upgrade documentado en el README del repo.
Patrones de “good practices” aplicados
- Newtype ids (
ContactId,NoteId) — previene pasar el id equivocado, costo cero en runtime. - Un solo
AppErrorenum →IntoResponse, mapeo HTTP en un solo lugar. - Capa repo separada de handlers — SQL nunca toca HTTP, handlers nunca
tocan SQL. Testeable sin servidor (
tests/contacts_repo.rs, 7 tests contra SQLite real en archivo temporal, no mocks). NewContact/NewNotecomo tipos separados deContact/Note— un form nunca puede inyectar id/timestamps.
Roadmap explícito (no construido aún, a propósito — YAGNI)
Companies, deals/pipeline, tasks/reminders, auth. Se agregan cuando haya necesidad real, no antes.
Estado al cerrar esta sesión
Compila limpio, 0 warnings, 7/7 tests pasan, smoke test manual completo
(crear/listar/filtrar/nota/cambio de estado/delete con cascada/404/400)
contra servidor real. Git inicializado (via cargo new), sin commit aún —
pendiente que Nef lo pida explícitamente.