builderbot icon indicating copy to clipboard operation
builderbot copied to clipboard

[🐛] error con encadenamiento de addAnswer, fallback con addAction (1.2.3, 1.2.4, 1.2.5, 1.2.7-alpha.5) y conexión con meta (1.2.6)

Open camilocalderont opened this issue 8 months ago • 4 comments

¿Que versión estas usando?

v2

¿Sobre que afecta?

Flujo de palabras (Flow)

Describe tu problema

Error con el addAnswer

Me pasa algo similar pero sin nisiquiera pasar por un goToFlow, en este caso tome el ejemplo de la documentación oficial: https://builderbot.vercel.app/en/add-functions#message-with-callback y solo le agregué un if para saber si el fallBack funcionaba.

En efecto funciona, pero luego del mensaje del flowDynamic no salta al siguiente addAnswer.

const mainFlow2 = addKeyword('pepe')
    .addAnswer('Hi!, Do you know 4+4?', {capture:true}, async (_, {flowDynamic, fallBack}) => {
        const sum = 4 + 4
        const value = _.body;
        if (Number(value) !== sum) {
            return fallBack(`No, ${value} no es correcto, intenta de nuevo`);
        }
        await flowDynamic(`Total: ${sum} pero tu dijiste ${value}`)
    })
    .addAnswer('¿Quieres saber algo más?', {capture:true}, async (_, {flowDynamic, fallBack}) => {
        const value = _.body;
        if (value !== 'si' && value !== 'no') {
            return fallBack(`No entiendo, por favor responde con "si" o "no"`);
        }
        await flowDynamic(`Tu respuesta fue ${value}`)
    })
    .addAction(async (_, {flowDynamic}) => {
        await flowDynamic(`Other message`)
    })

Este es el resultado, en lugar de saltar a la pregunta '¿Quieres saber algo más?', se finaliza el flujo. Image

En un ejemplo más complejo se puede ver el mismo comportamiento:

import { addKeyword, EVENTS } from '@builderbot/bot';
import { ApiResponse } from '../services/api/interfaces/api-response.interface';
import { ClientRequestDto, ClientResponseDto } from '../services/api/dto/client.dto';
import { ApiService } from '../services/api/api.service';

const apiService = new ApiService();



const registerFlow = addKeyword(EVENTS.ACTION)
    .addAnswer('👋 Hola, parece que eres nuevo por aquí.\n\n¿Quieres registrarte?',
        { capture: true,
            buttons: [
                { body: 'Si, quiero'},
                { body: 'No, gracias'}
            ]
        },
        async (ctx, ctxFn) => {
            if(ctx.body === 'Si, quiero'){
                await ctxFn.flowDynamic('✅ ¡Perfecto! Vamos a comenzar con tu registro 📝');
            }else if(ctx.body === 'No, gracias'){
                return ctxFn.endFlow('❌ El registro fue cancelado, puedes volver a escribirle al bot para registrarte cuando lo desees');
            }else{
                return ctxFn.fallBack('⚠️ Por favor, escoge una de las opciones');
            }
        }
    )
    .addAnswer('📝 ¿Cuál es tu nombre completo?',
        { capture: true },
        async (ctx, ctxFn) => {
            await ctxFn.flowDynamic(`👤 Gracias ${ctx.body}!`);
            await ctxFn.state.update({ firstName: ctx.body });
        }
    )
    .addAnswer('📧 ¿Cuál es tu correo electrónico?',
        { capture: true },
        async (ctx, ctxFn) => {
            // Validar formato de correo electrónico
            const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

            if (!emailRegex.test(ctx.body)) {
                return ctxFn.fallBack('⚠️ Por favor, ingresa un correo electrónico válido');
            }

            await ctxFn.state.update({ email: ctx.body });
        }
    )
    .addAnswer('🔢 ¿Cuál es tu número de identificación? solo escribe números',
        { capture: true },
        async (ctx, ctxFn) => {
            // Validar que sea un número de teléfono (simplificado)
            const identificacionRegex = /^\d{7,15}$/;
            const identificationNumber = ctx.body.replace(/\D/g, '');
            if (!identificacionRegex.test(identificationNumber)) {
                return ctxFn.fallBack('⚠️ Por favor, ingresa un número de teléfono válido (solo números)');
            }

            const state = ctxFn.state.getMyState();

            // Preparar objeto para enviar a la API
            const clientData: ClientRequestDto = {
                VcIdentificationNumber: identificationNumber,
                VcFirstName: state.firstName,
                VcEmail: state.email,
                VcPhone: ctx.body,
                VcFirstLastName: ' ',
            };

            try {
                // Llamar a la API para registrar el cliente
                const response = await apiService.request<ApiResponse<ClientResponseDto>>(
                    '/clients',
                    'POST',
                    clientData
                );

                if(response.status === 'success'){
                    await ctxFn.flowDynamic('🎉 ¡Excelente! Tus datos ya fueron registrados, ya puedes comenzar a utilizar nuestros servicios');
                }else{
                    await ctxFn.flowDynamic('❌ Lo siento, hubo un error al registrar tus datos. Por favor, intenta nuevamente más tarde.');
                }
            } catch (error) {
                console.error('Error al registrar cliente:', error);
                await ctxFn.flowDynamic('❌ Lo siento, hubo un error al registrar tus datos. Por favor, intenta nuevamente más tarde.');
            }
        }
    );

export { registerFlow };

Resultado con versiones: 1.2.3, 1.2.4 , 1.2.5 y 1.2.7-alpha.5 En este caso no pasa al siguiente bloque:

Image

Resultado con versiones: 1.2.6 En este caso, aunque se logra conectar al provider de meta, no envia mensajes, no muestra nada en consola por lo que es imposible hacer un debug.

Error con el fallBack con addAction

Buscando alternativas, se intentó usar addAction en lugar de addAnswer pero el comportamiento es igual errático En este caso luego de un goToFlow, el encadenamiento de los mensajes funciona, pero el fallback no realiza las validaciones correctas:


import { addKeyword, EVENTS } from '@builderbot/bot';
import { ApiResponse } from '../services/api/interfaces/api-response.interface';
import { ClientRequestDto, ClientResponseDto } from '../services/api/dto/client.dto';
import { ApiService } from '../services/api/api.service';

const apiService = new ApiService();



const registerFlow = addKeyword(EVENTS.ACTION)
.addAnswer('👋 Hola, parece que eres nuevo por aquí.\n\n¿Quieres registrarte?\n\nResponde *SI* o *NO* (también puedes usar 1 o 0)',
    { capture: true }
)
.addAction(async (ctx, ctxFn) => {
    // Convertir respuesta a minúsculas para facilitar validación
    const response = ctx.body.toLowerCase();

    // Validar que la respuesta sea una de las opciones permitidas
    if (response === 'si' || response === '1') {
        await ctxFn.flowDynamic('✅ ¡Perfecto! Vamos a comenzar con tu registro 📝');
    } else if (response === 'no' || response === '0') {
        return ctxFn.endFlow('❌ El registro fue cancelado, puedes volver a escribirle al bot para registrarte cuando lo desees');
    } else {
        return ctxFn.fallBack('⚠️ Por favor, responde solo con *SI* o *NO* (o 1 o 0)');
    }
})
.addAnswer('📝 ¿Cuál es tu nombre completo?',
    { capture: true }
)
.addAction(async (ctx, ctxFn) => {
    await ctxFn.flowDynamic(`👤 Gracias ${ctx.body}!`);
    await ctxFn.state.update({ firstName: ctx.body });
})
.addAnswer('📧 ¿Cuál es tu correo electrónico?',
    { capture: true }
)
.addAction(async (ctx, ctxFn) => {
    // Validar formato de correo electrónico
    const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

    if (!emailRegex.test(ctx.body)) {
        return ctxFn.fallBack('⚠️ Por favor, ingresa un correo electrónico válido');
    }

    await ctxFn.state.update({ email: ctx.body });
})
.addAnswer('🔢 ¿Cuál es tu número de identificación? solo escribe números',
    { capture: true }
)
.addAction(async (ctx, ctxFn) => {
    // Validar que sea un número de teléfono (simplificado)
    const identificacionRegex = /^\d{7,15}$/;
    const identificationNumber = ctx.body.replace(/\D/g, '');
    if (!identificacionRegex.test(identificationNumber)) {
        return ctxFn.fallBack('⚠️ Por favor, ingresa un número de teléfono válido (solo números)');
    }

    const state = ctxFn.state.getMyState();

    // Preparar objeto para enviar a la API
    const clientData: ClientRequestDto = {
        VcIdentificationNumber: identificationNumber,
        VcFirstName: state.firstName,
        VcEmail: state.email,
        VcPhone: ctx.body,
        VcFirstLastName: ' ',
    };

    try {
        // Llamar a la API para registrar el cliente
        const response = await apiService.request<ApiResponse<ClientResponseDto>>(
            '/clients',
            'POST',
            clientData
        );

        if(response.status === 'success'){
            await ctxFn.flowDynamic('🎉 ¡Excelente! Tus datos ya fueron registrados, ya puedes comenzar a utilizar nuestros servicios');
        }else{
            await ctxFn.flowDynamic('❌ Lo siento, hubo un error al registrar tus datos. Por favor, intenta nuevamente más tarde.');
        }
    } catch (error) {
        console.error('Error al registrar cliente:', error);
        await ctxFn.flowDynamic('❌ Lo siento, hubo un error al registrar tus datos. Por favor, intenta nuevamente más tarde.');
    }
});


export { registerFlow };

Este es el resultado:

Image

Reproducir error

No response

Información Adicional

No response

camilocalderont avatar May 11 '25 02:05 camilocalderont

Hola, tambien estoy revisando, usando la version v22.0 de meta, no me responde el bot, tal vez sea por la version o alguna actualización. Solo cree el proyecto usando builderbot por defecto, asigne las credenciales de meta y configure el webhook, todo bien (en consola me sale http server on, y que si esta conectado provider) pero no me responde el bot solo estoy usando el "welcomeFlow" que viene por defecto.

JavierRomualdo avatar May 11 '25 20:05 JavierRomualdo

Solo cree el proyecto usando builderbot por defecto, asigne las credenciales de meta y configure el webhook, todo bien (en consola me sale http serv

Me sucedía exactamente lo mismo con la versión 1.2.6. Cuando ví que desde Postman me funcionaba el hello_word, supe que el problema era por una actualización y que la solución era borrar la carpeta node_modules, bajar las versiones a 1.2.2 o 1.2.3 o 1.2.4 ya funciona el provider de Meta de forma correcta; pero igual las interacciones de flujos anidados no me funcionó para ninguna de las versiones que probé y eso que lo hice con ejemplos de la documentación inicial.

Te comparto la conversación de discord: https://discord.com/channels/915193197645402142/1360308708655235273/1370823579327008849

camilocalderont avatar May 11 '25 21:05 camilocalderont

Buenas puedes probar la versión 1.2.7 latest ya corregimos un bug que se tenia con meta que no respondía, ya vuelve a estar funcional ;)

leifermendez avatar May 12 '25 08:05 leifermendez

Buenas puedes probar la versión 1.2.7 latest ya corregimos un bug que se tenia con meta que no respondía, ya vuelve a estar funcional ;)

Hola, te puedo confirmar que en la versión 1.2.7 ya no se presenta el error de conexión con el proveedor de Meta. Pero, me falló el goToFlow, mira el ejemplo:


import { addKeyword, EVENTS } from "@builderbot/bot";
import { ApiService } from '../services/api/api.service';
import { ClientResponseDto } from '../services/api/dto/client.dto';
import { ApiResponse } from '../services/api/interfaces/api-response.interface';
import { registerFlow } from "./register.flow";

const apiService = new ApiService();


const mainFlow = addKeyword(EVENTS.WELCOME)
    .addAction(
        async (ctx, { flowDynamic, gotoFlow }) => {
            console.log(JSON.stringify(ctx));
            const numero = ctx.from;

            try {
                const response = await apiService.request<ApiResponse<ClientResponseDto>>(
                    '/clients/cellphone/:numero',
                    'GET',
                    null,
                    { numero }
                );

                if(response.status === 'success'){
                    const clientData = response?.data;
                        console.log('Cliente encontrado:', JSON.stringify(clientData));
                        await flowDynamic(`Hola ${clientData.VcFirstName}, bienvenido de nuevo!`);
                }else{
                    return gotoFlow(registerFlow);
                }
            } catch (error: any) {
                console.error('Cliente no encontrado: ', error.message);
                return gotoFlow(registerFlow);  <-- AQUI DEBERÍA IRSE AL registerFlow
            }
        }
    );


const registerFlow = addKeyword(EVENTS.ACTION)
  .addAnswer('👋 Hola, parece que eres nuevo por aquí.\n\n¿Quieres registrarte?\n\nResponde *SI* o *NO* (también puedes usar 1 o 0)',
      { capture: true }
  )
.addAction(async (ctx, ctxFn) => {
    // Convertir respuesta a minúsculas para facilitar validación
    const response = ctx.body.toLowerCase();

    // Validar que la respuesta sea una de las opciones permitidas
    if (response === 'si' || response === '1') {
        await ctxFn.flowDynamic('✅ ¡Perfecto! Vamos a comenzar con tu registro 📝');
    } else if (response === 'no' || response === '0') {
        return ctxFn.endFlow('❌ El registro fue cancelado, puedes volver a escribirle al bot para registrarte cuando lo desees');
    } else {
        return ctxFn.fallBack('⚠️ Por favor, responde solo con *SI* o *NO* (o 1 o 0)');
    }
})

En este caso si el API responde que el no está el número creado en la tabla de clientes se va por el catch y lo envía al registerFlow. Pero no dispara el flujo, en el WhatsApp se vé así:

Image

Y en la consula pasa por el console.error, pero no hace nada más:

Message Payload: { body: 'hola', from: '573333333333' }
{"type":"text","from":"573057467347","to":"15551468852","body":"hola","name":"Camilo","pushName":"Camilo","message_id":"wamid.HBgMNTczMDU3NDY3MzQ3FQIAEhgWM0VCMDM5QTJCRTZCMkM2QUEzMTBEMwA=","timestamp":"1747281899","host":"undefined"}
query: SELECT "ClientEntity"."Id" AS "ClientEntity_Id", "ClientEntity"."vc_identification_number" AS "ClientEntity_vc_identification_number", "ClientEntity"."vc_phone" AS "ClientEntity_vc_phone", "ClientEntity"."vc_nick_name" AS "ClientEntity_vc_nick_name", "ClientEntity"."vc_first_name" AS "ClientEntity_vc_first_name", "ClientEntity"."vc_second_name" AS "ClientEntity_vc_second_name", "ClientEntity"."vc_first_last_name" AS "ClientEntity_vc_first_last_name", "ClientEntity"."vc_second_last_name" AS "ClientEntity_vc_second_last_name", "ClientEntity"."vc_email" AS "ClientEntity_vc_email", "ClientEntity"."created_at" AS "ClientEntity_created_at", "ClientEntity"."updated_at" AS "ClientEntity_updated_at" FROM "Client" "ClientEntity" WHERE (("ClientEntity"."vc_phone" = $1)) LIMIT 1 -- PARAMETERS: ["573333333333"]
API Request Error: {
  "status": "error",
  "message": "No customer found with the phone number",
  "errors": [
    {
      "code": "NUMBER_DOES_NOT_EXIST",
      "message": "No customer found with the phone number 573333333333",
      "field": "cellphone"
    }
  ]
}
Error during API request to http://localhost:3000/api/v1/clients/cellphone/573333333333: {
  status: 'error',
  message: 'No customer found with the phone number',
  errors: [
    {
      code: 'NUMBER_DOES_NOT_EXIST',
      message: 'No customer found with the phone number 573333333333',
      field: 'cellphone'
    }
  ]
}
Cliente no encontrado:  No customer found with the phone number
Request failed with status code 400

Lo que me causa curiosidad es que yo en el servicio estoy devolviendo un 409, y en ningún lado devolvemos el mensaje "Request failed with status code 400" Es como si fuera un error interno de la librería que lo propagara y lo hiciera detener, pero lo curioso es que en las versiones anteriores, aunque imprimía el mensaje del servicio y el console.error, se dirigía al registerFlow.

Me podrías confirmar si estoy haciendo algo mal, o es un bug de la versión 1.2.7, mil gracias por la rapidez de la primera respuesta.

camilocalderont avatar May 15 '25 04:05 camilocalderont

¿Alguna novedad sobre esta ISSUE?

github-actions[bot] avatar Jul 14 '25 22:07 github-actions[bot]