From 9263d9a178ad21369061cefbea5e195835e1d098 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Fri, 17 Apr 2026 13:38:21 -0300 Subject: [PATCH] feat(web): panel de reserva de numeros en PdV detail (ADM-008) Gap detectado durante smoke: la DetailPage tenia los hooks useReservarNumero/useProximoNumero creados en Batch 6 pero faltaba el componente que los consume. SecuenciasPanel.tsx: tabla con los 6 tipos AFIP (FacturaA/B/C, NC A/B/C), proximo numero por tipo, boton Reservar. Toast con el numero reservado. Deshabilitado si PdV o Medio padre estan inactivos. Integrado en PuntoDeVentaDetailPage bajo guard de permiso. --- .../components/SecuenciasPanel.tsx | 96 +++++++++++++++++++ .../pages/PuntoDeVentaDetailPage.tsx | 8 ++ 2 files changed, 104 insertions(+) create mode 100644 src/web/src/features/puntos-de-venta/components/SecuenciasPanel.tsx diff --git a/src/web/src/features/puntos-de-venta/components/SecuenciasPanel.tsx b/src/web/src/features/puntos-de-venta/components/SecuenciasPanel.tsx new file mode 100644 index 0000000..9dfa2b0 --- /dev/null +++ b/src/web/src/features/puntos-de-venta/components/SecuenciasPanel.tsx @@ -0,0 +1,96 @@ +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { TipoComprobante } from '../types' +import { useReservarNumero, useProximoNumero } from '../hooks/useReservarNumero' + +const TIPOS: Array<{ value: TipoComprobante; label: string }> = [ + { value: TipoComprobante.FacturaA, label: 'Factura A' }, + { value: TipoComprobante.FacturaB, label: 'Factura B' }, + { value: TipoComprobante.FacturaC, label: 'Factura C' }, + { value: TipoComprobante.NotaCreditoA, label: 'Nota Crédito A' }, + { value: TipoComprobante.NotaCreditoB, label: 'Nota Crédito B' }, + { value: TipoComprobante.NotaCreditoC, label: 'Nota Crédito C' }, +] + +interface Props { + puntoDeVentaId: number + disabled: boolean +} + +export function SecuenciasPanel({ puntoDeVentaId, disabled }: Props) { + return ( +
+
+

Reserva de números de comprobante

+

+ Cada reserva incrementa el correlativo y devuelve el número asignado. +

+
+ + + + Tipo + Próximo número + Acción + + + + {TIPOS.map((tipo) => ( + + ))} + +
+
+ ) +} + +interface RowProps { + puntoDeVentaId: number + tipoValue: TipoComprobante + tipoLabel: string + disabled: boolean +} + +function SecuenciaRow({ puntoDeVentaId, tipoValue, tipoLabel, disabled }: RowProps) { + const proximo = useProximoNumero(puntoDeVentaId, tipoValue) + const reservar = useReservarNumero(puntoDeVentaId) + + const handleReservar = () => { + reservar.mutate(tipoValue, { + onSuccess: (data) => { + toast.success(`${tipoLabel}: número ${data.numeroReservado} reservado`) + }, + onError: (err: unknown) => { + const apiError = err as { response?: { data?: { error?: string } } } + const code = apiError.response?.data?.error ?? 'error' + toast.error(`No se pudo reservar: ${code}`) + }, + }) + } + + return ( + + {tipoLabel} + + {proximo.isLoading ? '…' : proximo.data?.proximoNumero ?? '—'} + + + + + + ) +} diff --git a/src/web/src/features/puntos-de-venta/pages/PuntoDeVentaDetailPage.tsx b/src/web/src/features/puntos-de-venta/pages/PuntoDeVentaDetailPage.tsx index bf7e68c..9d2f95a 100644 --- a/src/web/src/features/puntos-de-venta/pages/PuntoDeVentaDetailPage.tsx +++ b/src/web/src/features/puntos-de-venta/pages/PuntoDeVentaDetailPage.tsx @@ -7,6 +7,7 @@ import { useMedio } from '../../medios/hooks/useMedio' import { DeactivatePuntoDeVentaModal } from '../components/DeactivatePuntoDeVentaModal' import { MedioInactivoBanner } from '../components/MedioInactivoBanner' import { PdvInactivoBanner } from '../components/PdvInactivoBanner' +import { SecuenciasPanel } from '../components/SecuenciasPanel' function formatDate(iso: string | null): string { if (!iso) return '—' @@ -103,6 +104,13 @@ export function PuntoDeVentaDetailPage() { /> + + + + ) }