Dic. 2025 – actualidad
MangaKaCR POS & Inventory Platform
La plataforma de punto de venta, inventario y administración con la que opera MangaKaCR, una librería de manga en San José. La diseñé y construí por completo yo solo.
Mi rolDesarrollador único: arquitectura, backend, frontend, pruebas, CI/CD y operación en producción
- pruebas automatizadas
- 916
- pruebas automatizadas
- pantallas
- 28
- pantallas
- módulos de negocio
- 10
- módulos de negocio

MangaKaCR es una librería independiente de manga en San José. Antes manejaba sus ventas e inventario en un sistema heredado; lo reemplacé con una plataforma hecha desde cero alrededor de cómo trabaja realmente la tienda: ventas en mostrador con varios métodos de pago, apartados, turnos de caja, reabastecimientos masivos, cashback para clientes, avisos de reabastecimiento y un catálogo imprimible que se actualiza solo todos los días.
Fui la única persona en el proyecto. Diseñé la arquitectura y el modelo de dominio, escribí la API, la aplicación web de administración, los PDF y correos, las 916 pruebas automatizadas y los pipelines de CI/CD, migré la infraestructura a mitad del proyecto y lo despliego y le doy soporte en producción. La tienda me permitió publicarlo aquí.
Arquitectura
El sistema son dos aplicaciones .NET 9, cada una dividida en capas de arquitectura limpia (Dominio → Aplicación → Infraestructura → Presentación, más un proyecto de composición para la inyección de dependencias):
- Mangaka.Api: una Minimal API de ASP.NET Core con 20 grupos de endpoints, autenticación JWT, una política solo para administradores, manejo global de errores con ProblemDetails y dos servicios en segundo plano.
- Mangaka.Invoicing: la aplicación Blazor Server para el personal (MudBlazor + ApexCharts) con 28 pantallas y ~100 componentes Razor. Se comunica con la API mediante un cliente tipado generado con Microsoft Kiota a partir del documento OpenAPI, así que un cambio de contrato rompe la compilación en lugar de producción.
- Un modelo de dominio rico: entidades y objetos de valor que se validan a sí mismos (precio, costo, código CABYS, cédula, correo, teléfono, ubicación de bodega…) con excepciones de dominio traducidas a respuestas HTTP en el borde.
- PostgreSQL con 34 migraciones de EF Core, imágenes de productos en Cloudflare R2, correos transaccionales con Resend, documentos con QuestPDF, una sincronización diaria con Google Drive y consultas a la API de Hacienda.
Punto de venta
- Un POS con varias pestañas: los borradores abiertos de cada vendedor se restauran como pestañas, limitados a quien inició sesión (corregí un error en el que los carritos podían filtrarse entre vendedores).
- Ventas a cliente de contado o registrado. Al digitar una cédula se obtiene el nombre registrado desde la API pública de Hacienda, con caché de una hora (incluidos los no encontrados) para no saturar el servicio.
- Seis métodos de pago (efectivo con vuelto, tarjeta, transferencia, SINPE Móvil, CompraClick) más pagos combinados entre métodos, precios ajustables por línea y ofertas por monto o porcentaje.
- Emitir una factura rebaja el inventario y asigna su consecutivo, continuando la numeración del sistema heredado mediante una migración dedicada para que la secuencia de la tienda nunca se rompiera.
- Anulaciones dentro de una ventana de 24 horas, solo para administradores: se devuelve el inventario, se reintegran las ofertas con cupo limitado y se registra en la bitácora.
- Tiquetes de 80 mm y recibos A4 generados con QuestPDF, envío del recibo por correo al cliente y exportación de muchos recibos a un solo PDF A4.
- Cashback para clientes: cada venta acumula un 10%, el saldo completo se puede redimir (con tope en el total), se revierte al anular y el cajero puede excluir una venta. Los pagos finales de apartados no acumulan, a propósito.
Concurrencia sin errores
El error más difícil que rastreé: para que el carrito responda rápido, el POS envía una petición por producto en paralelo al guardar varios cambios. Cada petición cargaba la factura, cambiaba su propia línea y escribía totales calculados con las líneas que alcanzó a ver. Es una actualización perdida clásica: un carrito real de 9 líneas se emitió con el total de solo 2, aunque las 9 líneas sí se guardaron.
Lo resolví con un advisory lock de PostgreSQL con alcance de transacción, indexado por el número de factura, que se toma antes siquiera de leer la factura. Los cambios a una misma factura se serializan, las facturas distintas siguen en paralelo y el bloqueo se libera solo al confirmar o revertir.
- Otras condiciones de carrera que corregí: actualizaciones de cantidad y ubicación de productos, un buscador que perdía teclas mientras cargaba una página y el scroll infinito devolviendo filas duplicadas o faltantes con ediciones concurrentes.
- Sincronización en vivo entre pestañas: un pub/sub en proceso sobre los circuitos de Blazor Server envía los cambios de inventario, precio, ubicación y ofertas a todas las pestañas abiertas sin recargar. Una página de edición muestra un aviso en lugar de sobrescribir lo que estás escribiendo.
Apartados y turnos de caja
- Apartados: reservan inventario y congelan el precio de cada línea al crearse, cobran un aporte inicial y abonos posteriores, exigen un vencimiento de al menos 30 días, devuelven el inventario si se cancelan y al final se convierten en una factura completa, con sus propios tiquetes y recibos.
- Turnos: se abren con un monto inicial, registran entradas manuales de efectivo y se cierran con el efectivo contado y observaciones. Cualquier operación que cobre dinero se bloquea si no hay un turno abierto.
- Los reportes de turnos concilian ventas y abonos de apartados por período, cubiertos por pruebas de integración contra una base de datos real.
Inventario y catálogo
- Productos con códigos CABYS validados, autores, categorías, colecciones (con nombres alternativos), editoriales, demografías, tipos, ubicación en bodega, visibilidad y carga de imágenes a Cloudflare R2 con recorte y administración.
- Búsqueda ordenada por calidad de coincidencia, filtros, ordenamiento y scroll infinito, además de acciones masivas de borrado, precio y visibilidad y duplicación de productos con un clic.
- Borradores de reabastecimiento masivo: un reabastecimiento se puede guardar y retomar. Al aplicarlo actualiza el inventario y la ubicación de cada producto, escribe una entrada de bitácora por cambio y congela su reporte.
- “Avisar cuando llegue”: los clientes pueden seguir productos agotados. Solo una transición real de agotado a disponible los pone en cola, y un servicio en segundo plano agrupa todo lo que volvió para cada cliente en un solo correo tras una ventana de una hora.
- Un servicio en segundo plano genera cada mañana el PDF de la lista de productos y sobrescribe un archivo fijo en Google Drive, así el enlace que la tienda comparte con sus clientes nunca cambia.
Analítica, administración y bitácoras
- Analítica de ventas con KPIs, top 10 por autor, colección, categoría, editorial, demografía, producto, cliente y vendedor, tendencias diarias, semanales o mensuales y comparación con el período anterior o año contra año, todo en hora de Costa Rica.
- Inicio de sesión con Neon Auth. El primer ingreso crea una solicitud de acceso que un administrador aprueba (asignando un rol) o rechaza. Los administradores no pueden cambiar su propio rol ni estado.
- Bitácoras con búsqueda para productos (con detalle por campo), clientes, ofertas y facturas, y la configuración de la empresa (régimen tributario, actividad económica, términos y notas) que se imprime en cada recibo.
Calidad, entrega y operación
- 916 pruebas automatizadas en cinco proyectos: dominio, aplicación (Moq), infraestructura, presentación y 87 pruebas de integración contra un PostgreSQL real en Testcontainers para los caminos delicados (cashback, liquidación de apartados, conciliación de turnos, borrados en cascada).
- GitHub Actions para compilar y probar, análisis con CodeQL e imágenes Docker de ambas aplicaciones publicadas en GHCR en cada merge a main, construidas en un runner Docker propio que configuré. Análisis local con SonarQube y cobertura.
- A mitad del proyecto migré las imágenes de Supabase Storage a Cloudflare R2 y la autenticación y la base de datos a Neon, haciendo que la validación de tokens de .NET aceptara los JWT firmados con EdDSA de Neon.
- Como el registrador .cr de la tienda no permite delegar el DNS a Cloudflare, sirvo las imágenes de R2 mediante un Cloudflare Worker con caché en el borde en lugar de un dominio propio.
- Un flujo de Git con ramas por funcionalidad, versiones publicadas de development a main y un esquema documentado para importar masivamente los productos de la tienda en línea. Lo siguiente: conectar la tienda en línea mediante un backend-for-frontend.
Hecho con
Siguiente proyecto
LASCE Research Lab Platform →



