API Portas — Datos

Legajos y usuarios: consulta y alta/actualización, en reemplazo de la conexión directa a la base y de las llamadas posicionales a stored procedures

v1 · REST · JSON · Bearer auth · EN PRODUCCIÓN
Estado: los endpoints de datos están desplegados y operativos en https://api.portascloud.ar. Se pueden probar en vivo desde el playground.
ⓘ Mismo host y prefijo que la API de archivos. El path /portas/api es fijo.
▶ Probar la API de datos en vivo (playground) 📄 API de archivos

1. Introducción

La API de Datos expone por REST lo que hoy www.portas.com.ar (escritorio / móvil / importas) hace conectándose directo a la MySQL de FreeWayNet. Dos objetivos:

  1. Eliminar la conexión directa a la base: todo pasa por la API en DonWeb.
  2. Eliminar los parámetros posicionales: el cliente manda JSON nombrado; el servidor arma la llamada al stored procedure. Si falta un campo, no se "corre" nada.
Por dentro: el PHP de la API (DonWeb) se conecta a la MySQL de FreeWayNet (red Portas) y ejecuta el SELECT sobre las vistas vw_* (lectura) o el CALL al sp_* (escritura). Sincrónico: el alta devuelve el id_carpeta en la misma respuesta.

2. Autenticación y scopes

Toda llamada requiere Authorization: Bearer <api-key>.

Authorization: Bearer mkp_TUKEYPRIVADA1234567890abcdef
Scopes: datos:read para los GET y el login; datos:write para alta/actualización/evento y alta/actualización de usuarios. Sin el scope necesario → 403.

3. Convenciones

4.1 Legajo — lectura ★ vw_carpetas

GET/v1/legajos/{id_carpeta}scope: datos:read

Devuelve la carátula completa del legajo desde vw_carpetas (la misma vista que consume portas.com.ar), con los nombres ya resueltos (cliente, vía, mercadería, país, terminal como texto, no solo IDs), más contenedores[] y eventos[] (que no están en la vista).

Incluye además cuatro campos calculados, iguales a los que muestra portas.com.ar: tipo ("Importación" / "Exportación", derivado de operacion), estado (texto del ciclo de vida: Apertura de Legajo → SIMI Oficializada / Carga Arribada → Oficializada → Carga Liberada → Facturada → Cobrada, o Anulada / Anulada Aduana), responsable (nombre del responsable principal asignado al legajo) y en_curso (booleano con el mismo criterio del filtro "En Curso" del escritorio de portas.com.ar: true mientras no haya fecha_finalizacion_carga ni fecha_ingreso_deposito; ambas fechas crudas también vienen en carpeta).

curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/legajos/2026.04.98765"

Respuesta (recortada — carpeta trae ~107 campos)

{ "ok": true, "id_carpeta": "2026.04.98765", "carpeta": { "id_carpeta": "2026.04.98765", "operacion": 1, "tipo_operacion": "IMPORTACION A CONSUMO", "cliente": "FERRERO ARGENTINA S. A.", "id_cliente": 1167, "via": "Maritima", "mercaderia": "GRASA VEGETAL", "pais_origen": "ITALIA", "terminal": "TERMINAL 4 S.A.", "nombre_vapor": "SAN RAPHAEL MAERSK", "conocimiento_bl": "721330304", "kilos": 25704.00, "bultos": 34, "referencia": "5984MP", "anulada": 0, "tipo": "Importación", "estado": "Facturada", "responsable": "SUSANA BELEN ALFONSIN", "fecha_finalizacion_carga": "2026-03-20 14:05:00", "fecha_ingreso_deposito": null, "en_curso": false // ... resto de columnas de vw_carpetas }, "contenedores": [ { "id_envase": 2, "nro_contenedor": "MNBU4607758", "nro_precinto": "SL-123", "kilos": 11820.55, "bultos": 1 } ], "eventos": [ { "id_registro": 823198, "id_evento": 3, "fecha": "2026-03-14 10:30:00", "observacion": null } ] }
Si la carpeta no existe → 404 legajo_no_encontrado.

4.2 Listar legajos

GET/v1/legajosscope: datos:read

Parámetros de query

ParámetroTipoDescripción
aniointFiltra por año.
id_clienteint / listaFiltra por cliente. Acepta uno o varios separados por coma (84,1167): un usuario puede gestionar más de una empresa. Ausente = todos. Si viene pero no tiene ningún entero válido → 400 id_cliente_invalido.
operacionint1=Impo, 2=Expo.
en_cursoint1 = solo en curso (sin fecha_finalizacion_carga ni fecha_ingreso_deposito, igual que el filtro "En Curso" del escritorio); 0 = solo entregados; ausente = todas.
mercaderiaint / listaFiltra por mercadería: id_mercaderia del catálogo /v1/catalogos/mercaderias. Acepta uno o varios separados por coma (1181,875). Ausente = todas. Si viene pero no tiene ningún entero válido → 400 mercaderia_invalida.
tipo_mercaderiaint / listaLegajos cuyo tipo de mercadería sea alguno de estos. Se toma el tipo cargado en el legajo; si el legajo no lo tiene, el tipo principal de su mercadería. Mal escrito → 400 tipo_mercaderia_invalido.
grupoint / listaLo mismo, por grupo (1 a 4, ver tabla de grupos). grupo=1 = legajos de producto terminado.
referenciastringBúsqueda parcial sobre la referencia del cliente: referencia=MARCO encuentra "B11 430 6X2R MARCOPOLO". No distingue mayúsculas de minúsculas. Los caracteres % y _ se buscan tal cual, no como comodines.
fecha_desde / fecha_hastadateRango sobre fecha_oficializacion, en formato YYYY-MM-DD y con ambos extremos incluidos. Se puede mandar una sola de las dos, o las dos. Formato inválido → 400 fecha_invalida; fecha_desde posterior a fecha_hasta400 rango_invalido.
incluir_sin_oficializarint1 = suma al resultado los legajos todavía sin oficializar (fecha_oficializacion en null), que por definición no caen dentro de ningún rango. Default 0. Solo tiene efecto junto con fecha_desde/fecha_hasta.
limit / offsetintDefault 100, tope 500 / paginación (ver más abajo).
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/legajos?anio=2026&id_cliente=1167&limit=50" # Oficializados en julio, de dos clientes, buscando por referencia curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/legajos?id_cliente=84,1167&fecha_desde=2026-07-01&fecha_hasta=2026-07-31&referencia=MARCO"
Ojo con incluir_sin_oficializar: los legajos sin oficializar son muchos y de todos los años, no solo del período pedido. En la base de julio de 2026 el rango solo devuelve 232 legajos, y con este parámetro en 1 pasa a 14.395. Usarlo únicamente cuando se quiere "lo del período más todo lo que sigue pendiente"; para un listado por período, dejarlo apagado.
{ "ok": true, "count": 2, "limit": 50, "offset": 0, "total": 2, "has_more": false, "rows": [ { "id_carpeta": "2026.04.98765", "cliente": "FERRERO ARGENTINA S. A.", "via": "Maritima", "referencia": "5984MP", "fecha_apertura": "2026-02-27", "tipo": "Importación", "estado": "Facturada", "responsable": "SUSANA BELEN ALFONSIN", "fecha_llegada": "2026-03-13 00:00:00", "fecha_esperada_buque": "2026-03-12 00:00:00", "fecha_finalizacion_carga": "2026-03-20 14:05:00", "fecha_ingreso_deposito": null, "en_curso": false } ] }
Fecha de arribo: fecha_llegada es el arribo real; si viene null, usar fecha_esperada_buque como estimada (equivale a COALESCE(fecha_llegada, fecha_esperada_buque), el criterio del escritorio).

Paginado

La respuesta incluye los campos para paginar:

CampoTipoDescripción
countintCantidad de filas devueltas en esta página.
totalintTotal de legajos que matchean los filtros (todas las páginas). Total de páginas = ceil(total / limit).
has_morebooltrue si después de esta página quedan más registros (offset + count < total); false en la última página.

Para recorrer un listado completo: pedir con offset=0 y repetir sumando offset = offset + limit mientras has_more sea true. Para un paginador de UI: usar total para dibujar los números de página y pedir cada página con offset = (pagina - 1) * limit. El uso simple de siempre (un solo request con limit, sin offset) sigue funcionando igual.

4.3 Contenedores y eventos

Las listas anidadas del legajo, por separado (la carátula sale del compuesto 4.1). Ambas scope: datos:read.

MétodoEndpointDevuelve
GET/v1/legajos/{id_carpeta}/contenedoresLista de contenedores
GET/v1/legajos/{id_carpeta}/eventosEventos del legajo
{ "ok": true, "id_carpeta": "2026.04.98765", "rows": [ { "id_envase": 2, "nro_contenedor": "MNBU4607758", "kilos": 11820.55, "bultos": 1 } ] }

4.4 Catálogos

GET/v1/catalogos/{nombre}scope: datos:read

Valores válidos para resolver los IDs que piden las escrituras (mapean a las vw_* de FreeWayNet).

{nombre}Columnas
viasid_via, nombre
mercaderias FILTROSid_mercaderia, nombre, carga_peligrosa, id_tipo_principal, tipos[], grupos[] — acepta tipo_mercaderia, grupo e id_cliente (ver abajo)
tipos_mercaderiaid_tipo_mercaderia, nombre, id_grupo, grupo
unidades_negocioid_unidad_negocio, nombre
tipos_operacion_importaciones / ..._exportacionesid_tipo_operacion, nombre
paisesid_pais, nombre, codigo_maria
terminalesid_entidad, razon_social
transportistasid_transportista, razon_social
envasesid_envase, nombre
companias_aereasid_compania_aerea, nombre
agencias_maritimasid_entidad, razon_social
lineas_maritimasid_entidad, razon_social
monedasid_moneda, nombre, abreviatura, simbolo
clientesid_cliente, razon_social, nro_cuit (solo activos; para filtros y detalle ver 4.7)
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/catalogos/paises" // { "ok": true, "catalogo": "paises", "count": 240, "rows": [ { "id_pais": 80, "nombre": "URUGUAY", "codigo_maria": "225" } ] }

Mercaderías: tipos, grupos y filtros NUEVO

Cada mercadería tiene uno o más tipos (los de /v1/catalogos/tipos_mercaderia), y uno de ellos es el principal. Por ejemplo TAPAS es materia prima (principal) y también producto terminado. Cada tipo pertenece a su vez a un grupo, que es la agrupación que Portas usa para las notificaciones de arribo a depósito:

id_grupoGrupoTipos que lo forman (id_tipo_mercaderia)
1Producto terminado5 PRODUCTO TERMINADO
2Materia prima1 INSUMO · 7 MATERIA PRIMA · 8 EMBALAJES
3Sorpresas6 MUESTRAS · 9 SORPRESAS
4Repuestos2 REPUESTO
null(sin grupo)3 MAQUINARIA · 4 HELADERA

Parámetros de query

Los tres son opcionales y se combinan libremente (se exigen todos los que vengan). Sin ninguno devuelve el catálogo completo. Los nueve tipos, con el grupo de cada uno, se obtienen de GET /v1/catalogos/tipos_mercaderia.

ParámetroTipoDescripción
tipo_mercaderiaint / listaSi viene, deja solo las mercaderías que tengan al menos uno de estos tipos (7, o 1,7,8). Cuenta cualquiera de sus tipos, no solo el principal.
grupoint / listaAtajo por grupo: grupo=2 equivale a tipo_mercaderia=1,7,8. Valores 1 a 4; otro → 400 grupo_invalido. Si viene junto con tipo_mercaderia se exigen los dos.
id_clienteint / listaSolo las mercaderías del cliente: las que aparecen en algún legajo suyo, más las que se le asignaron al darlas de alta (por el POST de abajo o desde FreewayNet). Sin este filtro devuelve las ~4.000 del catálogo completo.

Un filtro mal escrito (grupo=abc) devuelve 400, nunca el catálogo entero. Una mercadería sin tipos cargados tiene tipos: [] y no sale en ningún filtro por tipo o grupo.

# Materia prima (en sentido amplio) que usa Ferrero curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/catalogos/mercaderias?grupo=2&id_cliente=1167" // { "ok": true, "catalogo": "mercaderias", "count": 31, "filtros": { "tipo_mercaderia": [], "grupo": [2], "id_cliente": [1167] }, // "rows": [ { // "id_mercaderia": 3616, "nombre": "ALUMINIO", "carga_peligrosa": false, "id_tipo_principal": 7, // "tipos": [ { "id_tipo_mercaderia": 7, "nombre": "MATERIA PRIMA", "principal": true, "id_grupo": 2, "grupo": "Materia prima" }, // { "id_tipo_mercaderia": 8, "nombre": "EMBALAJES", "principal": false, "id_grupo": 2, "grupo": "Materia prima" } ], // "grupos": [2] // } ] }

4.5 ETA y posición GPS de contenedores NUEVO

Seguimiento marítimo de los contenedores en tránsito: ETA, posición actual del buque (lat/lng), trayectoria del viaje y eventos por puerto (Load, Discharge, Gate out…). Los datos los alimenta FreeWayNet consultando la API de tracking (Searates) cada 8 horas y quedan en la tabla eta_contenedores; esta API los expone en lectura.

MétodoEndpointDevuelve
GET/v1/legajos/{id_carpeta}/etaTodos los contenedores en seguimiento del legajo
GET/v1/contenedores/{nro}/etaUn contenedor puntual + sus carpetas vinculadas
GET/v1/contenedores/etaTodos los contenedores en seguimiento, de todos los clientes (uso administrativo — ver más abajo)

Parámetros de query

ParámetroTipoDescripción
detalleint0 = respuesta liviana (sin locaciones ni trayectoria). Default: 1.
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/contenedores/MRKU6496680/eta"

Respuesta

{ "ok": true, "contenedor": { "contenedor": "MRKU6496680", "activo": true, "eta": "2026-08-19 22:00:00", "fecha_llegada": "2026-08-19 22:00:00", "fecha_actualizacion": "2026-07-02 00:15:22", "posicion": { "lat": -34.904, "lng": -33.422 }, "locaciones": [ { "nombre": "Hong Kong", "pais": "Hong Kong China", "codigo_pais": "HK", "locode": "HKHKG", "lat": 22.278, "lng": 114.175, "eventos": [ { "descripcion": "Load", "status": "CLL", "fecha": "2026-06-15 18:31:00", "actual": true, "buque": "MAERSK LIRQUEN", "viaje": "131W" } ] }, { "nombre": "Buenos Aires", "codigo_pais": "AR", /* ... */ } ], "trayectoria": [ [22.278, 114.175], [11.355, 109.019], /* ... puntos [lat,lng] del viaje ... */ [-34.613, -58.377] ], "carpetas": [ "2026.04.98765" ] } }
posicion y trayectoria pueden venir null / vacías si el proceso de tracking todavía no obtuvo datos para ese contenedor. trayectoria es una lista de pares [lat, lng] lista para dibujar como polilínea en un mapa (ver el ejemplo con mapa en el playground). Contenedor no seguido → 404 contenedor_no_encontrado.

Listado administrativo — todos los contenedores NUEVO

GET/v1/contenedores/etascope: admin:read

Devuelve todos los contenedores en seguimiento con el cliente y el interno de cada uno, sin filtrar por cliente. Pensado para pantallas internas de Portas (una torre de control de la flota marítima), no para clientes finales: por eso usa el scope admin:read y no datos:read.

Parámetros de query

ParámetroTipoDescripción
id_clienteint / listaDevuelve solo los contenedores de ese cliente. Acepta uno o varios separados por coma (84,1167). Ausente o vacío = todos los clientes. Sin ningún entero válido → 400 id_cliente_invalido.
detalleint1 agrega locaciones y trayectoria a cada contenedor. Default: 0 (liviano).
activosint0 incluye también los contenedores que ya no se siguen. Default: 1.
Ojo con detalle=1: acá la trayectoria se devuelve para todos los contenedores a la vez, así que la respuesta pasa de unas decenas de KB a varios MB. Conviene pedir el listado liviano para la grilla y traer la ruta del contenedor puntual con /v1/contenedores/{nro}/eta cuando el usuario lo selecciona.
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/contenedores/eta?id_cliente=1167"

Respuesta

{ "ok": true, "items": [ { "contenedor": "MNBU4163083", "activo": true, "eta": "2026-09-10 00:00:00", "fecha_llegada": "2026-09-10 07:00:00", "fecha_actualizacion": "2026-08-03 12:10:49", "posicion": { "lat": 30.607666, "lng": 122.0832 }, "id_cliente": 1167, "cliente": "FERRERO ARGENTINA S. A.", "id_carpeta": "2026.04.94994", "interno": 94994, "anio": 2026, "despachante": "04", "referencia": "7386ST", "vapor": "SAN LORENZO MAERSK", "bl": "BPH6904041880B", "terminal": "TERMINAL 4 S.A.", "fecha_salida": "2026-07-02", "fecha_esperada": "2026-08-27", "fecha_llegada": null, "legajos": [ { "id_carpeta": "2026.04.94994", "interno": 94994, "id_cliente": 1167, "cliente": "FERRERO ARGENTINA S. A." }, { "id_carpeta": "2026.04.94995", "interno": 94995, "id_cliente": 1167, "cliente": "FERRERO ARGENTINA S. A." } ] } ], "total": 58, "filtros": { "detalle": false, "id_cliente": null, "activos": true }, "resumen": { "contenedores": 58, "con_posicion": 52, "con_ruta": null, "ultima_actualizacion": "2026-08-03 15:49:55" } }
Un contenedor puede estar en más de un legajo: en ese caso aparece una sola vez en items y legajos[] lista todas sus carpetas (los campos id_carpeta / interno del nivel superior son los de la primera). resumen.con_ruta viene null cuando se pidió sin detalle=1, porque en ese modo la trayectoria no se arma y el conteo no aplica.

4.6 Cronograma de cargas NUEVO

GET/v1/cronogramascope: datos:read

Datos para armar el calendario semanal de cargas que el cliente veía en el escritorio viejo (/dashboard_cronograma). Devuelve una fila por legajo con actividad de carga en la ventana pedida, con las fechas crudas, los flags ya calculados (IMO, inspección, foto de precinto, atención) y las tarjetas resueltas con la misma lógica del sistema viejo, listas para ubicar en la grilla.

Parámetros de query

ParámetroTipoDescripción
id_clienteint / listaFiltra los legajos de un cliente (la vista que ese cliente veía en el escritorio viejo). Acepta uno o varios separados por coma (84,1167), para quien gestiona más de una empresa: sigue siendo la vista cliente, con las cargas de todas juntas. Sin este parámetro devuelve todos los clientes (vista interna). Sin ningún entero válido → 400 id_cliente_invalido.
diasintVentana hacia atrás en días (máx. 90). Default: 30 con id_cliente —sea uno o varios— (como la vista cliente del viejo), 10 sin él (vista interna / TV).
desdedateAlternativa a dias: fecha de corte YYYY-MM-DD. Pisa a dias.
Las coordinaciones futuras entran siempre (una carga coordinada sin inicio aparece aunque su fecha sea de la semana que viene — así se ven las cargas programadas). La ventana dias/desde solo corta hacia atrás. No se incluyen legajos anulados.
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/cronograma?id_cliente=1167&dias=30"

Respuesta

{ "ok": true, "generado": "2026-07-22T16:22:23-03:00", "desde": "2026-06-22 16:22:18", "total": 71, "items": [ { "id_carpeta": "2026.04.94588", "nro_orden": 94588, "tipo": "Importación", "via": "Maritima", "canal": { "id": 1, "nombre": "Verde", "color": "#16704e" }, "cliente": { "id": 1167, "razon_social": "FERRERO ARGENTINA S. A." }, "despacho": { "etiqueta": "Despacho", "numero": "26001IT14001912D" }, "destinacion": "IT14", "terminal": "TERMINAL 1, 2 Y 3", "mercaderia": "SORPRESAS", "estado": "Facturada", "responsables": { "carga1": "…", "carga2": "…", "carpeta": "…" }, "fechas": { "calendario": "2026-06-23 19:44:00", "aviso_carga": "2026-06-22 14:00:00", "coordinacion_carga": "2026-06-23 16:00:00", "ingreso_deposito": null, "verificacion": null, "control_documentacion": "2026-06-22 14:00:00", "inicio_carga": "2026-06-23 16:00:00", "finalizacion_carga": "2026-06-23 19:44:00", "oficializacion": "2026-06-18" }, "flags": { "carga_finalizada": true, // camión salió (flecha verde) "carga_en_curso": false, // inició antes de hoy y no finalizó (sirena) "atencion": false, // coordinada vencida sin inicio (triángulo) "verificado": false, // pulgar 2 "control_documental": true, // pulgar 1 "carga_peligrosa_imo": false, // ícono radiactivo "inspeccion_pendiente": null, // solo Ferrero canal rojo; null = no aplica "foto_precinto_requerida": false, "foto_precinto_pendiente": null // null = no aplica o no se pudo verificar }, "tarjetas": [ { "tipo": "carga", "fecha": "2026-06-23 19:44:00", "hora": "19:44" }, { "tipo": "control_documentacion", "fecha": "2026-06-22 14:00:00", "hora": "14:00" } ] } ] }

Cómo dibujar el calendario (mapa 1:1 con el escritorio viejo)

4.7 Clientes NUEVO

Consulta de clientes sobre la vista vw_clientes (solo campos identificatorios: razón social, CUIT, nro. importador/exportador, domicilio y localidad — sin datos comerciales ni contactos). Ambos scope: datos:read.

MétodoEndpointDevuelve
GET/v1/clientesListado paginado (default: solo activos)
GET/v1/clientes/{id_cliente}Detalle de un cliente (incluye inactivos)

Parámetros de query del listado

ParámetroTipoDescripción
qstringBúsqueda parcial (LIKE) sobre razon_social, nro_cuit y nro_imp_exp.
activoint | todosAusente → solo activos (default). 0 → solo inactivos. todos → sin filtro.
limit / offsetintPaginado. Default 100, máx. 500.
El listado filtra activos por default (lo que se necesita al crear legajos nuevos), pero el detalle por id no filtra: un legajo viejo puede referenciar un cliente hoy inactivo y GET /v1/clientes/{id} lo devuelve igual (con "activo": false). Para poblar un combo simple también está el catálogo liviano GET /v1/catalogos/clientes (ver 4.4).
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/clientes?q=ferrero" // { "ok": true, "count": 1, "limit": 100, "offset": 0, "rows": [ { "id_cliente": 1167, // "razon_social": "FERRERO ARGENTINA S. A.", "nro_cuit": "30-65831333-5", "nro_imp_exp": null, // "direccion": "JULIAN AGUERO 2830 Piso:1 T:3", "id_localidad": 166, "localidad": "MUNRO", // "codigo_postal": "1605", "web_carpeta": "general", "fecha_alta": null, "activo": true } ] }
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/clientes/1167" // { "ok": true, "cliente": { ...mismos campos... } } // 404 cliente_no_encontrado si el id no existe

5.1 Alta de legajo ★ sp_AltaLegajo_v2

POST/v1/legajosscope: datos:write

Crea un legajo nuevo. Devuelve el id_carpeta generado (AAAA.DD.NNNNN) en la misma respuesta.

CampoTipoRequisito
operacionint (1=Impo, 2=Expo)obligatorio
tipo_operacionstring(2)obligatorio
via, mercaderia, unidad_negocio, tipo_mercaderiaintobligatorio
responsableintopcional (default 152)
id_sistemaintopcional (trazabilidad)
idempotency_keystringopcional
Valores fijos (no se envían): id_cliente=1167, id_despachante='04', fecha_apertura=hoy.
curl -X POST \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: alta_2026_001" \ -d '{"operacion":1,"tipo_operacion":"01","via":3,"mercaderia":1,"unidad_negocio":1,"tipo_mercaderia":1,"id_sistema":1}' \ "https://api.portascloud.ar/v1/legajos" // 201 -> { "ok": true, "id_carpeta": "2026.04.93813", "resultado": 1, "ya_existia": false }

5.2 Actualización de legajo sp_ActualizarLegajo

PATCH/v1/legajos/{id_carpeta}scope: datos:write

Actualiza solo los campos enviados (los ausentes no se tocan). Reemplaza la llamada de 28 parámetros posicionales.

Campos admitidos (todos opcionales)

fecha_salida, fecha_esperada_buque, fecha_llegada, fecha_inicio_forzoso, fecha_fin_forzoso, id_envase, kilos, bultos, bultos_medidas, id_terminal, id_transportista, camion, conocimiento_crt, fecha_mercaderia_disponible_origen, registro_paquete, id_pais_origen_destino, nombre_vapor, conocimiento_bl, contenedores, id_compania_aerea, guia_madre, guia_hija, fecha_apertura, referencia, nro_factura, importe_factura, id_moneda_factura, fecha_factura, id_sistema, id_via, id_agencia_maritima, id_linea_maritima.

id_via: vía de transporte (id del catálogo vw_vias, el mismo que se envía en el alta). Antes solo se podía fijar al crear el legajo; ahora también se puede actualizar. Si el id no existe en el catálogo → 422 referencia_invalida.
id_agencia_maritima / id_linea_maritima: agencia y línea marítima del embarque (ids de los catálogos agencias_maritimas y lineas_maritimas; columna id_entidad). En la lectura del legajo vuelven como id_agencia_maritima + agencia_maritima (nombre) e id_linea_maritima + linea_maritima. Si el id no existe en el catálogo → 422 referencia_invalida.
contenedores: array de {id_envase, nro_contenedor, nro_precinto, kilos, bultos}. Omitido → no toca. [] → borra todos. Con items → reemplaza la lista completa.
# Parcial: solo 3 campos curl -X PATCH \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{"fecha_llegada":"2026-03-13","kilos":26000,"bultos":35}' \ "https://api.portascloud.ar/v1/legajos/2026.04.98765" // 200 -> { "ok": true, "id_carpeta": "2026.04.98765", "resultado": 1 }
Triggers: fecha_salida → evento 65; fecha_llegada → evento 48.

5.3 Alta de evento sp_AgregarEvento

POST/v1/legajos/{id_carpeta}/eventosscope: datos:write

Registra un evento y devuelve el id_registro (paso previo a subir un archivo de evento al disco G:).

CampoTipoRequisito
id_eventoint (3=Factura, 21=BL/CRT/AWB, 103=Packing List)obligatorio
fechadatetimeobligatorio
observacionstringopcional
curl -X POST -H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \ -d '{"id_evento":3,"fecha":"2026-03-14 10:30:00","observacion":"Factura del proveedor ABC"}' \ "https://api.portascloud.ar/v1/legajos/2026.04.98765/eventos" // 201 -> { "ok": true, "id_carpeta": "2026.04.98765", "id_registro": 823198 }

5.4 Subir archivo de un legajo ★ → disco G:

POST/v1/legajos/{id_carpeta}/archivosscope: uploadmultipart/form-data

Sube un archivo desde portas.com.ar. El servidor lo guarda en el storage de DonWeb, lo deja descargable de inmediato (registrado como archivo del legajo) y lo encola para que el agente de la red de Portas lo baje al disco G: en la ruta correcta.

Distinto de POST /v1/archivos (API de archivos): aquel publica lo que ya está en el G: (lo usa el proceso interno). Este es el camino inverso: lo que entra por portas.com.ar y tiene que terminar en el G:.

Campos

CampoTipoRequisitoDescripción
archivofileobligatorioEl binario (PDF por default).
id_evento + id_registrointdestino APara un archivo de evento. El id_registro se obtiene antes con POST .../eventos. Destino: Eventos/{id_evento}/{id_registro}. Van juntos (uno solo → 422).
sub_carpetastringdestino BPara un archivo de carpeta lógica (Gastos, Factura_Portas, …). Se usa si no viene el destino A.
hash_sha1string(40)opcionalSi difiere del archivo recibido → 422.
fecha_archivostringopcionalFecha lógica del documento.
idempotency_keystringopcionalEvita duplicación en reintentos.

Ejemplo — archivo de sub_carpeta

curl -X POST -H "Authorization: Bearer TU_API_KEY" \ -F "sub_carpeta=Gastos" \ -F "archivo=@/ruta/Factura_001.pdf" \ "https://api.portascloud.ar/v1/legajos/2026.04.98765/archivos"

Ejemplo — archivo de evento

# 1) registrar el evento -> id_registro curl -X POST -H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \ -d '{"id_evento":3,"fecha":"2026-03-14 10:30:00"}' \ "https://api.portascloud.ar/v1/legajos/2026.04.98765/eventos" # -> { "id_registro": 823198 } # 2) subir el archivo apuntando a ese evento curl -X POST -H "Authorization: Bearer TU_API_KEY" \ -F "id_evento=3" -F "id_registro=823198" \ -F "archivo=@/ruta/Factura.pdf" \ "https://api.portascloud.ar/v1/legajos/2026.04.98765/archivos"

Respuesta

{ "ok": true, "id_archivo": 288190, "nombre_archivo": "Factura.pdf", "sub_carpeta": "Eventos/3/823198", "path_relativo": "2026/04/98765/Eventos/3/823198/Factura.pdf", "hash_sha1": "e150e0e2…", "download_url": "https://api.portascloud.ar/v1/d/a01c471a…", "ya_existia": false, "estado": "OK", "estado_g": "pendiente" }
estado_g refleja la cola hacia el disco G:: pendientedescargandocolocado (el agente de la red lo bajó). El archivo ya es descargable por download_url aunque todavía esté pendiente de llegar al G:.

5.5 Alta de mercadería NUEVO

POST/v1/catalogos/mercaderiasscope: datos:write

Crea una mercadería en el catálogo de FreewayNet, con sus tipos, y la deja asignada al cliente para que GET /v1/catalogos/mercaderias?id_cliente=… la devuelva enseguida (antes había que esperar a que un legajo la usara).

CampoTipoRequisito
nombrestring(50)obligatorio — se guarda tal como viene
tipos_mercaderiaint[] (o "6,5")obligatorio — uno o más ids de /v1/catalogos/tipos_mercaderia
principalintopcional — uno de los enviados; default el primero
id_clienteintopcional — asigna la mercadería al cliente (recomendado: 1167)
carga_peligrosaboolopcional (default false)
idempotency_keystringopcional
Duplicados. El nombre se compara en mayúsculas y sin espacios ni puntos ("Kinder Joy." es lo mismo que "KINDERJOY"). Si ya existe, no se crea otra: responde 200 con ya_existia: true y la mercadería existente tal cual está, tipos incluidos (no se modifican). Si mandaron id_cliente, la asignación al cliente sí se registra.
curl -X POST \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{"nombre":"Nutella + F Coll","tipos_mercaderia":[5],"id_cliente":1167}' \ "https://api.portascloud.ar/v1/catalogos/mercaderias" // 201 -> { "ok": true, "ya_existia": false, "mensaje": "Mercaderia creada", // "mercaderia": { "id_mercaderia": 4043, "nombre": "Nutella + F Coll", "carga_peligrosa": false, "id_tipo_principal": 5, // "tipos": [ { "id_tipo_mercaderia": 5, "nombre": "PRODUCTO TERMINADO", "principal": true, "id_grupo": 1, "grupo": "Producto terminado" } ], // "grupos": [1] } } // 200 -> { "ok": true, "ya_existia": true, "mensaje": "Ya existia una mercaderia con ese nombre; ...", "mercaderia": { ... } } // 422 -> parametro_faltante (sin nombre o sin tipos) · parametro_invalido (principal no enviado en tipos) · referencia_invalida (tipo o cliente inexistente)

6. Usuarios NUEVO

Gestión de usuarios sobre vw_usuarios / sp_AltaUsuario / sp_ActualizarUsuario. Los usuarios que crea portas.com.ar gestionan el campo password (con su propio esquema de codificación, que la API no interpreta). El campo clave (de FreeWayNet) no se expone.

6.1 Listar — GET /v1/usuarios

scope: datos:read. Filtros: activo, id_perfil, id_sector, q, email, limit, offset. No incluye password (no se vuelcan hashes en masa).

Buscar por email: q busca con LIKE parcial sobre nombre, usuario y email a la vez (ej. ?q=j.perez@portas.com.ar ya encuentra por email). email filtra por igualdad exacta — útil para validar si existe un usuario con ese mail sin coincidencias parciales.
curl -H "Authorization: Bearer TU_API_KEY" \ "https://api.portascloud.ar/v1/usuarios?activo=1&q=gloria" // { "ok": true, "count": 1, "rows": [ { "id_usuario": 1001, "nombre": "Walter Gloria", "usuario": "w.gloria", "id_perfil": 3, "id_sector": 9, "email": "walter.gloria@portas.com.ar", "activo": true } ] }

6.2 Detalle — GET /v1/usuarios/{id}

scope: datos:read. Devuelve el usuario completo, incluido password. Si no existe → 404 usuario_no_encontrado.

6.3 Alta — POST /v1/usuarios

scope: datos:write. Único obligatorio: nombre. Campos: usuario, password, id_perfil, id_sector, email, activo y demográficos (direccion, telefonos, fecha_nacimiento, tipo_documento, nro_documento, estado_civil, nro_cuil, nro_legajo, nro_poder, fecha_ingreso, fecha_baja), más id_sistema.

curl -X POST -H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \ -d '{"nombre":"Juan Perez","usuario":"j.perez","password":"<hash>","id_perfil":3,"id_sector":9,"email":"j.perez@portas.com.ar","activo":1}' \ "https://api.portascloud.ar/v1/usuarios" // 201 -> { "ok": true, "id_usuario": 1012, "resultado": 1 }

6.4 Actualización — PATCH /v1/usuarios/{id}

scope: datos:write. Parcial (ausente = no tocar). Permite cambiar password. Si no existe → 404.

curl -X PATCH -H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \ -d '{"email":"nuevo@portas.com.ar","activo":1}' \ "https://api.portascloud.ar/v1/usuarios/1012" // 200 -> { "ok": true, "id_usuario": 1012, "resultado": 1 }

6.5 Login — POST /v1/usuarios/login

scope: datos:read. Valida credenciales server-side y devuelve los datos del usuario sin el hash.

El password se compara por igualdad exacta: enviá el valor ya codificado con el mismo esquema con que lo guardás (la API no hashea). Así no hace falta traerse el hash en cada login.
curl -X POST -H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \ -d '{"usuario":"j.perez","password":"<hash>"}' \ "https://api.portascloud.ar/v1/usuarios/login" // 200 -> { "ok": true, "usuario": { "id_usuario": 1012, "usuario": "j.perez", "nombre": "Juan Perez", "id_perfil": 3, "email": "...", "activo": true } } (sin password) // 401 -> { "ok": false, "error": "credenciales_invalidas" } | "usuario_inactivo"

7. Errores

Formato común: { "ok": false, "error": "codigo", "message": "...", "detalle": { "sp_resultado": -31 } }

Mapeo del resultado de los stored procedures

SPresultadoHTTPerror
Legajo (alta)1201
-30…-36422parametro_invalido
-50…-54500error_interno
Legajo (update)-1404legajo_no_encontrado
-2409legajo_anulado
-20…-25422referencia_invalida
-34…-36422dato_invalido
Evento-3422evento_invalido
-4422fecha_invalida
Usuario-10 (alta)422parametro_faltante
-1 (update)404usuario_no_encontrado

Transporte / auth

200 ok  ·  201 creado
400 bad_request · JSON malformado
401 missing_auth / invalid_key
403 missing_scope
404 not_found · recurso/endpoint
422 validación · ver tabla
502 db_unreachable · MySQL Portas sin respuesta
500 internal_error

8. Flujo completo

  1. Resolver IDs con catálogos: GET /v1/catalogos/vias, /paises, etc.
  2. Crear legajo: POST /v1/legajos → guardar el id_carpeta.
  3. Completar datos: PATCH /v1/legajos/{id_carpeta} (las veces que haga falta).
  4. Registrar eventos: POST /v1/legajos/{id_carpeta}/eventosid_registro.
  5. Subir archivos: POST /v1/legajos/{id_carpeta}/archivos (quedan descargables y bajan al disco G:). Para consultar lo ya publicado, la API de archivos.
  6. Mostrar al cliente: GET /v1/legajos/{id_carpeta} (datos) + GET /v1/operaciones/{id_carpeta}/paquetes (archivos).
  7. Usuarios: POST/PATCH /v1/usuarios para gestionar, POST /v1/usuarios/login para autenticar.