From d61292afa40e2e909698a032e150a1fa7a0afc85 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Fri, 17 Apr 2026 12:36:39 -0300 Subject: [PATCH] =?UTF-8?q?feat(web):=20feature=20puntos-de-venta=20?= =?UTF-8?q?=E2=80=94=20types,=20api,=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/puntos-de-venta.api.ts | 62 ++++++++++++++ .../puntos-de-venta/api/secuencias.api.ts | 22 +++++ .../puntos-de-venta/hooks/usePuntosDeVenta.ts | 83 +++++++++++++++++++ .../hooks/useReservarNumero.ts | 30 +++++++ src/web/src/features/puntos-de-venta/types.ts | 71 ++++++++++++++++ 5 files changed, 268 insertions(+) create mode 100644 src/web/src/features/puntos-de-venta/api/puntos-de-venta.api.ts create mode 100644 src/web/src/features/puntos-de-venta/api/secuencias.api.ts create mode 100644 src/web/src/features/puntos-de-venta/hooks/usePuntosDeVenta.ts create mode 100644 src/web/src/features/puntos-de-venta/hooks/useReservarNumero.ts create mode 100644 src/web/src/features/puntos-de-venta/types.ts diff --git a/src/web/src/features/puntos-de-venta/api/puntos-de-venta.api.ts b/src/web/src/features/puntos-de-venta/api/puntos-de-venta.api.ts new file mode 100644 index 0000000..eb7cfff --- /dev/null +++ b/src/web/src/features/puntos-de-venta/api/puntos-de-venta.api.ts @@ -0,0 +1,62 @@ +import { axiosClient } from '@/api/axiosClient' +import type { + CreatePuntoDeVentaRequest, + PuntoDeVentaCreated, + PuntoDeVentaDetail, + PuntoDeVentaListItem, + PuntosDeVentaQuery, + UpdatePuntoDeVentaRequest, + PagedResult, +} from '../types' + +export async function listPuntosDeVenta( + query: PuntosDeVentaQuery, +): Promise> { + const params = new URLSearchParams() + if (query.page !== undefined) params.set('page', String(query.page)) + if (query.pageSize !== undefined) params.set('pageSize', String(query.pageSize)) + if (query.medioId !== undefined) params.set('medioId', String(query.medioId)) + if (query.activo !== undefined) params.set('activo', String(query.activo)) + + const response = await axiosClient.get>( + '/api/v1/admin/puntos-de-venta', + { params }, + ) + return response.data +} + +export async function getPuntoDeVenta(id: number): Promise { + const response = await axiosClient.get( + `/api/v1/admin/puntos-de-venta/${id}`, + ) + return response.data +} + +export async function createPuntoDeVenta( + payload: CreatePuntoDeVentaRequest, +): Promise { + const response = await axiosClient.post( + '/api/v1/admin/puntos-de-venta', + payload, + ) + return response.data +} + +export async function updatePuntoDeVenta( + id: number, + payload: UpdatePuntoDeVentaRequest, +): Promise { + const response = await axiosClient.put( + `/api/v1/admin/puntos-de-venta/${id}`, + payload, + ) + return response.data +} + +export async function deactivatePuntoDeVenta(id: number): Promise { + await axiosClient.post(`/api/v1/admin/puntos-de-venta/${id}/deactivate`) +} + +export async function reactivatePuntoDeVenta(id: number): Promise { + await axiosClient.post(`/api/v1/admin/puntos-de-venta/${id}/reactivate`) +} diff --git a/src/web/src/features/puntos-de-venta/api/secuencias.api.ts b/src/web/src/features/puntos-de-venta/api/secuencias.api.ts new file mode 100644 index 0000000..0368a6c --- /dev/null +++ b/src/web/src/features/puntos-de-venta/api/secuencias.api.ts @@ -0,0 +1,22 @@ +import { axiosClient } from '@/api/axiosClient' +import type { TipoComprobante, ReservarNumeroResponse, ProximoNumeroResponse } from '../types' + +export async function reservarNumero( + puntoDeVentaId: number, + tipoComprobante: TipoComprobante, +): Promise { + const response = await axiosClient.post( + `/api/v1/admin/puntos-de-venta/${puntoDeVentaId}/secuencias/${tipoComprobante}/reservar`, + ) + return response.data +} + +export async function getProximoNumero( + puntoDeVentaId: number, + tipoComprobante: TipoComprobante, +): Promise { + const response = await axiosClient.get( + `/api/v1/admin/puntos-de-venta/${puntoDeVentaId}/secuencias/${tipoComprobante}/proximo`, + ) + return response.data +} diff --git a/src/web/src/features/puntos-de-venta/hooks/usePuntosDeVenta.ts b/src/web/src/features/puntos-de-venta/hooks/usePuntosDeVenta.ts new file mode 100644 index 0000000..6733f8d --- /dev/null +++ b/src/web/src/features/puntos-de-venta/hooks/usePuntosDeVenta.ts @@ -0,0 +1,83 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { + listPuntosDeVenta, + getPuntoDeVenta, + createPuntoDeVenta, + updatePuntoDeVenta, + deactivatePuntoDeVenta, + reactivatePuntoDeVenta, +} from '../api/puntos-de-venta.api' +import type { CreatePuntoDeVentaRequest, PuntosDeVentaQuery, UpdatePuntoDeVentaRequest } from '../types' + +export const puntosDeVentaListQueryKey = (query: PuntosDeVentaQuery) => + ['puntos-de-venta', 'list', query] as const + +export const puntoDeVentaDetailQueryKey = (id: number) => + ['puntos-de-venta', 'detail', id] as const + +// ─── List ──────────────────────────────────────────────────────────────────── + +export function usePuntosDeVentaList(query: PuntosDeVentaQuery) { + return useQuery({ + queryKey: puntosDeVentaListQueryKey(query), + queryFn: () => listPuntosDeVenta(query), + staleTime: 15_000, + }) +} + +// ─── Detail ────────────────────────────────────────────────────────────────── + +export function usePuntoDeVenta(id: number) { + return useQuery({ + queryKey: puntoDeVentaDetailQueryKey(id), + queryFn: () => getPuntoDeVenta(id), + enabled: !!id, + staleTime: 15_000, + }) +} + +// ─── Create ────────────────────────────────────────────────────────────────── + +export function useCreatePuntoDeVenta() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (payload: CreatePuntoDeVentaRequest) => createPuntoDeVenta(payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] }) + }, + }) +} + +// ─── Update ────────────────────────────────────────────────────────────────── + +export function useUpdatePuntoDeVenta(id: number) { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (payload: UpdatePuntoDeVentaRequest) => updatePuntoDeVenta(id, payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] }) + }, + }) +} + +// ─── Deactivate / Reactivate ───────────────────────────────────────────────── + +export function useDeactivatePuntoDeVenta() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (id: number) => deactivatePuntoDeVenta(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] }) + }, + }) +} + +export function useReactivatePuntoDeVenta() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (id: number) => reactivatePuntoDeVenta(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] }) + }, + }) +} diff --git a/src/web/src/features/puntos-de-venta/hooks/useReservarNumero.ts b/src/web/src/features/puntos-de-venta/hooks/useReservarNumero.ts new file mode 100644 index 0000000..dad0444 --- /dev/null +++ b/src/web/src/features/puntos-de-venta/hooks/useReservarNumero.ts @@ -0,0 +1,30 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { reservarNumero, getProximoNumero } from '../api/secuencias.api' +import type { TipoComprobante } from '../types' + +// ─── Reservar ──────────────────────────────────────────────────────────────── + +export function useReservarNumero(puntoDeVentaId: number) { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (tipoComprobante: TipoComprobante) => + reservarNumero(puntoDeVentaId, tipoComprobante), + onSuccess: (_data, tipoComprobante) => { + // Invalidate the proximo query for this pdv+tipo so it refetches + queryClient.invalidateQueries({ + queryKey: ['puntos-de-venta', 'proximo', puntoDeVentaId, tipoComprobante], + }) + }, + }) +} + +// ─── Próximo número (read-only) ────────────────────────────────────────────── + +export function useProximoNumero(puntoDeVentaId: number, tipoComprobante: TipoComprobante) { + return useQuery({ + queryKey: ['puntos-de-venta', 'proximo', puntoDeVentaId, tipoComprobante], + queryFn: () => getProximoNumero(puntoDeVentaId, tipoComprobante), + enabled: !!puntoDeVentaId, + staleTime: 5_000, + }) +} diff --git a/src/web/src/features/puntos-de-venta/types.ts b/src/web/src/features/puntos-de-venta/types.ts new file mode 100644 index 0000000..83858f0 --- /dev/null +++ b/src/web/src/features/puntos-de-venta/types.ts @@ -0,0 +1,71 @@ +// ADM-008 — shared types for puntos-de-venta feature + +export enum TipoComprobante { + FacturaA = 1, + FacturaB = 2, + FacturaC = 3, + NotaCreditoA = 4, + NotaCreditoB = 5, + NotaCreditoC = 6, +} + +export interface PuntoDeVentaListItem { + id: number + medioId: number + numeroAFIP: number + nombre: string + activo: boolean +} + +export interface PuntoDeVentaDetail { + id: number + medioId: number + numeroAFIP: number + nombre: string + activo: boolean + fechaCreacion: string + fechaModificacion: string | null +} + +export interface PuntoDeVentaCreated { + id: number + medioId: number + numeroAFIP: number + nombre: string + activo: boolean +} + +export interface CreatePuntoDeVentaRequest { + medioId: number + numeroAFIP: number + nombre: string +} + +export interface UpdatePuntoDeVentaRequest { + nombre: string + numeroAFIP: number +} + +export interface PuntosDeVentaQuery { + page?: number + pageSize?: number + medioId?: number + activo?: boolean +} + +export interface ReservarNumeroResponse { + tipoComprobante: TipoComprobante + numeroReservado: number +} + +export interface ProximoNumeroResponse { + tipoComprobante: TipoComprobante + proximoNumero: number +} + +export interface PagedResult { + items: T[] + page: number + pageSize: number + total: number +}