# MiChangarrito - Integraciones Externas ## Indice de Integraciones | Integracion | Categoria | Prioridad | Complejidad | |-------------|-----------|-----------|-------------| | Stripe | Pagos/Suscripciones | P0 | Media | | WhatsApp Business API | Mensajeria | P0 | Alta | | OpenRouter/LLM | Inteligencia Artificial | P0 | Media | | Mercado Pago Point | Terminal Pago | P1 | Media | | Firebase FCM | Push Notifications | P1 | Baja | | Clip | Terminal Pago | P2 | Media | | CoDi (Banxico) | Pagos QR | P2 | Alta | | Google Vision | OCR | P2 | Baja | | OpenAI Whisper | Transcripcion | P2 | Baja | --- ## 1. Stripe ### 1.1 Proposito - Suscripciones mensuales (Plan Changarrito, Plan Tiendita) - Pagos con tarjeta - Referencias de pago OXXO - Gestion de clientes y facturacion ### 1.2 Documentacion - Dashboard: https://dashboard.stripe.com/ - API Docs: https://docs.stripe.com/api - Subscriptions: https://docs.stripe.com/billing/subscriptions - OXXO: https://docs.stripe.com/payments/oxxo ### 1.3 SDK ```bash npm install stripe ``` ### 1.4 Configuracion ```typescript // config/stripe.config.ts import Stripe from 'stripe'; export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-12-18.acacia', }); export const STRIPE_CONFIG = { prices: { changarrito_monthly: process.env.STRIPE_PRICE_CHANGARRITO, tiendita_monthly: process.env.STRIPE_PRICE_TIENDITA, tokens_1000: process.env.STRIPE_PRICE_TOKENS_1000, tokens_3000: process.env.STRIPE_PRICE_TOKENS_3000, tokens_8000: process.env.STRIPE_PRICE_TOKENS_8000, tokens_20000: process.env.STRIPE_PRICE_TOKENS_20000, }, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, }; ``` ### 1.5 Flujos Principales #### Crear Suscripcion ```typescript async function createSubscription( customerId: string, priceId: string, ): Promise { return stripe.subscriptions.create({ customer: customerId, items: [{ price: priceId }], payment_behavior: 'default_incomplete', payment_settings: { save_default_payment_method: 'on_subscription', }, expand: ['latest_invoice.payment_intent'], }); } ``` #### Crear Pago OXXO ```typescript async function createOxxoPayment( amount: number, // en centavos customerEmail: string, customerName: string, ): Promise { return stripe.paymentIntents.create({ amount, currency: 'mxn', payment_method_types: ['oxxo'], payment_method_data: { type: 'oxxo', billing_details: { email: customerEmail, name: customerName, }, }, }); } ``` #### Webhook Handler ```typescript // webhooks/stripe.webhook.ts @Post('webhook/stripe') async handleStripeWebhook( @Req() req: RawBodyRequest, @Headers('stripe-signature') signature: string, ) { const event = stripe.webhooks.constructEvent( req.rawBody, signature, STRIPE_CONFIG.webhookSecret, ); switch (event.type) { case 'customer.subscription.created': case 'customer.subscription.updated': await this.handleSubscriptionUpdate(event.data.object); break; case 'customer.subscription.deleted': await this.handleSubscriptionCancelled(event.data.object); break; case 'invoice.paid': await this.handleInvoicePaid(event.data.object); break; case 'invoice.payment_failed': await this.handlePaymentFailed(event.data.object); break; case 'payment_intent.succeeded': await this.handlePaymentSucceeded(event.data.object); break; } return { received: true }; } ``` ### 1.6 Webhooks a Configurar | Evento | Accion | |--------|--------| | customer.subscription.created | Activar suscripcion, asignar tokens | | customer.subscription.updated | Actualizar plan | | customer.subscription.deleted | Cancelar acceso | | invoice.paid | Renovar periodo, resetear tokens | | invoice.payment_failed | Notificar, marcar past_due | | payment_intent.succeeded | Confirmar pago OXXO/tokens | ### 1.7 Comisiones | Metodo | Comision | |--------|----------| | Tarjeta nacional | 3.6% + $3 MXN + IVA | | OXXO | 3.6% + $3 MXN + IVA | | Tarjeta internacional | +0.5% | --- ## 2. WhatsApp Business API ### 2.1 Proposito - Canal principal de comunicacion con duenos - Gestion del negocio via chat con LLM - Recepcion de pedidos de clientes - Envio de notificaciones y recordatorios ### 2.2 Documentacion - Meta for Developers: https://developers.facebook.com/ - Cloud API: https://developers.facebook.com/docs/whatsapp/cloud-api - Webhooks: https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks ### 2.3 Configuracion Inicial 1. Crear app en Meta for Developers 2. Agregar producto "WhatsApp" 3. Configurar numero de telefono 4. Verificar negocio 5. Configurar webhooks ### 2.4 Endpoints Principales ```typescript // config/whatsapp.config.ts export const WHATSAPP_CONFIG = { apiVersion: 'v18.0', baseUrl: 'https://graph.facebook.com', phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID, accessToken: process.env.WHATSAPP_ACCESS_TOKEN, verifyToken: process.env.WHATSAPP_VERIFY_TOKEN, }; export const getWhatsAppUrl = (endpoint: string) => `${WHATSAPP_CONFIG.baseUrl}/${WHATSAPP_CONFIG.apiVersion}/${WHATSAPP_CONFIG.phoneNumberId}/${endpoint}`; ``` ### 2.5 Enviar Mensaje ```typescript // services/whatsapp.service.ts import axios from 'axios'; export class WhatsAppService { private readonly headers = { Authorization: `Bearer ${WHATSAPP_CONFIG.accessToken}`, 'Content-Type': 'application/json', }; // Mensaje de texto async sendTextMessage(to: string, text: string): Promise { await axios.post( getWhatsAppUrl('messages'), { messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'text', text: { body: text }, }, { headers: this.headers }, ); } // Mensaje con template (para iniciar conversacion) async sendTemplateMessage( to: string, templateName: string, components?: any[], ): Promise { await axios.post( getWhatsAppUrl('messages'), { messaging_product: 'whatsapp', to, type: 'template', template: { name: templateName, language: { code: 'es_MX' }, components, }, }, { headers: this.headers }, ); } // Mensaje con botones interactivos async sendButtonMessage( to: string, body: string, buttons: { id: string; title: string }[], ): Promise { await axios.post( getWhatsAppUrl('messages'), { messaging_product: 'whatsapp', to, type: 'interactive', interactive: { type: 'button', body: { text: body }, action: { buttons: buttons.map((b) => ({ type: 'reply', reply: { id: b.id, title: b.title }, })), }, }, }, { headers: this.headers }, ); } // Enviar imagen async sendImageMessage( to: string, imageUrl: string, caption?: string, ): Promise { await axios.post( getWhatsAppUrl('messages'), { messaging_product: 'whatsapp', to, type: 'image', image: { link: imageUrl, caption }, }, { headers: this.headers }, ); } } ``` ### 2.6 Webhook Handler ```typescript // webhooks/whatsapp.webhook.ts @Controller('webhook/whatsapp') export class WhatsAppWebhookController { // Verificacion inicial (GET) @Get() verify(@Query() query: any): string { const mode = query['hub.mode']; const token = query['hub.verify_token']; const challenge = query['hub.challenge']; if (mode === 'subscribe' && token === WHATSAPP_CONFIG.verifyToken) { return challenge; } throw new ForbiddenException('Verification failed'); } // Recibir mensajes (POST) @Post() async handleWebhook(@Body() body: any): Promise<{ status: string }> { const entry = body.entry?.[0]; const changes = entry?.changes?.[0]; const value = changes?.value; if (value?.messages) { for (const message of value.messages) { await this.processMessage(message, value.contacts?.[0]); } } if (value?.statuses) { for (const status of value.statuses) { await this.processStatus(status); } } return { status: 'ok' }; } private async processMessage(message: any, contact: any): Promise { const from = message.from; const messageType = message.type; const timestamp = message.timestamp; // Determinar si es dueno o cliente const tenant = await this.findTenantByPhone(from); const isOwner = !!tenant; // Procesar segun tipo switch (messageType) { case 'text': await this.handleTextMessage(from, message.text.body, isOwner, tenant); break; case 'image': await this.handleImageMessage(from, message.image, isOwner, tenant); break; case 'audio': await this.handleAudioMessage(from, message.audio, isOwner, tenant); break; case 'interactive': await this.handleInteractiveMessage(from, message.interactive, tenant); break; } } } ``` ### 2.7 Templates Requeridos | Template | Proposito | Variables | |----------|-----------|-----------| | welcome | Bienvenida a nuevo negocio | {{1}} nombre | | daily_summary | Resumen diario de ventas | {{1}} fecha, {{2}} total, {{3}} transacciones | | low_stock_alert | Alerta de stock bajo | {{1}} productos | | fiado_reminder | Recordatorio de fiado | {{1}} cliente, {{2}} monto, {{3}} dias | | order_confirmation | Confirmacion de pedido | {{1}} numero, {{2}} total | | payment_received | Confirmacion de pago | {{1}} monto | ### 2.8 Rate Limits | Tier | Mensajes/dia | |------|--------------| | Inicial | 250 | | Verificado | 1,000 | | Escalado | 10,000+ | --- ## 3. OpenRouter / LLM Providers ### 3.1 Proposito - Asistente IA para duenos (consultas, gestion) - Atencion a clientes (pedidos, precios) - Procesamiento de texto natural - Soporte tecnico automatizado ### 3.2 Documentacion - OpenRouter: https://openrouter.ai/docs - OpenAI: https://platform.openai.com/docs - Anthropic: https://docs.anthropic.com/ ### 3.3 Configuracion Agnostica ```typescript // config/llm.config.ts export type LLMProvider = 'openrouter' | 'openai' | 'anthropic' | 'ollama'; export interface LLMConfig { provider: LLMProvider; apiKey: string; baseUrl: string; model: string; maxTokens: number; temperature: number; } export const getLLMConfig = (): LLMConfig => { const provider = process.env.LLM_PROVIDER as LLMProvider; const configs: Record> = { openrouter: { baseUrl: 'https://openrouter.ai/api/v1', model: 'anthropic/claude-3-haiku', }, openai: { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4o-mini', }, anthropic: { baseUrl: 'https://api.anthropic.com/v1', model: 'claude-3-haiku-20240307', }, ollama: { baseUrl: 'http://localhost:11434/api', model: 'llama3', }, }; return { provider, apiKey: process.env.LLM_API_KEY, baseUrl: process.env.LLM_BASE_URL || configs[provider].baseUrl, model: process.env.LLM_MODEL || configs[provider].model, maxTokens: parseInt(process.env.LLM_MAX_TOKENS || '4096'), temperature: parseFloat(process.env.LLM_TEMPERATURE || '0.7'), }; }; ``` ### 3.4 LLM Service ```typescript // services/llm.service.ts import OpenAI from 'openai'; export class LLMService { private client: OpenAI; private config: LLMConfig; constructor() { this.config = getLLMConfig(); this.client = new OpenAI({ apiKey: this.config.apiKey, baseURL: this.config.baseUrl, }); } async chat( messages: { role: 'system' | 'user' | 'assistant'; content: string }[], tools?: any[], ): Promise<{ content: string; toolCalls?: any[]; usage: any }> { const response = await this.client.chat.completions.create({ model: this.config.model, messages, tools, max_tokens: this.config.maxTokens, temperature: this.config.temperature, }); const choice = response.choices[0]; return { content: choice.message.content || '', toolCalls: choice.message.tool_calls, usage: response.usage, }; } async chatWithTools( userMessage: string, systemPrompt: string, tools: Tool[], context: any, ): Promise { const messages = [ { role: 'system' as const, content: systemPrompt }, { role: 'user' as const, content: userMessage }, ]; const toolDefinitions = tools.map((t) => ({ type: 'function' as const, function: { name: t.name, description: t.description, parameters: t.parameters, }, })); let response = await this.chat(messages, toolDefinitions); // Procesar tool calls while (response.toolCalls && response.toolCalls.length > 0) { for (const toolCall of response.toolCalls) { const tool = tools.find((t) => t.name === toolCall.function.name); if (tool) { const args = JSON.parse(toolCall.function.arguments); const result = await tool.execute(args, context); messages.push({ role: 'assistant', content: null, tool_calls: [toolCall], } as any); messages.push({ role: 'tool' as any, content: JSON.stringify(result), tool_call_id: toolCall.id, } as any); } } response = await this.chat(messages, toolDefinitions); } return response.content; } } ``` ### 3.5 System Prompts ```typescript // prompts/owner.prompt.ts export const OWNER_SYSTEM_PROMPT = ` Eres el asistente virtual de MiChangarrito, ayudando al dueno de un negocio. CAPACIDADES: - Consultar ventas del dia, semana o mes - Consultar ganancias y utilidades - Ver inventario y stock - Alertar sobre productos por agotarse - Registrar ventas por chat - Agregar o modificar productos - Generar reportes - Recordar fiados pendientes PERSONALIDAD: - Amigable y servicial - Respuestas cortas y directas - Usa lenguaje coloquial mexicano - Ofrece sugerencias proactivas CONTEXTO DEL NEGOCIO: - Nombre: {{business_name}} - Giro: {{business_type}} - Plan: {{plan_name}} Responde siempre en espanol mexicano. `; // prompts/customer.prompt.ts export const CUSTOMER_SYSTEM_PROMPT = ` Eres el asistente de {{business_name}}, atendiendo a un cliente. CAPACIDADES: - Informar sobre productos disponibles - Dar precios - Tomar pedidos - Informar horarios - Informar sobre estado de pedidos RESTRICCIONES: - NO puedes dar descuentos sin autorizacion - NO tienes acceso a informacion financiera - Redirige consultas complejas al dueno Responde amablemente y de forma concisa. `; ``` ### 3.6 Costos por Modelo | Modelo | Input/1M tokens | Output/1M tokens | |--------|-----------------|------------------| | GPT-4o-mini | $0.15 | $0.60 | | Claude 3 Haiku | $0.25 | $1.25 | | Claude 3.5 Sonnet | $3.00 | $15.00 | | Llama 3 8B (OpenRouter) | $0.07 | $0.07 | --- ## 4. Mercado Pago Point ### 4.1 Proposito - Cobros con tarjeta via terminal fisica - Meses sin intereses ### 4.2 Documentacion - https://www.mercadopago.com.mx/developers/es/docs/mp-point/overview ### 4.3 SDK ```bash npm install mercadopago ``` ### 4.4 Integracion ```typescript // services/mercadopago.service.ts import { MercadoPagoConfig, Payment, PaymentMethod } from 'mercadopago'; export class MercadoPagoService { private client: MercadoPagoConfig; constructor() { this.client = new MercadoPagoConfig({ accessToken: process.env.MERCADOPAGO_ACCESS_TOKEN, }); } // Crear payment intent para terminal async createPointPayment( amount: number, description: string, externalReference: string, ): Promise { const response = await fetch( `https://api.mercadopago.com/point/integration-api/devices/${process.env.MERCADOPAGO_DEVICE_ID}/payment-intents`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.MERCADOPAGO_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount, description, external_reference: externalReference, print_on_terminal: true, }), }, ); return response.json(); } // Consultar estado de pago async getPaymentIntent(paymentIntentId: string): Promise { const response = await fetch( `https://api.mercadopago.com/point/integration-api/payment-intents/${paymentIntentId}`, { headers: { Authorization: `Bearer ${process.env.MERCADOPAGO_ACCESS_TOKEN}`, }, }, ); return response.json(); } // Cancelar payment intent async cancelPaymentIntent(paymentIntentId: string): Promise { await fetch( `https://api.mercadopago.com/point/integration-api/payment-intents/${paymentIntentId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.MERCADOPAGO_ACCESS_TOKEN}`, }, }, ); } } ``` ### 4.5 Webhooks ```typescript @Post('webhook/mercadopago') async handleMercadoPagoWebhook(@Body() body: any) { const { type, data } = body; if (type === 'point_integration_wh') { const paymentIntent = await this.mpService.getPaymentIntent(data.id); if (paymentIntent.state === 'FINISHED') { await this.salesService.confirmPayment( paymentIntent.external_reference, 'card_mercadopago', paymentIntent.id, ); } } return { received: true }; } ``` --- ## 5. Firebase Cloud Messaging ### 5.1 Proposito - Push notifications a app movil - Alertas en tiempo real ### 5.2 Documentacion - https://firebase.google.com/docs/cloud-messaging ### 5.3 SDK ```bash npm install firebase-admin ``` ### 5.4 Configuracion ```typescript // config/firebase.config.ts import * as admin from 'firebase-admin'; const serviceAccount = JSON.parse( process.env.FIREBASE_SERVICE_ACCOUNT_JSON || fs.readFileSync(process.env.FIREBASE_SERVICE_ACCOUNT_PATH, 'utf8'), ); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); export const messaging = admin.messaging(); ``` ### 5.5 Enviar Notificacion ```typescript // services/push.service.ts export class PushNotificationService { async sendToDevice( deviceToken: string, title: string, body: string, data?: Record, ): Promise { await messaging.send({ token: deviceToken, notification: { title, body }, data, android: { priority: 'high', notification: { sound: 'default', clickAction: 'FLUTTER_NOTIFICATION_CLICK', }, }, apns: { payload: { aps: { sound: 'default', badge: 1, }, }, }, }); } async sendToTopic( topic: string, title: string, body: string, data?: Record, ): Promise { await messaging.send({ topic, notification: { title, body }, data, }); } } ``` --- ## 6. Clip ### 6.1 Proposito - Cobros con tarjeta via terminal Clip ### 6.2 Documentacion - https://developer.clip.mx/ ### 6.3 Integracion (REST API) ```typescript // services/clip.service.ts export class ClipService { private readonly baseUrl = 'https://api-gw.payclip.com'; private readonly apiKey = process.env.CLIP_API_KEY; async createPaymentRequest( amount: number, reference: string, ): Promise { const response = await fetch(`${this.baseUrl}/paymentrequest/`, { method: 'POST', headers: { 'x-api-key': this.apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount, currency: 'MXN', reference, message: `Cobro ${reference}`, }), }); return response.json(); } async cancelPaymentRequest(code: string): Promise { await fetch(`${this.baseUrl}/paymentrequest/code/${code}`, { method: 'DELETE', headers: { 'x-api-key': this.apiKey }, }); } async getTransaction(receiptNo: string): Promise { const response = await fetch( `${this.baseUrl}/payments/receipt-no/${receiptNo}`, { headers: { 'x-api-key': this.apiKey }, }, ); return response.json(); } } ``` --- ## 7. CoDi (via Openpay) ### 7.1 Proposito - Cobros con QR sin comisiones ### 7.2 Documentacion - https://site.openpay.mx/docs/codi.html ### 7.3 SDK ```bash npm install openpay ``` ### 7.4 Integracion ```typescript // services/codi.service.ts const Openpay = require('openpay'); export class CodiService { private openpay: any; constructor() { this.openpay = new Openpay( process.env.OPENPAY_MERCHANT_ID, process.env.OPENPAY_PRIVATE_KEY, false, // production = false para sandbox ); } async createQRCharge( amount: number, description: string, orderId: string, ): Promise<{ qrUrl: string; chargeId: string }> { return new Promise((resolve, reject) => { this.openpay.charges.create( { method: 'codi', amount, description, order_id: orderId, codi_options: { mode: 'QR_CODE', }, }, (error: any, charge: any) => { if (error) { reject(error); } else { resolve({ qrUrl: charge.payment_method.barcode_url, chargeId: charge.id, }); } }, ); }); } async getChargeStatus(chargeId: string): Promise { return new Promise((resolve, reject) => { this.openpay.charges.get(chargeId, (error: any, charge: any) => { if (error) { reject(error); } else { resolve(charge.status); } }); }); } } ``` --- ## 8. Google Cloud Vision (OCR) ### 8.1 Proposito - Leer listas de precios desde fotos - Procesar notas de compra - Extraer texto de imagenes ### 8.2 Documentacion - https://cloud.google.com/vision/docs ### 8.3 Integracion ```typescript // services/ocr.service.ts import vision from '@google-cloud/vision'; export class OCRService { private client: vision.ImageAnnotatorClient; constructor() { this.client = new vision.ImageAnnotatorClient({ keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS, }); } async extractText(imageUrl: string): Promise { const [result] = await this.client.textDetection(imageUrl); const detections = result.textAnnotations; if (detections && detections.length > 0) { return detections[0].description || ''; } return ''; } async extractProducts( imageUrl: string, ): Promise<{ name: string; price: number }[]> { const text = await this.extractText(imageUrl); // Usar LLM para estructurar el texto const structured = await this.llmService.chat([ { role: 'system', content: 'Extrae productos y precios del siguiente texto. Responde en JSON: [{"name": "...", "price": 0.00}]', }, { role: 'user', content: text }, ]); return JSON.parse(structured.content); } } ``` --- ## 9. OpenAI Whisper (Transcripcion) ### 9.1 Proposito - Transcribir audios de WhatsApp - Comandos de voz ### 9.2 Documentacion - https://platform.openai.com/docs/guides/speech-to-text ### 9.3 Integracion ```typescript // services/transcription.service.ts import OpenAI from 'openai'; import fs from 'fs'; export class TranscriptionService { private openai: OpenAI; constructor() { this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); } async transcribeAudio(audioPath: string): Promise { const transcription = await this.openai.audio.transcriptions.create({ file: fs.createReadStream(audioPath), model: 'whisper-1', language: 'es', }); return transcription.text; } async transcribeFromUrl(audioUrl: string): Promise { // Descargar audio const response = await fetch(audioUrl); const buffer = await response.arrayBuffer(); const tempPath = `/tmp/audio_${Date.now()}.ogg`; fs.writeFileSync(tempPath, Buffer.from(buffer)); try { const text = await this.transcribeAudio(tempPath); return text; } finally { fs.unlinkSync(tempPath); } } } ``` --- ## Resumen de Variables de Entorno ```env # Stripe STRIPE_SECRET_KEY=sk_test_... STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_WEBHOOK_SECRET=whsec_... # WhatsApp WHATSAPP_VERIFY_TOKEN=... WHATSAPP_ACCESS_TOKEN=... WHATSAPP_PHONE_NUMBER_ID=... WHATSAPP_BUSINESS_ACCOUNT_ID=... # LLM LLM_PROVIDER=openrouter LLM_API_KEY=... LLM_MODEL=anthropic/claude-3-haiku LLM_BASE_URL=https://openrouter.ai/api/v1 # Mercado Pago MERCADOPAGO_ACCESS_TOKEN=... MERCADOPAGO_DEVICE_ID=... # Clip CLIP_API_KEY=... CLIP_MERCHANT_ID=... # CoDi (Openpay) OPENPAY_MERCHANT_ID=... OPENPAY_PRIVATE_KEY=... # Firebase FIREBASE_SERVICE_ACCOUNT_PATH=./firebase-sa.json # Google Cloud GOOGLE_APPLICATION_CREDENTIALS=./gcp-sa.json # OpenAI (Whisper) OPENAI_API_KEY=... ``` --- **Version:** 1.0.0 **Fecha:** 2026-01-04