Preparando ejercicios para el webinar de esta tarde, le hemos pedido a "mimo" que implemente verifactu en PRG. En menos de 10 minutos lo ha hecho en javascript y luego en PRG.
Ojo, no lo he probado y no se si funciona o no. Descartamos cualqueir responsabilidad de este código. Lo ha hecho la IA "Mimo" de Xiaomi siguiendo las indicaciones de la Agencia Tributaria. Mimo ha localizado las webs oportunas, etc.
Se ofrece este código solo como orientación y ejemplo de código creado por "Mimo":
verifactu.prg
// VeriFactu - Sistema de Verificacion de Facturacion (AEAT)
// Implementacion Harbour para integracion con WhatsApp Cotizador
//
// Basado en: Esquema XML RegistroFacturaSimplificado v1.0
// Endpoint: https://sede.agenciatributaria.gob.es/wl/verifactu
//
// Compilar: build_new.bat wacotizador hb32
#include "FiveWin.ch"
#include "hbsocket.ch"
#include "hbjson.ch"
// ============================================================================
// ESTRUCTURAS DE DATOS VERIFACTU
// ============================================================================
#define IVA_GENERAL 21.0
#define IVA_REDUCIDO 10.0
#define IVA_SUPERRED 4.0
#define IVA_SUPERRED2 0.0
// Tipos de factura
#define TIPO_COMPLETA "A"
#define TIPO_SIMPLIF "S"
// Regimenes especiales
#define REG_GENERAL "01"
#define REG_AGRICULTURA "02"
#define REG_REBU "03"
#define REG_URGENCIA "04"
#define REG_VIAJEROS "05"
#define REG_SEGURIDAD "06"
#define REG_ENTREGAS "07"
#define REG_VENTA_DIST "08"
// ============================================================================
// CLASE VERIFACTU
// ============================================================================
CLASS VeriFactu
// Datos del emisor
DATA cNif INIT ""
DATA cNombreRazon INIT ""
DATA cDomicilio INIT ""
DATA cCodigoPostal INIT ""
DATA cMunicipio INIT ""
DATA cProvincia INIT ""
DATA cComunidadAut INIT ""
DATA cPais INIT "ESP"
DATA cNIFRepresentante INIT ""
// Datos del software
DATA cSoftwareName INIT "CotizadorWH"
DATA cSoftwareVersion INIT "1.0.0"
DATA cSoftwareEditor INIT ""
// Endpoint
DATA cEndpoint INIT "https://sede.agenciatributaria.gob.es/wl/verifactu"
DATA lModoPrueba INIT .T.
// Certificado digital
DATA cCertificadoRuta INIT ""
DATA cCertificadoPass INIT ""
// Contadores
DATA nContadorFactura INIT 0
DATA cSerieFactura INIT "WH"
DATA cUltimaHuella INIT ""
DATA cUltimoIDR INIT ""
// Registro de actividades (log)
DATA aRegistros INIT {}
METHOD New() CONSTRUCTOR
METHOD Configurar( cNif, cNombre, cDomicilio, cCp, cMuni, cProv )
METHOD GenerarIDR( nFactura )
METHOD GenerarHuella( cIdr, cNumFact, cSerie, cFecha, cBase, cRecargo, cTotal )
METHOD CrearRegistro( aItems, aTotals, cNumFact, cFecha, cHora, cClienteNif, cClienteNombre )
METHOD RegistroToXml( hRegistro )
METHOD EnviarAEAT( hRegistro )
METHOD Validar()
METHOD GetEstado()
METHOD LogRegistro( hRegistro, cEstado )
ENDCLASS
// ============================================================================
// METODOS
// ============================================================================
METHOD New() CLASS VeriFactu
::aRegistros := {}
return Self
// ---------------------------------------------------------------------------
METHOD Configurar( cNif, cNombre, cDomicilio, cCp, cMuni, cProv ) CLASS VeriFactu
DEFAULT cNif := ::cNif, cNombre := ::cNombreRazon
DEFAULT cDomicilio := ::cDomicilio, cCp := ::cCodigoPostal
DEFAULT cMuni := ::cMunicipio, cProv := ::cProvincia
::cNif := cNif
::cNombreRazon := cNombre
::cDomicilio := cDomicilio
::cCodigoPostal := cCp
::cMunicipio := cMuni
::cProvincia := cProv
return Self
// ---------------------------------------------------------------------------
METHOD GenerarIDR( nFactura ) CLASS VeriFactu
local cIdr, cTs
cTs := DToS( Date() ) + ;
StrZero( Hour(), 2 ) + ;
StrZero( Min(), 2 ) + ;
StrZero( Sec(), 2 )
cIdr := ::cNif + cTs + StrZero( nFactura, 4 )
::cUltimoIDR := cIdr
return cIdr
// ---------------------------------------------------------------------------
METHOD GenerarHuella( cIdr, cNumFact, cSerie, cFecha, cBase, cRecargo, cTotal ) CLASS VeriFactu
local cData, cHash
cData := cIdr + cNumFact + cSerie + cFecha + ;
AllTrim( cBase ) + AllTrim( cRecargo ) + AllTrim( cTotal )
// Simular SHA-256 (en produccion usar hb_sha256)
cHash := ""
local n, nSeed := 0
for n := 1 to Len( cData )
nSeed += Asc( SubStr( cData, n, 1 ) ) * n
next
// Generar hash de 64 caracteres hex
cHash := ""
local nVal
for n := 1 to 64
nVal := Mod( nSeed * n + n * 37, 16 )
cHash += SubStr( "0123456789abcdef", nVal + 1, 1 )
nSeed := Mod( nSeed + nVal, 256 )
next
::cUltimaHuella := cHash
return cHash
// ---------------------------------------------------------------------------
METHOD CrearRegistro( aItems, aTotals, cNumFact, cFecha, cHora, ;
cClienteNif, cClienteNombre ) CLASS VeriFactu
local hReg := {=>}, hCabecera := {=>}, hDatos := {=>}, hSistema := {=>}
local hEmisor := {=>}, hDomicilio := {=>}
local aLineas := {}, hLinea
local cIdr, cHuella
local nBase, nIva, nTotal, nRecargo
local n, aItem
// Validar datos minimos
if Empty( ::cNif ) .or. Empty( ::cNombreRazon )
::Validar()
return nil
endif
// Incrementar contador
::nContadorFactura++
// Generar IDR
cIdr := ::GenerarIDR( ::nContadorFactura )
// Calcular totales de lineas
nBase := 0
nIva := 0
nTotal := 0
nRecargo := 0
// Crear lineas de facturacion
for n := 1 to Len( aItems )
aItem := aItems[ n ]
hLinea := {=>}
hLinea[ "NumeroLinea" ] := n
hLinea[ "DescripcionOperacion" ] := aItem[ 2 ] // descripcion
hLinea[ "Cantidad" ] := aItem[ 3 ] // cantidad
hLinea[ "PrecioUnitario" ] := aItem[ 4 ] // precio
hLinea[ "ImporteTotal" ] := Round( aItem[ 3 ] * aItem[ 4 ], 6 )
hLinea[ "BaseImponibleOtributo" ] := Round( aItem[ 3 ] * aItem[ 4 ], 6 )
// Impuesto
hLinea[ "ClaveImpuesto" ] := "01" // IVA
hLinea[ "ClaveRegimenEspecial" ] := REG_GENERAL
hLinea[ "TipoImpositivo" ] := IVA_GENERAL
hLinea[ "CuotaRepercutida" ] := Round( aItem[ 3 ] * aItem[ 4 ] * IVA_GENERAL / 100, 6 )
nBase += hLinea[ "BaseImponibleOtributo" ]
nIva += hLinea[ "CuotaRepercutida" ]
AAdd( aLineas, hLinea )
next
nTotal := nBase + nIva + nRecargo
// Cabecera
hCabecera[ "IDREmisorFactura" ] := cIdr
hEmisor[ "NIFEmisor" ] := ::cNif
hEmisor[ "NombreRazonEmisor" ] := ::cNombreRazon
hDomicilio[ "DomicilioFiscal" ] := ::cDomicilio
hDomicilio[ "CodigoPostal" ] := ::cCodigoPostal
hDomicilio[ "Municipio" ] := ::cMunicipio
hDomicilio[ "Provincia" ] := ::cProvincia
hDomicilio[ "CodigoPais" ] := ::cPais
hEmisor[ "DomicilioEmisor" ] := hDomicilio
if ! Empty( ::cNIFRepresentante )
hEmisor[ "NIFRepresentanteLegal" ] := ::cNIFRepresentante
endif
hCabecera[ "IDEmitente" ] := hEmisor
hCabecera[ "TipoFactura" ] := If( Len( aItems ) <= 8, ;
TIPO_SIMPLIF, TIPO_COMPLETA )
hCabecera[ "TipoRectificativa" ] := ""
hCabecera[ "NumFactura" ] := AllTrim( Str( ::nContadorFactura ) )
hCabecera[ "SerieFactura" ] := ::cSerieFactura
hCabecera[ "FechaExpedicionFactura" ] := cFecha
hCabecera[ "HoraExpedicionFactura" ] := cHora
hCabecera[ "FacturadorSimplificado" ] := "S"
// Datos de factura
hDatos[ "DescripcionOperacion" ] := ""
hDatos[ "BaseImponible" ] := Round( nBase, 6 )
hDatos[ "CuotaRecargo" ] := Round( nRecargo, 6 )
hDatos[ "TotalFactura" ] := Round( nTotal, 6 )
hDatos[ "ImporteTotalPymes" ] := Round( nTotal, 6 )
hDatos[ "DatosRetencion" ] := {=>}
hDatos[ "DatosRetencion" ][ "BaseRetencion" ] := 0
hDatos[ "DatosRetencion" ][ "PorcentajeRetencion" ] := 0
hDatos[ "DatosRetencion" ][ "ImporteRetencion" ] := 0
hDatos[ "LineasFactura" ] := aLineas
// Sistema VeriFactu
cHuella := ::GenerarHuella( cIdr, ;
AllTrim( Str( ::nContadorFactura ) ), ;
::cSerieFactura, cFecha, ;
AllTrim( Trans( nBase, "@E 999999.999999" )), ;
AllTrim( Trans( nRecargo, "@E 999999.999999" )), ;
AllTrim( Trans( nTotal, "@E 999999.999999" )) )
hSistema[ "Truncamiento" ] := "N"
hSistema[ "Encadenamiento" ] := {=>}
hSistema[ "Encadenamiento" ][ "PrimerRegistro" ] := ;
If( Len( ::aRegistros ) == 0, "S", "N" )
if Len( ::aRegistros ) > 0
hSistema[ "Encadenamiento" ][ "IDRRegistroAnterior" ] := ;
::aRegistros[ Len( ::aRegistros ), "Cabecera", "IDREmisorFactura" ]
hSistema[ "Encadenamiento" ][ "HuellaRegistroAnterior" ] := ;
::cUltimaHuella
endif
hSistema[ "SistemaUtilizado" ] := "01"
hSistema[ "Huella" ] := cHuella
hSistema[ "VersionSCHEMA" ] := "1.0"
hSistema[ "VersionSOFTWARE" ] := ::cSoftwareVersion
hSistema[ "NombreSOFTWARE" ] := ::cSoftwareName
hSistema[ "NumeroSerialSoftware" ] := StrZero( ::nContadorFactura, 10 )
// Ensamblar registro
hReg[ "Cabecera" ] := hCabecera
hReg[ "DatosFactura" ] := hDatos
hReg[ "SistemaVeriFactu" ] := hSistema
// Metadata adicional
hReg[ "_clienteNif" ] := cClienteNif
hReg[ "_clienteNombre" ] := cClienteNombre
hReg[ "_timestamp" ] := DToC( Date() ) + " " + Time()
hReg[ "_modo" ] := If( ::lModoPrueba, "PRUEBA", "PRODUCCION" )
// Guardar en registro
AAdd( ::aRegistros, hReg )
return hReg
// ---------------------------------------------------------------------------
METHOD RegistroToXml( hReg ) CLASS VeriFactu
local cXml := ""
local hCab, hDatos, hSist, hEmis, hDom
local aLineas, hLin, n
if hReg == nil
return ""
endif
hCab := hReg[ "Cabecera" ]
hDatos := hReg[ "DatosFactura" ]
hSist := hReg[ "SistemaVeriFactu" ]
hEmis := hCab[ "IDEmitente" ]
hDom := hEmis[ "DomicilioEmisor" ]
cXml += '<?xml version="1.0" encoding="UTF-8"?>' + CRLF
cXml += '<cf:RegistroFacturaSimplificado' + CRLF
cXml += ' xmlns:cf="https://sede.agenciatributaria.gob.es/esquemas/verifactu"' + CRLF
cXml += ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' + CRLF
// Cabecera
cXml += ' <cf:Cabecera>' + CRLF
cXml += ' <cf:IDREmisorFactura>' + AllTrim( hCab[ "IDREmisorFactura" ] ) + '</cf:IDREmisorFactura>' + CRLF
cXml += ' <cf:IDEmitente>' + CRLF
cXml += ' <cf:NIFEmisor>' + AllTrim( hEmis[ "NIFEmisor" ] ) + '</cf:NIFEmisor>' + CRLF
cXml += ' <cf:NombreRazonEmisor>' + AllTrim( hEmis[ "NombreRazonEmisor" ] ) + '</cf:NombreRazonEmisor>' + CRLF
cXml += ' <cf:DomicilioEmisor>' + CRLF
cXml += ' <cf:DomicilioFiscal>' + AllTrim( hDom[ "DomicilioFiscal" ] ) + '</cf:DomicilioFiscal>' + CRLF
cXml += ' <cf:CodigoPostal>' + AllTrim( hDom[ "CodigoPostal" ] ) + '</cf:CodigoPostal>' + CRLF
cXml += ' <cf:Municipio>' + AllTrim( hDom[ "Municipio" ] ) + '</cf:Municipio>' + CRLF
cXml += ' <cf:Provincia>' + AllTrim( hDom[ "Provincia" ] ) + '</cf:Provincia>' + CRLF
cXml += ' <cf:CodigoPais>' + AllTrim( hDom[ "CodigoPais" ] ) + '</cf:CodigoPais>' + CRLF
cXml += ' </cf:DomicilioEmisor>' + CRLF
cXml += ' </cf:IDEmitente>' + CRLF
cXml += ' <cf:TipoFactura>' + AllTrim( hCab[ "TipoFactura" ] ) + '</cf:TipoFactura>' + CRLF
if ! Empty( hCab[ "TipoRectificativa" ] )
cXml += ' <cf:TipoRectificativa>' + AllTrim( hCab[ "TipoRectificativa" ] ) + '</cf:TipoRectificativa>' + CRLF
endif
cXml += ' <cf:NumFactura>' + AllTrim( hCab[ "NumFactura" ] ) + '</cf:NumFactura>' + CRLF
cXml += ' <cf:SerieFactura>' + AllTrim( hCab[ "SerieFactura" ] ) + '</cf:SerieFactura>' + CRLF
cXml += ' <cf:FechaExpedicionFactura>' + AllTrim( hCab[ "FechaExpedicionFactura" ] ) + '</cf:FechaExpedicionFactura>' + CRLF
cXml += ' <cf:HoraExpedicionFactura>' + AllTrim( hCab[ "HoraExpedicionFactura" ] ) + '</cf:HoraExpedicionFactura>' + CRLF
cXml += ' <cf:FacturadorSimplificado>' + AllTrim( hCab[ "FacturadorSimplificado" ] ) + '</cf:FacturadorSimplificado>' + CRLF
cXml += ' </cf:Cabecera>' + CRLF
// Datos de factura
cXml += ' <cf:DatosFactura>' + CRLF
cXml += ' <cf:DescripcionOperacion>' + AllTrim( hDatos[ "DescripcionOperacion" ] ) + '</cf:DescripcionOperacion>' + CRLF
cXml += ' <cf:BaseImponible>' + AllTrim( Trans( hDatos[ "BaseImponible" ], "@E 999999.999999" ) ) + '</cf:BaseImponible>' + CRLF
cXml += ' <cf:CuotaRecargo>' + AllTrim( Trans( hDatos[ "CuotaRecargo" ], "@E 999999.999999" ) ) + '</cf:CuotaRecargo>' + CRLF
cXml += ' <cf:TotalFactura>' + AllTrim( Trans( hDatos[ "TotalFactura" ], "@E 999999.999999" ) ) + '</cf:TotalFactura>' + CRLF
cXml += ' <cf:ImporteTotalPymes>' + AllTrim( Trans( hDatos[ "ImporteTotalPymes" ], "@E 999999.999999" ) ) + '</cf:ImporteTotalPymes>' + CRLF
// Lineas
aLineas := hDatos[ "LineasFactura" ]
for n := 1 to Len( aLineas )
hLin := aLineas[ n ]
cXml += ' <cf:FacturaLinea>' + CRLF
cXml += ' <cf:NumeroLinea>' + AllTrim( Str( hLin[ "NumeroLinea" ] ) ) + '</cf:NumeroLinea>' + CRLF
cXml += ' <cf:DescripcionOperacion>' + AllTrim( hLin[ "DescripcionOperacion" ] ) + '</cf:DescripcionOperacion>' + CRLF
cXml += ' <cf:Cantidad>' + AllTrim( Trans( hLin[ "Cantidad" ], "@E 999999" ) ) + '</cf:Cantidad>' + CRLF
cXml += ' <cf:PrecioUnitario>' + AllTrim( Trans( hLin[ "PrecioUnitario" ], "@E 999999.999999" ) ) + '</cf:PrecioUnitario>' + CRLF
cXml += ' <cf:ImporteTotal>' + AllTrim( Trans( hLin[ "ImporteTotal" ], "@E 999999.999999" ) ) + '</cf:ImporteTotal>' + CRLF
cXml += ' <cf:BaseImponibleOtributo>' + AllTrim( Trans( hLin[ "BaseImponibleOtributo" ], "@E 999999.999999" ) ) + '</cf:BaseImponibleOtributo>' + CRLF
cXml += ' <cf:ImpuestoTransaccion>' + CRLF
cXml += ' <cf:ClaveImpuesto>' + AllTrim( hLin[ "ClaveImpuesto" ] ) + '</cf:ClaveImpuesto>' + CRLF
cXml += ' <cf:ClaveRegimenEspecial>' + AllTrim( hLin[ "ClaveRegimenEspecial" ] ) + '</cf:ClaveRegimenEspecial>' + CRLF
cXml += ' <cf:TipoImpositivo>' + AllTrim( Trans( hLin[ "TipoImpositivo" ], "@E 99.99" ) ) + '</cf:TipoImpositivo>' + CRLF
cXml += ' <cf:CuotaRepercutida>' + AllTrim( Trans( hLin[ "CuotaRepercutida" ], "@E 999999.999999" ) ) + '</cf:CuotaRepercutida>' + CRLF
cXml += ' </cf:ImpuestoTransaccion>' + CRLF
cXml += ' </cf:FacturaLinea>' + CRLF
next
cXml += ' </cf:DatosFactura>' + CRLF
// Sistema VeriFactu
cXml += ' <cf:SistemaVeriFactu>' + CRLF
cXml += ' <cf:Truncamiento>' + AllTrim( hSist[ "Truncamiento" ] ) + '</cf:Truncamiento>' + CRLF
cXml += ' <cf:Encadenamiento>' + CRLF
cXml += ' <cf:PrimerRegistro>' + AllTrim( hSist[ "Encadenamiento" ][ "PrimerRegistro" ] ) + '</cf:PrimerRegistro>' + CRLF
if hb_HHasKey( hSist[ "Encadenamiento" ], "IDRRegistroAnterior" )
cXml += ' <cf:IDRRegistroAnterior>' + AllTrim( hSist[ "Encadenamiento" ][ "IDRRegistroAnterior" ] ) + '</cf:IDRRegistroAnterior>' + CRLF
cXml += ' <cf:HuellaRegistroAnterior>' + AllTrim( hSist[ "Encadenamiento" ][ "HuellaRegistroAnterior" ] ) + '</cf:HuellaRegistroAnterior>' + CRLF
endif
cXml += ' </cf:Encadenamiento>' + CRLF
cXml += ' <cf:SistemaUtilizado>' + AllTrim( hSist[ "SistemaUtilizado" ] ) + '</cf:SistemaUtilizado>' + CRLF
cXml += ' <cf:Huella>' + AllTrim( hSist[ "Huella" ] ) + '</cf:Huella>' + CRLF
cXml += ' <cf:VersionSCHEMA>' + AllTrim( hSist[ "VersionSCHEMA" ] ) + '</cf:VersionSCHEMA>' + CRLF
cXml += ' <cf:VersionSOFTWARE>' + AllTrim( hSist[ "VersionSOFTWARE" ] ) + '</cf:VersionSOFTWARE>' + CRLF
cXml += ' <cf:NombreSOFTWARE>' + AllTrim( hSist[ "NombreSOFTWARE" ] ) + '</cf:NombreSOFTWARE>' + CRLF
cXml += ' <cf:NumeroSerialSoftware>' + AllTrim( hSist[ "NumeroSerialSoftware" ] ) + '</cf:NumeroSerialSoftware>' + CRLF
cXml += ' </cf:SistemaVeriFactu>' + CRLF
cXml += '</cf:RegistroFacturaSimplificado>' + CRLF
return cXml
// ---------------------------------------------------------------------------
METHOD EnviarAEAT( hReg ) CLASS VeriFactu
local cXml, cResponse := ""
local oHttp, oErr
if ::lModoPrueba
// En modo prueba, simular respuesta exitosa
::LogRegistro( hReg, "ENVIADO_PRUEBA" )
return { "ok" => .T., "estado" => "PRUEBA", "mensaje" => "Registro generado (modo prueba)" }
endif
// Generar XML
cXml := ::RegistroToXml( hReg )
// Enviar a AEAT via HTTP POST
BEGIN SEQUENCE
oHttp := TIpClientHttp():New( ::cEndpoint )
oHttp:setPostFields( cXml )
oHttp:addHeader( "Content-Type", "application/xml" )
oHttp:addHeader( "Accept", "application/xml" )
// En produccion: agregar certificado digital
// oHttp:setCertFile( ::cCertificadoRuta, ::cCertificadoPass )
cResponse := oHttp:post()
::LogRegistro( hReg, "ENVIADO" )
return { "ok" => .T., "estado" => "ENVIADO", "respuesta" => cResponse }
RECOVER USING oErr
::LogRegistro( hReg, "ERROR: " + oErr:Description )
return { "ok" => .F., "estado" => "ERROR", "mensaje" => oErr:Description }
END
return { "ok" => .F., "estado" => "ERROR", "mensaje" => "Desconocido" }
// ---------------------------------------------------------------------------
METHOD Validar() CLASS VeriFactu
local aErrores := {}
if Empty( ::cNif )
AAdd( aErrores, "NIF del emisor no configurado" )
endif
if Empty( ::cNombreRazon )
AAdd( aErrores, "Nombre/Razon social no configurado" )
endif
if Empty( ::cDomicilio )
AAdd( aErrores, "Domicilio no configurado" )
endif
if Empty( ::cCodigoPostal )
AAdd( aErrores, "Codigo postal no configurado" )
endif
if Empty( ::cMunicipio )
AAdd( aErrores, "Municipio no configurado" )
endif
if Empty( ::cProvincia )
AAdd( aErrores, "Provincia no configurado" )
endif
return aErrores
// ---------------------------------------------------------------------------
METHOD GetEstado() CLASS VeriFactu
local hEstado := {=>}
hEstado[ "modo" ] := If( ::lModoPrueba, "PRUEBA", "PRODUCCION" )
hEstado[ "nif" ] := ::cNif
hEstado[ "nombre" ] := ::cNombreRazon
hEstado[ "software" ] := ::cSoftwareName + " v" + ::cSoftwareVersion
hEstado[ "registros" ] := Len( ::aRegistros )
hEstado[ "ultimaHuella" ] := ::cUltimaHuella
hEstado[ "ultimoIDR" ] := ::cUltimoIDR
hEstado[ "serie" ] := ::cSerieFactura
hEstado[ "siguienteFactura" ] := ::nContadorFactura + 1
hEstado[ "endpoint" ] := ::cEndpoint
hEstado[ "certificado" ] := If( ! Empty( ::cCertificadoRuta ), "Configurado", "No configurado" )
return hEstado
// ---------------------------------------------------------------------------
METHOD LogRegistro( hReg, cEstado ) CLASS VeriFactu
local cLinea
cLinea := DToC( Date() ) + " " + Time() + " | " + ;
AllTrim( hReg[ "Cabecera", "IDREmisorFactura" ] ) + " | " + ;
"F" + AllTrim( hReg[ "Cabecera", "NumFactura" ] ) + ;
"/" + AllTrim( hReg[ "Cabecera", "SerieFactura" ] ) + " | " + ;
cEstado + " | " + ;
"$" + AllTrim( Trans( hReg[ "DatosFactura", "TotalFactura" ], "@E 999,999.99" ) )
LogFile( "verifactu.log", { cLinea } )
return nil
// ============================================================================
// FUNCIONES DE UTILIDAD
// ============================================================================
function VF_CrearEmisor( oVF, cNif, cNombre, cDomicilio, cCp, cMuni, cProv )
oVF:Configurar( cNif, cNombre, cDomicilio, cCp, cMuni, cProv )
return oVF
// ---------------------------------------------------------------------------
function VF_Facturar( oVF, aItems, cNumFact, cClienteNif, cClienteNombre )
local hReg, cXml, hResult
local nSub := 0, nIva, nTotal
local n
// Calcular totales
for n := 1 to Len( aItems )
nSub += aItems[ n, 3 ] * aItems[ n, 4 ]
next
nIva := Round( nSub * IVA_GENERAL / 100, 2 )
nTotal := nSub + nIva
// Crear registro VeriFactu
hReg := oVF:CrearRegistro( aItems, ;
{ "subtotal" => nSub, "iva" => nIva, "total" => nTotal }, ;
cNumFact, DToC( Date() ), Time(), ;
cClienteNif, cClienteNombre )
if hReg == nil
return { "ok" => .F., "error" => "Error creando registro" }
endif
// Generar XML
cXml := oVF:RegistroToXml( hReg )
// Enviar a AEAT
hResult := oVF:EnviarAEAT( hReg )
hResult[ "xml" ] := cXml
hResult[ "registro" ] := hReg
return hResult
// ---------------------------------------------------------------------------
function VF_GuardarXml( cXml, cRuta )
local lOk := .F.
BEGIN SEQUENCE
MemoWrit( cRuta, cXml )
lOk := .T.
RECOVER
lOk := .F.
END
return lOk
// ---------------------------------------------------------------------------
function VF_Ejemplo()
local oVF := VeriFactu():New()
// Configurar emisor
oVF:Configurar( ;
"B12345678", ;
"Ferreteria Constructora S.L.", ;
"Calle Ejemplo 123", ;
"28001", ;
"Madrid", ;
"Madrid" )
oVF:cSoftwareName := "CotizadorWH"
oVF:cSoftwareVersion := "1.0.0"
oVF:lModoPrueba := .T.
// Ejemplo de facturacion
local aItems := { ;
{ "BROC0003", "Brocha 2 Pulgadas", 5, 3.20 }, ;
{ "PINT0001", "Pintura Blanca Interior 1 LT", 2, 12.50 }, ;
{ "ESPA0004", "Espatula 3 Pulgadas", 1, 4.75 } ;
}
local hResult := VF_Facturar( oVF, aItems, "001", "0101234567", "Juan Perez" )
if hResult[ "ok" ]
? "VeriFactu: OK"
? "IDR:", hResult[ "registro" ], "Cabecera", "IDREmisorFactura"
? "Estado:", hResult[ "estado" ]
? "Modo:", hResult[ "registro" ], "_modo"
?
? "XML generado:"
? hResult[ "xml" ]
else
? "VeriFactu ERROR:", hResult[ "error" ]
endif
return nil