[{"data":1,"prerenderedAt":827},["ShallowReactive",2],{"sideproject-en-menuescolar-gestor-menus-escolars":3},{"id":4,"title":5,"body":6,"date":789,"description":790,"extension":791,"featured":792,"image":793,"locale":794,"meta":795,"navigation":814,"order":750,"path":815,"repo":816,"seo":817,"status":818,"stem":819,"technologies":820,"url":816,"__hash__":826},"sideprojects\u002Fen\u002Fsideprojects\u002Fmenuescolar-gestor-menus-escolars.md","menuescolar.es - School Menus Manager (2025)",{"type":7,"value":8,"toc":748},"minimark",[9,13,17,22,34,41,48,52,57,102,106,132,136,140,143,158,162,167,181,186,200,205,219,222,225,242,246,250,253,257,260,271,275,278,289,293,296,307,311,314,334,337,341,345,365,369,392,396,410,414,418,444,448,471,475,504,508,512,563,567,605,609,612,650,654,663,667,699,703,714,717,729,735,737,743],[10,11,5],"h1",{"id":12},"menuescolares-school-menus-manager-2025",[14,15,16],"p",{},"A SaaS platform to transform school menu PDFs into accessible digital calendars",[18,19,21],"h2",{"id":20},"the-project","The Project",[14,23,24,28,29,32],{},[25,26,27],"strong",{},"menuescolar.es"," is a web application that solves a real problem: many schools publish monthly menus in PDF format, which makes quick consultation difficult for families.",[30,31],"br",{},[30,33],{},[14,35,36,37,39],{},"The solution: a platform where each school can have its own public page with today's menu always visible and updated.",[30,38],{},[30,40],{},[14,42,43,44,46],{},"The process is simple: the school uploads a PDF of the monthly menu, Artificial Intelligence (Google Gemini) automatically extracts the meals for each day, and in seconds it's published in web format accessible from any device.",[30,45],{},[30,47],{},[18,49,51],{"id":50},"how-it-works","How It Works",[53,54,56],"h3",{"id":55},"for-schools-managers","For Schools (Managers)",[58,59,60,72,78,84,90,96],"ol",{},[61,62,63,66,67,71],"li",{},[25,64,65],{},"Registration"," → Account and organization creation with unique URL (example: ",[68,69,70],"code",{},"menuescolar.es\u002Fescola-abellerol",")",[61,73,74,77],{},[25,75,76],{},"Configuration"," → Customization with logo, description, and language preferences",[61,79,80,83],{},[25,81,82],{},"PDF Upload"," → Upload monthly calendar in PDF format",[61,85,86,89],{},[25,87,88],{},"AI Processing"," → Google Gemini analyzes the document and extracts structured meals",[61,91,92,95],{},[25,93,94],{},"Publication"," → Menus become automatically available at the public URL",[61,97,98,101],{},[25,99,100],{},"Share"," → Send the link to families and school staff",[53,103,105],{"id":104},"for-families-visitors","For Families (Visitors)",[58,107,108,114,120,126],{},[61,109,110,113],{},[25,111,112],{},"Direct access"," → Visit the school's URL (no registration needed)",[61,115,116,119],{},[25,117,118],{},"Visualization"," → Check today's menu or browse the monthly calendar",[61,121,122,125],{},[25,123,124],{},"Multi-device"," → Works perfectly on mobile, tablet, and desktop",[61,127,128,131],{},[25,129,130],{},"Multi-language"," → Available in Catalan, Spanish, English, Basque, and Galician",[18,133,135],{"id":134},"technical-architecture","Technical Architecture",[53,137,139],{"id":138},"multi-tenant-system","Multi-Tenant System",[14,141,142],{},"Each school has its data completely isolated:",[144,145,146,149,152,155],"ul",{},[61,147,148],{},"A database with strict organization → meals relationships",[61,150,151],{},"A single owner-user per organization (1:1 model)",[61,153,154],{},"Automatically generated unique URLs (slugs)",[61,156,157],{},"Logos and customized settings for each center",[53,159,161],{"id":160},"security-and-performance","Security and Performance",[14,163,164],{},[25,165,166],{},"Authentication and Authorization:",[144,168,169,172,175,178],{},[61,170,171],{},"JWT tokens managed by PocketBase",[61,173,174],{},"Ownership validation in every modification operation",[61,176,177],{},"AI API keys kept on server (never exposed to client)",[61,179,180],{},"Security headers with Helmet (CSP, X-Frame-Options, etc.)",[14,182,183],{},[25,184,185],{},"Abuse Protection:",[144,187,188,191,194,197],{},[61,189,190],{},"Rate limiting: 10 general requests every 15 minutes",[61,192,193],{},"PDF rate limiting: maximum 5 uploads per hour",[61,195,196],{},"CORS configured with allowed origins",[61,198,199],{},"Format and file size validation",[14,201,202],{},[25,203,204],{},"Optimizations:",[144,206,207,210,213,216],{},[61,208,209],{},"Multi-stage build with Docker (lightweight image)",[61,211,212],{},"Static cache with Vite hash (1 year)",[61,214,215],{},"TypeScript compilation for error detection at development time",[61,217,218],{},"Lazy loading of React components",[53,220,88],{"id":221},"ai-processing",[14,223,224],{},"The brain of the system is Google Gemini:",[58,226,227,230,233,236,239],{},[61,228,229],{},"The PDF is converted to Base64 for secure transport",[61,231,232],{},"It's sent to Gemini with a specific prompt to extract menus",[61,234,235],{},"The AI returns structured data (date + meal)",[61,237,238],{},"Data is validated and saved to the database",[61,240,241],{},"Immediately available on the public web",[18,243,245],{"id":244},"challenges-overcome","Challenges Overcome",[53,247,249],{"id":248},"extraction-accuracy","🤖 Extraction Accuracy",[14,251,252],{},"School menu PDFs come in very varied formats (tables, free text, images). Gemini has proven flexible enough to adapt to different layouts and correctly extract the data.",[53,254,256],{"id":255},"multi-tenant-isolation","🏢 Multi-Tenant Isolation",[14,258,259],{},"Ensuring that each school only sees and modifies its own data required:",[144,261,262,265,268],{},[61,263,264],{},"Strict foreign key relations in PocketBase",[61,266,267],{},"Ownership validation middleware in each endpoint",[61,269,270],{},"Exhaustive unauthorized access tests",[53,272,274],{"id":273},"email-system","📧 Email System",[14,276,277],{},"Integration with Resend to send:",[144,279,280,283,286],{},[61,281,282],{},"Welcome emails",[61,284,285],{},"Account verification",[61,287,288],{},"New menu notifications (future)",[53,290,292],{"id":291},"internationalization","🌍 Internationalization",[14,294,295],{},"Support for 5 languages with a 94KB translation file covering:",[144,297,298,301,304],{},[61,299,300],{},"Complete interface (buttons, messages, errors)",[61,302,303],{},"Date formats adapted to each language",[61,305,306],{},"Configuration per user and per organization",[53,308,310],{"id":309},"docker-deployment","🐳 Docker Deployment",[14,312,313],{},"Multi-stage Dockerfile that:",[58,315,316,322,328],{},[61,317,318,321],{},[25,319,320],{},"Stage 1",": Compiles React frontend with Vite",[61,323,324,327],{},[25,325,326],{},"Stage 2",": Copies build and prepares Node.js backend",[61,329,330,333],{},[25,331,332],{},"Result",": A single image serving static frontend + API",[14,335,336],{},"Compatible with Dokploy for automated deployments from GitHub.",[18,338,340],{"id":339},"tech-stack","Tech Stack",[53,342,344],{"id":343},"frontend","Frontend",[144,346,347,350,353,356,359,362],{},[61,348,349],{},"React 18 with TypeScript",[61,351,352],{},"Vite (ultra-fast build tool)",[61,354,355],{},"React Router 7 (client-side routing)",[61,357,358],{},"Tailwind CSS (utility-first styling)",[61,360,361],{},"PocketBase SDK (authentication client)",[61,363,364],{},"pdfjs-dist (PDF preview)",[53,366,368],{"id":367},"backend","Backend",[144,370,371,374,377,380,383,386,389],{},[61,372,373],{},"Node.js 22 & Express",[61,375,376],{},"PocketBase (SQLite database + auth)",[61,378,379],{},"Google Gemini API (AI extraction)",[61,381,382],{},"Resend (transactional email service)",[61,384,385],{},"Multer (file upload management)",[61,387,388],{},"Helmet (security headers)",[61,390,391],{},"Rate Limit (anti-abuse protection)",[53,393,395],{"id":394},"infrastructure","Infrastructure",[144,397,398,401,404,407],{},[61,399,400],{},"Docker (containerization)",[61,402,403],{},"Dokploy (automated deployment)",[61,405,406],{},"GitHub (version control)",[61,408,409],{},"PocketBase (separate hosting for auth and data)",[18,411,413],{"id":412},"main-features","Main Features",[53,415,417],{"id":416},"for-school-managers","For School Managers",[144,419,420,423,426,429,432,435,438,441],{},[61,421,422],{},"✅ Registration and organization creation with unique URL",[61,424,425],{},"✅ Upload monthly menu PDFs",[61,427,428],{},"✅ Automatic menu extraction with AI",[61,430,431],{},"✅ Customization with logo and description",[61,433,434],{},"✅ Default language configuration",[61,436,437],{},"✅ Mandatory email validation",[61,439,440],{},"✅ Complete menu management (editing, deletion)",[61,442,443],{},"✅ Private dashboard with statistics",[53,445,447],{"id":446},"for-families","For Families",[144,449,450,453,456,459,462,465,468],{},[61,451,452],{},"✅ Public access without registration",[61,454,455],{},"✅ Today's menu highlighted view",[61,457,458],{},"✅ Browseable monthly calendar",[61,460,461],{},"✅ Responsive design (mobile, tablet, desktop)",[61,463,464],{},"✅ Language switch (5 languages available)",[61,466,467],{},"✅ Fast and optimized loading",[61,469,470],{},"✅ Shareable friendly URLs",[53,472,474],{"id":473},"system-features","System Features",[144,476,477,480,483,486,489,492,495,498,501],{},[61,478,479],{},"✅ Multi-tenant with data isolation",[61,481,482],{},"✅ One organization per user",[61,484,485],{},"✅ Auto-generated unique slugs",[61,487,488],{},"✅ Template system (modern\u002Fclassic\u002Fcolorful)",[61,490,491],{},"✅ Duplicate menu detection (date + organization)",[61,493,494],{},"✅ PDF format validation",[61,496,497],{},"✅ Error handling with clear messages",[61,499,500],{},"✅ Server logs for debugging",[61,502,503],{},"✅ Health check endpoint for monitoring",[18,505,507],{"id":506},"roadmap-and-future-improvements","Roadmap and Future Improvements",[53,509,511],{"id":510},"planned-features","Planned Features",[144,513,514,521,527,533,539,545,551,557],{},[61,515,516,517,520],{},"🔄 ",[25,518,519],{},"Multiple menus per organization",": Standard menu, celiac, vegetarian, etc.",[61,522,516,523,526],{},[25,524,525],{},"Collaborators",": Allow multiple users per school",[61,528,516,529,532],{},[25,530,531],{},"Email notifications",": Automatic alerts for new menus",[61,534,516,535,538],{},[25,536,537],{},"Custom domains",": schoolmenu.escolapios.cat",[61,540,516,541,544],{},[25,542,543],{},"Public API",": Integration with other school systems",[61,546,516,547,550],{},[25,548,549],{},"Mobile app",": Native versions for iOS and Android",[61,552,516,553,556],{},[25,554,555],{},"Export",": Download menus in different formats",[61,558,516,559,562],{},[25,560,561],{},"Analytics",": Usage statistics and views",[53,564,566],{"id":565},"technical-improvements","Technical Improvements",[144,568,569,575,581,587,593,599],{},[61,570,516,571,574],{},[25,572,573],{},"Automated tests",": Coverage with Jest and React Testing Library",[61,576,516,577,580],{},[25,578,579],{},"CI\u002FCD pipeline",": GitHub Actions for tests and deployment",[61,582,516,583,586],{},[25,584,585],{},"Monitoring",": Integration with Sentry for error tracking",[61,588,516,589,592],{},[25,590,591],{},"Performance",": Lighthouse score 90+ in all categories",[61,594,516,595,598],{},[25,596,597],{},"SEO",": Dynamic meta tags for each organization",[61,600,516,601,604],{},[25,602,603],{},"PWA",": Offline capabilities with Service Workers",[18,606,608],{"id":607},"technical-documentation","Technical Documentation",[14,610,611],{},"The project includes comprehensive documentation:",[144,613,614,620,626,632,638,644],{},[61,615,616,619],{},[25,617,618],{},"README.md"," - Quick start guide and local setup",[61,621,622,625],{},[25,623,624],{},"DEPLOYMENT.md"," - Production deployment with Dokploy",[61,627,628,631],{},[25,629,630],{},"MULTI_TENANT_SETUP.md"," - Multi-tenant schema configuration",[61,633,634,637],{},[25,635,636],{},"POCKETBASE_SETUP.md"," - PocketBase installation and initialization",[61,639,640,643],{},[25,641,642],{},"PDF_UPLOAD_IMPLEMENTATION.md"," - PDF processing details",[61,645,646,649],{},[25,647,648],{},"TESTING_CHECKLIST.md"," - Testing scenarios and edge cases",[18,651,653],{"id":652},"project-status","Project Status",[14,655,656,659,660],{},[25,657,658],{},"Current status",": ✅ ",[25,661,662],{},"95% complete and ready for testing",[53,664,666],{"id":665},"implemented","Implemented",[144,668,669,672,675,678,681,684,687,690,693,696],{},[61,670,671],{},"✅ Functional multi-tenant architecture",[61,673,674],{},"✅ Authentication and authorization system",[61,676,677],{},"✅ Complete organization CRUD",[61,679,680],{},"✅ PDF upload and AI processing",[61,682,683],{},"✅ Public interface for organizations",[61,685,686],{},"✅ 5-language internationalization",[61,688,689],{},"✅ Automated Docker deployment",[61,691,692],{},"✅ Transactional email system",[61,694,695],{},"✅ Rate limiting and security",[61,697,698],{},"✅ Complete documentation",[53,700,702],{"id":701},"in-polish","In Polish",[144,704,705,708,711],{},[61,706,707],{},"🔧 Header branding",[61,709,710],{},"🔧 Component route refinement",[61,712,713],{},"🔧 Performance optimization",[715,716],"hr",{},[14,718,719,722,723],{},[25,720,721],{},"Try the platform",": ",[724,725,726],"a",{"href":726,"rel":727},"https:\u002F\u002Fmenuescolar.es",[728],"nofollow",[14,730,731,734],{},[25,732,733],{},"GitHub",": Private code (available upon request)",[715,736],{},[14,738,739],{},[740,741,742],"em",{},"Launch date: November 2025",[14,744,745],{},[740,746,747],{},"Developed by Albert Sarlé",{"title":749,"searchDepth":750,"depth":750,"links":751},"",2,[752,753,758,763,770,775,780,784,785],{"id":20,"depth":750,"text":21},{"id":50,"depth":750,"text":51,"children":754},[755,757],{"id":55,"depth":756,"text":56},3,{"id":104,"depth":756,"text":105},{"id":134,"depth":750,"text":135,"children":759},[760,761,762],{"id":138,"depth":756,"text":139},{"id":160,"depth":756,"text":161},{"id":221,"depth":756,"text":88},{"id":244,"depth":750,"text":245,"children":764},[765,766,767,768,769],{"id":248,"depth":756,"text":249},{"id":255,"depth":756,"text":256},{"id":273,"depth":756,"text":274},{"id":291,"depth":756,"text":292},{"id":309,"depth":756,"text":310},{"id":339,"depth":750,"text":340,"children":771},[772,773,774],{"id":343,"depth":756,"text":344},{"id":367,"depth":756,"text":368},{"id":394,"depth":756,"text":395},{"id":412,"depth":750,"text":413,"children":776},[777,778,779],{"id":416,"depth":756,"text":417},{"id":446,"depth":756,"text":447},{"id":473,"depth":756,"text":474},{"id":506,"depth":750,"text":507,"children":781},[782,783],{"id":510,"depth":756,"text":511},{"id":565,"depth":756,"text":566},{"id":607,"depth":750,"text":608},{"id":652,"depth":750,"text":653,"children":786},[787,788],{"id":665,"depth":756,"text":666},{"id":701,"depth":756,"text":702},"2025-11-20T12:00:00Z","Multi-tenant SaaS platform to manage school meal calendars with AI and display them on a personalized webapp for each school.","md",false,"https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_900\u002Fv1763640341\u002Fmenus_escolars_fearrf.png","en",{"slug":796,"type":797,"categories":798,"tags":799,"achievements":808,"liveUrl":726},"menuescolar-gestor-menus-escolars","sideproject",[797],[800,801,802,803,804,805,806,807],"React & TypeScript","Node.js & Express","PocketBase","Google Gemini AI","Multi-tenant SaaS","Docker","Tailwind CSS","Vite",[809,810,811,812,813],"AI-powered menu extraction from PDFs","Multi-tenant architecture with data isolation","5 languages support (CA, ES, EN, EU, GL)","Secure JWT authentication system","Automated deployment with Docker",true,"\u002Fen\u002Fsideprojects\u002Fmenuescolar-gestor-menus-escolars",null,{"title":5,"description":790},"published","en\u002Fsideprojects\u002Fmenuescolar-gestor-menus-escolars",[821,801,822,803,806,807,805,823,824,825],"React 18 & TypeScript","PocketBase (Auth & Database)","Resend (Email)","Multi-tenant Architecture","Internationalization (i18n)","RFgd47kDWBjXBpfiovvUfP2Z6UVUtYfhPyd8twQx3y4",1779354921589]