Caso de estudio · Sistema en producción

Un CRM a medida para una distribuidora veterinaria

Centraliza la base de clientes, la geolocaliza y arma las rutas de reparto con tráfico real — reemplazando planillas dispersas por un sistema único, privado y con memoria. Lo construí para RAOVET, la distribuidora donde trabajo, y se usa todos los días.

2026 · en uso diario Next.js · TypeScript · PostgreSQL Repo privado (datos reales)
400+
clientes activos gestionados
3
fuentes de datos unificadas
60
tests automáticos + CI en cada push
01

El problema

RAOVET es una distribuidora mayorista que le vende insumos a veterinarias y clínicas. Su información de clientes vivía repartida en tres fuentes que no se cruzaban entre sí: una agenda de contactos, una base comprada y el propio sistema de facturación. No había forma simple de responder preguntas básicas del día a día — ¿quién es un cliente activo?, ¿dónde está?, ¿a quién le toca reparto hoy y en qué orden conviene ir?

Antes de tocar código armé un pipeline de datos aparte para unificar más de 17.000 contactos de esas tres fuentes en una base curada de más de 400 clientes activos: deduplicación, normalización de teléfonos, segmentación por zona y por recencia de contacto. Ese trabajo fue el insumo; el CRM es lo que construí encima.

02

Lo que construí

Un sistema privado (login propio, sin acceso público, no indexado) que centraliza esa base y la vuelve operable:

Ficha de cliente editable
Con la regla de que lo cargado a mano nunca se pisa: reimportar la planilla original solo agrega clientes nuevos, nunca borra ni sobrescribe una edición.
Mapa y ruteo de reparto
Rutas con tráfico real (Google Routes API) que respetan la ventana horaria de atención de cada cliente — con un heurístico de ruteo propio, porque el optimizador nativo de Google ignora esas ventanas.
Avisos de clientes "de paso"
Al armar una ruta, el sistema sugiere a qué otros clientes avisarles por WhatsApp que el reparto pasa cerca de su zona.
Formulario público de alta
Un cliente potencial carga sus propios datos sin necesitar una cuenta. Entra a una cola de revisión — nunca se activa solo.
03

Las decisiones de datos que importaron

Esta es la parte que más se parece a un trabajo de analista, no solo de desarrollador:

3.1El CUIT no es un identificador único

Un mismo CUIT puede corresponder a varios clientes distintos — distintas veterinarias de un mismo hospital, o varias sucursales, cada una con su propio contacto. Asumir "un CUIT = un cliente" (el atajo obvio) hubiera fusionado registros que en la operación real son personas distintas. La deduplicación se diseñó respetando eso.

3.2El teléfono canónico no era obvio

La planilla original traía dos columnas de WhatsApp por cliente y un campo de estado (ok / cambiar / no) que decía cuál de las dos confiar. La regla de negocio no se adivinó: se confirmó antes de escribir una sola línea de normalización, porque equivocarse ahí significa mandarle un mensaje al número viejo de un cliente real.

3.3Un CUIT o un DNI, según quién completa el formulario

El alta pública la puede llenar cualquiera, y no todos tienen el CUIT a mano. En vez de forzar un solo formato, el campo deja elegir cuál se está cargando (con los guiones del CUIT puestos automáticamente al tipear) y por dentro normaliza los dos a una forma común, distinguible sin ambigüedad — sin duplicar la lógica de validación en dos caminos distintos.

3.4Aviso de duplicados, sin auto-fusionar

Cuando alguien nuevo entra por el formulario público, si comparte CUIT, WhatsApp o nombre exacto con un cliente que ya existe, se avisa en su ficha antes de confirmarlo — nunca se fusiona solo, porque un duplicado real y dos clientes legítimos con el mismo CUIT pueden verse exactamente igual desde el dato.

04

Ingeniería y confiabilidad

Ruteo propio
El orden de las paradas lo calcula un heurístico propio que respeta ventanas horarias, espera y casos infactibles — no el optimizador genérico de Google, que las ignora.
Freno de costos en código
Las llamadas a Google Maps cuestan plata real. Hay un tope diario contado por elementos facturables (no por request) y un tope de paradas por ruta, para que un uso descontrolado no termine en una factura sorpresa.
Defensa en profundidad
Cada Server Action valida su propia sesión y rol, sin depender solo del middleware — son endpoints direccionables por su cuenta, no solo rutas protegidas por el gate de navegación.
Calidad verificable
60 tests automáticos (Vitest) sobre la lógica de negocio pura — el heurístico de ruteo y las normalizaciones, incluido el CUIT con el algoritmo módulo-11 real de AFIP. CI en cada push (lint, tipos, tests, build) y backup diario de la base con retención de 30 días.
05

Estado y próximo paso

El CRM gestiona hoy la identidad y la logística de cada cliente. El próximo hito es sumar el sistema de facturación de la empresa — cruzando por el CUIT ya normalizado, con la misma regla de nunca pisar una edición manual — para ver también cuánto compra cada cliente, cada cuánto, y si tiene saldo pendiente.

Stack
Next.js 16TypeScriptPrismaPostgreSQL (Neon)Auth.js v5TailwindLeafletGoogle Maps PlatformZodVitestGitHub Actions
Sobre el código y los datos

El repositorio es privado: el sistema gestiona datos reales de clientes, así que ni el código ni la base son públicos. Si te interesa ver más — capturas sin datos sensibles, el detalle de alguna decisión o el README completo — escribime y te lo muestro.

¿Querés ver cómo lo pensé?

Puedo mostrarte el detalle de cualquier decisión de este caso, con capturas y sin datos sensibles.

Escribime por WhatsApp