Skip to content

Integrar un ERP ​

Esta guía es para quien programa la integración entre el ERP de la empresa y Safety Panel. Recorre el circuito completo en el orden en que conviene hacerlo. Cada paso enlaza a Recursos, donde están los campos, los filtros y los errores de cada llamada, y a Convenciones, que vale para todas. Si generás un cliente, partí de la especificación OpenAPI.

Quién es dueño de cada dato ​

El ERP ya lleva el personal, los contratos, el catálogo y el stock. Lo que Safety Panel aporta es la evidencia firmada. Cada dato tiene un solo dueño, y el otro sistema lo copia:

DatoDueñoSentidoRecurso
Empleados, legajo, empleador, sitio y puesto asignadosERP o RRHHERP → Safety PanelEmpleados
Sitios: alta y baja de contratosERPERP → Safety PanelSitios
Catálogo de EPP: código, marca, modeloERPERP → Safety PanelProductos de EPP
Stock de EPPERPERP → Safety Panel, o apagado en Safety PanelStock de EPP
Entrega firmada, devoluciones y constanciaSafety PanelSafety Panel → ERPEntregas de EPP
Capacitaciones, constancias e incidentesSafety PanelSafety Panel → ERPConstancias, registros e incidentes

Dos reglas valen para todo el circuito:

  • Nada se borra por la API. Los empleados se dan de baja y los sitios se archivan, y los dos se restauran con todo su historial. La evidencia se conserva siempre.
  • La firma ocurre siempre en Safety Panel. El ERP registra lo que despachó, pero solo el empleado firma, en su portal o en el tótem.

1. Emití una key para la integración ​

Emití una key solo para el ERP, con los permisos que va a usar y ninguno más. Si otro sistema (por ejemplo, el de RRHH) también se conecta, que tenga su propia key: así cada uno se revoca sin afectar al otro.

ParaPermisos
El personalemployees:write, sites:write, legal_entities:read y positions:read. Con legal_entities:write y positions:write, además, guardás tus códigos en las razones sociales y los puestos.
El catálogo y el stockppe_products:write, ppe_categories:read y ppe_stocks:write.
Las entregasppe_deliveries:write para crearlas y leerlas, ppe_needs:read para planificar y ppe_historical_deliveries:write para cargar el historial.
La evidenciacertifications:read y training_records:read. incidents:read solo si el ERP cruza los accidentes con el ausentismo.

Ningún permiso se da solo, ni incidents:read ni ningún otro (ver Permisos).

Si el ERP ya usaba una key anterior a los permisos, esa key solo lee la cuenta, los sitios, los grupos de sitios y los profesionales, y en ellos solo modifica sus campos externos: no da de alta, no archiva ni llega a los demás recursos de esta guía. Emití una nueva con los permisos de la tabla, cambiala en el ERP y revocá la anterior.

2. La carga inicial ​

No hay endpoints de carga masiva: la carga inicial es una llamada por registro. Hacela en este orden, porque cada paso usa los id del anterior:

  1. Razones sociales. Leé las razones sociales de la cuenta y asociá cada empresa del ERP con la suya (buscala con tax_id). No se crean por la API: un empleador nuevo se agrega en el panel.
  2. Sitios. Uno por contrato, con POST /api/v1/sites. Si el sitio ya existe en Safety Panel, guardale tu código con PATCH en lugar de crearlo. Cargale la dirección: las entregas de EPP la toman del sitio. Un sitio creado por la API nace con los módulos apagados: para registrar entregas de EPP en él, encendé Entregas de EPP en ese sitio desde el panel (ver Habilitación).
  3. Puestos. Leé los puestos de cada sitio y asocialos con los del ERP. Tampoco se crean por la API: se cargan en el panel.
  4. Empleados. Con POST /api/v1/employees, cada uno con su legal_entity_id.
  5. Asignaciones. Cada sitio y puesto de cada empleado, con PUT /api/v1/employees/:id/sites/:site_id.
  6. Catálogo. Cada producto con POST /api/v1/ppe_products y su código en external_reference, y habilitado en sus sitios con PUT /api/v1/ppe_products/:id/sites/:site_id.
  7. Stock, si Safety Panel lo refleja: PUT /api/v1/sites/:site_id/ppe_stocks/:ppe_product_id por producto y sitio. Ver El stock.
  8. Historial de entregas. Lo que el ERP ya entregó, con POST /api/v1/employees/:employee_id/ppe_historical_deliveries. Así las necesidades parten de la última entrega real. Esas entregas no son evidencia de firma: no tienen constancia ni descuentan stock. Ver Cargar el historial de entregas.

El ritmo ​

Hacé una pausa de 200 ms entre llamadas: son las 300 por minuto del límite de uso de la key. Cuentan todas las llamadas de la key, también las lecturas con que el ERP busca un registro, así que si la misma key hace otras llamadas mientras tanto, la pausa tiene que ser más larga. Ante un 429, esperá los segundos de retry-after y reintentá el mismo registro, no el siguiente.

Que un reintento no cree dos veces ​

Cada alta lleva el identificador del registro en el ERP en dos lugares:

El prefijo hace falta porque las claves valen para toda la key, no por recurso: si un sitio y un empleado comparten el número 1001 en el ERP, la segunda alta respondería 422 idempotency_key_reused en lugar de crearse.

Con esa clave, un reintento dentro de las 24 horas no crea el registro dos veces. Pasado ese plazo, buscalo por su external_reference antes de volver a crearlo: la búsqueda trae también los dados de baja y los archivados. Para las demás escrituras sobre un registro (una baja, una asignación), usá una clave nueva por operación.

Un ejemplo ​

Este bucle en Ruby crea un registro por llamada con la pausa, y reintenta con la misma clave y el mismo cuerpo un 429, un 409 idempotency_key_in_progress, un corte de red y un error de la puerta de entrada (502, 503, 504), que pueden llegar aunque el registro se haya guardado. Si un registro no responde después de 10 intentos, se detiene:

ruby
require "net/http"
require "json"

PAUSE = 0.2
ATTEMPTS = 10
RETRIABLE = %w[502 503 504].freeze

def create(http, token, path, key, record)
  ATTEMPTS.times do
    request = Net::HTTP::Post.new(path, "Authorization" => "Bearer #{token}", "Content-Type" => "application/json",
      "Idempotency-Key" => key)
    request.body = JSON.generate(record)

    begin
      response = http.request(request)
    rescue Net::OpenTimeout, Net::ReadTimeout, EOFError, SystemCallError
      sleep 5
      next
    end

    if response.code == "429"
      sleep Integer(response["retry-after"])
    elsif RETRIABLE.include?(response.code) || in_progress?(response)
      sleep 5
    else
      sleep PAUSE
      return response
    end
  end

  raise "#{key}: sin respuesta después de #{ATTEMPTS} intentos"
end

def in_progress?(response)
  response.code == "409" && JSON.parse(response.body).dig("error", "code") == "idempotency_key_in_progress"
rescue JSON::ParserError
  false
end

Se usa así, con un registro del ERP por llamada:

ruby
Net::HTTP.start("tuempresa.safetypanel.com.ar", 443, use_ssl: true) do |http|
  employees.each do |employee|
    key = "employee-#{employee["external_reference"]}"
    response = create(http, ENV.fetch("SAFETYPANEL_API_KEY"), "/api/v1/employees", key, employee)
    puts "#{employee["external_reference"]}: #{response.code}"
  end
end

Cualquier otra respuesta vuelve a tu sistema: un 201 trae el registro creado, y un 4xx dice en el mensaje qué corregir. Si la carga se detiene, volvé a correrla: dentro de las 24 horas, lo que ya se creó responde lo mismo sin duplicarse.

Una excepción: si al retomar la carga un registro cambió en el ERP, su alta ya no es el mismo pedido y responde 422 idempotency_key_reused. Ese registro sí se creó la primera vez. No lo vuelvas a crear con otra clave: buscalo por su external_reference y actualizalo con PATCH. Cualquier otro 4xx no guardó nada.

3. El personal ​

Altas ​

Un empleado nuevo en el ERP es un POST /api/v1/employees, y después un PUT por cada sitio donde trabaja. Ver Dar de alta un empleado y Asignar a un sitio.

Bajas ​

Cuando alguien deja la empresa, dalo de baja con PATCH /api/v1/employees/:id y {"status": "deleted"}, con una Idempotency-Key nueva: todo PATCH que envía status o legal_entity_id la exige, aunque sea el valor que ya tiene, igual que al reactivar. No hay DELETE: se conserva con todo su historial, sus entregas firmadas y sus constancias. Ver Dar de baja y reactivar.

Recontrataciones ​

Recontratar es reactivar, nunca crear de nuevo. Antes del alta, buscá a la persona por su DNI con GET /api/v1/employees?dni=…, que la encuentra en cualquier estado. Si está dada de baja, reactivala con PATCH y {"status": "active"}.

Si el alta llega igual, Safety Panel la frena: responde 422 employee_deactivated con el employee_id del empleado existente. Reactivá ese en lugar de crear otro.

Al reactivarlo conserva los sitios que tenía. Ajustalos con PUT y DELETE sobre sus sitios.

Cambios de sitio y de puesto ​

Un empleado que rota de sitio se mueve de a un sitio por llamada:

  • PUT /api/v1/employees/:id/sites/:site_id lo asigna, o le cambia el puesto si ya estaba.
  • DELETE /api/v1/employees/:id/sites/:site_id lo quita del sitio, sin borrar nada de lo que firmó ahí.

Las dos se pueden repetir sin efecto, así que el ERP puede enviar la asignación que tiene registrada sin preguntar antes. Ver Asignar a un sitio y Quitar de un sitio.

Contratos que terminan y vuelven ​

Cuando termina un contrato, archivá su sitio con PATCH /api/v1/sites/:id y {"status": "deleted"}, con una Idempotency-Key nueva; restaurarlo también la exige. Si el contrato vuelve, restauralo con {"status": "active"} en lugar de crear otro: lo encontrás por su external_reference. Ver Archivar y restaurar.

4. El circuito de EPP ​

Cada producto lleva en external_reference su código (SKU) en el ERP. Safety Panel no exige que el código sea único: si dos productos lo comparten, una entrega que lo nombra responde 422 ambiguous_external_reference con los matching_ids, en lugar de elegir uno en silencio. Corregí el código en el ERP o nombrá el producto por su ppe_product_id. Ver Productos de EPP.

La entrega ​

  1. El ERP registra lo que despachó con POST /api/v1/ppe_deliveries: el empleado, el sitio, los elementos con su código y el número de remito en external_reference. Puede quedar en borrador (draft), para completarla después, o lista para firmar (ready_to_sign). Ver Crear una entrega.
  2. El empleado la firma en Safety Panel, en su portal o en el tótem.
  3. El ERP se entera de la firma de una de dos formas:
    • con un webhook del evento ppe_delivery.signed, que sale al firmarse y se reintenta si tu sistema no responde. Ver Recibir eventos con un webhook;
    • o consultando cada tanto con GET /api/v1/ppe_deliveries?status=signed&signed_since=….
  4. Recién ahí el ERP descuenta su stock. Una entrega draft todavía puede cambiar, y una draft o ready_to_sign puede cancelarse: no la tomes como entregada.
  5. El ERP guarda la constancia. La constancia en PDF se genera unos segundos después de la firma, así que puede no estar todavía al llegar el aviso. Ver La constancia en PDF.

Una devolución avisa con ppe_delivery_return.created: es el momento de que el ERP sume al stock lo devuelto. Una entrega que no se va a firmar se cancela con PATCH, su motivo y una Idempotency-Key nueva. Ver Modificar o cancelar una entrega.

El stock ​

El dueño del stock es el ERP. Hay dos caminos:

  • Safety Panel lo refleja. El ERP empuja la cantidad absoluta de cada producto en cada sitio con PUT, y el sitio ve cuánto tiene y recibe el aviso de stock bajo. Safety Panel sigue descontando al firmarse una entrega, pero el último PUT del ERP es el que vale: el número de Safety Panel se pone al día con él. Para que coincida enseguida, enviá el PUT en cuanto el ERP procese cada firma o devolución. Ver Quién descuenta.
  • Safety Panel no lo lleva. Si no querés ese reflejo, pedí a Safety Panel que apague la gestión de stock en la cuenta. Las entregas funcionan igual, y un PUT de stock responde 409 ppe_stock_disabled.

Las necesidades ​

Para planificar compras y despachos, leé qué le falta a cada empleado según su puesto con GET /api/v1/ppe_needs?site_id=…&status=missing. Se calculan en el momento con la misma regla que el panel: no las copies, pedilas cada vez. Ver Necesidades de EPP.

5. La evidencia que lee el ERP ​

Lo que el ERP no tiene, y lee de Safety Panel para su legajo:

QuéCómoPara enterarse
Las entregas firmadas y su constanciaEntregas de EPP. El enlace a la constancia vence a los 5 minutos: descargala en el momento.ppe_delivery.signed y ppe_delivery_return.created
Las constancias de capacitación y su certificadoConstancias de capacitación. El certificado se descarga con la key, a no más de 30 por minuto, y cada descarga cuenta también en las 300.certification.expiring y certification.expired
Las capacitaciones presenciales y quiénes asistieronRegistros de capacitación.updated_since
Los incidentesIncidentes, con incidents:read. Sin datos de salud ni relatos.incident.created

Las constancias y los certificados son evidencia firmada con datos personales: guardalos como documentos confidenciales.

Para todo lo demás, y para lo que se haya perdido mientras un webhook estuvo desactivado, leé lo que cambió con updated_since. Ver Sincronizar con otro sistema.

Si una llamada falla ​

Ver qué hacer con cada respuesta en Sincronizar con otro sistema, y todos los códigos en Errores.

Documentación de Safety Panel, software de gestión de seguridad e higiene.