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

81 lines
2.9 KiB
Markdown

# 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:
```tsx
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:
```tsx
// 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