[{"data":1,"prerenderedAt":475},["ShallowReactive",2],{"project-supermarket-stock":3,"project-supermarket-stock-more":175},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":7,"summary":9,"stack":10,"repo":17,"demo":7,"year":18,"role":19,"featured":20,"order":21,"body":22,"_type":169,"_id":170,"_source":171,"_file":172,"_stem":173,"_extension":174},"\u002Fes\u002Fprojects\u002Fsupermarket-stock","projects",false,"","Sistema de Stock de Supermercado","Backend de gestión de inventario para una cadena de supermercados, construido en Laravel con un núcleo framework-agnostic y domain-driven. El dominio se testea por completo sin Laravel — PHP puro.",[11,12,13,14,15,16],"Laravel 11","PHP 8.3","PostgreSQL","Pest","Docker","GitHub Actions","https:\u002F\u002Fgithub.com\u002FFabrizio-Alvarez\u002FEjercicio-Arquitectura",2025,"Autor",true,2,{"type":23,"children":24,"toc":163},"root",[25,34,40,46,51,60,106,118,124,152,158],{"type":26,"tag":27,"props":28,"children":30},"element","h2",{"id":29},"descripción-general",[31],{"type":32,"value":33},"text","Descripción general",{"type":26,"tag":35,"props":36,"children":37},"p",{},[38],{"type":32,"value":39},"Un servicio backend para gestionar el stock de un supermercado: productos, niveles\nde stock, movimientos (recepciones, ventas, ajustes) y las reglas asociadas. Es\nprimero un estudio de arquitectura y segundo un servicio funcional — construido\npara demostrar un núcleo PHP limpio, framework-agnostic, capaz de sobrevivir a un\ncambio de framework.",{"type":26,"tag":27,"props":41,"children":43},{"id":42},"arquitectura-ddd-en-capas",[44],{"type":32,"value":45},"Arquitectura: DDD en capas",{"type":26,"tag":35,"props":47,"children":48},{},[49],{"type":32,"value":50},"El código está estructurado de forma que la lógica de negocio nunca dependa de\nLaravel:",{"type":26,"tag":35,"props":52,"children":53},{},[54],{"type":26,"tag":55,"props":56,"children":59},"img",{"alt":57,"src":58},"DDD en capas","\u002Fdiagrams\u002Fsupermarket-stock-ddd.svg",[],{"type":26,"tag":61,"props":62,"children":63},"ul",{},[64,76,86,96],{"type":26,"tag":65,"props":66,"children":67},"li",{},[68,74],{"type":26,"tag":69,"props":70,"children":71},"strong",{},[72],{"type":32,"value":73},"Dominio",{"type":32,"value":75}," — PHP puro. Entidades, value objects y servicios de dominio. Sin\nimports del framework, sin Eloquent, sin HTTP. Esta es la parte que se testea\nde forma aislada.",{"type":26,"tag":65,"props":77,"children":78},{},[79,84],{"type":26,"tag":69,"props":80,"children":81},{},[82],{"type":32,"value":83},"Aplicación",{"type":32,"value":85}," — casos de uso (handlers) que orquestan el dominio y declaran\ntransacciones y side effects como interfaces.",{"type":26,"tag":65,"props":87,"children":88},{},[89,94],{"type":26,"tag":69,"props":90,"children":91},{},[92],{"type":32,"value":93},"Infraestructura",{"type":32,"value":95}," — repositorios Eloquent, adaptadores del framework y\nclientes de terceros que implementan las interfaces de la capa de Aplicación.",{"type":26,"tag":65,"props":97,"children":98},{},[99,104],{"type":26,"tag":69,"props":100,"children":101},{},[102],{"type":32,"value":103},"Presentación",{"type":32,"value":105}," — una REST API que expone los casos de uso.",{"type":26,"tag":35,"props":107,"children":108},{},[109,111,116],{"type":32,"value":110},"Lo más importante es el ",{"type":26,"tag":69,"props":112,"children":113},{},[114],{"type":32,"value":115},"dominio testeado sin el framework",{"type":32,"value":117},": si Laravel\ndesapareciera mañana, las reglas de negocio y sus tests pasarían en verde.",{"type":26,"tag":27,"props":119,"children":121},{"id":120},"qué-incluye",[122],{"type":32,"value":123},"Qué incluye",{"type":26,"tag":61,"props":125,"children":126},{},[127,132,137,142,147],{"type":26,"tag":65,"props":128,"children":129},{},[130],{"type":32,"value":131},"Catálogo de productos y proyecciones de niveles de stock.",{"type":26,"tag":65,"props":133,"children":134},{},[135],{"type":32,"value":136},"Movimientos de stock como un ledger append-only con invariantes de dominio (sin\nstock negativo, totales consistentes).",{"type":26,"tag":65,"props":138,"children":139},{},[140],{"type":32,"value":141},"Endpoints REST para las operaciones comunes, validados en el límite.",{"type":26,"tag":65,"props":143,"children":144},{},[145],{"type":32,"value":146},"Un suite de tests en Pest que cubre las reglas del dominio y el comportamiento\nde la API.",{"type":26,"tag":65,"props":148,"children":149},{},[150],{"type":32,"value":151},"Entorno local dockerizado y un pipeline de GitHub Actions que ejecuta los tests\nen cada push.",{"type":26,"tag":27,"props":153,"children":155},{"id":154},"por-qué-este-proyecto",[156],{"type":32,"value":157},"Por qué este proyecto",{"type":26,"tag":35,"props":159,"children":160},{},[161],{"type":32,"value":162},"Trabajo día a día en PHP, así que quise tener un proyecto público que muestre cómo\npienso el diseño de backend en ese lenguaje: límites claros, invariantes enforced\nen el dominio, e infraestructura como detalle. DDD sobre un dominio real (aunque\nchico) es más honesto que otra demo CRUD más.",{"title":7,"searchDepth":21,"depth":21,"links":164},[165,166,167,168],{"id":29,"depth":21,"text":33},{"id":42,"depth":21,"text":45},{"id":120,"depth":21,"text":123},{"id":154,"depth":21,"text":157},"markdown","content:es:projects:supermarket-stock.md","content","es\u002Fprojects\u002Fsupermarket-stock.md","es\u002Fprojects\u002Fsupermarket-stock","md",[176,301],{"_path":177,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":178,"description":7,"summary":179,"stack":180,"repo":188,"demo":189,"year":190,"role":19,"featured":20,"order":191,"body":192,"_type":169,"_id":298,"_source":171,"_file":299,"_stem":300,"_extension":174},"\u002Fes\u002Fprojects\u002Fmnemo","Mnemo","Flashcards con repetición espaciada para mi propio estudio — el contenido vive en Markdown versionado con git, el estado SRS (SM-2 de Anki) en Postgres. Live en su propio subdominio, full-serverless en el free tier.",[181,182,183,184,185,186,187],"TypeScript","Next.js 15","Cloudflare Workers","Prisma","PostgreSQL (Neon)","Vitest","pnpm monorepo","https:\u002F\u002Fgithub.com\u002FFabrizio-Alvarez\u002Fmnemo","https:\u002F\u002Fmnemo.falvarez.dev",2026,1,{"type":23,"children":193,"toc":292},[194,198,203,220,226,272,278,283,287],{"type":26,"tag":27,"props":195,"children":196},{"id":29},[197],{"type":32,"value":33},{"type":26,"tag":35,"props":199,"children":200},{},[201],{"type":32,"value":202},"Una app personal de estudio con tarjetas, estilo Anki. El contenido — 8 mazos,\n256 tarjetas — vive en archivos Markdown versionados con git; el estado de\nrepetición espaciada (algoritmo SM-2 de Anki) vive en Postgres. Está en uso real\ndiario, y eso mantiene honesta cada decisión de diseño.",{"type":26,"tag":35,"props":204,"children":205},{},[206,211,213],{"type":26,"tag":69,"props":207,"children":208},{},[209],{"type":32,"value":210},"Live:",{"type":32,"value":212}," ",{"type":26,"tag":214,"props":215,"children":218},"a",{"href":189,"rel":216},[217],"nofollow",[219],{"type":32,"value":189},{"type":26,"tag":27,"props":221,"children":223},{"id":222},"arquitectura-monorepo-hexagonal",[224],{"type":32,"value":225},"Arquitectura: monorepo hexagonal",{"type":26,"tag":61,"props":227,"children":228},{},[229,244,262],{"type":26,"tag":65,"props":230,"children":231},{},[232,242],{"type":26,"tag":69,"props":233,"children":234},{},[235],{"type":26,"tag":236,"props":237,"children":239},"code",{"className":238},[],[240],{"type":32,"value":241},"@mnemo\u002Fdomain",{"type":32,"value":243}," — el parser de Markdown y el scheduler SM-2 como TypeScript\npuro: 67 tests, sin base de datos, sin framework. El paquete viaja intacto a\ncualquier runtime.",{"type":26,"tag":65,"props":245,"children":246},{},[247,252,254,260],{"type":26,"tag":69,"props":248,"children":249},{},[250],{"type":32,"value":251},"Contenido ≠ estado",{"type":32,"value":253}," como decisión central: los ",{"type":26,"tag":236,"props":255,"children":257},{"className":256},[],[258],{"type":32,"value":259},".md",{"type":32,"value":261}," son la fuente de verdad;\nla base de datos es derivada e idempotente — el seed se puede re-correr en\ncualquier momento sin perder el estado de scheduling.",{"type":26,"tag":65,"props":263,"children":264},{},[265,270],{"type":26,"tag":69,"props":266,"children":267},{},[268],{"type":32,"value":269},"Full-serverless en el free tier",{"type":32,"value":271},": Cloudflare Workers (vía OpenNext) + Neon\nPostgres, dominio propio, CI\u002FCD en cada push (GitHub Actions).",{"type":26,"tag":27,"props":273,"children":275},{"id":274},"más-allá-de-las-flashcards-modo-quiz",[276],{"type":32,"value":277},"Más allá de las flashcards: modo quiz",{"type":26,"tag":35,"props":279,"children":280},{},[281],{"type":32,"value":282},"Un modo quiz didáctico donde cada pregunta trae distractores plausibles escritos a\nmano y una explicación conceptual del \"por qué\" — las respuestas incorrectas\nenseñan en lugar de solo fallar. Los modos son extensiones del mismo dominio, no\napps separadas: extensibilidad hecha carne.",{"type":26,"tag":27,"props":284,"children":285},{"id":154},[286],{"type":32,"value":157},{"type":26,"tag":35,"props":288,"children":289},{},[290],{"type":32,"value":291},"Es el proyecto que más uso y el que muestra el arco completo: modelado de dominio\n(SM-2, parsing de contenido), un monorepo real con límites estrictos, y operación\nen producción en mi propio dominio — deployado, monitoreado, y a US$0\u002Fmes.",{"title":7,"searchDepth":21,"depth":21,"links":293},[294,295,296,297],{"id":29,"depth":21,"text":33},{"id":222,"depth":21,"text":225},{"id":274,"depth":21,"text":277},{"id":154,"depth":21,"text":157},"content:es:projects:mnemo.md","es\u002Fprojects\u002Fmnemo.md","es\u002Fprojects\u002Fmnemo",{"_path":302,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":303,"description":7,"summary":304,"stack":305,"repo":308,"demo":7,"year":18,"role":19,"featured":6,"order":309,"body":310,"_type":169,"_id":472,"_source":171,"_file":473,"_stem":474,"_extension":174},"\u002Fes\u002Fprojects\u002Fjob-tracker","Tracker de Aplicaciones Laborales","Un tracker para aplicaciones, entrevistas, contactos y follow-ups que efectivamente uso en mi propia búsqueda laboral. Node.js + TypeScript + Fastify + Prisma con JWT auth y una máquina de estados para el status de la aplicación.",[306,181,307,184,13,186],"Node.js","Fastify","https:\u002F\u002Fgithub.com\u002FFabrizio-Alvarez\u002Fjob-tracker",3,{"type":23,"children":311,"toc":466},[312,318,323,329,400,406,414,455,461],{"type":26,"tag":27,"props":313,"children":315},{"id":314},"por-qué-lo-construí",[316],{"type":32,"value":317},"Por qué lo construí",{"type":26,"tag":35,"props":319,"children":320},{},[321],{"type":32,"value":322},"Estaba buscando trabajo y llevando mi pipeline en una planilla. Las planillas son\npésimas para recordatorios, transiciones de status y \"¿cuándo fue la última vez\nque me respondió esta empresa?\". Por eso construí la herramienta que quería —\nque también es el tipo de side project más defendible: uno que efectivamente\nuso (dogfooding).",{"type":26,"tag":27,"props":324,"children":326},{"id":325},"qué-hace",[327],{"type":32,"value":328},"Qué hace",{"type":26,"tag":61,"props":330,"children":331},{},[332,344,364,376,388],{"type":26,"tag":65,"props":333,"children":334},{},[335,337,342],{"type":32,"value":336},"Trackea ",{"type":26,"tag":69,"props":338,"children":339},{},[340],{"type":32,"value":341},"aplicaciones",{"type":32,"value":343}," por empresa, con rol, fuente y notas de salario.",{"type":26,"tag":65,"props":345,"children":346},{},[347,349,354,356,362],{"type":32,"value":348},"Modela el status como una ",{"type":26,"tag":69,"props":350,"children":351},{},[352],{"type":32,"value":353},"máquina de estados",{"type":32,"value":355}," explícita: ",{"type":26,"tag":236,"props":357,"children":359},{"className":358},[],[360],{"type":32,"value":361},"applied → screening → interview → offer | rejected",{"type":32,"value":363},".",{"type":26,"tag":65,"props":365,"children":366},{},[367,369,374],{"type":32,"value":368},"Registra ",{"type":26,"tag":69,"props":370,"children":371},{},[372],{"type":32,"value":373},"follow-ups",{"type":32,"value":375}," y marca los que están vencidos.",{"type":26,"tag":65,"props":377,"children":378},{},[379,381,386],{"type":32,"value":380},"Mantiene una lista de ",{"type":26,"tag":69,"props":382,"children":383},{},[384],{"type":32,"value":385},"contactos",{"type":32,"value":387}," (recruiters, hiring managers) vinculados a las aplicaciones.",{"type":26,"tag":65,"props":389,"children":390},{},[391,393,398],{"type":32,"value":392},"Muestra ",{"type":26,"tag":69,"props":394,"children":395},{},[396],{"type":32,"value":397},"estadísticas de funnel",{"type":32,"value":399}," simples para ver dónde se pierden candidatos.",{"type":26,"tag":27,"props":401,"children":403},{"id":402},"decisiones-de-arquitectura",[404],{"type":32,"value":405},"Decisiones de arquitectura",{"type":26,"tag":35,"props":407,"children":408},{},[409],{"type":26,"tag":55,"props":410,"children":413},{"alt":411,"src":412},"State machine de status de la aplicación","\u002Fdiagrams\u002Fjob-tracker-state.svg",[],{"type":26,"tag":61,"props":415,"children":416},{},[417,426,435,445],{"type":26,"tag":65,"props":418,"children":419},{},[420,424],{"type":26,"tag":69,"props":421,"children":422},{},[423],{"type":32,"value":307},{"type":32,"value":425}," por sobre Express — validación basada en esquemas out of the box\ny una superficie tipada más ajustada, lo cual importa cuando el punto central\nes la corrección.",{"type":26,"tag":65,"props":427,"children":428},{},[429,433],{"type":26,"tag":69,"props":430,"children":431},{},[432],{"type":32,"value":184},{"type":32,"value":434}," para acceso a datos — queries tipadas y migraciones sin escribir SQL\na mano.",{"type":26,"tag":65,"props":436,"children":437},{},[438,443],{"type":26,"tag":69,"props":439,"children":440},{},[441],{"type":32,"value":442},"Status como máquina de estados",{"type":32,"value":444},", no un campo de texto libre — las transiciones\nilegales se rechazan en el límite, así las estadísticas de funnel son confiables.",{"type":26,"tag":65,"props":446,"children":447},{},[448,453],{"type":26,"tag":69,"props":449,"children":450},{},[451],{"type":32,"value":452},"JWT auth",{"type":32,"value":454}," con refresh — single-user por ahora, pero estructurado para que\nel multi-tenant no sea un rewrite.",{"type":26,"tag":27,"props":456,"children":458},{"id":457},"status",[459],{"type":32,"value":460},"Status",{"type":26,"tag":35,"props":462,"children":463},{},[464],{"type":32,"value":465},"Los flujos centrales funcionan y están cubiertos por tests de Vitest, con CI en\nverde. La UI está intencionalmente fuera de scope por ahora — el valor está en\nla API y el modelo de datos.",{"title":7,"searchDepth":21,"depth":21,"links":467},[468,469,470,471],{"id":314,"depth":21,"text":317},{"id":325,"depth":21,"text":328},{"id":402,"depth":21,"text":405},{"id":457,"depth":21,"text":460},"content:es:projects:job-tracker.md","es\u002Fprojects\u002Fjob-tracker.md","es\u002Fprojects\u002Fjob-tracker",1788209838595]