yazrkisvs3vvvsv33svrhetrjsvyvsvvyjsvsvs
tavsuysvssvvjvvyjyjkvsvqd
qfogsvvsvsegjdgdfgdskhgdksvqsvshdqsd
# TAREAS PENDIENTES — SuperCerebro

> Archivo vivo. Se actualiza en cada sesión de trabajo.
> Última actualización: 2026-05-21 (sesión 21 — Auditoría global: CSRF patch, Reportes JS completo, analysis_types CA, shareCAResult)
> Ver arquitectura completa: [memory/project_architecture.md](memory/project_architecture.md)
> Ver visión de negocio: [VISION_NEGOCIO.md](VISION_NEGOCIO.md)

---

## 🔴 BLOQUEANTE — Acción requerida por David

- [ ] **Recargar créditos Claude API**: ir a [console.anthropic.com](https://console.anthropic.com) → Plans & Billing → Add credits. Sin esto AVI no puede analizar ni generar texto.

---

## ✅ COMPLETADO — Sesión 12 (2026-05-19) — Google Service Account Migration

- [x] **`api/google-auth.php` — nuevo archivo centralizado**: `dm_google_access_token(scope)` + `_dm_sa_token()` JWT/RS256. SA primero, OAuth fallback. Cache de token en `/tmp/dm_goog_{hash}.token` (expire -60s margen).
- [x] **`api/google-data.php`**: reemplazado bloque OAuth (8 variables + curl) por `require google-auth.php` + `dm_google_access_token()`.
- [x] **`api/google-ads.php`**: mismo reemplazo.
- [x] **`api/google-ads-create.php`**: mismo reemplazo.
- [x] **`api/google-accounts.php`**: función local `google_get_access_token()` eliminada, usa `dm_google_access_token()`.
- [x] **`api/avi-agent.php`**: `ag_refresh_google_token()` ahora delega a `dm_google_access_token()`. Bloque GSC usa misma función con scope `webmasters.readonly`.
- [x] **`api/reports.php`**: bloque inline de OAuth reemplazado.
- [x] **`api/google-token-check.php`**: detecta modo SA vs OAuth, usa `dm_google_access_token()`, email de alerta actualizado.
- [x] **`config.php`**: agregada constante `GOOGLE_SERVICE_ACCOUNT_JSON` (vacía hasta que David suba el JSON).
- [x] **Deployed + linted en servidor**: 9 archivos, todos sin errores de sintaxis.

---

## ✅ COMPLETADO — Sesión 11A (2026-05-19) — Chat B

- [x] **Error handling Claude API — cobertura total**: `api/avi.php` y `api/avi-agent.php` detectan HTTP 401 (key inválida), 429 (rate limit), 529 (saturada) además de créditos agotados. Mensajes específicos en español para cada caso. Toasts diferenciados en `callAVI` y `callAVIAgent`.
- [x] **Verificación servidor**: crontab ya tenía los 5 crons activos. Migración 007 ya aplicada (`contact_email`, `report_auto_send` en `dm_clients`). Verificado vía MySQL SSH.
- [x] **Dashboard Semáforo — alertas proactivas**: panel `#ov-proactive-alerts` aparece si hay clientes en rojo, con detalle por canal. Badge `ov-alert-badge` en sidebar "Resumen". Timestamp `semaforo-ts` sobre la lista de clientes. Archivos: `sections/resumen.php`, `index.php`, `js/app.js`.
- [x] **Core Web Vitals — `api/pagespeed.php`**: nuevo — llama PageSpeed Insights API v5 (sin API key). LCP, FCP, TBT, CLS, SI, TTI + oportunidades. UI en `sections/seo.php`: score circle coloreado, grid de métricas, lista de oportunidades. URL auto-rellena con `seoAdvancedInit()`. JS: `runPageSpeed(strategy)`.
- [x] **Comparativa reportes — auto-carga**: `loadRptCompare()` ahora también se llama tras `generateReport()` con mapeo de período.

---

## ✅ COMPLETADO — Sesión 10A (2026-05-19) — Chat A

- [x] **Resumen — Panel "Campañas activas" por cliente**: card `ov-snapshot-card` en `sections/resumen.php` con grid 3 columnas (Meta | Google | SEO). `renderOvSnapshot()` — se actualiza progresivamente, limpia al cambiar cliente. Botón "↺ Actualizar".

---

## ✅ COMPLETADO — Sesiones 7-9 (2026-05-19)

### Sesión 9 — SEO Avanzado
- [x] **Core Web Vitals**: `api/pagespeed.php` + UI (ver Sesión 11A).
- [x] **Plan de contenido IA**: `runContentCalendar()` — plan 30 días desde GSC. `runContentIdeas()` — 15 ideas agrupadas. `seoAdvancedInit()` auto-rellena URL, CMS y keyword principal.
- [x] **Schema.org generator**: `runSchemaAdvanced()` + `seoQuickSchema(type)` — FAQ, LocalBusiness, Breadcrumb, Product, Review.
- [x] **Artículo SEO dedicado**: `api/seo-article.php` con keywords GSC reales como contexto. Panel con parámetros, copy y descarga .md.
- [x] **Auditoría técnica SEO**: shortcut `seo-audit` en AVI drawer → análisis con Claude.

### Sesión 8 — Dashboard Semáforo + Alertas
- [x] **Semáforo todos los clientes**: dots 🟢🟡🔴 por canal en lista de clientes. `loadDashboardSemaforo()` en carga inicial y al navegar a Resumen.
- [x] **`api/dashboard.php` `health_check`**: reglas Meta (gasto 3/7d), Google (IS%), SEO (clics vs período anterior). Histórico 14d.
- [x] **Alertas proactivas + badge sidebar**: ver Sesión 11A.

### Sesión 7 — Reportes v2
- [x] **Exportación PDF**: `exportReportPdf()` → `api/reports.php?action=export_pdf`. Botón "⬇ PDF".
- [x] **Envío email**: `toggleRptEmailRow()` + `sendReportEmail()`. Botón "📧 Enviar" en toolbar.
- [x] **Comparativa vs período anterior**: `loadRptCompare()` con ▲▼ deltas. Auto-cargada al generar o abrir reporte.
- [x] **AVI análisis en reporte**: `api/reports.php?action=generate` llama Claude + fallback PHP automático sin créditos.
- [x] **Migración 007 + crons**: verificados en servidor (ver Sesión 11A).

---

## ✅ COMPLETADO — Sesión 6 (2026-05-13)

- [x] **AVI Drawer flotante**: FAB ✦ + slide-in. Dual-mode: sin cliente → `callAVI` (general); con cliente → `callAVIAgent` (datos reales + acciones).
- [x] **Panel datos locales AVI**: Meta: dots ROAS + alerta frecuencia. Google: dots IS% + alerta presupuesto. SEO: dots posición. Sin cliente: mensaje centrado. Separador visual.
- [x] **Loading state + scroll**: shortcuts se deshabilitan durante llamada. `_aviFinish` reactiva y hace scroll a respuesta.
- [x] **AVI acciones expandidas**: 14 tipos — pause/activate campaña/adset/adgroup/anuncio, presupuesto, negativos, keywords positivas, cambio puja, reporte, email.
- [x] **Monitor OAuth Google**: `api/google-token-check.php` diario → email si token inválido. `dm_token_monitor` en DB.
- [x] **Renderizado markdown tables**: `renderMD()` detecta bloques `| col |` y genera tablas HTML con colores del tema.
- [x] **SEO — AVI panel con datos GSC**: `aviAgentSEOPanelUpdate()` + `#avi-seo-panel`. Auto-cargado al entrar a SEO.
- [x] **Meta wizard**: audiencias pre-guardadas. Facebook auto-carga. Google wizard: segmentación pre-guardada.
- [x] **Negativos IA**: términos ya en lista permanente se excluyen del resultado.

---

## ✅ COMPLETADO — Sesiones 1-5 (hasta 2026-05-11)

- [x] Split `index.php` (8,992 líneas → shell + 11 sections + js/app.js)
- [x] AVI Agente (`api/avi-agent.php` + modal confirmación + historial)
- [x] TikTok Ads completo (OAuth + `api/tiktok-data.php` + `sections/tiktok.php`)
- [x] Histórico métricas (`dm_metrics_history` + `api/metrics-snapshot.php` + cron `0 2 * * *`)
- [x] Reportes v1 (api/reports.php, historial, análisis Claude + fallback PHP)
- [x] Google Ads completo (campañas, keywords, negativos cron `0 3 * * *`, wizard, IS%)
- [x] Meta Ads completo (métricas, wizard campañas, imagen/video/post, audiencias)
- [x] Seguridad Fase 1 y Fase 2 (CSRF, auth, rate limiting)
- [x] Propuestas diseño web v3
- [x] Videos IA, Imágenes IA, Activos de Marca
- [x] Dark/Light mode, GSC, Shopify, multi-idioma base

---

## ✅ COMPLETADO — Sesión 13 (2026-05-19) — SEO Module Expansion + OAuth Fix

- [x] **`api/gsc-data.php` reescrito**: 6 queries GSC (summary, prev_period, keywords×50, pages×15, devices, countries). Nuevos campos: `deltas`, `devices`, `countries`, `quick_wins`, `low_ctr`, `no_clicks`. Cache key `gsc_v2_`.
- [x] **`api/pagespeed.php` expandido**: INP, TTFB, field data CrUX (`loadingExperience`), 8 oportunidades con desc, 7 diagnósticos, historial `dm_cwv_history` en DB.
- [x] **`api/sitemap.php` nuevo**: `action=list` (GSC sitemaps), `action=fetch` (parse sitemap.xml con follow redirect), `action=submit` (PUT a GSC detectando URL real).
- [x] **`sections/seo.php` reescrito**: 6 tabs (Rendimiento, Oportunidades, Sitemaps, CWV, Schema, Contenido). Cada tab con su panel de respuesta AVI independiente.
- [x] **`js/app.js` — funciones SEO nuevas**: `switchSEOTab`, `seoDeviceFilter`, `loadGSCData` reescrito, `renderGSCKeywords`, `renderGSCPages`, `renderGSCDevices`, `renderGSCCountries`, `renderGSCOpportunities`, `loadSitemapList`, `loadSitemapUrls`, `submitSitemap`, `renderCWVResult`, `loadCWVHistory`.
- [x] **`runPageSpeed` migrado** a usar `renderCWVResult()` — elimina rendering inline duplicado.
- [x] **`showPage('seo')` llama `loadGSCData`** — fix: antes no cargaba datos al navegar a SEO con cliente seleccionado.
- [x] **`switchSEOTab('sitemaps')` llama `loadSitemapList + loadSitemapUrls`** automáticamente.
- [x] **Botones AVI por tab**: cada tab tiene su `respId` — ya no sobrescriben el panel de Tab 1.
- [x] **`aviAgentSEOQuick(question, respId)`** acepta respId opcional.
- [x] **OAuth reconectado**: `redirect_uri` corregido a `https://dimark.co/supercerebro/api/oauth-callback.php`. App Google pasada a "En producción" — token nunca más expira en 7 días.
- [x] **`api/google-auth.php`**: SA restringido solo a scope `adwords`. GSC siempre usa OAuth.

---

## ✅ COMPLETADO — Sesión 14 (2026-05-19) — Auditoría completa de botones (3 chats en paralelo)

> **186 botones auditados** — todos tienen función JS. 0 funciones faltantes. Todos los fixes deployados.
> Deploy: `SSH_AUTH_SOCK="" rsync -avz -e "ssh -i ~/.ssh/dimark_supercerebro -p 22 -o IdentitiesOnly=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no" [archivo] dimark93@144.217.252.229:public_html/supercerebro/[destino]`

### CHAT 1 — SEO (40 botones) ✅ COMPLETADO
- [x] Tab 1 Rendimiento: GSC data fluye completo — métricas, deltas, keywords, países, dispositivos, chart
- [x] Tab 2 Oportunidades: `renderGSCOpportunities` puebla quick_wins, low_ctr, no_clicks
- [x] Tab 3 Sitemaps: carga automática al entrar, submit detecta URL real (sitemap_index.xml)
- [x] Tab 4 CWV: score → `renderCWVResult` → field data CrUX visible, oportunidades y diagnósticos
- [x] Tab 4 CWV: `runPageSpeed` envía `client_id` al servidor para guardar historial en DB
- [x] Tab 4 CWV: `loadCWVHistory` implementada — lee `api/cwv-history.php`, tabla con fecha/score/LCP/CLS/TBT
- [x] Tab 4 CWV: `api/cwv-history.php` — creado desde cero (endpoint faltante). Lee `dm_cwv_history`, auth + SHOW TABLES guard
- [x] Tab 5 Schema: 7 tipos quick schema — Fix: prompts `article` y `organization` faltaban → agregados a `seoQuickSchema()`
- [x] Tab 5 Schema: `runSchemaAdvanced` — presente y funcional
- [x] Tab 5 Schema: `runRobotsOptimizer` / `seo-robots` — Fix: `phMap` y `prompts` faltaban entrada → agregados a `seoTool()`/`runSEOTool()`
- [x] Tab 6 Contenido: `runSEOArticle`, `copySEOArticle`, `downloadSEOArticle`, `runContentCalendar`, `runContentIdeas` — todos presentes
- [x] AVI por tab: cada botón responde en su panel local (respId independiente por tab)
- [x] PHP lint servidor: `api/cwv-history.php` sin errores. `js/app.js` + `sections/seo.php` deployados y verificados

**Fixes aplicados por Chat 1:**
1. `api/cwv-history.php` — creado (era 404)
2. `seoQuickSchema()` — prompts `article` + `organization` agregados
3. `seoTool()` / `runSEOTool()` — `seo-robots` agregado en `phMap` + `prompts`

### CHAT 2 — Meta Ads (55 botones) — archivos asignados
- `sections/meta.php`
- `api/meta-data.php`, `api/meta-adsets.php`, `api/meta-ads.php`, `api/meta-create.php`
- `js/app.js` funciones Meta

**Tareas:**
- [x] Dashboard, Campañas, Conjuntos, Anuncios cargan datos reales — verificado, todo funcional
- [x] Wizard 4 pasos: brief AVI → audiencia → copy → preview → `executeWizard` — funcional completo
- [x] `quickCopy` 6 tipos: feed, stories, reels, carrusel, video_script, lineamientos_disenador — todos OK
- [x] Públicos: breakdowns plataforma/dispositivo/edad/género — `metaPubAVI` 5 shortcuts — OK
- [x] Competencia: `searchAdsLibrary`, `competenciaRapida` 4 tipos — funcional
- [x] AVI panel Meta: 5 shortcuts + `aviAgentMetaSend` en `avi-agent-resp-meta` — OK
- [x] `console.log` debug eliminado de `loadMetaData` — deployed app.js
- [x] **Auditoría completa 55 botones Meta**: todas las funciones verificadas en app.js — `runBriefAVI`, `selectFormat`, `switchAdMode`, `handleAdFileDrop/Select`, `clearAdImage`, `handleAdVideoSelect`, `clearAdVideo`, `loadPagePosts`, `toggleCheck`, `applyBriefPlan` y todas las demás confirmadas existentes. 0 funciones faltantes.

### CHAT 3 — Google Ads + Sitios Web (91 botones) — archivos asignados ✅ COMPLETADO
- `sections/google.php`, `sections/web.php`
- `api/google-data.php`, `api/google-ads.php`, `api/google-ads-create.php`, `api/google-accounts.php`
- `js/app.js` funciones Google + Web

**Sub-tarea Google (50 botones):**
- [x] Tabs principales cargan datos reales (Resumen, Campañas, Grupos, Anuncios, Keywords, Términos) — `switchGTab` dispara loader correcto por tab
- [x] Fix: `gtab-campaigns` faltaba clase `g-panel` — tab Campañas nunca aparecía
- [x] Negativos IA: `loadGAINegatives`, `applyAutoNegatives`, `applySelectedNegatives`, `toggleAutoMode` — todos presentes
- [x] Auction/Competencia: `loadGAuction` + `askAVI` — presentes
- [x] Segmentos: `switchGSeg` dispositivos/ubicaciones/edades — presente
- [x] Herramientas IA: `generateKeywords` 4 modos, `analyzeBudget`, `generateRSA` — presentes
- [x] Wizard nueva campaña: `openGCWizard()` **implementado desde cero** — modal 2 pasos (brief/tipo/objetivo → plan AVI → crear en Google Ads)

**Sub-tarea Web (41 botones):**
- [x] Fix: `wpanel-competencia` y `wpanel-shopify` estaban anidados dentro de `wpanel-propuestas` (faltaba `</div>`) — tabs invisibles, corregido
- [x] Fix: `switchWebTab` usaba `display='flex'` → cambiado a `'block'`
- [x] Propuestas: `showProposalForm`, `findCompetitors`, `saveAndGenerate`, `sendToClient`, `downloadProposalPDF`, `saveProposal`, `openFinalFull`, `downloadFinalHTML`, `copyLink` — todos presentes
- [x] Análisis Competencia: `runCompAnalysis`, `findCompetitorsCA`, `copyCAResult`, `shareCAResult` — presentes
- [x] Shopify: `loadShopifyData` — presente

**Verificación final:** 91 handlers auditados — 0 funciones faltantes. Desplegado en servidor.

---

## 🚀 PRÓXIMOS — Sesión 15 — Auditoría secciones faltantes (8 secciones, 3 chats)

> Chat 1: Resumen + Reportes
> Chat 2: TikTok + Imágenes IA + Videos IA
> Chat 3: Marcas + Perfil + Modales

### CHAT 1 — Resumen + Reportes ✅ COMPLETADO

**Resumen — `sections/resumen.php`:**
- [x] Panel semáforo: `loadDashboardSemaforo()` — dots 🟢🟡🔴, badge sidebar, alertas proactivas — funcional
- [x] Panel campañas activas (`ov-snapshot-card`): `renderOvSnapshot()`, botón ↺ Actualizar — funcional
- [x] AVI shortcuts: `askAVI('main')`, `generateMonthlyReport()`, `copyMonthlyReport()` — funcional
- [x] Fix: `generateMonthlyReport()` usaba `textContent` → `innerHTML = renderMD()` para renderizar markdown

**Reportes — `sections/reportes.php` + `api/reports.php` (funciones en `sections/modals.php`):**
- [x] `generateReport()` — `api/reports.php?action=generate`, Claude + fallback — presente
- [x] `exportReportPdf()`, `downloadReport()`, `printReport()`, `closeRptPreview()` — presentes
- [x] `toggleRptEmailRow()` + `sendReportEmail()` — presentes
- [x] `loadRptCompare()` — auto-cargada tras generar y al abrir historial — presente
- [x] `loadReportHistory()`, `loadReportById()`, `_rptSetBtn()` — presentes
- [x] `loadRptServices()`, `toggleRptService()`, `rptPeriodChange()` — presentes
- [x] `api/reports.php?action=services` + `api/client-report-settings.php` — verificados
- [x] Deployed `js/app.js` (fix renderMD)

---

### CHAT 2 — TikTok + Imágenes IA + Videos IA

**TikTok Ads — `sections/tiktok.php` + `api/tiktok-data.php`:**
- [x] Dashboard TikTok: métricas principales — `loadTikTokData`, `onTTperiodChange`, `switchTTTab` — definidas en inline script de tiktok.php
- [x] Campañas y Ad Groups: `loadTTAdgroups` presente — filtra por campaña con select
- [x] AVI TikTok: `ttAVIquick` (5 shortcuts) + `askTTAVI` (enviar) — presentes

**Imágenes IA — `sections/imagenes.php`:**
- [x] Tabs + modos: `switchImgTab`, `switchImgModo` — presentes en app.js
- [x] Generación prompt: `generateImgPrompt`, `generateImgToolPrompt` (6 herramientas), `copyImgPrompt` — presentes
- [x] Potenciar foto: `onImgPotChange`, `analyzeOwnPhoto`, `generateImgPotToolPrompt` — presentes
- [x] Replicar estilo: `onImgRefChange`, `analyzeImgReference`, `generateImgRefToolPrompt` — presentes
- [x] Ideas: `generateImgIdeas` (general/ads/viral/marca), `usarConceptoImg` — presentes
- [x] AVI herramientas y estrategia: `askAVI('img-tool')`, `askAVI('img-strat')` — presente

**Videos IA — `sections/videos.php`:**
- [x] Scripts: `generateVidScript`, `quickVidScript` (4 tipos), `copyVidScript` — presentes
- [x] Modo tomas: `generateTomasScript`, `quickTomasPrompt`, `copyTomasScript`, `generateTomasToolPrompt` — presentes
- [x] Modo referencia: `analyzeReference` (3 modos), `copyRefScript`, `generateRefToolPrompt`, `copyRefPrompt` — presentes
- [x] Hooks: `generateHooks` (4 tipos), `copyHooksResult`, `usarFormula` — presentes
- [x] Herramientas IA: `generateToolPrompt`, `generateToolPromptFromHerr`, `copyToolPrompt`, `copyHerrPrompt` — presentes
- [x] AVI: `askAVI('vid-tool')`, `askAVI('vid-strat')` — presente

**Auditoría Chat 2 Sesión 15: 0 funciones faltantes en las 3 secciones.**

---

### CHAT 3 — Marcas + Perfil + Modales ✅ COMPLETADO

**Activos de Marca — `sections/marcas.php`:**
- [x] `deleteBrandAsset`, `deleteBrandManual`, `extractLogoColors`, `extractBrandManual`, `saveBrandMeta` — todas presentes en `app.js` (estaban bajo `async function`, el check inicial fallaba por eso)
- [x] Helpers: `uploadBrandAsset`, `renderColorSwatches`, `getBrandContext`, `showBrandLogo`, `resetBrandLogo`, `showBrandStyle`, `resetBrandStyle`, `resetBrandManual` — todos presentes

**Perfil — `sections/profile.php`:**
- [x] Form nativo PHP (`method="POST" action="api/update-profile.php"`) — no requiere JS. Nombre + cambio contraseña funcional por diseño.

**Modales compartidos — `sections/modals.php`:**
- [x] `gcwNext`, `gcwPrev`, `gcwAddKw`, `gcwDelKw`, `gcwGenerateAI`, `gcwRemoveLoc`, `gcwSaveSeg` — definidas como inline `<script>` en `modals.php`
- [x] `aviHistoryToggle`, `aviHistoryLoad`, `aviHistoryClear` — inline en `modals.php`
- [x] `loadReportById`, `saveBudget`, `closeBudgetModal`, `closeEditor`, `previewEdited`, `saveEditedProposal` — inline en `modals.php`
- [x] Todas las funciones de Reportes (`generateReport`, `exportReportPdf`, `sendReportEmail`, `toggleRptEmailRow`, `loadReportHistory`, `closeRptPreview`, `downloadReport`, `printReport`, `rptAviAnalyze`, `saveRptClientSettings`, `toggleRptService`) — inline en `modals.php`

**Nota arquitectónica:** Las funciones de Reportes y Modales viven en `sections/modals.php` como `<script>` inline, no en `app.js`. Esto es intencional.

**Verificación final Chat 3:** 0 funciones faltantes en las 3 secciones.

---

## ✅ COMPLETADO — Sesión 16 (2026-05-20) — 3 features Q4 2026 (3 chats en paralelo)

> **Regla de archivos**: `js/app.js` → solo Chat 1 | `index.php` → solo Chat 2 | `sections/onboarding.php` + `api/onboarding-check.php` → solo Chat 3
> Deploy: `SSH_AUTH_SOCK="" rsync -avz -e "ssh -i ~/.ssh/dimark_supercerebro -p 22 -o IdentitiesOnly=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no" [archivo] dimark93@144.217.252.229:public_html/supercerebro/[destino]`

### CHAT 1 — Alertas Automáticas (caída >20% semanal) ✅ COMPLETADO
Archivos: `api/alerts-cron.php` (NUEVO), `api/dashboard.php`, `js/app.js`

- [x] `api/alerts-cron.php` — creado: cron lunes 9am, compara semana actual vs anterior (Meta gasto, Google gasto, SEO clics), caída ≥20% → email + `dm_alerts` DB. Anti-duplicado por semana.
- [x] `api/dashboard.php` — action `weekly_deltas` agregada: devuelve clientes con caída ≥threshold por canal. `health_check` enriquecido con `delta_pct` en Meta, Google y SEO.
- [x] `js/app.js` — `loadDashboardSemaforo()` actualizado: alertas muestran `delta_pct` como badge (rojo ≤-30%, amarillo ≤-20%). Llama `weekly_deltas` para alertas de canales amarillos también.
- [x] Migración DB: `dm_alerts` con `CREATE TABLE IF NOT EXISTS` en alerts-cron.php (auto-crea en primera ejecución)
- [x] Crontab servidor: `0 9 * * 1 /usr/local/bin/php alerts-cron.php >> logs/alerts-cron.log`
- [x] PHP lint: dashboard.php y alerts-cron.php — sin errores
- [x] Deploy: api/dashboard.php, api/alerts-cron.php, js/app.js

### CHAT 2 — Mobile Responsive ✅ COMPLETADO
Archivos: `index.php`

- [x] `index.php` — hamburger `.mob-menu-btn` + `#mob-overlay` + sidebar drawer (position:fixed, translateX) — funcional
- [x] `index.php` — `@media(max-width:768px)`: nav-c oculto, sidebar drawer, `.card{overflow-x:auto}`, `.tbl{min-width:480px}`, `.ph` columna, `#rpt-iframe{height:420px}`, `.live` oculto
- [x] `index.php` — `@media(max-width:480px)`: metrics 1 columna, fmt-grid 1 columna
- [x] `index.php` — `include sections/onboarding.php` con `file_exists` guard al final de `<main>`
- [x] `sections/seo.php` — tablas usan `.srow` flex (ya responsive). Sin cambios necesarios.
- [x] `sections/google.php` — todos los contenedores ya tienen `overflow-x:auto` inline. Sin cambios.
- [x] `sections/meta.php` — tablas ya tienen wrappers `overflow-x:auto` + `min-width` inline. Sin cambios.
- [x] `sections/reportes.php` — toolbar ya tiene `flex-wrap:wrap`. iframe fijado via CSS en index.php.
- [x] Deploy `index.php` → servidor. PHP lint OK. MD5: c2aa1942ff287c5f3f70587e6ebbce14

### CHAT 3 — Onboarding Wizard (nuevo cliente en 4 pasos) ✅ COMPLETADO
Archivos: `sections/onboarding.php` (NUEVO), `api/onboarding-check.php` (NUEVO)

- [x] `api/onboarding-check.php` — recibe `client_id`, devuelve `{meta, google, gsc, tiktok: bool}` + `details`. Auth + client access verificados. Reutiliza lógica de `reports.php?action=services`.
- [x] `sections/onboarding.php` — modal wizard 4 pasos con JS inline (IIFE, sin tocar app.js):
  - Paso 1: Cards por servicio con estado ✓/✗ desde `onboarding-check.php`. Toggle de selección.
  - Paso 2: Meta Ads — banner estado + instrucciones + botón "↺ Verificar conexión"
  - Paso 3: Google Ads + GSC — banners independientes + verificar ambos a la vez
  - Paso 4: Resumen final cards + botón "Ir al dashboard" → `showPage('resumen')`
  - Step indicators animados (dots + líneas). Cierre con clic en backdrop.
- [x] `window.obOpen(clientId)` — función pública para disparar el wizard desde cualquier botón
- [x] Deploy: ambos archivos en servidor
- [x] **Chat 2**: `include onboarding.php` agregado en `index.php` (con `file_exists` guard). Botón "⚙ Configurar cliente" agregado en banner de `sections/resumen.php` → llama `window.obOpen(currentClient)`. Deployados.

---

## ✅ COMPLETADO — Sesión 17 (2026-05-20) — 3 features (3 chats en paralelo)

> **Regla de archivos**: `js/app.js` → solo Chat 1 | `public-report.php` + `api/public-report-token.php` → solo Chat 2 | `sections/google.php` + `api/google-data.php` → solo Chat 3
> Deploy: `SSH_AUTH_SOCK="" rsync -avz -e "ssh -i ~/.ssh/dimark_supercerebro -p 22 -o IdentitiesOnly=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no" [archivo] dimark93@144.217.252.229:public_html/supercerebro/[destino]`

### CHAT 1 — Dashboard Semáforo v2 (filtros + CSV export) ✅ COMPLETADO
Archivos: `sections/resumen.php`, `js/app.js`

- [x] `sections/resumen.php` — barra de filtros sobre lista de clientes: botones Todos | Meta | Google | SEO + botón "⬇ CSV" + timestamp alineado a la derecha
- [x] `js/app.js` — `_semaforoData = d.clients` almacena datos globalmente tras cargar semáforo
- [x] `js/app.js` — `filterSemaforo(canal)`: filtra `.crow` por canal con estado rojo/amarillo. Botón activo resaltado.
- [x] `js/app.js` — `exportSemaforoCSV()`: CSV con columnas Cliente, Canal, Estado, Detalle, Delta%, Fecha. Descarga en browser.
- [x] PHP lint servidor resumen.php: sin errores. Deploy OK.

### CHAT 2 — API pública de reportes (URL compartible para clientes) ✅ COMPLETADO
Archivos: `public-report.php` (NUEVO), `api/public-report-token.php` (NUEVO), `api/reports.php`, `sections/reportes.php`, `sections/modals.php`

- [x] `api/public-report-token.php` — token 64-char hex, `dm_report_tokens` auto-crea con CREATE IF NOT EXISTS. Reutiliza token no expirado. action=revoke elimina por report_id+client_id.
- [x] `public-report.php` — sin login: valida token + expiración JOIN dm_reports+dm_clients. Renderiza HTML branded DIMARK. Página 410 si token expirado.
- [x] `api/reports.php` — action=share: crea tabla + token inline, devuelve URL completa `public-report.php?token=xxx`.
- [x] `sections/reportes.php` — botón "🔗 Compartir" en toolbar de preview junto a PDF/HTML/Imprimir.
- [x] `sections/modals.php` — `shareReport()`: POST a action=share, copia URL al clipboard, feedback visual "✓ Copiado" 2.5s.
- [x] DB: `dm_report_tokens` auto-crea en primera ejecución.
- [x] PHP lint 5 archivos en servidor — todos sin errores. Deployados.

### CHAT 3 — Keywords Quality Score (Google Ads) ✅ COMPLETADO
Archivos: `sections/google.php`, `js/app.js`

- [x] `api/google-ads.php` — QS ya estaba completo: `qs`, `qs_ad`, `qs_landing`, `qs_ctr` en query y response (campos `qualityInfo.*` de Google Ads API). `api/google-data.php` no era el archivo correcto.
- [x] `app.js` `renderGKeywords` — columna QS ya renderizada con semáforo (`gQS()`: 🔴1-3 🟡4-7 🟢8-10) + indicadores ▲●▼ por componente + fila coloreada si QS≤4. Filtro por QS (bajo/bueno/excelente/sin conv) ya operativo.
- [x] `sections/google.php` — panel `#g-qs-recs` agregado: muestra keywords con QS≤5 agrupadas por problema (copy anuncio / landing page / CTR esperado) con badge de conteo y acciones sugeridas
- [x] `app.js` `renderQSRecsPanel()` — genera grupos automáticamente desde `gKwData`. Se llama al final de `renderGKeywords()`. Oculta fallback AVI si hay keywords bajas.
- [x] `app.js` `analyzeQSWithAVI()` — construye prompt con las ≤30 keywords de QS más bajo (keyword, QS, componentes, métricas) y llama `callAVI` → responde en `#avi-g-keywords`
- [x] Deploy: `sections/google.php` + `js/app.js` — deployados en servidor

---

## ✅ COMPLETADO — Sesión 18 (2026-05-20) — 3 features (3 chats en paralelo)

> **Regla de archivos**: `js/app.js` + `api/i18n.php` → solo Chat 1 | `sections/email.php` + `api/email-data.php` + `index.php` → solo Chat 2 | `sections/tiktok.php` + `api/tiktok-data.php` → solo Chat 3
> Deploy: `SSH_AUTH_SOCK="" rsync -avz -e "ssh -i ~/.ssh/dimark_supercerebro -p 22 -o IdentitiesOnly=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no" [archivo] dimark93@144.217.252.229:public_html/supercerebro/[destino]`

### CHAT 1 — Multi-idioma (i18n completo) ✅ COMPLETADO
Archivos: `api/i18n.php` (NUEVO), `js/app.js`, `index.php`, `sections/resumen.php`

- [x] `api/i18n.php` — 5 idiomas (ES/EN/PT/FR/DE), 46 keys: nav, métricas, dashboard, AVI, botones, estados
- [x] `js/app.js` — `_t(key)`, `gtLang(lang)`, `applyTranslations()`, `initI18n()` — carga asíncrona desde `api/i18n.php`
- [x] `js/app.js` — `showPage()` refactorizado: usa `data-page` en vez de text matching (fix esencial para i18n)
- [x] `index.php` — `data-page` + `data-i18n` en botones `.nt` (nav top) y `.si` (sidebar) + "En línea"
- [x] `sections/resumen.php` — `data-i18n` en labels métricas, títulos cards, AVI name/placeholder/send, botones reporte
- [x] PHP lint servidor: sin errores. Deploy OK.

### CHAT 2 — Email Marketing (nueva sección) ✅ COMPLETADO
Archivos: `sections/email.php` (NUEVO), `api/email-data.php` (NUEVO), `index.php`, `sections/apis.php`, `api/save-credentials.php`, `api/get-credentials.php`, `js/app.js`

- [x] `api/email-data.php` — Mailchimp API v3: `action=campaigns` (30 campañas enviadas, open_rate, click_rate, cache 30min) + `action=audience` (listas con member_count, unsub_rate, bounce). Server extraído del sufijo del API key.
- [x] `sections/email.php` — 5 tabs: Dashboard, Campañas, Audiencias, Copys IA, Estrategia AVI. Estado vacío sin cliente y sin creds. Tablas con semáforo de color por benchmark (apertura ≥20%, clics ≥2%, baja <0.5%). Cards de audiencia con diagnóstico saludable/problema.
- [x] Tab Copys IA: 6 shortcuts AVI (asuntos, bienvenida, nurturing, promo, re-engagement, preheaders) + input libre.
- [x] Tab Estrategia AVI: 6 shortcuts (estrategia completa, calendario mensual, segmentación, automatizaciones, crecimiento lista, diagnóstico+benchmarks) + input libre.
- [x] `index.php` — botón nav "Email" + item sidebar "📧 Email Marketing" + `include sections/email.php`
- [x] `sections/apis.php` — card Mailchimp con nota "por cliente", botón → `saveMailchimpCreds()`
- [x] `api/save-credentials.php` — `mailchimp` agregado a `$allowed`
- [x] `api/get-credentials.php` — `mailchimp` agregado a `$services`
- [x] `js/app.js` — `saveMailchimpCreds()`: lee `currentClient` + `mailchimp-key`, POST a `save-credentials.php`
- [x] Deploy: 7 archivos deployados en servidor. Lint no disponible localmente (PHP no instalado en Mac).

### CHAT 3 — TikTok Ads (acciones write + métricas) + Logs + Tests ✅ COMPLETADO
Archivos: `sections/tiktok.php`, `api/tiktok-data.php`, `api/logger.php` (NUEVO), `tests/` (NUEVO)

**TikTok write + métricas:**
- [x] `api/tiktok-data.php` — actions `pause_campaign` / `activate_campaign` (TikTok API `campaign/status/update/`) + `change_tiktok_budget` + `pause_adgroup` / `activate_adgroup` — ya estaban implementados
- [x] `sections/tiktok.php` — tabla campañas: columna Presupuesto con botón ✎ para editar + columna Acciones con botones Pausar/Activar por fila. `ttPauseCampaign()`, `ttActivateCampaign()`, `ttChangeBudget()` implementados
- [x] `sections/tiktok.php` — Tab Resumen: 8 métricas (Inversión, Impresiones, Clics, CTR, Alcance, Conversiones, CPA, CPM) — ya estaban todas visibles
- [x] `sections/tiktok.php` — Tab AVI: shortcut "✦ Pausar campañas ineficientes" + `ttAVIPauseIneficientes()` que construye contexto desde `ttCampaigns[]` y llama AVI

**Logs estructurados:**
- [x] `api/logger.php` — clase `DmLog::info/warn/error(context, message, data[])`. JSON por línea en `logs/app-YYYY-MM-DD.log`. Auto-rotación: elimina archivos >30 días (static flag para no rotar más de 1 vez por request)
- [x] `api/avi-agent.php` — `DmLog::error/warn` en todos los errores Claude API (curl, 401, 429, 529, sin créditos, catch-all)
- [x] `api/reports.php` — `DmLog::error` en error HTTP Claude + `DmLog::warn` si JSON parse falla
- [x] `api/tiktok-data.php` — `DmLog::error` en fallo de `campaign/status/update/`

**Test suite básico:**
- [x] `tests/test-auth.php` — verifica que 5 endpoints sin sesión devuelven 401/302/403 o `ok:false`
- [x] `tests/test-reports.php` — verifica `action=list` devuelve `{ok:true, reports:[...]}`. Requiere `TEST_SESSION_COOKIE`, hace skip si no está configurado
- [x] `tests/test-meta.php` — verifica campos obligatorios (`spend`, `impressions`, `clicks`, `ctr`) en response de `meta-data.php`. Acepta `ok:false` con `error` si Meta no conectado
- [x] `tests/run.php` — runner CLI: auto-descubre `test-*.php`, imprime PASS/FAIL/SKIP con colores ANSI. Exit code 1 si algún test falla
- [x] Deploy: todos los archivos deployados en servidor (sections/tiktok.php, api/logger.php, api/avi-agent.php, api/reports.php, api/tiktok-data.php, tests/)

---

## 🚀 EN PROGRESO — Sesión 19 (2026-05-20) — Meta + Google + SEO al 100% (3 chats)

> **Regla de archivos**: `sections/meta.php` + `api/meta-data.php` → solo Chat 1 | `sections/google.php` + `api/google-ads.php` → solo Chat 2 | `sections/seo.php` + `api/seo-article.php` + `api/wp-publish.php` → solo Chat 3
> `js/app.js` → cada chat edita solo sus funciones, sin solapar
> Deploy: `SSH_AUTH_SOCK="" rsync -avz -e "ssh -i ~/.ssh/dimark_supercerebro -p 22 -o IdentitiesOnly=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no" [archivo] dimark93@144.217.252.229:public_html/supercerebro/[destino]`

### CHAT 1 — Meta Ads → 100% ✅ COMPLETADO
Archivos: `sections/meta.php`, `api/meta-data.php`, `js/app.js` (funciones Meta)

- [x] **Tab Creatividades**: `loadMetaCreativeRanking()` — score = ROAS×2 + CTR - freq×0.5. Top 3 (✦ TOP verde) + Bottom 3 (↓ LOW amarillo). Badge frecuencia. Botón "✦ Analizar creatividades con AVI"
- [x] **Alerta frecuencia visual**: badge pill coloreado en columna Frecuencia de Ad Sets — rojo (>3 / fatiga), amarillo (2-3 / vigilar), verde (<2 / OK). Tooltip con mensaje
- [x] **Breakdown por placement**: `loadMetaPlacementDashboard()` — barras Feed / Stories / Reels / Audience Network con % gasto e impresiones. En Tab Campañas
- [x] **Selector attribution window**: dropdown 1d_click / 7d_click / 7d_click_1d_view / 28d_click en header Meta. `onMetaAttributionChange()` persiste en localStorage y recarga datos. `api/meta-data.php` pasa `action_attribution_windows` configurable
- [x] **Budget pacing widget**: `renderMetaPacing()` — diario total × días mes vs gasto real. Barra de progreso + proyección fin de mes. Semáforo verde/amarillo/rojo
- [x] Deploy `api/meta-data.php` + `sections/meta.php` + `js/app.js` — PHP lint OK en servidor

### CHAT 2 — Google Ads → 100% ✅ COMPLETADO
Archivos: `sections/google.php`, `api/google-ads.php`, `js/app.js` (funciones Google)

- [x] **Budget pacing widget**: `renderGPacingWidget(camps)` — card `#g-pacing-card` en Tab Resumen. Barra progreso: gasto vs presupuesto mensual (daily_budget × días mes). Proyección fin de mes. Semáforo verde/amarillo/rojo. 4 métricas: proyección, restante, días left, campañas activas.
- [x] **RSA ad_strength badge en Tab Anuncios**: campo `ad_strength` ya estaba en query SELECT de `api/google-ads.php`. Ahora renderizado en `loadGAds()` — badge coloreado por rating (EXCELLENT=verde, GOOD=verde, POOR=rojo, PENDING=gris). Con `data-tip` descriptivo.
- [x] **Filtro tipo campaña en Tab Campañas**: botones Todas/Search/PMax/Display/Video en header. `filterGCampType(type, btn)` — filtra filas `#gcamp-row-{id}` por `camp.type`. `gCampTypeFilter` persiste entre cargas. Nota PMax visible al activar filtro PMax.
- [x] **Estado conversion tracking badge**: `loadGConversionsCheck()` — fetch `action=conversions_check` nuevo en `api/google-ads.php`. Badge `#g-conv-badge` verde "✓ N conv. activas" / rojo "⚠ Sin conversiones activas". Cache 60min server-side.
- [x] **Geographic overview en Tab Resumen**: `loadGGeoTop()` — fetch `action=geo_top` nuevo en `api/google-ads.php`. Card `#g-geo-card` con tabla top 5 ubicaciones por conversiones: mini barra, clics, conv., ROAS coloreado. Cache 60min.
- [x] Deploy: `sections/google.php` + `api/google-ads.php` + `js/app.js` deployados en servidor.

### CHAT 3 — SEO → 100% ✅ COMPLETADO
Archivos: `sections/seo.php`, `api/gsc-data.php`, `api/wp-publish.php` (NUEVO), `sections/apis.php`, `js/app.js`

- [x] **Rank tracking chart**: Tab Rendimiento — card "📈 Historial posición promedio". Chart.js línea, eje Y invertido. Botones 30d/60d/90d. Lee `dm_metrics_history` service=gsc via `api/gsc-data.php?action=position_history`. `loadRankHistory(period)`. Auto-carga al entrar a Rendimiento.
- [x] **Tab Cobertura GSC**: nuevo tab "📋 Cobertura" entre Oportunidades y Sitemaps. Cards Enviadas/Indexadas/No indexadas/Errores. Barra progreso % con semáforo. Tabla detalle por sitemap. AVI shortcuts. `api/gsc-data.php?action=cobertura` llama GSC Sitemaps API. `loadGSCCobertura(force)`. Auto-carga al entrar.
- [x] **Keyword gap vs competencia**: Tab Oportunidades — card con input dominio competidor + "✦ Analizar gap". `runKeywordGap()` construye prompt con top-30 keywords GSC y llama AVI.
- [x] **Publicar en WordPress**: Tab Contenido — botón "📤 Publicar en WordPress" tras generar artículo. `publishToWordPress()` usa `_lastSEOArticleRaw`, POST a `api/wp-publish.php`, confirm con link editor WP.
- [x] `api/wp-publish.php` (NUEVO): action=post → `/wp-json/wp/v2/posts` draft. Auth Basic + WP Application Password de `dm_credentials`. Devuelve `{ok, edit_url, post_id}`.
- [x] `sections/apis.php` — card WordPress por cliente: URL + usuario + App Password → `saveWPCreds()`.
- [x] Deploy: 5 archivos en servidor. SEO al 100%.

---

## ✅ COMPLETADO — Sesión 20 (2026-05-21) — Sitios Web al 100% (3 chats)

> **Última actualización**: 2026-05-21 Chat 1 completado
> Deploy: `SSH_AUTH_SOCK="" rsync -avz -e "ssh -i ~/.ssh/dimark_supercerebro -p 22 -o IdentitiesOnly=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no" [archivo] dimark93@144.217.252.229:public_html/supercerebro/[destino]/`

### CHAT 1 — WordPress real + infraestructura ✅ COMPLETADO
Archivos: `sections/web.php` (Tab General + Tab WordPress), `api/wp-data.php`, `api/uptime-check.php` (NUEVO), `api/ssl-check.php` (NUEVO), `js/app.js`

- [x] **Tab WordPress** nuevo tab — overview (nombre, versión WP, tema activo, posts/páginas totales), últimos 5 posts con estado, plugins con badge de actualizaciones pendientes. AVI con 3 shortcuts.
- [x] `api/wp-data.php` reescrito: usa `dm_get_credentials` con cifrado correcto. `action=overview` (tema activo via `/themes?status=active`, totales posts/páginas), `action=posts` (5 últimos), `action=plugins` (WP 5.5+, detecta updates). Cache 30-60min.
- [x] **Uptime real** `api/uptime-check.php` (NUEVO): curl HEAD al dominio del cliente (desde credenciales WP), mide latencia ms, guarda en `dm_uptime_log` (auto-crea tabla, purga >30d), calcula % uptime últimas 30 entradas. `action=history` para historial.
- [x] **SSL expiry** `api/ssl-check.php` (NUEVO): `stream_socket_client` + `capture_peer_cert`, parsea `validTo_time_t`, badge verde (>30d) / amarillo (8-30d) / rojo (≤7d). Cache 6h.
- [x] **PageSpeed inline** Tab General: carga URL desde `wp-data overview`, llama `api/pagespeed.php` en paralelo mobile+desktop, muestra score circles. Input manual si no hay WP configurado.
- [x] `sections/web.php` Tab General reemplazado: 3 cards dinámicos (Uptime, SSL, PageSpeed) + AVI con 3 shortcuts + input libre.
- [x] `js/app.js`: `loadWebOverview`, `loadWebUptime`, `loadWebSSL`, `loadWebPageSpeed`, `runWebPageSpeed`, `loadWPData`, `loadWPPlugins`, `webAVIquick`. `switchWebTab` actualizado para 'wordpress' y 'general'. `showPage('web')` y `selectClient` disparan `loadWebOverview`.
- [x] Deploy + lint: 4 archivos PHP sin errores en servidor. js/app.js deployado.

### CHAT 2 — Shopify completo + montar propuesta en WP ✅ COMPLETADO
Archivos: `sections/web.php` (Tab Shopify + Tab Propuestas), `api/shopify-data.php`, `api/wp-publish.php` (acción `page`)

- [x] **Shopify completo**: `api/shopify-data.php` — `top_products` (agrega line_items de órdenes 30d, top 5 por revenue), `abandoned_checkouts` (de `/checkouts.json`), `fulfillment_rate` (fulfilled/total_orders×100). Tab Shopify — semáforo tasa fulfillment (<60% rojo, 60-80% amarillo, >80% verde), cards "Top 5 productos" + "Carritos abandonados" con revenue en riesgo
- [x] **Montar propuesta aprobada en WP**: `sections/web.php` `#final-version-panel` — botón "🌐 Montar en WP". `mountProposalInWP(btn)` en `js/app.js` — fetch `proposals.php?action=get`, POST a `api/wp-publish.php {action:'page', client_id, title, content:proposal_final}`, abre editor WP en nueva pestaña si ok. CSRF token incluido.
- [x] `api/wp-publish.php` — `action=page` ya estaba implementado en Sesión 19 (linter lo agregó). `action=test` también presente.
- [x] Deploy: `api/shopify-data.php`, `sections/web.php`, `js/app.js` — 3 archivos deployados
- [x] **Fix reset por cliente**: función `_shReset()` en `js/app.js` — al cambiar de cliente, limpia semáforo, top products, carritos abandonados, métricas y badges antes de cargar los datos del nuevo cliente. Sin residuos de cliente anterior. Deploy `js/app.js`.

### CHAT 3 — Tests de integración + actualización VISION ✅ COMPLETADO
Archivos: `tests/`, `VISION_NEGOCIO.md`

- [x] `tests/test-wp.php` — auth block + response shape para `wp-data.php`, `uptime-check.php`, `ssl-check.php`. Usa `dm_skip` si endpoint aún no deployado (404)
- [x] `tests/test-meta-placement.php` — verifica `action=breakdown&breakdown=publisher_platform` — auth block + campos `label/spend/impressions` en breakdown
- [x] `tests/run.php` — actualizado: muestra conteo de archivos, URL base, session status, colores ANSI en resultado final (verde OK / rojo FAIL)
- [x] `VISION_NEGOCIO.md` — tabla módulos actualizada: Meta/Google/SEO/Sitios Web → 100%. Arquitectura v4, deuda técnica limpiada. Q4 completados detallados.
- [x] Deploy: `tests/` → servidor. 3 archivos deployados.

---

## ✅ COMPLETADO — Sesión 21 (2026-05-21) — Auditoría global: 0 errores JS

> Un solo chat — auditoría completa de funciones faltantes en toda la plataforma
> Deploy: `SSH_AUTH_SOCK="" rsync -avz -e "ssh -i ~/.ssh/dimark_supercerebro -p 22 -o IdentitiesOnly=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no" [archivo] dimark93@144.217.252.229:public_html/supercerebro/[destino]`

**Hallazgo clave:** TikTok, Email y Onboarding ya estaban 100% implementados en sus archivos PHP de sección (`<script>` inline). Las funciones del Wizard de Google también existían en app.js. La única brecha real era Reportes.

### CSRF Global — `js/app.js`
- [x] Patch global `window.fetch` al inicio del archivo — auto-agrega `X-CSRF-Token` a TODOS los POST. Cubre 90+ llamadas sin tener que editar individualmente cada fetch.
- [x] `fetchJSON(url, data)` simplificado — CSRF ya manejado por el patch global.

### Reportes — `js/app.js` (funciones añadidas)
- [x] `loadRptServices()` — detecta Meta/Google/SEO conectados via `api/reports.php?action=services`, activa/desactiva tarjetas con cursor y opacidad
- [x] `_rptRenderChk(svc)` — renderiza checkbox visual (checkmark + color rojo cuando seleccionado)
- [x] `toggleRptService(svc)` — toggle de selección solo si el servicio está conectado
- [x] `rptPeriodChange()` — muestra/oculta rango personalizado
- [x] `generateReport()` — POST a `api/reports.php` con periodo + servicios seleccionados, renderiza HTML en iframe con Blob URL, auto-lanza AVI análisis
- [x] `closeRptPreview()` — cierra la vista previa
- [x] `downloadReport()` — descarga el HTML como archivo con nombre basado en cliente + fecha
- [x] `printReport()` — llama `contentWindow.print()` sobre el iframe
- [x] `shareReport()` — POST action=share, copia URL pública al portapapeles
- [x] `exportReportPdf()` — POST action=export_pdf, descarga blob PDF (requiere wkhtmltopdf en servidor)
- [x] `toggleRptEmailRow()` — show/hide fila de email con focus automático
- [x] `sendReportEmail()` — POST action=send_email con email manual o del cliente
- [x] `rptAviAnalyze()` — llama AVI con contexto del período y servicios para análisis ejecutivo
- [x] `loadReportHistory()` — GET action=list, renderiza tabla con botones "Ver" por reporte
- [x] `loadReportById(id)` — GET action=get, carga HTML histórico en iframe + muestra AVI
- [x] `loadRptClientSettings(clientId)` — GET action=get de `client-report-settings.php`, pre-llena email + auto-envío
- [x] `saveRptClientSettings()` — POST a `client-report-settings.php` con email y checkbox auto-send

### Análisis Competencia — `api/competitive_analysis.php`
- [x] Lee `diferencial`, `valor`, `publico`, `ubicacion`, `analysis_types` del input JSON
- [x] Construye `$ctx_extra` con el contexto del negocio e incluye en el prompt
- [x] `$types_block` — si hay `analysis_types`, agrega sección "TIPOS DE ANÁLISIS SOLICITADOS" al prompt para que Claude cubra específicamente DOFA, Benchmarking, SEO, UX/UI, etc.

### Compartir CA — `js/app.js`
- [x] `shareCAResult()` — ya no muestra "próximamente". Copia el texto del análisis al portapapeles con `navigator.clipboard.writeText()`.

### Deploy
- [x] `js/app.js` — deployado (503KB, 8884 líneas)
- [x] `api/competitive_analysis.php` — deployado

---

## 🔵 BACKLOG — Features Futuras

- [ ] Multi-idioma v2: traducir secciones de AVI drawer y modales
- [ ] Email Marketing v2: envío de campaña draft desde UI (Mailchimp)
- [ ] TikTok Ads v2: integración TikTok Creative Center para trends de contenido
