ai-startup-skills
AI 제품을 만드는 스타트업을 위한 7단계 에이전트 스킬: 검증 → 비즈플랜 → 정책 → 로드쇼 → 고객 확보 → 가격 책정 → 등록
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A web-based Kanban board for tracking condominiums (condomínios) through the KMD3 Market sales funnel, from first prospecting to a signed contract. The team sees the same board in real time, can plan visit routes on a map, and gets reminders for every next step.
The interface is in Brazilian Portuguese. The code is plain HTML, CSS and JavaScript. It has no framework and no build step.
Navigation
Kanban board
Condo details
endereco ("Rua X, 155, Torre 2 - Bairro - Cidade - UF - 00000-000"), so the map, route, search and calendar links work as before and the database didn't change. Old addresses open split into fields and are kept exactly as typed unless edited.Busca Condomínio page (☰ → Busca Condomínio; it was called "Prospecção" before; old #prospeccao links still open it)
scripts/iptu.mjs) runs every month: it takes the newest IPTU file from GeoSampa, groups the 3.9 million properties into buildings (address, neighborhood, CEP, apartments, floors, year built, construction standard) and saves dados/prospeccao-sp.json, which is published with the site. It only rebuilds when a new year appears; to rebuild now, open Actions → Atualizar prospecção (IPTU) → Run workflow (tick "forcar"). Owner names and CPF/CNPJ are never in the list.scripts/cnefe.mjs) builds dados/prospeccao-vizinhas.json for the cities in scripts/cidades.json; it runs only by hand, because the Census data only changes with a new census. To add a city, put its IBGE code and name in that file and run the workflow. These buildings already have their map position, but no floors / year built / standard, and buildings finished after 2022 aren't there.Dashboard page (☰ → Dashboard)
Templates page (☰ → Templates)
Contacts page (☰ → Contatos)
Reminders (Lembretes)
notas_equipe (run database/notas_equipe.sql once); until it exists, notes stay in each browser and the page says so..ics file for Apple Calendar or Outlook (with an alarm 10 minutes before).Map view
rotas table (see below); without it, each browser keeps its own route.Team and access
Reliability
kmd3market/
├── index.html Page structure (HTML only)
├── style.css All styles
├── config.js Supabase URL and key
├── app.js Main app: board, panel, sync, reminders, team, login, admin
├── mapa.js Map view and route planner
├── prospeccao.js Busca Condomínio page
├── manifest.webmanifest App name, icons and colors (makes the site installable)
├── sw.js Service worker: opens the app offline, always fetches the newest version first
├── icons/ App icons (home screen, Android, iPhone)
├── tests/
│ ├── unit/ Unit tests for the app's logic (Node, no browser)
│ ├── e2e/ End-to-end tests in a real browser (Playwright)
│ ├── api/ Runs the Postman collection with Newman
│ └── helpers/ Test setup (sandbox loader)
├── postman/ Postman collection + environment for the Supabase API
├── database/ SQL schema for the Supabase tables (see database/README.md)
├── supabase/functions/ Edge Function admin-usuarios: create / cancel users, passwords (see supabase/README.md)
├── docs/testing/ Manual Postman checks of the external integrations, with screenshots
├── .github/workflows/ Runs the tests automatically on GitHub
├── package.json Test scripts and dev dependencies
└── README.md
All files must stay in the same folder as
index.html. The page finds its CSS and scripts by file name, so if one of them is missing or in a different folder, that part won't load. The logos are embedded insideindex.htmlitself; only the app icons (for installing) are inicons/.
Every file is commented in English to explain what each part does.
Script order matters.
index.htmlloadsconfig.js→app.js→mapa.js, in that order. Each file uses things defined in the one before it.
If the two values in config.js are empty, the app runs in local mode. There is no login, and data is saved only in the current browser (localStorage). This is useful for testing.
const SUPABASE_URL = "";
const SUPABASE_KEY = "";
Open index.html in a browser, or serve the folder with any static server:
npx serve .
# or
python3 -m http.server 8000
Fill in config.js with your Supabase project URL and publishable key, found in Supabase under Project Settings → API:
const SUPABASE_URL = "https://YOUR-PROJECT.supabase.co";
const SUPABASE_KEY = "sb_publishable_...";
The publishable key is meant to be public. Your data is protected by the Row Level Security rules in Supabase, so keep RLS enabled on every table.
The project has three layers of automated tests. All of them run in local mode (empty Supabase keys) except the API tests, so they never touch real data.
| Layer | What it checks | Tool | Count |
|---|---|---|---|
| Unit | The app's logic in isolation: XSS escaping, name normalization and duplicate detection, WhatsApp links, Brazilian date formats, date-range filter, reminder states (overdue / today / upcoming), calendar exports, database ↔ app conversion, legacy data migration, change history, board filters, dashboard numbers and the condo list behind each number (size bands, contacts, competitors, expiring contracts, stale deals), the IPTU robot and the IBGE (CNEFE) robot for nearby cities (columns, quotes, Latin-1 files, grouping units into buildings, neighborhood chosen by majority, garages / small buildings / older years left out, no owner data kept, GeoSampa download link), reading the list in the page and the street names sent to the map lookup, the user-management Edge Function (who may create / cancel / reset, creating with a temporary password, adding to the org and undoing the login if that fails, existing logins, cancel / reactivate rules, e-mail sent / not configured / failed, escaped e-mail, single-file copy up to date), the address fields (splitting old addresses, rebuilding them, round trip, CEP format, street search term, ViaCEP number ranges), the reminders calendar (whole weeks from Sunday, 4 / 5 / 6 rows, reminders grouped by day and time), a member's activity (plain-language lines, matching history by name, joining the two sources without duplicates, period / search, summary numbers, day headings). Also static checks: HTML ↔ JS wiring, script order, duplicate ids, and that no secret key is ever shipped to the browser | Node's built-in test runner (node:test) | 162 |
| End-to-end | The real app in a real browser: board renders, create / edit / delete a condo, duplicate-name warning, drag and drop between columns (and that it's saved), search and filters, CSV export, overdue reminders turning cards red, the reminders page, the reminders calendar (today's reminders, '+N mais', choosing a day, months, Hoje, phone dots), the team notepad (add / edit / delete, shared in the cloud, author-only edit, admin delete, read-only, missing table), editing reminders, postponing a reminder by 1 day or 1 week, XSS protection, the ☰ side menu (order, opening and closing, keyboard access, each item), the ☰ on every full-screen page (top left, menu above the page and Administração, going from page to page closes the old one, address follows, focus back, phone headers), contact types with "Outro…", the Contacts page (list, search, filter, standalone contacts, export, phone layout), the Templates page (create, copy, edit, delete, search), the Dashboard page (numbers, the condo list behind each number, "Ver no quadro", live update), the Prospecção page (list loaded from the robot's file or a clear error with "Tentar de novo", search, map positions looked up once and saved, "+ Funil", "+ Rota" and the stop turning into the card, Osasco with the city filter and positions from the Census, "Excluir" ticks in the list and the map popup, and their filter (local, shared through the database, read-only, missing table), phone tabs), the copy icon (inside the e-mail box and the phone box, after each e-mail and number on the Contatos page, and on each Timeline note), the SULTS toggle and badge, the "No SULTS" / "Fora do SULTS" filter, the hot-contact flame (toggle, card, filter, Dashboard, read-only, phone header), the address fields (old addresses split and kept, street suggestions by city with the CEP for the number first, CEP lookup, keyboard, offline, phone layout), the Visitar button and car badge, the map's Visitar filter, read-only users (role "leitor": everything visible, nothing editable), users in Administração (create with a temporary password, e-mail not sent → password shown to copy, form checks, function not published, cancel / reactivate, new password, new organization selected, phone layout), changing your own password (forced at first login, ☰ → Minha senha, "Esqueci minha senha" and the reset link), each member's activity in Administração (logins, changes and deletions by day, summary, period and search, opening a condo from it, the condos' history before is run, phone layout), staying on the same page after a refresh (every page and the map), the visit route (synced between two devices of the same person through a fake Supabase, newest change wins, adding from the condo panel, starting point by address and GPS, typed-address stops, sorting by proximity, the Google Maps link, all four exports, and the "Visitado" tick that takes the car off the card and puts it back when unticked, for condos and typed addresses, with the printed ✓ and read-only users), a well-formed accessibility tree on the main screens (no aria-label on generic elements, named buttons and fields, valid ARIA references; Lighthouse "Accessibility tree is not well-formed"), plus regression tests for fixed layout bugs (reminders count overflow, map legend scrolling with a mouse, no sideways scroll on phones, Administração cut off on phones, the installable app (manifest and icons, the service worker opening the app with no internet, "Instalar o app" with the Android install window, the iPhone steps and the Safari hint, hidden once installed), phones zooming in when a condo card or another page opened: no automatic cursor on touch screens and 16px text in every field, pinch-zoom still allowed, desktop unchanged) |
Requires Node.js 20 or newer.
npm install # installs Playwright and Newman (dev only)
npx playwright install chromium # downloads the test browser (first time only)
npm test # unit + end-to-end
npm run test:unit # unit only (no install needed)
npm run test:e2e # end-to-end only
The unit tests freeze the clock at 5 Oct 2026, 12:00 (São Paulo time), so date-dependent results are the same on any day. The end-to-end tests start a small local web server, replace config.js with empty keys, and block all internet requests, so they behave the same online and offline. The map tests use a small fake Leaflet (tests/e2e/fake-leaflet.js) that records what would be drawn, and a fake address search, so they can check the route line and its order without loading real maps.
These use a real login, passed as environment variables and never saved in the repository:
[email protected] KMD3_PASSWORD=yourpassword npm run test:api
The collection creates a test condo named [POSTMAN TESTE] and deletes it at the end. You can also import the two files in postman/ into the Postman app to run and read the requests by hand.
GitHub Actions (.github/workflows/tests.yml) runs the unit and end-to-end tests on every push and pull request. The API job only runs when started by hand from the Actions tab, using the KMD3_EMAIL and KMD3_PASSWORD repository secrets.
The app expects the following objects in the public schema.
condominios| Column | Type (suggested) | Notes |
|---|---|---|
id | text / uuid | Primary key. New rows use crypto.randomUUID() |
nome, endereco, zona, importante | text | |
aptos | integer | Number of apartments |
fase | text | Stage id, e.g. cadastro, reuniao, contrato |
status | text | verde, amarelo, vermelho, nenhum |
ordem | integer | Position inside the column |
contatos | jsonb | [{nome, telefone, email}] |
concorrente | text | |
fim_contrato | date | |
perfil, notas | text | |
responsavel, criado_por | text | |
criado_em, atualizado_em | timestamptz | |
historico | jsonb | Change log |
observacoes | jsonb | Timeline notes |
lembretes | jsonb | Reminders (optional, see below) |
sults | boolean | Green SULTS flag (optional, see below) |
quente | boolean | Hot contact flag: the flame on the card (optional, see below) |
If the lembretes, sults, visitar or quente column doesn't exist, the app still works, but that information is kept only on each person's device. A warning appears until the column is created. database/schema.sql adds them safely.
contatos_avulsosStandalone contacts from the Contatos page (contacts inside a condo stay in condominios.contatos). Every logged-in user can see and edit them. If the table doesn't exist yet, they are kept only in each person's browser and a warning explains it.
| Column | Type | Notes |
|---|---|---|
id | uuid | Primary key |
nome | text | Required |
telefone, email | text | |
tipo | text | "Vínculo" |
criado_por | text | Name of who added it |
criado_em | timestamptz |
templatesTexts from the Templates page. Every logged-in user can see and edit them. If the table doesn't exist yet, they are kept only in each person's browser and a warning explains it.
| Column | Type | Notes |
|---|---|---|
id | uuid | Primary key |
titulo, texto | text | Both required |
criado_por | text | Name of who created it |
criado_em, atualizado_em | timestamptz |
prospeccao_geoMap positions of the Prospecção buildings (the list itself is the file dados/prospeccao-sp.json). Each building is looked up once by its address and saved here for the whole team. If the table doesn't exist yet, positions are kept only in each browser (quitandinha:prospGeo).
| Column | Type | Notes |
|---|---|---|
id | text | Primary key: the building's id in the list (setor + quadra + condominium number) |
lat, lng | double precision | Position (empty if the address wasn't found) |
geo_precisao | text | numero (exact) or rua (only the street was found) |
geo_em | timestamptz | When it was looked up (not-found addresses are retried after 30 days) |
rotasEach person's visit route, so it follows them between devices (laptop, phone…). One row per user; Row Level Security lets each person see and change only their own. Every change is saved here and reaches the person's other open devices through Realtime (and when the app comes back to the screen). When two devices disagree, the most recent change wins. Without this table, the route stays in each browser.
| Column | Type | Notes |
|---|---|---|
usuario | uuid | Primary key: the logged-in user (auth.uid()) |
rota | jsonb | Stops in order (condo ids, or x_… for typed addresses and Prospecção buildings) |
extras | jsonb | Typed addresses and Prospecção buildings in the route (name, address, position) |
inicio | jsonb | Starting point, or empty |
atualizado_em | timestamptz | When the route last changed (decides which copy wins) |
equipe| Column | Type | Notes |
|---|---|---|
id | uuid / bigint | Primary key |
nome | text | Name shown in the app |
email | text | Optional. Links the login to the name |
Enable Realtime on the condominios table so everyone's board updates automatically.
| Function | Used for |
|---|---|
posso(acao) | Returns whether the current user can criar, editar or excluir |
sou_admin(), sou_super() | Shows the admin button and super-admin features |
admin_organizacoes() | Lists organizations with member and lead counts |
admin_membros(p_org) | Lists members of an organization and their permissions |
admin_definir_acesso(p_user, p_papel, p_ativo, p_ver, p_criar, p_editar, p_excluir) | Saves a member's role and permissions |
admin_remover_membro(p_user) | Removes a member |
adicionar_membro(p_email, p_papel, p_org) | Adds an existing login to an organization |
criar_organizacao(p_nome) | Creates an organization (super-admin) |
entrar_organizacao(p_nome) | Switches the super-admin to another organization |
The page you have open (Dashboard, Prospecção, Lembretes, Contatos, Templates, Equipe or the Mapa) is written in the address bar, e.g. …/#dashboard or …/#mapa. Refreshing the browser — or opening that link on another device — brings you back to that page instead of the board. Closing the page clears it from the address, so a refresh then opens the board as usual. An unknown address like #xyz just opens the board.
A person whose role can't create, edit or delete (the database's posso answers "no" to all three) gets a read-only app: they see the board, open cards and every page (Dashboard, Contatos, Templates, Prospecção, Lembretes, Mapa), but every button that changes data is hidden, the condo panel's fields are locked, the panel shows "Somente leitura", cards can't be dragged, and SULTS / Quente / Visitar only appear (as labels) when they're on. They can still copy templates, phone numbers, e-mails and notes, export lists, and open WhatsApp / call links. The real protection is the database's permissions (Row Level Security); the app just doesn't offer what the database would refuse.
In Administração, click a member's e-mail. A panel opens on the right (full screen on phones) with everything that person did, grouped by day (Hoje, Ontem, …) with the time: when they logged in, which condos they created, moved, changed or deleted, and the templates and contacts they touched. At the top: how many actions, how many different condos and how many new ones in the period (7, 30 or 90 days, or everything), plus their last activity. There's a search box, and clicking a condo name opens its card.
It uses two sources:
atividades table — created by running database/atividades.sql once in the Supabase SQL Editor. A database trigger writes a line for every condo, template and standalone contact that is created, changed or deleted (moving a card up/down inside a column and saves that change nothing are skipped), and the app writes a line when someone logs in ("Entrou no app") or opens it ("Abriu o app", at most every 30 minutes per device). Only admins of the organization (and super-admins) can read it, each person writes only their own lines, and nobody can edit or delete the log from the app.atividades.sql is run (the panel then says what's missing), and fills in the time before the table existed without showing any change twice.Column of atividades | Meaning |
|---|---|
usuario, email | Who (filled in by the database from the login) |
quando | When |
acao | entrou, abriu, criou, alterou, excluiu |
tipo | app, condominio, template, contato |
alvo_id, alvo | Which item and its name (kept even after it's deleted) |
detalhe | For condos, the history text, e.g. "Movido para Proposta enviada" |
Everything is done in Administração, through the Edge Function admin-usuarios (setup in supabase/README.md):
The secret key that creates logins stays inside Supabase. The browser only sends the person's own login token, and the function checks their permissions before doing anything.
index.html (not inside a subfolder). The .vercelignore file keeps the test files out of the deployed site.sw.js always asks the internet first and only uses its saved copy offline. If sw.js itself ever needs a clean start, change its CACHE name.| Service | Purpose | Notes |
|---|---|---|
| Supabase | Database, login, realtime | SDK loaded from jsDelivr, with unpkg and Skypack as fallbacks |
| Leaflet + leaflet.heat | Map and heatmap | Loaded from cdnjs only when the map is opened |
| Nominatim / OpenStreetMap | Address → coordinates | Limited to 1 request per second. Results are cached for 14 days when not found |
| CARTO | Map tiles | Key set in mapa.js (CARTO_KEY). If empty, falls back to OpenStreetMap tiles |
| Google Fonts | Schibsted Grotesk (one family for the whole app) |
The app keeps a local copy in localStorage so nothing is lost if the connection drops:
| Key | Contents |
|---|---|
quitandinha:condominios | Backup of all condos |
quitandinha:pendentes | Changes not yet sent to the database |
quitandinha:equipe | Team list (local mode) |
quitandinha:lembretes | Reminders backup |
quitandinha:sults | SULTS flags backup (only used if the database has no sults column) |
quitandinha:quente | Hot-contact flags backup (only used if the database has no quente column) |
quitandinha:visitar | Visitar flags backup (only used if the database has no visitar column) |
quitandinha:tiposContato | "Vínculo" types created with "Outro…" (so they show up right away) |
quitandinha:templates | Copy of the templates (the only copy if the database has no templates table) |
quitandinha:contatosAvulsos | Copy of the standalone contacts (the only copy if the database has no contatos_avulsos table) |
quitandinha:colunasInvertidas | Which columns are reversed |
quitandinha:geocache | Address coordinates for the map |
quitandinha:rota | The current visit route (stops in order) |
quitandinha:rotaExtras | Typed addresses added to the route |
quitandinha:rotaInicio | The route's starting point |
quitandinha:rotaAtualizadaEm | When this browser's copy of the route last changed (to compare with the database) |
quitandinha:prospAdicionados | Which Prospecção buildings were turned into cards from this browser |
quitandinha:prospGeo | Map positions of Prospecção buildings (only used if the database has no prospeccao_geo table) |
The quitandinha: prefix comes from the project's previous name. Changing it would make browsers lose their saved data and pending changes, so it was kept on purpose.
FASES list at the top of app.js. Changing an id affects condos already saved with that stage.:root at the top of style.css.CIDADE_PADRAO in mapa.js.ADM_PAPEIS in app.js. The real rules are enforced in the database.Private project of KMD3 Market Comércio Ltda. All rights reserved.
prospeccao_descartesatividades.sql| Playwright (Chromium) |
| 205 |
| API | The Supabase API the app depends on: login, CRUD on condominios, team list, permission functions, and that nothing is readable or writable without a login (Row Level Security) | Postman collection run by Newman | 19 requests |
visitar |
boolean |
| "Visitar" flag, the car on the card (optional, see below) |
sindico, sindico_tel, sindico_email, admin_nome, admin_contato, admin_tel, admin_email | text | Legacy columns. The app clears them and migrates old data to contatos |