Resolución N° 5.616/2024. Más información

Crear factura electrónica de ARCA en Delphi

Con pocas líneas de código


Crear factura electrónica de ARCA en Delphi

Si es tu primera vez conectándote con la facturación electrónica de ARCA, te recomiendo leer esta guía básica sobre su funcionamiento general.

Conectar tu sistema con la facturación electrónica de ARCA

Conectar tu sistema con la facturación electrónica de ARCA

Guía paso a paso

El primer paso para crear una factura electrónica de ARCA en Delphi es obtener el Código de Autorización Electrónico o CAE.

Esto lo vamos a hacer utilizando Afip SDK que nos permite conectarnos a los web services de ARCA sin complicarnos con el uso de SOAP y la autenticación.

Obtener la autorización

Lo primero que tenemos hacer es obtener el “Token authorization”, que seria la autorización en ARCA.

Para usar los web services de ARCA, se requiere un certificado digital, con Afip SDK puedes integrarte en modo desarrollo usando el CUIT 20409378472 sin necesidad de obtener un certificado, para poder integrarte lo más rápido posible.

Te dejo este enlace por si luego quieres usar tu propio certificado para desarrollo.

Debemos ejecutar una solicitud POST al endpoint

https://app.afipsdk.com/api/v1/afip/auth

A continuación, se muestra el código Delphi equivalente:

uses
  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient, System.JSON;

procedure ObtenerAutorizacion;
var
  Client: THTTPClient;
  AuthURL: string;
  AuthData: TJSONObject;
  Response: IHTTPResponse;
  Token, Sign: string;
  AuthResponse: TJSONObject;
begin
  Client := THTTPClient.Create;
  try
    AuthURL := 'https://app.afipsdk.com/api/v1/afip/auth';
    AuthData := TJSONObject.Create;
    try
      AuthData.AddPair('environment', 'dev');
      AuthData.AddPair('tax_id', '20409378472'); // CUIT a utilizar
      AuthData.AddPair('wsid', 'wsfe');

      Response := Client.Post(AuthURL,
        TStringStream.Create(AuthData.ToString, TEncoding.UTF8), nil,
        [TNetHeader.Create('Content-Type', 'application/json')]);

      if Response.StatusCode <> 200 then
      begin
        WriteLn(Format('Error en la autorización: %d', [Response.StatusCode]));
        Exit;
      end;

      AuthResponse := TJSONObject.ParseJSONValue(Response.ContentAsString(TEncoding.UTF8)) as TJSONObject;
      try
        Token := AuthResponse.GetValue<string>('token');
        Sign := AuthResponse.GetValue<string>('sign');

        WriteLn('Token: ' + Token);
        WriteLn('Sign: ' + Sign);
      finally
        AuthResponse.Free;
      end;
    finally
      AuthData.Free;
    end;
  finally
    Client.Free;
  end;
end;

Ahora tenemos token y sign que nos dio ARCA para usar el web service. No es necesario que lo guardemos, Afip SDK se encarga de esto por nosotros, debemos solicitarlo antes de cada llamada a los métodos del web service.

Obtener el número del último comprobante

Antes de crear la factura, necesitamos saber cuál es el último número de comprobante autorizado. Esto es fundamental para evitar el error (10016) El número o fecha del comprobante no se corresponde con el próximo a autorizar.

Para esto, vamos a usar el método FECompUltimoAutorizado haciendo una solicitud POST al endpoint:

https://app.afipsdk.com/api/v1/afip/requests
uses
  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient, System.JSON;

function ObtenerProximoNumeroComprobante(const Token, Sign: string): Integer;
var
  Client: THTTPClient;
  RequestURL: string;
  RequestData, ParamsData, AuthData: TJSONObject;
  Response: IHTTPResponse;
  ResponseJson, ResultJson: TJSONObject;
  CbteNro: Integer;
begin
  Client := THTTPClient.Create;
  try
    RequestURL := 'https://app.afipsdk.com/api/v1/afip/requests';
    RequestData := TJSONObject.Create;
    try
      RequestData.AddPair('environment', 'dev');
      RequestData.AddPair('method', 'FECompUltimoAutorizado');
      RequestData.AddPair('wsid', 'wsfe');

      ParamsData := TJSONObject.Create;
      try
        AuthData := TJSONObject.Create;
        AuthData.AddPair('Token', Token);
        AuthData.AddPair('Sign', Sign);
        AuthData.AddPair('Cuit', '20409378472');
        ParamsData.AddPair('Auth', AuthData);
        ParamsData.AddPair('PtoVta', TJSONNumber.Create(1));
        ParamsData.AddPair('CbteTipo', TJSONNumber.Create(6));
        RequestData.AddPair('params', ParamsData);
      except
        ParamsData.Free;
        raise;
      end;

      Response := Client.Post(RequestURL,
        TStringStream.Create(RequestData.ToString, TEncoding.UTF8), nil,
        [TNetHeader.Create('Content-Type', 'application/json')]);

      if Response.StatusCode <> 200 then
      begin
        WriteLn(Format('Error al obtener el último comprobante: %d', [Response.StatusCode]));
        Exit(-1);
      end;

      ResponseJson := TJSONObject.ParseJSONValue(Response.ContentAsString(TEncoding.UTF8)) as TJSONObject;
      try
        // El número siguiente es el último autorizado + 1
        ResultJson := ResponseJson.GetValue<TJSONObject>('FECompUltimoAutorizadoResult');
        CbteNro := ResultJson.GetValue<Integer>('CbteNro') + 1;
        WriteLn('Próximo número de comprobante: ' + CbteNro.ToString);
        Result := CbteNro;
      finally
        ResponseJson.Free;
      end;
    finally
      RequestData.Free;
    end;
  finally
    Client.Free;
  end;
end;

Ahora, con el número correcto, podemos crear la factura en el siguiente paso.

Crear la factura

Vamos a crear una Factura B por un importe de $121. En nuestro código, añadimos la siguiente lógica:

Debemos ejecutar una solicitud POST al endpoint

https://app.afipsdk.com/api/v1/afip/requests

A continuación, se muestra el código Delphi equivalente, usando el número de comprobante obtenido en el paso anterior:

uses
  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient, System.JSON;

procedure CrearFactura(const Token, Sign: string; NumeroComprobante: Integer);
var
  Client: THTTPClient;
  InvoiceURL: string;
  InvoiceData, ParamsData, AuthData, FeCabReq, FeDetReq, FECAEDetRequest, FeCAEReq: TJSONObject;
  IvaArray: TJSONArray;
  IvaItem: TJSONObject;
  Response: IHTTPResponse;
  InvoiceResponse: TJSONObject;
begin
  Client := THTTPClient.Create;
  try
    InvoiceURL := 'https://app.afipsdk.com/api/v1/afip/requests';

    // Construir el objeto JSON para la factura
    InvoiceData := TJSONObject.Create;
    try
      InvoiceData.AddPair('environment', 'dev');
      InvoiceData.AddPair('method', 'FECAESolicitar');
      InvoiceData.AddPair('wsid', 'wsfe');

      // Construir el objeto Params
      ParamsData := TJSONObject.Create;
      try
        // Objeto Auth
        AuthData := TJSONObject.Create;
        AuthData.AddPair('Token', Token);
        AuthData.AddPair('Sign', Sign);
        AuthData.AddPair('Cuit', '20409378472'); // CUIT utilizado
        ParamsData.AddPair('Auth', AuthData);

        // Objeto FeCAEReq
        FeCAEReq := TJSONObject.Create;
        try
          // FeCabReq
          FeCabReq := TJSONObject.Create;
          FeCabReq.AddPair('CantReg', TJSONNumber.Create(1));
          FeCabReq.AddPair('PtoVta', TJSONNumber.Create(1));
          FeCabReq.AddPair('CbteTipo', TJSONNumber.Create(6));
          FeCAEReq.AddPair('FeCabReq', FeCabReq);

          // FeDetReq
          FeDetReq := TJSONObject.Create;
          try
            FECAEDetRequest := TJSONObject.Create;
            FECAEDetRequest.AddPair('Concepto', TJSONNumber.Create(1));
            FECAEDetRequest.AddPair('DocTipo', TJSONNumber.Create(99));  // 99: Consumidor final
            FECAEDetRequest.AddPair('DocNro', TJSONNumber.Create(0));
            FECAEDetRequest.AddPair('CbteDesde', TJSONNumber.Create(NumeroComprobante));
            FECAEDetRequest.AddPair('CbteHasta', TJSONNumber.Create(NumeroComprobante));
            FECAEDetRequest.AddPair('CbteFch', FormatDateTime('yyyymmdd', Now));
            FECAEDetRequest.AddPair('ImpTotal', TJSONNumber.Create(121));
            FECAEDetRequest.AddPair('ImpTotConc', TJSONNumber.Create(0));
            FECAEDetRequest.AddPair('ImpNeto', TJSONNumber.Create(100));
            FECAEDetRequest.AddPair('ImpOpEx', TJSONNumber.Create(0));
            FECAEDetRequest.AddPair('ImpIVA', TJSONNumber.Create(21));
            FECAEDetRequest.AddPair('ImpTrib', TJSONNumber.Create(0));
            FECAEDetRequest.AddPair('MonId', 'PES');
            FECAEDetRequest.AddPair('MonCotiz', TJSONNumber.Create(1));
            FECAEDetRequest.AddPair('CondicionIVAReceptorId', TJSONNumber.Create(5));

            // Array de IVA: un elemento con Id = 5, BaseImp = 100 e Importe = 21
            IvaArray := TJSONArray.Create;
            IvaItem := TJSONObject.Create;
            IvaItem.AddPair('Id', TJSONNumber.Create(5));
            IvaItem.AddPair('BaseImp', TJSONNumber.Create(100));
            IvaItem.AddPair('Importe', TJSONNumber.Create(21));
            IvaArray.AddElement(IvaItem);

            FECAEDetRequest.AddPair('Iva', IvaArray);
            FeDetReq.AddPair('FECAEDetRequest', FECAEDetRequest);
            FeCAEReq.AddPair('FeDetReq', FeDetReq);
          except
            FeDetReq.Free;
            raise;
          end;

          ParamsData.AddPair('FeCAEReq', FeCAEReq);
          InvoiceData.AddPair('params', ParamsData);

          // Enviar la solicitud POST para crear la factura
          Response := Client.Post(InvoiceURL,
            TStringStream.Create(InvoiceData.ToString, TEncoding.UTF8), nil,
            [TNetHeader.Create('Content-Type', 'application/json')]);

          if Response.StatusCode <> 200 then
          begin
            WriteLn(Format('Error al crear la factura: %d', [Response.StatusCode]));
            Exit;
          end;

          InvoiceResponse := TJSONObject.ParseJSONValue(Response.ContentAsString(TEncoding.UTF8)) as TJSONObject;
          try
            WriteLn('Invoice: ' + InvoiceResponse.ToString);
          finally
            InvoiceResponse.Free;
          end;
        except
          ParamsData.Free;
          raise;
        end;
      finally
        InvoiceData.Free;
      end;
    finally
      Client.Free;
    end;
  end;
end;

Por ejemplo, para crear la factura correctamente, el flujo sería:

var
  Token, Sign: string;
  NumeroComprobante: Integer;
begin
  ObtenerAutorizacion; // Debe asignar Token y Sign
  NumeroComprobante := ObtenerProximoNumeroComprobante(Token, Sign);
  if NumeroComprobante > 0 then
    CrearFactura(Token, Sign, NumeroComprobante);
end;

En invoice tenemos el CAE y vencimiento correspondientes a la factura que acabamos de crear.

{
...
  "CAE": "12345678987654",
  "CAEFchVto": "20240327"
...
}

Ejemplos de otros tipos de comprobantes

Problemas comunes

Si estás teniendo el error (10016) El número o fecha del comprobante no se corresponde con el próximo a autorizar te recomiendo leer este artículo.

Error (10016) El numero o fecha del comprobante no se corresponde con el proximo a autorizar

Error (10016) El numero o fecha del comprobante no se corresponde con el proximo a autorizar

Pasos para resolver este error

Si estás teniendo el error (10242) El campo Condición IVA receptor es obligatorio. Consultar método FEParamGetCondicionIvaReceptor te recomiendo leer este artículo.

Error (10242) El campo Condicion IVA receptor no es un valor valido/es obligatorio

Error (10242) El campo Condicion IVA receptor no es un valor valido/es obligatorio

Pasos para resolver este error

Con la autorización creada ya podemos proceder a crear el PDF para presentarle a nuestro cliente. Puedes usar como base esta factura o ticket de ejemplo.

Luego, lo único que nos queda es pasar a modo producción, para más información de cómo hacerlo pueden dirigirse a la documentación de la librería https://docs.afipsdk.com/


Ante cualquier duda o pregunta al respecto, pueden resolverla rápidamente dentro de la Comunidad Afip SDK. Además, puedes unirte para estar al tanto de las novedades y problemas técnicos al usar los servicios de ARCA.

Conéctate a ARCA hoy mismo

Certificados, código, tutoriales, soporte... todo lo que necesitas para usar los web services de ARCA en un solo lugar.