Ir al contenido

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.
  • 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.
  • REVOKE explícito en toda tabla nueva (ADR-0006).
  • Aplicar la migración a las DOS bases: db:migrate y db:migrate:test.
  • /bin/rm para artefactos de drizzle-kit: rm está aliaseado a trash.
  • Leer la matriz antes de tocar permisos. Los cinco de clientes ya existen y son más finos de lo que uno diseñaría de memoria.

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:

  1. 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.
  2. 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.
  3. CHECK de que los cuatro campos de verificación van juntos o los cuatro nulos — la misma forma que cierres_procesamiento_completo en 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.

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 no 11 − 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.

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. Nunca DELETE.
  • El tipo (residencial/comercial) se puede cambiar después.

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.

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.

Archivos: api/src/modules/clientes/direcciones.ts

Cliente 1—N Dirección. Sin ruta_id: es M8.

Archivos: api/src/modules/clientes/{routes,validation}.ts

Los cinco permisos ya están en la matriz. No hay DELETE sobre un cliente.

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.

Colección 8-Clientes (y Sesion pasa a 9-). El roadmap pasa M5 a ✅.


La de la spec, más:

  1. Los tests de M1–M4 siguen en verde sin haberlos tocado.
  2. Quitar cualquiera de los tres invariantes de la base pone un test en rojo.