From 72777df52cdf023d5db3fc051d683a7b6aee3f1a Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 2 Jul 2026 21:42:37 -0500 Subject: [PATCH] fix: save and display professional profile address, payment methods, and docs - DTO: add payment_methods field so whitelist pipe no longer strips it - Service upsert(): extract payment_methods and write as Prisma nested upsert instead of passing flat object - Admin detail page: fix image regex to handle Firebase URLs with query params; fix payment_methods display for 1:1 Prisma relation (object, not array) Co-Authored-By: Claude Sonnet 4.6 --- admin/src/app/professionals/[id]/page.tsx | 22 ++++++++++++------- .../src/professionals/dto/professional.dto.ts | 10 ++++++++- .../professionals/professionals.service.ts | 20 ++++++++++++++--- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/admin/src/app/professionals/[id]/page.tsx b/admin/src/app/professionals/[id]/page.tsx index 135e42b..3960d67 100644 --- a/admin/src/app/professionals/[id]/page.tsx +++ b/admin/src/app/professionals/[id]/page.tsx @@ -23,7 +23,8 @@ interface Professional { rate?: number; average_score?: number; identification_picture?: string; certificate_picture?: string; banner_picture?: string; users?: User; schedules?: Schedule[]; - specializations?: Specialization[]; payment_methods?: PaymentMethod[]; + specializations?: Specialization[]; + payment_methods?: PaymentMethod | PaymentMethod[] | null; } interface Service { id: string; description?: string; rate?: number; status: string; day: string; } @@ -440,11 +441,12 @@ export default function ProfessionalDetailPage() { banner_picture: 'Foto de perfil / Banner', }; const url = professional[field]; + const isImage = url && /\.(jpg|jpeg|png|webp|gif)(\?|$)/i.test(url); return (

{labels[field]}

{url ? ( - url.match(/\.(jpg|jpeg|png|webp)$/i) ? ( + isImage ? ( {labels[field]} @@ -463,15 +465,19 @@ export default function ProfessionalDetailPage() { {/* Métodos de pago */} - {professional.payment_methods && professional.payment_methods.length > 0 && ( + {professional.payment_methods && ( Métodos de pago aceptados -
- {['nequi', 'datafono', 'transferencia'] - .filter((k) => (professional.payment_methods![0] as any)[k]) - .map((k) => {PAYMENT_LABELS[k]})} -
+ {(() => { + const pm = Array.isArray(professional.payment_methods) + ? (professional.payment_methods as PaymentMethod[])[0] + : professional.payment_methods as PaymentMethod; + const active = pm ? ['nequi', 'datafono', 'transferencia'].filter((k) => (pm as any)[k]) : []; + return active.length > 0 + ?
{active.map((k) => {PAYMENT_LABELS[k]})}
+ :

Ninguno configurado

; + })()}
)} diff --git a/backend/src/professionals/dto/professional.dto.ts b/backend/src/professionals/dto/professional.dto.ts index f5bf464..ef20a31 100644 --- a/backend/src/professionals/dto/professional.dto.ts +++ b/backend/src/professionals/dto/professional.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsOptional, IsArray, IsNumber, IsBoolean, MinLength, Matches } from 'class-validator'; +import { IsString, IsOptional, IsArray, IsNumber, IsBoolean, MinLength, Matches, IsObject } from 'class-validator'; export class CreateProfessionalDto { @IsString() @@ -80,6 +80,14 @@ export class UpdateProfessionalDto { @IsOptional() @IsString() location_preferences?: string; + + @IsOptional() + @IsObject() + payment_methods?: { + nequi?: boolean; + datafono?: boolean; + transferencia?: boolean; + }; } export class ScheduleDto { diff --git a/backend/src/professionals/professionals.service.ts b/backend/src/professionals/professionals.service.ts index e555b14..57914a3 100644 --- a/backend/src/professionals/professionals.service.ts +++ b/backend/src/professionals/professionals.service.ts @@ -83,11 +83,21 @@ export class ProfessionalsService { } async upsert(userId: string, data: any) { + const { payment_methods: pm, ...profData } = data; + + const buildPm = (forCreate = false) => { + if (!pm) return undefined; + const pmFields = { nequi: pm.nequi ?? false, datafono: pm.datafono ?? false, transferencia: pm.transferencia ?? false }; + return forCreate ? { create: pmFields } : { upsert: { update: pmFields, create: pmFields } }; + }; + const existing = await this.prisma.professionals.findUnique({ where: { user_id: userId } }); if (!existing) { await this.prisma.users.update({ where: { id: userId }, data: { pro_state: 1 } }); - const prof = await this.prisma.professionals.create({ data: { user_id: userId, is_active: false, ...data } }); + const createData: any = { user_id: userId, is_active: false, ...profData }; + if (pm) createData.payment_methods = buildPm(true); + const prof = await this.prisma.professionals.create({ data: createData }); const u = await this.prisma.users.findUnique({ where: { id: userId }, select: { name: true, email: true } }); if (u?.email) this.mail.sendProfessionalSubmitted(u.name || 'Usuario', u.email, data.profession || '').catch(() => {}); this.tryPush(userId, 'Solicitud recibida', 'Hemos recibido tu solicitud profesional. Te avisaremos cuando sea revisada.'); @@ -97,8 +107,10 @@ export class ProfessionalsService { // Re-submitting after rejection reset (pro_state=0): move back to pending const user = await this.prisma.users.findUnique({ where: { id: userId }, select: { pro_state: true, name: true, email: true } }); if (user?.pro_state === 0) { + const resubData: any = { ...profData, is_active: false }; + if (pm) resubData.payment_methods = buildPm(); await this.prisma.$transaction([ - this.prisma.professionals.update({ where: { user_id: userId }, data: { ...data, is_active: false } }), + this.prisma.professionals.update({ where: { user_id: userId }, data: resubData }), this.prisma.users.update({ where: { id: userId }, data: { pro_state: 1 } }), ]); if (user.email) this.mail.sendProfessionalSubmitted(user.name || 'Usuario', user.email, data.profession || '').catch(() => {}); @@ -106,7 +118,9 @@ export class ProfessionalsService { return this.prisma.professionals.findUnique({ where: { user_id: userId } }); } - return this.prisma.professionals.update({ where: { user_id: userId }, data }); + const updateData: any = { ...profData }; + if (pm) updateData.payment_methods = buildPm(); + return this.prisma.professionals.update({ where: { user_id: userId }, data: updateData }); } async updateSchedules(professionalId: string, schedules: any[]) {