Files
CRM_AGENTES_CARGA/frontend/src/lib/api/fin/concepts.ts
Jair Cedillo 5a112a0171 feat(fin): pantallas de conceptos y datos fiscales del emisor
Clientes API por dominio para los catálogos del SAT, conceptos y emisor. Los
catálogos se cachean en un Map del módulo tras la primera carga: son fijos y no
cambian durante la sesión.

Pantalla de conceptos (/dashboard/fin/conceptos) con tabla, buscador, filtro de
activos y alta/edición en diálogo. La clave de producto/servicio se elige con un
combobox que consulta el catálogo a partir de 2 caracteres, y el 409 del backend
por clave ya asignada se muestra junto al campo.

Sección de configuración fiscal (/dashboard/settings/facturacion) con razón
social, RFC (misma validación que el backend), régimen fiscal y CP. Si el GET
responde 404 se abre en modo alta, no como error; el guardar se deshabilita sin
fin.settings.edit.

Todo en Svelte 5 con runes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 16:58:09 -05:00

82 lines
2.6 KiB
TypeScript

/**
* Cliente API — Catálogo de conceptos de facturación.
*
* Cada concepto está ligado 1:1 a una clave de producto/servicio del SAT dentro de la
* empresa; el backend responde 409 si la clave ya está tomada.
*/
import { api } from '$lib/api';
import type { SatCatalogItem, SatUnitOfMeasure } from './catalogs';
export interface Concept {
id: number;
code: string;
description: string;
product_service_id: number;
unit_of_measure_id: number | null;
tax_object_id: number | null;
unit_price: number | null;
currency: string;
is_active: boolean;
notes: string | null;
product_service: SatCatalogItem | null;
unit_of_measure: SatUnitOfMeasure | null;
tax_object: SatCatalogItem | null;
tenant_id: number;
company_id: number;
created_by: string | null;
updated_by: string | null;
created_at: string;
updated_at: string;
}
export interface ConceptInput {
code: string;
description: string;
product_service_id: number;
unit_of_measure_id?: number | null;
tax_object_id?: number | null;
unit_price?: number | null;
currency?: string;
is_active?: boolean;
notes?: string | null;
}
export const conceptsAPI = {
async list(
companyId: number,
params?: { search?: string; active_only?: boolean; product_service_id?: number }
): Promise<Concept[]> {
const qs = new URLSearchParams({ company_id: String(companyId) });
if (params?.search) qs.set('search', params.search);
if (params?.active_only !== undefined) qs.set('active_only', String(params.active_only));
if (params?.product_service_id !== undefined)
qs.set('product_service_id', String(params.product_service_id));
const res = await api.get<Concept[]>(`/v1/fin/concepts?${qs}`);
if (res.error) throw new Error(res.error);
return res.data!;
},
async get(id: number, companyId: number): Promise<Concept> {
const res = await api.get<Concept>(`/v1/fin/concepts/${id}?company_id=${companyId}`);
if (res.error) throw new Error(res.error);
return res.data!;
},
async create(data: ConceptInput, companyId: number): Promise<Concept> {
const res = await api.post<Concept>(`/v1/fin/concepts?company_id=${companyId}`, data);
if (res.error) throw new Error(res.error);
return res.data!;
},
async update(id: number, data: Partial<ConceptInput>, companyId: number): Promise<Concept> {
const res = await api.patch<Concept>(`/v1/fin/concepts/${id}?company_id=${companyId}`, data);
if (res.error) throw new Error(res.error);
return res.data!;
},
async remove(id: number, companyId: number): Promise<void> {
const res = await api.delete(`/v1/fin/concepts/${id}?company_id=${companyId}`);
if (res.error) throw new Error(res.error);
}
};