Navigate back to the homepage

My recap of Scrimba’s #JavaScriptmas

Javier Zapata
December 20th, 2020 · 6 min read

This year Scrimba is holding an Advent calendar by releasing daily coding challenges from December 1st to the 24th. They are also giving away free yearly subscriptions each day and a USD 1,000 grand prize at the end.

I’ve been doing them since day one and I have to say that I’m having a blast! They are fun little challenges that don’t take more than five minutes and I find them as a perfect way to end my –coding– day. I specially like to check other’s people solutions afterwards, as some of them are pretty clever and I can always learn something new.

So, if you haven’t joined yet, what are your waiting for? It’s a lot of fun and you could end up winning the big prize! 🤑

What is Scrimba?

In case you didn’t know already, Scrimba is a learning platform for developers that teaches coding and other related skills through interactive screencasts, called “scrims” (they’re pretty neat, you can actually pause them at any time, edit and run code directly!). I’ve been using it since 2018 and it has become one of my favourite learning platforms. By the way, they have a lot of free courses if you want to check them out!.

Well, without further ado, here are my solutions:

Day 1: Candies

Challenge

n children have got m pieces of candy. They want to eat as much candy as they can, but each child must eat exactly the same amount of candy as any other child. Determine how many pieces of candy will be eaten by all the children together. Individual pieces of candy cannot be split.

My solution (🔗)

1function candies(children, candy) {
2 return Math.floor(candy / children) * children;
3}

Official solution (🔗)

1function candies(children, candy) {
2 const candyPerChild = Math.floor(candy / children);
3
4 return candyPerChild * children;
5}

Day 2: Deposit Profit

Challenge

You have deposited a specific amount of dollars into your bank account. Each year your balance increases at the same growth rate. Find out how long it would take for your balance to pass a specific threshold with the assumption that you don’t make any additional deposits.

My solution (🔗)

1function depositProfit(deposit, rate, threshold) {
2 const COMPOUND_RATE = 1 + rate / 100;
3 let years = 0;
4 let holdings = deposit;
5
6 while (holdings < threshold) {
7 years++;
8 holdings *= COMPOUND_RATE;
9 }
10
11 return years;
12}

Official solution (🔗)

1function depositProfit(deposit, rate, threshold) {
2 let year = 0;
3 let currentAccountValue = deposit;
4
5 while (threshold > currentAccountValue) {
6 currentAccountValue += currentAccountValue * (rate / 100);
7 year++;
8 }
9
10 return year;
11}

Day 3: Chunky Monkey

Challenge

Write a function that splits an array (first argument) into groups the length of size (second argument) and returns them as a multidimensional array.

My solution (🔗)

1function chunkyMonkey(values, size) {
2 let arr = [];
3 let offset = 0;
4 for (let i = 0; i < values.length / size; i++) {
5 offset = size * i;
6 arr.push(values.slice(offset, offset + size));
7 }
8 return arr;
9}

Official solution (🔗)

1function chunkyMonkey(values, size) {
2 const nested = [];
3 let count = 0;
4
5 while (count < values.length) {
6 nested.push(values.slice(count, (count += size)));
7 }
8
9 return nested;
10}

Day 4: Century From Year

Challenge

Given a year, return the century it is in. The first century spans from the year 1 up to and including the year 100, the second - from the year 101 up to and including the year 200, etc.

My solution (🔗)

1function centuryFromYear(num) {
2 const rest = num % 100;
3
4 let century = Math.floor(num / 100);
5 century += rest > 0 ? 1 : 0;
6
7 return century;
8}

Official solution (🔗)

1function centuryFromYear(year) {
2 const century = year / 100;
3
4 if (year % 100 === 0) {
5 return century;
6 }
7
8 return Math.floor(century) + 1;
9}

Day 5: Reverse a string

Challenge

Reverse the provided string. You may need to turn the string into an array before you can reverse it. Your result must be a string.

My solution (🔗)

1function reverseAString(str) {
2 return str
3 .split('')
4 .reverse()
5 .join('');
6}

Official solution (🔗)

1function reverseAString(str) {
2 let reversedStr = '';
3
4 for (let i = str.length - 1; i >= 0; i--) {
5 reversedStr += str[i];
6 }
7
8 return reversedStr;
9}

Day 6: Sort by Length

Challenge

Given an array of strings, sort them in the order of increasing lengths. If two strings have the same length, their relative order must be the same as in the initial array.

My solution (🔗)

1function sortByLength(strs) {
2 return strs.sort((a, b) => a.length - b.length);
3}

Official solution (🔗)

1function sortByLength(strs) {
2 return strs.sort((str1, str2) => str1.length - str2.length);
3}

Day 7: Count Vowel Consonant

Challenge

You are given a string s that consists of only lowercase English letters. If vowels (‘a’, ‘e’, ‘i’, ‘o’, and ‘u’) are given a value of 1 and consonants are given a value of 2, return the sum of all of the letters in the input string.

My solution (🔗)

1function countVowelConsonant(str) {
2 const counter = (count, char) => {
3 const isVowel = 'aeiou'.indexOf(char) !== -1;
4 return count + (isVowel ? 1 : 2);
5 };
6
7 return str.split('').reduce(counter, 0);
8}

Official solution (🔗)

1function countVowelConsonant(str) {
2 const vowels = ['a', 'e', 'i', 'o', 'u'];
3 const chars = str.split('');
4 const total = chars.reduce((acc, char) => {
5 if (vowels.includes(char)) {
6 return acc + 1;
7 }
8
9 return acc + 2;
10 }, 0);
11
12 return total;
13}

Day 8: Rolling Dice

Note: This challenge involves additional HTML and CSS, so I recommend checking the solutions directly at Scrimba.

Challenge

In this challenge a casino has asked you to make an online dice that works just like it would in real life. Using the pre-made dice face that represents ‘one’, make the faces for ‘two’, ‘three’, ‘four’, ‘five’ and ‘six’. Now when the users clicks the dice on the screen the dice is expected to show one of the faces randomly.

My solution (🔗)

1const dice = document.querySelector('.dice');
2const face = dice.querySelector('.face');
3const message = document.querySelector('.message');
4
5const roll = () => Math.ceil(Math.random() * 6);
6
7dice.addEventListener('click', (e) => {
8 const number = roll();
9 face.className = `face dot-${number}`;
10 message.innerHTML = `You got a ${number}!`;
11});

Official solution (🔗)

1const dice = document.querySelector('.dice');
2const allDots = Array.from(document.querySelectorAll('.dice div'));
3
4function rollDice() {
5 let randomNumber = 1 + Math.floor(Math.random() * 6);
6 console.log(randomNumber);
7
8 allDots.forEach((dot) => dot.classList.remove(...dot.classList));
9
10 if (randomNumber === 1) {
11 allDots[4].classList.add('dot');
12 }
13
14 if (randomNumber === 2) {
15 allDots[0].classList.add('dot');
16 allDots[8].classList.add('dot');
17 }
18 if (randomNumber === 3) {
19 allDots[0].classList.add('dot');
20 allDots[4].classList.add('dot');
21 allDots[8].classList.add('dot');
22 }
23 if (randomNumber === 4) {
24 allDots[0].classList.add('dot');
25 allDots[2].classList.add('dot');
26 allDots[6].classList.add('dot');
27 allDots[8].classList.add('dot');
28 }
29 if (randomNumber === 5) {
30 allDots[0].classList.add('dot');
31 allDots[2].classList.add('dot');
32 allDots[4].classList.add('dot');
33 allDots[6].classList.add('dot');
34 allDots[8].classList.add('dot');
35 }
36 if (randomNumber === 6) {
37 allDots[0].classList.add('dot');
38 allDots[2].classList.add('dot');
39 allDots[3].classList.add('dot');
40 allDots[5].classList.add('dot');
41 allDots[6].classList.add('dot');
42 allDots[8].classList.add('dot');
43 }
44}
45dice.addEventListener('click', rollDice);

Day 9: Sum Odd Fibonacci Numbers

Challenge

Given a positive integer num, return the sum of all odd Fibonacci numbers that are less than or equal to num. The first two numbers in the Fibonacci sequence are 1 and 1. Every additional number in the sequence is the sum of the two previous numbers. The first six numbers of the Fibonacci sequence are 1, 1, 2, 3, 5 and 8. For example, sumFibs(10) should return 10 because all odd Fibonacci numbers less than or equal to 10 are 1, 1, 3, and 5.

My solution (🔗)

1function sumOddFibonacciNumbers(num) {
2 let a = 1,
3 b = 1,
4 sum = a,
5 tmp;
6 while (sum < num) {
7 sum += b % 2 === 0 ? 0 : b;
8 tmp = a;
9 a = b;
10 b += tmp;
11 }
12 return sum;
13}

Official solution (🔗)

1function sumOddFibonacciNumbers(num) {
2 let sum = 0;
3 let previous = 0;
4 let current = 1;
5
6 while (current <= num) {
7 if (current % 2 === 1) {
8 sum += current;
9 }
10
11 const nextValue = current + previous;
12 previous = current;
13 current = nextValue;
14 }
15
16 return sum;
17}

Day 10: Adjacent Elements Product

Challenge

Given an array of integers, find the pair of adjacent elements that has the largest product and return that product.

My solution (🔗)

1function adjacentElementsProduct(nums) {
2 const products = nums.map((num, i, nums) => num * (nums[i + 1] ?? 1));
3 return Math.max(...products);
4}

Official solution (🔗)

1function adjacentElementsProduct(nums) {
2 let largestProduct = nums[0] * nums[1];
3
4 for (let i = 1; i < nums.length - 1; i++) {
5 const adjacentProduct = nums[i] * nums[i + 1];
6
7 if (largestProduct < adjacentProduct) {
8 largestProduct = adjacentProduct;
9 }
10 }
11
12 return largestProduct;
13}

Day 11: Avoid Obstacles

Challenge

You are given an array of integers representing coordinates of obstacles situated on a straight line.

Assume that you are jumping from the point with coordinate 0 to the right. You are allowed only to make jumps of the same length represented by some integer.

Find the minimal length of the jump enough to avoid all the obstacles.

My solution (🔗)

1function avoidObstacles(nums) {
2 let min = 1;
3 while (nums.some((num) => num % min === 0)) {
4 min++;
5 }
6 return min;
7}

Official solution (🔗)

1function avoidObstacles(nums) {
2 const largestNum = nums.sort((a, b) => a - b)[nums.length - 1];
3
4 for (let i = 1; i <= largestNum + 1; i++) {
5 if (nums.every((value) => value % i !== 0)) {
6 return i;
7 }
8 }
9}

Day 12: Valid Time

Challenge

Check if the given string is a correct time representation of the 24-hour clock.

My solution (🔗)

1function validTime(str) {
2 const [hour, minute] = str.split(':').map((x) => parseInt(x, 10));
3
4 return hour >= 0 && hour < 24 && minute >= 0 && minute < 60;
5}

Official solution (🔗)

1function validTime(str) {
2 const [hours, minutes] = str.split(':');
3
4 if (parseInt(hours) > 23 || parseInt(hours) < 0) {
5 return false;
6 }
7
8 if (parseInt(minutes) > 59 || parseInt(minutes) < 0) {
9 return false;
10 }
11
12 return true;
13}

Day 13: Extract Each Kth

Challenge

Given array of integers, remove each K​th element from it.

My solution (🔗)

1function extractEachKth(nums, index) {
2 return nums.filter((num) => num % index !== 0);
3}

Official solution (🔗)

1function extractEachKth(nums, index) {
2 return nums.filter((value, i) => (i + 1) % index !== 0);
3}

Day 14: Maximal Adjacent Difference

Challenge

Given an array of integers, find the maximal absolute difference between any two of its adjacent elements.

My solution (🔗)

1function arrayMaximalAdjacentDifference(nums) {
2 const diffs = nums.map((num, i, nums) =>
3 i ? Math.abs(num - nums[i - 1]) : 0
4 );
5
6 return Math.max(...diffs);
7}

Official solution (🔗)

1function arrayMaximalAdjacentDifference(nums) {
2 let maxDifference = 0;
3
4 for (let i = 0; i < nums.length - 1; i++) {
5 const absoluteDifference = Math.abs(nums[i] - nums[i + 1]);
6
7 if (maxDifference < absoluteDifference) {
8 maxDifference = absoluteDifference;
9 }
10 }
11
12 return maxDifference;
13}

Note: This challenge involves additional HTML and CSS, so I recommend checking the solutions directly at Scrimba.

Challenge

Create a carousel with JavaScript.

My solution (🔗)

1const prevButton = document.querySelector('.previous');
2const nextButton = document.querySelector('.next');
3const gallery = document.querySelector('.gallery');
4const cards = document.querySelectorAll('.card');
5
6let currentCard = 0;
7const lastCard = cards.length - 1;
8
9const slide = (num) => {
10 const prevCard = currentCard;
11 const nextCard = currentCard + num;
12
13 currentCard = Math.max(nextCard, 0);
14 currentCard = Math.min(currentCard, lastCard);
15
16 gallery.style = `transform: translateX(-${currentCard * 220}px);`;
17 cards[prevCard].classList.remove('current');
18 cards[currentCard].classList.add('current');
19
20 prevButton.style = 'opacity: 1';
21 nextButton.style = 'opacity: 1';
22 if (currentCard === 0) {
23 prevButton.style = 'opacity: .3';
24 } else if (currentCard === lastCard) {
25 nextButton.style = 'opacity: .3';
26 }
27};
28
29nextButton.addEventListener('click', () => slide(1));
30prevButton.addEventListener('click', () => slide(-1));

Official solution (🔗)

1const gallery = document.getElementsByClassName('gallery')[0];
2const prevBtn = document.getElementsByClassName('previous')[0];
3const nextBtn = document.getElementsByClassName('next')[0];
4const galleryCardCount = document.getElementsByClassName('card').length;
5
6let currentGalleryXOffset = 0;
7const endGalleryXOffset = (galleryCardCount - 1) * -220;
8
9prevBtn.addEventListener('click', galleryClickHandler);
10nextBtn.addEventListener('click', galleryClickHandler);
11
12function galleryClickHandler(event) {
13 let targetBtn = event.target.className;
14 if (targetBtn == 'previous' && currentGalleryXOffset < 0) {
15 currentGalleryXOffset += 220;
16 } else if (targetBtn == 'next' && currentGalleryXOffset > endGalleryXOffset) {
17 currentGalleryXOffset -= 220;
18 }
19
20 if (currentGalleryXOffset == 0) {
21 prevBtn.style.opacity = 0.3;
22 prevBtn.style.cursor = 'default';
23 } else {
24 prevBtn.style.opacity = 1; //disabled
25 prevBtn.style.cursor = 'pointer';
26 }
27
28 if (currentGalleryXOffset == endGalleryXOffset) {
29 nextBtn.style.opacity = 0.3;
30 nextBtn.style.cursor = 'default';
31 } else {
32 nextBtn.style.opacity = 1;
33 nextBtn.style.cursor = 'pointer';
34 }
35
36 gallery.style.transform = `translateX(${currentGalleryXOffset}px)`;
37}

Day 16: Insert Dashes

Challenge

Transform a given sentence into a new one with dashes between each two consecutive letters.

My solution (🔗)

1function insertDashes(arr) {
2 return arr
3 .split('')
4 .join('-')
5 .replace('- -', ' ');
6}

Official solution (🔗)

1function insertDashes(str) {
2 const words = str.split(' ');
3 const dashedWords = words.map((word) => {
4 const chars = word.split('');
5
6 return chars.join('-');
7 });
8
9 return dashedWords.join(' ');
10}

Day 17: Different symbols naive

Challenge

Given a string, find the number of different characters in it.

My solution (🔗)

1function differentSymbolsNaive(str) {
2 return new Set(str.split('')).size;
3}

Official solution (🔗)

1function differentSymbolsNaive(str) {
2 const chars = str.split('');
3
4 return new Set(chars).size;
5}

Day 18: Array previous less

Challenge

Given array of integers, for each position i, search among the previous positions for the last (from the left) position that contains a smaller value. Store this value at position i in the answer. If no such value can be found, store -1 instead.

My solution (🔗)

1function arrayPreviousLess(nums) {
2 return nums.map((num, i) => {
3 const prevNum = nums[i - 1] ?? -1;
4 return prevNum < num ? prevNum : -1;
5 });
6}

Official solution (🔗)

1function arrayPreviousLess(nums) {
2 const previousLess = [];
3
4 for (let i = nums.length - 1; i >= 0; i--) {
5 for (let j = i; j >= 0; j--) {
6 if (nums[i] > nums[j]) {
7 previousLess.unshift(nums[j]);
8 break;
9 } else if (j === 0) {
10 previousLess.unshift(-1);
11 }
12 }
13 }
14
15 return previousLess;
16}

Day 19: Alphabet Subsequence

Challenge

Check whether the given string is a subsequence of the plaintext alphabet.

My solution (🔗)

1function alphabetSubsequence(str) {
2 const expectedStr = [...new Set(str.split(''))].sort().join('');
3 return str === expectedStr;
4}

Official solution (🔗)

1function alphabetSubsequence(str) {
2 const chars = str.split('');
3 const charCodes = chars.map((char) => char.charCodeAt(0));
4
5 if (new Set(charCodes).size !== charCodes.length) {
6 return false;
7 }
8
9 for (let i = 0; i < charCodes.length - 1; i++) {
10 if (charCodes[i] > charCodes[i + 1]) {
11 return false;
12 }
13 }
14
15 return true;
16}

Day 20: Domain Type

Challenge

GoDaddy makes a lot of different top-level domains available to its customers. A top-level domain is one that goes directly after the last dot (’.’) in the domain name, for example .com in example.com. To help the users choose from available domains, GoDaddy is introducing a new feature that shows the type of the chosen top-level domain. You have to implement this feature. To begin with, you want to write a function that labels the domains as “commercial”, “organization”, “network” or “information” for .com, .org, .net or .info respectively. For the given list of domains return the list of their labels.

My solution (🔗)

1function domainType(domains) {
2 const types = new Map([
3 ['org', 'organization'],
4 ['com', 'commercial'],
5 ['net', 'network'],
6 ['info', 'information'],
7 ]);
8
9 return domains.map((domain) => {
10 const tld = domain
11 .split('.')
12 .pop()
13 .toLowerCase();
14 return types.has(tld) ? types.get(tld) : 'unknown';
15 });
16}

Official solution (🔗)

1function domainType(domains) {
2 const domainTypes = [];
3
4 for (let i = 0; i < domains.length; i++) {
5 const urlPieces = domains[i].split('.');
6 const domain = urlPieces[urlPieces.length - 1];
7
8 if (domain === 'org') {
9 domainTypes.push('organization');
10 } else if (domain === 'com') {
11 domainTypes.push('commercial');
12 } else if (domain === 'net') {
13 domainTypes.push('network');
14 } else if (domain === 'info') {
15 domainTypes.push('information');
16 }
17 }
18
19 return domainTypes;
20}

Day 21: Sum of Two

Challenge

You have two integer arrays, a and b, and an integer target value v. Determine whether there is a pair of numbers, where one number is taken from a and the other from b, that can be added together to get a sum of v. Return true if such a pair exists, otherwise return false.

My solution (🔗)

1function sumOfTwo(nums1, nums2, value) {
2 return nums1.some((num1) => nums2.includes(value - num1));
3}

Official solution (🔗)

1function sumOfTwo(nums1, nums2, value) {
2 const map = {};
3
4 for (let num of nums1) {
5 const difference = value - num;
6 map[difference] = difference;
7 }
8
9 for (let num of nums2) {
10 if (map.hasOwnProperty(num)) {
11 return true;
12 }
13 }
14
15 return false;
16}

Day 22: Extract Matrix Column

Challenge

Given a rectangular matrix and an integer column, return an array containing the elements of the columnth column of the given matrix (the leftmost column is the 0th one).

My solution (🔗)

1function extractMatrixColumn(matrix, column) {
2 return matrix.map((rect) => rect[column]);
3}

Official solution (🔗)

1function extractMatrixColumn(matrix, column) {
2 return matrix.map((row) => row[column]);
3}

Day 23: Social Media Input

Note: This challenge involves additional HTML and CSS, so I recommend checking the solutions directly at Scrimba.

Challenge

We are making a Social Media Character Counter! We want to display the available characters LEFT. Using the Keydown event should help you here. When the characters reach 20 and below, we want them to turn red. So we will use Javascript to add that styling to it. If the characters drop below 0, we want the button to be disabled BUT if there are only 0 characters left, we should still be able to tweet.

Keydown, addEventListeners, add and remove a class

My solution (🔗)

1const string = document.querySelector('#string');
2const btn = document.querySelector('#btn');
3const counter = document.querySelector('#counterFooter');
4const MAX_LENGTH = 140;
5
6string.addEventListener('keyup', (event) => {
7 const length = event.target.value.length;
8 const remaining = MAX_LENGTH - length;
9
10 counter.innerText = `${remaining}/${MAX_LENGTH}`;
11 counter.style.color = remaining <= 20 ? 'red' : 'white';
12
13 if (remaining < 0) {
14 btn.setAttribute('disabled', 'disabled');
15 btn.className = 'buttonDisabled';
16 } else {
17 btn.setAttribute('disabled', '');
18 btn.className = '';
19 }
20});

Official solution

N/A

Day 24: Test your agility

Note: This challenge involves additional HTML and CSS, so I recommend checking the solutions directly at Scrimba.

Challenge

Make a counter that increments every 75 milliseconds in the spin() function and display whether the player wins or loses in the stop() function.

My solution (🔗)

1// Globals
2let targetInt; // The target number to stop the wheel on
3let pushed = false; // Has the stop button been pushed - false is default
4
5// DOM Elements
6const spinningElem = document.getElementById('spinning'); // The spinning number
7const buttonElem = document.getElementById('buttonPressed');
8const targetElem = document.getElementById('targetNum');
9const resultElem = document.getElementById('result'); // Display your result message here
10
11// Functions
12const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
13
14const buttonPressed = () => (pushed = true);
15
16const setTargetInt = () => {
17 targetInt = Math.floor(Math.random() * 101);
18 targetElem.innerText = targetInt;
19};
20
21const spin = async () => {
22 let i = 0;
23 while (!pushed) {
24 i = i >= 100 ? 0 : i + 1;
25 spinningElem.innerText = i;
26 await sleep(75); // Paste this wherever you need to sleep the incrimentor
27 }
28
29 stop(i); // Trigger this function when the STOP button has been pushed
30};
31
32const stop = (i) => {
33 const diff = Math.abs(i - targetInt);
34
35 if (diff === 0) {
36 resultElem.innerText = `Yay! You did it!`;
37 } else {
38 resultElem.innerText = `Oh no, you lose! Off by ${diff}`;
39 }
40};
41
42// Event listeners
43buttonElem.addEventListener('click', buttonPressed);
44
45// Init
46setTargetInt();
47spin();

Official solution (🔗)

1// javascript
2
3//globals
4var pushed = false; //Has the stop button been pushed - false is default
5var targetInt; //The target number to stop the wheel on
6var spinningElem = document.getElementById('spinning'); //The spinning number
7
8//event listener
9document
10 .getElementById('buttonPressed')
11 .addEventListener('click', buttonPressed);
12
13//When the stop button is pushed
14function buttonPressed() {
15 pushed = true;
16}
17
18//set the target Int
19function setTargetInt() {
20 var targetElem = document.getElementById('targetNum');
21 targetInt = Math.floor(Math.random() * 101);
22 targetElem.innerHTML = targetInt;
23}
24
25//sleep const
26const sleep = (milliseconds) => {
27 return new Promise((resolve) => setTimeout(resolve, milliseconds));
28};
29
30//number spinner
31const spin = async () => {
32 for (var i = 0; i < 101; i++) {
33 if (i == 100) {
34 i = 0;
35 }
36 if (pushed == true) {
37 stop(i); //Trigger this function when the STOP button has been pushed
38 break;
39 } else {
40 spinningElem.innerHTML = i;
41 await sleep(75); //Paste this
42 }
43 }
44};
45
46function stop(i) {
47 var offBy = Math.abs(targetInt - (i - 1));
48 var message;
49
50 if (offBy == 0) {
51 message = 'You Win!';
52 } else {
53 message = 'Oh no, you lose! Off by ' + offBy.toString();
54 }
55 var result = document.getElementById('result');
56 result.innerHTML = message;
57}
58
59setTargetInt();
60spin();

And that’s it!

I would like to thank Scrimba for organizing this (it was a lot of fun!). And congratulations to @bhagwan_gb, who won the big prize!

See you all on #JavaScriptmas 2021!

Photo by Michael Hacker on Unsplash

More articles from Javier Zapata

Servicios de pago ofrecidos gratuitamente debido al Coronavirus COVID-19

Recursos para sobrellevar el confinamiento.

March 20th, 2020 · 1 min read

Diez cosas que he aprendido tras tres años yendo al gimnasio

JUST DO IT.

February 1st, 2020 · 4 min read
© 2020 Javier Zapata
Link to $https://www.linkedin.com/in/jzfgoLink to $https://github.com/jzfgoLink to $https://twitter.com/jzfgoLink to $https://instagram.com/jzfgo