Ir al contenido

Dashboard de Monitorización

Panel de control para supervisar el estado de los envíos a VeriFactu y gestionar errores.

  1. Ve a Panel de Negocio > VeriFactu (o usa ruta directa /owner/verifactu/dashboard).
  2. Revisa las tarjetas de estadísticas — resumen de envíos.
  3. Filtra por estado, negocio o rango de fechas — ajusta la vista.
  4. Revisa la distribución de errores — identifica problemas recurrentes.
  5. Consulta la tendencia diaria — gráfico de envíos vs errores (últimos 7 días).
  6. Navega por la tabla de facturas — últimas 100 facturas emitidas.
TarjetaMétricaColor
Total FacturasFacturas emitidas (no borradores)Azul
EnviadasStatus = SENTVerde
PendientesStatus = PENDING_RETRYAmarillo
ErroresStatus = ERROR + FAILEDRojo

Tasa de éxito:

Tasa = (Enviadas / Total con status) × 100
FiltroValores
Estado VeriFactuTodos, Enviadas, Error, Pendiente Reintento, Fallidas, No Enviadas
NegocioTodos, [lista de negocios]
Fecha InicioSelector de fecha
Fecha FinSelector de fecha

Tabla con los códigos de error más frecuentes:

CódigoDescripciónOcurrencias
9999Error interno del servicio15
TIMEOUTTimeout de conexión8
VAL-001NIF del emisor inválido3

Gráfico de barras horizontales que muestra:

  • Barra verde: Envíos exitosos
  • Barra roja: Errores
  • Total: Suma de ambos

Ejemplo:

16 feb [████████████ 12] [██ 2] 14 total
15 feb [██████████ 10] [████ 4] 14 total
  • Las tarjetas muestran números actualizados.
  • Los filtros funcionan y reducen la lista de facturas.
  • La tabla de errores muestra códigos reales si existen errores.
  • El gráfico de tendencias muestra datos de los últimos 7 días.

Detalles tecnicos — Dashboard de Monitorizacion

Sección titulada «Detalles tecnicos — Dashboard de Monitorizacion»
Componente: VeriFactuDashboard

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

Estado del componente:

const [stats, setStats] = useState<VeriFactuStats | null>(null);
const [invoices, setInvoices] = useState<Invoice[]>([]);
const [errorDistribution, setErrorDistribution] = useState<ErrorDistribution[]>([]);
const [dailyTrends, setDailyTrends] = useState<DailyTrend[]>([]);
const [filterStatus, setFilterStatus] = useState<string>("all");
const [filterBusiness, setFilterBusiness] = useState<string>("all");
const [filterStartDate, setFilterStartDate] = useState<string>("");
const [filterEndDate, setFilterEndDate] = useState<string>("");

Query de facturas:

let query = supabase
.from("invoices")
.select(`
*,
businesses!inner(name),
customers!inner(name)
`)
.neq("status", "draft")
.order("issue_date", { ascending: false })
.limit(100);
// Aplicar filtros
if (filterStatus === "not_sent") {
query = query.is("verifactu_status", null);
} else if (filterStatus !== "all") {
query = query.eq("verifactu_status", filterStatus.toUpperCase());
}
if (filterBusiness !== "all") {
query = query.eq("business_id", filterBusiness);
}
if (filterStartDate) {
query = query.gte("issue_date", filterStartDate);
}
if (filterEndDate) {
query = query.lte("issue_date", filterEndDate);
}

Cálculo de estadísticas:

const totalInvoices = invoiceData?.length || 0;
const sentInvoices = invoiceData?.filter(i => i.verifactu_status === 'SENT').length || 0;
const errorInvoices = invoiceData?.filter(i => i.verifactu_status === 'ERROR').length || 0;
const pendingRetryInvoices = invoiceData?.filter(i => i.verifactu_status === 'PENDING_RETRY').length || 0;
const failedInvoices = invoiceData?.filter(i => i.verifactu_status === 'FAILED').length || 0;
const successRate = totalInvoices > 0 ? (sentInvoices / totalInvoices) * 100 : 0;

Distribución de errores:

const errorMap = new Map<string, { description: string; count: number }>();
invoiceData?.forEach(invoice => {
if (invoice.verifactu_error_code &&
(invoice.verifactu_status === 'ERROR' || invoice.verifactu_status === 'FAILED')) {
const key = invoice.verifactu_error_code;
const existing = errorMap.get(key);
if (existing) {
existing.count++;
} else {
errorMap.set(key, {
description: invoice.verifactu_error_description || "Unknown error",
count: 1,
});
}
}
});

Tendencia diaria (últimos 7 días):

const last7Days = Array.from({ length: 7 }, (_, i) => {
const date = new Date();
date.setDate(date.getDate() - (6 - i));
return date.toISOString().split('T')[0];
});
const trends = last7Days.map(date => {
const dayInvoices = invoiceData?.filter(i =>
i.verifactu_sent_at?.startsWith(date)
) || [];
return {
date,
sent: dayInvoices.filter(i => i.verifactu_status === 'SENT').length,
errors: dayInvoices.filter(i =>
i.verifactu_status === 'ERROR' ||
i.verifactu_status === 'FAILED'
).length,
};
});
Queries útiles

Tasa de éxito por día (últimos 30 días):

SELECT
DATE(verifactu_sent_at) as fecha,
COUNT(*) FILTER (WHERE verifactu_status = 'SENT') as enviadas,
COUNT(*) as total,
ROUND(
COUNT(*) FILTER (WHERE verifactu_status = 'SENT')::numeric /
NULLIF(COUNT(*), 0) * 100,
2
) as tasa_exito_pct
FROM invoices
WHERE business_id = '<business_id>'
AND verifactu_sent_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(verifactu_sent_at)
ORDER BY fecha DESC;

Top 10 errores más frecuentes:

SELECT
verifactu_error_code,
verifactu_error_description,
COUNT(*) as ocurrencias,
ARRAY_AGG(invoice_number ORDER BY issue_date DESC) as ejemplos
FROM invoices
WHERE business_id = '<business_id>'
AND verifactu_error_code IS NOT NULL
GROUP BY verifactu_error_code, verifactu_error_description
ORDER BY ocurrencias DESC
LIMIT 10;