Skip to content

Gestión de Campos Personalizados — Frontend

Módulo: CRM Tipo: View Estado: Planificado Fecha: 2026-03-31 Referencia: crm-field.md · field-options.md


Descripción

Los campos personalizados (CRM Fields) permiten extender el formulario de registro de CRM con campos informativos adicionales, configurados por tipo de CRM y por empresa, sin necesidad de desarrollo. El backend está completamente implementado.

Este documento define el requerimiento frontend en dos apartados:

  1. Configuración de módulo: sección nueva en el CRM donde el administrador gestiona qué campos existen para cada tipo.
  2. Formulario de CRM: cómo esos campos aparecen y se guardan al crear o editar un registro de CRM.

Tipos de dato soportados

Los únicos tipos válidos son los que acepta el backend. El tipo CrmFieldDataType debe reflejar exactamente estos siete:

TipoDescripciónComponente de entrada
textTexto libreTextInput
numberNúmeroTextInput type=number
dateFechaDateInput
monedaImporte monetarioTextInput con formato moneda
porcentajePorcentajeTextInput con sufijo %
selectSelección de una opciónSelect con opciones cargadas
autocompleteReferencia a entidad del sistemaAutocomplete contra recurso externo

Corrección requerida antes de implementar: CrmFieldDataType en crm-field.types.ts incluye tipos que el backend rechaza (textarea, decimal, datetime, checkbox) y omite tipos válidos (moneda, porcentaje). Este es el primer cambio a realizar.


Apartado 1: Configuración de módulo

Dónde vive

El CRM tiene cuatro secciones en el sidebar: Bases, Movimientos, Informes y Utilidades (declarada pero vacía). La gestión de campos personalizados se incorpora como nuevo item dentro de la sección Utilidades, accesible desde cualquier tipo de CRM que tenga campos configurados o que permita configurarlos.

El item se llama "Campos personalizados" y su ruta es /configuracion/campos.

La visibilidad del item en la matriz del sidebar (typeConfigurationMatrix) debe activarse al menos para los tipos que usen campos personalizados. Si todos los tipos pueden tener campos, el item se agrega a todos.

Ruta y contexto de tipo

Los campos siempre pertenecen a un tipo de CRM específico. El tipo activo ya está disponible en el contexto global del módulo (TipoCRMProvider). Al entrar a la vista de campos, se trabaja automáticamente con el tipo activo seleccionado.

Ruta: /configuracion/campos

No se necesita un parámetro de tipo en la URL porque el contexto ya lo provee.

Vista: Listado de campos del tipo activo

Muestra los campos personalizados configurados para el tipo de CRM activo.

Encabezado:

  • Título: "Campos personalizados — {nombre del tipo}"
  • Botón "Nuevo campo" (solo administradores)

Tabla:

ColumnaDescripción
Etiquetalabel — texto que ve el usuario en el formulario
Nombre técnicofield_name — identificador del campo
Tipodata_type — badge por tipo (texto, número, fecha, etc.)
RequeridoBadge Sí / No
VisibleBadge Visible / Oculto
AccionesBotón Editar

Estado vacío: si el tipo no tiene campos, se muestra un estado vacío con mensaje explicativo y acceso directo a crear el primero.

API: GET /mod-crm/crm-types/:crmTypeId/crm_fields

Vista: Formulario de alta de campo

Crea un nuevo campo personalizado para el tipo activo.

Campos del formulario:

CampoInputReglas
Nombre técnico (field_name)TextInputRequerido, máx. 20 caracteres, sin espacios ni caracteres especiales, único en todo el sistema
Etiqueta (label)TextInputRequerido, máx. 50 caracteres
Tipo de dato (data_type)SelectRequerido, las 7 opciones disponibles
Es requerido (is_required)CheckboxPor defecto desactivado
Es visible (is_visible)CheckboxPor defecto activado
Valor por defecto (default_value)TextInputOpcional
Recurso (resource)Select de recursosSolo visible y requerido si data_type = autocomplete

Sección de opciones (aparece solo al seleccionar data_type = select):

Al elegir el tipo select, se expande una sección para definir las opciones del campo. El formulario no puede guardarse sin al menos una opción definida.

Cada opción tiene:

  • value — número entero, único dentro del campo, requerido
  • label — etiqueta visible para el usuario, requerida, máx. 100 caracteres
  • order — posición en la lista, opcional

La sección permite agregar filas y eliminar las que todavía no se guardaron.

API: POST /mod-crm/crm-types/:crmTypeId/crm_fields

Payload para tipo select:

json
{
  "field_name": "tipo_consulta",
  "label": "Tipo de Consulta",
  "data_type": "select",
  "is_required": true,
  "is_visible": true,
  "default_value": null,
  "resource": null,
  "options": [
    { "value": 1, "label": "Consulta de Precio", "order": 1 },
    { "value": 2, "label": "Consulta de Stock", "order": 2 },
    { "value": 3, "label": "Reclamo", "order": 3 }
  ]
}

Payload para tipo autocomplete:

json
{
  "field_name": "cliente_referido",
  "label": "Cliente Referido",
  "data_type": "autocomplete",
  "is_required": false,
  "is_visible": true,
  "default_value": null,
  "resource": "clientes",
  "options": []
}

Vista: Formulario de edición de campo

Modifica las propiedades editables de un campo existente. El nombre técnico y el tipo de dato no pueden cambiarse una vez creado — se muestran como texto informativo.

Campos editables:

CampoEditableObservación
field_nameNoSolo lectura, mostrar como texto
data_typeNoSolo lectura, mostrar como badge
label
is_required
is_visible
default_value
resourceNoSolo lectura si es autocomplete

Sección de opciones en edición (solo si el campo es tipo select):

Muestra las opciones activas con posibilidad de:

  • Editar label y order de opciones existentes
  • Agregar nuevas opciones
  • Eliminar opciones existentes (soft delete)

Las opciones eliminadas no se muestran en la sección de edición. La sincronización la maneja el backend: lo que se envíe en el array options define el estado final activo.

API:

  • GET /mod-crm/crm-types/:crmTypeId/crm_fields/:fieldId?include=options — cargar datos actuales con sus opciones
  • PUT /mod-crm/crm-types/:crmTypeId/crm_fields/:fieldId — guardar cambios

Payload de PUT:

json
{
  "label": "Tipo de Consulta",
  "is_required": true,
  "is_visible": true,
  "default_value": null,
  "options": [
    { "id": 1, "value": 1, "label": "Consulta de Precio", "order": 1 },
    { "id": 2, "value": 2, "label": "Consulta de Stock", "order": 2 }
  ]
}

Cambios de infraestructura para este apartado

ArchivoCambio
ts/crm/config/routes.tsxAgregar ruta /configuracion/camposCrmCamposView
ts/crm/config/sidebar.tsAgregar item "Campos personalizados" en sección Utilidades y activarlo en typeConfigurationMatrix
ts/crm/views/Crear CrmCamposView, CrmCamposTable, CrmCampoForm, CrmCampoOptionsEditor

Apartado 2: Campos en el formulario de CRM

Contexto actual

El formulario de registro de CRM (CrmRecordForm) ya incluye la sección "Campos Adicionales" usando el hook useCrmFields y el componente CrmDynamicFields. Los tipos text, number, date y checkbox renderizan correctamente. Los tipos select y autocomplete muestran un alert de "pendiente". El guardado de valores no está conectado.

Carga de campos con opciones

El hook useCrmFields debe solicitar los campos con sus opciones incluidas usando el parámetro ?include=options. Esto es necesario para que los campos tipo select tengan sus opciones disponibles sin llamadas adicionales.

Renderizado del tipo select

Para un campo de tipo select:

  • Renderizar como componente Select
  • Las opciones del campo llegan en field.options[] (filtrar activas: sin deleted_at)
  • Ordenar por order (nulls al final)
  • El valor que se guarda en el form es el value numérico de la opción elegida (como string)

Renderizado del tipo autocomplete

Para un campo de tipo autocomplete:

  • Renderizar como componente Autocomplete
  • field.resource indica la entidad contra la que buscar (clientes, proveedores, vendedores, productos)
  • El componente necesita un mapeo de resource → endpoint de búsqueda
  • El valor guardado es el ID de la entidad seleccionada (como string)

Si algún recurso no tiene endpoint de búsqueda disponible, puede diferirse. El tipo select es prioritario.

Guardado de valores al crear un registro

Los valores de los campos adicionales se envían dentro del payload de creación del CRM Record, no como llamada separada. El formato que espera el backend:

json
{
  "date": "2026-03-31 10:00:00",
  "contact_id": 42,
  "title": "Consulta técnica",
  "fields": [
    { "id": 1, "value": "texto libre" },
    { "id": 3, "value": "2" }
  ]
}

Donde:

  • id es el id de la fila en crm_fields (no el field_name)
  • value es siempre string (o null si el campo es opcional y no se completó)

Antes del submit, el formulario debe transformar el namespace fields.* al formato array:

ts
const fieldsPayload = crmFields
  .filter(field => field.is_visible)
  .map(field => ({
    id: field.id,
    value: formValues.fields[field.field_name] != null
      ? String(formValues.fields[field.field_name])
      : null
  }))

Guardado de valores al editar un registro

Al actualizar, la estrategia del backend es delete-all + reinsert: hay que enviar todos los campos en cada actualización, incluso los que no cambiaron.

El payload del PUT es idéntico al del POST: se incluyen todos los campos con sus valores actuales.

Carga de valores al editar un registro existente

Al abrir un registro existente para editar, los valores guardados se obtienen solicitando el record con ?include[]=fields. El response incluye los campos con su definición y el valor almacenado.

Al recibir la respuesta, se hidratan los valores en el namespace fields.* del formulario:

ts
// Del record existente:
// record.fields = [{ id: 1, field_id: 1, value: "texto libre" }, ...]
// La definición del campo (field_name) se obtiene correlacionando con crmFields

record.fields.forEach(fieldValue => {
  const field = crmFields.find(f => f.id === fieldValue.field_id)
  if (field) {
    form.setValue(`fields.${field.field_name}`, fieldValue.value)
  }
})

Validación dinámica

El schema de Zod del formulario de CRM Record debe construirse dinámicamente en base a los campos activos y visibles:

  • Campos con is_required = true y is_visible = true: requeridos en el schema
  • Campos con is_required = false o is_visible = false: opcionales o excluidos del schema
  • El schema se reconstruye cuando cambia la lista de campos del tipo

Invalidación de caché

Cuando el administrador crea o edita un campo desde la configuración, debe invalidarse el query crmQueryKeys.fields(crmTypeId). Esto asegura que el formulario de registro muestre los campos actualizados sin necesidad de recargar la página.

Cambios de infraestructura para este apartado

ArchivoCambio
crm-field.types.tsCorregir CrmFieldDataType a los 7 tipos válidos del backend
useCrmFields.tsSolicitar ?include=options para traer opciones de campos select
CrmDynamicFields.tsxImplementar select y autocomplete, eliminar alerts de pendiente
CrmRecordFormSerializar fields.* al hacer submit (POST/PUT) con formato [{id, value}]
CrmRecordFormHidratar fields.* al cargar un record existente
CrmRecordFormConstruir schema Zod dinámico respetando is_required por campo

Recursos disponibles para tipo autocomplete

Según la documentación del recurso (crm-field.md):

  • clientes
  • proveedores
  • vendedores
  • productos

El componente CrmDynamicFields necesita un mapeo explícito de nombre de recurso → endpoint o componente de autocomplete.


Orden de implementación sugerido

  1. Corregir CrmFieldDataType en crm-field.types.ts
  2. Agregar ruta e item de sidebar para configuración de campos
  3. Implementar listado de campos del tipo (solo lectura)
  4. Agregar formulario de alta para tipos simples (text, number, date, moneda, porcentaje)
  5. Agregar soporte a opciones en el formulario (tipo select)
  6. Agregar formulario de edición con soporte a modificación de opciones
  7. Actualizar useCrmFields para incluir opciones (?include=options)
  8. Implementar tipo select en CrmDynamicFields
  9. Conectar guardado de valores en submit del CRM Record
  10. Hidratar valores al editar un record existente con campos adicionales
  11. Implementar tipo autocomplete en CrmDynamicFields

Criterios de Aceptación

Apartado 1: Configuración de módulo

  • [ ] AC-CFG-001: El item "Campos personalizados" aparece en la sección Utilidades del sidebar del CRM
  • [ ] AC-CFG-002: La vista muestra los campos del tipo de CRM activo con etiqueta, nombre técnico, tipo, obligatoriedad y visibilidad
  • [ ] AC-CFG-003: Si el tipo no tiene campos, se muestra un estado vacío con acceso a crear el primero
  • [ ] AC-CFG-004: El administrador puede crear un campo de tipo text, number, date, moneda o porcentaje
  • [ ] AC-CFG-005: El sistema rechaza nombres de campo duplicados con mensaje de error claro
  • [ ] AC-CFG-006: Al seleccionar tipo select, aparece la sección de opciones en el formulario. No se puede guardar sin al menos una opción
  • [ ] AC-CFG-007: El administrador puede crear un campo de tipo autocomplete seleccionando el recurso externo
  • [ ] AC-CFG-008: El administrador puede editar la etiqueta, obligatoriedad, visibilidad y valor por defecto de un campo existente
  • [ ] AC-CFG-009: El nombre técnico y el tipo de dato se muestran como solo lectura en el formulario de edición
  • [ ] AC-CFG-010: El administrador puede agregar nuevas opciones a un campo select existente
  • [ ] AC-CFG-011: El administrador puede modificar la etiqueta y el orden de opciones existentes
  • [ ] AC-CFG-012: El administrador puede eliminar opciones (soft delete). Las eliminadas dejan de aparecer en el listado activo
  • [ ] AC-CFG-013: Al crear o editar un campo, el caché de campos del tipo se invalida

Apartado 2: Formulario de CRM

  • [ ] AC-FORM-001: Al crear un CRM Record, la sección "Campos Adicionales" muestra los campos visibles configurados para ese tipo
  • [ ] AC-FORM-002: Los campos con is_visible = false no aparecen en el formulario de registro
  • [ ] AC-FORM-003: Los campos con is_required = true son obligatorios en la validación del formulario
  • [ ] AC-FORM-004: Los campos de tipo select muestran un dropdown con las opciones activas ordenadas
  • [ ] AC-FORM-005: Los campos de tipo autocomplete muestran un autocomplete funcional contra el recurso configurado
  • [ ] AC-FORM-006: Al guardar el registro, los valores se incluyen en el payload como fields: [{id, value}]
  • [ ] AC-FORM-007: Al editar un CRM Record existente, los campos adicionales se pre-cargan con los valores guardados
  • [ ] AC-FORM-008: Al actualizar el registro, todos los campos se envían con sus valores actuales
  • [ ] AC-FORM-009: No aparece ningún alert de "pendiente" o tipo no soportado en el formulario de registro

Alineación de tipos

  • [ ] AC-TYP-001: CrmFieldDataType contiene exactamente text | number | date | moneda | porcentaje | select | autocomplete
  • [ ] AC-TYP-002: Los tipos textarea, decimal, datetime y checkbox no existen en CrmFieldDataType