# GITHUB REPO

*Complete Developer Onboarding & Development Guide*

## 🎯 Quick Start (5-minute setup)

```bash
gh repo clone Min11Benja/alertagas-twilio-sms
cd alertagas-twilio-sms
npm install
cp .env.example .env.local
# Fill environment variables (ask team lead)
npm run dev
```

***

## 📖 Table of Contents

1. [Architecture Overview](#-architecture-overview)
2. [Prerequisites](#-prerequisites)
3. [Environment Setup](#-environment-setup)
4. [Database Setup](#-database-setup)
5. [Twilio Configuration](#-twilio-configuration)
6. [Development Workflow](#-development-workflow)
7. [API Reference](#-api-reference)
8. [Deployment](#-deployment)
9. [Troubleshooting](#-troubleshooting)

***

## 🏗️ Architecture Overview

**Tech Stack**

* **Frontend:** Next.js 14+ (TypeScript) + Tailwind CSS
* **Backend:** Next.js API Routes + Server Components
* **Database:** Supabase (PostgreSQL) with Row Level Security
* **SMS:** Twilio Programmable SMS
* **Auth:** Supabase Authentication
* **Deployment:** Google Cloud Run + GitHub Actions

**Data Flow**

```
Twilio SMS → Webhook API → Supabase DB → Next.js Frontend → Real-time Updates
```

**Core Features**

* ✅ Secure Twilio webhook validation with signature verification
* ✅ SMS parsing and automatic sensor/client linking
* ✅ Real-time dashboard with gas level monitoring
* ✅ Multi-channel notifications (SMS + Email)
* ✅ Multi-tenant architecture with RLS
* ✅ Automated CI/CD with quality gates

***

## ⚙️ Prerequisites

### Required Tools

* **Node.js 18+** (`nvm use` automatically selects correct version)
* **npm** or **pnpm** (project uses npm by default)
* **Git** + GitHub CLI (recommended)
* **VS Code** with extensions:
  * ESLint, Prettier, TypeScript Hero
  * Tailwind CSS IntelliSense
  * Thunder Client (API testing)

### Accounts Needed

* **Twilio Account** with SMS-capable phone number
* **Supabase Project** for database and auth
* **Google Cloud Project** for deployment (production)

***

## 🔧 Environment Setup

### 1. Clone & Install

```bash
# Using GitHub CLI (recommended)
gh repo clone Min11Benja/alertagas-twilio-sms
cd alertagas-twilio-sms

# Using Git
git clone https://github.com/Min11Benja/alertagas-twilio-sms.git
cd alertagas-twilio-sms

# Install dependencies
npm install
```

### 2. Environment Configuration

Copy the environment template and fill with your credentials:

```bash
cp .env.example .env.local
```

**Required Variables** (get these from your team lead):

```env
# Twilio (get from Twilio Console)
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_auth_token_here

# Supabase (get from Project Settings → API)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOi...
SUPABASE_SERVICE_ROLE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOi...  # 🚨 SERVER ONLY

# Resend (Email Notifications - Optional)
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx

# Optional: Google Maps
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_maps_api_key
```

> 🔒 **Security Note:** Never commit `.env.local` or expose service role keys in client code.

***

## 🗃️ Database Setup

### Automated Schema Setup

1. Go to your Supabase project → **SQL Editor**
2. Run the complete schema script from the expanded section below
3. Verify setup with the provided validation query

<details>

<summary><strong>📋 Click to view complete SQL schema</strong></summary>

```sql
-- ============================================
-- ALERTAGAS — CLEAN DATABASE SCHEMA SETUP
-- For a new Supabase project (no prior objects)
-- ============================================

-- Extensions (for UUIDs, text search)
create extension if not exists pgcrypto;
create extension if not exists pg_trgm;

-- ============================================
-- ENUMS
-- ============================================
do $$ begin
  create type public.message_type as enum ('reading','heartbeat','battery','alert','other');
exception when duplicate_object then null; end $$;

do $$ begin
  create type public.battery_status as enum ('UNKNOWN','LOW','MEDIUM','HIGH','CHARGING');
exception when duplicate_object then null; end $$;

do $$ begin
  create type public.sms_status as enum ('received','parsed','linked','ignored','error');
exception when duplicate_object then null; end $$;

-- ============================================
-- TABLE: clients
-- ============================================
create table if not exists public.clients (
  id uuid primary key default gen_random_uuid(),
  name text not null,
  phone_number text,
  email text,
  contact_number text,
  address text,
  latitude numeric,
  longitude numeric,
  created_at timestamptz default now()
);

-- ============================================
-- TABLE: profiles
-- ============================================
create table if not exists public.profiles (
  user_id uuid primary key references auth.users(id) on delete cascade,
  name text,
  email text unique,
  client_id uuid references public.clients(id) on delete set null,
  role text default 'user',
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);
create index if not exists idx_profiles_client_id on public.profiles(client_id);

-- ============================================
-- TABLE: sensors
-- ============================================
create table if not exists public.sensors (
  id uuid primary key default gen_random_uuid(),
  sensor_name text,
  phone_number text not null unique,
  normalized_phone text,
  client_id uuid references public.clients(id) on delete set null,
  created_at timestamptz default now(),
  fill_level numeric,
  status text default 'active',
  last_updated timestamptz default now(),
  address text,
  threshold_percent integer default 20
);
create unique index if not exists uidx_sensors_normalized_phone
  on public.sensors (coalesce(normalized_phone, phone_number));
create index if not exists idx_sensors_client_id on public.sensors(client_id);

-- ============================================
-- TABLE: sms_messages
-- ============================================
create table if not exists public.sms_messages (
  id uuid primary key default gen_random_uuid(),
  from_number text not null,
  normalized_from text,
  body text not null,
  received_at timestamptz default now(),
  sensor_id uuid references public.sensors(id) on delete set null,
  client_id uuid references public.clients(id) on delete set null,
  status public.sms_status default 'received',
  message_type public.message_type default 'other',
  gas_level integer,
  battery public.battery_status default 'UNKNOWN',
  raw_payload jsonb,
  is_linked boolean default false,
  dedupe_key text,
  dedupe_hash text
);
create index if not exists idx_sms_messages_received_at on public.sms_messages(received_at desc);
create index if not exists idx_sms_messages_sensor_id   on public.sms_messages(sensor_id);
create index if not exists idx_sms_messages_client_id   on public.sms_messages(client_id);
create index if not exists idx_sms_messages_from_trgm   on public.sms_messages using gin (from_number gin_trgm_ops);
create unique index if not exists uidx_sms_dedupe_key   on public.sms_messages(dedupe_key);
create unique index if not exists uidx_sms_dedupe_hash  on public.sms_messages(dedupe_hash);

-- ============================================
-- TABLE: gas_readings
-- ============================================
create table if not exists public.gas_readings (
  id uuid primary key default gen_random_uuid(),
  sensor_id uuid not null references public.sensors(id) on delete cascade,
  client_id uuid not null references public.clients(id) on delete cascade,
  gas_level integer not null check (gas_level between 0 and 100),
  status text not null check (status in ('Low','Normal','High')),
  location jsonb,
  reading numeric check (reading is null or reading >= 0),
  recorded_at timestamptz default now()
);
create index if not exists idx_readings_sensor_time on public.gas_readings(sensor_id, recorded_at desc);
create index if not exists idx_readings_client_time on public.gas_readings(client_id, recorded_at desc);

-- ============================================
-- TABLE: alerts
-- ============================================
create table if not exists public.alerts (
  id uuid primary key default gen_random_uuid(),
  sensor_id uuid not null references public.sensors(id) on delete cascade,
  alert_type text not null,
  message text not null,
  status text default 'new',
  created_at timestamptz default now(),
  resolved_at timestamptz
);
create index if not exists idx_alerts_sensor_time on public.alerts(sensor_id, created_at desc);

-- ============================================
-- ROW LEVEL SECURITY (RLS)
-- ============================================
alter table public.clients      enable row level security;
alter table public.sensors      enable row level security;
alter table public.sms_messages enable row level security;
alter table public.gas_readings enable row level security;
alter table public.alerts       enable row level security;
alter table public.profiles     enable row level security;

-- Drop any existing policies to avoid duplicates
do $$
declare p record;
begin
  for p in select policyname, tablename from pg_policies where schemaname='public' loop
    execute format('drop policy if exists %I on public.%I;', p.policyname, p.tablename);
  end loop;
end $$;

-- ============================================
-- POLICIES (RLS)
-- ============================================
create policy profiles_self_select
  on public.profiles for select
  using (auth.uid() = user_id);

create policy profiles_self_update
  on public.profiles for update
  using (auth.uid() = user_id);

create policy clients_read_own
  on public.clients for select
  using (id = (select client_id from public.profiles where user_id = auth.uid()));

create policy sensors_read_own
  on public.sensors for select
  using (client_id = (select client_id from public.profiles where user_id = auth.uid()));

create policy sms_read_own
  on public.sms_messages for select
  using (client_id = (select client_id from public.profiles where user_id = auth.uid()));

create policy readings_read_own
  on public.gas_readings for select
  using (client_id = (select client_id from public.profiles where user_id = auth.uid()));

create policy alerts_read_own
  on public.alerts for select
  using (sensor_id in (
    select id from public.sensors
    where client_id = (select client_id from public.profiles where user_id = auth.uid())
  ));

-- ============================================
-- ✅ FINISHED
-- Verify everything
-- ============================================
select '✅ AlertaGas schema created successfully!' as status;
```

</details>

### Post-Setup Verification

```sql
-- Check tables were created
SELECT table_name FROM information_schema.tables 
WHERE table_schema = 'public' 
ORDER BY table_name;

-- Verify RLS is enabled
SELECT tablename, rowsecurity 
FROM pg_tables 
WHERE schemaname = 'public';
```

### Enable Real-time (Optional)

For live SMS updates in the dashboard:

1. Supabase Dashboard → **Database → Replication**
2. Enable **Realtime** for `public.sms_messages` table

***

## 📞 Twilio Configuration

### 1. Get Twilio Credentials

* Visit [Twilio Console](https://console.twilio.com)
* Copy **Account SID** and **Auth Token** to your `.env.local`

### 2. Buy a Phone Number

* In Twilio Console: **Phone Numbers → Manage → Buy a Number**
* Choose an SMS-capable number in your target region

### 3. Configure Webhook in Development

```bash
# Start development server
npm run dev

# Expose localhost (in separate terminal)
npx ngrok http 3000
```

**Twilio Webhook Setup:**

* Phone Numbers → Manage Numbers → \[Your Number]
* **A message comes in**: `POST` → `https://your-ngrok-url.ngrok.io/api/receive-sms`
* **Status callback**: (Optional) Same URL

***

## 🛠️ Development Workflow

### Starting Development

```bash
# Start development server
npm run dev

# Run in different terminals as needed
npm run lint:watch    # Watch mode for ESLint
npm run typecheck     # TypeScript validation
```

### Code Quality & Git Hooks

The project uses automated quality checks:

**Pre-commit (automatically runs):**

* ESLint on staged files
* TypeScript type checking
* Prettier code formatting
* Environment variable validation

**Pre-push (automatically runs):**

* Full test suite
* Build verification

**CI/CD Pipeline (on every PR):**

* Full linting and type checking
* Test suite execution
* Build verification
* Schema consistency checks

### Branch & Commit Convention

```bash
# Branch naming
git checkout -b feature/agt-123-add-sensor-search
git checkout -b bugfix/agt-456-fix-auth-redirect
git checkout -b hotfix/agt-789-critical-production-fix

# Commit messages
git commit -m "feat(agt-123): add sensor search functionality"
git commit -m "fix(agt-456): handle null sensor data gracefully"
git commit -m "chore: update dependencies to latest versions"
```

### Testing Your Setup

1. **Start services:** `npm run dev` + `ngrok http 3000`
2. **Update Twilio** with new ngrok URL
3. **Send test SMS** to your Twilio number
4. **Verify:**
   * SMS appears in Supabase `sms_messages` table
   * Message displays on <http://localhost:3000>
   * (If enabled) Real-time updates work automatically

***

## 🌐 API Reference

### Core Endpoints

| Endpoint                | Method | Auth             | Purpose                       |
| ----------------------- | ------ | ---------------- | ----------------------------- |
| `POST /api/receive-sms` | POST   | Twilio Signature | Receive & process inbound SMS |
| `POST /api/send-sms`    | POST   | Service Role     | Send outbound SMS             |
| `GET /api/clients`      | GET    | Browser (RLS)    | List user's clients           |
| `GET /api/sensors`      | GET    | Browser (RLS)    | List sensors with search      |
| `GET /api/sms/messages` | GET    | Browser (RLS)    | View SMS history              |
| `GET /api/gas_readings` | GET    | Browser (RLS)    | Gas level time series         |
| `GET /api/settings`     | GET    | Browser (RLS)    | Get global settings           |
| `PATCH /api/settings`   | PATCH  | Browser (RLS)    | Update global settings        |
| `PATCH /api/alerts/:id` | PATCH  | Browser (RLS)    | Resolve an alert              |

### Webhook Example

```typescript
// POST /api/receive-sms
// Headers: x-twilio-signature, Content-Type: application/x-www-form-urlencoded
// Body: From=+1234567890&Body=Gas%20level%3A%2075%25&To=+1987654320
```

### Client-Side Query Example

```typescript
// Using useFetchData hook (RLS-enforced)
const { data: sensors, loading } = useFetchData(
  '/api/sensors',
  { search: 'tank-1' }
);
```

***

## 📧 Notifications System

### Multi-Channel Alerts

Alertagas supports multiple notification channels for low tank alerts:

* **SMS** (Twilio) - Enabled by default
* **Email** (Resend) - Optional
* **In-App** (Real-time toasts) - Always enabled

### Email Setup (Optional)

1. Sign up at [resend.com](https://resend.com)
2. Create an API key
3. Add to `.env.local`:

```env
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
```

4. Enable email in Settings: `/dashboard/settings/globales`

### Settings Configuration

Configure notifications at `/dashboard/settings/globales`:

* Enable/disable SMS and email notifications
* Set default alert threshold (default: 20%)
* Configure email from address
* Enable auto-resolution of alerts

### Documentation

See [docs/alertagas-context/007-notifications-system.md](https://github.com/Min11Benja/alertagas-twilio-sms/blob/main/docs/alertagas-context/007-notifications-system.md) for complete documentation.

***

## 🚀 Deployment

### Production Deployment (Google Cloud Run)

**Prerequisites:**

* GCP Project with Cloud Run, Artifact Registry, and IAM APIs enabled
* Service account with Cloud Run Admin and Service Account User roles
* Workload Identity Federation configured for GitHub Actions

**GitHub Secrets Required:**

```env
GCP_WORKLOAD_IDENTITY_PROVIDER
GCP_SERVICE_ACCOUNT
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY
TWILIO_ACCOUNT_SID
TWILIO_AUTH_TOKEN
# ... other environment variables
```

**Deployment Process:**

* Automatic on push to `main` branch
* Manual trigger via GitHub Actions UI
* Health checks and rollback on failure

### Getting Service URL

```bash
gcloud run services describe nextjs-cloud-run \
  --region us-central1 \
  --format 'value(status.url)'
```

***

## 🐛 Troubleshooting

### Common Issues & Solutions

| Problem                               | Solution                                                           |
| ------------------------------------- | ------------------------------------------------------------------ |
| **Twilio 403 Signature Invalid**      | Verify `TWILIO_AUTH_TOKEN` matches Twilio console, check ngrok URL |
| **SMS Not Saving to Database**        | Check service role key, verify API route uses admin client         |
| **No Real-time Updates**              | Enable replication on `sms_messages` table in Supabase             |
| **Environment Variables Not Loading** | Restart dev server after editing `.env.local`                      |
| **RLS Blocking Data Access**          | Verify user has profile with client\_id, check policy conditions   |

### Debug Tools

```bash
# Check environment variables are loaded
npm run validate:env

# Inspect ngrok traffic
http://127.0.0.1:4040  # ngrok web interface

# Database inspection
npx supabase status    # Verify local Supabase connection
```

### Getting Help

1. Check application logs in browser console and terminal
2. Examine Supabase logs in dashboard
3. Use ngrok inspector to see webhook requests

## Documentation

> [**👉 Commander's Manual: How to Instruct Agents**](https://github.com/Min11Benja/alertagas-twilio-sms/blob/main/docs/USER_MANUAL.md) (Start Here!)

For full documentation, see [docs/README.md](https://github.com/Min11Benja/alertagas-twilio-sms/blob/main/docs/README.md).

* [Project Context](https://github.com/Min11Benja/alertagas-twilio-sms/blob/main/docs/project-context/README.md)
* [Agents](https://github.com/Min11Benja/alertagas-twilio-sms/blob/main/docs/agents/README.md)
* [Workflows](https://github.com/Min11Benja/alertagas-twilio-sms/blob/main/docs/workflows/README.md)

***

## 🔒 Security Notes

* **Service Role Key**: Never use in browser code - API routes only
* **Twilio Signatures**: Always validate in webhook endpoints
* **RLS Policies**: All tables have row-level security enabled
* **Environment Variables**: Keep secure variables in `.env.local` only

***

## 📞 Support & Resources

**Project Maintainer:** Jossue Benjamin Martínez Juárez\
📧 **Email:** <contacto@alertagas.com>\
🐙 **GitHub:** [@min11benja](https://github.com/Min11Benja)

**Useful Links:**

* [Project Repository](https://github.com/Min11Benja/alertagas-twilio-sms)
* [Supabase Documentation](https://supabase.com/docs)
* [Twilio SMS Guide](https://www.twilio.com/docs/sms)
* [Next.js Documentation](https://nextjs.org/docs)

***

## 📄 License

MIT License - See `LICENSE` file for details.

***

### 🎯 Quick Reference

```bash
# Daily development commands
npm run dev          # Start development
npm run lint         # Check code quality  
npm run typecheck    # Verify types
npm run build        # Production build test

# Deployment
git push origin main # Triggers auto-deploy

# Testing
curl -X POST https://your-app.ngrok.io/api/receive-sms \
  -d "From=+1234567890&Body=Test%20Message"
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs-api.alertagas.com/readme.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
