Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot]
e1d5cc14a1 docs(contributor): contrib-readme-action has updated readme 2023-02-06 09:16:22 +00:00
148 changed files with 3424 additions and 2956 deletions

View File

@@ -0,0 +1,52 @@
name: Rev Major Providers
on:
schedule:
- cron: '0 9 * * *'
jobs:
check-npm:
name: Install Dependencies
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'yarn'
registry-url: https://registry.npmjs.org/
- run: corepack enable
- name: Install NPM Dependencies
run: yarn install --immutable --network-timeout 300000
- name: Check Baileys
run: yarn node ./scripts/checker.js --name=baileys --stable=true
- name: Check Venom
run: yarn node ./scripts/checker.js --name=venom --stable=true
- name: Check web-whatsapp
run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=true
- name: Check Meta
run: yarn node ./scripts/checker.js --name=meta --stable=true
- name: Check Twilio
run: yarn node ./scripts/checker.js --name=twilio --stable=true
- name: Add and commit changes to gh-pages branch
run: |
git config --local user.email 'action@github.com'
git config --local user.name 'GitHub Action'
git add .
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: 'ci(providers): check provider versions'
create_branch: true
branch: feature/providers-major

View File

@@ -12,7 +12,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- test-unit - test-unit
- test-e2e
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -57,15 +56,20 @@ jobs:
- name: Unit Tests - name: Unit Tests
run: yarn test run: yarn test
############ UNIT TEST ############ ############ UNIT TEST ############
test-e2e: check-providers:
name: e2e Tests name: Check Providers Versions
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: outputs:
- test-unit commit: ${{ steps.vars.outputs.commit }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{github.event.after}}
persist-credentials: false
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@@ -79,5 +83,28 @@ jobs:
- name: Install NPM Dependencies - name: Install NPM Dependencies
run: yarn install --immutable --network-timeout 300000 run: yarn install --immutable --network-timeout 300000
- name: e2e Tests - name: Check Baileys
run: yarn test.e2e run: yarn node ./scripts/checker.js --name=baileys --stable=true
- name: Check Venom
run: yarn node ./scripts/checker.js --name=venom --stable=true
- name: Check web-whatsapp
run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=true
- name: Check Meta
run: yarn node ./scripts/checker.js --name=meta --stable=true
- name: Check Twilio
run: yarn node ./scripts/checker.js --name=twilio --stable=true
- name: Set output
id: vars
run: echo "commit=$(git log --format=%B -n 1 ${{github.event.after}})" >> $GITHUB_OUTPUT
- name: Commit & Push changes
uses: actions-js/push@master
with:
branch: feature/providers-major
github_token: ${{ secrets.GITHUB_TOKEN }}
force: true

17
.github/workflows/contributors.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Revisando Colaboradores
on:
schedule:
- cron: '0 9 * * *'
jobs:
contrib-readme-job:
runs-on: ubuntu-latest
name: A job to automate contrib in readme
steps:
- name: Contribute List
uses: akhilmhdh/contributors-readme-action@v2.3.6
with:
image_size: 50
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -36,7 +36,6 @@ tmp/
.fleet/ .fleet/
example-app*/ example-app*/
base-*/ base-*/
test-*.json
!starters/apps/base-*/ !starters/apps/base-*/
qr.svg qr.svg
package-lock.json package-lock.json

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh" . "$(dirname -- "$0")/_/husky.sh"
npm run lint:fix && npx --no -- commitlint --edit npx --no -- commitlint --edit

View File

@@ -1,5 +1,5 @@
packages/**/lib packages/**/lib
packages/docs/ packages/docs/*.json
**/.git **/.git
**/.svn **/.svn
**/.hg **/.hg

View File

@@ -1,3 +1,3 @@
{ {
"recommendations": ["xyc.vscode-mdx-preview", "vivaxy.vscode-conventional-commits", "mhutchie.git-graph"] "recommendations": ["xyc.vscode-mdx-preview"]
} }

View File

@@ -2,44 +2,6 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.1.21](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.20...v0.1.21) (2023-02-12)
### Features
* **provider:** :bug: add_location ([a46a9ef](https://github.com/leifermendez/bot-whatsapp/commit/a46a9efd8dbd921c773395d331154dc9a8aae783))
* **provider:** :rocket: feat Instance provider ([2278149](https://github.com/leifermendez/bot-whatsapp/commit/227814929561cedc11a1f69c8029515a7f47c9ff))
* **provider:** :rocket: provider raw ([2d44a00](https://github.com/leifermendez/bot-whatsapp/commit/2d44a002ff226fb0eb7362ad49936f1e00b84242))
* **provider:** :zap: add location provider ([c7de860](https://github.com/leifermendez/bot-whatsapp/commit/c7de860803fb362f5afe06cc38ad71b2c316d524))
* **provider:** :zap: add location provider ([c0ece6f](https://github.com/leifermendez/bot-whatsapp/commit/c0ece6feb2b0325476880a604c32de341eb60544))
* **provider:** :zap: support location <20> ([a147677](https://github.com/leifermendez/bot-whatsapp/commit/a147677a26b36bba429c3dd3c2c81a44a7a4d2b6))
### Bug Fixes
* arreglando dir y varios mensajes en dialog flow essential ([01c7db8](https://github.com/leifermendez/bot-whatsapp/commit/01c7db8fe7dda2482eb0aa1fd7b3469b6ae8eae1))
### [0.1.20](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.19...v0.1.20) (2023-02-05)
### Features
* **cli:** :fire: add regex expression in addKeyworkd ([e34560c](https://github.com/leifermendez/bot-whatsapp/commit/e34560c77d4852d2e90930f0858e51aa67d4eeab))
* **provider:** :zap: possible get class provider ([76ba717](https://github.com/leifermendez/bot-whatsapp/commit/76ba717927a75b3d6299206aa0b8aee2bc25b726))
### Bug Fixes
* **cli:** :zap: working flowDynamic test ([c0113ca](https://github.com/leifermendez/bot-whatsapp/commit/c0113ca49295aff220d8defcb53f2ba7f2872d75))
* **cli:** :zap: working flowDynamic test ([aef52d2](https://github.com/leifermendez/bot-whatsapp/commit/aef52d2694fa6616d614338643db198b4f7f1fe8))
* **cli:** :zap: working flowDynamic test ([f769320](https://github.com/leifermendez/bot-whatsapp/commit/f76932021ce968d93241b55cfcdb8ae0e0e6c934))
* **cli:** :zap: working flowDynamic test ([23e09ef](https://github.com/leifermendez/bot-whatsapp/commit/23e09efaeccaf51018c55da492edff45b625f0a9))
* **database:** add support emoji in mysql ([9311aa0](https://github.com/leifermendez/bot-whatsapp/commit/9311aa0a65623a1bf40e96207a281625154dae90))
* **database:** fix naming ([cd082f2](https://github.com/leifermendez/bot-whatsapp/commit/cd082f235012cd5f5844c6437f51711beee0c865))
* **database:** fix naming ([1afc3ba](https://github.com/leifermendez/bot-whatsapp/commit/1afc3ba182070713b5bec40eaab0fa1f680830cd))
* **database:** fix naming ([c9831d2](https://github.com/leifermendez/bot-whatsapp/commit/c9831d202ab2c85f15a0247cd2a2426bc435270c))
* **provider:** :zap: baily wa.link ([96c2bff](https://github.com/leifermendez/bot-whatsapp/commit/96c2bffd093269be8e39474a84c156938504a6cb))
### [0.1.19](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.18...v0.1.19) (2023-01-29) ### [0.1.19](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.18...v0.1.19) (2023-01-29)

View File

@@ -48,10 +48,10 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
</a> </a>
</td> </td>
<td align="center"> <td align="center">
<a href="https://github.com/aurik3"> <a href="https://github.com/leifermendezfroged">
<img src="https://avatars.githubusercontent.com/u/37228512?v=4" width="50;" alt="aurik3"/> <img src="https://avatars.githubusercontent.com/u/97020486?v=4" width="50;" alt="leifermendezfroged"/>
<br /> <br />
<sub><b>Null</b></sub> <sub><b>Leifer Mendez</b></sub>
</a> </a>
</td> </td>
<td align="center"> <td align="center">
@@ -61,19 +61,19 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Manuel Vicente Ortiz</b></sub> <sub><b>Manuel Vicente Ortiz</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/leifermendezfroged">
<img src="https://avatars.githubusercontent.com/u/97020486?v=4" width="50;" alt="leifermendezfroged"/>
<br />
<sub><b>Leifer Mendez</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/danielcasta0398"> <a href="https://github.com/danielcasta0398">
<img src="https://avatars.githubusercontent.com/u/98791147?v=4" width="50;" alt="danielcasta0398"/> <img src="https://avatars.githubusercontent.com/u/98791147?v=4" width="50;" alt="danielcasta0398"/>
<br /> <br />
<sub><b>Juan Daniel Castaño</b></sub> <sub><b>Juan Daniel Castaño</b></sub>
</a> </a>
</td>
<td align="center">
<a href="https://github.com/aurik3">
<img src="https://avatars.githubusercontent.com/u/37228512?v=4" width="50;" alt="aurik3"/>
<br />
<sub><b>Null</b></sub>
</a>
</td></tr> </td></tr>
<tr> <tr>
<td align="center"> <td align="center">
@@ -147,21 +147,28 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Null</b></sub> <sub><b>Null</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/Jhonarias13">
<img src="https://avatars.githubusercontent.com/u/19483021?v=4" width="50;" alt="Jhonarias13"/>
<br />
<sub><b>Jhon Freiman Arias</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/tonyvazgar"> <a href="https://github.com/tonyvazgar">
<img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/> <img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/>
<br /> <br />
<sub><b>Luis Antonio Vázquez García</b></sub> <sub><b>Luis Antonio Vázquez García</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/rrruuuyyy"> <a href="https://github.com/rrruuuyyy">
<img src="https://avatars.githubusercontent.com/u/33061671?v=4" width="50;" alt="rrruuuyyy"/> <img src="https://avatars.githubusercontent.com/u/33061671?v=4" width="50;" alt="rrruuuyyy"/>
<br /> <br />
<sub><b>Rodrigo Mendoza Cabrera</b></sub> <sub><b>Rodrigo Mendoza Cabrera</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/yond1994"> <a href="https://github.com/yond1994">
<img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/> <img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/>

View File

@@ -1,50 +0,0 @@
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
class MOCK_FLOW {
allCallbacks = { ref: () => 1 }
flowSerialize = []
flowRaw = []
find = (arg) => {
if (arg) {
return [{ answer: 'answer', ref: 'ref' }]
} else {
return null
}
}
findBySerialize = () => ({})
findIndexByRef = () => 0
}
const cleaName = (name) => {
name = name.toLowerCase()
name = name.replaceAll(' ', '-')
name = name.replaceAll(':', '-')
name = name.replaceAll('"', '-')
return name
}
/**
* Preparar env para el test
* @param {*} context
*/
const setup = async (context) => {
const name = cleaName(`${context.__suite__}-${context.__test__}`)
const filename = `test-${name}.json`
context.provider = new PROVIDER_DB()
context.database = new MOCK_DB({ filename })
context.flow = new MOCK_FLOW()
await delay(10)
}
const clear = async (context) => {
context.provider = null
context.database = null
context.flow = null
}
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}
module.exports = { setup, clear, delay }

6
__mocks__/mobile.mock.js Normal file
View File

@@ -0,0 +1,6 @@
const MOCK_MOBILE_WS = {
from: 'XXXXXX',
hasMedia: false,
}
module.exports = { MOCK_MOBILE_WS }

View File

@@ -1,51 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const suiteCase = suite('Flujo: Simple')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Responder a "hola"`, async ({ database, provider }) => {
const flow = addKeyword('hola').addAnswer('Buenas!').addAnswer('Como vamos!')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(10)
assert.is('Buenas!', database.listHistory[0].answer)
assert.is('Como vamos!', database.listHistory[1].answer)
assert.is(undefined, database.listHistory[2])
})
suiteCase(`NO reponder a "pepe"`, async ({ database, provider }) => {
const flow = addKeyword('hola').addAnswer('Buenas!').addAnswer('Como vamos!')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'pepe',
})
await delay(100)
assert.is(undefined, database.listHistory[0])
assert.is(undefined, database.listHistory[1])
})
suiteCase.run()

View File

@@ -1,31 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const suiteCase = suite('Flujo: Provider envia un location')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Responder a "CURRENT_LOCATION"`, async ({ database, provider }) => {
const flow = addKeyword('#CURRENT_LOCATION#').addAnswer('Gracias por tu location')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: '#CURRENT_LOCATION#',
})
await delay(200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is('Gracias por tu location', getHistory[0])
assert.is(undefined, getHistory[1])
})
suiteCase.run()

View File

@@ -1,51 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const suiteCase = suite('Flujo: sensitive')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Responder a "ole" en minuscula`, async ({ database, provider }) => {
const flow = addKeyword(['ola', 'ole'], { sensitive: true }).addAnswer('Bienvenido a la OLA')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'ole',
})
await delay(100)
assert.is('Bienvenido a la OLA', database.listHistory[0].answer)
assert.is(undefined, database.listHistory[1])
})
suiteCase(`NO Responder a "ole" en minuscula`, async ({ database, provider }) => {
const flow = addKeyword(['ola', 'ole'], { sensitive: true }).addAnswer('Bienvenido a la OLA')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'OLE',
})
await delay(100)
assert.is(undefined, database.listHistory[0])
assert.is(undefined, database.listHistory[1])
})
suiteCase.run()

View File

@@ -1,89 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const fakeHTTP = async () => {
await delay(10)
}
const suiteCase = suite('Flujo: hijos con callbacks')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Debe continuar el flujo del hijo`, async ({ database, provider }) => {
const flowCash = addKeyword('cash').addAnswer('Traeme los billetes! 😎')
const flowOnline = addKeyword('paypal')
.addAnswer('Voy generar un link de paypal *escribe algo*', { capture: true }, async (_, { flowDynamic }) => {
await fakeHTTP()
await flowDynamic('Esperate.... estoy generando esto toma su tiempo')
})
.addAnswer('Aqui lo tienes 😎😎', null, async (_, { flowDynamic }) => {
await fakeHTTP()
await flowDynamic('http://paypal.com')
})
.addAnswer('Apurate!')
const flujoPrincipal = addKeyword('hola')
.addAnswer('¿Como estas todo bien?')
.addAnswer('Espero que si')
.addAnswer('¿Cual es tu email?', { capture: true }, async (ctx, { fallBack }) => {
if (!ctx.body.includes('@')) {
return fallBack('Veo que no es um mail *bien*')
}
})
.addAnswer('Voy a validar tu email...', null, async (_, { flowDynamic }) => {
await fakeHTTP()
return flowDynamic('Email validado correctamten!')
})
.addAnswer('¿Como vas a pagar *paypal* o *cash*?', { capture: true }, async () => {}, [flowCash, flowOnline])
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await provider.delaySendMessage(30, 'message', {
from: '000',
body: 'test@test.com',
})
await provider.delaySendMessage(60, 'message', {
from: '000',
body: 'paypal',
})
await provider.delaySendMessage(90, 'message', {
from: '000',
body: 'continue!',
})
await delay(800)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is('¿Como estas todo bien?', getHistory[0])
assert.is('Espero que si', getHistory[1])
assert.is('¿Cual es tu email?', getHistory[2])
assert.is('test@test.com', getHistory[3])
assert.is('Voy a validar tu email...', getHistory[4])
assert.is('Email validado correctamten!', getHistory[5])
assert.is('¿Como vas a pagar *paypal* o *cash*?', getHistory[6])
assert.is('paypal', getHistory[7])
assert.is('Voy generar un link de paypal *escribe algo*', getHistory[8])
assert.is('continue!', getHistory[9])
assert.is('Esperate.... estoy generando esto toma su tiempo', getHistory[10])
assert.is('Aqui lo tienes 😎😎', getHistory[11])
assert.is('http://paypal.com', getHistory[12])
assert.is('Apurate!', getHistory[13])
assert.is(undefined, getHistory[14])
})
suiteCase.run()

View File

@@ -1,59 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const suiteCase = suite('Flujo: regex')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Responder a una expresion regular`, async ({ database, provider }) => {
const REGEX_CREDIT_NUMBER = `/(^4[0-9]{12}(?:[0-9]{3})?$)|(^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$)|(3[47][0-9]{13})|(^3(?:0[0-5]|[68][0-9])[0-9]{11}$)|(^6(?:011|5[0-9]{2})[0-9]{12}$)|(^(?:2131|1800|35\d{3})\d{11}$)/gm`
const flow = addKeyword(REGEX_CREDIT_NUMBER, { regex: true })
.addAnswer(`Gracias por proporcionar un numero de tarjeta valido`)
.addAnswer('Fin!')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: '374245455400126',
})
await delay(100)
assert.is('Gracias por proporcionar un numero de tarjeta valido', database.listHistory[0].answer)
assert.is('Fin!', database.listHistory[1].answer)
assert.is(undefined, database.listHistory[2])
})
suiteCase(`NO Responder a una expresion regular`, async ({ database, provider }) => {
const REGEX_CREDIT_NUMBER = `/(^4[0-9]{12}(?:[0-9]{3})?$)|(^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$)|(3[47][0-9]{13})|(^3(?:0[0-5]|[68][0-9])[0-9]{11}$)|(^6(?:011|5[0-9]{2})[0-9]{12}$)|(^(?:2131|1800|35\d{3})\d{11}$)/gm`
const flow = addKeyword(REGEX_CREDIT_NUMBER, { regex: true })
.addAnswer(`Gracias por proporcionar un numero de tarjeta valido`)
.addAnswer('Fin!')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(100)
assert.is(undefined, database.listHistory[0])
assert.is(undefined, database.listHistory[1])
})
suiteCase.run()

View File

@@ -1,40 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const suiteCase = suite('Flujo: capture')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Responder a "pregunta"`, async ({ database, provider }) => {
const flow = addKeyword(['hola'])
.addAnswer(['Hola como estas?', '¿Cual es tu edad?'], { capture: true })
.addAnswer('Gracias por tu respuesta')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await provider.delaySendMessage(10, 'message', {
from: '000',
body: '90',
})
await delay(100)
assert.is(['Hola como estas?', '¿Cual es tu edad?'].join('\n'), database.listHistory[0].answer)
assert.is('90', database.listHistory[1].answer)
assert.is('Gracias por tu respuesta', database.listHistory[2].answer)
assert.is(undefined, database.listHistory[3])
})
suiteCase.run()

View File

@@ -1,162 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const fakeHTTP = async (fakeData = []) => {
await delay(50)
const data = fakeData.map((u) => ({ body: `${u}` }))
return Promise.resolve(data)
}
const suiteCase = suite('Flujo: flowDynamic')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Responder con mensajes asyncronos`, async ({ database, provider }) => {
const MOCK_VALUES = [
'Bienvenido te envio muchas marcas (5510)',
'Seleccione marca del auto a cotizar, con el *número* correspondiente',
'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:',
'Los precios rondan:',
]
const flow = addKeyword(['hola'])
.addAnswer(MOCK_VALUES[0], null, async (_, { flowDynamic }) => {
const data = await fakeHTTP(['Ford', 'GM', 'BMW'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[1], null, async (_, { flowDynamic }) => {
const data = await fakeHTTP(['Ranger', 'Explorer'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[2], null, async (_, { flowDynamic }) => {
const data = await fakeHTTP(['Usado', 'Nuevos'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[3], null, async (_, { flowDynamic }) => {
const data = await fakeHTTP(['1000', '2000', '3000'])
return flowDynamic(data)
})
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(1500)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
//FlowDynamic
assert.is('Ford', getHistory[1])
assert.is('GM', getHistory[2])
assert.is('BMW', getHistory[3])
assert.is(MOCK_VALUES[1], getHistory[4])
//FlowDynamic
assert.is('Ranger', getHistory[5])
assert.is('Explorer', getHistory[6])
assert.is(MOCK_VALUES[2], getHistory[7])
//FlowDynamic
assert.is('Usado', getHistory[8])
assert.is('Nuevos', getHistory[9])
assert.is(MOCK_VALUES[3], getHistory[10])
//FlowDynamic
assert.is('1000', getHistory[11])
assert.is('2000', getHistory[12])
assert.is('3000', getHistory[13])
assert.is(undefined, getHistory[14])
})
suiteCase(`Responder con un "string"`, async ({ database, provider }) => {
const flow = addKeyword(['hola'])
.addAnswer('Como vas?', null, async (_, { flowDynamic }) => {
return flowDynamic('Todo bien!')
})
.addAnswer('y vos?')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(100)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is('Como vas?', getHistory[0])
assert.is('Todo bien!', getHistory[1])
assert.is('y vos?', getHistory[2])
assert.is(undefined, getHistory[3])
})
suiteCase(`Responder con un "array"`, async ({ database, provider }) => {
const flow = addKeyword(['hola'])
.addAnswer('Como vas?', null, async (_, { flowDynamic }) => {
return flowDynamic(['Todo bien!', 'trabajando'])
})
.addAnswer('y vos?')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(100)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is('Como vas?', getHistory[0])
assert.is('Todo bien!', getHistory[1])
assert.is('trabajando', getHistory[2])
assert.is('y vos?', getHistory[3])
assert.is(undefined, getHistory[4])
})
suiteCase(`Responder con un "object"`, async ({ database, provider }) => {
const flow = addKeyword(['hola'])
.addAnswer('Como vas?', null, async (_, { flowDynamic }) => {
return flowDynamic([{ body: 'Todo bien!' }])
})
.addAnswer('y vos?')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(100)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is('Como vas?', getHistory[0])
assert.is('Todo bien!', getHistory[1])
assert.is('y vos?', getHistory[2])
assert.is(undefined, getHistory[3])
})
suiteCase.run()

View File

@@ -1,167 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const fakeHTTP = async (fakeData = []) => {
await delay(50)
const data = fakeData.map((u) => ({ body: `${u}` }))
return Promise.resolve(data)
}
const suiteCase = suite('Flujo: endFlow')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Detener el flujo`, async ({ database, provider }) => {
const MOCK_VALUES = [
'Bienvenido te envio muchas marcas',
'Seleccione marca del auto a cotizar, con el *número* correspondiente',
'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:',
'Los precios rondan:',
]
const flow = addKeyword(['hola'])
.addAnswer(MOCK_VALUES[0], null, async (_, { flowDynamic }) => {
const data = await fakeHTTP(['Ford', 'GM', 'BMW'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[1], null, async (_, { endFlow }) => {
return endFlow()
})
.addAnswer(MOCK_VALUES[2])
.addAnswer(MOCK_VALUES[3], null, async (_, { flowDynamic }) => {
const data = await fakeHTTP(['1000', '2000', '3000'])
return flowDynamic(data)
})
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(900)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
//FlowDynamic
assert.is('Ford', getHistory[1])
assert.is('GM', getHistory[2])
assert.is('BMW', getHistory[3])
assert.is(MOCK_VALUES[1], getHistory[4])
//FlowDynamic
assert.is(undefined, getHistory[5])
assert.is(undefined, getHistory[6])
})
suiteCase(`Detener el flujo flowDynamic`, async ({ database, provider }) => {
const flow = addKeyword(['hola'])
.addAnswer('Buenas!', null, async (_, { endFlow, flowDynamic }) => {
await flowDynamic('Continuamos...')
return endFlow()
})
.addAnswer('Como estas!')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(100)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is('Buenas!', getHistory[0])
assert.is('Continuamos...', getHistory[1])
assert.is(undefined, getHistory[2])
})
suiteCase(`flowDynamic con capture`, async ({ database, provider }) => {
const MOCK_VALUES = ['¿CUal es tu email?', 'Continuamos....', '¿Cual es tu edad?']
const flow = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic, fallBack }) => {
const validation = ctx.body.includes('@')
if (validation) {
const getDataFromApi = await fakeHTTP(['Gracias por tu email se ha validado de manera correcta'])
return flowDynamic(getDataFromApi)
}
return fallBack()
}
)
.addAnswer(MOCK_VALUES[1])
.addAnswer(MOCK_VALUES[2], { capture: true }, async (ctx, { flowDynamic, fallBack }) => {
if (ctx.body !== '18') {
await delay(20)
return fallBack('Ups creo que no eres mayor de edad')
}
return flowDynamic('Bien tu edad es correcta!')
})
.addAnswer('Puedes pasar')
createBot({
database,
provider,
flow: createFlow([flow]),
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await provider.delaySendMessage(10, 'message', {
from: '000',
body: 'this is not email value',
})
await provider.delaySendMessage(20, 'message', {
from: '000',
body: 'test@test.com',
})
await provider.delaySendMessage(90, 'message', {
from: '000',
body: '20',
})
await provider.delaySendMessage(200, 'message', {
from: '000',
body: '18',
})
await delay(900)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('this is not email value', getHistory[1])
assert.is(MOCK_VALUES[0], getHistory[2])
assert.is('test@test.com', getHistory[3])
assert.is('Gracias por tu email se ha validado de manera correcta', getHistory[4])
assert.is(MOCK_VALUES[1], getHistory[5])
assert.is(MOCK_VALUES[2], getHistory[6])
assert.is('20', getHistory[7])
assert.is('Ups creo que no eres mayor de edad', getHistory[8])
assert.is('18', getHistory[9])
assert.is('Bien tu edad es correcta!', getHistory[10])
assert.is('Puedes pasar', getHistory[11])
})
suiteCase.run()

View File

@@ -1,97 +0,0 @@
const { suite } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, createBot, createFlow } = require('../packages/bot/index')
const { setup, clear, delay } = require('../__mocks__/env')
const suiteCase = suite('Flujo: manejo de estado')
suiteCase.before.each(setup)
suiteCase.after.each(clear)
suiteCase(`Debe retornar un mensaje resumen`, async ({ database, provider }) => {
let STATE_APP = {}
const MOCK_VALUES = ['¿Cual es tu nombre?', '¿Cual es tu edad?', 'Tu datos son:']
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic }) => {
STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], name: ctx.body }
flowDynamic('Gracias por tu nombre!')
}
)
.addAnswer(
MOCK_VALUES[1],
{
capture: true,
},
async (ctx, { flowDynamic }) => {
STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], age: ctx.body }
await flowDynamic(`Gracias por tu edad! ${STATE_APP[ctx.from].name}`)
}
)
.addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => {
flowDynamic(`Nombre: ${STATE_APP[ctx.from].name} Edad: ${STATE_APP[ctx.from].age}`)
})
.addAnswer('🤖🤖 Gracias por tu participacion')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
await provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await provider.delaySendMessage(5, 'message', {
from: '001',
body: 'hola',
})
await provider.delaySendMessage(10, 'message', {
from: '000',
body: 'Leifer',
})
await provider.delaySendMessage(15, 'message', {
from: '000',
body: '90',
})
await provider.delaySendMessage(20, 'message', {
from: '001',
body: 'Maria',
})
await provider.delaySendMessage(25, 'message', {
from: '001',
body: '100',
})
await delay(1000)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('¿Cual es tu nombre?', getHistory[1])
assert.is('Leifer', getHistory[2])
assert.is('Gracias por tu nombre!', getHistory[3])
assert.is('¿Cual es tu edad?', getHistory[4])
assert.is('90', getHistory[5])
assert.is('Gracias por tu edad! Leifer', getHistory[6])
assert.is('Tu datos son:', getHistory[7])
assert.is('Nombre: Leifer Edad: 90', getHistory[8])
assert.is('🤖🤖 Gracias por tu participacion', getHistory[9])
assert.is('Maria', getHistory[10])
assert.is('Gracias por tu nombre!', getHistory[11])
assert.is('100', getHistory[12])
assert.is(undefined, getHistory[13])
})
suiteCase.run()

41
__test__/01-case.test.js Normal file
View File

@@ -0,0 +1,41 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot')
test(`[Caso - 01] Flow Basico`, async () => {
const [VALUE_A, VALUE_B] = ['hola', 'buenas']
const flow = addKeyword(VALUE_A).addAnswer(VALUE_B)
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
createBot({
database,
flow: createFlow([flow]),
provider,
})
provider.delaySendMessage(100, 'message', {
from: '000',
body: VALUE_A,
})
await delay(100)
const prevMsg = database.getPrevByNumber('000')
assert.is(prevMsg.answer, VALUE_B)
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

99
__test__/02-case.test.js Normal file
View File

@@ -0,0 +1,99 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
console.log('⚡ Server request!')
await delay(50)
console.log('⚡ Server return!')
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
console.log(data)
return Promise.resolve(data)
}
test(`[Caso - 02] Flow (flowDynamic)`, async () => {
const MOCK_VALUES = [
'Bienvenido te envio muchas marcas (5510)',
'Seleccione marca del auto a cotizar, con el *número* correspondiente',
'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:',
'Los precios rondan:',
]
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(MOCK_VALUES[0], null, async (ctx, { flowDynamic }) => {
console.log('execute...')
const data = await fakeHTTP(['Ford', 'GM', 'BMW'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[1], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['Ranger', 'Explorer'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['Usado', 'Nuevos'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[3], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['1000', '2000', '3000'])
return flowDynamic(data)
})
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
//FlowDynamic
assert.is('1 Ford', getHistory[1])
assert.is('2 GM', getHistory[2])
assert.is('3 BMW', getHistory[3])
assert.is(MOCK_VALUES[1], getHistory[4])
//FlowDynamic
assert.is('1 Ranger', getHistory[5])
assert.is('2 Explorer', getHistory[6])
assert.is(MOCK_VALUES[2], getHistory[7])
//FlowDynamic
assert.is('1 Usado', getHistory[8])
assert.is('2 Nuevos', getHistory[9])
assert.is(MOCK_VALUES[3], getHistory[10])
//FlowDynamic
assert.is('1 1000', getHistory[11])
assert.is('2 2000', getHistory[12])
assert.is('3 3000', getHistory[13])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

44
__test__/03-case.test.js Normal file
View File

@@ -0,0 +1,44 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot/index')
test(`[Caso - 03] Flow puro`, async () => {
const MOCK_VALUES = ['Bienvenido a mi tienda', 'Como estas?']
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(MOCK_VALUES[0])
.addAnswer(MOCK_VALUES[1])
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(10)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is(MOCK_VALUES[1], getHistory[1])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

82
__test__/04-case.test.js Normal file
View File

@@ -0,0 +1,82 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
console.log('⚡ Server request!')
await delay(50)
console.log('⚡ Server return!')
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
console.log(data)
return Promise.resolve(data)
}
test(`[Caso - 04] Romper flujo (endFlow)`, async () => {
const MOCK_VALUES = [
'Bienvenido te envio muchas marcas (5510)',
'Seleccione marca del auto a cotizar, con el *número* correspondiente',
'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:',
'Los precios rondan:',
]
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(MOCK_VALUES[0], null, async (ctx, { flowDynamic }) => {
console.log('execute...')
const data = await fakeHTTP(['Ford', 'GM', 'BMW'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[1], null, async (ctx, { endFlow }) => {
return endFlow()
})
.addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['Usado', 'Nuevos'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[3], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['1000', '2000', '3000'])
return flowDynamic(data)
})
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
//FlowDynamic
assert.is('1 Ford', getHistory[1])
assert.is('2 GM', getHistory[2])
assert.is('3 BMW', getHistory[3])
assert.is(MOCK_VALUES[1], getHistory[4])
assert.is(undefined, getHistory[5])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

118
__test__/05-case.test.js Normal file
View File

@@ -0,0 +1,118 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
await delay(5)
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
return Promise.resolve(data)
}
test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => {
const MOCK_VALUES = [
'¿CUal es tu email?',
'Continuamos....',
'¿Cual es tu edad?',
]
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic, fallBack }) => {
const validation = ctx.body.includes('@')
if (validation) {
const getDataFromApi = await fakeHTTP([
'Gracias por tu email se ha validado de manera correcta',
])
return flowDynamic(getDataFromApi)
}
return fallBack(validation)
}
)
.addAnswer(MOCK_VALUES[1])
.addAnswer(
MOCK_VALUES[2],
{ capture: true },
async (ctx, { flowDynamic, fallBack }) => {
if (ctx.body !== '18') {
await delay(50)
return fallBack(false, 'Ups creo que no eres mayor de edad')
}
return flowDynamic('Bien tu edad es correcta!')
}
)
.addAnswer('Puedes pasar')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
provider.delaySendMessage(10, 'message', {
from: '000',
body: 'this is not email value',
})
provider.delaySendMessage(20, 'message', {
from: '000',
body: 'test@test.com',
})
provider.delaySendMessage(90, 'message', {
from: '000',
body: '20',
})
provider.delaySendMessage(200, 'message', {
from: '000',
body: '18',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('this is not email value', getHistory[1])
assert.is(MOCK_VALUES[0], getHistory[2])
assert.is('test@test.com', getHistory[3])
assert.is(
'1 Gracias por tu email se ha validado de manera correcta',
getHistory[4]
)
assert.is(MOCK_VALUES[1], getHistory[5])
assert.is(MOCK_VALUES[2], getHistory[6])
assert.is('20', getHistory[7])
assert.is('Ups creo que no eres mayor de edad', getHistory[8])
assert.is('18', getHistory[9])
assert.is('Bien tu edad es correcta!', getHistory[10])
assert.is('Puedes pasar', getHistory[11])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

111
__test__/06-case.test.js Normal file
View File

@@ -0,0 +1,111 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
await delay(5)
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
return Promise.resolve(data)
}
test(`[Caso - 06] Finalizar Flujo (endFlow)`, async () => {
const MOCK_VALUES = [
'¿CUal es tu email?',
'Continuamos....',
'¿Cual es tu edad?',
]
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic, fallBack }) => {
const validation = ctx.body.includes('@')
if (validation) {
const getDataFromApi = await fakeHTTP([
'Gracias por tu email se ha validado de manera correcta',
])
return flowDynamic(getDataFromApi)
}
return fallBack(validation)
}
)
.addAnswer(MOCK_VALUES[1], null, async (_, { endFlow }) => {
return endFlow()
})
.addAnswer(
MOCK_VALUES[2],
{ capture: true },
async (ctx, { flowDynamic, fallBack }) => {
if (ctx.body !== '18') {
await delay(50)
return fallBack(false, 'Ups creo que no eres mayor de edad')
}
return flowDynamic('Bien tu edad es correcta!')
}
)
.addAnswer('Puedes pasar')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
provider.delaySendMessage(10, 'message', {
from: '000',
body: 'this is not email value',
})
provider.delaySendMessage(20, 'message', {
from: '000',
body: 'test@test.com',
})
provider.delaySendMessage(90, 'message', {
from: '000',
body: '20',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('this is not email value', getHistory[1])
assert.is(MOCK_VALUES[0], getHistory[2])
assert.is('test@test.com', getHistory[3])
assert.is(
'1 Gracias por tu email se ha validado de manera correcta',
getHistory[4]
)
assert.is(MOCK_VALUES[1], getHistory[5])
assert.is('20', getHistory[6])
assert.is(undefined, getHistory[7])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

View File

@@ -1,10 +1,28 @@
module.exports = { module.exports = {
disableEmoji: false, disableEmoji: false,
format: '{type}{scope}: {emoji}{subject}', format: '{type}{scope}: {emoji}{subject}',
list: ['test', 'feat', 'fix', 'chore', 'docs', 'refactor', 'style', 'ci', 'perf'], list: [
'test',
'feat',
'fix',
'chore',
'docs',
'refactor',
'style',
'ci',
'perf',
],
maxMessageLength: 64, maxMessageLength: 64,
minMessageLength: 3, minMessageLength: 3,
questions: ['type', 'scope', 'subject', 'body', 'breaking', 'issues', 'lerna'], questions: [
'type',
'scope',
'subject',
'body',
'breaking',
'issues',
'lerna',
],
scopes: [], scopes: [],
types: { types: {
chore: { chore: {
@@ -38,7 +56,8 @@ module.exports = {
value: 'perf', value: 'perf',
}, },
refactor: { refactor: {
description: 'A code change that neither fixes a bug or adds a feature', description:
'A code change that neither fixes a bug or adds a feature',
emoji: '(💡)', emoji: '(💡)',
value: 'refactor', value: 'refactor',
}, },
@@ -48,7 +67,8 @@ module.exports = {
value: 'release', value: 'release',
}, },
style: { style: {
description: 'Markup, white-space, formatting, missing semi-colons...', description:
'Markup, white-space, formatting, missing semi-colons...',
emoji: '(💄)', emoji: '(💄)',
value: 'style', value: 'style',
}, },
@@ -60,7 +80,8 @@ module.exports = {
messages: { messages: {
type: "Select the type of change that you're committing:", type: "Select the type of change that you're committing:",
customScope: 'Select the scope this component affects:', customScope: 'Select the scope this component affects:',
subject: 'Write a short, imperative mood description of the change:\n', subject:
'Write a short, imperative mood description of the change:\n',
body: 'Provide a longer description of the change:\n ', body: 'Provide a longer description of the change:\n ',
breaking: 'List any breaking changes:\n', breaking: 'List any breaking changes:\n',
footer: 'Issues this commit closes, e.g #123:', footer: 'Issues this commit closes, e.g #123:',

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/root", "name": "@bot-whatsapp/root",
"version": "0.1.21", "version": "0.1.19",
"description": "Bot de wahtsapp open source para MVP o pequeños negocios", "description": "Bot de wahtsapp open source para MVP o pequeños negocios",
"main": "app.js", "main": "app.js",
"private": true, "private": true,
@@ -25,7 +25,7 @@
"copy.lib": "node ./scripts/move.js", "copy.lib": "node ./scripts/move.js",
"test.unit": "node ./node_modules/uvu/bin.js packages test", "test.unit": "node ./node_modules/uvu/bin.js packages test",
"test.e2e": "node ./node_modules/uvu/bin.js __test__", "test.e2e": "node ./node_modules/uvu/bin.js __test__",
"test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit", "test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit && npm run test.e2e",
"test": "npm run test.coverage", "test": "npm run test.coverage",
"cli": "node ./packages/cli/bin/cli.js", "cli": "node ./packages/cli/bin/cli.js",
"create": "node ./packages/create-bot-whatsapp/bin/create.js", "create": "node ./packages/create-bot-whatsapp/bin/create.js",

View File

@@ -43,7 +43,8 @@ class CoreClass {
}, },
{ {
event: 'require_action', event: 'require_action',
func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) => printer(instructions, title), func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) =>
printer(instructions, title),
}, },
{ {
event: 'ready', event: 'ready',
@@ -51,7 +52,8 @@ class CoreClass {
}, },
{ {
event: 'auth_failure', event: 'auth_failure',
func: ({ instructions }) => printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'), func: ({ instructions }) =>
printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'),
}, },
{ {
@@ -76,7 +78,9 @@ class CoreClass {
if (!body.length) return if (!body.length) return
let prevMsg = await this.databaseClass.getPrevByNumber(from) let prevMsg = await this.databaseClass.getPrevByNumber(from)
const refToContinue = this.flowClass.findBySerialize(prevMsg?.refSerialize) const refToContinue = this.flowClass.findBySerialize(
prevMsg?.refSerialize
)
if (prevMsg?.ref) { if (prevMsg?.ref) {
const ctxByNumber = toCtx({ const ctxByNumber = toCtx({
@@ -84,12 +88,15 @@ class CoreClass {
from, from,
prevRef: prevMsg.refSerialize, prevRef: prevMsg.refSerialize,
}) })
await this.databaseClass.save(ctxByNumber) this.databaseClass.save(ctxByNumber)
} }
// 📄 Crar CTX de mensaje (uso private) // 📄 Crar CTX de mensaje (uso private)
const createCtxMessage = (payload = {}, index = 0) => { const createCtxMessage = (payload = {}, index = 0) => {
const body = typeof payload === 'string' ? payload : payload?.body ?? payload?.answer const body =
typeof payload === 'string'
? payload
: payload?.body ?? payload?.answer
const media = payload?.media ?? null const media = payload?.media ?? null
const buttons = payload?.buttons ?? [] const buttons = payload?.buttons ?? []
const capture = payload?.capture ?? false const capture = payload?.capture ?? false
@@ -110,118 +117,97 @@ class CoreClass {
} }
// 📄 Finalizar flujo // 📄 Finalizar flujo
const endFlow = const endFlow = async (message = null) => {
(flag) => prevMsg = null
async (message = null) => { endFlowFlag = true
flag.endFlow = true if (message)
endFlowFlag = true this.sendProviderAndSave(from, createCtxMessage(message))
if (message) this.sendProviderAndSave(from, createCtxMessage(message)) clearQueue()
clearQueue() return
sendFlow([]) }
return
} // 📄 Continuar con el siguiente flujo
const continueFlow = async () => {
const cotinueMessage =
this.flowClass.find(refToContinue?.ref, true) || []
sendFlow(cotinueMessage, from, { continue: true })
return
}
// 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx // 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx
const sendFlow = async (messageToSend, numberOrId, options = { prev: prevMsg }) => { const sendFlow = async (
if (options.prev?.options?.capture) await cbEveryCtx(options.prev?.ref) messageToSend,
numberOrId,
options = { continue: false }
) => {
if (!options.continue && prevMsg?.options?.capture)
await cbEveryCtx(prevMsg?.ref)
const queue = [] const queue = []
for (const ctxMessage of messageToSend) { for (const ctxMessage of messageToSend) {
if (endFlowFlag) return if (endFlowFlag) return
const delayMs = ctxMessage?.options?.delay || 0 const delayMs = ctxMessage?.options?.delay || 0
if (delayMs) await delay(delayMs) if (delayMs) await delay(delayMs)
await QueuePrincipal.enqueue(() => QueuePrincipal.enqueue(() =>
this.sendProviderAndSave(numberOrId, ctxMessage).then(() => resolveCbEveryCtx(ctxMessage)) Promise.all([
this.sendProviderAndSave(numberOrId, ctxMessage).then(
() => resolveCbEveryCtx(ctxMessage)
),
])
) )
} }
return Promise.all(queue) return Promise.all(queue)
} }
const continueFlow = async () => {
const currentPrev = await this.databaseClass.getPrevByNumber(from)
const nextFlow = (await this.flowClass.find(refToContinue?.ref, true)) ?? []
const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize)
const isContinueFlow = filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref)
if (!isContinueFlow) {
const refToContinueChild = this.flowClass.getRefToContinueChild(currentPrev?.keyword)
const flowStandaloneChild = this.flowClass.getFlowsChild()
const nextChildMessages =
(await this.flowClass.find(refToContinueChild?.ref, true, flowStandaloneChild)) || []
if (nextChildMessages?.length) return await sendFlow(nextChildMessages, from, { prev: undefined })
}
if (!isContinueFlow) {
await sendFlow(filterNextFlow, from, { prev: undefined })
return
}
}
// 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje
const fallBack = const fallBack = async (next = false, message = null) => {
(flag) => QueuePrincipal.queue = []
async (message = null) => { if (next) return continueFlow()
QueuePrincipal.queue = [] return this.sendProviderAndSave(from, {
flag.fallBack = true ...prevMsg,
await this.sendProviderAndSave(from, { answer:
...prevMsg, typeof message === 'string'
answer: typeof message === 'string' ? message : message?.body ?? prevMsg.answer, ? message
options: { : message?.body ?? prevMsg.answer,
...prevMsg.options, options: {
buttons: prevMsg.options?.buttons, ...prevMsg.options,
}, buttons: message?.buttons ?? prevMsg.options?.buttons,
}) },
return })
} }
// 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes // 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes
// para evitar bloque de whatsapp // para evitar bloque de whatsapp
const flowDynamic = const flowDynamic = async (listMsg = []) => {
(flag) => if (!Array.isArray(listMsg)) listMsg = [listMsg]
async (listMsg = []) => {
flag.flowDynamic = true
if (!Array.isArray(listMsg)) listMsg = [listMsg]
const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) const parseListMsg = listMsg.map((opt, index) =>
createCtxMessage(opt, index)
)
if (endFlowFlag) return if (endFlowFlag) return
for (const msg of parseListMsg) { for (const msg of parseListMsg) {
await this.sendProviderAndSave(from, msg) await this.sendProviderAndSave(from, msg)
}
await continueFlow()
return
} }
return continueFlow()
}
// 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback
const resolveCbEveryCtx = async (ctxMessage) => { const resolveCbEveryCtx = async (ctxMessage) => {
if (!ctxMessage?.options?.capture) return await cbEveryCtx(ctxMessage?.ref) if (!ctxMessage?.options?.capture)
return await cbEveryCtx(ctxMessage?.ref)
} }
// 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo // 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo
const cbEveryCtx = async (inRef) => { const cbEveryCtx = async (inRef) => {
let flags = {
endFlow: false,
fallBack: false,
flowDynamic: false,
wait: true,
}
const provider = this.providerClass
if (!this.flowClass.allCallbacks[inRef]) return Promise.resolve() if (!this.flowClass.allCallbacks[inRef]) return Promise.resolve()
return this.flowClass.allCallbacks[inRef](messageCtxInComming, {
const argsCb = { fallBack,
provider, flowDynamic,
fallBack: fallBack(flags), endFlow,
flowDynamic: flowDynamic(flags), continueFlow,
endFlow: endFlow(flags), })
}
await this.flowClass.allCallbacks[inRef](messageCtxInComming, argsCb)
const wait = !(!flags.endFlow && !flags.fallBack && !flags.flowDynamic)
if (!wait) await continueFlow()
return
} }
// 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa // 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa
@@ -233,7 +219,7 @@ class CoreClass {
msgToSend = this.flowClass.find(body, false, flowStandalone) || [] msgToSend = this.flowClass.find(body, false, flowStandalone) || []
await sendFlow(msgToSend, from) sendFlow(msgToSend, from)
return return
} }
@@ -243,7 +229,7 @@ class CoreClass {
if (typeCapture === 'boolean' && fallBackFlag) { if (typeCapture === 'boolean' && fallBackFlag) {
msgToSend = this.flowClass.find(refToContinue?.ref, true) || [] msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
await sendFlow(msgToSend, from) sendFlow(msgToSend, from)
return return
} }
} }
@@ -258,11 +244,12 @@ class CoreClass {
* @param {*} ctxMessage ver más en GLOSSARY.md * @param {*} ctxMessage ver más en GLOSSARY.md
* @returns * @returns
*/ */
sendProviderAndSave = async (numberOrId, ctxMessage) => { sendProviderAndSave = (numberOrId, ctxMessage) => {
const { answer } = ctxMessage const { answer } = ctxMessage
await this.providerClass.sendMessage(numberOrId, answer, ctxMessage) return Promise.all([
await this.databaseClass.save({ ...ctxMessage, from: numberOrId }) this.providerClass.sendMessage(numberOrId, answer, ctxMessage),
return this.databaseClass.save({ ...ctxMessage, from: numberOrId }),
])
} }
/** /**
@@ -292,7 +279,9 @@ class CoreClass {
for (const ctxMessage of messageToSend) { for (const ctxMessage of messageToSend) {
const delayMs = ctxMessage?.options?.delay || 0 const delayMs = ctxMessage?.options?.delay || 0
if (delayMs) await delay(delayMs) if (delayMs) await delay(delayMs)
QueuePrincipal.enqueue(() => this.sendProviderAndSave(numberOrId, ctxMessage)) QueuePrincipal.enqueue(() =>
this.sendProviderAndSave(numberOrId, ctxMessage)
)
} }
return Promise.all(queue) return Promise.all(queue)
} }

View File

@@ -8,7 +8,8 @@ const { addKeyword, addAnswer, addChild, toSerialize } = require('./io/methods')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBot = async ({ flow, database, provider }, args = {}) => new CoreClass(flow, database, provider, args) const createBot = async ({ flow, database, provider }, args = {}) =>
new CoreClass(flow, database, provider, args)
/** /**
* Crear instancia de clase Io (Flow) * Crear instancia de clase Io (Flow)
@@ -28,7 +29,8 @@ const createFlow = (args) => {
*/ */
const createProvider = (providerClass = class {}, args = null) => { const createProvider = (providerClass = class {}, args = null) => {
const providerInstance = new providerClass(args) const providerInstance = new providerClass(args)
if (!providerClass.prototype instanceof ProviderClass) throw new Error('El provider no implementa ProviderClass') if (!providerClass.prototype instanceof ProviderClass)
throw new Error('El provider no implementa ProviderClass')
return providerInstance return providerInstance
} }

View File

@@ -25,9 +25,9 @@ class FlowClass {
let refSymbol = null let refSymbol = null
overFlow = overFlow ?? this.flowSerialize overFlow = overFlow ?? this.flowSerialize
const mapSensitive = (str, mapOptions = { sensitive: false, regex: false }) => { /** Retornar expresion regular para buscar coincidencia */
if (mapOptions.regex) return new RegExp(str) const mapSensitive = (str, flag = false) => {
const regexSensitive = mapOptions.sensitive ? 'g' : 'i' const regexSensitive = flag ? 'g' : 'i'
if (Array.isArray(str)) { if (Array.isArray(str)) {
return new RegExp(str.join('|'), regexSensitive) return new RegExp(str.join('|'), regexSensitive)
} }
@@ -35,7 +35,9 @@ class FlowClass {
} }
const findIn = (keyOrWord, symbol = false, flow = overFlow) => { const findIn = (keyOrWord, symbol = false, flow = overFlow) => {
const sensitive = refSymbol?.options?.sensitive || false
capture = refSymbol?.options?.capture || false capture = refSymbol?.options?.capture || false
if (capture) return messages if (capture) return messages
if (symbol) { if (symbol) {
@@ -44,9 +46,7 @@ class FlowClass {
if (refSymbol?.ref) findIn(refSymbol.ref, true) if (refSymbol?.ref) findIn(refSymbol.ref, true)
} else { } else {
refSymbol = flow.find((c) => { refSymbol = flow.find((c) => {
const sensitive = c?.options?.sensitive || false return mapSensitive(c.keyword, sensitive).test(keyOrWord)
const regex = c?.options?.regex || false
return mapSensitive(c.keyword, { sensitive, regex }).test(keyOrWord)
}) })
if (refSymbol?.ref) findIn(refSymbol.ref, true) if (refSymbol?.ref) findIn(refSymbol.ref, true)
return messages return messages
@@ -56,40 +56,10 @@ class FlowClass {
return messages return messages
} }
findBySerialize = (refSerialize) => this.flowSerialize.find((r) => r.refSerialize === refSerialize) findBySerialize = (refSerialize) =>
this.flowSerialize.find((r) => r.refSerialize === refSerialize)
findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref) findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref)
getRefToContinueChild = (keyword) => {
try {
const flowChilds = this.flowSerialize
.reduce((acc, cur) => {
const merge = [...acc, cur?.options?.nested].flat(2)
return merge
}, [])
.filter((i) => !!i && i?.refSerialize === keyword)
.shift()
return flowChilds
} catch (e) {
return undefined
}
}
getFlowsChild = () => {
try {
const flowChilds = this.flowSerialize
.reduce((acc, cur) => {
const merge = [...acc, cur?.options?.nested].flat(2)
return merge
}, [])
.filter((i) => !!i)
return flowChilds
} catch (e) {
return []
}
}
} }
module.exports = FlowClass module.exports = FlowClass

View File

@@ -17,10 +17,15 @@ const addAnswer =
* @returns * @returns
*/ */
const getAnswerOptions = () => ({ const getAnswerOptions = () => ({
media: typeof options?.media === 'string' ? `${options?.media}` : null, media:
typeof options?.media === 'string' ? `${options?.media}` : null,
buttons: Array.isArray(options?.buttons) ? options.buttons : [], buttons: Array.isArray(options?.buttons) ? options.buttons : [],
capture: typeof options?.capture === 'boolean' ? options?.capture : false, capture:
child: typeof options?.child === 'string' ? `${options?.child}` : null, typeof options?.capture === 'boolean'
? options?.capture
: false,
child:
typeof options?.child === 'string' ? `${options?.child}` : null,
delay: typeof options?.delay === 'number' ? options?.delay : 0, delay: typeof options?.delay === 'number' ? options?.delay : 0,
}) })
@@ -44,7 +49,8 @@ const addAnswer =
* Esta funcion aplana y busca los callback anidados de los hijos * Esta funcion aplana y busca los callback anidados de los hijos
* @returns * @returns
*/ */
const getCbFromNested = () => flatObject(Array.isArray(nested) ? nested : [nested]) const getCbFromNested = () =>
flatObject(Array.isArray(nested) ? nested : [nested])
const callback = typeof cb === 'function' ? cb : () => null const callback = typeof cb === 'function' ? cb : () => null

View File

@@ -8,14 +8,12 @@ const { toJson } = require('./toJson')
* @param {*} options {sensitive:boolean} default false * @param {*} options {sensitive:boolean} default false
*/ */
const addKeyword = (keyword, options) => { const addKeyword = (keyword, options) => {
if (typeof keyword !== 'string' && !Array.isArray(keyword)) {
throw new Error('DEBE_SER_STRING_ARRAY_REGEX')
}
const parseOptions = () => { const parseOptions = () => {
const defaultProperties = { const defaultProperties = {
sensitive: typeof options?.sensitive === 'boolean' ? options?.sensitive : false, sensitive:
regex: typeof options?.regex === 'boolean' ? options?.regex : false, typeof options?.sensitive === 'boolean'
? options?.sensitive
: false,
} }
return defaultProperties return defaultProperties

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/bot", "name": "@bot-whatsapp/bot",
"version": "0.0.100-alpha.0", "version": "0.0.91-alpha.0",
"description": "", "description": "",
"main": "./lib/bundle.bot.cjs", "main": "./lib/bundle.bot.cjs",
"scripts": { "scripts": {

View File

@@ -20,11 +20,10 @@ class ProviderClass extends EventEmitter {
*/ */
sendMessage = async (userId, message) => { sendMessage = async (userId, message) => {
if (NODE_ENV !== 'production') console.log('[sendMessage]', { userId, message }) if (NODE_ENV !== 'production')
console.log('[sendMessage]', { userId, message })
return message return message
} }
getInstance = () => this.vendor
} }
module.exports = ProviderClass module.exports = ProviderClass

View File

@@ -2,7 +2,13 @@ const { test } = require('uvu')
const assert = require('uvu/assert') const assert = require('uvu/assert')
const FlowClass = require('../io/flow.class') const FlowClass = require('../io/flow.class')
const MockProvider = require('../../../__mocks__/mock.provider') const MockProvider = require('../../../__mocks__/mock.provider')
const { createBot, CoreClass, createFlow, createProvider, ProviderClass } = require('../index') const {
createBot,
CoreClass,
createFlow,
createProvider,
ProviderClass,
} = require('../index')
class MockFlow { class MockFlow {
allCallbacks = { ref: () => 1 } allCallbacks = { ref: () => 1 }
@@ -17,8 +23,6 @@ class MockFlow {
} }
findBySerialize = () => ({}) findBySerialize = () => ({})
findIndexByRef = () => 0 findIndexByRef = () => 0
getRefToContinueChild = () => ({})
getFlowsChild = () => []
} }
class MockDBA { class MockDBA {
@@ -96,13 +100,20 @@ test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
await createBot(setting) await createBot(setting)
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r)) mockProvider.on(
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action) mockProvider.delaySendMessage(
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -110,12 +121,21 @@ test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action)) assert.is(
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure)) assert.is(
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message)) assert.is(
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test(`[Bot] Probando Flujos Internos`, async () => { test(`[Bot] Probando Flujos Internos`, async () => {
@@ -146,13 +166,20 @@ test(`[Bot] Probando Flujos Internos`, async () => {
await createBot(setting) await createBot(setting)
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r)) mockProvider.on(
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action) mockProvider.delaySendMessage(
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -160,12 +187,21 @@ test(`[Bot] Probando Flujos Internos`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action)) assert.is(
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure)) assert.is(
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message)) assert.is(
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test(`[Bot] Probando Flujos Nested`, async () => { test(`[Bot] Probando Flujos Nested`, async () => {
@@ -198,13 +234,20 @@ test(`[Bot] Probando Flujos Nested`, async () => {
botInstance.sendProviderAndSave('xxxxx', 'xxxxx') botInstance.sendProviderAndSave('xxxxx', 'xxxxx')
botInstance.continue('xxxxx', 'xxxxx') botInstance.continue('xxxxx', 'xxxxx')
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r)) mockProvider.on(
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action) mockProvider.delaySendMessage(
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -212,12 +255,21 @@ test(`[Bot] Probando Flujos Nested`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action)) assert.is(
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure)) assert.is(
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message)) assert.is(
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test.run() test.run()

View File

@@ -35,7 +35,10 @@ test('Debere probar toSerialize', () => {
const ARRANGE = { const ARRANGE = {
keyword: ['hola!', 'ole'], keyword: ['hola!', 'ole'],
} }
const MAIN_CTX = addKeyword(ARRANGE.keyword).addAnswer('Segundo!').addAnswer('Segundo!').toJson() const MAIN_CTX = addKeyword(ARRANGE.keyword)
.addAnswer('Segundo!')
.addAnswer('Segundo!')
.toJson()
const [ANSWER_A] = MAIN_CTX const [ANSWER_A] = MAIN_CTX
@@ -68,7 +71,9 @@ test('Debere probar la anidación', () => {
answer_A: 'Bienvenido', answer_A: 'Bienvenido',
answer_B: 'Continuar', answer_B: 'Continuar',
} }
const MAIN_CTX = addKeyword(ARRANGE.keyword).addAnswer(ARRANGE.answer_A).addAnswer(ARRANGE.answer_B) const MAIN_CTX = addKeyword(ARRANGE.keyword)
.addAnswer(ARRANGE.answer_A)
.addAnswer(ARRANGE.answer_B)
assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B) assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B)
}) })
@@ -102,7 +107,10 @@ test('Debere probar error las addAnswer', () => {
}) })
test('Obtener toJson', () => { test('Obtener toJson', () => {
const [ctxA, ctxB, ctxC] = addKeyword('hola').addAnswer('pera!').addAnswer('chao').toJson() const [ctxA, ctxB, ctxC] = addKeyword('hola')
.addAnswer('pera!')
.addAnswer('chao')
.toJson()
assert.is(ctxA.keyword, 'hola') assert.is(ctxA.keyword, 'hola')
assert.match(ctxA.ref, /^key_/) assert.match(ctxA.ref, /^key_/)

View File

@@ -1,3 +1,4 @@
const delay = (miliseconds) => new Promise((res) => setTimeout(res, miliseconds)) const delay = (miliseconds) =>
new Promise((res) => setTimeout(res, miliseconds))
module.exports = { delay } module.exports = { delay }

View File

@@ -3,7 +3,9 @@ const flatObject = (listArray = []) => {
if (!listArray.length) return {} if (!listArray.length) return {}
const cbNestedObj = cbNestedList.map(({ ctx }) => ctx?.callbacks).filter((i) => !!i) const cbNestedObj = cbNestedList
.map(({ ctx }) => ctx?.callbacks)
.filter((i) => !!i)
const queueCb = cbNestedObj.reduce((acc, current) => { const queueCb = cbNestedObj.reduce((acc, current) => {
const getKeys = Object.keys(current) const getKeys = Object.keys(current)
const parse = getKeys.map((icb, i) => ({ const parse = getKeys.map((icb, i) => ({

View File

@@ -16,6 +16,9 @@ const generateRef = (prefix = false) => {
* @returns * @returns
*/ */
const generateRefSerialize = ({ index, answer, keyword }) => const generateRefSerialize = ({ index, answer, keyword }) =>
crypto.createHash('md5').update(JSON.stringify({ index, answer, keyword })).digest('hex') crypto
.createHash('md5')
.update(JSON.stringify({ index, answer, keyword }))
.digest('hex')
module.exports = { generateRef, generateRefSerialize } module.exports = { generateRef, generateRefSerialize }

View File

@@ -4,7 +4,9 @@ const printer = (message, title) => {
if (NODE_ENV !== 'test') { if (NODE_ENV !== 'test') {
// console.clear() // console.clear()
if (title) console.log(bgRed(`${title}`)) if (title) console.log(bgRed(`${title}`))
console.log(yellow(Array.isArray(message) ? message.join('\n') : message)) console.log(
yellow(Array.isArray(message) ? message.join('\n') : message)
)
console.log(``) console.log(``)
} }
} }

View File

@@ -5,9 +5,15 @@ const checkNodeVersion = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log(bgCyan('🚀 Revisando tu Node.js')) console.log(bgCyan('🚀 Revisando tu Node.js'))
const version = process.version const version = process.version
const majorVersion = parseInt(version.replace('v', '').split('.').shift()) const majorVersion = parseInt(
version.replace('v', '').split('.').shift()
)
if (majorVersion < 16) { if (majorVersion < 16) {
console.error(red(`🔴 Se require Node.js 16 o superior. Actualmente esta ejecutando Node.js ${version}`)) console.error(
red(
`🔴 Se require Node.js 16 o superior. Actualmente esta ejecutando Node.js ${version}`
)
)
console.log(``) console.log(``)
reject('ERROR_NODE') reject('ERROR_NODE')
} }

View File

@@ -2,7 +2,10 @@ const rimraf = require('rimraf')
const { yellow } = require('kleur') const { yellow } = require('kleur')
const { join } = require('path') const { join } = require('path')
const PATH_WW = [join(process.cwd(), '.wwebjs_auth'), join(process.cwd(), 'session.json')] const PATH_WW = [
join(process.cwd(), '.wwebjs_auth'),
join(process.cwd(), 'session.json'),
]
const cleanSession = () => { const cleanSession = () => {
const queue = [] const queue = []

View File

@@ -23,7 +23,11 @@ const JSON_TEMPLATE = {
const PATH_CONFIG = join(process.cwd(), 'config.json') const PATH_CONFIG = join(process.cwd(), 'config.json')
const jsonConfig = () => { const jsonConfig = () => {
return writeFile(PATH_CONFIG, JSON.stringify(JSON_TEMPLATE, null, 2), 'utf-8') return writeFile(
PATH_CONFIG,
JSON.stringify(JSON_TEMPLATE, null, 2),
'utf-8'
)
} }
module.exports = { jsonConfig } module.exports = { jsonConfig }

View File

@@ -20,9 +20,13 @@ const installDeps = (pkgManager, packageList) => {
const installSingle = (pkgInstall) => () => { const installSingle = (pkgInstall) => () => {
new Promise((resolve) => { new Promise((resolve) => {
try { try {
childProcess = spawn(pkgManager, [PKG_OPTION[pkgManager], pkgInstall], { childProcess = spawn(
stdio: 'inherit', pkgManager,
}) [PKG_OPTION[pkgManager], pkgInstall],
{
stdio: 'inherit',
}
)
childProcess.on('error', (e) => { childProcess.on('error', (e) => {
console.error(e) console.error(e)

View File

@@ -30,7 +30,9 @@ const startInteractive = async () => {
await nextSteps() await nextSteps()
} catch (e) { } catch (e) {
console.error(bgRed(`Ups! 🙄 algo no va bien.`)) console.error(bgRed(`Ups! 🙄 algo no va bien.`))
console.error(bgRed(`Revisa los requerimientos minimos en la documentacion`)) console.error(
bgRed(`Revisa los requerimientos minimos en la documentacion`)
)
} }
} }
@@ -80,7 +82,8 @@ const nextSteps = async () => {
const { outDir = '', providerDb = [], providerWs = [] } = response const { outDir = '', providerDb = [], providerWs = [] } = response
const createApp = async (templateName = null) => { const createApp = async (templateName = null) => {
if (!templateName) throw new Error('TEMPLATE_NAME_INVALID: ', templateName) if (!templateName)
throw new Error('TEMPLATE_NAME_INVALID: ', templateName)
const possiblesPath = [ const possiblesPath = [
join(__dirname, '..', '..', 'starters', 'apps', templateName), join(__dirname, '..', '..', 'starters', 'apps', templateName),
@@ -112,7 +115,11 @@ const nextSteps = async () => {
const vendorProvider = async () => { const vendorProvider = async () => {
const [answer] = providerWs const [answer] = providerWs
if (!providerWs.length) { if (!providerWs.length) {
console.log(red(`Debes seleccionar un proveedor de whatsapp. Tecla [Space] para seleccionar`)) console.log(
red(
`Debes seleccionar un proveedor de whatsapp. Tecla [Space] para seleccionar`
)
)
process.exit(1) process.exit(1)
} }
return answer return answer
@@ -125,7 +132,11 @@ const nextSteps = async () => {
const dbProvider = async () => { const dbProvider = async () => {
const [answer] = providerDb const [answer] = providerDb
if (!providerDb.length) { if (!providerDb.length) {
console.log(red(`Debes seleccionar un proveedor de base de datos. Tecla [Space] para seleccionar`)) console.log(
red(
`Debes seleccionar un proveedor de base de datos. Tecla [Space] para seleccionar`
)
)
process.exit(1) process.exit(1)
} }
return answer return answer

View File

@@ -38,8 +38,10 @@ class DialogFlowCXContext extends CoreClass {
* */ * */
} }
if (!this.optionsDX.location.length) throw new Error('LOCATION_NO_ENCONTRADO') if (!this.optionsDX.location.length)
if (!this.optionsDX.agentId.length) throw new Error('AGENTID_NO_ENCONTRADO') throw new Error('LOCATION_NO_ENCONTRADO')
if (!this.optionsDX.agentId.length)
throw new Error('AGENTID_NO_ENCONTRADO')
const rawJson = readFileSync(GOOGLE_ACCOUNT_PATH, 'utf-8') const rawJson = readFileSync(GOOGLE_ACCOUNT_PATH, 'utf-8')
const { project_id, private_key, client_email } = JSON.parse(rawJson) const { project_id, private_key, client_email } = JSON.parse(rawJson)
@@ -84,7 +86,9 @@ class DialogFlowCXContext extends CoreClass {
}, },
} }
const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [null] const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [
null,
]
const listMessages = single.queryResult.responseMessages.map((res) => { const listMessages = single.queryResult.responseMessages.map((res) => {
if (res.message == 'text') { if (res.message == 'text') {
@@ -92,11 +96,17 @@ class DialogFlowCXContext extends CoreClass {
} }
if (res.message == 'payload') { if (res.message == 'payload') {
const { media = null, buttons = [], answer = '' } = res.payload.fields const {
const buttonsArray = buttons?.listValue?.values?.map((btnValue) => { media = null,
const { stringValue } = btnValue.structValue.fields.body buttons = [],
return { body: stringValue } answer = '',
}) } = res.payload.fields
const buttonsArray = buttons?.listValue?.values?.map(
(btnValue) => {
const { stringValue } = btnValue.structValue.fields.body
return { body: stringValue }
}
)
return { return {
answer: answer?.stringValue, answer: answer?.stringValue,
options: { options: {

View File

@@ -5,7 +5,8 @@ const DialogCXFlowClass = require('./dialogflow-cx.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotDialog = async ({ database, provider }, _options) => new DialogCXFlowClass(database, provider, _options) const createBotDialog = async ({ database, provider }, _options) =>
new DialogCXFlowClass(database, provider, _options)
module.exports = { module.exports = {
createBotDialog, createBotDialog,

View File

@@ -65,7 +65,10 @@ class DialogFlowContext extends CoreClass {
* para evitar este problema. * para evitar este problema.
* https://github.com/codigoencasa/bot-whatsapp/pull/140 * https://github.com/codigoencasa/bot-whatsapp/pull/140
*/ */
const session = this.sessionClient.projectAgentSessionPath(this.projectId, from) const session = this.sessionClient.projectAgentSessionPath(
this.projectId,
from
)
const reqDialog = { const reqDialog = {
session, session,
queryInput: { queryInput: {
@@ -76,11 +79,15 @@ class DialogFlowContext extends CoreClass {
}, },
} }
const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [null] const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [
null,
]
const { queryResult } = single const { queryResult } = single
const msgPayload = queryResult?.fulfillmentMessages?.find((a) => a.message === 'payload') const msgPayload = queryResult?.fulfillmentMessages?.find(
(a) => a.message === 'payload'
)
// Revisamos si el dialogFlow tiene multimedia // Revisamos si el dialogFlow tiene multimedia
if (msgPayload && msgPayload?.payload) { if (msgPayload && msgPayload?.payload) {
@@ -104,19 +111,11 @@ class DialogFlowContext extends CoreClass {
return return
} }
/* const ctxFromDX = { const ctxFromDX = {
answer: queryResult?.fulfillmentText, answer: queryResult?.fulfillmentText,
} */ }
const messagesFromCX = queryResult['fulfillmentMessages'] this.sendFlowSimple([ctxFromDX], from)
.map((a) => {
if (a.message === 'text') {
return { answer: a.text.text[0] }
}
})
.filter((e) => e)
this.sendFlowSimple(messagesFromCX, from)
} }
} }

View File

@@ -5,7 +5,8 @@ const DialogFlowClass = require('./dialogflow.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotDialog = async ({ database, provider }) => new DialogFlowClass(database, provider) const createBotDialog = async ({ database, provider }) =>
new DialogFlowClass(database, provider)
module.exports = { module.exports = {
createBotDialog, createBotDialog,

View File

@@ -5,7 +5,8 @@ const MockClass = require('./mock.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotMock = async ({ database, provider }) => new MockClass(database, provider) const createBotMock = async ({ database, provider }) =>
new MockClass(database, provider)
module.exports = { module.exports = {
createBotMock, createBotMock,

View File

@@ -1,79 +1,63 @@
const { join } = require('path') const { join } = require('path')
const { existsSync } = require('fs') const { existsSync, writeFileSync, readFileSync } = require('fs')
const { writeFile, readFile } = require('fs').promises
class JsonFileAdapter { class JsonFileAdapter {
db db
pathFile pathFile
listHistory = [] listHistory = []
options = { filename: 'db.json' }
constructor(options = {}) { constructor() {
this.options = { ...this.options, ...options } this.pathFile = join(process.cwd(), 'db.json')
this.pathFile = join(process.cwd(), this.options.filename)
this.init().then() this.init().then()
} }
/** databaseExists() {
* Revisamos si existe o no el json file return existsSync(this.pathFile)
* @returns }
*/
init = async () => { async init() {
if (existsSync(this.pathFile)) { const dbExists = await this.databaseExists()
return Promise.resolve()
} if (!dbExists) {
try { const data = {
const parseData = JSON.stringify([], null, 2) history: [],
return writeFile(this.pathFile, parseData, 'utf-8') }
} catch (e) { await this.saveData(data)
return Promise.reject(e.message)
} }
} }
validateJson = (raw) => { readDatabase() {
try { const db = readFileSync(this.pathFile)
return JSON.parse(raw) return JSON.parse(db)
} catch (e) {
return {}
}
} }
/** saveData(data) {
* Leer archivo y parsear writeFileSync(this.pathFile, JSON.stringify(data, null, 2))
* @returns
*/
readFileAndParse = async () => {
const data = await readFile(this.pathFile, 'utf-8')
const parseData = this.validateJson(data)
return parseData
} }
/**
* Buscamos el ultimo mensaje por numero
* @param {*} from
* @returns
*/
getPrevByNumber = async (from) => { getPrevByNumber = async (from) => {
const history = await this.readFileAndParse() const { history } = await this.readDatabase()
if (!history.length) { if (!history.length) {
return [] return null
} }
const result = history const result = history.filter((res) => res.from === from).pop()
.slice()
.reverse() return {
.filter((i) => !!i.keyword) ...result,
return result.find((a) => a.from === from) }
} }
/**
* Guardar dato
* @param {*} ctx
*/
save = async (ctx) => { save = async (ctx) => {
this.db = await this.readDatabase()
this.db.history.push(ctx)
await this.saveData(this.db)
this.listHistory.push(ctx) this.listHistory.push(ctx)
const parseData = JSON.stringify(this.listHistory, null, 2) console.log('Guardado en DB...', ctx)
await writeFile(this.pathFile, parseData, 'utf-8')
} }
} }

View File

@@ -10,10 +10,7 @@ class MockDatabase {
constructor() {} constructor() {}
getPrevByNumber = (from) => { getPrevByNumber = (from) => {
const history = this.listHistory const history = this.listHistory.slice().reverse()
.slice()
.reverse()
.filter((i) => !!i.keyword)
return history.find((a) => a.from === from) return history.find((a) => a.from === from)
} }

View File

@@ -24,12 +24,18 @@ class MongoAdapter {
} }
getPrevByNumber = async (from) => { getPrevByNumber = async (from) => {
const result = await this.db.collection('history').find({ from }).sort({ _id: -1 }).limit(1).toArray() const result = await this.db
.collection('history')
.find({ from })
.sort({ _id: -1 })
.limit(1)
.toArray()
return result[0] return result[0]
} }
save = async (ctx) => { save = async (ctx) => {
await this.db.collection('history').insert(ctx) await this.db.collection('history').insert(ctx)
console.log('Guardando DB...', ctx)
this.listHistory.push(ctx) this.listHistory.push(ctx)
} }
} }

View File

@@ -46,8 +46,18 @@ class MyslAdapter {
}) })
save = (ctx) => { save = (ctx) => {
const values = [[ctx.ref, ctx.keyword, ctx.answer, ctx.refSerialize, ctx.from, JSON.stringify(ctx.options)]] const values = [
const sql = 'INSERT INTO history (ref, keyword, answer, refSerialize, phone, options ) values ?' [
ctx.ref,
ctx.keyword,
ctx.answer,
ctx.refSerialize,
ctx.from,
JSON.stringify(ctx.options),
],
]
const sql =
'INSERT INTO history (ref, keyword, answer, refSerialize, phone, options ) values ?'
this.db.query(sql, [values], (err) => { this.db.query(sql, [values], (err) => {
if (err) throw err if (err) throw err
@@ -61,14 +71,14 @@ class MyslAdapter {
const tableName = 'history' const tableName = 'history'
const sql = `CREATE TABLE ${tableName} const sql = `CREATE TABLE ${tableName}
(id INT AUTO_INCREMENT PRIMARY KEY, (id INT AUTO_INCREMENT PRIMARY KEY,
ref varchar(255) NOT NULL, ref varchar(255) NOT NULL,
keyword varchar(255) NOT NULL, keyword varchar(255) NOT NULL,
answer longtext NOT NULL, answer longtext NOT NULL,
refSerialize varchar(255) NOT NULL, refSerialize varchar(255) NOT NULL,
phone varchar(255) NOT NULL, phone varchar(255) NOT NULL,
options longtext NOT NULL) options longtext NOT NULL
CHARACTER SET utf8mb4 COLLATE utf8mb4_General_ci` )`
this.db.query(sql, (err) => { this.db.query(sql, (err) => {
if (err) throw err if (err) throw err

View File

@@ -8,5 +8,6 @@
font-family: IBMPlexMono-Regular; font-family: IBMPlexMono-Regular;
src: url(IBMPlexMono-Regular-subset.woff2) format('woff2'), src: url(IBMPlexMono-Regular-subset.woff2) format('woff2'),
url(IBMPlexMono-Regular-subset.zopfli.woff) format('woff'); url(IBMPlexMono-Regular-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+2C, U+2E, U+41-43, U+46, U+49, U+4B-4F, U+53-55, U+58, U+61-65, U+67-69, U+6C-76; unicode-range: U+20, U+2C, U+2E, U+41-43, U+46, U+49, U+4B-4F, U+53-55, U+58,
U+61-65, U+67-69, U+6C-76;
} }

View File

@@ -8,6 +8,6 @@
font-family: IBMPlexMono-SemiBold; font-family: IBMPlexMono-SemiBold;
src: url(IBMPlexMono-SemiBold-subset.woff2) format('woff2'), src: url(IBMPlexMono-SemiBold-subset.woff2) format('woff2'),
url(IBMPlexMono-SemiBold-subset.zopfli.woff) format('woff'); url(IBMPlexMono-SemiBold-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+24, U+2E, U+30, U+38, U+39, U+41, U+42, U+44, U+47, U+4E, U+4F, U+52-55, U+57, U+59, U+65, unicode-range: U+20, U+24, U+2E, U+30, U+38, U+39, U+41, U+42, U+44, U+47,
U+68, U+6F, U+72, U+74; U+4E, U+4F, U+52-55, U+57, U+59, U+65, U+68, U+6F, U+72, U+74;
} }

View File

@@ -6,8 +6,9 @@
@font-face { @font-face {
font-family: Pally-Variable; font-family: Pally-Variable;
src: url(Pally-Variable-subset.woff2) format('woff2'), url(Pally-Variable-subset.zopfli.woff) format('woff'); src: url(Pally-Variable-subset.woff2) format('woff2'),
unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+39, U+41-43, U+46, U+49-4D, U+53, U+55, U+58, U+61-65, U+67-69, url(Pally-Variable-subset.zopfli.woff) format('woff');
U+6B-77, U+79; unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+39, U+41-43, U+46,
U+49-4D, U+53, U+55, U+58, U+61-65, U+67-69, U+6B-77, U+79;
font-weight: 400 700; font-weight: 400 700;
} }

View File

@@ -8,5 +8,6 @@
font-family: SourceSerifPro-Regular; font-family: SourceSerifPro-Regular;
src: url(SourceSerifPro-Regular-subset.woff2) format('woff2'), src: url(SourceSerifPro-Regular-subset.woff2) format('woff2'),
url(SourceSerifPro-Regular-subset.zopfli.woff) format('woff'); url(SourceSerifPro-Regular-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+2C, U+2E, U+41-44, U+49, U+4A, U+4C, U+53, U+55, U+61-65, U+67-69, U+6B-76, U+79; unicode-range: U+20, U+2C, U+2E, U+41-44, U+49, U+4A, U+4C, U+53, U+55,
U+61-65, U+67-69, U+6B-76, U+79;
} }

View File

@@ -6,8 +6,9 @@
@font-face { @font-face {
font-family: Synonym-Variable; font-family: Synonym-Variable;
src: url(Synonym-Variable-subset.woff2) format('woff2'), url(Synonym-Variable-subset.zopfli.woff) format('woff'); src: url(Synonym-Variable-subset.woff2) format('woff2'),
unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+35, U+41-44, U+46, U+47, U+49, U+4B-4F, U+53-55, U+57-59, U+61, url(Synonym-Variable-subset.zopfli.woff) format('woff');
U+63-65, U+67-69, U+6C-76; unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+35, U+41-44, U+46, U+47,
U+49, U+4B-4F, U+53-55, U+57-59, U+61, U+63-65, U+67-69, U+6C-76;
font-weight: 400 700; font-weight: 400 700;
} }

View File

@@ -6,7 +6,8 @@
@font-face { @font-face {
font-family: TenorSans-Regular; font-family: TenorSans-Regular;
src: url(TenorSans-Regular-subset.woff2) format('woff2'), url(TenorSans-Regular-subset.zopfli.woff) format('woff'); src: url(TenorSans-Regular-subset.woff2) format('woff2'),
unicode-range: U+20, U+24, U+2E, U+30, U+36, U+46, U+49, U+4A, U+53, U+54, U+61, U+63, U+65, U+69, U+6B, U+6E, U+6F, url(TenorSans-Regular-subset.zopfli.woff) format('woff');
U+72-75, U+79; unicode-range: U+20, U+24, U+2E, U+30, U+36, U+46, U+49, U+4A, U+53, U+54,
U+61, U+63, U+65, U+69, U+6B, U+6E, U+6F, U+72-75, U+79;
} }

View File

@@ -56,7 +56,10 @@ export const DigitalOcean = () => (
h-3.1V2h3.2V14.9z M361.9,21.1v1.1c0,4.4,2.6,6.6,6.2,6.6c3.7,0,6.5-2.5,6.5-7.2c0-4.6-2.8-7.1-6.5-7.1 h-3.1V2h3.2V14.9z M361.9,21.1v1.1c0,4.4,2.6,6.6,6.2,6.6c3.7,0,6.5-2.5,6.5-7.2c0-4.6-2.8-7.1-6.5-7.1
C364.5,14.5,361.9,16.8,361.9,21.1z" C364.5,14.5,361.9,16.8,361.9,21.1z"
/> />
<path class="st0" d="M386.3,40.9l4.6-10.7L383.2,12h3.6l5.8,14.5l5.8-14.5h3.6l-12.2,28.9H386.3z" /> <path
class="st0"
d="M386.3,40.9l4.6-10.7L383.2,12h3.6l5.8,14.5l5.8-14.5h3.6l-12.2,28.9H386.3z"
/>
</g> </g>
</g> </g>
<g id="XMLID_2369_"> <g id="XMLID_2369_">
@@ -108,7 +111,14 @@ export const DigitalOcean = () => (
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2 c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2
C225.7,46.5,224.3,45.9,222.6,45.9z" C225.7,46.5,224.3,45.9,222.6,45.9z"
/> />
<rect id="XMLID_276_" x="217.6" y="63" class="st0" width="9.8" height="39.1" /> <rect
id="XMLID_276_"
x="217.6"
y="63"
class="st0"
width="9.8"
height="39.1"
/>
<path <path
id="XMLID_273_" id="XMLID_273_"
class="st0" class="st0"
@@ -118,7 +128,14 @@ export const DigitalOcean = () => (
c3.5-3.6,5.3-8.4,5.3-14.2V63h-9.7V66.3z M260.6,89.4c-1.7,2-3.9,2.9-6.8,2.9c-2.8,0-5-0.9-6.7-2.9c-1.7-1.9-2.5-4.5-2.5-7.7 c3.5-3.6,5.3-8.4,5.3-14.2V63h-9.7V66.3z M260.6,89.4c-1.7,2-3.9,2.9-6.8,2.9c-2.8,0-5-0.9-6.7-2.9c-1.7-1.9-2.5-4.5-2.5-7.7
c0-3.2,0.9-5.8,2.5-7.7c1.7-1.9,3.9-2.9,6.7-2.9c2.8,0,5,1,6.8,2.9c1.7,2,2.6,4.6,2.6,7.7C263.2,84.9,262.3,87.5,260.6,89.4z" c0-3.2,0.9-5.8,2.5-7.7c1.7-1.9,3.9-2.9,6.7-2.9c2.8,0,5,1,6.8,2.9c1.7,2,2.6,4.6,2.6,7.7C263.2,84.9,262.3,87.5,260.6,89.4z"
/> />
<rect id="XMLID_272_" x="281.3" y="63" class="st0" width="9.8" height="39.1" /> <rect
id="XMLID_272_"
x="281.3"
y="63"
class="st0"
width="9.8"
height="39.1"
/>
<path <path
id="XMLID_271_" id="XMLID_271_"
class="st0" class="st0"
@@ -132,7 +149,14 @@ export const DigitalOcean = () => (
d="M312.7,52.5H303V63h-5.6v9h5.6v16.2c0,5.1,1,8.7,3,10.8c2,2.1,5.6,3.2,10.6,3.2 d="M312.7,52.5H303V63h-5.6v9h5.6v16.2c0,5.1,1,8.7,3,10.8c2,2.1,5.6,3.2,10.6,3.2
c1.6,0,3.2-0.1,4.8-0.2l0.4,0v-9l-3.4,0.2c-2.3,0-3.9-0.4-4.7-1.2c-0.8-0.8-1.1-2.6-1.1-5.2V72h9.2v-9h-9.2V52.5z" c1.6,0,3.2-0.1,4.8-0.2l0.4,0v-9l-3.4,0.2c-2.3,0-3.9-0.4-4.7-1.2c-0.8-0.8-1.1-2.6-1.1-5.2V72h9.2v-9h-9.2V52.5z"
/> />
<rect id="XMLID_269_" x="368" y="46.6" class="st0" width="9.8" height="55.5" /> <rect
id="XMLID_269_"
x="368"
y="46.6"
class="st0"
width="9.8"
height="55.5"
/>
<path <path
id="XMLID_268_" id="XMLID_268_"
class="st0" class="st0"

View File

@@ -5,7 +5,14 @@ import logoSrc from '~/assets/images/chatbot-whatsapp.png?width=64&height=64&png
export default component$(() => ( export default component$(() => (
<span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white flex items-center"> <span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white flex items-center">
<img src={logoSrc} class="inline-block mr-1" width={32} height={32} alt="Qwind Logo" loading="lazy" /> <img
src={logoSrc}
class="inline-block mr-1"
width={32}
height={32}
alt="Qwind Logo"
loading="lazy"
/>
Chatbot Chatbot
</span> </span>
)) ))

View File

@@ -1,5 +1,11 @@
export const Netlify = () => ( export const Netlify = () => (
<svg xmlns="http://www.w3.org/2000/svg" width={147} height={40} role="img" fill="currentColor"> <svg
xmlns="http://www.w3.org/2000/svg"
width={147}
height={40}
role="img"
fill="currentColor"
>
<g fill-rule="evenodd"> <g fill-rule="evenodd">
<path d="M53.37 12.978l.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568H60.79V19.676c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zM77.748 32.35c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726.918.914 2.073 1.371 3.464 1.371 1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35V26.76c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431V16.142H86.62v-3.164h3.122V8.355h4.261zm11.137 23.643h-4.262v-27h4.262v27zm9.172 0h-4.262v-19.02h4.262v19.02zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96V16.142h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0-1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117l3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595l4.016 12.903z" /> <path d="M53.37 12.978l.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568H60.79V19.676c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zM77.748 32.35c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726.918.914 2.073 1.371 3.464 1.371 1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35V26.76c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431V16.142H86.62v-3.164h3.122V8.355h4.261zm11.137 23.643h-4.262v-27h4.262v27zm9.172 0h-4.262v-19.02h4.262v19.02zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96V16.142h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0-1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117l3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595l4.016 12.903z" />
<path <path

View File

@@ -13,7 +13,10 @@ export const RouterHead = component$(() => {
<title>{head.title}</title> <title>{head.title}</title>
<link rel="canonical" href={loc.href} /> <link rel="canonical" href={loc.href} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
{head.meta.map((m) => ( {head.meta.map((m) => (

View File

@@ -34,8 +34,14 @@ export const Social = () => {
content="https://campaign.codigoencasa.com" content="https://campaign.codigoencasa.com"
/> />
<meta property="og:site_name" content="campaign.codigoencasa.com" /> */} <meta property="og:site_name" content="campaign.codigoencasa.com" /> */}
<meta property="og:image" content="https://i.imgur.com/0HpzsEm.png"></meta> <meta
<meta property="og:image:secure_url" content="https://i.imgur.com/0HpzsEm.png" /> property="og:image"
content="https://i.imgur.com/0HpzsEm.png"
></meta>
<meta
property="og:image:secure_url"
content="https://i.imgur.com/0HpzsEm.png"
/>
<meta property="og:image:type" content="image/png"></meta> <meta property="og:image:type" content="image/png"></meta>
<meta property="og:image:width" content="1200"></meta> <meta property="og:image:width" content="1200"></meta>
<meta property="og:image:height" content="630"></meta> <meta property="og:image:height" content="630"></meta>
@@ -46,7 +52,10 @@ export const Social = () => {
name="twitter:title" name="twitter:title"
content="💻 Conviértete en un Programador Backend aprendiendo todo de Cloud y Nodejs" content="💻 Conviértete en un Programador Backend aprendiendo todo de Cloud y Nodejs"
/> />
<meta name="twitter:image" content="https://i.imgur.com/0HpzsEm.png" /> <meta
name="twitter:image"
content="https://i.imgur.com/0HpzsEm.png"
/>
</> </>
) )
} }

View File

@@ -27,7 +27,9 @@ export default component$((props: ItemProps) => {
// TODO: // TODO:
document.body.classList.toggle('overflow-hidden') document.body.classList.toggle('overflow-hidden')
document.getElementById('header')?.classList.toggle('h-screen') document.getElementById('header')?.classList.toggle('h-screen')
document.querySelector('#header nav')?.classList.toggle('hidden') document
.querySelector('#header nav')
?.classList.toggle('hidden')
}} }}
> >
<IconMenu class={iconClass} /> <IconMenu class={iconClass} />

View File

@@ -10,13 +10,16 @@ interface ItemProps {
export default component$((props: ItemProps) => { export default component$((props: ItemProps) => {
const { iconClass } = props const { iconClass } = props
const store = useStore({ const store = useStore({
theme: (typeof window !== 'undefined' && window?.localStorage?.theme) || undefined, theme:
(typeof window !== 'undefined' && window?.localStorage?.theme) ||
undefined,
}) })
useClientEffect$(() => { useClientEffect$(() => {
store.theme = store.theme =
window.localStorage.theme === 'dark' || window.localStorage.theme === 'dark' ||
(!('theme' in window.localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) (!('theme' in window.localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
? 'dark' ? 'dark'
: 'light' : 'light'
}) })
@@ -39,7 +42,11 @@ export default component$((props: ItemProps) => {
} }
}} }}
> >
{store.theme == 'dark' ? <IconMoon class={iconClass} /> : <IconSun class={iconClass} />} {store.theme == 'dark' ? (
<IconMoon class={iconClass} />
) : (
<IconSun class={iconClass} />
)}
</button> </button>
) )
}) })

View File

@@ -7,7 +7,9 @@ export const IconArrowDownRight = (props: ItemProps) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class={`icon icon-tabler icon-tabler-arrow-down-right ${className || 'w-5 h-5'}`} class={`icon icon-tabler icon-tabler-arrow-down-right ${
className || 'w-5 h-5'
}`}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -8,7 +8,9 @@ export const IconMenu = (props: ItemProps) => {
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink"
class={`icon icon-tabler icon-tabler-menu ${className || 'w-5 h-5'}`} class={`icon icon-tabler icon-tabler-menu ${
className || 'w-5 h-5'
}`}
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >

View File

@@ -7,7 +7,9 @@ export const IconMoon = (props: ItemProps) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class={`icon icon-tabler icon-tabler-moon ${className || 'w-5 h-5'}`} class={`icon icon-tabler icon-tabler-moon ${
className || 'w-5 h-5'
}`}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -23,7 +23,9 @@ export default component$(
<div class="pt-2 space-y-4 justify-center flex"> <div class="pt-2 space-y-4 justify-center flex">
<figcaption class="text-sm"> <figcaption class="text-sm">
<div class={'font-semibold truncate'}>{props.user.login}</div> <div class={'font-semibold truncate'}>
{props.user.login}
</div>
</figcaption> </figcaption>
</div> </div>
</figure> </figure>

View File

@@ -32,7 +32,8 @@ export default component$((props: { users: User[] }) => {
Super estrellas Super estrellas
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
Todo es posible gracias a el mayor recursos de todos, el recurso humano. Tu tambien puedes{' '} Todo es posible gracias a el mayor recursos de todos, el
recurso humano. Tu tambien puedes{' '}
<a class={'font-semibold'} href="/docs/contributing"> <a class={'font-semibold'} href="/docs/contributing">
formar parte formar parte
</a> </a>

View File

@@ -15,8 +15,8 @@ export default component$(() => {
📄 Editar esta pagina 📄 Editar esta pagina
</a> </a>
<p class={'text-xs'}> <p class={'text-xs'}>
Forma parte de esta comunidad mejorando la documentación siente libre de poder agregar o editar Forma parte de esta comunidad mejorando la documentación
lo que quieras siente libre de poder agregar o editar lo que quieras
</p> </p>
</li> </li>
</ul> </ul>

View File

@@ -56,9 +56,13 @@ export default component$(() => {
<IconArrowDownRight class="w-7 h-7 text-primary-600 inline-block" /> <IconArrowDownRight class="w-7 h-7 text-primary-600 inline-block" />
{question} {question}
</div> </div>
{answer.split('\n\n').map((paragraph) => ( {answer
<p class="text-gray-700 dark:text-gray-400 mb-2">{paragraph}</p> .split('\n\n')
))} .map((paragraph) => (
<p class="text-gray-700 dark:text-gray-400 mb-2">
{paragraph}
</p>
))}
</div> </div>
))} ))}
</div> </div>

View File

@@ -50,11 +50,13 @@ export default component$(() => {
Caracteristicas Caracteristicas
</p> </p>
<h2 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading"> <h2 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading">
Nuestras principales <span class="whitespace-nowrap">funciones</span> Nuestras principales{' '}
<span class="whitespace-nowrap">funciones</span>
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
El secreto es mantener los procesos repetitivos en procesos automatizados simples, por eso te El secreto es mantener los procesos repetitivos en
mostramos en que destacamos. procesos automatizados simples, por eso te mostramos en
que destacamos.
</p> </p>
</div> </div>
<div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0"> <div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0">
@@ -68,8 +70,12 @@ export default component$(() => {
</div> </div>
</div> </div>
<div> <div>
<h3 class="mb-3 text-xl font-bold">{title}</h3> <h3 class="mb-3 text-xl font-bold">
<p class="text-gray-600 dark:text-slate-400">{description}</p> {title}
</h3>
<p class="text-gray-600 dark:text-slate-400">
{description}
</p>
</div> </div>
</div> </div>
))} ))}

View File

@@ -9,24 +9,36 @@ import { src as placeholder } from '~/assets/images/chatbot-whatsapp.png?width=4
export default component$(() => { export default component$(() => {
return ( return (
<section class={` from-white via-purple-50 to-sky-100 dark:bg-none mt-[-95px]`}> <section
class={` from-white via-purple-50 to-sky-100 dark:bg-none mt-[-95px]`}
>
<div class="max-w-6xl mx-auto px-4 sm:px-6 md:flex md:h-screen 2xl:h-auto pt-[72px]"> <div class="max-w-6xl mx-auto px-4 sm:px-6 md:flex md:h-screen 2xl:h-auto pt-[72px]">
<div class="py-12 md:py-12 lg:py-16 block md:flex text-center md:text-left"> <div class="py-12 md:py-12 lg:py-16 block md:flex text-center md:text-left">
<div class="pb-12 md:pb-0 md:py-0 max-w-5xl mx-auto md:pr-16 flex items-center basis-[56%]"> <div class="pb-12 md:pb-0 md:py-0 max-w-5xl mx-auto md:pr-16 flex items-center basis-[56%]">
<div> <div>
<h1 class="text-5xl md:text-[3.48rem] font-bold leading-tighter tracking-tighter mb-4 font-heading px-4 md:px-0"> <h1 class="text-5xl md:text-[3.48rem] font-bold leading-tighter tracking-tighter mb-4 font-heading px-4 md:px-0">
Crear chatbot <span class="sm:whitespace-nowrap text-[#25b637]">WhatsApp</span> Crear chatbot{' '}
<br class="hidden lg:block" /> <span class="lg:inline">en minutos</span> <span class="sm:whitespace-nowrap text-[#25b637]">
WhatsApp
</span>
<br class="hidden lg:block" />{' '}
<span class="lg:inline">en minutos</span>
</h1> </h1>
<div class="max-w-3xl mx-auto"> <div class="max-w-3xl mx-auto">
<p class="text-xl text-gray-600 mb-8 dark:text-slate-400"> <p class="text-xl text-gray-600 mb-8 dark:text-slate-400">
<span class="font-semibold ">Con esta libreria, </span> <span class="font-semibold ">
Con esta libreria,{' '}
</span>
<span class="font-semibold "> <span class="font-semibold ">
puedes configurar respuestas automatizadas para preguntas frecuentes puedes configurar respuestas
automatizadas para preguntas frecuentes
</span>{' '} </span>{' '}
, recibir y responder mensajes de manera automatizada, y hacer un seguimiento de las , recibir y responder mensajes de manera
interacciones con los clientes. Además, nuestro Chatbot se integra fácilmente con automatizada, y hacer un seguimiento de las
otros sistemas y herramientas que ya esté utilizando en su negocio. interacciones con los clientes. Además,
nuestro Chatbot se integra fácilmente con
otros sistemas y herramientas que ya esté
utilizando en su negocio.
</p> </p>
<div class="max-w-xs sm:max-w-md flex flex-nowrap flex-col sm:flex-col gap-4 m-auto md:m-0 justify-center md:justify-start"> <div class="max-w-xs sm:max-w-md flex flex-nowrap flex-col sm:flex-col gap-4 m-auto md:m-0 justify-center md:justify-start">
@@ -36,7 +48,10 @@ export default component$(() => {
</code> </code>
</div> </div>
<div class="flex w-full sm:w-auto gap-3"> <div class="flex w-full sm:w-auto gap-3">
<a href="/docs" class="btn bg-gray-50 dark:bg-transparent"> <a
href="/docs"
class="btn bg-gray-50 dark:bg-transparent"
>
Ver documentación Ver documentación
</a> </a>
<a <a

View File

@@ -32,9 +32,14 @@ export default component$((props: { users: User[] }) => {
Miembros Miembros
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
Conviértete en un miembro destacado y forma parte del proyecto y disfruta de manera adelantada Conviértete en un miembro destacado y forma parte del
de las actualizaciones{' '} proyecto y disfruta de manera adelantada de las
<a class={'font-semibold'} target={'_blank'} href="https://opencollective.com/bot-whatsapp"> actualizaciones{' '}
<a
class={'font-semibold'}
target={'_blank'}
href="https://opencollective.com/bot-whatsapp"
>
Únete Únete
</a> </a>
</p> </p>

View File

@@ -5,45 +5,55 @@ import { DocumentationCtx } from '~/contexts'
/** /**
* options = [] array con la lista de opciones de la documentacion * options = [] array con la lista de opciones de la documentacion
*/ */
export default component$(({ options = [] }: { options: DocumentationCtx[] }) => { export default component$(
return ( ({ options = [] }: { options: DocumentationCtx[] }) => {
<div> return (
{options.map((item, i) => ( <div>
<UlCompoent key={i} title={item.title} list={item.list} /> {options.map((item, i) => (
))} <UlCompoent key={i} title={item.title} list={item.list} />
</div> ))}
) </div>
}) )
}
)
export const UlCompoent = component$((porps: { title: string; list: { link: string; name: string }[] }) => { export const UlCompoent = component$(
return ( (porps: { title: string; list: { link: string; name: string }[] }) => {
<ul> return (
<li class="mt-2 lg:mt-2"> <ul>
<h5 class="mb-8 lg:mb-3 font-semibold text-slate-900 dark:text-slate-200">{porps.title}</h5> <li class="mt-2 lg:mt-2">
<LiComponent list={porps.list} /> <h5 class="mb-8 lg:mb-3 font-semibold text-slate-900 dark:text-slate-200">
</li> {porps.title}
</ul> </h5>
) <LiComponent list={porps.list} />
})
export const LiComponent = component$((porps: { list: { link: string; name: string }[] }) => {
const location = useLocation()
const currentPage = location.pathname
return (
<ul class="space-y-6 lg:space-y-2 border-l border-slate-100 dark:border-slate-800">
{porps.list.map((opt) => (
<li>
<Link
class={[
currentPage === `${opt.link}/` ? 'font-semibold' : '',
'block border-l pl-4 -ml-px border-transparent hover:border-slate-400 dark:hover:border-slate-500 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300 ',
]}
href={opt.link}
>
{opt.name}
</Link>
</li> </li>
))} </ul>
</ul> )
) }
}) )
export const LiComponent = component$(
(porps: { list: { link: string; name: string }[] }) => {
const location = useLocation()
const currentPage = location.pathname
return (
<ul class="space-y-6 lg:space-y-2 border-l border-slate-100 dark:border-slate-800">
{porps.list.map((opt) => (
<li>
<Link
class={[
currentPage === `${opt.link}/`
? 'font-semibold'
: '',
'block border-l pl-4 -ml-px border-transparent hover:border-slate-400 dark:hover:border-slate-500 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300 ',
]}
href={opt.link}
>
{opt.name}
</Link>
</li>
))}
</ul>
)
}
)

View File

@@ -1,63 +1,74 @@
import { component$ } from '@builder.io/qwik' import { component$ } from '@builder.io/qwik'
export const ButtonLink = component$((props: { name: string; link: string; direction: 'left' | 'right' }) => { export const ButtonLink = component$(
const ArrowRight = () => ( (props: { name: string; link: string; direction: 'left' | 'right' }) => {
<svg const ArrowRight = () => (
viewBox="0 0 3 6" <svg
class="ml-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300" viewBox="0 0 3 6"
> class="ml-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300"
<path >
d="M0 0L3 3L0 6" <path
fill="none" d="M0 0L3 3L0 6"
stroke="currentColor" fill="none"
stroke-width="2" stroke="currentColor"
stroke-linecap="round" stroke-width="2"
stroke-linejoin="round" stroke-linecap="round"
></path> stroke-linejoin="round"
</svg> ></path>
) </svg>
)
const ArrowLeft = () => ( const ArrowLeft = () => (
<svg <svg
viewBox="0 0 3 6" viewBox="0 0 3 6"
class="mr-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300" class="mr-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300"
> >
<path <path
d="M3 0L0 3L3 6" d="M3 0L0 3L3 6"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
></path> ></path>
</svg> </svg>
) )
return ( return (
<a class="group flex items-center hover:text-slate-900 dark:hover:text-white" href={props.link}> <a
{props.direction === 'left' ? ( class="group flex items-center hover:text-slate-900 dark:hover:text-white"
<> href={props.link}
<ArrowLeft /> >
{props.name} {props.direction === 'left' ? (
</> <>
) : ( <ArrowLeft />
<> {props.name}
{props.name} </>
<ArrowRight /> ) : (
</> <>
)} {props.name}
</a> <ArrowRight />
) </>
}) )}
</a>
)
}
)
export default component$((props: { pages: ({ name: string; link: string } | null)[] }) => { export default component$(
const { pages } = props (props: { pages: ({ name: string; link: string } | null)[] }) => {
return ( const { pages } = props
<div class="text-sm leading-6 mt-12"> return (
<div class="mb-10 text-slate-700 font-semibold flex justify-between items-center dark:text-slate-200"> <div class="text-sm leading-6 mt-12">
{pages[0] ? <ButtonLink direction="left" {...pages[0]} /> : null} <div class="mb-10 text-slate-700 font-semibold flex justify-between items-center dark:text-slate-200">
{pages[1] ? <ButtonLink direction="right" {...pages[1]} /> : null} {pages[0] ? (
<ButtonLink direction="left" {...pages[0]} />
) : null}
{pages[1] ? (
<ButtonLink direction="right" {...pages[1]} />
) : null}
</div>
</div> </div>
</div> )
) }
}) )

View File

@@ -5,25 +5,33 @@ export default component$(() => {
<div class="px-4 py-8 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl"> <div class="px-4 py-8 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl">
<div class="grid grid-cols-2 row-gap-8 md:grid-cols-4"> <div class="grid grid-cols-2 row-gap-8 md:grid-cols-4">
<div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0"> <div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">132K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">
132K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Downloads Downloads
</p> </p>
</div> </div>
<div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0"> <div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">24.8K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">
24.8K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Stars Stars
</p> </p>
</div> </div>
<div class="text-center md:border-r dark:md:border-slate-500 font-heading"> <div class="text-center md:border-r dark:md:border-slate-500 font-heading">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1]">10.3K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1]">
10.3K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Forks Forks
</p> </p>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">48.4K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">
48.4K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Users Users
</p> </p>

View File

@@ -13,4 +13,5 @@ export interface User {
avatar_url: string avatar_url: string
} }
export const GlobalStore = createContext<DocumentationCtx[]>('documentation-site') export const GlobalStore =
createContext<DocumentationCtx[]>('documentation-site')

View File

@@ -1,5 +1,14 @@
import { component$, useContextProvider, useStore, useStyles$ } from '@builder.io/qwik' import {
import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city' component$,
useContextProvider,
useStore,
useStyles$,
} from '@builder.io/qwik'
import {
QwikCityProvider,
RouterOutlet,
ServiceWorkerRegister,
} from '@builder.io/qwik-city'
import { RouterHead } from '~/components/core/RouterHead' import { RouterHead } from '~/components/core/RouterHead'
import { DarkThemeLauncher } from '~/components/core/DarkThemeLauncher' import { DarkThemeLauncher } from '~/components/core/DarkThemeLauncher'
@@ -69,7 +78,10 @@ export default component$(() => {
<QwikCityProvider> <QwikCityProvider>
<head> <head>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<RouterHead /> <RouterHead />

View File

@@ -4,10 +4,11 @@ import Navigation from '../../../components/widgets/Navigation'
# DataBase (Base de datos) # DataBase (Base de datos)
<Alert> <Alert>
⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar
**user, host, password** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **user, host, password** para esos
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta casos te recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo

View File

@@ -22,7 +22,12 @@ Tan sencillo como decir **palabra/s clave** y **mensaje a responder**
Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const flowPrincipal = addKeyword(['hola', 'alo']) const flowPrincipal = addKeyword(['hola', 'alo'])
.addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?'])
@@ -34,10 +39,11 @@ const flowPrincipal = addKeyword(['hola', 'alo'])
## Provider (Proveedor) ## Provider (Proveedor)
<Alert> <Alert>
⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar
**token, api, etc.** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **token, api, etc.** para esos casos te
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio
@@ -65,10 +71,11 @@ Los proveedores disponibles hasta el momento son los siguientes:
## DataBase (Base de datos) ## DataBase (Base de datos)
<Alert> <Alert>
⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar
**user, host, password** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **user, host, password** para esos
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta casos te recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo

View File

@@ -5,7 +5,12 @@ import Navigation from '../../../components/widgets/Navigation'
Si copias y pegas este codigo y tu entorno de trabajo cumple con todos los requesitos te debe funcionar abajo explico muy por encima Si copias y pegas este codigo y tu entorno de trabajo cumple con todos los requesitos te debe funcionar abajo explico muy por encima
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp') const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock') const MockAdapter = require('@bot-whatsapp/database/mock')
@@ -37,7 +42,12 @@ main()
En esta parte solo estamos declaramos las dependencias que vamos a utilizar. Si quieres saber a fondo cada una de las funciones te recomiendo pasarte por la seccion de **[conceptos](/docs/concepts)** En esta parte solo estamos declaramos las dependencias que vamos a utilizar. Si quieres saber a fondo cada una de las funciones te recomiendo pasarte por la seccion de **[conceptos](/docs/concepts)**
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp') const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock') const MockAdapter = require('@bot-whatsapp/database/mock')

View File

@@ -9,7 +9,12 @@ Tan sencillo como decir **palabra/s clave** y **mensaje a responder**
Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const flowPrincipal = addKeyword(['hola', 'alo']) const flowPrincipal = addKeyword(['hola', 'alo'])
.addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?'])
@@ -76,21 +81,37 @@ Esta funcion se utliza para responder un mensaje despues del `addKeyword()`
```js ```js
const { addKeyword } = require('@bot-whatsapp/bot') const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Este mensaje se enviara 1 segundo despues', { const flowString = addKeyword('hola').addAnswer(
delay: 1000, 'Este mensaje se enviara 1 segundo despues',
}) {
delay: 1000,
}
)
const flowString = addKeyword('hola').addAnswer('Este mensaje envia una imagen', { const flowString = addKeyword('hola').addAnswer(
media: 'https://i.imgur.com/0HpzsEm.png', 'Este mensaje envia una imagen',
}) {
media: 'https://i.imgur.com/0HpzsEm.png',
}
)
const flowString = addKeyword('hola').addAnswer('Este mensaje envia tres botones', { const flowString = addKeyword('hola').addAnswer(
buttons: [{ body: 'Boton 1' }, { body: 'Boton 2' }, { body: 'Boton 3' }], 'Este mensaje envia tres botones',
}) {
buttons: [
{ body: 'Boton 1' },
{ body: 'Boton 2' },
{ body: 'Boton 3' },
],
}
)
const flowString = addKeyword('hola').addAnswer('Este mensaje espera una respueta del usuario', { const flowString = addKeyword('hola').addAnswer(
capture: true, 'Este mensaje espera una respueta del usuario',
}) {
capture: true,
}
)
``` ```
--- ---
@@ -102,9 +123,13 @@ Este argumento se utiliza para obtener el contexto de la conversación
```js ```js
const { addKeyword } = require('@bot-whatsapp/bot') const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Indica cual es tu email', null, (ctx) => { const flowString = addKeyword('hola').addAnswer(
console.log('👉 Informacion del contexto: ', ctx) 'Indica cual es tu email',
}) null,
(ctx) => {
console.log('👉 Informacion del contexto: ', ctx)
}
)
``` ```
--- ---
@@ -118,9 +143,13 @@ se repetira el mensaje `Indica cual es tu email`
```js ```js
const { addKeyword } = require('@bot-whatsapp/bot') const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Indica cual es tu email', null, (ctx, { fallBack }) => { const flowString = addKeyword('hola').addAnswer(
if (!ctx.body.includes('@')) return fallBack() 'Indica cual es tu email',
}) null,
(ctx, { fallBack }) => {
if (!ctx.body.includes('@')) return fallBack()
}
)
``` ```
--- ---
@@ -152,65 +181,57 @@ const flowString = addKeyword('hola')
## endFlow() ## endFlow()
Esta funcion se utliza para **finalizar un flujo con dos** o más addAnswer. Un ejemplo de uso sería registrar 3 datos de un usuario en 3 preguntas distinas y Esta funcion se utliza para finalizar un flujo con dos o más addAnswer. Un ejemplo de uso sería registrar 3 datos de un usuario en 3 preguntas distinas y
que el usuario **pueda finalizar por él mismo el flujo**. que el usuario pueda finalizar por él mismo el flujo.
Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones. Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones.
```js ```js
let nombre; const flowFormulario = addKeyword(['Hola'])
let apellidos;
let telefono;
const flowFormulario = addKeyword(['Hola','⬅️ Volver al Inicio'])
.addAnswer( .addAnswer(
['Hola!','Para enviar el formulario necesito unos datos...' ,'Escriba su *Nombre*'], ['Hola!', 'Escriba su *Nombre* para generar su solicitud'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx, { flowDynamic, endFlow }) => { async (ctx, { flowDynamic, endFlow }) => {
if (ctx.body == '❌ Cancelar solicitud') if (ctx.body == '❌ Cancelar solicitud') {
return endFlow({body: '❌ Su solicitud ha sido cancelada ❌', // Aquí terminamos el flow si la condicion se comple await flowDynamic([
buttons:[{body:'⬅️ Volver al Inicio' }] // Y además, añadimos un botón por si necesitas derivarlo a otro flow {
body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
buttons: [{ body: '⬅️ Volver al Inicio' }],
}) },
nombre = ctx.body ])
return flowDynamic(`Encantado *${nombre}*, continuamos...`) return endFlow()
}
} }
) )
.addAnswer( .addAnswer(
['También necesito tus dos apellidos'], ['También necesito tus dos apellidos'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx, { flowDynamic, endFlow }) => { async (ctx, { flowDynamic, endFlow }) => {
if (ctx.body == '❌ Cancelar solicitud') if (ctx.body == '❌ Cancelar solicitud') {
return endFlow({body: '❌ Su solicitud ha sido cancelada ❌', await flowDynamic([
buttons:[{body:'⬅️ Volver al Inicio' }] {
body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
buttons: [{ body: '⬅️ Volver al Inicio' }],
}) },
apellidos = ctx.body ])
return flowDynamic(`Perfecto *${nombre}*, por último...`) return endFlow()
}
} }
) )
.addAnswer( .addAnswer(
['Dejeme su número de teléfono y le llamaré lo antes posible.'], ['Dejeme su número de teléfono y le llamaré lo antes posible.'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx, { flowDynamic, endFlow }) => { async (ctx, { flowDynamic, endFlow }) => {
if (ctx.body == '❌ Cancelar solicitud') if (ctx.body == '❌ Cancelar solicitud') {
return endFlow({body: '❌ Su solicitud ha sido cancelada ❌', await flowDynamic([
buttons:[{body:'⬅️ Volver al Inicio' }] {
}) body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
buttons: [{ body: '⬅️ Volver al Inicio' }],
},
telefono = ctx.body ])
await delay(2000) return endFlow()
return flowDynamic(`Estupendo *${nombre}*! te dejo el resumen de tu formulario }
\n- Nombre y apellidos: *${nombre} ${apellidos}*
\n- Telefono: *${telefono}*`)
} }
) )
``` ```
--- ---

View File

@@ -4,9 +4,10 @@ import Navigation from '../../components/widgets/Navigation'
# Introducción # Introducción
<Alert> <Alert>
**Atención** estás leyendo la documentación de la **versión v2** de esta librería, si vienes de la versión anterior **Atención** estás leyendo la documentación de la **versión v2** de esta
te recomendamos pasarte por la sección de **[migración](/docs/migration/)** para que puedas disfrutar de las nuevas librería, si vienes de la versión anterior te recomendamos pasarte por la
características. sección de **[migración](/docs/migration/)** para que puedas disfrutar de
las nuevas características.
</Alert> </Alert>
## ¿Qué es esto? ## ¿Qué es esto?
@@ -33,7 +34,10 @@ npm create bot-whatsapp@latest
muted muted
playsinline playsinline
> >
<source src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm" type="video/webm" /> <source
src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm"
type="video/webm"
/>
</video> </video>
</div> </div>

View File

@@ -28,7 +28,10 @@ El **CLI** te hace una revisión previa, de versión de Node y sistema operativo
muted muted
playsinline playsinline
> >
<source src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm" type="video/webm" /> <source
src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm"
type="video/webm"
/>
</video> </video>
</div> </div>
@@ -51,8 +54,10 @@ Cada plantilla tiene sus dependencias necesarias basadas en tu previa selección
``` ```
<Alert> <Alert>
📄 Si deseas cambiar tu **proveedor o tu motor** de base de datos no es necesario volver ejecutar el CLI (lo puedes 📄 Si deseas cambiar tu **proveedor o tu motor** de base de datos no es
hacer sin problema) aunque tambien basta con solo modificar un par de lineas. [Ver explicación](/docs/essential) necesario volver ejecutar el CLI (lo puedes hacer sin problema) aunque
tambien basta con solo modificar un par de lineas. [Ver
explicación](/docs/essential)
</Alert> </Alert>
--- ---

View File

@@ -27,7 +27,11 @@ export default component$(() => {
<NavBar options={store} /> <NavBar options={store} />
</div> </div>
<div class={'lg:pl-[14.5rem] lg:pr-[14.5rem]'}> <div class={'lg:pl-[14.5rem] lg:pr-[14.5rem]'}>
<div class={'slot max-w-3xl mx-auto relative z-20 p-5 xl:max-w-none'}> <div
class={
'slot max-w-3xl mx-auto relative z-20 p-5 xl:max-w-none'
}
>
<Slot /> <Slot />
</div> </div>
</div> </div>
@@ -50,7 +54,8 @@ export const head: DocumentHead = {
meta: [ meta: [
{ {
name: 'description', name: 'description',
content: 'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource', content:
'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource',
}, },
], ],
} }

View File

@@ -49,7 +49,11 @@ En la **_versión (legacy)_** se implementas los flujos de esta manera, en dos a
"title": "¿Que te interesa ver?", "title": "¿Que te interesa ver?",
"message": "Abajo unos botons", "message": "Abajo unos botons",
"footer": "", "footer": "",
"buttons": [{ "body": "Telefonos" }, { "body": "Computadoras" }, { "body": "Otros" }] "buttons": [
{ "body": "Telefonos" },
{ "body": "Computadoras" },
{ "body": "Otros" }
]
} }
}, },
"catalogo": { "catalogo": {
@@ -66,28 +70,51 @@ En esta versión es mucho más sencillo, abajo encontrarás un ejemplo del mismo
```js ```js
//app.js //app.js
const { createBot, createProvider, createFlow, addKeyword, addChild } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
addChild,
} = require('@bot-whatsapp/bot')
const BaileysProvider = require('@bot-whatsapp/provider/baileys') //Provider const BaileysProvider = require('@bot-whatsapp/provider/baileys') //Provider
const MockAdapter = require('@bot-whatsapp/database/mock') //Base de datos const MockAdapter = require('@bot-whatsapp/database/mock') //Base de datos
/** /**
* Declarando flujos principales. * Declarando flujos principales.
*/ */
const flowHola = addKeyword(['hola', 'ola', 'alo']).addAnswer('Bienvenido a tu tienda online!') const flowHola = addKeyword(['hola', 'ola', 'alo']).addAnswer(
'Bienvenido a tu tienda online!'
)
const flowAdios = addKeyword(['adios', 'bye']).addAnswer('Que te vaya bien!!').addAnswer('Hasta luego!') const flowAdios = addKeyword(['adios', 'bye'])
.addAnswer('Que te vaya bien!!')
.addAnswer('Hasta luego!')
const flowProductos = addKeyword(['productos', 'info']).addAnswer('Te envio una imagen', { const flowProductos = addKeyword(['productos', 'info']).addAnswer(
buttons: [{ body: 'Telefonos' }, { body: 'Computadoras' }, { body: 'Otros' }], 'Te envio una imagen',
}) {
buttons: [
{ body: 'Telefonos' },
{ body: 'Computadoras' },
{ body: 'Otros' },
],
}
)
const flowCatalogo = addKeyword(['imagen', 'foto']).addAnswer('Te envio una imagen', { const flowCatalogo = addKeyword(['imagen', 'foto']).addAnswer(
media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif', 'Te envio una imagen',
}) { media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif' }
)
const main = async () => { const main = async () => {
const adapterDB = new MockAdapter() const adapterDB = new MockAdapter()
const adapterFlow = createFlow([flowHola, flowAdios, flowProductos, flowCatalogo]) //Se crean los flujos. const adapterFlow = createFlow([
flowHola,
flowAdios,
flowProductos,
flowCatalogo,
]) //Se crean los flujos.
const adapterProvider = createProvider(BaileysProvider) const adapterProvider = createProvider(BaileysProvider)
createBot({ createBot({
flow: adapterFlow, flow: adapterFlow,

View File

@@ -49,15 +49,17 @@ Qwik is a new kind of web framework that can deliver instant loading web applica
<div class="card"> <div class="card">
<h3>Instant-on</h3> <h3>Instant-on</h3>
<p> <p>
Unlike other frameworks, Qwik is resumable which means Qwik applications require 0 hydration. This allows Unlike other frameworks, Qwik is resumable which means Qwik
Qwik apps to have instant-on interactivity, regardless of size or complexity applications require 0 hydration. This allows Qwik apps to have
instant-on interactivity, regardless of size or complexity
</p> </p>
</div> </div>
<div class="card"> <div class="card">
<h3>Optimized for speed</h3> <h3>Optimized for speed</h3>
<p> <p>
Qwik has unprecedented performance, offering sub-second full page loads even on mobile devices. Qwik Qwik has unprecedented performance, offering sub-second full page
achieves this by delivering pure HTML, and incrementally loading JS only as-needed. loads even on mobile devices. Qwik achieves this by delivering pure
HTML, and incrementally loading JS only as-needed.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -4,10 +4,11 @@ import Navigation from '../../../components/widgets/Navigation'
# Proveedores # Proveedores
<Alert> <Alert>
⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar
**token, api, etc.** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **token, api, etc.** para esos casos te
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio

View File

@@ -59,7 +59,10 @@ En el **archivo principal** del bot donde estás implementando la función del a
- **jwtToken:** Lo puedes encontrar en la pagina anterior - **jwtToken:** Lo puedes encontrar en la pagina anterior
- **verifyToken:** Puedes escribir lo que quieras es como una palabra clave - **verifyToken:** Puedes escribir lo que quieras es como una palabra clave
<Alert>En el ejemplo de abajo puedes ver como una sugerencia de como puede ser utilizando variables de entorno</Alert> <Alert>
En el ejemplo de abajo puedes ver como una sugerencia de como puede ser
utilizando variables de entorno
</Alert>
```js ```js
const main = async () => { const main = async () => {

View File

@@ -5,7 +5,10 @@ import Navigation from '../../../../components/widgets/Navigation'
Twilio es una plataforma de desarrollo que permite a los desarrolladores construir aplicaciones de comunicación en la nube y sistemas web. Las API de comunicaciones de Twilio permiten a las empresas proporcionar la experiencia de comunicación adecuada para sus clientes dentro de la web y las aplicaciones móviles. Al usar las API de Twilio, los desarrolladores pueden agregar rápidamente esta funcionalidad a una aplicación, como mensajes de voz, videollamadas, mensajes de texto y más. Twilio es una plataforma de desarrollo que permite a los desarrolladores construir aplicaciones de comunicación en la nube y sistemas web. Las API de comunicaciones de Twilio permiten a las empresas proporcionar la experiencia de comunicación adecuada para sus clientes dentro de la web y las aplicaciones móviles. Al usar las API de Twilio, los desarrolladores pueden agregar rápidamente esta funcionalidad a una aplicación, como mensajes de voz, videollamadas, mensajes de texto y más.
<Alert>Twilio te proporciona una cuenta **Sandbox** para que puedas probar gratuitamente el servicio</Alert> <Alert>
Twilio te proporciona una cuenta **Sandbox** para que puedas probar
gratuitamente el servicio
</Alert>
### Requerimientos ### Requerimientos
@@ -63,7 +66,10 @@ En el **archivo principal** del bot donde estás implementando la función del a
- **ACC_VENDOR:** Es el numero de whatsapp (si ya tienes el plan de pago de Twilio usa el numero que compraste), si aun estas en modo - **ACC_VENDOR:** Es el numero de whatsapp (si ya tienes el plan de pago de Twilio usa el numero que compraste), si aun estas en modo
sandbox utliza el numero proporcionado en el paso numero 2 sandbox utliza el numero proporcionado en el paso numero 2
<Alert>En el ejemplo de abajo puedes ver como una sugerencia de como puede ser utilizando variables de entorno</Alert> <Alert>
En el ejemplo de abajo puedes ver como una sugerencia de como puede ser
utilizando variables de entorno
</Alert>
```js ```js
const main = async () => { const main = async () => {

View File

@@ -13,7 +13,8 @@ import { GITHUB_TOKEN } from './docs/constant'
// import { SearchModal } from '~/components/widgets/SearchModal' // import { SearchModal } from '~/components/widgets/SearchModal'
export const onGet: RequestHandlerNetlify = async ({ platform }) => { export const onGet: RequestHandlerNetlify = async ({ platform }) => {
const CHECK_GITHUB_TOKEN = (platform as any)?.['GITHUB_TOKEN'] ?? GITHUB_TOKEN const CHECK_GITHUB_TOKEN =
(platform as any)?.['GITHUB_TOKEN'] ?? GITHUB_TOKEN
const dataGithub = await fetchGithub(CHECK_GITHUB_TOKEN) const dataGithub = await fetchGithub(CHECK_GITHUB_TOKEN)
const dataOpenCollective = await fetchOpenCollective() const dataOpenCollective = await fetchOpenCollective()
return { return {
@@ -51,7 +52,8 @@ export const head: DocumentHead = {
meta: [ meta: [
{ {
name: 'description', name: 'description',
content: 'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource', content:
'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource',
}, },
], ],
} }

View File

@@ -3,14 +3,17 @@
* @returns * @returns
*/ */
export const fetchGithub = async (token: string) => { export const fetchGithub = async (token: string) => {
const data = await fetch(`https://api.github.com/repos/codigoencasa/bot-whatsapp/contributors`, { const data = await fetch(
method: 'GET', `https://api.github.com/repos/codigoencasa/bot-whatsapp/contributors`,
headers: { {
Accept: 'application/vnd.github+json', method: 'GET',
'X-GitHub-Api-Version': '2022-11-28', headers: {
Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json',
}, 'X-GitHub-Api-Version': '2022-11-28',
}) Authorization: `Bearer ${token}`,
},
}
)
const listUsers = await data.json() const listUsers = await data.json()
return listUsers.map((u: any) => ({ return listUsers.map((u: any) => ({
...u, ...u,

View File

@@ -3,9 +3,12 @@
* @returns * @returns
*/ */
export const fetchOpenCollective = async () => { export const fetchOpenCollective = async () => {
const data = await fetch(`https://opencollective.com/bot-whatsapp/members/users.json?limit=22&offset=0`, { const data = await fetch(
method: 'GET', `https://opencollective.com/bot-whatsapp/members/users.json?limit=22&offset=0`,
}) {
method: 'GET',
}
)
const listUsers = await data.json() const listUsers = await data.json()
return listUsers.map((u: any) => ({ return listUsers.map((u: any) => ({
html_url: u.profile, html_url: u.profile,

View File

@@ -76,7 +76,9 @@ module.exports = {
a: { a: {
fontWeight: theme('fontWeight.semibold'), fontWeight: theme('fontWeight.semibold'),
textDecoration: 'none', textDecoration: 'none',
borderBottom: `1px solid ${theme('colors.sky.300')}`, borderBottom: `1px solid ${theme(
'colors.sky.300'
)}`,
}, },
'a:hover': { 'a:hover': {
borderBottomWidth: '2px', borderBottomWidth: '2px',

View File

@@ -3,7 +3,11 @@
"scope": "javascriptreact,typescriptreact", "scope": "javascriptreact,typescriptreact",
"prefix": "q:onGet", "prefix": "q:onGet",
"description": "onGet function for a route index", "description": "onGet function for a route index",
"body": ["export const onGet: RequestHandler = (request) => {", " $0", "};"] "body": [
"export const onGet: RequestHandler = (request) => {",
" $0",
"};"
]
}, },
"onGet (typed)": { "onGet (typed)": {
"scope": "javascriptreact,typescriptreact", "scope": "javascriptreact,typescriptreact",

Some files were not shown because too many files have changed in this diff Show More