Skip to main content
Traducción. Autoritativo: fs-core-0002-contacts.md.

Contexto

El contacto es la entidad de la que trata toda la suite. Dos decisiones hacen la nuestra distinta de las herramientas construidas afuera. Un contacto puede ser una empresa (DEC-A1). En Latinoamérica un programa de fidelización o un CRM rutinariamente tienen negocios como clientes —un distribuidor, una clínica, una franquicia— y modelar al cliente como una persona con cumpleaños se rompe apenas aparece una empresa. La división por contact_kind atraviesa validación, triggers de fecha y campos obligatorios. El documento es un campo de primera clase, validado y protegido. Es como una empresa realmente reconoce a su cliente entre sistemas, así que debe normalizarse y validarse con dígito verificador por tipo (DEC-A3). Es también el campo más sensible de la plataforma, así que se enmascara por defecto y su valor completo vive tras un permiso dedicado con cada lectura auditada (DEC-A4). La unicidad es por tenant, nunca global: la misma persona es legítimamente cliente de dos empresas distintas, y esos dos registros nunca deben verse entre sí.

Alcance (normativo)

  • core.contacts con contact_kind persona o empresa.
  • Documento: normalización, validación de formato, validación de DV donde exista algoritmo.
  • Índice único parcial por (tenant, tipo, documento).
  • Enmascaramiento por defecto, lectura completa tras core.contacts.read_national_id, cada lectura completa auditada.
  • Configuración de tenant que hace obligatorio el documento.
  • Campos base por tipo: persona con nombres y birth_date; empresa con legal_name y sin fecha de nacimiento.
  • CRUD de gestión.

Fuera de alcance (normativo)

  • Resolución de identidad y fusiones — FS-CORE-0003.
  • Atributos personalizados tipados — FS-CORE-0011.
  • Consentimiento — FS-CORE-0004. Que un contacto exista no es un contacto al que se le pueda escribir.
  • Importación masiva — FS-CORE-0013.

Comportamiento (normativo)

  1. contact_kind es person | company y es inmutable después de crearse. Cambiarlo invalidaría los campos y triggers que dependen de él; la operación correcta es un contacto nuevo.
  2. Un contacto empresa tiene legal_name y sin birth_date. Los triggers de fecha aplican según el tipo: una persona tiene cumpleaños, una empresa tiene aniversario de constitución.
  3. El documento se normaliza antes de persistir — un normalizador por tipo quita separadores y canonicaliza, de modo que 12.345.678-5 y 123456785 son el mismo valor.
  4. El formato se valida contra la regex del tipo, siempre. Un valor que no calza se rechaza, nunca se guarda “para limpiar después”: DEC-A3 existe porque los datos de identidad sucios son irrecuperables a escala.
  5. Donde hay algoritmo de dígito verificador se ejecuta, y dv_validated registra si pasó. Donde no lo hay, aplica solo la validación de formato y el flag queda en false.
  6. La unicidad es un índice único parcial sobre (tenant_id, national_id_type, national_id) WHERE national_id IS NOT NULL. El mismo documento en tenants distintos siempre se permite.
  7. La creación directa con documento duplicado se rechaza. La importación masiva enruta los duplicados a una cola de revisión de fusión (DEC-A6, FS-CORE-0013).
  8. El documento se enmascara por defecto en toda UI y toda respuesta de API. El valor completo exige core.contacts.read_national_id, y cada lectura completa escribe en core.audit_log — incluidas las exportaciones, registradas como acceso masivo.
  9. El documento es opcional por defecto y configurable como obligatorio por tenant (DEC-A5).
  10. PROHIBIDO: registrar en logs un documento completo · devolverlo sin enmascarar sin el permiso · guardarlo sin normalizar.

Datos (normativo)

Sin particionar — los contactos son un conjunto de trabajo, leído constantemente y acotado por el tamaño del tenant. Clasificación de datos: el documento es sensitive; nombres, email y teléfono son personal. Rige la legislación más estricta entre países soportados (DEC-A4, piso: Ley 21.719 + LGPD).

API (normativo)

El documento completo tiene su propio endpoint en vez de un flag de query. Una ruta separada hace inequívocos el chequeo de permiso, la entrada de auditoría y el rate limit, y vuelve imposible una exposición accidental por un serializador compartido.

Eventos (normativo)

core.contact.created y core.contact.updated, ambos disponibles como webhooks salientes. Los payloads llevan contact_id y los nombres de campos cambiados — nunca el documento, independiente de la configuración de PII del endpoint.

Criterios de aceptación (normativo)

  1. Un RUT chileno con dígito verificador incorrecto se rechaza; el mismo con el correcto se guarda normalizado, con dv_validated = true.
  2. Un CPF brasileño y un CUIT argentino validan cada uno contra su propio algoritmo, con fixtures válidos e inválidos conocidos.
  3. 12.345.678-5 y 123456785 colisionan en el índice único — son el mismo valor tras normalizar.
  4. El mismo documento bajo dos tenants distintos se acepta, y ninguno puede leer la fila del otro.
  5. Un contacto empresa rechaza birth_date; un contacto persona rechaza legal_name.
  6. contact_kind no se puede cambiar después de crearse.
  7. Leer un contacto sin core.contacts.read_national_id devuelve un valor enmascarado en toda forma de respuesta que el módulo expone.
  8. Cada llamada al endpoint de valor completo produce exactamente una fila de core.audit_log nombrando al actor.
  9. Negativo: ninguna línea de log, payload de evento ni mensaje de error contiene un documento completo — verificado por un barrido sobre un set sembrado de requests.

Ejecución

Un solo slice, comando síncrono. Schema y normalizadores en TS-001; endpoints en el primer slice de API del módulo. Normalizadores y algoritmos de DV viven en packages/core como funciones puras con fixtures exhaustivos — es el tipo de código que se escribe una vez y se confía por años, así que se testea en consecuencia.

Preguntas abiertas

Changelog

Registro de entrega

Aún no implementado.