How to build a crafty chatbot for WordPress and outperform any AI

No comments

09.04.2026|

No comments

Tiempo de lectura Lectura: 8 min, 43 s
Número de palabras Palabras: 1613
Número de visitas Visitas: 33
Icono de traducción
JRMora assistant
Hello! I'm the blog's virtual assistant. Your questions help us improve our answers. How can I help you today?

It is possible to create a support chat for a very specific use that is more responsive than an AI. It will depend on how much you want to build a knowledge base that covers all the possible answers you need to provide.

This chat is NOT AI. It is a proprietary fuzzy logic (Fuzzy Search) chatbot in JavaScript (very basic at the moment). Nothing is stored outside this server, in fact nothing is stored on the server either if you don't want it to. Everything happens in your browser

Obviously, you will not have a conversational chat or an infinite knowledge base on any subject because what you want is for it to be useful for a visitor who is looking for anything related to your website, your activity, your services and/or your person. The answers will be limited to what you want the chat to answer, which is not little.

I leave the code with some basic comments about how it works in case you want to try it.

HTML. Paint the chat box where you add this html block.

<div id="jrmora-bot-container">
    <div id="jrmora-bot-header">
        <div id="jrmora-bot-status"></div>
        <strong>Título del asistente</strong>
    </div>

    <div id="jrmora-chat-window">
        <div class="msg-bot">
            ¡Hola! Soy el asistente virtual del blog. Tus preguntas mejorarán las respuestas ¿En qué puedo ayudarte hoy?
        </div>
    </div>

    <div id="jrmora-bot-input-area">
        <input type="text" id="jrmora-user-input" placeholder="Escribe tu duda aquí...">
        <button onclick="jrmoraSendMessage()">Enviar</button>
    </div>
</div>

Javascipt. You can add it as a snippet or in the functions.php of your child theme. This is where you define the behaviour. In the code there are two example questions. The "tags" value collects the words or phrases that will return the answer contained in the "res" value.

From this structure you can add as many answers as you need:

{
    "tags": ["word1","word2 word3"],
    "res": "Answer here"
  },

JAVASCRIPT



const jrmoraConocimiento = [
         {
    "tags": ["licencia","licencia creative commons","que licencia","tipo de licencia","cc by-nc-nd","puedo usar","permiso uso","derechos de autor","propiedad intelectual","usar tus dibujos","compartir viñetas"],
    "res": "Para todas las imágenes firmadas por JRMora se aplica la licencia Atribución-NoComercial-SinDerivadas 4.0 Internacional (CC BY-NC-ND 4.0). Info: https://jrmora.com/licencia/"
  },
  {
    "tags": ["sobre el blog","autor","informacion del sitio","datos del autor","historia del blog","acerca de"],
    "res": "Aquí puedes consultar más datos sobre el autor y el blog: https://jrmora.com/about/"
  },
] 
// --- UTILIDADES ---

// Quita tildes y pasa a minúsculas
function normalizar(texto) {
    return texto.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}

// Busca URLs en el texto y las convierte en enlaces HTML
function enlazarTexto(texto) {
    const urlRegex = /(https?:\/\/[^\s]+)/g;
    return texto.replace(urlRegex, function(url) {
        const limpiaUrl = url.replace(/[.,]$/, "");
        return `<a href="${limpiaUrl}" target="_blank" style="color: #d32f2f; font-weight: bold; text-decoration: underline;">${limpiaUrl}</a>`;
    });
}

// --- LÓGICA PRINCIPAL ---

function jrmoraSendMessage() {
    const input = document.getElementById('jrmora-user-input');
    const windowChat = document.getElementById('jrmora-chat-window');
    const userText = input.value.trim();

    if (userText === "") return;

    // Muestra lo que el usuario ha escrito
    windowChat.innerHTML += `<div class="msg-user">${userText}</div>`;
    
    // Limpieza y Focus inmediato para usabilidad móvil
    input.value = "";
    input.focus(); 
    
    windowChat.scrollTop = windowChat.scrollHeight;

    const query = normalizar(userText);
    const mensajeError = "Lo siento. No soy una IA y no tengo una respuesta guardada para eso. Puedes consultar por palabras sueltas, buscar temas específicos con la lupa del blog o contactar directamente aquí: https://tuweb/contacto/";
    let respuestaFinal = mensajeError;

    // Recorre la base de conocimiento (jrmoraConocimiento debe estar definido antes)
    for (const item of jrmoraConocimiento) {
        const coincidencia = item.tags.some(tag => query.includes(normalizar(tag)));
        if (coincidencia) {
            respuestaFinal = item.res;
            break;
        }
    }

    // --- GUARDADO DE LOGS (PHP) ---
    fetch('/chat-logger.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            pregunta: userText,
            respuesta: (respuestaFinal === mensajeError) ? "DESCONOCIDA" : "OK"
        })
    }).catch(err => console.log('Error en registro:', err));

    // Muestra la respuesta con un pequeño retraso
    const respuestaConLinks = enlazarTexto(respuestaFinal);

    setTimeout(() => {
        windowChat.innerHTML += `<div class="msg-bot">${respuestaConLinks}</div>`;
        windowChat.scrollTop = windowChat.scrollHeight;
    }, 500);
}

// --- EVENTOS ---

document.addEventListener('DOMContentLoaded', () => {
    const inputElement = document.getElementById('jrmora-user-input');
    if(inputElement) {
        inputElement.addEventListener('keydown', function (e) {
            if (e.key === 'Enter') {
                e.preventDefault(); 
                jrmoraSendMessage();
            }
        });
    }
});

In // --- SAVING LOGS (PHP) --- there is a file called chat-logger.php (optional use) if you want the chat to save in plain text a list of the queries - This is the code of chat-logger.php that you must add to the root where you have installed your blog.

chat-logger.php

<?php
// Solo aceptamos peticiones POST para mayor seguridad
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $json = file_get_contents('php://input');
    $data = json_decode($json, true);

    if ($data && isset($data['pregunta'])) {
        $fecha = date('Y-m-d H:i:s');
        $pregunta = strip_tags($data['pregunta']);
        $respuesta = strip_tags($data['respuesta']);
        
        // El nombre del archivo tiene un sufijo aleatorio por seguridad
        $archivoLog = 'registro_consultas_chat_45637.txt';
        
        $linea = "[$fecha] USER: $pregunta | BOT: $respuesta" . PHP_EOL;
        
        file_put_contents($archivoLog, $linea, FILE_APPEND);
    }
}

You can change the name of the file that will be generated from:"query_log_chat_45637.txt" and call it whatever you want.

Finally, to give this file an extra layer of security, add this code to the end of your .htaccess:

 <Files "registro_consultas_chat_45637.txt">
      Order Deny,Allow
      Deny from all
  </Files>
</IfModule>

This will create a .txt log in the root of your site so we avoid creating a table in the database. In the log the questions asked are saved and only the date and time of the questions are noted, where"OK" means that the bot answered something found in the words of the knowledge base and"UNKNOWN" means that it did not find a valid answer.

This is still a local way to "train" your bot by checking what people are asking if you think it is relevant. It allows you to get out of your own speech bubble and find out how users actually ask questions. Once you've detected those patterns and enriched your search strings, the log will have done its job and you can dispense with it so the system can fly solo, light and clean.

CSS- Well that's it, finally the style for the chat box. It's the one I use here. You can tweak it to your liking.

#jrmora-bot-container {
    max-width: 500px;
    width: 95%; 
    margin: 20px auto;
    border: 1px solid #e0e0e0;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 10px 25px rgba(0,0,0,0.1);
    background: #fff;
    display: flex;
    flex-direction: column;
    /* Hereda la fuente de tu blog */
}

#jrmora-bot-header {
    background: #222;
    color: #fff;
    padding: 15px;
    display: flex;
    align-items: center;
    gap: 10px;
}

#jrmora-bot-status {
    width: 10px;
    height: 10px;
    background: #00ff00;
    border-radius: 50%;
}

#jrmora-chat-window {
    height: 400px;
    max-height: 65vh; 
    overflow-y: auto;
    padding: 15px;
    background: #fdfdfd;
    display: flex;
    flex-direction: column;
    gap: 12px;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
}

.msg-bot {
    background: #f1f1f1;
    padding: 12px 16px;
    border-radius: 15px 15px 15px 0;
    max-width: 85%;
    align-self: flex-start;
    line-height: 1.4;
    color: #333;
    /* En PC se verá al tamaño de fuente de tus posts */
    font-size: 0.95em;
}

.msg-user {
    background: #222;
    color: #fff;
    padding: 12px 16px;
    border-radius: 15px 15px 0 15px;
    max-width: 85%;
    align-self: flex-end;
    line-height: 1.4;
    font-size: 0.95em;
}

#jrmora-bot-input-area {
    padding: 12px;
    border-top: 1px solid #eee;
    display: flex;
    gap: 8px;
    background: #fff;
    align-items: center;
}

#jrmora-user-input {
    flex: 1;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 8px;
    outline: none;
    min-width: 0;
    /* Hereda fuente del sistema */
}

#jrmora-bot-input-area button {
    background: #222;
    color: #fff;
    border: none;
    padding: 12px 18px;
    border-radius: 8px;
    cursor: pointer;
    font-weight: bold;
    flex-shrink: 0;
}

#jrmora-bot-input-area button:active {
    background: #d32f2f;
}

/* --- AJUSTES ESPECÍFICOS PARA MÓVILES --- */
@media screen and (max-width: 768px) {
    /* Mensajes un poco más compactos para que no ocupen media pantalla */
    .msg-bot, .msg-user {
        font-size: 15px;
    }
    
    /* PARCHE CRÍTICO: 16px solo en el input evita el zoom de Firefox/Chrome/Safari */
    #jrmora-user-input {
        font-size: 16px !important;
    }
}

The advantages of creating such a support chat are many.

You have full control of the answers from the start because you write them yourself.

No distractions. There are no whimsical algorithms that freak out, interpreting things in their own way and inventing answers or creating answers on topics unrelated to what you are interested in answering.

There is always room for improvement. The more you work on the possible questions and their answers, the better you will perform.

It is secure. Total privacy. The data does not leave your server. You can even have it running without saving anything at all if you wish.

Performance. Virtually zero impact on loading speed offering immediate responses in just a few milliseconds, no external APIs or third party requests. No impact.

Zero cost for the use of AI tokens. You will only spend your time expanding and improving the possible answers to the questions.

This chat, for the moment, is basic, but functional. In parallel I am testing a more complex version for the green button you will see on the posts, with the text"Quick summary" (also without AI), which reads answers from three sources: Custom fields, post content and the chatbot's knowledge base. This button is still in a very primitive stage and although I'm still testing it to create a small, improved internal infrastructure I'm not sure if I'll develop it further because to get really fine-tuned responses it needs a lot more rethinking and work.

What I don't rule out in the future is turning this support chatbot into a plugin to make entering tags and responses simpler, faster and more intuitive.

Related articles

Leave a comment

Anything to say?

Este blog se aloja en LucusHost

LucusHost, el mejor hosting