Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
node_crisp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
web
node_crisp
Commits
be3049ab
Commit
be3049ab
authored
Nov 25, 2021
by
Sebastián Long
Browse files
Options
Browse Files
Download
Plain Diff
Finish CHATBOTS-15
parents
4a1e1763
a70c76b9
Changes
9
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
455 additions
and
208 deletions
+455
-208
.gitignore
.gitignore
+2
-0
README.md
README.md
+43
-0
crisp.js
crisp.js
+12
-12
is.js
endpoints/is.js
+12
-18
sendinblue.js
endpoints/sendinblue.js
+11
-6
index.js
index.js
+27
-10
index.js
logger/index.js
+24
-0
package-lock.json
package-lock.json
+321
-160
package.json
package.json
+3
-2
No files found.
.gitignore
View file @
be3049ab
...
...
@@ -13,3 +13,5 @@ jspm_packages/
/.idea/modules.xml
/.idea/vcs.xml
/.idea/workspace.xml
/.idea
/logs
\ No newline at end of file
README.md
View file @
be3049ab
# Crisp Chatbot
Este proyecto es un servidor escrito en NodeJS que brinda la interacción de usuario para los chatbots
de reserva de turnos. Se basa en un servicio de eventos, los cuales recibe de Crisp cada vez que el usuario escribe
un mensaje. Cuando esto sucede, interceptamos el mensaje y manejamos los datos del usuario, sobre todo
para saber en qué paso se encuentra dentro de la conversación, y los datos que fue ingresando.
Actualmente el chatbot se despliega en dos sitios web: reserva de turnos bajo demanda (IPS), y
reserva de turnos programados.
El chatbot de reserva de turnos programados se carga en el sitio de Integrando Pacientes.
El script que lo carga (configurado en el proyecto de dicha aplicación web), postea
además los datos del usuario en la API de Crisp, tales como su nombre, apellido y teléfono.
Dichos datos son recuperados por este servidor la primera vez que el usuario interacciona
con el chatbot de reserva de turnos programados, y los envía a Sendinblue. En Sendinblue
tenemos una lista de correos donde enviamos tutoriales de cómo usar el chatbot.
## Configuración
Los siguientes parametros deben ser configurados en
[
default.json
](
./config/default.json
)
*
credenciales.crisp.identifier: identifier para la integración con Crisp.
*
credenciales.crisp.key: key para la integración con Crisp.
*
credenciales.sendinblue.apikey: API Key de Sendinblue.
*
credenciales.isApiToken: API Token de Integrando Salud.
*
isBaseUrl: URL base de Integrando Salud (https://www.integrandosalud.com/src-v2/public/api/v1)
*
sendingbluBaseUrl: URL base de Sendingblue (https://api.sendinblue.com/v3)
*
sendingblueListId: ID de la lista de correo de Sendinblue (54)
*
crispWebsiteIdIpsTurnosBajoDemanda: Website ID para el website de turnos bajo demanda.
*
crispWebsiteIdIsTurnosProgramados Website ID para el website de turnos programados.
*
idAgendaIPS: ID de la agenda del IPS.
*
idDominioIPS: ID del dominio del IPS.
## Ejecución
Para correr el proyecto, una vez seteada la configuración, correr el siguiente comando:
```npm run start```
## Logs
Los logs se almacenan en el directorio
[
/logs
](
./logs
)
.
*
El archivo
[
error.log
](
./logs/error.log
)
muestra solamente errores.
*
El archivo
[
info.log
](
./logs/error.log
)
muestra tanto errores como logs de información.
\ No newline at end of file
crisp.js
View file @
be3049ab
var
Crisp
=
require
(
"node-
crisp-api"
);
var
CrispClient
=
new
Crisp
();
const
Crisp
=
require
(
"
crisp-api"
);
const
CrispClient
=
new
Crisp
();
const
config
=
require
(
'config'
);
var
identifier
=
config
.
get
(
'credenciales.crisp.identifier'
);
var
key
=
config
.
get
(
'credenciales.crisp.key'
);
le
t
SIGUIENTE_PASO
=
'siguiente_paso'
;
const
identifier
=
config
.
get
(
'credenciales.crisp.identifier'
);
const
key
=
config
.
get
(
'credenciales.crisp.key'
);
cons
t
SIGUIENTE_PASO
=
'siguiente_paso'
;
const
DELAY_MS
=
1000
;
let
localData
=
{};
CrispClient
.
authenticate
(
identifier
,
key
);
CrispClient
.
authenticate
Tier
(
"plugin"
,
identifier
,
key
);
async
function
sendTextMessage
(
website_id
,
session_id
,
message
)
{
await
composeMessage
(
website_id
,
session_id
,
DELAY_MS
);
await
CrispClient
.
website
Conversations
.
sendMessage
(
await
CrispClient
.
website
.
sendMessageInConversation
(
website_id
,
session_id
,
{
type
:
"text"
,
...
...
@@ -25,14 +25,14 @@ async function sendTextMessage(website_id, session_id, message) {
}
async
function
composeMessage
(
website_id
,
session_id
,
ms
){
await
CrispClient
.
website
Conversations
.
composeMessage
(
await
CrispClient
.
website
.
composeMessageInConversation
(
website_id
,
session_id
,
{
"type"
:
"start"
,
"from"
:
"operator"
}
);
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
ms
));
await
CrispClient
.
website
Conversations
.
composeMessage
(
await
CrispClient
.
website
.
composeMessageInConversation
(
website_id
,
session_id
,
{
"type"
:
"stop"
,
...
...
@@ -46,7 +46,7 @@ async function showWritingIcon(website_id, session_id){
async
function
sendPickerMessage
(
website_id
,
session_id
,
id
,
title
,
choices
)
{
await
composeMessage
(
website_id
,
session_id
,
DELAY_MS
);
await
CrispClient
.
website
Conversations
.
sendMessage
(
await
CrispClient
.
website
.
sendMessageInConversation
(
website_id
,
session_id
,
{
type
:
"picker"
,
...
...
@@ -84,7 +84,7 @@ async function getUserData(website_id, session_id) {
}
async
function
updateCrispData
(
website_id
,
session_id
,
correo
,
tipo_documento
,
numero_documento
,
fecha_nacimiento
,
sexo
)
{
await
CrispClient
.
website
Conversations
.
updateMeta
(
website_id
,
session_id
,
{
await
CrispClient
.
website
.
updateConversationMetas
(
website_id
,
session_id
,
{
email
:
correo
,
data
:
{
tipo_documento
:
tipo_documento
,
...
...
@@ -96,7 +96,7 @@ async function updateCrispData(website_id, session_id, correo, tipo_documento, n
}
async
function
getCrispData
(
website_id
,
session_id
)
{
return
await
CrispClient
.
website
Conversations
.
getMeta
(
website_id
,
session_id
);
return
await
CrispClient
.
website
.
getConversationMetas
(
website_id
,
session_id
);
}
async
function
updateUserData
(
website_id
,
session_id
,
data
)
{
...
...
endpoints/is.js
View file @
be3049ab
const
config
=
require
(
'config'
);
const
axios
=
require
(
'axios'
);
let
_
=
require
(
'lodash'
);
const
logger
=
require
(
'../logger/index.js'
);
var
AUTH_TOKEN
=
config
.
get
(
'credenciales.isApiToken'
);
const
AUTH_TOKEN
=
config
.
get
(
'credenciales.isApiToken'
);
let
isClient
=
axios
.
create
({
baseURL
:
config
.
get
(
'isBaseUrl'
),
...
...
@@ -22,14 +23,6 @@ function sortProvinciasByName(provincias){
return
provinciasByName
;
}
function
getProvinciaById
(
provincias
,
id
)
{
for
(
let
i
=
0
;
i
<
provincias
.
length
;
i
++
)
{
if
(
provincias
[
i
].
id_provincia
===
id
)
{
return
provincias
[
i
];
}
}
}
module
.
exports
=
{
getProvincias
:
async
function
()
{
...
...
@@ -39,7 +32,7 @@ module.exports =
return
sortProvinciasByName
(
provincias
);
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
});
},
getMedicosByName
:
async
function
(
nombreMedico
,
idProvincia
)
{
...
...
@@ -48,7 +41,7 @@ module.exports =
return
response
.
data
.
data
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
});
},
getEspecialidadesMedico
:
async
function
(
idPersonaInstitucional
,
idProvincia
)
{
...
...
@@ -57,7 +50,7 @@ module.exports =
return
response
.
data
.
data
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
});
},
getTurnosDisponibles
:
async
function
(
idAgenda
,
idPersonaInstitucional
,
fechaUsa
)
{
...
...
@@ -66,7 +59,7 @@ module.exports =
return
response
.
data
.
turnos
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
});
},
getSiguientesTurnosDisponibles
:
async
function
(
idAgenda
,
idPersonaInstitucional
)
{
...
...
@@ -75,7 +68,7 @@ module.exports =
return
response
.
data
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
});
},
existeUsuario
:
async
function
(
tipo_documento
,
numero_documento
,
fecha_nacimiento
,
sexo
){
...
...
@@ -84,7 +77,7 @@ module.exports =
return
response
.
data
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
});
},
crearTurno
:
async
function
(
idPersonaFederada
,
idHorario
,
fechaHora
)
{
...
...
@@ -97,7 +90,8 @@ module.exports =
return
response
.
data
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
return
error
;
});
},
federarUsuario
:
async
function
(
tipoDocumento
,
numeroDocumento
,
fechaNacimiento
,
sexo
,
mail
,
telefonoCelular
,
idDominio
)
{
...
...
@@ -119,7 +113,7 @@ module.exports =
return
response
.
data
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
return
{
success
:
false
};
});
},
...
...
@@ -129,7 +123,7 @@ module.exports =
return
response
.
data
;
})
.
catch
(
function
(
error
)
{
console
.
log
(
error
);
logger
.
error
(
error
.
data
);
return
{
success
:
false
};
});
}
...
...
endpoints/sendinblue.js
View file @
be3049ab
const
config
=
require
(
'config'
);
const
axios
=
require
(
'axios'
);
const
logger
=
require
(
'../logger/index.js'
);
const
API_KEY
=
config
.
get
(
'credenciales.sendinblue.apiKey'
);
const
COUNTRY_CODE_ARG
=
"+54"
;
...
...
@@ -13,12 +14,12 @@ let sendinblueClient = axios.create({
async
function
saveContactToList
(
email
,
nombres
,
apellidos
,
sms
,
listId
){
sms
=
parseSms
(
sms
);
createOrUpdateContact
(
email
,
nombres
,
apellidos
,
sms
)
.
then
(
function
(
data
){
console
.
log
(
`Usuario
${
email
}
creado/actualizado`
);
.
then
(
function
(){
logger
.
info
(
`Usuario
${
email
}
creado/actualizado`
);
addContactToList
(
email
,
listId
);
})
.
catch
(
function
(
error
)
{
console
.
log
(
`Error al crear/actualizar usuario en sendinblue:
${
JSON
.
stringify
(
error
.
response
.
data
)}
`
);
logger
.
error
(
`Error al crear/actualizar usuario en sendinblue:
${
JSON
.
stringify
(
error
.
response
.
data
)}
`
);
});
}
...
...
@@ -40,7 +41,6 @@ async function createOrUpdateContact(email, nombres, apellidos, sms){
})
.
catch
(
async
function
(
error
)
{
if
(
error
.
response
.
data
.
code
===
ERROR_CODE_DUPLICATE_PARAMETER
){
console
.
log
(
error
.
response
.
data
)
return
await
sendinblueClient
.
put
(
encodeURI
(
`/contacts/
${
email
}
`
),
{
"attributes"
:
{
"NOMBRE"
:
nombres
,
...
...
@@ -52,6 +52,10 @@ async function createOrUpdateContact(email, nombres, apellidos, sms){
return
response
.
data
;
})
}
else
{
logger
.
error
(
error
.
data
);
return
error
;
}
});
}
...
...
@@ -60,11 +64,12 @@ async function addContactToList(email, listId){
"emails"
:
[
email
]
})
.
then
(
async
function
(
response
)
{
console
.
log
(
`Usuario
${
email
}
agregado a lista
${
listId
}
`
)
logger
.
info
(
`Usuario
${
email
}
agregado a lista
${
listId
}
`
)
return
response
.
data
;
})
.
catch
(
async
function
(
error
)
{
console
.
log
(
`Error al agregar al usuario
${
email
}
a la lista
${
listId
}
:
${
JSON
.
stringify
(
error
.
response
.
data
)}
`
);
logger
.
error
(
`Error al agregar al usuario
${
email
}
a la lista
${
listId
}
:`
);
logger
.
error
(
error
.
response
.
data
);
});
}
...
...
index.js
View file @
be3049ab
const
axios
=
require
(
'axios'
);
const
config
=
require
(
'config'
);
const
handlerTurnosProgramados
=
require
(
'./handlers/is_turnos_programados.js'
);
const
handlerTurnosBajoDemanda
=
require
(
'./handlers/ips_turnos_bajo_demanda'
);
const
crisp
=
require
(
'./crisp.js'
);
const
utils
=
require
(
'./utils.js'
);
const
pasosTurnosProgramados
=
require
(
'./handlers/pasos_is_turnos_programados.js'
);
const
pasosIpsTurnosBajoDemanda
=
require
(
'./handlers/pasos_ips_turnos_bajo_demanda'
);
const
is
=
require
(
'./endpoints/is.js'
);
const
sendinblue
=
require
(
'./endpoints/sendinblue.js'
);
const
logger
=
require
(
'./logger/index.js'
);
const
WEBSITE_ID_IPS_TURNOS_BAJO_DEMANDA
=
config
.
get
(
'crispWebsiteIdIpsTurnosBajoDemanda'
);
const
WEBSITE_ID_TURNOS_PROGRAMADOS
=
config
.
get
(
'crispWebsiteIdIsTurnosProgramados'
);
const
REINICIAR
=
'REINICIAR'
;
crisp
.
CrispClient
.
userProfile
.
get
().
then
(
function
(
myProfile
)
{
console
.
log
(
`El chatbot esta escuchando eventos (profile name:
${
myProfile
.
first_name
}
)`
);
});
crisp
.
CrispClient
.
plugin
.
getConnectAccount
()
.
then
(
account
=>
{
console
.
log
(
'El chatbot esta escuchando eventos'
);
logger
.
info
(
'Servicio iniciado'
);
logger
.
info
(
"Plugin ID:"
);
logger
.
info
(
account
.
plugin_id
);
})
.
catch
(
error
=>
{
console
.
error
(
'Error al inicializar el chatbot: '
,
error
);
logger
.
error
(
'Error al inicializar el chatbot:'
);
logger
.
error
(
error
);
});
crisp
.
CrispClient
.
on
(
"message:updated"
,
async
function
(
message
)
{
logger
.
info
(
'Opcion del usuario:'
);
logger
.
info
(
message
.
content
);
darSiguientePaso
(
message
,
message
.
website_id
,
message
.
session_id
);
})
crisp
.
CrispClient
.
on
(
"message:send"
,
async
function
(
message
)
{
if
(
message
.
content
.
toUpperCase
()
===
'REINICIAR'
){
if
(
message
.
content
.
toUpperCase
()
===
REINICIAR
){
await
crisp
.
updateUserData
(
message
.
website_id
,
message
.
session_id
,
{});
}
let
siguientePaso
=
await
crisp
.
getSiguientePaso
(
message
.
website_id
,
message
.
session_id
);
logger
.
info
(
`Usuario tipea:
${
message
.
content
}
`
)
if
(
siguientePaso
==
null
)
{
if
(
message
.
website_id
===
WEBSITE_ID_TURNOS_PROGRAMADOS
){
enviarUserAListaSendingblue
(
message
.
website_id
,
message
.
session_id
,
config
.
get
(
'sendingblueListId'
));
...
...
@@ -36,7 +47,12 @@ crisp.CrispClient.on("message:send", async function (message) {
}
darSiguientePaso
(
message
,
message
.
website_id
,
message
.
session_id
)
.
catch
((
ignorar
)
=>
{})
//Ignorar error cuando el usuario escribe un mensaje en lugar de usar el picker, o viceversa
.
catch
((
error
)
=>
{
logger
.
error
(
`Error al procesar el mensaje del usuario:`
);
logger
.
error
(
error
);
crisp
.
sendTextMessage
(
message
.
website_id
,
message
.
session_id
,
`Ocurrió un error al procesar su respuesta. Por favor, verifique los datos ingresados y reintente. Si quiere comenzar desde el inicio, escriba
${
REINICIAR
}
.`
)
})
});
async
function
darSiguientePaso
(
message
,
website_id
,
session_id
)
{
...
...
@@ -59,9 +75,10 @@ async function enviarUserAListaSendingblue(website_id, session_id, listId){
let
email
=
crispUsrData
[
'email'
];
let
phone
=
crispUsrData
[
'phone'
];
console
.
log
(
nombres
,
apellidos
,
email
,
phone
);
logger
.
info
(
nombres
,
apellidos
,
email
,
phone
);
sendinblue
.
saveContactToList
(
email
,
nombres
,
apellidos
,
phone
,
listId
);
}
catch
(
e
)
{
console
.
log
(
`Hubo un error al intentar enviar el usuario a la lista de sendinblue. Error:
${
e
}
`
);
logger
.
error
(
`Hubo un error al intentar enviar el usuario a la lista de sendinblue. Error:`
);
logger
.
error
(
e
);
}
}
\ No newline at end of file
logger/index.js
0 → 100644
View file @
be3049ab
const
winston
=
require
(
'winston'
);
const
{
combine
,
timestamp
,
printf
}
=
winston
.
format
;
const
logFormat
=
printf
(({
level
,
message
,
timestamp
})
=>
{
return
`
${
timestamp
}
${
level
}
:
${
JSON
.
stringify
(
message
)}
`
;
});
const
logger
=
winston
.
createLogger
({
level
:
'info'
,
format
:
combine
(
timestamp
(),
logFormat
),
defaultMeta
:
{
service
:
'chatbot'
},
transports
:
[
// Guardar logs con nivel `error` y por debajo en `error.log`
new
winston
.
transports
.
File
({
filename
:
'logs/error.log'
,
level
:
'error'
,
handleRejections
:
true
}),
// Guardar logs con nivel `info` y por debajo en `info.log`
new
winston
.
transports
.
File
({
filename
:
'logs/info.log'
}),
],
});
module
.
exports
=
logger
;
\ No newline at end of file
package-lock.json
View file @
be3049ab
This diff is collapsed.
Click to expand it.
package.json
View file @
be3049ab
...
...
@@ -11,10 +11,11 @@
"author"
:
""
,
"license"
:
"
ISC
"
,
"dependencies"
:
{
"
axios
"
:
"
^0.21.
1
"
,
"
axios
"
:
"
^0.21.
4
"
,
"
config
"
:
"
^3.3.6
"
,
"
crisp-api
"
:
"
5.0.1
"
,
"
lodash
"
:
"
^4.17.21
"
,
"
node-crisp-api
"
:
"
^1.12.2
"
"
winston
"
:
"
^3.3.3
"
},
"devDependencies"
:
{
"
nodemon
"
:
"
^2.0.7
"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment