
Projects
Personal project · 2026 · Applied AI · RAG
Ask Leonardo da Vinci
Leonardo da Vinci left more than 7,500 written pages. For the first time, software uses them to speak with him without inventing answers. A neural network finds, among 1,565 passages from his notebooks, the ones that answer each question, and the system checks that every quotation exists in the original.
“Act as if you were…” is one of the most common requests an artificial intelligence receives. When asked to be someone, the model claims it can, but it invents answers, changes the way that person speaks and gives opinions on things they never considered. To stop inventing it would need an enormous amount of context about that person: their thoughts, their experiences, what they once wondered. Leonardo left it in writing. All his life he filled notebooks, a mind transfusion onto paper: anatomy, optics, painting, machines, fables and even his shopping lists. His expressions, his answers and his opinions do not need to be invented: he wrote them himself.
The site welcomes visitors into Leonardo's workshop, seated right in front of him: you ask him a question and he answers in the first person, with the sources in plain view. Everything in quotation marks comes word for word from his notebooks. For those who do not know what to ask, Guided questions offer a map of 22 sections and 396 subjects, each one turned into a question with one click. What is not in his notebooks is not invented: the site says so. All of it in Spanish and English, with a 3D library, a 3D virtual museum and a vector space that shows how it works on the inside.
It runs on a RAG pipeline written from scratch, with no frameworks such as LangChain or LlamaIndex, so that every step can be controlled and measured on its own. The question becomes a vector right in the browser; if no passage is close enough, the system abstains without calling the model. If there is material, a hybrid search picks three passages, Gemini answers with them in front of it, and the code verifies every quotation.
0 of 187
Stack
- Next.js
- React
- TypeScript
- Three.js
- RAG
- Transformers.js
- Gemini API
- Python
The difference can be measured
Language model acting as Leonardo
96.9%156 of 161 quotations
Ask Leonardo da Vinci
0%0 of 187 quotations
Same model, same prompt and the same 120 questions. Every quotation was searched for in the full text, in both languages.
| Also measured | Result |
|---|---|
| Questions answerable from the notebooks that the system wrongly refused | 0, across 170 test questions |
| Notebook subjects the search finds in both languages | 94% |
evals/out/.Asking Leonardo

- Answers with sources. Leonardo answers in the first person from the retrieved passages, with the sources below and a link to each passage on Project Gutenberg.
- Verbatim quotations. Everything in quotation marks comes word for word from his notebooks. If a quotation does not match the original, it is not shown as a quotation.
- Guided questions. What do you ask one of the most brilliant minds in history? He wrote about so many things that it can even feel intimidating. That is why there is a map of 22 sections and 396 subjects he did write about, plus six suggested questions: pick a subject and one click turns it into a question. Anyone not used to AI can focus on reading the answers instead of crafting a prompt.
- What he could not write. Some things Leonardo, naturally, did not leave in his notebooks, like his own death, along with facts he rarely wrote down about himself (“what day were you born?”, “who was your teacher?”). There, the site says so and shows the exact Wikipedia excerpt, with attribution, outside his voice.
- What was lost. Over the centuries, part of his notebooks was lost. The Mona Lisa? His manuscripts never mention it, and the site shows the editor's note that confirms it. The mystery remains, but nothing is invented.
- Bilingual. The whole site, including Leonardo's answers, is complete in Spanish and English.
3D Library


Five volumes with 27 plates by Leonardo. The volumes come off the shelf, leaves turn with the curl of a real book and, on desktop, a loupe lets you read the handwriting up close.
3D Virtual museum

A gallery you walk through, on mobile too, with nine of his works and a label for each one.
Vector space
Section lit…
1,402 passages · 22 sections · 384 → 3 dimensions
It is the site's “How it works” section. It shows visitors the passages turned into vectors and how a question finds its own. It sits in a website for the general public on purpose: the technical soundness of the system is what backs every answer from Leonardo.
This is not an illustrative animation. These are the project's real vectors: the 1,402 passages the system searches, placed by its neural network and brought down from 384 dimensions to 3 so they can be seen. Light up a section and the closeness makes sense: to the neural network, passages about the same thing tend to sit together.
Architecture
The idea was the starting point, not the result. It took measuring and choosing models, calibrating the semantic search, letting only Leonardo speak (not his editor), reining in the prose AI models tend to invent, and checking that every quotation exists in the notebooks. The text the system searches is the edition Jean Paul Richter transcribed and translated in 1888, now in the public domain.
- Questionembedding in the browserIt becomes a vector on the asker's device, with no API.
- Curated caseslayer 0What is known not to be in the notebooks: a refusal with the evidence cited.
- Similarity thresholdlayer 1If no passage is close enough, it abstains without calling the model.
- Hybrid retrievalcosine + BM25 · RRFPicks the three passages that will back the answer.
- Generationlayer 2 · GeminiThe model answers, or declines, with the passages in front of it.
- Verificationlayer 3 · codeEvery quotation is compared against the original passage.
- Answerwith its sourcesIn Leonardo's voice, with the passages in plain view.
At two of the steps the system can abstain before calling the model: it would rather say the notebooks do not cover a subject than invent an answer.
- Cosine decides, fusion only orders. A ranking always has a first place, whether or not relevant material exists; what determines whether there is an answer is similarity against a calibrated threshold.
- One index and one threshold per language. With a shared threshold, the filter's accuracy dropped from 88.4% to 70.5%.
- Embeddings in the browser. The query is vectorised on the user's device: no cost per query and no quota that can run out.
- No persistent server and no vector database. The index is an int8 binary versioned in Git, BM25 is precomputed, and the 3D sections load only when someone visits them.
Tech stack
| Layer | Technology |
|---|---|
| Frontend | Next.js 16 (App Router), React 19, TypeScript, Three.js |
| Embeddings | multilingual-e5-small (384 dimensions, int8) · Transformers.js in the browser |
| Retrieval | Dense cosine + precomputed BM25, RRF fusion |
| Generation | Gemini 3.1 Flash-Lite, with GPT-OSS 120B on Groq as fallback |
| Ingestion | Python: parsing the Gutenberg edition, voice separation, chunking, embeddings and calibration |
| Evaluation | 170 questions across 8 categories · automated judge validated against human labelling |
| Security | Cloudflare Turnstile, hashed-IP rate limiting and a global daily budget |
| Hosting & CI | Vercel · GitHub Actions |
Personal project · 2026 · Data · ETL
Datos Pauta Oficial
The first and only unified database of Argentine government advertising: 540,413 advertising orders from the national government, Buenos Aires City, Buenos Aires Province and Santa Fe (2003–2025), with amounts adjusted for inflation (CPI). It is queried on the website through an explorable table, rankings and the “Cuánto recibió” (how much it received) search by supplier, media outlet or media group, with the methodology published.
Before, that data was scattered across portals, spreadsheets and PDFs from each jurisdiction, with no unified, simple way to access it or compare it. The Python ETL includes a Buenos Aires Province 2020–2024 dataset reconstructed from more than 500 PDF resolutions, data that does not exist in any open data portal.
Backendless architecture: the SQLite database (~173 MB) is hosted on Cloudflare R2 and the browser queries only the bytes it needs through HTTP Range Requests.
540,413
Stack
- Python
- ETL
- SQLite
- Astro
- React
- TypeScript
- Cloudflare R2/Pages
Buenos Aires Province, rebuilt from PDFs
Buenos Aires Province does not publish its 2020 to 2024 advertising orders in open datasets: the information sat in individual PDF resolutions. A script extracted the data resolution by resolution (more than 500) to reconstruct the detail of each order: supplier, media outlet, amount and file number. That stretch is a dataset of the project's own.
Coverage
PBA 2020–2024 · Rebuilt from more than 500 PDF resolutions
540,413 orders in total, 2003–2025
| Jurisdiction | Period | Orders |
|---|---|---|
| Nación | 2009–2022 | 225,367 |
| CABA | 2003–2024 | 209,877 |
| Santa Fe | 2008–2023 | 61,187 |
| PBA | 2020–2025 | 43,982 |
| Total | 2003–2025 | 540,413 |
Architecture
The project runs with no backend or servers: the full SQLite database (~173 MB) lives on Cloudflare R2 split into chunks, and the browser queries only the bytes it needs through HTTP Range Requests, thanks to sql.js-httpvfs.
- Official sourcesCSV · Excel · PDFEach jurisdiction publishes in its own way.
- ExtractionPythonOne extractor per jurisdiction.
- Unificationunificar.py · validar.pyA single validated canonical CSV: 8 columns, 540,413 rows.
- Databasebuild_db.py · SQLiteAmounts deflated by CPI (INDEC) and precomputed caches.
- StorageCloudflare R2The database, split into 20 MiB chunks.
- Queryingsql.js-httpvfsSQL from the browser, which requests only the chunks it needs.
- WebsiteAstro · React · Cloudflare PagesStatic, with React islands: no backend or servers.
Features
- Explorable table of 540 thousand orders. Cross filters by jurisdiction and year, grouping by supplier or media outlet with expandable rows. The initial state is precomputed into the HTML, and the database opens only when the user interacts. The value shown is the exact raw figure from the official source.
- “Cuánto recibió” (how much it received). Search by supplier, media outlet or media group; group mode consolidates each holding's companies according to the Media Ownership Monitor Argentina, with the source cited. It generates a 1080 × 1080 image for sharing, with pure Canvas, and links that restore the search.
- Rankings of suppliers, media outlets and groups by jurisdiction and year, served from caches precomputed at build time.
- Public methodology: sources, deflation and normalization criteria documented on the site.


Data decisions
- Deflated, not nominal amounts. Everything is in constant pesos (INDEC CPI): comparing 2009 advertising with 2024 in nominal pesos makes no sense in Argentina.
- The exact figure, even at the cost of convenience. The table shows every order as it appears in the official source, without correcting or grouping names, even though that makes it less comfortable to read: the same supplier can appear spelled several ways. Rankings and “Cuánto recibió” only unify spelling variants of the same company name, from a conservatively curated list; consolidation by media group is a separate option, with its source cited.
- Honesty about the gaps. Every source limitation (rows without a date, partial periods) is documented rather than hidden.
- An informative stance: no accusation, no opinion.
Tech stack
| Layer | Technology |
|---|---|
| ETL | Python: per-jurisdiction extractors, unification and validation |
| Database | Static SQLite, queried from the browser with sql.js-httpvfs |
| Framework | Astro 6 (static output) + React 19 (islands) |
| Language | TypeScript |
| Search | MiniSearch (client-side index) |
| Hosting | Cloudflare Pages + Cloudflare R2 |
| Deflation | INDEC CPI, amounts in constant pesos |
Freelance · Dec 2025 – Jun 2026 · Full stack
SeViVe
A booking and management application for a pilates studio, in production with more than 200 users. Students book their classes from their phones and the administration runs the studio from its panel. REST API in Python/FastAPI and PostgreSQL.
I built it as the sole developer for a studio in Canning, Buenos Aires (a franchise), from defining requirements with the client to production launch. Since development ended, I remain responsible for support, server administration and client-requested updates.
Bookings with concurrency control to prevent overbooking, expiring credits, recurring slots with automatic booking, attendance tracking and a calendar with configurable holidays. Accounts created by the administration merge automatically with the student's own account at sign-up (by national ID), preserving history, credits and slots.
It installs as a PWA on any device, with a step-by-step guide designed for non-technical users.
200+
Stack
- Flutter
- Python
- FastAPI
- PostgreSQL
- Firebase
- Docker
- Nginx
What it solves
The goal: an easy-to-use application that lets the studio delegate and organize its management, while keeping any student from breaking the business rules.
- Bookings with concurrency control. The backend validates spots, credits and schedules on every booking: two students cannot both take the same last spot.
- Recurring slots and classes. The administration creates a class once and the system generates the weekly instances around holidays; memberships book themselves every week.
- Expiring credits, deducted automatically on each booking, and cancellation with penalties for late notice or no-shows.
- Account merging by national ID. The administration can add students who do not have the app yet, so that spots and statistics are real. When that student signs up with their ID, both accounts merge and keep recurring slots, credits and history.
- Attendance. The administration takes it from the day's schedule, and the monthly calendar keeps every class taught with its occupancy and attendance.
- Admin panel: per-student history, blocking users with cancellation of their future bookings, a global pause on bookings, and holidays added from the app, with no deploy.
- Communication: announcements with push notifications and feedback after every class, with an email alert to the administration on a negative answer.
- Medical certificate: PDF or image upload, with automatic compression.
- Monthly raffle among active students, weighted by attendance, to encourage consistency.
- Installation without an app store. It is a PWA: it installs from the browser on a phone, tablet or computer, with a step-by-step guide designed for non-technical users.
Architecture
- Bookings with concurrency control
- Expiring credits
- Recurring slots, booked automatically
- Account merging by national ID
A single Flutter app (Riverpod, Freezed, GoRouter) for students and administration, served as a PWA. Sign-in is Google's, through Firebase, and the backend verifies that identity on every request.
Nginx receives the traffic over HTTPS, rate-limits requests per IP address and serves the app and the uploaded files. Behind it, the asynchronous FastAPI API (SQLModel, asyncpg) works on PostgreSQL 16, with schema changes versioned in Alembic. Push notifications go out through Firebase Cloud Messaging.
Everything runs in three Docker Compose containers (database, API and Nginx) on a VPS I manage: deployment with a script that applies the migrations, backups and troubleshooting.
Tech stack
| Layer | Technology |
|---|---|
| App | Flutter 3, Riverpod, Freezed, GoRouter, Dio · installable as a PWA |
| Backend | Python, FastAPI (async), SQLModel, asyncpg |
| Database | PostgreSQL 16, Alembic |
| Authentication | Firebase Authentication (Google) |
| Files and notifications | Firebase Storage, Firebase Cloud Messaging |
| Infrastructure | Docker Compose, Nginx, VPS on Hetzner |
Personal project · 2026 · AI · Automation
Inversiones en Argentina
Uses AI to track private investments in Argentina. It identifies the real investments, discards duplicates and publishes the results on the website and in a Telegram channel.
The information was scattered across news outlets, X accounts and official registries, and each investment shows up repeated in many outlets, written in different ways. A scheduled GitHub Actions workflow gathers those sources and adds a Google search with Gemini and Search Grounding.
A layered system decides what gets published: Gemini structures each mention, deterministic rules and Jev (TypeSafe) separate the real investments from everything else, and pgvector retrieves similar records. The same project, reported by two outlets, can read very differently, and two distinct projects in the same region can look almost identical: that is why similarity only suggests candidates, and the decision is made by identity rules and a decision model (Jev). FastAPI API and a Next.js frontend with server-side rendering, on Vercel.
Stack
- Python
- FastAPI
- Gemini
- Jev (TypeSafe)
- PostgreSQL/pgvector
- GitHub Actions
- Next.js
The pipeline of each run
- SourcesX · RSS · RIGISpecialised accounts, outlets by sector and the official registry.
- FilterdeterministicRemoves the noise: fewer tokens and simpler decisions downstream.
- ExtractionGemini + GroundingEvery mention becomes structured data.
- ValidationrulesOut go debt, loans, share purchases and launches.
- JudgementJev (TypeSafe)Is it a concrete private investment?
- Deduplicationpgvector + identitySimilarity retrieves; identity decides.
- Publishingweb · TelegramOnly what is new, unattended.
Each layer discards something and leaves the next one less noise and less room for error. Without Jev, the pipeline runs the same on its deterministic layers.
- Collection. Specialised X accounts (Apify API), outlets from different sectors via RSS, paginated until the whole window is covered, and the official RIGI registry (the incentive regime for large investments) incrementally: a fingerprint of each project is stored and only new or changed ones are re-sent.
- Relevance filter. A deterministic filter discards the structural noise in the feeds before it reaches Gemini. It does more than save tokens: the following steps receive less irrelevant material, so their decisions are simpler and leave less room for error. It favours not losing anything: when in doubt it lets an item through, because over-filtering means losing a real investment.
- Extraction with Gemini and Search Grounding. The collected material goes in as the first source and Gemini searches Google for news from the period as the second, focused on the sectors the owned sources cover least. It returns each investment as structured JSON.
- Validation. A deterministic net discards whatever is not a new productive investment: debt issuance, loans, company incorporations, share purchases, contracts won by suppliers and product launches.
- Semantic judgement with Jev. A model that returns typed decisions with calibrated probability, instead of text, answers whether each record is a concrete private investment.
- Deduplication. The embedding retrieves the closest candidates; the decision is made by identity rules and Jev, answering whether two records describe the same project.
- Publishing and status report. New records are stored and published at once to the website and Telegram. Each run ends by reporting the status of every source, and fails if one that should be responding returned nothing.
Why similarity retrieves but does not decide. Over a thematically narrow corpus, the vector space compresses. Two distinct projects in the same sector and region can look as alike as the same project reported by two outlets, and no threshold separates those two cases.
Features

- Interactive timeline sorted by announcement date, with each investment's status (confirmed, announced or under evaluation), readable amounts (USD 40M, USD 1.2B), province and jobs when available.
- Real-time search across company, description and location, accent-insensitive: “Neuquen” finds the investments in Neuquén.
- Indexable: the first page is server-rendered, with structured data (JSON-LD),
sitemap.xmlandrobots.txt. - REST API in FastAPI with automatic documentation.
Tech stack
| Layer | Technology |
|---|---|
| Automation | GitHub Actions (every 72 hours) |
| Sources | Apify (X), outlet RSS feeds, official RIGI registry |
| Generative AI | Gemini 2.5 Flash with Google Search Grounding |
| Semantic judgement | Jev (TypeSafe) |
| Embeddings | gemini-embedding (768 dimensions) |
| Database | PostgreSQL (Neon) with pgvector and unaccent |
| API | Python, FastAPI (async), Mangum, on Vercel |
| Frontend | Server-rendered Next.js 16, TypeScript, Tailwind CSS, Framer Motion |
| Publishing | Telegram Bot API |
Personal project · 2026 · Computer vision
FaceHunt 2
A facial recognition system with AI and deep learning that, from a photo, finds every appearance of a person in hours of video, even in profile, partially covered or at low resolution.
Everything is processed locally with GPU acceleration, without sending data to the cloud. It is a desktop application that returns exact time ranges, with a thumbnail, a mini-clip and direct access to each moment, for a local file or a YouTube video. Applicable to security, investigation and video analysis.
Facial recognition with InsightFace (ArcFace, 512-dimensional embeddings) on ONNX Runtime, with GPU acceleration based on the available hardware and cross-frame tracking. It is a full rebuild of the first version, FaceHunt (2025): from TensorFlow on CPU to ONNX Runtime on GPU.
7 s
Stack
- Python
- InsightFace
- ArcFace
- ONNX Runtime
- FastAPI
- pywebview
- pytest
What it does
You load one or more photos of the person and a video (a local file or a YouTube URL). The application returns the exact time ranges in which they appear, each with a thumbnail of the face, an animated mini-clip of the moment and a direct jump, plus a clickable timeline of the whole video.
How a search works
- Reference. The photos are validated and a 512-dimensional embedding is computed per face; with several photos they are averaged, for a more stable identification.
- Video. The file or URL is validated (YouTube is downloaded with yt-dlp).
- Background analysis. A producer thread reads frames and another analyses them: detection with SCRFD, cosine comparison against the reference and tracking of each face across frames.
- Live progress. Progress, remaining time and matches arrive through Server-Sent Events, and the search can be cancelled at any moment.
- Results. Matches are grouped into ranges, each with its thumbnail, mini-clip and link.

Features
- 100% local and private. Photos and videos never leave the computer: no online server and no cloud upload.
- Current facial recognition. InsightFace
antelopev2(SCRFD-10G + ArcFace ResNet100 trained on Glint360K, 512-dimensional embeddings) on ONNX Runtime, with flip-TTA so appearances are not missed. - Automatic GPU acceleration. It picks the best available provider (CUDA, DirectML or CPU): it works with NVIDIA, AMD and Intel cards on Windows.
- Temporal tracking. It groups faces into tracklets across frames and compares against the aggregated embedding, which recovers appearances in degraded frames (in profile, covered or at low resolution).
- Two modes: fast (2 frames per second) and thorough (5 frames per second and smaller faces).
- One-click executable for Windows, with pytest tests for the pure logic.
From FaceHunt to FaceHunt 2
| FaceHunt (2025) | FaceHunt 2 (2026) | |
|---|---|---|
| Engine | TensorFlow on CPU | ONNX Runtime on GPU (CUDA, DirectML or CPU) |
| Model | FaceNet, 128 dimensions | ArcFace ResNet100, 512 dimensions |
| Result | Loose timestamps | Ranges with thumbnail, mini-clip and timeline |
| Use | Web interface: demo on Hugging Face Spaces or a Docker image | One-click desktop application, no server or cloud |
Tech stack
| Layer | Technology |
|---|---|
| Recognition | InsightFace (SCRFD + ArcFace), ONNX Runtime |
| Video | OpenCV, yt-dlp |
| Backend | Python, FastAPI, Server-Sent Events |
| Interface | HTML, CSS and JavaScript with no build step, in a pywebview window |
| Testing and packaging | pytest, PyInstaller, Windows installer |
First version

FaceHunt 2025
Deep learning facial recognition in video: DeepFace (FaceNet) with two analysis modes (high precision with RetinaFace, balanced with MTCNN), batched frame extraction with OpenCV and YouTube downloads with yt-dlp. It started as a Tkinter desktop app and gained a FastAPI API with a step-by-step web interface. It was published as a web demo on Hugging Face Spaces and as an image on Docker Hub, and runs on Windows, macOS and Linux.
- Python
- DeepFace
- FaceNet
- RetinaFace
- FastAPI
- Docker
- Hugging Face Spaces
Personal project · 2026 · Chrome extension
MementoLife
A Chrome extension published on the Chrome Web Store that replaces the new tab with a grid of the weeks of a life, calculated from the date of birth.
Light, dark or system theme, a full interface in 6 languages and a historical fact for every day of the year. No network connections and a single permission (storage): the date of birth is stored only on the device.
TypeScript with no framework or bundler: native ESM compiled with tsc. Unit and snapshot tests with Vitest, end-to-end tests with Playwright on the packaged extension, and CI with GitHub Actions.
3 paths
Stack
- TypeScript
- Manifest V3
- SVG
- Vitest
- Playwright
- GitHub Actions
What it does
Every dot is one week. The weeks lived are drawn filled in, the ones ahead barely there, and a ring marks the current week. Life expectancy can be set between 20 and 100 years, and every day shows a different historical fact, in all 6 languages, which can be turned off.
The grid, live
The grid on this page is drawn with the same technique as the extension: three accumulated SVG paths (past, future and ring) instead of one element per cell. In the extension, that avoids 4,160 elements and brings rendering down from 10.00 ms to 1.30 ms, measured against the alternative of one element per week.
Screenshots




Privacy
- No network connections.
- A single permission:
storage. - The date of birth is stored in
chrome.storage.local, never insync: it never travels through the Google account.
Tech stack
| Layer | Technology |
|---|---|
| Language | TypeScript |
| Runtime | No framework, no bundler: native ESM, tsc as the only build step |
| Render | SVG from a pure core: no DOM, no chrome.*, the date comes in as a parameter |
| Testing | Vitest (unit + snapshots) + Playwright (end-to-end on the real package) |
| Packaging | Manifest V3, a single permission, no service worker |
| CI | GitHub Actions on every push |
Project history
MementoLife started as an Android app that put the grid on the lock screen. It worked, but MIUI and One UI silently ignore attempts to update only the lock screen (setBitmap(..., FLAG_LOCK) does not fail: it does nothing). Rather than break one of the project's design decisions, the platform changed.
Rugidos Fiestas Tandil · 2023 – present · Commercial website
Rugidos Fiestas
Commercial website for an events company, mobile first and optimized for local search.
I designed, built and maintain the company's commercial website, in production. The current version is built with Next.js, TypeScript, Tailwind CSS and Framer Motion, with my own UX/UI design.
Heavy effects are turned off on phones to keep scrolling smooth, the Instagram feed and the reviews are local content instead of external widgets, and structured data helps with local search.
95 / 100
Stack
- Next.js
- TypeScript
- Tailwind CSS
- Framer Motion
- Lenis
- shadcn/ui
- Vercel
The website
The website is the sales channel of Rugidos Fiestas Tandil, a children's events business: it has to show the venue, the services and the extras, and lead visitors to a WhatsApp enquiry. That is why it was designed for phones first.


Sections and features
- Our venue: a gallery with a keyboard-navigable lightbox and a grid of the venue's features.
- Star Academy: on desktop, a radial mask that follows the cursor reveals the “superstar” look under the casual one; on phones, a before/after slider dragged with a finger.
- Real hover detection (
hover: hoverandpointer: fine) instead of a width breakpoint, so the experience does not break on touch tablets. - Reviews: a Google Reviews summary and marquees with a manual pause, with no dependency on widgets.
- Contact: a WhatsApp button that opens the chat with the message already written, the venue's address with its Google map, the social links and a selection of Instagram posts.
Performance
| Lighthouse | Mobile | Desktop |
|---|---|---|
| Performance | 95 | 99 |
| Accessibility | 96 | 96 |
| Best practices | 100 | 100 |
| SEO | 100 | 100 |
| LCP | 2.9 s | 0.8 s |
| CLS | 0 | 0 |
- Parallax, the cards' 3D effect, frosted glass and entrance animations are turned off on phones, so content arrives sooner and scrolling stays smooth on mid-range devices.
- The logo went from PNG (127 KB) to WebP (7 KB), and the hero's critical asset is requested with high priority.
- The venue and extras photos were batch-processed with Sharp: from ~15.6 MB to ~1.8 MB.
- The Instagram feed and the Google reviews are served as local content: no external dependencies and no layout shifts while they load.

Tech stack
| Layer | Technology |
|---|---|
| Framework | Next.js (App Router), TypeScript |
| Styling and components | Tailwind CSS, shadcn/ui, Radix UI, Lucide |
| Motion | Framer Motion, Lenis (desktop only) |
| Deploy | Vercel, Vercel Analytics |
First version

RugidosWebSite 2023 2023 – 2026
In production from 2023 until it was replaced by the current version. HTML, CSS and JavaScript with no frameworks, with automatic deploy to cPanel. Built without AI assistance.
- HTML5
- CSS3
- JavaScript
Rugidos Fiestas Tandil · 2025 · Desktop application
FreeMagicMirror
A touchscreen photo booth application in use at the Rugidos venue, distributed as a portable Windows executable.
Capture with an animated countdown, a multitouch editor with free drawing and stickers that scale and rotate with the fingers, and automatic saving to a local gallery. Designed for children: animated videos, smooth transitions and large controls.
Python, Kivy and OpenCV. A hidden admin panel (five taps in a corner) sets the camera, orientation and monitor, and the app runs in kiosk mode, full screen and borderless.
Stack
- Python
- Kivy
- OpenCV
- PyInstaller
- Docker
The on-screen flow
- Start: a looping video invites people to touch the screen.
- Pose: an animation suggests poses for the photo.
- Countdown and capture from the camera.
- Editor: free drawing in five colors, stickers that scale, rotate and move with several fingers, and undo.
- Saving to a local gallery, with incremental numbering.
Technical decisions
- The camera starts after the videos, at 10 frames per second, so playback does not stutter.
- Videos decoded with FFPyPlayer, integrated into Kivy, and the whole canvas (photo, drawings and stickers) exported to PNG.
- Hidden admin panel: camera detection with OpenCV, portrait or landscape orientation with window adjustment, and choice of output monitor.
- Paths that detect whether the app runs packaged with PyInstaller, so the executable finds its resources when copied to another computer.
Distribution
A portable Windows executable (.exe) with no external dependencies, published in the repository's Releases, and an image on Docker Hub for Linux.

About
I build complete software products, from design to production, with a focus on backend and applied artificial intelligence. I have experience with end-to-end projects, personal and freelance, with real clients and users, using Python, React and Next.js, PostgreSQL and REST APIs.
Building on the foundation my Systems Engineering studies give me (programming logic, mathematical analysis and algorithm design), I teach myself current technologies and apply them in personal projects with published code. I combine this with running the operations of a small business, where I am gaining knowledge of business administration, product and team management.
I am proficient in AI applied to work and to product solutions: it is part of my development process and of the products I build. The product, design and architecture decisions are my own.
- Location
- Argentina · Remote work
- Languages
- Spanish (native) · English (B2)
- Citizenship
- Argentine and Italian (EU passport)
- Education
- Systems Engineering · UNICEN · 2022 – present
- Certification
- Scrum Fundamentals Certified (SFC) · VMEdu · 2024
Experience
Rugidos Fiestas Tandil
Events SME · Tandil, Argentina
- Software DeveloperSep 2023 – present
- Operations ManagerSep 2019 – present
- Designed, built and maintain the company's commercial website, in production: first version in 2023 and a full redesign in 2026, with my own UX/UI design.
- Built FreeMagicMirror, a touchscreen photo booth application used at events.
- Manage daily operations: event organization, internal process optimization and service model improvements, some of them implemented with in-house software.
SeViVe
Pilates studio (franchise) · Canning, Buenos Aires
- Freelance Full Stack DeveloperDec 2025 – Jun 2026
- Built, as the sole developer, a booking and management application with more than 200 users, from defining requirements with the client to production launch: bookings with concurrency control, expiring credits, recurring slots and an admin panel.
- Since development ended, I remain responsible for support, server administration and client-requested updates.
Navkok Security Group SRL
Private security company · Argentina
- Freelance Web DeveloperMar 2026 – Apr 2026
- Redesigned the corporate website, from selling the project to its completion: information architecture, UX/UI design, corporate copy and multimedia content produced with AI tools.


