Skip to content

Registro de Usuarios por Vertical (Strapi)

En este apartado se documenta el flujo completo de registro de usuarios a través del endpoint personalizado de Strapi /api/user-endpoints/register, desde el frontend Nuxt 3.


Permitir el registro dinámico de usuarios en distintas verticales del proyecto, enviando la información necesaria a Strapi con un payload adaptado.


/services/strapi/registration.ts


El servicio registerUserForVertical recibe los datos del usuario desde un formulario (como RegisterForm.vue) y construye un payload válido para Strapi.

El servicio utiliza useFetch para enviar una solicitud POST al endpoint de registro de Strapi y maneja la respuesta adecuadamente.

import type {
UserRegistrationPayload,
Phone,
RegisteredUser,
} from '@/interfaces/api/strapi/register'
import type { Strapi } from '@/interfaces/api/common'
import type { LanguageCode } from '@/types/common'
import { isProduction } from '@/core/utils'
import { visibleVertical } from '@/core/config'
export const registerUserForVertical = async (
_lang: LanguageCode,
{
name,
surname,
nameLastName,
email,
password,
vertical,
code,
phone: phoneNumber,
country,
}: UserRegistrationPayload,
): Promise<Strapi<RegisteredUser> | null> => {
/**
* Selecciona el vertical actual según el entorno (producción o desarrollo)
* y se obtiene la URL base de Strapi eliminando una barra final si existe
*/
const strapiBaseUrl = (
isProduction() ? visibleVertical.productionApi : visibleVertical.developmentApi
).replace(/\/$/, '')
// Si nameLastName está presente, lo usa. Si no, lo construye a partir de name y surname
const fullName = nameLastName?.trim() || `${name} ${surname}`.trim()
// Construye el objeto phone si hay código y número, o deja undefined
const phone: Phone | undefined =
code && phoneNumber ? { codePhone: code, number: phoneNumber } : undefined
// Define el payload que se enviará a Strapi, incluyendo billing y mailing addresses
const payload = {
email,
password,
vertical,
nameLastName: fullName,
phone,
mailingAddress: {
name: fullName,
country,
phone,
},
billingAddress: {
name: fullName,
country,
phone,
},
}
try {
// Llama al endpoint de Strapi usando useFetch
const { data, error } = await useFetch<RegisteredUser>(
`${strapiBaseUrl}/api/user-endpoints/register`,
{
method: 'POST',
body: payload,
},
)
// Si hay error o no hay datos, devuelve null
if (error.value || !data.value) return null
// Devuelve los datos en formato Strapi
return {
data: data.value,
meta: {},
}
} catch {
// En caso de error inesperado, devuelve null
return null
}
}

Estas son las interfaces y tipos utilizados en el servicio de registro de usuarios. Hay que fijarse en la estructura del type Users en Strapi y adaptarlo a las necesidades del backend.

Los datos correspondientes a las direcciones de envío y facturación no son obligatorios, pero se pueden enviar aprovechando que estos datos ya están disponibles en el formulario de registro.

Se podrían modificar las interfaces para adaptarlas al payload que se quiera enviar a Strapi.

export interface Phone {
codePhone: string
number: string
}
export interface Address {
name: string
city: string
street: string
country: string
phone: Phone
}
export interface UserRegistrationParams {
name: string
surname: string
email: string
password: string
code: string
phone: string
country: string
vertical: string
nameLastName: string
}
export interface UserRegistrationPayload
extends Record<string, string | number | boolean> {
name: string
surname: string
email: string
password: string
code: string
phone: string
country: string
vertical: string
nameLastName: string
policy: boolean
}
export interface RegisteredUser {
id: number
email: string
username: string
phone: Phone
mailingAddress: Address
billingAddress: Address
nameLastName: string
}

En el formulario de registro, se utiliza el servicio registerUserForVertical para enviar los datos del usuario al endpoint de Strapi.

Se utiliza el composable useServices para llamar al servicio de registro y manejar la respuesta. El siguiente código muestra cómo se implementa el registro de usuarios en un componente Vue:

import { showAlert, message } from '@/constants/showAlert'
import type { RegisterFormLocales } from '@/interfaces/locales/verticals/shared/forms'
import type { RegisterData } from '@/types/forms'
import { sendZohoForm } from '@/core/utils'
import { visibleVertical } from '@/core/config'
import type {
UserRegistrationPayload,
RegisteredUser,
} from '@/interfaces/api/strapi/register'
import type { LoginResponse, LoginPayload } from '@/interfaces/api/strapi/login'
import type { Strapi } from '@/interfaces/api/common'
const authStore = useAuthStore() // Se obtiene la instancia de la store de autenticación
const { jwt } = storeToRefs(authStore) // Se extrae 'jwt' como referencia reactiva
const { data } = await useLocales<RegisterFormLocales>('register') // Carga los textos localizados
const registerFormLocales = ref(data)
const { data: registerLocales } = await useLocales<RegisterFormLocales>('register') // (Podría evitarse la doble carga)
const isEditing = ref(false) // Flag para modo edición del formulario
const countries = useCountries() // Hook que devuelve la lista de países
const verticalName = ref(wordCapitalizer(visibleVertical.name)) // Obtiene el nombre capitalizado del vertical
useInvalidRedirection(['registro', 'iniciar-sesion']) // Redirige si el usuario ya está autenticado
// Configura el formulario con campos iniciales, mensajes de error y códigos de país
const {
formData: register,
invalidFields,
validationMessages,
codes,
} = useForm<RegisterData, RegisterFormLocales>(
{
name: '',
surname: '',
country: '',
code: '',
phone: '',
email: '',
password: '',
policy: false,
youre_a_bot: false,
},
shallowRef(registerLocales),
)
// Lógica de registro
const registerProcess = async () => {
if (register.youre_a_bot) {
navigateTo('/') // Previene bots con redirección
return
}
// Se extraen los campos necesarios del formulario
const {
name,
surname,
email,
phone,
country,
code: phoneCode,
password,
policy,
} = register
// Se envía la información del registro a Zoho (no bloqueante)
sendZohoForm(visibleVertical, 'register', {
nombre: name,
apellidos: surname,
email,
telefono: phone,
pais: country[2],
codigoTelefono: phoneCode,
}).catch(() => {})
try {
// Se construye el payload del usuario a registrar
const payload: UserRegistrationPayload = {
email,
password,
phone,
code: phoneCode,
vertical: verticalName.value,
nameLastName: `${name} ${surname}`,
identificationNumber: 'N/A',
country: Array.isArray(country) ? country[2] : country,
policy,
name,
surname,
city: '',
}
// Llama al servicio de registro
const result = await useServices<Strapi<RegisteredUser>, UserRegistrationPayload>(
'userRegistration',
payload,
)
// Si el registro falla, se muestra error
if (!result || !result.data) {
showAlert.value = true
message.value = registerFormLocales.value?.api_error ?? ''
return
}
// Intenta iniciar sesión automáticamente con los datos registrados
const loginResult = await useServices<Strapi<LoginResponse>, LoginPayload>(
'userLogin',
{
email: payload.email,
password: payload.password,
vertical: payload.vertical,
},
)
// Si falla el login, también se muestra error
if (!loginResult || !loginResult.data?.jwt) {
showAlert.value = true
message.value = registerFormLocales.value?.api_error ?? ''
return
}
// Guarda el JWT en localStorage y en la store
localStorage.setItem('jwt', loginResult.data.jwt)
jwt.value = loginResult.data.jwt
// Redirige a la ruta deseada (por defecto '/')
const redirection = localStorage.getItem('redirection')
localStorage.removeItem('redirection')
navigateTo(redirection)
} catch {
// Cualquier error muestra el mensaje de error de la API
showAlert.value = true
message.value = registerFormLocales.value?.api_error ?? ''
}
}
</script>

🔧 Importacion del servicio en la vertical

Section titled “🔧 Importacion del servicio en la vertical”

Para utilizar el servicio en la vertical, hay que incluirlo en la interfaz de las verticales en /interfaces/api/verticals.ts

import type {
RegisteredUser,
UserRegistrationPayload,
} from '@/interfaces/api/strapi/register'
// ... otras importaciones
interface VerticalsServices {
experts: FunctionService<Expert[], FilterParams>
expertsBySlug: FunctionService<Expert, FilterParams>
partners: FunctionService<Partner[], FilterParams>
partnersBySlug: FunctionService<Partner, FilterParams>
premium?: FunctionService<PremiumPage>
dentalProcedure?: FunctionService<{ products: Product[] }>
series: FunctionService<Serie[], FilterParams>
seriesBySlug: FunctionService<Serie, FilterParams>
episodesBySlug: FunctionService<Episode, Slug>
seriesPage: FunctionService<Serie, FilterParams>
seriesByCategory: FunctionService<FilteredSeriesByCategory, FilterParams>
blogs: FunctionService<Blog[], FilterParams>
blogBySlug: FunctionService<Blog, FilterParams>
traumaHome?: FunctionService<TraumatologyHomeApi, FilterParams>
getHome?: FunctionService<unknown, FilterParams>
getLegalPages?: FunctionService<LegalPagesApi, FilterParams>
getModuleCategories: FunctionService<CategoriesByModule, ModuleCategoriesParams>
inspiriaTalks?: FunctionService<TalksCard[], FilterParams>
// ---> userRegistration: FunctionService<Strapi<RegisteredUser>, UserRegistrationPayload>
userLogin: FunctionService<Strapi<LoginResponse>, LoginPayload>
allContents: FunctionService<AllContents[]>
}
/// Verticales de ejemplo:
export interface TraumatologyVertical extends Verticals {
services: VerticalsServices & {
traumaHome: FunctionService<TraumatologyHomeApi, FilterParams>
getModuleCategories: FunctionService<CategoriesByModule, ModuleCategoriesParams>
// ---> userRegistration: FunctionService<Strapi<RegisteredUser>, UserRegistrationPayload>
userLogin: FunctionService<Strapi<LoginResponse>, LoginPayload>
}
zohoConfig?: ZohoConfig
}
export interface InspiriaVertical extends Verticals {
services: VerticalsServices & {
getModuleCategories: FunctionService<CategoriesByModule, ModuleCategoriesParams>
dentalProcedure: FunctionService<{ products: Product[] }>
inspiriaTalks: FunctionService<TalksCard[], FilterParams>
// ---> userRegistration: FunctionService<Strapi<RegisteredUser>, UserRegistrationPayload>
userLogin: FunctionService<Strapi<LoginResponse>, LoginPayload>
getInspiriaLive: FunctionService<InspiriaLive, FilterParams>
}
zohoConfig?: ZohoConfig
}

Y posteriormente, importar el servicio en el archivo de la vertical correspondiente en /constants/verticals.ts

export const INSPIRIA: InspiriaVertical = {
name: 'inspiria',
subdomain: 'inspiriadental',
i18nDirectory: 'inspiria',
primaryColor: '#92ffac',
secondaryColor: '#92ffac',
productionURL: 'https://pre.inspiriadental.com/',
productionApi: 'https://multiverticales.alebateducation.com/',
developmentApi: 'http://localhost:1337',
favicon: '/inspiria.png',
verticalLogo: '/inspiria-logo.svg',
exclusivePages: [
'plan-site',
'dental-procedures-simulator',
'videos-3d-formacion-dental',
'kit-digital',
'inspiria-talks',
'who-we-are',
],
strapiService: 'inspiria',
services: {
experts: async (lang, vertical: FilterParams) => await getExperts(lang, vertical),
expertsBySlug: async (lang, { slug, vertical }: FilterParams) =>
await getExpertsBySlug(lang, { slug, vertical }),
partners: async (lang, vertical: FilterParams) => await getPartners(lang, vertical),
partnersBySlug: async (lang, { slug, vertical }: FilterParams) =>
await getPartnersBySlug(lang, { slug, vertical }),
premium: getPremiumPage,
dentalProcedure: getDentalProcedurePage,
series: async (lang, vertical: FilterParams) => await getSeries(lang, vertical),
seriesBySlug: async (lang, { slug, vertical }: FilterParams) =>
await getSeriesBySlug(lang, { slug, vertical }),
episodesBySlug: async (lang, { slug }: Slug) =>
await getEpisodeBySlug(lang, { slug }),
seriesPage: async (lang, { vertical }: Vertical) =>
await getSeriesPage(lang, { vertical }),
seriesByCategory: async (lang, { slug, vertical }: FilterParams) =>
await getSeriesByCategory(lang, { slug, vertical }),
blogs: async (lang, vertical: FilterParams) => await getBlogs(lang, vertical),
blogBySlug: async (lang, { slug, vertical }: FilterParams) =>
await getBlogBySlug(lang, { slug, vertical }),
getLegalPages: async (lang) => await getLegalPages(lang),
getInspiriaLive: async (lang) => await getInspiriaLive(lang),
getModuleCategories: async (
lang,
{ categoryModule, vertical }: ModuleCategoriesParams,
) => await getModuleCategories(lang, { categoryModule, vertical }),
inspiriaTalks: async () => await getTalks(),
// ---> userRegistration: registerUserForVertical,
userLogin: loginUserForVertical,
allContents: async (lang) => await getAllContents(lang),
},