fix: guardar el telefono en el formato que usa WhatsApp #5

Merged
jpbeldomenico merged 2 commits from fix/telefono-formato-whatsapp into main 2026-08-05 15:03:23 -03:00
Owner

El problema

Un cliente registrado por la web no lo reconocia el bot de WhatsApp. El formulario aceptaba el telefono en cualquier formato (1141984844, 01157260394, 11 4198-4844) pero Meta manda el wa_id como 5491141984844, asi que la busqueda por telefonoNorm no encontraba nada, mandaba al cliente al alta, y ahi chocaba contra el email ya registrado:

🤖 No te tengo registrado. ¿Cuál es tu nombre?
...
🤖 Ese email ya está registrado con otro número. Escribí otro o comunicate con el taller.

Callejon sin salida. No lo agarraron ni los tests (que seedean telefonoNorm ya normalizado) ni la revision con IA; aparecio al correr el bot contra una copia de la base de produccion.

La solucion

El campo de telefono del registro muestra +54 9 fijo y grisado y el cliente completa solo la caracteristica (sin el 0) y el abonado (sin el 15): 10 digitos. Con eso el numero queda guardado igual al wa_id, sin heuristicas para adivinar prefijos.

  • src/lib/telefono.ts: esLocalArgentinoValido y aFormatoWhatsapp (prefijo 549).
  • src/components/ui/Input.tsx: props opcionales prefijo y ayuda. No cambia como se ve en los demas formularios.
  • src/app/cliente/registro/page.tsx: filtra a digitos mientras se escribe, tope de 10, y avisa si empieza con 0 o si faltan digitos.
  • src/app/api/cliente/registro/route.ts: valida el formato, guarda telefonoNorm ya en formato WhatsApp y rechaza un telefono repetido con 409 (antes reventaba contra el indice unico).

La herramienta con la que se encontro

scripts/chat-bot.ts permite conversar con el bot desde la consola contra cualquier base, sin Meta ni credenciales, usando la misma funcion que invoca el webhook:

DATABASE_URL=... npx tsx scripts/chat-bot.ts 5491141984844

Contra una copia del dump de produccion se verifico el ciclo completo: alta por web con el formato nuevo, el bot lo reconoce por telefono, pide el email de verificacion, rechaza el equivocado, acepta el correcto y sigue a elegir mecanico.

Pendiente aparte

Los 5 clientes que ya existen en produccion tienen telefonoNorm en null y siguen sin poder usar el bot. Son registros de prueba salvo quiza uno, y hay dos con el mismo telefono, asi que conviene decidir a mano si se limpian o se completan.

🤖 Generated with Claude Code

## El problema Un cliente registrado por la web **no lo reconocia el bot de WhatsApp**. El formulario aceptaba el telefono en cualquier formato (`1141984844`, `01157260394`, `11 4198-4844`) pero Meta manda el `wa_id` como `5491141984844`, asi que la busqueda por `telefonoNorm` no encontraba nada, mandaba al cliente al alta, y ahi chocaba contra el email ya registrado: ``` 🤖 No te tengo registrado. ¿Cuál es tu nombre? ... 🤖 Ese email ya está registrado con otro número. Escribí otro o comunicate con el taller. ``` Callejon sin salida. No lo agarraron ni los tests (que seedean `telefonoNorm` ya normalizado) ni la revision con IA; aparecio al correr el bot contra una copia de la base de produccion. ## La solucion El campo de telefono del registro muestra **`+54 9` fijo y grisado** y el cliente completa solo la caracteristica (sin el 0) y el abonado (sin el 15): 10 digitos. Con eso el numero queda guardado igual al `wa_id`, sin heuristicas para adivinar prefijos. - `src/lib/telefono.ts`: `esLocalArgentinoValido` y `aFormatoWhatsapp` (prefijo `549`). - `src/components/ui/Input.tsx`: props opcionales `prefijo` y `ayuda`. No cambia como se ve en los demas formularios. - `src/app/cliente/registro/page.tsx`: filtra a digitos mientras se escribe, tope de 10, y avisa si empieza con 0 o si faltan digitos. - `src/app/api/cliente/registro/route.ts`: valida el formato, guarda `telefonoNorm` ya en formato WhatsApp y rechaza un telefono repetido con 409 (antes reventaba contra el indice unico). ## La herramienta con la que se encontro `scripts/chat-bot.ts` permite conversar con el bot desde la consola contra cualquier base, sin Meta ni credenciales, usando la misma funcion que invoca el webhook: ``` DATABASE_URL=... npx tsx scripts/chat-bot.ts 5491141984844 ``` Contra una copia del dump de produccion se verifico el ciclo completo: alta por web con el formato nuevo, el bot lo reconoce por telefono, pide el email de verificacion, rechaza el equivocado, acepta el correcto y sigue a elegir mecanico. ## Pendiente aparte Los 5 clientes que ya existen en produccion tienen `telefonoNorm` en null y siguen sin poder usar el bot. Son registros de prueba salvo quiza uno, y hay dos con el mismo telefono, asi que conviene decidir a mano si se limpian o se completan. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpbeldomenico added 1 commit 2026-08-05 14:57:46 -03:00
fix(registro): guardar el telefono en el formato que usa WhatsApp
Revisión con IA / ai-review (pull_request) Successful in 2m12s
ffa32a113d
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>
ai-bot reviewed 2026-08-05 14:59:33 -03:00
ai-bot left a comment
Collaborator

Inline review

Inline review
@@ -0,0 +34,4 @@
const texto = linea.trim();
if (texto === "/salir") break;
if (texto === "/reset") {
await prisma.conversacionWhatsapp.deleteMany({ where: { telefono } });
Collaborator

El comando /reset usa el teléfono crudo del CLI, pero las conversaciones se guardan en formato WhatsApp (549...). Borrará 0 o la conversación equivocada.

const telefonoNorm = aFormatoWhatsapp(telefono);
      await prisma.conversacionWhatsapp.deleteMany({ where: { telefono: telefonoNorm } });

#ai-review-inline

El comando /reset usa el teléfono crudo del CLI, pero las conversaciones se guardan en formato WhatsApp (549...). Borrará 0 o la conversación equivocada. ```suggestion const telefonoNorm = aFormatoWhatsapp(telefono); await prisma.conversacionWhatsapp.deleteMany({ where: { telefono: telefonoNorm } }); ``` #ai-review-inline
@@ -21,1 +25,4 @@
{ status: 400 }
);
const existe = await prisma.cliente.findUnique({ where: { email: email.toLowerCase() } });
Collaborator

Condición de carrera: entre verificar email/teléfono y crear el cliente, otra request puede registrar los mismos datos. Usá una transacción o confía en constraints únicos de la BD.

const cliente = await prisma.$transaction(async (tx) => {
  const existeEmail = await tx.cliente.findUnique({ where: { email: email.toLowerCase() } });
  if (existeEmail) throw new Error('EMAIL_EXISTS');
  const yaUsado = await tx.cliente.findFirst({ where: { telefonoNorm } });
  if (yaUsado) throw new Error('PHONE_EXISTS');
  return tx.cliente.create({ data: { email: email.toLowerCase().trim(), nombre, apellido, telefono: normalizarTelefono(telefono), telefonoNorm } });
});

#ai-review-inline

Condición de carrera: entre verificar email/teléfono y crear el cliente, otra request puede registrar los mismos datos. Usá una transacción o confía en constraints únicos de la BD. ```suggestion const cliente = await prisma.$transaction(async (tx) => { const existeEmail = await tx.cliente.findUnique({ where: { email: email.toLowerCase() } }); if (existeEmail) throw new Error('EMAIL_EXISTS'); const yaUsado = await tx.cliente.findFirst({ where: { telefonoNorm } }); if (yaUsado) throw new Error('PHONE_EXISTS'); return tx.cliente.create({ data: { email: email.toLowerCase().trim(), nombre, apellido, telefono: normalizarTelefono(telefono), telefonoNorm } }); }); ``` #ai-review-inline
@@ -9,0 +18,4 @@
* Parte local del número: característica sin el 0 y abonado sin el 15.
* En Argentina son siempre 10 dígitos (ej: 11 4198-4844, 351 555-4444).
*/
export function esLocalArgentinoValido(local: string): boolean {
Collaborator

La validación no rechaza números que empiezan con '15' (prefijo móvil antiguo). El comentario dice 'sin el 15' pero el código solo chequea '0'.

export function esLocalArgentinoValido(local: string): boolean {
  const d = normalizarTelefono(local);
  return d.length === 10 && !d.startsWith('0') && !d.startsWith('15');
}

#ai-review-inline

La validación no rechaza números que empiezan con '15' (prefijo móvil antiguo). El comentario dice 'sin el 15' pero el código solo chequea '0'. ```suggestion export function esLocalArgentinoValido(local: string): boolean { const d = normalizarTelefono(local); return d.length === 10 && !d.startsWith('0') && !d.startsWith('15'); } ``` #ai-review-inline
ai-bot reviewed 2026-08-05 14:59:33 -03:00
ai-bot left a comment
Collaborator

Inline review

Inline review
ai-bot reviewed 2026-08-05 14:59:34 -03:00
ai-bot left a comment
Collaborator

Inline review

Inline review
ai-bot reviewed 2026-08-05 14:59:34 -03:00
ai-bot left a comment
Collaborator

Inline review

Inline review
@@ -22,2 +29,4 @@
if (existe) return NextResponse.json({ error: "El email ya está registrado" }, { status: 409 });
// `telefonoNorm` queda igual al `wa_id` que manda Meta, que es como lo busca el bot.
const telefonoNorm = aFormatoWhatsapp(telefono);
Collaborator

Si llaman al API directo con un número que ya trae '549', normalizarTelefono lo deja y aFormatoWhatsapp le agrega otro '549'. El frontend lo evita (maxLength=10), pero el backend no se protege.

const telefonoNorm = aFormatoWhatsapp(telefono);
  // Validación extra: que el input sea solo la parte local (10 dígitos)
  if (normalizarTelefono(telefono).length !== 10) {
    return NextResponse.json({ error: 'Formato de teléfono inválido' }, { status: 400 });
  }

#ai-review-inline

Si llaman al API directo con un número que ya trae '549', normalizarTelefono lo deja y aFormatoWhatsapp le agrega otro '549'. El frontend lo evita (maxLength=10), pero el backend no se protege. ```suggestion const telefonoNorm = aFormatoWhatsapp(telefono); // Validación extra: que el input sea solo la parte local (10 dígitos) if (normalizarTelefono(telefono).length !== 10) { return NextResponse.json({ error: 'Formato de teléfono inválido' }, { status: 400 }); } ``` #ai-review-inline
@@ -19,0 +25,4 @@
setForm((f) => ({ ...f, telefono: soloDigitos }));
if (soloDigitos === "") setTelefonoError("");
else if (soloDigitos.startsWith("0")) setTelefonoError("Sin el 0 de la característica");
else if (soloDigitos.length < 10) setTelefonoError("Faltan dígitos (son 10 en total)");
Collaborator

Muestra error 'Faltan dígitos' mientras el usuario está tipeando (antes de llegar a 10). Molesto. Mejor validar solo al blur o al submit.

else if (soloDigitos.length < 10 && e.type === 'blur') setTelefonoError('Faltan dígitos (son 10 en total)');

#ai-review-inline

Muestra error 'Faltan dígitos' mientras el usuario está tipeando (antes de llegar a 10). Molesto. Mejor validar solo al blur o al submit. ```suggestion else if (soloDigitos.length < 10 && e.type === 'blur') setTelefonoError('Faltan dígitos (son 10 en total)'); ``` #ai-review-inline
ai-bot reviewed 2026-08-05 14:59:34 -03:00
ai-bot left a comment
Collaborator

Inline review

Inline review
Collaborator

Qué cambia
Se agrega validación y normalización de teléfonos argentinos en el registro de clientes: el frontend muestra prefijo fijo "+54 9" y acepta solo 10 dígitos (característica sin 0, abonado sin 15); el backend valida, normaliza y guarda tanto telefono (solo dígitos) como telefonoNorm en formato WhatsApp (549 + 10 dígitos) para que coincida con el wa_id de Meta. También se crea scripts/chat-bot.ts para probar el bot por consola contra la BD real.

Riesgos

  • Cambio de contrato de datos: nuevo campo telefonoNorm en Cliente y cambio de formato en telefono. Requiere migración de Prisma y backfill de clientes existentes; sin eso, registros previos quedarán inconsistentes y el bot no los reconocerá.
  • Validación hardcodeada para Argentina: esLocalArgentinoValido rechaza cualquier número que no sea 10 dígitos sin 0 inicial; si el producto se usa en otros países, rompe el registro.
  • Script de prueba escribe en BD real: chat-bot.ts usa DATABASE_URL y procesarMensaje directo; si se ejecuta en prod o contra BD compartida, crea turnos y conversaciones reales.
  • Duplicidad de teléfono: el chequeo findFirst({ where: { telefonoNorm } }) depende de índice único; confirmar que exista en migración.

Antes de aprobar

  1. Revisar y aplicar migración Prisma que agregue telefonoNorm con índice único y haga backfill de telefonoNorm = '549' + normalizarTelefono(telefono) en clientes actuales.
  2. Probar registro con números válidos (1141984844, 3515554444) e inválidos (con 0, con 15, <10 dígitos, >10 dígitos).
  3. Verificar que el bot resuelve wa_id → telefonoNorm y encuentra al cliente.
  4. Confirmar que chat-bot.ts solo está en scripts/ y no se incluye en build/deploy; agregar guardia if (process.env.NODE_ENV === 'production') process.exit(1) por seguridad.
  5. Evaluar si la validación argentina debe ser configurable o si el alcance del producto es solo AR.

#ai-review-summary

**Qué cambia** Se agrega validación y normalización de teléfonos argentinos en el registro de clientes: el frontend muestra prefijo fijo "+54 9" y acepta solo 10 dígitos (característica sin 0, abonado sin 15); el backend valida, normaliza y guarda tanto `telefono` (solo dígitos) como `telefonoNorm` en formato WhatsApp (549 + 10 dígitos) para que coincida con el `wa_id` de Meta. También se crea `scripts/chat-bot.ts` para probar el bot por consola contra la BD real. **Riesgos** - **Cambio de contrato de datos**: nuevo campo `telefonoNorm` en `Cliente` y cambio de formato en `telefono`. Requiere migración de Prisma y backfill de clientes existentes; sin eso, registros previos quedarán inconsistentes y el bot no los reconocerá. - **Validación hardcodeada para Argentina**: `esLocalArgentinoValido` rechaza cualquier número que no sea 10 dígitos sin 0 inicial; si el producto se usa en otros países, rompe el registro. - **Script de prueba escribe en BD real**: `chat-bot.ts` usa `DATABASE_URL` y `procesarMensaje` directo; si se ejecuta en prod o contra BD compartida, crea turnos y conversaciones reales. - **Duplicidad de teléfono**: el chequeo `findFirst({ where: { telefonoNorm } })` depende de índice único; confirmar que exista en migración. **Antes de aprobar** 1. Revisar y aplicar migración Prisma que agregue `telefonoNorm` con índice único y haga backfill de `telefonoNorm = '549' + normalizarTelefono(telefono)` en clientes actuales. 2. Probar registro con números válidos (1141984844, 3515554444) e inválidos (con 0, con 15, <10 dígitos, >10 dígitos). 3. Verificar que el bot resuelve `wa_id` → `telefonoNorm` y encuentra al cliente. 4. Confirmar que `chat-bot.ts` solo está en `scripts/` y no se incluye en build/deploy; agregar guardia `if (process.env.NODE_ENV === 'production') process.exit(1)` por seguridad. 5. Evaluar si la validación argentina debe ser configurable o si el alcance del producto es solo AR. #ai-review-summary
jpbeldomenico added 1 commit 2026-08-05 15:03:04 -03:00
fix: detalles del formato de telefono senalados en la revision
Revisión con IA / ai-review (pull_request) Successful in 25s
9e93808dc1
- esLocalArgentinoValido tambien rechaza los que arrancan con 15: ninguna
  caracteristica argentina empieza asi, es el viejo prefijo de celular.
- El formulario ya no reta "faltan digitos" mientras se esta tipeando; ese
  aviso espera a que el campo pierda el foco. Los errores seguros (0 o 15
  adelante) se siguen mostrando al instante.
- chat-bot.ts normalizaba el telefono para conversar pero no para /reset, asi
  que el reset no encontraba la conversacion y no borraba nada.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jpbeldomenico merged commit 2baa7952fa into main 2026-08-05 15:03:23 -03:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jpbeldomenico/taller_automotriz#5