Contact Request (descubrimiento cross-tenant)
Objetivo
Permitir que personal de una clínica distinta acceda al perfil de un dueño externo, con el dueño confirmando en su propio dispositivo — sin que el código pase nunca por quien lo solicita.
Cómo empieza
Staff en petid-crm, sección Usuarios externos, hace click en un usuario con externalAccess = true.
Pasos principales
POST /users/:userId/contact-request→CreateContactRequestUseCase: validatarget.externalAccess, genera código de 6 dígitos (randomInt(100000, 999999)), lo guarda hasheado en Redis (ContactRequest, TTL 15 min), emiteContactRequestCreatedEvent. Devuelve solo{ requestId }— el código nunca viaja en esta respuesta.- El evento dispara push (
contact-request-push) al dispositivo del target, con el código en eldatasilencioso del push, no en el título/cuerpo visible. petid-clientrecibe el push →access-request.tsx, gateado porLocalAuthentication.authenticateAsync()(biometría del dispositivo) antes de mostrar el código.- El requester obtiene el código verbalmente del dueño y lo introduce →
POST /users/:userId/verify-code { requestId, code }→VerifyContactRequestUseCasevalida víaContactRequest.isValidFor(userId, hashedCode, now)y devuelve el perfil.
Reglas de negocio
- Un solo error genérico (
ContactRequestInvalidError, 400) cubre tanto código incorrecto como expirado — el cliente no puede distinguir cuál fue. No hay 410 vs 422 separados. markUsedno borra el registro ni toca el TTL de Redis — elusedAtqueda seteado pero la key sigue viva hasta su expiración natural (por si hiciera falta auditar).
Interacciones
petid-crm → petid-api → evento → notification-api (push) → petid-client (biometría) → dueño le dicta el código al staff → petid-crm de vuelta a petid-api.
Dónde seguir mirando
petid-api/src/application/users/create-contact-request.use-case.ts,verify-contact-request.use-case.tspetid-api/src/domain/contact-requests/contact-request.entity.ts(isValidFor)petid-api/src/infrastructure/persistence/contact-requests/redis-contact-request.repository.tspetid-client/src/app/(home)/access-request.tsx
Casos especiales
- El TTL físico de Redis se calcula como
floor((expiresAt - now) / 1000)al crear el registro — por el redondeo hacia abajo, la key de Redis puede desaparecer hasta ~1 segundo antes deexpiresAt. En la práctica: unverify-codejusto en el segundo límite puede fallar por “no encontrado” aunque lógicamente debería seguir siendo válido. - Esto es exclusivo de petid — la misma feature existía en
template-api/template-crm/template-expopero se eliminó por completo el 2026-09-02 (no tenía sentido en un template genérico). Si buscás referencias en esos repos, no las vas a encontrar.