Especificación para el proveedor del ERP
Qué información necesita la aplicación para funcionar sobre su ERP. No es una lista de rutas a copiar: es el contenido que cada respuesta debe traer. Ustedes exponen los servicios como prefieran; la traducción a nuestro formato corre por nuestra cuenta.
Cada campo se puede expandir para ver tipo, formato y un ejemplo. Cada servicio trae un ejemplo completo de respuesta.
Cómo funciona la conexión
No hace falta que respeten nuestros nombres de ruta, de campo ni el formato de la respuesta. Lo que se acuerda es qué dato trae cada servicio y qué significa.
Del lado nuestro hay una pieza que traduce su API a la forma que la aplicación espera. Mientras el dato exista y su significado esté declarado, el mapeo es trabajo nuestro. Lo que no exista en su API sencillamente no se implementa: la aplicación pierde esa función y el resto sigue operando.
Esa pieza consulta los datos del ERP en su API. Por diseño, lo que la aplicación necesita guardar también queda en su infraestructura, en tablas que ustedes crean con un script nuestro (ver Tablas de Ramaflex).
Los ejemplos de este documento usan nombres de campo ilustrativos, solo para mostrar el dato esperado. No son un formato obligatorio.
Aplican a todos los servicios
Todo valor monetario en pesos enteros. Los redondeos intermedios cambian los totales y descuadran la factura; conviene que el redondeo ocurra una sola vez y del lado de ustedes.
La convención pedida es: el precio unitario va antes de descuento; los totales de línea, después. Si su ERP trabaja al revés no es impedimento — declárenlo y entreguen además el descuento aplicado y la regla de redondeo, y nosotros traducimos en ambos sentidos. Lo que no puede quedar es sin declarar: traducir entre convenciones solo es exacto si el redondeo está especificado, y si no, aparecen diferencias de un peso al cuadrar.
Cada producto debe traer su tasa de IVA y la de impuestos adicionales. La aplicación totaliza sin red, así que no puede deducirlas: si faltan, asume una tasa fija y factura mal los productos exentos.
ISO-8601 con offset. Una fecha sin zona hace que los documentos del día desaparezcan del filtro del vendedor cuando el teléfono y el servidor no coinciden.
Ninguna consulta puede obligar a traer todo. Se requiere filtro desde fecha y paginación. Un catálogo de 30 mil productos sin paginar no llega por red móvil.
Cada condición de error necesita un código legible por máquina que no cambie. No sirve distinguir casos por el texto del mensaje.
Los códigos viajan recortados, sin espacios de relleno a la derecha. Un código con espacio sobrante se compara como distinto y hace desaparecer, en silencio, todo el historial del vendedor.
Un campo que no aplica debe venir nulo, no en cero ni en blanco. La aplicación distingue "no hay dato" de "el dato es cero" y toma decisiones distintas.
Cómo conversan los dos sistemas
Lo anterior define qué dato viaja. Esta sección define cómo viaja. Son las decisiones que no se ven en un ejemplo de respuesta y que, si quedan sin acordar, aparecen recién cuando algo falla en terreno.
Hoy tratamos cualquier respuesta 400 o superior como un fallo del servicio, sin distinguir entre ellas. Para que podamos reaccionar distinto hace falta acordar dos cosas: qué familia de estado usan para cada situación —no encontrado, dato inválido, sin permiso, error interno— y en qué campo del cuerpo viaja el código estable. Un ejemplo concreto: "la modalidad no existe" es un problema de configuración que hay que mostrarle a alguien, y "la base no responde" es un problema transitorio que hay que reintentar. Si las dos llegan como un 500 sin código, las tratamos igual y una de las dos se maneja mal.
Soportamos exactamente dos, y hay que elegir una por servicio: por cursor, donde nos devuelven un valor que pedimos en la llamada siguiente hasta que deje de venir; o por número de página, donde enviamos página y tamaño y cortamos cuando la página viene incompleta. Si su API pagina de otra forma —offset y limit, cabecera Link, token con vencimiento— es perfectamente viable, pero hay que declararlo antes: es trabajo de traducción, no un mapeo.
Cortamos a las 100 páginas por consulta y devolvemos aviso. Es una protección contra un cursor que nunca cierra, no un límite de negocio. Si un servicio suyo puede superar ese volumen con el tamaño de página que acordemos, conviene saberlo de antemano y subir el tope para ese servicio: entregar un catálogo incompleto en silencio es peor que entregarlo con advertencia.
Las fechas que ustedes devuelven van en ISO-8601 con offset. Falta acordar lo simétrico: en qué formato y zona enviamos el "modificado desde" de las consultas incrementales. Nuestra propuesta es el mismo ISO-8601 con offset explícito, para que no dependa de la zona del servidor de ninguno de los dos. Si su API espera otra cosa —una fecha sin hora, un epoch, hora local— díganlo y lo enviamos así.
Hoy un campo obligatorio ausente no genera error: el registro llega incompleto y el efecto aparece más tarde y lejos del origen. Por eso conviene acordarlo explícitamente por servicio: si falta el precio de un producto, ¿se descarta ese producto, se descarta la página, o falla la sincronización completa? Las tres son defendibles y la peor es la que está por defecto.
Necesitamos saber si la API es alcanzable desde nuestros servidores, si el certificado es de una autoridad reconocida o autofirmado, y si hay que solicitar apertura de firewall o declarar direcciones IP de origen. No es un detalle de última hora: es lo primero que detiene un piloto, y suele depender de un tercero con sus propios plazos.
Estas dos reglas se cruzan y hay que resolverlas juntas. La aplicación reintenta indefinidamente un documento sin respuesta, como dice la sección siguiente. Si su API tiene un límite de peticiones por minuto, ese reintento legítimo puede quedar bloqueado y el documento no se graba nunca. Si existe el límite, necesitamos el número y, sobre todo, qué estado devuelven al alcanzarlo, para distinguirlo de un fallo real y esperar en vez de insistir.
Si cambia un campo, un vocabulario o una ruta, hoy nos enteraríamos cuando se rompa en un teléfono. Basta con acordar algo mínimo: una versión en la ruta o en una cabecera, y un canal para avisar de cambios incompatibles con antelación. El traductor está de nuestro lado y se adapta rápido, pero solo si sabe que tiene que adaptarse.
Lo más importante del documento
Cada documento que la aplicación envía lleva una clave única generada en el teléfono, antes del primer intento. Si no hay respuesta o la respuesta tarda, la aplicación reintenta con la misma clave, indefinidamente. Es su comportamiento normal, no una falla.
Su servicio debe garantizar que una clave repetida no crea un segundo documento, y que el reintento devuelve el folio del documento que ya existe. Sin ese folio la aplicación no puede cerrar el ciclo y el documento queda pendiente para siempre en el teléfono del vendedor.
La aplicación trata un error confirmado como permiso para reemplazar el documento: el vendedor lo edita y se envía uno nuevo, con clave nueva.
Si su servicio responde error después de haber grabado, se genera un duplicado real en el ERP. Si no pueden garantizarlo, respondan sin error y dejen que la aplicación reintente con la misma clave: el reintento es seguro, el error no.
Los documentos que la aplicación descarga después se aparean con los del teléfono por esa clave. Si el documento creado no la conserva y no la devuelve al consultarlo, la aplicación no reconoce lo que ella misma creó.
Bloque obligatorio
Con estos la aplicación inicia sesión, sincroniza y registra una venta. Todo lo demás es opcional y puede quedar para una segunda etapa.
booleano
true
Nunca devuelvan la clave ni su hash. Solo el veredicto. Si la credencial es incorrecta, un error con código estable — no una respuesta vacía.
texto
sin espacios de relleno
"11"
Es el código con que la aplicación pedirá después su cartera, su historial y sus metas. Si aquí viene con espacios y en otro servicio sin ellos, las comparaciones fallan y el vendedor ve todo vacío, sin ningún error.
{
"valida": true,
"vendedor": "11"
}texto
sin relleno
"11"
texto
"Claudia Fuentes"
texto
con dígito verificador
"15482930-4"
texto
"cfuentes@empresa.cl"
texto
"PREVENTA"
Es la llave con que se consulta el servicio de reglas de la modalidad, que define bodega, caja y lista de precio. Un vendedor sin modalidad no puede abrir una venta.
booleano
false
{
"codigo": "11",
"nombre": "Claudia Fuentes",
"rut": "15482930-4",
"correo": "cfuentes@empresa.cl",
"modalidad": "PREVENTA",
"inactivo": false
}texto
"01"
Casi todas las consultas llevan este código. Si su ERP maneja una sola empresa, igual necesitamos un valor constante con el que identificarla.
texto
"76543210-3"
"Distribuidora del Sur SpA"
booleano
true
Define si un pedido aún no facturado descuenta del cupo disponible. Si el dato falta, el control de crédito no se aplica y no se emite ningún aviso: el vendedor puede vender por sobre el cupo sin enterarse.
{
"codigo": "01",
"rut": "76543210-3",
"razonSocial": "Distribuidora del Sur SpA",
"pedidoConsumeCredito": true
}número
porcentaje
15
Debe venir resuelto, no como una regla a interpretar. Si en su ERP el tope depende del producto, de la lista o del cliente, resuélvanlo antes de responder: la aplicación aplica el número tal cual, sin red.
lista de textos
["01P", "02P"]
lista de textos
["910", "920"]
Si la lista viene vacía, el vendedor no ve stock de ningún producto y no puede vender. Es una causa frecuente de "la aplicación no muestra productos" que en realidad es configuración del ERP.
lista de textos
["PEDIDO", "BOLETA", "FACTURA"]
Los valores concretos se acuerdan al definir el vocabulario de tipos de documento. Lo importante es que sea una lista cerrada y estable.
{
"topeDescuento": 15,
"listasPermitidas": ["01P", "02P"],
"bodegasPermitidas": ["910", "920"],
"tiposDocumento": ["PEDIDO", "BOLETA", "FACTURA"]
}texto
"01P"
Sin este valor el catálogo queda vacío o cae a la lista del cliente, que puede no ser la correcta. Es el campo que más veces explica un catálogo sin precios.
texto
"910"
texto
"001"
texto
"CAJA01"
lista de textos
["FACTURA_EXENTA"]
{
"empresa": "01",
"modalidad": "PREVENTA",
"listaVenta": "01P",
"bodega": "910",
"sucursal": "001",
"caja": "CAJA01",
"tiposExcluidos": ["FACTURA_EXENTA"]
}texto y texto
"CL0042" · "001"
Las dos identifican la venta. Si su ERP no maneja sucursales, devuelvan un valor constante: la aplicación necesita el par completo para grabar.
texto
"Minimarket Los Aromos"
texto
cuerpo-DV
"77921052-9"
Incluyan el dígito verificador. Si llega sin él, calcularlo por checksum produce un RUT que también valida pero no es el del cliente; ya ocurrió y el documento sale a nombre equivocado.
texto
"Av. Los Aromos 1450"
"Puente Alto" · "Santiago"
número decimal
-33.61169 · -70.57556
texto
"02P"
Si el cliente no tiene lista propia, devuelvan nulo y se usa la de la modalidad. No devuelvan vacío ni cero.
texto y número
"CREDITO" · 30
Sin este dato el arqueo de caja emite avisos de pagos faltantes que son falsos positivos: el vendedor cree que dejó de cobrar algo que en realidad era a plazo.
texto
"MA" (martes)
booleano
false
Ausente se interpreta como "no se sabe" y no bloquea. Si su ERP maneja bloqueo de clientes, conviene enviarlo siempre.
fecha y hora
ISO-8601 con offset
"2026-09-10T14:32:05-03:00"
Es lo que permite pedir solo lo que cambió. Sin ella, cada sincronización tiene que bajar la cartera completa.
{
"pagina": 1,
"total": 214,
"clientes": [
{
"codigo": "CL0042",
"sucursal": "001",
"razonSocial": "Minimarket Los Aromos",
"rut": "77921052-9",
"direccion": "Av. Los Aromos 1450",
"comuna": "Puente Alto",
"ciudad": "Santiago",
"latitud": -33.61169,
"longitud": -70.57556,
"listaPrecio": "02P",
"condicionPago": "CREDITO",
"diasPlazo": 30,
"diaVisita": "MA",
"diaCobro": "VI",
"bloqueado": false,
"modificadoEl": "2026-09-10T14:32:05-03:00"
}
]
}texto
"116231"
Debe ser el mismo código en catálogo, lista de precio y stock. Si en alguno viene distinto, el producto aparece sin precio o sin existencia.
texto
"Bebida Cola 1,5 L"
texto
"7801234567890"
Habilita buscar el producto escaneando o tecleando el código de barras.
texto
"UN"
texto
"CJ"
Si el producto se vende en dos unidades, hace falta además cuántas unidades primarias contiene la secundaria, para poder convertir.
booleano, uno por unidad
true · false
Determina si el vendedor puede digitar 1,5 o solo enteros. Se necesita uno por cada unidad de venta.
texto por nivel
"BEB" · "GAS" · "COLA"
Los niveles pueden repetir código entre sí. El mismo código puede significar cosas distintas en el nivel 1 y en el 2, así que el nivel debe venir junto al código. Resolver el nombre sin el nivel da el nombre equivocado.
texto
"ANDINA"
número decimal
1.62
{
"pagina": 1,
"total": 3480,
"productos": [
{
"codigo": "116231",
"descripcion": "Bebida Cola 1,5 L",
"codigoBarras": "7801234567890",
"unidad1": "UN",
"unidad2": "CJ",
"unidadesPorUnidad2": 6,
"divisible1": true,
"divisible2": false,
"familia": { "nivel": 1, "codigo": "BEB", "nombre": "Bebidas" },
"subfamilia": { "nivel": 2, "codigo": "GAS", "nombre": "Gaseosas" },
"linea": { "nivel": 3, "codigo": "COLA", "nombre": "Cola" },
"marca": "ANDINA",
"pesoKg": 1.62
}
]
}texto
"116231"
entero
pesos, antes de descuento
1290
Si su convención es otra, decláren la y entreguen la regla de redondeo. Ver "Precios: una sola convención" en las reglas generales.
número
porcentaje
19
número
porcentaje
10
Si este dato falta, la aplicación asume una tasa fija y termina cobrando IVA a un producto exento. Envíen cero explícito cuando no aplique.
lista de objetos
desde 12 → 1190
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| desdeCantidad | sí | número | La cantidad a partir de la cual rige ese precio. El tramo se entiende abierto hacia arriba hasta el siguiente. |
| precioUnitario | sí | entero | El unitario de ese tramo, antes de descuento, igual que el precio base. |
Solo si esa política existe en su ERP. La aplicación los muestra como referencia al vendedor. No los aplica sola: el precio que se cobra sigue siendo el que el vendedor elige en pantalla.
número
porcentaje
5
{
"lista": "01P",
"pagina": 1,
"cursorSiguiente": "116240",
"precios": [
{
"producto": "116231",
"precioUnitario": 1290,
"tasaIva": 19,
"tasaAdicional": 0,
"descuentoLista": 5,
"tramos": [
{ "desdeCantidad": 12, "precioUnitario": 1190 }
]
}
]
}texto
"116231"
texto
"910"
número decimal
248 (UN) · 41 (CJ)
Si el producto se vende en dos unidades, hacen falta las dos existencias, o la conversión para calcularlas.
{
"stock": [
{ "producto": "116231", "bodega": "910", "existencia1": 248, "existencia2": 41 },
{ "producto": "116232", "bodega": "910", "existencia1": 0, "existencia2": 0 }
]
}texto
UUID v4
"ca12ec77-62bb-4f00-8226-1bc654d5a0c1"
Sin esta clave la aplicación no puede reconocer los documentos que ella misma creó, y quedan duplicados en pantalla: uno local y otro descargado. Es el campo que cierra el ciclo de idempotencia.
texto
"0000001244" · "BOLETA"
fecha y hora
ISO-8601 con offset
"2026-09-10T11:05:00-03:00"
texto
"CL0042" · "001"
texto
"11"
Es el código del vendedor, el mismo del servicio 2. Sin él la aplicación no puede separar los documentos propios de los del resto del equipo, y el historial del vendedor sale mezclado.
texto
"PENDIENTE"
Aplica sobre todo a las notas de venta y pedidos: pendiente de despacho, despachado, facturado, anulado. Los valores concretos se acuerdan en el vocabulario. Sin este campo la aplicación muestra el documento pero no puede decir en qué etapa va, y el vendedor no sabe si lo que tomó ya se despachó.
entero
12354 · 2347 · 14701
lista de objetos
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| producto | sí | texto | El código del catálogo. Si no calza con el del servicio 7, la línea se muestra sin nombre ni imagen. |
| descripcion | sí | texto | La del momento de la venta. Se usa para reimprimir el documento tal como salió, aunque el producto se haya renombrado después. |
| cantidad | sí | número | En la unidad de la línea. |
| unidad | sí | texto | Cuál de las dos unidades del producto se usó. |
| precioUnitario | sí | entero | Antes de descuento. |
| descuento | no | número | El porcentaje aplicado. Si hubo cascada, puede venir el consolidado. |
| tasaIva | sí | número | La de esa línea. Un documento puede mezclar afectos y exentos. |
| tasaAdicional | sí | número | Cero si no tiene. |
| netoLinea | sí | entero | Después de descuento. |
| brutoLinea | sí | entero | Después de descuento, con impuestos. |
Si mandan el total bruto de una línea, manden también el neto. Los dos van después de descuento. Si llega solo el bruto, la aplicación vuelve a restar el descuento y muestra un neto equivocado.
texto
"PENDIENTE"
{
"documentos": [
{
"claveOrigen": "ca12ec77-62bb-4f00-8226-1bc654d5a0c1",
"tipo": "BOLETA",
"folio": "0000001244",
"emitidoEl": "2026-09-10T11:05:00-03:00",
"cliente": "CL0042",
"sucursal": "001",
"vendedor": "11",
"totalNeto": 12354,
"totalImpuestos": 2347,
"totalBruto": 14701,
"estadoPago": "PENDIENTE",
"lineas": [
{
"producto": "116231",
"descripcion": "Bebida Cola 1,5 L",
"cantidad": 12,
"unidad": "UN",
"precioUnitario": 1290,
"descuento": 5,
"tasaIva": 19,
"tasaAdicional": 0,
"netoLinea": 14706,
"brutoLinea": 17500
}
]
}
]
}texto
UUID v4
"ca12ec77-62bb-4f00-8226-1bc654d5a0c1"
Se genera una sola vez, al crear el documento, y no cambia entre reintentos. Es la clave sobre la que se apoyan las tres reglas de grabación.
"11" · "01" · "PREVENTA"
texto
"BOLETA"
Del vocabulario acordado. Es uno de los valores que hay que fijar antes de empezar a mapear.
"CL0042" · "001"
entero
14701
La aplicación totaliza sin red y envía su resultado. Si su ERP recalcula y llega a otro número, hay que decidir cuál manda: lo esperable es que el ERP mande, pero el vendedor ya le mostró el suyo al cliente, así que la diferencia tiene que ser cero.
"CREDITO"
ISO-8601 con offset
"2026-09-12T00:00:00-03:00"
texto
lista de objetos
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| producto | sí | texto | El código del catálogo, servicio 7. Es la llave con que ustedes resuelven qué se vendió. |
| descripcion | no | texto | La que vio el vendedor al cerrar. Viaja para dejar constancia de lo que se le mostró, no para que ustedes la usen como dato. |
| cantidad | sí | número | Expresada en la unidad de la línea, no en la unidad base. Puede ser decimal si el producto es divisible. |
| unidad | sí | texto | Cuál de las dos unidades del producto se usó. Sin ella, doce cajas y doce unidades son el mismo número. |
| bodega | no | texto | Solo cuando difiere de la bodega de la modalidad. Si no viene, se entiende la de la modalidad. |
| precioUnitario | sí | entero | Antes de descuento, según la convención de las reglas generales. |
| descuentos | no | lista de números | Hasta cuatro porcentajes en cascada: cada uno se aplica sobre el resultado del anterior, no sobre el precio original. Si su ERP admite uno solo, díganlo y consolidamos. |
| montoDescuentoLinea | sí | entero | El descuento ya resuelto en pesos. Va aparte de los porcentajes porque el redondeo de la cascada tiene que quedar fijado por quien lo calculó. |
| tasaIva | sí | número | La del producto, no una tasa general. Un exento viaja en cero y eso es un dato, no un faltante. |
| tasaAdicional | sí | número | Impuesto adicional del producto. Cero si no tiene. |
| netoLinea | sí | entero | Después de descuento. Es el valor que cuadra contra el total del documento. |
| brutoLinea | sí | entero | Neto más impuestos de la línea, ya redondeado. |
La convención de precios es la de las reglas generales: el unitario antes de descuento, los totales de línea después. Enviamos las dos cosas —los porcentajes y el monto ya resuelto— para que el redondeo no quede a interpretación: es la diferencia entre cuadrar y no cuadrar por un peso.
lista de objetos
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| medio | sí | texto | Efectivo, cheque, transferencia o tarjeta. Los valores concretos se acuerdan en el vocabulario. |
| monto | sí | entero | En pesos. La suma de los pagos puede ser menor que el bruto del documento: el saldo queda por cobrar. |
| banco | no | texto | Solo si el medio es cheque. Es el código del maestro de bancos, si ese servicio existe. |
| numeroCheque | no | texto | Solo si el medio es cheque. |
| fechaCheque | no | fecha | Solo si el medio es cheque. Es la fecha de cobro, que puede ser futura. |
Si la venta se cobra en el momento, el pago viaja junto al documento y no por el servicio de cobranza. Los tres campos del cheque son obligatorios cuando el medio es cheque, y sobran en cualquier otro caso.
texto
"0000001244"
texto o número
91
"ca12ec77-62bb-4f00-8226-1bc654d5a0c1"
booleano
true
Ante una clave repetida, respondan éxito con este indicador en verdadero y el folio del documento que ya existe. No respondan error: para la aplicación un error significa que puede reemplazar el documento, y ahí se produce el duplicado.
{
"clave": "ca12ec77-62bb-4f00-8226-1bc654d5a0c1",
"vendedor": "11",
"empresa": "01",
"modalidad": "PREVENTA",
"tipoDocumento": "BOLETA",
"cliente": "CL0042",
"sucursal": "001",
"condicionPago": "CREDITO",
"fechaEntrega": "2026-09-12T00:00:00-03:00",
"observacion": "Dejar en bodega trasera",
"totalBruto": 14701,
"lineas": [
{
"producto": "116231",
"descripcion": "Bebida Cola 1,5 L",
"cantidad": 12,
"unidad": "UN",
"bodega": "910",
"precioUnitario": 1290,
"descuentos": [5, 0, 0, 0],
"montoDescuentoLinea": 774,
"tasaIva": 19,
"tasaAdicional": 0,
"netoLinea": 14706,
"brutoLinea": 17500
}
],
"pagos": [
{ "medio": "EFECTIVO", "monto": 9701 },
{ "medio": "CHEQUE", "monto": 5000,
"banco": "012", "numeroCheque": "0004471", "fechaCheque": "2026-10-15" }
]
}{
"clave": "ca12ec77-62bb-4f00-8226-1bc654d5a0c1",
"folio": "0000001244",
"tipoDocumento": "BOLETA",
"id": 91,
"claveYaUsada": false
}{
"clave": "ca12ec77-62bb-4f00-8226-1bc654d5a0c1",
"folio": "0000001244",
"tipoDocumento": "BOLETA",
"id": 91,
"claveYaUsada": true
}Bloque obligatorio · actualizado en la versión 8
La aplicación y su backoffice guardan datos propios de la operación: licencias, versión y estado de cada teléfono, actividades, concursos y ubicaciones. Por diseño, esos datos viven junto al ERP, en dieciséis tablas en su infraestructura: así toda la información del negocio queda en un solo lugar, con el respaldo y la seguridad que ustedes ya administran.
Les pedimos tres cosas: (1) crear las tablas con nuestro script; (2) publicar un solo endpoint, {urlBase}/{tabla}, que responda a GET, PUT y DELETE; (3) pasar la prueba de conformidad y avisarnos. Les entregamos el script, el código del endpoint en cuatro lenguajes y la prueba.
Estas tablas no se relacionan con las de su ERP (sin claves foráneas) y pueden ir en otra base o esquema. Su ERP no necesita usarlas: las leemos y escribimos nosotros, a través de su API. Respáldenlas como al resto de sus datos.
| Motor | Script |
|---|---|
| SQL Server (probado en 2017, 2019, 2022 y 2025) | migraciones/sqlserver.sql |
| PostgreSQL (probado en 10, 11, 12, 13, 14, 15, 16, 17 y 18) | migraciones/postgresql.sql |
| MySQL (probado en 5.7, 8.0, 8.4 y 9.7) | migraciones/mysql.sql |
XESQUEMA.CATALOGO. El código del endpoint lo lee de ahí, así que no hay ningún otro archivo que mantener.| Regla | Detalle |
|---|---|
| Crea solo lo que falta | Revisa cada tabla, columna e índice y crea el que no exista. Lo que ya existe queda igual. |
| Nunca borra | No borra, renombra ni cambia nada, y no toca los datos. Se ejecuta sin detener la aplicación y se puede repetir sin efecto. |
| Anota su versión | Al terminar anota su número en XESQUEMA. Así sabemos qué versión tiene cada instalación. |
| Se toma solo | El script guarda el catálogo nuevo en XESQUEMA y el endpoint lo lee en menos de un minuto. No hay que reiniciar ni cambiar el código del endpoint. |
| Si no lo ejecutan | Solo la función de la aplicación que necesita lo nuevo queda apagada; el resto sigue funcionando. Les avisamos por correo con la versión y para qué sirve. |
| Tabla | Clave | Qué guarda |
|---|---|---|
| XESQUEMA | VERSION | La versión del script aplicada y, en CATALOGO, el catálogo de las tablas que lee el endpoint. La escribe solo el script; por la API es de solo lectura. |
| XREGISTER | KOFU | Licencia de cada vendedor: si está habilitado y cuántos equipos puede usar. |
| XREGISTERDEV | KOFU + DEVICEID | Equipos asociados a cada licencia. |
| XLICCONF | IDXLICCONF (siempre 1) | Tope de vendedores con licencia. Una sola fila. |
| XMOVILDOC | HASH | Documentos emitidos desde la aplicación, para medir su uso. |
| XMOVILVERSION | KOFU + DEVICEID | Versión de la aplicación en cada equipo. |
| XMOVILESTADO | EMPRESA + KOFU + DEVICEID | Último estado informado por cada equipo: pendientes por subir, reloj y zona horaria. |
| XMOVILESTADOSYNC | EMPRESA + KOFU + DEVICEID + SYNC | Resultado de cada tipo de sincronización por equipo. |
| XACTFLUJO | EMPRESA | Definición de las actividades que el vendedor completa en la visita. |
| XACTREG | UUID | Cada actividad registrada en terreno, con sus respuestas. |
| XACTFOTO | UUIDREG + CAMPO + HASH | Referencia a las fotos de cada actividad. Los archivos no van en la tabla. |
| XCONCURSO | EMPRESA + PERIODO | Definición del concurso de ventas del período. |
| XCONCURSOAV | EMPRESA + PERIODO + KOFU | Avance de cada vendedor en el concurso. |
| XTABFUMEIGN | EMPRESA + AGRUPACION + CODIGO | Familias de productos que no cuentan para las metas. |
| XMAEENGPS | UUID | Ubicaciones de clientes capturadas en terreno y si fueron aprobadas. |
| XPRODIMG | KOPR + ORDEN | Referencia a las imágenes de cada producto. Los archivos no van en la tabla. |
GET (listar), PUT (guardar) y DELETE (borrar). El nombre de la tabla va en la URL.{urlBase}/{tabla}| Parte | Qué es |
|---|---|
| {urlBase} | La definen ustedes y nos la informan. No cambia. Por ejemplo, https://api.myerp.cl/ramaflex. |
| {tabla} | Cambia en cada llamada: es el nombre de la tabla que leemos o escribimos (XACTREG, XMOVILESTADO…). Solo se aceptan las dieciséis del catálogo. |
https://api.myerp.cl/ramaflex/{tabla}| Método | Operación | Llamada de ejemplo |
|---|---|---|
| GET | Listar | GET https://api.myerp.cl/ramaflex/XACTREG?KOFU=11 |
| PUT | Guardar | PUT https://api.myerp.cl/ramaflex/XMOVILESTADO |
| DELETE | Borrar | DELETE https://api.myerp.cl/ramaflex/XCONCURSOAV?PERIODO=2026-11 |
| Framework | Declaración del endpoint |
|---|---|
| Node.js · Express | app.all('/ramaflex/:tabla', …) |
| Python · Flask | @app.route('/ramaflex/<tabla>', methods=['GET', 'PUT', 'DELETE']) |
| Java · HttpServer del JDK | servidor.createContext("/ramaflex/", …) |
| C# · ASP.NET Core | app.Map("/ramaflex/{tabla}", …) |
{tabla} de la URL y busca esa tabla en el catálogo que el script guardó en XESQUEMA.CATALOGO, que dice qué columnas tiene, cuál es su clave y de qué tipo es cada columna. Con eso arma la consulta en el momento. Por eso se programa una vez y sirve para todas las tablas, también para las que traigan las próximas versiones del script.GET https://api.myerp.cl/ramaflex/XMOVILVERSION?KOFU=11&orden=-ULTIMAVEZ| Paso | Qué hace el código |
|---|---|
| 1 · La tabla | Toma XMOVILVERSION de la ruta y la busca en el catálogo. Si no está, responde TABLA_DESCONOCIDA y no consulta la base. |
| 2 · Las columnas | Busca cada columna del filtro y del orden (KOFU, ULTIMAVEZ) entre las columnas de esa tabla en el catálogo. Si alguna no está, responde COLUMNA_DESCONOCIDA. |
| 3 · Los valores | Convierte cada valor según el tipo que dice el catálogo: KOFU es texto de hasta 20 caracteres. Si no calza, responde DATO_INVALIDO. |
| 4 · La consulta | Escribe la consulta con los nombres ya validados y los valores como parámetros, nunca pegados al texto. Completa el orden con la clave de la tabla para que las páginas no se repitan. |
| 5 · La respuesta | Convierte cada fila a JSON según el tipo de cada columna (las fechas, en UTC con Z) y responde { "filas": [...] }. |
"XMOVILVERSION": {
"desde": 1,
"clave": ["KOFU", "DEVICEID"],
"columnas": {
"KOFU": { "tipo": "texto", "largo": 20, "nulo": false },
"DEVICEID": { "tipo": "texto", "largo": 64, "nulo": false },
"APPVERSION": { "tipo": "texto", "largo": 20, "nulo": false, "defecto": "" },
"PLATAFORMA": { "tipo": "texto", "largo": 32, "nulo": true },
"EMPRESA": { "tipo": "texto", "largo": 10, "nulo": true },
"PRIMERAVEZ": { "tipo": "fecha", "nulo": false },
"DESDEVERSION": { "tipo": "fecha", "nulo": false },
"ULTIMAVEZ": { "tipo": "fecha", "nulo": false }
}
}
-- La consulta que arma el código (SQL Server; @p1 = '11', @p2 = 0, @p3 = 1000):
SELECT KOFU, DEVICEID, APPVERSION, PLATAFORMA, EMPRESA, PRIMERAVEZ, DESDEVERSION, ULTIMAVEZ
FROM XMOVILVERSION
WHERE KOFU = @p1
ORDER BY ULTIMAVEZ DESC, KOFU, DEVICEID
OFFSET @p2 ROWS FETCH NEXT @p3 ROWS ONLYGET {urlBase}/{tabla}?filtros · {tabla} es la tabla que se leeGET https://api.myerp.cl/ramaflex/XACTREG?EMPRESA=01&KOFU=11&FECHA.desde=2026-11-01T00:00:00Z&orden=-FECHA&pagina=1&limite=500| Parámetro | Significado |
|---|---|
| COLUMNA=valor | La columna es igual al valor. Si se repite la misma columna, basta con que sea igual a cualquiera de los valores (KOFU=11&KOFU=12). |
| COLUMNA.desde · COLUMNA.hasta | Rango, con ambos extremos incluidos. Sirve para fechas, números y texto. |
| orden | Columna por la que se ordena; con un guion delante, de mayor a menor. Se puede repetir. |
| pagina · limite | Página desde 1 y filas por página, hasta 1000 (por defecto 1 y 1000). Una página con menos filas que el límite es la última. |
| Sin filtros | Devuelve todas las filas, por páginas. |
200 { "filas": [ … ] } con todas las columnas de cada fila, también las nulas. Si ninguna fila cumple el filtro: { "filas": [] }.200
{
"filas": [
{
"IDXACTREG": 381,
"UUID": "3f1c2a9e-5b7d-4c11-9a0e-2d6f8b4e7c10",
"EMPRESA": "01",
"KOFU": "11",
"KOEN": "76123456",
"SUEN": "",
"ACTIVIDAD": "QUIEBRE_STOCK",
"FECHA": "2026-11-03T14:22:05.000Z",
"FECHAREG": "2026-11-03T14:22:41.512Z",
"VERSION": null,
"RESPUESTAS": "{\"hayQuiebre\":true}",
"APPVERSION": "1.15.0",
"DEVICEID": "a41f09c2e7"
}
]
}PUT {urlBase}/{tabla} · {tabla} es la tabla donde se guarda · cuerpo { "filas": [ … ] }, hasta 500 filasPUT https://api.myerp.cl/ramaflex/XMOVILESTADO| Regla | Detalle |
|---|---|
| Solo lo enviado | Al actualizar, se cambian solo las columnas que vienen en la fila; las demás quedan como estaban. Al crear, las que no vienen toman su valor por defecto o quedan nulas. |
| Todo o nada | Si una fila falla, no se guarda ninguna y la respuesta es un error. Aplica la regla 2 de grabación. |
| Repetible | Enviar dos veces la misma llamada deja el mismo resultado. Así podemos reintentar sin riesgo. |
| Autonuméricos | Las columnas ID… autonuméricas las asigna su base. Nunca las enviamos y la respuesta las devuelve. |
200 { "filas": [ … ] } con las filas tal como quedaron guardadas (todas las columnas), en el mismo orden en que llegaron.PUT https://api.myerp.cl/ramaflex/XMOVILESTADO
{
"filas": [
{
"EMPRESA": "01",
"KOFU": "11",
"DEVICEID": "a41f09c2e7",
"APPVERSION": "1.15.0",
"PLATAFORMA": "android",
"PENDIENTES": "{\"documentosSinSubir\":0}",
"RECIBIDOEN": "2026-11-03T14:30:01.204Z"
}
]
}
→ 200
{
"filas": [
{
"EMPRESA": "01",
"KOFU": "11",
"DEVICEID": "a41f09c2e7",
"APPVERSION": "1.15.0",
"PLATAFORMA": "android",
"ENVIADOEN": null,
"HUSOMINUTOS": null,
"PENDIENTES": "{\"documentosSinSubir\":0}",
"RECIBIDOEN": "2026-11-03T14:30:01.204Z"
}
]
}DELETE {urlBase}/{tabla}?filtros · {tabla} es la tabla donde se borra · los mismos filtros de ListarDELETE https://api.myerp.cl/ramaflex/XCONCURSOAV?EMPRESA=01&PERIODO=2026-11200 { "borradas": 14 }, con la cantidad de filas borradas (0 si ninguna cumplía el filtro).FILTRO_REQUERIDO y no borrar nada: un filtro olvidado no puede vaciar una tabla.{ "ok": false, "codigo": "…", "mensaje": "…" }. Nosotros decidimos por codigo; el mensaje es para leerlo en el log.| codigo | HTTP | Cuándo |
|---|---|---|
| NO_AUTENTICADO | 401 o 403 | Falta la credencial o no es válida. Si la rechaza su gateway antes de llegar al endpoint, basta el estado: el cuerpo puede ser el de su API. |
| TABLA_DESCONOCIDA | 404 | {tabla} no está en el catálogo. |
| COLUMNA_DESCONOCIDA | 400 | Un filtro, un orden o una fila trae una columna que esa tabla no tiene. |
| CLAVE_INCOMPLETA | 400 | Una fila del PUT no trae todas las columnas de la clave, o trae alguna en null. |
| DATO_INVALIDO | 400 | Un valor no calza con el tipo o el largo de su columna, falta un dato obligatorio, o el cuerpo no es { "filas": [ … ] } con hasta 500 filas. |
| FILTRO_REQUERIDO | 400 | DELETE sin ningún filtro. |
| SOLO_LECTURA | 405 | PUT o DELETE sobre XESQUEMA. |
| METODO_NO_PERMITIDO | 405 | Un método distinto de GET, PUT o DELETE. |
| CONFLICTO | 409 | Dos escrituras de la misma fila al mismo tiempo. Reintentamos. |
| ERROR_INTERNO | 500 | Cualquier otra falla. No se grabó nada. |
Cada fila es un objeto con los nombres de columna en mayúsculas, tal como en el script, aunque su motor los guarde en minúsculas. Fechas en ISO-8601 en UTC (2026-11-03T14:22:05.000Z), booleanos true/false, números como números y ausencia de dato como null. Los textos vuelven sin espacios de relleno.
Toda respuesta, de éxito o de error, trae Content-Type: application/json y el JSON en la raíz, sin envoltorio ({ "filas": [ … ] }, no { "code": 0, "data": … }), aunque la llamada traiga las cabeceras del resto de su API. Llamamos con Connection: close: si su servidor cierra la conexión después de responder, la respuesta debe decir Connection: close.
Son tablas chicas: crecen con la cantidad de vendedores, equipos y visitas, no con el volumen de ventas. Cada llamada debería responder en menos de un segundo.
Pueden leerlas cuando quieran. Por ejemplo, las ubicaciones aprobadas (XMAEENGPS con ESTADO = 'A') sirven para actualizar la ficha del cliente. No es necesario para que la aplicación funcione.
https://api.myerp.cl/ramaflex. En cada llamada, la tabla es lo que va después de la URL base.PUT https://api.myerp.cl/ramaflex/XACTREG
{
"filas": [
{
"UUID": "3f1c2a9e-5b7d-4c11-9a0e-2d6f8b4e7c10",
"EMPRESA": "01",
"KOFU": "11",
"KOEN": "76123456",
"ACTIVIDAD": "QUIEBRE_STOCK",
"FECHA": "2026-11-03T14:22:05Z",
"FECHAREG": "2026-11-03T14:22:41.512Z",
"RESPUESTAS": "{\"hayQuiebre\":true}",
"APPVERSION": "1.15.0",
"DEVICEID": "a41f09c2e7"
}
]
}
→ 200 (IDXACTREG lo asignó la base; SUEN tomó su valor por defecto y VERSION quedó nula)
{
"filas": [
{
"IDXACTREG": 1,
"UUID": "3f1c2a9e-5b7d-4c11-9a0e-2d6f8b4e7c10",
"EMPRESA": "01",
"KOFU": "11",
"KOEN": "76123456",
"SUEN": "",
"ACTIVIDAD": "QUIEBRE_STOCK",
"FECHA": "2026-11-03T14:22:05.000Z",
"FECHAREG": "2026-11-03T14:22:41.512Z",
"VERSION": null,
"RESPUESTAS": "{\"hayQuiebre\":true}",
"APPVERSION": "1.15.0",
"DEVICEID": "a41f09c2e7"
}
]
}GET https://api.myerp.cl/ramaflex/XACTREG
?EMPRESA=01&KOFU=11&FECHA.desde=2026-11-01T00:00:00Z
&orden=-FECHA&pagina=1&limite=500
(una sola URL; aquí partida en líneas para leerla)
→ 200 (la misma fila del ejemplo 1, con todas sus columnas)
{
"filas": [
{ "IDXACTREG": 1, "UUID": "3f1c2a9e-…", "EMPRESA": "01",
"KOFU": "11", …, "VERSION": null, … }
]
}Ya existe la fila EMPRESA 01, KOFU 11, DEVICEID a41f09c2e7, con PLATAFORMA "android" y APPVERSION "1.15.0".
Se envía solo la clave y lo que cambió:
PUT https://api.myerp.cl/ramaflex/XMOVILESTADO
{
"filas": [
{
"EMPRESA": "01",
"KOFU": "11",
"DEVICEID": "a41f09c2e7",
"APPVERSION": "1.16.0",
"RECIBIDOEN": "2026-11-04T09:00:00Z"
}
]
}
→ 200 (PLATAFORMA y PENDIENTES siguen como estaban)
{
"filas": [
{
"EMPRESA": "01",
"KOFU": "11",
"DEVICEID": "a41f09c2e7",
"APPVERSION": "1.16.0",
"PLATAFORMA": "android",
"ENVIADOEN": null,
"HUSOMINUTOS": null,
"PENDIENTES": "{\"documentosSinSubir\":0}",
"RECIBIDOEN": "2026-11-04T09:00:00.000Z"
}
]
}PUT https://api.myerp.cl/ramaflex/XCONCURSOAV
{
"filas": [
{ "EMPRESA": "01", "PERIODO": "2026-11", "KOFU": "11",
"GENERADOEN": "2026-11-04T09:00:00Z", "AVANCE": "{}",
"FECHAMOD": "2026-11-04T09:00:00Z" },
{ "EMPRESA": "01", "PERIODO": "2026-11", "KOFU": "12",
"GENERADOEN": "2026-11-04T09:00:00Z", "AVANCE": "{}",
"FECHAMOD": "2026-11-04T09:00:00Z" }
]
}
→ 200 { "filas": [ …las dos filas guardadas… ] }
DELETE https://api.myerp.cl/ramaflex/XCONCURSOAV?EMPRESA=01&PERIODO=2026-11
→ 200 { "borradas": 2 }GET https://api.myerp.cl/ramaflex/XESQUEMA
→ 200 (el script versión 2 aplicado; CATALOGO acortado aquí)
{
"filas": [
{
"VERSION": 2,
"DESCRIPCION": "El catalogo de las tablas se guarda en XESQUEMA.CATALOGO",
"APLICADA": "2026-11-06T12:00:00.000Z",
"CATALOGO": "{\"descripcion\":\"Tablas de Ramaflex ...\",\"version\":2,\"tablas\":{...}}"
}
]
}# Una tabla de su ERP: no está en el catálogo
GET https://api.myerp.cl/ramaflex/MAEEN
→ 404 { "ok": false, "codigo": "TABLA_DESCONOCIDA",
"mensaje": "Tabla desconocida: MAEEN" }
# XACTREG no tiene la columna CLAVE
GET https://api.myerp.cl/ramaflex/XACTREG?CLAVE=1
→ 400 { "ok": false, "codigo": "COLUMNA_DESCONOCIDA",
"mensaje": "Columna desconocida: CLAVE" }
# Falta DEVICEID, que es parte de la clave
PUT https://api.myerp.cl/ramaflex/XMOVILESTADO
{ "filas": [ { "EMPRESA": "01", "KOFU": "11" } ] }
→ 400 { "ok": false, "codigo": "CLAVE_INCOMPLETA",
"mensaje": "Falta la clave: EMPRESA + KOFU + DEVICEID" }
# HUSOMINUTOS es un entero
PUT https://api.myerp.cl/ramaflex/XMOVILESTADO
{ "filas": [ { "EMPRESA": "01", "KOFU": "11", "DEVICEID": "x",
"HUSOMINUTOS": "tres", "RECIBIDOEN": "2026-11-04T09:00:00Z" } ] }
→ 400 { "ok": false, "codigo": "DATO_INVALIDO",
"mensaje": "HUSOMINUTOS: valor inválido" }
# XESQUEMA solo la escriben los scripts
PUT https://api.myerp.cl/ramaflex/XESQUEMA
{ "filas": [ { "VERSION": 2 } ] }
→ 405 { "ok": false, "codigo": "SOLO_LECTURA",
"mensaje": "XESQUEMA es de solo lectura." }
# Borrar sin filtro
DELETE https://api.myerp.cl/ramaflex/XCONCURSOAV
→ 400 { "ok": false, "codigo": "FILTRO_REQUERIDO",
"mensaje": "Borrar exige al menos un filtro." }XESQUEMA.CATALOGO), cada minuto. Solo deben conectar su autenticación y su conexión a la base.| Lenguaje | Archivos | Estado |
|---|---|---|
| Pseudocódigo | ramaflex/pseudocodigo.txt | Es la regla. Si implementan en otro lenguaje, sigan este texto. |
| Node.js (Express) | tablas-ramaflex.js · servidor.js · package.json · LEEME.txt | Probado con Node 24. |
| Python (Flask) | ramaflex_tablas.py · servidor.py · requirements.txt · LEEME.txt | Probado con Python 3.11. |
| Java (JDBC; ejemplo con el servidor del JDK) | TablasRamaflex.java · ServidorEjemplo.java ·LEEME.txt | Probado con Java 11, con el servidor del JDK. |
| C# .NET 8 (ASP.NET Core) | TablasRamaflex.cs · Motores.cs · Program.cs · .csproj · LEEME.txt | Probado con .NET 8. |
| OpenAPI 3 | ramaflex/openapi.yaml | Para generar el esqueleto en cualquier lenguaje. |
RAMAFLEX_CABECERAS='{…las cabeceras que les indiquemos…}' node conformidad.js https://api.myerp.cl/ramaflex <token de la credencial de servicio>| Dato | Cuál usar |
|---|---|
| URL | La URL base de su endpoint en su ambiente de pruebas: {urlBase}, sin /{tabla}. |
| Token | Uno de la credencial de servicio que nos dieron, obtenido con su login como lo obtendríamos nosotros. Viaja como Authorization: Bearer <token>. |
| Cabeceras | Las mismas que enviamos en cada llamada a su API; se las indicamos por correo. Sin ellas la prueba puede pasar y nuestras llamadas fallar (por ejemplo, si una cabecera cambia el formato de la respuesta). |
ZZPRUEBA). Ejecútenla contra una base de pruebas con el script aplicado, nunca en producción. Cuando pase completa, avísennos.XESQUEMA, prueba cada tabla con una lectura de una fila (que responda y con todas sus columnas), prueba los servicios livianos (empresas, funcionarios, modalidad y permisos) y lista lo que falta.ZZPRUEBA, solo en su ambiente de pruebas.Bloque opcional
Cada uno habilita una función de la aplicación. El que no exista simplemente no se implementa: la función queda apagada y el resto opera igual. Van agrupados por la función que habilitan, y cada uno se detalla igual que los obligatorios.
"116231" · "910"
número
248 · 36
número
212
Envíen el resultado, no solo los insumos. Si la aplicación tuviera que restar por su cuenta, replicaría una regla que es de ustedes: qué estados de pedido comprometen existencia y cuáles no.
{
"stock": [
{
"producto": "116231",
"bodega": "910",
"existencia": 248,
"comprometido": 36,
"disponible": 212
}
]
}"FACTURA" · "0000004512"
ISO-8601 con offset
"2026-08-12T00:00:00-03:00"
"2026-09-11T00:00:00-03:00"
entero
89250 · 45000
Los dos, no solo el saldo: el vendedor necesita ver el total original para conversar el abono con el cliente.
"ABONADO"
{
"cliente": "CL0042",
"documentos": [
{
"tipo": "FACTURA",
"folio": "0000004512",
"emitidoEl": "2026-08-12T00:00:00-03:00",
"venceEl": "2026-09-11T00:00:00-03:00",
"monto": 89250,
"saldo": 45000,
"estado": "ABONADO"
}
]
}"EFECTIVO" · 45000
Los medios posibles se acuerdan en el vocabulario: efectivo, cheque, transferencia, tarjeta.
lista de objetos
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| tipo | sí | texto | Tipo del documento que se salda, del mismo vocabulario que el resto. |
| folio | sí | texto | Folio del documento que se salda. Junto al tipo lo identifica. |
| monto | sí | entero | Cuánto de este pago se aplica a ese documento. La suma de los montos debe dar el total del pago. |
Un pago puede repartirse entre varios documentos. Si su ERP aplica el abono por su cuenta, dígalo: la aplicación puede enviar solo el monto total.
banco, número y fecha
"012" · "0001234" · 2026-09-25
"RC0000891"
{
"clave": "3c9d5e71-8b42-4a0f-9d16-5e7c2b8f3a41",
"cliente": "CL0042",
"sucursal": "001",
"vendedor": "11",
"medio": "EFECTIVO",
"monto": 45000,
"aplicaA": [
{ "tipo": "FACTURA", "folio": "0000004512", "monto": 45000 }
]
}{
"clave": "3c9d5e71-8b42-4a0f-9d16-5e7c2b8f3a41",
"numero": "REC-0000891",
"yaExistia": false
}"012" · "Banco Estado"
{
"bancos": [
{ "codigo": "012", "nombre": "Banco Estado" },
{ "codigo": "001", "nombre": "Banco de Chile" }
]
}entero
1500000
entero
890500
entero
609500
Qué entra en el cálculo —si los pedidos pendientes descuentan cupo, si los cheques en cartera cuentan— es una regla de ustedes. Envíen el resultado para que la aplicación no tenga que replicarla.
{
"creditos": [
{
"cliente": "CL0042",
"sucursal": "001",
"cupo": 1500000,
"deuda": 890500,
"disponible": 609500
}
]
}lista de objetos
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| tipo | sí | texto | Tipo de documento. |
| folio | sí | texto | Folio del documento. |
| emitidoEl | sí | fecha y hora | ISO-8601 con offset. |
| cliente | sí | texto | Código del cliente. |
| sucursal | sí | texto | Sucursal del cliente. |
| vendedor | sí | texto | Quién emitió. |
| totalNeto | sí | entero | Del documento completo. |
| totalImpuestos | sí | entero | Del documento completo. |
| totalBruto | sí | entero | Del documento completo. |
| saldo | no | entero | Lo que queda por cobrar, si su ERP lo lleva. |
Es el mismo contenido que el historial, pero en una estructura plana: encabezado y totales, sin el detalle de líneas. Buscar un campo en el nivel equivocado es una fuente conocida de falsos negativos.
entero
14701 · 0
{
"documentos": [
{
"tipo": "BOLETA",
"folio": "0000001244",
"emitidoEl": "2026-09-10T11:05:00-03:00",
"cliente": "CL0042",
"sucursal": "001",
"vendedor": "11",
"totalNeto": 12354,
"totalImpuestos": 2347,
"totalBruto": 14701,
"saldo": 0
}
]
}lista de objetos
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| documento | sí | objeto | El documento consultado, con tipo y folio. Es el centro de la cadena. |
| origenes | sí | lista de objetos | Los documentos que lo originaron, cada uno con tipo y folio. Lista vacía si no viene de ninguno. |
| destinos | sí | lista de objetos | Los documentos que resultaron de él, con la misma forma. Lista vacía si todavía no derivó en nada. |
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| tipo | sí | texto | Tipo del documento relacionado, del mismo vocabulario que el resto. |
| folio | sí | texto | Folio del documento relacionado. |
| relacion | no | texto | Qué clase de vínculo es: facturación, anulación, devolución. Si su ERP solo maneja una clase, se puede omitir. |
Basta la relación directa de origen y destino; la aplicación arma el árbol completo encadenando las respuestas. Acepta consultas en lote para no pedir uno por uno.
{
"cadenas": [
{
"documento": { "tipo": "FACTURA", "folio": "0000004512" },
"origenes": [ { "tipo": "PEDIDO", "folio": "V010066868" } ],
"destinos": [ { "tipo": "GUIA", "folio": "0000008891" } ]
}
]
}"0000004512" → "V010066868"
Es un caso puntual de la cadena de documentos. Si ese servicio ya cubre la relación pedido–factura, este no hace falta.
{
"origenes": [
{ "factura": "0000004512", "pedido": "V010066868" }
]
}imagen PNG
576 px (80 mm)
Es el servicio más prescindible de todos: la aplicación sabe componer el comprobante por su cuenta con los datos del documento. Solo tiene sentido si el comprobante tiene un formato legal o de marca que deba salir del ERP.
{
"documento": { "tipo": "BOLETA", "folio": "0000001244" },
"ancho": 576,
"formato": "image/png",
"imagen": "iVBORw0KGgoAAAANSUhEUgAAAkAAAA... (base64)"
}
// También sirve devolver el PNG directo, con Content-Type: image/png.
// Si eligen esa forma, declárenlo: el traductor no adivina cuál de las dos es.RUT con dígito verificador
"77921052-9"
"Minimarket Los Aromos"
"Av. Los Aromos 1450" · "Puente Alto"
La comuna se elige de la lista del servicio de territorios, para que llegue con un valor que su ERP reconozca.
"CL0318" · "001"
La aplicación lo necesita de inmediato: sin el código no puede emitirle el documento en la misma visita, que es el motivo de dar el alta en terreno.
{
"codigo": "CL0318",
"sucursal": "001",
"creado": true
}"13201" · "Puente Alto"
nivel 3, depende de "13"
La aplicación usa el nivel para encadenar los selectores: elegida la región, solo ofrece sus comunas.
{
"territorios": [
{ "codigo": "13", "nombre": "Región Metropolitana", "nivel": 2, "padre": "CL" },
{ "codigo": "13201", "nombre": "Puente Alto", "nivel": 3, "padre": "13" }
]
}número (1, 2 o 3)
2
El nivel es obligatorio junto al código. Un mismo código puede existir en dos niveles con nombres distintos; resolver el nombre sin el nivel entrega el equivocado. Medido en una instalación real: pasaba en 11 de 13 empresas.
"GAS" · "Gaseosas"
{
"familias": [
{ "nivel": 1, "codigo": "BEB", "nombre": "Bebidas" },
{ "nivel": 2, "codigo": "GAS", "nombre": "Gaseosas" },
{ "nivel": 3, "codigo": "COLA", "nombre": "Cola" }
]
}"PR-2026-14" · "Lleve 12, pague 10"
ISO-8601 con offset
01-sep a 30-sep
La aplicación sincroniza y opera sin red durante horas: necesita las fechas para dejar de ofrecer una promoción vencida aunque no haya vuelto a sincronizar.
lista de objetos
productos ["116231"]
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| dimension | sí | texto | Sobre qué se define el alcance: producto, familia, cliente o zona. Los valores se acuerdan en el vocabulario. |
| valores | sí | lista de textos | Los códigos alcanzados en esa dimensión. Lista vacía significa "ninguno", no "todos": si la promoción es universal, díganlo con una dimensión propia y no con una lista vacía. |
Una promoción puede tener varios alcances a la vez —por ejemplo una familia de productos y una zona de clientes— y se entienden combinados: aplica cuando se cumplen todos.
objeto
descuento 12%
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| tipo | sí | texto | Qué clase de beneficio es: descuento porcentual, descuento en monto, o bonificación en producto. Los valores se acuerdan en el vocabulario. |
| valor | sí | número | El porcentaje o el monto, según el tipo. La aplicación no deduce cuál es: lo decide el tipo. |
| productoBonificado | no | texto | Solo si el tipo es bonificación en producto: qué producto se regala. |
Si su ERP maneja promociones complejas —combos, escalas, condiciones cruzadas— conviene acordar primero cuáles se pueden expresar de forma declarativa. La aplicación no ejecuta reglas arbitrarias: muestra el beneficio, no lo calcula.
{
"promociones": [
{
"id": "PR-2026-14",
"descripcion": "Lleve 12, pague 10",
"desde": "2026-09-01T00:00:00-03:00",
"hasta": "2026-09-30T23:59:59-03:00",
"aplicaA": [
{ "dimension": "PRODUCTO", "valores": ["116231"] },
{ "dimension": "ZONA", "valores": ["SUR"] }
],
"beneficio": { "tipo": "DESCUENTO_PORCENTAJE", "valor": 12 }
}
]
}"IVA" · "IVA 19%"
número
19
"GENERAL" · "ADICIONAL"
La aplicación los trata distinto al totalizar y al mostrar el desglose, así que la distinción tiene que venir declarada.
{
"impuestos": [
{ "codigo": "IVA", "nombre": "IVA 19%", "tasa": 19, "tipo": "GENERAL" },
{ "codigo": "ILA", "nombre": "Impuesto bebidas", "tasa": 10, "tipo": "ADICIONAL" }
]
}lista de objetos
"116231" → ["IVA", "ILA"]
| propiedad | oblig. | tipo | qué es |
|---|---|---|---|
| producto | sí | texto | Código del catálogo. |
| impuestos | sí | lista de textos | Los códigos del maestro de impuestos que le aplican. Cada código lleva su tasa en «Definición de impuestos»; aquí van solo los códigos, para no repetir la tasa producto por producto. |
Un producto exento debe venir con lista vacía, no ausente. La diferencia entre "no aplica ninguno" y "no vino el dato" cambia el total.
{
"impuestosPorProducto": [
{ "producto": "116231", "impuestos": ["IVA", "ILA"] },
{ "producto": "220145", "impuestos": [] }
]
}"2026-09"
número
8500000 · 5320400
El avance debe venir calculado. Si la aplicación lo dedujera de los documentos que tiene sincronizados, mostraría una cifra distinta de la del ERP.
"VENTA_PESOS"
{
"metas": [
{
"vendedor": "11",
"periodo": "2026-09",
"definicion": "VENTA_PESOS",
"meta": 8500000,
"avance": 5320400
}
]
}"VENTA_PESOS" · "Venta en pesos"
"PESOS"
Si alguna meta se mide en kilos, el catálogo tiene que traer el peso unitario del producto; sin ese dato la meta no se puede representar.
{
"definiciones": [
{ "codigo": "VENTA_PESOS", "nombre": "Venta en pesos", "unidad": "PESOS" },
{ "codigo": "VENTA_KILOS", "nombre": "Venta en kilos", "unidad": "KILOS" }
]
}"03" · "Local cerrado"
Una lista corta y estable funciona mejor: el vendedor la usa de pie, en la puerta del local.
{
"motivos": [
{ "codigo": "01", "descripcion": "Tiene stock suficiente" },
{ "codigo": "02", "descripcion": "Sin capacidad de pago" },
{ "codigo": "03", "descripcion": "Local cerrado" }
]
}Fuera del alcance
Cómo se prueba y con qué se mide
Necesitamos un ambiente contra el cual equivocarnos. Sin él, la conformidad se prueba contra la producción de un cliente, que es exactamente donde no conviene descubrir que un campo llega distinto. No hace falta que sea grande, pero sí que tenga los casos incómodos: un cliente con varias sucursales, un producto exento, una existencia en cero, un documento ya pagado y un vendedor sin permisos. Con datos perfectos no se prueba nada.
La paginación resuelve el tamaño, no la espera. Conviene fijar un objetivo verificable: por ejemplo, páginas de mil registros respondiendo en menos de cinco segundos en el peor caso. El antecedente es concreto y medido en nuestra instalación: un catálogo de treinta y tres mil productos servido sin paginar dejaba la sincronización en tiempo de espera, y el vendedor abría la aplicación sin precios y sin saber por qué.
La aplicación consulta; su API responde. No necesitamos que nos notifiquen nada. Lo que sí conviene declarar es la frecuencia esperada: un equipo de veinte vendedores sincronizando al inicio de la jornada produce un pico concentrado, y es mejor dimensionarlo antes que descubrirlo el primer lunes.
Un contacto técnico con nombre, y una vía para reportar. Durante la puesta en marcha la mayoría de los hallazgos son de datos, no de código —un vocabulario que trae un valor nuevo, un campo que en un cliente viene nulo— y se resuelven en minutos si hay a quién preguntarle.
Antes de empezar
{urlBase}/{tabla}.