Portafolio · Vending · Hardware + software
Milodón Vending
App Android + panel web para operar máquinas expendedoras.
- Cliente
- Milodón Vending
- Tipo
- App móvil
- Año
- 2025
- Estado
- Publicada

El encargo
Qué se resolvió.
Sistema completo para máquinas expendedoras: app Android nativa que controla el hardware (matriz de motores) y cobra con un POS Transbank integrado, más un panel web de control remoto con telemetría en vivo, ventas, inventario, alertas de hardware y cierre de caja diario.
Milodón Vending opera máquinas expendedoras físicas — la primera, instalada en un gimnasio de Sportlife — y necesitaba algo que un software de vending genérico no ofrece: hablar directo con su hardware (una placa controladora propia, un lector de pago cashless y un terminal Transbank) y dejar que el dueño la maneje desde el celular sin ir a destrabar un motor a mano.
La solución son dos piezas que comparten un mismo Firestore en tiempo real: una app Android nativa que vive dentro de la máquina y gobierna cada parte del ciclo de venta, y un panel web para el dueño con ventas, inventario, logs y una consola para mandarle comandos de vuelta al hardware.
En simple: la máquina vende sola. Cada transacción, log y falla sube a Firestore al instante. Desde el panel el dueño ve todo en vivo y puede mandar comandos de vuelta — girar un motor, prender un LED — sin tocar la máquina. Si algo se rompe, Telegram avisa antes de que el cliente se queje.


La máquina y la app que la gobierna
Una tablet Android reemplaza al controlador de fábrica y gobierna cada parte del ciclo de venta: pantalla, cobro y motor.
- 1
Pantalla de autoservicio en Java nativo
Bienvenida → categorías → productos → detalle → pago → dispensado. Sin WebView ni Compose: vistas Android nativas corriendo sobre un panel táctil embebido.
- 2
Cobro con POS Transbank antes de girar el motor
El terminal IM30 se conecta por USB y cobra primero. Si el motor falla al girar, el sistema reversa el cobro — nunca se cobra por algo que no se entregó.
- 3
Control directo del motor por puerto serie
La app habla por serie con la placa controladora de la máquina para girar el motor exacto de la fila y columna que corresponde al producto comprado.
- 4
Panel técnico oculto en la propia máquina
Con PIN, para calibrar motores o reconectar el POS sin depender de un técnico remoto ni reiniciar el equipo.
Categorías fijas: un mosaico, dos pantallas
El mismo layout de categorías vive en dos lugares — el panel web las edita, la app las muestra — y ambos entienden la misma regla de armado.

6 categorías fijas, no una lista abierta
5 categorías de producto más "Todos los productos" — cada una es un documento en Firestore con nombre, imagen y orden.
El mosaico se arma solo
La primera y la última ocupan el ancho completo; las cuatro del medio van de a pares. El dueño solo sube nombre e imagen, sin tocar una línea de código.
Grilla de 2 columnas sobre fondo negro
Un RecyclerView con GridLayoutManager reproduce el mismo criterio: si el total de categorías es impar, la última ocupa las dos columnas.
Antes era una regla fija, ahora es una regla real
El primer código dejaba el ancho completo pegado a las posiciones 0 y 5 a mano — con una cantidad distinta de categorías el layout se rompía. Se generalizó a "la última, si el total es impar".
Imágenes cacheadas antes que nada
Carga primero desde caché offline y solo pide la red si no la encuentra — la grilla no depende de tener señal en ese instante.
Recreación fiel del layout de categorías de la app a partir de sus propios recursos (íconos y paleta), no una captura de pantalla del dispositivo.



El sistema de carga de productos
La pieza más particular del panel: el SKU de cada producto es la dirección física de un motor, no un código inventado.
- 1
El SKU es de solo lectura, a propósito
Es el mismo número que identifica al motor (110, 120, 230…). No se puede escribir un SKU que no exista físicamente en la máquina.
- 2
Imagen por archivo o por URL
Se sube un archivo directo a Firebase Storage o se pega una URL — la vista previa se actualiza al instante, sin recargar la página.
- 3
Ficha nutricional, no solo nombre y precio
Proteína, calorías, cafeína, carbohidratos, azúcar y taurina por producto — pensado para bebidas energéticas y suplementos, el catálogo real de la máquina.
- 4
La categoría se lee en vivo desde Firestore
El selector no es una lista fija en el código: cada vez que se abre el formulario, trae las categorías que existen ese día.

Ventas y estado de la máquina, en vivo
El dueño ve la operación sin ir a la máquina: estado, ingresos y comportamiento del negocio calculados en el momento, no en un reporte del día siguiente.
- 1
Estado ONLINE / STANDBY / OFFLINE
Calculado según la última transmisión de la máquina, con un banner de alerta visual si hay una falla de hardware activa (motor, POS o placa).
- 2
Ingresos, ventas y producto top del día
Sumados en vivo desde Firestore cada vez que se carga el panel — sin batch ni reporte que llega horas después.
- 3
Panel de ventas con gráficos por período
Históricos de día/semana/mes/año y distribución de transacciones aprobadas, fallidas y pendientes, con Chart.js sobre los mismos datos.

Inventario mapeado a la matriz física
Cada casillero del panel corresponde 1 a 1 a un motor real dentro de la máquina — fila y columna, no un número inventado.
- 1
La grilla del panel es la grilla de la máquina
Filas de 6 y de 9 motores, con los mismos códigos (110, 120…) que la placa controladora usa para dirigir el giro.
- 2
Semáforo de stock por motor
Verde con stock normal, ámbar en ≤3 unidades, rojo en cero — de un vistazo, sin abrir cada ficha.
- 3
Más vendido y menos vendido, sin planilla aparte
Calculado sobre el mismo inventario que ve la app, para decidir qué reponer antes de ir a cargar la máquina.

Una consola para hablarle directo al hardware
La herramienta más cruda del panel: mandarle comandos a la placa controladora desde el navegador, sin ir a la máquina.
- 1
Los mismos comandos ASCII que corren por el puerto serie
(POLL) para el latido de la placa, (LED1)/(LED0), giro de motor por SKU o una trama cruda — la sintaxis exacta que la placa entiende.
- 2
Girar un motor sin ir a la máquina
Para destrabar un producto atascado o probar un motor nuevo desde el escritorio, en vez de abrir la máquina en el local.
- 3
Historial en vivo, con la respuesta del hardware
Cada comando emitido queda registrado con su estado y lo que contestó la placa, escuchando la colección en Firestore en tiempo real.

Logs, alertas y cierre de caja solo
Cuando algo falla, no hay que estar mirando la máquina para enterarse — y al final del día, nadie tiene que cerrar la caja a mano.
- 1
Consola de logs en vivo
Eventos de hardware, comunicación serie y respuestas de la app, con filtro por nivel (error/warn/info) y búsqueda de texto.
- 2
Alertas a Telegram sin exponer el token
Una Cloud Function separada escucha nuevas alertas en Firestore y le avisa al equipo — la app nunca tiene las credenciales del bot.
- 3
Cierre de caja automático a las 04:00
Todos los días el sistema cierra el lote de ventas solo, sin que nadie tenga que hacerlo a mano al final del turno.
Con qué habla el sistema.
Transbank · POS IM30
Cobro con tarjeta por USB con el protocolo de "POS Autoservicio" reimplementado a mano — no hay SDK oficial para este flujo.
Firebase · Firestore, Auth y Storage
El sistema nervioso: transacciones, logs, comandos remotos y catálogo, compartidos en tiempo real entre la máquina y el panel.
Telegram Bot API
Alertas al equipo despachadas por una Cloud Function aparte, para que el token del bot nunca viaje dentro de la app.
Placa controladora + lector cashless
Motores y bus de pago por puerto serie, con un protocolo de tramas ASCII propio para cada instrucción de hardware.
¿Tienes un proyecto parecido?
Cuéntanos qué necesitas. Te respondemos con un plan concreto para tu caso, no con una plantilla genérica.