diff --git a/c-busqueda/handler.cr b/c-busqueda/handler.cr
new file mode 100644
index 0000000..ee6c82b
--- /dev/null
+++ b/c-busqueda/handler.cr
@@ -0,0 +1,216 @@
+require "http/request"
+require "http/headers"
+require "ishi/html"
+require "json"
+
+class Handler
+ def format_buffer(buffer, canvas_name)
+ # Process the gnuplot output so it works in the page
+ #
+ # buffer is the Ishi output
+ # name is a string to replace for gnuplot_canvas so
+ # we can have multiple charts in a page
+
+ html = buffer.to_s.split("\n")
+ html = html[html.index("")]
+ html = html.join("\n") + %(
+
+
+
+
+
+ |
+
+
+
+ )
+ # This ID needs to be unique in case
+ # we have 2 charts in the same page
+ html.gsub("gnuplot_canvas", canvas_name)
+ end
+
+ def query(sql)
+ # Runs a SQL query against the Rqlite database.
+ #
+ # Returns an array of values (which need to be casted)
+ # Or nil if there are no results
+
+ params = URI::Params.encode({"q": sql})
+ response = HTTP::Client.get URI.new(
+ "http",
+ "10.61.0.1",
+ 4001,
+ "/db/query",
+ params)
+
+ # This API only has a values key when there are actual results
+ results = JSON.parse(response.body)["results"][0].as_h
+ if results.has_key?("values")
+ return results["values"].as_a
+ end
+ # No result, return nil
+ nil
+ end
+
+ def normalize_name(s)
+ # Remove diacritics, turn lowercase
+ normalized = s.unicode_normalize(:nfkd).chars
+ normalized.reject! { |c|
+ !c.ascii_letter?
+ }.join("").downcase
+ end
+
+ def feminidad(nombre)
+ nombre = nombre.to_s.upcase
+ sql1 = %(
+ SELECT COALESCE(frecuencia,0)
+ FROM mujeres WHERE nombre='#{nombre}'
+
+ )
+ sql2 = %(
+ SELECT COALESCE(frecuencia,0)
+ FROM hombres WHERE nombre='#{nombre}'
+ )
+
+ # Yes this database is upper case
+ mujeres = query(sql1)
+ mujeres = mujeres.nil? ? 0 : mujeres[0][0].as_i
+ hombres = query(sql2)
+ hombres = hombres.nil? ? 0 : hombres[0][0].as_i
+ if hombres == mujeres == 0
+ return 0.5
+ end
+ return mujeres / (hombres + mujeres)
+ end
+
+ def split_por_genero(nombres)
+ femeninos = Array(Array(String | Int32)).new
+ masculinos = Array(Array(String | Int32)).new
+ nombres.map { |n|
+ fem = feminidad(n[1])
+ # El overlap en 0.5 es intencional!
+ if fem >= 0.5
+ femeninos << n
+ end
+ if fem <= 0.5
+ masculinos << n
+ end
+ }
+ {
+ "f": femeninos,
+ "m": masculinos,
+ }
+ end
+
+ def run(request : HTTP::Request)
+ unless (body = request.body).nil?
+ query = Hash(String, String).from_json(body)
+ else
+ query = {"p": "", "g": "", a: ""}
+ end
+
+ p! query
+
+ # Sanitize input.
+ # Each one either a valid string or nil
+ prefijo = query.fetch("p", "")
+ genero = query.fetch("g", "")
+ año = query.fetch("a", "")
+
+ if !prefijo.empty?
+ prefijo = normalize_name(prefijo)
+ else
+ prefijo = nil
+ end
+
+ if !["f", "m"].includes?(genero)
+ genero = nil
+ end
+
+ if año.empty?
+ año = nil
+ # TODO: check for valid integer
+ end
+
+ if prefijo.nil? && año.nil?
+ # Global totals
+ sql = %(
+ SELECT total, nombre
+ FROM totales
+ ORDER BY total DESC
+ LIMIT 50
+ )
+ elsif prefijo.nil? && !año.nil?
+ # Per-year totals
+ sql = %(
+ SELECT contador, nombre
+ FROM nombres
+ WHERE
+ anio = '#{año}'
+ ORDER BY contador DESC
+ LIMIT 50
+ )
+ elsif !prefijo.nil? && año.nil?
+ # Filter only by prefix
+ sql = %(
+ SELECT total, nombre
+ FROM totales
+ WHERE
+ nombre LIKE '#{prefijo}%'
+ ORDER BY total DESC
+ LIMIT 50
+ )
+ else
+ # We have all the filters
+ sql = %(
+ SELECT contador, nombre
+ FROM nombres
+ WHERE
+ anio = '#{año}' AND
+ nombre LIKE '#{prefijo}%'
+ ORDER BY contador DESC
+ LIMIT 50
+ )
+ end
+ results = query(sql)
+
+ if results.nil?
+ # This is bad 😀
+ return {
+ body: "Que raro, no tengo *idea*!",
+ status_code: 200,
+ headers: HTTP::Headers{"Content-Type" => "text/html"},
+ }
+ end
+ datos = results.map { |r|
+ [r[0].as_i, r[1].as_s]
+ }
+
+ if genero
+ datos = split_por_genero(datos)[genero]
+ end
+
+ datos = datos[..10]
+
+ buffer = IO::Memory.new
+ Ishi.new(buffer) do
+ x = (1..10).to_a
+ y = (1..10).to_a
+ canvas_size(800, 300)
+ plot(x,y, style: :boxes, fs: 0.25)
+ .boxwidth(0.5)
+ .ylabel("Popularidad")
+ .xlabel("Nombre")
+ end
+
+ {
+ body: format_buffer(buffer, "busqueda"),
+ status_code: 200,
+ headers: HTTP::Headers{"Content-Type" => "text/html"},
+ }
+ end
+end
\ No newline at end of file
diff --git a/c-busqueda/shard.yml b/c-busqueda/shard.yml
new file mode 100644
index 0000000..87fc635
--- /dev/null
+++ b/c-busqueda/shard.yml
@@ -0,0 +1,6 @@
+name: busqueda
+version: 0.1.0
+
+dependencies:
+ ishi:
+ github: toddsundsted/ishi
diff --git a/functions.yml b/functions.yml
index ba4fe5a..67d34de 100644
--- a/functions.yml
+++ b/functions.yml
@@ -21,3 +21,10 @@ functions:
image: ralsina/c-historico:latest
build_args:
ADDITIONAL_PACKAGE: gnuplot
+ c-busqueda:
+ lang: crystal-http
+ handler: ./c-busqueda
+ image: ralsina/c-busqueda:latest
+ build_args:
+ ADDITIONAL_PACKAGE: gnuplot
+