Plan de M5 — Clientes
Objetivo: implementar M5 según la spec de diseño — identidad del cliente, documento con estado de verificación, tipo con lista de precios, direcciones como entidad, y crédito que la base no deja habilitar sin verificar.
Dominio: Clientes — 16 reglas, 13 confirmadas.
Estado: ✅ Terminado (26-ago-2026) — las 9 tasks cerradas.
| Task | Commit | |
|---|---|---|
| T1 — Schema, migración e invariantes | ✅ | 3854e72 |
| T2 — El dígito de verificación | ✅ | 3854e72 |
| T3 — Alta, edición y baja | ✅ | 3854e72 |
| T4 — Verificación | ✅ | 3854e72 |
| T5 — Crédito | ✅ | 3854e72 |
| T6 — Direcciones | ✅ | 3854e72 |
| T7 — Endpoints | ✅ | 3854e72 |
T8 — Pantalla en web/ |
✅ | 5a92506 |
| T9 — Bruno, documentación y cierre | ✅ | abb1781 + este commit |
Lo que se verificó, y cómo. Los tres invariantes de la base se probaron
quitándolos de aquazaku_test y confirmando la suite en rojo: sin el
CHECK de crédito cae el test que escribe directo contra la base; sin el
UNIQUE caen el duplicado real y el de los ceros a la izquierda.
El DV se probó al revés: recortando la tabla de pesos a los nueve de la norma. Ahí caen los tres tests de diez dígitos — que es exactamente el error que en producción no se notaría.
Lo que cambió respecto del plan. Nada de fondo. Apareció un detalle que el
plan no anticipaba: normalizar el documento tiene que quitar los ceros a la
izquierda, o 079123456 y 79123456 son dos filas distintas para el UNIQUE
— el duplicado entrando por la puerta de atrás que RN-CLI-08 viene a cerrar.
La compuerta de M5 es el DÍGITO, no la concurrencia
Sección titulada «La compuerta de M5 es el DÍGITO, no la concurrencia»M2, M3 y M4 tuvieron el mismo riesgo de fondo —dos personas moviendo el mismo
saldo— y lo resolvieron con el UPDATE condicional. M5 no mueve saldos.
Su riesgo es otro y es peor de encontrar: un DV mal calculado no falla. Devuelve un dígito, se imprime en una factura, y nadie lo nota hasta que la DIAN rechaza algo o un cliente dice «ese no es mi NIT». Con nueve pesos sobre una cédula de diez dígitos, la suma da 84 en vez de 127 — y las dos formas responden.
Por eso el cálculo va primero, es una función pura sin base de datos, y se prueba contra los tres vectores de la norma antes de que nada dependa de él.
T2 dígito de verificación ──┐ ├── T3 alta/edición/baja ── T4 verificación ── T5 crédito ──┐T1 schema e invariantes ────┘ ├── T7 endpoints ── T8 pantalla └── T6 direcciones ───────────────────────────────────────────┘ T9 bruno + docs- T2 no depende de nada: es aritmética. Puede ir en paralelo con T1.
- T5 va después de T4 porque el crédito exige verificación, y probar el rechazo necesita poder verificar.
- T9 cierra.
Restricciones globales
Sección titulada «Restricciones globales»- Convención del proyecto: 2 espacios, comillas simples, sin punto y coma.
- Conventional commits, sin
Co-Authored-By. Nunca buildear. - Cada commit deja el proyecto en verde: tests + typecheck, verificado por código de salida.
- Migraciones en SQL explícito, escritas a mano.
REVOKEexplícito en toda tabla nueva (ADR-0006).- Aplicar la migración a las DOS bases:
db:migrateydb:migrate:test. /bin/rmpara artefactos de drizzle-kit:rmestá aliaseado atrash.- Leer la matriz antes de tocar permisos. Los cinco de
clientesya existen y son más finos de lo que uno diseñaría de memoria.
Task 1 — Schema, migración e invariantes
Sección titulada «Task 1 — Schema, migración e invariantes»Archivos: api/src/db/schema.ts, api/drizzle/00XX_*.sql
Dos tablas: clientes y direcciones. Enums para tipo, tipo_documento,
verificacion_estado y verificacion_metodo.
Los tres invariantes que van a la base, no al servicio:
UNIQUE (tipo_documento, numero_documento)— sobre el PAR, no sobre el número: el NIT de una persona natural se basa en su cédula, y prohibir el cruce haría imposible un caso legítimo.CHECK (NOT credito_habilitado OR verificacion_estado = 'verificado')—RN-CLI-15, y cubre los dos caminos: habilitar sin verificar, y desverificar con crédito puesto.CHECKde que los cuatro campos de verificación van juntos o los cuatro nulos — la misma forma quecierres_procesamiento_completoen M4.
clientes no es append-only: un cliente se edita. Lo que no se hace es
borrarlo (RN-CLI-02), así que va REVOKE DELETE y se desactiva.
Prueba de que el invariante existe: quitar cada CHECK tiene que poner un
test en rojo.
Task 2 — El dígito de verificación
Sección titulada «Task 2 — El dígito de verificación»Archivos: api/src/modules/clientes/documento.ts + tests
Función pura. Sin base de datos, sin dependencias.
- Primos consecutivos de derecha a izquierda, tantos como dígitos.
resto = suma mod 11; si es 0 o 1 el DV es el resto, si no11 − resto.- Los tres vectores del dominio como test:
123456789→6,900123456→8,79123456→0. - El caso de diez dígitos como test aparte, con el número que la tabla de nueve pesos calcularía mal.
- Normalización del número: sin puntos, sin espacios, sin guion. Lo que se guarda es el número base.
Task 3 — Alta, edición y baja
Sección titulada «Task 3 — Alta, edición y baja»Archivos: api/src/modules/clientes/service.ts
- El documento se exige (
RN-CLI-13): sin él, no hay alta. - El cruce CC/NIT advierte, no bloquea. El servicio devuelve el aviso con el cliente que ya existe, y quien registra decide.
- Baja =
activo: false. NuncaDELETE. - El tipo (
residencial/comercial) se puede cambiar después.
Task 4 — Verificación
Sección titulada «Task 4 — Verificación»Archivos: api/src/modules/clientes/verificacion.ts
Pasar de pendiente a verificado escribe los cuatro campos juntos: estado,
quién, cuándo y con qué método. El método se deriva del rol de quien verifica
(seller_manual / pos_manual / admin_oficial) — no se pide por parámetro,
porque dejarlo elegir permitiría que un seller marque admin_oficial.
Task 5 — Crédito
Sección titulada «Task 5 — Crédito»Archivos: api/src/modules/clientes/credito.ts
Habilitar exige verificado; el CHECK lo sostiene y el servicio explica.
limite en null es «sin tope» y es el default — el dominio dice que forzar un
número hoy es inventarlo.
Task 6 — Direcciones
Sección titulada «Task 6 — Direcciones»Archivos: api/src/modules/clientes/direcciones.ts
Cliente 1—N Dirección. Sin ruta_id: es M8.
Task 7 — Endpoints
Sección titulada «Task 7 — Endpoints»Archivos: api/src/modules/clientes/{routes,validation}.ts
Los cinco permisos ya están en la matriz. No hay DELETE sobre un cliente.
Task 8 — Pantalla en web/
Sección titulada «Task 8 — Pantalla en web/»Archivos: web/src/app/(app)/modulos/clientes/, web/src/components/clientes/
Tarjetas filtrables y ficha, como usuarios. El DV se muestra junto al número
—900123456-8— y se dice que lo calcula el sistema.
Los cuatro saldos dicen que todavía no hay de dónde calcularlos. No muestran cero.
Task 9 — Bruno, documentación y cierre
Sección titulada «Task 9 — Bruno, documentación y cierre»Colección 8-Clientes (y Sesion pasa a 9-). El roadmap pasa M5 a ✅.
Definition of Done de M5
Sección titulada «Definition of Done de M5»La de la spec, más:
- Los tests de M1–M4 siguen en verde sin haberlos tocado.
- Quitar cualquiera de los tres invariantes de la base pone un test en rojo.
