Commit Graph
78 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 749a1e860d feat(professionals): send FCM push notifications on status changes
Same events that trigger email (approve, deny, deactivate, setPending, upsert)
now also call tryPush() to send a silent FCM push to the professional's device.
NotificationsModule added to ProfessionalsModule imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 08:57:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1f3efb50da Add SMTP test email endpoint and mail logs in admin settings
- POST /settings/test-email: send test email to diagnose SMTP config
- Admin settings: test email form + last 20 email logs with status/error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:20:56 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b0ca6804fa Fix JSON parse error on empty responses: handle empty body in API client and return message from saveSmtpConfig
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:12:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 27f4a2d7ec Add transactional emails: welcome, professional submission, status changes
- mail/mail.service.ts: MailService with HTML templates (welcome, submitted,
  approved, rejected, pending, deactivated). Silent if SMTP not configured.
- mail/mail.module.ts: global module so all services can inject MailService
- auth.service.ts: send welcome email on register()
- professionals.service.ts: notify admin on new submission; notify professional
  on approve/deny/deactivate/setPending

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:01:45 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0a67f5d80a Add Verifik RETHUS integration: backend module, settings auth flow, admin query button
- verifik.service.ts: email OTP auth, token storage/refresh, RETHUS query by professional
- verifik.controller.ts: send-otp, confirm, refresh, rethus/professional/:id endpoints
- settings/page.tsx: Verifik connection section (email→OTP→token)
- professionals/[id]/page.tsx: Consultar RETHUS button with inline result display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:50:35 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 66bc47b6e1 Fix UpdateProfessionalDto: add identification_picture and certificate_picture
These fields were missing from the DTO so NestJS/class-validator was stripping
them from PATCH /professionals/me requests before they reached Prisma.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:41:27 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 aa35f4b661 Always show documents section in professional detail, with No subido fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:31:40 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b0e3467565 Fix resetRejected: always reset pro_state even without professional record
Previous code deleted the professional record; if pro_state=3 but no record
exists, the 404 blocked the retry. Now we unconditionally reset pro_state=0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:03:31 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5f02c45759 Fix retry flow: reset pro_state without deleting professional record
- resetRejected: only set pro_state=0, no DELETE (avoids FK constraint errors
  from services/reputations with onDelete: NoAction)
- upsert: detect re-submission after rejection (pro_state=0) and set pro_state=1
  so the request appears in admin pending list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:00:38 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0899105a52 Add rejection retry flow with configurable wait days
- Backend: DELETE /professionals/me resets rejected state so user can retry
- Backend: findRejected/findPending now filter by pro_state
- Admin settings: rejection_wait_days field (default 7)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 18:03:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 3fd7a33fc1 Add rejected professionals tab and fix deny to keep record
- deny() now marks is_active:false instead of deleting the record
- New GET /professionals/rejected endpoint (pro_state:3)
- findPendingApprovals now filters only pro_state:1
- Admin list: third tab 'No aprobados' with approve/review actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 18:00:03 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 86c6f3e6b7 Add persistent volume for uploads to survive redeployments
Files uploaded by users were lost on every container rebuild.
Named volume uploads_data mounts to /app/uploads and is managed by Coolify.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:55:29 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 aa241d733d Set STORAGE_URL so uploaded files get public URLs
Without this, files were saved with http://localhost:3000/uploads/... URLs
which are unreachable from the browser. Now uses the public backend domain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:54:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7935e48fae Add status management and documents view to pro detail page
- Backend: deactivate and set-pending endpoints for status control
- Admin detail: Aprobar/Rechazar/Desactivar/En revisión buttons based on current state
- Admin detail: Documentos section showing ID photo and certificate
- Admin detail: deny now confirms before deleting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:49:28 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 efb48e017f Show email/phone in pro detail and improve RETHUS section
- Backend: include email and phone in users select for professionals
- Admin detail page: RETHUS section shows cedula+code with copy buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:47:01 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f845350911 Fix Decimal.toFixed crash and new professionals default is_active
- Detail page: use Number() before toFixed() since Prisma returns Decimal objects
- Service upsert: create professionals with is_active:false so they appear as pending

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:39:56 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 053f2d0b56 Fix pro_state field name mismatch in /auth/me response
Flutter reads 'professional_state' but Prisma returns 'pro_state'.
Add professional_state alias in me() response so proState is read correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:38:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5380a894b1 Fix useParams null crash on detail pages
useParams() can return null during SSR — destructuring null throws TypeError.
Use optional chaining params?.id to prevent the crash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:35:42 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d1f93d6d13 Add Ver button to active professionals in admin list
Active professionals now have a link to the detail page where
they can be edited, RETHUS validated, etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:27:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d551739d0c Fix upsert: create professional record if none exists
PATCH /professionals/me now works for first-time submissions.
Creates the record with pro_state=1 if user has no professional entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:20:35 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ce769dfe8d Fix professional 500 error, add SMTP config UI and message history page
- Fix route ordering in professionals.controller: PATCH me was captured by PATCH :id causing 500
- Admin settings: add SMTP email config section (host, port, user, pass, sender)
- Admin sidebar: add Mensajes link
- Admin: new /mensajes page with SMS/email history, channel filter and pagination

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:08:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6da45ac9d1 chore: JWT expira en 365 días (1 año)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 11:38:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 30588effec feat: endpoint GET /locations/cities/all para lista plana de ciudades
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 11:38:26 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 522adc3b74 feat: SMTP config en DB, historial mensajes, JWT 90d, email OTP con Prisma
- schema.prisma: modelo message_logs (channel, recipient, body, status, error)
- email-otp.service.ts: lee config SMTP desde DB (settings.smtp_config), registra log en message_logs
- sms.service.ts: registra log en message_logs tras cada envío (éxito y error)
- auth.module.ts: agrega PrismaModule, JWT expira en 90d
- settings.controller.ts: GET/PATCH /settings/smtp + GET /settings/message-logs
- settings.service.ts: método getMessageLogs con paginación y filtro por canal
- settings.module.ts: importa AuthModule + EmailOtpService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 11:35:17 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a9b5e413e3 feat: página admin /admin-sugerencias + enlace en sidebar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 19:46:30 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8a1f9687cd feat: filtrar profesionales por ciudad con query param ?city=
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 19:26:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f94c2f96b0 fix: POST /suggestions requiere JWT (solo usuarios autenticados)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 19:23:20 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c919bb737f fix: reemplazar Textarea (no existe) con textarea nativo en página sugerencias
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 19:19:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 42d41eb199 feat: módulo de sugerencias (backend) + página pública /sugerencias (admin)
- Backend: POST /suggestions (público) + GET /suggestions (JWT admin)
- Admin: página pública /sugerencias con formulario de sugerencias
- schema.prisma: modelo suggestions
- AuthGuard + Sidebar: /sugerencias como ruta pública

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 19:05:58 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4e932c2347 revert: quitar url del datasource que rompió prisma generate en build
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:56:00 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a7562bca71 fix: quitar dummy URL de prisma generate, no necesita conexión real
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:54:46 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 446c79fbfe fix: quitar prisma migrate deploy del CMD, se corre manualmente
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:53:07 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8c05b09f28 fix: prisma generate usa DATABASE_URL dummy en build (no necesita conexión real)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:52:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 21d7707bc0 fix: agregar DATABASE_URL al datasource de Prisma y CMD no-fatal para migrate
- schema.prisma: url = env("DATABASE_URL") era requerido por prisma migrate deploy
- Dockerfile: cambiar && por ; para que el servidor arranque aunque migrate falle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:50:44 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f2bd710694 fix: correr prisma migrate deploy al arrancar el servidor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:29:56 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c1e9dba2d1 feat: campo RETHUS con validación manual desde el admin
- Prisma schema: rethus_code y rethus_validated en tabla professionals
- Migración 0001: ALTER TABLE agrega ambas columnas
- DTO: rethus_code y rethus_validated en CreateProfessionalDto y UpdateProfessionalDto
- Service: nuevo método validateRethus, rethus_code incluido en requestProfessional
- Controller: endpoint POST /professionals/:id/validate-rethus (JWT protegido)
- Admin listado pendientes: columna RETHUS con ícono verde/amarillo y enlace al detalle
- Admin detalle profesional: card de validación RETHUS con botón consultar Minsalud y marcar como validado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 18:23:55 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 46fcb57edf fix: settings siempre retorna objeto, autocomplete Colombia con bias de ubicacion
- GET /settings retorna {} en lugar de undefined cuando no hay configuracion
  (evita error jsonDecode en Flutter que dejaba soporte en gris)
- Autocomplete: agrega components=country:co, radius 20km, remove types=address
  para mostrar mas resultados cercanos al usuario

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 10:11:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f66b1a9bc1 fix: agregar campos soporte faltantes en admin settings
- support_title, support_description, support_days, support_hours
  (los que lee la app Flutter para la pantalla de soporte)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 09:54:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7e68fcc2bb feat: email OTP para vincular correo al perfil
- EmailOtpService: genera/verifica codigo 6 digitos via SMTP
  (requiere env vars: SMTP_HOST, SMTP_USER, SMTP_PASS, EMAIL_FROM)
- POST /auth/send-email-otp: envia codigo al correo indicado
- POST /auth/link-email-otp: verifica codigo y vincula email+pass
- nodemailer agregado como dependencia

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 09:35:01 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d3ca99fa1b feat: agregar endpoint /autocomplete en admin Next.js
Proxy a Google Places Autocomplete API:
- Lee la API key de /settings/maps-key del backend
- Acepta params: input, location
- Devuelve { results: [{ formatted_address, place_id }] }
- Headers CORS para permitir acceso desde app.prosapp.co

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 08:51:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 abc19505ae feat: edicion de usuarios y profesionales desde el admin
Backend: agrega PATCH /users/:id y PATCH /professionals/:id (protegidos).
Admin: pagina de usuario con edicion de nombre/telefono/ciudad/genero;
pagina de profesional con dos secciones editables independientes
(datos personales y perfil profesional).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 08:22:40 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e7afbf6d7b feat: restore global settings with proper fields in settings page
Restores: version Android/iOS, horario soporte, email/telefono soporte,
modo mantenimiento. Organizado en secciones: Ajustes generales,
Integraciones (Maps), Documentos legales.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 08:02:29 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 3e1632e776 feat: Google Maps API key management in admin settings
- Backend: GET /settings/maps-key (public) + PATCH /settings/maps (protected)
- Admin: sección para guardar/ver estado de la Maps API key
- Flutter web carga la key dinámicamente desde el backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 07:59:19 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 39c4301d1b feat: full locations CRUD + legal policies in settings
Backend:
- CRUD completo para countries, regions y cities
- GET /settings/policy/:key — página HTML pública para políticas
- GET/PATCH /settings/policies/:key — admin endpoints protegidos

Admin:
- /locations: árbol interactivo País → Región → Ciudad con add/edit/delete
- /settings: editor de Política de Privacidad y Términos con enlace público copiable
- Sidebar: Ciudades → Ubicaciones

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 07:45:53 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5c740420e9 fix: return JSON body from send-otp endpoint
Flutter client calls jsonDecode on response body — empty void response
throws FormatException even when SMS was sent successfully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 22:11:29 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a067e5d260 fix: handle network errors and non-JSON SMS responses as 400 not 500
Catch fetch network errors and non-JSON response bodies to prevent
unhandled exceptions from becoming opaque 500s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 19:21:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5c3e3c4bc8 fix: handle null in service status select onValueChange
TypeScript error: Select onValueChange passes string | null, not string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 16:44:49 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a81a01a06d feat: use real ProsApp logo in admin login and sidebar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 16:15:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a5c1dd7c33 feat: improve admin login UI and sidebar branding
- Login page: split layout with gradient panel (desktop) + form,
  ProsApp logo mark, feature list, consistent blue brand colors
- Sidebar: replace plain text with logo mark + brand name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 16:12:56 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7783cac3fe feat: SMS OTP auth, phone verification gate, admin comments/edit/status pages
Backend:
- Add SmsService + SmsModule: send OTP via u-site.app provider, 5-min TTL
- Auth endpoints: POST /auth/send-otp, POST /auth/phone (login by phone+code),
  POST /auth/verify-phone (link), PATCH /auth/change-password
- is_phone_verified included in JWT token response
- GET /comments (admin, JWT-protected) with author/destination names

Admin:
- Users list: link to detail page per row
- User detail: inline edit form (name, city, phone) with PATCH /users/:id
- Services list: link to detail page per row
- Service detail: status change dropdown (PATCH /services/:id/status)
- New Comments page: summary stats + full table with star ratings
- New SMS settings page: configure API key + send test SMS
- Sidebar: added Comments and SMS entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 15:53:32 -05:00