Commit Graph

5 Commits

Author SHA1 Message Date
Beldor ffa32a113d fix(registro): guardar el telefono en el formato que usa WhatsApp
Revisión con IA / ai-review (pull_request) Successful in 2m12s
Un cliente registrado por la web no lo reconocia el bot: el formulario dejaba
escribir el numero en cualquier formato (1141984844, 01157260394) mientras que
Meta manda el wa_id 5491141984844, asi que la busqueda por telefonoNorm no
encontraba nada, mandaba al alta y ahi chocaba contra el email ya registrado.
Quedaba en un callejon sin salida.

Ahora el campo muestra "+54 9" fijo y grisado y el cliente completa solo la
caracteristica (sin el 0) y el abonado (sin el 15), 10 digitos. El backend
valida ese formato y guarda telefonoNorm ya como lo espera el bot. Se rechaza
tambien un telefono repetido, que antes reventaba con el indice unico.

Se agrega scripts/chat-bot.ts, que es como se encontro esto: permite conversar
con el bot desde la consola contra cualquier base, sin Meta ni credenciales.
Corriendolo contra una copia de la base de produccion se ve el ciclo completo:
alta por web -> el bot lo reconoce por telefono -> pide el email -> reserva.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:56:15 -03:00
Beldor 91112862a5 fix: los cuatro hallazgos reales de la revision con IA
Revisión con IA / ai-review (pull_request) Successful in 29s
- crearTurno verificaba disponibilidad y creaba el turno en dos operaciones
  sueltas: dos clientes podian pasar la verificacion a la vez y sobrevender el
  mismo horario. Ahora va todo en una transaccion, serializada por jefe con
  pg_advisory_xact_lock. slotEstaDisponible acepta el cliente de la transaccion.
  Probado contra un Postgres real: 5 reservas simultaneas del mismo slot con
  capacidad 1 dan 1 ok + 4 conflictos y una sola fila; sacando el lock, entran
  las 5.
- La deduplicacion del webhook estaba en memoria y solo servia con una
  instancia. Pasa a la base con el modelo MensajeWhatsappProcesado: el insert
  del id es atomico por el indice unico, asi que aguanta reintentos
  concurrentes de Meta (verificado). Se limpian los ids de mas de 7 dias.
- postMensaje no reintentaba: ahora reintenta 3 veces ante 429, 5xx y fallas
  de red, con backoff exponencial y jitter. Un 4xx nuestro no se reintenta.
- /api/cliente/servicios devolvia los trabajos de un jefe dado de baja; ahora
  responde 404 si el jefe no existe o no esta activo.

test:envio suma los casos de reintento (35 asserts). El Prisma en memoria del
test del bot ahora entiende $transaction y $executeRaw.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:21:35 -03:00
Beldor 44aeb58e77 feat(cliente): invertir el flujo web a jefe -> trabajo
Revisión con IA / ai-review (pull_request) Failing after 2m12s
Los trabajos ahora son por jefe mecanico, asi que el cliente elige primero
el mecanico y despues el trabajo, igual que en el bot de WhatsApp. Antes
listaba todos los servicios del taller y recien despues pedia el jefe.

- src/lib/servicios.ts: regla unica de que trabajos ofrece un jefe = los
  suyos + los globales del taller (jefeMecanicoId null, los carga el admin).
  Asi no se rompe lo que ya existe en produccion, donde todos los servicios
  son globales.
- /api/cliente/servicios ahora exige jefeMecanicoId y aplica esa regla.
- /api/cliente/slots y crearTurno rechazan un trabajo propio de OTRO jefe
  (400): antes se podia reservar con el jefe A la duracion del trabajo del B.
- whatsapp-bot: usa la misma regla, asi la web y el bot ofrecen lo mismo.
- cliente/turnos: pasos invertidos, cabeceras y back acordes, y estado vacio
  propio cuando el jefe no tiene trabajos.
- test-whatsapp-bot: soporte de OR en el Prisma en memoria, trabajo global en
  el seed y escenario D (globales visibles, trabajo ajeno rechazado). 31 asserts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:55:47 -03:00
jpbeldomenico 15d6e8a2ee feat(whatsapp): webhook + envio por Graph API (F0)
Ultima fase del bot de reservas: conecta la maquina de conversacion ya
existente (whatsapp-bot.ts) con la WhatsApp Business Cloud API.

- src/lib/whatsapp.ts: traduce RespuestaBot -> payloads de Meta (texto e
  interactive/button, con los limites de 3 botones / 20 chars / 4096 chars),
  aplana el payload del webhook y valida X-Hub-Signature-256 en tiempo
  constante.
- src/app/api/whatsapp/webhook/route.ts: GET de verificacion y POST de
  mensajes. Valida la firma sobre el cuerpo crudo, deduplica por message id
  (Meta reintenta) y siempre devuelve 200 para no entrar en loop de retries.
- scripts/test-whatsapp-envio.ts (npm run test:envio): 30 asserts sobre la
  capa Meta con fetch stubbeado, sin DB ni credenciales.
- docs/whatsapp.md con el alta en el panel de Meta y las 4 env vars.

Falta solo cargar las credenciales de Meta en el .env de produccion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:55:47 -03:00
Beldor 0046b6c3cf feat(whatsapp): máquina de conversación del bot de reservas (F2) + test
- lib/turnos.ts: crearTurno() compartido (valida servicio/jefe/slot); cliente/turnos POST lo reutiliza
- lib/telefono.ts: normalización de teléfono (E.164 sin '+') para matchear el wa_id
- lib/whatsapp-bot.ts: procesarMensaje(telefono, texto), state machine completa:
  identidad por teléfono + verificación por email registrado / alta nueva,
  elegir mecánico (botones) -> trabajo (letras) -> horario (letras, con "más") -> confirmar,
  crea turno sin auto/km; expiración 30min, "cancelar" global, manejo de inválidos y 409.
  Devuelve objetos-respuesta (texto/botones) -> independiente de Meta (lo traduce F0).
- scripts/test-whatsapp-bot.ts + npm run test:bot: corre la máquina de estados con un
  Prisma en memoria (sin DB ni Meta), ejercitando scheduling y crearTurno reales. 27/27 OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 09:55:47 -03:00