Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Internet (https://www.clubdelphi.com/foros/forumdisplay.php?f=3)
-   -   TICKET BAI (TicketBAI); Nuevo sistema de la Agencia Tributaria del Pais Vasco (https://www.clubdelphi.com/foros/showthread.php?t=94264)

joselugrk 27-04-2021 19:24:07

Anular Factura
 
Cita:

Empezado por Neftali [Germán.Estévez] (Mensaje 540716)
Ojo, que a lo mejor estás confundiendo la anulación con la rectificación.
La primera simplemente envías el XML de anulación y la factura se elimina del sistema. En el segundo caso, creas la nueva factura rectificativa (en negativo como comentas) se genera el nuevo XML y se envía (no como anulacion sino como factura nueva).

Hola Neftali - Keys, primero muchas gracias. No lo tengo claro. He leído en una Web (no sé si se puede poner el link en el foro)

" ------------------------------------------------------------------------------------------------------------------------------------------
Cuándo puedo anular una factura
Los motivos por los que una empresa puede realizar una cancelación de invoice o e factura, pueden ser, entre otros:
  • No se ha recibido el producto, aunque se haya pagado.
  • No se ha llevado a cabo un servicio, aunque se haya abonado
  • Directamente cuando un producto ha sido devuelto
Solo es factible anular una factura si ha sido recién enviada y no está incluida en un trimestre contable ya cerrado. Es decir: no tienen el IVA liquidado. En este caso, cancelamos la factura y emitimos una nota de crédito.

En el caso de que el invoice, o la factura electrónica, tenga un error (faltan datos, se han calculado mal los impuestos) y el trimestre contable/administrativo ya esté cerrado y liquidado, se debe emitir una factura rectificativa, de la que hablaremos en otro artículo.

Una de las preguntas comunes es: ¿puedo anular una factura aunque ya haya pasado el periodo impositivo en el que se registró? La AEAT dice que sí.

¿Qué es una nota de crédito?

La nota de crédito es un documento legal en el cual queda constancia del reembolso, al incluir todos los importes finales en negativo de la factura a cancelar. De esta manera, a efectos prácticos, es como si se anulara la otra factura. Y Hacienda tiene constancia de que ese dinero ya no está en la caja de la empresa.

La nota de crédito se realiza como una factura normal, pero que se identifique claramente que es una nota de crédito. El cliente debe recibir la nota junto a copia de la factura original, y que se vea la anulación del importe económico.

Volviendo a Hacienda, debes mantener las facturas antiguas, aunque estén “anuladas” con una nota de crédito, por si acaso en algún momento tuvieras una inspección de la Agencia Tributaria. Así podrás demostrar que la nota de crédito se realizó en base a una factura real.

Cómo anular una factura.
Teniendo en cuenta todo lo anterior, para anular una factura, es necesario:

Hacer la factura nueva, donde quede claro que es una “nota de crédito”.
Asignarle un número correlativo al de la serie de facturas que ya se han emitido. No debe ser correlativo al de la factura que estás anulando, si no que si en el período contable corriente llevas 22 invoice o facturas, la nota sería la 23.
Incluir el número de referencia de la factura anulada.
Y no te olvides de incluir las cantidades que se han anulado, en negativo.

Conclusión
Los errores en las facturas pueden ocurrir, como humanos que somos, por diferentes motivos. Y es importante saber gestionar esas facturas erróneas, por ejemplo para hacer anulaciones.

------------------------------------------------------------------------------------------------------------------------------------------ "
Por esto, si anulo una factura, TicketBAI me dice que tengo que crear un XML de Anulación con los dato indicados en el esquema del XML de Anulación. Pero arriba indica que se debería crear, también, una nota de crédito (factura negativa en la misma serie) e enviársela al cliente para que tenga constancia de la anulación de la factura. ¿A está factura negativa le debo crear su XML de Alta? Ya que si no creo el XML de Alta me va a quedar un número de factura que no le he enviado a Diputación.

Y para las Facturas rectificativas se crea un XML de Alta indicando la factura que se rectifica. Pero se crea otra factura en una serie diferente con los datos rectificados.


Igual tienes razón y me estoy liando yo solo.

¿Cómo debería realizarlo?

Saludos y muchas gracias,
Joselu:confused::confused:

joselugrk 27-04-2021 20:17:31

Envío de Ficheros con ChilKat
 
Cita:

Empezado por aar1 (Mensaje 540715)
Hola joselugrk.

Para realizar la conexión del envío debes utilizar ChilkatSocket, este es un ejemplo en VB6 (Bizkaia):

Código Delphi [-]
Dim socket As New ChilkatSocket
success = socket.SetSslClientCertPfx("tu certificado.fpx", "contraseña del certificado")
success = socket.Connect("pruesarrerak.bizkaia.eus", 443, True, 5000)

Para comprimir y realizar el envío utiliza ChilkatRest y ChilkatStringBuilder:

Código Delphi [-]
Dim rest As New ChilkatRest
success = rest.UseConnection(socket, True)

rest.AddHeader "Accept-Encoding", "gzip"
rest.AddHeader "Content-Encoding", "gzip"
rest.AddHeader "Content-Length", filesize
rest.AddHeader "Content-Type", "application/octet-stream"
rest.AddHeader "eus-bizkaia-n3-version", "1.0"
rest.AddHeader "eus-bizkaia-n3-content-type", "application/xml"
rest.AddHeader "eus-bizkaia-n3-data", cadenajson

Dim dat As String

Dim stringBuilder As New ChilkatStringBuilder
success = stringBuilder.LoadFile("archivo XML a enviar", "utf-8")

dat = stringBuilder.GetAsString()

success = rest.SendReqStringBody("POST", "/N3B4000M/aurkezpena", dat)

No hace falta comprimir el archivo antes de enviarlo, ChilkatRest lo comprime y lo envía.

Para leer la cabecera de la respuesta:

Código Delphi [-]
success = rest.ReadResponseHeader()
dat = rest.ResponseHeader()

Para leer el archivo comprimido incluido en el body de la respuesta:

Código Delphi [-]
Dim aux_bin As New ChilkatBinData

aux_bin.AppendBinary (rest.ReadRespBodyBinary())
aux_bin.WriteFile "nombre del archivo comprimido donde se guarda la respuesta"
Dim gzip As New ChilkatGzip
success = gzip.UncompressFile("nombre del archivo comprimido donde se guarda la respuesta", "nombre del archivo XML descomprimido")

No he incluido las verificaciones de la variable success para no extender el código.

Espero que te sirva de ayuda.

Un saludo.

Lo reviso, y sobre todo muchas gracias por todo.
Joselu

Elyas 28-04-2021 15:32:04

Hola chic@s, tengo algunas dudas y me gustaría saber cómo afrontáis vosotros estas situaciones.

Pj. qué valores han de ir al registrar la primera factura en ticketbai en el encadenamiento?

- NumFacturaAnterior ?
- FechaExpedicionFacturaAnterior ?
- SignatureValueFirmaFacturaAnterior ?

Si no ponemos nada devuelve error y no registra la factura. En la primera si van los datos de la anterior factura y ésta no está registrada en ticketbai, entonces nos dará un warning? es eso aceptable o hay que hacerlo de otra forma?

--------

Y por otro lado vosotros cómo lo organizáis si tenéis un cuadro como este:

Factura Nº 003 vamos a registrar esta factura
Factura Nº 002 ya generada, NO registrada en ticketbai (pj vuestro servidor caído o similar)
Factura Nº 001 ya generada, registrada en ticketbai

al registrar la factura Nº 003, nos dará error en el encadenamiento al referirse a la 002 por tanto cómo lo resolvéis? si al registrar la 003 tomamos la 001 como la anterior entonces rompemos el orden en el que se generaron las facturas en nuestro sistema o eso da igual? siempre que una vez resuelto el problema de la factura 002, al registrarla cogeríamos la última factura aquella que esté registrada arriba aunque por orden de fechas no se cumple el encadenamiento? ¿Cómo hacéis vosotros en caso de que una factura no se ha registrado por alguna razón y queréis tramitar las demás? cómo actuáis?

Un saludo, y muchas gracias de antelación lo que compartan su experiencia!

Neftali [Germán.Estévez] 28-04-2021 15:47:34

Cita:

Empezado por Elyas (Mensaje 540727)
Pj. qué valores han de ir al registrar la primera factura en ticketbai en el encadenamiento?
- NumFacturaAnterior ?
- FechaExpedicionFacturaAnterior ?
- SignatureValueFirmaFacturaAnterior ?

Si no ponemos nada devuelve error y no registra la factura. En la primera si van los datos de la anterior factura y ésta no está registrada en ticketbai, entonces nos dará un warning? es eso aceptable o hay que hacerlo de otra forma?


En el caso de la primera factura no debes rellenar la información de la factura anterior. El bloque:
Cita:

<EncadenamientoFacturaAnterior>
...
</EncadenamientoFacturaAnterior>
No debe estar presente (los TAGs tampoco).

Debería devolverte una respuesta con un "Warning/aviso", pero te la aceptan igualmente. Algo como esto.





Esto que te he explicado es para la primera. Por ejemplo la primera de una serie.

Neftali [Germán.Estévez] 28-04-2021 15:53:26

Cita:

Empezado por Elyas (Mensaje 540727)
Y por otro lado vosotros cómo lo organizáis si tenéis un cuadro como este:

Factura Nº 003 vamos a registrar esta factura
Factura Nº 002 ya generada, NO registrada en ticketbai (pj vuestro servidor caído o similar)
Factura Nº 001 ya generada, registrada en ticketbai

al registrar la factura Nº 003, nos dará error en el encadenamiento al referirse a la 002 por tanto cómo lo resolvéis?

Las facturas que subas a ticketBAI siempre deben encadenarse con la última subida, así que en tu caso la 3 tiene que ir encadenada con la 1. Cualquier otra cosa te dará error.
Otro problema es que tal vez luego tengas que justificar qué ha pasado con la NUMFACT=2.

Nosotros, lo que hacemos es asignar el NUMDOC al subir la factura, por lo tanto siempre son correlativas.

Cita:

Empezado por Elyas (Mensaje 540727)
¿Cómo hacéis vosotros en caso de que una factura no se ha registrado por alguna razón y queréis tramitar las demás? cómo actuáis?


En nuestro caso no queda registrada como factura, sino como documento temporal.
Cuando se "finaliza" ese documento se le asigna fecha, numero y se sube a TBAI.

Elyas 28-04-2021 16:51:26

¡¡¡Muchísimas gracias por las respuestas!!!
^\||/^\||/^\||/^\||/

Sistel 29-04-2021 00:24:10

Cita:

Empezado por bilbur (Mensaje 540623)
Cuando vaya avanzando un poco más, si a alguien le interesa, subiré lo que estoy desarrolando en PHP (sin dependencia de terceros ni para generar el xml, firmar ni enviar)

Hola Bilbur.

Yo también trabajo en PHP y me interesa controlar todo el proceso (generación, firma y envío) dependiendo lo menos posible de librerías ajenas.
Cualquier información sobre el tema, me interesa.

Gracias

bilbur 29-04-2021 16:16:34

Cita:

Empezado por Sistel (Mensaje 540736)
Hola Bilbur.

Yo también trabajo en PHP y me interesa controlar todo el proceso (generación, firma y envío) dependiendo lo menos posible de librerías ajenas.
Cualquier información sobre el tema, me interesa.

Gracias


Hasta hoy tengo completo TBAI GIP


Genero xml
Firmo xml
Envio xml firmado a Gipuzkoa (curl php)

Todo OK

Ahora estoy con TBAI BIZ

Genero y firmo igual que TBAI GIP es lógico
No consigo envia a BIZ con curl php (llevo días sin avanzar nada)


Si te interesa, vamos por partes y probando lo que finciona

Sistel 29-04-2021 16:28:03

Cita:

Empezado por bilbur (Mensaje 540737)
...
Ahora estoy con TBAI BIZ
Genero y firmo igual que TBAI GIP es lógico
No consigo envia a BIZ con curl php (llevo días sin avanzar nada)
...

Hola Bilbur

¿Obtienes alguna respuesta al php-curl que lanzas?

Cuando, en su día, desarrollé el módulo de SII, tuve problemas con el php-curl.
Sin embargo me iba bien si lo lanzaba mediante shell_exec("curl --connect-timeout ...")
Así que lo dejé de esa manera.

Saludos

Neftali [Germán.Estévez] 29-04-2021 16:40:21

Cita:

Empezado por bilbur (Mensaje 540737)
Ahora estoy con TBAI BIZ
Genero y firmo igual que TBAI GIP es lógico
No consigo envia a BIZ con curl php (llevo días sin avanzar nada)

Yo os diría que realizarais pruebas con Postman, por ejemplo, para probar las diferentes partes del proceso.
Por ejemplo, se pueden configurar los parámetros de cabecera, el certificado de envío y enviar uno de los ficheros de ejemplo de BATUZ; Con eso ya podréis obtener una respuesta como esta:



Que ya son códigos de error y respuesta de BATUZ.
A partir de ahí se trata de ir centrándose en otros aspectos.

Modificar el fichero de ejemplo, por uno de los vuestros, y de ahí en adelante.

En el caso del ejemplo el problema del certificado (Interesado no identificado), porque la gente de BATUZ no admite certificados de prueba como si se hare en TicketBAI (**NOTA**, por si alguien lo está haciendo así que lo tenga en cuenta...)

Para la parte del JSON, también podéis utilizar uno de ejemplo que se puede descargar de aquí y modificarlo lévemente.

Sistel 29-04-2021 16:53:11

Cita:

Empezado por Neftali [Germán.Estévez] (Mensaje 540739)
Yo os diría que realizarais pruebas con Postman, por ejemplo, para probar las diferentes partes del proceso.
Por ejemplo, se pueden configurar los parámetros de cabecera, el certificado de envío y enviar uno de los ficheros de ejemplo de BATUZ; Con eso ya podréis obtener una respuesta como esta:



Que ya son códigos de error y respuesta de BATUZ.
A partir de ahí se trata de ir centrándose en otros aspectos.

Modificar el fichero de ejemplo, por uno de los vuestros, y de ahí en adelante.

En el caso del ejemplo el problema del certificado (Interesado no identificado), porque la gente de BATUZ no admite certificados de prueba como si se hare en TicketBAI (**NOTA**, por si alguien lo está haciendo así que lo tenga en cuenta...)

Para la parte del JSON, también podéis utilizar uno de ejemplo que se puede descargar de aquí y modificarlo lévemente.

Hola Neftali.

Magnífica idea probar los envíos con Postman o Imsomnia.

¡Muchas gracias!

bilbur 30-04-2021 20:29:32

Cita:

Empezado por Sistel (Mensaje 540738)
Hola Bilbur

¿Obtienes alguna respuesta al php-curl que lanzas?

Cuando, en su día, desarrollé el módulo de SII, tuve problemas con el php-curl.
Sin embargo me iba bien si lo lanzaba mediante shell_exec("curl --connect-timeout ...")
Así que lo dejé de esa manera.

Saludos


Esto es para chinarse.
Al final consigo envío y respuesta TBAI BIZ


Correcciones previas:


Error en el json pues yo ponía ejerc 2022 y debo poner 2021
Error en el json que si el nif no coincide con el del obligado tributario
etc


Error de certificado, resulta que el de izenpe (que me dieron para pruebas NO vale)
Error de certificado, resulta que el de FNMT de representante tampoco vale
He cambiado todo a mi certificado personal de FNMT y ya funciona


Recibo respuesta, todos los regitros erróneos
Descomprimo 2 veces la respuesta y me sale el motivo del error (y esto si que es para chinarse)


<SituacionRegistro>
<EstadoRegistro>Incorrecto</EstadoRegistro>
<CodigoErrorRegistro>B4_2000019</CodigoErrorRegistro>
<DescripcionErrorRegistro>
BaseRectificada: La Base Rectificada solo puede venir informada si Tipo (de rectificativa) es S.
</DescripcionErrorRegistro>
</SituacionRegistro>


Eso ya lo sabía, pero en TBAI GIP me daba la misma factura como correcta con aviso
011 Aviso: Error validación de negocio: FacturaRectificativa.

Comento a TBAI GIP y contestan:

Egun on, Buenos días
El motivo del error es que siendo una factura rectificativa, no se indica cual es la factura rectificada.
En TicketBAI siempre es obligatorio informar la factura rectificada correspondiente a la rectificativa.
Un saludo,


Ahora informo de las bases rectificadas en GIP pero no en BIZ y además tb hay que informar de la factura rectificada, tanto GIP como BIZ aunque sea dato no obligatorio (por lo memos hasta el otro día)


Lo importante es esto:
eus-bizkaia-n3-codigo-respuesta:

eus-bizkaia-n3-numero-registro:

eus-bizkaia-n3-tipo-respuesta: Correcto

Content-Type: application/xml;charset=UTF-8

Ordenaré el código PHP ya funcionando y con permiso de los administradores y a petición de Sistel iré subiendo las partes de código que me solicite.

Gracias a todos por todo, me estáis ayudando bastante más de lo que parece

Sistel 01-05-2021 10:33:15

Cita:

Empezado por bilbur (Mensaje 540750)
...
Eso ya lo sabía, pero en TBAI GIP me daba la misma factura como correcta con aviso
011 Aviso: Error validación de negocio: FacturaRectificativa.

Comento a TBAI GIP y contestan:
Egun on, Buenos días
El motivo del error es que siendo una factura rectificativa, no se indica cual es la factura rectificada.
En TicketBAI siempre es obligatorio informar la factura rectificada correspondiente a la rectificativa.
Un saludo,

Ahora informo de las bases rectificadas en GIP pero no en BIZ y además tb hay que informar de la factura rectificada, tanto GIP como BIZ aunque sea dato no obligatorio (por lo memos hasta el otro día)
...

Bueno, en Gipuzkoa, no sé como será, pero en Bizkaia, en el caso de factura rectificativa, sí es obligatorio el número de factura que rectifica.
Boletín Oficial de Bizkaia nº 175 del 11 de septiembre de 2020
Artículo 3, apartado g
Y también aparece en rojo (obligatorio) en la estructura (página 11)

Cita:

Empezado por bilbur (Mensaje 540750)
...
Ordenaré el código PHP ya funcionando y con permiso de los administradores y a petición de Sistel iré subiendo las partes de código que me solicite.
...

Muchísimas gracias, Bilbur, por tu ayuda.
Seguro que también les será muy útil a otros colegas que leen este foro, aunque no participen.

Un saludo

bilbur 01-05-2021 11:37:05

Cita:

Empezado por Sistel (Mensaje 540753)
Bueno, en Gipuzkoa, no sé como será, pero en Bizkaia, en el caso de factura rectificativa, sí es obligatorio el número de factura que rectifica.
Boletín Oficial de Bizkaia nº 175 del 11 de septiembre de 2020
Artículo 3, apartado g
Y también aparece en rojo (obligatorio) en la estructura (página 11)


Cierto, me lié con el SII puesto que en el xml que genero copié lo que tenía del SII y lo tenía como no obligatorio.


Errores de importancia relativa que se van corrigiendo conforme a los resultados de las pruebas.


Por otro lado, cuando queras empezamos con PHP

Galaxian 01-05-2021 21:17:15

¿Alguien sabe si el encadenamiento con la factura anterior es relativo o absoluto? es decir, si se encadena con la última factura de su serie o con la última generada sea de la serie que sea?

bilbur 02-05-2021 15:59:14

Cita:

Empezado por Galaxian (Mensaje 540757)
¿Alguien sabe si el encadenamiento con la factura anterior es relativo o absoluto? es decir, si se encadena con la última factura de su serie o con la última generada sea de la serie que sea?


Se encadena con la última generada sea de la serie que sea

bilbur 02-05-2021 16:35:57

PHP Generación del xml
 
Generación previa a su firma
En mi caso sólo vendo bienes, no servicios

La intención es ver la estructura del xml

Código PHP:

 $xml  = "<?xml version='1.0' encoding='UTF-8'?>";     
    $xml .= "<T:TicketBai xmlns:T='urn:ticketbai:emision' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' 
    xsi:schemaLocation='http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd'>";
    $xml .= "<Cabecera>";
    $xml .= "<IDVersionTBAI>".$versionTB."</IDVersionTBAI>";
     
    $xml .= "</Cabecera>";  
    $xml .= "<Sujetos>";
    $xml .= "<Emisor>";
    $xml .= "<NIF>".$cif_emisor."</NIF>";    
    $xml .= "<ApellidosNombreRazonSocial>".$nomb_emisor."</ApellidosNombreRazonSocial>";
    $xml .= "</Emisor>";   

    if ($ano_cli == 'S') $cif_cli = $nif_simplificado;  
    // $ano_cli si es fra simplificada  $nif_simplificado = 00000000T
    $xml .= "<Destinatarios>";
    $xml .= "<IDDestinatario>";    // $pai_cli = país del cliente
    if($pai_cli != 'ES' && $es_exportacion == 'SI')    
    {
    $xml .= "<IDOtro>";
    $xml .= "<CodigoPais>".$pai_cli."</CodigoPais>";
    $xml .= "<IDType>06</IDType>";    
    $xml .= "<ID>".$cif_cli."</ID>";
    $xml .= "</IDOtro>";    
    } elseif($es_intracomunitaria == 'SI')    
    {
    $xml .= "<IDOtro>";
    $xml .= "<IDType>02</IDType>";    
    $xml .= "<ID>".$cif_cli."</ID>";
    $xml .= "</IDOtro>";        
    }
    else    
    $xml .= "<NIF>".$cif_cli."</NIF>";
    $xml .= "<ApellidosNombreRazonSocial>".$nomb_cli."</ApellidosNombreRazonSocial>";
    $xml .= "<CodigoPostal>".$cpo_cli."</CodigoPostal>";  
    $ClientAdresse = elimina_acentos($ClientAdresse);      
    $xml .= "<Direccion>".$ClientAdresse."</Direccion>";     
    $xml .= "</IDDestinatario>";
    $xml .= "</Destinatarios>";
    $xml .= "<EmitidaPorTercerosODestinatario>N</EmitidaPorTercerosODestinatario>";  
    $xml .= "</Sujetos>";
$hora = time();
$hora = (date("H:i:s"));
  
    $xml .= "<Factura>";
    $xml .= "<CabeceraFactura>";  
    $xml .= "<SerieFactura>".$seriefactura."</SerieFactura>";  
    $xml .= "<NumFactura>".$numero_factura."</NumFactura>";
    $xml .= "<FechaExpedicionFactura>".$fecha_factura_TBAI."</FechaExpedicionFactura>";
    $xml .= "<HoraExpedicionFactura>".$hora."</HoraExpedicionFactura>";
    if($ano_cli == 'S')
    $xml .= "<FacturaSimplificada>".$ano_cli."</FacturaSimplificada>"; 
    $xml .= "<FacturaEmitidaSustitucionSimplificada>N</FacturaEmitidaSustitucionSimplificada>";
    if($FacturaRectificativa == 'S')
    {
    $base_ivaR = number_format($bimp_alb*(-1),2,".","");      
    $imp_ivaR  = number_format($iiva_alb*(-1),2,".","");    
    $imp_reqR  = number_format($ire_alb*(-1), 2,".",""); 
    
    $xml .= "<FacturaRectificativa>";  
    if($ano_cli == 'S')
    $xml .= "<Codigo>R5</Codigo>"; 
    else
    $xml .= "<Codigo>R1</Codigo>";   
    $xml .= "<Tipo>I</Tipo>";   
    
    if ($hacienda_foral == 'GIP')    
    {    
    $xml .= "<ImporteRectificacionSustitutiva>";      
    $xml .= "<BaseRectificada>".$base_ivaR."</BaseRectificada>";    
    $xml .= "<CuotaRectificada>".$imp_ivaR."</CuotaRectificada>";    
    $xml .= "<CuotaRecargoRectificada>".$imp_reqR."</CuotaRecargoRectificada>";    
    $xml .= "</ImporteRectificacionSustitutiva>";  
    }
    
    $xml .= "</FacturaRectificativa>";  


    $xml .= "<FacturasRectificadasSustituidas>";  
    $xml .= "<IDFacturaRectificadaSustituida>";    
    $xml .= "<NumFactura>".$rectifica1."</NumFactura>";
    $xml .= "<FechaExpedicionFactura>".$rectifica2."</FechaExpedicionFactura>";    
    $xml .= "</IDFacturaRectificadaSustituida>";      
    $xml .= "</FacturasRectificadasSustituidas>"; 

    }
    $xml .= "</CabeceraFactura>";     

    $xml .= "<DatosFactura>";   
    
    if($FacturaRectificativa == 'S') 
    {        
    $xml .= "<FechaOperacion>".$rectifica2."</FechaOperacion>"; 
    $xml .= "<DescripcionFactura>FACTURA RECTIFICATIVA</DescripcionFactura>";       
    }    
     else
    {
     $xml .= "<FechaOperacion>".$fecha_factura_TBAI."</FechaOperacion>";  
    $xml .= "<DescripcionFactura>FACTURA DE VENTA</DescripcionFactura>";     
    }

/////////// detalle factura ////////////////////////

    if($enviar_detalle_lineas == 'SI')
    {
    $xml .= "<DetallesFactura>";
    
    if($lineas_factura)
    foreach ($lineas_factura as $clave => $valor)
    {
    $valor_valor = explode("|",$valor);
    $des_art   = $valor_valor[1];
    $neto_lin  = number_format($valor_valor[2],2,".","");  
    $prec_lin  = number_format($valor_valor[3],8,".",""); 
    $impo_lin  = $valor_valor[4];
    $impo_lin  = $impo_lin + $impo_lin*($tipo_iva/100) + $impo_lin*($tipo_req/100); 
    $impo_lin  = number_format($impo_lin,2,".",""); 

    $xml .= "<IDDetalleFactura>";
    $des_art = elimina_acentos($des_art);    
    $des_art = preg_replace("([^ A-Za-z0-9])", "", $des_art);     
    $xml .= "<DescripcionDetalle>".$des_art."</DescripcionDetalle>"; 
    $xml .= "<Cantidad>".$neto_lin."</Cantidad>"; 
    $xml .= "<ImporteUnitario>".$prec_lin."</ImporteUnitario>"; 
    $xml .= "<ImporteTotal>".$impo_lin."</ImporteTotal>";     
    $xml .= "</IDDetalleFactura>";
    }       // fin for
    unset($lineas_factura);
    
    $xml .= "</DetallesFactura>";  
    }
    
#########################   totales    

    $xml .= "<ImporteTotalFactura>".$total_factura."</ImporteTotalFactura>"; 

    if($retencion)
    $xml .= "<RetencionSoportada>".$retencion."</RetencionSoportada>"; 
    
    $xml .= "<Claves>";   
    $xml .= "<IDClave>"; 
    $xml .= "<ClaveRegimenIvaOpTrascendencia>".$ClaveRegimenEspecialOTrascendencia."</ClaveRegimenIvaOpTrascendencia>"; 
    $xml .= "</IDClave>";     
    $xml .= "</Claves>";    
  
    $xml .= "</DatosFactura>";    

    $tipo_iva_m = number_format($tipo_iva,2,".","");
    if($tipo_req!=0 && $ire_alb!=0)
    $tipo_req_m = number_format($tipo_req,2,".","");
 
 
    if($es_exportacion == 'SI')    
    {        
    $xml .= "<TipoDesglose>";
    $xml .= "<DesgloseTipoOperacion>";
    $xml .= "<Entrega>";    
    $xml .= "<Sujeta>";
    $xml .= "<Exenta>";
    $xml .= "<DetalleExenta>";        
    $xml .= "<CausaExencion>E2</CausaExencion>";
    $xml .= "<BaseImponible>".$base_iva."</BaseImponible>";
    $xml .= "</DetalleExenta>";        
    $xml .= "</Exenta>";
    $xml .= "</Sujeta>";
    $xml .= "</Entrega>";    
    $xml .= "</DesgloseTipoOperacion>";
    $xml .= "</TipoDesglose>";
    }
    elseif($es_intracomunitaria == 'SI')     
    {
    $xml .= "<TipoDesglose>";
    $xml .= "<DesgloseTipoOperacion>";
    $xml .= "<Entrega>";    
    $xml .= "<Sujeta>";
    $xml .= "<Exenta>";
    $xml .= "<DetalleExenta>";    
    $xml .= "<CausaExencion>E5</CausaExencion>";
    $xml .= "<BaseImponible>".$base_iva."</BaseImponible>";
    $xml .= "</DetalleExenta>";        
    $xml .= "</Exenta>";
    $xml .= "</Sujeta>";
    $xml .= "</Entrega>";    
    $xml .= "</DesgloseTipoOperacion>";
    $xml .= "</TipoDesglose>";    
    } else {
    $xml .= "<TipoDesglose>";
    $xml .= "<DesgloseFactura>";
    $xml .= "<Sujeta>";
    $xml .= "<NoExenta>";
    $xml .= "<DetalleNoExenta>";    
    $xml .= "<TipoNoExenta>S1</TipoNoExenta>";
    $xml .= "<DesgloseIVA>";        
    $xml .= "<DetalleIVA>";
    $xml .= "<BaseImponible>".$base_iva."</BaseImponible>";
    $xml .= "<TipoImpositivo>".$tipo_iva_m."</TipoImpositivo>";
    $xml .= "<CuotaImpuesto>".$imp_iva."</CuotaImpuesto>";
    if($tipo_req!=0 && $ire_alb!=0)
    {
    $xml .= "<TipoRecargoEquivalencia>".$tipo_req_m."</TipoRecargoEquivalencia>";
    $xml .= "<CuotaRecargoEquivalencia>".$imp_req."</CuotaRecargoEquivalencia>";
    }
    $xml .= "</DetalleIVA>";
    $xml .= "</DesgloseIVA>";
    $xml .= "</DetalleNoExenta>";
    $xml .= "</NoExenta>";    
    $xml .= "</Sujeta>";
    $xml .= "</DesgloseFactura>";        
    $xml .= "</TipoDesglose>";
     } 
    $xml .= "</Factura>";

</div>Tened en cuenta u obviad las variables de PHP que vienen de una consulta sql previa


Seguimos con el encadenamiento dentro del mismo script
Busco la factura firmada anterior



Código PHP:

    $firmada_anterior $directorio_TBAI.$factura_anterior;
    
$file_anterior $firmada_anterior;
    if (
file_exists($file_anterior)) {
    
$xml_anterior simplexml_load_file($firmada_anterior);
    
$SerieFacturaAnterior $xml_anterior->Factura->CabeceraFactura->SerieFactura;    
    
$NumFacturaAnterior $xml_anterior->Factura->CabeceraFactura->NumFactura;    
    
$FechaExpedicionFacturaAnterior =    $xml_anterior->Factura->CabeceraFactura->FechaExpedicionFactura;        
    
$signatureNode $xml_anterior->children('ds'true)->Signature;    
    
$SignatureValueFirmaFacturaAnterior $signatureNode->SignatureValue;    
    } else {
    
$enviada_anterior $directorio_TBAI_envios.$factura_anterior;    
    
$file_enviado $enviada_anterior;    
    if (
file_exists($file_enviado)) {    
    
$xml_anterior simplexml_load_file($enviada_anterior);
    
$SerieFacturaAnterior $xml_anterior->Factura->CabeceraFactura->SerieFactura;        
    
$NumFacturaAnterior $xml_anterior->Factura->CabeceraFactura->NumFactura;    
    
$FechaExpedicionFacturaAnterior $xml_anterior->Factura->CabeceraFactura->FechaExpedicionFactura;        
    
$signatureNode $xml_anterior->children('ds'true)->Signature;    
    
$SignatureValueFirmaFacturaAnterior $signatureNode->SignatureValue;    
    } else {    
    
$NumFacturaAnterior 'Primera factura del ciclo de facturacion';    
    
$SignatureValueFirmaFacturaAnterior 'Primera factura del ciclo de facturacion';        
    }
    }    
    
$SignatureValueFirmaFacturaAnterior substr($SignatureValueFirmaFacturaAnterior,0,100);

    
$xml .= "<HuellaTBAI>";
    
$xml .= "<EncadenamientoFacturaAnterior>";
    
$xml .= "<SerieFacturaAnterior>".$SerieFacturaAnterior."</SerieFacturaAnterior>";
    
$xml .= "<NumFacturaAnterior>".$NumFacturaAnterior."</NumFacturaAnterior>";
    
$xml .= "<FechaExpedicionFacturaAnterior>".$FechaExpedicionFacturaAnterior."</FechaExpedicionFacturaAnterior>";    
    
$xml .= "<SignatureValueFirmaFacturaAnterior>".$SignatureValueFirmaFacturaAnterior."</SignatureValueFirmaFacturaAnterior>";
    
$xml .= "</EncadenamientoFacturaAnterior>";
    
$xml .= "<Software>";
    
$xml .= "<LicenciaTBAI>".$LicenciaTBAI."</LicenciaTBAI>";    
    
$xml .= "<EntidadDesarrolladora>";
    
$xml .= "<NIF>".$nif_fabricante."</NIF>";
    
$xml .= "</EntidadDesarrolladora>";    
    
$xml .= "<Nombre>".$SoftwareNombre."</Nombre>";
    
$xml .= "<Version>".$SoftwareVersion."</Version>";
    
$xml .= "</Software>";
    
$xml .= "<NumSerieDispositivo>".$NumSerieDispositivo_TBAI."</NumSerieDispositivo>";
  
$xml .= "</HuellaTBAI>";

   
$xml .= "</T:TicketBai>"

Fima del xml que detallaré más adelante en otro envío


Código PHP:

$guardar_como $tbai_alb.'.xsig';
    
    require_once(
$_SERVER['DOCUMENT_ROOT'].'/xxxxxx/ticketBAI/src/XmlTools.php' );
    require_once(
$_SERVER['DOCUMENT_ROOT'].'/xxxxxx/ticketBAI/src/firmador.php' );
    
$dir_cert = ($_SERVER['DOCUMENT_ROOT'].'/infoxxxxrmix/_certificandos'.$alias_pdf.'/xxxxxxx.pfx' );    
    
$fac = new Firmador();
    
$xmlF $fac -> firmar($dir_cert,$cal_cert$xml$hacienda_foral);

    
$directorio_ventas $directorio_TBAI.$guardar_como;  
     
file_put_contents($directorio_ventas$xmlF); 

</div>

Queda enviar el desarrollo de la firma donde intervienen XmlTools.php, firmador.php y el certificado de firma y su clave.

bilbur 02-05-2021 16:52:30

PHP Firma del xml
 
Nos hemos quedado aquí:

Código PHP:

$guardar_como $tbai_alb.'.xsig';
    
    require_once(
$_SERVER['DOCUMENT_ROOT'].'/xxx/ticketBAI/src/XmlTools.php' );
    require_once(
$_SERVER['DOCUMENT_ROOT'].'/xxx/ticketBAI/src/firmador.php' );
    
$dir_cert = ($_SERVER['DOCUMENT_ROOT'].'/xxx/_certificandos'.'/xxx.pfx' );    
    
$fac = new Firmador();
    
$xmlF $fac -> firmar($dir_cert,$cal_cert$xml$hacienda_foral);

    
$directorio_ventas $directorio_TBAI.$guardar_como;  
    
file_put_contents($directorio_ventas$xmlF); 

Lo que hago es indicar donde guardarlo, en mi caso con el mismo nombre número de orden de emisión de la factura.

XmlTools.php:
Código PHP:

class XmlTools {
    public function 
randomId() {
    if (
function_exists('random_int')) return random_int(0x100000000x7FFFFFFF);
    return 
rand(100000999999);
    }
    public function 
generateGUID($prefix='pfx')
    {
        
$uuid md5(uniqid(mt_rand(), true));
        
$guid $prefix.substr($uuid08)."-".
                
substr($uuid84)."-".
                
substr($uuid124)."-".
                
substr($uuid164)."-".
                
substr($uuid2012);
        return 
$guid;
    }
 } 

</div>

Este es el importante: firmador.php
Código PHP:

<?php
class Firmador{
    
    const 
POLITICA_FIRMA_BIZ = array(
    
"name" => "Politica de firma TicketBAI 1.0",
    
"url" => "https://www.batuz.eus/fitxategiak/batuz/ticketbai/sinadura_elektronikoaren_zehaztapenak_especificaciones_de_la_firma_electronica_v1_0.pdf"
    
"digest" => "Quzn98x3PMbSHwbUzaj5f5KOpiH0u8bvmwbbbNkO9Es="
    
);

    const 
POLITICA_FIRMA_GIP = array(
    
"name" => "Politica de firma TicketBAI 1.0",
    
"url" => "https://www.gipuzkoa.eus/ticketbai/sinadura",  
    
"digest" => "dTtPpv4fWTcejeVx7+91ILruFX3HysbngBlllJm4i/E="
    
);
    
    private 
$signTime         NULL;
    private 
$signPolicy       NULL;
    private 
$publicKey        NULL;
    private 
$privateKey       NULL;
    private 
$cerROOT          NULL;
    private 
$cerINTERMEDIO    NULL;
    private 
$tipoDoc          '01';

    public function 
retC14DigestSha1($strcadena)
    {
    
$strcadena    str_replace("\r"""str_replace("\n"""$strcadena));
    
$d1p        = new DOMDocument('1.0''UTF-8');
    
$d1p->loadXML($strcadena);
    
$strdata    $d1p->C14N();
    return 
base64_encode(hash('sha256' $strdatatrue ));
    }

    public function 
firmar($certificadop12$clavecertificado$xmlsinfirma$hacienda_foral)
    {
    if (!
$pfx file_get_contents($certificadop12))
    {
    echo 
"Error: No se puede leer el fichero del certificado o no existe en la ruta especificada\n";
    exit;
    }
    if (
openssl_pkcs12_read($pfx$key$clavecertificado))
    {
    
$this->publicKey    $key["cert"];
    
$this->privateKey   $key["pkey"];
    
$complem              openssl_pkey_get_details(openssl_pkey_get_private($this->privateKey));
    
$this->Modulus        base64_encode($complem['rsa']['n']);
    
$this->Exponent       base64_encode($complem['rsa']['e']);
    }
    else
    {
    echo 
"Error: No se puede leer el almacén de certificados o la clave no es la correcta.\n";
    exit;
    }

    if (
$hacienda_foral == "BIZ")    
    
$this->signPolicy             self::POLITICA_FIRMA_BIZ;
    if (
$hacienda_foral == "GIP")    
    
$this->signPolicy             self::POLITICA_FIRMA_GIP;
    
$tools = new XmlTools();
    
$this->signatureID             $tools->generateGUID('xmldsig-');
    
$this->signatureValue         $this->signatureID.'-sigvalue';
    
$this->XadesObjectId          "XadesObjectId-".$this->signatureID;
    
$this->KeyInfoId              "KeyInfoId-".$this->signatureID;
    
$this->Reference0Id           $this->signatureID.'-ref0';
    
$this->Reference1Id           "ReferenceKeyInfo";
    
$this->SignedProperties       $this->signatureID.'-signedprops';
    
$this->qualifyingProperties "QualifyingProperties-".$this->signatureID;
    
    
$xml1                       $xmlsinfirma;
    
$xml1                       $this->insertaFirma($xml1);
    return 
$xml1;
    }

    public function 
insertaFirma($xml){
      

      
    if (
is_null($this->publicKey) || is_null($this->privateKey))
    return 
$xml;

    
$d = new DOMDocument('1.0''UTF-8');
    
$d->loadXML($xml);
    
$canonizadoreal $d->C14N();

    
$xmlns 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ' .
             
'xmlns:T="urn:ticketbai:emision" ' .
             
'xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"';
            
    
$xmnls_signeg        'xmlns:T="urn:ticketbai:emision" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';
    
$xmlns_keyinfo       'xmlns:T="urn:ticketbai:emision" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';
    
$xmnls_signedprops 'xmlns:T="urn:ticketbai:emision" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';

    
$signTime1 date('Y-m-d\TH:i:s+01:00');
    
$certData   openssl_x509_parse($this->publicKey);
    
$certDigest base64_encode(openssl_x509_fingerprint($this->publicKey"sha256"true));
    
$certIssuer = array();

    foreach (
$certData['issuer'] as $item=>$value)
    {
    
$certIssuer[] = $item '=' $value;
    }
    
$certIssuer implode(', 'array_reverse($certIssuer));

    
$prop '<xades:SignedProperties Id="'.$this->SignedProperties.'">' .
      
'<xades:SignedSignatureProperties>'.
          
'<xades:SigningTime>'.$signTime1.'</xades:SigningTime>' .
          
'<xades:SigningCertificate>'.
              
'<xades:Cert>'.
                  
'<xades:CertDigest>' .
                      
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>'.
                      
'<ds:DigestValue>'.$certDigest.'</ds:DigestValue>'.
                  
'</xades:CertDigest>'.
                  
'<xades:IssuerSerial>' .
                      
'<ds:X509IssuerName>'.$certIssuer.'</ds:X509IssuerName>'.
                      
'<ds:X509SerialNumber>'.$certData['serialNumber'].'</ds:X509SerialNumber>' .
                  
'</xades:IssuerSerial>'.
              
'</xades:Cert>'.
          
'</xades:SigningCertificate>' .
          
'<xades:SignaturePolicyIdentifier>'.
              
'<xades:SignaturePolicyId>' .
                  
'<xades:SigPolicyId>'.
                      
'<xades:Identifier>'.$this->signPolicy['url'].'</xades:Identifier>'.
                      
'<xades:Description>'.$this->signPolicy['name'].'</xades:Description>'.
                  
'</xades:SigPolicyId>'.
                  
'<xades:SigPolicyHash>' .
                      
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />'.
                      
'<ds:DigestValue>'.$this->signPolicy['digest'].'</ds:DigestValue>'.
                  
'</xades:SigPolicyHash>'.
                  
'<xades:SigPolicyQualifiers>'.
                    
'<xades:SigPolicyQualifier>'.
                      
'<xades:SPURI>'.$this->signPolicy['url'].'</xades:SPURI>'.
                    
'</xades:SigPolicyQualifier>'.
                  
'</xades:SigPolicyQualifiers>'.
              
'</xades:SignaturePolicyId>' .
          
'</xades:SignaturePolicyIdentifier>'.
      
'</xades:SignedSignatureProperties>'.
      
'<xades:SignedDataObjectProperties>'.
          
'<xades:DataObjectFormat ObjectReference="#'.$this->Reference0Id.'">'.
                  
'<xades:ObjectIdentifier>' .
                    
'<xades:Identifier Qualifier="OIDAsURN">urn:oid:1.2.840.10003.5.109.10</xades:Identifier>' .
                  
'</xades:ObjectIdentifier>' .          
              
'<xades:MimeType>text/xml</xades:MimeType>'.
              
'<xades:Encoding>UTF-8</xades:Encoding>'.
          
'</xades:DataObjectFormat>'.
      
'</xades:SignedDataObjectProperties>'.
      
'</xades:SignedProperties>';

    
// Prepare key info
    
$publicPEM "";
    
openssl_x509_export($this->publicKey$publicPEM);
    
$publicPEM str_replace("-----BEGIN CERTIFICATE-----"""$publicPEM);
    
$publicPEM str_replace("-----END CERTIFICATE-----"""$publicPEM);
    
$publicPEM str_replace("\r"""str_replace("\n"""$publicPEM));

    
$kInfo '<ds:KeyInfo Id="'.$this->KeyInfoId.'">' .
                
'<ds:X509Data>'  .
                    
'<ds:X509Certificate>'.$publicPEM.'</ds:X509Certificate>' .
                
'</ds:X509Data>' .
                
'<ds:KeyValue>'.
                
'<ds:RSAKeyValue>'.
                    
'<ds:Modulus>'.$this->Modulus.'</ds:Modulus>'.
                    
'<ds:Exponent>'.$this->Exponent.'</ds:Exponent>'.
                
'</ds:RSAKeyValue>'.
                
'</ds:KeyValue>'.                
             
'</ds:KeyInfo>';

    
$keyinfo_para_hash1 str_replace('<ds:KeyInfo''<ds:KeyInfo ' $xmlns_keyinfo$kInfo);
    
$kInfoDigest $this->retC14DigestSha1($keyinfo_para_hash1);
    
$aconop     str_replace('<xades:SignedProperties''<xades:SignedProperties ' $xmnls_signedprops$prop);
    
$propDigest $this->retC14DigestSha1($aconop);
    
$documentDigest base64_encode(hash('sha256'$canonizadorealtrue));
        
    
// Prepare signed info
    
$sInfo '<ds:SignedInfo>' .
        
'<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>' .
 
        
'<ds:Reference URI="#'.$this->SignedProperties.'" Type="http://uri.etsi.org/01903#SignedProperties">' .
        
'<ds:Transforms>' .
        
'<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'</ds:Transforms>' .        
        
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>' .
        
'<ds:DigestValue>'.$propDigest.'</ds:DigestValue>' .
        
'</ds:Reference>' .

        
'<ds:Reference Id="'.$this->Reference1Id.'" URI="#'.$this->KeyInfoId.'">' .
        
'<ds:Transforms>' .
        
'<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'</ds:Transforms>' .        
        
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />' .
        
'<ds:DigestValue>'.$kInfoDigest.'</ds:DigestValue>' .
        
'</ds:Reference>' .
        
        
'<ds:Reference Id="'.$this->Reference0Id.'" URI=""
        Type="http://www.w3.org/2000/09/xmldsig#Object">' 
.
        
'<ds:Transforms>' .
        
'<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>' .        
        
'<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'</ds:Transforms>' .
        
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>' .
        
'<ds:DigestValue>'.$documentDigest.'</ds:DigestValue>' .
        
'</ds:Reference>' .

        
'</ds:SignedInfo>';


    
$signaturePayload str_replace('<ds:SignedInfo''<ds:SignedInfo ' $xmnls_signeg$sInfo);

    
$d1p = new DOMDocument('1.0''UTF-8');
    
$d1p->loadXML($signaturePayload);
    
$signaturePayload $d1p->C14N();
    
    
$signatureResult "";
    
$algo "SHA256";
    
    
openssl_sign($signaturePayload$signatureResult$this->privateKey$algo);
    
$signatureResult base64_encode($signatureResult);
    
$sig '<ds:Signature Id="'.$this->signatureID.'" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.
    
$sInfo.'<ds:SignatureValue Id="'.$this->signatureValue.'">'.$signatureResult'
    </ds:SignatureValue>'
.
    
$kInfo .
    
'<ds:Object Id="'.$this->XadesObjectId .'">'.
    
'<xades:QualifyingProperties Id="'.$this->qualifyingProperties.'" 
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Target="#'
.$this->signatureID.'" 
    xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">'

    
$prop .
    
'</xades:QualifyingProperties>
    </ds:Object>
    </ds:Signature>'
;

    
$xml str_replace('</T:TicketBai>'$sig '</T:TicketBai>'$xml);
    return 
$xml;
    }
}
?>

Mucha atención al contenido de firmador.php
Es posible que algo sobre pero no me atrevo a tocar nada ya que finciona bien.
Proximamente hablaremos del envío de los xml firmados a GIP y a BIZ, este último tiene su miga.
A vuestra disposición para comentar y aclarar lo que requiráis


Sería bueno poder firmar sólo con los .pem y sin clave de certificado pero quizás no se pueda.


Un saludo

Sistel 02-05-2021 19:04:28

Cita:

Empezado por bilbur (Mensaje 540763)
Nos hemos quedado aquí:
...

¡¡¡¡ Genial, Bilbur !!!!

Estaba atrancado con el tema de la firma.
No conseguía firmar el XML sin errores.
Seguro que con tu clase Firmador, salgo, por fín, del atolladero.

¡¡¡ Muchísimas Gracias !!!
Apunta que te debo alguna, para cuando acabe la pandemia.

Un cordial saludo

Neftali [Germán.Estévez] 03-05-2021 09:35:37

Cita:

Empezado por Galaxian (Mensaje 540757)
¿Alguien sabe si el encadenamiento con la factura anterior es relativo o absoluto? es decir, si se encadena con la última factura de su serie o con la última generada sea de la serie que sea?

Cita:

Empezado por bilbur (Mensaje 540761)
Se encadena con la última generada sea de la serie que sea


Nosotros encadenamos con la última factura independientemente de la serie.
(gracias por la corrección bilbur)

bilbur 03-05-2021 09:50:24

Cita:

Empezado por Neftali [Germán.Estévez] (Mensaje 540765)
Nosotros encadenamos con la última factura de la misma serie.


Preguntas frecuentes


¿Cómo se realiza el encadenamiento de la factura anterior?


El sistema TicketBAI implica que cada factura debe identificar la factura anterior (serie,
número, fecha y 100 primeros caracteres de la firma). Aunque en el esquema del
fichero TicketBAI estos campos no son obligatorios, esta circunstancia se debe a que
existen varias situaciones en los que este requisito no es exigible (p. ej. la primera
factura, o en caso de rotura del dispositivo).
Dentro de este encadenamiento, cada factura solo puede tener como máximo una
única factura anterior, es decir cada factura de un obligado tributario solo puede estar
referenciada como factura anterior en una única factura de dicho obligado tributario.
El cumplimiento del requisito de encadenamiento depende de la forma de
funcionamiento del sistema de facturación:
Si un mismo dispositivo o centro de facturación emite facturas de diferentes
series, el requisito de encadenamiento debe identificar la factura anterior
expedida, con independencia de la serie dentro de ese dispositivo o centro de
facturación (por ejemplo, a una factura de una serie ordinaria le puede preceder
una factura rectificativa de otra serie).


Igualmente, si el sistema de facturación es un sistema totalmente centralizado,
el encadenamiento se refiere a la última factura expedida por el sistema en
orden temporal.

En concreto, la aplicación “Haz tu factura” funcionaría como un sistema de facturación
totalmente “centralizado”, por lo que encadena todas las facturas en orden temporal.
Si son necesarios varios procesos de facturación (se podrían asimilar a centros de
facturación) para dar respuesta a varios usuarios concurrentes de una misma empresa
de forma paralela, se debería encadenar en orden temporal dentro de cada proceso o
centro de facturación, con independencia de la serie. En este caso, debe existir un
número acotado de procesos o colas de facturación, asociado al número de usuarios
concurrentes por empresa emisora de facturas

Neftali [Germán.Estévez] 03-05-2021 13:57:50

Cita:

Empezado por Neftali [Germán.Estévez] (Mensaje 540765)
Nosotros encadenamos con la última factura de la misma serie.


Tienes razón bilbur.
Estaba pensando en el cálculo del HASh no del encadenamiento...

Borro el mensaje anterior para evitar confusiones.

bilbur 03-05-2021 15:48:49

Cita:

Empezado por Sistel (Mensaje 540764)
¡¡¡¡ Genial, Bilbur !!!!

Estaba atrancado con el tema de la firma.
No conseguía firmar el XML sin errores.
Seguro que con tu clase Firmador, salgo, por fín, del atolladero.

Un cordial saludo


Si necesitas ayuda en la implementación, me dices. No sea que se me olvidara subir algo.
Para comprobar las firmas y antes de enviarlas yo utilizo estos dos enlaces

http://tools.chilkat.io/xmlDsigVerify.cshtml

https://web.uanataca.com/pe/servicio...ma-electronica

Si te da correcto en las dos validaciones pues ¡eureka!

bilbur 03-05-2021 20:21:32

PHP Envío a TBAI GIP
 
Os adjunto lo que tengo hecho para el envío a Gipuzkoa
Este envío es el más sencillo
Más o menos lo que hago es:
  1. Proceso previo o simultáneo a la emisión y firma de la factura
  2. En el directorio donde están las facturas firmadas, creo, si no están, los directorios de Envios y Respuestas.
  3. Si la factura firmada existe en el directorio de facturas firmadas, la envío a TBAI GIP
  4. Me guardo la respuesta y si es afirmativa muevo la factura firmada al directorio de Envios
<div>
Código PHP:

if($hacienda_foral == 'GIP' && $orden_tbai != '000000')     {    
    
$directorio_TBAI $originales."TBAI/".$cif_340."/";
    
$directorio_TBAI_envios $originales."TBAI/".$cif_340."/Envios/";        
    if (!
is_dir($directorio_TBAI_envios))  mkdir($directorio_TBAI_envios0777);
    
$directorio_respuestas $directorio_TBAI.'Respuestas/';   
    if (!
is_dir($directorio_respuestas))  mkdir($directorio_respuestas0777);    

    
$estado_tbai '';
    
$xml_enviado_tbai $orden_tbai.'.xsig';
    
$guardado_enviar_tbai $directorio_TBAI.$xml_enviado_tbai;    
    if (
file_exists($guardado_enviar_tbai)) 
    {    
    
$estado_tbai 'No enviado';
    
$descripcion_TBAI $estado_tbai;
    
$xml file_get_contents($guardado_enviar_tbai);

    
$header_soap = array(
    
"Content-Type: application/xml;charset=utf-8",
    
"Accept: application/xml",
    
"Content-length: ".strlen($xml)
    );

$soap_do curl_init();
curl_setopt($soap_doCURLOPT_URL$url_TBAI );
curl_setopt($soap_doCURLOPT_CONNECTTIMEOUT100);
curl_setopt($soap_doCURLOPT_TIMEOUT,        100);
curl_setopt($soap_doCURLOPT_RETURNTRANSFERtrue);
curl_setopt($soap_doCURLOPT_FOLLOWLOCATIONtrue);
curl_setopt($soap_doCURLOPT_SSL_VERIFYPEERfalse);
curl_setopt($soap_doCURLOPT_SSL_VERIFYHOSTfalse);
curl_setopt($soap_doCURLOPT_USERAGENT'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
curl_setopt($soap_doCURLOPT_POST,           true );
curl_setopt($soap_doCURLOPT_SSLCERT,        getcwd().'/_certificandos'.$alias_pdf.'/'.$cif_340.'.pem' );
curl_setopt($soap_doCURLOPT_POSTFIELDS,     $xml);
curl_setopt($soap_doCURLOPT_HTTPHEADER,     $header_soap);

    
$response curl_exec($soap_do);     
    if(!
curl_exec($soap_do)) {
    
$err 'Error: ' curl_error($soap_do);
    
curl_close($soap_do);
    echo 
"<br>No se puede conectar con la TBAI GIP.<br><br>";    
    
print_r($err);
    echo 
"<br>";    
    echo 
"<br><b>Respuesta de la TBAI GIP: 312</b><br><br>";    
    
print_r($response);
    } else {
    
curl_close($soap_do);

    
$response preg_replace('/(<\s*)\w+:/','$1',$response);   // removes <xxx:
    
$response preg_replace('/(<\/\s*)\w+:/','$1',$response); // removes </xxx:
    
$response preg_replace('/\s+xmlns:[^>]+/','',$response); // removes xmlns:...    

    
$use_errors libxml_use_internal_errors(true);    
    
$parser simplexml_load_string($response);
    if (
$parser === false) {
    echo 
"<br>No se puede cargar el fichero xml devuelto por la TBA GIP.<br><br>";
    echo 
"<br>";    
    echo 
"<br><b>Respuesta de la TBAI GIP:</b><br><br>";    
    
print_r($response);
    }
    
libxml_clear_errors();
    
libxml_use_internal_errors($use_errors);    
    if (
$parser === false) exit();
    
$parser->asXML($directorio_respuestas.$orden_tbai.'.xml');
    }

    
$facturas_en_TBAI $parser;
    if (
$facturas_en_TBAI === false)    
    {
    echo 
"<br><b>Respuesta de TBAI parser:</b><br><br>";
    
print_r ($facturas_en_TBAI);
    }
    
$guardado_enviado_tbai $directorio_TBAI_envios.$xml_enviado_tbai;
    
    
$nodo $facturas_en_TBAI->Salida;
    
$Estado $nodo->Estado;    
    if(
$Estado == '00')    
    
rename($guardado_enviar_tbai,$guardado_enviado_tbai); /// atencion a esto
    
unset($facturas_en_TBAI);
    }    
     } 
// if($hacienda_foral == 'GIP') 

</div>Aparte tengo un script para visualizar la respuesta
En este lo más interesante puede ser la generación de la url del mensaje TBAI para verlo como si se tratara del código QR



Código PHP:

<?php

    $directorio_respuestas 
$directorio_TBAI.'Respuestas/';   
    if (!
is_dir($directorio_respuestas))  mkdir($directorio_respuestas0777);
    
$directorio_envios $directorio_TBAI.'Envios/';   
    if (!
is_dir($directorio_envios))  mkdir($directorio_envios0777);
    
    
$estado_tbai '';

    
$xml_enviado_tbai $orden_tbai.'.xsig';    
    
$guardado_enviado_tbai $directorio_envios.$xml_enviado_tbai;    
    if (
file_exists($guardado_enviado_tbai)) 
    
$estado_tbai 'Enviado'; else $estado_tbai 'No enviado';
    
$descripcion_TBAI $estado_tbai;

    if(
$estado_tbai == 'Enviado')
    {    
    
$msg_TBAI '';
    
$xml_actual_enviado simplexml_load_file($guardado_enviado_tbai);    
    
$signatureNodeActual $xml_actual_enviado->children('ds'true)->Signature;    
    
$SignatureValueFirmaFacturaActual $signatureNodeActual->SignatureValue;
    
$SignatureValueFirmaFacturaActual substr($SignatureValueFirmaFacturaActual,0,13);    
    
$factura_tbai_serie  $xml_actual_enviado->Factura->CabeceraFactura->SerieFactura;    
    
$factura_tbai_numero $xml_actual_enviado->Factura->CabeceraFactura->NumFactura;
    
$factura_tbai_fecha  $xml_actual_enviado->Factura->CabeceraFactura->FechaExpedicionFactura;    
    
$factura_tbai_fecha_dd substr($factura_tbai_fecha,0,2);
    
$factura_tbai_fecha_mm substr($factura_tbai_fecha,3,2);    
    
$factura_tbai_fecha_aa substr($factura_tbai_fecha,-2);        
    
$factura_tbai_importe  $xml_actual_enviado->Factura->DatosFactura->ImporteTotalFactura;        
    
$TBAIID  'TBAI'.$caracterSeparador.$cif_340.$caracterSeparador.$factura_tbai_fecha_dd;
    
$TBAIID .= $factura_tbai_fecha_mm.$factura_tbai_fecha_aa.$caracterSeparador;
    
$TBAIID .= $SignatureValueFirmaFacturaActual.$caracterSeparador;
    
$TBAIID utf8_encode($TBAIID);    
    
$cod_crc8 crc8($TBAIID);
    
$TBAIID  $TBAIID.$cod_crc8;
    unset(
$xml_actual_enviado);
    
$msg_TBAI =  $url_TBAI_QR.'?id='.$TBAIID.'&s='.$factura_tbai_serie.'&nf='.$factura_tbai_numero.'&i='.$factura_tbai_importe;
    
$msg_TBAI utf8_encode($msg_TBAI);    
    
$cod_crc8 crc8($msg_TBAI);
    
$msg_TBAI $msg_TBAI.'&cr='.$cod_crc8;    
    
$xml_respuesta $orden_tbai.'.xml';
    
$guardado_respuesta $directorio_respuestas.$xml_respuesta;
    if (
file_exists($guardado_respuesta)) 
    {
    
$respuesta_tbai simplexml_load_file($guardado_respuesta);
    if (
$respuesta_tbai === false) {
    
$estado_tbai 'No se puede leer la respuesta de TBAI';
    } else {
    
$estado_tbai 'Existe respuesta de TBAI';
    }
    } else {
    
$estado_tbai 'No se encuentra la respuesta de TBAI';
    
$sin_respuesta_tbai     '<?xml version="1.0" encoding="UTF-8"?>';
    
$sin_respuesta_tbai    .= '<Salida></Salida>';
    
$respuesta_tbai simplexml_load_string($sin_respuesta_tbai);    
    }
    
$descripcion_TBAI $estado_tbai;

    
$nodo $respuesta_tbai->Salida;
    
$Estado $nodo->Estado;
    
$FechaRecepcion $nodo->FechaRecepcion;

    if(
$Estado == '00')
    {    
    
$IdentificadorTBAI $nodo->IdentificadorTBAI;    
    
$Descripcion $nodo->Descripcion;
    
$Descripcion utf8_decode($Descripcion);
    
$CSV_tbai 'CSV: '.$nodo->CSV;
    
$ResultadosValidacion $nodo->ResultadosValidacion;
    
$CodigoRV $ResultadosValidacion->Codigo;
    if(
$CodigoRV)    
    
$DescripcionRV $errores_envio_TBAI["$CodigoRV"];    
    
$estado_tbai 'Correcto';    
    
$descripcion_TBAI $estado_tbai.': '.$IdentificadorTBAI.' '.$FechaRecepcion.' '.$Descripcion.' '.$CSV_tbai;
    if(
$CodigoRV)
    {    
    
$descripcion_TBAI .= ' '.$CodigoRV.' '.$DescripcionRV;
    
$estado_tbai 'Aviso';    
    }
    }    
    
    if(
$Estado == '01')    
    {
    
$ResultadosValidacion $nodo->ResultadosValidacion;
    
$CodigoRV $ResultadosValidacion->Codigo;
    
$DescripcionRV $errores_envio_TBAI["$CodigoRV"];    
    if(
substr($DescripcionRV,0,5) == 'Aviso' || $CodigoRV == '005' )
    
$estado_tbai 'Aviso';    
    else    
$estado_tbai 'Rechazado';
    
$descripcion_TBAI $estado_tbai.': '.$FechaRecepcion.' '.$CodigoRV.' '.$DescripcionRV;
    }
    unset(
$respuesta_tbai);
    } 
//  enviado

Tengo, si interesa, la generación del código QR para PDF con la libreria fpdf.


Comentamos ...

Galaxian 04-05-2021 08:56:52

Cita:

Empezado por bilbur (Mensaje 540761)
Se encadena con la última generada sea de la serie que sea

Muchas gracias bilbur.

Aprovecho la ocasión para lanzar otra pregunta -de nota- al que ni las haciendas de Guipúzcoa y Vizcaya han respondido: ¿como se declaran las operaciones hechas a comisión por terminales externos pero que se cobran con el programa (por ejemplo las recargas de móviles)? En estos casos, el programa genera una factura simplificada por importe cero, pues las ventas a comisión no son ventas propias del negocio:

• ¿Hay declarar estas facturas? En su caso ¿debe llevar el QR?
• ¿Qué pasa con la comisión? ¿no habría que declararla en el 140 de batuz?
• Y el más difícil todavía: ¿y si la comisión es variable dependiendo de las ventas (ONCE y loterías) y no se conoce hasta que se recibe la factura?

¿Nadie más se ha encontrado con este caso o alguno similar?

Un saludo

keys 04-05-2021 09:34:53

Hola.

Yo entiendo que hablas de un suplido.

En el caso en el que, en una factura, por ejemplo, además de los honorarios de un profesional se documentase un suplido, el importe del suplido no formaría parte de la base imponible del IVA pero estas facturas deben cumplir igualmente los requisitos TicketBAI.

En cambio, si únicamente se quisiera documentar un suplido, se trataría de una operación en la que no se documenta ninguna entrega de bienes ni prestación de servicios realizada por el profesional en el desarrollo de su actividad, ya que en estos casos el empresario actuaría como mero intermediario, satisfaciendo sumas de dinero del cliente a un tercero por mandato expreso. En este caso, no habría obligación de expedir factura de acuerdo con lo dispuesto en el Reglamento de facturación, pudiéndose documentar la operación en un recibo u otro justificante. Por tanto, un documento en el que solo se informe de un suplido quedaría fuera del ámbito de aplicación de TicketBAI.

Luego en el 140 tiene que ir como un ingreso sin factura.

bilbur 04-05-2021 11:05:17

Cita:

Empezado por Galaxian (Mensaje 540782)
Muchas gracias bilbur.

Aprovecho la ocasión para lanzar otra pregunta -de nota- al que ni las haciendas de Guipúzcoa y Vizcaya han respondido: ¿como se declaran las operaciones hechas a comisión por terminales externos pero que se cobran con el programa (por ejemplo las recargas de móviles)? En estos casos, el programa genera una factura simplificada por importe cero, pues las ventas a comisión no son ventas propias del negocio:

• ¿Hay declarar estas facturas? En su caso ¿debe llevar el QR?
• ¿Qué pasa con la comisión? ¿no habría que declararla en el 140 de batuz?
• Y el más difícil todavía: ¿y si la comisión es variable dependiendo de las ventas (ONCE y loterías) y no se conoce hasta que se recibe la factura?

¿Nadie más se ha encontrado con este caso o alguno similar?

Un saludo


Entiendo que si se emite factura, aunque importe sea cero, hay que declararla en TBAI y estar a lo que ponga en la factura emitida.
Yo si que las declaro en SII.
A mi se me da, a veces, el caso de factura emitida total cero por venta de mercancía p.e. 10 y devolución de envases tb de -10, total 0,00.

Así no "pierdo" número de factura ni encadenamientos

Lo mejor, probar ahora en pruebas (BIZ y GIP) y ver el resultado
Si da error, enviar correo a los dos y a esperar

batuzail 04-05-2021 11:49:28

Proceso de facturación
 
Hola,Voy a empezar a desarrollar el acceso a ticketBai, tengo una pregunta de concepto, por si alguno que ya tenga la solución puede contestarme.
En mi caso generamos la facturas una vez a la semana, generamos cientos de facturas.


¿Hay que realizar una petición por factura y asignar a ese documento el código y el QR en el momento de la generación?


¿Se pueden generar las facturas y posteriormente acceder a ticketbai cuando se imprimen?


Voy implementar sobre VB6 y .NET, usando Chilkat y SecureBlackBlox.


Gracias

keys 04-05-2021 11:58:14

Cita:

Empezado por bilbur (Mensaje 540784)
Entiendo que si se emite factura, aunque importe sea cero, hay que declararla en TBAI y estar a lo que ponga en la factura emitida.
Yo si que las declaro en SII.
A mi se me da, a veces, el caso de factura emitida total cero por venta de mercancía p.e. 10 y devolución de envases tb de -10, total 0,00.

Así no "pierdo" número de factura ni encadenamientos

Lo mejor, probar ahora en pruebas (BIZ y GIP) y ver el resultado
Si da error, enviar correo a los dos y a esperar

El problema es que ese tipo de iva es un iva no sujeto y como envies una factura con importe cero y con iva no sujeto TBAI da error. A nosotros ya se nos planteo el caso y nos contestaron con lo que he puesto antes. Si el importe es cero y no hay base imponible hay que generar un recibo y no una factura.

Sólo se puede incluir en una factura que tenga otros conceptos con base imponible a iva.

Sistel 04-05-2021 12:12:59

Cita:

Empezado por batuzail (Mensaje 540785)
...
En mi caso generamos la facturas una vez a la semana, generamos cientos de facturas.
¿Hay que realizar una petición por factura y asignar a ese documento el código y el QR en el momento de la generación?
¿Se pueden generar las facturas y posteriormente acceder a ticketbai cuando se imprimen?
...

Hola,

La factura está completa cuando obtienes su XML firmado.
Para obtener el XML firmado de la factura no necesitas acceder a ningún sitio externo.
Es un proceso local.

Otra cosa diferente es su envío a Hacienda Foral.
Si es de Gipuzkoa, debe hacerse el envío inmediatamente después de haber generado el XML firmado (salvo que obtengas de Hacienda una autorización para el envío posterior por no tener conexión ni fija ni móvil a Internet).
Si es de Bizkaia, tienes el plazo normal que había para las declaraciones de IVA (trimestrales o mensuales según la empresa o en un plazo de 4 días si es una empresa obligada al SII), pero se envían dentro del capítulo 1.1 del LROE.

Pero para imprimirla (o enviarla por email como PDF) tienes que añadir en la parte de abajo del documento el código TBAI y el código QR.
Esos códigos TBAI y QR puedes obtenerlos cuando vayas a imprimirla. No los necesitas antes.

Es lo que yo he entendido, pero se admiten correcciones.

Saludos

Galaxian 04-05-2021 12:15:31

Cita:

Empezado por keys (Mensaje 540786)
El problema es que ese tipo de iva es un iva no sujeto y como envies una factura con importe cero y con iva no sujeto TBAI da error. A nosotros ya se nos planteo el caso y nos contestaron con lo que he puesto antes. Si el importe es cero y no hay base imponible hay que generar un recibo y no una factura.

Sólo se puede incluir en una factura que tenga otros conceptos con base imponible a iva.

Esa es otra, porque las ventas en agencia (que así es como se llaman oficialmente), además de a retención por IRPF al ser una actividad profesional, están sujetas a IVA, aunque sobre la comisión, por lo que es un IVA soportado, no repercutido.

Cágate lorito.

batuzail 04-05-2021 12:27:57

Cita:

Empezado por Sistel (Mensaje 540787)
Hola,

La factura está completa cuando obtienes su XML firmado.
Para obtener el XML firmado de la factura no necesitas acceder a ningún sitio externo.
Es un proceso local.

Otra cosa diferente es su envío a Hacienda Foral.
Si es de Gipuzkoa, debe hacerse el envío inmediatamente después de haber generado el XML firmado (salvo que obtengas de Hacienda una autorización para el envío posterior por no tener conexión ni fija ni móvil a Internet).
Si es de Bizkaia, tienes el plazo normal que había para las declaraciones de IVA (trimestrales o mensuales según la empresa o en un plazo de 4 días si es una empresa obligada al SII), pero se envían dentro del capítulo 1.1 del LROE.

Pero para imprimirla (o enviarla por email como PDF) tienes que añadir en la parte de abajo del documento el código TBAI y el código QR.
Esos códigos TBAI y QR puedes obtenerlos cuando vayas a imprimirla. No los necesitas antes.

Es lo que yo he entendido, pero se admiten correcciones.

Saludos


Genial, entonces el proceso consta en generar y firmar xml cuando en mi sistsema se genera la factura y en el momento de imprimir/enviar la factura envio a TBAI y recupero codigo y QR para insertar en el documento.


Gracias

Galaxian 04-05-2021 12:30:19

Cita:

Empezado por keys (Mensaje 540783)
Hola.

Yo entiendo que hablas de un suplido.

En el caso en el que, en una factura, por ejemplo, además de los honorarios de un profesional se documentase un suplido, el importe del suplido no formaría parte de la base imponible del IVA pero estas facturas deben cumplir igualmente los requisitos TicketBAI.

En cambio, si únicamente se quisiera documentar un suplido, se trataría de una operación en la que no se documenta ninguna entrega de bienes ni prestación de servicios realizada por el profesional en el desarrollo de su actividad, ya que en estos casos el empresario actuaría como mero intermediario, satisfaciendo sumas de dinero del cliente a un tercero por mandato expreso. En este caso, no habría obligación de expedir factura de acuerdo con lo dispuesto en el Reglamento de facturación, pudiéndose documentar la operación en un recibo u otro justificante. Por tanto, un documento en el que solo se informe de un suplido quedaría fuera del ámbito de aplicación de TicketBAI.

Luego en el 140 tiene que ir como un ingreso sin factura.

No es un suplido ya que no es el vendedor el que pone el dinero sino el cliente. En todo caso sería un cobro por adelantado.

Me da que esto va a ser trabajo del gestor, que es el que lo declarará en 140 1.2.

keys 04-05-2021 12:51:31

Segun dice en la pregunta

Cita:

En estos casos, el programa genera una factura simplificada por importe cero, pues las ventas a comisión no son ventas propias del negocio:
Por lo tanto es un servicio que realiza otra empresa pero lo paga a través de ti. Si no estoy equivovado es un suplido.

Cita:

Un gasto suplido es un gasto que un profesional tiene a cuenta de un tercero. Es decir, se trata de un gasto que el profesional paga en nombre del cliente, quien posteriormente asumirá este importe

Galaxian 04-05-2021 13:06:18

Cita:

Empezado por keys (Mensaje 540791)
Un gasto suplido es un gasto que un profesional tiene a cuenta de un tercero. Es decir, se trata de un gasto que el profesional paga en nombre del cliente, quien posteriormente asumirá este importe

Yo no lo veo así: no es el profesional quien adelanta el dinero sino el que lo cobra del cliente, por lo que sería un cobro por adelantado del que detraerá una parte por su servicio. Es decir, un cobro por adelantado por partida doble: una parte por cuenta del vendedor y otra por cuenta propia al haber cobrado por adelantado su comisión, de la que deberá soportar el IVA y la retención.

Neftali [Germán.Estévez] 04-05-2021 15:32:24

Hola batuzail.
Te recomiendo que te leas todo el hilo porque te aclarará bastantes dudas. Hay bastantes mensajes, pero creo que vale la pena el tiempo.

Aquí también tienes un documento con preguntas y respuestas que han publicado desde TicketBAI/Batuz. Ahí también aclaran muchos conceptos.
Por lo demás en cuanto a las preguntas, este gráfico aclara bastante bien los pasos del proceso.

Cita:

Empezado por batuzail (Mensaje 540785)
En mi caso generamos la facturas una vez a la semana, generamos cientos de facturas.
¿Hay que realizar una petición por factura y asignar a ese documento el código y el QR en el momento de la generación?

Alava y Guipuzcoa exigen que el envío sea inmediato tras generar la factura. Cuando generes la factura debes crear el XML, firmarlo y enviarlo (para ello has generado el IDTBAI). Revisa los pasos del gráfico anterior.

Cita:

Empezado por batuzail (Mensaje 540785)
¿Se pueden generar las facturas y posteriormente acceder a ticketbai cuando se imprimen?

Si te refieres a enviarlas a la administración, tal y como te he dicho, el envío debe ser en el momento de realizar la factura.
La impresión en papel/EMail/PDF/... (con el QR y el Id de TBAI incluídos) puede ser posterior.

bilbur 04-05-2021 19:06:37

Cita:

Empezado por keys (Mensaje 540786)
El problema es que ese tipo de iva es un iva no sujeto y como envies una factura con importe cero y con iva no sujeto TBAI da error. A nosotros ya se nos planteo el caso y nos contestaron con lo que he puesto antes. Si el importe es cero y no hay base imponible hay que generar un recibo y no una factura.

Sólo se puede incluir en una factura que tenga otros conceptos con base imponible a iva.


Para complicarlo más:
Factura emitida base imponible cero
Código:

<Factura>
<CabeceraFactura>
<SerieFactura>FV</SerieFactura>
<NumFactura>005864</NumFactura>
<FechaExpedicionFactura>04-05-2021</FechaExpedicionFactura>
<HoraExpedicionFactura>18:48:34</HoraExpedicionFactura>
<FacturaEmitidaSustitucionSimplificada>N</FacturaEmitidaSustitucionSimplificada>
</CabeceraFactura>
<DatosFactura>
<FechaOperacion>04-05-2021</FechaOperacion>
<DescripcionFactura>FACTURA DE VENTA</DescripcionFactura>
<ImporteTotalFactura>0.00</ImporteTotalFactura>
<Claves>
<IDClave>
<ClaveRegimenIvaOpTrascendencia>01</ClaveRegimenIvaOpTrascendencia>
</IDClave>
</Claves>
</DatosFactura>
<TipoDesglose>
<DesgloseFactura>
<Sujeta>
<NoExenta>
<DetalleNoExenta>
<TipoNoExenta>S1</TipoNoExenta>
<DesgloseIVA>
<DetalleIVA>
<BaseImponible>0.00</BaseImponible>
<TipoImpositivo>4.00</TipoImpositivo>
<CuotaImpuesto>0.00</CuotaImpuesto>

</DetalleIVA>
</DesgloseIVA>
</DetalleNoExenta>
</NoExenta>
</Sujeta>
</DesgloseFactura>
</TipoDesglose>
</Factura>

Respuesta TBAI BIZ


Código:

<Registro>
<Identificador>
<IDFactura>
<SerieFactura>FV</SerieFactura>
<NumFactura>005864</NumFactura>
<FechaExpedicionFactura>04-05-2021</FechaExpedicionFactura>
</IDFactura>
</Identificador>
<SituacionRegistro>
<EstadoRegistro>Correcto</EstadoRegistro>
</SituacionRegistro>
 </Registro>

Parece ser que SI se pueden enviar facturas con base imponible, cuotas y total cero
He probado con facturas simplificadas total cero
Código:

<Registro>
<Identificador>
<IDFactura>
<SerieFactura>SV</SerieFactura>
<NumFactura>000444</NumFactura>
<FechaExpedicionFactura>04-05-2021</FechaExpedicionFactura>
</IDFactura>
</Identificador>
<SituacionRegistro>
<EstadoRegistro>Correcto</EstadoRegistro>
</SituacionRegistro>
</Registro>

También correcto en TBAi BIZ


Es posible que no tenga que ver con vuestra casuística, las pruebas son venta de bienes, no prestaciones de servicios.


Un saludo

keys 05-05-2021 08:09:54

Hola.

Una factura con base 0 si puedes mandar. Lo que no puedes mandar es una factura con solo una línea de no sujeta. Que es como se envía un suplido.

Elyas 05-05-2021 09:59:12

Yo sigo liado con los encadenamientos xDD, cuál de estos supuestos vale y cuál de estos ,no?

Pj.

Sería correcto hacerlo así OPCION A:

1.Registramos la factura 001, recibida
2.Registramos la factura 002, rechazadas (pj por fallo del sistema ticketbai)
3.Registramos la factura 003 y la encadenamos a la última factura recibida en TicketBAI, la 001, recibida
4.Se arregla el error y ahora volvemos a mandar la 002 encadenando a la última factura registrada en ticketbai, la 003.

O hay que hacerlo tal que así OPCIÓN B:

1.Registramos la factura 001, recibida
2.Registramos la factura 002, rechazada (pj por fallo del sistema ticketbai)
3.Registramos la factura 003, encadenando a la 002, obtenemos el warning.
4.Volvemos a enviar la 002 encadenada a la 001.

p.d. me han dichos los de ticketbai que van a sacar pronto nueva versión con opción de subsanar facturas. cómo es posible que a estas alturas metan nuevas funciones posiblemente tan vitales? en fin

tejano 05-05-2021 10:58:07

Factura encadenada
 
Cita:

Empezado por Elyas (Mensaje 540804)
Yo sigo liado con los encadenamientos xDD, cuál de estos supuestos vale y cuál de estos ,no?

Pj.

Sería correcto hacerlo así OPCION A:

1.Registramos la factura 001, recibida
2.Registramos la factura 002, rechazadas (pj por fallo del sistema ticketbai)
3.Registramos la factura 003 y la encadenamos a la última factura recibida en TicketBAI, la 001, recibida
4.Se arregla el error y ahora volvemos a mandar la 002 encadenando a la última factura registrada en ticketbai, la 003.

O hay que hacerlo tal que así OPCIÓN B:

1.Registramos la factura 001, recibida
2.Registramos la factura 002, rechazada (pj por fallo del sistema ticketbai)
3.Registramos la factura 003, encadenando a la 002, obtenemos el warning.
4.Volvemos a enviar la 002 encadenada a la 001.

p.d. me han dichos los de ticketbai que van a sacar pronto nueva versión con opción de subsanar facturas. cómo es posible que a estas alturas metan nuevas funciones posiblemente tan vitales? en fin

Os adjunto respuesta de Batuz con respuesta a las encadenadas. Nosotros tenemos varias delegaciones y cada delegación una serie de factura y no tienen las facturas del resto de delegaciones, por lo que el encadenamiento lo vamos a realizar en base a la serie.

Contestación de Batuz:

El sistema TicketBAI implica que cada factura debe identificar la factura anterior (serie, número, fecha y 100 primeros caracteres de la firma). Aunque en el esquema del fichero TicketBAI estos campos no son obligatorios, esta circunstancia se debe a que existen varias situaciones en los que este requisito no es exigible (p. ej. la primera factura, o en caso de rotura del dispositivo).

El cumplimiento del requisito de encadenamiento depende de la forma de funcionamiento del sistema de facturación:
- Si un mismo dispositivo emite facturas de diferentes series, el requisito de encadenamiento debe identificar la factura anterior expedida, con independencia de la serie (por ejemplo, a una factura de una serie ordinaria le puede preceder una factura rectificativa de otra serie).
- Igualmente, si el sistema de facturación es un sistema centralizado que genera las series y los números de facturación, con independencia del dispositivo que se esté utilizando, el encadenamiento se refiere a la última factura expedida por el sistema.
En concreto, “Haz tu factura” funcionaría como un sistema de facturación “centralizado”, por lo que encadena las facturas independientemente de la serie.
- No obstante, si existen diferentes dispositivos o centros de facturación, los cuales sólo expiden facturas dentro de una serie específica para cada dispositivo o centro, el encadenamiento se produce dentro de cada serie.
Este último parece ser el caso al que se refiere la pregunta planteada.


La franja horaria es GMT +2. Ahora son las 05:52:35.

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