This commit is contained in:
Lucas Tettamanti
2026-02-04 16:06:51 -03:00
parent 2f8e267268
commit 5e79f17d00
21 changed files with 291 additions and 599 deletions

View File

@@ -2,8 +2,6 @@ import {
handleListOrders,
handleGetProductsWithStock,
handleCreateTestOrder,
handleCreatePaymentLink,
handleSimulateMpWebhook,
} from "../handlers/testing.js";
import { handleGetOrderStats } from "../handlers/stats.js";
@@ -59,47 +57,3 @@ export const makeCreateTestOrder = (tenantIdOrFn) => async (req, res) => {
}
};
export const makeCreatePaymentLink = (tenantIdOrFn) => async (req, res) => {
try {
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
const { woo_order_id, amount } = req.body || {};
if (!woo_order_id) {
return res.status(400).json({ ok: false, error: "woo_order_id_required" });
}
if (!amount || Number(amount) <= 0) {
return res.status(400).json({ ok: false, error: "amount_required" });
}
const result = await handleCreatePaymentLink({
tenantId,
wooOrderId: woo_order_id,
amount: Number(amount)
});
res.json(result);
} catch (err) {
console.error("[testing] createPaymentLink error:", err);
res.status(500).json({ ok: false, error: err.message || "internal_error" });
}
};
export const makeSimulateMpWebhook = (tenantIdOrFn) => async (req, res) => {
try {
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
const { woo_order_id, amount } = req.body || {};
if (!woo_order_id) {
return res.status(400).json({ ok: false, error: "woo_order_id_required" });
}
const result = await handleSimulateMpWebhook({
tenantId,
wooOrderId: woo_order_id,
amount: Number(amount) || 0
});
res.json(result);
} catch (err) {
console.error("[testing] simulateMpWebhook error:", err);
res.status(500).json({ ok: false, error: err.message || "internal_error" });
}
};

View File

@@ -20,6 +20,7 @@ export async function getSettings({ tenantId }) {
pickup_hours_start::text as pickup_hours_start,
pickup_hours_end::text as pickup_hours_end,
schedule,
delivery_zones,
created_at, updated_at
FROM tenant_settings
WHERE tenant_id = $1
@@ -48,6 +49,7 @@ export async function upsertSettings({ tenantId, settings }) {
pickup_hours_start,
pickup_hours_end,
schedule,
delivery_zones,
} = settings;
const sql = `
@@ -55,9 +57,9 @@ export async function upsertSettings({ tenantId, settings }) {
tenant_id, store_name, bot_name, store_address, store_phone,
delivery_enabled, delivery_days, delivery_hours_start, delivery_hours_end, delivery_min_order,
pickup_enabled, pickup_days, pickup_hours_start, pickup_hours_end,
schedule
schedule, delivery_zones
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
ON CONFLICT (tenant_id) DO UPDATE SET
store_name = COALESCE(EXCLUDED.store_name, tenant_settings.store_name),
bot_name = COALESCE(EXCLUDED.bot_name, tenant_settings.bot_name),
@@ -73,6 +75,7 @@ export async function upsertSettings({ tenantId, settings }) {
pickup_hours_start = COALESCE(EXCLUDED.pickup_hours_start, tenant_settings.pickup_hours_start),
pickup_hours_end = COALESCE(EXCLUDED.pickup_hours_end, tenant_settings.pickup_hours_end),
schedule = COALESCE(EXCLUDED.schedule, tenant_settings.schedule),
delivery_zones = COALESCE(EXCLUDED.delivery_zones, tenant_settings.delivery_zones),
updated_at = NOW()
RETURNING
id, tenant_id,
@@ -85,6 +88,7 @@ export async function upsertSettings({ tenantId, settings }) {
pickup_hours_start::text as pickup_hours_start,
pickup_hours_end::text as pickup_hours_end,
schedule,
delivery_zones,
created_at, updated_at
`;
@@ -104,6 +108,7 @@ export async function upsertSettings({ tenantId, settings }) {
pickup_hours_start || null,
pickup_hours_end || null,
schedule ? JSON.stringify(schedule) : null,
delivery_zones ? JSON.stringify(delivery_zones) : null,
];
const { rows } = await pool.query(sql, params);

View File

@@ -42,6 +42,7 @@ export async function handleGetSettings({ tenantId }) {
pickup_hours_start: "08:00",
pickup_hours_end: "20:00",
schedule: createDefaultSchedule(),
delivery_zones: {},
is_default: true,
};
}
@@ -60,6 +61,7 @@ export async function handleGetSettings({ tenantId }) {
pickup_hours_start: settings.pickup_hours_start?.slice(0, 5) || "08:00",
pickup_hours_end: settings.pickup_hours_end?.slice(0, 5) || "20:00",
schedule,
delivery_zones: settings.delivery_zones || {},
is_default: false,
};
}
@@ -103,7 +105,8 @@ function buildScheduleFromLegacy(settings) {
function validateSchedule(schedule) {
if (!schedule || typeof schedule !== "object") return;
const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/;
// Acepta HH:MM o HH:MM:SS (la BD puede devolver con segundos)
const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/;
for (const type of ["delivery", "pickup"]) {
const typeSchedule = schedule[type];
@@ -240,6 +243,7 @@ export async function handleSaveSettings({ tenantId, settings }) {
delivery_hours_end: result.delivery_hours_end?.slice(0, 5),
pickup_hours_start: result.pickup_hours_start?.slice(0, 5),
pickup_hours_end: result.pickup_hours_end?.slice(0, 5),
delivery_zones: result.delivery_zones || {},
},
message: "Configuración guardada correctamente",
};

View File

@@ -1,5 +1,4 @@
import { createOrder, syncOrdersIncremental } from "../../4-woo-orders/wooOrders.js";
import { createPreference, reconcilePayment } from "../../6-mercadopago/mercadoPago.js";
import { listProducts } from "../db/repo.js";
import * as ordersRepo from "../../4-woo-orders/ordersRepo.js";
@@ -73,74 +72,3 @@ export async function handleCreateTestOrder({ tenantId, basket, address, wa_chat
};
}
/**
* Crea un link de pago de MercadoPago
*/
export async function handleCreatePaymentLink({ tenantId, wooOrderId, amount }) {
if (!wooOrderId) {
throw new Error("missing_woo_order_id");
}
if (!amount || Number(amount) <= 0) {
throw new Error("invalid_amount");
}
const pref = await createPreference({
tenantId,
wooOrderId,
amount: Number(amount),
});
return {
ok: true,
preference_id: pref?.preference_id || null,
init_point: pref?.init_point || null,
sandbox_init_point: pref?.sandbox_init_point || null,
};
}
/**
* Simula un webhook de MercadoPago con pago exitoso
* No pasa por el endpoint real (requiere firma HMAC)
* Crea un payment mock y llama a reconcilePayment directamente
*/
export async function handleSimulateMpWebhook({ tenantId, wooOrderId, amount }) {
if (!wooOrderId) {
throw new Error("missing_woo_order_id");
}
// Crear payment mock con status approved
const mockPaymentId = `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const mockPayment = {
id: mockPaymentId,
status: "approved",
status_detail: "accredited",
external_reference: `${tenantId}|${wooOrderId}`,
transaction_amount: Number(amount) || 0,
currency_id: "ARS",
date_approved: new Date().toISOString(),
date_created: new Date().toISOString(),
payment_method_id: "test",
payment_type_id: "credit_card",
payer: {
email: "test@test.com",
},
order: {
id: `pref-test-${wooOrderId}`,
},
};
// Reconciliar el pago (actualiza mp_payments y cambia status de orden a processing)
const result = await reconcilePayment({
tenantId,
payment: mockPayment,
});
return {
ok: true,
payment_id: mockPaymentId,
woo_order_id: result?.woo_order_id || wooOrderId,
status: "approved",
order_status: "processing",
reconciled: result?.payment || null,
};
}