Appearance
TODO — Pendiente de aprobación
Recursos que NO se migran en la primera corrida y quedan a la espera de decisión del cliente / lead técnico.
1. Meetings / Appointments / Calendars / Calendar Events
Estado: ❌ Pausado.
Por qué se sacó del scope inicial:
- El modelo
meetingsdel destino está pensado alrededor de Google Calendar (camposgoogle_event_id,google_meet_link,google_calendar_id,synced,synced_at). En GHL los appointments viven dentro de su propio sistema de booking — no hay un Google Calendar Event ID que mapear. - Hay que decidir si:
- Migrar appointments sin Google sync y dejar
synced=false(los usuarios re-conectarían su Google después en imcrmdev). - No migrar histórico y arrancar limpio.
- Migrar solo eventos futuros (a partir de hoy) y descartar el histórico.
- Migrar appointments sin Google sync y dejar
- También falta saber si Trebol usa el módulo de citas activamente (volumen real, importancia para retención de cliente).
Lo que queda en pausa:
| Recurso GHL | Endpoint | Destino propuesto |
|---|---|---|
| Calendarios (catálogo) | GET /calendars/?locationId=… | calendars (NUEVA) |
| Calendar Events (rango) | GET /calendars/events?calendarId=…&startTime=…&endTime=… | meetings |
| Appointments por contacto | GET /contacts/{id}/appointments | meetings |
Mapping propuesto (cuando se apruebe)
Calendar (calendars — NUEVA)
| Origen | Destino |
|---|---|
id | external_id |
name | name |
description | description |
calendarType | calendar_type |
eventType | event_type |
slotDuration | slot_duration |
teamMembers[] | team_members[] |
widgetSlug | widget_slug |
Appointment / Calendar Event (meetings)
| Origen | Destino | Nota |
|---|---|---|
id | external_id | |
title | title | |
notes o description | description | |
startTime | start_time | parse ISO |
endTime | end_time | parse ISO |
address | location | |
appointmentStatus | status | mapear: confirmed→confirmed, cancelled→cancelled, noshow→no_show, default → scheduled |
assignedUserId | owner_user_id | resolver a users._id destino |
contactId | contact_id | resolver a contacts_new._id destino |
calendarId | custom_fields.calendar_id | preserva FK lógica |
dateAdded | created_at | |
dateUpdated | updated_at | |
| — | google_event_id | vacío (no aplica) |
| — | google_meet_link | vacío |
| — | google_calendar_id | vacío |
| — | synced | false (forzado) |
| — | attendees[] | array vacío (GHL no expone igual) |
Decisiones que necesitamos antes de arrancar
- [ ] ¿Migra histórico, solo futuro, o nada?
- [ ] Si migra histórico, ¿cuántos años atrás? (recomendación: 1 año)
- [ ] ¿Los usuarios van a re-conectar Google Calendar manualmente en imcrmdev, o es deal-breaker tener sync inmediato?
- [ ] ¿Hay que enviar notificaciones (
reminders_sent[]) post-migración? (Recomendación: NO, son citas pasadas)
2. Custom Field Definitions (catálogo en colección propia)
Estado: ❌ Pausado (la persistencia del catálogo). El uso interno sí se mantiene — explicación abajo.
¿Qué es un Custom Field Definition?
En GHL, un Custom Field es un campo extra que se agrega a un contacto, oportunidad u otro objeto. Por ejemplo, para Trebol probablemente existen:
- "Bank Name" (texto)
- "Comments & Questions" (texto largo)
- "B-018-GLB. SMS Opt-Out Date" (fecha)
- "Gender" (single option: Male/Female)
- etc.
Trebol tiene 227 custom field definitions.
Cada definición dice: "este campo se llama X, es de tipo TEXTO/FECHA/NÚMERO/RADIO, su key interna es contact.bank_name, va en la posición 50 del formulario, etc."
Cada contacto tiene un array customFields: [{id, value}] donde id es el id de la definición y value es el valor para ese contacto.
Por qué se sacó al TODO
La pregunta abierta es: ¿queremos que imcrmdev replique el concepto de "custom fields" como entidad de primera clase?
- Si SÍ: necesitamos una colección
custom_field_definitions(con tipo, opciones de picklist, posición en el form) para que el frontend pueda renderizar formularios dinámicos por tenant. Hoy imcrmdev no tiene este concepto — loscustom_fieldsencontacts_newson un objeto libre tipo{key: value}sin metadatos. - Si NO: los valores quedan guardados en
contacts_new.custom_fieldscomo objeto libre (que es lo que el script ya hace), pero el frontend no sabe que "bank_name" debe renderizarse como texto y "gender" como select.
Esta decisión es de producto, no de migración.
Lo que el script SÍ hace en este momento
Aunque NO persiste el catálogo en Mongo, el script igual lee las 227 definiciones de GHL en memoria al arrancar (ctx.custom_field_map). Eso lo necesitamos para resolver:
js
// GHL devuelve esto en cada contacto:
{ "customFields": [{ "id": "0QstDUndlPhfmKoKoD3k", "value": "Bank of America" }] }
// El script lo guarda en Mongo así:
{ "custom_fields": { "bank_name": "Bank of America" } }
// ↑ resuelto desde la definiciónSin la lectura de definiciones, las keys quedarían como 0QstDUndlPhfmKoKoD3k — ilegibles.
Por eso el script:
- ✅ Sigue llamando
GET /locations/{id}/customFieldsal arrancar - ✅ Sigue resolviendo
id → fieldKeypara los contactos y oportunidades - ❌ NO escribe la colección
custom_field_definitionsen Mongo
Cuando producto decida, agregar la persistencia es un cambio de 5 líneas (re-activar el bulk_upsert en migrate_custom_fields).
Decisiones que necesitamos
- [ ] ¿imcrmdev va a soportar custom fields como concepto de producto? (admin UI para crearlos, formularios dinámicos)
- [ ] Si sí, ¿el modelo de datos es por-tenant (
company_id+field_key) o global? - [ ] ¿Mantener el concepto de folders (
parentId) para agrupar campos en la UI?
Mapping propuesto (cuando se apruebe)
custom_field_definitions (NUEVA)
| Origen GHL | Destino |
|---|---|
id | external_id |
name | name |
fieldKey | field_key |
placeholder | placeholder |
dataType | data_type (TEXT, LARGE_TEXT, NUMERICAL, DATE, MONETORY, CHECKBOX, RADIO, SINGLE_OPTIONS, MULTIPLE_OPTIONS, FILE_UPLOAD, SIGNATURE) |
position | position |
documentType | document_type |
parentId | folder_id |
model | model (contact, opportunity, etc.) |
picklistOptions[] | options[] |
standard | standard |
dateAdded | created_at |