mogos propuesta · v1.0 · jul 2026

Cotización pagada → orden

El pago entra. La orden no aparece.

Una cotización de servicio se paga y, en ocasiones, hay que convertirla en orden. Hoy no existe ninguna conversión — y hay algo más incómodo: en este modelo el flujo corre al revés. Primero alguien crea la orden a mano, y de ella nace la prefactura. Ir en la otra dirección se puede, pero hay que hacerlo a propósito.

El modelo
1 columna anulable
Para crear la orden
1 campo falta · 1 no es de fiar
Dependencia
El pago al proveedor no existe todavía
Riesgo
Bajo · aditivo
01El flujo corre al revés
verificado en el código

Buscamos la conversión antes de diseñarla. No existe, y no existe de ninguna forma: no hay createOrderFromQuote, ni convertQuotation, ni toOrder, ni fromQuote. El módulo de cotizaciones tiene cero referencias a órdenes o fletes, y ServiceQuote no tiene orderId, ni freightId, ni containerId.

Y hay un solo sitio en toda la API donde nace una orden: orders.service.ts, dentro de una transacción con reintento. Uno.

-- el ciclo real, hoy
Orden  (creada a mano)  ──consolidar N──▶  Flete  ──▶  crea su factura sola
                                                          (Quotation, $0, PENDING)

-- lo que pide Pedro
Cotización  ──pagada──▶  ???  ──▶  Orden

La factura nace de la cosa física, no al revés. Eso no invalida lo que pide Pedro — la mayoría de sus cotizaciones sí terminan en un envío — pero significa que la conversión va contra el grano del modelo, y las cosas que van contra el grano se diseñan explícitas, no automáticas por debajo.

El precedente que existe, y por qué no lo copiamos

ProductSale.orderId enlaza una venta con la orden que mueve el carro. Pero es @unique —uno a uno— y su comentario explica por qué: «un carro físico es una orden y una venta». Además se enlaza a mano, con un endpoint de admin: tampoco es una conversión.

Aquí uno a uno sería falso: tres productos de tres proveedores viajan por separado. Una cotización puede producir varias órdenes.

02Qué falta para hacer una orden
exactamente un campo

Sorprendentemente poco. Crear una orden exige solo dos campos; todo lo demás —el usuario, el almacén destino, el flete, la dirección, el tracking, las cajas, el packing list— es opcional.

Lo que exige una orden¿Lo tiene la cotización?
shippingTypeLo tiene, pero no es de fiar. Ver abajo.
originWarehouseIdNo. La cotización tiene corridorId (China → Venezuela), no un almacén. ¿Yiwu, Guangzhou, Shenzhen?
El segundo campo existe, pero es una suposición

shippingType es obligatorio al crear la cotización de servicio, y el asistente lo arranca en SEA. Pero Pedro dijo lo contrario en la misma reunión: «aquí en este punto todavía no se sabe si va a ser marítimo o aéreo, nos faltan datos» — y añadió cuándo sí se sabe: «ya cuando nosotros le pagamos al proveedor ya tenemos todo».

O sea: el campo se rellena meses antes de conocerse. La conversión no puede confiarlo; tiene que reconfirmarlo. Son dos preguntas, no una.

Del almacén de origen hay mejor noticia: el dato ya está modelado. WarehouseCorridor relaciona almacén y corredor con un role (ORIGIN · DESTINATION · TRANSIT) y una priority. El almacén de origen de un corredor es una consulta.

Pero nada lo lee

WarehouseCorridorRoleEnum.ORIGIN aparece una sola vez en toda la API, y es dentro de un ejemplo de Swagger. Ningún código resuelve el almacén de origen desde el corredor. El dato está, la consulta no existe.

Es la única pieza de lógica nueva de este proyecto: un resolutor de una consulta, con priority como desempate. Y si el corredor viene vacío —hay cotizaciones antiguas sin él— se pregunta, no se adivina.

03El disparador
una dependencia que no escondo

Antes del disparador, una palabra que decide el diseño: «en ocasiones». No toda cotización de servicio pagada se vuelve una orden. El propio Pedro lo dijo: «hay casos puntualitos como ese, que el carajo dice: mira, mándamelo a esta dirección que yo hago el envío» — clientes que tienen su propia logística en China. La mayoría se convierten. No todas.

Eso solo ya descarta el automatismo ciego: un sistema que convierte siempre obliga a cancelar a mano las que no tocaban, que es más trabajo que crear a mano las que sí.

Y sobre cuándo, Pedro fue preciso: «yo a la hora de realizar el pago al proveedor, yo coloco aquí pago… y luego esto se convierte en un flete».

Ese evento no existe en la plataforma. Pagarle al proveedor es la cuenta por pagar, que hoy es un tab que dice «Próximamente». El pago que la plataforma sí conoce es el que hace el cliente sobre la cotización de servicio — y son cosas distintas: el cliente paga primero, Mogos le paga al proveedor después, y la mercancía se mueve al final.

Lo que hacemos

Una acción explícita, «Convertir en orden», que aparece en el centro de acciones en cuanto la cotización de servicio queda pagada. Alguien la ejecuta, y se le preguntan las dos cosas: el almacén de origen y confirmar aéreo o marítimo.

Cuando exista el pago al proveedor, esa misma acción puede dispararse sola — el trabajo no se tira, se le pone el disparador correcto.

Lo que no hacemos

Hacerlo automático hoy con el pago del cliente. Sería el mismo botón con otro significado, cambiado en silencio: se crearían órdenes de mercancía que todavía no está pagada al proveedor, y por tanto que nadie ha empezado a fabricar.

Pedro pidió automático, y lo tendrá. Pero automático desde el evento que él describió, no desde el que hay a mano.

04El modelo
una columna
model Order {
  serviceQuoteId  String?   // de qué cotización salió. N órdenes por cotización.
}

Eso es todo el modelo. Va en la orden, no en la cotización, precisamente para que sean varias: tres productos de tres proveedores chinos salen en tres momentos distintos, y forzar @unique obligaría a mentir en el segundo envío.

Y da lo que hace falta en las dos direcciones: desde la cotización, «estas son sus órdenes»; desde la orden, «esta venía de aquí» — que es lo que hoy no se puede contestar y obliga a buscar a mano.

05El packing list llega después
y no puede bloquear

En la reunión Pedro dijo que al pagar ya se tiene el packing list — «Ya, sí, claro». A veces. Otras veces el proveedor lo manda días más tarde. Exigirlo para crear la orden convertiría un trámite ocasional en un bloqueo permanente.

No hace falta: packingListExtractionId y packingListFiles son opcionales al crear una orden. La orden nace sin él y lo recibe cuando llegue — con el extractor que ya existe, que lee el archivo y saca los CBM, que es exactamente la funcionalidad que Pedro mencionó.

1
La cotización de servicio se paga ya existe

El dinero se registra en su prefactura — ServiceQuote.amountPaid existe pero nadie lo escribe; el estado real se lee de paymentStatus. Aparece la acción.

2
Convertir en orden nuevo

Hereda el cliente. Resuelve el almacén de origen del corredor y reconfirma aéreo o marítimo, porque ese campo se rellenó cuando nadie lo sabía.

3
Llega el packing list después

El extractor lee el archivo y saca los CBM. Ya funciona para las órdenes que se crean a mano.

4
Aprobar y consolidar ya existe

Las dos acciones ya están en el centro de acciones. La orden entra al flujo normal sin nada especial.

La orden convertida no es una orden distinta. Desde el paso 3 es indistinguible de una creada a mano, y recorre el mismo camino hasta el contenedor. Eso es lo que hace que este proyecto sea pequeño.

06Dónde aparece
el centro de acciones ya existe

Pedro pidió «un centro de acciones donde el equipo vea tareas pendientes: el cliente pagó, entonces conviértelo en orden y agrega el packing list». Ese centro ya está construido y es la pantalla de inicio del admin: cinco tipos de acción, carriles de «mías / sin tomar / de otros», «Tomar» y «Soltar», realtime y atajos de teclado.

Tipo de acciónEstado
APPROVE_ORDERya existe
CONSOLIDATE_ORDERya existe
RESPOND_CONVERSATIONya existe
ATTEND_HANDOFFya existe
COLLECT_PAYMENTya existe
CONVERT_TO_ORDERlo único nuevo

Y el feed se calcula al leer — no hay tabla de cola que mantener ni sincronizar. Añadir un tipo es añadir una consulta al conjunto que ya se ejecuta en paralelo. Pedro pidió una pantalla que ya tiene; lo que faltaba era una fila en ella.

07Qué NO hacemos
y por qué
×

Convertir siempre, automáticamente

Dos razones, y cualquiera basta. Una: Pedro dijo «en ocasiones» — hay clientes con logística propia en China. Dos: él describió el pago al proveedor como disparador, y ese evento no existe; usar el del cliente sería cambiar el significado del botón en silencio.

×

Exigir el packing list

Llega del proveedor, a veces días después. Es opcional en el modelo por una buena razón. Exigirlo convertiría un trámite ocasional en un bloqueo permanente.

×

Confiar en el tipo de envío guardado

Es obligatorio al crear y el asistente lo pone en SEA, meses antes de que se sepa. La conversión lo reconfirma. Un contenedor que sale por avión cuesta caro.

×

Adivinar el almacén de origen

Si el corredor no lo resuelve, se pregunta. Una orden en el almacén equivocado se descubre cuando la mercancía no aparece, que es el peor momento posible.

×

Una orden por cotización

Sería copiar ProductSale.orderId @unique sin su razón. Tres proveedores despachan en tres momentos.

×

Tocar el camino Orden → Flete → Contenedor

Funciona y mueve carga real. La orden convertida entra por el mismo sitio que cualquier otra y desde ahí es indistinguible. Ese es todo el truco.

08Qué se puede romper
y cómo lo sabemos
RiesgoArnés
Órdenes huérfanas El cliente paga, se crea la orden, y el trato se cae porque el proveedor no puede entregar. La orden se cancela — ya hay estado para eso — pero conviene una prueba de que cancelar la orden no toca la factura ni el pago.
Conversión doble Dos personas ejecutan la acción a la vez y salen dos órdenes. El centro de acciones ya resuelve esto con su mecanismo de «Tomar», que tiene un índice único parcial detrás. Hay que usarlo, no reinventarlo.
El almacén de origen mal resuelto La consulta es nueva y no tiene precedente en el repo. Prueba con un corredor que tenga dos almacenes de origen y distinta priority, y otra con corredor nulo que debe pedir el dato en vez de elegir uno.
Órdenes sin packing list llenando la cola Entran al centro de acciones como APPROVE_ORDER, que ya escala a prioridad alta a las 48 horas. Conviene medir si el volumen nuevo ahoga la cola antes de soltarlo.
La dependencia real Mientras el pago al proveedor no exista, esto queda como acción manual. Es la mitad de lo que Pedro pidió. La otra mitad vive en el proyecto de la ganancia real, y conviene decírselo en esos términos y no como si estuviera resuelto.

Una columna, una consulta y una fila en una pantalla que ya existe.

Es el proyecto más pequeño de los tres, y lo es porque casi todo estaba hecho: la orden solo exige dos campos, la cotización ya tiene uno, el almacén de origen ya está modelado, el packing list ya se extrae solo y el centro de acciones ya está construido. Lo que faltaba era el enlace — y decidir, en voz alta, cuál pago dispara qué.

Se añade

Order.serviceQuoteId. Nada más.

Se escribe

El resolutor de almacén de origen por corredor.

Se reusa

El extractor de packing list y el centro de acciones.

Se espera

El pago al proveedor, para el disparador automático.

mogos · propuesta de cotización pagada → orden · v1.0 Fuente: reunión con Pedro, 21 de julio de 2026