{
  "info": {
    "name": "Acqua ERP OData",
    "description": "API OData v4 de Acqua ERP.\n\nANTES DE EMPEZAR\n1. Pestana Variables de esta coleccion: completar serviceRoot y apiKey.\n   serviceRoot TIENE que terminar en barra, por ejemplo:\n   https://mi-host/synergy-server/jwt-odata-key/\n2. Correr Descubrimiento > Documento de servicio para ver que entidades\n   tiene habilitadas tu cliente, y poner una de ellas en la variable\n   entitySet. Todos los demas requests la usan.\n3. Los ejemplos de campos (code, name, city_code, Localidad) son de\n   demostracion: reemplazalos por los de tu entidad, que salen del\n   $metadata.\n\nAUTENTICACION\nLa coleccion esta configurada con API key en el header X-API-Key, que\nse hereda en todos los requests. Para usar JWT en su lugar: cambiar\nserviceRoot de /jwt-odata-key/ a /jwt-odata/ y usar la carpeta\nAutenticacion JWT, que deja el token listo para los requests que la\nsobreescriben.\n\nDocumentacion completa: api-docs/odata-api.html del servidor.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-API-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "serviceRoot",
      "value": "https://TU-HOST/synergy-server/jwt-odata-key/",
      "type": "string",
      "description": "Raiz del servicio. Tiene que terminar en barra."
    },
    {
      "key": "serviceRootJwt",
      "value": "https://TU-HOST/synergy-server/jwt-odata/",
      "type": "string",
      "description": "Raiz del canal JWT, solo para la carpeta 6. Tiene que terminar en barra."
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string",
      "description": "Credencial para el endpoint /jwt-odata-key/."
    },
    {
      "key": "entitySet",
      "value": "o_customers",
      "type": "string",
      "description": "Entity set a consultar. Sacalo del documento de servicio."
    },
    {
      "key": "key",
      "value": "'C0001'",
      "type": "string",
      "description": "Clave de una entidad, con comillas simples si es texto."
    },
    {
      "key": "user",
      "value": "",
      "type": "string",
      "description": "Solo para JWT."
    },
    {
      "key": "password",
      "value": "",
      "type": "string",
      "description": "Solo para JWT."
    },
    {
      "key": "idClient",
      "value": "",
      "type": "string",
      "description": "Cliente de integracion. Solo para JWT."
    },
    {
      "key": "token",
      "value": "",
      "type": "string",
      "description": "Lo completa solo el request Obtener token."
    }
  ],
  "item": [
    {
      "name": "1. Descubrimiento",
      "description": "Empezar por aca: dice que entidades tenes y como son.",
      "item": [
        {
          "name": "Documento de servicio",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}",
            "description": "Lista los entity sets habilitados para tu cliente, con su titulo legible. Es la fuente de verdad: lo que no aparece aca, no esta habilitado."
          }
        },
        {
          "name": "$metadata (XML)",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}$metadata",
            "description": "CSDL completo: campos con tipo y longitud, claves, y navigation properties con su ReferentialConstraint. Las etiquetas en espanol van en Core.Description."
          }
        },
        {
          "name": "$metadata (JSON)",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}$metadata?$format=json",
            "description": "El mismo CSDL en JSON, mas comodo de recorrer desde codigo."
          }
        }
      ]
    },
    {
      "name": "2. Lectura",
      "description": "Formas de direccionar datos.",
      "item": [
        {
          "name": "Coleccion (primera pagina)",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$top=5&$count=true",
            "description": "@odata.count trae el total, independiente del $top. Si hay mas filas, la respuesta incluye @odata.nextLink: se usa tal cual."
          }
        },
        {
          "name": "Entidad por clave",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}({{key}})",
            "description": "Clave simple. Los textos van entre comillas simples, los numeros sin comillas. 404 si no existe."
          }
        },
        {
          "name": "Entidad con clave compuesta",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}o_stock(warehouse='DEP01',sku='A-100')",
            "description": "Con mas de un campo en la clave hay que usar nombre=valor. Ajustar a una entidad real con clave compuesta."
          }
        },
        {
          "name": "Una propiedad",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}({{key}})/name",
            "description": "Devuelve {\"value\": ...}. Si el campo es null, responde 204 sin cuerpo."
          }
        },
        {
          "name": "Valor crudo de una propiedad",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}({{key}})/name/$value",
            "description": "Devuelve el valor como text/plain, sin JSON alrededor."
          }
        },
        {
          "name": "$count de la coleccion",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}/$count",
            "description": "Cantidad de filas como text/plain. Respeta $filter. OJO: no funciona con clave, /Set(k)/$count devuelve 400."
          }
        },
        {
          "name": "Seleccion de campos y orden",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$select=code,name&$orderby=name desc&$top=20",
            "description": "Las claves se devuelven siempre, aunque no se pidan en $select."
          }
        }
      ]
    },
    {
      "name": "3. Filtros",
      "description": "$filter, $search y los casos que suelen confundir.",
      "item": [
        {
          "name": "contains",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$filter=contains(name,'molinos')&$top=20",
            "description": "contains escapa los comodines: contains(x,'50%') busca el texto 50% literal."
          }
        },
        {
          "name": "Operadores combinados",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$filter=contains(name,'sa') and active eq true&$top=20",
            "description": "Precedencia de menor a mayor: or, and, not, comparacion, aritmetica."
          }
        },
        {
          "name": "in",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$filter=code in ('C0001','C0002','C0003')",
            "description": "Equivale a una cadena de or."
          }
        },
        {
          "name": "Campo nulo",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$filter=city_code eq null&$top=20",
            "description": "null solo se compara con eq y ne; con gt/ge/lt/le devuelve 400."
          }
        },
        {
          "name": "Fecha",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$filter=created ge 2024-01-01T00:00:00Z&$top=20",
            "description": "Las fechas van en ISO 8601 y SIN comillas. year(created) eq 2024 tambien sirve."
          }
        },
        {
          "name": "Filtrar por el maestro relacionado",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$filter=Localidad/name eq 'Rosario'&$top=20",
            "description": "Se puede filtrar por un campo del maestro apuntado sin expandirlo. Solo funciona en relaciones de muchos a uno."
          }
        },
        {
          "name": "$search",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$search=molinos&$top=20",
            "description": "Texto libre sobre las columnas de texto del listado. Acepta frases entre comillas dobles y AND/OR/NOT. Devuelve 501 si la entidad no lo soporta."
          }
        }
      ]
    },
    {
      "name": "4. Maestros relacionados",
      "description": "$expand y navegacion por URL: el motivo principal de la API.",
      "item": [
        {
          "name": "Expandir el maestro apuntado",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$expand=Localidad&$top=5",
            "description": "Trae el maestro relacionado completo. Si la FK es null, la propiedad vale null."
          }
        },
        {
          "name": "Expandir solo un campo",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$expand=Localidad($select=name)&$top=5",
            "description": "En una relacion de muchos a uno solo se aceptan $select y $expand anidados."
          }
        },
        {
          "name": "Expandir todas las navegaciones",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}?$expand=*&$top=5",
            "description": "Un nivel, sin opciones anidadas."
          }
        },
        {
          "name": "Expandir una coleccion con opciones",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}o_cities?$expand=Clientes($select=code,name;$orderby=name;$top=5;$count=true)&$top=3",
            "description": "Las opciones anidadas se separan con punto y coma, no con &. Ajustar los nombres a una relacion real de uno a muchos."
          }
        },
        {
          "name": "Navegar por URL",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}({{key}})/Localidad",
            "description": "204 sin cuerpo si la FK esta en null; 404 si apunta a una fila inexistente."
          }
        },
        {
          "name": "Coleccion navegada",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}o_cities('ROS')/Clientes?$top=10&$count=true",
            "description": "Despues del segmento de navegacion valen las mismas query options que en una coleccion normal."
          }
        },
        {
          "name": "Solo las referencias",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRoot}}o_cities('ROS')/Clientes/$ref",
            "description": "Devuelve los @odata.id sin los datos."
          }
        }
      ]
    },
    {
      "name": "5. Escritura",
      "description": "Las escrituras pasan por la transaccion real del ERP: corren las reglas de negocio del maestro, asi que un 400 puede traer mensajes de negocio en details.",
      "item": [
        {
          "name": "Crear",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{serviceRoot}}{{entitySet}}",
            "description": "201 con la entidad creada y el header Location. Con 'Prefer: return=minimal' devuelve 204 y la URL en OData-EntityId.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"C0999\",\n  \"name\": \"Nuevo Cliente SA\",\n  \"city_code\": \"ROS\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Modificar",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{serviceRoot}}{{entitySet}}({{key}})",
            "description": "Asigna solo las propiedades presentes en el cuerpo. 200 con la entidad releida de la base.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Nombre corregido SA\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Modificar (PUT)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{serviceRoot}}{{entitySet}}({{key}})",
            "description": "ATENCION: en este servicio PUT se comporta igual que PATCH, no resetea los campos omitidos. Conviene usar PATCH y ser explicito.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Nombre corregido SA\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Eliminar",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": "{{serviceRoot}}{{entitySet}}({{key}})",
            "description": "204 sin cuerpo. Puede fallar con 400 si una regla del ERP lo impide."
          }
        }
      ]
    },
    {
      "name": "6. Autenticacion JWT",
      "description": "Solo si usas el endpoint /jwt-odata/ en lugar de /jwt-odata-key/. Usa la variable serviceRootJwt, que es independiente de serviceRoot: el login no existe en el canal de API key.",
      "item": [
        {
          "name": "Obtener token",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{serviceRootJwt}}?type=token",
            "description": "Hace login y guarda el token en la variable de coleccion. Vence a las 12 horas. Si no se puede resolver el id_client, devuelve 400 explicando que falta en lugar de un token inservible.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"user\": \"{{user}}\",\n  \"password\": \"{{password}}\",\n  \"id_client\": \"{{idClient}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Guarda el token en la variable de coleccion: el resto de los",
                  "// requests de la carpeta JWT lo usan automaticamente.",
                  "const body = pm.response.json();",
                  "if (body.token) {",
                  "    pm.collectionVariables.set(\"token\", body.token);",
                  "    pm.collectionVariables.set(\"idClient\", body.id_client);",
                  "}",
                  "pm.test(\"devuelve un token\", function () {",
                  "    pm.expect(body.token).to.be.a(\"string\");",
                  "});",
                  "pm.test(\"devuelve el id_client\", function () {",
                  "    pm.expect(body.id_client).to.be.a(\"string\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Documento de servicio (Bearer)",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{serviceRootJwt}}",
            "description": "Mismo recurso que en Descubrimiento, pero autenticando con el token.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{token}}",
                  "type": "string"
                }
              ]
            }
          }
        }
      ]
    }
  ]
}
