feat(invitations): reenviar invitaciones pendientes desde admin

Backend:
- client-invitations.service.ts: funcion resendInvitation() que
  genera nuevo token, actualiza expiresAt y reenvia el email.
- Controller + routes: POST /invitations/client/:id/resend

Frontend:
- API client + hook useResendInvitation con invalidacion de cache.
- Pagina /admin/invitar-cliente: boton 'Reenviar' por cada
  invitacion pendiente en la tabla.

Refs: docs/CAMBIOS-2026-05-09.md
This commit is contained in:
Horux Dev
2026-05-13 23:19:07 +00:00
parent b3b2838b6d
commit 69bf7417a8
6 changed files with 88 additions and 2 deletions

View File

@@ -207,6 +207,46 @@ export async function registerFromInvitation(
};
}
export async function resendInvitation(invitationId: string, invitedByName: string) {
const invitation = await prisma.clientInvitation.findUnique({
where: { id: invitationId },
});
if (!invitation) {
throw new Error('Invitación no encontrada');
}
if (invitation.status !== 'pending') {
throw new Error('Solo se pueden reenviar invitaciones pendientes');
}
const newToken = crypto.randomBytes(32).toString('hex');
const newExpiresAt = new Date(Date.now() + INVITATION_EXPIRY_DAYS * 24 * 60 * 60 * 1000);
await prisma.clientInvitation.update({
where: { id: invitationId },
data: {
token: newToken,
expiresAt: newExpiresAt,
sentAt: new Date(),
},
});
const baseUrl = process.env.WEB_URL || 'https://horuxfin.com';
const registerUrl = `${baseUrl}/invitacion/registro/${newToken}`;
await emailService.sendClientInvitation(invitation.email, {
invitedByName,
registerUrl,
expiresAt: newExpiresAt.toLocaleDateString('es-MX', {
day: 'numeric',
month: 'long',
year: 'numeric',
}),
nombreDespacho: invitation.nombreDespacho,
});
return { message: 'Invitación reenviada' };
}
export async function listInvitations() {
return prisma.clientInvitation.findMany({
orderBy: { createdAt: 'desc' },