Files
mercadodevida/work/artifacts/F-074/architect.md
2026-08-19 19:25:39 +02:00

2.9 KiB

F-074 — Architect: Audit shows real-time logs from application services

Current state

The admin audit page (/audit) fetches audit log entries once on mount via useEffect. There is no polling — new entries only appear after a page refresh.

Goal

Add polling to the audit page so new log entries appear automatically without refresh. Entries from application services (product edits, category changes, tax rate toggles, etc.) will show up in real-time on the audit page.

Scope IN

  • project/apps/admin/src/app/(dashboard)/audit/page.tsx:
    • Add a useEffect with setInterval (5 second interval) that fetches the latest entries when page === 0 (first page)
    • Prepend new entries to the list (deduplicate by id)
    • Add a liveIndicator state to show "● En vivo" when polling is active on page 0
    • Stop polling when navigating to page > 0 or when filter is active
    • Show count of new entries (e.g., "+3 nuevas" badge) when new entries arrive
    • Clear new entry count when user views them

Scope OUT

  • Backend — no changes (existing GET /admin/audit is sufficient for polling)
  • No SSE/WebSocket (simple polling is sufficient)
  • No changes to the audit logger itself

Design

Polling strategy:

const [newCount, setNewCount] = useState(0);
const [pollingActive, setPollingActive] = useState(false);

useEffect(() => {
  if (page !== 0 || debounced) { setPollingActive(false); return; }
  setPollingActive(true);
  const interval = setInterval(async () => {
    try {
      const data = await auditApi.list({ limit: PAGE_SIZE, offset: 0 });
      const existingIds = new Set(items.map(i => i.id));
      const newItems = (data.items ?? []).filter(i => !existingIds.has(i.id));
      if (newItems.length > 0) {
        setItems(prev => [...newItems, ...prev].slice(0, PAGE_SIZE));
        setNewCount(prev => prev + newItems.length);
      }
    } catch { /* silent */ }
  }, 5000);
  return () => { clearInterval(interval); setPollingActive(false); };
}, [page, debounced, items]);

UI indicator:

// In header:
{pollingActive && (
  <span className="inline-flex items-center gap-1.5 text-xs text-green-600 font-medium">
    <span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
    En vivo
  </span>
)}
{newCount > 0 && (
  <button onClick={() => setNewCount(0)}
    className="ml-2 px-2 py-0.5 bg-green-100 text-green-700 text-xs rounded-full">
    +{newCount} nueva{newCount !== 1 ? 's' : ''}
  </button>
)}

Risk

  • Low risk: purely frontend polling, no backend or DB changes
  • No performance concern: 5-second polling is lightweight
  • Polling stops automatically when filter is active or on paginated views

Verification

  • npx tsc --noEmit admin
  • npx eslint on changed file
  • ./scripts/verify.sh green
  • Manual: open audit page, perform an action (e.g. toggle a tax rate), observe new entry appears within 5 seconds