feat(cfdi): add autocomplete for emisor and receptor filters

- Add /cfdi/emisores and /cfdi/receptores API endpoints
- Search by RFC or nombre with ILIKE
- Show suggestions dropdown while typing (min 2 chars)
- Click suggestion to select and populate filter input
- Show loading state while searching

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Consultoria AS
2026-02-17 07:07:01 +00:00
parent 5c6367839f
commit 0e49c0922d
5 changed files with 173 additions and 7 deletions

View File

@@ -67,6 +67,42 @@ export async function getXml(req: Request, res: Response, next: NextFunction) {
}
}
export async function getEmisores(req: Request, res: Response, next: NextFunction) {
try {
if (!req.tenantSchema) {
return next(new AppError(400, 'Schema no configurado'));
}
const search = (req.query.search as string) || '';
if (search.length < 2) {
return res.json([]);
}
const emisores = await cfdiService.getEmisores(req.tenantSchema, search);
res.json(emisores);
} catch (error) {
next(error);
}
}
export async function getReceptores(req: Request, res: Response, next: NextFunction) {
try {
if (!req.tenantSchema) {
return next(new AppError(400, 'Schema no configurado'));
}
const search = (req.query.search as string) || '';
if (search.length < 2) {
return res.json([]);
}
const receptores = await cfdiService.getReceptores(req.tenantSchema, search);
res.json(receptores);
} catch (error) {
next(error);
}
}
export async function getResumen(req: Request, res: Response, next: NextFunction) {
try {
if (!req.tenantSchema) {

View File

@@ -10,6 +10,8 @@ router.use(tenantMiddleware);
router.get('/', cfdiController.getCfdis);
router.get('/resumen', cfdiController.getResumen);
router.get('/emisores', cfdiController.getEmisores);
router.get('/receptores', cfdiController.getReceptores);
router.get('/:id', cfdiController.getCfdiById);
router.get('/:id/xml', cfdiController.getXml);
router.post('/', cfdiController.createCfdi);

View File

@@ -387,6 +387,28 @@ export async function deleteCfdi(schema: string, id: string): Promise<void> {
await prisma.$queryRawUnsafe(`DELETE FROM "${schema}".cfdis WHERE id = $1`, id);
}
export async function getEmisores(schema: string, search: string, limit: number = 10): Promise<{ rfc: string; nombre: string }[]> {
const result = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string }[]>(`
SELECT DISTINCT rfc_emisor as rfc, nombre_emisor as nombre
FROM "${schema}".cfdis
WHERE rfc_emisor ILIKE $1 OR nombre_emisor ILIKE $1
ORDER BY nombre_emisor
LIMIT $2
`, `%${search}%`, limit);
return result;
}
export async function getReceptores(schema: string, search: string, limit: number = 10): Promise<{ rfc: string; nombre: string }[]> {
const result = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string }[]>(`
SELECT DISTINCT rfc_receptor as rfc, nombre_receptor as nombre
FROM "${schema}".cfdis
WHERE rfc_receptor ILIKE $1 OR nombre_receptor ILIKE $1
ORDER BY nombre_receptor
LIMIT $2
`, `%${search}%`, limit);
return result;
}
export async function getResumenCfdis(schema: string, año: number, mes: number) {
const result = await prisma.$queryRawUnsafe<[{
total_ingresos: number;