From 3fd7a33fc1dec75cc7f4e5fd03e6f184e61d9b3f Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sun, 28 Jun 2026 18:00:03 -0500 Subject: [PATCH] Add rejected professionals tab and fix deny to keep record - deny() now marks is_active:false instead of deleting the record - New GET /professionals/rejected endpoint (pro_state:3) - findPendingApprovals now filters only pro_state:1 - Admin list: third tab 'No aprobados' with approve/review actions Co-Authored-By: Claude Sonnet 4.6 --- admin/src/app/professionals/page.tsx | 66 ++++++++++++++++++- .../professionals/professionals.controller.ts | 9 +++ .../professionals/professionals.service.ts | 29 +++++--- 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/admin/src/app/professionals/page.tsx b/admin/src/app/professionals/page.tsx index 33306ec..d47e102 100644 --- a/admin/src/app/professionals/page.tsx +++ b/admin/src/app/professionals/page.tsx @@ -26,6 +26,7 @@ interface Professional { export default function ProfessionalsPage() { const [approved, setApproved] = useState([]); const [pending, setPending] = useState([]); + const [rejected, setRejected] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -35,8 +36,9 @@ export default function ProfessionalsPage() { Promise.all([ api.get<{ data: Professional[]; meta: any }>('/professionals').then((r) => r.data).catch(() => [] as Professional[]), api.get<{ data: Professional[]; meta: any }>('/professionals/pending').then((r) => r.data).catch(() => [] as Professional[]), + api.get<{ data: Professional[]; meta: any }>('/professionals/rejected').then((r) => r.data).catch(() => [] as Professional[]), ]) - .then(([a, p]) => { setApproved(a); setPending(p); }) + .then(([a, p, r]) => { setApproved(a); setPending(p); setRejected(r); }) .catch(() => setError('Error al cargar los profesionales')) .finally(() => setLoading(false)); }, []); @@ -55,6 +57,12 @@ export default function ProfessionalsPage() { load(); }; + const reactivate = async (id: string) => { + await api.post(`/professionals/${id}/set-pending`); + toast.success('Movido a revisión'); + load(); + }; + return (

Profesionales

@@ -70,6 +78,7 @@ export default function ProfessionalsPage() { Activos ({approved.length}) Pendientes ({pending.length}) + No aprobados ({rejected.length}) @@ -170,6 +179,61 @@ export default function ProfessionalsPage() { + + + + + + + Nombre + Email + Teléfono + RETHUS + Acción + + + + {loading ? ( + Cargando... + ) : rejected.length === 0 ? ( + Sin resultados + ) : ( + rejected.map((p) => ( + + + + {p.users?.name} + + + {p.users?.email || '—'} + {p.users?.phone || '—'} + + {p.rethus_code ? ( +
+ {p.rethus_validated + ? + : } + {p.rethus_code} +
+ ) : ( + No aplica + )} +
+ + + + + + + +
+ )) + )} +
+
+
+
+
)}
diff --git a/backend/src/professionals/professionals.controller.ts b/backend/src/professionals/professionals.controller.ts index ff5d9f0..8ba7f99 100644 --- a/backend/src/professionals/professionals.controller.ts +++ b/backend/src/professionals/professionals.controller.ts @@ -30,6 +30,15 @@ export class ProfessionalsController { return this.pros.findPendingApprovals(page, limit); } + @Get('rejected') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + findRejected(@Req() req) { + const page = +(req.query.page || 1); + const limit = +(req.query.limit || 20); + return this.pros.findRejected(page, limit); + } + @Post(':id/approve') @UseGuards(JwtAuthGuard) @ApiBearerAuth() diff --git a/backend/src/professionals/professionals.service.ts b/backend/src/professionals/professionals.service.ts index 7fa742d..22fe53d 100644 --- a/backend/src/professionals/professionals.service.ts +++ b/backend/src/professionals/professionals.service.ts @@ -112,14 +112,30 @@ export class ProfessionalsService { async findPendingApprovals(page = 1, limit = 20) { const skip = (page - 1) * limit; + const where = { is_active: false, users: { pro_state: 1 } }; const [data, total] = await Promise.all([ this.prisma.professionals.findMany({ - where: { is_active: false }, + where, skip, take: limit, - include: { users: { select: { id: true, name: true, email: true, phone: true, created_at: true } } }, + include: { users: { select: { id: true, name: true, email: true, phone: true, created_at: true, pro_state: true } } }, }), - this.prisma.professionals.count({ where: { is_active: false } }), + this.prisma.professionals.count({ where }), + ]); + return { data, meta: { total, page, limit, totalPages: Math.ceil(total / limit) || 1 } }; + } + + async findRejected(page = 1, limit = 20) { + const skip = (page - 1) * limit; + const where = { is_active: false, users: { pro_state: 3 } }; + const [data, total] = await Promise.all([ + this.prisma.professionals.findMany({ + where, + skip, + take: limit, + include: { users: { select: { id: true, name: true, email: true, phone: true, created_at: true, pro_state: true } } }, + }), + this.prisma.professionals.count({ where }), ]); return { data, meta: { total, page, limit, totalPages: Math.ceil(total / limit) || 1 } }; } @@ -146,11 +162,8 @@ export class ProfessionalsService { if (!prof) throw new NotFoundException('Profesional no encontrado'); await this.prisma.$transaction([ - this.prisma.professionals.delete({ where: { id: professionalId } }), - this.prisma.users.update({ - where: { id: prof.user_id }, - data: { pro_state: 3 }, - }), + this.prisma.professionals.update({ where: { id: professionalId }, data: { is_active: false } }), + this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 3 } }), ]); return { message: 'Solicitud rechazada' }; }