{
  "name": "Checklist de apertura y cierre: avisos y resumen semanal",
  "nodes": [
    {
      "parameters": {
        "content": "> Plantilla de partida de fernandorivera.es. Úsala y adáptala libremente; sin garantía; revisa los datos que tratas.\n\n## Checklist de apertura y cierre\nTres partes en un mismo flujo:\n1. **Aviso inmediato**: cuando entra una respuesta con alguna tarea en «No» o con incidencia, manda un Telegram al encargado.\n2. **Aviso de que falta**: a la hora límite de apertura y de cierre comprueba qué tiendas no han enviado la checklist del día.\n3. **Resumen del lunes**: junta las incidencias de la semana y una IA las agrupa por tema.\n\nEs una **plantilla de partida, no probada en tu cuenta**: revísala y pruébala con respuestas de prueba antes de usarla.\n\nMontaje completo en «Monta tu app: checklist de apertura y cierre en el móvil», en fernandorivera.es.",
        "height": 400,
        "width": 520,
        "color": 4
      },
      "id": "65bc797b-8527-5e24-a211-68534499dc02",
      "name": "Nota: qué hace",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -620
      ]
    },
    {
      "parameters": {
        "content": "## 1. La hoja de respuestas\nEs la hoja que crea Google Forms (o la tabla de AppSheet/Glide). Columnas esperadas:\n\n`Marca temporal` · `Tienda` · `Tipo` (Apertura / Cierre) · `Persona` · una columna por tarea con respuesta **Sí / No** · `Incidencia` · `Foto`\n\n- Las tareas son preguntas de opción múltiple obligatorias con Sí y No. El código avisa de cualquier columna que valga «No».\n- El nombre de la tienda tiene que escribirse igual en el formulario y en los nodos de comprobación.\n- Si cambias el nombre de una columna en el formulario, cámbialo aquí.",
        "height": 400,
        "width": 520,
        "color": 5
      },
      "id": "97ab0f9f-f037-5a03-8802-f3fe298c41dc",
      "name": "Nota: la hoja",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        -660
      ]
    },
    {
      "parameters": {
        "content": "## 2. Configura\n- Cambia `TU_ID_DE_HOJA` en los nodos de Google Sheets y crea la credencial de Google.\n- **Telegram**: crea un bot con @BotFather, guarda el token en una credencial de Telegram de n8n y pon el `chat_id` del encargado o del grupo en `TU_CHAT_ID`.\n- **Qué comprobar (apertura / cierre)**: lista de tiendas separadas por comas, exactamente como aparecen en el formulario.\n- **Configuración del resumen**: URL y modelo de tu IA y correo de destino. Credencial *Header Auth* con `Authorization` = `Bearer TU_CLAVE`.\n\n¿WhatsApp en vez de Telegram? Necesitas WhatsApp Business Platform (Cloud API) de Meta y plantillas de mensaje aprobadas. Cambia los nodos de Telegram por el nodo de WhatsApp Business Cloud de n8n.",
        "height": 420,
        "width": 560,
        "color": 5
      },
      "id": "f59aa8d4-6e1d-5d95-bd1d-c92e0297c6c8",
      "name": "Nota: configura",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        360,
        -660
      ]
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Respuestas de formulario 1",
          "mode": "name"
        },
        "event": "rowAdded",
        "options": {}
      },
      "id": "ba1786ef-acd4-569d-907b-42b9d26fdbe6",
      "name": "Nueva respuesta en la hoja",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "typeVersion": 1,
      "position": [
        -700,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const salida = [];\nfor (const item of $input.all()) {\n  const r = item.json;\n  const fallos = Object.keys(r).filter(k => String(r[k]).trim().toLowerCase() === 'no');\n  const incidencia = String(r['Incidencia'] || '').trim();\n  if (!fallos.length && !incidencia) continue;\n  salida.push({ json: {\n    texto: '⚠️ ' + (r['Tipo'] || 'Checklist') + ' · ' + r['Tienda'] + ' (' + r['Marca temporal'] + ')\\n' +\n      (fallos.length ? 'Sin hacer: ' + fallos.join(', ') + '\\n' : '') +\n      (incidencia ? 'Incidencia: ' + incidencia + '\\n' : '') +\n      (r['Foto'] ? 'Foto: ' + r['Foto'] : '')\n  } });\n}\nreturn salida;"
      },
      "id": "3664c567-a106-5fbc-999d-2e154175d693",
      "name": "¿Algo en No o con incidencia?",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -480,
        0
      ]
    },
    {
      "parameters": {
        "chatId": "TU_CHAT_ID",
        "text": "={{ $json.texto }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "9235a517-91c2-5927-a415-de9d6ba313f4",
      "name": "Telegram: algo falla",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -260,
        0
      ],
      "webhookId": "be4fde2d-baec-5da4-ac4f-103e56b41a18"
    },
    {
      "parameters": {
        "content": "### Aviso de que falta\nAjusta las horas: por ejemplo, 15 minutos después de la apertura y 30 después del cierre. Si una tienda abre más tarde los domingos, duplica el disparador con su propio horario o quítala de la lista ese día.",
        "height": 200,
        "width": 420,
        "color": 3
      },
      "id": "ddf250a3-a60a-5916-8de1-2cb4c8a71fb0",
      "name": "Nota: hora límite",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        200
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 10,
              "triggerAtMinute": 15
            }
          ]
        }
      },
      "id": "b0b2eb5f-0e21-5e1a-815c-b961b9b3402c",
      "name": "Hora límite apertura 10:15",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -700,
        440
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 21,
              "triggerAtMinute": 45
            }
          ]
        }
      },
      "id": "ff6cc82f-4014-5791-910a-8e794c60460c",
      "name": "Hora límite cierre 21:45",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -700,
        620
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "7674673e-435b-5ccf-ac42-073bf4ac0626",
              "name": "tipo",
              "value": "Apertura",
              "type": "string"
            },
            {
              "id": "4953f8fd-e8c3-5f03-9af7-f6c1e05bce44",
              "name": "tiendas",
              "value": "Tienda Centro, Tienda Norte",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "1460c968-8200-5137-8a5f-234239a8b41d",
      "name": "Qué comprobar (apertura)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -480,
        440
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "07343a4c-02dd-5758-a89a-c880cdeae1d4",
              "name": "tipo",
              "value": "Cierre",
              "type": "string"
            },
            {
              "id": "4953f8fd-e8c3-5f03-9af7-f6c1e05bce44",
              "name": "tiendas",
              "value": "Tienda Centro, Tienda Norte",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "7799443e-cb1e-58e6-8edd-906cf74d84dc",
      "name": "Qué comprobar (cierre)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -480,
        620
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Respuestas de formulario 1",
          "mode": "name"
        },
        "options": {}
      },
      "id": "3617c00b-0069-5b09-841d-609931e44180",
      "name": "Leer respuestas",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -260,
        530
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "// Convierte la marca temporal de Google Forms (03/11/2026 9:42:10) o una fecha ISO en DateTime.\nconst leerFecha = v => {\n  const s = String(v || '').trim();\n  let d = DateTime.fromFormat(s, 'd/M/yyyy H:mm:ss', { zone: 'Europe/Madrid' });\n  if (!d.isValid) d = DateTime.fromFormat(s, 'd/M/yyyy H:mm', { zone: 'Europe/Madrid' });\n  if (!d.isValid) d = DateTime.fromISO(s, { zone: 'Europe/Madrid' });\n  return d;\n};\n// Qué disparador ha lanzado esta ejecución.\nlet cfg;\ntry { cfg = $('Qué comprobar (apertura)').first().json; } catch (e) { cfg = $('Qué comprobar (cierre)').first().json; }\nconst hoy = $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd');\nconst tiendas = String(cfg.tiendas).split(',').map(t => t.trim()).filter(Boolean);\nconst hechas = new Set($input.all()\n  .filter(i => String(i.json['Tipo']).trim() === cfg.tipo)\n  .filter(i => { const d = leerFecha(i.json['Marca temporal']); return d.isValid && d.toFormat('yyyy-MM-dd') === hoy; })\n  .map(i => String(i.json['Tienda']).trim()));\nconst faltan = tiendas.filter(t => !hechas.has(t));\nif (!faltan.length) return [];\nreturn [{ json: { texto: '⏰ ' + cfg.tipo + ' sin registrar a las ' + $now.toFormat('HH:mm') + ': ' + faltan.join(', ') } }];"
      },
      "id": "b85e3471-b2cc-58b2-b3d4-dba8ea851c16",
      "name": "Tiendas sin checklist hoy",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -40,
        530
      ]
    },
    {
      "parameters": {
        "chatId": "TU_CHAT_ID",
        "text": "={{ $json.texto }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "390a095b-483e-5a83-9132-9f8643cfd9c5",
      "name": "Telegram: falta la checklist",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        180,
        530
      ],
      "webhookId": "adec4b4b-ce3b-52db-9b1f-66fd6cf2e76b"
    },
    {
      "parameters": {
        "content": "### Resumen del lunes\nSolo se envían a la IA **tienda, fecha, tipo y texto de la incidencia**. Ni nombres de quien rellena ni fotos. Pide al equipo que en el campo de incidencias no escriba nombres de clientes ni comentarios sobre compañeros.",
        "height": 240,
        "width": 420,
        "color": 3
      },
      "id": "773198c7-3f9b-54dc-bf8e-4d1d9ed9cc49",
      "name": "Nota: resumen",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        860,
        800
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "id": "3ca70a2d-46a5-598a-9ea7-771cb9aeb0ad",
      "name": "Cada lunes 8:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -700,
        900
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e78667b3-95a6-5d75-970b-7662b77fc6ae",
              "name": "url_api_ia",
              "value": "https://api.openai.com/v1/chat/completions",
              "type": "string"
            },
            {
              "id": "cd22518e-6fd7-5190-9a10-54d01ad4fe0a",
              "name": "modelo",
              "value": "TU_MODELO",
              "type": "string"
            },
            {
              "id": "fce004d4-b835-53fb-975c-12ed21a060f1",
              "name": "email_resumen",
              "value": "TU_CORREO",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "fc1abbfe-adc3-5f0e-805b-a4d6227c0eb5",
      "name": "Configuración",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -480,
        900
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Respuestas de formulario 1",
          "mode": "name"
        },
        "options": {}
      },
      "id": "adff8d25-7478-5094-a513-d1ff27321f7e",
      "name": "Leer respuestas (semana)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -260,
        900
      ]
    },
    {
      "parameters": {
        "jsCode": "// Convierte la marca temporal de Google Forms (03/11/2026 9:42:10) o una fecha ISO en DateTime.\nconst leerFecha = v => {\n  const s = String(v || '').trim();\n  let d = DateTime.fromFormat(s, 'd/M/yyyy H:mm:ss', { zone: 'Europe/Madrid' });\n  if (!d.isValid) d = DateTime.fromFormat(s, 'd/M/yyyy H:mm', { zone: 'Europe/Madrid' });\n  if (!d.isValid) d = DateTime.fromISO(s, { zone: 'Europe/Madrid' });\n  return d;\n};\nconst desde = $now.setZone('Europe/Madrid').minus({ days: 7 }).startOf('day');\nconst lineas = $input.all()\n  .map(i => ({ r: i.json, d: leerFecha(i.json['Marca temporal']) }))\n  .filter(x => x.d.isValid && x.d >= desde && String(x.r['Incidencia'] || '').trim())\n  .map(x => '- ' + x.r['Tienda'] + ' · ' + x.d.toFormat('dd/MM') + ' · ' + x.r['Tipo'] + ': ' + String(x.r['Incidencia']).trim());\nconst cfg = $('Configuración').first().json;\nif (!lineas.length) return [{ json: { vacio: true, lineas: '' } }];\nreturn [{ json: { vacio: false, lineas: lineas.join('\\n'), body: {\n  model: cfg.modelo, temperature: 0.2,\n  messages: [{ role: 'user', content: \"Te paso las incidencias de apertura y cierre de la última semana, una por línea, con tienda, fecha, tipo e incidencia. Agrúpalas por tema (mantenimiento, mercancía, sistemas, seguridad, limpieza, otros). Di cuáles se repiten y en qué tiendas, y cuáles parecen seguir sin resolver. Termina con tres acciones concretas para esta semana. Máximo 15 líneas, en español de España. No inventes causas ni datos que no estén en el texto y no menciones a personas.\" + '\\n\\n' + lineas.join('\\n') }]\n} } }];"
      },
      "id": "379d1b5d-d593-515b-bfea-9175ace203d4",
      "name": "Incidencias de la semana",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -40,
        900
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Configuración').first().json.url_api_ia }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "19940526-fffe-50ac-8b4e-1f9e38d089b7",
      "name": "IA agrupa incidencias",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        400,
        860
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Configuración').first().json.email_resumen }}",
        "subject": "=Incidencias de apertura y cierre · semana al {{ $now.toFormat('dd/MM') }}",
        "emailType": "text",
        "message": "={{ $json.choices[0].message.content }}\n\n---\nIncidencias originales:\n{{ $('Incidencias de la semana').first().json.lineas }}\n\nResumen automático: revísalo antes de reenviarlo.",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "387ad4d6-d5c9-5906-97d0-616fddfbe717",
      "name": "Enviar resumen",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        620,
        860
      ],
      "webhookId": "a25bcd0e-eacd-5964-b66a-f6f77cd230a9"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "72035855-d070-534a-87d1-a940fb2b742e",
              "leftValue": "={{ $json.vacio }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "c572132b-68b1-56f7-8059-e4bab954905c",
      "name": "¿Hay incidencias?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        180,
        900
      ]
    },
    {
      "parameters": {},
      "id": "0c96e7c0-70a5-5cc9-a983-f1a38f9d9f36",
      "name": "Semana sin incidencias",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        400,
        1020
      ]
    }
  ],
  "connections": {
    "Nueva respuesta en la hoja": {
      "main": [
        [
          {
            "node": "¿Algo en No o con incidencia?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "¿Algo en No o con incidencia?": {
      "main": [
        [
          {
            "node": "Telegram: algo falla",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hora límite apertura 10:15": {
      "main": [
        [
          {
            "node": "Qué comprobar (apertura)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hora límite cierre 21:45": {
      "main": [
        [
          {
            "node": "Qué comprobar (cierre)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Qué comprobar (apertura)": {
      "main": [
        [
          {
            "node": "Leer respuestas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Qué comprobar (cierre)": {
      "main": [
        [
          {
            "node": "Leer respuestas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer respuestas": {
      "main": [
        [
          {
            "node": "Tiendas sin checklist hoy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tiendas sin checklist hoy": {
      "main": [
        [
          {
            "node": "Telegram: falta la checklist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cada lunes 8:00": {
      "main": [
        [
          {
            "node": "Configuración",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuración": {
      "main": [
        [
          {
            "node": "Leer respuestas (semana)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer respuestas (semana)": {
      "main": [
        [
          {
            "node": "Incidencias de la semana",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Incidencias de la semana": {
      "main": [
        [
          {
            "node": "¿Hay incidencias?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "¿Hay incidencias?": {
      "main": [
        [
          {
            "node": "IA agrupa incidencias",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Semana sin incidencias",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IA agrupa incidencias": {
      "main": [
        [
          {
            "node": "Enviar resumen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "tags": []
}
