feat(invitations): flujo de invitacion de clientes por email
Backend:
- Nuevo modelo Prisma ClientInvitation con token unico, expiracion
y estados (pending/accepted/expired).
- Migracion: 20260511213955_add_client_invitations
- Service client-invitations.service.ts: crear invitacion,
validar token, registrar desde invitacion (reutiliza logica
de creacion de tenant + usuario de despacho.service).
- Controller + routes: POST /invitations/client (admin),
GET /invitations/client/validate/:token (publico),
POST /invitations/client/register/:token (publico),
GET /invitations/client (admin).
- Email template client-invitation.ts con link a
/invitacion/registro/{token}.
- Agregado sendClientInvitation a email.service.
Frontend:
- Pagina /invitacion/registro/[token] para que el invitado
complete registro (nombre, password, despacho, RFC, perfil).
- Pagina /admin/invitar-cliente para que admin global envie
invitaciones y vea el historial.
- Hooks useCreateInvitation, useValidateInvitationToken,
useRegisterFromInvitation, useClientInvitations.
- API client lib/api/client-invitations.ts.
Infra:
- PM2 ecosystem.config.js: usa node --import tsx con
kill_timeout aumentado a 15s para evitar EADDRINUSE.
- React Query retry=2 con delay exponencial para resiliencia.
Refs: docs/CAMBIOS-2026-05-09.md
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "client_invitations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"invited_by" TEXT NOT NULL,
|
||||
"nombre_despacho" TEXT,
|
||||
"rfc" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"token" TEXT NOT NULL,
|
||||
"sent_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"accepted_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "client_invitations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "client_invitations_token_key" ON "client_invitations"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "client_invitations_token_idx" ON "client_invitations"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "client_invitations_status_idx" ON "client_invitations"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "client_invitations_email_idx" ON "client_invitations"("email");
|
||||
@@ -500,6 +500,27 @@ model TrialInvitation {
|
||||
@@map("trial_invitations")
|
||||
}
|
||||
|
||||
/// Invitaciones para nuevos clientes enviadas por admin global.
|
||||
/// El destinatario recibe un email con un link para completar su registro.
|
||||
model ClientInvitation {
|
||||
id String @id @default(uuid())
|
||||
email String
|
||||
invitedBy String @map("invited_by")
|
||||
nombreDespacho String? @map("nombre_despacho")
|
||||
rfc String?
|
||||
status String @default("pending") // pending | accepted | expired
|
||||
token String @unique
|
||||
sentAt DateTime @default(now()) @map("sent_at")
|
||||
expiresAt DateTime @map("expires_at")
|
||||
acceptedAt DateTime? @map("accepted_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([token])
|
||||
@@index([status])
|
||||
@@index([email])
|
||||
@@map("client_invitations")
|
||||
}
|
||||
|
||||
/// Catálogo despacho — precios + limits editables por admin global.
|
||||
/// Las `features` siguen viviendo en TS (`DESPACHO_PLANS` en `@horux/shared`)
|
||||
/// porque están acopladas a UI/middleware y son contrato de código.
|
||||
|
||||
Reference in New Issue
Block a user