Commit Graph
101 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 b4857dbc75 fix: notify professional via FCM when service is created
Backend create() now looks up the professional's fcm_token and sends a
push notification after the service is persisted. This replaces the
unreliable client-side notification in the Flutter app (which couldn't
use the server FCM key).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 09:33:38 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d7a0769b46 feat: filter professionals by city and proximity
- findAllActive now accepts city, lat, lng params
- City filter: case-insensitive contains on users.city
- Proximity sort: Haversine within city before services/score ranking
- Controller exposes ?city=&lat=&lng= query params

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 20:35:03 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 00a6f637f6 feat: rank professionals by completed services + score, search by name/profession/city
- GET /professionals now accepts ?search= instead of ?city=/page/limit
- Filters each word across profession, user name, and user city (AND of OR)
- Counts completed services per professional via Prisma _count filter
- Sorts: most completed services first, then highest average_score
- Always returns top 7 results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 20:18:14 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 74be21eb4e fix: use 'as any[]' for notIn array to avoid readonly tuple type error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 20:02:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7d481b3451 Exclude self_booked/denied/cancelled from professional services list
findByProfessional had no status filter, so blocked slots (self_booked)
and historical cancelled/denied services appeared in the active view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 19:54:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c720911172 add configurable appointment slot duration per professional
DB: slot_duration_minutes INT DEFAULT 30 on professionals table
Backend DTO: slot_duration_minutes (optional int, 5-480 min) in UpdateProfessionalDto

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 19:24:49 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 afbf4d537e show gender on admin professional detail page
Add gender: true to users select in findById() (both lookup paths)
and render it in the personal data card in the admin frontend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 19:03:02 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2180e0480c add admin services-by-professional endpoint + calendar view in admin panel
Backend:
- GET /services/admin/professional/:id (JWT-guarded) returns paginated
  services for any professional with embedded user (client) info

Admin professional detail page:
- Replace broken all-services-then-filter with new endpoint
- Add month calendar with colored dots per day (click to filter table)
- Services table shows client name/phone, time, colored status badges
- Fix DAY_NAMES array (was 0=Domingo, now 0=Lunes per DB convention)
- Schedule row shows both ranges + continuous_day flag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 18:58:42 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8a97b52eb7 feat(services): add POST /services/block endpoint for slot blocking
- BlockSlotDto: validates day (ISO date) and range1_hour1 (HH:MM)
- ServicesService.blockSlot(): finds professional by userId, checks for
  conflicts (any non-cancelled/denied service at that slot), then creates
  a service with status=self_booked using the professional's own user_id
- ServicesController: POST /services/block (JWT-guarded) wired to blockSlot

Existing PATCH /services/:id/status → cancelled handles unblocking since
VALID_TRANSITIONS already allows self_booked → cancelled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 18:26:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c8b2c0c901 fix(services): add double-booking check and schedule validation on create
Before creating a service, now:
1. Checks if the requested slot (day + range1_hour1) is already occupied by
   a non-cancelled/denied service for that professional — rejects with 400
2. Validates the requested day has an enabled schedule for the professional;
   rejects if the professional does not work that weekday
3. Validates the requested hour falls within the configured hour ranges
   (continuous day: range1_hour1→range2_hour2; split day: morning + afternoon
   blocks) — rejects if outside configured hours

day_of_week conversion: JS Date.getDay() (0=Sun) → DB convention (0=Mon)
via (getDay()+6)%7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 17:55:55 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 72777df52c 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 <noreply@anthropic.com>
2026-07-02 21:42:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1e0de51ec5 feat(settings): add domicilios and tarifas toggles in admin panel
Both flags are read by prosappweb from /settings to enable/disable
delivery service and rate display. Saves via the existing PATCH /settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 20:48:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4d98c7cb70 feat(auth): reject login and invalidate token for blocked users
Checks is_active on email login, phone OTP login, and /auth/me so
existing tokens also stop working immediately after a user is blocked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:56:28 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a8842a2728 feat(users): add block/unblock and delete from admin panel
- Migration 0004: adds is_active column to users table
- Backend: DELETE /users/:id endpoint + is_active field in UpdateUserDto
- Admin UI: block/unblock toggle and delete with confirmation on user detail
- Users list: shows "Bloqueado" badge for inactive users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:55:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 04f1801b89 fix(entrypoint): let 0001 migration run so rethus columns are created on deploy
Baselining 0001 caused rethus_code/rethus_validated to never be added
automatically. The SQL already uses IF NOT EXISTS so it is safe to deploy
on every fresh container even if the columns already exist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:34:46 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 57c159e91f fix(migrations): baseline existing tables + add 0003 for missing suggestions table
- entrypoint.sh: resolve --applied 0000 and 0001 before deploying,
  so prisma skips them and only runs 0002 (message_logs) and 0003 (suggestions)
- migration 0003: CREATE TABLE IF NOT EXISTS suggestions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:17:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9703b1f08e fix(db): use postgres container name as host instead of IP, fix space in user
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:10:27 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f6e34807ce fix(network): revert to bridge network — host mode breaks Docker internal routing to 10.0.1.9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:59:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 46f3085432 fix(network): use host network mode so backend can reach LAN postgres at 10.0.1.9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:42:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 54bc39bb34 fix(config): hardcode DATABASE_URL in entrypoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:28:28 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e9e3d8141d fix(entrypoint): remove set -e, add verbose logging, don't block on migration failure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:24:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6288b9e43b chore(config): add DATABASE_URL to backend service in docker-compose
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:14:09 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1e63ed1873 feat(db): idempotent migrations + auto-deploy on startup
- Rewrite 0000_init with IF NOT EXISTS on all tables, indexes and enums;
  wrap FK constraints in DO/EXCEPTION blocks so re-runs never fail
- Add missing suggestions table to init migration
- Add migration 0002 to create message_logs table (IF NOT EXISTS)
- Add entrypoint.sh: runs prisma migrate deploy before starting the server
- Update Dockerfile to copy entrypoint.sh and use it as CMD
- Add prisma:migrate script to package.json for manual runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:10:33 -05:00
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