Skip to content

Mapping campo a campo

Convención del CRM destino

El CRM imcrmdev modela "company" (B2B) como un contacto con service_type=corporate. Por eso GHL Businesses y GHL Contacts ambos terminan en la colección contacts_new, diferenciados por service_type:

  • service_type=residential — personas (las 44,918 contactos de GHL)
  • service_type=corporate — empresas (los 6 GHL Businesses, ver sección Business)

El vínculo persona→empresa se preserva en custom_fields.parent_business_external_id.

Contact (persona, service_type=residential)

GHL GET /contacts/{id} → imcrmdev contacts_new

Campo origenCampo destinoTransformación / nota
idexternal_idguardado tal cual
_idgenerado por Mongo
company_idtenant Trebol (bootstrap)
service_typesiempre "residential"
assignedToassignedAgentIdsi existe en users.external_id, se reemplaza por su publicid; si no, se guarda crudo
assignedToownerAgentIdmismo valor — el responsable actual del lead es el dueño de la cartera en imcrmdev
assignedTocreated_bysi nulo → fallback al system_user_id de bootstrap
firstNamefirst_name
lastNamelast_name
firstName + lastNamenameconcatenación; si vacío → phone o email
emailemailomitir si vacío, omitir y loguear si duplicado
emailemail_opted_infalse si dndSettings.Email.status == "active" (active = bloqueado)
phonephoneE.164 tal cual
countryaddress.country
stateaddress.state
cityaddress.city
address1address.address
postalCodeaddress.postcode
dateOfBirthcustom_fields.date_of_birth(no hay campo top-level en destino)
companyNamecustom_fields.current_insuranceEn GHL Trebol este campo guarda la aseguradora actual del prospecto ("Geico", "Progressive"…), NO su empleador
businessIdcustom_fields.parent_business_external_idSi está set, además se denormaliza el name del business migrado en custom_fields.parent_business_name
tags[]custom_fields.tagsarray tal cual
customFields[]custom_fields.{fieldKey}resolver idfieldKey usando el catálogo cargado en memoria al inicio de la pasada
dndSettingscustom_fields.dnd_settingsobjeto completo
additionalEmails[]custom_fields.additional_emails
additionalPhones[]custom_fields.additional_phones
dateAddedcreated_atparse ISO
dateUpdatedupdated_atparse ISO
(todo el JSON)_ghl_rawpreservación
(timestamp ahora)_migrated_at

Campos del destino que quedan vacíos

billing_account_id, account_id, network_information, linked_status, network_status, contract_info, onus, website (si no viene), whatsapp_opted_in.

Business (B2B Company → contact corporate)

GHL GET /businesses/?locationId={id} → imcrmdev contacts_new (service_type=corporate).

Volumen real en Trebol: 6 docs (LLCs de clientes commercial). Por eso este recurso se ejecuta como catálogo (full refresh siempre, antes de la fase de contacts).

Campo origenCampo destinoTransformación / nota
idexternal_id
service_typesiempre "corporate"
created_bysystem_user_id (los businesses no tienen owner asignado por GHL)
namenametrim de espacios extra
namefirst_namefull nombre — el destino requiere first_name no vacío
last_namestring vacío
phonephone
emailemailomitir si vacío
addressaddress.address
cityaddress.city
stateaddress.state
postalCodeaddress.postcode
countryaddress.country
customFields[]custom_fields.{fieldKey}resolver con el catálogo en memoria
custom_fields.is_businesstrue (flag para queries y UI)
createdAtcreated_at
updatedAtupdated_at
(todo)_ghl_raw

Vínculo persona → business

Cuando un contacto persona en GHL tiene businessId apuntando a uno de estos businesses (en Trebol son ~6 casos, 0.01% de los contactos), el script:

  1. Lee el business primero (corre antes en SYNC_ORDER).
  2. Cachea business_name_map: { ghl_business_id → name } en memoria.
  3. Al insertar el contacto persona, agrega:
    js
    custom_fields: {
      parent_business_external_id: "6977a0bb1d90c36acab33ed9",  // GHL id del business
      parent_business_name: "Moreno's Contractors LLC"          // denormalizado
    }

Para resolver el FK a _id de Mongo posteriormente, basta una query: db.contacts_new.findOne({ external_id: parent_business_external_id, service_type: 'corporate' }). El índice (custom_fields.parent_business_external_id) hace esa lookup eficiente.

Note (embebido en contacts_new.notes[])

GHL GET /contacts/{id}/notes → imcrmdev contacts_new.notes (array embebido tipo ContactNote)

Campo origenCampo destino
id_id (string, NO objectid — llega como GHL id)
bodycontent
title (vacío, GHL no tiene título)
userIdagentId
userId resueltoagentName
dateAddedcreatedAt
dateAddedupdatedAt

Opportunity (opportunities)

GHL GET /opportunities/search → imcrmdev opportunities

Campo origenCampo destino
idexternal_id
namename
monetaryValueamount
pipelineIdpipeline_id (FK a sales_pipelines.external_id)
pipelineId resueltopipeline (string, nombre legible)
pipelineStageIdstage_id (FK a sales_pipeline_stages.external_id)
pipelineStageId resueltostage (string, nombre legible)
effectiveProbabilityprobability
statusstatus (open / won / lost / abandoned)
sourcesource
assignedTo resueltoassigned_to (publicid del user destino)
contactId resueltocontact_id (ObjectId del contacto destino)
contact.namecontact_name (denormalizado)
customFields[]custom_fields.{fieldKey}
lastStatusChangeAtlast_status_change_at
lastStageChangeAtlast_stage_change_at
createdAtcreated_at
updatedAtupdated_at
(todo)_ghl_raw

Campos destino vacíos

accountExecutive, serviceBundle, installationSLA, mrcType (todos ISP-specific).

Pipeline (sales_pipelines)

GHL GET /opportunities/pipelines → imcrmdev sales_pipelines (NUEVA)

Campo origenCampo destino
idexternal_id
namename
dateAddedcreated_at
dateUpdatedupdated_at
showInFunnel, showInPieChartdisplay.show_in_funnel, .show_in_pie_chart
useOpportunityProbabilityuse_opportunity_probability

Cada stage se explosiona en sales_pipeline_stages:

OrigenDestino
stages[].idexternal_id
stages[].namename
stages[].positionorder
stages[].stageWinProbabilitywin_probability
pipeline.idpipeline_id (FK external_id)

Conversation (conversations)

Campo origenCampo destino
idexternal_id
contactId resueltocontact_id
lastMessageDate (epoch ms)last_message_date (Date)
lastMessageTypelast_message_type
lastMessageBodylast_message_body
lastMessageDirectionlast_message_direction
unreadCountunread_count
assignedTo resueltoassigned_to
tags[]tags
typetype (TYPE_PHONE, TYPE_EMAIL, etc.)

Message (messages)

Campo origenCampo destino
idexternal_id
conversationIdconversation_id (FK)
contactId resueltocontact_id
directiondirection
statusstatus
messageTypemessage_type
bodybody
fromfrom
toto
dateAddedcreated_at
attachments[]attachments[] (URLs, sin descargar)

Task (tasks)

GHL GET /contacts/{id}/tasks → imcrmdev tasks

Campo origenCampo destino
idexternal_id
titletitle
bodydescription
dueDatedueDate
completedstatus (true → COMPLETED, false → PENDING)
assignedTo resueltoassignedTo
contactIdassociations[] ({ associatedType: "contact", associatedId })