La restricción del artesano.
Productos hechos a mano desde Ucrania, con envío a siete países. La infraestructura financiera y logística sobre la que funciona el mercado —Monobank para pagos, Nova Post para envíos, los tipos de cambio diarios del Banco Nacional de Ucrania para fijar precios— no tiene equivalente en un stack de ecommerce europeo genérico.
El modelo de pago por sí solo descarta Stripe. El modelo de pago fraccionado de Monobank cobra un depósito al confirmar el pedido y dispara el cargo del saldo cuando el pedido alcanza el estado «listo para enviar». Son dos tramos de pago, sincronizados con el estado de producción, con firmas ECDSA en cada webhook y en cada transición. Stripe no tiene ninguna primitiva para esto. Tendrías que simularlo con dos cargos independientes y reconciliar el estado tú mismo, y cualquier brecha en la reconciliación es una superficie de fraude.
El modelo logístico descarta Shippo. Nova Post, el transportista dominante en Ucrania, expone un directorio de ciudades, sucursales y clasificadores que hay que sincronizar y consultar localmente. Las guías de envío se generan de forma programática en el momento del envío y el PDF de la etiqueta resultante necesita alojarse en algún lugar recuperable. Nada en el mundo de las abstracciones de transportistas genéricos se ajusta a esto de forma limpia.
El modelo de precios descarta los ajustes de divisa por región. El artesano vende algunos productos a nivel global en EUR y otros solo en UAH porque la cadena de suministro es local. Unos pocos productos llevan anulaciones explícitas en UAH o USD que difieren de la base convertida por tipo de cambio. Las reglas por región no pueden expresar «este producto concreto tiene su propio precio en UAH independientemente del locale del comprador». Las anulaciones por producto sí pueden.
Una configuración de Stripe + Shippo + WooCommerce no puede modelar «depósito ahora en UAH, saldo al estar listo en EUR, gestionado por Nova Post» sin código a medida en cada costura. La construcción a medida no fue una preferencia: fue un requisito del mercado.
Lo que construimos.
Una sola plataforma: una tienda de cara al cliente en 7 locales, un ciclo de vida de producción (borrador → en curso → listo → enviado) y un panel de operaciones que el fundador gestiona en solitario.
La capa de pago gestiona el flujo completo de pago fraccionado de Monobank. Depósito cobrado al confirmar el pedido. Cargo del saldo disparado cuando el estado del pedido pasa a «listo para enviar». Cada webhook se verifica contra una clave pública PEM de Monobank cacheada usando ECDSA SHA256 con rotación. Los tramos de pago son registros en la base de datos, vinculados al estado del pedido, no reconciliados a posteriori.
La capa de precios lee las anulaciones de divisa por producto antes de recurrir a la base convertida por tipo de cambio. Cada fila de producto tiene un precio base en EUR y columnas opcionales de anulación en UAH y USD. Cuando hay una anulación presente, la tienda la usa. Cuando está ausente, el tipo de cambio diario del Banco Nacional de Ucrania convierte la base en EUR. El tipo del NBU se sincroniza por cron cada día.
La capa logística funciona sobre un directorio de Nova Post sincronizado. Las ciudades, sucursales y clasificadores se paginan desde la API de Nova Post y se almacenan localmente: la selección de dirección en el checkout consulta la copia local, no la API en vivo. En el momento del envío, la plataforma genera una guía a través de la API de Nova Post y almacena el PDF de la etiqueta resultante como un objeto prefirmado en Cloudflare R2.
El ciclo de vida de producción es el andamiaje que une los tramos de pago y el envío. La máquina de estados del pedido —cinco estados, un registro de eventos, un carrusel de fotos de progreso— existe para que la transición de depósito a saldo tenga un disparador definido y la generación de la guía tenga un momento definido. La máquina de estados no es el producto. Es el armazón portante de los flujos financieros y logísticos.
- F · 01Pago fraccionado con Monobank
- Depósito cobrado al confirmar el pedido; el saldo se dispara con «listo para enviar». Los tramos de pago son registros de base de datos vinculados al estado del pedido. Verificación de webhooks ECDSA SHA256 con rotación de clave PEM.
- F · 02Multidivisa por producto
- Precio base en EUR con columnas opcionales de anulación en UAH y USD por producto. El resolvedor de precios lee la anulación antes de recurrir al tipo de cambio diario del NBU. Control a nivel de artesano, no a nivel de región.
- F · 03Integración con Nova Post
- Directorio de ciudades y sucursales sincronizado localmente mediante un trabajo programado. Guías generadas en el momento del envío a través de la API de Nova Post. PDF de etiquetas almacenados como objetos prefirmados en Cloudflare R2.
- F · 04Ciclo de vida de producción
- Máquina de pedidos de cinco estados (borrador → en curso → listo → enviado → cancelado) con registro de eventos y carrusel de fotos de progreso. Andamiaje para los tramos de pago y la generación de guías, no el objeto central.
- F · 05Tienda en 7 locales
- Tienda de cara al cliente en siete idiomas. Las anulaciones de divisa por producto y el respaldo del tipo de cambio del NBU hacen que cada locale vea precios contextualmente correctos sin configuración por región.
- F · 06Panel de operaciones a medida
- Kanban de producción, sparkline de ingresos de 30 días en EUR, indicadores de salud de integraciones (Monobank / Nova Post / R2 / email), visualización de frescura del cron y una franja de atención. Una sola pantalla para toda la operación.
Las decisiones de ingeniería fintech.
Verificación de webhooks con ECDSA. Monobank firma los webhooks de pago con una clave privada; el receptor debe verificar contra una clave pública cacheada, rotar la caché cuando Monobank publica una nueva y rechazar cualquier webhook que no pase la comprobación de firma. Una autenticación de juguete sería un valor de cabecera comprobado. La autenticación real es ECDSA SHA256 contra una clave PEM con rotación.
Tramos de pago en la capa de datos. El modelo de pago fraccionado no es una configuración de pasarela de pago. Es un modelo de datos: un pedido tiene uno o más tramos de pago, cada uno con un importe, una divisa, un estado y un disparador. El tramo del depósito se dispara al confirmar el pedido. El tramo del saldo se dispara en la transición de estado. El registro de eventos lleva el rastro de auditoría. Reconciliar dos cargos de API no relacionados a posteriori significaría que la fuente de verdad es el panel de Monobank, no tu propia base de datos. Rechazamos ese intercambio.
Anulaciones de divisa por producto. La tabla de precios tiene tres columnas de precio opcionales más allá de la base en EUR: UAH, USD y un indicador de «precio bajo petición». El resolvedor de precios las lee en orden de prioridad: anulación explícita → base convertida por tipo de cambio → petición. El artesano controla los precios a nivel de producto, no a nivel de región.
La especificidad del mercado local es el foso. Un competidor puede copiar el diseño de la tienda. No puede ejecutar el mismo modelo de pago sin reescribir la capa de pago para la API específica de Monobank.
Sincronización del directorio de Nova Post. Llamar a la API en vivo de Nova Post en cada entrada de dirección en el checkout acoplaría el flujo de compra a una dependencia externa sin ningún SLA que la plataforma controle. La sincronización del directorio se ejecuta como un trabajo programado: páginas de ciudades, sucursales y clasificadores escritas localmente, el checkout consulta la copia local. La única llamada en vivo a Nova Post en la ruta crítica de compra es la generación de la guía en el momento del envío, y eso ocurre después de que el pedido ya esté pagado.
El resultado.
El fundador gestiona toda la operación desde una sola pantalla. Kanban de producción por estado de pedido, un sparkline de ingresos de 30 días en EUR, indicadores de salud de las integraciones de Monobank, Nova Post, R2 y email, visualización de la frescura del cron y una franja de atención que muestra cualquier cosa que requiera acción. Cerca de 1–2K líneas de componente de panel, hecho a propósito, no genérico.
145 commits en 3 semanas. 659 archivos de test que cubren la ruta ECDSA de Monobank, la reconciliación del directorio de Nova Post, la semántica de los lotes del carrito, la captura de instantáneas de divisa y las transiciones de estado de los tramos de pago. Trazas de Sentry al 5% en producción con un depurador de PII. Axiom para eventos estructurados de heartbeat del cron. La observabilidad de nivel producción no se añadió a posteriori: formó parte del primer sprint.
El stack no es a medida porque quisiéramos escribir código a medida. Es a medida porque el mercado no tiene raíles estándar.
