Primer commit

This commit is contained in:
2026-05-30 14:31:19 -06:00
commit a35d26fac0
277 changed files with 265240 additions and 0 deletions
@@ -0,0 +1,798 @@
# GoHighLevel Users API — Documentación Completa por Endpoint
> **Fuentes:** [GitHub OpenAPI spec](https://github.com/GoHighLevel/highlevel-api-docs/blob/main/apps/users.json) · [Marketplace Docs](https://marketplace.gohighlevel.com/docs/ghl/users/) · Perplexity Search
> **Base URL:** `https://services.leadconnectorhq.com`
> **Auth:** `Authorization: Bearer {TOKEN}` | `Version: 2021-07-28` | `User-Agent: Mozilla/5.0`
---
## 1. POST /users/ — Create User
Crea un nuevo usuario en la agencia y lo asigna a una o más locations.
### cURL
```bash
curl -X POST https://services.leadconnectorhq.com/users/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Version: 2021-07-28" \
-H "User-Agent: Mozilla/5.0" \
-d '{
"companyId": "ve9EPM428h8vShlRW1KT",
"firstName": "John",
"lastName": "Deo",
"email": "john@deo.com",
"password": "*******",
"phone": "+18832327657",
"type": "account",
"role": "admin",
"locationIds": ["C2QujeCh8ZnC7al2InWR"],
"permissions": {
"contactsEnabled": true,
"opportunitiesEnabled": true,
"conversationsEnabled": true
},
"scopes": ["contacts.write", "opportunities.write"]
}'
```
### Python
```python
import http.client
import json
def create_user(token, company_id, first_name, last_name, email, password,
location_ids, role="user", type_="account", phone=None,
permissions=None, scopes=None, language="es"):
"""
POST /users/ — Create a user in GHL.
Args:
token: API token (pit-xxx or OAuth)
company_id: Agency ID
location_ids: List of location IDs to assign user to
permissions: Dict of 39 boolean flags (see §6 of research doc)
scopes: List of scope strings (see §7 of research doc)
Returns:
dict with user data (id, name, email, phone) or error
"""
payload = {
"companyId": company_id,
"firstName": first_name,
"lastName": last_name,
"email": email,
"password": password,
"type": type_,
"role": role,
"locationIds": location_ids,
"platformLanguage": language
}
if phone:
payload["phone"] = phone
if permissions:
payload["permissions"] = permissions
if scopes:
payload["scopes"] = scopes
body = json.dumps(payload).encode()
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
conn.request("POST", "/users/", body, {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Version": "2021-07-28",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0"
})
res = conn.getresponse()
data = json.loads(res.read().decode())
conn.close()
if res.status == 201:
return data.get("user", data) # Response wraps in 'user' sometimes
else:
return {"error": res.status, "message": data.get("message", str(data))}
# ── Usage ──
user = create_user(
token="pit-xxxxxxxx",
company_id="ve9EPM428h8vShlRW1KT",
first_name="Martin",
last_name="Macedo",
email="abejorrito79@gmail.com",
password="********",
phone="+527775610738",
location_ids=["U0S0QntXgSOz9Fx18Db4"],
role="user",
permissions={
"contactsEnabled": True,
"opportunitiesEnabled": True,
"conversationsEnabled": True,
"assignedDataOnly": True
},
scopes=["contacts.write", "opportunities.write", "conversations.write"]
)
print(user) # {"id": "0IHuJvc2ofPAAA8GzTRi", "name": "Martin Macedo", ...}
```
### Request Body Schema
| Campo | Tipo | Requerido | Descripción |
|-------|------|-----------|-------------|
| `companyId` | string | ✅ | ID de la agencia |
| `firstName` | string | ✅ | Nombre |
| `lastName` | string | ✅ | Apellido |
| `email` | string | ✅ | Email (debe ser único) |
| `password` | string | ✅ | Contraseña |
| `type` | string | ✅ | `account` o `agency` |
| `role` | string | ✅ | `admin`, `user`, `agency_admin` |
| `locationIds` | string[] | ✅ | Array de Location IDs |
| `phone` | string | | Teléfono en E.164 |
| `permissions` | object | | 39 flags booleanos |
| `scopes` | string[] | | 189 scopes OAuth |
| `scopesAssignedToOnly` | string[] | | Scopes asignados |
| `profilePhoto` | string | | URL de foto |
| `twilioPhone` | object | | `{"locId": "+123"}` — semántica replace |
| `platformLanguage` | string | | `es`, `en_US`, `fr_CA`, etc. |
### Response `201 Created`
```json
{
"id": "0IHuJvc2ofPAAA8GzTRi",
"name": "John Deo",
"firstName": "John",
"lastName": "Deo",
"email": "john@deo.com",
"phone": "+1 808-868-8888",
"extension": "",
"permissions": {...},
"scopes": "...",
"roles": {...},
"deleted": false,
"lcPhone": {"locationId": "+1234556677"},
"platformLanguage": "en_US"
}
```
---
## 2. GET /users/{userId} — Get User
Obtiene todos los datos de un usuario por su ID.
### cURL
```bash
curl -X GET https://services.leadconnectorhq.com/users/{userId} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Version: 2021-07-28" \
-H "User-Agent: Mozilla/5.0"
```
### Python
```python
import http.client
import json
def get_user(token, user_id):
"""GET /users/{userId} — Get user details by ID."""
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
conn.request("GET", f"/users/{user_id}", "", {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Version": "2021-07-28",
"User-Agent": "Mozilla/5.0"
})
res = conn.getresponse()
data = json.loads(res.read().decode())
conn.close()
if res.status == 200:
return data.get("user", data)
return {"error": res.status, "message": data.get("message", str(data))}
# ── Usage ──
user = get_user(token="pit-xxxxxxxx", user_id="0IHuJvc2ofPAAA8GzTRi")
print(user["firstName"], user["email"], user["permissions"])
```
### Response `200`
```json
{
"id": "0IHuJvc2ofPAAA8GzTRi",
"name": "John Deo",
"firstName": "John",
"lastName": "Deo",
"email": "john@deo.com",
"phone": "+1 808-868-8888",
"extension": "",
"permissions": {...},
"scopes": "...",
"roles": {...},
"deleted": false,
"lcPhone": {"locationId": "+1234556677"},
"platformLanguage": "en_US"
}
```
---
## 3. PUT /users/{userId} — Update User
Actualiza datos del usuario. **Todos los campos son opcionales** — solo envía lo que necesites cambiar.
### cURL — Cambiar permisos y scopes
```bash
curl -X PUT https://services.leadconnectorhq.com/users/{userId} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Version: 2021-07-28" \
-H "User-Agent: Mozilla/5.0" \
-d '{
"permissions": {"contactsEnabled": true, "opportunitiesEnabled": true},
"scopes": ["contacts.write", "opportunities.write"]
}'
```
### cURL — Mover usuario a otra location
```bash
curl -X PUT https://services.leadconnectorhq.com/users/{userId} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Version: 2021-07-28" \
-H "User-Agent: Mozilla/5.0" \
-d '{"locationIds": ["LOC_ID_NUEVA"]}'
```
### Python
```python
import http.client
import json
def update_user(token, user_id, **kwargs):
"""
PUT /users/{userId} — Update user fields.
All kwargs are optional. Only send what you want to change.
Examples:
update_user(token, uid, permissions={"contactsEnabled": True})
update_user(token, uid, location_ids=["LOC_A", "LOC_B"])
update_user(token, uid, password="new_password")
update_user(token, uid, scopes=["contacts.write"])
update_user(token, uid, role="admin", type="account")
"""
# Filter out None values
payload = {k: v for k, v in kwargs.items() if v is not None}
if not payload:
return {"error": "No fields to update"}
body = json.dumps(payload).encode()
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
conn.request("PUT", f"/users/{user_id}", body, {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Version": "2021-07-28",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0"
})
res = conn.getresponse()
data = json.loads(res.read().decode())
conn.close()
if res.status == 200:
return data.get("user", data)
return {"error": res.status, "message": data.get("message", str(data))}
# ── Usage examples ──
# Example 1: Update permissions and scopes
update_user(
token="pit-xxxxxxxx",
user_id="0IHuJvc2ofPAAA8GzTRi",
permissions={
"contactsEnabled": True,
"opportunitiesEnabled": True,
"conversationsEnabled": True,
"assignedDataOnly": True
},
scopes=["contacts.write", "opportunities.write", "conversations.write"]
)
# Example 2: Move user to a different location
update_user(
token="pit-xxxxxxxx",
user_id="0IHuJvc2ofPAAA8GzTRi",
location_ids=["U0S0QntXgSOz9Fx18Db4"] # ZACATEPEC
)
# Example 3: Change role and type
update_user(
token="pit-xxxxxxxx",
user_id="0IHuJvc2ofPAAA8GzTRi",
role="admin",
type="account"
)
# Example 4: Reset password
update_user(
token="pit-xxxxxxxx",
user_id="0IHuJvc2ofPAAA8GzTRi",
password="new_secure_password"
)
```
### Campos actualizables
| Campo | Tipo | Deprecado | Descripción |
|-------|------|-----------|-------------|
| `firstName` | string | | Nombre |
| `lastName` | string | | Apellido |
| `email` | string | ⚠️ SÍ | Ya no soportado por seguridad |
| `password` | string | | Nueva contraseña |
| `phone` | string | | Teléfono |
| `type` | string | | `account` / `agency` |
| `role` | string | | `admin` / `user` / `agency_admin` |
| `companyId` | string | | ID de agencia (requerido para agency-level) |
| `locationIds` | string[] | | Reasignar sucursales |
| `permissions` | object | | 39 flags booleanos |
| `scopes` | string[] | | 189 scopes OAuth |
| `scopesAssignedToOnly` | string[] | | Scopes asignados |
| `profilePhoto` | string | | URL de foto |
| `twilioPhone` | object | | Números inbound — semántica replace |
| `platformLanguage` | string | | `es`, `en_US`, etc. |
---
## 4. DELETE /users/{userId} — Delete User
Soft delete asíncrono. El usuario se encola para eliminación y toma efecto en minutos.
### cURL
```bash
curl -X DELETE https://services.leadconnectorhq.com/users/{userId} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Version: 2021-07-28" \
-H "User-Agent: Mozilla/5.0"
```
### Python
```python
import http.client
import json
def delete_user(token, user_id):
"""DELETE /users/{userId} — Soft delete (async, takes minutes)."""
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
conn.request("DELETE", f"/users/{user_id}", "", {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Version": "2021-07-28",
"User-Agent": "Mozilla/5.0"
})
res = conn.getresponse()
data = json.loads(res.read().decode())
conn.close()
return data # {"succeded": true, "message": "Queued deleting user..."}
# ── Usage ──
result = delete_user(token="pit-xxxxxxxx", user_id="0IHuJvc2ofPAAA8GzTRi")
print(result["message"]) # "Queued deleting user with e-mail john@deo.com..."
```
### Response `200`
```json
{
"succeded": true,
"message": "Queued deleting user with e-mail john@deo.com and name John Deo. Will take effect in a few minutes."
}
```
---
## 5. GET /users/search — Search Users
Búsqueda avanzada con filtros por companyId, locationId, nombre, email, rol, tipo, IDs específicos, y ordenamiento.
### Parameters
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Version` | string | ✅ | API Version header |
| `companyId` | string | ✅ | ID de la agencia |
| `query` | string | | Búsqueda por nombre, email o teléfono |
| `locationId` | string | | Filtrar por location |
| `type` | string | | `agency`, `account` |
| `role` | string | | `admin`, `user`, `agency_admin` |
| `ids` | string | | IDs separados por coma |
| `skip` | string | | Paginación offset (default: 0) |
| `limit` | string | | Page size (default: 25) |
| `sort` | string | | Campo de orden: `dateAdded` |
| `sortDirection` | string | | `asc` o `desc` |
### cURL
```bash
curl -X GET "https://services.leadconnectorhq.com/users/search?companyId=5DP41231LkQsiKESj6rh&locationId=C2QujeCh8ZnC7al2InWR&query=John&limit=10&sort=dateAdded&sortDirection=desc" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Version: 2021-07-28" \
-H "User-Agent: Mozilla/5.0"
```
### Python
```python
import http.client
import json
import urllib.parse
def search_users(token, company_id, query=None, location_id=None,
user_type=None, role=None, ids=None, skip=0, limit=25,
sort="dateAdded", sort_direction="desc"):
"""GET /users/search — Advanced user search."""
params = {
"companyId": company_id,
"skip": str(skip),
"limit": str(limit),
"sort": sort,
"sortDirection": sort_direction
}
if query:
params["query"] = query
if location_id:
params["locationId"] = location_id
if user_type:
params["type"] = user_type
if role:
params["role"] = role
if ids:
params["ids"] = ids
qs = urllib.parse.urlencode(params)
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
conn.request("GET", f"/users/search?{qs}", "", {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Version": "2021-07-28",
"User-Agent": "Mozilla/5.0"
})
res = conn.getresponse()
data = json.loads(res.read().decode())
conn.close()
if res.status == 200:
return data # {"users": [...], "count": N}
return {"error": res.status, "message": data.get("message", str(data))}
# ── Usage examples ──
# Find all users in a location
users = search_users(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
location_id="U0S0QntXgSOz9Fx18Db4")
print(f"Found {users['count']} users in ZACATEPEC")
# Search by name
users = search_users(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
query="Martin Macedo")
for u in users.get("users", []):
print(f" {u['id']}: {u['name']} ({u['email']})")
# Filter by role
admins = search_users(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
role="admin", limit=50)
```
### Response `200`
```json
{
"users": [
{
"id": "0IHuJvc2ofPAAA8GzTRi",
"name": "John Deo",
"firstName": "John",
"lastName": "Deo",
"email": "john@deo.com",
"phone": "+1 808-868-8888",
"extension": "",
"permissions": {...},
"scopes": "...",
"roles": {...},
"deleted": false,
"lcPhone": {"locationId": "+1234556677"},
"platformLanguage": "en_US"
}
],
"count": 1
}
```
---
## 6. POST /users/search/filter-by-email — Filter by Email
Filtra usuarios por array de emails. Ideal para verificar duplicados antes de crear.
### cURL
```bash
curl -X POST https://services.leadconnectorhq.com/users/search/filter-by-email \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Version: 2021-07-28" \
-H "User-Agent: Mozilla/5.0" \
-d '{
"companyId": "ve9EPM428h8vShlRW1KT",
"emails": ["john@deo.com", "jane@deo.com"],
"includeDeleted": false
}'
```
### Python
```python
import http.client
import json
def filter_users_by_email(token, company_id, emails, include_deleted=False):
"""
POST /users/search/filter-by-email — Check if emails already exist.
Args:
emails: List of email strings to check
include_deleted: Include soft-deleted users
Returns:
List of users matching the emails
"""
body = json.dumps({
"companyId": company_id,
"emails": emails,
"includeDeleted": include_deleted
}).encode()
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
conn.request("POST", "/users/search/filter-by-email", body, {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Version": "2021-07-28",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0"
})
res = conn.getresponse()
data = json.loads(res.read().decode())
conn.close()
if res.status == 200:
return data # Array or {"users": [...]}
return {"error": res.status, "message": data.get("message", str(data))}
# ── Usage: Check for duplicate before creating ──
def email_exists(token, company_id, email):
"""Returns True if a user with this email already exists."""
result = filter_users_by_email(token, company_id, [email])
users = result.get("users", result if isinstance(result, list) else [])
return len(users) > 0
existing = email_exists(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
email="abejorrito79@gmail.com")
if existing:
print("⚠️ Email already taken — cannot create user")
else:
print("✅ Email available — safe to create")
```
### Response 200
Devuelve array de usuarios que coinciden con los emails proporcionados.
---
## 7. GET /users/ — Get User by Location ⚠️ DEPRECADO
**No usar.** Reemplazado por `GET /users/search` con query param `locationId`.
```bash
# ❌ Deprecado
GET /users/?locationId=LOC_ID
# ✅ Correcto
GET /users/search?locationId=LOC_ID&companyId=COMPANY_ID
```
---
## 8. Response Codes
| Código | Significado | Schema |
|--------|-------------|--------|
| `200` | OK (GET, PUT, DELETE) | `UserSuccessfulResponseDto` / `DeleteUserSuccessfulResponseDto` |
| `201` | Created (POST) | `UserSuccessfulResponseDto` |
| `400` | Bad Request | `BadRequestDTO` |
| `401` | Unauthorized — token sin scope | `UnauthorizedDTO` |
| `422` | Unprocessable — email duplicado, locationIds inválido | `UnprocessableDTO` |
---
## 9. Scope Requirements
| Operación | Scope |
|-----------|-------|
| GET (search, get by id, filter by email) | `users.readonly` |
| POST (create) | `users.write` |
| PUT (update) | `users.write` |
| DELETE | `users.write` |
---
## 10. Notas importantes
1. **Email no se puede actualizar** — el campo `email` en PUT está deprecado por seguridad
2. **Delete es asíncrono** — la respuesta confirma encolado, no eliminación inmediata
3. **TwilioPhone tiene semántica replace** — si lo envías, reemplaza todo el mapa; no hace merge
4. **Scopes vacíos deshabilitan todo**`"scopes": []` quita todos los permisos
5. **companyId es obligatorio** en search y filter-by-email — se obtiene del `GET /locations/{id}`
6. **locationIds acepta array** — un usuario puede pertenecer a múltiples sucursales simultáneamente
7. **Versión 2021-07-28** — todos los endpoints usan esta versión; algunos aceptan 2023-02-21
---
## 11. Python Helper — Clase completa
```python
import http.client
import json
import urllib.parse
class GHLUsersAPI:
"""Complete wrapper for GoHighLevel Users API."""
BASE = "services.leadconnectorhq.com"
def __init__(self, token):
self.token = token
self.headers = {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Version": "2021-07-28",
"User-Agent": "Mozilla/5.0"
}
def _request(self, method, path, body=None):
"""Generic HTTP request helper."""
conn = http.client.HTTPSConnection(self.BASE, timeout=30)
conn.request(method, path, body or "", self.headers)
res = conn.getresponse()
data = json.loads(res.read().decode())
conn.close()
return res.status, data
def create(self, company_id, first_name, last_name, email, password,
location_ids, role="user", type_="account", **kwargs):
"""POST /users/ — Create user."""
payload = {
"companyId": company_id, "firstName": first_name,
"lastName": last_name, "email": email, "password": password,
"type": type_, "role": role, "locationIds": location_ids,
"platformLanguage": kwargs.get("language", "es")
}
for k in ("phone", "permissions", "scopes", "profilePhoto", "twilioPhone"):
if k in kwargs and kwargs[k] is not None:
payload[k] = kwargs[k]
status, data = self._request("POST", "/users/",
json.dumps(payload).encode())
if status == 201:
return data.get("user", data)
raise Exception(f"Create failed: {status} {data.get('message', data)}")
def get(self, user_id):
"""GET /users/{userId} — Get user."""
status, data = self._request("GET", f"/users/{user_id}")
if status == 200:
return data.get("user", data)
raise Exception(f"Get failed: {status}")
def update(self, user_id, **kwargs):
"""PUT /users/{userId} — Update user fields."""
payload = {k: v for k, v in kwargs.items() if v is not None}
if not payload:
raise Exception("No fields to update")
status, data = self._request("PUT", f"/users/{user_id}",
json.dumps(payload).encode())
if status == 200:
return data.get("user", data)
raise Exception(f"Update failed: {status} {data.get('message', data)}")
def delete(self, user_id):
"""DELETE /users/{userId} — Soft delete."""
status, data = self._request("DELETE", f"/users/{user_id}")
if status == 200:
return data
raise Exception(f"Delete failed: {status}")
def search(self, company_id, **kwargs):
"""GET /users/search — Search users."""
params = {"companyId": company_id, "skip": "0", "limit": "25"}
for k in ("query", "locationId", "type", "role", "ids", "sort",
"sortDirection"):
if k in kwargs and kwargs[k] is not None:
params[k] = str(kwargs[k])
qs = urllib.parse.urlencode(params)
status, data = self._request("GET", f"/users/search?{qs}")
if status == 200:
return data
raise Exception(f"Search failed: {status}")
def filter_by_email(self, company_id, emails, include_deleted=False):
"""POST /users/search/filter-by-email — Filter by email list."""
payload = {"companyId": company_id, "emails": emails,
"includeDeleted": include_deleted}
status, data = self._request("POST", "/users/search/filter-by-email",
json.dumps(payload).encode())
if status == 200:
return data
raise Exception(f"Filter failed: {status}")
# ── Complete usage example ──
api = GHLUsersAPI(token="pit-xxxxxxxx")
# 1. Check if email exists
try:
existing = api.filter_by_email("ve9EPM428h8vShlRW1KT", ["test@example.com"])
print(f"Email check: {len(existing.get('users', []))} found")
except:
print("Email available")
# 2. Create user
user = api.create(
company_id="ve9EPM428h8vShlRW1KT",
first_name="Martin", last_name="Macedo",
email="abejorrito79@gmail.com", password="********",
phone="+527775610738",
location_ids=["U0S0QntXgSOz9Fx18Db4"],
role="user",
permissions={"contactsEnabled": True, "opportunitiesEnabled": True},
scopes=["contacts.write", "opportunities.write"]
)
print(f"Created: {user['id']}")
# 3. Update permissions
api.update(user["id"],
permissions={"contactsEnabled": True, "opportunitiesEnabled": True,
"conversationsEnabled": True, "assignedDataOnly": True},
scopes=["contacts.write", "opportunities.write", "conversations.write"])
# 4. Search users in location
result = api.search("ve9EPM428h8vShlRW1KT",
locationId="U0S0QntXgSOz9Fx18Db4")
print(f"Users in ZACATEPEC: {result['count']}")
# 5. Delete user (if needed)
# api.delete(user["id"])
```
@@ -0,0 +1,11 @@
#Como mapear datos por location
Hacer peticiones get de schema para obtener toda la información con https://services.leadconnectorhq.com/objects/ empezando con contacts y opportunities. Siguiente fase es hacer para cada campo obtenido de la petición anterior https://services.leadconnectorhq.com/objects/:key para todos los campos personalizados haciendo corrrecto mapeo de todos los campos con fieldkey haciendo fallback en el nombre para detectar anomalias.
Con lo anterior ya podremos identificar todos los campos haciendo el filtrado por nombre o por fieldkey (en caso de petición del usuario.) Esto surje de la necesidad de que las peticiones tipo get contact y get opportunnity no proporcionan los fieldkeys de los campos personalizados porque solo da el id del campo personalizado y el value.
# Clientes
Algunos clientes utilizan el mismo teléfono para hacer más de un registro de formulario, a veces incluso el mismo email pero esto ultimo es menos probable.
# Consideraciones principales para sincronización de contactos y oportunidades.
## Contactos
-Hacer primer busqueda por teléfono, email y nombre en ese orden. Normalizar texto de nombre para evitar errores de acentos, mayusculas y minusculas.