@jjlmoya/utils-science 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +64 -0
- package/src/category/i18n/en.ts +97 -0
- package/src/category/i18n/es.ts +97 -0
- package/src/category/i18n/fr.ts +96 -0
- package/src/category/index.ts +17 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +24 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/asteroid-impact/AsteroidImpact.css +799 -0
- package/src/tool/asteroid-impact/bibliography.astro +14 -0
- package/src/tool/asteroid-impact/component.astro +436 -0
- package/src/tool/asteroid-impact/constants.ts +67 -0
- package/src/tool/asteroid-impact/helpers.ts +17 -0
- package/src/tool/asteroid-impact/i18n/en.ts +153 -0
- package/src/tool/asteroid-impact/i18n/es.ts +153 -0
- package/src/tool/asteroid-impact/i18n/fr.ts +153 -0
- package/src/tool/asteroid-impact/index.ts +24 -0
- package/src/tool/asteroid-impact/logic/impactPhysics.ts +86 -0
- package/src/tool/asteroid-impact/script.ts +256 -0
- package/src/tool/asteroid-impact/seo.astro +15 -0
- package/src/tool/asteroid-impact/ui-helpers.ts +56 -0
- package/src/tool/asteroid-impact/ui.ts +69 -0
- package/src/tool/asteroid-impact/utils.ts +17 -0
- package/src/tool/cellular-renewal/CellularRenewal.css +1 -0
- package/src/tool/cellular-renewal/bibliography.astro +14 -0
- package/src/tool/cellular-renewal/component.astro +387 -0
- package/src/tool/cellular-renewal/i18n/en.ts +170 -0
- package/src/tool/cellular-renewal/i18n/es.ts +170 -0
- package/src/tool/cellular-renewal/i18n/fr.ts +170 -0
- package/src/tool/cellular-renewal/index.ts +24 -0
- package/src/tool/cellular-renewal/logic/CellularRenewalEngine.ts +50 -0
- package/src/tool/cellular-renewal/seo.astro +14 -0
- package/src/tool/colony-counter/ColonyCounter.css +473 -0
- package/src/tool/colony-counter/bibliography.astro +14 -0
- package/src/tool/colony-counter/component.astro +358 -0
- package/src/tool/colony-counter/i18n/en.ts +151 -0
- package/src/tool/colony-counter/i18n/es.ts +151 -0
- package/src/tool/colony-counter/i18n/fr.ts +151 -0
- package/src/tool/colony-counter/index.ts +27 -0
- package/src/tool/colony-counter/seo.astro +15 -0
- package/src/tool/microwave-detector/MicrowaveDetector.css +122 -0
- package/src/tool/microwave-detector/bibliography.astro +14 -0
- package/src/tool/microwave-detector/component.astro +650 -0
- package/src/tool/microwave-detector/i18n/en.ts +155 -0
- package/src/tool/microwave-detector/i18n/es.ts +155 -0
- package/src/tool/microwave-detector/i18n/fr.ts +155 -0
- package/src/tool/microwave-detector/index.ts +24 -0
- package/src/tool/microwave-detector/logic/MicrowaveEngine.ts +89 -0
- package/src/tool/microwave-detector/seo.astro +14 -0
- package/src/tool/simulation-probability/SimulationProbability.css +1 -0
- package/src/tool/simulation-probability/bibliography.astro +14 -0
- package/src/tool/simulation-probability/component.astro +348 -0
- package/src/tool/simulation-probability/i18n/en.ts +184 -0
- package/src/tool/simulation-probability/i18n/es.ts +184 -0
- package/src/tool/simulation-probability/i18n/fr.ts +184 -0
- package/src/tool/simulation-probability/index.ts +24 -0
- package/src/tool/simulation-probability/logic/SimulationEngine.ts +42 -0
- package/src/tool/simulation-probability/seo.astro +14 -0
- package/src/tools.ts +15 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "./ColonyCounter.css";
|
|
3
|
+
import { Icon } from "astro-icon/components";
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<div class="colony-container">
|
|
7
|
+
<div class="colony-main-card">
|
|
8
|
+
<div class="colony-grid">
|
|
9
|
+
<div class="colony-canvas-section">
|
|
10
|
+
<div class="colony-canvas-wrapper">
|
|
11
|
+
<input type="file" id="image-upload" accept="image/*" class="colony-hidden" />
|
|
12
|
+
|
|
13
|
+
<div id="upload-prompt" class="colony-upload-prompt">
|
|
14
|
+
<Icon name="mdi:upload" class="colony-upload-icon" />
|
|
15
|
+
<div class="colony-upload-text">
|
|
16
|
+
<p class="colony-upload-title">
|
|
17
|
+
Haz clic para subir tu placa de Petri
|
|
18
|
+
</p>
|
|
19
|
+
<p class="colony-upload-subtitle">
|
|
20
|
+
Sube una foto de tu placa y empieza a contar colonias
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<canvas
|
|
26
|
+
id="petri-canvas"
|
|
27
|
+
class="colony-hidden colony-petri-canvas"
|
|
28
|
+
></canvas>
|
|
29
|
+
|
|
30
|
+
<div id="mode-indicator" class="colony-hidden colony-mode-indicator">
|
|
31
|
+
<p class="colony-mode-label">
|
|
32
|
+
Modo Actual
|
|
33
|
+
</p>
|
|
34
|
+
<p id="current-mode" class="colony-mode-value">
|
|
35
|
+
Tipo A
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="colony-control-panel">
|
|
42
|
+
<div class="colony-control-section">
|
|
43
|
+
<h3 class="colony-control-title">
|
|
44
|
+
Tipo de Colonia
|
|
45
|
+
</h3>
|
|
46
|
+
|
|
47
|
+
<div class="colony-button-grid">
|
|
48
|
+
<button
|
|
49
|
+
id="mode-a"
|
|
50
|
+
class="colony-mode-btn colony-mode-btn-active"
|
|
51
|
+
>
|
|
52
|
+
<span class="colony-color-dot colony-color-a"></span>
|
|
53
|
+
Tipo A
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
id="mode-b"
|
|
57
|
+
class="colony-mode-btn"
|
|
58
|
+
>
|
|
59
|
+
<span class="colony-color-dot colony-color-b"></span>
|
|
60
|
+
Tipo B
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="colony-divider"></div>
|
|
66
|
+
|
|
67
|
+
<div class="colony-control-section">
|
|
68
|
+
<h3 class="colony-control-title">
|
|
69
|
+
Conteo
|
|
70
|
+
</h3>
|
|
71
|
+
|
|
72
|
+
<div class="colony-count-grid">
|
|
73
|
+
<div class="colony-count-box colony-count-box-a">
|
|
74
|
+
<p class="colony-count-label colony-count-label-a">
|
|
75
|
+
Tipo A
|
|
76
|
+
</p>
|
|
77
|
+
<p id="count-a" class="colony-count-value">
|
|
78
|
+
0
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="colony-count-box colony-count-box-b">
|
|
82
|
+
<p class="colony-count-label colony-count-label-b">
|
|
83
|
+
Tipo B
|
|
84
|
+
</p>
|
|
85
|
+
<p id="count-b" class="colony-count-value">
|
|
86
|
+
0
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="colony-count-total">
|
|
92
|
+
<p class="colony-count-label-total">
|
|
93
|
+
Total UFC
|
|
94
|
+
</p>
|
|
95
|
+
<p id="count-total" class="colony-count-value-total">
|
|
96
|
+
0
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="colony-divider"></div>
|
|
102
|
+
|
|
103
|
+
<div class="colony-control-section colony-button-section">
|
|
104
|
+
<button
|
|
105
|
+
id="undo-btn"
|
|
106
|
+
class="colony-action-btn colony-undo-btn"
|
|
107
|
+
>
|
|
108
|
+
<Icon name="mdi:undo" class="colony-btn-icon" />
|
|
109
|
+
Deshacer Último
|
|
110
|
+
</button>
|
|
111
|
+
|
|
112
|
+
<button
|
|
113
|
+
id="clear-btn"
|
|
114
|
+
class="colony-action-btn colony-clear-btn"
|
|
115
|
+
>
|
|
116
|
+
<Icon name="mdi:delete-sweep" class="colony-btn-icon" />
|
|
117
|
+
Limpiar Todo
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="colony-info-text">
|
|
122
|
+
<p>
|
|
123
|
+
<Icon name="mdi:information" class="colony-info-icon" /> Haz clic en la placa para
|
|
124
|
+
marcar colonias
|
|
125
|
+
</p>
|
|
126
|
+
<p>
|
|
127
|
+
<Icon name="mdi:palette" class="colony-info-icon" /> Cambia el tipo antes de marcar
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<script is:inline>
|
|
136
|
+
const canvas = document.getElementById("petri-canvas");
|
|
137
|
+
const ctx = canvas?.getContext("2d");
|
|
138
|
+
const imageUpload = document.getElementById("image-upload");
|
|
139
|
+
const uploadPrompt = document.getElementById("upload-prompt");
|
|
140
|
+
const modeIndicator = document.getElementById("mode-indicator");
|
|
141
|
+
|
|
142
|
+
const modeABtn = document.getElementById("mode-a");
|
|
143
|
+
const modeBBtn = document.getElementById("mode-b");
|
|
144
|
+
const undoBtn = document.getElementById("undo-btn");
|
|
145
|
+
const clearBtn = document.getElementById("clear-btn");
|
|
146
|
+
|
|
147
|
+
const countAEl = document.getElementById("count-a");
|
|
148
|
+
const countBEl = document.getElementById("count-b");
|
|
149
|
+
const countTotalEl = document.getElementById("count-total");
|
|
150
|
+
const currentModeEl = document.getElementById("current-mode");
|
|
151
|
+
|
|
152
|
+
let currentMode = "A";
|
|
153
|
+
let colonies = [];
|
|
154
|
+
let uploadedImage = null;
|
|
155
|
+
|
|
156
|
+
const COLONY_RADIUS = 8;
|
|
157
|
+
const COLORS = {
|
|
158
|
+
A: "#14b8a6",
|
|
159
|
+
B: "#a855f7",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
if (uploadPrompt) {
|
|
163
|
+
uploadPrompt.addEventListener("click", () => {
|
|
164
|
+
if (imageUpload) imageUpload.click();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (imageUpload) {
|
|
169
|
+
imageUpload.addEventListener("change", (imageEvent) => {
|
|
170
|
+
const target = imageEvent.target;
|
|
171
|
+
const file = target?.files?.[0];
|
|
172
|
+
if (!file) return;
|
|
173
|
+
|
|
174
|
+
const reader = new FileReader();
|
|
175
|
+
reader.onload = (event) => {
|
|
176
|
+
const img = new Image();
|
|
177
|
+
img.onload = () => {
|
|
178
|
+
uploadedImage = img;
|
|
179
|
+
if (uploadPrompt) uploadPrompt.classList.add("colony-hidden");
|
|
180
|
+
if (canvas) canvas.classList.remove("colony-hidden");
|
|
181
|
+
if (modeIndicator) modeIndicator.classList.remove("colony-hidden");
|
|
182
|
+
resizeCanvas();
|
|
183
|
+
};
|
|
184
|
+
img.src = event.target?.result || "";
|
|
185
|
+
};
|
|
186
|
+
reader.readAsDataURL(file);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function resizeCanvas() {
|
|
191
|
+
if (!canvas) return;
|
|
192
|
+
const rect = canvas.getBoundingClientRect();
|
|
193
|
+
canvas.width = rect.width;
|
|
194
|
+
canvas.height = rect.height;
|
|
195
|
+
redraw();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function drawImage() {
|
|
199
|
+
if (!ctx || !canvas || !uploadedImage) return;
|
|
200
|
+
|
|
201
|
+
const canvasAspect = canvas.width / canvas.height;
|
|
202
|
+
const imageAspect = uploadedImage.width / uploadedImage.height;
|
|
203
|
+
|
|
204
|
+
let drawWidth, drawHeight, offsetX, offsetY;
|
|
205
|
+
|
|
206
|
+
if (canvasAspect > imageAspect) {
|
|
207
|
+
drawHeight = canvas.height;
|
|
208
|
+
drawWidth = uploadedImage.width * (canvas.height / uploadedImage.height);
|
|
209
|
+
offsetX = (canvas.width - drawWidth) / 2;
|
|
210
|
+
offsetY = 0;
|
|
211
|
+
} else {
|
|
212
|
+
drawWidth = canvas.width;
|
|
213
|
+
drawHeight = uploadedImage.height * (canvas.width / uploadedImage.width);
|
|
214
|
+
offsetX = 0;
|
|
215
|
+
offsetY = (canvas.height - drawHeight) / 2;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
ctx.save();
|
|
219
|
+
ctx.beginPath();
|
|
220
|
+
ctx.arc(
|
|
221
|
+
canvas.width / 2,
|
|
222
|
+
canvas.height / 2,
|
|
223
|
+
Math.min(canvas.width, canvas.height) / 2,
|
|
224
|
+
0,
|
|
225
|
+
Math.PI * 2
|
|
226
|
+
);
|
|
227
|
+
ctx.clip();
|
|
228
|
+
ctx.drawImage(uploadedImage, offsetX, offsetY, drawWidth, drawHeight);
|
|
229
|
+
ctx.restore();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function drawColonies() {
|
|
233
|
+
if (!ctx) return;
|
|
234
|
+
|
|
235
|
+
colonies.forEach((colony) => {
|
|
236
|
+
ctx.fillStyle = COLORS[colony.type];
|
|
237
|
+
ctx.strokeStyle = "white";
|
|
238
|
+
ctx.lineWidth = 2;
|
|
239
|
+
|
|
240
|
+
ctx.beginPath();
|
|
241
|
+
ctx.arc(colony.x, colony.y, COLONY_RADIUS, 0, Math.PI * 2);
|
|
242
|
+
ctx.fill();
|
|
243
|
+
ctx.stroke();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function redraw() {
|
|
248
|
+
if (!ctx || !canvas) return;
|
|
249
|
+
|
|
250
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
251
|
+
|
|
252
|
+
if (uploadedImage) {
|
|
253
|
+
drawImage();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
drawColonies();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function updateCounts() {
|
|
260
|
+
const countA = colonies.filter((c) => c.type === "A").length;
|
|
261
|
+
const countB = colonies.filter((c) => c.type === "B").length;
|
|
262
|
+
const total = colonies.length;
|
|
263
|
+
|
|
264
|
+
if (countAEl) countAEl.innerText = countA.toString();
|
|
265
|
+
if (countBEl) countBEl.innerText = countB.toString();
|
|
266
|
+
if (countTotalEl) countTotalEl.innerText = total.toString();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function playClickSound() {
|
|
270
|
+
try {
|
|
271
|
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
272
|
+
const oscillator = audioContext.createOscillator();
|
|
273
|
+
const gainNode = audioContext.createGain();
|
|
274
|
+
|
|
275
|
+
oscillator.connect(gainNode);
|
|
276
|
+
gainNode.connect(audioContext.destination);
|
|
277
|
+
|
|
278
|
+
oscillator.frequency.value = 800;
|
|
279
|
+
oscillator.type = "sine";
|
|
280
|
+
|
|
281
|
+
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
282
|
+
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
|
|
283
|
+
|
|
284
|
+
oscillator.start(audioContext.currentTime);
|
|
285
|
+
oscillator.stop(audioContext.currentTime + 0.1);
|
|
286
|
+
} catch {
|
|
287
|
+
void 0;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function setMode(mode) {
|
|
292
|
+
currentMode = mode;
|
|
293
|
+
|
|
294
|
+
if (currentModeEl) {
|
|
295
|
+
currentModeEl.innerText = `Tipo ${mode}`;
|
|
296
|
+
currentModeEl.className =
|
|
297
|
+
mode === "A"
|
|
298
|
+
? "colony-mode-value colony-mode-a"
|
|
299
|
+
: "colony-mode-value colony-mode-b";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
document.querySelectorAll(".colony-mode-btn").forEach((btn) => {
|
|
303
|
+
btn.classList.remove("colony-mode-btn-active");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const activeBtn = mode === "A" ? modeABtn : modeBBtn;
|
|
307
|
+
if (activeBtn) {
|
|
308
|
+
activeBtn.classList.add("colony-mode-btn-active");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (canvas) {
|
|
313
|
+
canvas.addEventListener("click", (e) => {
|
|
314
|
+
if (!canvas) return;
|
|
315
|
+
const rect = canvas.getBoundingClientRect();
|
|
316
|
+
const x = e.clientX - rect.left;
|
|
317
|
+
const y = e.clientY - rect.top;
|
|
318
|
+
|
|
319
|
+
const centerX = canvas.width / 2;
|
|
320
|
+
const centerY = canvas.height / 2;
|
|
321
|
+
const radius = Math.min(canvas.width, canvas.height) / 2;
|
|
322
|
+
const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
|
|
323
|
+
|
|
324
|
+
if (distance > radius - 20) return;
|
|
325
|
+
|
|
326
|
+
colonies.push({ x, y, type: currentMode });
|
|
327
|
+
playClickSound();
|
|
328
|
+
redraw();
|
|
329
|
+
updateCounts();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (modeABtn) modeABtn.addEventListener("click", () => setMode("A"));
|
|
334
|
+
if (modeBBtn) modeBBtn.addEventListener("click", () => setMode("B"));
|
|
335
|
+
|
|
336
|
+
if (undoBtn) {
|
|
337
|
+
undoBtn.addEventListener("click", () => {
|
|
338
|
+
if (colonies.length > 0) {
|
|
339
|
+
colonies.pop();
|
|
340
|
+
redraw();
|
|
341
|
+
updateCounts();
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (clearBtn) {
|
|
347
|
+
clearBtn.addEventListener("click", () => {
|
|
348
|
+
if (confirm("¿Seguro que quieres borrar todas las colonias marcadas?")) {
|
|
349
|
+
colonies = [];
|
|
350
|
+
redraw();
|
|
351
|
+
updateCounts();
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
window.addEventListener("resize", resizeCanvas);
|
|
357
|
+
resizeCanvas();
|
|
358
|
+
</script>
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
2
|
+
|
|
3
|
+
const slug = 'colony-counter';
|
|
4
|
+
const title = 'Colony Counter: Digital CFU Counting Tool for Petri Plates';
|
|
5
|
+
const description = 'Digital tool for counting bacterial colonies on Petri plates. Differentiate types, avoid errors, and calculate CFU with precision.';
|
|
6
|
+
|
|
7
|
+
export const content: ToolLocaleContent = {
|
|
8
|
+
slug,
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
faqTitle: 'Frequently Asked Questions',
|
|
12
|
+
bibliographyTitle: 'Bibliographic References',
|
|
13
|
+
ui: {
|
|
14
|
+
uploadTitle: 'Click to upload your Petri dish',
|
|
15
|
+
uploadSubtitle: 'Upload a photo of your plate and start counting colonies',
|
|
16
|
+
currentModeLabel: 'Current Mode',
|
|
17
|
+
typeA: 'Type A',
|
|
18
|
+
typeB: 'Type B',
|
|
19
|
+
colonyType: 'Colony Type',
|
|
20
|
+
counting: 'Counting',
|
|
21
|
+
totalCFU: 'Total CFU',
|
|
22
|
+
undo: 'Undo Last',
|
|
23
|
+
clearAll: 'Clear All',
|
|
24
|
+
infoClick: 'Click on the plate to mark colonies',
|
|
25
|
+
infoChange: 'Change type before marking',
|
|
26
|
+
confirmClear: 'Are you sure you want to clear all marked colonies?',
|
|
27
|
+
},
|
|
28
|
+
seo: [
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Digital Microbiology: Precise Colony Counting',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: 'Counting bacterial colonies on Petri plates is a fundamental technique in microbiology. Traditionally performed with a hand counter and marker, it is easy to lose count or mark the same colony twice. This digital tool eliminates those errors and allows visual differentiation between colony types.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Why Colony Counting Matters',
|
|
41
|
+
level: 3,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'The number of colonies on a plate is directly proportional to the concentration of viable microorganisms in the original sample. This data is critical in:',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'list',
|
|
49
|
+
items: [
|
|
50
|
+
'<strong>Food Quality Control:</strong> Detecting bacterial contamination.',
|
|
51
|
+
'<strong>Pharmaceutical Research:</strong> Evaluating antibiotic efficacy.',
|
|
52
|
+
'<strong>Clinical Diagnosis:</strong> Quantifying infections in patient samples.',
|
|
53
|
+
'<strong>Biotechnology:</strong> Optimizing production cultures for recombinant proteins.',
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'title',
|
|
58
|
+
text: 'Colony Forming Units (CFU)',
|
|
59
|
+
level: 3,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'paragraph',
|
|
63
|
+
html: 'Each visible colony on a plate is assumed to originate from a single viable cell. That\'s why they are called <strong>CFU</strong> (Colony Forming Units).',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'paragraph',
|
|
67
|
+
html: '<strong>Concentration Formula:</strong><br><code>CFU/mL = (Colonies Counted × Dilution Factor) / Volume Plated</code>',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'title',
|
|
71
|
+
text: 'Best Practices for Counting',
|
|
72
|
+
level: 3,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: 'title',
|
|
76
|
+
text: 'Countable Range',
|
|
77
|
+
level: 4,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'paragraph',
|
|
81
|
+
html: 'The ideal range for manual counting is <strong>30 to 300 colonies</strong> per plate. Below 30, statistical error is too high. Above 300, colonies begin to merge and individual distinction becomes impossible.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'title',
|
|
85
|
+
text: 'Colony Types',
|
|
86
|
+
level: 4,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'paragraph',
|
|
90
|
+
html: 'On selective or differential media, it is common to see multiple colony morphologies:',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'list',
|
|
94
|
+
items: [
|
|
95
|
+
'<strong>Type A (Teal/Green):</strong> Large, mucoid colonies, typical of Gram-negative lactose-fermenting bacteria.',
|
|
96
|
+
'<strong>Type B (Pink/Purple):</strong> Small, translucent colonies, non-fermenters.',
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: 'paragraph',
|
|
101
|
+
html: 'Our tool allows differentiation of up to two colony types with distinct colors, facilitating differential counting without the need for physical markers.',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
faq: [
|
|
105
|
+
{
|
|
106
|
+
question: 'What is CFU counting?',
|
|
107
|
+
answer: 'Colony Forming Units (CFU) is a measurement that estimates the number of viable bacteria or fungal cells in a sample. It is assumed that each visible colony originated from a single cell or group of cells.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
question: 'Why is a digital counter better than manual counting?',
|
|
111
|
+
answer: 'Digital counting avoids human error in "losing track" while visually marking colonies. Our tool additionally allows differentiation of colony types by colors, facilitating analysis of mixed plates.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
question: 'How are CFU per milliliter calculated?',
|
|
115
|
+
answer: 'The number of colonies counted is multiplied by the inverted dilution factor. For example, if you count 30 colonies in a 1:100 dilution, the original sample contains 3000 CFU/ml.',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
question: 'When is a plate considered "uncountable"?',
|
|
119
|
+
answer: 'In standard microbiology, if there are more than 250-300 colonies, the plate is considered too crowded (Too Numerous To Count, TNTC) and the data is unreliable due to colony competition.',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
bibliography: [
|
|
123
|
+
{
|
|
124
|
+
name: 'FDA - Bacteriological Analytical Manual',
|
|
125
|
+
url: 'https://www.fda.gov/food/laboratory-methods-food/bacteriological-analytical-manual-bam',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'ISO 4833 - Colony Count Technique',
|
|
129
|
+
url: 'https://www.iso.org/standard/53728.html',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
howTo: [
|
|
133
|
+
{
|
|
134
|
+
name: 'Prepare the plate image',
|
|
135
|
+
text: 'Place your Petri plate against a dark background or use a trans-illuminator so colonies contrast clearly.',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'Identify colony types',
|
|
139
|
+
text: 'Use different marker colors to group colonies by morphology, color, or size.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'Mark each colony',
|
|
143
|
+
text: 'Click on each visible colony. The tool automatically numbers each click to prevent repetition or missed colonies.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'Calculate final concentration',
|
|
147
|
+
text: 'Enter the plated volume and dilution factor to get the final result in CFU/ml or CFU/g.',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
schemas: [],
|
|
151
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
2
|
+
|
|
3
|
+
const slug = 'contador-colonias';
|
|
4
|
+
const title = 'Contador de Colonias: Conteo Digital de UFC en Placas de Petri';
|
|
5
|
+
const description = 'Herramienta digital para contar colonias bacterianas en placas de Petri. Diferencia tipos, evita errores y calcula UFC con precisión.';
|
|
6
|
+
|
|
7
|
+
export const content: ToolLocaleContent = {
|
|
8
|
+
slug,
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
faqTitle: 'Preguntas Frecuentes',
|
|
12
|
+
bibliographyTitle: 'Referencias Bibliográficas',
|
|
13
|
+
ui: {
|
|
14
|
+
uploadTitle: 'Haz clic para subir tu placa de Petri',
|
|
15
|
+
uploadSubtitle: 'Sube una foto de tu placa y empieza a contar colonias',
|
|
16
|
+
currentModeLabel: 'Modo Actual',
|
|
17
|
+
typeA: 'Tipo A',
|
|
18
|
+
typeB: 'Tipo B',
|
|
19
|
+
colonyType: 'Tipo de Colonia',
|
|
20
|
+
counting: 'Conteo',
|
|
21
|
+
totalCFU: 'Total UFC',
|
|
22
|
+
undo: 'Deshacer Último',
|
|
23
|
+
clearAll: 'Limpiar Todo',
|
|
24
|
+
infoClick: 'Haz clic en la placa para marcar colonias',
|
|
25
|
+
infoChange: 'Cambia el tipo antes de marcar',
|
|
26
|
+
confirmClear: '¿Seguro que quieres borrar todas las colonias marcadas?',
|
|
27
|
+
},
|
|
28
|
+
seo: [
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Microbiología Digital: Conteo Preciso de Colonias',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: 'El conteo de colonias bacterianas en placas de Petri es una técnica fundamental en microbiología. Tradicionalmente se realiza con un contador manual de mano y un marcador, pero es fácil perder la cuenta o marcar dos veces la misma colonia. Esta herramienta digital elimina esos errores y permite diferenciar visualmente entre tipos de colonias.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Por Qué Importa el Conteo',
|
|
41
|
+
level: 3,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'El número de colonias en una placa es directamente proporcional a la concentración de microorganismos viables en la muestra original. Este dato es crítico en:',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'list',
|
|
49
|
+
items: [
|
|
50
|
+
'<strong>Control de calidad alimentaria:</strong> Detectar contaminación bacteriana.',
|
|
51
|
+
'<strong>Investigación farmacéutica:</strong> Evaluar eficacia de antibióticos.',
|
|
52
|
+
'<strong>Diagnóstico clínico:</strong> Cuantificar infecciones en muestras de pacientes.',
|
|
53
|
+
'<strong>Biotecnología:</strong> Optimizar cultivos de producción de proteínas recombinantes.',
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'title',
|
|
58
|
+
text: 'Unidades Formadoras de Colonias (UFC)',
|
|
59
|
+
level: 3,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'paragraph',
|
|
63
|
+
html: 'Cada colonia visible en la placa se asume que proviene de una única célula viable. Por eso se llaman <strong>UFC</strong> (Unidades Formadoras de Colonias) o <strong>CFU</strong> en inglés (Colony Forming Units).',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'paragraph',
|
|
67
|
+
html: '<strong>Fórmula de Concentración:</strong><br><code>UFC/mL = (Colonias contadas × Factor de dilución) / Volumen sembrado</code>',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'title',
|
|
71
|
+
text: 'Buenas Prácticas de Conteo',
|
|
72
|
+
level: 3,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: 'title',
|
|
76
|
+
text: 'Rango Contable',
|
|
77
|
+
level: 4,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'paragraph',
|
|
81
|
+
html: 'El rango ideal para conteo manual es de <strong>30 a 300 colonias</strong> por placa. Por debajo de 30, el error estadístico es demasiado alto. Por encima de 300, las colonias empiezan a fusionarse y es imposible distinguirlas individualmente.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'title',
|
|
85
|
+
text: 'Tipos de Colonias',
|
|
86
|
+
level: 4,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'paragraph',
|
|
90
|
+
html: 'En medios selectivos o diferenciales, es común ver múltiples morfologías de colonias:',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'list',
|
|
94
|
+
items: [
|
|
95
|
+
'<strong>Tipo A (Teal/Verde):</strong> Colonias grandes, mucoides, típicas de bacterias Gram negativas fermentadoras de lactosa.',
|
|
96
|
+
'<strong>Tipo B (Rosa/Púrpura):</strong> Colonias pequeñas, translúcidas, no fermentadoras.',
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: 'paragraph',
|
|
101
|
+
html: 'Nuestra herramienta permite diferenciar hasta dos tipos de colonias con colores distintos, facilitando el conteo diferencial sin necesidad de marcadores físicos.',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
faq: [
|
|
105
|
+
{
|
|
106
|
+
question: '¿Qué es el conteo de UFC?',
|
|
107
|
+
answer: 'Las Unidades Formadoras de Colonias (UFC) es una unidad de medida que estima el número de bacterias o células fúngicas viables en una muestra. Se asume que cada colonia visible se originó a partir de una única célula o un grupo de ellas.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
question: '¿Por qué es mejor un contador digital que uno manual?',
|
|
111
|
+
answer: 'El conteo digital evita el error humano de "perder la cuenta" al marcar visualmente cada colonia. Nuestra herramienta permite además diferenciar tipos de colonias por colores, facilitando el análisis de placas mixtas.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
question: '¿Cómo se calculan las UFC por mililitro?',
|
|
115
|
+
answer: 'Se multiplica el número de colonias contadas por el factor de dilución invertido. Por ejemplo, si cuentas 30 colonias en una dilución 1:100, la muestra original tiene 3000 UFC/ml.',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
question: '¿Cuándo se considera una placa "uncountable"?',
|
|
119
|
+
answer: 'En microbiología estándar, si hay más de 250-300 colonias, la placa se considera demasiado poblada (Too Numerous To Count, TNTC) y los datos no son fiables debido a la competencia entre colonias.',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
bibliography: [
|
|
123
|
+
{
|
|
124
|
+
name: 'FDA - Bacteriological Analytical Manual',
|
|
125
|
+
url: 'https://www.fda.gov/food/laboratory-methods-food/bacteriological-analytical-manual-bam',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'ISO 4833 - Colony Count Technique',
|
|
129
|
+
url: 'https://www.iso.org/standard/53728.html',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
howTo: [
|
|
133
|
+
{
|
|
134
|
+
name: 'Preparar la imagen de la placa',
|
|
135
|
+
text: 'Coloca tu placa de Petri sobre un fondo oscuro o utiliza un transiluminador para que las colonias contrasten claramente.',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'Identificar tipos de colonias',
|
|
139
|
+
text: 'Utiliza diferentes colores de marca para agrupar colonias según su morfología, color o tamaño.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'Marcar cada colonia',
|
|
143
|
+
text: 'Haz clic sobre cada colonia visible. La herramienta numerará cada impacto automáticamente para no repetir ni olvidar ninguna.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'Calcular concentración final',
|
|
147
|
+
text: 'Introduce el volumen sembrado y el factor de dilución para obtener el resultado final en UFC/ml o UFC/g.',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
schemas: [],
|
|
151
|
+
};
|