diff --git a/_extensions/shafayetShafee/downloadthis/_extension.yml b/_extensions/shafayetShafee/downloadthis/_extension.yml
new file mode 100644
index 0000000..6299f54
--- /dev/null
+++ b/_extensions/shafayetShafee/downloadthis/_extension.yml
@@ -0,0 +1,8 @@
+title: Downloadthis
+author: Shafayet Khan Shafee
+version: 1.1.0
+quarto-required: ">=1.2.0"
+contributes:
+ shortcodes:
+ - downloadthis.lua
+
diff --git a/_extensions/shafayetShafee/downloadthis/downloadthis.lua b/_extensions/shafayetShafee/downloadthis/downloadthis.lua
new file mode 100644
index 0000000..5219082
--- /dev/null
+++ b/_extensions/shafayetShafee/downloadthis/downloadthis.lua
@@ -0,0 +1,121 @@
+--[[
+MIT License
+
+Copyright (c) 2023 Shafayet Khan Shafee
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+
+
+local str = pandoc.utils.stringify
+--local p = quarto.log.output
+
+local function ensureHtmlDeps()
+ quarto.doc.add_html_dependency({
+ name = "downloadthis",
+ version = "1.9.1",
+ stylesheets = {"resources/css/downloadthis.css"}
+ })
+end
+
+local function optional(arg, default)
+ if arg == nil or arg == ""
+ then
+ return default
+ else
+ return arg
+ end
+end
+
+function import(script)
+ local path = PANDOC_SCRIPT_FILE:match("(.*[/\\])")
+ package.path = path .. script .. ";" .. package.path
+ return require(script)
+end
+
+local puremagic = import("puremagic.lua")
+
+return {
+ ['downloadthis'] = function(args, kwargs, meta)
+
+ -- args and kwargs
+ local file_path = str(args[1])
+ local extension = "." .. file_path:match("[^.]+$")
+ local dname = optional(str(kwargs["dname"]), "file")
+ local dfilename = dname .. extension
+ local btn_label = " " .. optional(str(kwargs["label"]), "Download") .. " "
+ local btn_type = optional(str(kwargs["type"]), "default")
+ local icon = optional(str(kwargs["icon"]), "download")
+ local class = " " .. optional(str(kwargs["class"]), "")
+ local rand = "dnldts" .. str(math.random(1, 65000))
+ local id = optional(str(kwargs["id"]), rand)
+ -- reading files
+ local fh = io.open(file_path, "rb")
+ if not fh then
+ io.stderr:write("Cannot open file " ..
+ file_path ..
+ " | Skipping adding buttons\n")
+ return pandoc.Null()
+ else
+ local contents = fh:read("*all")
+ fh:close()
+
+ -- creating dataURI object
+ local b64_encoded = quarto.base64.encode(contents)
+ local mimetype = puremagic.via_path(file_path)
+ local data_uri = 'data:' .. mimetype .. ";base64," .. b64_encoded
+
+ -- js code taken from
+ -- https://github.com/fmmattioni/downloadthis/blob/master/R/utils.R#L59
+ local js = [[fetch('%s').then(res => res.blob()).then(blob => {
+ const downloadURL = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ document.body.appendChild(a);
+ a.href = downloadURL;
+ a.download = '%s'; a.click();
+ window.URL.revokeObjectURL(downloadURL);
+ document.body.removeChild(a);
+ });]]
+
+ local clicked = js:format(data_uri, dfilename)
+
+ -- creating button
+ local button =
+ ""
+ if quarto.doc.is_format("html:js") and quarto.doc.has_bootstrap()
+ then
+ ensureHtmlDeps()
+ return pandoc.RawInline('html',
+ "" .. button .. ""
+ )
+ else
+ return pandoc.Null()
+ end
+ end
+ end
+}
+
+
diff --git a/_extensions/shafayetShafee/downloadthis/puremagic.lua b/_extensions/shafayetShafee/downloadthis/puremagic.lua
new file mode 100644
index 0000000..4420292
--- /dev/null
+++ b/_extensions/shafayetShafee/downloadthis/puremagic.lua
@@ -0,0 +1,735 @@
+-- puremagic 1.0.1
+-- Copyright (c) 2014 Will Bond
+-- Licensed under the MIT license.
+
+
+function basename(path)
+ local basename_match = path:match('[/\\]([^/\\]+)$')
+ if basename_match then
+ return basename_match, nil
+ end
+
+ return path, nil
+end
+
+
+function extension(path)
+ path = path:lower()
+ local tar_match = path:match('%.(tar%.[^.]+)$')
+ if tar_match then
+ return tar_match
+ end
+ if path:sub(#path - 11, #path) == '.numbers.zip' then
+ return 'numbers.zip'
+ end
+ if path:sub(#path - 9, #path) == '.pages.zip' then
+ return 'pages.zip'
+ end
+ if path:sub(#path - 7, #path) == '.key.zip' then
+ return 'key.zip'
+ end
+ return path:match('%.([^.]+)$')
+end
+
+
+function in_table(value, list)
+ for i=1, #list do
+ if list[i] == value then
+ return true
+ end
+ end
+ return false
+end
+
+
+function string_to_bit_table(chars)
+ local output = {}
+ for char in chars:gmatch('.') do
+ local num = string.byte(char)
+ local bits = {0, 0, 0, 0, 0, 0, 0, 0}
+ for bit=8, 1, -1 do
+ if num > 0 then
+ bits[bit] = math.fmod(num, 2)
+ num = (num - bits[bit]) / 2
+ end
+ end
+ table.insert(output, bits)
+ end
+ return output
+end
+
+
+function bit_table_to_string(bits)
+ local output = {}
+ for i = 1, #bits do
+ local num = tonumber(table.concat(bits[i]), 2)
+ table.insert(output, string.format('%c', num))
+ end
+ return table.concat(output)
+end
+
+
+function bitwise_and(a, b)
+ local a_bytes = string_to_bit_table(a)
+ local b_bytes = string_to_bit_table(b)
+
+ local output = {}
+ for i = 1, #a_bytes do
+ local bits = {0, 0, 0, 0, 0, 0, 0, 0}
+ for j = 1, 8 do
+ if a_bytes[i][j] == 1 and b_bytes[i][j] == 1 then
+ bits[j] = 1
+ else
+ bits[j] = 0
+ end
+ end
+ table.insert(output, bits)
+ end
+
+ return bit_table_to_string(output)
+end
+
+
+-- Unpack a little endian byte string into an integer
+function unpack_le(chars)
+ local bit_table = string_to_bit_table(chars)
+ -- Merge the bits into a string of 1s and 0s
+ local result = {}
+ for i=1, #bit_table do
+ result[#chars + 1 - i] = table.concat(bit_table[i])
+ end
+ return tonumber(table.concat(result), 2)
+end
+
+
+-- Unpack a big endian byte string into an integer
+function unpack_be(chars)
+ local bit_table = string_to_bit_table(chars)
+ -- Merge the bits into a string of 1s and 0s
+ for i=1, #bit_table do
+ bit_table[i] = table.concat(bit_table[i])
+ end
+ return tonumber(table.concat(bit_table), 2)
+end
+
+
+-- Takes the first 4-8k of an EBML file and identifies if it is matroska or webm
+-- and it it contains just video or just audio.
+function ebml_parse(content)
+ local position = 1
+ local length = #content
+
+ local header_token, header_value, used_bytes = ebml_parse_section(content)
+ position = position + used_bytes
+
+
+ if header_token ~= '\x1AE\xDF\xA3' then
+ return nil, 'Unable to find EBML ID'
+ end
+
+ -- The matroska spec sets the default doctype to be 'matroska', however
+ -- many file specify this anyway. The other option is 'webm'.
+ local doctype = 'matroska'
+ if header_value['B\x82'] then
+ doctype = header_value['B\x82']
+ end
+
+ if doctype ~= 'matroska' and doctype ~= 'webm' then
+ return nil, 'Unknown EBML doctype'
+ end
+
+ local segment_position = nil
+ local track_position = nil
+ local has_video = false
+ local found_tracks = false
+
+ while position <= length do
+ local ebml_id, ebml_value, used_bytes = ebml_parse_section(content:sub(position, length))
+ position = position + used_bytes
+
+ -- Segment
+ if ebml_id == '\x18S\x80g' then
+ segment_position = position
+ end
+
+ -- Meta seek information
+ if ebml_id == '\x11M\x9Bt' then
+ -- Look for the seek info about the tracks token
+ for i, child in ipairs(ebml_value['M\xBB']) do
+ if child['S\xAB'] == '\x16T\xAEk' then
+ track_position = segment_position + unpack_be(child['S\xAC'])
+ position = track_position
+ break
+ end
+ end
+ end
+
+ -- Track
+ if ebml_id == '\x16T\xAEk' then
+ found_tracks = true
+ -- Scan through each track looking for video
+ for i, child in ipairs(ebml_value['\xAE']) do
+ -- Look to see if the track type is video
+ if unpack_be(child['\x83']) == 1 then
+ has_video = true
+ break
+ end
+ end
+ break
+ end
+ end
+
+ if found_tracks and not has_video then
+ if doctype == 'matroska' then
+ return 'audio/x-matroska'
+ else
+ return 'audio/webm'
+ end
+ end
+
+ if doctype == 'matroska' then
+ return 'video/x-matroska'
+ else
+ return 'video/webm'
+ end
+end
+
+
+-- Parses a section of an EBML document, returning the EBML ID at the beginning,
+-- plus the value as a table with child EBML IDs as keys and the number of
+-- bytes from the content that contained the ID and value
+function ebml_parse_section(content)
+ local ebml_id, element_length, used_bytes = ebml_id_and_length(content)
+
+ -- Don't parse the segment since it is the whole file!
+ if ebml_id == '\x18\x53\x80\x67' then
+ return ebml_id, nil, used_bytes
+ end
+
+ local ebml_value = content:sub(used_bytes + 1, used_bytes + element_length)
+ used_bytes = used_bytes + element_length
+
+ -- We always parse the return value of level 0/1 elements
+ local recursive_parse = false
+ if #ebml_id == 4 then
+ recursive_parse = true
+
+ -- We need Seek information
+ elseif ebml_id == '\x4D\xBB' then
+ recursive_parse = true
+
+ -- We want the top-level of TrackEntry to grab the TrackType
+ elseif ebml_id == '\xAE' then
+ recursive_parse = true
+ end
+
+ if recursive_parse then
+ local buffer = ebml_value
+ ebml_value = {}
+
+ -- Track which child entries have been converted to an array
+ local array_children = {}
+
+ while #buffer > 0 do
+ local child_ebml_id, child_ebml_value, child_used_bytes = ebml_parse_section(buffer)
+
+ if array_children[child_ebml_id] then
+ table.insert(ebml_value[child_ebml_id], child_ebml_value)
+
+ -- Single values are just stores by themselves
+ elseif ebml_value[child_ebml_id] == nil then
+ -- Force seek info and tracks to be arrays even if there is only one
+ if child_ebml_id == 'M\xBB' or child_ebml_id == '\xAE' then
+ child_ebml_value = {child_ebml_value}
+ array_children[child_ebml_id] = true
+ end
+ ebml_value[child_ebml_id] = child_ebml_value
+
+ -- If there is already a value for the ID, turn it into a table
+ else
+ ebml_value[child_ebml_id] = {ebml_value[child_ebml_id], child_ebml_value}
+ array_children[child_ebml_id] = true
+ end
+
+ -- Move past the part we've parsed
+ buffer = buffer:sub(child_used_bytes + 1, #buffer)
+ end
+ end
+
+ return ebml_id, ebml_value, used_bytes
+end
+
+
+-- Should accept 12+ bytes, will return the ebml id, the data length and the
+-- number of bytes that were used to hold those values.
+function ebml_id_and_length(chars)
+ -- The ID is encoded the same way as the length, however, we don't want
+ -- to remove the length bits from the ID value or intepret it as an
+ -- unsigned int since all of the documentation online references the IDs in
+ -- encoded form.
+ local _, id_length = ebml_length(chars:sub(1, 4))
+ local ebml_id = chars:sub(1, id_length)
+
+ local remaining = chars:sub(id_length + 1, id_length + 8)
+ local element_length, used_bytes = ebml_length(remaining)
+
+ return ebml_id, element_length, id_length + used_bytes
+end
+
+
+-- Should accept 8+ bytes, will return the data length plus the number of bytes
+-- that were used to hold the data length.
+function ebml_length(chars)
+ -- We substring chars to ensure we don't build a huge table we don't need
+ local bit_tables = string_to_bit_table(chars:sub(1, 8))
+
+ local value_length = 1
+ for i=1, #bit_tables[1] do
+ if bit_tables[1][i] == 0 then
+ value_length = value_length + 1
+ else
+ -- Clear the indicator bit so the rest of the byte
+ bit_tables[1][i] = 0
+ break
+ end
+ end
+
+ local bits = {}
+ for i=1, value_length do
+ table.insert(bits, table.concat(bit_tables[i]))
+ end
+
+ return tonumber(table.concat(bits), 2), value_length
+end
+
+
+function binary_tests(content, ext)
+ local length = #content
+ local _1_8 = content:sub(1, 8)
+ local _1_7 = content:sub(1, 7)
+ local _1_6 = content:sub(1, 6)
+ local _1_5 = content:sub(1, 5)
+ local _1_4 = content:sub(1, 4)
+ local _1_3 = content:sub(1, 3)
+ local _1_2 = content:sub(1, 2)
+ local _9_12 = content:sub(9, 12)
+
+
+ -- Images
+ if _1_4 == '\xC5\xD0\xD3\xC6' then
+ -- With a Windows-format EPS, the file starts right after a 30-byte
+ -- header, or a 30-byte header followed by two bytes of padding
+ if content:sub(33, 42) == '%!PS-Adobe' or content:sub(31, 40) == '%!PS-Adobe' then
+ return 'application/postscript'
+ end
+ end
+
+ if _1_8 == '%!PS-Ado' and content:sub(9, 10) == 'be' then
+ return 'application/postscript'
+ end
+
+ if _1_4 == 'MM\x00*' or _1_4 == 'II*\x00' then
+ return 'image/tiff'
+ end
+
+ if _1_8 == '\x89PNG\r\n\x1A\n' then
+ return 'image/png'
+ end
+
+ if _1_6 == 'GIF87a' or _1_6 == 'GIF89a' then
+ return 'image/gif'
+ end
+
+ if _1_4 == 'RIFF' and _9_12 == 'WEBP' then
+ return 'image/webp'
+ end
+
+ if _1_2 == 'BM' and length > 14 and in_table(content:sub(15, 15), {'\x0C', '(', '@', '\x80'}) then
+ return 'image/x-ms-bmp'
+ end
+
+ local normal_jpeg = length > 10 and in_table(content:sub(7, 10), {'JFIF', 'Exif'})
+ local photoshop_jpeg = length > 24 and _1_4 == '\xFF\xD8\xFF\xED' and content:sub(21, 24) == '8BIM'
+ if normal_jpeg or photoshop_jpeg then
+ return 'image/jpeg'
+ end
+
+ if _1_4 == '8BPS' then
+ return 'image/vnd.adobe.photoshop'
+ end
+
+ if _1_8 == '\x00\x00\x00\x0CjP ' and _9_12 == '\r\n\x87\n' then
+ return 'image/jp2'
+ end
+
+ if _1_4 == '\x00\x00\x01\x00' then
+ return 'application/vnd.microsoft.icon'
+ end
+
+
+ -- Audio/Video
+ if _1_4 == '\x1AE\xDF\xA3' and length > 1000 then
+ local mimetype, err = ebml_parse(content)
+
+ if mimetype then
+ return mimetype
+ end
+ end
+
+ if _1_4 == 'MOVI' then
+ if in_table(content:sub(5, 8), {'moov', 'mdat'}) then
+ return 'video/quicktime'
+ end
+ end
+
+ if length > 8 and content:sub(5, 8) == 'ftyp' then
+ local lower_9_12 = _9_12:lower()
+
+ if in_table(lower_9_12, {'avc1', 'isom', 'iso2', 'mp41', 'mp42', 'mmp4', 'ndsc', 'ndsh', 'ndsm', 'ndsp', 'ndss', 'ndxc', 'ndxh', 'ndxm', 'ndxp', 'ndxs', 'f4v ', 'f4p ', 'm4v '}) then
+ return 'video/mp4'
+ end
+
+ if in_table(lower_9_12, {'msnv', 'ndas', 'f4a ', 'f4b ', 'm4a ', 'm4b ', 'm4p '}) then
+ return 'audio/mp4'
+ end
+
+ if in_table(lower_9_12, {'3g2a', '3g2b', '3g2c', 'kddi'}) then
+ return 'video/3gpp2'
+ end
+
+ if in_table(lower_9_12, {'3ge6', '3ge7', '3gg6', '3gp1', '3gp2', '3gp3', '3gp4', '3gp5', '3gp6', '3gs7'}) then
+ return 'video/3gpp'
+ end
+
+ if lower_9_12 == 'mqt ' or lower_9_12 == 'qt ' then
+ return 'video/quicktime'
+ end
+
+ if lower_9_12 == 'jp2 ' then
+ return 'image/jp2'
+ end
+ end
+
+ -- MP3
+ if bitwise_and(_1_2, '\xFF\xF6') == '\xFF\xF2' then
+ local byte_3 = content:sub(3, 3)
+ if bitwise_and(byte_3, '\xF0') ~= '\xF0' and bitwise_and(byte_3, "\x0C") ~= "\x0C" then
+ return 'audio/mpeg'
+ end
+ end
+ if _1_3 == 'ID3' then
+ return 'audio/mpeg'
+ end
+
+ if _1_4 == 'fLaC' then
+ return 'audio/x-flac'
+ end
+
+ if _1_8 == '0&\xB2u\x8Ef\xCF\x11' then
+ -- Without writing a full-on ASF parser, we can just scan for the
+ -- UTF-16 string "AspectRatio"
+ if content:find('\x00A\x00s\x00p\x00e\x00c\x00t\x00R\x00a\x00t\x00i\x00o', 1, true) then
+ return 'video/x-ms-wmv'
+ end
+ return 'audio/x-ms-wma'
+ end
+
+ if _1_4 == 'RIFF' and _9_12 == 'AVI ' then
+ return 'video/x-msvideo'
+ end
+
+ if _1_4 == 'RIFF' and _9_12 == 'WAVE' then
+ return 'audio/x-wav'
+ end
+
+ if _1_4 == 'FORM' and _9_12 == 'AIFF' then
+ return 'audio/x-aiff'
+ end
+
+ if _1_4 == 'OggS' then
+ local _29_33 = content:sub(29, 33)
+ if _29_33 == '\x01vorb' then
+ return 'audio/vorbis'
+ end
+ if _29_33 == '\x07FLAC' then
+ return 'audio/x-flac'
+ end
+ if _29_33 == 'OpusH' then
+ return 'audio/ogg'
+ end
+ -- Theora and OGM
+ if _29_33 == '\x80theo' or _29_33 == 'vide' then
+ return 'video/ogg'
+ end
+ end
+
+ if _1_3 == 'FWS' or _1_3 == 'CWS' then
+ return 'application/x-shockwave-flash'
+ end
+
+ if _1_3 == 'FLV' then
+ return 'video/x-flv'
+ end
+
+
+ if _1_5 == '%PDF-' then
+ return 'application/pdf'
+ end
+
+ if _1_5 == '{\\rtf' then
+ return 'text/rtf'
+ end
+
+
+ -- Office '97-2003 formats
+ if _1_8 == '\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1' then
+ if in_table(ext, {'xls', 'csv', 'tab'}) then
+ return 'application/vnd.ms-excel'
+ end
+ if ext == 'ppt' then
+ return 'application/vnd.ms-powerpoint'
+ end
+ -- We default to word since we need something if the extension isn't recognized
+ return 'application/msword'
+ end
+
+ if _1_8 == '\x09\x04\x06\x00\x00\x00\x10\x00' then
+ return 'application/vnd.ms-excel'
+ end
+
+ if _1_6 == '\xDB\xA5\x2D\x00\x00\x00' or _1_5 == '\x50\x4F\x5E\x51\x60' or _1_4 == '\xFE\x37\x00\x23' or _1_3 == '\x94\xA6\x2E' then
+ return 'application/msword'
+ end
+
+ if _1_4 == 'PK\x03\x04' then
+ -- Office XML formats
+ if ext == 'xlsx' then
+ return 'application/vnd.ms-excel'
+ end
+
+ if ext == 'pptx' then
+ return 'application/vnd.ms-powerpoint'
+ end
+
+ if ext == 'docx' then
+ return 'application/msword'
+ end
+
+ -- Open Office formats
+ if ext == 'ods' then
+ return 'application/vnd.oasis.opendocument.spreadsheet'
+ end
+
+ if ext == 'odp' then
+ return 'application/vnd.oasis.opendocument.presentation'
+ end
+
+ if ext == 'odt' then
+ return 'application/vnd.oasis.opendocument.text'
+ end
+
+ -- iWork - some programs like Mac Mail change the filename to
+ -- .numbers.zip, etc
+ if ext == 'pages' or ext == 'pages.zip' then
+ return 'application/vnd.apple.pages'
+ end
+ if ext == 'key' or ext == 'key.zip' then
+ return 'application/vnd.apple.keynote'
+ end
+ if ext == 'numbers' or ext == 'numbers.zip' then
+ return 'application/vnd.apple.numbers'
+ end
+
+ -- Otherwise just a zip
+ return 'application/zip'
+ end
+
+
+ -- Archives
+ if length > 257 then
+ if content:sub(258, 263) == 'ustar\x00' then
+ return 'application/x-tar'
+ end
+ if content:sub(258, 265) == 'ustar\x40\x40\x00' then
+ return 'application/x-tar'
+ end
+ end
+
+ if _1_7 == 'Rar!\x1A\x07\x00' or _1_8 == 'Rar!\x1A\x07\x01\x00' then
+ return 'application/x-rar-compressed'
+ end
+
+ if _1_2 == '\x1F\x9D' then
+ return 'application/x-compress'
+ end
+
+ if _1_2 == '\x1F\x8B' then
+ return 'application/x-gzip'
+ end
+
+ if _1_3 == 'BZh' then
+ return 'application/x-bzip2'
+ end
+
+ if _1_6 == '\xFD7zXZ\x00' then
+ return 'application/x-xz'
+ end
+
+ if _1_6 == '7z\xBC\xAF\x27\x1C' then
+ return 'application/x-7z-compressed'
+ end
+
+ if _1_2 == 'MZ' then
+ local pe_header_start = unpack_le(content:sub(61, 64))
+ local signature = content:sub(pe_header_start + 1, pe_header_start + 4)
+
+ if signature == 'PE\x00\x00' then
+ local image_file_header_start = pe_header_start + 5
+ local characteristics = content:sub(image_file_header_start + 18, image_file_header_start + 19)
+ local is_dll = bitwise_and(characteristics, '\x20\x00') == '\x20\x00'
+
+ if is_dll then
+ return 'application/x-msdownload'
+ end
+
+ return 'application/octet-stream'
+ end
+ end
+
+ return nil
+end
+
+
+function text_tests(content)
+ local lower_content = content:lower()
+
+ if content:find('^%%!PS-Adobe') then
+ return 'application/postscript'
+ end
+
+ if lower_content:find(' self.saldo:\n",
+ " print(\"Error: Fondos insuficientes.\")\n",
+ " return\n",
+ " self.saldo -= monto\n",
+ "\n",
+ "\n",
+ " def consultar_saldo(self):\n",
+ " \"\"\"Devuelve el saldo actual.\n",
+ "\n",
+ " Returns\n",
+ " -------\n",
+ " float\n",
+ " Saldo disponible en la cuenta.\n",
+ " \"\"\"\n",
+ " return self.saldo"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3cb891e0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "help(CuentaBancaria)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d8150810",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "help(CuentaBancaria.depositar)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8da90f1d",
+ "metadata": {},
+ "source": [
+ "### Anotaciones de tipo"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "58d7afd8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def saludar(nombre: str) -> str:\n",
+ " return f\"¡Hola, {nombre}!\"\n",
+ "\n",
+ "saludar(\"Guido\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2d0a047f",
+ "metadata": {},
+ "source": [
+ "## Estrategias de encapsulamiento"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "d29cdf62",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Estudiante:\n",
+ " def __init__(self, nombre, ingreso, carrera):\n",
+ " self.nombre = nombre\n",
+ " self.ingreso = ingreso\n",
+ " self.carrera = carrera\n",
+ "\n",
+ " def resumen(self):\n",
+ " return f\"Estudiante(nombre={self.nombre}, ingreso={self.ingreso}, carrera={self.carrera})\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7cbbda52",
+ "metadata": {},
+ "source": [
+ "### _Setters_ y _getters_"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dda85bce",
+ "metadata": {},
+ "source": [
+ "### Atributos \"protegidos\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "85a2ecc2",
+ "metadata": {},
+ "source": [
+ "### Atributos \"\"\"privados\"\"\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a4d6b4ea",
+ "metadata": {},
+ "source": [
+ "### Cuasi-atributos (el decorador `@property`)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8aa6c79b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Estudiante:\n",
+ " def __init__(self, nombre):\n",
+ " self.nombre = nombre\n",
+ "\n",
+ " @property\n",
+ " def nombre(self):\n",
+ " return self._nombre\n",
+ "\n",
+ " @nombre.setter\n",
+ " def nombre(self, valor):\n",
+ " if isinstance(valor, str):\n",
+ " self._nombre = valor\n",
+ " else:\n",
+ " print(\"El nombre debe ser de tipo 'str'\")\n",
+ "\n",
+ " def resumen(self):\n",
+ " return f\"Estudiante(nombre={self.nombre})\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "725e0d3b",
+ "metadata": {},
+ "source": [
+ "## Atributos y métodos de clase"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "30c08ada",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Gato:\n",
+ " especie = \"Felis catus\"\n",
+ "\n",
+ " def __init__(self, nombre, raza=None):\n",
+ " self.nombre = nombre\n",
+ " self.raza = raza\n",
+ "\n",
+ " def resumen(self):\n",
+ " return f\"Gato(nombre={self.nombre}, raza={self.raza})\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7a7418fc",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Perro:\n",
+ " especie = \"Canis lupus familiaris\"\n",
+ "\n",
+ " def __init__(self, nombre, raza=None):\n",
+ " self.nombre = nombre\n",
+ " self.raza = raza\n",
+ "\n",
+ " def resumen(self):\n",
+ " return f\"Perro(nombre={self.nombre}, raza={self.raza})\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cba8a2be",
+ "metadata": {},
+ "source": [
+ "Otro ejemplo..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b4e8e09d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Usuario:\n",
+ " total_usuarios = 0\n",
+ "\n",
+ " def __init__(self, nombre):\n",
+ " self.nombre = nombre\n",
+ " Usuario.total_usuarios += 1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c316542e",
+ "metadata": {},
+ "source": [
+ "El decorador `@classmethod`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0ee1c8d5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Estudiante:\n",
+ " def __init__(self, nombre, ingreso):\n",
+ " self.nombre = nombre\n",
+ " self.ingreso = ingreso\n",
+ "\n",
+ " @classmethod\n",
+ " def desde_texto(cls, texto):\n",
+ " nombre, ingreso = texto.split(\",\")\n",
+ " return cls(nombre, int(ingreso))\n",
+ "\n",
+ " def resumen(self):\n",
+ " return f\"Estudiante(nombre={self.nombre}, ingreso={self.ingreso})\""
+ ]
+ }
+ ],
+ "metadata": {
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/recursos/slides/01.qmd b/recursos/clases/01.qmd
similarity index 100%
rename from recursos/slides/01.qmd
rename to recursos/clases/01.qmd
diff --git a/recursos/slides/02_1.qmd b/recursos/clases/02_1.qmd
similarity index 100%
rename from recursos/slides/02_1.qmd
rename to recursos/clases/02_1.qmd
diff --git a/recursos/slides/02_2.qmd b/recursos/clases/02_2.qmd
similarity index 100%
rename from recursos/slides/02_2.qmd
rename to recursos/clases/02_2.qmd
diff --git a/recursos/clases/04.qmd b/recursos/clases/04.qmd
new file mode 100644
index 0000000..1760586
--- /dev/null
+++ b/recursos/clases/04.qmd
@@ -0,0 +1,13 @@
+---
+title: "Clase 4: Programación Orientada a Objetos"
+---
+
+{{< downloadthis ../../notebooks/clase_04.ipynb dname=clase_04 label="Descargar Notebook" icon=journal-code type=default class=data-button id=clase_04 >}}
+
+
+