Ir al contenido

VeriFactu

Ruta: /owner/settings/verifactu Sistema oficial de la AEAT para el registro electrónico de operaciones de facturación en tiempo real.


Sistema AEAT

VeriFactu (Veificación de Factura) es el sistema oficial de la Agencia Estatal de Administración Tributaria (AEAT) que permite el registro electrónico en tiempo real de las operaciones de facturación. Cumple con el Real Decreto 1007/2023 sobre sistemas informáticos de facturación.

Características principales:

  • Registro automático de facturas en la AEAT al momento de emisión
  • Generación de Código Seguro de Verificación (CSV)
  • Código QR en cada factura para validación
  • Cadena de bloques (blockchain) de registros
  • Cumplimiento normativo RD 1007/2023

Estado actual:

PeriodoObligatoriedad
2025-2027Opcional (sistema voluntario)
Desde 2027Obligatorio para todos los negocios

Para activar VeriFactu en tu negocio necesitas:

RequisitoDescripción
Plan activoPlan Pro o superior con feature verifactu: true
Autorización legalFirma digital del documento de autorización
Opt-inActivación voluntaria en configuración de negocio
Datos fiscales completosNIF, nombre fiscal, región tributaria

IMPORTANTE: VeriFactu es un sistema opt-in hasta 2027. Debes activarlo explícitamente.


El submódulo VeriFactu incluye:

Secciones principales:

SecciónFunción
AutorizaciónFirma digital del documento legal
Estado de envíosMonitoreo de facturas enviadas a AEAT
EstadísticasTasas de éxito, errores, tendencias
ConfiguraciónOpt-in, región fiscal, certificados

Indicadores de estado:

BadgeColorSignificado
SENTVerdeFactura registrada correctamente en AEAT
ERRORRojoError temporal, se reintentará automáticamente
PENDING_RETRYAmarilloEn cola de reintento
FAILEDGrisError permanente, requiere intervención

FunciónDescripciónDocumentación
Autorizar VeriFactuFirma documento legal con DocuSealVer
Activar/DesactivarOpt-in voluntario del sistemaVer
Ver DashboardMonitoreo de envíos y erroresVer
Reintentar EnvíosReenvío masivo de facturas con errorVer
Revocar AutorizaciónCancelación de la autorización legalVer
Descargar LogsExportar registros de envíosVer

┌─────────────────┐
│ Opt-in VeriFactu│
│ (businesses) │
└────────┬────────┘
┌─────────────────┐
│ Firmar │
│ Autorización │
│ (DocuSeal) │
└────────┬────────┘
┌─────────────────┐
│ Emitir Factura │
│ (status: sent) │
└────────┬────────┘
┌─────────────────┐
│ Edge Function │
│ verifactu-send │
└────────┬────────┘
┌─────────────────┐
│ API VeriFactu │
│ (Irene Solutions)│
└────────┬────────┘
┌─────────────────┐
│ AEAT Response │
│ CSV + QR + URL │
└─────────────────┘

Estados de envío:

EstadoDescripciónSiguiente paso
NULLFactura no enviada (borrador o VeriFactu inactivo)Emitir factura
SENTEnviada y aceptada por AEATEstado final
ERRORError temporal (ej: timeout)Reintento automático
PENDING_RETRYEn cola de reintento (max 5 intentos)Esperar reintento
FAILEDError permanente después de 5 reintentosRevisión manual

El CSV es un código único generado por la AEAT que certifica el registro de la factura.

Ejemplo: 1A2B3C4D5E6F7G8H9I0J

Uso:

  • Aparece en el PDF de la factura
  • Permite validación en web de AEAT
  • Prueba de registro electrónico

Cada factura registrada incluye un código QR que contiene:

  • Número de factura
  • NIF del emisor
  • Importe total
  • CSV
  • URL de validación AEAT

Generación:

  • Formato: BMP codificado en Base64
  • Se incluye automáticamente en PDFs
  • Lectura con cualquier app de QR

Enlace directo a la web de la AEAT para verificar el registro:

https://www.agenciatributaria.gob.es/AEAT.internet/Inicio/...?csv=CODIGO

VeriFactu soporta múltiples sistemas tributarios españoles:

SistemaCódigoTerritorioDescripción
IVA01Península, BalearesImpuesto sobre el Valor Añadido
IPSI02Ceuta, MelillaImpuesto sobre Producción, Servicios e Importación
IGIC03CanariasImpuesto General Indirecto Canario
Otros05VariosOtros impuestos especiales

Detección automática:

Factux determina el sistema fiscal basándose en:

  1. Campo tax_system de la factura (si está definido)
  2. Campo default_tax_system del negocio
  3. Campo fiscal_region del negocio

TipoCódigoDescripción
Factura ordinariaF1Factura estándar completa
Factura simplificadaF2Factura simplificada (tickets)
Factura sustitutivaF3Emitida en sustitución de simplificadas
TipoCódigoDescripciónMotivo
Error fundado en derechoR1Corrección de erroresPrecio, cantidad o IVA incorrecto
Art. 80.3 LIVAR2Devolución de mercancíasCliente devuelve productos
Art. 80.4 LIVAR3Créditos incobrablesImpagos confirmados
Resto de causasR4Otras rectificacionesDescuentos posteriores
Rectificativa simplificadaR5Rectificación de factura simplificadaCorrección de tickets

Tipos de rectificación:

CódigoDescripción
SSustitución completa (reemplaza la factura original)
IPor diferencias (solo envía el delta)

ErrorCausaSolución
VeriFactu no disponiblePlan sin feature verifactuActualizar a Plan Pro o superior
Autorización pendienteDocumento no firmadoCompletar firma en DocuSeal
Opt-in inactivoNo activado en businessesActivar en Configuración > VeriFactu
CódigoDescripciónSolución
9999Error interno del servicioReintento automático
TIMEOUTTimeout de conexiónReintento automático
VAL-xxxError de validación de datosRevisar datos de factura y cliente

Detalles técnicos
Tabla: verifactu_authorizations

Almacena las autorizaciones legales firmadas digitalmente.

ColumnaTipoDescripción
idUUIDIdentificador único
business_idUUIDFK a businesses
docuseal_submission_idTEXTID de envío en DocuSeal
docuseal_document_urlTEXTURL del PDF firmado
statusTEXTpending, signed, declined, revoked, expired
signed_atTIMESTAMPTZFecha y hora de firma
declined_atTIMESTAMPTZFecha de rechazo
revoked_atTIMESTAMPTZFecha de revocación
business_nameTEXTSnapshot del nombre del negocio
business_tax_idTEXTSnapshot del NIF del negocio
signer_nameTEXTNombre del firmante
signer_emailTEXTEmail del firmante
legal_textTEXTTexto legal completo del documento
document_hashTEXTSHA-256 del PDF firmado
created_atTIMESTAMPTZFecha de creación
updated_atTIMESTAMPTZÚltima actualización
created_byUUIDFK a auth.users
revoked_byUUIDFK a auth.users (quien revocó)
revocation_reasonTEXTMotivo de revocación

Restricciones:

  • Un negocio solo puede tener UNA autorización activa (pending o signed)
  • Index único: idx_one_active_auth_per_business
Tabla: verifactu_retry_queue

Cola de reintentos para envíos fallidos.

ColumnaTipoDescripción
idUUIDIdentificador único
invoice_idUUIDFK a invoices
operationTEXTcreate o cancel
retry_countINTEGERNúmero de reintentos (max 5)
last_errorTEXTÚltimo mensaje de error
last_retry_atTIMESTAMPTZÚltima tentativa
next_retry_atTIMESTAMPTZPróximo reintento programado
created_atTIMESTAMPTZFecha de creación

Restricción:

  • Unique: (invoice_id, operation)
Columnas VeriFactu en invoices
ColumnaTipoDescripción
verifactu_statusTEXTSENT, ERROR, PENDING_RETRY, FAILED
verifactu_csvTEXTCódigo Seguro de Verificación
verifactu_qr_codeTEXTImagen QR en Base64 (BMP)
verifactu_qr_urlTEXTURL del bitmap QR
verifactu_validation_urlTEXTURL de validación AEAT
verifactu_extern_keyTEXTID de blockchain
verifactu_xml_requestTEXTXML enviado a AEAT
verifactu_xml_responseTEXTXML de respuesta AEAT
verifactu_error_codeTEXTCódigo de error AEAT
verifactu_error_descriptionTEXTDescripción del error
verifactu_sent_atTIMESTAMPTZFecha de envío
verifactu_last_retry_atTIMESTAMPTZÚltimo reintento
verifactu_retry_countINTEGERContador de reintentos
PolíticaTablaRegla
Business members can view authorizationsverifactu_authorizationsMiembros del negocio pueden ver
Business owners can create authorizationsverifactu_authorizationsSolo owners pueden crear
Business owners can update authorizationsverifactu_authorizationsSolo owners pueden actualizar
Service role can manage authorizationsverifactu_authorizationsService role acceso total
Users can view their business retry queueverifactu_retry_queueVer reintentos del negocio
Service role can manage retry queueverifactu_retry_queueService role acceso total
Function: create-verifactu-authorization

Descripción: Crea una nueva autorización y genera el documento de firma con DocuSeal.

Trigger: Usuario (owner) solicita autorización desde UI.

Flujo:

  1. Verifica que usuario sea owner del negocio
  2. Comprueba que no exista autorización activa
  3. Genera texto legal con datos del negocio
  4. Crea submission en DocuSeal con plantilla
  5. Inserta registro en verifactu_authorizations
  6. Retorna embed_url para firma inline

Inputs:

{
business_id: string
}

Output:

{
success: true,
authorization: {
id: string,
embed_url: string,
status: "pending"
}
}

Variables de entorno:

  • DOCUSEAL_API_KEY: API key de DocuSeal
  • DOCUSEAL_TEMPLATE_ID: ID de plantilla de autorización
  • DOCUSEAL_API_URL: URL base API (default: https://api.docuseal.co)
Function: docuseal-webhook

Descripción: Webhook que recibe notificaciones de DocuSeal cuando se completa/rechaza una firma.

Trigger: DocuSeal envía POST cuando cambia estado de documento.

Flujo:

  1. Valida signature del webhook
  2. Extrae submission_id del payload
  3. Busca autorización con ese docuseal_submission_id
  4. Actualiza estado según evento:
    • form.completedstatus = 'signed', signed_at = NOW()
    • form.declinedstatus = 'declined', declined_at = NOW()
  5. Actualiza URL del documento firmado

Eventos soportados:

  • form.completed: Documento firmado
  • form.declined: Usuario rechazó firma
Function: verifactu-send-invoice (dentro de create-invoice/update-invoice)

Descripción: Envía factura a VeriFactu cuando se emite (status: sent).

Trigger:

  • Factura cambia a status sent o paid
  • VeriFactu está habilitado para el negocio

Flujo:

  1. Verifica opt-in del negocio
  2. Verifica autorización firmada
  3. Carga datos: factura, negocio, cliente, items
  4. Transforma a formato VeriFactu API
  5. Determina sistema fiscal (IVA/IGIC/IPSI)
  6. Calcula tax scheme y tax type
  7. Envía a API de Irene Solutions
  8. Procesa respuesta:
    • Éxito (ResultCode = 0): Guarda CSV, QR, URL → verifactu_status = 'SENT'
    • Error: Guarda código y descripción → verifactu_status = 'ERROR' + encola reintento

Variables de entorno:

Function: verifactu-retry

Descripción: Cron job que procesa cola de reintentos.

Trigger: Cron (cada 5 minutos).

Flujo:

  1. Busca entradas en verifactu_retry_queue donde next_retry_at sea anterior o igual a ahora y retry_count sea menor a 5
  2. Para cada entrada:
    • Reintenta envío a VeriFactu
    • Si éxito: elimina de cola, marca factura como SENT
    • Si error: incrementa retry_count, calcula next_retry_at (exponential backoff)
  3. Marca como FAILED si retry_count es 5 o más

Backoff exponencial:

  • Intento 1: +5 min
  • Intento 2: +15 min
  • Intento 3: +30 min
  • Intento 4: +1 hora
  • Intento 5: +2 horas
  • Después de 5: FAILED
Types: VeriFactu API
enum InvoiceType {
F1 = "F1", // Factura ordinaria
F2 = "F2", // Factura Simplificada
F3 = "F3", // Factura emitida en sustitución de facturas simplificadas
R1 = "R1", // Factura Rectificativa: Error fundado en derecho
R2 = "R2", // Factura Rectificativa: Art. 80.3 LIVA (devolución)
R3 = "R3", // Factura Rectificativa: Art. 80.4 LIVA (créditos incobrables)
R4 = "R4", // Factura Rectificativa: Resto de causas
R5 = "R5", // Factura Rectificativa Simplificada
}
type RectificationTypeCode = 'S' | 'I'; // S=Sustitución, I=Por diferencias
interface TaxItem {
Tax?: string; // "01" (IVA), "02" (IPSI), "03" (IGIC), "05" (Otros)
TaxScheme?: string; // Default "01" (Régimen General)
TaxType?: string; // Default "S1" (Sujeta y No exenta)
TaxException?: string; // E1-E6 para operaciones exentas
TaxBase: number; // Base imponible (decimal 12,2)
TaxRate: number; // Tipo impositivo % (decimal 3,2)
TaxAmount: number; // Cuota (decimal 12,2)
TaxRateSurcharge?: number; // Tipo recargo equivalencia (decimal 3,2)
TaxAmountSurcharge?: number; // Cuota recargo (decimal 12,2)
}
interface VeriFactuInvoice {
Status: string; // "DRAFT" or "FINAL"
InvoiceType?: string; // F1, F2, R1-R5 (default F1)
InvoiceID: string; // Número de factura
InvoiceDate: string; // YYYY-MM-DD
SellerID: string; // NIF del negocio (9 chars)
CompanyName: string; // Nombre del negocio (max 120)
RelatedPartyID?: string; // ID del cliente (max 20)
RelatedPartyName?: string; // Nombre del cliente (max 120)
RelatedPartyIDType?: string; // Default "02" (NIF-IVA)
Text?: string; // Descripción (max 500)
TaxItems: TaxItem[];
RectificationType?: string; // "S" o "I" (default "I")
IsInvoiceFix?: boolean; // Subsanación
IsRejected?: boolean; // Subsanación con rechazo previo
RectificationItems?: RectificationItem[];
RectificationTaxBase?: number;
RectificationTaxAmount?: number;
RectificationTaxAmountSurcharge?: number;
}
interface VeriFactuResponse {
ResultCode: number; // 0 = success
ResultMessage?: string;
Return?: {
ExternKey?: string; // Blockchain ID
StatusResponse?: string; // "Correcto"
ErrorCode?: string; // Código error AEAT o "9999" interno
ErrorDescription?: string;
CSV?: string; // Código Seguro de Verificación
QrCode?: string; // Base64 bitmap (.bmp)
Xml?: string; // XML enviado a AEAT
Response?: string; // XML respuesta de AEAT
QrCodeUrl?: string; // URL al bitmap QR
ValidationUrl?: string; // URL validación AEAT
};
}
Verificar estado de VeriFactu para un negocio
-- Comprobar opt-in, plan y autorización
SELECT
b.id,
b.name,
b.verifactu_opt_in,
sp.features->>'verifactu' as plan_feature,
bs.status as subscription_status,
va.status as auth_status,
va.signed_at,
va.revoked_at
FROM businesses b
LEFT JOIN business_subscriptions bs ON bs.business_id = b.id
LEFT JOIN subscription_plans sp ON bs.plan_id = sp.id
LEFT JOIN verifactu_authorizations va ON va.business_id = b.id
AND va.status IN ('pending', 'signed')
WHERE b.id = '<business_id>';

Interpretación:

  • verifactu_opt_in = true → Negocio activó VeriFactu
  • plan_feature = true → Plan incluye VeriFactu
  • subscription_status = 'active' → Suscripción válida
  • auth_status = 'signed' → Autorización completada
  • Si todo OK → VeriFactu operativo
Facturas con errores de VeriFactu
SELECT
i.invoice_number,
i.issue_date,
i.total_amount,
i.verifactu_status,
i.verifactu_error_code,
i.verifactu_error_description,
i.verifactu_retry_count,
i.verifactu_last_retry_at
FROM invoices i
WHERE i.business_id = '<business_id>'
AND i.verifactu_status IN ('ERROR', 'PENDING_RETRY', 'FAILED')
ORDER BY i.issue_date DESC;
Cola de reintentos activa
SELECT
rq.*,
i.invoice_number,
i.issue_date,
i.total_amount
FROM verifactu_retry_queue rq
JOIN invoices i ON i.id = rq.invoice_id
WHERE i.business_id = '<business_id>'
AND rq.retry_count < 5
ORDER BY rq.next_retry_at;
Estadísticas de envíos (últimos 30 días)
SELECT
COUNT(*) FILTER (WHERE verifactu_status = 'SENT') as enviadas,
COUNT(*) FILTER (WHERE verifactu_status = 'ERROR') as con_error,
COUNT(*) FILTER (WHERE verifactu_status = 'PENDING_RETRY') as pendientes,
COUNT(*) FILTER (WHERE verifactu_status = 'FAILED') as fallidas,
COUNT(*) FILTER (WHERE verifactu_status IS NULL) as no_enviadas,
ROUND(
COUNT(*) FILTER (WHERE verifactu_status = 'SENT')::numeric /
NULLIF(COUNT(*) FILTER (WHERE verifactu_status IS NOT NULL), 0) * 100,
2
) as tasa_exito_pct
FROM invoices
WHERE business_id = '<business_id>'
AND status != 'draft'
AND issue_date >= CURRENT_DATE - INTERVAL '30 days';
Distribución de errores
SELECT
verifactu_error_code,
verifactu_error_description,
COUNT(*) as ocurrencias
FROM invoices
WHERE business_id = '<business_id>'
AND verifactu_status IN ('ERROR', 'FAILED')
AND verifactu_error_code IS NOT NULL
GROUP BY verifactu_error_code, verifactu_error_description
ORDER BY ocurrencias DESC
LIMIT 10;

Componente: VeriFactuAuthorizationForm

Ubicación: src/features/invoices/sub-modules/verifactu/components/VeriFactuAuthorizationForm.tsx

Función: Interfaz de gestión de autorizaciones legales.

Features:

  • Creación de nueva autorización
  • Firma embebida con DocuSeal (iframe)
  • Polling automático cada 3s para detectar firma completada
  • Descarga del documento firmado
  • Revocación de autorización

Props: Ninguna (usa contexto de autenticación)

Estados:

  • Sin autorización → Botón “Crear Autorización”
  • Pendiente → Formulario DocuSeal embebido
  • Firmada → Badge verde + botón descarga + botón revocar
  • Rechazada/Revocada → Mensaje + botón “Crear Nueva”
Componente: VeriFactuQRCode

Ubicación: src/features/invoices/sub-modules/verifactu/components/VeriFactuQRCode.tsx

Función: Renderiza el código QR de VeriFactu en PDFs y vistas.

Props:

interface VeriFactuQRCodeProps {
qrCode: string | null; // Base64 BMP
csv?: string | null; // Código CSV
validationUrl?: string | null; // URL AEAT
status?: string | null; // Estado VeriFactu
}

Renderizado:

  • Imagen QR desde Base64 (data:image/bmp;base64,…)
  • Badge “Enviado” si status = SENT
  • CSV en formato mono-espaciado
  • Enlace “Validar en AEAT” si validationUrl presente
Componente: VeriFactuStatus

Ubicación: src/features/invoices/sub-modules/verifactu/components/VeriFactuStatus.tsx

Función: Badge de estado VeriFactu en listas de facturas.

Variants:

  • badge: Badge pequeño para tablas
  • full: Vista completa con detalles y acciones

Estados:

  • SENT: Badge verde con icono de candado
  • ERROR: Badge rojo con icono de alerta + tooltip con error
  • PENDING_RETRY: Badge amarillo con icono de reloj
  • FAILED: Badge gris con icono de X
  • NULL: Sin badge (VeriFactu no activo o borrador)
Página: VeriFactuDashboard

Ubicación: src/features/invoices/sub-modules/verifactu/pages/VeriFactuDashboard.tsx

Función: Panel de administración de VeriFactu.

Secciones:

  1. Tarjetas estadísticas: Total facturas, Enviadas, Pendientes, Errores
  2. Acciones masivas:
    • Reintentar facturas fallidas
    • Migrar facturas antiguas
    • Descargar logs CSV
  3. Filtros: Estado VeriFactu, Negocio, Rango de fechas
  4. Distribución de errores: Tabla con códigos más frecuentes
  5. Tendencia diaria: Gráfico de envíos vs errores (últimos 7 días)
  6. Tabla de facturas: Últimas 100 con estado VeriFactu

Acceso: Solo administradores/owners


VeriFactu utiliza DocuSeal para la firma digital de autorizaciones.

Flujo:

  1. Edge function create-verifactu-authorization crea submission en DocuSeal
  2. DocuSeal genera PDF con plantilla predefinida
  3. Usuario firma documento mediante iframe embebido (embed_src)
  4. DocuSeal envía webhook a docuseal-webhook al completar
  5. Webhook actualiza estado en verifactu_authorizations

Configuración requerida:

VariableDescripción
DOCUSEAL_API_KEYAPI key de cuenta DocuSeal
DOCUSEAL_TEMPLATE_IDID de plantilla de autorización VeriFactu
DOCUSEAL_API_URLURL base API (default: https://api.docuseal.co)

Webhook URL:

https://<proyecto>.supabase.co/functions/v1/docuseal-webhook

Eventos suscritos:

  • form.completed: Documento firmado
  • form.declined: Usuario rechazó firma

VeriFactu utiliza la API de Irene Solutions como intermediario con la AEAT.

Endpoints utilizados:

EndpointMétodoFunción
/Invoices/CreatePOSTCrear registro de factura
/Invoices/CancelPOSTAnular factura
/Invoices/GetQrCodePOSTObtener QR de factura

Autenticación:

  • ServiceKey en el body del request (NO en headers)
  • Variable: VERIFACTU_SERVICE_KEY

Timeout:

  • 3 segundos por request
  • Si timeout → status ERROR + reintento automático

Respuesta exitosa:

{
"ResultCode": 0,
"Return": {
"CSV": "1A2B3C4D5E6F7G8H9I0J",
"QrCode": "iVBORw0KGgoAAAANS...",
"QrCodeUrl": "https://...",
"ValidationUrl": "https://www.agenciatributaria.gob.es/...",
"ExternKey": "blockchain-id-12345",
"StatusResponse": "Correcto"
}
}

Respuesta con error:

{
"ResultCode": 1,
"Return": {
"ErrorCode": "VAL-001",
"ErrorDescription": "NIF del emisor inválido"
}
}