HANAWA行政書士事務所のロゴ HANAWA行政書士事務所 建設・製造・産廃業向け 許認可 × 外国人雇用 × 補助金 × 福利厚生
090-3718-2803 9:00-23:00 年中無休(土日祝日・20時以降は事前予約)

コラム

実践編 第5回:GASとGEMの違い|Geminiの「Gem」で定型化し、GASで自動化する最短ルート【行政書士×開業×AI】

2026年2月21日

Q:GASとGEM(GeminiのGem)は何が違い、行政書士業務ではどちらから触るのが近道ですか? A:一般的には、GEM(Gem)は「指示を定型化して出力を安…

<p><strong>Q:GASとGEM(GeminiのGem)は何が違い、行政書士業務ではどちらから触るのが近道ですか?<br />
A:</strong>一般的には、GEM(Gem)は「指示を定型化して出力を安定させる仕組み」、GASは「Googleサービス上の処理を自動で動かす仕組み」です。第5回では、①Geminiで"文書テンプレ用のGem"を1つ作り、②スプレッドシートの内容をもとにGoogleドキュメント作成とGmail下書きを自動で作るGASを1本動かすところまでを目標とします。機能名や画面は更新される場合があるため、表示差分が出やすい箇所は適宜補足します。</p>

<hr />
<p>AIに慣れが出てきた段階で多くの方が次に悩むのが、「GASの自動化に行くべきか」「Gemini側の機能(Gem)を使い込むべきか」という分岐です。(Googleユーザの場合)</p>

<p>実務では、いきなり自動化に走るより、まず出力がブレない文書テンプレを作り、必要な範囲だけをGASで自動化する方が、結果的に早い場面が多いです。今回はその土台として、違いと基本操作を「手を動かして」つかんでいきましょう。</p>

<p>※本稿では便宜上「GEM」を「GeminiのGems等の"カスタム指示(定型化)"機能」として扱います(表示名や配置は更新される可能性があります)。<br />
※本項では、Googleベースでお話させていただきますが、他のツールでも同様ことができます。また、Googleベースでもやりかたは他にもありますので一例として参考にしてください。</p>

<hr />
<h2>目次</h2>

<ol>
<li>1分で整理する|GASとGEMの役割はここが違う</li>
<li>2つの準備|使い始める前に整える設定と前提</li>
<li>3ステップで触る|GEM(Gems)の基本操作(PC)</li>
<li>4ステップで動かす|GASの基本操作(PC)</li>
<li>5分で実践する|GEMで下書き文書を作り、GASで自動化を1つ動かす</li>
<li>6つのつまずき|動かない・ブレるときの見直し順</li>
<li>7日以内に定着|実務への落とし込みと次回予告</li>
</ol>

<hr />
<h2>1. 1分で整理する|GASとGEMの役割はここが違う</h2>

<h3>GASは「処理と自動化」、GEMは「指示と定型化(文書化)」</h3>

<p><strong>GEM(Gem)</strong>:Geminiに「この条件なら、こういう形で出力してほしい」という指示を固定する仕組みです。毎回のコピペ指示を減らし、出力を揃えやすくします。</p>

<p><strong>GAS(Google Apps Script)</strong>:スプレッドシート/ドキュメント/Gmailなどを連携して自動で動かす仕組みです。クリック作業や転記を減らします。</p>

<blockquote>
<p><strong>つまずきポイント</strong>:「GEMで自動化できる」と思い込むと混乱しやすいです。GEMは"処理を実行する自動化"ではなく、主に"出力ルールの固定(定型化)"が得意です。処理の自動実行は基本的にGASの領域になります。</p>
</blockquote>

<hr />
<h3>迷ったときの3判断軸(繰り返し/入力の形/出力の安定)</h3>

<ul>
<li><strong>繰り返し回数</strong>:同じ作業が週に何回あるか(少ないならGEMで十分な場合も多いです)</li>
<li><strong>入力の形</strong>:入力が表(スプレッドシート)で整っているか(整っているほどGASが効きます)</li>
<li><strong>出力の安定</strong>:出力文書の型が決まっているか(決まっていないなら先にGEMで固めましょう)</li>
</ul>

<blockquote>
<p><strong>つまずきポイント</strong>:出力の型が未確定のままGASを組むと、後から文書テンプレの変更が頻発し、結局やり直しになりがちです。まずGEMで「型」を決め、GASは"運搬係"にする発想が安全です。</p>
</blockquote>

<hr />
<h2>2. 2つの準備|使い始める前に整える設定と前提</h2>

<h3>Gemini側で確認すること(利用可否・画面導線・保存場所)</h3>

<ul>
<li>GeminiでGem(Gems)機能が表示されるか(契約プランや管理設定で差が出る場合があります)</li>
<li>「Gem管理」や「Gems」など、Gemを作る導線がどこにあるか(表示名は更新される可能性があります)</li>
<li>作ったGemがどこから呼び出せるか(利用環境により、表示位置や呼び出し導線が異なる場合があります)</li>
</ul>

<blockquote>
<p><strong>つまずきポイント</strong>:Gem作成ボタンが見つからない場合は、①アカウント切替、②ブラウザとアプリの表示差、③管理者設定やプラン、の順で疑うと切り分けが早いです。</p>
</blockquote>

<hr />
<h3>GAS側で確認すること(Googleドライブ・権限・テスト実行の考え方)</h3>

<ul>
<li>Googleドライブにスプレッドシートを作れる状態か</li>
<li>GASを初回実行すると、<strong>権限許可(承認)</strong>が求められることが多いです</li>
<li>いきなり自動実行(トリガー)ではなく、最初は手動実行でログ確認するのが基本です</li>
</ul>

<blockquote>
<p><strong>つまずきポイント</strong>:「権限が怖い」と感じる場合は、まず"テスト用のダミーデータ"で動作確認してから実データに切り替えると安心です。</p>
</blockquote>

<hr />
<h2>3. 3ステップで触る|GEM(Gems)の基本操作(PC)</h2>

<h3>Gemの新規作成〜「指示文書(テンプレ)」の登録</h3>

<p><strong>PC手順(一般的な流れ)</strong></p>

<ol>
<li>ブラウザでGeminiを開き、左メニュー周辺にある<strong>「Gems」「Gem管理」「Gemマネージャー」</strong>等を探します(表示名は更新される可能性があります)。</li>
<li><strong>「新規作成」「+Gemを作成」</strong>等をクリックします。</li>
<li>Gemの「名前」と「指示(ルール)」を入力して保存します。</li>
</ol>

<p><strong>スマホ手順(一般的な流れ)</strong></p>

<ol>
<li>Geminiアプリ、またはモバイルブラウザでGeminiを開きます。</li>
<li>メニュー内の<strong>Gems(Gem管理)</strong>を探し、新規作成を選びます。</li>
<li>指示を入力して保存します。</li>
</ol>

<p>※スマホは画面が省略表示になりやすいです。見つからない場合は「PC表示(デスクトップ表示)」に切り替えると改善することがあります。</p>

<blockquote>
<p><strong>つまずきポイント</strong>:指示が長くても、運用で守れなければ効果が薄れます。まずは「出力の見出し」「分量」「禁止事項(断定しない等)」の3点に絞ると定着しやすいです。</p>
</blockquote>

<hr />
<h3>実務での呼び出し〜出力文書の整形まで(短縮・丁寧化・言い過ぎ回避)</h3>

<ul>
<li>Gemを呼び出したら、毎回の入力は「案件の要点」だけに寄せ、出力を揃えます。</li>
<li>出力をそのまま使わず、短縮/見出し付け/安全側の表現などを追い指示で整形します。</li>
</ul>

<blockquote>
<p><strong>つまずきポイント</strong>:出力が"丁寧すぎる""長すぎる"は頻出の問題です。Gem側に「最大○字」「箇条書き優先」など分量ルールを入れると安定しやすいです。</p>
</blockquote>

<hr />
<h2>4. 4ステップで動かす|GASの基本操作(PC)</h2>

<h3>新規プロジェクト作成→貼り付け→実行→ログ確認</h3>

<p><strong>PC手順(スプレッドシートから作る方法:分かりやすいことが多いです)</strong></p>

<ol>
<li>Googleスプレッドシートを開きます。</li>
<li>上部メニューの<strong>「拡張機能」→「Apps Script」</strong>を開きます(表示名は更新される可能性があります)。</li>
<li>エディタが開いたら、コードを貼り付けて保存します。</li>
<li>上部の実行で関数を動かし、権限許可→ログ確認を行います。</li>
</ol>

<p><strong>スマホ手順(結論:作成・編集はPC推奨)</strong></p>

<p>スマホでもブラウザでApps Scriptを開ける場合はありますが、編集・デバッグが難しいことが多いです。基本はPCで作成し、スマホは"結果確認(作られた文書や下書きの確認)"に回すのが無難です。</p>

<blockquote>
<p><strong>つまずきポイント</strong>:実行時エラーの多くは「サービス権限」「シート名・列名の不一致」が原因です。まずは"シート名"と"ヘッダー名"を固定すると解決が早いです。</p>
</blockquote>

<hr />
<h3>トリガー設定(手動/時間/編集)と権限の通し方</h3>

<ul>
<li><strong>手動実行</strong>:まずはここから(最小の動作確認)</li>
<li><strong>時間トリガー</strong>:毎日9時に作る、などの定期実行</li>
<li><strong>編集トリガー</strong>:シート編集時に動かす(ただし誤作動防止の設計が重要です)</li>
</ul>

<blockquote>
<p><strong>つまずきポイント</strong>:スマホアプリでは"編集イベント"の扱いが期待と違う場合があります。最初は時間トリガーか、PCでの手動実行で安定運用を作る方が再現しやすいです。</p>
</blockquote>

<hr />
<h2>5. 5分で実践する|GEMで下書き文書を作り、GASで自動化を1つ動かす</h2>

<h3>例題の全体像(スプレッドシート→定型文書→Gmail下書き/またはGoogleドキュメント)</h3>

<p>今回は「初回相談後フォロー」想定で、次の流れを作ります。</p>

<ol>
<li>スプレッドシートに要点を1行で入力します</li>
<li>GASでその行からGoogleドキュメントを自動作成します</li>
<li>さらにGmail下書きを自動作成します(送信はしません)</li>
<li>GEM(Gem)側では、フォロー文書の<strong>テンプレ(出力の型)</strong>を固定して、入力が多少ブレても形を揃えます</li>
</ol>

<blockquote>
<p><strong>つまずきポイント</strong>:GeminiをGASで"直接呼び出す"連携は、APIや契約・認証・運用ルールが絡むため、第5回では扱いません。今回は「Gemで型を作る」+「GASで運搬する」に絞ります。</p>
</blockquote>

<hr />
<h3>最小構成の手順(テンプレ文書の用意→GEMで文書部品化→GASで差し込み)</h3>

<p><strong>Step A:スプレッドシートを用意(例)</strong></p>

<p>シート名:フォロー</p>

<p>1行目(見出し):</p>

<table style="width: 707.778px;">
<thead>
<tr>
<th style="width: 88px;">列</th>
<th style="width: 197px;">内容</th>
<th style="width: 402px;">例</th>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 88px;">A</td>
<td style="width: 197px;">案件名</td>
<td style="width: 402px;">在留資格相談</td>
</tr>
<tr>
<td style="width: 88px;">B</td>
<td style="width: 197px;">相手呼称</td>
<td style="width: 402px;">ご相談者さま</td>
</tr>
<tr>
<td style="width: 88px;">C</td>
<td style="width: 197px;">メール</td>
<td style="width: 402px;">test@example.com</td>
</tr>
<tr>
<td style="width: 88px;">D</td>
<td style="width: 197px;">要点</td>
<td style="width: 402px;">必要資料の候補と次回の流れ</td>
</tr>
<tr>
<td style="width: 88px;">E</td>
<td style="width: 197px;">次アクション日</td>
<td style="width: 402px;">2026/02/28</td>
</tr>
</tbody>
</table>

<hr />
<h3>コピペ用プロンプト(GEM / Gem 用)</h3>

<h4>コピペ用プロンプト①(基本・汎用テンプレ:Gemに登録推奨)</h4>

<table border="0" cellpadding="1" cellspacing="1" style="width:750px;background-color: #f0f0f0;">
<tbody>
<tr>
<td>
<p>あなたは行政書士事務所の業務補助者です。次の入力から「初回相談後フォロー文書」の下書きを作成してください。<br />
推測で作らず、不足があれば不足点を「確認事項」に回してください。</p>

<p>【入力】<br />
- 案件名:<br />
- 相手呼称:(例:ご相談者さま)<br />
- 要点:(箇条書き推奨)<br />
- 次アクション日:(YYYY/MM/DD 推奨)</p>

<p>【出力の目的】<br />
相手に次の手順と確認事項を分かりやすく伝えるためのフォロー文書(送付前提の下書き)</p>

<p>【出力条件】<br />
- 日本語、です・ます調<br />
- 構成は必ず次の順で出す<br />
  1) 件名(1行)<br />
  2) あいさつ(2〜3行)<br />
  3) 本日の要点(箇条書き 3〜6点)<br />
  4) 次の手順(箇条書き 2〜4点。次アクション日を【】で強調)<br />
  5) 確認事項(箇条書き 3点。不足情報はここに列挙)<br />
  6) 結び(2行)<br />
- 分量:400〜650字<br />
- 断定・結果保証に見える表現は避け、「一般的には」「場合によっては」を必要箇所で使う<br />
- 法的判断・要件判断はしない(根拠不明の決めつけをしない)<br />
- 固有名詞・個人情報(氏名、住所、連絡先、番号類)を文書に入れない<br />
- 用語は「文書」で統一し、言い換えない</p>
</td>
</tr>
</tbody>
</table>

<p><strong>使いどころ</strong>:Gemに登録して「出力の型」を固定するときに使います。</p>

<p><strong>入力時の注意(匿名化)</strong>:固有名詞・番号類は入れず、役割名+要点に寄せてください。</p>

<p><strong>NG例(やりがちな悪い入力)</strong>: 「実名・住所・番号類も入れて、必ず許可が出ると書いて」 →特定可能性や誤認誘発が増え、対外文書として危険です。</p>

<hr />
<h4>コピペ用プロンプト②(具体例:匿名化入力例)</h4>

<table border="0" cellpadding="1" cellspacing="1" style="width:750px;background-color: #f0f0f0;">
<tbody>
<tr>
<td>
<p>次の入力で「初回相談後フォロー文書」の下書きを作成してください。推測で作らず、不足は「確認事項」に回してください。</p>

<p>【入力】<br />
- 案件名:在留資格の更新相談<br />
- 相手呼称:ご相談者さま<br />
- 要点:<br />
  - 現状整理(前提の確認)<br />
  - 必要資料は状況により追加の可能性<br />
  - 次回面談までの準備候補(例:在留状況の経緯が分かる資料)<br />
  - 想定スケジュールは目安で変動<br />
- 次アクション日:2026/02/28</p>

<p>【出力条件】<br />
- 日本語、です・ます調<br />
- 構成:件名/あいさつ/本日の要点/次の手順/確認事項/結び<br />
- 分量:450〜700字<br />
- 断定・結果保証に見える表現は避け、「一般的には」「場合によっては」を必要箇所で使う<br />
- 法的判断・要件判断はしない<br />
- 固有名詞・個人情報は入れない<br />
- 用語は「文書」で統一し、言い換えない</p>
</td>
</tr>
</tbody>
</table>

<p><strong>使いどころ</strong>:出力の見本を作り、Gemの安定化に使うときに活用してください。</p>

<p><strong>入力時の注意(匿名化)</strong>:固有名詞を入れずに成立する粒度で要点化します。</p>

<hr />
<h4>コピペ用プロンプト③(編集:追いプロンプト)</h4>

<table border="0" cellpadding="1" cellspacing="1" style="width:750px;background-color: #f0f0f0;">
<tbody>
<tr>
<td>
<p>いまの下書き文書を、次の条件で編集してください。意味は変えず、言い過ぎや誤認誘発を減らしてください。</p>

<p>【編集条件】<br />
- 250〜380字に短縮<br />
- 1文は長くしすぎない(目安:60字以内)<br />
- 断定・結果保証に見える表現は安全側へ置換(「一般的には」「場合によっては」を必要箇所で追加)<br />
- 「次アクション日」を【】で強調し、ひと目で分かる位置に置く<br />
- 用語は「文書」で統一し、言い換えない</p>

<p>【出力形式】<br />
編集後の文書のみ(解説は不要)</p>
</td>
</tr>
</tbody>
</table>

<p><strong>使いどころ</strong>:長い下書きを「送れる形」に整えるときに使います。</p>

<p><strong>入力時の注意(匿名化)</strong>:編集対象の文書にも固有名詞・番号類を混ぜない前提で運用してください。</p>

<hr />
<h4>コピペ用プロンプト④(短縮・整形用:任意)</h4>

<table border="0" cellpadding="1" cellspacing="1" style="width:750px;background-color: #f0f0f0;">
<tbody>
<tr>
<td>次の文書を、見出し3つ(要点/次の手順/確認事項)に再構成してください。<br />
各見出しは最大2行、全体は300〜450字に収めてください。<br />
用語は「文書」で統一し、言い換えないでください。</td>
</tr>
</tbody>
</table>

<hr />
<h4>コピペ用プロンプト⑤(安全表現化チェック:任意)</h4>

<table border="0" cellpadding="1" cellspacing="1" style="width:750px;background-color: #f0f0f0;">
<tbody>
<tr>
<td>次の文書について、(1)断定に見える箇所、(2)結果保証に見える箇所、(3)誤認を招きそうな箇所を抽出し、<br />
それぞれを安全側の表現に置換した「修正版の文書」を提示してください。<br />
修正版は読みやすさを落とさず、過度に長くしないでください。<br />
用語は「文書」で統一し、言い換えないでください。</td>
</tr>
</tbody>
</table>

<hr />
<h3>コピペ用コード(GAS:強化版)</h3>

<p>以下は「ヘッダー名参照」「二重作成防止」「未処理行のみ処理」「ステータス書き戻し」まで入れた実務向けの最小構成です。</p>

<table border="0" cellpadding="1" cellspacing="1" style="background-color: #f0f0f0;width:750px;">
<tbody>
<tr>
<td>
<p>/***************<br />
 * 設定(ここだけ先に確認)<br />
 ***************/<br />
const CONFIG = {<br />
  SHEET_NAME: 'フォロー',<br />
  HEADER_ROW: 1,</p>

<p>  // 入力列(ヘッダー名で参照:列順が変わっても壊れにくい)<br />
  HEADERS: {<br />
    matter: '案件名',<br />
    honorific: '相手呼称',<br />
    email: 'メール',<br />
    points: '要点',<br />
    nextDate: '次アクション日',<br />
  },</p>

<p>  // 出力列(なければ自動作成)<br />
  OUT_HEADERS: {<br />
    docUrl: '作成文書URL',<br />
    draftId: '下書きID',<br />
    status: '処理状態',<br />
    lastRun: '最終実行日時',<br />
    error: 'エラー',<br />
  },</p>

<p>  // 動作<br />
  PROCESS_ONLY_WHEN_STATUS_EMPTY: true, // true推奨:未処理行のみ対象<br />
  CREATE_DRAFT: true,                  // true:Gmail下書き作成(送信なし)<br />
};</p>

<p>function onOpen() {<br />
  SpreadsheetApp.getUi()<br />
    .createMenu('自動化')<br />
    .addItem('初期セットアップ(列を追加)', 'setupSheet_')<br />
    .addSeparator()<br />
    .addItem('2行目を処理(テスト用)', 'processRow2_')<br />
    .addItem('未処理行を一括処理', 'processPendingRows_')<br />
    .addToUi();<br />
}</p>

<p>/***************<br />
 * 初期セットアップ:出力列を自動追加<br />
 ***************/<br />
function setupSheet_() {<br />
  const sheet = getSheet_();<br />
  const headerMap = getHeaderMap_(sheet);</p>

<p>  // 出力列がなければ右端に追加<br />
  const lastCol = sheet.getLastColumn();<br />
  let col = lastCol;</p>

<p>  Object.values(CONFIG.OUT_HEADERS).forEach(name => {<br />
    if (!headerMap[name]) {<br />
      col += 1;<br />
      sheet.getRange(CONFIG.HEADER_ROW, col).setValue(name);<br />
    }<br />
  });</p>

<p>  SpreadsheetApp.getUi().alert('セットアップ完了:出力列を確認してください。');<br />
}</p>

<p>/***************<br />
 * テスト用:2行目だけ処理<br />
 ***************/<br />
function processRow2_() {<br />
  processRow_(2);<br />
}</p>

<p>/***************<br />
 * 未処理行を一括処理<br />
 ***************/<br />
function processPendingRows_() {<br />
  const lock = LockService.getScriptLock();<br />
  const gotLock = lock.tryLock(30 * 1000);<br />
  if (!gotLock) {<br />
    throw new Error('同時実行を検知しました。少し待ってから再実行してください。');<br />
  }</p>

<p>  try {<br />
    const sheet = getSheet_();<br />
    const headerMap = getHeaderMap_(sheet);<br />
    ensureOutHeaders_(sheet, headerMap);</p>

<p>    const lastRow = sheet.getLastRow();<br />
    if (lastRow <= CONFIG.HEADER_ROW) return;</p>

<p>    for (let r = CONFIG.HEADER_ROW + 1; r <= lastRow; r++) {<br />
      // ステータスが空の行だけ(推奨)<br />
      if (CONFIG.PROCESS_ONLY_WHEN_STATUS_EMPTY) {<br />
        const status = getCellByHeader_(sheet, headerMap, r, CONFIG.OUT_HEADERS.status);<br />
        if (String(status).trim() !== '') continue;<br />
      }<br />
      processRow_(r, sheet, headerMap);<br />
    }<br />
  } finally {<br />
    lock.releaseLock();<br />
  }<br />
}</p>

<p>/***************<br />
 * 行処理本体<br />
 ***************/<br />
function processRow_(rowNumber, sheetOpt, headerMapOpt) {<br />
  const sheet = sheetOpt || getSheet_();<br />
  const headerMap = headerMapOpt || getHeaderMap_(sheet);<br />
  ensureOutHeaders_(sheet, headerMap);</p>

<p>  const out = CONFIG.OUT_HEADERS;</p>

<p>  // 既にURLがあれば二重作成しない(安全策)<br />
  const existingUrl = getCellByHeader_(sheet, headerMap, rowNumber, out.docUrl);<br />
  if (String(existingUrl).trim() !== '') {<br />
    writeStatus_(sheet, headerMap, rowNumber, 'SKIP(既に作成済み)', '', '');<br />
    return;<br />
  }</p>

<p>  try {<br />
    // 入力取得<br />
    const matter = String(getCellByHeader_(sheet, headerMap, rowNumber, CONFIG.HEADERS.matter) || '').trim();<br />
    const honorific = String(getCellByHeader_(sheet, headerMap, rowNumber, CONFIG.HEADERS.honorific) || 'ご相談者さま').trim();<br />
    const email = String(getCellByHeader_(sheet, headerMap, rowNumber, CONFIG.HEADERS.email) || '').trim();<br />
    const pointsRaw = getCellByHeader_(sheet, headerMap, rowNumber, CONFIG.HEADERS.points);<br />
    const nextDateRaw = getCellByHeader_(sheet, headerMap, rowNumber, CONFIG.HEADERS.nextDate);</p>

<p>    if (!matter) throw new Error('案件名が空です。');<br />
    if (CONFIG.CREATE_DRAFT && !email) throw new Error('メールが空です(下書き作成が有効です)。');<br />
    if (CONFIG.CREATE_DRAFT && !looksLikeEmail_(email)) throw new Error('メール形式が不自然です。');</p>

<p>    const points = normalizePoints_(pointsRaw);<br />
    const nextDate = formatDateSafe_(nextDateRaw);</p>

<p>    const body = buildBody_({ matter, honorific, points, nextDate });<br />
    const subject = `【${matter}】フォローのご連絡`;</p>

<p>    // Doc作成<br />
    const doc = DocumentApp.create(`フォロー_${sanitizeForTitle_(matter)}_${timestamp_()}`);<br />
    doc.getBody().setText(body);<br />
    doc.saveAndClose();</p>

<p>    // 下書き作成(送信しない)<br />
    let draftId = '';<br />
    if (CONFIG.CREATE_DRAFT) {<br />
      const draft = GmailApp.createDraft(email, subject, body);<br />
      try {<br />
        draftId = String(draft.getId() || '');<br />
      } catch (e) {<br />
        draftId = '';<br />
      }<br />
    }</p>

<p>    // 書き戻し<br />
    setCellByHeader_(sheet, headerMap, rowNumber, out.docUrl, doc.getUrl());<br />
    setCellByHeader_(sheet, headerMap, rowNumber, out.draftId, draftId);<br />
    writeStatus_(sheet, headerMap, rowNumber, 'OK', '', '');</p>

<p>  } catch (err) {<br />
    writeStatus_(sheet, headerMap, rowNumber, 'ERROR', '', String(err));<br />
  }<br />
}</p>

<p>/***************<br />
 * 文書テンプレ(ここを差し替えやすく)<br />
 ***************/<br />
function buildBody_(p) {<br />
  const next = p.nextDate ? `【${p.nextDate}】` : '【日付未入力】';</p>

<p>  return (<br />
`【${p.matter}】フォローのご連絡</p>

<p>${p.honorific}</p>

<p>本日はご相談ありがとうございました。以下、本日の要点を共有いたします。<br />
${p.points}</p>

<p>次の手順:<br />
- ${next} までに、準備資料の候補をご確認ください(場合によっては追加が必要になります)<br />
- 不明点があれば、まとめてご連絡ください</p>

<p>確認事項:<br />
- 前提条件に変更がないか<br />
- 期限や優先順位のご希望<br />
- 追加で確認したい点</p>

<p>どうぞよろしくお願いいたします。<br />
`);<br />
}</p>

<p>/***************<br />
 * ヘルパー<br />
 ***************/<br />
function getSheet_() {<br />
  const ss = SpreadsheetApp.getActiveSpreadsheet();<br />
  const sheet = ss.getSheetByName(CONFIG.SHEET_NAME);<br />
  if (!sheet) throw new Error(`シート「${CONFIG.SHEET_NAME}」が見つかりません。`);<br />
  return sheet;<br />
}</p>

<p>function getHeaderMap_(sheet) {<br />
  const lastCol = sheet.getLastColumn();<br />
  const headers = sheet.getRange(CONFIG.HEADER_ROW, 1, 1, lastCol).getValues()[0];<br />
  const map = {};<br />
  headers.forEach((h, idx) => {<br />
    const name = String(h || '').trim();<br />
    if (name) map[name] = idx + 1;<br />
  });<br />
  return map;<br />
}</p>

<p>function ensureOutHeaders_(sheet, headerMap) {<br />
  const lastCol = sheet.getLastColumn();<br />
  let col = lastCol;</p>

<p>  Object.values(CONFIG.OUT_HEADERS).forEach(name => {<br />
    if (!headerMap[name]) {<br />
      col += 1;<br />
      sheet.getRange(CONFIG.HEADER_ROW, col).setValue(name);<br />
      headerMap[name] = col;<br />
    }<br />
  });<br />
}</p>

<p>function getCellByHeader_(sheet, headerMap, row, headerName) {<br />
  const col = headerMap[headerName];<br />
  if (!col) throw new Error(`ヘッダー「${headerName}」が見つかりません。`);<br />
  return sheet.getRange(row, col).getValue();<br />
}</p>

<p>function setCellByHeader_(sheet, headerMap, row, headerName, value) {<br />
  const col = headerMap[headerName];<br />
  sheet.getRange(row, col).setValue(value);<br />
}</p>

<p>function writeStatus_(sheet, headerMap, row, status, lastRun, error) {<br />
  const out = CONFIG.OUT_HEADERS;<br />
  setCellByHeader_(sheet, headerMap, row, out.status, status);<br />
  setCellByHeader_(sheet, headerMap, row, out.lastRun, lastRun || Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm:ss'));<br />
  setCellByHeader_(sheet, headerMap, row, out.error, error || '');<br />
}</p>

<p>function normalizePoints_(pointsRaw) {<br />
  if (pointsRaw === null || pointsRaw === undefined) return '-(要点未入力)';<br />
  const s = String(pointsRaw).trim();<br />
  if (!s) return '-(要点未入力)';</p>

<p>  if (s.includes('\n') || s.startsWith('-') || s.startsWith('・')) {<br />
    return s<br />
      .split('\n')<br />
      .map(line => line.trim())<br />
      .filter(Boolean)<br />
      .map(line => (line.startsWith('-') || line.startsWith('・')) ? line : `- ${line}`)<br />
      .join('\n');<br />
  }<br />
  return `- ${s}`;<br />
}</p>

<p>function formatDateSafe_(v) {<br />
  if (!v) return '';<br />
  if (Object.prototype.toString.call(v) === '[object Date]' && !isNaN(v.getTime())) {<br />
    return Utilities.formatDate(v, Session.getScriptTimeZone(), 'yyyy/MM/dd');<br />
  }<br />
  return String(v).trim();<br />
}</p>

<p>function looksLikeEmail_(email) {<br />
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);<br />
}</p>

<p>function sanitizeForTitle_(s) {<br />
  return String(s).replace(/[\\\/:*?"<>|]/g, '').slice(0, 50);<br />
}</p>

<p>function timestamp_() {<br />
  return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyyMMdd_HHmmss');<br />
}</p>
</td>
</tr>
</tbody>
</table>

<hr />
<p><strong>PC手順(実行)</strong></p>

<ol>
<li>スプレッドシート →「拡張機能」→「Apps Script」を開きます</li>
<li>上のコードを貼り付けて保存します</li>
<li>スプレッドシートを再読み込みし、メニュー「自動化」から以下の順で実行します
<ul>
<li>「初期セットアップ(列を追加)」</li>
<li>「2行目を処理(テスト用)」</li>
</ul>
</li>
<li>「作成文書URL」と「下書きID」「処理状態」が更新され、Gmail下書きに文書が作成されます</li>
</ol>

<p><strong>スマホ手順(実行の代替)</strong></p>

<p>スマホのスプレッドシートアプリではカスタムメニューが表示されない場合があります。その場合は①PCで一度実行する、または②時間トリガーで定期実行する、のいずれかに寄せると再現しやすいです。</p>

<blockquote>
<p><strong>つまずきポイント</strong>:「自動化」メニューが出ない場合は、スプレッドシートの再読み込み、onOpenが保存されているか、アカウントが同じかを順に確認してください。</p>
</blockquote>

<blockquote>
<p><strong>NG例(運用のやりがち)</strong>:本番データでいきなり「未処理行を一括処理」を実行する。まずは必ず「2行目を処理(テスト用)」で挙動を確認してから本番に移ってください。</p>
</blockquote>

<hr />
<h2>6. 6つのつまずき|動かない・ブレるときの見直し順</h2>

<h3>GEM側のつまずき(指示が曖昧/出力が揺れる/長すぎる)と直し方</h3>

<ul>
<li><strong>曖昧</strong>:禁止事項・分量・見出し構成を追加します</li>
<li><strong>揺れる</strong>:「件名/要点/次の手順/確認事項」の型を固定し、入力項目も固定します</li>
<li><strong>長い</strong>:「最大○字」「各見出しは最大2行」など上限を明示します</li>
</ul>

<p><strong>代替手順</strong>:Gemにこだわらず、まずは「コピペ用プロンプト①」をそのまま使って安定化させてからGem登録する方法でも問題ありません。</p>

<hr />
<h3>GAS側のつまずき(権限/実行エラー/トリガー不発)と代替手順</h3>

<ul>
<li><strong>権限</strong>:初回実行時の許可を通します(テスト用の行で実行してください)</li>
<li><strong>実行エラー</strong>:シート名、ヘッダー名、空欄チェックから順に確認します</li>
<li><strong>トリガー不発</strong>:時間トリガーに寄せるか、手動実行で安定運用を先に作りましょう</li>
</ul>

<p><strong>代替手順</strong>:まずは「ドキュメント作成だけ」に絞って動かし、次にGmail下書きを追加すると切り分けが容易です。</p>

<hr />
<h2>7. 7日以内に定着|実務への落とし込みと次回予告</h2>

<h3>まず固定する「文書テンプレ」と「入力項目」の型</h3>

<ul>
<li>文書テンプレ(見出し・分量・禁止事項)をGemで固定します</li>
<li>入力項目(案件名/要点/次アクション日など)をスプレッドシートで固定します</li>
</ul>

<p>この2つが揃うと、GASは"運搬係"として安定して働くようになります。</p>

<hr />
<h3>予告:GAS編/GEM編で深掘りするポイント</h3>

<p>次回以降のGAS編では「トリガー運用」「転記・整形の自動化」「誤作動防止」を、GEM編では「事務所内で使い回せるGem設計」「テンプレの育て方(編集指示の型)」を深掘りしていきます。</p>

<hr />
<h2>脚注</h2>

<p>Gem(Gems)やApps Script、トリガー等の仕様や導線は更新される場合があります。運用前に、Google公式のヘルプ/開発者向け文書で最新表示をご確認ください。</p>

<hr />
<h2>免責</h2>

<p>本稿は一般的な情報提供を目的としたものであり、個別具体の事案に対する法的助言を行うものではありません。実務への適用にあたっては、事案の前提・契約内容・運用体制等に応じて適切にご判断ください。</p>

<hr />
<h2>HANAWA行政書士事務所</h2>

<p><a href="https://hanawa-office.jp/">HANAWA行政書士事務所公式サイト</a></p>

<p><a href="https://hanawa-office.jp/AI_knowledge/index.php">AI活用学習サイト</a></p>

<p><a href="https://hanawa-office.jp/blog_detail.php?id=475">前の記事:実践編 第4回:ホームページの"修正"をAIで進める</a></p>

<p><a href="https://hanawa-office.jp/blog_detail.php?id=482">次の記事:実践編 第6回:NotebookLMで「事務所の独自辞書」を作り、根拠つきの文書下書きを速く作る手順</a></p>
前のページに戻る
フォーム 電話 LINE