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.
🧠 Objetivo
Section titled “🧠 Objetivo”Permitir el registro dinámico de usuarios en distintas verticales del proyecto, enviando la información necesaria a Strapi con un payload adaptado.
📁 Ubicación del servicio
Section titled “📁 Ubicación del servicio”/services/strapi/registration.ts
🧱 Estructura del servicio
Section titled “🧱 Estructura del servicio”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 }}🧱 Interfaces o tipos utilizados
Section titled “🧱 Interfaces o tipos utilizados”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}🛠️ Uso en el formulario de Registro
Section titled “🛠️ Uso en el formulario de Registro”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ónconst { jwt } = storeToRefs(authStore) // Se extrae 'jwt' como referencia reactiva
const { data } = await useLocales<RegisterFormLocales>('register') // Carga los textos localizadosconst 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 formularioconst 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ísconst { 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 registroconst 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), },