[{"data":1,"prerenderedAt":428},["ShallowReactive",2],{"navigation":3,"post-el-algoritmo-que-se-moria-en-cdmx":26,"surround-el-algoritmo-que-se-moria-en-cdmx":421},[4],{"title":5,"path":6,"stem":7,"children":8,"page":25},"Blog","/blog","blog",[9,13,17,21],{"title":10,"path":11,"stem":12},"From Mockup to Market: My End-to-End Product Design Process","/blog/from-mockup-to-market","blog/from-mockup-to-market",{"title":14,"path":15,"stem":16},"How I Built My Design System from Scratch","/blog/how-i-built-my-own-design-system-from-scratch","blog/how-i-built-my-own-design-system-from-scratch",{"title":18,"path":19,"stem":20},"The Psychology of Color in UI Design","/blog/psychology-of-color-in-ui-design","blog/psychology-of-color-in-ui-design",{"title":22,"path":23,"stem":24},"The Case for Slow Design in a Fast-Paced Digital World","/blog/slow-design-in-fast-paced-digital-world","blog/slow-design-in-fast-paced-digital-world",false,{"_id":27,"author":28,"body":31,"categories":403,"date":416,"excerpt":417,"image":418,"slug":419,"title":420},"cb0ffb12-515e-4629-8cba-0a777b25bdec",{"avatar":29,"name":30},null,"David de los Santos Cuy Sanchez",[32,43,51,60,68,76,84,97,120,124,141,149,157,173,182,190,199,214,218,226,250,253,261,284,287,295,303,315,327,339,347,355,363,378,386,394],{"_key":33,"_type":34,"children":35,"markDefs":41,"style":42},"90d3e62f478d","block",[36],{"_key":37,"_type":38,"marks":39,"text":40},"3a434f38fbbf","span",[],"Hace algunos años trabajé en un proyecto de marketing por teléfono. El objetivo era generar listas de números telefónicos válidos para hacer campañas de llamadas en distintas ciudades de México. Si en algún momento recibiste una de esas llamadas... lo siento. No fue personal.",[],"normal",{"_key":44,"_type":34,"children":45,"markDefs":50,"style":42},"4e47ac2bdfc4",[46],{"_key":47,"_type":38,"marks":48,"text":49},"c0e508fd210a",[],"El punto técnico del asunto era este: necesitábamos generar números de teléfono que fueran válidos según la regulación del IFT (Instituto Federal de Telecomunicaciones). Y para eso, construimos un algoritmo. Uno que funcionaba. Pero que a cierta escala, simplemente se moría.",[],{"_key":52,"_type":34,"children":53,"markDefs":58,"style":59},"1e9ed0d2c461",[54],{"_key":55,"_type":38,"marks":56,"text":57},"3fa9e1a24174",[],"Cómo funcionan los teléfonos en México (lo mínimo necesario)",[],"h2",{"_key":61,"_type":34,"children":62,"markDefs":67,"style":42},"9fa7d58a8375",[63],{"_key":64,"_type":38,"marks":65,"text":66},"5f2095d5719e",[],"En México, todos los números telefónicos tienen 10 dígitos. Lo interesante es que los primeros 2, 3 o 4 dígitos están asignados a una localidad y estado específicos según la numeración publicada por el IFT. Esto significa que si sabes en qué ciudad quieres operar, ya conoces una parte del número desde el inicio.",[],{"_key":69,"_type":34,"children":70,"markDefs":75,"style":42},"f58b0f20e55e",[71],{"_key":72,"_type":38,"marks":73,"text":74},"5baf4eaf7988",[],"Pero el IFT no solo define los prefijos — también establece restricciones sobre qué combinaciones de dígitos son válidas. No todos los sufijos son permitidos. Hay reglas.",[],{"_key":77,"_type":34,"children":78,"markDefs":83,"style":42},"67f751872b52",[79],{"_key":80,"_type":38,"marks":81,"text":82},"6ddf127b0f4b",[],"Así que el problema real era: dado un prefijo de ciudad, generar todas las combinaciones válidas de los dígitos restantes (entre 6 y 8 dígitos, dependiendo del prefijo), respetando las restricciones del regulador.",[],{"_key":85,"_type":34,"children":86,"markDefs":96,"style":59},"2212c2b4f080",[87,91],{"_key":88,"_type":38,"marks":89,"text":90},"5877aa202cab",[],"La primera versión: el infierno de los ",{"_key":92,"_type":38,"marks":93,"text":95},"58885ebef781",[94],"code","for",[],{"_key":98,"_type":34,"children":99,"markDefs":119,"style":42},"e9e7c41779fb",[100,104,107,111,115],{"_key":101,"_type":38,"marks":102,"text":103},"201d0b67773a",[],"La implementación inicial fue lo más natural que se me ocurrió en ese momento: ciclos ",{"_key":105,"_type":38,"marks":106,"text":95},"633587068758",[94],{"_key":108,"_type":38,"marks":109,"text":110},"745ee24e8e55",[]," anidados. La lógica era directa: iterar sobre cada posición de dígito, generar todas las combinaciones posibles, y dentro de cada iteración aplicar los ",{"_key":112,"_type":38,"marks":113,"text":114},"70a2c6cc130c",[94],"if",{"_key":116,"_type":38,"marks":117,"text":118},"1c8849272ddc",[]," con las reglas del IFT para filtrar las combinaciones inválidas.",[],{"_key":121,"_type":94,"code":122,"language":123},"0ead3705212c","for d1 in range(10):\n    for d2 in range(10):\n        for d3 in range(10):\n            for d4 in range(10):\n                for d5 in range(10):\n                    for d6 in range(10):\n                        for d7 in range(10):\n                            if cumple_reglas_ift(d1, d2, d3, d4, d5, d6, d7):\n                                guardar_numero(prefijo, d1, d2, d3, d4, d5, d6, d7)","python",{"_key":125,"_type":34,"children":126,"markDefs":140,"style":42},"f24cf9a89f62",[127,131,136],{"_key":128,"_type":38,"marks":129,"text":130},"28a4ebd4b98b",[],"En el peor caso —ciudades con prefijo de 3 dígitos, donde quedan 7 dígitos libres— la complejidad llegaba a ",{"_key":132,"_type":38,"marks":133,"text":135},"8dd084b5bcbc",[134],"strong","O(10⁷)",{"_key":137,"_type":38,"marks":138,"text":139},"828c7cafb978",[],", es decir, hasta 10 millones de iteraciones solo para generar las combinaciones, antes siquiera de aplicar los filtros.",[],{"_key":142,"_type":34,"children":143,"markDefs":148,"style":42},"b33a6756f211",[144],{"_key":145,"_type":38,"marks":146,"text":147},"4ae9209ca2c8",[],"Para ciudades pequeñas, funcionaba. Tardaba un rato, pero terminaba. Para ciudades como CDMX o Monterrey, el proceso tardaba horas. Literalmente. El algoritmo era correcto. El problema era el enfoque.",[],{"_key":150,"_type":34,"children":151,"markDefs":156,"style":59},"863f504e7493",[152],{"_key":153,"_type":38,"marks":154,"text":155},"428843959d09",[],"El insight clave: el problema no era el algoritmo, era la herramienta",[],{"_key":158,"_type":34,"children":159,"markDefs":172,"style":42},"507730ec2243",[160,164,168],{"_key":161,"_type":38,"marks":162,"text":163},"dd8a2a0d1998",[],"El cuello de botella no era la lógica de negocio — las reglas del IFT eran las mismas sin importar cómo las implementara. El problema era que estaba usando un proceso iterativo para resolver algo que en realidad era un problema de ",{"_key":165,"_type":38,"marks":166,"text":167},"77d53425fe58",[134],"combinación de conjuntos de datos",{"_key":169,"_type":38,"marks":170,"text":171},"31b685004f83",[],". Y los motores de bases de datos llevan décadas siendo increíblemente buenos para exactamente eso.",[],{"_key":174,"_type":34,"children":175,"markDefs":180,"style":181},"3479596c856e",[176],{"_key":177,"_type":38,"marks":178,"text":179},"a60bc4b5bf13",[],"En lugar de generar las combinaciones con código, ¿qué tal si modelo las reglas como datos y dejo que la base de datos haga el cruce?",[],"blockquote",{"_key":183,"_type":34,"children":184,"markDefs":189,"style":59},"7c00b48dfd7d",[185],{"_key":186,"_type":38,"marks":187,"text":188},"302a9e98de07",[],"La solución: tablas temporales + JOINs + bulk insert",[],{"_key":191,"_type":34,"children":192,"markDefs":197,"style":198},"34b5f5e68e6a",[193],{"_key":194,"_type":38,"marks":195,"text":196},"2c7d38fa7cfe",[],"1. Las reglas del IFT se convirtieron en tablas",[],"h3",{"_key":200,"_type":34,"children":201,"markDefs":213,"style":42},"045ee1956838",[202,206,209],{"_key":203,"_type":38,"marks":204,"text":205},"7a354e22bf92",[],"En lugar de tener las restricciones codificadas en ",{"_key":207,"_type":38,"marks":208,"text":114},"30fe688b920a",[94],{"_key":210,"_type":38,"marks":211,"text":212},"3e5c2ed82484",[]," dentro de los ciclos, las traduje a tablas temporales en la base de datos. Cada tabla representaba los valores válidos para una posición de dígito, condicionados al estado y localidad seleccionados.",[],{"_key":215,"_type":94,"code":216,"language":217},"b1ddb377c2b3","-- Ejemplo simplificado\nCREATE TEMPORARY TABLE digitos_validos_pos1 AS\nSELECT valor FROM reglas_ift\nWHERE estado = 'CDMX' AND posicion = 1;","sql",{"_key":219,"_type":34,"children":220,"markDefs":225,"style":198},"0ad52f872d6e",[221],{"_key":222,"_type":38,"marks":223,"text":224},"a7c2dcc98e4f",[],"2. El cruce de combinaciones se hace con JOINs",[],{"_key":227,"_type":34,"children":228,"markDefs":249,"style":42},"1a452d620af8",[229,233,237,241,245],{"_key":230,"_type":38,"marks":231,"text":232},"847dc56b3cb4",[],"En lugar de ciclos anidados, un ",{"_key":234,"_type":38,"marks":235,"text":236},"777502230ca8",[94],"SELECT",{"_key":238,"_type":38,"marks":239,"text":240},"1c0e072e05f8",[]," con ",{"_key":242,"_type":38,"marks":243,"text":244},"bdac5b9c7608",[94],"JOIN",{"_key":246,"_type":38,"marks":247,"text":248},"d240f09a082d",[]," entre las tablas de cada posición genera el producto cartesiano de combinaciones válidas directamente en el motor de base de datos. El motor aprovecha índices, paralelismo y optimizaciones de query que ningún loop en aplicación puede igualar.",[],{"_key":251,"_type":94,"code":252,"language":217},"b302c353c012","SELECT\n    p.prefijo,\n    d1.valor, d2.valor, d3.valor,\n    d4.valor, d5.valor, d6.valor, d7.valor\nFROM prefijos p\nJOIN digitos_validos_pos1 d1 ON 1=1\nJOIN digitos_validos_pos2 d2 ON 1=1\nJOIN digitos_validos_pos3 d3 ON 1=1\nJOIN digitos_validos_pos4 d4 ON 1=1\nJOIN digitos_validos_pos5 d5 ON 1=1\nJOIN digitos_validos_pos6 d6 ON 1=1\nJOIN digitos_validos_pos7 d7 ON 1=1\nWHERE p.ciudad = 'CDMX'\n  AND NOT (d1.valor = 0 AND d2.valor = 0) -- ejemplo de restricción",{"_key":254,"_type":34,"children":255,"markDefs":260,"style":198},"a0f5801b6f60",[256],{"_key":257,"_type":38,"marks":258,"text":259},"d996c4256323",[],"3. El resultado va directo a tablas particionadas con bulk insert",[],{"_key":262,"_type":34,"children":263,"markDefs":283,"style":42},"cf9c69827c31",[264,268,271,275,279],{"_key":265,"_type":38,"marks":266,"text":267},"f7a064957732",[],"La tabla resultante del ",{"_key":269,"_type":38,"marks":270,"text":236},"308f568b9d0e",[94],{"_key":272,"_type":38,"marks":273,"text":274},"f4be85a8e9b1",[]," no se traía a la aplicación registro por registro — se insertaba directamente en las tablas finales mediante un ",{"_key":276,"_type":38,"marks":277,"text":278},"6024b75d1a22",[94],"INSERT INTO ... SELECT",{"_key":280,"_type":38,"marks":281,"text":282},"f9e3488a5dae",[],", con particionamiento por estado/ciudad e índices ya definidos para las consultas posteriores.",[],{"_key":285,"_type":94,"code":286,"language":217},"17afdb6229c3","INSERT INTO numeros_generados (ciudad, numero_completo)\nSELECT\n    'CDMX',\n    CONCAT(p.prefijo, d1.valor, d2.valor, d3.valor, d4.valor, d5.valor, d6.valor, d7.valor)\nFROM prefijos p\nJOIN digitos_validos_pos1 d1 ON 1=1\nJOIN digitos_validos_pos2 d2 ON 1=1\nJOIN digitos_validos_pos3 d3 ON 1=1\nJOIN digitos_validos_pos4 d4 ON 1=1\nJOIN digitos_validos_pos5 d5 ON 1=1\nJOIN digitos_validos_pos6 d6 ON 1=1\nJOIN digitos_validos_pos7 d7 ON 1=1\nWHERE p.ciudad = 'CDMX';",{"_key":288,"_type":34,"children":289,"markDefs":294,"style":59},"0c6d956d1bda",[290],{"_key":291,"_type":38,"marks":292,"text":293},"a29d7e3d02aa",[],"El resultado",[],{"_key":296,"_type":34,"children":297,"markDefs":302,"style":42},"63963460a7be",[298],{"_key":299,"_type":38,"marks":300,"text":301},"40ef0fa0fa47",[],"El cambio fue contundente:",[],{"_key":304,"_type":34,"children":305,"markDefs":314,"style":42},"8c4085678058",[306,310],{"_key":307,"_type":38,"marks":308,"text":309},"f5e5ce0f1d89",[134],"Ciudad pequeña:",{"_key":311,"_type":38,"marks":312,"text":313},"f0b5498684a0",[]," de ~5 min a menos de 10 segundos.",[],{"_key":316,"_type":34,"children":317,"markDefs":326,"style":42},"41e406961ab8",[318,322],{"_key":319,"_type":38,"marks":320,"text":321},"1437108651ac",[134],"Monterrey:",{"_key":323,"_type":38,"marks":324,"text":325},"2e266efb3a00",[]," de ~3 horas a 2-3 minutos.",[],{"_key":328,"_type":34,"children":329,"markDefs":338,"style":42},"ad65654189ef",[330,334],{"_key":331,"_type":38,"marks":332,"text":333},"9b2df157bf63",[134],"CDMX:",{"_key":335,"_type":38,"marks":336,"text":337},"78d04524de94",[]," de ~5+ horas a 2-3 minutos.",[],{"_key":340,"_type":34,"children":341,"markDefs":346,"style":42},"dc6a90661a3d",[342],{"_key":343,"_type":38,"marks":344,"text":345},"c13662992f32",[],"De horas a minutos. Sin cambiar las reglas de negocio, sin comprar servidores, sin paralelismo manual. Solo cambiando dónde y cómo se hacía el trabajo.",[],{"_key":348,"_type":34,"children":349,"markDefs":354,"style":59},"265166cf1bc9",[350],{"_key":351,"_type":38,"marks":352,"text":353},"3c6a98a79b7b",[],"La lección que me quedó",[],{"_key":356,"_type":34,"children":357,"markDefs":362,"style":42},"532d36fd15e5",[358],{"_key":359,"_type":38,"marks":360,"text":361},"b64be6262a1e",[],"Este caso me enseñó algo que sigo aplicando como principio de arquitectura: antes de optimizar el código, pregúntate si estás usando la herramienta correcta para el problema.",[],{"_key":364,"_type":34,"children":365,"markDefs":377,"style":42},"7c3422d389dd",[366,370,373],{"_key":367,"_type":38,"marks":368,"text":369},"2674c80799ec",[],"Los ciclos ",{"_key":371,"_type":38,"marks":372,"text":95},"8db7b3c276e0",[94],{"_key":374,"_type":38,"marks":375,"text":376},"8d6aed6c318c",[]," son intuitivos y fáciles de depurar, pero son herramientas de procesamiento secuencial. Los motores de bases de datos son herramientas diseñadas específicamente para cruzar, filtrar y mover grandes volúmenes de datos. Cuando el problema es fundamentalmente de conjuntos, la base de datos casi siempre va a ganar.",[],{"_key":379,"_type":34,"children":380,"markDefs":385,"style":42},"ed92b06dd1aa",[381],{"_key":382,"_type":38,"marks":383,"text":384},"9f95870ba3d5",[],"No es que los loops estén mal — es que había un desajuste entre el problema y la solución. Y reconocer ese desajuste a tiempo es, en gran medida, el trabajo de un buen arquitecto.",[],{"_key":387,"_type":34,"children":388,"markDefs":393,"style":181},"7fb673bafb0e",[389],{"_key":390,"_type":38,"marks":391,"text":392},"2f4ef85bf127",[],"Si tienes un algoritmo que se muere con volumen, vale la pena preguntarse: ¿estoy procesando datos con código cuando debería estar procesando datos con datos?",[],{"_key":395,"_type":34,"children":396,"markDefs":402,"style":42},"7c72500dbfd4",[397],{"_key":398,"_type":38,"marks":399,"text":401},"cc2eb832e72a",[400],"em","¿Tienes algún caso similar donde cambiar de herramienta (no de algoritmo) fue la solución? Me encantaría leerlo en los comentarios.",[],[404,407,410,413],{"_id":405,"title":406},"4f01c389-7d15-408d-8138-f56d4ef9d369","architecture",{"_id":408,"title":409},"50c5952c-04c4-40b0-b76a-51bb562b4733","systemdesign",{"_id":411,"title":412},"76ab166c-a322-429a-97eb-32e348983968","programming",{"_id":414,"title":415},"f4e5f66e-b5f7-4544-ac6f-f1e54a1ba4ec","backend","2026-03-31T08:00:00.000Z","Cómo un generador de números telefónicos válidos para campañas en México pasó de horas de loops en Python a minutos usando tablas, JOINs y bulk inserts en la base de datos.","https://cdn.sanity.io/images/my6ptkxm/production/3a912206db8fcb7dccd6b3563e0a6cc199075e2c-1376x768.png","el-algoritmo-que-se-moria-en-cdmx","El algoritmo que se moría en CDMX: cómo pasé de O(n⁷) a una consulta en base de datos",{"next":422,"prev":425},{"slug":423,"title":424},"cuando-el-negocio-no-es-el-tuyo-facturacion-event-driven","Cuando el negocio no es el tuyo: cómo entender el dominio para rediseñar un sistema de facturación con event-driven",{"slug":426,"title":427},"como-conecte-slack-notion-google-calendar-sanity-claude-orquestador","Cómo conecté Slack, Notion, Google Calendar y Sanity con Claude como orquestador para automatizar mi blog",1775193800328]