{"id":18206,"date":"2026-07-23T17:06:32","date_gmt":"2026-07-24T00:06:32","guid":{"rendered":"https:\/\/torosdetijuana.com\/?page_id=18206"},"modified":"2026-07-23T17:26:54","modified_gmt":"2026-07-24T00:26:54","slug":"playoff-bracket","status":"publish","type":"page","link":"https:\/\/torosdetijuana.com\/index.php\/playoff-bracket\/","title":{"rendered":"playoff bracket"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"18206\" class=\"elementor elementor-18206\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b886fba e-con-full e-flex e-con e-parent\" data-id=\"b886fba\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-05abefc elementor-widget elementor-widget-html\" data-id=\"05abefc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!--\n  BRACKET DE POSTEMPORADA LMB \u00b7 MLBAM StatsAPI \u00b7 TEMA ROJO TOROS\n  Listo para pegar completo en un widget HTML de Elementor.\n  Configuraci\u00f3n principal: busca \"CONFIG\" al inicio del JavaScript.\n-->\n\n<style>\n  #lmbx-app,\n  #lmbx-app * {\n    box-sizing: border-box;\n  }\n\n  #lmbx-app {\n    --lmbx-red: #d71920;\n    --lmbx-red-dark: #8f0b11;\n    --lmbx-navy: #5b0b10;\n    --lmbx-blue: #9f1118;\n    --lmbx-gold: #65070b;\n    --lmbx-ink: #2d1719;\n    --lmbx-muted: #726467;\n    --lmbx-line: #e7dadd;\n    --lmbx-soft: #fbf6f7;\n    --lmbx-white: #ffffff;\n    width: 100%;\n    color: var(--lmbx-ink);\n    font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n    font-size: 15px;\n    line-height: 1.4;\n  }\n\n  #lmbx-app button,\n  #lmbx-app input,\n  #lmbx-app select {\n    font: inherit;\n  }\n\n  #lmbx-app button {\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  .lmbx-shell {\n    position: relative;\n    width: 100%;\n    overflow: hidden;\n    border: 1px solid #e1e6ed;\n    border-radius: 20px;\n    background: var(--lmbx-white);\n    box-shadow: 0 18px 50px rgba(91, 11, 16, 0.12);\n  }\n\n  .lmbx-header {\n    position: relative;\n    display: grid;\n    grid-template-columns: minmax(0, 1fr) auto;\n    gap: 28px;\n    align-items: center;\n    min-height: 168px;\n    padding: 28px 34px;\n    overflow: hidden;\n    color: #fff;\n    background:\n      radial-gradient(circle at 84% 18%, rgba(255, 255, 255, 0.16), transparent 24%),\n      linear-gradient(115deg, #4b060a 0%, #b10f18 58%, #61080d 100%);\n  }\n\n  .lmbx-header::before,\n  .lmbx-header::after {\n    content: \"\";\n    position: absolute;\n    pointer-events: none;\n  }\n\n  .lmbx-header::before {\n    right: -70px;\n    bottom: -130px;\n    width: 360px;\n    height: 360px;\n    border: 54px solid rgba(255, 255, 255, 0.11);\n    border-radius: 50%;\n  }\n\n  .lmbx-header::after {\n    left: 0;\n    right: 0;\n    bottom: 0;\n    height: 5px;\n    background: linear-gradient(90deg, var(--lmbx-red) 0 58%, #fff 58% 61%, var(--lmbx-red-dark) 61% 100%);\n  }\n\n  .lmbx-title-wrap,\n  .lmbx-header-meta {\n    position: relative;\n    z-index: 1;\n  }\n\n  .lmbx-kicker {\n    display: flex;\n    gap: 10px;\n    align-items: center;\n    margin-bottom: 7px;\n    color: rgba(255, 255, 255, 0.78);\n    font-size: 11px;\n    font-weight: 800;\n    letter-spacing: 0.17em;\n    text-transform: uppercase;\n  }\n\n  .lmbx-kicker::before {\n    content: \"\";\n    width: 36px;\n    height: 3px;\n    border-radius: 999px;\n    background: #fff;\n  }\n\n  .lmbx-main-title {\n    margin: 0;\n    font-size: clamp(30px, 5vw, 58px);\n    font-weight: 900;\n    letter-spacing: -0.045em;\n    line-height: 0.97;\n    text-transform: uppercase;\n  }\n\n  .lmbx-main-title span {\n    color: #fff;\n  }\n\n  .lmbx-main-title strong {\n    color: #ffd5d8;\n    font-weight: 900;\n  }\n\n  .lmbx-subtitle {\n    max-width: 720px;\n    margin: 12px 0 0;\n    color: rgba(255, 255, 255, 0.76);\n    font-size: 14px;\n  }\n\n  .lmbx-header-meta {\n    display: flex;\n    flex-direction: column;\n    align-items: flex-end;\n    gap: 12px;\n  }\n\n  .lmbx-season-medallion {\n    display: grid;\n    place-items: center;\n    width: 92px;\n    height: 92px;\n    border: 1px solid rgba(255, 255, 255, 0.24);\n    border-radius: 50%;\n    background: rgba(255, 255, 255, 0.08);\n    box-shadow: inset 0 0 0 8px rgba(255, 255, 255, 0.035);\n    text-align: center;\n    backdrop-filter: blur(7px);\n  }\n\n  .lmbx-season-medallion small {\n    display: block;\n    color: rgba(255, 255, 255, 0.62);\n    font-size: 9px;\n    font-weight: 800;\n    letter-spacing: 0.15em;\n    text-transform: uppercase;\n  }\n\n  .lmbx-season-medallion b {\n    display: block;\n    margin-top: -1px;\n    color: #fff;\n    font-size: 26px;\n    line-height: 1;\n  }\n\n  .lmbx-source-line {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: flex-end;\n    gap: 7px;\n  }\n\n  .lmbx-chip {\n    display: inline-flex;\n    gap: 6px;\n    align-items: center;\n    min-height: 27px;\n    padding: 5px 9px;\n    border: 1px solid rgba(255, 255, 255, 0.17);\n    border-radius: 999px;\n    color: rgba(255, 255, 255, 0.84);\n    background: rgba(255, 255, 255, 0.08);\n    font-size: 10px;\n    font-weight: 750;\n    white-space: nowrap;\n  }\n\n  .lmbx-chip-dot {\n    width: 7px;\n    height: 7px;\n    border-radius: 50%;\n    background: #4ee38a;\n    box-shadow: 0 0 0 3px rgba(78, 227, 138, 0.14);\n  }\n\n  .lmbx-toolbar {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    gap: 16px;\n    padding: 13px 20px;\n    border-bottom: 1px solid var(--lmbx-line);\n    background: #fff;\n  }\n\n  .lmbx-tabs {\n    display: inline-flex;\n    gap: 4px;\n    padding: 4px;\n    border-radius: 12px;\n    background: #eef1f5;\n  }\n\n  .lmbx-tab {\n    min-height: 36px;\n    padding: 7px 15px;\n    border: 0;\n    border-radius: 9px;\n    color: #687386;\n    background: transparent;\n    font-size: 12px;\n    font-weight: 800;\n    cursor: pointer;\n    transition: 160ms ease;\n  }\n\n  .lmbx-tab:hover {\n    color: var(--lmbx-navy);\n  }\n\n  .lmbx-tab[aria-selected=\"true\"] {\n    color: #fff;\n    background: var(--lmbx-navy);\n    box-shadow: 0 5px 14px rgba(143, 11, 17, 0.24);\n  }\n\n  .lmbx-toolbar-actions {\n    display: flex;\n    align-items: center;\n    gap: 11px;\n    min-width: 0;\n  }\n\n  .lmbx-updated {\n    min-width: 0;\n    color: var(--lmbx-muted);\n    font-size: 10px;\n    text-align: right;\n  }\n\n  .lmbx-updated strong {\n    display: block;\n    overflow: hidden;\n    color: #344054;\n    font-size: 11px;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .lmbx-refresh {\n    display: inline-flex;\n    justify-content: center;\n    align-items: center;\n    width: 38px;\n    height: 38px;\n    flex: 0 0 38px;\n    border: 1px solid #d7dde6;\n    border-radius: 11px;\n    color: var(--lmbx-navy);\n    background: #fff;\n    cursor: pointer;\n    transition: 160ms ease;\n  }\n\n  .lmbx-refresh:hover {\n    border-color: var(--lmbx-red);\n    color: var(--lmbx-red);\n    transform: translateY(-1px);\n  }\n\n  .lmbx-refresh:disabled {\n    opacity: 0.5;\n    cursor: wait;\n    transform: none;\n  }\n\n  .lmbx-refresh svg {\n    width: 17px;\n    height: 17px;\n    fill: none;\n    stroke: currentColor;\n    stroke-linecap: round;\n    stroke-linejoin: round;\n    stroke-width: 2;\n  }\n\n  .lmbx-refresh.is-loading svg {\n    animation: lmbx-spin 0.9s linear infinite;\n  }\n\n  @keyframes lmbx-spin {\n    to { transform: rotate(360deg); }\n  }\n\n  .lmbx-notice {\n    display: flex;\n    gap: 9px;\n    align-items: flex-start;\n    margin: 14px 20px 0;\n    padding: 10px 12px;\n    border: 1px solid #f1d38a;\n    border-radius: 11px;\n    color: #6e520e;\n    background: #fff8e6;\n    font-size: 11px;\n  }\n\n  .lmbx-notice.is-cache {\n    border-color: #efc6ca;\n    color: #7f1d24;\n    background: #fff4f5;\n  }\n\n  .lmbx-notice-icon {\n    display: grid;\n    place-items: center;\n    width: 18px;\n    height: 18px;\n    flex: 0 0 18px;\n    border-radius: 50%;\n    color: #fff;\n    background: currentColor;\n    font-size: 11px;\n    font-weight: 900;\n  }\n\n  .lmbx-panel {\n    display: none;\n  }\n\n  .lmbx-panel.is-active {\n    display: block;\n  }\n\n  .lmbx-bracket-wrap {\n    padding: 18px 0 8px;\n    background:\n      linear-gradient(rgba(244, 246, 249, 0.94), rgba(244, 246, 249, 0.94)),\n      repeating-linear-gradient(45deg, transparent 0 18px, rgba(143, 11, 17, 0.035) 18px 19px);\n  }\n\n  .lmbx-bracket-intro {\n    display: flex;\n    justify-content: space-between;\n    gap: 16px;\n    align-items: center;\n    padding: 0 22px 14px;\n  }\n\n  .lmbx-bracket-intro h2 {\n    margin: 0;\n    color: var(--lmbx-navy);\n    font-size: 17px;\n    line-height: 1.2;\n  }\n\n  .lmbx-bracket-intro p {\n    margin: 4px 0 0;\n    color: var(--lmbx-muted);\n    font-size: 11px;\n  }\n\n  .lmbx-qualifier-pill {\n    display: inline-flex;\n    gap: 7px;\n    align-items: center;\n    padding: 7px 11px;\n    border: 1px solid #d8dee7;\n    border-radius: 999px;\n    color: #344054;\n    background: #fff;\n    font-size: 10px;\n    font-weight: 800;\n    white-space: nowrap;\n  }\n\n  .lmbx-qualifier-pill b {\n    display: grid;\n    place-items: center;\n    width: 19px;\n    height: 19px;\n    border-radius: 50%;\n    color: #fff;\n    background: var(--lmbx-red);\n    font-size: 10px;\n  }\n\n  .lmbx-desktop-bracket {\n    display: block;\n  }\n\n  .lmbx-bracket-scroll {\n    width: 100%;\n    overflow-x: auto;\n    overscroll-behavior-inline: contain;\n    padding: 0 16px 10px;\n    scrollbar-color: #bbc4d1 transparent;\n    scrollbar-width: thin;\n  }\n\n  .lmbx-bracket-scroll::-webkit-scrollbar {\n    height: 8px;\n  }\n\n  .lmbx-bracket-scroll::-webkit-scrollbar-thumb {\n    border: 2px solid transparent;\n    border-radius: 999px;\n    background: #bbc4d1;\n    background-clip: padding-box;\n  }\n\n  .lmbx-board {\n    position: relative;\n    display: grid;\n    grid-template-columns: 184px 166px 164px 196px 164px 166px 184px;\n    gap: 32px;\n    width: 1352px;\n    min-width: 1352px;\n    height: 660px;\n    margin: 0 auto;\n  }\n\n  .lmbx-round {\n    position: relative;\n    z-index: 2;\n    min-width: 0;\n  }\n\n  .lmbx-round-head {\n    display: flex;\n    flex-direction: column;\n    justify-content: flex-end;\n    height: 68px;\n    padding: 0 4px 12px;\n    text-align: center;\n  }\n\n  .lmbx-round-head strong {\n    display: block;\n    color: var(--lmbx-navy);\n    font-size: 12px;\n    font-weight: 900;\n    line-height: 1.12;\n    text-transform: uppercase;\n  }\n\n  .lmbx-round-head span {\n    display: block;\n    margin-top: 4px;\n    color: #7b8494;\n    font-size: 8.5px;\n    font-weight: 750;\n    letter-spacing: 0.04em;\n    line-height: 1.2;\n    text-transform: uppercase;\n  }\n\n  .lmbx-round.is-final .lmbx-round-head strong {\n    color: var(--lmbx-red);\n    font-size: 13px;\n  }\n\n  .lmbx-round-body {\n    position: relative;\n    height: 582px;\n  }\n\n  .lmbx-series {\n    position: absolute;\n    left: 0;\n    width: 100%;\n    height: 144px;\n    overflow: hidden;\n    border: 1px solid #dbe1e9;\n    border-top: 4px solid var(--lmbx-red);\n    border-radius: 13px;\n    background: #fff;\n    box-shadow: 0 9px 23px rgba(91, 11, 16, 0.10);\n    transition: 170ms ease;\n  }\n\n  .lmbx-series:hover {\n    z-index: 5;\n    border-color: #c8d0dc;\n    box-shadow: 0 15px 32px rgba(91, 11, 16, 0.16);\n    transform: translateY(-2px);\n  }\n\n  .lmbx-series.is-south {\n    border-top-color: var(--lmbx-blue);\n  }\n\n  .lmbx-series.is-final {\n    border-top-color: var(--lmbx-gold);\n    box-shadow: 0 12px 29px rgba(101, 7, 11, 0.18);\n  }\n\n  .lmbx-series.is-waiting {\n    border-top-color: #a7b0be;\n    background: #fbfcfd;\n    box-shadow: 0 6px 16px rgba(91, 11, 16, 0.07);\n  }\n\n  .lmbx-series.is-featured {\n    box-shadow: 0 0 0 2px rgba(215, 25, 32, 0.16), 0 10px 28px rgba(91, 11, 16, 0.14);\n  }\n\n  .lmbx-series-top {\n    display: flex;\n    justify-content: space-between;\n    gap: 6px;\n    align-items: center;\n    min-height: 26px;\n    padding: 4px 8px;\n    border-bottom: 1px solid #edf0f4;\n    background: #fafbfc;\n  }\n\n  .lmbx-series-label {\n    overflow: hidden;\n    color: #667085;\n    font-size: 8px;\n    font-weight: 900;\n    letter-spacing: 0.05em;\n    text-overflow: ellipsis;\n    text-transform: uppercase;\n    white-space: nowrap;\n  }\n\n  .lmbx-status {\n    display: inline-flex;\n    align-items: center;\n    min-height: 17px;\n    max-width: 94px;\n    padding: 2px 6px;\n    overflow: hidden;\n    border-radius: 999px;\n    color: #667085;\n    background: #e9edf2;\n    font-size: 7.5px;\n    font-weight: 900;\n    letter-spacing: 0.025em;\n    line-height: 1;\n    text-overflow: ellipsis;\n    text-transform: uppercase;\n    white-space: nowrap;\n  }\n\n  .lmbx-status.is-live {\n    color: #fff;\n    background: var(--lmbx-red);\n    animation: lmbx-live-pulse 1.7s ease-in-out infinite;\n  }\n\n  .lmbx-status.is-final {\n    color: #166534;\n    background: #dcfce7;\n  }\n\n  .lmbx-status.is-series {\n    color: #7d151c;\n    background: #fde8ea;\n  }\n\n  @keyframes lmbx-live-pulse {\n    50% { opacity: 0.72; }\n  }\n\n  .lmbx-team-row {\n    display: grid;\n    grid-template-columns: 22px 31px minmax(0, 1fr) auto;\n    gap: 5px;\n    align-items: center;\n    min-height: 46px;\n    padding: 4px 8px;\n    border-bottom: 1px solid #f0f2f5;\n  }\n\n  .lmbx-team-row:last-of-type {\n    border-bottom: 0;\n  }\n\n  .lmbx-team-row.is-winner {\n    background: linear-gradient(90deg, rgba(215, 25, 32, 0.08), rgba(215, 25, 32, 0.015));\n  }\n\n  .lmbx-series.is-south .lmbx-team-row.is-winner {\n    background: linear-gradient(90deg, rgba(159, 17, 24, 0.11), rgba(159, 17, 24, 0.018));\n  }\n\n  .lmbx-team-row.is-eliminated {\n    opacity: 0.55;\n  }\n\n  .lmbx-team-row.is-placeholder {\n    color: #8b94a3;\n  }\n\n  .lmbx-seed {\n    display: grid;\n    place-items: center;\n    width: 20px;\n    height: 20px;\n    border: 1px solid #d9dfe7;\n    border-radius: 50%;\n    color: #556070;\n    background: #fff;\n    font-size: 8px;\n    font-weight: 900;\n  }\n\n  .lmbx-seed.is-wildcard {\n    color: #fff;\n    border-color: var(--lmbx-gold);\n    background: var(--lmbx-gold);\n  }\n\n  .lmbx-logo {\n    position: relative;\n    display: grid;\n    place-items: center;\n    width: 30px;\n    height: 30px;\n    overflow: hidden;\n    border: 1px solid #e2e7ed;\n    border-radius: 50%;\n    color: var(--lmbx-navy);\n    background: #fff;\n  }\n\n  .lmbx-logo-fallback {\n    position: absolute;\n    inset: 0;\n    display: grid;\n    place-items: center;\n    padding: 2px;\n    font-size: 7px;\n    font-weight: 950;\n    letter-spacing: -0.03em;\n    text-align: center;\n  }\n\n  .lmbx-logo img {\n    position: relative;\n    z-index: 1;\n    display: block;\n    width: 25px;\n    height: 25px;\n    object-fit: contain;\n    background: #fff;\n  }\n\n  .lmbx-team-copy {\n    min-width: 0;\n  }\n\n  .lmbx-team-name {\n    display: flex;\n    gap: 3px;\n    align-items: center;\n    min-width: 0;\n    color: #1f2a3c;\n    font-size: 10.5px;\n    font-weight: 850;\n    line-height: 1.1;\n  }\n\n  .lmbx-team-name > span:first-child {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .lmbx-wildcard-tag {\n    display: inline-grid;\n    place-items: center;\n    min-width: 13px;\n    height: 13px;\n    padding: 0 3px;\n    border-radius: 999px;\n    color: #fff;\n    background: var(--lmbx-gold);\n    font-size: 6px;\n    font-weight: 950;\n  }\n\n  .lmbx-team-sub {\n    display: block;\n    margin-top: 2px;\n    overflow: hidden;\n    color: #8a93a1;\n    font-size: 7.5px;\n    line-height: 1.05;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .lmbx-team-metric {\n    min-width: 30px;\n    color: var(--lmbx-navy);\n    font-size: 11px;\n    font-weight: 950;\n    line-height: 1;\n    text-align: right;\n    white-space: nowrap;\n  }\n\n  .lmbx-team-metric.is-series-score {\n    min-width: 18px;\n    font-size: 17px;\n  }\n\n  .lmbx-team-row.is-winner .lmbx-team-metric::after {\n    content: \"\u2713\";\n    margin-left: 3px;\n    color: #1a8f4b;\n    font-size: 9px;\n  }\n\n  .lmbx-series-foot {\n    display: flex;\n    justify-content: space-between;\n    gap: 5px;\n    align-items: center;\n    min-height: 22px;\n    padding: 3px 8px;\n    overflow: hidden;\n    color: #7c8695;\n    background: #fbfcfd;\n    font-size: 7.5px;\n    font-weight: 700;\n    line-height: 1;\n  }\n\n  .lmbx-series-foot span {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .lmbx-series-foot b {\n    flex: 0 0 auto;\n    color: #515d6d;\n    font-size: 7px;\n    letter-spacing: 0.04em;\n    text-transform: uppercase;\n  }\n\n  .lmbx-links {\n    position: absolute;\n    inset: 0;\n    z-index: 1;\n    width: 100%;\n    height: 100%;\n    overflow: visible;\n    pointer-events: none;\n  }\n\n  .lmbx-link-path {\n    fill: none;\n    stroke: #b7c0cd;\n    stroke-linecap: round;\n    stroke-linejoin: round;\n    stroke-width: 1.6;\n  }\n\n  .lmbx-link-path.is-north {\n    stroke: rgba(215, 25, 32, 0.54);\n  }\n\n  .lmbx-link-path.is-south {\n    stroke: rgba(159, 17, 24, 0.62);\n  }\n\n  .lmbx-link-path.is-final {\n    stroke: rgba(101, 7, 11, 0.78);\n    stroke-width: 2;\n  }\n\n  .lmbx-board-watermark {\n    position: absolute;\n    top: 52%;\n    left: 50%;\n    z-index: 0;\n    color: rgba(143, 11, 17, 0.03);\n    font-size: 108px;\n    font-weight: 950;\n    letter-spacing: -0.08em;\n    line-height: 1;\n    transform: translate(-50%, -50%) rotate(-7deg);\n    user-select: none;\n  }\n\n  .lmbx-mobile-bracket {\n    display: none;\n  }\n\n  .lmbx-legend {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px 16px;\n    align-items: center;\n    padding: 12px 22px 5px;\n    color: #737d8d;\n    font-size: 9px;\n  }\n\n  .lmbx-legend-item {\n    display: inline-flex;\n    gap: 6px;\n    align-items: center;\n  }\n\n  .lmbx-legend-swatch {\n    width: 16px;\n    height: 4px;\n    border-radius: 999px;\n    background: var(--lmbx-red);\n  }\n\n  .lmbx-legend-swatch.is-south {\n    background: var(--lmbx-blue);\n  }\n\n  .lmbx-legend-swatch.is-final {\n    background: var(--lmbx-gold);\n  }\n\n  .lmbx-rule-note {\n    margin: 9px 22px 15px;\n    padding: 10px 12px;\n    border-left: 4px solid var(--lmbx-gold);\n    border-radius: 7px;\n    color: #667085;\n    background: #fff;\n    font-size: 10px;\n  }\n\n  .lmbx-rule-note strong {\n    color: #344054;\n  }\n\n  .lmbx-standings-wrap {\n    padding: 22px;\n    background: #f5f7fa;\n  }\n\n  .lmbx-standings-header {\n    display: flex;\n    justify-content: space-between;\n    gap: 16px;\n    align-items: flex-end;\n    margin-bottom: 16px;\n  }\n\n  .lmbx-standings-header h2 {\n    margin: 0;\n    color: var(--lmbx-navy);\n    font-size: 20px;\n  }\n\n  .lmbx-standings-header p {\n    margin: 4px 0 0;\n    color: var(--lmbx-muted);\n    font-size: 11px;\n  }\n\n  .lmbx-cut-key {\n    display: inline-flex;\n    gap: 7px;\n    align-items: center;\n    color: #5d6878;\n    font-size: 10px;\n    font-weight: 750;\n    white-space: nowrap;\n  }\n\n  .lmbx-cut-key::before {\n    content: \"\";\n    width: 22px;\n    height: 8px;\n    border-radius: 2px;\n    background: rgba(215, 25, 32, 0.13);\n    box-shadow: inset 3px 0 0 var(--lmbx-red);\n  }\n\n  .lmbx-standings-grid {\n    display: grid;\n    grid-template-columns: repeat(2, minmax(0, 1fr));\n    gap: 18px;\n  }\n\n  .lmbx-table-card {\n    min-width: 0;\n    overflow: hidden;\n    border: 1px solid #dee4ec;\n    border-radius: 15px;\n    background: #fff;\n    box-shadow: 0 7px 21px rgba(91, 11, 16, 0.075);\n  }\n\n  .lmbx-table-title {\n    display: flex;\n    justify-content: space-between;\n    gap: 10px;\n    align-items: center;\n    min-height: 51px;\n    padding: 11px 14px;\n    color: #fff;\n    background: var(--lmbx-red);\n  }\n\n  .lmbx-table-card.is-south .lmbx-table-title {\n    background: var(--lmbx-blue);\n  }\n\n  .lmbx-table-title h3 {\n    margin: 0;\n    font-size: 15px;\n    text-transform: uppercase;\n  }\n\n  .lmbx-table-title span {\n    padding: 4px 7px;\n    border-radius: 999px;\n    color: rgba(255, 255, 255, 0.9);\n    background: rgba(255, 255, 255, 0.14);\n    font-size: 8px;\n    font-weight: 850;\n    letter-spacing: 0.04em;\n    text-transform: uppercase;\n  }\n\n  .lmbx-table-scroll {\n    overflow-x: auto;\n  }\n\n  .lmbx-table {\n    width: 100%;\n    min-width: 500px;\n    border-collapse: collapse;\n    table-layout: fixed;\n  }\n\n  .lmbx-table th,\n  .lmbx-table td {\n    height: 42px;\n    padding: 6px 7px;\n    border-bottom: 1px solid #edf0f4;\n    font-size: 10px;\n    text-align: center;\n    white-space: nowrap;\n  }\n\n  .lmbx-table th {\n    height: 34px;\n    color: #7a8493;\n    background: #f8f9fb;\n    font-size: 8px;\n    font-weight: 900;\n    letter-spacing: 0.04em;\n    text-transform: uppercase;\n  }\n\n  .lmbx-table th:first-child,\n  .lmbx-table td:first-child {\n    width: 38px;\n  }\n\n  .lmbx-table th:nth-child(2),\n  .lmbx-table td:nth-child(2) {\n    width: 43%;\n    text-align: left;\n  }\n\n  .lmbx-table tbody tr:last-child td {\n    border-bottom: 0;\n  }\n\n  .lmbx-table tbody tr.is-qualified {\n    background: linear-gradient(90deg, rgba(215, 25, 32, 0.08), rgba(215, 25, 32, 0.018));\n  }\n\n  .lmbx-table-card.is-south .lmbx-table tbody tr.is-qualified {\n    background: linear-gradient(90deg, rgba(159, 17, 24, 0.10), rgba(159, 17, 24, 0.02));\n  }\n\n  .lmbx-table tbody tr.is-cut td {\n    border-bottom: 3px solid rgba(215, 25, 32, 0.68);\n  }\n\n  .lmbx-table-card.is-south .lmbx-table tbody tr.is-cut td {\n    border-bottom-color: rgba(159, 17, 24, 0.72);\n  }\n\n  .lmbx-table tbody tr.is-featured td {\n    font-weight: 800;\n  }\n\n  .lmbx-rank {\n    display: inline-grid;\n    place-items: center;\n    width: 22px;\n    height: 22px;\n    border-radius: 50%;\n    color: #5a6575;\n    background: #eef1f5;\n    font-size: 9px;\n    font-weight: 900;\n  }\n\n  tr.is-qualified .lmbx-rank {\n    color: #fff;\n    background: var(--lmbx-red);\n  }\n\n  .lmbx-table-card.is-south tr.is-qualified .lmbx-rank {\n    background: var(--lmbx-blue);\n  }\n\n  .lmbx-standing-team {\n    display: grid;\n    grid-template-columns: 29px minmax(0, 1fr);\n    gap: 7px;\n    align-items: center;\n    min-width: 0;\n  }\n\n  .lmbx-standing-team .lmbx-logo {\n    width: 28px;\n    height: 28px;\n  }\n\n  .lmbx-standing-team .lmbx-logo img {\n    width: 23px;\n    height: 23px;\n  }\n\n  .lmbx-standing-name {\n    min-width: 0;\n  }\n\n  .lmbx-standing-name strong,\n  .lmbx-standing-name small {\n    display: block;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .lmbx-standing-name strong {\n    color: #263246;\n    font-size: 10px;\n  }\n\n  .lmbx-standing-name small {\n    margin-top: 1px;\n    color: #949cab;\n    font-size: 7.5px;\n  }\n\n  .lmbx-pct {\n    color: var(--lmbx-navy);\n    font-weight: 900;\n  }\n\n  .lmbx-positive {\n    color: #188149;\n    font-weight: 800;\n  }\n\n  .lmbx-negative {\n    color: #bd2d33;\n    font-weight: 800;\n  }\n\n  .lmbx-footer {\n    display: flex;\n    justify-content: space-between;\n    gap: 14px;\n    align-items: center;\n    padding: 13px 20px;\n    border-top: 1px solid var(--lmbx-line);\n    color: #818a99;\n    background: #fff;\n    font-size: 9px;\n  }\n\n  .lmbx-footer strong {\n    color: #4c5868;\n  }\n\n  .lmbx-loading,\n  .lmbx-error {\n    display: grid;\n    place-items: center;\n    min-height: 390px;\n    padding: 34px;\n    border: 1px solid #e0e5ec;\n    border-radius: 18px;\n    background: #f7f8fa;\n    text-align: center;\n  }\n\n  .lmbx-loader-ball {\n    position: relative;\n    width: 58px;\n    height: 58px;\n    margin: 0 auto 17px;\n    border: 3px solid #e4e8ee;\n    border-top-color: var(--lmbx-red);\n    border-radius: 50%;\n    animation: lmbx-spin 0.8s linear infinite;\n  }\n\n  .lmbx-loader-ball::before,\n  .lmbx-loader-ball::after {\n    content: \"\";\n    position: absolute;\n    top: 50%;\n    width: 19px;\n    height: 1px;\n    background: #d9dee6;\n  }\n\n  .lmbx-loader-ball::before {\n    left: 6px;\n    transform: rotate(58deg);\n  }\n\n  .lmbx-loader-ball::after {\n    right: 6px;\n    transform: rotate(-58deg);\n  }\n\n  .lmbx-loading h3,\n  .lmbx-error h3 {\n    margin: 0;\n    color: var(--lmbx-navy);\n    font-size: 18px;\n  }\n\n  .lmbx-loading p,\n  .lmbx-error p {\n    max-width: 500px;\n    margin: 7px auto 0;\n    color: var(--lmbx-muted);\n    font-size: 11px;\n  }\n\n  .lmbx-error-icon {\n    display: grid;\n    place-items: center;\n    width: 54px;\n    height: 54px;\n    margin: 0 auto 15px;\n    border-radius: 50%;\n    color: #fff;\n    background: var(--lmbx-red);\n    font-size: 27px;\n    font-weight: 900;\n  }\n\n  .lmbx-error button {\n    margin-top: 17px;\n    padding: 9px 15px;\n    border: 0;\n    border-radius: 10px;\n    color: #fff;\n    background: var(--lmbx-red);\n    font-size: 11px;\n    font-weight: 850;\n    cursor: pointer;\n  }\n\n  @media (max-width: 900px) {\n    .lmbx-header {\n      grid-template-columns: 1fr;\n      min-height: auto;\n      padding: 25px 24px 28px;\n    }\n\n    .lmbx-header-meta {\n      flex-direction: row;\n      justify-content: space-between;\n      align-items: center;\n    }\n\n    .lmbx-season-medallion {\n      width: 74px;\n      height: 74px;\n    }\n\n    .lmbx-season-medallion b {\n      font-size: 21px;\n    }\n\n    .lmbx-source-line {\n      max-width: 310px;\n    }\n\n    .lmbx-standings-grid {\n      grid-template-columns: 1fr;\n    }\n  }\n\n  @media (max-width: 760px) {\n    .lmbx-shell {\n      border-radius: 14px;\n    }\n\n    .lmbx-header {\n      padding: 23px 18px 25px;\n    }\n\n    .lmbx-main-title {\n      font-size: 33px;\n    }\n\n    .lmbx-subtitle {\n      font-size: 12px;\n    }\n\n    .lmbx-header-meta {\n      align-items: flex-end;\n    }\n\n    .lmbx-season-medallion {\n      width: 66px;\n      height: 66px;\n      flex: 0 0 66px;\n    }\n\n    .lmbx-season-medallion b {\n      font-size: 18px;\n    }\n\n    .lmbx-source-line {\n      justify-content: flex-end;\n    }\n\n    .lmbx-chip {\n      min-height: 24px;\n      padding: 4px 7px;\n      font-size: 8px;\n    }\n\n    .lmbx-toolbar {\n      align-items: stretch;\n      padding: 10px 12px;\n    }\n\n    .lmbx-tabs {\n      flex: 1 1 auto;\n    }\n\n    .lmbx-tab {\n      flex: 1 1 50%;\n      padding-inline: 9px;\n    }\n\n    .lmbx-updated {\n      display: none;\n    }\n\n    .lmbx-bracket-intro {\n      align-items: flex-start;\n      padding-inline: 15px;\n    }\n\n    .lmbx-qualifier-pill {\n      display: none;\n    }\n\n    .lmbx-desktop-bracket {\n      display: none;\n    }\n\n    .lmbx-mobile-bracket {\n      display: block;\n      padding: 0 14px 8px;\n    }\n\n    .lmbx-mobile-zone {\n      margin-bottom: 20px;\n      padding: 14px;\n      border: 1px solid #dfe5ec;\n      border-radius: 14px;\n      background: rgba(255, 255, 255, 0.92);\n    }\n\n    .lmbx-mobile-zone-head {\n      display: flex;\n      justify-content: space-between;\n      gap: 10px;\n      align-items: center;\n      margin-bottom: 13px;\n      padding-bottom: 10px;\n      border-bottom: 3px solid var(--lmbx-red);\n    }\n\n    .lmbx-mobile-zone.is-south .lmbx-mobile-zone-head {\n      border-bottom-color: var(--lmbx-blue);\n    }\n\n    .lmbx-mobile-zone-head h3 {\n      margin: 0;\n      color: var(--lmbx-navy);\n      font-size: 16px;\n      text-transform: uppercase;\n    }\n\n    .lmbx-mobile-zone-head span {\n      color: #7c8696;\n      font-size: 8px;\n      font-weight: 800;\n      text-transform: uppercase;\n    }\n\n    .lmbx-mobile-round {\n      margin-top: 16px;\n    }\n\n    .lmbx-mobile-round:first-of-type {\n      margin-top: 0;\n    }\n\n    .lmbx-mobile-round-title {\n      display: flex;\n      justify-content: space-between;\n      gap: 8px;\n      align-items: center;\n      margin-bottom: 8px;\n      color: #536072;\n      font-size: 9px;\n      font-weight: 900;\n      letter-spacing: 0.06em;\n      text-transform: uppercase;\n    }\n\n    .lmbx-mobile-round-title::after {\n      content: \"\";\n      height: 1px;\n      flex: 1 1 auto;\n      background: #dce2ea;\n    }\n\n    .lmbx-mobile-cards {\n      display: grid;\n      gap: 10px;\n    }\n\n    .lmbx-mobile-cards .lmbx-series {\n      position: relative;\n      top: auto !important;\n      left: auto;\n      height: 144px;\n    }\n\n    .lmbx-mobile-final {\n      margin: 0 14px 20px;\n      padding: 15px;\n      border: 1px solid #edc9cd;\n      border-radius: 15px;\n      background: linear-gradient(145deg, #fff 0%, #fff5f6 100%);\n    }\n\n    .lmbx-mobile-final h3 {\n      margin: 0 0 10px;\n      color: var(--lmbx-red-dark);\n      font-size: 15px;\n      text-align: center;\n      text-transform: uppercase;\n    }\n\n    .lmbx-mobile-final .lmbx-series {\n      position: relative;\n      top: auto !important;\n      height: 144px;\n    }\n\n    .lmbx-legend,\n    .lmbx-rule-note {\n      margin-left: 14px;\n      margin-right: 14px;\n      padding-left: 0;\n      padding-right: 0;\n    }\n\n    .lmbx-rule-note {\n      padding: 10px 11px;\n    }\n\n    .lmbx-standings-wrap {\n      padding: 15px 12px;\n    }\n\n    .lmbx-standings-header {\n      align-items: flex-start;\n      margin-bottom: 12px;\n    }\n\n    .lmbx-standings-header h2 {\n      font-size: 17px;\n    }\n\n    .lmbx-cut-key {\n      display: none;\n    }\n\n    .lmbx-footer {\n      flex-direction: column;\n      align-items: flex-start;\n    }\n  }\n\n  @media (max-width: 420px) {\n    .lmbx-header-meta {\n      flex-direction: column;\n      align-items: flex-start;\n    }\n\n    .lmbx-source-line {\n      justify-content: flex-start;\n    }\n\n    .lmbx-season-medallion {\n      display: none;\n    }\n\n    .lmbx-toolbar-actions {\n      flex: 0 0 auto;\n    }\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    #lmbx-app *,\n    #lmbx-app *::before,\n    #lmbx-app *::after {\n      scroll-behavior: auto !important;\n      animation-duration: 0.01ms !important;\n      animation-iteration-count: 1 !important;\n      transition-duration: 0.01ms !important;\n    }\n  }\n  #lmbx-app .lmbx-tab {\n  appearance: none !important;\n  -webkit-appearance: none !important;\n  color: #687386 !important;\n  background: transparent !important;\n  border: 0 !important;\n  border-radius: 9px !important;\n  box-shadow: none !important;\n}\n\n#lmbx-app .lmbx-tab[aria-selected=\"true\"] {\n  color: #ffffff !important;\n  background: #8f0b11 !important;\n  box-shadow: 0 5px 14px rgba(143, 11, 17, 0.24) !important;\n}\n\n#lmbx-app .lmbx-refresh {\n  appearance: none !important;\n  -webkit-appearance: none !important;\n  width: 38px !important;\n  height: 38px !important;\n  min-width: 38px !important;\n  padding: 0 !important;\n  border: 1px solid #d7dde6 !important;\n  border-radius: 11px !important;\n  color: #8f0b11 !important;\n  background: #ffffff !important;\n  box-shadow: none !important;\n}\n\n#lmbx-app .lmbx-refresh:hover {\n  border-color: #d71920 !important;\n  color: #d71920 !important;\n  background: #ffffff !important;\n}\n<\/style>\n\n<div id=\"lmbx-app\" aria-live=\"polite\">\n  <div class=\"lmbx-loading\">\n    <div>\n      <div class=\"lmbx-loader-ball\" aria-hidden=\"true\"><\/div>\n      <h3>Consultando MLBAM\u2026<\/h3>\n      <p>Obteniendo el standing de la Liga Mexicana de Beisbol y los resultados de postemporada.<\/p>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(() => {\n  \"use strict\";\n\n  \/* Tema visual: blanco con vivos rojos.\n     ============================================================\n     CONFIGURACI\u00d3N PRINCIPAL\n     ============================================================ *\/\n  const CONFIG = Object.freeze({\n    mountId: \"lmbx-app\",\n    season: 2026,\n    leagueId: 125,       \/\/ Liga Mexicana de Beisbol\n    sportId: 23,         \/\/ Independent Leagues en MLBAM\n    topPerZone: 6,\n    featuredTeamId: 5010, \/\/ Toros de Tijuana; usa null para no destacar equipo\n    apiBase: \"https:\/\/statsapi.mlb.com\/api\/v1\",\n    logoBase: \"https:\/\/www.mlbstatic.com\/team-logos\",\n    refreshMinutes: 15,\n    requestTimeoutMs: 15000,\n    cacheMaxHours: 72,\n    timeZone: \"America\/Tijuana\",\n    locale: \"es-MX\"\n  });\n\n  const root = document.getElementById(CONFIG.mountId);\n  if (!root) return;\n\n  const POSTSEASON_TYPES = Object.freeze({\n    F: \"Primer Playoff\",\n    D: \"Series de Zona\",\n    L: \"Campeonato de Zona\",\n    W: \"Serie del Rey\"\n  });\n\n  const TEAM_OVERRIDES = Object.freeze({\n    434:  { short: \"Bravos\",         abbr: \"LEO\", zone: \"south\" },\n    447:  { short: \"Algodoneros\",    abbr: \"LAG\", zone: \"north\" },\n    496:  { short: \"Leones\",         abbr: \"YUC\", zone: \"south\" },\n    502:  { short: \"Saraperos\",      abbr: \"SLT\", zone: \"north\" },\n    520:  { short: \"Pericos\",        abbr: \"PUE\", zone: \"south\" },\n    523:  { short: \"Piratas\",        abbr: \"CAM\", zone: \"south\" },\n    528:  { short: \"Rieleros\",       abbr: \"AGS\", zone: \"north\" },\n    532:  { short: \"Diablos Rojos\",  abbr: \"MEX\", zone: \"south\" },\n    536:  { short: \"Tecolotes\",      abbr: \"LAR\", zone: \"north\" },\n    560:  { short: \"Acereros\",       abbr: \"MVA\", zone: \"north\" },\n    562:  { short: \"Sultanes\",       abbr: \"MTY\", zone: \"north\" },\n    569:  { short: \"Tigres\",         abbr: \"QRO\", zone: \"south\" },\n    575:  { short: \"Dorados\",        abbr: \"CHH\", zone: \"north\" },\n    579:  { short: \"Guerreros\",      abbr: \"OAX\", zone: \"south\" },\n    442:  { short: \"Olmecas\",        abbr: \"TAB\", zone: \"south\" },\n    4444: { short: \"Caliente\",       abbr: \"DUR\", zone: \"north\" },\n    5010: { short: \"Toros\",          abbr: \"TIJ\", zone: \"north\" },\n    5567: { short: \"El \u00c1guila\",      abbr: \"VER\", zone: \"south\" },\n    6303: { short: \"Conspiradores\",  abbr: \"QRO\", zone: \"south\" },\n    6304: { short: \"Charros\",        abbr: \"JAL\", zone: \"north\" }\n  });\n\n  const state = {\n    activeTab: \"bracket\",\n    model: null,\n    bracket: null,\n    loading: false,\n    notice: \"\",\n    noticeType: \"\",\n    timer: null,\n    lastSuccessfulFetch: 0\n  };\n\n  const CACHE_KEY = `lmbx-bracket-v5-${CONFIG.leagueId}-${CONFIG.season}`;\n  const htmlMap = { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#039;\" };\n  const escapeHtml = (value) => String(value ?? \"\").replace(\/[&<>\"']\/g, char => htmlMap[char]);\n  const asNumber = (value, fallback = 0) => {\n    const number = Number(value);\n    return Number.isFinite(number) ? number : fallback;\n  };\n  const asRank = (value) => {\n    const number = Number.parseInt(value, 10);\n    return Number.isFinite(number) ? number : 999;\n  };\n\n  function apiUrl(path, params = {}) {\n    const url = new URL(`${CONFIG.apiBase}${path}`);\n    Object.entries(params).forEach(([key, value]) => {\n      if (value !== undefined && value !== null && value !== \"\") {\n        url.searchParams.set(key, String(value));\n      }\n    });\n    return url.toString();\n  }\n\n  async function fetchJson(url) {\n    const controller = new AbortController();\n    const timer = window.setTimeout(() => controller.abort(), CONFIG.requestTimeoutMs);\n    try {\n      const response = await fetch(url, {\n        method: \"GET\",\n        mode: \"cors\",\n        credentials: \"omit\",\n        cache: \"no-store\",\n        signal: controller.signal\n      });\n      if (!response.ok) throw new Error(`MLBAM respondi\u00f3 ${response.status}`);\n      return await response.json();\n    } finally {\n      window.clearTimeout(timer);\n    }\n  }\n\n  async function fetchTeams() {\n    const primary = apiUrl(\"\/teams\", {\n      leagueIds: CONFIG.leagueId,\n      season: CONFIG.season,\n      hydrate: \"division\"\n    });\n\n    try {\n      const data = await fetchJson(primary);\n      if (Array.isArray(data?.teams) && data.teams.length) return data;\n      throw new Error(\"La respuesta de equipos lleg\u00f3 vac\u00eda\");\n    } catch (primaryError) {\n      const fallback = apiUrl(\"\/teams\", {\n        sportId: CONFIG.sportId,\n        season: CONFIG.season,\n        hydrate: \"division\"\n      });\n      const data = await fetchJson(fallback);\n      if (!Array.isArray(data?.teams)) throw primaryError;\n      return {\n        ...data,\n        teams: data.teams.filter(team => Number(team?.league?.id) === CONFIG.leagueId)\n      };\n    }\n  }\n\n  async function fetchAllData() {\n    const standingsUrl = apiUrl(\"\/standings\", {\n      leagueId: CONFIG.leagueId,\n      season: CONFIG.season,\n      standingsTypes: \"regularSeason\",\n      hydrate: \"team\"\n    });\n\n    const standingsPromise = fetchJson(standingsUrl);\n    const teamsPromise = fetchTeams();\n    const schedulePromises = Object.keys(POSTSEASON_TYPES).map(async type => ({\n      type,\n      data: await fetchJson(apiUrl(\"\/schedule\", {\n        sportId: CONFIG.sportId,\n        season: CONFIG.season,\n        gameType: type\n      }))\n    }));\n\n    const [standingsResult, teamsResult, ...scheduleResults] = await Promise.allSettled([\n      standingsPromise,\n      teamsPromise,\n      ...schedulePromises\n    ]);\n\n    if (standingsResult.status !== \"fulfilled\") throw standingsResult.reason;\n\n    const schedules = scheduleResults\n      .filter(result => result.status === \"fulfilled\")\n      .map(result => result.value);\n\n    return {\n      standings: standingsResult.value,\n      teams: teamsResult.status === \"fulfilled\" ? teamsResult.value : { teams: [] },\n      schedules,\n      scheduleFailures: scheduleResults.filter(result => result.status === \"rejected\").length,\n      teamsFailed: teamsResult.status === \"rejected\"\n    };\n  }\n\n  function zoneFromText(value) {\n    const text = String(value ?? \"\").normalize(\"NFD\").replace(\/[\\u0300-\\u036f]\/g, \"\").toLowerCase();\n    if (\/north|norte\/.test(text)) return \"north\";\n    if (\/south|sur\/.test(text)) return \"south\";\n    return null;\n  }\n\n  function fallbackZone(teamId) {\n    return TEAM_OVERRIDES[teamId]?.zone ?? null;\n  }\n\n  function compactName(value, fallback = \"Por definir\") {\n    const name = String(value ?? fallback).trim();\n    return name\n      .replace(\/^Club de Beisbol\\s+\/i, \"\")\n      .replace(\/^Club de B\u00e9isbol\\s+\/i, \"\")\n      .replace(\/\\s+de\\s+Tijuana$\/i, \"\")\n      .replace(\/\\s+de\\s+Monterrey$\/i, \"\")\n      .replace(\/\\s+de\\s+Puebla$\/i, \"\")\n      .replace(\/\\s+de\\s+Oaxaca$\/i, \"\")\n      .replace(\/\\s+de\\s+Yucat[a\u00e1]n$\/i, \"\")\n      .replace(\/\\s+de\\s+Campeche$\/i, \"\")\n      .replace(\/\\s+de\\s+Tabasco$\/i, \"\")\n      .replace(\/\\s+de\\s+Durango$\/i, \"\")\n      .replace(\/\\s+de\\s+Jalisco$\/i, \"\")\n      .replace(\/\\s+de\\s+Chihuahua$\/i, \"\")\n      .replace(\/\\s+de\\s+Aguascalientes$\/i, \"\")\n      .replace(\/\\s+del\\s+Norte$\/i, \"\")\n      .trim();\n  }\n\n  function splitRecordValue(teamRecord, type) {\n    const list = teamRecord?.records?.splitRecords;\n    if (!Array.isArray(list)) return \"\u2014\";\n    const match = list.find(record => String(record?.type).toLowerCase() === String(type).toLowerCase());\n    if (!match) return \"\u2014\";\n    return `${asNumber(match.wins)}-${asNumber(match.losses)}`;\n  }\n\n  function buildTeamMetaMap(teamsJson) {\n    const map = new Map();\n    (teamsJson?.teams ?? []).forEach(team => {\n      const id = Number(team?.id);\n      if (!Number.isFinite(id)) return;\n      map.set(id, team);\n    });\n    return map;\n  }\n\n  function normalizeStandings(standingsJson, teamsJson) {\n    const records = Array.isArray(standingsJson?.records) ? standingsJson.records : [];\n    if (!records.length) throw new Error(\"MLBAM no devolvi\u00f3 registros de standing para la temporada seleccionada.\");\n\n    const metaMap = buildTeamMetaMap(teamsJson);\n    const zones = { north: [], south: [] };\n    let latestUpdate = null;\n\n    records.forEach((record, recordIndex) => {\n      const recordZone = zoneFromText(record?.division?.name);\n      const lastUpdated = record?.lastUpdated;\n      if (lastUpdated && (!latestUpdate || new Date(lastUpdated) > new Date(latestUpdate))) {\n        latestUpdate = lastUpdated;\n      }\n\n      (record?.teamRecords ?? []).forEach(teamRecord => {\n        const id = Number(teamRecord?.team?.id);\n        if (!Number.isFinite(id)) return;\n\n        const meta = metaMap.get(id) ?? {};\n        const override = TEAM_OVERRIDES[id] ?? {};\n        const inferredZone = recordZone\n          || zoneFromText(meta?.division?.name)\n          || fallbackZone(id)\n          || (records.length === 2 ? (recordIndex === 0 ? \"north\" : \"south\") : null);\n\n        if (!inferredZone || !zones[inferredZone]) return;\n\n        const pctNumber = asNumber(teamRecord?.winningPercentage ?? teamRecord?.leagueRecord?.pct, 0);\n        const fullName = meta?.name || teamRecord?.team?.name || `Equipo ${id}`;\n        const shortName = override.short\n          || meta?.teamName\n          || meta?.clubName\n          || meta?.shortName\n          || compactName(fullName);\n        const abbreviation = override.abbr\n          || meta?.abbreviation\n          || meta?.fileCode?.toUpperCase()\n          || compactName(shortName).slice(0, 3).toUpperCase();\n\n        zones[inferredZone].push({\n          id,\n          fullName,\n          shortName: compactName(shortName),\n          abbreviation,\n          zone: inferredZone,\n          wins: asNumber(teamRecord?.wins),\n          losses: asNumber(teamRecord?.losses),\n          record: `${asNumber(teamRecord?.wins)}-${asNumber(teamRecord?.losses)}`,\n          pct: pctNumber,\n          pctText: String(teamRecord?.winningPercentage ?? teamRecord?.leagueRecord?.pct ?? \".000\").replace(\/^0(?=\\.)\/, \"\"),\n          apiRank: asRank(teamRecord?.divisionRank),\n          gamesBack: String(teamRecord?.gamesBack ?? \"\u2014\"),\n          runDifferential: asNumber(teamRecord?.runDifferential),\n          streak: teamRecord?.streak?.streakCode || teamRecord?.streak?.streakType || \"\u2014\",\n          lastTen: splitRecordValue(teamRecord, \"lastTen\"),\n          clinched: Boolean(teamRecord?.clinched),\n          seed: null,\n          qualifies: false\n        });\n      });\n    });\n\n    [\"north\", \"south\"].forEach(zone => {\n      zones[zone].sort((a, b) => {\n        const bothHaveOfficialRank = a.apiRank < 999 && b.apiRank < 999;\n        if (bothHaveOfficialRank && a.apiRank !== b.apiRank) return a.apiRank - b.apiRank;\n        if (b.pct !== a.pct) return b.pct - a.pct;\n        if (b.wins !== a.wins) return b.wins - a.wins;\n        if (b.runDifferential !== a.runDifferential) return b.runDifferential - a.runDifferential;\n        return a.shortName.localeCompare(b.shortName, CONFIG.locale);\n      });\n\n      zones[zone].forEach((team, index) => {\n        team.seed = index + 1;\n        team.qualifies = index < CONFIG.topPerZone;\n      });\n    });\n\n    if (zones.north.length < CONFIG.topPerZone || zones.south.length < CONFIG.topPerZone) {\n      throw new Error(\"No fue posible separar correctamente los equipos de Zona Norte y Zona Sur.\");\n    }\n\n    return { zones, latestUpdate };\n  }\n\n  function normalizeGame(game, requestedType) {\n    const away = game?.teams?.away ?? {};\n    const home = game?.teams?.home ?? {};\n    const awayId = Number(away?.team?.id);\n    const homeId = Number(home?.team?.id);\n    if (!Number.isFinite(awayId) || !Number.isFinite(homeId)) return null;\n\n    return {\n      gamePk: game?.gamePk ?? `${requestedType}-${awayId}-${homeId}-${game?.gameDate ?? Math.random()}`,\n      type: game?.gameType || requestedType,\n      gameDate: game?.gameDate ?? null,\n      status: {\n        abstract: game?.status?.abstractGameState ?? \"Preview\",\n        detailed: game?.status?.detailedState ?? \"Programado\",\n        code: game?.status?.statusCode ?? \"\",\n        coded: game?.status?.codedGameState ?? \"\"\n      },\n      away: {\n        id: awayId,\n        name: away?.team?.name ?? `Equipo ${awayId}`,\n        score: asNumber(away?.score),\n        isWinner: away?.isWinner === true\n      },\n      home: {\n        id: homeId,\n        name: home?.team?.name ?? `Equipo ${homeId}`,\n        score: asNumber(home?.score),\n        isWinner: home?.isWinner === true\n      }\n    };\n  }\n\n  function normalizeGames(schedulePayloads, allowedIds) {\n    const deduped = new Map();\n    schedulePayloads.forEach(({ type, data }) => {\n      (data?.dates ?? []).forEach(dateBlock => {\n        (dateBlock?.games ?? []).forEach(rawGame => {\n          const game = normalizeGame(rawGame, type);\n          if (!game) return;\n          if (!allowedIds.has(game.away.id) || !allowedIds.has(game.home.id)) return;\n          deduped.set(String(game.gamePk), game);\n        });\n      });\n    });\n    return [...deduped.values()].sort((a, b) => new Date(a.gameDate || 0) - new Date(b.gameDate || 0));\n  }\n\n  function makeModel(payload) {\n    const standingModel = normalizeStandings(payload.standings, payload.teams);\n    const allTeams = [...standingModel.zones.north, ...standingModel.zones.south];\n    const teamMap = new Map(allTeams.map(team => [team.id, team]));\n    const allowedIds = new Set(teamMap.keys());\n    const games = normalizeGames(payload.schedules, allowedIds);\n\n    return {\n      zones: standingModel.zones,\n      games,\n      updatedAt: standingModel.latestUpdate || new Date().toISOString(),\n      fetchedAt: Date.now(),\n      cached: false,\n      scheduleFailures: payload.scheduleFailures,\n      teamsFailed: payload.teamsFailed,\n      teamMap\n    };\n  }\n\n  function cacheModel(model) {\n    try {\n      localStorage.setItem(CACHE_KEY, JSON.stringify({\n        version: 5,\n        savedAt: Date.now(),\n        updatedAt: model.updatedAt,\n        zones: model.zones,\n        games: model.games\n      }));\n    } catch (_) {\n      \/\/ El widget sigue funcionando aunque el navegador bloquee localStorage.\n    }\n  }\n\n  function readCachedModel() {\n    try {\n      const raw = localStorage.getItem(CACHE_KEY);\n      if (!raw) return null;\n      const data = JSON.parse(raw);\n      const ageHours = (Date.now() - asNumber(data.savedAt)) \/ 3600000;\n      if (data.version !== 5 || ageHours > CONFIG.cacheMaxHours) return null;\n      if (!data?.zones?.north?.length || !data?.zones?.south?.length) return null;\n      const allTeams = [...data.zones.north, ...data.zones.south];\n      return {\n        zones: data.zones,\n        games: Array.isArray(data.games) ? data.games : [],\n        updatedAt: data.updatedAt || data.savedAt,\n        fetchedAt: data.savedAt,\n        cached: true,\n        scheduleFailures: 0,\n        teamsFailed: false,\n        teamMap: new Map(allTeams.map(team => [Number(team.id), team]))\n      };\n    } catch (_) {\n      return null;\n    }\n  }\n\n  function isFinalGame(game) {\n    return game?.status?.abstract === \"Final\"\n      || game?.status?.code === \"F\"\n      || game?.status?.coded === \"F\";\n  }\n\n  function isLiveGame(game) {\n    return game?.status?.abstract === \"Live\"\n      || [\"I\", \"M\", \"N\"].includes(game?.status?.coded)\n      || game?.status?.code === \"I\";\n  }\n\n  function isCancelledGame(game) {\n    const text = `${game?.status?.detailed ?? \"\"} ${game?.status?.code ?? \"\"}`.toLowerCase();\n    return \/cancel|postpon|suspend|aplaz|posp\/.test(text) || game?.status?.code === \"C\";\n  }\n\n  function pairKey(idA, idB) {\n    return [Number(idA), Number(idB)].sort((a, b) => a - b).join(\"-\");\n  }\n\n  \/* Orden visual de la llave:\n     - Primer Playoff, de arriba hacia abajo: 6 vs 1, 2 vs 5 y 4 vs 3;\n     - dentro de esas tarjetas se respeta exactamente el orden indicado;\n     - en las rondas posteriores se conserva el criterio de mejor equipo abajo. *\/\n  const FIRST_ROUND_VISUAL_ORDER = Object.freeze([\n    Object.freeze([6, 1]),\n    Object.freeze([5, 2]),\n    Object.freeze([4, 3])\n  ]);\n\n  function compareTeamsBestAtBottom(a, b) {\n    const seedA = asRank(a?.seed);\n    const seedB = asRank(b?.seed);\n    const hasSeedA = seedA < 999;\n    const hasSeedB = seedB < 999;\n\n    if (hasSeedA && hasSeedB && seedA !== seedB) return seedB - seedA;\n    if (hasSeedA !== hasSeedB) return hasSeedA ? 1 : -1;\n\n    const pctA = asNumber(a?.pct, -1);\n    const pctB = asNumber(b?.pct, -1);\n    if (pctA !== pctB) return pctA - pctB;\n\n    const winsA = asNumber(a?.wins);\n    const winsB = asNumber(b?.wins);\n    if (winsA !== winsB) return winsA - winsB;\n\n    const diffA = asNumber(a?.runDifferential);\n    const diffB = asNumber(b?.runDifferential);\n    if (diffA !== diffB) return diffA - diffB;\n\n    return String(a?.shortName ?? \"\").localeCompare(String(b?.shortName ?? \"\"), CONFIG.locale);\n  }\n\n  function orderTeamsBestAtBottom(teams) {\n    return [...teams].sort(compareTeamsBestAtBottom);\n  }\n\n  function strongestTeamInSeries(series) {\n    const ordered = orderTeamsBestAtBottom(series?.teams ?? []);\n    return ordered[ordered.length - 1] ?? placeholderTeam(\"Por definir\");\n  }\n\n  function orderSeriesBestAtBottom(seriesList) {\n    return [...seriesList].sort((a, b) =>\n      compareTeamsBestAtBottom(strongestTeamInSeries(a), strongestTeamInSeries(b))\n    );\n  }\n\n  function firstRoundPairIndex(teams) {\n    const seeds = (teams ?? [])\n      .map(team => asRank(team?.seed))\n      .filter(seed => seed < 999);\n    if (seeds.length !== 2) return 999;\n\n    const index = FIRST_ROUND_VISUAL_ORDER.findIndex(pair =>\n      pair.every(seed => seeds.includes(seed))\n    );\n    return index === -1 ? 999 : index;\n  }\n\n  function orderFirstRoundTeams(teams) {\n    const copy = [...(teams ?? [])];\n    const pairIndex = firstRoundPairIndex(copy);\n    if (pairIndex === 999) return orderTeamsBestAtBottom(copy);\n\n    return FIRST_ROUND_VISUAL_ORDER[pairIndex]\n      .map(seed => copy.find(team => asRank(team?.seed) === seed))\n      .filter(Boolean);\n  }\n\n  function orderFirstRoundSeries(seriesList) {\n    return [...seriesList].sort((a, b) => {\n      const indexA = firstRoundPairIndex(a?.teams);\n      const indexB = firstRoundPairIndex(b?.teams);\n      if (indexA !== indexB) return indexA - indexB;\n      return compareTeamsBestAtBottom(strongestTeamInSeries(a), strongestTeamInSeries(b));\n    });\n  }\n\n  function firstRoundLabel(teams) {\n    const seeds = (teams ?? [])\n      .map(team => asRank(team?.seed))\n      .filter(seed => seed < 999);\n    return seeds.length === 2 ? `${seeds[0]} vs ${seeds[1]}` : \"Primer Playoff\";\n  }\n\n  function zoneForPair(idA, idB, teamMap) {\n    const zoneA = teamMap.get(Number(idA))?.zone;\n    const zoneB = teamMap.get(Number(idB))?.zone;\n    if (zoneA && zoneA === zoneB) return zoneA;\n    return \"final\";\n  }\n\n  function gameGroups(model, type, zone) {\n    const groups = new Map();\n    model.games\n      .filter(game => game.type === type)\n      .filter(game => zoneForPair(game.away.id, game.home.id, model.teamMap) === zone)\n      .forEach(game => {\n        const key = pairKey(game.away.id, game.home.id);\n        if (!groups.has(key)) groups.set(key, { key, games: [] });\n        groups.get(key).games.push(game);\n      });\n\n    return [...groups.values()]\n      .map(group => ({\n        ...group,\n        games: group.games.sort((a, b) => new Date(a.gameDate || 0) - new Date(b.gameDate || 0))\n      }))\n      .sort((a, b) => new Date(a.games[0]?.gameDate || 0) - new Date(b.games[0]?.gameDate || 0));\n  }\n\n  function gameTeamFallback(id, group, zone) {\n    const game = group?.games?.find(item => item.away.id === id || item.home.id === id);\n    const side = game?.away.id === id ? game.away : game?.home;\n    const override = TEAM_OVERRIDES[id] ?? {};\n    const fullName = side?.name || `Equipo ${id}`;\n    return {\n      id,\n      fullName,\n      shortName: override.short || compactName(fullName),\n      abbreviation: override.abbr || compactName(fullName).slice(0, 3).toUpperCase(),\n      zone,\n      wins: 0,\n      losses: 0,\n      record: \"\u2014\",\n      pct: 0,\n      pctText: \"\u2014\",\n      seed: null,\n      qualifies: true,\n      runDifferential: 0,\n      gamesBack: \"\u2014\",\n      streak: \"\u2014\",\n      lastTen: \"\u2014\"\n    };\n  }\n\n  function placeholderTeam(name, abbreviation = \"\u2014\", zone = null) {\n    return {\n      id: null,\n      fullName: name,\n      shortName: name,\n      abbreviation,\n      zone,\n      wins: 0,\n      losses: 0,\n      record: \"\u2014\",\n      pct: 0,\n      pctText: \"\u2014\",\n      seed: null,\n      qualifies: false,\n      isPlaceholder: true\n    };\n  }\n\n  function formatGameDate(value, includeTime = false) {\n    if (!value) return \"Fecha por confirmar\";\n    const date = new Date(value);\n    if (Number.isNaN(date.getTime())) return \"Fecha por confirmar\";\n    const options = {\n      day: \"numeric\",\n      month: \"short\",\n      timeZone: CONFIG.timeZone\n    };\n    if (includeTime) {\n      options.hour = \"numeric\";\n      options.minute = \"2-digit\";\n    }\n    return new Intl.DateTimeFormat(CONFIG.locale, options).format(date).replace(\".\", \"\");\n  }\n\n  function seriesFromGroup(group, round, zone, model) {\n    const firstGame = group.games[0];\n    const ids = [firstGame.away.id, firstGame.home.id];\n    const rawTeams = ids.map(id => model.teamMap.get(id) || gameTeamFallback(id, group, zone));\n    const teams = round === \"F\"\n      ? orderFirstRoundTeams(rawTeams)\n      : orderTeamsBestAtBottom(rawTeams);\n\n    const wins = Object.fromEntries(ids.map(id => [id, 0]));\n    const runsFor = Object.fromEntries(ids.map(id => [id, 0]));\n    let completedGames = 0;\n    let liveGame = null;\n\n    group.games.forEach(game => {\n      if (isLiveGame(game)) liveGame = game;\n      if (!isFinalGame(game)) return;\n      completedGames += 1;\n      runsFor[game.away.id] = asNumber(runsFor[game.away.id]) + game.away.score;\n      runsFor[game.home.id] = asNumber(runsFor[game.home.id]) + game.home.score;\n\n      let winnerId = null;\n      if (game.away.isWinner) winnerId = game.away.id;\n      else if (game.home.isWinner) winnerId = game.home.id;\n      else if (game.away.score > game.home.score) winnerId = game.away.id;\n      else if (game.home.score > game.away.score) winnerId = game.home.id;\n\n      if (winnerId) wins[winnerId] = asNumber(wins[winnerId]) + 1;\n    });\n\n    const winnerId = ids.find(id => wins[id] >= 4) ?? null;\n    const loserId = winnerId ? ids.find(id => id !== winnerId) : null;\n    const upcoming = group.games.find(game => !isFinalGame(game) && !isLiveGame(game) && !isCancelledGame(game));\n    const hasScheduled = group.games.some(game => !isFinalGame(game) && !isCancelledGame(game));\n    const scoreA = wins[teams[0].id] ?? 0;\n    const scoreB = wins[teams[1].id] ?? 0;\n\n    let status = \"scheduled\";\n    let statusText = \"Programada\";\n    if (winnerId) {\n      status = \"final\";\n      statusText = `Final ${scoreA}-${scoreB}`;\n    } else if (liveGame) {\n      status = \"live\";\n      statusText = \"En vivo\";\n    } else if (completedGames > 0) {\n      status = \"series\";\n      statusText = `Serie ${scoreA}-${scoreB}`;\n    } else if (!hasScheduled) {\n      status = \"waiting\";\n      statusText = \"Por confirmar\";\n    }\n\n    const totalRuns = runsFor[ids[0]] + runsFor[ids[1]];\n    const runDiff = {\n      [ids[0]]: runsFor[ids[0]] - runsFor[ids[1]],\n      [ids[1]]: runsFor[ids[1]] - runsFor[ids[0]]\n    };\n\n    return {\n      round,\n      zone,\n      mode: \"actual\",\n      teams,\n      wins,\n      runsFor,\n      runDiff,\n      completedGames,\n      winner: winnerId ? model.teamMap.get(winnerId) || teams.find(team => team.id === winnerId) : null,\n      loser: loserId ? model.teamMap.get(loserId) || teams.find(team => team.id === loserId) : null,\n      winnerId,\n      loserId,\n      status,\n      statusText,\n      liveGame,\n      nextGame: liveGame || upcoming || null,\n      totalRuns,\n      label: round === \"F\"\n        ? firstRoundLabel(teams)\n        : POSTSEASON_TYPES[round],\n      note: winnerId\n        ? `${completedGames} juegos \u00b7 serie concluida`\n        : liveGame\n          ? `${formatGameDate(liveGame.gameDate, true)} \u00b7 juego en curso`\n          : upcoming\n            ? `Pr\u00f3ximo: ${formatGameDate(upcoming.gameDate, true)}`\n            : \"Calendario MLBAM\",\n      wildcardIds: []\n    };\n  }\n\n  function projectedSeries(round, zone, teamA, teamB, options = {}) {\n    const rawTeams = [teamA, teamB];\n    const teams = options.preserveOrder\n      ? rawTeams\n      : round === \"F\"\n        ? orderFirstRoundTeams(rawTeams)\n        : orderTeamsBestAtBottom(rawTeams);\n    const waiting = teams.some(team => team?.isPlaceholder);\n    return {\n      round,\n      zone,\n      mode: waiting ? \"waiting\" : \"projection\",\n      teams,\n      wins: {},\n      runsFor: {},\n      runDiff: {},\n      completedGames: 0,\n      winner: null,\n      loser: null,\n      winnerId: null,\n      loserId: null,\n      status: waiting ? \"waiting\" : \"projection\",\n      statusText: waiting ? \"Por definir\" : \"AL MOMENTO\",\n      liveGame: null,\n      nextGame: null,\n      label: options.label || (round === \"F\" ? firstRoundLabel(teams) : POSTSEASON_TYPES[round]),\n      note: options.note || (waiting ? \" \" : \" \"),\n      wildcardIds: options.wildcardIds || []\n    };\n  }\n\n  function firstRound(zone, model) {\n    const seeds = model.zones[zone].slice(0, CONFIG.topPerZone);\n    const pairs = FIRST_ROUND_VISUAL_ORDER.map(([topSeed, bottomSeed]) => [topSeed - 1, bottomSeed - 1]);\n    const groups = gameGroups(model, \"F\", zone);\n    const groupMap = new Map(groups.map(group => [group.key, group]));\n    const matched = [];\n\n    pairs.forEach(([a, b]) => {\n      const teamA = seeds[a];\n      const teamB = seeds[b];\n      const group = groupMap.get(pairKey(teamA.id, teamB.id));\n      matched.push(group\n        ? seriesFromGroup(group, \"F\", zone, model)\n        : projectedSeries(\"F\", zone, teamA, teamB, { preserveOrder: true }));\n    });\n\n    const actualCount = matched.filter(series => series.mode === \"actual\").length;\n    if (groups.length >= 3 && actualCount < groups.length) {\n      return orderFirstRoundSeries(\n        groups.slice(0, 3).map(group => seriesFromGroup(group, \"F\", zone, model))\n      );\n    }\n\n    return matched;\n  }\n\n  function calculateWildcard(firstSeries) {\n    if (!firstSeries.length || !firstSeries.every(series => series.winner && series.loser)) return null;\n\n    const candidates = firstSeries.map(series => ({\n      team: series.loser,\n      originalWinner: series.winner,\n      games: series.completedGames,\n      regularPct: series.loser.pct,\n      firstRoundDiff: asNumber(series.runDiff?.[series.loser.id]),\n      seed: asRank(series.loser.seed)\n    }));\n\n    candidates.sort((a, b) => {\n      if (b.games !== a.games) return b.games - a.games;\n      if (b.regularPct !== a.regularPct) return b.regularPct - a.regularPct;\n      if (b.firstRoundDiff !== a.firstRoundDiff) return b.firstRoundDiff - a.firstRoundDiff;\n      return a.seed - b.seed;\n    });\n\n    return candidates[0] || null;\n  }\n\n  function zoneSeries(zone, model, firstSeries, wildcardInfo) {\n    const groups = gameGroups(model, \"D\", zone);\n    if (groups.length) {\n      const actual = orderSeriesBestAtBottom(\n        groups.slice(0, 2).map(group => seriesFromGroup(group, \"D\", zone, model))\n      );\n      while (actual.length < 2) {\n        actual.push(projectedSeries(\n          \"D\",\n          zone,\n          placeholderTeam(\"Ganador por definir\", \"G\", zone),\n          placeholderTeam(\"Rival por definir\", \"R\", zone),\n          { label: `Serie ${actual.length + 1}`, note: \"Pendiente de calendario MLBAM\" }\n        ));\n      }\n      if (wildcardInfo?.team?.id) {\n        actual.forEach(series => {\n          if (series.teams.some(team => team.id === wildcardInfo.team.id)) {\n            series.wildcardIds = [wildcardInfo.team.id];\n          }\n        });\n      }\n      return orderSeriesBestAtBottom(actual);\n    }\n\n    if (!wildcardInfo) {\n      return [\n        projectedSeries(\n          \"D\",\n          zone,\n          placeholderTeam(\"Mejor ganador\", \"1\", zone),\n          placeholderTeam(\"Comod\u00edn\", \"C\", zone),\n          { label: \"Llave A\", note: \" \" }\n        ),\n        projectedSeries(\n          \"D\",\n          zone,\n          placeholderTeam(\"Ganador restante\", \"G\", zone),\n          placeholderTeam(\"Ganador restante\", \"G\", zone),\n          { label: \"Llave b \", note: \" \" }\n        )\n      ];\n    }\n\n    const winners = firstSeries.map(series => series.winner).sort((a, b) => asRank(a.seed) - asRank(b.seed));\n    const bestEligibleWinner = winners.find(team => team.id !== wildcardInfo.originalWinner.id);\n    const remainingWinners = winners.filter(team => team.id !== bestEligibleWinner.id);\n\n    return orderSeriesBestAtBottom([\n      projectedSeries(\"D\", zone, bestEligibleWinner, wildcardInfo.team, {\n        label: \"Serie de Zona A\",\n        note: \"Comod\u00edn inicia como visitante\",\n        wildcardIds: [wildcardInfo.team.id]\n      }),\n      projectedSeries(\"D\", zone, remainingWinners[0], remainingWinners[1], {\n        label: \"Serie de Zona B\",\n        note: \"Reacomodo oficial por siembra\",\n        wildcardIds: [wildcardInfo.team.id]\n      })\n    ]);\n  }\n\n  function championshipSeries(zone, model, zoneRound, wildcardInfo) {\n    const groups = gameGroups(model, \"L\", zone);\n    if (groups.length) {\n      const actual = seriesFromGroup(groups[0], \"L\", zone, model);\n      if (wildcardInfo?.team?.id && actual.teams.some(team => team.id === wildcardInfo.team.id)) {\n        actual.wildcardIds = [wildcardInfo.team.id];\n      }\n      return [actual];\n    }\n\n    if (zoneRound.every(series => series.winner)) {\n      return [projectedSeries(\"L\", zone, zoneRound[0].winner, zoneRound[1].winner, {\n        label: `Final ${zone === \"north\" ? \"Norte\" : \"Sur\"}`,\n        note: \"Ganadores de las Series de Zona\",\n        wildcardIds: wildcardInfo?.team?.id ? [wildcardInfo.team.id] : []\n      })];\n    }\n\n    return [projectedSeries(\n      \"L\",\n      zone,\n      placeholderTeam(\" \", \"A\", zone),\n      placeholderTeam(\" \", \"B\", zone),\n      { label: `Campeonato ${zone === \"north\" ? \"Norte\" : \"Sur\"}`, note: \"\" }\n    )];\n  }\n\n  function buildZoneBracket(zone, model) {\n    const first = firstRound(zone, model);\n    const wildcard = calculateWildcard(first);\n    const division = zoneSeries(zone, model, first, wildcard);\n    const championship = championshipSeries(zone, model, division, wildcard);\n    return { first, division, championship, wildcard };\n  }\n\n  function finalSeries(model, north, south) {\n    const groups = gameGroups(model, \"W\", \"final\");\n    const wildcardIds = [north.wildcard?.team?.id, south.wildcard?.team?.id].filter(Boolean);\n\n    if (groups.length) {\n      const actual = seriesFromGroup(groups[0], \"W\", \"final\", model);\n      actual.wildcardIds = wildcardIds.filter(id => actual.teams.some(team => team.id === id));\n      actual.label = \"Serie del Rey\";\n      return actual;\n    }\n\n    const northChampion = north.championship[0]?.winner;\n    const southChampion = south.championship[0]?.winner;\n    if (northChampion && southChampion) {\n      return projectedSeries(\"W\", \"final\", northChampion, southChampion, {\n        label: \"Serie del Rey\",\n        note: \"Campe\u00f3n ZN vs Campe\u00f3n ZR\",\n        wildcardIds\n      });\n    }\n\n    return projectedSeries(\n      \"W\",\n      \"final\",\n      placeholderTeam(\"Campe\u00f3n Zona Norte\", \"N\", \"north\"),\n      placeholderTeam(\"Campe\u00f3n Zona Sur\", \"S\", \"south\"),\n      { label: \"Serie del Rey\", note: \" \", wildcardIds }\n    );\n  }\n\n  function buildBracket(model) {\n    const north = buildZoneBracket(\"north\", model);\n    const south = buildZoneBracket(\"south\", model);\n    return {\n      north,\n      south,\n      final: finalSeries(model, north, south)\n    };\n  }\n\n  function logoMarkup(team) {\n    const abbreviation = escapeHtml(team?.abbreviation || \"\u2014\");\n    if (!team?.id) {\n      return `<span class=\"lmbx-logo\" aria-hidden=\"true\"><span class=\"lmbx-logo-fallback\">${abbreviation}<\/span><\/span>`;\n    }\n    return `\n      <span class=\"lmbx-logo\" aria-hidden=\"true\">\n        <span class=\"lmbx-logo-fallback\">${abbreviation}<\/span>\n        <img data-lmbx-logo=\"${Number(team.id)}\" alt=\"\" loading=\"lazy\" decoding=\"async\">\n      <\/span>`;\n  }\n\n  function statusClass(series) {\n    if (series.status === \"live\") return \"is-live\";\n    if (series.status === \"final\") return \"is-final\";\n    if (series.status === \"series\") return \"is-series\";\n    return \"\";\n  }\n\n  function teamMetric(series, team) {\n    if (team?.isPlaceholder) return \"\u2014\";\n    if (series.mode === \"actual\") return String(asNumber(series.wins?.[team.id]));\n    return team.record || team.pctText || \"\u2014\";\n  }\n\n  function teamRowMarkup(series, team) {\n    const isWinner = Boolean(series.winnerId && team.id === series.winnerId);\n    const isEliminated = Boolean(series.loserId && team.id === series.loserId);\n    const isWildcard = Boolean(team.id && series.wildcardIds?.includes(team.id));\n    const rowClasses = [\n      \"lmbx-team-row\",\n      isWinner ? \"is-winner\" : \"\",\n      isEliminated ? \"is-eliminated\" : \"\",\n      team?.isPlaceholder ? \"is-placeholder\" : \"\"\n    ].filter(Boolean).join(\" \");\n    const metricClass = series.mode === \"actual\" ? \"is-series-score\" : \"\";\n    const sub = team?.isPlaceholder\n      ? \" \"\n      : `PCT ${team.pctText || \"\u2014\"} \u00b7 ${team.record || \"\u2014\"}`;\n\n    return `\n      <div class=\"${rowClasses}\" title=\"${escapeHtml(team?.fullName || team?.shortName || \"Por definir\")}\">\n        <span class=\"lmbx-seed ${isWildcard ? \"is-wildcard\" : \"\"}\">${isWildcard ? \"C\" : escapeHtml(team?.seed ?? \"\u2014\")}<\/span>\n        ${logoMarkup(team)}\n        <span class=\"lmbx-team-copy\">\n          <span class=\"lmbx-team-name\">\n            <span>${escapeHtml(team?.shortName || \"Por definir\")}<\/span>\n            ${isWildcard ? '<span class=\"lmbx-wildcard-tag\">WC<\/span>' : \"\"}\n          <\/span>\n          <span class=\"lmbx-team-sub\">${escapeHtml(sub)}<\/span>\n        <\/span>\n        <span class=\"lmbx-team-metric ${metricClass}\">${escapeHtml(teamMetric(series, team))}<\/span>\n      <\/div>`;\n  }\n\n  function seriesCardMarkup(series, slot, top = null, extraClass = \"\") {\n    const containsFeatured = CONFIG.featuredTeamId && series.teams.some(team => Number(team?.id) === Number(CONFIG.featuredTeamId));\n    const classes = [\n      \"lmbx-series\",\n      series.zone === \"south\" ? \"is-south\" : \"\",\n      series.zone === \"final\" ? \"is-final\" : \"\",\n      series.mode === \"waiting\" ? \"is-waiting\" : \"\",\n      containsFeatured ? \"is-featured\" : \"\",\n      extraClass\n    ].filter(Boolean).join(\" \");\n    const style = top === null ? \"\" : ` style=\"top:${Number(top)}px\"`;\n    const roundText = series.round === \"F\"\n      ? \"Primer Playoff\"\n      : series.round === \"D\"\n        ? \"Serie de Zona\"\n        : series.round === \"L\"\n          ? \"Campeonato\"\n          : \"Final LMB\";\n\n    return `\n      <article class=\"${classes}\" data-slot=\"${escapeHtml(slot)}\"${style} aria-label=\"${escapeHtml(series.label)}\">\n        <div class=\"lmbx-series-top\">\n          <span class=\"lmbx-series-label\">${escapeHtml(series.label || roundText)}<\/span>\n          <span class=\"lmbx-status ${statusClass(series)}\">${escapeHtml(series.statusText)}<\/span>\n        <\/div>\n        ${teamRowMarkup(series, series.teams[0])}\n        ${teamRowMarkup(series, series.teams[1])}\n        <div class=\"lmbx-series-foot\">\n          <span>${escapeHtml(series.note || \"\")}<\/span>\n          <b>${escapeHtml(roundText)}<\/b>\n        <\/div>\n      <\/article>`;\n  }\n\n  function roundMarkup({ title, subtitle, series, prefix, positions, className = \"\" }) {\n    return `\n      <section class=\"lmbx-round ${className}\">\n        <header class=\"lmbx-round-head\">\n          <strong>${escapeHtml(title)}<\/strong>\n          <span>${escapeHtml(subtitle)}<\/span>\n        <\/header>\n        <div class=\"lmbx-round-body\">\n          ${series.map((item, index) => seriesCardMarkup(item, `${prefix}-${index}`, positions[index])).join(\"\")}\n        <\/div>\n      <\/section>`;\n  }\n\n  function desktopBracketMarkup(bracket) {\n    return `\n      <div class=\"lmbx-desktop-bracket\">\n        <div class=\"lmbx-bracket-scroll\">\n          <div class=\"lmbx-board\" data-lmbx-board>\n            <div class=\"lmbx-board-watermark\" aria-hidden=\"true\">LMB<\/div>\n            <svg class=\"lmbx-links\" aria-hidden=\"true\"><\/svg>\n            ${roundMarkup({\n              title: \"Primer Playoff\",\n              subtitle: \"Zona Norte \u00b7 6-1 \/ 5-2 \/ 4-3\",\n              series: bracket.north.first,\n              prefix: \"n-f\",\n              positions: [26, 219, 412]\n            })}\n            ${roundMarkup({\n              title: \"Series de Zona\",\n              subtitle: \" \",\n              series: bracket.north.division,\n              prefix: \"n-d\",\n              positions: [123, 315]\n            })}\n            ${roundMarkup({\n              title: \"Campeonato Norte\",\n              subtitle: \" \",\n              series: bracket.north.championship,\n              prefix: \"n-l\",\n              positions: [219]\n            })}\n            ${roundMarkup({\n              title: \"Serie del Rey\",\n              subtitle: \"Campe\u00f3n Norte vs Sur\",\n              series: [bracket.final],\n              prefix: \"final\",\n              positions: [219],\n              className: \"is-final\"\n            })}\n            ${roundMarkup({\n              title: \"Campeonato Sur\",\n              subtitle: \" \",\n              series: bracket.south.championship,\n              prefix: \"s-l\",\n              positions: [219]\n            })}\n            ${roundMarkup({\n              title: \"Series de Zona\",\n              subtitle: \" \",\n              series: bracket.south.division,\n              prefix: \"s-d\",\n              positions: [123, 315]\n            })}\n            ${roundMarkup({\n              title: \"Primer Playoff\",\n              subtitle: \"Zona Sur \u00b7 6-1 \/ 5-2 \/ 4-3\",\n              series: bracket.south.first,\n              prefix: \"s-f\",\n              positions: [26, 219, 412]\n            })}\n          <\/div>\n        <\/div>\n      <\/div>`;\n  }\n\n  function mobileRoundMarkup(title, series, prefix) {\n    return `\n      <div class=\"lmbx-mobile-round\">\n        <div class=\"lmbx-mobile-round-title\">${escapeHtml(title)}<\/div>\n        <div class=\"lmbx-mobile-cards\">\n          ${series.map((item, index) => seriesCardMarkup(item, `${prefix}-m-${index}`)).join(\"\")}\n        <\/div>\n      <\/div>`;\n  }\n\n  function mobileZoneMarkup(zone, data) {\n    const isSouth = zone === \"south\";\n    return `\n      <section class=\"lmbx-mobile-zone ${isSouth ? \"is-south\" : \"\"}\">\n        <header class=\"lmbx-mobile-zone-head\">\n          <h3>Zona ${isSouth ? \"Sur\" : \"Norte\"}<\/h3>\n          <span>Clasifican seis<\/span>\n        <\/header>\n        ${mobileRoundMarkup(\"Primer Playoff\", data.first, `${isSouth ? \"s\" : \"n\"}-f`)}\n        ${mobileRoundMarkup(\"Series de Zona \", data.division, `${isSouth ? \"s\" : \"n\"}-d`)}\n        ${mobileRoundMarkup(`Campeonato de la Zona ${isSouth ? \"Sur\" : \"Norte\"}`, data.championship, `${isSouth ? \"s\" : \"n\"}-l`)}\n      <\/section>`;\n  }\n\n  function mobileBracketMarkup(bracket) {\n    return `\n      <div class=\"lmbx-mobile-bracket\">\n        ${mobileZoneMarkup(\"north\", bracket.north)}\n        ${mobileZoneMarkup(\"south\", bracket.south)}\n        <section class=\"lmbx-mobile-final\">\n          <h3>Serie del Rey<\/h3>\n          ${seriesCardMarkup(bracket.final, \"final-m\")}\n        <\/section>\n      <\/div>`;\n  }\n\n  function standingRowMarkup(team, index) {\n    const diffClass = team.runDifferential > 0 ? \"lmbx-positive\" : team.runDifferential < 0 ? \"lmbx-negative\" : \"\";\n    const rowClasses = [\n      team.qualifies ? \"is-qualified\" : \"\",\n      index === CONFIG.topPerZone - 1 ? \"is-cut\" : \"\",\n      Number(team.id) === Number(CONFIG.featuredTeamId) ? \"is-featured\" : \"\"\n    ].filter(Boolean).join(\" \");\n    const diffText = team.runDifferential > 0 ? `+${team.runDifferential}` : String(team.runDifferential);\n\n    return `\n      <tr class=\"${rowClasses}\">\n        <td><span class=\"lmbx-rank\">${team.seed}<\/span><\/td>\n        <td>\n          <span class=\"lmbx-standing-team\">\n            ${logoMarkup(team)}\n            <span class=\"lmbx-standing-name\">\n              <strong>${escapeHtml(team.shortName)}<\/strong>\n              <small>${escapeHtml(team.fullName)}<\/small>\n            <\/span>\n          <\/span>\n        <\/td>\n        <td>${team.wins}<\/td>\n        <td>${team.losses}<\/td>\n        <td class=\"lmbx-pct\">${escapeHtml(team.pctText)}<\/td>\n        <td>${escapeHtml(team.gamesBack)}<\/td>\n        <td class=\"${diffClass}\">${escapeHtml(diffText)}<\/td>\n        <td>${escapeHtml(team.lastTen)}<\/td>\n      <\/tr>`;\n  }\n\n  function standingsTableMarkup(zone, teams) {\n    const isSouth = zone === \"south\";\n    return `\n      <section class=\"lmbx-table-card ${isSouth ? \"is-south\" : \"\"}\">\n        <header class=\"lmbx-table-title\">\n          <h3>Zona ${isSouth ? \"Sur\" : \"Norte\"}<\/h3>\n          <span>Solo pasan 6 a Playoff<\/span>\n        <\/header>\n        <div class=\"lmbx-table-scroll\">\n          <table class=\"lmbx-table\">\n            <thead>\n              <tr>\n                <th>Pos<\/th>\n                <th>Equipo<\/th>\n                <th>G<\/th>\n                <th>P<\/th>\n                <th>PCT<\/th>\n                <th>JV<\/th>\n                <th>DIF<\/th>\n                <th>\u00dalt. 10<\/th>\n              <\/tr>\n            <\/thead>\n            <tbody>${teams.map(standingRowMarkup).join(\"\")}<\/tbody>\n          <\/table>\n        <\/div>\n      <\/section>`;\n  }\n\n  function formatUpdated(value) {\n    const date = new Date(value);\n    if (Number.isNaN(date.getTime())) return \"Sin fecha disponible\";\n    return new Intl.DateTimeFormat(CONFIG.locale, {\n\n    }).format().replace(\".\", \"\");\n  }\n\n  function noticeMarkup() {\n    if (!state.notice) return \"\";\n    return `\n      <div class=\"lmbx-notice ${state.noticeType === \"cache\" ? \"is-cache\" : \"\"}\" role=\"status\">\n        <span class=\"lmbx-notice-icon\">!<\/span>\n        <span>${escapeHtml(state.notice)}<\/span>\n      <\/div>`;\n  }\n\n  function appMarkup(model, bracket) {\n    const sourceLabel = model.cached ? \" \" : \" \";\n    const updateLabel = model.cached ? \"\u00daltima copia guardada\" : \"\";\n\n    return `\n      <div class=\"lmbx-shell\">\n        <header class=\"lmbx-header\">\n          <div class=\"lmbx-title-wrap\">\n            <div class=\"lmbx-kicker\">Liga Mexicana de Beisbol<\/div>\n            <h1 class=\"lmbx-main-title\"><span>Postemporada<\/span> <strong>2026<\/strong><\/h1>\n            <p class=\"lmbx-subtitle\"><\/p>\n          <\/div>\n\n        <\/header>\n\n        <div class=\"lmbx-toolbar\">\n          <div class=\"lmbx-tabs\" role=\"tablist\" aria-label=\"Vistas del widget\">\n            <button class=\"lmbx-tab\" type=\"button\" role=\"tab\" data-tab=\"bracket\" aria-selected=\"${state.activeTab === \"bracket\"}\">Bracket<\/button>\n            <button class=\"lmbx-tab\" type=\"button\" role=\"tab\" data-tab=\"standings\" aria-selected=\"${state.activeTab === \"standings\"}\">Posiciones<\/button>\n          <\/div>\n          <div class=\"lmbx-toolbar-actions\">\n            <div class=\"lmbx-updated\">\n              ${updateLabel}\n              <strong>${escapeHtml(formatUpdated(model.updatedAt))}<\/strong>\n            <\/div>\n            <button class=\"lmbx-refresh ${state.loading ? \"is-loading\" : \"\"}\" type=\"button\" aria-label=\"Actualizar datos\" title=\"Actualizar datos\" ${state.loading ? \"disabled\" : \"\"}>\n              <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M20 11a8.1 8.1 0 0 0-15.5-3M4 4v4h4M4 13a8.1 8.1 0 0 0 15.5 3M20 20v-4h-4\"\/><\/svg>\n            <\/button>\n          <\/div>\n        <\/div>\n\n        ${noticeMarkup()}\n\n        <section class=\"lmbx-panel ${state.activeTab === \"bracket\" ? \"is-active\" : \"\"}\" data-panel=\"bracket\" role=\"tabpanel\">\n          <div class=\"lmbx-bracket-wrap\">\n            <div class=\"lmbx-bracket-intro\">\n              <div>\n                <h2><\/h2>\n                <p><\/p>\n              <\/div>\n              \n            <\/div>\n            ${desktopBracketMarkup(bracket)}\n            ${mobileBracketMarkup(bracket)}\n            <div class=\"lmbx-legend\" aria-label=\"Leyenda de colores\">\n              <span class=\"lmbx-legend-item\"><i class=\"lmbx-legend-swatch\"><\/i>Zona Norte<\/span>\n              <span class=\"lmbx-legend-item\"><i class=\"lmbx-legend-swatch is-south\"><\/i>Zona Sur<\/span>\n              <span class=\"lmbx-legend-item\"><i class=\"lmbx-legend-swatch is-final\"><\/i>Serie del Rey<\/span>\n            <\/div>\n            <div class=\"lmbx-rule-note\">\n              Despu\u00e9s del Primer Playoff avanzan los tres ganadores y el mejor perdedor como Comod\u00edn.\n            <\/div>\n          <\/div>\n        <\/section>\n\n        <section class=\"lmbx-panel ${state.activeTab === \"standings\" ? \"is-active\" : \"\"}\" data-panel=\"standings\" role=\"tabpanel\">\n          <div class=\"lmbx-standings-wrap\">\n            <header class=\"lmbx-standings-header\">\n              <div>\n                <h2>Standing por zonas<\/h2>\n                <p>El corte de postemporada se marca despu\u00e9s de la sexta posici\u00f3n.<\/p>\n              <\/div>\n              <span class=\"lmbx-cut-key\">Zona de clasificaci\u00f3n<\/span>\n            <\/header>\n            <div class=\"lmbx-standings-grid\">\n              ${standingsTableMarkup(\"north\", model.zones.north)}\n              ${standingsTableMarkup(\"south\", model.zones.south)}\n            <\/div>\n          <\/div>\n        <\/section>\n\n        <footer class=\"lmbx-footer\">\n          \n        <\/footer>\n      <\/div>`;\n  }\n\n  function loadingMarkup() {\n    return `\n      <div class=\"lmbx-loading\">\n        <div>\n          <div class=\"lmbx-loader-ball\" aria-hidden=\"true\"><\/div>\n          <h3>Consultando MLBAM\u2026<\/h3>\n          <p>Obteniendo el standing de la LMB y los juegos de cada ronda de postemporada.<\/p>\n        <\/div>\n      <\/div>`;\n  }\n\n  function errorMarkup(error) {\n    return `\n      <div class=\"lmbx-error\">\n        <div>\n          <div class=\"lmbx-error-icon\" aria-hidden=\"true\">!<\/div>\n          <h3>No fue posible cargar el bracket<\/h3>\n          <p>${escapeHtml(error?.message || \"La API de MLBAM no respondi\u00f3. Verifica la conexi\u00f3n del sitio e int\u00e9ntalo de nuevo.\")}<\/p>\n          <button type=\"button\" data-retry>Reintentar consulta<\/button>\n        <\/div>\n      <\/div>`;\n  }\n\n  function wireLogos() {\n    root.querySelectorAll(\"img[data-lmbx-logo]\").forEach(img => {\n      const teamId = Number(img.dataset.lmbxLogo);\n      const sources = [\n        `${CONFIG.logoBase}\/team-primary-on-light\/${teamId}.svg`,\n        `${CONFIG.logoBase}\/team-cap-on-light\/${teamId}.svg`,\n        `${CONFIG.logoBase}\/${teamId}.svg`\n      ];\n      let index = 0;\n      img.addEventListener(\"error\", () => {\n        index += 1;\n        if (index < sources.length) img.src = sources[index];\n        else img.style.display = \"none\";\n      });\n      img.src = sources[0];\n    });\n  }\n\n  function svgPath(path, className) {\n    return `<path class=\"lmbx-link-path ${className}\" d=\"${path}\"\/>`;\n  }\n\n  function cardPoint(board, selector, side) {\n    const element = board.querySelector(`[data-slot=\"${selector}\"]`);\n    if (!element) return null;\n    const boardRect = board.getBoundingClientRect();\n    const rect = element.getBoundingClientRect();\n    const scaleX = board.offsetWidth \/ boardRect.width;\n    const scaleY = board.offsetHeight \/ boardRect.height;\n    return {\n      x: ((side === \"right\" ? rect.right : rect.left) - boardRect.left) * scaleX,\n      y: (rect.top + rect.height \/ 2 - boardRect.top) * scaleY\n    };\n  }\n\n  function connectorPaths(board, sourceSlots, targetSlots, direction, className) {\n    const sourceSide = direction === \"ltr\" ? \"right\" : \"left\";\n    const targetSide = direction === \"ltr\" ? \"left\" : \"right\";\n    const sources = sourceSlots.map(slot => cardPoint(board, slot, sourceSide)).filter(Boolean);\n    const targets = targetSlots.map(slot => cardPoint(board, slot, targetSide)).filter(Boolean);\n    if (!sources.length || !targets.length) return [];\n\n    if (sources.length === 1 && targets.length === 1) {\n      const s = sources[0];\n      const t = targets[0];\n      const mid = (s.x + t.x) \/ 2;\n      return [svgPath(`M ${s.x} ${s.y} H ${mid} V ${t.y} H ${t.x}`, className)];\n    }\n\n    const sourceEdge = direction === \"ltr\"\n      ? Math.max(...sources.map(point => point.x))\n      : Math.min(...sources.map(point => point.x));\n    const targetEdge = direction === \"ltr\"\n      ? Math.min(...targets.map(point => point.x))\n      : Math.max(...targets.map(point => point.x));\n    const trunkX = (sourceEdge + targetEdge) \/ 2;\n    const ys = [...sources, ...targets].map(point => point.y);\n    const minY = Math.min(...ys);\n    const maxY = Math.max(...ys);\n    const paths = [svgPath(`M ${trunkX} ${minY} V ${maxY}`, className)];\n    sources.forEach(point => paths.push(svgPath(`M ${point.x} ${point.y} H ${trunkX}`, className)));\n    targets.forEach(point => paths.push(svgPath(`M ${trunkX} ${point.y} H ${point.x}`, className)));\n    return paths;\n  }\n\n  function drawConnections() {\n    const board = root.querySelector(\"[data-lmbx-board]\");\n    const svg = board?.querySelector(\".lmbx-links\");\n    if (!board || !svg || window.getComputedStyle(board.closest(\".lmbx-desktop-bracket\")).display === \"none\") return;\n\n    svg.setAttribute(\"viewBox\", `0 0 ${board.offsetWidth} ${board.offsetHeight}`);\n    const paths = [\n      ...connectorPaths(board, [\"n-f-0\", \"n-f-1\", \"n-f-2\"], [\"n-d-0\", \"n-d-1\"], \"ltr\", \"is-north\"),\n      ...connectorPaths(board, [\"n-d-0\", \"n-d-1\"], [\"n-l-0\"], \"ltr\", \"is-north\"),\n      ...connectorPaths(board, [\"n-l-0\"], [\"final-0\"], \"ltr\", \"is-final\"),\n      ...connectorPaths(board, [\"s-f-0\", \"s-f-1\", \"s-f-2\"], [\"s-d-0\", \"s-d-1\"], \"rtl\", \"is-south\"),\n      ...connectorPaths(board, [\"s-d-0\", \"s-d-1\"], [\"s-l-0\"], \"rtl\", \"is-south\"),\n      ...connectorPaths(board, [\"s-l-0\"], [\"final-0\"], \"rtl\", \"is-final\")\n    ];\n    svg.innerHTML = paths.join(\"\");\n  }\n\n  function setActiveTab(tab) {\n    state.activeTab = tab;\n    root.querySelectorAll(\"[data-tab]\").forEach(button => {\n      button.setAttribute(\"aria-selected\", String(button.dataset.tab === tab));\n    });\n    root.querySelectorAll(\"[data-panel]\").forEach(panel => {\n      panel.classList.toggle(\"is-active\", panel.dataset.panel === tab);\n    });\n    if (tab === \"bracket\") window.requestAnimationFrame(drawConnections);\n  }\n\n  function wireUi() {\n    root.querySelectorAll(\"[data-tab]\").forEach(button => {\n      button.addEventListener(\"click\", () => setActiveTab(button.dataset.tab));\n    });\n    root.querySelector(\".lmbx-refresh\")?.addEventListener(\"click\", () => loadData(true));\n    root.querySelector(\"[data-retry]\")?.addEventListener(\"click\", () => loadData(false));\n    wireLogos();\n    window.requestAnimationFrame(() => window.requestAnimationFrame(drawConnections));\n  }\n\n  function render() {\n    if (!state.model || !state.bracket) return;\n    root.innerHTML = appMarkup(state.model, state.bracket);\n    wireUi();\n  }\n\n  async function loadData(manual = false) {\n    if (state.loading) return;\n    state.loading = true;\n    state.notice = \"\";\n    state.noticeType = \"\";\n\n    if (!state.model) root.innerHTML = loadingMarkup();\n    else render();\n\n    try {\n      const payload = await fetchAllData();\n      const model = makeModel(payload);\n      state.model = model;\n      state.bracket = buildBracket(model);\n      state.lastSuccessfulFetch = Date.now();\n\n      if (payload.scheduleFailures === Object.keys(POSTSEASON_TYPES).length) {\n        state.notice = \"El standing se actualiz\u00f3, pero MLBAM no devolvi\u00f3 todav\u00eda los calendarios de postemporada. El bracket se muestra como proyecci\u00f3n.\";\n        state.noticeType = \"warning\";\n      } else if (payload.scheduleFailures > 0) {\n        state.notice = \"Algunas rondas de postemporada a\u00fan no est\u00e1n disponibles en MLBAM; las dem\u00e1s se muestran con datos reales.\";\n        state.noticeType = \"warning\";\n      } else if (payload.teamsFailed) {\n        state.notice = \"El standing est\u00e1 actualizado; se usaron nombres compactos locales porque el cat\u00e1logo de equipos no respondi\u00f3.\";\n        state.noticeType = \"warning\";\n      }\n\n      cacheModel(model);\n    } catch (error) {\n      const cached = state.model || readCachedModel();\n      if (cached) {\n        state.model = cached;\n        state.model.cached = true;\n        state.bracket = buildBracket(state.model);\n        state.notice = manual\n          ? \"No se pudo completar la actualizaci\u00f3n. Se conservan los datos m\u00e1s recientes guardados en este navegador.\"\n          : \"MLBAM no respondi\u00f3; se muestra la copia local m\u00e1s reciente guardada en este navegador.\";\n        state.noticeType = \"cache\";\n      } else {\n        root.innerHTML = errorMarkup(error);\n        state.loading = false;\n        wireUi();\n        return;\n      }\n    } finally {\n      state.loading = false;\n    }\n\n    render();\n  }\n\n  function scheduleAutoRefresh() {\n    if (state.timer) window.clearInterval(state.timer);\n    state.timer = window.setInterval(() => {\n      if (!document.hidden) loadData(false);\n    }, CONFIG.refreshMinutes * 60 * 1000);\n  }\n\n  let resizeTimer = null;\n  window.addEventListener(\"resize\", () => {\n    window.clearTimeout(resizeTimer);\n    resizeTimer = window.setTimeout(drawConnections, 120);\n  }, { passive: true });\n\n  document.addEventListener(\"visibilitychange\", () => {\n    if (!document.hidden && Date.now() - state.lastSuccessfulFetch > CONFIG.refreshMinutes * 60 * 1000) {\n      loadData(false);\n    }\n  });\n\n  loadData(false);\n  scheduleAutoRefresh();\n})();\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Consultando MLBAM\u2026 Obteniendo el standing de la Liga Mexicana de Beisbol y los resultados de postemporada.<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-18206","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/pages\/18206","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/comments?post=18206"}],"version-history":[{"count":7,"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/pages\/18206\/revisions"}],"predecessor-version":[{"id":18218,"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/pages\/18206\/revisions\/18218"}],"wp:attachment":[{"href":"https:\/\/torosdetijuana.com\/index.php\/wp-json\/wp\/v2\/media?parent=18206"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}