Un sistema de autopago construido en 2 días para una tienda desatendida

Bohfula / ボーフラ
Escrito por:
Un sistema de autopago construido en 2 días para una tienda desatendida

Nota: Este artículo es una traducción automática del artículo original en japonés. Si nota algún error de traducción, por favor háganoslo saber.

A continuación, explicamos el sistema de autopago completamente sin efectivo que construimos en solo dos días para la apertura de LOPPO Art Supply Shibuya Store (un punto de venta desatendido).

Introducción: El proyecto de tienda desatendida de LOPPO Art Supply

LOPPO Art Supply es una marca de materiales artísticos para animación cel que comenzó como un pasatiempo de Takahashi y se ha comercializado principalmente a través de ventas en línea. A principios de este año, decidimos alquilar un pequeño local como espacio de eventos para LOPPO Art Supply. Habíamos planeado usar una esquina como almacén, pero surgió la idea: "Si de todas formas vamos a almacenar inventario aquí, ¿por qué no convertirlo en un punto de venta?"

Desde hacía tiempo existían solicitudes de una tienda física, pero asegurar los recursos humanos necesarios para las operaciones de la tienda había sido un desafío. Fue entonces cuando se nos ocurrió el concepto de una "tienda de materiales artísticos desatendida". Piensen en un puesto de verduras de autoservicio, pero para materiales de animación cel — un lugar de ensueño (¡o de locura!) donde se puede comprar las 24 horas del día, los 365 días del año.

Tienda de materiales artísticos desatendida (imagen conceptual) Tienda de materiales artísticos desatendida (imagen conceptual)

1. Desafíos de las soluciones de autopago existentes

Para crear una tienda desatendida que no requiera personal en el lugar, es esencial un sistema de autopago completamente autónomo. Para este proyecto, decidimos aceptar únicamente pagos sin efectivo como medida de prevención contra el robo.

Inicialmente consideramos soluciones de autopago comerciales, pero encontramos dos problemas importantes:

  1. Altos costos fijos mensuales además de los costos iniciales de instalación
  2. Largos ciclos de liquidación hasta el depósito

Para garantizar la sostenibilidad, es importante mantener los costos fijos al mínimo mientras se mantiene un flujo de caja saludable. Fue entonces cuando dirigimos nuestra atención al sistema de pago Square, que habíamos utilizado previamente en ventas en eventos.

Square admite una amplia variedad de métodos de pago, no cobra tarifas mensuales (solo comisiones por transacción) y ofrece depósitos rápidos — tan pronto como el siguiente día hábil. Además, la Square API permite la creación de aplicaciones personalizadas. Como ya poseíamos un Square Terminal, determinamos que aprovecharlo también mantendría bajos los costos iniciales.

Sin embargo, había una gran restricción en el desarrollo. Ya estábamos desbordados con la fabricación de productos para la apertura de la tienda y solo pudimos dedicar apenas 2 días al desarrollo del sistema de autopago.

2. Diseño del sistema: Un autopago seguro y fácil de usar

Arquitectura general

Diagrama de arquitectura del sistema

El sistema está compuesto por los siguientes componentes principales:

  • Servidor de aplicaciones Linux: Frontend en React y backend en Express
  • Sistema en tienda: Terminal cliente en modo quiosco Windows 11 Pro, periféricos y Square Terminal
  • Servicios Square: Square API, datos maestros de productos
  • Monitoreo y operaciones: Cámaras de seguridad, sistema de alimentación ininterrumpida (SAI), redundancia de red

Desde la perspectiva del cliente, solo son visibles el monitor táctil, el escáner de códigos de barras y el Square Terminal. El cliente funciona en modo quiosco de Windows 11 Pro, mientras que la lógica principal de la aplicación reside en una máquina Linux ubicada fuera de la tienda.

Seguridad y red

La red opera bajo un entorno VPN utilizando Tailscale, que protege la comunicación entre el terminal cliente y el servidor Linux. Además, todos los dispositivos están conectados a un sistema de alimentación ininterrumpida para protección contra rayos y cortes de energía, y la red está configurada con redundancia para garantizar un funcionamiento estable.

La adopción de Tailscale también facilita el mantenimiento remoto. No se almacenan datos locales en ningún terminal — todos los datos se obtienen de los sistemas de Square.

Configuración de hardware

  • Monitor táctil
  • Escáner de códigos de barras USB
  • Square Terminal (procesamiento de pagos e impresión de recibos)
  • Cámara de seguridad (para monitoreo en tiempo real)

Implementación del frontend

Pantalla de selección de método de pago Pantalla de selección de método de pago

El frontend está construido con React y consta de las siguientes pantallas principales:

  1. Pantalla de escaneo de productos
  2. Pantalla de selección de método de pago
  3. Pantalla de procesamiento de pago
  4. Pantalla de pago completado

Aunque utilizando traducción automática, también implementamos soporte multilingüe, cubriendo 6 idiomas: japonés, inglés, francés, español, chino tradicional y chino simplificado. Esto permite que los visitantes internacionales utilicen el sistema con confianza.

// Ejemplo de configuración de idiomas
const translations = {
  ja: {
    title: 'セルフレジシステム',
    scanTitle: '商品スキャン',
    // ...omitido
  },
  en: {
    title: 'Self-Checkout System',
    scanTitle: 'Product Scan',
    // ...omitido
  },
  // Otros idiomas...
};

También diseñamos el sistema para priorizar la entrada del escáner de códigos de barras, buscando una interfaz que los usuarios puedan operar sin confusión.

3. Puntos clave de la integración con Square API

Procesamiento de pagos con Terminal API

Entre las APIs de Square, la Terminal API es particularmente importante. Nos permite enviar solicitudes de procesamiento de pagos al Square Terminal.

// Crear checkout en Terminal
app.post("/api/create-terminal-checkout", async (req, res) => {
  try {
    const { order, amountMoney, paymentType = "CARD_PRESENT" } = req.body;

    const ALLOWED = new Set([
      "CARD_PRESENT",
      "FELICA_TRANSPORTATION_GROUP",
      "FELICA_ID",
      "FELICA_QUICPAY",
      "QR_CODE"
    ]);

    if (!ALLOWED.has(paymentType)) {
      return res.status(400).json({ error: "Método de pago no soportado especificado" });
    }

    // Crear el pedido primero
    const orderId = await createOrder(order);

    // Crear Square Terminal Checkout
    const checkoutResponse = await squareClient.terminal.checkouts.create({
      idempotencyKey: randomUUID(),
      checkout: {
        amountMoney: {
          // El monto debe ser BigInt
          amount: BigInt(amountMoney.amount),
          currency: amountMoney.currency,
        },
        deviceOptions: {
          deviceId: SQUARE_DEVICE_ID,
          skip_receipt_screen: true,
          show_itemized_cart: false,
        },
        referenceId: orderId,
        orderId,
        note: "Pago en autopago LOPPO",
        paymentType: paymentType
      },
    });

    res.json(checkoutResponse);
  } catch (error) {
    handleError("Error al crear Terminal Checkout", error, res);
  }
});

Diversos métodos de pago

Dado que Square Terminal admite una amplia variedad de métodos de pago, los clientes pueden pagar con su método preferido:

  • Tarjetas de crédito/débito
  • Tarjetas IC de transporte (Suica/PASMO, etc.)
  • iD
  • QUICPay
  • Pagos con código QR (PayPay, etc.)

Tenga en cuenta que las tarjetas UnionPay no son compatibles.

Consulta del estado del pago mediante polling

Dado que el procesamiento del pago se realiza en el Square Terminal, necesitamos consultar el estado mediante polling para detectar la finalización o la cancelación.

// Consultar el estado del pago mediante polling
const checkPaymentStatus = async () => {
  try {
    const statusResponse = await fetch(`/api/get-checkout-status?checkoutId=${data.checkout.id}`);
    const statusData = await statusResponse.json();

    if (statusData.status === 'COMPLETED') {
      setPaymentStatus(t.paymentCompleted);
      // Procesamiento de finalización
      setTimeout(() => {
        setStatus('complete');
        setCart([]);
      }, 2000);
    } else if (statusData.status === 'CANCELED' || statusData.status === 'CANCEL_REQUESTED') {
      setPaymentStatus(t.paymentCanceled);
      setTimeout(() => {
        setStatus('ready');
      }, 3000);
    } else {
      // Si aún no se ha completado, verificar de nuevo
      setPaymentStatus(t.processing);
      setTimeout(checkPaymentStatus, 2000);
    }
  } catch (error) {
    console.error(t.statusCheckFailed, error);
    setPaymentStatus(t.statusCheckFailed);
    setTimeout(() => {
      setStatus('ready');
    }, 3000);
  }
};

Gestión de datos maestros de productos

Toda la información de productos se registra a través del panel de Square y se obtiene mediante la API. Esto simplifica las tareas operativas como agregar productos o cambiar precios.

app.get("/api/catalog-items", async (_req, res) => {
  try {
    const TYPES = "ITEM,ITEM_VARIATION,CATEGORY,IMAGE"; // Listar todos los tipos necesarios
    //------------------------------------------------------------------
    // 1. Cargar todo
    //------------------------------------------------------------------
    const objects = [];
    for await (const obj of await squareClient.catalog.list({ types: TYPES }))
      objects.push(obj);

    //------------------------------------------------------------------
    // 2. Crear mapas para CATEGORY / IMAGE / VARIATION primero
    //------------------------------------------------------------------
    const imageMap     = {};
    const categoryMap  = {};
    const variationMap = {};

    // ...omitido (lógica de creación de mapas)

    //------------------------------------------------------------------
    // 3. Expandir ITEMs e incrustar información usando los mapas creados arriba
    //------------------------------------------------------------------
    const filtered = objects
      .filter((o) => o.type === "ITEM")
      .map((item) => {
        // ...omitido (lógica de transformación de datos)
      })
      // -- Aplicar filtros de requisitos aquí --
      .filter(
        (item) =>
          !item.isArchived &&
          item.categoryNames.includes("六方画材")
      );

    res.json(filtered);
  } catch (error) {
    handleError("Error al obtener artículos del catálogo", error, res);
  }
});

4. Desarrollo rápido con LLMs

La característica destacada de este proyecto es que se completó en solo 2 días. Esto fue posible gracias al uso de LLMs (Modelos de Lenguaje de Gran Escala).

Desglose del tiempo de desarrollo

  • Desarrollo del sistema base: ~2 horas
  • Refinamiento y ajustes de UI: ~4 horas
  • Pruebas y despliegue: tiempo restante

Cómo usamos Claude 3.7 Sonnet

Utilizamos principalmente Claude 3.7 Sonnet durante el desarrollo para optimizar la implementación. Manejó no solo la lógica de la aplicación sino también el diseño de la UI sin esfuerzo, e incluso preparó documentación de configuración — un asistente verdaderamente completo. Las sugerencias del LLM fueron especialmente valiosas para el código de soporte multilingüe y la integración con Square API.

También probamos combinar ChatGPT 4o y ChatGPT o3, pero en cuanto a la comprensión de aplicaciones web, no estaban a la altura de 3.7 Sonnet.

Ejemplos prácticos del uso de LLMs

Esta es una advertencia común al usar LLMs para el desarrollo, pero es difícil usar el código generado tal cual — es esencial entenderlo y hacer las modificaciones necesarias. Por ejemplo, se necesitaron las siguientes correcciones para la integración con Square Terminal API:

  1. Adición de métodos de pago: El código generado por el LLM solo soportaba pagos con tarjeta de crédito, por lo que necesitábamos agregar una pantalla de selección de método de pago
  2. Manejo de errores: El manejo de eventos de cancelación de pago del Terminal era incorrecto, así que lo corregimos basándonos en la documentación de la API
  3. Seguridad: Parte de la comunicación entre aplicaciones utilizaba protocolos inseguros, así que construimos una VPN privada para asegurar la ruta de comunicación

El LLM proporcionó la estructura básica del código, pero los ajustes para estar listo para producción tuvieron que hacerse manualmente.

5. Internacionalización y usabilidad

Implementación multilingüe

Para atender a visitantes internacionales, el sistema soporta 6 idiomas: japonés, inglés, francés, español, chino tradicional y chino simplificado. La configuración de idioma se gestiona dentro de los componentes React, y todo el texto en pantalla se obtiene de objetos de traducción.

// Gestión de estado de selección de idioma
const [language, setLanguage] = useState('ja'); // Establecer idioma predeterminado en japonés
// Obtener configuración de idioma
const t = translations[language];

// Ejemplo de uso
<h1 className="text-4xl font-bold">{t.title}</h1>
<p className="text-lg text-gray-700 mb-6">
  {t.scanDescription}
</p>

Consideraciones de usabilidad

Buscamos una experiencia de usuario similar a las máquinas de autopago en supermercados y tiendas de conveniencia, incorporando las siguientes decisiones de diseño:

  1. Prioridad del escaneo de códigos de barras: Se acepta la entrada de teclado en cualquier parte de la página, priorizando siempre la entrada del escáner de códigos de barras
  2. Botones grandes: Tamaños de botones optimizados para una fácil operación táctil
  3. Retroalimentación clara: Mensajes fáciles de entender que muestran los resultados de las operaciones

A través de estas decisiones de diseño, creemos haber logrado una interfaz que los usuarios pueden operar sin confusión.

6. Consideraciones operativas

Monitoreo en tiempo real y respuesta ante incidentes

Se instalaron cámaras de red en la tienda, lo que nos permite verificar el estado de la tienda en tiempo real. Si ocurre un problema, los clientes pueden llamar al número de teléfono publicado en el escaparate para obtener asistencia.

Cuando se detecta una anomalía operativa, contamos con un sistema para llegar al lugar dentro de 30 minutos a 2 horas. Además, como alternativa en caso de que el terminal de pago no funcione, podemos proporcionar un enlace de pago después del hecho para completar la transacción.

Para un funcionamiento estable, también contamos con respaldo de energía por SAI, redundancia de red y reinicios periódicos durante los períodos de inactividad.

Hubo un incidente en que el cable de alimentación del cliente se soltó porque no estaba completamente insertado, pero desde entonces el sistema ha estado funcionando de manera muy estable.

7. Resultados e impacto

Ampliación de oportunidades de venta

La apertura de la tienda desatendida nos permitió asegurar valiosas oportunidades de venta 24/7 cerca de una estación de tren céntrica. El mayor logro fue satisfacer la demanda de una tienda física mientras se superaban las limitaciones de recursos humanos.

Tendencias de métodos de pago

Todos los métodos de pago implementados se utilizan de manera bastante equitativa, pero los más populares en orden son:

  1. Tarjetas IC de transporte
  2. Pagos con código QR (PayPay, etc.)
  3. Tarjetas de crédito (pagos sin contacto)

8. Planes futuros y expansión

Expansión a ventas en línea

La actual tienda en línea de LOPPO Art Supply está construida sobre BASE, pero planeamos migrarla a un sistema basado en Square API. Esto no solo reducirá las comisiones por transacción, sino que también mejorará el flujo de compra de productos y unificará la gestión de inventario y ventas entre la tienda física y las ventas en línea.

Además, nos gustaría ofrecer la opción de comprar en línea y recoger en tienda.

Conclusión: Si puedes hacer pintura, puedes construir un sistema de caja

LOPPO ha estado reproduciendo artesanalmente diversos materiales artísticos para animación cel, y esta vez construimos artesanalmente un sistema de caja.

Las aplicaciones de software a veces pueden construirse con relativa facilidad aprovechando APIs existentes y la asistencia de LLMs, como se demuestra aquí. Dicho esto, nunca imaginamos que un sistema de caja pudiera construirse tan fácilmente, y fue una experiencia tremendamente educativa.

Principios clave

  1. Aprovechamiento de servicios existentes: Maximizar el uso de plataformas existentes como Square API
  2. Utilización de herramientas de apoyo como LLMs: Adopción proactiva de herramientas que impulsen la eficiencia del desarrollo
  3. Enfoque en el alcance mínimo necesario: Mantener la implementación simple concentrándose en las funcionalidades esenciales

Esperamos que este artículo sirva como referencia útil para cualquier persona interesada en construir un sistema de autopago o que esté considerando el uso de Square API.

El código fuente completo del sistema que construimos está disponible en GitHub. loppo-llc/loppo-register - GitHub

Bohfula / ボーフラ

Bohfula / ボーフラ

Un desarrollador de videojuegos independiente con una cabeza peculiar en forma de tetera. A menudo es convocado por Takahashi para ayudar con las operaciones y la publicidad de LOPPO.