Hola a todos,
Comparto un ejemplo funcional en para la generaci贸n del C贸digo QR exigido por la AEAT en el marco de Veri*factu.
Este c贸digo muestra c贸mo construir la URL de validaci贸n (verificable y no verificable), codificar correctamente los par谩metros en UTF-8 mediante UrlEncode() y generar la imagen BMP del QR usando SimpleCodeGenerator.exe.
Incluye:📌 Nota: El c贸digo se publica solo como ejemplo educativo, sin garant铆as para uso en producci贸n.
Espero que sea 煤til para quien est茅 implementando Veri*factu o necesite entender el formato del QR de la AEAT.
Cualquier mejora, duda o sugerencia es bienvenida.
Un abrazo,
Biel Maim贸
Comparto un ejemplo funcional en para la generaci贸n del C贸digo QR exigido por la AEAT en el marco de Veri*factu.
Este c贸digo muestra c贸mo construir la URL de validaci贸n (verificable y no verificable), codificar correctamente los par谩metros en UTF-8 mediante UrlEncode() y generar la imagen BMP del QR usando SimpleCodeGenerator.exe.
Incluye:
- Ejemplos de facturas verificables y no verificables.
- Funci贸n UrlEncode() compatible con UTF-8.
- Funci贸n GeneraQRBmp()
- Preparaci贸n de datos de ejemplo en CargaFra() para pruebas r谩pidas.
/*
Ejemplo de generaci贸n de c贸digo QR AEAT - Veri*factu
漏 2025 Biel Maim贸
*/
#include "FiveWin.ch"
#define VERI_ENV_PROD .F. // .T. = Producci贸n | .F. = Pruebas
//-----------------------------------------------------------------------------
FUNCTION Main(...)
LOCAL hLisFra:={=>}, hFra, cId, cFile
CargaFra(hLisFra) //Carga facturas en un hash, modificando esta funci贸n puede cargar de cualquier origen
FOR EACH cId IN hLisFra:Keys()
hFra=hLisFra[ cId ]
FormatFra(hFra)
hFra["QR" ] := GeneraQRTxt( hFra )
cFile := NombreArchivo( hFra )
GeneraQrBmp( hFra["QR"], cFile )
MsgInfo(hFra["QR"]," Factura "+hFra["serie"]+"|"+hFra["numero"])
NEXT
RETURN NIL
//-----------------------------------------------------------------------------
STATIC FUNCTION GeneraQRTxt( hFra )
LOCAL lVerif,cBase,cQr
lVerif := IIF( HB_HHasKey( hFra, "verificable" ), hFra["verificable"], .F. )
cBase := QRaeatUrl( lVerif )
cQr := cBase +;
"?nif=" + UrlEncode( hFra["nif"] ) + ;
"&numserie=" + UrlEncode( hFra["serie"] + hFra["numero"] ) + ;
"&fecha=" + UrlEncode( Transform( hFra["fecha"], "DD-MM-YYYY") ) + ;
"&importe=" + UrlEncode( _Dec2( Val( hFra["importe"] ) / 100 ) )
RETURN cQr
//-----------------------------------------------------------------------------
STATIC FUNCTION GeneraQRBmp( cQr, cFile, nScale )
LOCAL cExe, cCommand
hb_Default( @nScale, 5 )
cExe := "SimpleCodeGenerator.exe"
cFile+='.BMP'
cQr := StrTran( cQr, '"', '\"' )
cCommand := cExe + ' /ErrorCorrection 2 /save "' + cQr + '" "' + cFile + '" ' + LTrim(Str(nScale))
FErase(cFile)
hb_processRun( cCommand )
RETURN File( cFile ) //Verdadero si se ha creado el fichero
//------------------------------------------------------------------------------
STATIC FUNCTION QRAeatURL( lVerificable )
LOCAL cUrl
IF lVerificable
cUrl:=IIF( VERI_ENV_PROD, ;
"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR", ;
"https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR" )
ELSE
cUrl:=IIF( VERI_ENV_PROD, ;
"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu", ;
"https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu" )
ENDIF
RETURN cURl
//------------------------------------------------------------------------------
// Formatea un n煤mero como "21.00" sin ceros a la izquierda
STATIC FUNCTION _Dec2( nVal )
LOCAL nCents, lNeg, nAbs, nInt, nFrac, cOut
nCents := Int( nVal * 100 + IIF( nVal >= 0, 0.5, -0.5 ) )
lNeg := ( nCents < 0 ) // Bandera para negativo
nAbs := Abs( nCents ) // Valor absoluto
nInt := Int( nAbs / 100 ) // Parte entera
nFrac := nAbs % 100 // Parte fraccionaria
cOut := ""
IF lNeg
cOut += "-"
ENDIF
cOut += LTrim( Str( nInt, 0 ) ) + "." + StrZero( nFrac, 2 )
RETURN cOut
//-----------------------------------------------------------------------------
STATIC FUNCTION CargaFra( hLisFra )
LOCAL cID
//--Fra 1
cID := "Fra1"
hLisFra[ cID ] := {=>}
hLisFra[ cID ][ "nif" ] := "B12345678"
hLisFra[ cID ][ "serie" ] := "F2025"
hLisFra[ cID ][ "numero" ] := 100
hLisFra[ cID ][ "tipo" ] := 'F1'
hLisFra[ cID ][ "operacion" ] := '01'
hLisFra[ cID ][ "nifReceptor" ] := "12345678Z"
hLisFra[ cID ][ "fecha" ] := CTOD("02/08/2025")
hLisFra[ cID ][ "horaUtc" ] := '12:21:12'
hLisFra[ cID ][ "importe" ] := 135.32
hLisFra[ cID ][ "desglose" ] := {}
hLisFra[ cID ][ "verificable" ] := .T.
AddIva( hLisFra[cID],21,107.22,22.52,5.20,5.58)
//--Fra 2
cID := "Fra2"
hLisFra[ cID ] := {=>}
hLisFra[ cID ][ "nif" ] := "B12345678"
hLisFra[ cID ][ "serie" ] := "F2025"
hLisFra[ cID ][ "numero" ] := 101
hLisFra[ cID ][ "tipo" ] := 'F1'
hLisFra[ cID ][ "operacion" ] := '01'
hLisFra[ cID ][ "nifReceptor" ] := "43052326H"
hLisFra[ cID ][ "fecha" ] := CTOD("02/08/2025")
hLisFra[ cID ][ "horaUtc" ] := '15:14:00'
hLisFra[ cID ][ "importe" ] :=917.7
hLisFra[ cID ][ "desglose" ] := {}
hLisFra[ cID ][ "verificable" ] := .F.
AddIva( hLisFra[cID],21,805,169.05)
AddIva( hLisFra[cID],10,452,45.2)
AddIva( hLisFra[cID],21,805,169.05)
//--Fra 3
cID := "Fra3"
hLisFra[ cID ] := {=>}
hLisFra[ cID ][ "nif" ] := "B12345678"
hLisFra[ cID ][ "serie" ] := "F2025"
hLisFra[ cID ][ "numero" ] := 102
hLisFra[ cID ][ "tipo" ] := 'F2'
hLisFra[ cID ][ "operacion" ] := '01'
hLisFra[ cID ][ "nifReceptor" ] := ""
hLisFra[ cID ][ "fecha" ] := CTOD("03/08/2025")
hLisFra[ cID ][ "horaUtc" ] := '20:20:12'
hLisFra[ cID ][ "importe" ] := 77.44
hLisFra[ cID ][ "desglose" ] := {}
hLisFra[ cID ][ "verificable" ] := .T.
AddIva( hLisFra[cID],21,64,13.44)
RETURN NIL
//------------------------------------------------------------------------------
STATIC FUNCTION AddIva( hFra, nTipo, nBase, nCuotaIva, nTipoR, nCuotaR )
LOCAL h,lAdd
h:=FindTipo( hFra, nTipo )
lAdd:=IIf(h[ "tipo" ]==0,.T.,.F.)
hb_Default( @nTipoR, 0)
h[ "tipo" ] := nTipo
h[ "base" ] += nBase
h[ "cuotaIva"] += nCuotaIva
IF nTipoR> 0
h[ "TipoR" ] := nTipoR
h[ "CuotaR" ] += nCuotaR
ENDIF
IF lAdd
AAdd( hFra[ "desglose" ], h )
ENDIF
RETURN NIL
//------------------------------------------------------------------------------
STATIC FUNCTION FindTipo( hFra, nTipo )
LOCAL i, h
FOR i := 1 TO Len( hFra["desglose"] )
IF hFra["desglose"][i]["tipo"] == nTipo
h:=hFra["desglose"][i]
EXIT
ENDIF
NEXT
IF h==NIL
h:={=>}
h[ "tipo" ] := 0
h[ "base" ] := 0
h[ "cuotaIva"] := 0
h[ "TipoR" ] := 0
h[ "CuotaR" ] := 0
ENDIF
RETURN h
//-----------------------------------------------------------------------------
STATIC FUNCTION FormatFra( hFra )
hFra["numero"] := StrZero( hFra["numero"], 10 )
// Fecha ISO (YYYY-MM-DD)
hFra["fechaISO"] := Str( Year( hFra["fecha"] ), 4 ) + "-" + ;
StrZero( Month( hFra["fecha"] ), 2 ) + "-" + ;
StrZero( Day( hFra["fecha"] ), 2 )
// Importe en c茅ntimos como entero de 12 d铆gitos
hFra["importe"] := STRZERO( INT( hFra["importe"] * 100 ), 12 )
RETURN NIL
//-----------------------------------------------------------------------------
FUNCTION NombreArchivo( hFra )
RETURN AllTrim( hFra["nif" ] ) + "_" + ;
AllTrim( hFra["serie" ] ) + "_" + ;
AllTrim( hFra["numero"] ) + "_" + ;
DToS( hFra["fecha"])
//------------------------------------------------------------------------------
STATIC FUNCTION UrlEncode( cIn )
LOCAL nLen,cOut,nI,cChar,cHex
IF ! HB_IsString( cIn )
RETURN ""
ENDIF
cIn := hb_StrToUTF8( cIn )
nLen := Len( cIn )
cOut := ""
cHex := "0123456789ABCDEF"
FOR nI := 1 TO nLen
cChar := SubStr( cIn, nI, 1 )
IF IsUnreserved( cChar )
cOut += cChar
ELSE
cOut += "%" + ;
SubStr( cHex, Int( Asc( cChar ) / 16 ) + 1, 1 ) + ;
SubStr( cHex, ( Asc( cChar ) % 16 ) + 1, 1 )
ENDIF
NEXT
RETURN cOut
//-----------------------------------------------------------------------------
STATIC FUNCTION IsUnreserved( cChar )
RETURN ( cChar >= "A" .AND. cChar <= "Z" ) .OR. ;
( cChar >= "a" .AND. cChar <= "z" ) .OR. ;
( cChar >= "0" .AND. cChar <= "9" ) .OR. ;
cChar == "-" .OR. cChar == "_" .OR. cChar == "." .OR. cChar == "~"Cualquier mejora, duda o sugerencia es bienvenida.
Un abrazo,
Biel Maim贸
Biel Maim贸
https://henkobusiness.com/
https://henkobusiness.com/