Skip to content

Paquete Compartido @repo/crm-services

Concentrar lógica de dominio CRM reusable entre web y mobile (mocks, reglas y entrada HTTP), sin duplicar datos en pantallas.

La regla canónica del monorepo es: normalizar una vez en shared, orquestar en web cuando haga falta, pintar específico por plataforma.

  • createMockCrmServices(options?)
  • createHttpCrmServices({ apiClient })
  • dashboard
  • agenda
  • cartera
  • catalogo
  • interactions
  • campaigns
  • kanban
  • chatsSinAsignar
  • customFields
  • Todos los métodos están tipados con @repo/shared-types.
  • Implementación HTTP usa @repo/api-client.
  • Mock-first como default para paridad funcional y desarrollo.
  • Mock-first controlado: válido mientras backend todavía no completa un módulo o el flujo real aún no está listo para producto.
  • Web mantiene entrypoints existentes en apps/web/app/services/*.service.ts como wrappers (compatibilidad de imports).
  • Mobile consume singleton getMobileCrmServices() y evita latencia mock artificial.
  • La prioridad de conexión real es:
    1. backend
    2. web
    3. mobile
  • Cuando un módulo todavía no está listo en backend o no ha sido validado en web, mantenerlo mock-first en native no cuenta como deuda automática.
  • Regla permanente de performance:
    • preferir endpoints filtrados del backend antes que overfetch + filtro local
    • solo hacer fallback más amplio cuando el contrato backend todavía no exista o una validación temporal lo exija
    • no cargar datos secundarios si la UI no los va a renderizar
  • No introducir dependencias de UI (React, RN, Next) dentro del paquete.
  • Mantener funciones de dominio puras y adapters por app para mapear view-models.
  • Cualquier endpoint nuevo debe agregar primero contrato en @repo/shared-types.
  • No convertir este paquete en BFF: auth, [REDACTED]s, secretos y policy de sesión siguen fuera, en adapters por plataforma.
  • No dejar que la normalización reusable se disperse en route.ts o componentes si puede vivir aquí.
  • getCarteraItems() mezcla backend real + mock de forma explícita:
    • reales: grupo, cliente/lead, creacion, groupId
    • render derivado: vendedor
    • mock temporal: contacto, ultimaCompra, frecuencia, agenda, interaccion
  • La implementación HTTP de cartera consume:
    • GET /leads
    • GET /employees
    • GET /lead-assignments
    • GET /group-assignments
  • La columna vendedor puede fusionar asignaciones directas a lead y heredadas por grupo para render.
  • La mutación canónica para asignar vendedor desde cartera es assignSellerToGroups() y crea POST /group-assignments.
  • El paquete expone groupId dentro de cada CarteraItem para que cualquier app convierta selección de filas a groupIds únicos sin duplicar lógica.
  • getCarteraItemById() se usa hoy como detalle base de cartera y resuelve GET /groups/:id, porque la ruta interna /cartera/[id] representa el grupo visible, no un lead individual.
  • El paquete ahora también expone:
    • getGroupInvoices(groupId, { page })
    • getGroupContacts(groupId)
    • updateContact(contactId, input)
  • getGroupInvoices(groupId, { page }) consume el contrato oficial:
    • GET /groups/:id/invoices?page=n
  • Esa respuesta ya llega con:
    • data
    • pagination por día
    • day
    • settings
    • emptyState
  • Regla de performance para este método:
    • nunca sobretraer historial completo si backend ya pagina por día
    • el consumidor debe pedir el siguiente lote solo cuando la UI lo necesite
  • getGroupContacts(groupId) resuelve la jerarquía real de BoostAPI:
    • usa GET /contacts?group_id=... como path canónico y más barato para el detalle de cartera
    • no asume 1 group : 1 lead
  • updateContact(contactId, input) hace PATCH /contacts/:id con el contrato oficial de canales/manual channels; no toca el grupo legacy Contactos.
  • CarteraContact también refleja metadatos de performance para UI:
    • hasCustomFieldValues
    • customFieldValuesCount
  • Esos flags permiten que web/mobile solo llamen GET /contacts/:id/custom-field-values cuando el contacto realmente trae extras.
  • El módulo customFields ya expone contratos HTTP + mock para:
    • getCustomFieldTypes()
    • getCustomFieldGroups()
    • getCustomFields()
    • getGroupCustomFieldValues(groupId)
    • saveGroupCustomFieldValues(groupId, input)
    • getContactCustomFieldValues(contactId)
    • saveContactCustomFieldValues(contactId, input)
    • getCurrencyCatalog()
    • getDepartmentCatalog()
    • getMunicipalityCatalog(departmentId?)
    • requestMediaUpload(input)
    • getMediaDownloadUrl(input)
  • La implementación HTTP consume directamente BoostAPI:
    • GET /groups/:id/invoices
    • GET /custom-field-types
    • GET /custom-field-groups
    • GET /custom-fields
    • GET /groups/:id/custom-field-values
    • PUT /groups/:id/custom-field-values
    • GET /contacts/:id/custom-field-values
    • PUT /contacts/:id/custom-field-values
    • GET /catalogs/currencies
    • GET /catalogs/departments
    • GET /catalogs/municipalities
    • POST /media/presign-upload
    • GET /media/presign-download
  • El paquete no interpreta todavía settings.readonly para bloquear edición por sí mismo; esa decisión de UI sigue en las apps consumidoras.
  • Aun así, la capa ya transporta esos settings nuevos sin romper contratos:
    • readonly
    • override_permission_code
    • contexts
  • Regla compartida para futuras apps consumidoras:
    • los campos readonly siguen visibles
    • la UI podrá decidir bloquearlos antes de mutar
    • pero el enforcement real sigue viniendo del 403 de BoostAPI
  • contexts queda solo como preparación futura; @repo/crm-services no debe empezar a filtrar por pantalla sin un contrato backend explícito.
  • La jerarquía de ownership que esta capa debe seguir manteniendo es:
    • group > lead/client > contact
    • con client tratado como lead con zauru_id, no como tipo nuevo
  • Regla de consumo importante:
    • GET /custom-field-groups y GET /custom-fields en BoostAPI son paginados y usan limit=10 por default.
    • La implementación HTTP del monorepo fuerza limit alto (100 y 200) para evitar perder campos cuando el schema de una entidad crece.
    • Si en el futuro el catálogo crece más, el siguiente paso correcto es paginar explícitamente o filtrar custom_fields por custom_field_group_id; no volver a confiar en defaults del backend.
  • Los mocks de customFields ya reflejan el blueprint dev actual de BoostAPI para Informacion personalizada del cliente, incluyendo currency, department, municipality, image, video y pdf.
  • Los mocks de cartera ahora también incluyen páginas de Facturas para no romper la pestaña cuando web trabaja sin boostToken.
  • La capa HTTP y los mocks ya cubren también el segundo entry de facturas:
    • GET /groups/:id/invoices/:invoiceId
  • Ese detalle se carga on-demand al abrir el ojito; no se overfetchea junto con la tabla principal.
  • El mapper HTTP del detalle ya expone el shape normalizado de línea que frontend debe preferir:
    • lineKind
    • code
    • name
    • productType
    • trackingName
    • expirationDate
    • expirationLabel
  • Los campos raw (item, bundle, serial, lot, serialId, lotId) se preservan para debugging y futuras mejoras sin romper el contrato compartido.
  • Para cartera detalle, el paquete entrega estructura y valores suficientes para renderizar:
    • la sección dinámica Informacion personalizada del cliente a nivel group
    • los extras dinámicos record_type = contact dentro de cada card superior de contacto
  • La regla operativa en detalle es:
    • pedir grupos por record_type
    • pedir campos por custom_field_group_id
    • pedir valores del contacto solo cuando hasCustomFieldValues = true
  • La administración de schema (custom_field_groups.* / custom_fields.*) sigue fuera de alcance por ahora.
  • Fallos al leer employees, lead-assignments o group-assignments no deben tumbar la tabla.
  • En la implementación HTTP actual esos fallos degradan a arrays vacíos y hacen console.warn, permitiendo que la pantalla siga renderizando.
  • El console.log temporal del payload de GET /leads vive en la implementación web server-side mientras se valida el contrato con backend.
  • En detalle de cartera, si fallan custom-field-groups, custom-fields, catálogos o URLs firmadas, la app degrada la sección dinámica sin bloquear la entrada al grupo mientras existan cartera + groups.view.
  • Mobile ya puede consumir los mismos contratos y métodos porque @repo/crm-services no depende de Next ni de React Native.
  • Hoy apps/native/lib/crm-services.ts usa createMockCrmServices({ simulateDelay: false }).
  • Cuando native migre cartera a datos reales, el cambio esperado es reemplazar ese adapter por createHttpCrmServices({ apiClient }), manteniendo el mismo API de métodos.
  • La primera UI mobile de asignación de vendedor debe llamar services.cartera.assignSellerToGroups(); no debe crear una variante propia por lead.
  • La auditoría auth-bff-shared-audit define el criterio oficial para decidir qué flujos pueden salir de mocks hacia shared-with-platform-auth y cuáles deben quedarse detrás del BFF web.
  • La transición de native a real data debe respetar este orden:
    1. contrato estable en backend
    2. validación real en web
    3. salida de mocks en native por slice