Migración desde michangarrito/apps/mcp-server - Estándar multi-repo v2

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rckrdmrd 2026-01-16 08:26:55 -06:00
parent 1e34b6828c
commit d08364c9eb
11 changed files with 2983 additions and 3 deletions

9
.env Normal file
View File

@ -0,0 +1,9 @@
# MiChangarrito MCP Server - Environment Variables
# Backend API
BACKEND_URL=http://localhost:3141
BACKEND_API_PREFIX=/api/v1
# Server Name
MCP_SERVER_NAME=michangarrito-mcp
MCP_SERVER_VERSION=1.0.0

View File

@ -1,3 +0,0 @@
# michangarrito-mcp-server-v2
MCP Server de michangarrito - Workspace V2

1836
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "michangarrito-mcp-server",
"version": "1.0.0",
"description": "MiChangarrito MCP Server - Tools for LLM integration",
"author": "ISEM",
"private": true,
"license": "MIT",
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"axios": "^1.6.5",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.10.8",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}

150
src/index.ts Normal file
View File

@ -0,0 +1,150 @@
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { productTools } from './tools/products.js';
import { orderTools } from './tools/orders.js';
import { fiadoTools } from './tools/fiado.js';
import { customerTools } from './tools/customers.js';
import { inventoryTools } from './tools/inventory.js';
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3141';
// Create server instance
const server = new Server(
{
name: 'michangarrito-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// Combine all tools
const allTools = [
...productTools,
...orderTools,
...fiadoTools,
...customerTools,
...inventoryTools,
];
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: allTools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = allTools.find((t) => t.name === name);
if (!tool) {
return {
content: [{ type: 'text', text: `Tool not found: ${name}` }],
isError: true,
};
}
try {
const result = await tool.handler(args || {}, BACKEND_URL);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
return {
content: [{ type: 'text', text: `Error: ${message}` }],
isError: true,
};
}
});
// List resources
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'michangarrito://config/business',
name: 'Business Configuration',
description: 'Current business settings and configuration',
mimeType: 'application/json',
},
{
uri: 'michangarrito://catalog/categories',
name: 'Product Categories',
description: 'List of product categories',
mimeType: 'application/json',
},
],
}));
// Read resources
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
switch (uri) {
case 'michangarrito://config/business':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify({
name: 'MiChangarrito',
currency: 'MXN',
timezone: 'America/Mexico_City',
fiadoEnabled: true,
maxFiadoAmount: 500,
workingHours: {
open: '07:00',
close: '22:00',
},
}),
},
],
};
case 'michangarrito://catalog/categories':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify([
{ id: 'bebidas', name: 'Bebidas', icon: '🥤' },
{ id: 'botanas', name: 'Botanas', icon: '🍿' },
{ id: 'abarrotes', name: 'Abarrotes', icon: '🛒' },
{ id: 'lacteos', name: 'Lácteos', icon: '🥛' },
{ id: 'panaderia', name: 'Panadería', icon: '🍞' },
{ id: 'limpieza', name: 'Limpieza', icon: '🧹' },
]),
},
],
};
default:
throw new Error(`Resource not found: ${uri}`);
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MiChangarrito MCP Server running on stdio');
}
main().catch(console.error);

175
src/tools/customers.ts Normal file
View File

@ -0,0 +1,175 @@
import axios from 'axios';
interface Tool {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
handler: (args: any, backendUrl: string) => Promise<any>;
}
export const customerTools: Tool[] = [
{
name: 'get_customer_info',
description: 'Obtiene informacion de un cliente por telefono',
inputSchema: {
type: 'object',
properties: {
phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
},
required: ['phone'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(`${backendUrl}/api/v1/customers/phone/${args.phone}`);
return {
success: true,
customer: data,
};
} catch (error) {
return {
success: true,
customer: null,
message: 'Cliente no encontrado',
isNewCustomer: true,
};
}
},
},
{
name: 'register_customer',
description: 'Registra un nuevo cliente',
inputSchema: {
type: 'object',
properties: {
phone: {
type: 'string',
description: 'Numero de telefono',
},
name: {
type: 'string',
description: 'Nombre completo del cliente',
},
email: {
type: 'string',
description: 'Email del cliente (opcional)',
},
address: {
type: 'string',
description: 'Direccion del cliente (opcional)',
},
},
required: ['phone', 'name'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.post(`${backendUrl}/api/v1/customers`, {
phone: args.phone,
name: args.name,
email: args.email,
address: args.address,
});
return {
success: true,
customer: data,
message: 'Cliente registrado exitosamente',
};
} catch (error) {
return {
success: true,
customer: {
id: 'new-customer-id',
phone: args.phone,
name: args.name,
createdAt: new Date().toISOString(),
},
message: 'Cliente registrado exitosamente',
};
}
},
},
{
name: 'get_customer_purchase_history',
description: 'Obtiene el historial de compras de un cliente',
inputSchema: {
type: 'object',
properties: {
phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
limit: {
type: 'number',
description: 'Numero de compras a mostrar',
default: 10,
},
},
required: ['phone'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(
`${backendUrl}/api/v1/customers/phone/${args.phone}/purchases?limit=${args.limit || 10}`
);
return {
success: true,
purchases: data,
};
} catch (error) {
return {
success: true,
purchases: [],
summary: {
totalPurchases: 0,
totalSpent: 0,
averageTicket: 0,
favoriteProducts: [],
},
message: 'Sin historial de compras',
};
}
},
},
{
name: 'get_customer_stats',
description: 'Obtiene estadisticas del cliente (total compras, productos favoritos, etc)',
inputSchema: {
type: 'object',
properties: {
phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
},
required: ['phone'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(`${backendUrl}/api/v1/customers/phone/${args.phone}/stats`);
return {
success: true,
stats: data,
};
} catch (error) {
return {
success: true,
stats: {
totalOrders: 0,
totalSpent: 0,
averageTicket: 0,
lastVisit: null,
memberSince: new Date().toISOString(),
loyaltyPoints: 0,
favoriteProducts: [],
},
};
}
},
},
];

253
src/tools/fiado.ts Normal file
View File

@ -0,0 +1,253 @@
import axios from 'axios';
interface Tool {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
handler: (args: any, backendUrl: string) => Promise<any>;
}
export const fiadoTools: Tool[] = [
{
name: 'get_fiado_balance',
description: 'Consulta el saldo de fiado (credito) de un cliente',
inputSchema: {
type: 'object',
properties: {
customer_phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
customer_id: {
type: 'string',
description: 'ID del cliente',
},
},
},
handler: async (args, backendUrl) => {
try {
const phone = args.customer_phone;
const { data } = await axios.get(`${backendUrl}/api/v1/customers/phone/${phone}/fiado`);
return {
success: true,
balance: data,
};
} catch (error) {
return {
success: true,
balance: {
currentDebt: 0,
creditLimit: 500,
availableCredit: 500,
lastPaymentDate: null,
pendingItems: [],
},
message: 'Cliente sin deuda de fiado',
};
}
},
},
{
name: 'create_fiado',
description: 'Registra una compra a fiado (credito) para un cliente',
inputSchema: {
type: 'object',
properties: {
customer_phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
amount: {
type: 'number',
description: 'Monto de la compra a fiado',
},
description: {
type: 'string',
description: 'Descripcion de la compra',
},
due_date: {
type: 'string',
description: 'Fecha de vencimiento (YYYY-MM-DD)',
},
},
required: ['customer_phone', 'amount'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.post(`${backendUrl}/api/v1/customers/fiado`, {
phone: args.customer_phone,
amount: args.amount,
description: args.description,
dueDate: args.due_date,
});
return {
success: true,
fiado: data,
message: `Fiado de $${args.amount} registrado exitosamente`,
};
} catch (error) {
return {
success: true,
fiado: {
id: 'mock-fiado-id',
amount: args.amount,
status: 'pending',
createdAt: new Date().toISOString(),
},
message: `Fiado de $${args.amount} registrado exitosamente`,
};
}
},
},
{
name: 'register_fiado_payment',
description: 'Registra un abono o pago al fiado de un cliente',
inputSchema: {
type: 'object',
properties: {
customer_phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
amount: {
type: 'number',
description: 'Monto del abono',
},
payment_method: {
type: 'string',
enum: ['cash', 'transfer', 'card'],
description: 'Metodo de pago',
},
fiado_id: {
type: 'string',
description: 'ID del fiado especifico a abonar (opcional)',
},
},
required: ['customer_phone', 'amount'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.post(`${backendUrl}/api/v1/customers/fiado/payment`, {
phone: args.customer_phone,
amount: args.amount,
paymentMethod: args.payment_method || 'cash',
fiadoId: args.fiado_id,
});
return {
success: true,
payment: data,
message: `Abono de $${args.amount} registrado exitosamente`,
};
} catch (error) {
return {
success: true,
payment: {
id: 'mock-payment-id',
amount: args.amount,
remainingDebt: 0,
createdAt: new Date().toISOString(),
},
message: `Abono de $${args.amount} registrado exitosamente`,
};
}
},
},
{
name: 'get_fiado_history',
description: 'Obtiene el historial de fiados y pagos de un cliente',
inputSchema: {
type: 'object',
properties: {
customer_phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
status: {
type: 'string',
enum: ['pending', 'paid', 'overdue', 'all'],
description: 'Filtrar por estado',
},
limit: {
type: 'number',
description: 'Numero maximo de registros',
default: 10,
},
},
required: ['customer_phone'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(
`${backendUrl}/api/v1/customers/phone/${args.customer_phone}/fiado/history`
);
return {
success: true,
history: data,
};
} catch (error) {
return {
success: true,
history: {
fiados: [],
payments: [],
summary: {
totalDebts: 0,
totalPayments: 0,
currentBalance: 0,
},
},
message: 'Sin historial de fiados',
};
}
},
},
{
name: 'check_fiado_eligibility',
description: 'Verifica si un cliente puede comprar a fiado y cuanto',
inputSchema: {
type: 'object',
properties: {
customer_phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
amount: {
type: 'number',
description: 'Monto que desea fiar',
},
},
required: ['customer_phone'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(
`${backendUrl}/api/v1/customers/phone/${args.customer_phone}/fiado/eligibility`
);
const eligible = !args.amount || data.availableCredit >= args.amount;
return {
success: true,
eligible,
availableCredit: data.availableCredit,
currentDebt: data.currentDebt,
creditLimit: data.creditLimit,
message: eligible
? `Puede fiar hasta $${data.availableCredit}`
: `Credito insuficiente. Disponible: $${data.availableCredit}`,
};
} catch (error) {
return {
success: true,
eligible: true,
availableCredit: 500,
currentDebt: 0,
creditLimit: 500,
message: 'Cliente elegible para fiado',
};
}
},
},
];

173
src/tools/inventory.ts Normal file
View File

@ -0,0 +1,173 @@
import axios from 'axios';
interface Tool {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
handler: (args: any, backendUrl: string) => Promise<any>;
}
export const inventoryTools: Tool[] = [
{
name: 'check_stock',
description: 'Verifica el stock actual de un producto',
inputSchema: {
type: 'object',
properties: {
product_id: {
type: 'string',
description: 'ID del producto',
},
product_name: {
type: 'string',
description: 'Nombre del producto (busqueda)',
},
},
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(`${backendUrl}/api/v1/inventory/${args.product_id}/stock`);
return {
success: true,
stock: data,
};
} catch (error) {
return {
success: true,
stock: {
productId: args.product_id,
currentStock: 24,
minStock: 5,
maxStock: 50,
status: 'ok',
lastUpdated: new Date().toISOString(),
},
};
}
},
},
{
name: 'get_low_stock_products',
description: 'Lista productos con stock bajo que necesitan reabastecimiento',
inputSchema: {
type: 'object',
properties: {
threshold: {
type: 'number',
description: 'Umbral de stock bajo (usa el minimo configurado si no se especifica)',
},
},
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(`${backendUrl}/api/v1/inventory/low-stock`);
return {
success: true,
lowStockProducts: data,
};
} catch (error) {
return {
success: true,
lowStockProducts: [],
message: 'No hay productos con stock bajo',
};
}
},
},
{
name: 'record_inventory_movement',
description: 'Registra un movimiento de inventario (entrada, salida, ajuste)',
inputSchema: {
type: 'object',
properties: {
product_id: {
type: 'string',
description: 'ID del producto',
},
type: {
type: 'string',
enum: ['purchase', 'sale', 'adjustment', 'waste', 'return'],
description: 'Tipo de movimiento',
},
quantity: {
type: 'number',
description: 'Cantidad (positiva para entradas, negativa para salidas)',
},
reason: {
type: 'string',
description: 'Razon o nota del movimiento',
},
cost: {
type: 'number',
description: 'Costo unitario (para compras)',
},
},
required: ['product_id', 'type', 'quantity'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.post(`${backendUrl}/api/v1/inventory/movements`, {
productId: args.product_id,
type: args.type,
quantity: args.quantity,
reason: args.reason,
unitCost: args.cost,
});
return {
success: true,
movement: data,
message: `Movimiento de ${args.quantity} unidades registrado`,
};
} catch (error) {
return {
success: true,
movement: {
id: 'mock-movement-id',
productId: args.product_id,
type: args.type,
quantity: args.quantity,
createdAt: new Date().toISOString(),
},
message: `Movimiento de ${args.quantity} unidades registrado`,
};
}
},
},
{
name: 'get_inventory_value',
description: 'Calcula el valor total del inventario',
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Categoria para filtrar (opcional)',
},
},
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(`${backendUrl}/api/v1/inventory/value`);
return {
success: true,
value: data,
};
} catch (error) {
return {
success: true,
value: {
totalCost: 15000,
totalSaleValue: 22000,
potentialProfit: 7000,
productCount: 150,
lastUpdated: new Date().toISOString(),
},
};
}
},
},
];

189
src/tools/orders.ts Normal file
View File

@ -0,0 +1,189 @@
import axios from 'axios';
interface Tool {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
handler: (args: any, backendUrl: string) => Promise<any>;
}
export const orderTools: Tool[] = [
{
name: 'create_order',
description: 'Crea un nuevo pedido para un cliente',
inputSchema: {
type: 'object',
properties: {
customer_phone: {
type: 'string',
description: 'Numero de telefono del cliente',
},
items: {
type: 'array',
description: 'Lista de productos con cantidades',
items: {
type: 'object',
properties: {
product_id: { type: 'string' },
quantity: { type: 'number' },
},
},
},
payment_method: {
type: 'string',
enum: ['cash', 'card', 'fiado'],
description: 'Metodo de pago',
},
notes: {
type: 'string',
description: 'Notas adicionales del pedido',
},
},
required: ['customer_phone', 'items'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.post(`${backendUrl}/api/v1/orders`, {
customerPhone: args.customer_phone,
items: args.items,
paymentMethod: args.payment_method || 'cash',
notes: args.notes,
source: 'whatsapp',
});
return {
success: true,
order: data,
message: `Pedido ${data.orderNumber} creado exitosamente`,
};
} catch (error) {
const orderNumber = `MCH-${Date.now().toString(36).toUpperCase()}`;
return {
success: true,
order: {
id: 'mock-id',
orderNumber,
status: 'pending',
items: args.items,
total: args.items.reduce((sum: number, i: any) => sum + (i.quantity * 18), 0),
createdAt: new Date().toISOString(),
},
message: `Pedido ${orderNumber} creado exitosamente`,
};
}
},
},
{
name: 'get_order_status',
description: 'Consulta el estado de un pedido',
inputSchema: {
type: 'object',
properties: {
order_number: {
type: 'string',
description: 'Numero de pedido (ej: MCH-ABC123)',
},
customer_phone: {
type: 'string',
description: 'Telefono del cliente para buscar sus pedidos',
},
},
},
handler: async (args, backendUrl) => {
try {
let url = `${backendUrl}/api/v1/orders`;
if (args.order_number) {
url = `${backendUrl}/api/v1/orders/number/${args.order_number}`;
} else if (args.customer_phone) {
url = `${backendUrl}/api/v1/orders?phone=${args.customer_phone}`;
}
const { data } = await axios.get(url);
return {
success: true,
orders: Array.isArray(data) ? data : [data],
};
} catch (error) {
return {
success: true,
orders: [],
message: 'No se encontraron pedidos activos',
};
}
},
},
{
name: 'update_order_status',
description: 'Actualiza el estado de un pedido',
inputSchema: {
type: 'object',
properties: {
order_id: {
type: 'string',
description: 'ID del pedido',
},
status: {
type: 'string',
enum: ['confirmed', 'preparing', 'ready', 'completed', 'cancelled'],
description: 'Nuevo estado del pedido',
},
},
required: ['order_id', 'status'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.patch(`${backendUrl}/api/v1/orders/${args.order_id}/status`, {
status: args.status,
});
return {
success: true,
order: data,
message: `Pedido actualizado a estado: ${args.status}`,
};
} catch (error) {
return {
success: true,
message: `Pedido actualizado a estado: ${args.status}`,
};
}
},
},
{
name: 'cancel_order',
description: 'Cancela un pedido',
inputSchema: {
type: 'object',
properties: {
order_id: {
type: 'string',
description: 'ID del pedido a cancelar',
},
reason: {
type: 'string',
description: 'Razon de la cancelacion',
},
},
required: ['order_id'],
},
handler: async (args, backendUrl) => {
try {
await axios.patch(`${backendUrl}/api/v1/orders/${args.order_id}/status`, {
status: 'cancelled',
cancellationReason: args.reason,
});
return {
success: true,
message: 'Pedido cancelado exitosamente',
};
} catch (error) {
return {
success: true,
message: 'Pedido cancelado exitosamente',
};
}
},
},
];

155
src/tools/products.ts Normal file
View File

@ -0,0 +1,155 @@
import axios from 'axios';
import { z } from 'zod';
interface Tool {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
handler: (args: any, backendUrl: string) => Promise<any>;
}
export const productTools: Tool[] = [
{
name: 'list_products',
description: 'Lista productos disponibles, opcionalmente filtrados por categoria',
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Categoria para filtrar (bebidas, botanas, abarrotes, lacteos)',
},
search: {
type: 'string',
description: 'Termino de busqueda por nombre',
},
limit: {
type: 'number',
description: 'Numero maximo de productos a retornar',
default: 20,
},
},
},
handler: async (args, backendUrl) => {
try {
const params = new URLSearchParams();
if (args.category) params.append('category', args.category);
if (args.search) params.append('search', args.search);
if (args.limit) params.append('limit', args.limit.toString());
const { data } = await axios.get(`${backendUrl}/api/v1/products?${params}`);
return {
success: true,
count: data.length,
products: data.map((p: any) => ({
id: p.id,
name: p.name,
price: p.salePrice,
stock: p.stock,
category: p.category,
available: p.stock > 0,
})),
};
} catch (error) {
// Mock response for development
return {
success: true,
count: 5,
products: [
{ id: '1', name: 'Coca-Cola 600ml', price: 18.0, stock: 24, category: 'bebidas', available: true },
{ id: '2', name: 'Sabritas Original', price: 15.0, stock: 12, category: 'botanas', available: true },
{ id: '3', name: 'Leche Lala 1L', price: 28.0, stock: 8, category: 'lacteos', available: true },
{ id: '4', name: 'Pan Bimbo Grande', price: 45.0, stock: 5, category: 'panaderia', available: true },
{ id: '5', name: 'Fabuloso 1L', price: 32.0, stock: 6, category: 'limpieza', available: true },
].filter((p) => !args.category || p.category === args.category),
};
}
},
},
{
name: 'get_product_details',
description: 'Obtiene detalles completos de un producto especifico',
inputSchema: {
type: 'object',
properties: {
product_id: {
type: 'string',
description: 'ID del producto',
},
product_name: {
type: 'string',
description: 'Nombre del producto (busqueda aproximada)',
},
},
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(`${backendUrl}/api/v1/products/${args.product_id}`);
return {
success: true,
product: data,
};
} catch (error) {
return {
success: true,
product: {
id: args.product_id || '1',
name: args.product_name || 'Producto de ejemplo',
description: 'Descripcion del producto',
price: 18.0,
stock: 24,
category: 'bebidas',
barcode: '7501055300051',
},
};
}
},
},
{
name: 'check_product_availability',
description: 'Verifica si un producto esta disponible y su cantidad en stock',
inputSchema: {
type: 'object',
properties: {
product_id: {
type: 'string',
description: 'ID del producto',
},
quantity: {
type: 'number',
description: 'Cantidad deseada',
default: 1,
},
},
required: ['product_id'],
},
handler: async (args, backendUrl) => {
try {
const { data } = await axios.get(`${backendUrl}/api/v1/products/${args.product_id}`);
const requested = args.quantity || 1;
return {
success: true,
available: data.stock >= requested,
currentStock: data.stock,
requestedQuantity: requested,
message:
data.stock >= requested
? `Si hay ${requested} unidades disponibles`
: `Solo hay ${data.stock} unidades, necesitas ${requested}`,
};
} catch (error) {
return {
success: true,
available: true,
currentStock: 24,
requestedQuantity: args.quantity || 1,
message: 'Producto disponible',
};
}
},
},
];

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}