Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Envío de registros y sus respuestas (https://www.clubdelphi.com/foros/forumdisplay.php?f=66)
-   -   Demo con código Verifactu.dll para (delphi 7) (https://www.clubdelphi.com/foros/showthread.php?t=97004)

seccion_31 05-03-2025 09:15:19

Cita:

Empezado por Quim Herrera (Mensaje 562367)
No sé si ya lo habéis detectado, creo que hay un error en uVerifactuFuncs.pas en TEventosWebNode.WebNodeBeforePost al validar un NIF con P12 o PFX en la asignación del nombre del archivo y password:

Código Delphi [-]
 if sesion=-1 then // viene de un validador de nif
  begin
      pass:=nifValidator;
      cert:=nifValidatorPass;
  end
  else     // viene de un objeto verifactu

deberia ser:

Código Delphi [-]
  if sesion=-1 then // viene de un validador de nif
  begin
      cert:=nifValidator;
      pass:=nifValidatorPass;
  end
  else     // viene de un objeto verifactu
Saludos

gracias, añado la correccion.

seccion_31 05-03-2025 09:22:14

Cita:

Empezado por DarkDudae (Mensaje 562419)
No, no se interrumpe.

He probado a hacer un envío con 4 facturas. La segunda de ellas con el NIF incorrecto. Las otras 3 se envían sin problemas.

aparentemente la huella, seria la misma para la siguiente ¿no? entiendo que no habria problema de encadenamiento, al enviar la que falta, subsanado el error.


los datos de huella son ciertos, ocurra el fallo que ocurra:

Código:

  CadenaTemporalHuella:= 'IDEmisorFactura='+        alta.IDFactura.IDEmisorFactura+'&'+
                          'NumSerieFactura='+        alta.IDFactura.NumSerieFactura+'&'+
                          'FechaExpedicionFactura='+  alta.IDFactura.FechaExpedicionFactura+'&'+
                          'TipoFactura='+            TipoFacturaVerifactuToString( alta.TipoFactura )+'&'+
                          'CuotaTotal='+              alta.CuotaTotal+'&'+
                          'ImporteTotal='+            alta.ImporteTotal+'&'+
                          'Huella='+                  huellaAnterior+'&'+
                          'FechaHoraHusoGenRegistro='+alta.FechaHoraHusoGenRegistro.NativeToXS;

Saludos !


La carga del certificado PFX o P12 no tengo ni idea, hay algun usuario que le funcionó (podeis revisar el hilo). Lamentablemente mas alla de eso no tengo tiempo para comprobar, pero mirare un poco por google. El codigo es copy-paste de uno que supuestamente funcionaba.

( ¿has aplicado la correccion de Quim Herrera ? )

Saludos !

seccion_31 05-03-2025 09:50:00

El usuario garada tiene un codigo ligeramente distinto en:

https://www.clubdelphi.com/foros/showthread.php?t=95356

dale una mirada al hilo.

Este es el codigo, señalo en rojo, lo que puede ser distinto. la carga del pfx la deja libre, sin determinar.

Código:

procedure HTTPRIOHTTPWebNode1BeforePost(const HTTPReqResp: THTTPReqResp; Data: Pointer);

  procedure CheckError(Puntero: Pointer);
  begin
    if not Assigned(Puntero) then
      RaiseLastOSError;
  end;

const
  INTERNET_OPTION_CLIENT_CERT_CONTEXT = 84;
  Pass = 'LaContraseña';
var
  pStore: HCERTSTORE;
  pCert: PCERT_CONTEXT;
  DataBlob: CRYPT_DATA_BLOB;
  PFX: AnsiString;
begin
  pStore := nil;
  pCert := nil;

  PFX := FuncionQueLeaElFicheroPFX;

  try
    DataBlob.cbData := Length(PFX);
    DataBlob.pbData := @PFX[1];

    // Almacen temporal con el contenido del PFX
    pStore := PFXImportCertStore(DataBlob, PWideChar(Pass), {PKCS12_NO_PERSIST_KEY + }PKCS12_INCLUDE_EXTENDED_PROPERTIES);
    CheckError(pStore);

    // Buscar un certificado con clave privada
    // Solo debería haber uno
    pCert := CertFindCertificateInStore(pStore,
                                        X509_ASN_ENCODING,
                                        0,
                                        CERT_FIND_HAS_PRIVATE_KEY, //CERT_FIND_ANY,
                                        nil,
                                        nil);
    CheckError(pCert);

    // Pasarlo al servicio
    InternetSetOption(Data, INTERNET_OPTION_CLIENT_CERT_CONTEXT, pCert, SizeOf(CERT_CONTEXT));
  finally
    if Assigned(pCert) then
      CertFreeCertificateContext(pCert);

    if Assigned(pStore) then
      CertCloseStore(pStore, 0);
  end;
end;


elguille 05-03-2025 10:16:03

Cita:

Empezado por ramherfer (Mensaje 562447)
Hola termino de hacer una prueba validando NIF del cliente, por hacer una prueba rápida y sencilla con PFX y me pide que seleccione un certificado del almacen. Esto con el nombre de certificado cierto que no ocurre y funciona bien. Habiendo realizado una prueba con validación, creo que tampoco funcionará con los envíos. No lo he probado en los envíos por si se me lía un trifostio. Luego si acaso probaré con una factura y amplio la información lo tengo fácil para cambiar el uso de certificados.

Probado, pide el certificado tanto al comprobar el nif como al facturar del almacen de certificados.

Creo que pide el certificado por el error que señalo, al no poder cargarse este, te enseña los del almacen.

Saludos

elguille 05-03-2025 10:34:17

Cita:

Empezado por seccion_31 (Mensaje 562467)
El usuario garada tiene un codigo ligeramente distinto en:

https://www.clubdelphi.com/foros/showthread.php?t=95356

dale una mirada al hilo.

Este es el codigo, señalo en rojo, lo que puede ser distinto. la carga del pfx la deja libre, sin determinar.

Código:

procedure HTTPRIOHTTPWebNode1BeforePost(const HTTPReqResp: THTTPReqResp; Data: Pointer);

  procedure CheckError(Puntero: Pointer);
  begin
    if not Assigned(Puntero) then
      RaiseLastOSError;
  end;

const
  INTERNET_OPTION_CLIENT_CERT_CONTEXT = 84;
  Pass = 'LaContraseña';
var
  pStore: HCERTSTORE;
  pCert: PCERT_CONTEXT;
  DataBlob: CRYPT_DATA_BLOB;
  PFX: AnsiString;
begin
  pStore := nil;
  pCert := nil;

  PFX := FuncionQueLeaElFicheroPFX;

  try
    DataBlob.cbData := Length(PFX);
    DataBlob.pbData := @PFX[1];

    // Almacen temporal con el contenido del PFX
    pStore := PFXImportCertStore(DataBlob, PWideChar(Pass), {PKCS12_NO_PERSIST_KEY + }PKCS12_INCLUDE_EXTENDED_PROPERTIES);
    CheckError(pStore);

    // Buscar un certificado con clave privada
    // Solo debería haber uno
    pCert := CertFindCertificateInStore(pStore,
                                        X509_ASN_ENCODING,
                                        0,
                                        CERT_FIND_HAS_PRIVATE_KEY, //CERT_FIND_ANY,
                                        nil,
                                        nil);
    CheckError(pCert);

    // Pasarlo al servicio
    InternetSetOption(Data, INTERNET_OPTION_CLIENT_CERT_CONTEXT, pCert, SizeOf(CERT_CONTEXT));
  finally
    if Assigned(pCert) then
      CertFreeCertificateContext(pCert);

    if Assigned(pStore) then
      CertCloseStore(pStore, 0);
  end;
end;


Ese codigo en delphi 10.3 me da error aqui
Código Delphi [-]
    pStore := PFXImportCertStore(DataBlob, PWideChar(widestring(pass)), PKCS12_INCLUDE_EXTENDED_PROPERTIES);
debido a que cambia
Código Delphi [-]
DataBlob.pbData := @PFX[1];
si lo dejo en
Código Delphi [-]
DataBlob.pbData := @PFX[0];
va bien pero en la linea internetsetoption recibo el mismo error que al principio, "codigo error 6"

DarkDudae 05-03-2025 15:45:33

Cita:

Empezado por seccion_31 (Mensaje 562463)
aparentemente la huella, seria la misma para la siguiente ¿no? entiendo que no habria problema de encadenamiento, al enviar la que falta, subsanado el error.

Cada registro de facturación tiene que tener su huella y su encadenamiento, independientemente de que la AEAT lo haya rechazado por, por ejemplo, un NIF incorrecto.

seccion_31 06-03-2025 12:02:27

Cita:

Empezado por elguille (Mensaje 562471)
Ese codigo en delphi 10.3 me da error aqui
Código Delphi [-]
    pStore := PFXImportCertStore(DataBlob, PWideChar(widestring(pass)), PKCS12_INCLUDE_EXTENDED_PROPERTIES);
debido a que cambia
Código Delphi [-]
DataBlob.pbData := @PFX[1];
si lo dejo en
Código Delphi [-]
DataBlob.pbData := @PFX[0];
va bien pero en la linea internetsetoption recibo el mismo error que al principio, "codigo error 6"

He revisado el codigo y hay un monton de errores ahi dentro

el codigo que carga el certificado esta correcto

pero no se transmite el password a la DLL
y en la DLL el array de sesiones se comprueba al llegar a la zona de carga del certificado de forma erronea.

creo que corrigiendo esto:

en el componente:

Código:

function TVerifactu.validNIF(nif, nombre:string; var resultado, nombreRes:string):boolean;
var
    valida_nif: TValidadorNIF;
    j:integer;
begin
      checkAutoRun;

      result:=false;

      valida_nif.nombreCertificado:=Certificado;
      valida_nif.passwordCerficado:=passwordCertificado;
      valida_nif.nifvalidar      :=nif;
      valida_nif.nombre          :=nombre;
      valida_nif.resultado        :='';
      valida_Nif.error            :=true;

      validarNIF( valida_nif );

      if valida_Nif.error then
      begin
              resultado:='';
              nombreRes:='';
              result:=false;
              exit;
      end;

      resultado:=valida_Nif.resultado;
      nombreRes:=valida_Nif.resultadoN;
      result:=true;
end;


y esto en la DLL:

Código:

procedure TEventosWebNode.WebNodeBeforePost(const HTTPReqResp: THTTPReqResp;
  Data: Pointer);
var
  pStore: HCERTSTORE;
  pCert: PCERT_CONTEXT;
  DataBlob: CRYPT_BIT_BLOB;
  PFX: TBytes;
  pass,cert:string;
begin
  if sesion=-1 then // viene de un validador de nif
  begin
      pass:=nifValidatorPass;
      cert:=nifValidator;
  end
  else    // viene de un objeto verifactu
  begin
      pass:=sesiones[sesion].inicio.passwordCertificado;
      cert:=sesiones[sesion].inicio.nombreCertificado;
  end;


  if (pos('.PFX',uppercase(cert))=0) and
    (pos('.P12',uppercase(cert))=0) then exit;


  cargaCertificado(data, cert, pass);
end;


ya deberia funcionar, a mi me funciona.

A principios de la semana que viene publicare una nueva version corregida.

Pero ya esta solucionado.

slds

ramherfer 06-03-2025 19:58:18

Buenas tardes.
Si se le factura a un cliente con recargo de equivalencia no se si está asignando la clave de régimen 01 (Operación de Régimen General).
Creo que en este caso debería de asignar la clave de regimen 18 (Recargo de equivalencia).

Por favor corregirme si me equivoco.

seccion_31 07-03-2025 10:09:51

Cita:

Empezado por ramherfer (Mensaje 562513)
Buenas tardes.
Si se le factura a un cliente con recargo de equivalencia no se si está asignando la clave de régimen 01 (Operación de Régimen General).
Creo que en este caso debería de asignar la clave de regimen 18 (Recargo de equivalencia).

Por favor corregirme si me equivoco.

Que yo sepa debe quedarse como 01.

En uveriFactuFuncs.

La variable Condatos en la consulta se quedaba siempre a false, al menos en la version que yo tengo.
Si os ocurre esto, cambiar esta funcion asi:



Código:

function consultar(sesion:integer; var resultado:TConsultaResultado):boolean;
var
  consulta          : ConsultaFactuSistemaFacturacion;                  // Objeto a enviar
  res              : RespuestaConsultaFactuSistemaFacturacionType;    // Respuesta tras el envio

  HTTPRIO1          : THTTPRIO;

  j                : integer;

  direccion_envio,  estado  : string;

  eventos      : TEventosHttpPrio;
  eventosWebNode: TEventosWebNode;
begin
      consulta:=ConsultaFactuSistemaFacturacion.Create;

      consulta.Cabecera:=CabeceraConsultaSf.Create;
      consulta.Cabecera.ObligadoEmision            :=ObligadoEmisionConsultaType.Create;
      consulta.Cabecera.ObligadoEmision.NombreRazon :=sesiones[sesion].inicio.emisor.NombreRazonEmisor;
      consulta.Cabecera.ObligadoEmision.NIF        :=sesiones[sesion].inicio.emisor.nifEmisor;

      consulta.FiltroConsulta:=LRFiltroRegFacturacionType.Create;
      consulta.FiltroConsulta.PeriodoImputacion          :=PeriodoImputacionType.Create;
      consulta.FiltroConsulta.PeriodoImputacion.Ejercicio :=resultado.Cabecera.ejercicio;
      consulta.FiltroConsulta.PeriodoImputacion.Periodo  :=veriFactuPeriodo( resultado.Cabecera.periodo );

      if resultado.Cabecera.NumSerieFactura='' then
      begin
          consulta.FiltroConsulta.FechaExpedicionFactura:=FechaExpedicionConsultaType.Create;
          if resultado.Cabecera.dfecha=resultado.Cabecera.hfecha then
          begin
            consulta.FiltroConsulta.FechaExpedicionFactura.FechaExpedicionFactura:=verifactuFecha(datetostr(resultado.Cabecera.dfecha));
          end else
          begin
            consulta.FiltroConsulta.FechaExpedicionFactura.RangoFechaExpedicion:=RangoFechaExpedicionType.Create;
            consulta.FiltroConsulta.FechaExpedicionFactura.RangoFechaExpedicion.Desde:=verifactuFecha(datetoStr(resultado.Cabecera.dfecha));
            consulta.FiltroConsulta.FechaExpedicionFactura.RangoFechaExpedicion.Hasta:=verifactuFecha(datetoStr(resultado.Cabecera.hfecha));
          end;
      end
      else
      begin
          consulta.FiltroConsulta.NumSerieFactura:=resultado.Cabecera.NumSerieFactura;
      end;

      If resultado.Cabecera.filtroSIF then
      Begin
            //Vamos a establecer un filtro por sistema informático (opcional)
            consulta.FiltroConsulta.SistemaInformatico:=SistemaInformaticoType.Create;
            consulta.FiltroConsulta.SistemaInformatico.NombreRazon                := sesiones[sesion].inicio.sistemaInformatico.razonSocial;
            consulta.FiltroConsulta.SistemaInformatico.NIF                        := sesiones[sesion].inicio.sistemaInformatico.nif;
            consulta.FiltroConsulta.SistemaInformatico.NombreSistemaInformatico    := sesiones[sesion].inicio.sistemaInformatico.nombre;
            consulta.FiltroConsulta.SistemaInformatico.IdSistemaInformatico        := sesiones[sesion].inicio.sistemaInformatico.ID;
            consulta.FiltroConsulta.SistemaInformatico.Version                    := sesiones[sesion].inicio.sistemaInformatico.Version;
            consulta.FiltroConsulta.SistemaInformatico.NumeroInstalacion          := sesiones[sesion].inicio.sistemaInformatico.NumeroInstalacion;
            consulta.FiltroConsulta.SistemaInformatico.TipoUsoPosibleSoloVerifactu := SiNoType.S;

            if sesiones[sesion].inicio.sistemaInformatico.multiplesOT then
                consulta.FiltroConsulta.SistemaInformatico.TipoUsoPosibleMultiOT      := SiNoType.S
            else
                consulta.FiltroConsulta.SistemaInformatico.TipoUsoPosibleMultiOT      := SiNoType.N;
      End;

      eventos:=TEventosHttpPrio.Create;

      eventos.fileNameBefore  :=sesiones[sesion].inicio.saveXMLenvio;
      eventos.fileNameAfter  :=sesiones[sesion].inicio.saveXMLResultado;

      eventosWebNode:=TEventosWebNode.Create;
      eventosWebNode.sesion:=sesion;

      HTTPRIO1:=THTTPRIO.create(nil);
      HTTPRIO1.onBeforeExecute:=eventos.BeforeExecute;
      HTTPRIO1.onAfterExecute :=eventos.AfterExecute;

      // Cargar certificado del disco
      if ( pos('.PFX',uppercase(sesiones[sesion].inicio.nombreCertificado))<>0) or
        ( pos('.P12',uppercase(sesiones[sesion].inicio.nombreCertificado))<>0) then
          HTTPRIO1.HTTPWebNode.onBeforePost:=eventosWebNode.WebNodeBeforePost;

      // --> envío pre-produccion:  'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP';
      try
          // Cargar certificado del almacen
          if ( pos('.PFX',uppercase(sesiones[sesion].inicio.nombreCertificado))=0) and
            ( pos('.P12',uppercase(sesiones[sesion].inicio.nombreCertificado))=0) then
            HTTPRIO1.HTTPWebNode.ClientCertificate.SerialNum := Buscar_Certificado_SERIAL( sesiones[sesion].inicio.nombreCertificado );  // coloca el certificado

          direccion_envio :=sesiones[sesion].inicio.direccion_envio;

          res:=  RespuestaConsultaFactuSistemaFacturacionType.Create;
          res:=  GetsfPortTypeVerifactu( false, direccion_envio , HTTPRIO1 ).ConsultaFactuSistemaFacturacion( consulta ); // Enviarlo !

          // limpiar resultado antes de procesarlo
          clear_consulta(resultado);

          // resultado del envio:
          resultado.ConDatos:=(res.ResultadoConsulta=ResultadoConsultaType.ConDatos);
          resultado.error  :=false;
          resultado.errorDes:='';



          procesarConsulta(res,resultado );
      except
      on E:Exception do
      begin
          resultado.ConDatos:=false;
          resultado.error:=true;
          resultado.errorDes:=Format('(%s)-%s',[E.ClassName, E.Message]);
          result:=false;
          exit;
      end;

            // Finalmente:
            // HTTPRIO1.free;  //<- no puede ser destruido con NIL como constructor
            //eventos.Free;
      end;
      result:=true;
end;


ramherfer 07-03-2025 12:03:14

Cita:

Empezado por seccion_31 (Mensaje 562524)
Que yo sepa debe quedarse como 01.

En uveriFactuFuncs.

La variable Condatos en la consulta se quedaba siempre a false, al menos en la version que yo tengo.
Si os ocurre esto, cambiar esta funcion asi:



Código:

function consultar(sesion:integer; var resultado:TConsultaResultado):boolean;
var
  consulta          : ConsultaFactuSistemaFacturacion;                  // Objeto a enviar
  res              : RespuestaConsultaFactuSistemaFacturacionType;    // Respuesta tras el envio

  HTTPRIO1          : THTTPRIO;

  j                : integer;

  direccion_envio,  estado  : string;

  eventos      : TEventosHttpPrio;
  eventosWebNode: TEventosWebNode;
begin
      consulta:=ConsultaFactuSistemaFacturacion.Create;

      consulta.Cabecera:=CabeceraConsultaSf.Create;
      consulta.Cabecera.ObligadoEmision            :=ObligadoEmisionConsultaType.Create;
      consulta.Cabecera.ObligadoEmision.NombreRazon :=sesiones[sesion].inicio.emisor.NombreRazonEmisor;
      consulta.Cabecera.ObligadoEmision.NIF        :=sesiones[sesion].inicio.emisor.nifEmisor;

      consulta.FiltroConsulta:=LRFiltroRegFacturacionType.Create;
      consulta.FiltroConsulta.PeriodoImputacion          :=PeriodoImputacionType.Create;
      consulta.FiltroConsulta.PeriodoImputacion.Ejercicio :=resultado.Cabecera.ejercicio;
      consulta.FiltroConsulta.PeriodoImputacion.Periodo  :=veriFactuPeriodo( resultado.Cabecera.periodo );

      if resultado.Cabecera.NumSerieFactura='' then
      begin
          consulta.FiltroConsulta.FechaExpedicionFactura:=FechaExpedicionConsultaType.Create;
          if resultado.Cabecera.dfecha=resultado.Cabecera.hfecha then
          begin
            consulta.FiltroConsulta.FechaExpedicionFactura.FechaExpedicionFactura:=verifactuFecha(datetostr(resultado.Cabecera.dfecha));
          end else
          begin
            consulta.FiltroConsulta.FechaExpedicionFactura.RangoFechaExpedicion:=RangoFechaExpedicionType.Create;
            consulta.FiltroConsulta.FechaExpedicionFactura.RangoFechaExpedicion.Desde:=verifactuFecha(datetoStr(resultado.Cabecera.dfecha));
            consulta.FiltroConsulta.FechaExpedicionFactura.RangoFechaExpedicion.Hasta:=verifactuFecha(datetoStr(resultado.Cabecera.hfecha));
          end;
      end
      else
      begin
          consulta.FiltroConsulta.NumSerieFactura:=resultado.Cabecera.NumSerieFactura;
      end;

      If resultado.Cabecera.filtroSIF then
      Begin
            //Vamos a establecer un filtro por sistema informático (opcional)
            consulta.FiltroConsulta.SistemaInformatico:=SistemaInformaticoType.Create;
            consulta.FiltroConsulta.SistemaInformatico.NombreRazon                := sesiones[sesion].inicio.sistemaInformatico.razonSocial;
            consulta.FiltroConsulta.SistemaInformatico.NIF                        := sesiones[sesion].inicio.sistemaInformatico.nif;
            consulta.FiltroConsulta.SistemaInformatico.NombreSistemaInformatico    := sesiones[sesion].inicio.sistemaInformatico.nombre;
            consulta.FiltroConsulta.SistemaInformatico.IdSistemaInformatico        := sesiones[sesion].inicio.sistemaInformatico.ID;
            consulta.FiltroConsulta.SistemaInformatico.Version                    := sesiones[sesion].inicio.sistemaInformatico.Version;
            consulta.FiltroConsulta.SistemaInformatico.NumeroInstalacion          := sesiones[sesion].inicio.sistemaInformatico.NumeroInstalacion;
            consulta.FiltroConsulta.SistemaInformatico.TipoUsoPosibleSoloVerifactu := SiNoType.S;

            if sesiones[sesion].inicio.sistemaInformatico.multiplesOT then
                consulta.FiltroConsulta.SistemaInformatico.TipoUsoPosibleMultiOT      := SiNoType.S
            else
                consulta.FiltroConsulta.SistemaInformatico.TipoUsoPosibleMultiOT      := SiNoType.N;
      End;

      eventos:=TEventosHttpPrio.Create;

      eventos.fileNameBefore  :=sesiones[sesion].inicio.saveXMLenvio;
      eventos.fileNameAfter  :=sesiones[sesion].inicio.saveXMLResultado;

      eventosWebNode:=TEventosWebNode.Create;
      eventosWebNode.sesion:=sesion;

      HTTPRIO1:=THTTPRIO.create(nil);
      HTTPRIO1.onBeforeExecute:=eventos.BeforeExecute;
      HTTPRIO1.onAfterExecute :=eventos.AfterExecute;

      // Cargar certificado del disco
      if ( pos('.PFX',uppercase(sesiones[sesion].inicio.nombreCertificado))<>0) or
        ( pos('.P12',uppercase(sesiones[sesion].inicio.nombreCertificado))<>0) then
          HTTPRIO1.HTTPWebNode.onBeforePost:=eventosWebNode.WebNodeBeforePost;

      // --> envío pre-produccion:  'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP';
      try
          // Cargar certificado del almacen
          if ( pos('.PFX',uppercase(sesiones[sesion].inicio.nombreCertificado))=0) and
            ( pos('.P12',uppercase(sesiones[sesion].inicio.nombreCertificado))=0) then
            HTTPRIO1.HTTPWebNode.ClientCertificate.SerialNum := Buscar_Certificado_SERIAL( sesiones[sesion].inicio.nombreCertificado );  // coloca el certificado

          direccion_envio :=sesiones[sesion].inicio.direccion_envio;

          res:=  RespuestaConsultaFactuSistemaFacturacionType.Create;
          res:=  GetsfPortTypeVerifactu( false, direccion_envio , HTTPRIO1 ).ConsultaFactuSistemaFacturacion( consulta ); // Enviarlo !

          // limpiar resultado antes de procesarlo
          clear_consulta(resultado);

          // resultado del envio:
          resultado.ConDatos:=(res.ResultadoConsulta=ResultadoConsultaType.ConDatos);
          resultado.error  :=false;
          resultado.errorDes:='';



          procesarConsulta(res,resultado );
      except
      on E:Exception do
      begin
          resultado.ConDatos:=false;
          resultado.error:=true;
          resultado.errorDes:=Format('(%s)-%s',[E.ClassName, E.Message]);
          result:=false;
          exit;
      end;

            // Finalmente:
            // HTTPRIO1.free;  //<- no puede ser destruido con NIL como constructor
            //eventos.Free;
      end;
      result:=true;
end;


No el código no lo he alterado. Digo que si emitimos una factura a un cliente con recargo de equivalencia, la clave de regimen pone 01 y debiera ser clave 18, ya que no es una operación del Régimen General (01), se trata de una operación del Régimen de Recargo de Equivalencia (18). O por lo menos eso es lo que tengo entendido, corregirme si me equivoco.
Lo mismo que ocurriría si es una operación REBU 03 (Régimen de bienes usados), no la puedo calificar con el Régimen General (01).

seccion_31 07-03-2025 12:25:13

Cita:

Empezado por ramherfer (Mensaje 562528)
No el código no lo he alterado. Digo que si emitimos una factura a un cliente con recargo de equivalencia, la clave de regimen pone 01 y debiera ser clave 18, ya que no es una operación del Régimen General (01), se trata de una operación del Régimen de Recargo de Equivalencia (18). O por lo menos eso es lo que tengo entendido, corregirme si me equivoco.
Lo mismo que ocurriría si es una operación REBU 03 (Régimen de bienes usados), no la puedo calificar con el Régimen General (01).

creo que aqui se aclara todo, normalmente sigo creyendo que sera 01:

https://arnaldes.com/expedientes-de-...sos-indebidos/

A ver que conclusiones sacamos

Saludos !

seccion_31 11-03-2025 08:19:47

en la proxima version que publicaremos este fin de semana incluira previsiblemente entre otras tres flags de factura

subsanacion
rechazoprevioExiste
rechazoPrevioNoExiste

Con la intencion de marcar a true la modalidad de rechazoprevio, cuando se envie un registro por un fallo, por ejemplo el de un DNI. (subsanacion, rechazoPrevioNoExiste)

Tenemos el siguiente caso:

Factura 1 aceptada huella: A
Factura 2 error por dni huella B
Factura 3 aceptada huella C

Factura 2 aceptada huella D

Cuando se vuelva a enviar la factura 2, tendra una nueva huella D y habria que marchar esos dos flags.

Ahora viene una pregunta:


El resto de huellas de la 2 en adelante y hasta el fin de la facturacion estara mal por dos causas:

https://www.agenciatributaria.es/sta..._registros.pdf

Cita:

a) Datos de campos a utilizar en el caso de registros de facturación de alta
(y la “ruta” de su localización dentro del registro):
1. IDEmisorFactura (RegistroAlta/IDFactura/IDEmisorFactura)
2. NumSerieFactura (RegistroAlta/IDFactura/NumSerieFactura)
3. FechaExpedicionFactura
(RegistroAlta/IDFactura/FechaExpedicionFactura)
4. TipoFactura (RegistroAlta/TipoFactura)
5. CuotaTotal (RegistroAlta/CuotaTotal)
6. ImporteTotal (RegistroAlta/ImporteTotal)
7. Huella (RegistroAlta/Encadenamiento/RegistroAnterior/Huella)
8. FechaHoraHusoGenRegistro
(RegistroAlta/FechaHoraHusoGenRegistro)
Porque la huella de la factura anterior forma parte del calculo de huella de la factura actual, y ademas porque se envia como dato adicional en el apartado factura Anterior.

¿esto es asi?

Saludos !

bmfranky 11-03-2025 09:31:26

Cita:

Empezado por seccion_31 (Mensaje 562555)
en la proxima version que publicaremos este fin de semana incluira previsiblemente entre otras tres flags de factura

subsanacion
rechazoprevioExiste
rechazoPrevioNoExiste

Con la intencion de marcar a true la modalidad de rechazoprevio, cuando se envie un registro por un fallo, por ejemplo el de un DNI. (subsanacion, rechazoPrevioNoExiste)

Tenemos el siguiente caso:

Factura 1 aceptada huella: A
Factura 2 error por dni huella B
Factura 3 aceptada huella C

Factura 2 aceptada huella D

Cuando se vuelva a enviar la factura 2, tendra una nueva huella D y habria que marchar esos dos flags.

Ahora viene una pregunta:


El resto de huellas de la 2 en adelante y hasta el fin de la facturacion estara mal por dos causas:

https://www.agenciatributaria.es/sta..._registros.pdf



Porque la huella de la factura anterior forma parte del calculo de huella de la factura actual, y ademas porque se envia como dato adicional en el apartado factura Anterior.

¿esto es asi?

Saludos !

Hola, no, ellos quieren que el encadenamiento sea consistente, da igual que el encadenamiento que uses de base para calcular el siguiente sea erroneo, siempre que el actual este bien sacado, siempre has de usar el encadenamiento que pasaste anteriormente, osea el ultimo que calculaste, si es erroneo, lo solucionas, usando el ultimo que enviaste.




Cita:

Factura 1 aceptada huella: A
Factura 2 error por dni huella B
Factura 3 aceptada huella C <<- aunque el anterior Hash es incorrecto, este no , porque su semilla es la que tu enviaste , aunque fuese mal calculada, por algun motivo.

Factura 2 aceptada huella D <<-- esta está correctamente encadenada con la 3, porque la 3 se encadeno con el hash que enviaste y que ahora estas corrigiendo.


Abreviando, calculas el hash con datos erroneos , te da bbbb, lo usas en el siguiente aunque sea erroneo, el calculo da ccc, para el siguiente el calculo cccc, es correcto porque en su formula, usaste bbbb que es lo que enviaste.


Tenemos un hilo entero aqui, al respecto.

Ten en cuenta que corrijes la huella del 2 registro en la aeat, creando un nuevo registro, no sustituyes ese registro en tu SIF, se queda inalterado.

seccion_31 11-03-2025 11:01:33

gracias por la respuesta bmfranky !

entonces tenemos

ENVIO 1
---------
F0001-ACEPTADA ENLACE CON NADA, PRIMER REGISTRO
F0002-ACEPTADA ENLACE CON F0001
F0003-DNI ERROR ENLACE CON F0002
F0004-ACEPTADA ENLACE CON F0003

ENVIO 2
---------
F0003 ACEPTADA-DNI YA CORRECTO - ENLACE CON F0004
F0005 ACEPTADA ENLACE CON F0004 ¿o con F0003?
F0006 ACEPTADA ENLACE CON F0005

¿seria asi?

DarkDudae 11-03-2025 11:12:09

La F0005 iría con la F0003 en ese caso. Ya que la F0003 aceptada, es cronológicamente su predecesora.

bmfranky 11-03-2025 11:23:20

Encadenamiento Registros , No Facturas
 
Cita:

Empezado por seccion_31 (Mensaje 562558)
gracias por la respuesta bmfranky !

entonces tenemos

ENVIO 1
---------
F0001-ACEPTADA ENLACE CON NADA, PRIMER REGISTRO
F0002-ACEPTADA ENLACE CON F0001
F0003-DNI ERROR ENLACE CON F0002
F0004-ACEPTADA ENLACE CON F0003

ENVIO 2
---------
F0003 ACEPTADA-DNI YA CORRECTO - ENLACE CON F0004
F0005 ACEPTADA ENLACE CON F0004 ¿o con F0003?
F0006 ACEPTADA ENLACE CON F0005

¿seria asi?

Cita:

Empezado por DarkDudae (Mensaje 562559)
La F5 iría con la F3 en ese caso. Ya que la F3 aceptada es cronológicamente su predecesora.


Hola, el encadenamiento es sobre el registro anterior, has de coger de el sus datos para el siguiente encadenamiento


Cita:

ENVIO 1
---------
NReg - Fac
0000 - F0001-ACEPTADA ENLACE CON NADA, PRIMER REGISTRO
0001 - F0002-ACEPTADA ENLACE CON 0000
0002 - F0003-DNI ERROR ENLACE CON 0001
0003 - F0004-ACEPTADA ENLACE CON 0002

ENVIO 2
---------
0004 - F0003 ACEPTADA-DNI YA CORRECTO - ENLACE CON 0003
0005 - F0005 ACEPTADA ENLACE CON 0004
0006 - F0006 ACEPTADA ENLACE CON 0005

Se enlaza con el contenido del registro anterior, sea de alta, baja, modificacion, no con la factura.

DarkDudae 11-03-2025 11:26:07

Cita:

Empezado por bmfranky (Mensaje 562561)
Se enlaza con el contenido del registro anterior, sea de alta, baja, modificacion, no con la factura.

Exacto, me refería justo a a eso. Pensaba que se sobreentendía.

bmfranky 11-03-2025 11:28:25

Cita:

Empezado por DarkDudae (Mensaje 562562)
Exacto, me refería justo a a eso. Pensaba que se sobreentendía.

Si, pero como en el ejemplo, se hacia referencia al numero de factura F0003, he creido que se entenderia mejor de esta manera, un saludo.

DarkDudae 11-03-2025 11:37:30

Cita:

Empezado por bmfranky (Mensaje 562564)
Si, pero como en el ejemplo, se hacia referencia al numero de factura F0003, he creido que se entenderia mejor de esta manera, un saludo.

Sí, se entiende mucho mejor.

seccion_31 11-03-2025 11:45:25

gracias por las respuestas !

¿y si el emisor tiene varias series? (una para tickets, y otra para facturas )

por ejemplo

F0003 ACEPTADA ENLAZADO CON F0002
V0002 ACEPTADA ¿ENLAZADO CON F0003?

saludos !

bmfranky 11-03-2025 11:51:03

Cita:

Empezado por seccion_31 (Mensaje 562573)
gracias por las respuestas !

¿y si el emisor tiene varias series? (una para tickets, y otra para facturas )

por ejemplo

F0003 ACEPTADA ENLAZADO CON F0002
V0002 ACEPTADA ¿ENLAZADO CON F0003?

saludos !


Da igual el contenido del registro, yo lo hago , encadeno 3 series de facturas, F1 de reparacion FR1, F1 de materiales FV1, y F2 de contado FC1, aparte de cuando haga las R que tambien lo hare.


Cita:

0005 - F0003 ACEPTADA ENLAZADO CON 0004
0006 - V0002 ACEPTADA ENLAZADO CON 0005

Al calcular el nuevo hash de encadenamiento usas el contenido del anterior, sea cual sea.(Hojo siempre del mismo NIF/Empresa)

delphiGar 11-03-2025 12:13:24

Siempre se encadena con el ultimo registro enviado, da igual si es correcto, Incorrecto o Aceptada con Errores.

Tambien da igual el tipo de factura y el tipo de registro Alta o Anulacion.

Lo mejor es guardar los datos del ultimo registro enviado y con eso construyes el siguiente.

rci 11-03-2025 12:30:14

Cita:

Empezado por delphiGar (Mensaje 562579)
Siempre se encadena con el ultimo registro enviado, da igual si es correcto, Incorrecto o Aceptada con Errores.

Tambien da igual el tipo de factura y el tipo de registro Alta o Anulacion.

Lo mejor es guardar los datos del ultimo registro enviado y con eso construyes el siguiente.


yo creo que no es el último enviado, sino el último registro de facturación generado

delphiGar 11-03-2025 12:43:29

Cita:

Empezado por rci (Mensaje 562584)
yo creo que no es el último enviado, sino el último registro de facturación generado

A buen entededor pocas palabras bastan.

Se entiende que ultimo enviado = ultimo generado.

seccion_31 11-03-2025 12:59:05

Cita:

Empezado por delphiGar (Mensaje 562579)
Siempre se encadena con el ultimo registro enviado, da igual si es correcto, Incorrecto o Aceptada con Errores.

Tambien da igual el tipo de factura y el tipo de registro Alta o Anulacion.

Lo mejor es guardar los datos del ultimo registro enviado y con eso construyes el siguiente.

Vamos a ver, que lo entienda bien: (otro caso, espero que el ultimo)

si envio la F3 con error en DNI y es la ultima del envio, y da resultado Incorrecto....

¿ El siguiente envio de la F3 ira enlazado con la huella de la F3 que dio errores (Incorrecta) ? (¿y en teoria no esta en la AEAT?)


hoy me he quedado sorprendido, porque hasta ahora no lo entendia asi.

Las que dan errores, No se quedan en la AEAT ¿no?, esto me descoloca un poco, a mi esto de enlazar me cuadraba con las aceptadas, o aceptas con errores, pero con las incorrectas... ahi ya me quede.

Saludos y gracias a todos

bmfranky 11-03-2025 13:01:43

Cita:

Empezado por seccion_31 (Mensaje 562586)
Vamos a ver, que lo entienda bien: (otro caso, espero que el ultimo)

si envio la F3 con error en DNI y es la ultima del envio, y da resultado Incorrecto....

¿ El siguiente envio de la F3 ira enlazado con la huella de la F3 que dio errores ?


hoy me he quedado sorprendido, porque hasta ahora no lo entendia asi.

Las que dan errores, No se quedan en la AEAT ¿no?, esto me descoloca un poco, a mi esto de enlazar me cuadraba con las aceptadas, o aceptas con errores, pero con las incorrectas... ahi ya me quede.

Saludos y gracias a todos

Hola, realmente lo que se encadenan son los registros que crea tu SIF, que se almacenan inmutables , no los que acepta o no la AEAT.

Para la aeat, el registro anterior con o sin errores , es el que hay que tener en cuenta para encadenar.

Osea creas el REgistro, lo almacenas, lo envias, se rechaza, creas otro registro , que soluciona los errores del registro enviado, encadenas con el anterior de la lista, sea o no correcto, enviado o no, rechazado o no, y continuas con la serie.

Mira en mi caso, hubo un error en el envio.

delphiGar 11-03-2025 13:06:46

Cita:

Empezado por seccion_31 (Mensaje 562586)
Vamos a ver, que lo entienda bien: (otro caso, espero que el ultimo)

si envio la F3 con error en DNI y es la ultima del envio, y da resultado Incorrecto....

¿ El siguiente envio de la F3 ira enlazado con la huella de la F3 que dio errores (Incorrecta) ? (¿y en teoria no esta en la AEAT?)


hoy me he quedado sorprendido, porque hasta ahora no lo entendia asi.

Las que dan errores, No se quedan en la AEAT ¿no?, esto me descoloca un poco, a mi esto de enlazar me cuadraba con las aceptadas, o aceptas con errores, pero con las incorrectas... ahi ya me quede.

Saludos y gracias a todos

Correcto, tienes que utilizar para el encadenamiento esos datos sean del tipo que sean y sin o con errores!

DarkDudae 11-03-2025 13:06:59

Cita:

Empezado por bmfranky (Mensaje 562587)
Hola, realmente lo que se encadenan son los registros que crea tu SIF, que se almacenan inmutables , no los que acepta o no la AEAT.

Para la aeat, el registro anterior con o sin errores , es el que hay que tener en cuenta para encadenar.

Exacto. Así es. Pensad que un auditor de Hacienda nos puede pedir (y debemos tener disponibles para ellos de forma sencilla desde la interface del programa) un registro de facturación, que no dejan de ser los registros que crea el SIF y que deben almacenarse inmutables, independientemente si los ha aceptado Verifactu o no. Además, esta es nuestra garantía de que nosotros (como mínimo) intentamos hacer las cosas de forma correcta.

bmfranky 11-03-2025 13:11:21


En esta captura se ve , que tube que modificar a huevos por un fallo mio al enviar, si veis es el mismo numero de factura, distinto contenido, la anterior no reza en la AEAT para nada.


Me pase de 3.000€, Si lo se la forma correcta es una rectificativa, pero aun no las implemento.

seccion_31 11-03-2025 13:11:57

gracias a todos

ahora si, ya quedo claro.


la semana que viene publicare una nueva version con los tres flags nuevos para permitir la correccion del registro, como he indicado.

Saludos !

DarkDudae 11-03-2025 13:17:09

Cita:

Empezado por bmfranky (Mensaje 562590)

En esta captura se ve , que tube que modificar a huevos por un fallo mio al enviar, si veis es el mismo numero de factura, distinto contenido, la anterior no reza en la AEAT para nada.


Me pase de 3.000€, Si lo se la forma correcta es una rectificativa, pero aun no las implemento.

Perdona mi indiscreción y este pequeño off-topic, pero...¿eso es el Heidi? En caso de que uses MySQL, te sugiero que, si almacenas (al igual que yo) los xml de cada registro, a la hora de hacer el INSERT/UPDATE, uses el comando COMPRESS(Campo_xml). Y cuando quieras visualizarlo, simplemente UNCOMPRESS(campo_xml). De esta forma tu registro de facturación ocupará bastante menos y será más rápido de consultar. Además este proceso prácticamente no agrega tiempo de procesado a la base de datos.

Neftali [Germán.Estévez] 11-03-2025 13:19:13

Cita:

Empezado por rci (Mensaje 562584)
yo creo que no es el último enviado, sino el último registro de facturación generado

Creo que eso es lo correcto.

Cita:

Empezado por delphiGar (Mensaje 562585)
Se entiende que ultimo enviado = ultimo generado.

No es por ser puristas, pero [rci] tiene razón (piensa siempre en sistemas verifactu y no-verifactu).
En Verifactu es posible que generado=enviado, pero en No-Verifactu no se envía nada y tal vez alguien se esté liando con eso.

delphiGar 11-03-2025 13:34:56

Cita:

Empezado por Neftali [Germán.Estévez] (Mensaje 562594)
Creo que eso es lo correcto.


No es por ser puristas, pero [rci] tiene razón (piensa siempre en sistemas verifactu y no-verifactu).
En Verifactu es posible que generado=enviado, pero en No-Verifactu no se envía nada y tal vez alguien se esté liando con eso.

Se entiende que en un sistema NO verifactu no envias mientras no haya requerimiento, sigues facturando y claro es el utlimo generado, y ese es el que hay que tener en cuenta.

Y como decia lo he echo sobre la base de ultimo registro generado = ultimo registro enviado en el caso de Verifactu y con el planteamiento que hacian los compañeros.

Lo importante aqui es que quede claro el concepto de lo que hay que utilizar.

bmfranky 11-03-2025 14:17:31

Cita:

Empezado por DarkDudae (Mensaje 562593)
Perdona mi indiscreción y este pequeño off-topic, pero...¿eso es el Heidi? En caso de que uses MySQL, te sugiero que, si almacenas (al igual que yo) los xml de cada registro, a la hora de hacer el INSERT/UPDATE, uses el comando COMPRESS(Campo_xml). Y cuando quieras visualizarlo, simplemente UNCOMPRESS(campo_xml). De esta forma tu registro de facturación ocupará bastante menos y será más rápido de consultar. Además este proceso prácticamente no agrega tiempo de procesado a la base de datos.

No hombre, nada que perdonar, se agradece 100% cualquier consejo, en cuanto mi otro trabajo me deje volver a ponerme al tajo con esto, pienso implementarlo.
Gracias.

seccion_31 11-03-2025 14:35:29

Ya tengo el codigo corregido. Pero....

1 He enviado una factura con error: A24033 , adrede. La unica del paquete
2 Me ha sido rechazada. Incorrecto. La archivo en el registro.
3 Corrijo la factura
4 Cuando intento enviarla de nuevo pongo como anterior su misma version fallida: (es decir huella rechazada, numero rechazado: ella misma la A24033 )

y me devuelve esto:

3002 No existe el registro de facturación.

...

Saludos !

bmfranky 11-03-2025 14:42:31

Cita:

Empezado por seccion_31 (Mensaje 562597)
Ya tengo el codigo corregido. Pero....

1 He enviado una factura con error: A24033 , adrede. La unica del paquete
2 Me ha sido rechazada. Incorrecto. La archivo en el registro.
3 Corrijo la factura
4 Cuando intento enviarla de nuevo pongo como anterior su misma version fallida: (es decir huella rechazada, numero rechazado: ella misma la A24033 )

y me devuelve esto:

3002 No existe el registro de facturación.

...

Saludos !

Si te han rechazado el registro anterior no existe en la aeat. registro previo N.
Ten encuenta que lo que no existe es el registro, no los datos de la factura, que es lo que usas para hacer el encadenamiento.

delphiGar 11-03-2025 14:59:54

Cita:

Empezado por seccion_31 (Mensaje 562597)
Ya tengo el codigo corregido. Pero....

1 He enviado una factura con error: A24033 , adrede. La unica del paquete
2 Me ha sido rechazada. Incorrecto. La archivo en el registro.
3 Corrijo la factura
4 Cuando intento enviarla de nuevo pongo como anterior su misma version fallida: (es decir huella rechazada, numero rechazado: ella misma la A24033 )

y me devuelve esto:

3002 No existe el registro de facturación.

...

Saludos !

Si el primer registro que enviastes de esa factura te lo devuelven como Incorrecto y no se registra en la AEAT, la subsanacion tienes que hacerlo con:
Subsanacion=S
RechazoPrevio=X

Si, por el contrario, el pimer registro te lo acepta y mandas un segundo por subsanacion y te lo rechaza tienes que hacelo para el siguiente:
Subsanacion=S
RechazoPrevio=S

Por lo general, si el registro esta en la AEAT y en caso de Subsanacion=S, RechazoPrevio=S, si no esta en la AEAT y es una subsanacion RechazoPrevio=X

seccion_31 11-03-2025 15:17:03

Cita:

Empezado por delphiGar (Mensaje 562599)
Si el primer registro que enviastes de esa factura te lo devuelven como Incorrecto y no se registra en la AEAT, la subsanacion tienes que hacerlo con:
Subsanacion=S
RechazoPrevio=X

Si, por el contrario, el pimer registro te lo acepta y mandas un segundo por subsanacion y te lo rechaza tienes que hacelo para el siguiente:
Subsanacion=S
RechazoPrevio=S

Por lo general, si el registro esta en la AEAT y en caso de Subsanacion=S, RechazoPrevio=S, si no esta en la AEAT y es una subsanacion RechazoPrevio=X

efectivamente, ya esta funcionando. gracias !!

tengo que comprobar los encadenamientos, pero ya esta. Cuando actualice el componente, ya se podra hacer asi.

slds !

Me gustaria facilitar estos encadenamientos en el componente, pero todavia no tengo claro si es muy seguro y factible.

delphiGar 11-03-2025 17:05:22

Cita:

Empezado por seccion_31 (Mensaje 562600)
efectivamente, ya esta funcionando. gracias !!

tengo que comprobar los encadenamientos, pero ya esta. Cuando actualice el componente, ya se podra hacer asi.

slds !

Me gustaria facilitar estos encadenamientos en el componente, pero todavia no tengo claro si es muy seguro y factible.

Deberias crearte una tabla o algun tipo de fichero donde guardar los datos de ultimo registro generado, en cuanto a los campos IDEMISOR, NUMSERIEFACTURA, FECHAEXPEDICION Y HUELLAANTERIOR, y de esta forma siempre que generes el registro nuevo obtienes los datos de este y al mismo tiempo guardas los del Nuevo en esta misma tabla o fichero, puedes tener en la tabla o fichero un solo registro con estos datos, si inicialmente no hay ninguno, no existe registro anterior, y por lo tanto el registro anterior sera igual a Primerregistro=S e insertas los nuevos, si por el contrario existe el registro coges esos datos y editas para poner los nuevos.

seccion_31 11-03-2025 17:46:52

ahora me doy cuenta que no necesito el registro anterior en el array de facturas

solo necesitaria el registro inicial como huella anterior del primer registro del array (o ninguno si es primer envio) del paquete de envio.

el array va ordenado cronologicamente (por orden de llegada) y el registro anterior, esta simplemente en j-1

al termino se guarda la ultima, que sera la inicial del siguiente paquete de envio. Y listo. Teniendo en cuenta su estado por subsanacion y rechazoprevio.


La franja horaria es GMT +2. Ahora son las 18:50:30.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi