feat(F-194): completed feature

This commit is contained in:
chattie
2026-08-22 22:29:26 +02:00
parent ca12f46bff
commit b85670cb51
15 changed files with 73 additions and 6 deletions

View File

@@ -7322,6 +7322,23 @@
"security": false, "security": false,
"qa": false "qa": false
} }
},
{
"id": "F-194",
"type": "fix",
"title": "Fix crypto.randomUUID runtime error and move IVA/audit/logs into settings",
"description": "1) Replace bare crypto.randomUUID() calls in POS PaymentModal and cashier page with the safe generateIdempotencyKey fallback. 2) Move tax-rates, audit and logs navigation and routes into the settings section.",
"priority": "high",
"risk": "low",
"status": "done",
"created_at": "2026-08-22",
"gates": {
"reviewer": true,
"security": true,
"qa": true,
"close": true
},
"completed_at": "2026-08-22T20:29:26Z"
} }
] ]
} }

View File

@@ -1,5 +1,6 @@
'use client'; 'use client';
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import Link from 'next/link';
import { auditApi, type AuditEntry } from '@/lib/api-client'; import { auditApi, type AuditEntry } from '@/lib/api-client';
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
@@ -79,6 +80,7 @@ export default function AuditLogPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<Link href="/settings" className="text-sm text-[#2D6A4F] hover:underline"> Ajustes</Link>
<h1 className="text-2xl font-bold text-gray-900">Log de auditoría <h1 className="text-2xl font-bold text-gray-900">Log de auditoría
{pollingActive && ( {pollingActive && (
<span className="ml-3 inline-flex items-center gap-1.5 text-xs text-green-600 font-medium align-middle"> <span className="ml-3 inline-flex items-center gap-1.5 text-xs text-green-600 font-medium align-middle">

View File

@@ -1,4 +1,5 @@
'use client'; 'use client';
import Link from 'next/link';
import { ServerLogViewer } from '@/components/ServerLogViewer'; import { ServerLogViewer } from '@/components/ServerLogViewer';
export default function ServerLogsPage() { export default function ServerLogsPage() {
@@ -10,6 +11,7 @@ export default function ServerLogsPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<Link href="/settings" className="text-sm text-[#2D6A4F] hover:underline"> Ajustes</Link>
<h1 className="text-2xl font-bold text-gray-900">Logs del servidor</h1> <h1 className="text-2xl font-bold text-gray-900">Logs del servidor</h1>
<p className="text-sm text-gray-500 mt-0.5"> <p className="text-sm text-gray-500 mt-0.5">
Stream en tiempo real via SSE. Las líneas erróneas se resaltan en rojo. Máximo 200 líneas. Stream en tiempo real via SSE. Las líneas erróneas se resaltan en rojo. Máximo 200 líneas.

View File

@@ -1,5 +1,6 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Link from 'next/link';
import { settingsApi, type StoreSettings } from '@/lib/api-client'; import { settingsApi, type StoreSettings } from '@/lib/api-client';
type FormData = StoreSettings; type FormData = StoreSettings;
@@ -105,6 +106,17 @@ export default function SettingsPage() {
{t.label} {t.label}
</button> </button>
))} ))}
<div className="my-3 border-t border-gray-200" />
<p className="px-4 text-xs font-semibold uppercase tracking-wide text-gray-400">Sistema</p>
<Link href="/settings/tax-rates" className="flex w-full items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-600 hover:bg-gray-100 transition-colors">
<span>💰</span> IVA
</Link>
<Link href="/settings/audit" className="flex w-full items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-600 hover:bg-gray-100 transition-colors">
<span>📋</span> Auditoría
</Link>
<Link href="/settings/logs" className="flex w-full items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-600 hover:bg-gray-100 transition-colors">
<span>🖥</span> Logs
</Link>
</nav> </nav>
{/* Form area */} {/* Form area */}

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { taxApi, type TaxRate } from '@/lib/api-client'; import { taxApi, type TaxRate } from '@/lib/api-client';
import Link from 'next/link';
import { RowActions } from '@/components/ui/RowActions'; import { RowActions } from '@/components/ui/RowActions';
export default function TaxRatesPage() { export default function TaxRatesPage() {
@@ -69,6 +70,7 @@ export default function TaxRatesPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<Link href="/settings" className="text-sm text-[#2D6A4F] hover:underline"> Ajustes</Link>
<h1 className="text-2xl font-bold text-gray-900">Tipos impositivos (IVA)</h1> <h1 className="text-2xl font-bold text-gray-900">Tipos impositivos (IVA)</h1>
<p className="text-sm text-gray-500 mt-0.5">Configura los tipos de IVA aplicables a los productos.</p> <p className="text-sm text-gray-500 mt-0.5">Configura los tipos de IVA aplicables a los productos.</p>
</div> </div>

View File

@@ -55,10 +55,10 @@ export const NAV_ITEMS: NavItem[] = [
{ href: '/reviews', label: 'Reseñas', icon: '⭐', permission: 'reviews.read' }, { href: '/reviews', label: 'Reseñas', icon: '⭐', permission: 'reviews.read' },
{ href: '/cms', label: 'CMS', icon: '📄', permission: 'cms.read' }, { href: '/cms', label: 'CMS', icon: '📄', permission: 'cms.read' },
{ href: '/users', label: 'Usuarios', icon: '🔐', permission: 'admin-users.read' }, { href: '/users', label: 'Usuarios', icon: '🔐', permission: 'admin-users.read' },
{ href: '/tax-rates', label: 'IVA', icon: '📊', permission: 'orders.read' },
{ href: '/audit', label: 'Auditoría', icon: '📋', permission: 'audit.read' },
{ href: '/logs', label: 'Logs', icon: '🖥️', permission: 'audit.read' },
{ href: '/settings', label: 'Ajustes', icon: '⚙️', permission: 'dashboard' }, { href: '/settings', label: 'Ajustes', icon: '⚙️', permission: 'dashboard' },
{ href: '/settings/tax-rates', label: 'IVA', icon: '💰', permission: 'orders.read' },
{ href: '/settings/audit', label: 'Auditoría', icon: '📋', permission: 'audit.read' },
{ href: '/settings/logs', label: 'Logs', icon: '🖥️', permission: 'audit.read' },
]; ];
export function visibleNavItems(role: Role): NavItem[] { export function visibleNavItems(role: Role): NavItem[] {

View File

@@ -274,7 +274,7 @@ export default function RegisterPage() {
setCart((current) => [ setCart((current) => [
...current, ...current,
{ {
lineId: `free-${crypto.randomUUID()}`, lineId: `free-${generateIdempotencyKey()}`,
kind: 'free', kind: 'free',
variantId: null, variantId: null,
productId: null, productId: null,

View File

@@ -3,6 +3,7 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { formatPrice } from '@/lib/money'; import { formatPrice } from '@/lib/money';
import type { PaymentAllocation, PaymentMethod } from '@/types/checkout'; import type { PaymentAllocation, PaymentMethod } from '@/types/checkout';
import { generateIdempotencyKey } from '@/lib/idempotency';
interface PaymentModalProps { interface PaymentModalProps {
method: PaymentMethod; method: PaymentMethod;
@@ -48,7 +49,7 @@ export default function PaymentModal({
return; return;
} }
onAdd({ onAdd({
id: crypto.randomUUID(), id: generateIdempotencyKey(),
methodCode: method.code, methodCode: method.code,
methodLabel: method.label, methodLabel: method.label,
kind: method.kind, kind: method.kind,

View File

@@ -0,0 +1,21 @@
# F-194 — Fix evidence
## Changes
### Fix 1: `crypto.randomUUID()` runtime TypeError
- `project/apps/pos/src/components/PaymentModal.tsx`: replaced `crypto.randomUUID()` with `generateIdempotencyKey()` (already imported pattern from `@/lib/idempotency` with safe fallback).
- `project/apps/pos/src/app/(terminal)/page.tsx`: same fix for the free-item lineId.
### Fix 2: IVA, Auditoría, Logs → inside Settings
- Moved routes: `app/(dashboard)/{tax-rates,audit,logs}/page.tsx``app/(dashboard)/settings/{tax-rates,audit,logs}/page.tsx`
- Updated `permissions.ts` NAV_ITEMS: `/tax-rates``/settings/tax-rates`, `/audit``/settings/audit`, `/logs``/settings/logs`
- Added "Sistema" sub-navigation in settings sidebar with links to each sub-page.
- Added "← Ajustes" breadcrumb link at the top of each moved page.
## Validation
- Admin typecheck: PASS
- POS typecheck: PASS
- Admin build: PASS (routes show `/settings/{audit,logs,tax-rates}`)
- POS build: PASS
- Backend unit tests: 268/268 PASS
- `./scripts/verify.sh`: PASS

View File

@@ -0,0 +1 @@
{"feature_id":"F-194","agent":"leader","stage":"close","verdict":"APPROVED","checks":[{"item":"All gates APPROVED","ok":true},{"item":"Builds and tests pass","ok":true}],"issues":[]}

View File

@@ -0,0 +1 @@
{"feature_id":"F-194","agent":"qa","stage":"qa_gate","verdict":"APPROVED","acceptance":[{"id":1,"criterion":"PaymentModal no longer calls bare crypto.randomUUID()","ok":true},{"id":2,"criterion":"Terminal free-item uses safe ID generation","ok":true},{"id":3,"criterion":"IVA/Audit/Logs are accessible under /settings/...","ok":true},{"id":4,"criterion":"Old /tax-rates, /audit, /logs routes removed","ok":true},{"id":5,"criterion":"Typecheck/builds/regression green","ok":true}],"issues":[]}

View File

@@ -0,0 +1 @@
{"feature_id":"F-194","agent":"reviewer","stage":"review_gate","verdict":"APPROVED","checks":[{"item":"crypto.randomUUID replaced with safe fallback","ok":true},{"item":"Routes moved from top-level to settings sub-paths","ok":true},{"item":"Navigation items updated to new paths","ok":true},{"item":"Settings sidebar links to sub-pages","ok":true},{"item":"Back-to-settings breadcrumbs on each page","ok":true},{"item":"Typecheck and builds pass","ok":true}],"issues":[]}

View File

@@ -0,0 +1 @@
{"feature_id":"F-194","agent":"security","stage":"security_gate","verdict":"APPROVED","checks":[{"item":"No new auth/authz changes","ok":true},{"item":"No secrets or sensitive data introduced","ok":true},{"item":"Moved pages preserve existing RBAC","ok":true}],"issues":[]}

View File

@@ -502,3 +502,9 @@
- Seguridad: login y sesiones bloquean cuentas inactivas/eliminadas; revocación y auditoría atómicas; sesión de caja abierta impide la baja. - Seguridad: login y sesiones bloquean cuentas inactivas/eliminadas; revocación y auditoría atómicas; sesión de caja abierta impide la baja.
- Integridad: la fila/UUID se conserva para atribución histórica y la apertura de caja se serializa con la baja mediante bloqueo de fila. - Integridad: la fila/UUID se conserva para atribución histórica y la apertura de caja se serializa con la baja mediante bloqueo de fila.
- Evidencia: 354/354 tests con PostgreSQL real en secuencia, migración up/no-op/down/up y builds backend/admin verdes; `work/artifacts/F-187/`. - Evidencia: 354/354 tests con PostgreSQL real en secuencia, migración up/no-op/down/up y builds backend/admin verdes; `work/artifacts/F-187/`.
## F-194 cerrada (2026-08-22) — Fix crypto.randomUUID runtime error and move admin modules to settings
- Gates: reviewer APPROVED, security APPROVED, qa APPROVED, verify.sh exit 0.
- Fix 1: replaced bare crypto.randomUUID() with generateIdempotencyKey() in PaymentModal and terminal page.
- Fix 2: moved tax-rates, audit and logs routes from top-level dashboard into /settings/... with breadcrumb navigation.
- Evidencia: admin/POS typecheck and builds green; 268/268 unit tests pass; verify.sh green.

View File

@@ -6,6 +6,6 @@
"state": "waiting", "state": "waiting",
"next_agent": "leader", "next_agent": "leader",
"waiting_for": "Seleccionar una feature pending y actualizar este estado", "waiting_for": "Seleccionar una feature pending y actualizar este estado",
"updated_at": "2026-08-22T20:23:50Z", "updated_at": "2026-08-22T20:29:26Z",
"timeline": [] "timeline": []
} }