Miguel Angel Meza Mendez

Ingeniero en sistemas, intentando ser mejor cada día.

Trabajo en el corporativo de Converse México, en su tienda en línea. Casi siempre estoy en el backend de Magento, pero también hago partes de la PWA en React, pruebas de carga y consultas a la base de datos.

Me gusta entender por qué falla algo antes de arreglarlo, y dejarlo con pruebas para que no vuelva a pasar.

Trabajo
Converse México
Desde
Ciudad de México · remoto
Miguel Angel Meza Mendez
19

módulos propios de Magento en Converse

+350 mil

clientes listos para migrar con sus pedidos, carritos y wishlist

98.4 %

de peticiones desde caché tras arreglar los 502

291

pruebas automatizadas en mi propia plataforma

Experiencia

Dónde he trabajado y los problemas que me tocó resolver.

Converse México

may 2026 – hoy

Desarrollador Magento

Entré para el backend de Magento y terminé tocando casi todo: módulos nativos, la PWA en React, integraciones, pruebas de rendimiento y análisis de la base de datos. Llevo 19 módulos propios y cambios en más de 30 de los existentes.

Adobe Commerce 2.4.8 Cloud PHP 8 GraphQL MySQL Fastly PWA Studio React Apollo Jest Cypress k6 Botpress
datos

La migración de más de 350 mil clientes con todo su historial

Para pasar de la tienda anterior a la nueva hay que llevar a cada cliente con sus direcciones, sus pedidos (con facturas, envíos y notas de crédito), su wishlist y su carrito activo. Sin perder nada, y en una sola noche de mantenimiento.

Lista para producción. En la corrida de prueba: 5.4 millones de filas, entre ellas 184 mil pedidos, 151 mil facturas, 145 mil envíos, 258 mil wishlists y 14 mil carritos activos, sin perder un registro.

Cómo lo resolví
  • Scripts en Python que leen los respaldos de mysqldump directamente y en streaming, tabla por tabla. La primera versión cargaba todo en memoria y moría a la mitad por falta de RAM.
  • Remapeo de los atributos personalizados de clientes y direcciones (el teléfono y diez campos de dirección de México) a los IDs de la nueva plataforma, y descarte automático de las columnas que la nueva no tiene.
  • Un paso final que vuelve a conectar pedidos, wishlist y carritos con el catálogo nuevo por SKU.
  • En una de las corridas, el SQL de 2.25 GB llegó corrupto al servidor y el import tronó a la mitad. Desde entonces todo viaja comprimido y se verifica con sha256 antes y después.
  • Encontré y corregí un bug de mi propio parser: solo leía el primer bloque INSERT de cada tabla y contaba 2,867 clientes en lugar de cientos de miles.
  • Dejé el procedimiento documentado paso a paso para la noche de la migración: mantenimiento, respaldo fresco, limpieza, importación y remapeo.

Con: Python, mysqldump, SQL, gzip, sha256

rendimiento

La ficha de producto no se podía cachear y la tienda empezó a dar 502

Un plugin metía el ID de sesión en el contexto HTTP, así que Fastly guardaba una versión de la página por cada visitante. Además, la plantilla de reseñas imprimía datos del cliente dentro de HTML compartido. Con tráfico alto, PHP no alcanzaba.

Prueba de carga con k6 (10 usuarios, 10 minutos): hit ratio de caché de 98.4 % en 43 mil peticiones, 0 errores 5xx y la página de categoría bajó de 636 ms a 93 ms.

Cómo lo resolví
  • Saqué el ID de sesión del contexto HTTP y lo generé en el navegador.
  • Los datos del cliente ahora los pinta el JS desde customer-data, no la plantilla cacheada.
  • Agregué cache tags por producto a las consultas GraphQL propias, para que Fastly purgue solo lo que cambia.
  • Versioné el JS del buscador, que se quedaba un año en caché sin forma de invalidarlo.

Con: plugins de Magento, GraphQL @cache, Fastly, k6

módulo

Las preventas se operaban con SQL a mano

Abrir registros, mover productos a una categoría restringida y dar o quitar permisos a cada grupo de clientes se hacía con UPDATE directos a la base.

Unas 3 mil líneas que reemplazaron al módulo anterior y al SQL manual.

Cómo lo resolví
  • Un módulo nuevo con una máquina de cinco estados (programada, registro abierto, registro cerrado, venta activa, cerrada) que mueve un cron cada 5 minutos, con botones de respaldo en el admin.
  • Guarda el grupo original de cada cliente para regresarlo al cerrar la campaña.
  • Se integra con los permisos de catálogo de Adobe Commerce y reindexa en el momento, sin esperar al cron.
  • Widget en la tienda para registrarse y exportación a PDF de los registrados.

Con: db_schema, cron, observers, UI Components, CatalogPermissions

operación

Limpiar cuentas basura sin tocar a clientes reales

La base tenía bots, cuentas de prueba, cuentas sin confirmar y abandonadas, mezcladas con clientes reales.

En producción.

Cómo lo resolví
  • Siete filtros SQL enchufables por inyección de dependencias, con vista previa en el admin.
  • Una cola en la base que procesa un cron por lotes. Justo antes de borrar, vuelve a revisar si el cliente compró o inició sesión mientras esperaba.
  • Borra con el repositorio oficial de Magento para que corran los eventos nativos.
  • De paso, las cuentas que entran con Google o Facebook ya se marcan como confirmadas.

Con: DI, cola en db_schema, cron, comando de consola

operación

Marcar pedidos como entregados, uno por uno

Llegaban archivos con miles de pedidos para marcar como entregados y se hacía a mano.

En producción.

Cómo lo resolví
  • Subes un CSV en el admin y solo se tocan pedidos en proceso y 100 % facturados.
  • Si el pedido no tiene envío, lo crea. Con varias fuentes de inventario fallaba al descontar stock, y lo resolví asignando la fuente correcta.
  • Lo que no cumple se reporta en un CSV de rechazos, y cada pedido lleva un comentario de auditoría.

Con: Sales, Shipments, MSI

seguridad

Login con Google que fallaba y menús de cuenta equivocados

El regreso de Google fallaba la validación anti-CSRF porque el state solo vivía en la sesión, y el menú de cuenta mostraba un estado equivocado porque venía de una caché compartida.

Cómo lo resolví
  • Guardé el state también en una cookie sensible y lo comparo con hash_equals.
  • El menú de cuenta se pinta del lado del cliente.
  • El cierre de sesión pasó a POST con form_key.

Con: OAuth, customer-data, RequireJS

pwa

Meses sin intereses con tarjeta en la PWA

El formulario de tarjeta de la PWA nunca ofrecía meses; solo el botón de PayPal, en su propio popup.

Unas 400 líneas de pruebas. Probado en sandbox con una Visa nueva a 3 meses y una AMEX guardada a 6.

Cómo lo resolví
  • En el backend, una query GraphQL que obtiene el client token de PayPal y un arreglo para que las tarjetas guardadas no perdieran el plazo.
  • En el front, Card Fields con los plazos que da el banco, respetando los mínimos del admin, para tarjeta nueva y guardada.

Con: React, PayPal JS SDK, GraphQL, Jest

pwa

El carrito dejaba agregar piezas que no había

El botón + dejaba pasar piezas que Magento rechazaba después con «Not enough items for sale».

Cómo lo resolví
  • Expuse en GraphQL lo vendible de la talla elegida.
  • Un selector de cantidad propio que se detiene en el tope en el mismo clic.
  • Separé los errores de stock de los de mínimos y múltiplos, para no bloquear de más.

Con: React, Apollo, GraphQL, Jest

integración

Un chatbot de soporte sin pagar por IA

Querían un chatbot de soporte sin pagar llamadas a un modelo de IA, y con forma de pasar a una persona.

Funciona de punta a punta sin costo por llamadas a IA; la IA se sumará solo en casos específicos.

Cómo lo resolví
  • Bot en Botpress con menú: tiendas, pedido, envío, productos y hablar con alguien.
  • Firma OAuth 1.0a escrita a mano para consultar la API REST de Magento.
  • Paso a un agente humano en LiveChat con su API de chat y webhooks, que Botpress no trae.
  • En la PWA, la burbuja se puede arrastrar solo en pantallas táctiles.

Con: Botpress ADK, TypeScript, OAuth 1.0a, LiveChat, Pointer Events

calidad

Medir la PWA en un celular lento

Había una auditoría de rendimiento de la PWA y nadie había comprobado qué era cierto.

Confirmé 4 puntos, descarté 2 y encontré 3 riesgos nuevos; uno era un aviso de actualización que veía casi todo visitante nuevo.

Cómo lo resolví
  • La medí con k6 browser en un Chromium real, simulando 4G y un CPU cuatro veces más lento.

Con: k6 browser, Chromium

front

Carga infinita y errores de scripts de terceros

Un loader se quedaba colgado porque un script se cargaba dos veces y la política de seguridad (CSP) bloqueaba scripts inline. Además, el JS de Dotdigital y el de recomendaciones de Adobe tronaban en la ficha.

En producción.

Cómo lo resolví
  • Loader compatible con CSP y sin la doble carga.
  • Dos módulos pequeños que corrigen los scripts de terceros sin tocar vendor/.
  • Pruebas en Cypress para reproducir cada falla antes de corregirla.

Con: CSP, RequireJS, Cypress

QACi

jun 2024 – feb 2026

Ingeniero en Sistemas

Era el único de sistemas. Además del soporte a los equipos y del WordPress, rehíce su sistema de certificados y su sitio en Laravel 12, que siguen en uso.

Laravel 12 PHP 8 MySQL Blade Bootstrap 5 FPDI QR Power BI
migración

Pasar de WordPress y PHP plano a Laravel sin romper folios

El sitio estaba en WordPress y el sistema de certificados en PHP plano, repartido en carpetas, con SQL concatenado y contraseñas guardadas en texto plano.

Cómo lo resolví
  • Un solo proyecto en Laravel 12 con el sitio público y el panel interno.
  • Consultas parametrizadas, contraseñas con bcrypt, validación, CSRF y rutas protegidas.
  • El buscador consulta la base vieja y la nueva, así los certificados ya emitidos siguen validando.

Con: Laravel 12, Query Builder, MySQL

automatización

Certificados que se llenaban a mano

Cada certificado se armaba a mano o con un generador frágil.

17 plantillas oficiales de cinco normas ISO.

Cómo lo resolví
  • Un formulario genera el PDF sobre la plantilla oficial de cada norma, con la tipografía de la marca y el color de la norma.
  • Cada certificado lleva un QR que apunta a su página de verificación.
  • El mismo flujo en inglés.

Con: FPDI, FPDF, endroid/qr-code

flujo

El ciclo de vida del certificado

Cómo lo resolví
  • Nace como borrador; al liberarlo se regenera con la plantilla final y un QR nuevo.
  • Se puede mostrar u ocultar, renovar, y marcar como vencido o cancelado.
  • La consulta pública enseña el PDF que corresponde a cada estado.
automatización

Cartas de inicio de certificación

Cómo lo resolví
  • PDF sobre el membrete oficial con el texto armado a partir de la razón social, el RFC y la dirección.
  • Una o varias normas por carta (sistema integral), con QR.

Con: FPDI, MySQL

formulario

Las solicitudes llegaban sin estructura

Las solicitudes de cotización llegaban por correo, cada una con datos distintos.

Cómo lo resolví
  • Un formulario por pasos con unos 133 campos, donde las preguntas cambian según la norma que elige el cliente.
  • Avisa al momento a cinco buzones internos y el panel guarda el detalle por norma.

Con: Blade, JavaScript, Bootstrap 5

sitio

Noticias con likes y comentarios

Cómo lo resolví
  • Panel para publicar noticias con varias imágenes.
  • Feed público con likes y comentarios sin cuenta, limitados a uno por navegador.

Con: JavaScript, fetch, SweetAlert2

datos

Cómo cambian las empresas al certificarse

Cómo lo resolví
  • Dashboards en Power BI que comparan a cada empresa antes y después de certificarse y de mejorar sus procesos.

Con: Power BI, SQL

Proyectos

Lo que he construido por mi cuenta y en equipo.

Sitio de MJ Nexus
sitio · seo

Sitio de MJ Nexus

El sitio de la agencia que fundé con José: páginas web, sistemas, apps y automatización para negocios pequeños.

  • SEO local desde el código: datos estructurados (JSON-LD), Open Graph, sitemap y robots.
  • Alta en Google Business Profile, Search Console, Bing Places y Bing Webmaster.
  • Formulario de contacto con honeypot y límite de intentos contra spam.
Laravel 13 Blade Tailwind ver el sitio →
Blisse MX
tienda · en construcción

Blisse MX

Mi tienda en línea de ropa americana. Ya está publicada; los productos y el pago vienen después.

  • Inventario por talla y color; una pieza única es una variante con stock 1.
  • La bolsa funciona sin cuenta y se fusiona con la tuya al iniciar sesión.
  • Categorías de Mujer, Hombre y Unisex, carruseles en la portada y panel propio para productos y secciones.
  • Las fotos se optimizan al subirlas (máximo 1600 px, en JPG).
Laravel 13 Tailwind MySQL GD ver la tienda →
App de entrenamiento
app · pwa

App de entrenamiento

La hice para que clientes y coaches lleven rutinas, comidas y progreso en un solo lugar, desde el celular. Lleva unos 50 usuarios activos a 2 meses de su lanzamiento.

  • El historial nunca se sobrescribe: cada cambio a una rutina o plan de comidas cierra la versión anterior, así las semanas pasadas y las estadísticas no se mueven.
  • Cada coach solo puede ver y editar a sus propios clientes, con políticas de acceso y pruebas que lo cubren.
  • Se instala como app (PWA), está pensada primero para pantalla táctil y convierte en el navegador las fotos HEIC del iPhone.
  • Chat entre coach y cliente, notificaciones y un resumen semanal que se genera como imagen.
Laravel 13 PHP 8.4 MySQL Tailwind PWA PHPUnit ver la app →
equipo de 3 · en uso

Dragon Gym

Para un gimnasio que necesitaba controlar ventas, inventario y membresías. Lo hicimos entre tres y lo siguen usando.

  • Mi parte: el inventario y el punto de venta del panel, y en la app el pago de membresía por transferencia con comprobante.
  • Panel web en PHP con MVC propio: punto de venta que descuenta inventario en una transacción, corte de caja diario y siete gráficas de estadísticas.
  • Reportes en Excel y PDF, recuperación de contraseña por correo y roles de administrador y coach.
  • App en Flutter para socios: acceso por SMS con Firebase y PIN, ocupación del gym en tiempo real, promociones con código de barras y pago de membresía con comprobante.
  • La app y el panel comparten la misma base de datos a través de una API REST.
PHP MySQL Flutter Dart Firebase

Fuera del código

Lo que hago cuando no estoy programando.

Gym

todos los días

Fútbol

Básquetbol

Videojuegos

competitivos

Contacto

¿Tienes una vacante o un proyecto en mente? Escríbeme a mezamendezmiguel@hotmail.com o por WhatsApp; suelo contestar el mismo día.