Field maps
Cómo mapear columnas de tu fuente a campos lógicos del SDK y payloads Brightmarket.
El SDK no asume nombres de columnas de tu ERP. Vos definís en src/constants.py cómo se llaman tus campos fuente para cada clave lógica del SDK. El sync traduce esas filas a payloads de la API Brightmarket (externalId, name, defaultPrice, …).
Guía relacionada: constants.py (value maps), adapter.py (lectura).
Flujo
Tu BD / vista → adapter.get_*() → filas crudas (dict u objetos)
↓
constants.py (*_FIELD_MAP + value maps)
↓
brightmarket_data_sync.sync → writers → API- Field maps (
CLIENT_FIELD_MAP, …): nombre de columna fuente por clave lógica (external_id,article_external_id, …). - Value maps (
SEXO_MAPPING, …): traducen códigos de tu sistema a valores Brightmarket (MALE,ACCUMULATION, …).
Field maps por entidad
Cada dict en constants.py usa claves lógicas del SDK (izquierda) → tu columna fuente (valor string).
CLIENT_FIELD_MAP — flags -clients, -update_clients
| Clave lógica | Requerida | Uso en Brightmarket |
|---|---|---|
external_id | sí | externalId del cliente |
name | sí | Nombre para armar name / lastName |
fidelity | no | fidelity (si falta, usa external_id) |
birthdate | no | birthdate (formato fuente YYYYMMDD u otro parseable) |
sex | no | Código fuente → SEXO_MAPPING → sex |
department_id | no | Id región → DEPARTAMENTO_* / COUNTRY_MAPPING |
phone | no | Teléfono |
email | no | |
client_level | no | Código nivel → CLIENT_LEVEL_MAPPING → add_client_level tras crear |
Opt-in: CLIENT_STORE_ONLY_CONTACT_VALIDATION = True hace que sync_clients envíe storeOnlyContactValidation en create/update y pase phone/email también en update. Default False (comportamiento 2.0.0).
SKU_FIELD_MAP — flag -skus
| Clave lógica | Requerida | Uso |
|---|---|---|
article_external_id | sí | Agrupa filas por artículo; externalId del artículo |
sku_external_id | sí | externalId del SKU |
description | no | Nombre y descripción del artículo |
brand | no | Marca (brandExternalId) |
genre, category, class_ | no | Arman categoryExternalId |
color, size | no | Variante del SKU |
price, cost | no | defaultPrice y cost |
Por defecto categoryExternalId es {genre}-{category}-{class_} y no se crean categorías en la API.
Opt-ins:
SKU_CREATE_CATEGORIES = True— crea categoría padre (genre) y hija (categoryExternalIdconfatherExternalId) antes decreate_article.SKU_CATEGORY_SEPARATOR = "/"— une solo segmentos no vacíos (ej.Genero/Categoria). Si no se define, se mantiene el formato legacy con guiones.
Passthrough: si get_skus() devuelve dicts con externalId y lista skus (payload listo para la API), el SDK los envía sin field map.
TICKET_FIELD_MAP — flag -tickets
| Clave lógica | Requerida | Uso |
|---|---|---|
ticket_id | sí | Agrupa líneas; code del ticket |
client_external_id | sí | Cliente del ticket |
sku | sí | SKU de la línea |
quantity | sí | Cantidad |
price | sí | Precio unitario |
date, sale_point_id, total, subtotal, iva | no | Cabecera del ticket |
branch_id | no | Si está mapeado, resuelve channelId vía get_branch_office_channel_id |
promo_id | no | Si está mapeado, colecta IDs de promo por línea, resuelve el externalId vigente con resolve_promotion_external_id_for_date (fecha del ticket) y envía promotionExternalIds |
Opcional en el adapter: get_pagos_by_ticket() → medios de pago por ticket.
PAYMENT_METHOD_FIELD_MAP — flag -payment_methods
| Clave lógica | Requerida |
|---|---|
external_id | sí |
name | sí |
PROMOTION_FIELD_MAP — flag -promotions
| Clave lógica | Requerida |
|---|---|
external_id | sí |
name | sí |
start_date, end_date, discount_percent, sku_external_ids | no |
También podés devolver dicts promotion_data listos (promotion + regularPromotionType); el SDK detecta la forma.
REWARD_FIELD_MAP — flag -rewards
| Clave lógica | Requerida |
|---|---|
client_external_id | sí |
segment | sí |
message | sí |
incentive | sí |
expiration | sí |
Requiere ORGANIZATION_ID en .env.
POINTS_MOVEMENT_FIELD_MAP — flag -points_movements
| Clave lógica | Requerida |
|---|---|
client_external_id | sí |
movement_type | sí |
points | sí |
expiration_date, comments | no |
Los códigos de movement_type se traducen con POINTS_MOVEMENT_TYPE_MAPPING.
POINTS_EXPIRATION_FIELD_MAP — ajustes post-sync (opcional)
Solo si implementás get_points_expiration_adjustments() en el adapter:
| Clave lógica | Requerida |
|---|---|
client_external_id | sí |
points | sí |
comments | no |
STOCK_ADJUSTMENT_FIELD_MAP — flag -stock_adjustments
| Clave lógica | Requerida |
|---|---|
sku_external_id | sí |
branch_office_external_id | sí |
stock | sí |
reason, date | no |
Sin id de sucursal después de -stock_adjustments sincroniza todas las sucursales que devuelva get_stock_adjustments(); con id, filtra esa sucursal.
Qué mapa necesita cada flag CLI
| Flag | *_FIELD_MAP requerido |
|---|---|
-clients / -update_clients | CLIENT_FIELD_MAP |
-skus | SKU_FIELD_MAP (o payloads listos) |
-tickets | TICKET_FIELD_MAP |
-payment_methods | PAYMENT_METHOD_FIELD_MAP |
-promotions | PROMOTION_FIELD_MAP (o promotion_data listo) |
-rewards | REWARD_FIELD_MAP |
-points_movements | POINTS_MOVEMENT_FIELD_MAP |
-stock_adjustments | STOCK_ADJUSTMENT_FIELD_MAP |
| Ajustes de vencimiento | POINTS_EXPIRATION_FIELD_MAP + hook en adapter |
mappers_from_constants(constants) carga los maps presentes en tu módulo. Si corrés un sync sin el map correspondiente, el SDK falla con un mensaje explícito (ej. Client sync requires CLIENT_FIELD_MAP in client constants).
Ejemplos
Dos clientes con distintas columnas fuente; el SDK es el mismo.
Vista SQL con nombres “clásicos”:
CLIENT_FIELD_MAP = {
"external_id": "IdCliente",
"name": "Nombre",
"fidelity": "RUC",
"birthdate": "FechaNacimiento",
"sex": "Sexo",
"department_id": "IdDepartamento",
}
SKU_FIELD_MAP = {
"article_external_id": "CodigoArticulo",
"sku_external_id": "CodigoInterno",
"description": "Descripcion",
"brand": "Marca",
"color": "Color",
"size": "Talle",
"price": "Precio",
"cost": "Costo",
}Otra fuente con nombres propios:
CLIENT_FIELD_MAP = {
"external_id": "cust_id",
"name": "full_name",
"sex": "gender",
}
SKU_FIELD_MAP = {
"article_external_id": "articulo",
"sku_external_id": "Interno",
"description": "WebDescription",
"brand": "Marca",
"cost": "PREC_COS",
"price": "PRECIO_UNI",
"color": "COLOR",
"size": "NRO",
}El adapter devuelve filas tal como vienen de la consulta; los field maps conectan esas columnas con el sync.
Extensiones
| Caso | Solución |
|---|---|
| Reparar clientes por IDs sin re-query global | sync_clients_created_from_rows(rows, api, mappers) |
| Payloads de artículo/SKU demasiado ricos para un map | Devolver dicts listos desde get_skus() o handlers propios + writers del SDK |
| Ajuste de puntos por vencimiento | get_points_expiration_adjustments() + POINTS_EXPIRATION_FIELD_MAP |
| Cerrar conexiones del adapter | Implementar adapter.close(); el runner del SDK lo llama al finalizar |
Errores frecuentes
CLIENT_FIELD_MAP is not configured— falta el dict o no está importado en el módulo que pasás amappers_from_constants.missing required logical keys: external_id— el dict existe pero le faltan claves obligatorias (valores vacíos no cuentan).- Sync crea filas vacías — el nombre de columna en el map no coincide con las claves de tus dicts (revisá mayúsculas y nombres exactos de la vista SQL).