Skip to main content
Traducción. Autoritativo: fs-core-0011-custom-attributes.md.

Contexto

Todo tenant tiene campos que no anticipamos, y las dos respuestas obvias son ambas incorrectas. Un JSONB libre es inconsultable e intipable, así que el constructor de segmentos no lo puede ofrecer y la validación no lo puede proteger. Una migración por tenant no es un producto. La respuesta es una definición tipada: el tenant declara el atributo y su tipo una vez, y esa declaración alimenta la validación Zod al escribir, los campos disponibles en el constructor de segmentos y la presentación en la consola. Los valores viven en JSONB sobre el contacto; el schema de ese JSONB es dato.

Alcance (normativo)

  • core.attribute_definitions: clave, etiqueta, tipo y restricciones por tenant.
  • Tipos: string, number, date, boolean, enum.
  • Generación de esquema Zod desde las definiciones, cacheada por tenant.
  • Validación de los atributos personalizados del contacto en cada escritura.
  • Exposición al DSL de segmentos como objetivos de condición tipados.
  • Promoción de un atributo caliente a columna real, como operación documentada.

Fuera de alcance (normativo)

  • Atributos sobre entidades distintas del contacto. Loyalty y CRM definen los suyos si los necesitan.
  • Atributos calculados o derivados. Un valor que se calcula es un segmento, no un atributo.
  • Definiciones compartidas entre tenants. Toda definición pertenece a exactamente un tenant.

Comportamiento (normativo)

  1. Una definición está acotada a un tenant. key es único por tenant e inmutable — renombrarlo dejaría huérfano a todo segmento que lo referencie.
  2. El tipo es inmutable tras la creación. Cambiar string a number contra valores existentes es una migración, no una edición; la operación correcta es una definición nueva.
  3. Los valores se validan contra el esquema Zod generado en cada escritura. Un valor inválido se rechaza — un atributo tipado que acepta cualquier cosa en silencio es un JSONB disfrazado.
  4. Las definiciones enum llevan sus valores permitidos; agregar uno está bien, quitar uno solo se permite cuando ningún contacto lo tiene.
  5. Las definiciones se cachean por tenant y se invalidan al cambiar. La validación está en el camino de escritura y nunca debe unir tablas.
  6. Borrar una definición es un borrado lógico. Los valores quedan sobre los contactos; solo se retira la definición, porque segmentos y exportaciones pueden seguir referenciando datos históricos.
  7. El DSL de segmentos ve las definiciones como objetivos de condición tipados — un atributo date ofrece operadores de fecha, uno number ofrece numéricos.
  8. Promoción: un atributo usado en caminos calientes se puede promover a columna real mediante una migración documentada. La definición pasa a apuntar a la columna y se descarta la copia JSONB. Esta es la salida de emergencia que mantiene honesto al patrón a escala.
  9. PROHIBIDO: un atributo personalizado que contenga un documento de identidad o cualquier valor que la plataforma ya modela.

Datos (normativo)

Los valores viven en core.contacts.custom_attributes JSONB, con índice GIN para las consultas de segmentos.

API (normativo)

Eventos (normativo)

Ninguno. Un cambio de definición es administrativo y queda cubierto por la auditoría; los cambios de atributos del contacto ya emiten core.contact.updated.

Criterios de aceptación (normativo)

  1. Un valor que viola su tipo se rechaza con un error tipado que nombra el atributo.
  2. key y type no se pueden cambiar tras la creación.
  3. Quitar un valor de enum que todavía tiene un contacto se rechaza.
  4. Una definición con borrado lógico deja sus valores intactos y legibles.
  5. Las condiciones de segmento sobre un atributo date ofrecen operadores de fecha y rechazan una comparación de string.
  6. La validación no hace ninguna unión de base de datos en el camino de escritura.
  7. Un atributo promovido se lee desde su columna sin que cambie nada desde la vista del llamador.
  8. Negativo: una definición con una clave que colisiona con un campo de plataforma se rechaza.

Ejecución

Un solo slice, comando síncrono. F1b. El generador Zod vive en packages/core junto al generador de unions de catálogos — mismo patrón, misma disciplina de caché.

Preguntas abiertas

Changelog

Registro de entrega

Aún no implementado.