Skip to content

✨ Servicio de Registro de Usuario por Medio de API

La API register-user está diseñada para permitir el registro de nuevos usuarios en el sistema, así como la sincronización de una contraseña específica por vertical en caso de que el usuario ya exista.

📒 Este diseño responde a una necesidad particular: que un mismo usuario (identificado por su correo electrónico) pueda tener distintas credenciales dependiendo de la vertical del sistema con la que esté interactuando.

La lógica de esta API no solo permite crear nuevos usuarios, sino que también contempla escenarios en los que un usuario ya está registrado pero está creando otra cuenta desde una vertical. En ese caso, la API actualizará sus credenciales de forma controlada sin sobrescribir las contraseñas previamente registradas.

Además del manejo de credenciales, esta API también permite registrar o actualizar algunos de los datos personales del usuario, incluyendo su número de teléfono, nombre completo, número de identificación y dos tipos de dirección: una de envío (mailingAddress) y una de facturación (billingAddress).

Esta API no requiere autenticación JWT, ya que está pensada como un endpoint público para el proceso inicial de registro o sincronización.


⚙️ Funcionamiento interno de la API

El componente completo del registro es el siguiente:

src\api\user-endpoints\controllers\register-user.ts
function validateRequestBody(body) {
validateKeys(body, User.VALID_USER_REGISTER_KEYS, User.FIELD_ROOT);
if (body.phone !== undefined) {
if (typeof body.phone !== User.FIELD_OBJECT)
throw new Error(ERROR_INVALID_FORMAT + User.COMPONENT_NAME_PHONE);
validateKeys(body.phone, User.VALID_PHONE_KEYS, User.COMPONENT_NAME_PHONE);
}
const addressFields = [
{
value: body.billingAddress,
validKeys: User.VALID_BILLING_ADDRESS_KEYS,
context: User.COMPONENT_NAME_BILLING,
},
{
value: body.mailingAddress,
validKeys: User.VALID_MAILING_ADDRESS_KEYS,
context: User.COMPONENT_NAME_MAILING,
},
];
for (const { value, validKeys, context } of addressFields) {
if (value !== undefined) {
if (typeof value !== User.FIELD_OBJECT)
throw new Error(ERROR_INVALID_FORMAT + context);
validateKeys(value, validKeys, context);
if (value.phone !== undefined) {
if (typeof value.phone !== User.FIELD_OBJECT)
throw new Error(ERROR_INVALID_FORMAT + `${context}.phone`);
validateKeys(value.phone, User.VALID_PHONE_KEYS, `${context}.phone`);
}
}
}
}
const validatePhoneFields = (phone?: {
codePhone?: string;
number?: string;
}) => {
if (!phone) return;
validateFieldPatterns(
{ codePhone: phone.codePhone },
{ codePhone: FIELD_VALIDATIONS.codePhone }
);
validateFieldPatterns(
{ number: phone.number },
{ number: FIELD_VALIDATIONS.number }
);
};
export default {
async registerUser(ctx) {
const body = ctx.request.body;
try {
validateRequestBody(ctx.request.body);
validateRequiredFields(body, User.VERIFICATION_KEYS);
const { email, password, vertical, ...cleanBody } = body;
validateFieldPatterns(
{ password: password },
{ password: FIELD_VALIDATIONS.password }
);
validatePhoneFields(cleanBody.phone);
validatePhoneFields(cleanBody.billingAddress?.phone);
validatePhoneFields(cleanBody.mailingAddress?.phone);
const strapiUser = await findOne(
User.USER,
User.FIELDS_USER,
{ email },
User.COMPONENTS_USER
);
if (strapiUser === NOT_FOUND) {
await create(User.USER, {
email,
password,
identificationNumber: cleanBody.identificationNumber,
nameLastName: cleanBody.nameLastName,
role: 2,
username: email,
verticalsUserPasswords: [
{
vertical,
verticalPassword: password,
},
],
phone: cleanBody.phone,
billingAddress: cleanBody.billingAddress,
mailingAddress: cleanBody.mailingAddress,
});
} else {
const updatedData = {
...strapiUser,
...cleanBody,
mailingAddress: {
...strapiUser.mailingAddress,
...cleanBody.mailingAddress,
},
billingAddress: {
...strapiUser.billingAddress,
...cleanBody.billingAddress,
},
};
await syncPasswordWithVerticals(email, password, vertical, updatedData);
}
const registeredUser = await findOne(
User.USER,
User.FIELDS_USER,
{ email },
User.COMPONENTS_USER
);
return ctx.send(registeredUser, 200);
} catch (error) {
return ctx.badRequest(error);
}
},
};

Cuando se realiza una solicitud a esta API, el primer paso es la validación del cuerpo para garantizar que no se estén enviando campos inesperados o mal estructurados.

function validateRequestBody(body) {
validateKeys(body, User.VALID_USER_REGISTER_KEYS, User.FIELD_ROOT);
if (body.phone !== undefined) {
if (typeof body.phone !== User.FIELD_OBJECT)
throw new Error(ERROR_INVALID_FORMAT + User.COMPONENT_NAME_PHONE);
validateKeys(body.phone, User.VALID_PHONE_KEYS, User.COMPONENT_NAME_PHONE);
}
const addressFields = [
{
value: body.billingAddress,
validKeys: User.VALID_BILLING_ADDRESS_KEYS,
context: User.COMPONENT_NAME_BILLING,
},
{
value: body.mailingAddress,
validKeys: User.VALID_MAILING_ADDRESS_KEYS,
context: User.COMPONENT_NAME_MAILING,
},
];
for (const { value, validKeys, context } of addressFields) {
if (value !== undefined) {
if (typeof value !== User.FIELD_OBJECT)
throw new Error(ERROR_INVALID_FORMAT + context);
validateKeys(value, validKeys, context);
if (value.phone !== undefined) {
if (typeof value.phone !== User.FIELD_OBJECT)
throw new Error(ERROR_INVALID_FORMAT + `${context}.phone`);
validateKeys(value.phone, User.VALID_PHONE_KEYS, `${context}.phone`);
}
}
}
}

Asi como tambien valida los campos mediante sus regex correspondientes para cada uno de los campos de telefono y contraseña. esta se encuentra en el mismo URL que el de arriba->validateFieldPatterns.

const validatePhoneFields = (phone?: {
codePhone?: string;
number?: string;
}) => {
if (!phone) return;
validateFieldPatterns(
{ codePhone: phone.codePhone },
{ codePhone: FIELD_VALIDATIONS.codePhone }
);
validateFieldPatterns(
{ number: phone.number },
{ number: FIELD_VALIDATIONS.number }
);
};
validateFieldPatterns(
{ password: password },
{ password: FIELD_VALIDATIONS.password }
);
validatePhoneFields(cleanBody.phone);
validatePhoneFields(cleanBody.billingAddress?.phone);
validatePhoneFields(cleanBody.mailingAddress?.phone);

asegurandose contengan únicamente las propiedades válidas.

🧾 Datos esperados en el cuerpo de la solicitud (body)

El cuerpo debe enviarse en formato JSON y puede incluir los siguientes campos:

  • email (obligatorio): correo electrónico único del usuario.
  • password (obligatorio): contraseña que se asociará a la vertical.
  • vertical (obligatorio): nombre de la vertical para la cual se está registrando la contraseña.
  • identificationNumber: número de identificación oficial o empresarial.
  • nameLastName: nombre completo del usuario.
  • phone: teléfono de contacto en un objeto con los campos codePhone y number.
  • mailingAddress: dirección de envío, con campos como name, street, city, zipCode, country, etc.
  • billingAddress: dirección de facturación, con campos como name, company, identificationNumber, phone(Como objeto igualmente), street, city, country, etc.

Antes de procesar cualquier operación, el controlador valida que solo se estén enviando las claves permitidas, tanto a nivel raíz como en las direcciones anidadas.


Una vez validado el contenido, se separan las credenciales (email, password, vertical) del resto de los datos (cleanBody). Esto facilita el manejo diferenciado de las contraseñas y los datos personales del usuario.

const { email, password, vertical, ...cleanBody } = body;

Luego, el sistema intenta localizar un usuario existente con el mismo correo electrónico. Aquí se distinguen dos posibles caminos:

  • Si el usuario no existe, se crea uno nuevo en la base de datos con todos los datos proporcionados. También se inicializa el campo verticalsUserPasswords, que es un arreglo de objetos con las claves vertical y verticalPassword. Esto permite que un mismo usuario pueda autenticarse con distintas contraseñas dependiendo de la vertical en la que esté operando.
if (strapiUser === NOT_FOUND) {
await create(User.USER, {
email,
password,
identificationNumber: cleanBody.identificationNumber,
nameLastName: cleanBody.nameLastName,
role: 2,
username: email,
verticalsUserPasswords: [
{
vertical,
verticalPassword: password,
},
],
phone: cleanBody.phone,
billingAddress: cleanBody.billingAddress,
mailingAddress: cleanBody.mailingAddress,
});
}
  • Si el usuario ya existe, entonces se invoca una función auxiliar llamada syncPasswordWithVerticals. Esta función verifica si el usuario ya tiene una contraseña asociada a la vertical solicitada. Si ya existe, lanza un error y detiene el proceso para evitar sobrescrituras no deseadas. Si no existe aún una contraseña para esa vertical, la agrega al arreglo verticalsUserPasswords. Esta operación es atómica y segura, y además permite actualizar el resto de los datos del usuario si fueron incluidos en la solicitud.

Antes de eso se hace un llenado de la información del usuario por si al cambiar la contraseña es necesario actualizar alguno de los datos anteriormente mencionados del usuario sin sobrescribir los datos antiguos si no están siendo modificados 👍

else {
const updatedData = {
...strapiUser,
...cleanBody,
mailingAddress: {
...strapiUser.mailingAddress,
...cleanBody.mailingAddress,
},
billingAddress: {
...strapiUser.billingAddress,
...cleanBody.billingAddress,
},
};
await syncPasswordWithVerticals(email, password, vertical, updatedData);
}

🔐 ¿Qué hace exactamente syncPasswordWithVerticals?

  • Vease su funcionamiento Aquí.

🔐 Encriptado de contraseñas

  • ⚠️ Para el encriptado de las contraseñas vease Aquí igualmente.

📤 Ejemplo de solicitud

URL: /api/user-endpoints/register

Método: POST

Body ejemplo:

{
"email": "prueba@prueba.com",
"password": "MiContraseñaSegura123",
"vertical": "Pharma",
"phone": { "codePhone": "+52", "number": "5551234567" },
"identificationNumber": "ABC123456",
"nameLastName": "Fernando Rodríguez",
"mailingAddress": {
"name": "2donombre",
"city": "CIUDADO"
},
"billingAddress": {
"name": "123123123"
}
}

✅ Respuestas esperadas

✔️ Registro exitoso

{
"id": 1,
"email": "nuevo@email.com",
"nameLastName": "Fernando Nuevo",
...
}

❌ Campos inválidos o faltantes

{
"data": null,
"error": {
"status": 400,
"name": "Error",
"message": "Missing required parameters: password",
"details": {}
}
}
{
"data": null,
"error": {
"status": 400,
"name": "Error",
"message": "Not Allowed fields at root: passwordss",
"details": {}
}
}

❌ Contraseña ya registrada para esa vertical

{
"data": null,
"error": {
"status": 400,
"name": "Error",
"message": "Error: A password record already exists for this vertical",
"details": {}
}
}